使用神经网络自动检测文本对话中的情绪


对话系统的主要任务之一不仅是提供用户所需的信息,而且还要生成尽可能多的人为答案。 识别对话者的情绪不再只是一个很酷的功能,而是必不可少的。 在本文中,我们将研究用于确定文本对话中的情绪的递归神经网络体系结构,体系结构参加了SemEval-2019任务3“ EmoContext” ,这是计算机语言学的年度竞赛。 任务是通过聊天机器人和一个人参加的三个评论来对情绪(“快乐”,“悲伤”,“愤怒”和“其他”)进行分类。

在本文的第一部分中,我们将考虑EmoContext中设置的任务以及组织者提供的数据。 在第二部分和第三部分中,我们分析了文本的初步处理以及单词的矢量表示方式。 在第四部分中,我们描述了我们在比赛中使用的LSTM体系结构。 该代码使用Keras库以Python编写。

1.培训数据


SemEval-2019的曲目“ EmoContext”致力于在文本对话中定义情感,同时考虑了对应的上下文。 在这种情况下,上下文是对话参与者的几个连续的发言。 对话中有两个参与者:一个匿名用户(他拥有第一个和第三个副本)和一个聊天机器人Ruuh (他拥有第二个副本)。 基于三个副本,有必要确定用户在向聊天机器人编写答案时经历了什么情绪(表1)。 总体而言,数据集的标记包含四种情绪:“快乐”,“悲伤”,“愤怒”或“其他”(表1)。 此处提供了详细描述:( Chatterjee等,2019 )。

表1.来自EmoContext数据集的示例( Chatterjee等,2019
用户(阶段1)互动机器人(第一阶段)用户(阶段2)真正的课
我刚刚有资格参加纳巴德实习哇! 那真是个好消息。 恭喜你!我开始哭了幸福感
你怎么敢打我的孩子如果你把我的车弄坏了,我也会帮你的只需尝试一次怒气
我更被你伤害了你不是这个意思说你爱我悲伤
我会做的夜晚。好吧 让我保持循环。没有给出WhatsApp编号。其他

比赛期间,组织者提供了一些数据集。 训练数据集(训练)由30,160个手动标记的文本组成。 在这些文本中,大约有5000个属于“快乐”,“悲伤”和“愤怒”类别的对象,以及来自“其他”类别的15000个文本(表2)。

组织者还提供了用于开发(Dev)和测试(Test)的数据集,其中与训练数据集不同,情绪类别的分布与现实生活相对应:“快乐”,“悲伤”和“生气”,剩下的就是班上的“他人”。 由Microsoft提供的数据,您可以在LinkedIn上官方组中下载。

表2.情感类别标签在数据集中的分布( Chatterjee et al。,2019 )。
数据中心幸福感悲伤怒气其他合计
培训课程
14.07%
18.11%
18.26%
49.56%
30160
发展
5.15%
4.54%
5.45%
84.86%
2755
测验
5.16%
4.54%
5.41%
84.90%
5509
遥控
33.33%
33.33%
33.33%
0%
90万

除了这些数据,我们还从Twitter收集了90万条英语消息,以创建一个远程数据集(每种情感30万条推文)。 创建它时,我们遵循Go等人的策略。 (2009年),在该框架中,消息仅与与情绪相关的单词的存在相关联,例如#生气,#烦恼,#happy,#sad,#surprised等。 术语列表基于SemEval-2018 AIT DISC( Duppada等人,2018 )中的术语。

EmoContext竞赛中的主要质量指标是三种情绪类别(即“快乐”,“悲伤”和“愤怒”类别)的平均F1度量。

def preprocessData(dataFilePath, mode): conversations = [] labels = [] with io.open(dataFilePath, encoding="utf8") as finput: finput.readline() for line in finput: line = line.strip().split('\t') for i in range(1, 4): line[i] = tokenize(line[i]) if mode == "train": labels.append(emotion2label[line[4]]) conv = line[1:4] conversations.append(conv) if mode == "train": return np.array(conversations), np.array(labels) else: return np.array(conversations) texts_train, labels_train = preprocessData('./starterkitdata/train.txt', mode="train") texts_dev, labels_dev = preprocessData('./starterkitdata/dev.txt', mode="train") texts_test, labels_test = preprocessData('./starterkitdata/test.txt', mode="train") 

2.文本预处理


在训练之前,我们使用Ekphrasis工具对文本进行了预处理(Baziotis等,2017)。 它有助于纠正拼写,规范单词,句段,并确定应使用特殊标签删除,规范或注释哪些标记。 在预处理阶段,我们执行了以下操作:

  • URL和邮件,日期和时间,昵称,百分比,货币和数字已替换为相应的标签。
  • 重复,删节,拉长的大写术语,并附有适当的标签。
  • 伸长的单词已自动更正。

此外,Emphasis包含一个令牌识别器,可以识别大多数表情符号,表情符号和复杂表达式,以及日期,时间,货币和首字母缩写词。

表3.文本预处理的示例。
源文字预处理文字
我觉得你...我要分解成百万片 <allcaps>我感觉到你</ allcaps>。 <重复>我要分解成百万个
累了,我也想念你:-(累了,我也想念你<sad>
您应该使用以下网址www.youtube.com/watch? v=99myH1orbs4您应该听<elongated>这样:<url>
我的公寓照顾它。 我的房租大约是650美元。我的公寓照顾它。 我的租金在<money>左右。

 from ekphrasis.classes.preprocessor import TextPreProcessor from ekphrasis.classes.tokenizer import SocialTokenizer from ekphrasis.dicts.emoticons import emoticons import numpy as np import re import io label2emotion = {0: "others", 1: "happy", 2: "sad", 3: "angry"} emotion2label = {"others": 0, "happy": 1, "sad": 2, "angry": 3} emoticons_additional = { '(^・^)': '<happy>', ':‑c': '<sad>', '=‑d': '<happy>', ":'‑)": '<happy>', ':‑d': '<laugh>', ':‑(': '<sad>', ';‑)': '<happy>', ':‑)': '<happy>', ':\\/': '<sad>', 'd=<': '<annoyed>', ':‑/': '<annoyed>', ';‑]': '<happy>', '(^ ^)': '<happy>', 'angru': 'angry', "d‑':": '<annoyed>', ":'‑(": '<sad>', ":‑[": '<annoyed>', '( ? )': '<happy>', 'x‑d': '<laugh>', } text_processor = TextPreProcessor( # terms that will be normalized normalize=['url', 'email', 'percent', 'money', 'phone', 'user', 'time', 'url', 'date', 'number'], # terms that will be annotated annotate={"hashtag", "allcaps", "elongated", "repeated", 'emphasis', 'censored'}, fix_html=True, # fix HTML tokens # corpus from which the word statistics are going to be used # for word segmentation segmenter="twitter", # corpus from which the word statistics are going to be used # for spell correction corrector="twitter", unpack_hashtags=True, # perform word segmentation on hashtags unpack_contractions=True, # Unpack contractions (can't -> can not) spell_correct_elong=True, # spell correction for elongated words # select a tokenizer. You can use SocialTokenizer, or pass your own # the tokenizer, should take as input a string and return a list of tokens tokenizer=SocialTokenizer(lowercase=True).tokenize, # list of dictionaries, for replacing tokens extracted from the text, # with other expressions. You can pass more than one dictionaries. dicts=[emoticons, emoticons_additional] ) def tokenize(text): text = " ".join(text_processor.pre_process_doc(text)) return text 

3.单词的向量表示


向量表示已成为使用深度学习创建NLP系统的大多数方法的组成部分。 为了确定最合适的矢量映射模型,我们尝试使用Word2Vec( Mikolov等,2013 ),GloVe( Pennington等,2014 )和FastText( Joulin等,2017 ),以及预先训练的DataStories向量( Baziotis等。 。,2017 )。 Word2Vec通过假设在相似的上下文中发现了语义相关的单词来查找单词之间的关系。 Word2Vec尝试预测目标单词(CBOW体系结构)或上下文(Skip-Gram体系结构),即最小化损失函数,而GloVe计算单词向量,从而减小邻接矩阵的维数。 FastText的逻辑与Word2Vec的逻辑相似,不同之处在于它使用符号n-gram来构建单词向量,从而可以解决未知单词的问题。

对于所有提到的模型,我们使用作者提供的默认训练参数。 我们基于这些向量表示中的每一个训练了一个简单的LSTM模型(dim = 64),并使用交叉验证比较了分类效率。 预先训练的DataStories向量显示了F1措施中的最佳结果。

为了丰富选择的向量映射以使词具有情感色彩,我们决定使用自动标记的Distant 数据集对向量进行微调( Deriu et al。,2017 )。 我们使用Distant数据集训练了一个简单的LSTM网络,以对“邪恶”,“悲伤”和“快乐”消息进行分类。 嵌入层在训练的第一个迭代过程中被冻结,以避免矢量权重的强烈变化,并且在接下来的五个迭代中,解冻该层。 训练后,“延迟的”向量将被保存以供以后在神经网络中使用,并被共享

 def getEmbeddings(file): embeddingsIndex = {} dim = 0 with io.open(file, encoding="utf8") as f: for line in f: values = line.split() word = values[0] embeddingVector = np.asarray(values[1:], dtype='float32') embeddingsIndex[word] = embeddingVector dim = len(embeddingVector) return embeddingsIndex, dim def getEmbeddingMatrix(wordIndex, embeddings, dim): embeddingMatrix = np.zeros((len(wordIndex) + 1, dim)) for word, i in wordIndex.items(): embeddingMatrix[i] = embeddings.get(word) return embeddingMatrix from keras.preprocessing.text import Tokenizer embeddings, dim = getEmbeddings('emosense.300d.txt') tokenizer = Tokenizer(filters='') tokenizer.fit_on_texts([' '.join(list(embeddings.keys()))]) wordIndex = tokenizer.word_index print("Found %s unique tokens." % len(wordIndex)) embeddings_matrix = getEmbeddingMatrix(wordIndex, embeddings, dim) 

4.神经网络架构


递归神经网络(RNN)是专门处理一系列事件的一系列神经网络。 与传统的神经网络不同,RNN被设计为使用内部天平处理序列。 为此,计算图RNN包含反映事件序列中先前信息对当前事件的影响的周期。 LSTM神经网络(长期短期记忆)是RNN在1997年的扩展( Hochreiter和Schmidhuber,1997年 )。 连接LSTM递归池可避免爆发和褪色问题。 传统LSTM仅在沿一个方向处理序列时保留过去的信息。 双向运行的双向LSTM结合了两个隐藏的LSTM层的输出,它们在相反的方向上传输信息-一个在时间上,另一个在相反的方向上-从而同时从过去和将来的状态接收数据( Schuster和Paliwal,1997年 )。


图1:简化的体系结构版本。 LSTM模块在第一阶段和第三阶段使用相同的权重。

图1给出了所描述方法的简化表示。神经网络的体系结构由一个嵌入层和两个双向LTSM模块(dim = 64)组成。 第一LTSM模块分析第一用户的单词(即会话的第一和第三副本),第二模块分析第二用户的单词(第二副本)。 在第一阶段,使用预先训练的矢量表示的每个用户的单词被馈送到相应的双向LTSM模块中。 然后将得到的三个特征图组合成一个平面特征向量,然后转移到完全连接的隐藏层(dim = 30),该层分析提取的特征之间的相互作用。 最后,使用softmax激活函数在输出层中处理这些特性,以确定最终的类别标签。 为了减少过度拟合,在向量表示的各层之后,添加了具有高斯噪声的正则化层,并为每个LTSM模块(p = 0.2)和一个隐藏的全连接层(p = 0.1)添加了缺失层( Srivastava et al。,2014

 from keras.layers import Input, Dense, Embedding, Concatenate, Activation, \ Dropout, LSTM, Bidirectional, GlobalMaxPooling1D, GaussianNoise from keras.models import Model def buildModel(embeddings_matrix, sequence_length, lstm_dim, hidden_layer_dim, num_classes, noise=0.1, dropout_lstm=0.2, dropout=0.2): turn1_input = Input(shape=(sequence_length,), dtype='int32') turn2_input = Input(shape=(sequence_length,), dtype='int32') turn3_input = Input(shape=(sequence_length,), dtype='int32') embedding_dim = embeddings_matrix.shape[1] embeddingLayer = Embedding(embeddings_matrix.shape[0], embedding_dim, weights=[embeddings_matrix], input_length=sequence_length, trainable=False) turn1_branch = embeddingLayer(turn1_input) turn2_branch = embeddingLayer(turn2_input) turn3_branch = embeddingLayer(turn3_input) turn1_branch = GaussianNoise(noise, input_shape=(None, sequence_length, embedding_dim))(turn1_branch) turn2_branch = GaussianNoise(noise, input_shape=(None, sequence_length, embedding_dim))(turn2_branch) turn3_branch = GaussianNoise(noise, input_shape=(None, sequence_length, embedding_dim))(turn3_branch) lstm1 = Bidirectional(LSTM(lstm_dim, dropout=dropout_lstm)) lstm2 = Bidirectional(LSTM(lstm_dim, dropout=dropout_lstm)) turn1_branch = lstm1(turn1_branch) turn2_branch = lstm2(turn2_branch) turn3_branch = lstm1(turn3_branch) x = Concatenate(axis=-1)([turn1_branch, turn2_branch, turn3_branch]) x = Dropout(dropout)(x) x = Dense(hidden_layer_dim, activation='relu')(x) output = Dense(num_classes, activation='softmax')(x) model = Model(inputs=[turn1_input, turn2_input, turn3_input], outputs=output) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc']) return model model = buildModel(embeddings_matrix, MAX_SEQUENCE_LENGTH, lstm_dim=64, hidden_layer_dim=30, num_classes=4) 

5.结果


在寻找最佳架构时,我们不仅尝试了层中神经元的数量,激活函数和规则化参数,还尝试了神经网络本身的架构。 这在原始工作中有更详细的描述。

上一节中描述的体系结构在Train数据集上进行训练并在Dev数据集上进行验证时显示出最佳结果,因此已在比赛的最后阶段使用。 在最后一个测试数据集中,该模型显示的平均F1测度为72.59%,所有参与者中最大的成功率为79.59%。 但是,我们的结果远远高于组织者设定的基准值58.68%。

单词的模型和矢量表示的源代码可在GitHub上获得。
文章的完整版本以及与任务描述一起使用的信息均位于ACL Anthology网站上。
可以从官方LinkedIn组下载培训数据集

报价单:

 @inproceedings{smetanin-2019-emosense, title = "{E}mo{S}ense at {S}em{E}val-2019 Task 3: Bidirectional {LSTM} Network for Contextual Emotion Detection in Textual Conversations", author = "Smetanin, Sergey", booktitle = "Proceedings of the 13th International Workshop on Semantic Evaluation", year = "2019", address = "Minneapolis, Minnesota, USA", publisher = "Association for Computational Linguistics", url = "https://www.aclweb.org/anthology/S19-2034", pages = "210--214", } 

Source: https://habr.com/ru/post/zh-CN463045/


All Articles