Advertisement

基于情感分析的LSTM预测股票走势

阅读量:

目录

一.LSTM

二.股票数据

三.文本数据

四.文本数据情感分析

五.数据合并,归一化分析

六.对变量进行相关性分析

七.基于lstm进行股票价格预测

一.LSTM

在金融时间序列分析中,长短期记忆网络(LSTM)因其能够捕捉数据中的长期依赖关系而被广泛采用。本文就不在此进行多说,相关文章可以借鉴<如何从RNN起步,一步一步通俗理解LSTM>,LSTM的主要优势在于它的门控机制,包括遗忘门、输入门和输出门,如图2所示

二.股票数据

研究采集2000年至2024年的浦发银行股票交易数据,涵盖开盘价、收盘价、最高价、最低价、成交量及成交金额等。爬取东方财经网的浦发银行评论共3479条,从国家统计局与Choice金融终端共收集到2000-2024年的股票价格数据共3393条数据,如图下所示 :

open close high low volume sum
2010-02-03 19.31 19.89 19.93 19.01 76493075 1.5E+09
2010-02-04 19.61 19.66 19.89 19.6 46162099 9.09E+08
2010-02-05 19.2 19.42 19.68 19.16 43895954 8.5E+08
2010-02-08 19.45 19.22 19.5 19.08 37230982 7.17E+08
2010-02-09 19.2 19.42 19.49 19.17 26496190 5.14E+08
…………………………………………………………………………………………………………………………………………
2024-01-26 6.78 6.87 6.91 6.75 70997933 4.85E+08
2024-01-29 6.86 6.9 6.97 6.85 55269921 3.82E+08
2024-01-30 6.9 6.83 6.95 6.82 40737046 2.8E+08
2024-01-31 6.81 6.83 6.88 6.74 45661999 3.11E+08
2024-02-01 6.83 6.8 6.88 6.78 44252421 3.02E+08
2024-02-02 6.82 6.84 6.92 6.72 59482913 4.07E+08

三.文本数据

文本数据则包括新闻报道、金融报告以及社交媒体上的投资讨论,这些数据主要来自于东方财富网股吧,数据源丰富了本研究的情感分析和文本挖掘部分。为了确保数据的有效性和可靠性,所有文本数据均从公开可信的渠道收集,并通过程序自动化工具每日更新和存储数据。部分数据如下:

四.文本数据情感分析

案例采用基于机器学习的情感分析方法,主要利用了针对中文文本处理的工具SnowNLP以评估金融文本的情绪倾向。SnowNLP的核心功能是情感分析,该功能基于朴素贝叶斯分类器实现。

在本案例中,代码通过调用SnowNLP(x).sentiments对评论标题进行情感得分计算,鉴于每天的评论数量众多,本研究对这些得分进行平均处理,以获得每日的情绪综合评分。,此方法返回一个介于 0(完全消极)到 1(完全积极)之间的分数。得分反映了文本内容的情绪倾向,通过设置阈值(0.5),将情感得分转化为具体的情绪类别(正面或负面),进而进行进一步的分析和统计。这种方法对于分析时间序列数据中的情绪变化尤其有用,还加深了我们对市场情绪变化的理解,使得预测更具前瞻性和适应性。代码如下所示:

复制代码
 import pandas as pd

    
 from snownlp import SnowNLP
    
  
    
 # 加载数据
    
 data = pd.read_excel('浦发银行.xlsx')
    
  
    
 # 确保时间列存在且格式正确。由于时间列包括具体时间,因此需要匹配包括时间的格式
    
 data['update_time'] = pd.to_datetime(data['update_time'], format='%Y/%m/%d %H:%M', errors='coerce')
    
 # 确保时间和标题都不是空值
    
 data = data.dropna(subset=['update_time', 'title'])
    
  
    
 # 情感分析,计算情感得分
    
 data['sentiment_score'] = data['title'].apply(lambda x: SnowNLP(x).sentiments)
    
  
    
 # 根据情感得分定义情感类型
    
 threshold = 0.5
    
 data['sentiment_type'] = data['sentiment_score'].apply(lambda x: 'positive' if x >= threshold else 'negative')
    
  
    
 # 对每天的数据分组并计算positive和negative的平均值
    
 result = data.groupby(data['update_time'].dt.date).agg({
    
     'title': 'count',  # 计算每天的标题数量
    
     'sentiment_score': ['mean', 'count'],  # 计算每天的平均情感得分及其数量
    
     'sentiment_type': lambda x: (x == 'positive').mean()  # 计算正面情感的比例
    
 }).reset_index()
    
  
    
 # 重命名列便于理解
    
 result.columns = ['Date', 'Title Count', 'Average Sentiment Score', 'Sentiment Count', 'Positive Sentiment Ratio']
    
  
    
 # 创建新的列pos和neg
    
 result['Positive'] = result.apply(lambda x: x['Average Sentiment Score'] if x['Positive Sentiment Ratio'] >= 0.5 else None, axis=1)
    
 result['Negative'] = result.apply(lambda x: x['Average Sentiment Score'] if x['Positive Sentiment Ratio'] < 0.5 else None, axis=1)
    
  
    
 # 使用0.5填充空缺值(根据需要可以调整这个值)
    
 result['Positive'].fillna(0.5, inplace=True)
    
 result['Negative'].fillna(0.5, inplace=True)
    
  
    
 # 保存到Excel文件
    
 result.to_excel('分析结果.xlsx', index=False)
    
    
    
    
    python
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-17/9Gk0T2eyM6vWDwEhXZ7bmOlxNazs.png)
日期 积极情绪指数 日期 积极情绪指数
2018-12-15 0.8553 2018-11-30 0.5903
2018-12-16 0.9605 2018-12-01 0.8363
2018-12-19 0.6188 2018-12-02 0.9439
2018-12-21 0.5056 2018-12-04 0.7746
2018-12-27 0.9627 2018-12-06 0.7256
2018-12-29 0.7180 2018-12-08 0.7935
2018-12-30 0.6807 2018-12-10 0.8210

五.数据合并,归一化分析

复制代码
 import pandas as pd

    
  
    
 # Load both Excel files
    
 result_path = '/mnt/data/分析结果.xlsx'
    
 data_result = pd.read_excel(result_path)
    
  
    
 bank_data_path = '/mnt/data/浦发银行.xlsx'
    
 data_bank = pd.read_excel(bank_data_path)
    
  
    
 # Ensure both date columns are in the same datetime format
    
 data_bank['日期'] = pd.to_datetime(data_bank['日期'], errors='coerce')
    
 data_result['Date'] = pd.to_datetime(data_result['Date'], errors='coerce')
    
  
    
 # Attempt to merge again
    
 merged_data_final = pd.merge(data_result, data_bank, left_on='Date', right_on='日期', how='inner')
    
  
    
 # Save the correctly merged data to a new Excel file
    
 final_merged_file_path = '/mnt/data/final_merged_data.xlsx'
    
 merged_data_final.to_excel(final_merged_file_path, index=False)
    
    
    
    
    python
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-17/p8EmQrFc97DVSJPNbH5IRiA3yeft.png)

六.对变量进行相关性分析

分析结果如下,由此可见,pos.neg与股票技术的相关性很强

七.基于lstm进行股票价格预测

复制代码
 #!/usr/bin/python3

    
 # -*- encoding: utf-8 -*-
    
 from this import s
    
  
    
 import matplotlib.pyplot as plt
    
 import numpy as np
    
 import tushare as ts
    
 import pandas as pd
    
 import torch
    
 from torch import nn
    
 import datetime
    
 import time
    
 from sklearn.metrics import r2_score  # Import r2_score from sklearn
    
  
    
 DAYS_FOR_TRAIN = 10
    
  
    
  
    
 class LSTM_Regression(nn.Module):
    
     """
    
     使用LSTM进行回归
    
   22.         参数:
    
     - input_size: feature size
    
     - hidden_size: number of hidden units
    
     - output_size: number of output
    
     - num_layers: layers of LSTM to stack
    
     """
    
  
    
     def __init__(self, input_size, hidden_size, output_size=1, num_layers=2):
    
     super().__init__()
    
  
    
     self.lstm = nn.LSTM(input_size, hidden_size, num_layers)
    
     self.fc = nn.Linear(hidden_size, output_size)
    
  
    
     def forward(self, _x):
    
     x, _ = self.lstm(_x)  # _x is input, size (seq_len, batch, input_size)
    
     s, b, h = x.shape  # x is output, size (seq_len, batch, hidden_size)
    
     x = x.view(s * b, h)
    
     x = self.fc(x)
    
     x = x.view(s, b, -1)  # 把形状改回来
    
     return x
    
  
    
  
    
 def create_dataset(data, days_for_train=5) -> (np.array, np.array):
    
     """
    
     根据给定的序列data,生成数据集
    
   48.         数据集分为输入和输出,每一个输入的长度为days_for_train,每一个输出的长度为1。
    
     也就是说用days_for_train天的数据,对应下一天的数据。
    
   51.         若给定序列的长度为d,将输出长度为(d-days_for_train+1)个输入/输出对
    
     """
    
     dataset_x, dataset_y = [], []
    
     for i in range(len(data) - days_for_train):
    
     _x = data[i:(i + days_for_train)]
    
     dataset_x.append(_x)
    
     dataset_y.append(data[:, 2][i + days_for_train])
    
     return (np.array(dataset_x), np.array(dataset_y))
    
  
    
  
    
 # 定义一个函数来对DataFrame的指定列进行归一化
    
 def normalize_columns(df, columns_to_normalize):
    
     for column in columns_to_normalize:
    
     df[column] = (df[column] - df[column].min()) / (df[column].max() - df[column].min())
    
     return df
    
  
    
  
    
 if __name__ == '__main__':
    
     t0 = time.time()
    
     data = pd.read_csv('浦发银行单一.csv', index_col='Date')  # 读取文件
    
     print(data.shape)
    
     data = data.astype('float32').values  # 转换数据类型
    
     data = data
    
     print(data.shape)
    
     print(data)
    
     for i in range(data.shape[1]):
    
     plt.plot(data[:, i])
    
     plt.savefig('data.png', format='png', dpi=200)
    
     plt.close()
    
     # 将价格标准化到0~1
    
     max_value = np.max(data[:, i])
    
     min_value = np.min(data[:, i])
    
     data[:, i] = (data[:, i] - min_value) / (max_value - min_value)
    
     print(data[:, i])
    
  
    
  
    
     dataset_x, dataset_y = create_dataset(data, DAYS_FOR_TRAIN)
    
  
    
     train_size = int(len(dataset_x) * 0.70)
    
  
    
     train_x = dataset_x[:train_size]
    
     train_y = dataset_y[:train_size]
    
  
    
    
    
     train_x = train_x.reshape(-1, 1, DAYS_FOR_TRAIN * 6)
    
     train_y = train_y.reshape(-1, 1, 1)
    
  
    
  
    
     train_x = torch.from_numpy(train_x)
    
     train_y = torch.from_numpy(train_y)
    
     #
    
     model = LSTM_Regression(6 * DAYS_FOR_TRAIN, 10, output_size=1, num_layers=4)  
    
  
    
     model_total = sum([param.nelement() for param in model.parameters()])  # 计算模型参数
    
     print("Number of model_total parameter: %.8fM" % (model_total / 1e6))
    
  
    
     train_loss = []
    
     loss_function = nn.MSELoss()
    
     optimizer = torch.optim.Adam(model.parameters(), lr=1e-2, betas=(0.9, 0.999), eps=1e-08, weight_decay=0)
    
     for i in range(350):
    
     out = model(train_x)
    
     loss = loss_function(out, train_y)
    
     loss.backward()
    
     optimizer.step()
    
     optimizer.zero_grad()
    
     train_loss.append(loss.item())
    
  
    
     # 将训练过程的损失值写入文档保存,并在终端打印出来
    
     with open('log.txt', 'a+') as f:
    
         f.write('{} - {}\n'.format(i + 1, loss.item()))
    
     if (i + 1) % 1 == 0:
    
         print('Epoch: {}, Loss:{:.5f}'.format(i + 1, loss.item()))
    
  
    
     # 画loss曲线
    
     plt.figure()
    
     plt.plot(train_loss, 'b', label='loss')
    
     plt.title("Train_Loss_Curve")
    
     plt.ylabel('train_loss')
    
     plt.xlabel('epoch_num')
    
     plt.savefig('loss.png', format='png', dpi=200)
    
     plt.close()
    
  
    
     # torch.save(model.state_dict(), 'model_params.pkl')  # 可以保存模型的参数供未来使用
    
     t1 = time.time()
    
     T = t1 - t0
    
     print('The training time took %.2f' % (T / 60) + ' mins.')
    
  
    
     tt0 = time.asctime(time.localtime(t0))
    
     tt1 = time.asctime(time.localtime(t1))
    
     print('The starting time was ', tt0)
    
     print('The finishing time was ', tt1)
    
  
    
     # for test
    
     model = model.eval()  # 转换成测试模式
    
     # model.load_state_dict(torch.load('model_params.pkl'))  # 读取参数
    
  
    
     # 注意这里用的是全集 模型的输出长度会比原数据少DAYS_FOR_TRAIN 填充使长度相等再作图
    
     dataset_x = dataset_x.reshape(-1, 1, DAYS_FOR_TRAIN * 6)  # (seq_size, batch_size, feature_size)
    
     dataset_x = torch.from_numpy(dataset_x)
    
  
    
     pred_test = model(dataset_x)  # 全量训练集
    
     # 的模型输出 (seq_size, batch_size, output_size)
    
     pred_test = pred_test.view(-1).data.numpy()
    
     pred_test = np.concatenate((np.zeros(DAYS_FOR_TRAIN), pred_test))  # 填充0 使长度相同
    
     assert len(pred_test) == len(data)
    
  
    
     # Calculate R^2 score
    
     r2 = r2_score(data[:, 3], pred_test)
    
     print(f'R^2 score: {r2:.4f}')
    
  
    
     plt.plot(pred_test, 'r', label='prediction')
    
     plt.plot(data[:, 3], 'b', label='real')
    
     plt.plot((train_size, train_size), (0, 1), 'g--')  # 分割线 左边是训练数据 右边是测试数据的输出
    
     plt.legend(loc='best')
    
     plt.title(f'Prediction vs Real (R^2 score: {r2:.4f})')
    
     plt.savefig('result.png', format='png', dpi=200)
    
     plt.show()
    
     plt.close()
    
  
    
    
    
    
    python
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-17/psz8BlvSWt9iOZTDRNAXaMug3mnw.png)

为了维护稳定,对每列进行单独归一化处理,防止量纲问题,将每列的值转换到0-1的范围。鉴于LSTM需要序列数据作为输入,使用滑动窗口的方法从时间序列中提取特征,在代码中,days_for_train是窗口代码,即使用days_for_train**** 天的数据来预测下一天的数据。这里的_x是包含连续几天的数据特征。

数据整形(reshape):输入的数据应该是(序列长度seq_len,批大小batch_size,特征数量input_size),这一特点是为了符合pytorch的要求,每个训练样本都被处理为单独的一个小批量,而每个批量都包含了序列的全部时间与全部特征,使得LSTM有效的学习数据序列的时间依赖性。

批处理与模型输入:将数据转化为pytorch的tensor输入模型,对参数进行调整,最大化捕捉特征关系,提高拟合度并且避免过度拟合。随后做对比分析,并用r-square来做拟合程度评估

在原有的技术指标之上加入了pos与neg的变量,R^2 表示有预测值与真实值的拟合程度,越接近1表示拟合度越高,再加入pos与neg变量之后,拟合度由0.8186提高到了0.8824,可知pos与neg对预测结果的影响很大。本文LSTM代码借鉴LSTM-代码讲解(股票预测)

全部评论 (0)

还没有任何评论哟~