宽客秀

宽客秀

Quant.Show的Web3站点,Archives from quant.show

基于神经网络的情感分析

1. 概述#

情感分析是自然语言处理中常见的案例,多为对语言文字所表达的积极或消极情感做判断。之前,读过云帆先生写的一篇 《自然语言处理用于量化分析》,受益匪浅,再加上近期在自然语言处理的项目上盘桓了一段时间,本文就当是对自然语言处理中的情感分析话题做了简单的心得总结。

本文仍然採用云帆先生用的 Keras 框架,神经网络上以 Embedding+LSTM 或者 Embedding+CNN+Pooling 为主。(本文对作为自然语言处理特有的 Embedding 会着重做一些说明。)

训练数据集及验证数据集採用 Sarcasm_Headlines_Dataset(Misra,Rishabh and Arora, Prahal,2019),
该数据将文章标题分类为讽刺性或非讽刺性,具体而言包含 3 个属性(本文主要使用前 2 个属性):
1)is_sarcastic: 1 if the record is sarcastic otherwise 0
2)headline: the headline of the news article
3)article_link: link to the original news article. Useful in collecting supplementary data

预训练词向量数据採用 GloVe(Jeffrey Pennington,Richard Socher, and Christopher D. Manning,2014),该词向量是在 2014 年的英文维基百科上训练的,有 400k 个不同的词,每个词用 100 维向量表示。除 GloVe 以外,word2vec-api也提供了很多预训练词向量库。中文词向量也提供了不少基于中文的预训练词向量库。

2. 数据获取#

2.1 从文件从获取数据#

def parse_data(file):
for l in open(file,'r'):
yield json.loads(l)

data = list(parse_data('./Sarcasm_Headlines_Dataset_v2.json'))

2.2 将数据划分为训练集和验证集#

X = []
y = []
for s in data:
X.append(s['headline'])
y.append(s['is_sarcastic'])

训练集(包含验证集):测试集 = 9:1#

trainval_size = int(len(X) * train_ratio)

训练集及验证集#

X_trainval = X[_size]
y_trainval = y[_size]

训练集:验证集 = 8:2#

X_train, X_valid,y_train, y_valid = train_test_split(X_trainval,
y_trainval,
test_size = valid_ratio,
random_state=12)

测试集#

X_test = X[trainval_size:]
y_test = y[trainval_size:]

2.3 生成训练及验证用的序列#

1、生成 tokenizer
tokenizer 的主要作用是把所有词语变成索引。

# 定义 tokenizer
tokenizer = Tokenizer(num_words = num_words ,oov_token=oov_token)

训练 tokenizer#

tokenizer.fit_on_texts(trainning_sentences)

tokenizer.word_index#

word_index = tokenizer.word_index

2、由词语组成的句子转换成成由索引组成的序列
注意此处,上述代码的 num_words 参数的效果就显现出来了。每个序列中,不一定每个词语都有索引对应,只有那些频率最高的(Top num_words)的词才会有索引对应,而低频词此时就直接被忽略。所以,理论上 num_words<=word_index。

sequences = token.texts_to_sequences(sentences)

3、对齐序列
虽然通过 tokenizer,让句子变成了数字序列,但由于每个句子的词语数不同,所以句子长短不一,从而导致序列为非整齐的序列。此时还需要做对齐序列处理。
对齐序列的方法是按照最长句子的长度(或指定的长度)对齐,空缺地方补 0,补 0 的方式可分为前补 0 或后补 0。本文采用 16 位作为句子的最长长度,不足 16 位的句子采用前补 0 的方式,来对齐序列。

paddedSequecnes = pad_sequences(sequences,
padding=padding,
maxlen=MAX_SEQUENCE_LENGTH,
truncating=trunc_type)

3. 模型构建及训练#

3.1 模型构建#

1,Embedding 层 + Flatten 层
目前为止,我们得到了一个整数序列,这个序列是一个 2D 张量,其 shape 为(batch_size,sequence_length)(其中 batch_size 如果没有特别指定的话,就是 None)。但是这种做法并没有抓住词与词的意思之间的联系。

常见的做法是把整数用 One-hot 转成高维稀疏向量再转成低维密集向量,因此一个词可能用 100D, 300D 或 500D 的向量来表示,而词与词之间的相似度就用向量之间内积表示。这个过程就叫做 word2vec,即把单词转成换向量的过程,就是用 Embedding () 来实现。

首先我们要把序列中的每个词进行 One-hot 编码转换,让其成为一个只有 0-1 的稀疏矩阵。这么做的目的是什么呢?答案:计算简单。稀疏矩阵做矩阵计算的时候,只需要把 1 对应位置的数相乘求和就行。而之前由 0-9 数字组成的 1D 列表显然计算是慢了。

接下来,让稀疏矩阵 (m,n) 乘以一个词向量矩阵 (n, output_dim),形成一个新的词向量矩阵(m, output_dim)。

最后,把所有输入的序列中的整数,替换为相对应的词向量,从而形成新的张量,这个张量扩展为 3D 张量,其 shape 为(batch_size,sequence_length,output_dim),其中的 output_dim 指的是词向量的维度。

对于自然语言处理来说,Embedding 层是必须的,且必须作为第一层。详细的 Embedding 原理参考如下:

02e73a94d4cd2815079ccb22ab798af0

Embedding(num_words,
EMBEDDING_DIM,
input_length=MAX_SEQUENCE_LENGTH,
trainable=True),
Flatten(),

2,LSTM 层
LSTM 层可以布一层,也可以布多层,唯一要注意的如果是布多层的时候,第一层的参数 return_sequences 要设定为 True。

Bidirectional(LSTM(64,return_sequences=True)),
Bidirectional(LSTM(32)),

3,Conv1D 层 + GlobalMaxPooling1D 层
卷积层 + 池化层。

Conv1D(128, 5, activation='relu'),
GlobalMaxPooling1D(),

3.2 预训练词向量的引入#

还是回到 Embedding 层。可以看出,之前的代码在使用 Keras 的中 Embedding 层时候,并没有引入预训练词向量,其词向量先是随机初始化,然后,在训练数据的过程中自行训练完成。

现在我们尝试一下,引入预训练词向量 GloVe。
首先,构造 embedding_matrix,其中该 matrix 的维度应与 GloVe 的维度保持一致。
然后,将预训练词向量的值映射到 embedding_matrix 中。
最后,在 Embedding 层中指定引入 embedding_matrix。指定 embeddings_initializer=[embeddings_matrix] ,并且 trainable=False(表示在训练的过程中不更新词向量)。

Embedding(num_words,
EMBEDDING_DIM,
embeddings_initializer=Constant(embedding_matrix),
input_length=MAX_SEQUENCE_LENGTH,
trainable=False),

3.3 模型训练#

我们使用不同的 layer 设计,结合对预训练词向量引入与否,进行编译与训练。

4. 模型预测#

根据 Layer 设计的不同及是否引入预训练词向量,模型预测结果参考如下:

29705b1f81f94f5d81f4b59b15063b29

以 case6 为例,acc 和 loss 的图示参考如下:

84d4014fa62f4765627bdfd318ee5217

ca34c73ba4a2a3f7dbd689b0c96f94f3

由上可知,
1)在目前的参数设置前提下,CNN+Polling 的设计和 LSTM 不相伯仲还要好。。。不过这说明不了什么问题,关键还是要看参数的设计。但 LSTM 确实很消耗资源。
2)预训练词向量的引入可以减少训练的时间,但并不一定能保证模型更优。

5. 中文情感分析应用#

目前为止,我们都是在英文的基础上进行研究。那么,中文该如何做呢?
其实中文和英文最大的差别在于英文的词语是自然切分的,而中文是连在一起的。所以,对于中文而言,首先要做的是在 tokenizer 之前,做中文分词。目前常用的分词方法,比如说 jieba 分词,便能很好地解决这一问题。

其次,要解决的就是预训练词向量。从上述的结果可以看出,使用预训练的词向量作为特征是非常有效的。所以,要尽可能引入预训练词向量。一般情况下预训练词向量有两种获取方式:一是别人训练好的百科数据。优势:包含词语多,符合日常用语的语义;劣势:专有名词不足,占用空间大;二是自己训练。优势:专有名词,针对具体任务语义更准确; 劣势:泛化性差。

语义识别目前比较好的 Python 库有 gensim,其中也包含了很多词向量导入和训练的方法。

解决分词和词向量库的问题后,剩下的思路基本上一样了。


本文用到的文件的百度网盘下载地址如下:
链接:https://pan.baidu.com/s/1jH\_4Vzj1kh4af6domUCObQ
提取码:cr0t

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。