使用循环神经网络(RNN)实现影评情感分类
创新互联专业为企业提供临沂网站建设、临沂做网站、临沂网站设计、临沂网站制作等企业网站建设、网页设计与制作、临沂企业网站模板建站服务,十余年临沂做网站经验,不只是建网站,更提供有价值的思路和整体网络服务。作为对循环神经网络的实践,我用循环神经网络做了个影评情感的分类,即判断影评的感情色彩是正面的,还是负面的。
选择使用RNN来做情感分类,主要是因为影评是一段文字,是序列的,而RNN对序列的支持比较好,能够“记忆”前文。虽然可以提取特征词向量,然后交给传统机器学习模型或全连接神经网络去做,也能取得很好的效果,但只从端对端的角度来看的话,RNN无疑是最合适的。
以下介绍实现过程。
一、数据预处理
本文中使用的训练数据集为https://www.cs.cornell.edu/people/pabo/movie-review-data/上的sentence polarity dataset v1.0,包含正负面评论各5331条。可以点击进行下载。
数据下载下来之后需要进行解压,得到rt-polarity.neg和rt-polarity.pos文件,这两个文件是Windows-1252编码的,先将它转成unicode处理起来会更方便。
补充一下小知识,当我们打开一个文件,发现乱码,却又不知道该文件的编码是什么的时候,可以使用python的chardet类库进行判断,这里的Windows-1252就是使用该类库检测出来的。
在数据预处理部分,我们要完成如下处理过程:
1.转码
即将文件转为unicode编码,方便我们后续操作。读取文件,转换编码,重新写入到新文件即可。不存在技术难点。
2.生成词汇表
读取训练文件,提取出所有的单词,并统计各个单词出现的次数。为了避免低频词的干扰,同时减少模型参数,我们只保留部分高频词,比如这里我只保存出现次数前9999个,同时将低频词标识符
3.借助词汇表将影评转化为词向量
单词是没法直接输入给模型的,所以我们需要将词汇表中的每个单词对应于一个编号,将影评数据转化成词向量。方便后面生成词嵌入矩阵。
4.填充词向量并转化为np数组
因为不同评论的长度是不同的,我们要组成batch进行训练,就需要先将其长度统一。这里我选择以最长的影评为标准,对其他较短的影评的空白部分进行填充。然后将其转化成numpy的数组。
5.按比例划分数据集
按照机器学习的惯例,数据集应被划分为三份,即训练集、开发集和测试集。当然,有时也会只划分两份,即只包括训练集和开发集。
这里我划分成三份,训练集、开发集和测试集的占比为[0.8,0.1,0.1]。划分的方式为轮盘赌法,在numpy中可以使用cumsum和searchsorted来简洁地实现轮盘赌法。
6.打乱数据集,写入文件
为了取得更好的训练效果,将数据集随机打乱。为了保证在训练和模型调整的过程中训练集、开发集、测试集不发生改变,将三个数据集写入到文件中,使用的时候从文件中读取。
下面贴上数据预处理的代码,注释写的很细,就不多说了。
# -*- coding: utf-8 -*- # @Time : 18-3-14 下午2:28 # @Author : AaronJny # @Email : Aaron__7@163.com import sys reload(sys) sys.setdefaultencoding('utf8') import collections import settings import utils import numpy as np def create_vocab(): """ 创建词汇表,写入文件中 :return: """ # 存放出现的所有单词 word_list = [] # 从文件中读取数据,拆分单词 with open(settings.NEG_TXT, 'r') as f: f_lines = f.readlines() for line in f_lines: words = line.strip().split() word_list.extend(words) with open(settings.POS_TXT, 'r') as f: f_lines = f.readlines() for line in f_lines: words = line.strip().split() word_list.extend(words) # 统计单词出现的次数 counter = collections.Counter(word_list) sorted_words = sorted(counter.items(), key=lambda x: x[1], reverse=True) # 选取高频词 word_list = [word[0] for word in sorted_words] word_list = [''] + word_list[:settings.VOCAB_SIZE - 1] # 将词汇表写入文件中 with open(settings.VOCAB_PATH, 'w') as f: for word in word_list: f.write(word + '\n') def create_vec(txt_path, vec_path): """ 根据词汇表生成词向量 :param txt_path: 影评文件路径 :param vec_path: 输出词向量路径 :return: """ # 获取单词到编号的映射 word2id = utils.read_word_to_id_dict() # 将语句转化成向量 vec = [] with open(txt_path, 'r') as f: f_lines = f.readlines() for line in f_lines: tmp_vec = [str(utils.get_id_by_word(word, word2id)) for word in line.strip().split()] vec.append(tmp_vec) # 写入文件中 with open(vec_path, 'w') as f: for tmp_vec in vec: f.write(' '.join(tmp_vec) + '\n') def cut_train_dev_test(): """ 使用轮盘赌法,划分训练集、开发集和测试集 打乱,并写入不同文件中 :return: """ # 三个位置分别存放训练、开发、测试 data = [[], [], []] labels = [[], [], []] # 累加概率 rate [0.8,0.1,0.1] cumsum_rate [0.8,0.9,1.0] rate = np.array([settings.TRAIN_RATE, settings.DEV_RATE, settings.TEST_RATE]) cumsum_rate = np.cumsum(rate) # 使用轮盘赌法划分数据集 with open(settings.POS_VEC, 'r') as f: f_lines = f.readlines() for line in f_lines: tmp_data = [int(word) for word in line.strip().split()] tmp_label = [1, ] index = int(np.searchsorted(cumsum_rate, np.random.rand(1) * 1.0)) data[index].append(tmp_data) labels[index].append(tmp_label) with open(settings.NEG_VEC, 'r') as f: f_lines = f.readlines() for line in f_lines: tmp_data = [int(word) for word in line.strip().split()] tmp_label = [0, ] index = int(np.searchsorted(cumsum_rate, np.random.rand(1) * 1.0)) data[index].append(tmp_data) labels[index].append(tmp_label) # 计算一下实际上分割出来的比例 print '最终分割比例', np.array([map(len, data)], dtype=np.float32) / sum(map(len, data)) # 打乱数据,写入到文件中 shuffle_data(data[0], labels[0], settings.TRAIN_DATA) shuffle_data(data[1], labels[1], settings.DEV_DATA) shuffle_data(data[2], labels[2], settings.TEST_DATA) def shuffle_data(x, y, path): """ 填充数据,生成np数组 打乱数据,写入文件中 :param x: 数据 :param y: 标签 :param path: 保存路径 :return: """ # 计算影评的大长度 maxlen = max(map(len, x)) # 填充数据 data = np.zeros([len(x), maxlen], dtype=np.int32) for row in range(len(x)): data[row, :len(x[row])] = x[row] label = np.array(y) # 打乱数据 state = np.random.get_state() np.random.shuffle(data) np.random.set_state(state) np.random.shuffle(label) # 保存数据 np.save(path + '_data', data) np.save(path + '_labels', label) def decode_file(infile, outfile): """ 将文件的编码从'Windows-1252'转为Unicode :param infile: 输入文件路径 :param outfile: 输出文件路径 :return: """ with open(infile, 'r') as f: txt = f.read().decode('Windows-1252') with open(outfile, 'w') as f: f.write(txt) if __name__ == '__main__': # 解码文件 decode_file(settings.ORIGIN_POS, settings.POS_TXT) decode_file(settings.ORIGIN_NEG, settings.NEG_TXT) # 创建词汇表 create_vocab() # 生成词向量 create_vec(settings.NEG_TXT, settings.NEG_VEC) create_vec(settings.POS_TXT, settings.POS_VEC) # 划分数据集 cut_train_dev_test()
另外有需要云服务器可以了解下创新互联scvps.cn,海内外云服务器15元起步,三天无理由+7*72小时售后在线,公司持有idc许可证,提供“云服务器、裸金属服务器、高防服务器、香港服务器、美国服务器、虚拟主机、免备案服务器”等云主机租用服务以及企业上云的综合解决方案,具有“安全稳定、简单易用、服务可用性高、性价比高”等特点与优势,专为企业上云打造定制,能够满足用户丰富、多元化的应用场景需求。