策略算法工程师之路-文本情感分类(Sentiment Analysis)
目录
1.情感分析简述
2.情感字典匹配方法
3.依存句法方法
4.传统模型方法
5.深度模型方法
5.1) TextCNN
5.2) LSTM(BiLSTM)
5.3) Attention机制
5.4) HAN
5.5) SelfAttention (暂无)
5.6) DeepMoji (暂无)
5.7) 多层级多标签分类网络(HMCN)
1.情感分析简述
情感分析( Sentiment Analysis )又称倾向性分析,或意见挖掘,它是对带有情感色彩的主观性文本进行分析、处理、归纳和推理的过程。利用情感分析能力,可以针对带有主观描述的自然语言文本,自动判断该文本的情感正负倾向并给出相应的结果。在 评论分析与决策 、 电商评论分类 以及 舆情监控 中有非常广泛的应用。
如下是 百度大脑 提供的 情感分析 服务,感受一下。
通常情感识别依据所处理文本的级别分为以下三类:
- 文本级别:通过完整文档或段落来获取情绪;
- 句子级别:获得单句的情绪。
- 子句级别:获得句子中,子表达的情感。
本文主要讲解 句子级别 的情感分析。
参考资料:
1.清华大学:网络社交媒体的情感认知与计算
http://www. 360doc.com/content/16/0 505/18/31267089_556548348.shtml
2.情感字典方法
基于词典的情感分析是最简单的策略,即根据文本中出现的 情感词 判断一句话的情感极性。该方法的核心是维护 情感词典 (可以是通用情感词典,也可以是 领域专用 的情感词典)。英文的词典有很多,中文主要有知网整理的情感词典Hownet和台湾大学整理发布的NTUSD两个情感词典,还有哈工大信息检索研究室开源的《同义词词林》。
1) 情感词典
下面是词典中的部分情感词,词后面的数字代表其 情感正负程度。 比如" fuck "是负向情感词 , 其情感值是 " -6.70400012637"。再比如" 好赞 "是正向情感词,其情感值是"4.03663526"。情感词及其极性可以由人工搜集标注,也可以由算法挖掘。
词典下载: https:// bosonnlp.com/resources/ BosonNLP_sentiment_score.zip
...
最尼玛 -6.70400012637
扰民 -6.49756445867
fuck... -6.32963390433
Fxxk -5.87247473641
MLP -5.87247473641
吃哑巴亏 -5.77120419579
IAQI -5.77107837123
太好 1.51096421722
好赞 4.03663526827
...
2).文本分词
原句: 真是太差劲了!
for word in jieba.cut("真是太差劲了!"):
print(word)
分词: 真是太 / 差劲 / 了 / !
3).情感计算
查询情感词典, 累加 每个词的 情感值 。
0 [词典中没有'真是太',默认为0] -2.76814873241 ["差劲"]+ 0 ["了"] = -2.76814873241
因此" 真是太差劲了! "的情感极性为 负向。
4). 完整代码
import re
import jieba
# 基于词典的情感识别
class DictBasedSentAnal:
# 加载情感词典
def __init__(self):
self.__root_dir = 'dict/'
self.__sent_dict__ = self.__read_dict(self.__root_dir+'BosonNLP_sentiment_score.txt')
# 情感极性分析
def analyse(self, sentence):
score = 0.0
for word in jieba.cut(sentence):
score += self.__sent_dict__.get(word, 0)
return score
# 读取词典
@staticmethod
def __read_dict(path, encoding='utf-8'):
sent_dict = {}
return sent_dict
以上代码主要参考(复制)自:
https://www. jianshu.com/p/a7b76beea 6cf
5).考虑程度副词
原句: 这个游戏不是太好玩。
分词: 这个/游戏/不是/太好玩/。
按照上面的算法,如果仅仅考虑" 太好玩 ",原句的情感是正向的,显然这是不对的,主要是由于忽略了 情感词 ( 太好玩 )前面的 程度副词 ( 不是 )。由于 程度副词 是对情感词 情感极性 的 加强 ,所以最终的情感值可以通过程度副词的 程度值(正值表示加强,负值表示否定) 乘以情感词的情感值。
还是上面的例子, "太好玩(+2)" * "不是(-1)" = -1 ,原句的情感极性为" -2 "。
Tips:
情感词:是主体对某一客体表示内在评价的词语,带有强烈的感情色彩。
程度副词:本身没有情感倾向,但能够增强或减弱情感强度
否定词:本身也没有情感倾向,但能改变情感的极性
停用词:完全没有用或者没有意义的词
参考资料:
1.基于情感词典的文本情感分析
https://www. jianshu.com/p/92584d21f e9f
2.基于词典的中文情感倾向分析算法设计
3.基于情感词典进行情感态度分析
https:// blog.csdn.net/qq_391876 75/article/details/85100560
4.基于词典的中文情感倾向分析算法设计
https://www. jianshu.com/p/f2ba15868 996
5.文本情感分类
https://www. jianshu.com/p/258b6f09b f4c
6.情感分析:几乎包括你需要知道的所有(二)
https://www. jianshu.com/p/e424061ac c51
7.深入理解NLP中的文本情感分析(华为)
https://www. biaodianfu.com/sentimen t-analysis.html
8.文本情感分类(一):传统模型
https:// spaces.ac.cn/archives/3 360
3.基于依存句法的规则方法
前面所讲的基于情感词典的方法只是简单的匹配,而实际的语言是复杂的非线性系统。一种改进的思路是利用 句法分析技术 将文本解析成 结构化的语法树 ,然后依据规则从中提取感兴趣的内容。句法分析是根据给定的语法体系,自动推导出句子的句子结构,分析句子包含的句法单元及其之间关系,并转化为结构化的句法分析树。如下图( http:// ltp.ai/demo.html ):
其中,ADV->状中结构,ATT->定中关系,HED->核心关系,RAD->右附加关系。除此之外,在NLP中 依存句法分析 是 更加常见 的句法分析技术。
上图中边的含义如下:
\begin{align*} &Exp->当事关系 \\ &mDegr->程度标记 \\ &mPars->插入语标记 \\ &mTone->语气标记 \\ &Nmod->名字修饰角色 \\ &Root->根节点 \end{align*}
Root 根结点指向"棒",表明"棒"是整个句子的 核心 。以"棒"为起点的各边指向的句子成分,分别以某种" 依赖关系" 依赖于核心词"棒"。因此以 核心词为线索 可以解析出句子的 语法结构 ,进一步依据" 预定义的规则" 抽取出评论文本中的 依存情感单元, 包括 评论对象 、 情感词 、 否定词 、 程度副词 等。
下面是几条简单的规则:
1).否定词修饰情感词
否定词修饰情感词会改变情感倾向 ,如"不喜欢"这个短语 中,"不"作为否定词修饰"欢"。
2).程度词修饰情感词
程度词修饰情感词会改变情感强度 ,如"太棒" 短语中,"太"作为程度词修饰"喜欢"。
3).否定词修饰否定词
否定词修饰否定词即为双重否定 ,一般情况下 不影响原来的情感倾向 ,对于情感程度的影响需要具体分析依存关系。如"不是不喜欢"中,第一个"不"作为否定词修饰了"是",后面的"不喜欢"和"是"是动宾关系。
4).否定词修饰程度词
否定词修饰程度词相当于对程度的否定 ,而 非情感的否定 ,因此否定词修饰后,最终 情感倾向未变 ,但是 程度有所降低 。如"不很喜欢"中,"不"修饰了"很",然后才是"很"修饰"喜欢"。"很"作为程度词表现了一种情感等级的加强,但是“不”作 为否定词对“很”这个程度词的否定使得最终的强度有一定的削减。"不很喜欢"这个情感短语仍然表现了"喜欢"这种情感类 别,但是在强度上 强于"喜欢"这个情感词 , 弱于"很喜欢"这个情感短语 。
5).程度词修饰否定词
程度词修饰否定词相当于给否定加个强度 ,因为否定本身无强度可言,最终的 强度会随否定词一起携带到情感词上 。如 "很不喜欢"中,"很"修饰"不",然后才是"不"修饰"喜欢"。因此,"很不喜欢"在情感类别上是"厌恶",在强度 上比"不喜欢"更强,和"很喜欢"强度一致。
6).程度词修饰程度词
程度词修饰程度相当于程度的叠加 。多用于 微博等非正式语料 中,此类表达方式多用于 表现强烈情感。 如"非常极其喜欢"中,"非常"修饰"极其",然后是"极其"修饰"喜欢"。对于此类的表述,其情感强度相比于"非常喜欢"和"极其喜欢"都要强一些。
以上只是列举了几个简单的规则示例,实际应用中需要依据所处理的领域做 Domain Special 的规则抽取。
示例Demo参考:
https:// github.com/ruizhang1993 /Dependency_SentimentAnalysis
参考资料:
1.基于改进依存句法的微博情感分析研究_李雪红
2.基于关键词和关键句抽取的用户评论情感分析_喻影
3.基于依存句法的评论情感极性分析_邹海林
4.基于依存句法的情感分析
https://www. jianshu.com/p/66947e2f4 084
5.基于句法依存关系的微博情感分析方法_文坤梅
6.基于依存关系的旅游景点评论的特征-观点对抽取
7.基于依存句法树方法的微博文本的情感分析研究_王彬菁
8.基于依存关系的旅游景点评论的特征-观点对抽取
9.意图识别(规则模板解析、深度学习意图识别)
4.传统模型方法
语言系统是相当复杂的,前面介绍的 基于情感词典 或 基于依存句法 的文本情感分类只是一个规则系统,其性能是有限的,依赖于领域专家的总结,不同领域间的适用性较差。基于机器学习(朴素贝叶斯、最大熵、SVM、LR、树模型等)的方法可以从数据中 自动学习Pattern 。本小节 暂时 只总结下文本分类问题常用的特征。
常用特征:
1).统计特征
比如 句子长度 , 词长度 , 标点数量 , 标点种类 , 词性序列 等。
2).句子表示
BOW(bag of word ,n-gram)、tf-idf、LDA等。
3).编辑距离
4).位置特征
关键词位置等
5).句法特征
6).预训练向量
Word2vec、Doc2vec 等 。
这里有一个基于SVM的简单示例:
https:// github.com/BUPTLdy/Sent iment-Analysis/blob/master/code/Sentiment_svm.py
参考资料:
1.竞赛专题(四)特征工程-竞赛中的必杀技
https:// cloud.tencent.com/devel oper/article/1507320
2.从Kaggle赛题: Quora Question Pairs 看文本相似性/相关性
https:// zhuanlan.zhihu.com/p/35 093355
3.蚂蚁金融NLP竞赛——文本语义相似度赛题总结
https:// blog.csdn.net/u01473253 7/article/details/81038260
4.通过kaggle比赛学习机器学习文本分类方法
https:// zhuanlan.zhihu.com/p/34 899693?group_id=961190993937268736
5.Kaggle 恶意评论(toxic comment classification)分类 top 1%方案
https:// zhuanlan.zhihu.com/p/34 922134
6. NLP-SentimentAnalysisForChineseText
https:// github.com/yirui-wang-0 212/NLP-SentimentAnalysisForChineseText
5.深度模型方法
传统机器学习做文本分类需要做大量的 特征工程 (feature engineering),限制了预测性能的提升。近些年深度学习在NLP得到了广泛的应用,首先深度学习将算法工程师从繁杂的特征工程中解放出来,其次赋予算法工程师 根据领域问题先验定制化网络结构 的能力。接下来分别介绍情感分类或者文本分类中常见的网络结构。
5.1 TextCNN网络
TextCNN是一个通用的文本分类框架,在这里用于情感识别。
Paper: Convolutional Neural Networks for Sentence Classification
TextCNN的网络架构如下:
一张更清晰的:
1).输入层
# 输入占位符
self.input_x = tf.placeholder(tf.int32, [None, sequence_length], name="input_x")
self.input_y = tf.placeholder(tf.float32, [None, num_classes], name="input_y")
sequence_length :输入文本的长度。通常采用 截断 或 补"0" 的方式将句子归一化到统一长度,比如可以设为 80 。
num_classes: 分类数量。根据需要识别的 情感级别数量 定义。比如有三种情感等级,正向、负向、中性,此时num_classes应设为 3。
2).Embedding层
在文本分类任务中,通常将Word映射到Embeddding之后再输入到后续网络。给定一个文档单词序列,"A B A C B F G", 文档中每个不同的单词都对应一个低维向量表示。比如,A对应的向量为[0.1 0.6 -0.5],B对应的向量为[-0.2 0.9 0.7] 。很多词语的意思是各个方向发散开的(多义性),而多维向量解决了词语的多方向发散问题,因此可以表达更丰富的语义。
# Embedding layer
with tf.device('/cpu:0'), tf.name_scope("embedding"):
self.W = tf.Variable(
tf.random_uniform([vocab_size, embedding_size], -1.0, 1.0),
name="W")
self.embedded_chars = tf.nn.embedding_lookup(self.W, self.input_x)
self.embedded_chars_expanded = tf.expand_dims(self.embedded_chars, -1)
vocab_size: 字典大小。
embedding_size: Embedding向量维度。
参考资料:
1.什么是 word embedding?
https://www. zhihu.com/question/3227 5069
3).卷积层
# 卷积与池化层
pooled_outputs = []
for i, filter_size in enumerate(filter_sizes):
with tf.name_scope("conv-maxpool-%s" % filter_size):
# 卷积层(Convolution Layer)
filter_shape = [filter_size, embedding_size, 1, num_filters]
W = tf.Variable(tf.truncated_normal(filter_shape, stddev=0.1), name="W")
b = tf.Variable(tf.constant(0.1, shape=[num_filters]), name="b")
conv = tf.nn.conv2d(
self.embedded_chars_expanded,
strides=[1, 1, 1, 1],
padding="VALID",
name="conv")
# 非线性变换
h = tf.nn.relu(tf.nn.bias_add(conv, b), name="relu")
# 对卷积层的输出做Maxpooling操作
pooled = tf.nn.max_pool(
ksize=[1, sequence_length - filter_size + 1, 1, 1],
strides=[1, 1, 1, 1],
padding='VALID',
name="pool")
pooled_outputs.append(pooled)
filter_sizes: 不同类型(这里主要指 尺寸 )卷积核的数量。
num_filters: 同一类型卷积核的数量。
所以, 总的卷积核的数量 是 filter\_sizes\times num\_filters 。
不同尺寸的卷积核可以捕获 不同范围内Word间的关系 。理论上尺寸越大越能捕获 全局的语义信息 。
4).输出层
num_filters_total = num_filters * len(filter_sizes)
self.h_pool = tf.concat(pooled_outputs, 3)
self.h_pool_flat = tf.reshape(self.h_pool, [-1, num_filters_total])
# 最终输出
with tf.name_scope("output"):
W = tf.get_variable(
shape=[num_filters_total, num_classes],
initializer=tf.contrib.layers.xavier_initializer())
b = tf.Variable(tf.constant(0.1, shape=[num_classes]), name="b")
l2_loss += tf.nn.l2_loss(W)
l2_loss += tf.nn.l2_loss(b)
self.scores = tf.nn.xw_plus_b(self.h_drop, W, b, name="scores")
self.predictions = tf.argmax(self.scores, 1, name="predictions")
完整代码如下:
https:// github.com/diaosj/cnn-t ext-classification-tf/blob/master/text_cnn.py
https:// cloud.tencent.com/devel oper/article/1006130
可以试着运行下,在tensorboard中的可视化结果如下:
5.2 LSTM模型
前面讲过基于CNN的文本分类尽管在实践中取得了较好的效果,但由于CNN本身的特性导致其忽略了 Word 的序列依赖关系。设想,当我们理解一句话时通常" 从前往后 "依次阅读每个 Word ,同时结合单词间的 前后依赖关系, 理解整句话的含义。本小节讲解的 LSTM (Long Short-term Memory Neural Networks)模型可以捕获 Word 的依赖关系,通常用于 序列建模 。 LSTM 是对传统 RNN 的改进,缓解了 梯度爆炸 问题。
LSTM的内部结构:
本小节的 网络结构定义 如下( 基于Keras ):
from sklearn.cross_validation import train_test_split
from keras.models import Sequential
from keras.layers.embeddings import Embedding
from keras.layers.recurrent import LSTM
from keras.layers.core import Dense, Dropout,Activation
from keras.models import model_from_yaml
np.random.seed(1337) # For Reproducibility
import sys
sys.setrecursionlimit(1000000)
import yaml
import keras
#定义网络结构
# n_symbols:
# embedding_weights:Embedding矩阵
# x_train:训练集X
# y_train:训练集y
# x_test:测试集x
# y_test:测试集y
def train_lstm(n_symbols,embedding_weights,x_train,y_train,x_test,y_test):
print('开始定义网络...')
model = Sequential()
# 将Word转换成Embedding向量
model.add(Embedding(output_dim=vocab_dim,
input_dim=n_symbols,
mask_zero=True,
weights=[embedding_weights],
input_length=input_length)) # Adding Input Length
# LSTM
model.add(LSTM(output_dim=50, activation='tanh', inner_activation='hard_sigmoid'))
model.add(Dropout(0.5))
# 全链接层
model.add(Dense(3, activation='softmax')) # Dense=>全连接层,输出维度=1
model.add(Activation('softmax'))
print('Compiling the Model...')
model.compile(loss='categorical_crossentropy',
optimizer='adam',metrics=['accuracy'])
print("开始训练...")
model.fit(x_train , y_train , batch_size=batch_size , epochs=n_epoch , verbose=1)
print("模型评估...")
score = model.evaluate(x_test, y_test,batch_size=batch_size)
print('ACC:'+str(score))
模型训练:
train_lstm(n_symbols,embedding_weights,x_train,y_train,x_test,y_test)
开始训练...
Epoch 1/10
16870/16870 [==============================] - 68s 4ms/step - loss: 0.9180 - acc: 0.6228
Epoch 2/10
16870/16870 [==============================] - 55s 3ms/step - loss: 0.6898 - acc: 0.8638
Epoch 3/10
16870/16870 [==============================] - 61s 4ms/step - loss: 0.6756 - acc: 0.8767
Epoch 4/10
16870/16870 [==============================] - 55s 3ms/step - loss: 0.6712 - acc: 0.8811
Epoch 10/10
16870/16870 [==============================] - 62s 4ms/step - loss: 0.6226 - acc: 0.9289
Evaluate...
4218/4218 [==============================] - 5s 1ms/step
Test score:[0.6559340375585543, 0.8944997629773397]
在模型训练前需要对文本做处理:
1).Embedding词向量训练
可以用 gensim 工具包中的 word2vec 训练词向量 。 以词语" 不错 "为例:
word_vectors["不错"]
array([-0.21249017, -0.7439336 , 1.5525742 , 2.523707 , -1.26884 ,
0.94706655, -1.4531343 , 0.3719145 , -1.798436 , -2.8654885 ,
-0.98588014, 2.590885 , -1.296376 , -1.5622058 , -1.3199229 ,
-0.5774353 , -0.9784841 , -0.08609965, 1.6380359 , -0.71032125,
0.8648587 , 2.1153228 , 0.34322342, -1.0344014 , -1.1715566 ,
-1.1469606 , -1.0197061 , 2.4668577 , -1.4312339 , 0.5667959 ,
-0.64566624, -1.432156 , -0.712759 , -0.17578368, 0.33848813,
-1.9471674 , -1.3971925 , -6.4188747 , -1.7790052 , 0.03861272,
-0.35280612, 1.0356431 , -1.1207013 , 1.240891 , -1.5177649 ,
1.3788583 , 2.014057 , -0.9945446 , 1.1588986 , 0.21868615,
1.1321787 , -0.98895115, -1.8484693 , -1.7223359 , 1.3790622 ,
2.2856283 , 0.45740244, 0.39467898, -0.38629273, 0.8045153 ,
1.4975424 , -0.43285686, -1.0815718 , -0.55428714, -1.1573505 ,
-0.14508727, 3.0183074 , 0.9997595 , 3.1770122 , 3.4902725 ,
-0.1341271 , -0.3940994 , 0.35976872, -0.8396342 , -0.14406282,
0.04484363, 0.45852518, -2.6293077 , -1.2270257 , -0.2648226 ,
3.6768708 , 0.06674827, 1.4748832 , 1.652305 , -1.1041977 ,
-0.0180153 , 0.41195512, 1.0792816 , -2.2666452 , 1.0985105 ,
0.5582074 , -0.78833973, 0.7233552 , 0.28352848, 1.1825304 ,
-0.6322761 , 1.9660307 , 1.0732826 , -3.7055943 , -1.2365547 ],
dtype=float32)
2).原始文本
0 做为一本声名在外的流行书,说的还是广州的外企,按道理应该和我的生存环境差不多啊。但是一看之下...
1 作者完全是以一个过来的自认为是成功者的角度去写这个问题,感觉很不客观。虽然不是很喜欢,但是,...
2 作者提倡内调,不信任化妆品,这点赞同。但是所列举的方法太麻烦,配料也不好找。不是太实用。
3 作者的文笔还行,但通篇感觉太琐碎,有点文人的无病呻吟。自由主义者。作者的品性不敢苟同,无民族...
...
3).文本统计
对分词后的语料建立 Word->ID 的映射。
{
'一样': 482,
'一根': 483,
'一楼': 484,
'一模一样': 485,
'一次': 486,
'一次性': 487,
'一次次': 488,
}
4).文本转换成ID序列
原句:做为一本声名在外的流行书,说的还是广州的外企,按道理应该和我的生存环境差不多啊。但是一看之下...
分词:['做','为','一本','声名在外','的','流行','书',',','说','的','还是','广州','的','外企',...]
ID :[ 1489, 947, 478, ...]
上面的模型取得的ACC(准确率)为:0.8944
下面是几个预测的例子感受下:
1). 酒店的环境非常好,价格也便宜,值得推荐
Label : positive(正向)
Predict: positive(正向)
2).手机质量太差了,傻逼店家,赚黑心钱,以后再也不会买了
Label : negative(负向)
Predict: negative(负向)
3).太好了
Label : positive(正向)
Predict: positive(正向)
4).太强了
Label : positive(正向)
Predict: negative(负向) 预测错误!
...
预测效果还是不错的,不过显然“ 太强了 ”预测错了。接下来对上面的网络定义做一下改进,用BiLSTM替换成LSTM。相比于LSTM只能捕获单向的依赖,BiLSTM的改进之处在于可以捕获双向的依赖,如下图:
借助 Keras 只需要对网络定义稍作改动:
...
model.add(Embedding(output_dim=vocab_dim,
input_dim=n_symbols,
mask_zero=True,
weights=[embedding_weights],
input_length=input_length)) # Adding Input Length
#model.add(LSTM(output_dim=50, activation='tanh', inner_activation='hard_sigmoid'))
model.add(Bidirectional(LSTM(output_dim=50, activation='tanh', inner_activation='hard_sigmoid'),merge_mode='concat'))
...
重新训练模型,ACC为 :0.9011。
重新预测"太强了":
Label : positive(正向)
Predict: positive(正向)
除此之外还可以尝试 多层LSTM ,感兴趣的可以试下。
代码参考:
https:// github.com/ble55ing/LST M-Sentiment_analysis
另外还可以考虑 深度LSTM ( BiLSTM ),如下图:
5.3 Attention机制
Attention机制通俗的说,对于某个时刻的输出y,它在输入x上各个部分上的 注意力 ,这里的注意力也就是 权重 ,即 输入x的各个部分对某时刻输入y贡献的权重 。具体的在情感识别任务中,原句中 每个Word对情感类别的影响是不同的 。如下图所示网络结构中 a(h_{t}) 部分的作用是用于计算原句中每个Word对应的权重。 a(h_{t}) 部分的参数与原有网络(这里为LSTM)同时训练,其输入为每个Word对应的隐向量。
在前面LSTM的基础上加入Attention机制,网络定义结构如下:
from sklearn.cross_validation import train_test_split
from keras.models import Sequential
from keras.layers.embeddings import Embedding
from keras.layers.recurrent import LSTM
from keras.layers.core import Dense, Dropout,Activation
from keras.models import model_from_yaml
np.random.seed(1337) # For Reproducibility
import sys
sys.setrecursionlimit(1000000)
import yaml
import keras
# 定义网络结构
# n_symbols:
# embedding_weights:Embedding矩阵
# x_train:训练集X
# y_train:训练集y
# x_test:测试集x
# y_test:测试集y
def train_lstm(n_symbols,embedding_weights,x_train,y_train,x_test,y_test):
print('开始定义网络...')
model = Sequential()
# 将Word转换成Embedding向量
model.add(Embedding(output_dim=vocab_dim,
input_dim=n_symbols,
mask_zero=True,
weights=[embedding_weights],
input_length=input_length)) # Adding Input Length
# LSTM
# 注意这里加了return_sequences=True参数,表示每个Word都会输出一个"隐向量",而不是仅返回最后一个Word的向量。
model.add(Bidirectional(LSTM(output_dim=50, activation='tanh', inner_activation='hard_sigmoid',return_sequences=True)))
model.add(Attention())
model.add(Dropout(0.5))
# 全链接层
model.add(Dense(3, activation='softmax')) # Dense=>全连接层,输出维度=1
model.add(Activation('softmax'))
print('Compiling the Model...')
model.compile(loss='categorical_crossentropy',
optimizer='adam',metrics=['accuracy'])
print("开始训练...")
model.fit(x_train , y_train , batch_size=batch_size , epochs=n_epoch , verbose=1)
print("模型评估...")
score = model.evaluate(x_test, y_test,batch_size=batch_size)
print('ACC:'+str(score))
其中, Attention() 是我们基于Keras的Layer扩展机制自定义的层,计算原理如下。
代码如下:
from tensorflow.keras import backend as K
from tensorflow.keras import initializers, regularizers, constraints
from tensorflow.keras.layers import Layer
class Attention(Layer):
def __init__(self, step_dim,
W_regularizer=None, b_regularizer=None,
W_constraint=None, b_constraint=None,
bias=True, **kwargs):
# Input shape(输入尺寸)
3D tensor with shape: `(samples, steps, features)`.
# Output shape(输出尺寸)
2D tensor with shape: `(samples, features)`.
使用方法非常简单,只需要把Attention层加在RNN(GRU/LSTM/SimpleRNN)后面即可,并把设置return_sequences=True.
Example:
model.add(LSTM(64, return_sequences=True))
model.add(Attention())
# next add a Dense layer (for classification/regression)
hidden = LSTM(64, return_sequences=True)(words)
sentence = Attention()(hidden)
# next add a Dense layer (for classification/regression)
self.supports_masking = True
self.init = initializers.get('glorot_uniform')
# 模型超参数
self.W_regularizer = regularizers.get(W_regularizer)
self.b_regularizer = regularizers.get(b_regularizer)
self.W_constraint = constraints.get(W_constraint)
self.b_constraint = constraints.get(b_constraint)
self.bias = bias
self.step_dim = step_dim
self.features_dim = 0
super(Attention, self).__init__(**kwargs)
# Attention层的参数定义
def build(self, input_shape):
# (samples, steps, features)
assert len(input_shape) == 3
self.W = self.add_weight(shape=(input_shape[-1],),
initializer=self.init,
name='{}_W'.format(self.name),
regularizer=self.W_regularizer,
constraint=self.W_constraint)
self.features_dim = input_shape[-1]
if self.bias:
self.b = self.add_weight(shape=(input_shape[1],),
initializer='zero',
name='{}_b'.format(self.name),
regularizer=self.b_regularizer,
constraint=self.b_constraint)
else:
self.b = None
self.built = True
def compute_mask(self, input, input_mask=None):
return None
# Attention层的计算逻辑
# x的尺寸:(samples, steps, features)
def call(self, x, mask=None):
features_dim = self.features_dim
step_dim = self.step_dim
e = K.reshape(K.dot(K.reshape(x, (-1, features_dim)), K.reshape(self.W, (features_dim, 1))), (-1, step_dim)) # e = K.dot(x, self.W)
if self.bias:
e += self.b
e = K.tanh(e)
a = K.exp(e)
# apply mask after the exp. will be re-normalized next
if mask is not None:
# cast the mask to floatX to avoid float64 upcasting in theano
a *= K.cast(mask, K.floatx())
# 归一化
a /= K.cast(K.sum(a, axis=1, keepdims=True) + K.epsilon(), K.floatx())
a = K.expand_dims(a)
c = K.sum(a * x, axis=1)
return c
# Attention层的输出尺寸
# 2D tensor with shape: `(samples, features)`.
def compute_output_shape(self, input_shape):
return input_shape[0], self.features_dim
用上节的数据训练网络准确率为 0.8924,性能没有得到提升,还需要进一步深入调参。 感兴趣的可以试一下。
最期兴起的Transformer、Bert,其 核心思量也是Attention ,都可以尝试一下。
参考资料:
1. 达观数据曾彦能:如何用深度学习做好长文本分类与法律文书智能化处理
2.tensorflow文本分类实战(五)——HAN模型
https:// zhuanlan.zhihu.com/p/97 528889
3.keras实现Attention机制
https://www. jianshu.com/p/31c0acf94 e0e
4.tensorflow文本分类实战(四)——Bi-LSTM+Attention
https:// zhuanlan.zhihu.com/p/97 525394
5.python - Keras attention layer over LSTM
http://www. itkeyword.com/doc/34175 03109270651x622/python-keras-attention-layer-over-lstm
6.tensorflow文本分类实战(五)——HAN模型
https:// zhuanlan.zhihu.com/p/97 528889
https:// github.com/philipperemy /keras-attention-mechanism
8.[深度应用]·Keras实现Self-Attention文本分类(机器如何读懂人心)
https:// cloud.tencent.com/devel oper/article/1451523
9.基于Keras实现双向LSTM,可视化其注意力机制
http://www. elecfans.com/d/786485.h tml
10. 文本分类实战(五)—— Bi-LSTM + Attention模型
https://www. cnblogs.com/jiangxinyan g/p/10208227.html
11.[深度应用]·Keras实现Self-Attention文本分类
https:// cloud.tencent.com/devel oper/article/1451523
12.基于改进注意力机制的短文本情感分析研究_杨帆
13.干货 | Attention注意力机制超全综述
https:// cloud.tencent.com/devel oper/article/1480510
14.什么是注意力机制?
https://www. cnblogs.com/ydcode/p/11 038064.html
https:// github.com/mpk001/Senti ment_analysis
5.4 HAN模型
HAN(Hierarchical Attention Networks), 层次注意力网络 。对 文档/较长文本 进行分类的时候,仅仅对word粒度进行Attention是不够的,还需要对各个句子(短句)进行Attention的学习, 不同句子也需要分配不同的权重 , 每个句子里的词语也分配不同的权重 。 网络结构如下图所示:
基于Keras的网络结构定义如下( 参考自: https:// zhuanlan.zhihu.com/p/97 528889 ):
from tensorflow.keras import Input, Model
from tensorflow.keras.layers import Embedding, Dense, Dropout, Bidirectional, LSTM, TimeDistributed
# 定义网络结构
# n_symbols:
# maxlen_sentence:最大子句数量
# maxlen_word :子句最大数量
# max_features :Word数量
# class_num :情感类别数量
# embedding_dims :Word Embedding向量维度
class HAN(object):
def __init__(self, maxlen_sentence, maxlen_word, max_features, embedding_dims,
class_num=3,
last_activation='softmax'):
self.maxlen_sentence = maxlen_sentence
self.maxlen_word = maxlen_word
self.max_features = max_features
self.embedding_dims = embedding_dims
self.class_num = class_num
self.last_activation = last_activation
# 模型定义
def get_model(self):
# 单词(Word)部分
input_word = Input(shape=(self.maxlen_word,))
x_word = Embedding(self.max_features, self.embedding_dims, input_length=self.maxlen_word)(input_word)
x_word = Bidirectional(LSTM(128, return_sequences=True))(x_word)
x_word = Attention(self.maxlen_word)(x_word)
model_word = Model(input_word, x_word)
# 句子部分
input = Input(shape=(self.maxlen_sentence, self.maxlen_word))
x_sentence = TimeDistributed(model_word)(input)