引言
本文是学习完集智学园《PyTorch入门课程:火炬上的深度学习》系列课之后的梳理。
该系列课程包含大量的实操任务,如文本分类,手写数字识别,翻译器,作曲机,AI游戏等。而在实操中还融入了大量机器学习领域的基础和经典知识,如反向传播原理,神经元原理剖析,NLP领域的RNN,LSTM,word2vec,以及图像领域的迁移学习,卷积神经网络等等,非常适合想要入门机器学习的同学,是当前网络上绝无仅有的好课程。
正文
本文的任务,是希望基于电商的商品评论文本数据实现一个神经网络模型,用于判断一段评论是好评还是差评。这个任务本质上是一个二分类问题。同理,二分类问题还可以解决诸如筛选无意义评论,判断一张图片属于猫还狗等任务。区别就在于输入的数据是文本,图片,还是其他数据类型。
开始之前先允许我做一个简单的划重点,以便大家对全文内容有一个初步的认识。我们要完成这个任务的核心分为两点:
首先,是数据处理环节。在你的个神经网络——共享单车预测器中,我也花了大量的篇幅来讲数据处理,其中用到了one-hot,归一化等基本的数据处理操作。而在本任务中,数据处理更是重中之重。主要涉及到,如何让机器识别文本,并用其进行计算的问题。在这里,我们主要使用了简单的词袋模型(BOW)。其中对中文的处理还需要使用到正则匹配以及jieba分词工具。
其次,就是基本的神经网络训练步骤,其实这个步骤和你的个神经网络——共享单车预测器一文的步骤基本大同小异,无非就是建立模型,输入数据,得到预测值,反向传播计算loss,再进行梯度运算调整权重,并不断重复这整个过程,直到loss不再下降,所以对训练步骤的说明就不再详细展开了,还不了解的同学可以回过头看上一篇文章。不同之处仅仅在于,分类任务的损失函数和回归任务不同,这一点待会儿会具体说明。
下面就开始正式操作环节,让我们先来看数据处理。
1. 数据预处理
先来看一下我们用于建模的数据格式。分成了两个文本文件,good.txt和bad.txt,分别存放好评和差评,每一行代表一条评论。数据来源于京东(2013年),可在集智学园github上获取数据文件。
将文件按行读取并依次存储,我们可以在读取的时候就过滤掉标点,并对句子进行分词。这样可以得到两个数组,一个好评文本数组,一个差评文本数组。
其中过滤标点可以直接使用正则表达式,分词则可以直接使用结巴分词库,这个工具可以准确地把一个句子分割为几个有意义的词语。一句话特定词语出现的次数多,比如“很好”,“赞”等,通常我们就可以认为这句话是一句正向评论。所以中文词语才是我们要分析的小单元。
import re #正则表达式的包
import jieba #结巴分词包
# 正向文本
with open('good.txt', 'r') as fr:
for idx, line in enumerate(fr):
if True:
#过滤标点符号
line = re.sub("[\s+\.\!\/_,$%^*(+\"\'“”《》?“]+|[+——!,。?、~@#¥%……&*():]+", "", line)
#分词
words = jieba.lcut(line)
if len(words) > 0:
all_words += words
pos_sentences.append(words)
# 负向文本处理方法相同,得到数组neg_sentences
BOW(词袋模型)
要让计算机能够处理文本,首先就要想办法将文本向量化。BOW方法就是一个非常容易理解的文本向量化方法。我举一个简单的例子大家就能一目了然。它的思路就是把文本所包含的所有词汇数量作为向量的维度,把词语在当前句子中出现的频数作为对应位置的值。
句子1:“我喜欢跳舞,小明也喜欢。”
句子2:“我也喜欢唱歌。”
上面是两句话,我们现在想要把这两句话用向量表示。从这两句话中我们可以提取出一个词典,包含了这两句话中的所有词汇。
词典 = {1:“我”,2:“喜欢”,3:“跳舞”,4:“小明”,5:“也”,6:“唱歌”}
文本所包含的所有词汇数量作为向量的维度,把词语在当前句子中出现的频数作为对应位置的值。那么,我们就立刻有了句子的向量表示。
句子 1:[1, 2, 1, 1, 1, 0]句子 2:[1, 1, 0, 0, 1, 1]
回到我们的任务中来。依照上面的思路,我们就需要建立一个包含所有词汇的大字典,并统计每个词语的词频,从而就能得到每个句子的向量表示。这里使用collections工具可以让词频统计更加简单 。
from collections import Counter #搜集器,可以让统计词频更简单
diction = {} # 要建立的大字典
cnt = Counter(all_words)
for word, freg in cnt.items():
diction[word] = [len(diction), freg] # 在字典中存储每个词语的编号,以及词频
建立好大字典后,开始逐行处理评论文本
dataset = [] # 所有句子的向量表示集合,即我们训练,测试要使用到的数据
# 处理正向评论
for sentence in pos_sentences:
new_sentence = []
for l in sentence:
if l in diction:
new_sentence.append(word2index(l, diction))
dataset.append(sentence2vec(new_sentence, diction))
labels.append(0) #正标签为0
sentences.append(sentence)
# 其中
# word2index是根据单词获得编码函数
# sentence2vec是把目标句子转化为向量函数
# 这里不详细展示,大家可以尝试自己编写
# 源代码可以从文章开头提到的集智学园github地址中下载
dataset和label就包含了我们需要的所有信息,包括文本数据和对应标签。接下去,我们就可以进入训练模型的步骤了
接下去,就可以开始训练模型了。下面的部分代码量比较多,再次强调,训练过程代码的详细说明,与你的个神经网络——共享单车预测器这篇文章大同小异,所以本文和它重复的部分代码不会再做详细解释。所以下面的代码都是纸老虎而已。
2. 开始训练
2.1 构建输入和目标函数,构建模型
即处理初始数据,基于dataset和label把数据分为训练集,校验集和测试集
#对整个数据集进行划分,分为:训练集、校准集和测试集,其中校准和测试集合的长度都是整个数据集的10分之一
test_size = len(dataset) // 10
train_data = dataset[2 * test_size :]
train_label = labels[2 * test_size :]
valid_data = dataset[: test_size]
valid_label = labels[: test_size]
test_data = dataset[test_size : 2 * test_size]
test_label = labels[test_size : 2 * test_size]
使用pytorch可以快速建立一个简单的神经网络模型
# 输入维度为词典的大小:每一段评论的词袋模型
model = nn.Sequential(
nn.Linear(len(diction), 10),
nn.ReLU(),
nn.Linear(10, 2),
nn.LogSoftmax(),
)
- 输入文本向量,长度为字典大小
- 经过一层非线性变换relu
- 经过一层线性变换
- 经过归一化logSoftmax
这里的为什么输出是二维,我们的label不是1或者0吗?
其实为了这里为了方便计算,我们将标签做了one-hot编码,one-hot编码的作用在上篇文章也提到过,是因为这里的0和1并没有“1比0大”这样的概念,就像星期一星期二一样,他们都是类型变量,为了避免类型变量0和1的数值大小影响了神经网络的训练。
2.1 训练 + 校验过程
先直接上代码
# 损失函数为交叉熵
cost = torch.nn.NLLLoss()
# 优化算法为Adam,可以自动调节学习率
optimizer = torch.optim.Adam(model.parameters(), lr = 0.01)
records = []
#循环10个Epoch
losses = []
for epoch in range(10):
for i, data in enumerate(zip(train_data, train_label)):
x, y = data
x = torch.tensor(x, requires_grad = True, dtype = torch.float).view(1, -1)
y = torch.tensor([y], dtype = torch.long)
optimizer.zero_grad()
predict = model(x)
loss = cost(predict, y)
# 将损失函数数值加入到列表中
losses.append(loss.data.numpy())
# 开始进行梯度反传
loss.backward()
# 开始对参数进行一步优化
optimizer.step()
# 每隔3000步,跑一下校验数据集的数据,输出临时结果
if i % 3000 == 0:
rights = []
val_losses = []
for j, val in enumerate(zip(valid_data, valid_label)):
x, y = val
x = torch.tensor(x, requires_grad = True, dtype = torch.float).view(1, -1)
y = torch.tensor([y], dtype = torch.long)
predict = model(x)
# 调用rightness函数计算准确度
right = rightness(predict, y)
rights.append(right)
loss = cost(predict, y)
val_losses.append(loss.data.numpy())
# 将校验集合上面的平均准确度计算出来
right_ratio = 1.0 * np.sum([i[0] for i in rights]) / np.sum([i[1] for i in rights])
print('第{}轮,训练损失:{:.2f}, 校验损失:{:.2f}, 校验准确率: {:.2f}'.format(epoch, np.mean(losses), np.mean(val_losses), right_ratio))
records.append([np.mean(losses), np.mean(val_losses), right_ratio])
这里想要着重强调以下几点:
首先,上面的代码和上篇文章略有不同的是,这里的校验过程和训练过程写在了一起,但思路还是一样的,使用校验集数据在训练好的模型上跑,观察校验集val_loss的变化情况。这样的结果会更加客观
其次,针对分类问题,还可以计算结果的准确度rightness。对真实标签和预测出的值进行比较,计算预测的准确度。其中真实标签和预测值都是二维矩阵
def rightness(predictions, labels):
# """计算预测错误率的函数
# 其中predictions是模型给出的一组预测结果
# batch_size行num_classes列的矩阵
# labels是数据之中的正确答案""",
# 对于任意一行(一个样本)的输出值的第1个维度,求大,得到每一行的大元素的下标
pred = torch.max(predictions.data, 1)[1]
# 将下标与labels中包含的类别进行比较,并累计得到比较正确的数量
rights = pred.eq(labels.data.view_as(pred)).sum()
# 返回正确的数量和这一次一共比较了多少元素
return rights, len(labels)
后,也是重要的,就是损失函数torch.nn.NLLLoss(),即交叉熵。
二分类的交叉熵公式如下:
这也是当前任务使用的方法,其中:
y —— 表示样本的label,正样本是1,负样本是0
p —— 表示样本预测为正的概率。
神经网络对于分类问题的预测值通常是一个概率,在当前任务中,比如预测吐出了[0.8, 0.2],这意味着,神经网络预测当前样本为1的概率更大(位的数值更大)。交叉熵是用于计算分类问题的预测损失,即如果真实样本是1,那就对比[0.8, 0.2]和[1,0]之间的“差”,这个“差”值越小,说明预测和真实就越接近。
当loss不再下降时,模型基本完成训练,下图是绘制了训练集loss,校验集loss和准确度的变化情况。
我们可以认为,校验集loss和训练集loss重合的部分,模型的效果是好的,再继续训练下去,虽然训练集的loss还在持续下降,但是校验集loss却不降反升,这个时候模型已经过拟合了。
3. 测试模型效果。
rights = []
for i, data in enumerate(zip(test_data, test_label)):
x, y = data
x = torch.tensor(x, requires_grad = True, dtype = torch.float).view(1, -1)
y = torch.tensor([y], dtype = torch.long)
predict = model(x)
right = rightness(predict, y)
rights.append(right)
right_ratio = 1.0 * np.sum([i[0] for i in rights]) / np.sum([i[1] for i in rights])
print(' 测试准确率: {:.2f}'.format(right_ratio))
终输出准确率:0.91。
到这里,整个任务就完成了,我们得到了一个可以分辨好评还是差评的文本分类器,并且这个分类器的准确率可达91%
结束
感谢您看到这里。文本分类器的坑填完了。
在今后的一段时间里,我还会尝试图像识别,文本翻译,AI游戏等真实案例。所有学习案例都来自张江老师的PyTorch与深度学习课程。
望与大家共勉。
作者:如意同学
链接:https://juejin.im/post/5dbbeb6a518825225b66b114
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。