CNN实现文本情感分类
前言
作为一个OIer,原本以为入门一些语言和框架不是一件难事,然而真正去实践了才知道python以及一系列库的函数和库实在是用处繁多,每次看到一句话就要在网上查很久它的用处,并且描述的语言通常有点晦涩,尤其是对于只是自学了一下python的我来说,在这里通过举一个例子来简单实现一份深度学习的代码,并且用并不严谨但易懂的语言解释,同时记录一下实验结果和感想。
门槛要求,基本学习过神经网络的原理(指上了不明所以的AI导论的课),对于实现方法有所困惑(指什么都不会写)
问题
这个任务来自THU人工智能导论第二次大作业,将一个已经完成了分词的中文句子进行分析,将它分类为正面或负面的情感
下发文件包括:
train.txt,test.txt,validation.txt,每个文件代表一组数据,每个文件内由若干行组成
- 每行开头是0/1表示负面或正面,之后是分词后的一句话,用空格分词
- train.txt有接近20000句,validation.txt有接近5000句,test.txt有接近400句
wiki_word2vec_50.bin,词向量包,输入后可以得到每个词的长度为50的向量,可以用gensim导入,详见代码
词向量:我们要判别两个词语是否相似,可以通过训练出词向量,对于每个词用向量表示,如果两个向量差越小,就代表着两个词越接近。
wiki_word2vec_50.bin里包含了十几万个词语,虽然并不完整,但只有少部分的词语没有,大体上并不影响训练的结果
当然,也可以有更长的词向量,能够更加精确地描述一个词语的信息
方法
考虑使用CNN模型
对于一个句子,大小为L×50L\times 50L×50(50是词向量大小),有若干个大小为k×50k\times 50k×50的卷积核,对于一个卷积核可以将句子变成(L−k+1)×1(L-k+1)\times 1(L−k+1)×1的矩阵,再进行max_poolingmax\_poolingmax_pooling(最大池化,就是将这个矩阵变成一个值,为矩阵中的最大值)以及激活函数relurelurelu(对0取max),这样就能得到一个大小为卷积核个数ccc的向量,最后进行c→2c\to 2c→2的一个全连接,以及softmax激活函数得到0,10,10,1的概率
好的,你已经知道CNN是什么了,你可以轻松写出代码了!()
然而,如果你不想在BP算法的时候手算梯度,你应该至少要会一种深度学习框架,通俗的说就是一个你能想到的常见的操作都帮你封装好了的模板库,你只需要import一下它就可以帮你写好代码(bushi)!
我用的是pytorch
Pytorch
一个流行的模板库(深度学习框架),里面包含了非常多的函数,如果你不想将pytorch官方的手册看一遍,你最好找一份朴素的实现从最简单的开始做起
在这里,仅仅介绍在本次实验之前你需要知道的关于Pytorch的知识
- Pytorch中已经封装好的操作都是对于其自带的变量类型
torch.tensor
来做的,tensor是一个类似numpy的数组,你可以通过tensor.ones(),tensor.zeros(),tensor.randn()
来新建一个 tensor,你甚至可以将一个numpy或list直接变成tensor - 如何构建神经网络?让我们用代码来一点点解释!
实现
import torch #你已经下载好了pytorch,import它就完事了
import gensim #便于我们导入word2vec50.bin词向量
import math
import torchvision #在某些时候你可用它来指表,直观显示你的训练情况,不过由于这里是入门教程就不用了
import torch.nn as nn #便于简化代码,下同
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import DataLoader,Datasettrain_input_name = 'train_shuffle.txt'
test_input_name = 'test.txt'
all_input_name = 'validation.txt'
# train是我们的训练数据(很大),test是一组小数据,跑起来比较快,便于我们实时观测当前训练结果,validation是一组不大不小的数据,相对test运行次数更少,它可以帮助我们更加精确地得到训练的结果。需要注意的是test和validation是不参与训练的,避免出现过拟合的情况(即网络仅仅在训练数据下优秀),仅仅作为测试使用# 读取word2vec
print("Loading word2vec...")
model = gensim.models.KeyedVectors.load_word2vec_format('wiki_word2vec_50.bin', binary=True)
words = model.key_to_index.keys() # gensim用法,model是一个dictionary,这样就成功提取出了词语库train_batch_size = 64 # 批量训练,这个变量表示一批的大小,提高训练的效率
test_batch_size = 1000 # 测试数据一批的大小vec_size = 50 # 词向量的大小
learning_rate = 0.005 # 训练率,是重要的超参数之一,过高时容易左右横跳,过低时训练太慢
n_epoch = 100000 # 将train训练多少次,这里设了个很大的数(图一乐),使得它停不下来
momentum = 0.5 # 表示对于一次梯度下降,要继承上一次下降方向的多少倍
random_seed = 114514
torch.manual_seed(random_seed) # 随机种子,生成初始网络limit_size = 1e9# 读入部分
def readdata(name):file = open(name,'r',encoding='utf-8') #由于是中文数据,要加utf-8data = file.readlines() # data是一个字符串构成的list,分别对应每一行List = [] # 第i个元素储存第i个句子的词向量矩阵Target = [] # 第i个元素储存第i个句子的结果cnt = 0# 这三个不重要log_cnt = 1000 for line in data:L = line[2:].split(' ') L[len(L)-1] = L[len(L)-1].strip() # 忽略这个字符串后的空格sentence = torch.zeros(len(L),vec_size,dtype=torch.float)# 构建了一个len(L)*vec_size大小,数据类型为实数的tensorfor i in range(len(L)) : # 枚举每一个单词if L[i] in words: # 如果单词在单词表里有sentence[i:] =torch.tensor(model.get_vector(L[i]))# model.get_vector(word)得到word的词向量List.append(sentence),Target.append(int(line[0]))if cnt % log_cnt == 0 :print("Reading Sentence of {}".format(name),cnt,)cnt += 1if cnt >= limit_size :breakreturn List,torch.tensor(Target)class Mydataset(Dataset):def __init__(self, List, Target):self.List = Listself.Target = Targetdef __len__(self):return len(self.List)def __getitem__(self, index):return self.List[index], self.Target[index]def my_collate(batch):data = [item[0] for item in batch]target = [item[1] for item in batch]target = torch.tensor(target)return [data, target]train_x, train_y = readdata(train_input_name)
train_ds = Mydataset(train_x, train_y)
# train_ds是一个list,每一个元素是一个(train_x[i],train_y[i]),Mydataset主要是将长度不同的train_x合起来,详情略,不过只需要超上面的代码即可(比较通用)
train_dataloader = DataLoader(train_ds, batch_size = train_batch_size, shuffle = True, collate_fn = my_collate)
# 含义为将train_ds这个list按照batch_size一组打包,并且随机排列,collate_fn主要是由于上一句train_ds自定义了,所以这里也要自定义(用于将一个batch里的data和target分开),详情略了#同上,分别输入三组数据
test_x, test_y = readdata(test_input_name)
test_ds = Mydataset(test_x, test_y)
test_dataloader = DataLoader(test_ds, batch_size = test_batch_size, shuffle = True, collate_fn= my_collate)all_x, all_y = readdata(all_input_name)
all_ds = Mydataset(all_x, all_y)
all_dataloader = DataLoader(all_ds, batch_size = test_batch_size, shuffle = True, collate_fn= my_collate)# 卷积核的个数(重要的指标,需要自己定义,对网络的训练效果有不小的影响)
ConvList = []
for i in range(100):ConvList.append(3)
for i in range(100):ConvList.append(4)
for i in range(100):ConvList.append(5)# 构造神经网络Net
class Net(nn.Module): # 从nn.Module继承子类(相当于引用已经给你封装好的东西)# 在Net里必须要定义__init__(self)和forward(self,...)# __init__是初始化一个类的时候会运行的部分,你需要将需要训练的网络权值提前定义好,通过pytorch已经封装好的一些函数# forward(self,...)需要定义如果...作为输入,网络的输出是什么,network(x)就能返回network.forward(x)的结果def __init__(self):super(Net, self).__init__() # 我也不知道有什么用,反正都要加self.conv = [] #一组卷积核,用list存for size in ConvList : self.conv.append(nn.Conv2d(1, 1, kernel_size=(size, vec_size)))# nn.Conv2d()是一个卷积核,分别表示,输入通道数,输出通道数,卷积核的尺寸为size*vec_size,# 定义的时候自动随机初始化self.fc2 = nn.Linear(len(ConvList), 2)# 定义卷积层,是从卷积核个数到2的卷积层def forward(self, x):y = torch.zeros(x.size(0),1,len(ConvList))# batch_size * 1 * 卷积核个数,表示卷积、池化后排成一排的结果for i in range(len(ConvList)):z = (self.conv[i])(x) # Conv2d可以看成一个函数,给一个tensor就能出一个卷积后的tensorz = F.relu(F.max_pool2d(z, kernel_size=(z.size(2),1))) # max_pool2d表示二维池化,对z池化,池化的核的大小为 句子长度*1,相当于取最大值# 得到[batch_size,1,1,1]的tensorz = z.view(-1)# 拍扁成一维数组y[:,0,i] = zy = F.softmax(self.fc2(y),dim=2)# 必须要加dim=2,表示对第2维softmax(维数从0开始)return y.view(-1,2)# 第1维是大小2,第0维是size/2,也是batch_size
network = Net()
optimizer = optim.SGD(network.parameters(), lr=learning_rate, momentum=momentum)
# 对于类Net的实例化,optimizer用来自动算梯度,自动更新网络# 从目录中提取已经训练过的网络,用于存档和读档
network_state_dict = torch.load('model.pth')
network.load_state_dict(network_state_dict)
optimizer_state_dict = torch.load('optimizer.pth')
optimizer.load_state_dict(optimizer_state_dict)# 将batch(一个list)变成一个句子长度一样的tensor,这样才能喂到网络里去计算
def expand(batch):max_len = 0for sen in batch :max_len =max(max_len, sen.size(0))out = torch.zeros(len(batch), 1, max_len, vec_size)for i in range(len(batch)):for j in range(batch[i].size(0)):out[i, 0, j] = batch[i][j]return out# 定义损失函数为二分类的交叉熵
def loss_fun(pred, tar):loss = 0for i in range(tar.size(0)):loss -= torch.log(pred[i][tar[i]])# 注意这里不能用math.log,要用torch里带的才能求梯度return losslog_interval = 30
def train(epoch):print('----Epoch {} Start---'.format(epoch))loss_sum = 0# 这里就是train_dataloader的具体用法了,enumerate可以理解为它的指针列表for batch_idx, (x, y) in enumerate(train_dataloader):network.train() # 告诉神经网络它正在训练optimizer.zero_grad() # 清空梯度(上一次训练的梯度可能会残留)x = expand(x) # 将x扩展为一个tensor,由于之前dataloader的定义这里x还是一个list(tensor)pred = network(x) # 网络输出结果loss = loss_fun(pred, y)/train_batch_sizeloss.backward() # 自动BP,torch好评optimizer.step() # 更新网络,torch好评# 按时输出以及存档、测试if batch_idx % log_interval == 0:print('Train Epoch: {} [{:.0f}%]'.format(epoch, 100.0 * batch_idx / len(train_dataloader)))torch.save(network.state_dict(), './model.pth')torch.save(optimizer.state_dict(), './optimizer.pth')test(test_dataloader)# 存档torch.save(network.state_dict(), './model.pth')torch.save(optimizer.state_dict(), './optimizer.pth')def Accurate(pred, y):cnt = 0for i in range(y.size(0)):if pred[i][0]>pred[i][1]:k = 0else:k = 1if k == y[i]:cnt += 1return cnt# 测试一下精确度
def test(test_dataloader):network.eval() #告诉网络你正在测试loss_sum = 0AC = 0Total = 0with torch.no_grad(): # 『请』不要计算梯度for batch_idx, (x, y) in enumerate(test_dataloader):x = expand(x)pred = network(x)loss_sum += loss_fun(pred, y).item()AC += Accurate(pred, y) # 准确率Total += y.size(0)print("Test Output : {} AC rate : {}%".format(loss_sum,100.0*AC/Total))test(test_dataloader) # 事前测一测
for epoch in range(n_epoch): train(epoch)# 历史版本存档torch.save(network.state_dict(), './model/model{}.pth'.format(epoch))torch.save(optimizer.state_dict(), './model/optimizer{}.pth'.format(epoch))print('Result of Epoch ',epoch)test(all_dataloader)print('\n')
结论
卷积核的个数对实验的结果有着至关重要的影响
- 在比较少的时候(20个3×33\times 33×3,30个2×22\times 22×2,20个1×11\times11×1),一开始只能训练到65(正确率),经过50轮epoch才能到73,最后在74收敛
- 在比较多的时候(100个3×33\times 33×3,100个4×44\times44×4,100个5×55\times 55×5),很快就能跑到70,经过70轮之后最好能到77,但是在epoch不断变大,甚至到200轮的时候一度下降到了74、73,出现了过拟合的情况,尽管lost仍然在缓慢降低,但准确率却变低了
- 在不多不少的时候也能跑出2的成绩
- 在卷积核更多的时候并没有明显的优化,反而是训练速度更慢了
learning_rate
是一个比较重要的参数一开始设置到了0.5,网络的训练就完全没有效果了,曾让我一度怀疑是不是哪里写错了
不同的问题要具体分析,但是0.01和0.001的在正确率上的区别并不明显
由于是白嫖机房的机子,并没有NvidaGPU,所以只能暂且不用GPU了
RNN优化并不明显,速度会更慢
问了一下别的同学似乎也就做到80-82的样子,似乎被词向量和数据限制住了上限
感想
- 这是我第一次训练神经网络,从连python都不会到自己写出来(当然少不了查找资料),花费了一个星期的时间,虽然连续开十几个标签页递归式查找资料真的很痛苦,但是当你看到屏幕上不断跳动的Loss Output在不断变低时的喜悦是难以言表的。
- 当然,作为菜鸟在这个方面还有很多不会的东西,上文里甚至可能会有写错了的东西,但不管怎么样这都是一个不断进步的过程。
- 与此同时也是一个我对于人工智能怯魅的过程,其实也没有那么神奇嘛,都是计算机一点点统计出来、算出来的,不过也常常难免为这种统计的有效性而惊讶。
CNN实现文本情感分类相关推荐
- NLP之基于TextCNN的文本情感分类
TextCNN 文章目录 TextCNN 1.理论 1.1 基础概念 **最大汇聚(池化)层:** ![请添加图片描述](https://img-blog.csdnimg.cn/10e6e1ed6bf ...
- 基于Bert的文本情感分类
详细代码已上传到github: click me 摘 要: 情感分类是对带有感情色彩的主观性文本进行分析.推理的过程,即分析说话人的态度,推断其所包含的情感类别.传统机器学习在处理情感分类问 ...
- 循环神经网络实现文本情感分类之使用LSTM完成文本情感分类
循环神经网络实现文本情感分类之使用LSTM完成文本情感分类 1. 使用LSTM完成文本情感分类 在前面,使用了word embedding去实现了toy级别的文本情感分类,那么现在在这个模型中添加上L ...
- 循环神经网络实现文本情感分类之Pytorch中LSTM和GRU模块使用
循环神经网络实现文本情感分类之Pytorch中LSTM和GRU模块使用 1. Pytorch中LSTM和GRU模块使用 1.1 LSTM介绍 LSTM和GRU都是由torch.nn提供 通过观察文档, ...
- python 多分类情感_文本情感分类(一):传统模型
前言:四五月份的时候,我参加了两个数据挖掘相关的竞赛,分别是物电学院举办的"亮剑杯",以及第三届 "泰迪杯"全国大学生数据挖掘竞赛.很碰巧的是,两个比赛中,都有 ...
- 二十一、文本情感分类二
1.1 文本训练化概述 深度学习构建模型前需要将文本转化为向量表示(Word Embedding).首先需要将文本转化为数字(文本序列化),在把数字转化为向量. 可以考虑把文本中的每个词语和其对应的数 ...
- python 多分类情感_python 文本情感分类
对于一个简单的文本情感分类来说,其实就是一个二分类,这篇博客主要讲述的是使用scikit-learn来做文本情感分类.分类主要分为两步:1)训练,主要根据训练集来学习分类模型的规则.2)分类,先用已知 ...
- python 多分类情感词典_基于情感词典的文本情感分类
基于情感词典的文本情感分类 传统的基于情感词典的文本情感分类,是对人的记忆和判断思维的最简单的模拟,如上图.我们首先通过学习来记忆一些基本词汇,如否定词语有"不",积极词语有&qu ...
- tensorflow笔记-文本情感分类
本文是在学习tensorflow2.0官方教程时的一个笔记,原始教程请见文本情感分类 准备工作 1. 安装tensorflow并导入相关库 如果已经安装了可以略去此步 !pip install ten ...
最新文章
- seaborn系列 (2) | 散点图scatterplot()
- python电脑如何下载软件-Python如何下载文件
- 如何利用ZBrush中的DynaMesh创建身体(一)
- android环境搭建出错,androidstudio配置环境遇到的各种错误(持续更新中)
- 姿态估计 | OpenPose Plus值得期待
- Java中的 WeakReference 和 SoftReference
- 输入一个天数计算几周零几天 C语言,结构体练习——计算输入的日期为本年度第几天...
- Mongodb命令操作增删改查
- delphi 字符串处理中的怪异现象与处理
- 微信小程序开发【前端+后端(java)】
- 点云配准(CloudCompare软件)
- python椭圆花瓣_python之花瓣美女下载详解
- POJ-1436___Horizontally Visible Segments —— 线段树
- Win10怎么设置不进入屏保也不关闭显示器
- urllib3爬取网页源代码(爬虫)
- How to get current full screen dimention and orientation in run time
- sklearn.neighbors.KNeighborsClassifier()函数解析
- 选择城市,按城市的首字母进行排序
- 2021年危险化学品经营单位主要负责人免费试题及危险化学品经营单位主要负责人考试总结
- Java IO 编程