13.人工智能原理-LSTM网络:自然语言处理实践

目录

       一、循环神经网络RNN 

       二、长短时记忆网络-LSTM

       三、编程实验

       四、总结

       五、往期内容


一、循环神经网络RNN 

       上节课我们利用词嵌入把句子转化为词向量序列的详细过程,但忽略了语言数据在时间上的关联性这节课来让我们的神经网络具有处理这种关联的能力。

       为了讲解更简明一些,我们不考虑预处理这一过程,假设都已经处理成了合适的300维词向量,现在我们开始改造一下神经网络的工作模式:

       首先简化神经网络,然后我们将每次的输出值保存起来,与下一个词向量一起作为下一次的输入,直到得到最后的预测输出。

这样一个句子的每个词对最后预测输出的影响就在每一次的保存和下一步的数据的共同作用中持续到了最后,我们把这样的神经网络称为:RNN循环神经网络。

不过一般便于我们把网络在时间上逐步的行为在空间上展示,我们常常这样作图:

       首先,循环神经网络中的激活函数多采用双取正切函数tanh,而不是relu,当然也可以使用relu函数。像Karas这种编程框架中的循环神经网络,默认使用的激活函数就是tanh,然后循环神经网络中的第一步,不像后面那样,有上一个输出反送回来的数据一起作为输入。但为了保证每一步操作的统一性,我们一般也会手动添加一个共同的输入,比如一个全0的向量,最后是这个循环神经网络的结构图。

如此,我们就能清晰地看出来循环神经网络的前向传播的过程,而反向传播则是从最后的输出层开始,把误差从相反的方向依次从后往前传播。当然,这个反向传播的过程也是按照时间进行的。

所以为什么这样的循环神经网络就可以应对在时间上彼此依赖的序列问题呢?

       我们用一个简单的例子来解释一下。比如对于一个视频,“好看”肯定是正面的评论,那么把这个词的词向量输入将产生正向的分类结果,而在好看前加一个“不”肯定是一个负面评价,“不”这个否定词的输出结果和下一个词向量“好看”合并起来作为输入,那么通过训练就有机会把这个正面词汇“好看”给消解掉,从而让最后的输出值改变,从而产生负向分类的效果。

如果“好看”前面是“非常”这个词,那么“非常”作为一个用来增强状态的副词,输出的结果和下一个词“好看”合并起来作为输入,就有机会把这个正面词汇“好看”给增强,让最后正向的概率值更高。

同样如果是“非常,不,好看”三个词,那么“非常”这个词的输出和“不”一起输入就会增强“不”的输出,而这个被增强的“不”的输出在和“好看”一起输入的时候,就会对这个正面词汇产生更强的消解,得到更加负面的输出。

       所以循环神经网络可以在一定程度上应对这种在时间上有依赖的序列问题。当然我们同样可以用多个这样的RNN结构,形成一个多层的循环神经网络。 

二、长短时记忆网络-LSTM

       循环神经网络虽然也可以堆叠成多层的,但是人们轻易不会构造的太深,一般来说2到3层就可以了。因为循环神经网络会在时间上展开,所以网络结构将会变得很大,训练起来相对于其他的结构更加的困难。虽然这种标准的循环神经网络可以在一定程度上应对这种在时间上有依赖的序列问题,但是对于长依赖的问题效果就不太好了。

现在我们来看一句话,单看后面,我们会认为老虎扬子鳄袋鼠等等都是合理的。

https://img-blog.csdnimg.cn/8d699068ec274c9fb3099f7eb8904827.png#pic_center

但是根据前面的四川,我会认为这个词大概率是熊猫,而不是其他动物。

此时我们发现,四川这个词距离后面填空非常远,换句话说:依赖的路径十分的长,标准的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准确率会更高。

五、往期内容

引言:来一场人工智能的奇妙冒险吧~

1.一元一次函数感知器:如何描述直觉

2.方差代价函数:知错

3.梯度下降:能改

4.曲面梯度下降和反向传播:能改

5.激活函数:给机器注入灵魂

6.隐藏层:神经网络为什么working

7.高维空间:机器如何面对越来越复杂的问题

8.初识Keras:轻松完成神经网络模型搭建

9.深度学习:神奇的DeepLearning

10.卷积神经网络:打破图像识别的瓶颈

11. 卷积神经网络:图像识别实战

12.循环:序列依赖问题

13.LSTM网络:自然语言处理实践

14.机器学习:最后一节课也是第一节课

视频链接:  https://pan.baidu.com/s/1S77vyAqHh8pK8_dAy3NMbw?pwd=6ty1

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. ARIMA 2. SARIMA 3. VAR 4. Auto-ARIMA 5. Auto-SARIMA 6. LSTM 7. GRU 8. RNN 9. CNN 10. MLP 11. DNN 12. MLP-LSTM 13. MLP-GRU 14. MLP-RNN 15. MLP-CNN 16. LSTM-ARIMA 17. LSTM-MLP 18. LSTM-CNN 19. GRU-ARIMA 20. GRU-MLP 21. GRU-CNN 22. RNN-ARIMA 23. RNN-MLP 24. RNN-CNN 25. CNN-ARIMA 26. CNN-MLP 27. CNN-LSTM 28. CNN-GRU 29. ARIMA-SVM 30. SARIMA-SVM 31. VAR-SVM 32. Auto-ARIMA-SVM 33. Auto-SARIMA-SVM 34. LSTM-SVM 35. GRU-SVM 36. RNN-SVM 37. CNN-SVM 38. MLP-SVM 39. LSTM-ARIMA-SVM 40. LSTM-MLP-SVM 41. LSTM-CNN-SVM 42. GRU-ARIMA-SVM 43. GRU-MLP-SVM 44. GRU-CNN-SVM 45. RNN-ARIMA-SVM 46. RNN-MLP-SVM 47. RNN-CNN-SVM 48. CNN-ARIMA-SVM 49. CNN-MLP-SVM 50. CNN-LSTM-SVM 51. CNN-GRU-SVM 52. ARIMA-RF 53. SARIMA-RF 54. VAR-RF 55. Auto-ARIMA-RF 56. Auto-SARIMA-RF 57. LSTM-RF 58. GRU-RF 59. RNN-RF 60. CNN-RF 61. MLP-RF 62. LSTM-ARIMA-RF 63. LSTM-MLP-RF 64. LSTM-CNN-RF 65. GRU-ARIMA-RF 66. GRU-MLP-RF 67. GRU-CNN-RF 68. RNN-ARIMA-RF 69. RNN-MLP-RF 70. RNN-CNN-RF 71. CNN-ARIMA-RF 72. CNN-MLP-RF 73. CNN-LSTM-RF 74. CNN-GRU-RF 75. ARIMA-XGBoost 76. SARIMA-XGBoost 77. VAR-XGBoost 78. Auto-ARIMA-XGBoost 79. Auto-SARIMA-XGBoost 80. LSTM-XGBoost 81. GRU-XGBoost 82. RNN-XGBoost 83. CNN-XGBoost 84. MLP-XGBoost 85. LSTM-ARIMA-XGBoost 86. LSTM-MLP-XGBoost 87. LSTM-CNN-XGBoost 88. GRU-ARIMA-XGBoost 89. GRU-MLP-XGBoost 90. GRU-CNN-XGBoost 91. RNN-ARIMA-XGBoost 92. RNN-MLP-XGBoost 93. RNN-CNN-XGBoost 94. CNN-ARIMA-XGBoost 95. CNN-MLP-XGBoost 96. CNN-LSTM-XGBoost 97. CNN-GRU-XGBoost 98. ARIMA-ANN 99. SARIMA-ANN 100. VAR-ANN 上面这些缩写模型的全称及相关用途功能详细解释
07-15

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值