目录
一、循环神经网络RNN
上节课我们利用词嵌入
把句子转化为词向量序列
的详细过程,但忽略了语言数据在时间上的关联性
,这节课来让我们的神经网络具有处理这种关联的能力。
为了讲解更简明一些,我们不考虑预处理这一过程,假设都已经处理成了合适的300维词向量,现在我们开始改造一下神经网络的工作模式:
首先简化神经网络,然后我们将每次的输出值保存起来,与下一个词向量一起作为下一次的输入,直到得到最后的预测输出。
这样一个句子的每个词对最后预测输出的影响就在每一次的保存和下一步的数据的共同作用中持续到了最后,我们把这样的神经网络称为:RNN循环神经网络。
不过一般便于我们把网络在时间上逐步的行为在空间上展示,我们常常这样作图:
首先,循环神经网络中的激活函数多采用双取正切函数tanh,而不是relu,当然也可以使用relu函数。像Karas这种编程框架中的循环神经网络,默认使用的激活函数就是tanh,然后循环神经网络中的第一步,不像后面那样,有上一个输出反送回来的数据一起作为输入。但为了保证每一步操作的统一性,我们一般也会手动添加一个共同的输入,比如一个全0的向量,最后是这个循环神经网络的结构图。
如此,我们就能清晰地看出来循环神经网络的前向传播的过程,而反向传播则是从最后的输出层开始,把误差从相反的方向依次从后往前传播。当然,这个反向传播的过程也是按照时间进行的。
所以为什么这样的循环神经网络就可以应对在时间上彼此依赖的序列问题呢?
我们用一个简单的例子来解释一下。比如对于一个视频,“好看”肯定是正面的评论,那么把这个词的词向量输入将产生正向的分类结果,而在好看前加一个“不”肯定是一个负面评价,“不”这个否定词的输出结果和下一个词向量“好看”合并起来作为输入,那么通过训练就有机会把这个正面词汇“好看”给消解掉,从而让最后的输出值改变,从而产生负向分类的效果。
如果“好看”前面是“非常”这个词,那么“非常”作为一个用来增强状态的副词,输出的结果和下一个词“好看”合并起来作为输入,就有机会把这个正面词汇“好看”给增强,让最后正向的概率值更高。
同样如果是“非常,不,好看”三个词,那么“非常”这个词的输出和“不”一起输入就会增强“不”的输出,而这个被增强的“不”的输出在和“好看”一起输入的时候,就会对这个正面词汇产生更强的消解,得到更加负面的输出。
所以循环神经网络可以在一定程度上应对这种在时间上有依赖的序列问题。当然我们同样可以用多个这样的RNN结构,形成一个多层的循环神经网络。
二、长短时记忆网络-LSTM
循环神经网络虽然也可以堆叠成多层的,但是人们轻易不会构造的太深,一般来说2到3层就可以了。因为循环神经网络会在时间上展开,所以网络结构将会变得很大,训练起来相对于其他的结构更加的困难。虽然这种标准的循环神经网络可以在一定程度上应对这种在时间上有依赖的序列问题,但是对于长依赖的问题效果就不太好了。
现在我们来看一句话,单看后面
,我们会认为“老虎”、“扬子鳄”、“袋鼠”等等都是合理的。
但是根据前面的四川
,我会认为这个词大概率是
“
熊猫
”
,而不是其他动物。
此时我们发现,“四川”这个词距离后面填空非常远,换句话说:依赖的路径十分的长
,标准的RNN结构在这种“
长依赖
”
问题上表现并不好。所以人们又对神经网络进行了改造,其中比较著名的便是:LSTM
为了清晰的解释LSTM结构的工作原理,我们还是从一个简单的标准RNN结构开始慢慢改造。
激活函数为tanh函数为tanh层,激活函数为sigmoid函数为delta层。
标准RNN结构:
LSTM结构:多了细胞状态,和tanh 函数运算。这个细胞状态就是LSTM结构能应对长依赖问题的关键,这个结构的输入相应的变成了上一个细胞状态和上一步的输出。
为了实现记忆和遗忘,LSTM结构使用了两个门来实现。第一个叫遗忘门,使用一个sigmoid的网络层,我们知道格sigmoid的输出值都是在0到1之间。如果我们让这个sigmoid的输出和上个细胞状态值相乘:
sigmoid的输出值为0时,结果就相当于把上个细胞值全部丢弃,也就是全部忘记;
sigmoid的输出值为1时,则上次的细胞值全部保留,也就是全部记忆;
sigmoid的输出值为0到1之间,则相当于遗忘掉部分或者记忆部分。
这个sigmoid层的输入则是本次的词向量和上一次的输出合并的数据,所以这样就可以通过本次和之前数据共同决定忘记多少之前的细胞值。
比如在前面那句话中遇到四川这个词的时候,就知道地点发生了变化,从而忘记北京和上海这两个地点。
第二个叫控制门,这个门用来控制是否更新本次的细胞状态值。这样网络就会选择重要的词汇更新细胞状态,比如遇到四川这个词的时候,就选择更新我们的细胞状态。
最终的输出还有一个输出门,我们说细胞状态值经过一个tanh函数之后,就是本次的输出值。LSTM结构让它也被一个门控制着,这样就可以在遇到重要词汇的时候产生强输出,而不重要的时候产生弱输出。
LSTM之所以能够应对更长的序列依赖,正是因为除了输入和输出以外,它还添加了细胞状态的概念。如果我们单独看这个细胞状态的传递路径,你就会发现,只要遗忘门和记忆门训练得当,就能让像四川这种关键词在循环的过程中被传递的很长,甚至到最后一步,从而达到长时记忆的效果。而像性格这种对预测不重要的词也能在短时间内被遗忘,从而达到短时记忆的效果。
LSTM网络是上个世纪90年代就已经出现了一种循环神经网络结构。在循环神经网络中有着极高的地位,后来人们又提出了各种变种的循环神经网络,很多都是基于LSTM的改进,其中最为著名的就是GRU网络,它简化了LSTM结构,很多时候效果和LSTM也很接近,所以目前大家都乐于使用GRU结构。
有关LSTM的更多理解,可以参考这篇博客:《Understanding LSTM Networks》
同学们可以自己研究一下相比于LSTM,GRU做了哪些改变和简化。
三、编程实验
1、首先,我们要下载第三方预训练词向量。参考链接:mirrors / embedding / chinese-word-vectors · GitCode
以下项目演示为红框内容(这个1.7G的预训练词向量文件的加载过程十分的长):
2.词向量处理工具:chinese_vec.py
import os
import numpy as np
def load_word_vecs():
embeddings_index = {}
f = open(os.path.dirname(os.path.abspath(__file__)) + '/sgns.target.word-word.dynwin5.thr10.neg5.dim300.iter5',
encoding='utf8')
f.readline() # escape first line
for line in f:
values = line.split()
word = values[0]
coefs = np.asarray(values[1:], dtype='float32')
embeddings_index[word] = coefs
f.close()
print('Found %s word vectors.' % len(embeddings_index))
return embeddings_index
3.一份网购评论数据:online_shopping_10_cats.csv
下载链接:https://pan.baidu.com/s/1F6phMStwytLIEDrglKZYtA?pwd=5gq
文中数字是情感标签数据,1是正面评价,0是负面评价。
4.数据操作封装工具:shopping_data.py
import os #os模块提供的就是各种 Python 程序与操作系统进行交互的接口。
import keras
import numpy as np
import keras.preprocessing.text as text
import re #re模块,正则表达式
import jieba
import random
def load_data():
xs = []
ys = []
with open(os.path.dirname(os.path.abspath(__file__))+'/online_shopping_10_cats.csv','r',encoding='gb18030') as f:
line=f.readline()#escape first line"label review"
while line:
line=f.readline()
if not line:
break
contents = line.split(',')
# if contents[0]==" 书籍":
# continue
label = int(contents[1])
review = contents[2]
if len(review)>1000:
continue
xs.append(review)
ys.append(label)
xs = np.array(xs)
ys = np.array(ys)
#打乱数据集
indies = [i for i in range(len(xs))]
random.seed(666)
random.shuffle(indies)
xs = xs[indies]
ys = ys[indies]
m = len(xs)
cutpoint = int(m*4/5)
x_train = xs[:cutpoint]
y_train = ys[:cutpoint]
x_test = xs[cutpoint:]
y_test = ys[cutpoint:]
print('总样本数量:%d' % (len(xs)))
print('训练集数量:%d' % (len(x_train)))
print('测试集数量:%d' % (len(x_test)))
return x_train,y_train,x_test,y_test
def createWordIndex(x_train,x_test):
x_all = np.concatenate((x_train,x_test),axis=0)
#建立词索引
tokenizer = text.Tokenizer()
#create word index
word_dic = {}
voca = []
for sentence in x_all:
# 去掉标点
sentence = re.sub("[\s+\.\!\/_,$%^*(+\"\']+|[+——!,。?、~@#¥%……&*()]+", "", sentence)
# 结巴分词
cut = jieba.cut(sentence)
#cut_list = [ i for i in cut ]
for word in cut:
if not (word in word_dic):
word_dic[word]=0
else:
word_dic[word] +=1
voca.append(word)
word_dic = sorted(word_dic.items(), key = lambda kv:kv[1],reverse=True)
voca = [v[0] for v in word_dic]
tokenizer.fit_on_texts(voca)
print("voca:"+str(len(voca)))
return len(voca),tokenizer.word_index
def word2Index(words,word_index):
vecs = []
for sentence in words:
# 去掉标点
sentence = re.sub("[\s+\.\!\/_,$%^*(+\"\']+|[+——!,。?、~@#¥%……&*()]+", "", sentence)
# 结巴分词
cut = jieba.cut(sentence)
#cut_list = [ i for i in cut ]
index=[]
for word in cut:
if word in word_index:
index.append(float(word_index[word]))
# if len(index)>25:
# index = index[0:25]
vecs.append(np.array(index))
return np.array(vecs)
5.模型训练(不用预训练词向量):comments_lstm0.py
import shopping_data
from keras.utils import pad_sequences# 数据对齐
from keras.models import Sequential#堆叠神经网络序列的载体
from keras.layers import Dense, Embedding #引入全连接层,一层神经网络;引入嵌入层
from keras.layers import Flatten# 数组平铺
from keras.layers import LSTM#导入LSTM
x_train, y_train, x_test, y_test = shopping_data.load_data()
# 打印数据集
# print('x_train.shape:', x_train.shape)
# print('y_train.shape:', y_train.shape)
# print('x_test.shape:', x_test.shape)
# print('y_test.shape:', y_test.shape)
# print(x_train[0])
# print(y_train[0])
#第一步,把数据集中所有的文本转化为词典的索引值,程序去遍历语料中的句子,那如果是中文,就进行分词
vocalen, word_index = shopping_data.createWordIndex(x_train, x_test)
#vocalen为这个词典的词汇数量, word_index为训练集和测试集全部预料的词典
print(word_index)
print('词典总词数:', vocalen)
# 第二步,将每句话转化为索引向量
x_train_index = shopping_data.word2Index(x_train, word_index)
x_test_index = shopping_data.word2Index(x_test, word_index)
#第三步 ,每一句话的索引向量个数不一样,我们需要把序列按照maxlen对齐
maxlen = 25
x_train_index = pad_sequences(x_train_index, maxlen=maxlen)
x_test_index = pad_sequences(x_test_index, maxlen=maxlen)
# 神经网络模型
model = Sequential()
model.add(
Embedding(
trainable=False, # 是否可训练:是否让这一层在训练的时候更新参数
input_dim=vocalen, # 输入维度,所有词汇的数量
output_dim=300, # 输出维度,每个词向量的特征维度
input_length=maxlen # 序列长度,每句话长度为25的对齐
)
)
#LSTM神经网络
model.add(LSTM(
128, # 输出数据的维度
return_sequences=True # 每一个都输出结果
))#前面一层需要每一步输出结果,作为下一层的输入
model.add(LSTM(128))
# 二分类问题,使用sigmoid激活函数
model.add(Dense(1, activation='sigmoid'))
# 配置模型
model.compile(
loss='binary_crossentropy', # 适用于二分类问题的交叉熵代价函数
optimizer='adam', # adam是一种使用动量的自适应优化器,比普通的sgd优化器更快
metrics=['accuracy']
)
#loss(损失函数、代价函数):binary_crossentropy适用于二分类问题的交叉熵代价函数
# optimizer(优化器):adam是一种使用动量的自适应优化器,比普通的sgd优化器更快
# metrics(评估标准):accuracy(准确度);
#训练数据fit, batch_size送入批次的数据,显卡越好,可以送的数据越多,只能用CPU训练,batch_size就设置小一点
model.fit(x_train_index, y_train, batch_size=512, epochs=200)
# 用来测试集上的评估
score, acc = model.evaluate(x_test_index, y_test)
print('Test score:', score)
print('Test accuracy:', acc)
代码解释: LSTM不需要全连接层,也不需要数组平铺flattern
上节课全连接层结果:
trainable=False,不训练词嵌入矩阵,测试集准确率为73.08%
trainable=True,训练词嵌入矩阵,测试集准确率为81.69%
本节课LSTM结果:
trainable=False,词嵌入训练的结果:
比全连接层的准确率要高一点的。
trainable=True,词嵌入训练的结果:
结论:和全连接层差不多。
当我们把嵌入层的训练打开之后,嵌入层的参数矩阵就会同步参与网络的训练,而我们这里嵌入层的参数数量有300乘以22875,那就要比后面的神经网络中的参数还要多。所以在训练的时候,嵌入层的参数就会占用很大的比重,换句话说,训练的重心跑到了这个词嵌入矩阵参数上,所以导致了LSTM和普通的全员阶层在打开这个嵌入层的训练之后,变得差距不是很大。而我们这里的语调数据集又不是很丰富,所以最后的词向量训练的效果必然不会很好,所以我们说更常用的方法呢,是使用别人在海量数据集上训练出来的词向量。
6.模型训练(用预训练词向量):comments_lstm.py
import shopping_data
from keras.utils import pad_sequences# 数据对齐
from keras.models import Sequential#堆叠神经网络序列的载体
from keras.layers import Dense, Embedding #引入全连接层,一层神经网络;引入嵌入层
from keras.layers import Flatten# 数组平铺
from keras.layers import LSTM#导入LSTM
import chinese_vec # 读取中文词向量工具
import numpy as np
x_train, y_train, x_test, y_test = shopping_data.load_data()
# 打印数据集
# print('x_train.shape:', x_train.shape)
# print('y_train.shape:', y_train.shape)
# print('x_test.shape:', x_test.shape)
# print('y_test.shape:', y_test.shape)
# print(x_train[0])
# print(y_train[0])
#第一步,把数据集中所有的文本转化为词典的索引值,程序去遍历语料中的句子,那如果是中文,就进行分词
vocalen, word_index = shopping_data.createWordIndex(x_train, x_test)
#vocalen为这个词典的词汇数量, word_index为训练集和测试集全部预料的词典
print(word_index)
print('词典总词数:', vocalen)
# 第二步,将每句话转化为索引向量
x_train_index = shopping_data.word2Index(x_train, word_index)
x_test_index = shopping_data.word2Index(x_test, word_index)
#第三步 ,每一句话的索引向量个数不一样,我们需要把序列按照maxlen对齐
maxlen = 25
x_train_index = pad_sequences(x_train_index, maxlen=maxlen)
x_test_index = pad_sequences( x_test_index, maxlen=maxlen)
# 自行构造词嵌入矩阵
word_vecs = chinese_vec.load_word_vecs()
embedding_matrix = np.zeros((vocalen, 300))#把嵌入层的嵌入矩阵替换为0矩阵,(语调数量,维度数量300)
#从预处理中词库找到这个词的索引
for word, i in word_index.items():
embedding_vector = word_vecs.get(word)
if embedding_vector is not None:#有可能这个词不在词库中
embedding_matrix[i] = embedding_vector#如果有,替换这一行的词向量
# 神经网络模型
model = Sequential()
model.add(
Embedding(
trainable=True, # 是否可训练:是否让这一层在训练的时候更新参数
weights=[embedding_matrix],
input_dim=vocalen, # 输入维度,所有词汇的数量
output_dim=300, # 输出维度,每个词向量的特征维度
input_length=maxlen # 序列长度,每句话长度为25的对齐
)
)
#LSTM神经网络
model.add(LSTM(
128, # 输出数据的维度
return_sequences=True # 每一个都输出结果
))#前面一层需要每一步输出结果,作为下一层的输入
model.add(LSTM(128))
# 二分类问题,使用sigmoid激活函数
model.add(Dense(1, activation='sigmoid'))
# 配置模型
model.compile(
loss='binary_crossentropy', # 适用于二分类问题的交叉熵代价函数
optimizer='adam', # adam是一种使用动量的自适应优化器,比普通的sgd优化器更快
metrics=['accuracy']
)
#loss(损失函数、代价函数):binary_crossentropy适用于二分类问题的交叉熵代价函数
# optimizer(优化器):adam是一种使用动量的自适应优化器,比普通的sgd优化器更快
# metrics(评估标准):accuracy(准确度);
#训练数据fit, batch_size送入批次的数据,显卡越好,可以送的数据越多,只能用CPU训练,batch_size就设置小一点
model.fit(x_train_index, y_train, batch_size=512, epochs=200)
# 用来测试集上的评估
score, acc = model.evaluate(x_test_index, y_test)
print('Test score:', score)
print('Test accuracy:', acc)
trainable=False,词嵌入不训练的结果:
结论:比自训练的准确率高3个点
trainable=True,词嵌入训练的结果:
结论:和自训练差不多,按道理应该高出很多
四、总结
本节课介绍了循环神经网络RNN和长短时记忆网络-LSTM, 让我们的神经网络具有处理这种关联的能力。在实验编程中,我们将用全连接层的神经网络代码和用LSTM网络代码进行了比较,我们发现用LSTM网络代码会比用全连接层的神经网络代码准确率高,如果用第三方预处理的库,LSTM准确率会更高。
五、往期内容
13.LSTM网络:自然语言处理实践
14.机器学习:最后一节课也是第一节课
视频链接: https://pan.baidu.com/s/1S77vyAqHh8pK8_dAy3NMbw?pwd=6ty1