上篇文章讲到了卷积神经网络,卷积神经网络在提取图像特征方面可以做的很好,但是在自然语言处理方面,更多的是使用循环神经网络来完成相应的工作。这篇文章记录了如何使用PaddlePaddle搭建一个循环神经网络并且完成情感分析的模型训练,并且用这个模型来预测一些语句的情感。
环境:ubuntu18.04
python版本:python2.7
训练模型
首先导入python模块,重点介绍一下imdb库,这是一个数据集的库,这个数据集是一个英文电影的评论数据集,数据结构是句子和对应这个句子的两个分类,分别是正面和负面,句子存在的形式是分词对应的标签数字。
#coding:utf-8
'''
created on February 11 15:38 2019
@author:lhy
'''
import paddle
import paddle.dataset.imdb as imdb
import paddle.fluid as fluid
import numpy as np
循环神经网络发展至今已经有不少其他升级版的循环神经网络来实现更好的功能,比如说长短期记忆网络。下面代码是一个比较简单的循环神经网络,首先经过fluid.layers.embedding()
层,这个层主要的作用是接受数据的ID输入,我们在输入一个句子的时候,需要将每个单词转化成对应的ID,再输入到神经网络中,所以这里用这个接口来接受分词数据ID的输入。然后是全连接层,接着是循环神经网络块,经过循环神经网络块之后再经过一个sequence_last_step
接口,这个接口通常是使用在序列函数最后一步,最后经过softmax激活函数的输出层,分类大小为2,正负面评论。
#简单的循环神经网络
def rnn_net(ipt,input_dim):
#将句子分词的IDs作为输入
emb=fluid.layers.embedding(input=ipt,size=[input_dim,128],is_sparse=True)
sentence=fluid.layers.fc(input=emb,size=128,act='tanh')
#循环神经网络块
rnn=fluid.layers.DynamicRNN()
with rnn.block():
word=rnn.step_input(sentence)
prev=rnn.memory(shape=[128])
hidden=fluid.layers.fc(input=[word,prev],size=128,act='relu')
rnn.update_memory(prev,hidden)
rnn.output(hidden)
last=fluid.layers.sequence_last_step(rnn())#这个接口通常使用在序列函数最后一步
out=fluid.layers.fc(input=last,size=2,act='softmax')
return out
下面代码是一个简单的长短期记忆网络,这个网络是从循环神经网络演化过来的。当序列数据较长时,循环神经网络容易出现梯度消失或者梯度爆炸现象,而长短期记忆网络就可以解决这个问题。
网络开始时同样是一个embedding
接口,接着是一个全连接层,接下来是一个dynamic_lstm
长短期记忆操作接口,通过这个接口就可以容易搭建一个长短期记忆网络。然后是经过两个序列池操作,类型是最大池化。最后也是一个输出大小为2的输出层。
#长短期记忆网络
def lstm_net(ipt,input_dim):
#将句子分词的词向量作为输入
emb=fluid.layers.embedding(input=ipt,size=[input_dim,128],is_sparse=True)
#第一个全连接层
fc1=fluid.layers.fc(input=emb,size=128)
#进行一个长短期记忆操作
lstm1,_=fluid.layers.dynamic_lstm(input=fc1,size=128)
#第一个最大序列池操作
fc2=fluid.layers.sequence_pool(input=fc1,pool_type='max')
#第二个最大序列池操作
lstm2=fluid.layers.sequence_pool(input=lstm1,pool_type='max')
#以softmax作为全连接的输出层,大小为2,也就是只有正反面两种评价
out=fluid.layers.fc(input=[fc2,lstm2],size=2,act='softmax')
return out
我们使用的数据属于序列数据(句子中单词序列),所以我们可以设置lod_level为1,当这个参数不是0的时候,表示输入的数据是序列数据,lod_level默认值是0。
#定义输入数据,lod_level不为0指定输入数据为序列数据
words=fluid.layers.data(name='words',shape=[1],dtype='int64',lod_level=1)
label=fluid.layers.data(name='label',shape=[1],dtype='int64')
我们的数据是以数据标签的方式表示一个句子,所以每个句子都是以一串整数来表示的,每个数字都是对应的一个单词,所有单词对应数字标签的映射就构成了一个数据字典。格式如下:
dict['word']=label
#读取数据字典
print('加载数据字典中...')
word_dict=imdb.word_dict()
#获取数据字典长度
dict_dim=len(word_dict)
长短期记忆网络和循环神经网络可以分别选择测试一下,看看有什么差别:
#获取长短期记忆网络
model=lstm_net(words,dict_dim)
#获取循环神经网络
#model=rnn_net(words,dict_dim)
定义损失函数,获取准确率用fluid.layers.accuracy
,然后克隆一个主程序进行测试和预测使用。
#定义损失函数和准确率,分类问题还是使用交叉熵损失函数
cost=fluid.layers.cross_entropy(input=model,label=label)
avg_cost=fluid.layers.mean(cost)
accuracy=fluid.layers.accuracy(input=model,label=label)
#克隆一个测试程序
test_program=fluid.default_main_program().clone(for_test=True)
定义优化方法,这里使用Adagrad优化器,这种优化器多用于处理系数数据。然后创建执行器,初始化参数。
#选择优化器,这里选择Adagrad优化方法,这种优化方法多用于处理稀疏数据
optimizer=fluid.optimizer.AdagradOptimizer(learning_rate=0.002)
opt=optimizer.minimize(avg_cost)
#创建执行器,这次数据集很大,但是我的虚拟机也跑不了CUDA,有条件的就跑CUDA
place=fluid.CPUPlace()
#place=fluid.CUDAPlace(1)
exe=fluid.Executor(place)
#初始化参数
exe.run(fluid.default_startup_program())
#由于数据集比较大,为了加快数据的读取速度,使用paddle.reader.shuffle()先将数据按照设置的大小读入到缓存中,并且打乱顺序
#获取训练和测试数据
print("获取训练数据中...")
train_reader=paddle.batch(paddle.reader.shuffle(imdb.train(word_dict),25000),batch_size=128)
print("获取测试数据中...")
test_reader=paddle.batch(imdb.test(word_dict),batch_size=128)
#定义数据输入维度
feeder=fluid.DataFeeder(place=place,feed_list=[words,label])
开始训练,一共训练3轮。
#开始训练
for pass_id in range(3):#跑一遍我的虚拟机就够呛,但是为了收敛效果更好,就跑三遍吧
train_cost=0
for batch_id,data in enumerate(train_reader()):
train_cost=exe.run(program=fluid.default_main_program(),feed=feeder.feed(data),fetch_list=[avg_cost])
if batch_id%50==0:
print('Pass:%d,Batch:%d,Cost:%0.5f'%(pass_id,batch_id,train_cost[0]))
#开始测试
test_costs=[]
test_accs=[]
for batch_id,data in enumerate(test_reader()):
test_cost,test_acc=exe.run(program=test_program,feed=feeder.feed(data),fetch_list=[avg_cost,accuracy])
test_costs.append(test_cost[0])
test_accs.append(test_acc[0])
#计算这次batch经过后,平均的损失值和准确率
test_cost=(sum(test_costs)/len(test_costs))
test_acc=(sum(test_accs)/len(test_accs))
print('Test:%d,Cost:%0.5f,acc:%0.5f'%(pass_id,test_cost,test_acc))
运行结果:
Pass:0,Batch:0,Cost:0.69789
Test:0,Cost:0.69101,acc:0.53392
Pass:0,Batch:50,Cost:0.59317
Test:0,Cost:0.59615,acc:0.81339
Pass:0,Batch:100,Cost:0.49529
Test:0,Cost:0.52556,acc:0.83066
Pass:0,Batch:150,Cost:0.44073
Test:0,Cost:0.47325,acc:0.83556
Pass:1,Batch:0,Cost:0.42478
Test:1,Cost:0.43922,acc:0.84197
Pass:1,Batch:50,Cost:0.37079
Test:1,Cost:0.41244,acc:0.84528
Pass:1,Batch:100,Cost:0.34953
Test:1,Cost:0.39366,acc:0.84991
Pass:1,Batch:150,Cost:0.35182
Test:1,Cost:0.37899,acc:0.85202
Pass:2,Batch:0,Cost:0.43041
Test:2,Cost:0.36894,acc:0.85474
Pass:2,Batch:50,Cost:0.29719
Test:2,Cost:0.35815,acc:0.85720
Pass:2,Batch:100,Cost:0.30568
Test:2,Cost:0.35992,acc:0.84239
Pass:2,Batch:150,Cost:0.31048
Test:2,Cost:0.32697,acc:0.86552
可以看到损失值在不断减小,准确率在不断上升。
预测数据
接下来用主程序克隆的测试程序来测试三句话,看看效果怎么样。
#预测数据
#定义预测句子,第一个是中性的,第二个是偏向正面的,第三个是偏向负面
test_str=['I read the book','I am so happy','this is a bad movie']
#将句子转化成一个个单词
reviews=[c.split() for c in test_str]
#将句子中的单词转换成字典中的标签
#字典中没有的单词一律标签为<unk>对应的标签
unk=word_dict['<unk>']
#获取每句话对应的标签
lod=[]
for c in reviews:
#需要将单词进行字符串的utf-8编码转换,对于没见过的单词就使用<unk>的标签
lod.append([word_dict.get(words.encode('utf-8'),unk) for words in c])
#获取输入数据维度的大小,换句话说就是获取每句话的单词数量
base_shape=[[len(c) for c in lod]]
#将想要预测的数据转换成张量,准备开始预测
tensor_words=fluid.create_lod_tensor(lod,base_shape,place)
#开始预测
results=exe.run(program=test_program,feed={'words':tensor_words,'label':np.array([[0],[0],[0]]).astype('int64')},fetch_list=[model])
#打印每句话的正负面概率
for i,r in enumerate(results[0]):
print('%s的预测结果是:正面概率为:%0.5f,反面概率为:%0.5f'%(test_str[i],r[0],r[1]))
输出:
I read the book的预测结果是:正面概率为:0.53733,反面概率为:0.46267
I am so happy的预测结果是:正面概率为:0.57755,反面概率为:0.42245
this is a bad movie的预测结果是:正面概率为:0.27065,反面概率为:0.72935
情感归类的结果还是蛮明显的。
全部代码
全部代码如下:
#coding:utf-8
'''
created on February 11 15:38 2019
@author:lhy
'''
import paddle
import paddle.dataset.imdb as imdb
import paddle.fluid as fluid
import numpy as np
#简单的循环神经网络
def rnn_net(ipt,input_dim):
#将句子分词的IDs作为输入
emb=fluid.layers.embedding(input=ipt,size=[input_dim,128],is_sparse=True)
sentence=fluid.layers.fc(input=emb,size=128,act='tanh')
#循环神经网络块
rnn=fluid.layers.DynamicRNN()
with rnn.block():
word=rnn.step_input(sentence)
prev=rnn.memory(shape=[128])
hidden=fluid.layers.fc(input=[word,prev],size=128,act='relu')
rnn.update_memory(prev,hidden)
rnn.output(hidden)
last=fluid.layers.sequence_last_step(rnn())#这个接口通常使用在序列函数最后一步
out=fluid.layers.fc(input=last,size=2,act='softmax')
return out
#长短期记忆网络
def lstm_net(ipt,input_dim):
#将句子分词的词向量作为输入
emb=fluid.layers.embedding(input=ipt,size=[input_dim,128],is_sparse=True)
#第一个全连接层
fc1=fluid.layers.fc(input=emb,size=128)
#进行一个长短期记忆操作
lstm1,_=fluid.layers.dynamic_lstm(input=fc1,size=128)
#第一个最大序列池操作
fc2=fluid.layers.sequence_pool(input=fc1,pool_type='max')
#第二个最大序列池操作
lstm2=fluid.layers.sequence_pool(input=lstm1,pool_type='max')
#以softmax作为全连接的输出层,大小为2,也就是只有正反面两种评价
out=fluid.layers.fc(input=[fc2,lstm2],size=2,act='softmax')
return out
#定义数据层,我们使用的数据属于序列数据,所以我们可以设置lod_level为1,当这个参数不是0
#的时候,表示输入的数据是序列数据,lod_level默认值是0
words=fluid.layers.data(name='words',shape=[1],dtype='int64',lod_level=1)
label=fluid.layers.data(name='label',shape=[1],dtype='int64')
#我们的数据是以数据标签的方式表示一个句子,所以每个句子都是以一串整数来表示的,每个数字都是对应的一个单词
#所以这个数据集就会有一个字典,这个字典式训练数据中出现单词对应的数字标签
#读取数据字典
print('加载数据字典中...')
word_dict=imdb.word_dict()
#获取数据字典长度
dict_dim=len(word_dict)
#获取长短期记忆网络
model=lstm_net(words,dict_dim)
#获取循环神经网络
#model=rnn_net(words,dict_dim)
#定义损失函数和准确率,分类问题还是使用交叉熵损失函数
cost=fluid.layers.cross_entropy(input=model,label=label)
avg_cost=fluid.layers.mean(cost)
accuracy=fluid.layers.accuracy(input=model,label=label)
#克隆一个测试程序
test_program=fluid.default_main_program().clone(for_test=True)
#选择优化器,这里选择Adagrad优化方法,这种优化方法多用于处理稀疏数据
optimizer=fluid.optimizer.AdagradOptimizer(learning_rate=0.002)
opt=optimizer.minimize(avg_cost)
#创建执行器,这次数据集很大,但是我的虚拟机也跑不了CUDA,有条件的就跑CUDA
place=fluid.CPUPlace()
#place=fluid.CUDAPlace(1)
exe=fluid.Executor(place)
#初始化参数
exe.run(fluid.default_startup_program())
#由于数据集比较大,为了加快数据的读取速度,使用paddle.reader.shuffle()先将数据按照设置的大小读入到缓存中
#获取训练和测试数据
print("获取训练数据中...")
train_reader=paddle.batch(paddle.reader.shuffle(imdb.train(word_dict),25000),batch_size=128)
print("获取测试数据中...")
test_reader=paddle.batch(imdb.test(word_dict),batch_size=128)
#定义数据输入维度
feeder=fluid.DataFeeder(place=place,feed_list=[words,label])
#开始训练
for pass_id in range(3):#跑一边我这虚拟机就够呛
train_cost=0
for batch_id,data in enumerate(train_reader()):
train_cost=exe.run(program=fluid.default_main_program(),feed=feeder.feed(data),fetch_list=[avg_cost])
if batch_id%50==0:
print('Pass:%d,Batch:%d,Cost:%0.5f'%(pass_id,batch_id,train_cost[0]))
#开始测试
test_costs=[]
test_accs=[]
for batch_id,data in enumerate(test_reader()):
test_cost,test_acc=exe.run(program=test_program,feed=feeder.feed(data),fetch_list=[avg_cost,accuracy])
test_costs.append(test_cost[0])
test_accs.append(test_acc[0])
#计算这次batch经过后,平均的损失值和准确率
test_cost=(sum(test_costs)/len(test_costs))
test_acc=(sum(test_accs)/len(test_accs))
print('Test:%d,Cost:%0.5f,acc:%0.5f'%(pass_id,test_cost,test_acc))
#预测数据
#定义预测句子,第一个是中性的,第二个是偏向正面的,第三个是偏向负面
test_str=['I read the book','I am so happy','this is a bad movie']
#将句子转化成一个个单词
reviews=[c.split() for c in test_str]
#将句子中的单词转换成字典中的标签
#字典中没有的单词一律标签为<unk>对应的标签
unk=word_dict['<unk>']
#获取每句话对应的标签
lod=[]
for c in reviews:
#需要将单词进行字符串的编码转换
lod.append([word_dict.get(words.encode('utf-8'),unk) for words in c])
#获取输入数据维度的大小
#获取每句话的单词数量
base_shape=[[len(c) for c in lod]]
print(base_shape)
print(lod)
#将想要预测的数据转换成张量,准备开始预测
tensor_words=fluid.create_lod_tensor(lod,base_shape,place)
#开始预测
results=exe.run(program=test_program,feed={'words':tensor_words,'label':np.array([[0],[0],[0]]).astype('int64')},fetch_list=[model])
#打印每句话的正负面概率
for i,r in enumerate(results[0]):
print('%s的预测结果是:正面概率为:%0.5f,反面概率为:%0.5f'%(test_str[i],r[0],r[1]))