任务说明: 通过对电影评论历史数据分析,构建深度学习分类模型,最终完成对新的数据样本的识别分类。
任务要求: 运用神经网络算法,创建、训练、评估模型,完成对电影评论的情感分类任务。
数据集说明: IMDB数据集包含来自互联网的50000条严重两极分化的评论,该数据被分为用于训练的25000条评论和用于测试的25000条评论,训练集和测试集都包含50%的正面评价和50%的负面评价。
- 情绪类别:正面/负面
- 文件样本格式:标签 + 英文文本 + \n
一、模块导入
import numpy as np
import pandas as pd
import paddle
import paddle.nn as nn
import paddle.nn.functional as F
from paddle.nn import LSTM, Embedding, Dropout, Linear
import paddlenlp
二、加载数据
print('loading dataset...')
train_dataset = paddle.text.datasets.Imdb(mode='train') #导入训练集
test_dataset = paddle.text.datasets.Imdb(mode='test') #导入测试集
print('loading finished')
三、获取词表
word_dict = train_dataset.word_idx # 获取数据集的词表
#pad设置, 设置填充词为<pad>,用于后续填充句子,以使批数据训练时句子长度保持一致
#添加'pad'字段到词表最后
word_dict['<pad>'] = len(word_dict) #查看词表前五个词和id
for k in list(word_dict)[:5]:print("{}:{}".format(k.decode('ASCII'), word_dict[k])) #将bytes转化成ASCII字符串,并查看id序号
print("...")
#打印后词表五个词和id号
for k in list(word_dict)[-5:]:#如果字典键的类型不是str,则进行转换print("{}:{}".format(k if isinstance(k, str) else k.decode('ASCII'), word_dict[k]))
# 查看词表中单词的总个数
print("totally {} words".format(len(word_dict)))
四、构造数据结构
# 读取数据归一化处理
seq_len = 200 #句子的最大长度
def create_padded_dataset(dataset):pad_id = word_dict['<pad>']padded_sents = [] #存放句子id信息的列表valid_length=[]#计算句子转换成id后的长度labels = [] #存放标签id的列表for batch_id, data in enumerate(dataset):sent, label = data[0], data[1] #获取 数据id和数据标签#对每一个句子长度小于200的进行padding填充,padded_sent = np.concatenate([sent[:seq_len], [pad_id] * (seq_len - len(sent))]).astype('int64')padded_sents.append(padded_sent) #存放句子idvalid_length.append(np.array(len(padded_sent),dtype="int64")) labels.append(label)#存放标签return np.array(padded_sents),np.array(valid_length) ,np.array(labels) # 对train、test数据进行实例化
#返回句子和标签
train_sents, train_valid_length,train_labels = create_padded_dataset(train_dataset)
test_sents,test_valid_length, test_labels = create_padded_dataset(test_dataset)# 查看数据大小及举例内容
print(train_sents.shape)
print(train_labels.shape)
print(train_valid_length.shape)
print(test_valid_length.shape)
print(test_sents.shape)
print(test_labels.shape)
(1) ny.concatenate() 详解
在数据处理和机器学习任务中,常常需要合并多个数组或矩阵。NumPy库中的 np.concatenate 函数是一个强大的工具,用于沿指定轴将多个数组合并成一个新的数组。详细介绍如下:
1.函数定义和参数
numpy.concatenate((a1, a2, ...), axis=0, out=None)
np.concatenate 函数接收以下参数:
(a1, a2, …): 数组序列,注意要用 () 或者 [] 符号括起来,否则会报错。
axis: 指定合并的轴,即沿着哪个维度进行合并。默认值为 0,表示沿着第一个维度进行合并。
out: 指定输出数组的可选参数。
2.合并一维数组
import numpy as np
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
c = np.concatenate((a, b))
print(c)
输出结果:
[1 2 3 4 5 6]
3.合并二维数组
import numpy as np
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
c = np.concatenate((a, b), axis=1#沿着轴1(列)进行合并)
print(c)
输出结果:
[[1 2 5 6][3 4 7 8]]
五、组装成mini-batch
batch_size = 64 #批数据数量
class IMDBDataset(paddle.io.Dataset):'''继承paddle.io.Dataset类进行封装数据'''def __init__(self, sents, valid_lengths,labels):self.sents = sentsself.valid_lengths=valid_lengthsself.labels = labels#根据给定索引获取数据集中的样本#在 paddle.io.DataLoader 中需要使用此函数通过下标获取样本def __getitem__(self, index):data = self.sents[index]valid_length=self.valid_lengths[index]label = self.labels[index]#返回句子id,标签idreturn data,valid_length, labeldef __len__(self):#返回句子个数return len(self.sents)train_dataset = IMDBDataset(train_sents,train_valid_length, train_labels)
test_dataset = IMDBDataset(test_sents,test_valid_length, test_labels)train_loader = paddle.io.DataLoader(train_dataset, #数据集return_list=True, #以list 形式返回shuffle=True,#是否打乱mini-batch的索引顺序batch_size=batch_size,#批数据的数量drop_last=True#去除末尾剩余数据)
test_loader = paddle.io.DataLoader(test_dataset, return_list=True,shuffle=True, batch_size=batch_size, drop_last=True)
如果批次大小(batch_size)增大,通常可以线性增加学习率(learning_rate)。经验法则是,当批次大小增加到原来的k倍时,学习率乘以 k。例如,如果批次大小从 32 增加到 64,学习率可以翻倍。
注 :以上经验法则仅适用于批次大小相对较小的时候。
六、定义LSTM网络
LSTM网络结构如下:
- 第一层:word Embedding层;(文本向量化)
- 第二层:seq2vec层;(序列学习)
- 第三层:全连接层;(特征学习)
- 第四层:输出层;(分类)
import paddlenlp as ppnlp
class LSTMModel (nn.Layer):#Layer首字母必须大写def __init__(self,vocab_size,#词表长度num_classes,#分类数emb_dim=128,padding_idx=0,lstm_hidden_size=198,direction='forward',lstm_layers=1,dropout_rate=0.0,pooling_type=None,fc_hidden_size=128):super().__init__()#文本向量化#首先将输入word_id 查表后映射成word embeddingself.embedder=nn.Embedding(num_embeddings=vocab_size,embedding_dim=emb_dim,padding_idx=padding_idx)#序列学习#将word_embedding经过LSTMEncoder变换到文本语义特征空间中self.lstm_encoder=ppnlp.seq2vec.LSTMEncoder(emb_dim,lstm_hidden_size,num_layers=lstm_layers,direction=direction,dropout=dropout_rate,pooling_type=pooling_type)#特征学习self.fc=nn.Linear(self.lstm_encoder.get_output_dim(),fc_hidden_size)#输出层self.output_layer=nn.Linear(fc_hidden_size,num_classes)def forward(self,text,seq_len):embedded_text=self.embedder(text)text_repr=self.lstm_encoder(embedded_text,sequence_length=seq_len)fc_out=paddle.tanh(self.fc(text_repr))logits=self.output_layer(fc_out)probs=F.softmax(logits,axis=-1)return probs
七、模型训练
(1) 定义lstm训练参数
epoch_num=4#迭代次数
batch_size=64#mini_batch样本数量
learning_rate=0.001#学习率(控制参数更新步长)
dropout_rate=0.2#随机让部分神经元失效的比率
embedding_size=256
hidden_size=256
num_layers=1
#词表大小
vocab_size = len(word_dict)
print(vocab_size)
(2) 实例化LSTM模型
model=LSTMModel(vocab_size,num_classes=2,emb_dim=embedding_size,lstm_layers=num_layers,direction='bidirectional',padding_idx=word_dict['<pad>']
)
#指定优化策略,更新模型参数
optimizer=paddle.optimizer.Adam(learning_rate=learning_rate,beta1=0.9,beta2=0.999,parameters=model.parameters())
(3) 定义训练函数
steps=[]
losses=[]
acc=[]
def train(model):model.train()global_step=0for epoch in range(epoch_num):for step,(sentences,valid_length,labels) in enumerate(train_loader):logits=model(sentences,valid_length)# 计算损失loss = F.cross_entropy(input=logits, label=labels, soft_label=False)loss = paddle.mean(loss)ACC = paddle.metric.accuracy(logits, labels)# 后向传播loss.backward()# 更新参数optimizer.step()# 清除梯度optimizer.clear_grad()global_step+=1if global_step % 50 == 0:# 记录当前步骤的loss变化情况steps.append(global_step)losses.append(loss.numpy()[0])acc.append(ACC.numpy()[0])print("epoch %d, step %d, loss %.3f,acc %.4f" % (epoch,global_step, loss.numpy()[0],ACC.numpy()[0]))
train(model)
# 保存模型,包含两部分:模型参数和优化器参数
model_name = "sentiment_classifier"
# 保存训练好的模型参数
paddle.save(model.state_dict(), "checkpoint/{}.pdparams".format(model_name))
# 保存优化器参数,方便后续模型继续训练
paddle.save(optimizer.state_dict(), "checkpoint/{}.pdopt".format(model_name))
八、模型评估
@paddle.no_grad()
#七、模型评估
def evaluate(model):# 开启模型测试模式,在该模式下,网络不会进行梯度更新model.eval()# 定义以上几个统计指标tp, tn, fp, fn = 0, 0, 0, 0total_steps=0for sentences,valid_lens, labels in test_loader:total_steps+=1# 获取模型对当前batch的输出结果logits = model(sentences,valid_lens)# 使用softmax进行归一化probs = F.softmax(logits)eval_acc=paddle.metric.accuracy(probs,labels)# 把输出结果转换为numpy array数组,比较预测结果和对应label之间的关系,并更新tp,tn,fp和fnprobs = probs.numpy()for i in range(len(probs)):# 当样本是的真实标签是正例if labels[i][0] == 1:# 模型预测是正例if probs[i][1] > probs[i][0]:tp += 1# 模型预测是负例else:fn += 1# 当样本的真实标签是负例else:# 模型预测是正例if probs[i][1] > probs[i][0]:fp += 1# 模型预测是负例else:tn += 1if total_steps % 100==0:print(eval_acc.numpy()[0])# 整体准确率accuracy = (tp + tn) / (tp + tn + fp + fn)# 输出最终评估的模型效果print("TP: {}\nFP: {}\nTN: {}\nFN: {}\n".format(tp, fp, tn, fn))print("Accuracy: %.4f" % accuracy)
# 加载训练好的模型进行预测,重新实例化一个模型,然后将训练好的模型参数加载到新模型里面
state_dict=paddle.load('checkpoint/sentiment_classifier.pdparams')
model.load_dict(state_dict)
# 评估模型
evaluate(model)
九、可视化
import matplotlib.pyplot as plt
# 可视化定义
def draw_process(title,color,steps,losses,label):plt.title(title, fontsize=24) #设置图像标题,fontszie标题字号plt.xlabel("steps", fontsize=20) #设置横轴标题,字号plt.ylabel(label, fontsize=20) #设置竖轴标题,字号plt.plot(steps,losses, color=color,label=label) #根据横轴(inters)、竖轴(data)数据,用指定的颜色(color)及指定的图像描述(label)绘图plt.legend() #显示labelplt.grid() #显示网格plt.show() #显示图像
# 可视化查看#绘制损失函数与迭代次数变化图象
draw_process("trainning loss","red",steps,losses,"trainning loss")
#绘制准确率值与迭代次数变化图象
draw_process("trainning acc","green",steps,acc,"trainning acc")
# 筛选出训练过程中损失最小和准确度最高的模型
lowest_loss_idx = losses.index(min(losses))
print(lowest_loss_idx)