0 前言

🔥 Hi,大家好,这里是丹成学长的毕设系列文章!

🔥 对毕设有任何疑问都可以问学长哦!

这两年开始,各个学校对毕设的要求越来越高,难度也越来越大… 毕业设计耗费时间,耗费精力,甚至有些题目即使是专业的老师或者硕士生也需要很长时间,所以一旦发现问题,一定要提前准备,避免到后面措手不及,草草了事。

为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天要分享的新项目是

🚩 基于RNN的金融预测

🥇学长这里给一个题目综合评分(每项满分5分)

  • 难度系数:4分
  • 工作量:4分
  • 创新点:3分

🧿 选题指导, 项目分享:

https://gitee.com/yaa-dc/BJH/blob/master/gg/cc/README.md

在这里插入图片描述

1 RNN算法简介

RNN(Recurrent Neural Network)是一类用于处理序列数据的神经网络。首先学长给大家介绍什么是序列数据。时间序列数据是指在不同时间点上收集到的数据,这类数据反映了某一事物、现象等随时间的变化状态或程度。这是时间序列数据的定义,当然这里也可以不是时间,比如文字序列,但总归序列数据有一个特点——后面的数据跟前面的数据有关系。

2 RNN的结构及变体

同学们学过神经网络的话,大概都清楚常见的神经网络包括输入层、隐层、输出层, 通过激活函数控制输出,层与层之间通过权值连接。激活函数是事先确定好的,那么神经网络模型通过训练“学“到的东西就蕴含在“权值“中。基础的神经网络只在层与层之间建立了权连接,RNN最大的不同之处就是在层之间的神经元之间也建立的权连接。具体如图所示:

在这里插入图片描述
这是一个标准的RNN结构图,图中每个箭头代表做一次变换,也就是说箭头连接带有权值。左侧是折叠起来的样子,右侧是展开的样子,左侧中h旁边的箭头代表此结构中的“循环“体现在隐层。在展开结构中我们可以观察到,在标准的RNN结构中,隐层的神经元之间也是带有权值的。也就是说,随着序列的不断推进,前面的隐层将会影响后面的隐层。图中O代表输出,y代表样本给出的确定值,L代表损失函数,我们可以看到,“损失“也是随着序列的推荐而不断积累的。除上述特点之外,标准RNN的还有以下特点:

  • 1、权值共享,图中的W全是相同的,U和V也一样。
  • 2、每一个输入值都只与它本身的那条路线建立权连接,不会和别的神经元连接。

以上是RNN的标准结构,然而在实际中这一种结构并不能解决所有问题,例如我们输入为一串文字,输出为分类类别,那么输出就不需要一个序列,只需要单个输出。具体如图所示:

在这里插入图片描述
在这里插入图片描述
同样的,我们有时候还需要单输入但是输出为序列的情况。那么就可以使用如下结构:
在这里插入图片描述
还有一种结构是输入虽是序列,但不随着序列变化,就可以使用如下结构:
在这里插入图片描述
原始的N vs N RNN要求序列等长,然而我们遇到的大部分问题序列都是不等长的,如机器翻译中,源语言和目标语言的句子往往并没有相同的长度。下面我们来介绍RNN最重要的一个变种:N vs M。这种结构又叫Encoder-Decoder模型,也可以称之为Seq2Seq模型。

在这里插入图片描述

从名字就能看出,这个结构的原理是先编码后解码。左侧的RNN用来编码得到c,拿到c后再

在这里插入图片描述
在这里插入图片描述
除了以上这些结构以外RNN还有很多种结构,用于应对不同的需求和解决不同的问题。还想继续了解可以看一下下面这个博客,里面又介绍了几种不同的结构。但相同的是循环神经网络除了拥有神经网络都有的一些共性元素之外,它总要在一个地方体现出“循环“,而根据“循环“体现方式的不同和输入输出的变化就形成了多种RNN结构。

3 标准RNN的前向输出流程

上面介绍了RNN有很多变种,但其数学推导过程其实都是大同小异。这里就介绍一下标准结构的RNN的前向传播过程。

在这里插入图片描述
学长给大家介绍一下各个符号的含义:x是输入,h是隐层单元,o为输出,L为损失函数,y为训练集的标签。这些元素右上角带的t代表t时刻的状态,其中需要注意的是,因策单元h在t时刻的表现不仅由此刻的输入决定,还受t时刻之前时刻的影响。V、W、U是权值,同一类型的权连接权值相同。有了上面的解释,前向传播算法其实非常简单,对于t时刻:

在这里插入图片描述

其中delta()为激活函数,一般来说会选择tanh函数,b为偏置。即t时刻的输出就更为简单:

在这里插入图片描述

最终模型的预测输出为:

在这里插入图片描述
其中delta为激活函数,通常RNN用于分类,故这里一般用softmax函数。

4 RNN的训练方法——BPTT

BPTT(back-propagation through time)算法是常用的训练RNN的方法,其实本质还是BP算法,只不过RNN处理时间序列数据,所以要基于时间反向传播,故叫随时间反向传播。BPTT的中心思想和BP算法相同,沿着需要优化的参数的负梯度方向不断寻找更优的点直至收敛。综上所述,BPTT算法本质还是BP算法,BP算法本质还是梯度下降法,那么求各个参数的梯度便成了此算法的核心。

在这里插入图片描述

这个结构图观察,需要寻优的参数有三个,分别是U、V、W。与BP算法不同的是,其中W和U两个参数的寻优过程需要追溯之前的历史数据,参数V相对简单只需关注目前,那么我们就来先求解参数V的偏导数。

在这里插入图片描述
其实这个式子看起来简单但是求解起来很容易出错,因为其中嵌套着激活函数函数,是复合函数的求道过程。RNN的损失也是会随着时间累加的,所以不能只求t时刻的偏导。

在这里插入图片描述

5 LSTM算法

接下来给大家详细介绍LSTM的相关内容。长短期记忆网络是RNN的一种变体,RNN由于梯度消失的原因只能有短期记忆,LSTM网络通过精妙的门控制将加法运算带入网络中,一定程度上解决了梯度消失的问题。只能说一定程度上,过长的序列还是会出现“梯度消失”,因此LSTM叫长一点的“短时记忆”。

5.1 长期依赖(Long-Term Dependencies)问题

RNN 的关键点之一就是他们可以用来连接先前的信息到当前的任务上,有时候,我们仅仅需要知道先前的信息来执行当前的任务。例如,我们有一个语言模型用来基于先前的词来预测下一个词。如果我们试着预测 “the clouds are in the sky” 最后的词,我们并不需要任何其他的上下文 —— 因此下一个词很显然就应该是 sky。在这样的场景中,相关的信息和预测的词位置之间的间隔是非常小的,RNN 可以学会使用先前的信息。

在这里插入图片描述
但是同样会有一些更加复杂的场景。假设我们试着去预测“I grew up in France… I speak fluent French”最后的词。当前的信息建议下一个词可能是一种语言的名字,但是如果我们需要弄清楚是什么语言,我们是需要先前提到的离当前位置很远的 France 的上下文的。这说明相关信息和当前预测位置之间的间隔就肯定变得相当的大。但是,在这个间隔不断增大时,RNN 会丧失学习到连接如此远的信息的能力。

在这里插入图片描述
在理论上,RNN 绝对可以处理这样的 长期依赖 问题。人们可以仔细挑选参数来解决这类问题中的最初级形式,但在实践中,RNN 肯定不能够成功学习到这些知识。Bengio, et al. (1994)等人对该问题进行了深入的研究,他们发现一些使训练 RNN 变得非常困难的相当根本的原因。不过,LSTM 并没有这个问题。

5.2 LSTM 网络

Long Short Term 网络—— 一般就叫做 LSTM ——是一种 RNN 特殊的类型,可以学习长期依赖信息。LSTM 由Hochreiter & Schmidhuber (1997)提出,并在近期被Alex Graves进行了改良和推广。在很多问题,LSTM 都取得相当巨大的成功,并得到了广泛的使用。LSTM 通过刻意的设计来避免长期依赖问题。记住长期的信息在实践中是 LSTM 的默认行为,而非需要付出很大代价才能获得的能力!所有 RNN 都具有一种重复神经网络模块的链式的形式。在标准的 RNN 中,这个重复的模块只有一个非常简单的结构,例如一个 tanh 层。

在这里插入图片描述
LSTM 同样是这样的结构,但是重复的模块拥有一个不同的结构。不同于 单一神经网络层,整体上除了h在随时间流动,细胞状态c也在随时间流动,细胞状态c就代表着长期记忆。

在这里插入图片描述

5.3 LSTM 的核心思想

LSTM 的关键就是细胞状态,水平线在图上方贯穿运行。细胞状态类似于传送带。直接在整个链上运行,只有一些少量的线性交互。信息在上面流传保持不变会很容易。

在这里插入图片描述
LSTM 有通过精心设计的称作为“门”的结构来去除或者增加信息到细胞状态的能力。门是一种让信息选择式通过的方法。他们包含一个 sigmoid 神经网络层和一个 pointwise 乘法操作。

在这里插入图片描述
Sigmoid 层输出 0 到 1 之间的数值,描述每个部分有多少量可以通过。0 代表“不许任何量通过”,1 就指“允许任意量通过”!LSTM 拥有三个门,来保护和控制细胞状态。

6 RNN算法的核心代码

6.1 基于Pytorch的RNN实现

在实际代码操作之前,我们实验的环境依然是前面文章用到的环境。不过我们需要的是得安装好pytorch三方库,安装只需要:

python3 -m pip install --upgrade torch torchvision -i https://pypi.tuna.tsinghua.edu.cn/simple

import torch
import torch.nn
class RnnCore(nn.Module):
    def __init__(self,inputSize,hiddenSize,outputSize,layers=1):
        super(RnnCore,self).__init__()
        self.hidden_size = hiddenSize
        self.num_layers = layers
        self.embedding = nn.Embedding(inputSize,hiddenSize)
        self.rnn = nn.RNN(hiddenSize,hiddenSize,layers,batch_first=True)
        self.fc = nn.Linear(hiddenSize,outputSize)
        self.softmax = nn.LogSoftmax(dim=1)
    def forward(self,inputs,hidden):
        x = self.embedding(inputs)
        output,hidden = self.rnn(x,hidden)
        output = output[:,-1,:]
        output = self.fc(output)
        return output,hidden
    def initHidden(self):
        return Variable(torch.zeros(self.num_layers,1,self,hidden_size))

6.2 基于Pytorch实现的序列生成实战

6.2.1 任务描述

观察以下序列:
01
0011
000111
00001111
…………
不难发下其规律:
1、它们都只包含0和1
2、它们的长度不相等
3、0和1的数量是相同的,出现是连续的
4、通用的表示为 ‘0’* n + ‘1’ *n,n表示0和1出现的数量
  这个序列在计算机中,我们称其为上下文无关文法,简单的说,就是可以被一组替代规则所生成,而与所处的上下文本身是无关的。

6.2.2 任务分析

  • 1、如果出现的序列是0000,那么下一位是0还是1显然不能确定
  • 2、如果出现的序列是00001,那么下一位是1
  • 3、如果序列是00001111,此时0和1的数量相同,显然这个序列下一步应该结束

下面我们使用RNN来完成这个序列生成的任务。主要可以分为训练学习和序列生成两个步骤,在训练阶段,RNN尝试用前面的字符来预测下一个,在生成阶段,RNN会根据给点的种子来生成一个完整的序列。

#encoding=utf-8
import torch
import torch.nn  as nn
import torch.optim
from torch.autograd import Variable
from collections import Counter
import matplotlib 
import matplotlib.pyplot as plt
from matplotlib import rc
import numpy as np
class SimpleRnn(nn.Module):
    def __init__(self,input_size,hidden_size,output_size,num_layers=1):
        super(SimpleRnn,self).__init__()
        self.hidden_size = hidden_size
        self.num_layers  = num_layers
        self.embedding = nn.Embedding(input_size,hidden_size)
        self.rnn = nn.RNN(hidden_size,hidden_size,num_layers,batch_first= True)
        self.fc = nn.Linear(hidden_size,output_size)
        self.softmax = nn.LogSoftmax(dim=1)
    def forward(self,inputs,hidden):
        x = self.embedding(inputs)
        output,hidden = self.rnn(x,hidden)
        output = output[:,-1,:]
        output = self.fc(output)
        output = self.softmax(output)  
        return output,hidden
    def initHidden(self):
        return Variable(torch.zeros(self.num_layers,1,self.hidden_size))
train_set = []
validset = []
sample = 2000
sz = 10
probablity = 1.0 *np.array([10,6,4,3,1,1,1,1,1,1])
probablity = probablity[:sz]
probablity = probablity / sum(probablity)
for m in range(2000):
    n = np.random.choice(range(1,sz+1),p=probablity)
    inputs = [0]*n + [1]*n
    inputs.insert(0,3)
    inputs.append(2)
    train_set.append(inputs)
for m in range(sample // 10):
    n =np.random.choice(range(1,sz+1),p=probablity)
    inputs = [0] * n + [1] *n
    inputs.insert(0,3)
    inputs.append(2)
    validset.append(inputs)
for m in range(2):
    n = sz + m
    inputs = [0] * n + [1] *n
    inputs.insert(0,3)
    inputs.append(2)
    validset.append(inputs)
rnn = SimpleRnn(input_size=4, hidden_size=2, output_size=3)
criterion = torch.nn.NLLLoss() 
optimizer = torch.optim.Adam(rnn.parameters(),lr=0.001)
num_epoch = 50
results = []
for epoch in range(num_epoch):
    train_loss = 0
    np.random.shuffle(train_set)
    for i,seq in enumerate(train_set):
        loss = 0
        hidden = rnn.initHidden() 
        for t in range(len(seq)-1):
            x = Variable(torch.LongTensor([seq[t]]).unsqueeze(0))
            y = Variable(torch.LongTensor([seq[t+1]]))
            output,hidden = rnn(x,hidden)
            loss += criterion(output,y)
        loss = 1.0 * loss / len(seq)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        train_loss += loss
        if i>0 and i % 500 ==0:
            print('第{}轮,第{}个,训练平均loss:{:.2f}'.format(epoch,i,train_loss.data.numpy()/i))
    valid_loss = 0
    errors = 0
    show_out_p =''
    show_out_t = ''
    for i,seq in enumerate(validset):
        loss = 0
        outstring = ''
        targets = ''
        diff = 0
        hidden = rnn.initHidden()
        for t in range(len(seq)-1):
            x = Variable(torch.LongTensor([seq[t]]).unsqueeze(0))
            y = Variable(torch.LongTensor([seq[t+1]]))
            output,hidden = rnn(x,hidden)
            data = output.data.numpy()
            print("the output is ",data)
            mm = torch.max(output,1)[1][0]
            outstring += str(mm.data.numpy())
            targets += str(y.data.numpy()[0])
            loss += criterion(output,y)
            diff += 1 - mm.eq(y).data.numpy()[0]
        loss = 1.0 * loss / len(seq)
        valid_loss += loss
        errors += diff
        if np.random.rand() < 0.1:
            show_out_p += outstring 
            show_out_t += targets
        print(output[0][2].data.numpy())
        print('第{}轮,训练loss: {:.2f},校验loss:{:.2f},错误率:{:.2f}'.format(epoch,train_loss.data.numpy()/len(train_set),                                                                               valid_loss.data.numpy()/len(validset)                                                                                   ,1.0*errors/len(validset)))
        print("the show output is: ",show_out_p)
        print("the show taget is: ",show_out_t)
        results.append([train_loss.data.numpy()/len(train_set),valid_loss/len(train_set),1.0*errors/len(validset)])

在这里插入图片描述

7 基于RNN的新闻预测金融市场变化

每日新闻预测金融市场变化,在这里我们会学到如何有效地使用word2vec。其文本内容如下所示:

在这里插入图片描述
具体实现如下:

7.1 导入各种库以及文本

import pandas as pd
import numpy as np
from sklearn.metrics import roc_auc_score
from datetime import date
data = pd.read_csv(r'F:\南昌大学\自然语言处理\学习自然语言处理资料\课件资料\6DLinNLP\input\Combined_News_DJIA.csv')
data.head()

在这里插入图片描述

7.2 分割测试/训练集

train = data[data['Date'] < '2015-01-01']
test = data[data['Date'] > '2014-12-31']

然后,我们把每条新闻做成一个单独的句子,集合在一起:

X_train = train[train.columns[2:]]
corpus = X_train.values.flatten().astype(str)

X_train = X_train.values.astype(str)
X_train = np.array([' '.join(x) for x in X_train])
X_test = test[test.columns[2:]]
X_test = X_test.values.astype(str)
X_test = np.array([' '.join(x) for x in X_test])
y_train = train['Label'].values
y_test = test['Label'].values

这里我们注意,我们需要三样东西:corpus是全部我们『可见』的文本资料。我们假设每条新闻就是一句话,把他们全部flatten()了,我们就会得到list of sentences。同时我们的X_train和X_test可不能随便flatten,他们需要与y_train和y_test对应。

corpus[:3]
X_train[:1]
y_train[:5]
from nltk.tokenize import word_tokenize
corpus = [word_tokenize(x) for x in corpus]
X_train = [word_tokenize(x) for x in X_train]
X_test = [word_tokenize(x) for x in X_test]
X_train[:2]
corpus[:2]

在这里插入图片描述

7.3 预处理

  • 小写化
  • 删除停止词
  • 删除数字与符号
  • lemma
from nltk.corpus import stopwords
stop = stopwords.words('english')
import re
def hasNumbers(inputString):
    return bool(re.search(r'\d', inputString))
def isSymbol(inputString):
    return bool(re.match(r'[^\w]', inputString))
from nltk.stem import WordNetLemmatizer
wordnet_lemmatizer = WordNetLemmatizer()
def check(word):
    word= word.lower()
    if word in stop:
        return False
    elif hasNumbers(word) or isSymbol(word):
        return False
    else:
        return True
def preprocessing(sen):
    res = []
    for word in sen:
        if check(word):
            word = word.lower().replace("b'", '').replace('b"', '').replace('"', '').replace("'", '')
            res.append(wordnet_lemmatizer.lemmatize(word))
    return res

在这里插入图片描述

7.4 训练NLP模型

有了这些干净的数据集,我们可以做我们的NLP模型了。学长这里最简单的Word2Vec网络模型

from gensim.models.word2vec import Word2Vec
model = Word2Vec(corpus, size=128, window=5, min_count=5, workers=4)
model['ok']

在这里插入图片描述

7.5 建立ML模型

这里,因为我们128维的每一个值都是连续关系的。不是分裂开考虑的。所以,道理上讲,我们是不太适合用RandomForest这类把每个column当做单独的variable来看的方法。我们来看看比较适合连续函数的方法——SVM。

from sklearn.svm import SVR
from sklearn.model_selection import cross_val_score

params = [0.1,0.5,1,3,5,7,10,12,16,20,25,30,35,40]
test_scores = []
for param in params:
    clf = SVR(gamma=param)
    test_score = cross_val_score(clf, X_train, y_train, cv=3, scoring='roc_auc')
    test_scores.append(np.mean(test_score))
import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(params, test_scores)
plt.title("Param vs CV AUC Score");

在这里插入图片描述

8 最后

更多推荐