使用神经网络的文本对话中的情境情感检测


如今,与对话代理人交谈已成为日常工作,对于对话系统产生尽可能像人的响应至关重要。 作为主要方面之一,应主要注意向用户提供情感感知的响应。 在本文中,我们将描述用于文本对话中情感检测的递归神经网络体系结构,该体系结构参加了SemEval-2019任务3“ EmoContext” ,即语义评估年度研讨会。 任务目标是在3轮对话数据集中对情绪(即快乐,悲伤,愤怒和其他)进行分类。

本文的其余部分安排如下。 第1部分简要概述了EmoContext任务和提供的数据。 因此,第2节和第3节重点介绍了文本预处理和词嵌入。 在第4节中,我们描述了提交中使用的LSTM模型的体系结构。 总之,介绍了我们系统的最终性能和源代码。 该模型是使用Keras库在Python中实现的。

1.培训数据


SemEval-2019任务3“ EmoContext”专注于文本对话中的上下文情感检测。 在EmoContext中,给定文本用户话语以及会话中的2个上下文转折,我们必须对下一个用户话语的情绪进行分类是“快乐”,“悲伤”,“生气”还是“其他”(表1)。 仅有两个对话参与者:一个匿名者(Tuen-1和Turn-3)和基于AI的聊天机器人Ruuh (Turn-2)。 有关详细说明,请参阅( Chatterjee et al。,2019 )。

表1.显示EmoContext数据集的示例( Chatterjee et al。,2019
用户(Turn-1)会话代理(Turn-1)用户(Turn-2)真正的课
我刚刚有资格参加纳巴德实习哇! 那真是个好消息。 恭喜你!我开始哭了快乐的
你怎么敢打我的孩子如果你把我的车弄坏了,我也会帮你的只需尝试一次生气
我更被你伤害了你不是这个意思说你爱我伤心
我会做的夜晚。好吧 让我保持循环。没有给出WhatsApp编号。其他

在比赛中,我们获得了任务组织者提供的30160个带有人标签的文本,其中大约有5000个样本来自“愤怒”,“悲伤”,“快乐”类和15000个“其他”类(表2)。 由组织者提供的开发和测试集(与培训集相反)具有真实的分布,每个情感班级的真实分布大约为4%,“其他”班级的则为其余。 数据由Microsoft提供,可以在官方LinkedIn组中找到

表2.数据集中的情感班级标签分布( Chatterjee等,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万

除了这些数据之外,我们还收集了90万条英语推文,以便为每种情感创建300,000条推文的遥远数据集。 为了形成遥远的数据集,我们基于Go等人的策略。 (2009年),在此方法下,我们仅将推文与与情感相关的单词(例如“ #angry”,“#annoyed”,“#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和Glove模型都从它们的共现信息中学习单词的几何编码,但是本质上前者是一种预测模型,而后者是一种基于计数的模型。 换句话说,尽管Word2Vec尝试预测目标单词(CBOW体系结构)或上下文(Skip-gram体系结构),即为了最大程度地减少损失函数,但GloVe会计算单词向量,从而在共现计数矩阵上进行降维。 FastText与Word2Vec非常相似,除了它使用字符n-gram来学习单词向量,因此它能够解决词汇不足的问题。

对于上述所有技术,我们使用了作者提供的默认训练婴儿车。 我们基于每个嵌入训练一个简单的LSTM模型(dim = 64),并使用交叉验证比较有效性。 根据结果​​,DataStories预训练的嵌入显示出最佳的平均F1分数。

为了使所选词嵌入具有词的情感极性,我们考虑通过对自动标记的远距离数据集上的嵌入进行微调来执行远距离的预训练短语。 使用预训练的重要性已在( Deriu et al。,201 7)中得到了证明。 我们使用遥远的数据集来训练简单的LSTM网络,以对愤怒,悲伤和快乐的推文进行分类。 为了避免嵌入权重发生显着变化,在第一个训练时期将嵌入层冻结,然后在接下来的5个时期中将其冻结。 在训练阶段之后,经过微调的嵌入被保存用于进一步的训练阶段, 并公开发布

 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)是一系列人工神经网络,专门处理顺序数据。 与传统的神经网络相反,RRN被设计为通过共享处理序列的内部权重来处理序列数据。 为此,RRN的计算图包括周期,代表先前信息对当前信息的影响。 作为RNN的扩展,1997年引入了长短期记忆网络(LSTM)( Hochreiter和Schmidhuber,1997年 )。 在LSTM中,循环单元以特定方式连接,以避免梯度问题消失和爆炸。 传统的LSTM仅保留过去的信息,因为它们仅在一个方向上处理序列。 双向LSTM结合了两个在相反方向上移动的隐藏LSTM层的输出,其中一个在时间上向前移动,另一个在时间上向后移动,从而能够同时捕获过去和未来状态的信息( Schuster和Paliwal,1997年 )。


图1:所建议架构的较小版本的架构。 第一轮和第三轮的LSTM单位具有相同的权重。

图1提供了我们的方法的高级概述。神经网络的拟议架构包括嵌入单元和两个双向LSTM单元(dim = 64)。 前一个LSTM单元旨在分析第一个用户的讲话(即对话的第一回合和第三回合),而后者旨在分析第二个用户的讲话(即第二回合)。 这两个单元不仅学习语义和情感特征表示,而且还学习如何捕获特定于用户的对话特征,从而可以更准确地对情感进行分类。 第一步,使用预先训练的词嵌入,将每个用户的话语反馈到相应的双向LSTM单元中。 接下来,将这三个特征图串联在平坦的特征向量中,然后传递到完全连接的隐藏层(dim = 30),该层分析获得的向量之间的相互作用。 最终,这些功能通过softmax激活函数在输出层中进行操作,以预测最终的类别标签。 为了减少过度拟合,在嵌入层之后添加了具有高斯噪声的正则化层,在每个LSTM单元(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.结果


在寻找最佳架构的过程中,我们不仅尝试了层中的单元数,激活函数和正则化参数,还尝试了神经网络的架构。 有关此短语的详细信息,请参见原始论文

上一节中描述的模型在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-CN439850/


All Articles