IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 人工智能 -> Pytorch实验5:疫情微博文本情感分类 (简化版SMP2020赛题) -> 正文阅读

[人工智能]Pytorch实验5:疫情微博文本情感分类 (简化版SMP2020赛题)

编者按

作者:北京交通大学计算机学院 秦梓鑫
学号:21120390
代码仅供参考,欢迎交流;请勿用于任何形式的课程作业。如有任何错误,敬请批评指正~
系列文章:

赛题内容

任务背景

2020年初,新型疫情来势汹汹,对人们的生产生活产生了巨大的影响,引发舆论广泛关注。在以微博为代表的社交媒体上,疫情相关的话题引起了网友们的广泛讨论。

基于自然语言处理技术,深入挖掘微博文本中蕴含的情感态度信息,可以明确公众态度、感知情绪变化、辅助政府决策、引导网络正能量,具有研究意义和社会价值。

本实验的任务是:使用深度学习方法,对给定的疫情微博数据集进行情感分析,输出微博蕴含的情绪类别。任务的优化目标是:提高在测试集上的评估得分。特别地,训练数据不能脱离数据集范围,不可以引入外部语料、预训练模型。

评估指标

本任务的评价指标为:宏精准率(macro-Precision)、宏召回率(macro-Recall) 、宏F1值(macro-F1)。

以上指标均适用于多分类问题。其计算方法如下:
(1)首先统计各个类标的TP、FP、FN、TN;
(2)分别计算各自的精准率(Precision)、召回率(Recall)和F1值;
(3)宏精准率由每个类各自的精准率直接取平均值得到;
(4)宏召回率和宏F1值(macro-F1)的计算方法类似。
在类别不均衡的情况下,使用宏系列指标会更关注小类别的分类效果。

数据集

数据集以json文件的形式发布。其中,训练集包含8606条中文微博,测试集包含2000条中文微博,每一条微博有唯一的情绪归属。

情绪共有六个分类,包括:积极(happy)、愤怒(angry)、悲伤(sad)、恐惧(fear)、惊奇(surprise)和无情绪(neural)。

实验设计

本实验基于跨行业数据挖掘标准流程(CRISP-DM)进行实验环节设计。

数据理解

词频统计

给定的训练集、测试集通过jieba库分词后,得到词语共计5049个。

词频基本符合长尾分布,除了“武汉”“加油”“我们”“疫情”等词语频繁出现外,多数词语出现次数较少。
全部微博文本(训练集+测试集)生成的词云;词的字体大小对应该词出现的频率
图:全部微博文本(训练集+测试集)生成的词云;

情绪分布

可以观察到:训练集和测试集的情绪分布基本一致,但也存在样本类不均衡的问题。其中:情绪为积极(happy)的微博文本最多;情绪为悲伤(sad)、恐惧(fear)和惊讶(surprise)的文本较少
在这里插入图片描述

数据预处理

数据清洗

在训练过程中,与作者表达情感无关的、过于小众化的文本会造成干扰。此外,文本中还夹杂着一些无语义的特殊符号。

本实验中,我们搜集到以下类型的噪声和干扰,并进行了针对性处理:

类别示例文本处理
@微博用户25//@皮衣要穿在對的溫度:宝贝也要注意!删去
特殊符号、标点符号我参与了@新浪科技 发起的投票你过年还出去玩么?,我投给了不去,在家待着这个选项,你也快来表态吧~删去
停用词(没有表达情感信息的虚词)哈哈哈哈太可爱!给姥爷防范意识点赞删去
繁体中文#甬抗肺炎# which disappear after beautiful hover.承诺常常很像蝴蝶,美丽的飞旋然后不见。固執等著誰 卻驚覺已無法倒退人生在世翻译为简体中文

数据增广

在前期实验中,我们输出的错误数据(misclassified items)进行了调研,发现集中于小分类样本。因此,我们采用文本回译的方式,对悲伤(sad)、恐惧(fear)和惊讶(surprise)三类的样本进行了数据增广,以增加训练数据的表达力。
在这里插入图片描述
后续实验结果显示,对于小类别数据的小面积增广对指标有一定的提升作用。
在这里插入图片描述
经过数据清洗后的词云分布如图所示。可以感知,筛选后的词集能更明确地反映情绪信息。

模型设计

本次实验中,我们对比了多种模型,最终选择BiLSTM+Attention模型。模型的结构如下图所示。
在这里插入图片描述

文本表示和词嵌入

首先,文本数据经过清洗、增广和分词之后,以one-hot encoding的方式进行编码。

X = ( x 1 , x 2 , … , x n ) , X ∈ R N × D X=\left(x_1,x_2,\ldots,x_n\right),X\in R^{N\times D} X=(x1?,x2?,,xn?),XRN×D

其中:N是微博文本数量,D是数据集形成的语料库中词的数量。
之后,编码后的张量送去嵌入层(embedding)进行嵌入。

E = M e m b e d X E=M_{embed}X E=Membed?X
E = ( e 1 , e 2 , … , e N ) , E ∈ R N × d E=\left(e_1,e_2,\ldots,e_N\right),E\in R^{N\times d} E=(e1?,e2?,,eN?),ERN×d

其中: d d d是文本的嵌入长度,是一个超参数; M e m b e d M_{embed} Membed?是词嵌入矩阵。本模型中, d d d设置为256。

双向LSTM

之后,文本的嵌入表示被依次送入双向LSTM中。
首先介绍LSTM单元;在每一个时刻t,每个LSTM单元完成以下计算:

门控单元的计算

f t = σ ( W f [ h t ? 1 , e t ] ˙ + b f ) f_t=\sigma\left(W_f\dot{\left[h_{t-1},e_t\right]}+b_f\right) ft?=σ(Wf?[ht?1?,et?]˙?+bf?)
i t = σ ( W i [ h t ? 1 , e t ] ˙ + b i ) i_t=\sigma\left(W_i\dot{\left[h_{t-1},e_t\right]}+b_i\right) it?=σ(Wi?[ht?1?,et?]˙?+bi?)
o t = σ ( W o [ h t ? 1 , e t ] ˙ + b o ) o_t=\sigma\left(W_o\dot{\left[h_{t-1},e_t\right]}+b_o\right) ot?=σ(Wo?[ht?1?,et?]˙?+bo?)

候选状态、内部状态和输出的更新

c t ~ = t a n h ( W c e t + U c h t ? 1 + b c ) \widetilde{c_t}=tanh\left(W_ce_t+U_ch_{t-1}+b_c\right) ct? ?=tanh(Wc?et?+Uc?ht?1?+bc?)
c t = f t ⊙ c t ? 1 + i t ⊙ c t ~ c_t=f_t\odot c_{t-1}+i_t\odot\widetilde{c_t} ct?=ft?ct?1?+it?ct? ?
h t = o t ⊙ t a n h ( c t ) h_t=o_t\odot tanh\left(c_t\right) ht?=ot?tanh(ct?)

其中, σ \sigma σ是Sigmoid激活函数, e t e_t et?是第 t t t个嵌入词向量; c t , f t , i t , o t c_t,f_t,i_t,o_t ct?,ft?,it?,ot?都是门控单元;所有的 W W W b b b是模型的参数。在双向LSTM中,我们输入词嵌入 E = ( e 1 , e 2 , … , e N ) E=\left(e_1,e_2,\ldots,e_N\right) E=(e1?,e2?,,eN?),在 2 L 2L 2L个LSTM单元中获得输出 h = ( h 1 , h 2 , … , h 2 L ) h=\left(h_1,h_2,\ldots,h_{2L}\right) h=(h1?,h2?,,h2L?)

最终,将前向的隐藏状态和后向的隐藏状态拼接,形成最终的隐藏层表示。

注意力层 + 单层MLP

在注意力层中,每一个隐藏状态被计算和其它状态的注意力分数。

a i = t a n h ( W h h i + b h ) , a i ∈ [ ? 1 , 1 ] a_i=tanh\left(W_hh_i+b_h\right),a_i\in\left[-1,1\right] ai?=tanh(Wh?hi?+bh?),ai?[?1,1]

w i = e x p ( a i ) ∑ t = 1 N e x p ( a t ) , ∑ i = 1 N w i = 1 w_i=\frac{exp\left(a_i\right)}{\sum_{t=1}^{N}exp\left(a_t\right)},\sum_{i=1}^{N}{w_i=1} wi?=t=1N?exp(at?)exp(ai?)?,i=1N?wi?=1

随后,注意力分数和隐藏状态最初的值被加权融合,得到文本的最终表示,并送入全连接层中进行六分类的预测。

r ? = ? ∑ i = 1 N w i h i , r ∈ R 2 L r\ =\ \sum_{i=1}^{N}{w_ih_i,r\in R^{2L}} r?=?i=1N?wi?hi?,rR2L

代码实现

数据读取和预处理

采用python内置的json包进行读取:

import json
def resolveJson(path):
    file = open(path, "rb")
    file_json = json.load(file)
    record = []
    for i in range(len(file_json)):
        fileJson =  file_json[i]
        id = fileJson["id"]
        content = fileJson["content"]
        label = fileJson["label"]
        record.append([id,content,label])
    return record

使用re包进行正则化筛选,实现数据清洗:

def clean(json_text):
    print("Clean data")
    # 加载停用词列表
    with open('cn_stopwords.txt', encoding='utf-8') as f_stop:
        stopwords = [line.strip() for line in f_stop]
        f_stop.close()

    for json_item in json_text:
        text = json_item[1]
        import re
        text = re.sub(r'\/\/\@.*?(\:|\:)', "",text) #清除被转发用户用户名
        text = re.sub(r'\#.*?\#',"",text) # 清除话题
        text = re.sub(r'\【.*?\】', "", text)  # 清除话题
        text = re.sub(r'(https|http)?:\/\/(\w|\.|\/|\?|\=|\&|\%)*\b', "", text, flags=re.MULTILINE) # 清除连接
        text = re.sub("[\s+\.\!\/_,$%^*(+\"\']+|[+——!,。?、~@#¥%……&*()]", "", text) # 去除中文标点符号
        # 去除停用词
        outstr=''
        for word in text.split():
            if word not in stopwords:
                if word != '/t':
                    outstr += word
        json_item[1] = outstr
    print("Data is cleaned!")
    return json_text

使用有道翻译API进行数据回译,其中:appid 和 app_secret需要自行手动申请百度翻译开放平台有道翻译开放平台)。

实验过程中也考虑过百度翻译API,但百度翻译API存在请求长度限制,不适用于大规模的文本数据。

import jionlp as jio

# 初始化应用
youdao_free_api = jio.YoudaoFreeApi()
youdao_api = jio.YoudaoApi(
        [{'appid': '请填入你的APPID',
          'app_secret': '请填入你的APP密钥'}])

def back_trans(text):
    # 支持语言:中文(zh-CHS)、英文(en)、日文(ja)、法文(fr)、西班牙语(es)、韩文(ko)、葡萄牙文(pt)、俄语(ru)、德语(de)
    # print(youdao_api.__doc__
    trans_youdao = youdao_api(text=text,from_lang='zh-CHS', to_lang='en') #中译英
    back_trans_yy = youdao_api(text=trans_youdao,from_lang='en', to_lang='zh-CHS') #英译中
    return back_trans_yy

词嵌入和标签嵌入

词嵌入的核心步骤如下:

(1)对情感标签进行编码

    def encode_label(labels,title):
        encoder = {'neural':0,'happy':1,'angry':2,'sad':3,'fear':4,'surprise':5}
        labels2 = [encoder[label] for label in labels]
        return labels2

(2)对文本进行分词,整理出词库mywords;根据词库,对文本进行one-hot编码,将文本转化为向量

# 对文本进行编码
# 中文分词
import jieba
mywords = " ".join(jieba.cut(mytext))
# 将文本token为id列表
def get_tokenized_text(text,labels,word_to_index):
    if len(text) == len(labels):
        vol = len(text)
        for i in range(vol):
            temp = []
            # 对每一行文本进行分词
            import jieba
            textline = text[i]
            word_list = " ".join(jieba.cut(textline))
            for word in word_list:
                if (word in word_to_index.keys()):
                    temp.append(int(word_to_index[word]))
                else:
                    temp.append(0)
            yield [temp, labels[i]]

(3) 对长短不一的向量,进行补充(padding),形成长度相同的向量。

补充函数的定义如下:

def padding(x,max_length):
    if len(x)>max_length:
        text = x[:max_length]
    else:
        text = x + [1] * (max_length - len(x))
    return text

(4) 建立文本-情感编码的对应,便于加载到Dataloader

# 文本处理为相同长度的序列
# 核心模块:文本转向量,向量转固定长度的张量
def process_text(text,labels,word_to_index):
    data = get_tokenized_text(text,labels,word_to_index)
    max_length = 50
    labeltensor = torch.IntTensor(labels)
    samples = []
    for content in data:
        text_to_sequence = padding(content[0],max_length)
        samples.append(text_to_sequence)
    sampletensor = torch.LongTensor(samples) # Long type will cause error in training,but is essential in embedding
    return sampletensor,labeltensor

模型构建

此处的实现参考了博客《Attention 扫盲:注意力机制及其 PyTorch 应用实现

class BiLSTM_Attention(nn.Module):
    def __init__(self, total_word_count, hidden_size, num_layers):
        super(BiLSTM_Attention, self).__init__()
        self.word_embeddings = nn.Embedding(total_word_count, 1024)
        self.encoder = nn.LSTM(input_size=1024,hidden_size=hidden_size, num_layers=num_layers,batch_first=True,bidirectional=True)
        # 初始时间步和最终时间步的隐藏状态作为全连接层输入
        self.w_omega = nn.Parameter(torch.Tensor(
            hidden_size * 2, hidden_size * 2))
        self.u_omega = nn.Parameter(torch.Tensor(hidden_size * 2, 1)) #对hiddensize进行降维,以便得到每个h的注意力权重
        self.decoder = nn.Linear(2 * hidden_size, 6)

        nn.init.uniform_(self.w_omega, -0.1, 0.1)
        nn.init.uniform_(self.u_omega, -0.1, 0.1)

    def forward(self, inputs):
        # inputs的形状是(batch_size,seq_len)
        embeddings = self.word_embeddings(inputs.to(device)).to(device)
        # 提取词特征,输出形状为(batch_size,seq_len,embedding_dim)
        # rnn.LSTM只返回最后一层的隐藏层在各时间步的隐藏状态。
        outputs, _ = self.encoder(embeddings) # output, (h, c)
        # outputs形状是(batch_size,seq_len, 2 * num_hiddens)
        #x = outputs.permute(1, 0, 2)
        # x形状是(batch_size, seq_len(timestep), 2 * num_hiddens)
        x = outputs.to(device)
        #print(x.size())
        # Attention过程
        u = torch.tanh(torch.matmul(x, self.w_omega)).to(device)
        # u形状是(batch_size, seq_len, 2 * num_hiddens)
        att = torch.matmul(u, self.u_omega).to(device)
        # att形状是(batch_size, seq_len, 1)
        import torch.nn.functional as F
        att_score = F.softmax(att, dim=1).to(device)
        # att_score形状仍为(batch_size, seq_len, 1)
        scored_x = x * att_score.to(device)
        # scored_x形状是(batch_size, seq_len, 2 * num_hiddens)
        # Attention过程结束

        feat = torch.sum(scored_x, dim=1).to(device)  # 加权求和
        # feat形状是(batch_size, 2 * num_hiddens)
        outs = self.decoder(feat)
        # out形状是(batch_size, 6)
        return outs.to(device)

基准模型(baselines)

实验中使用了RNN,GRU,LSTM,Transformer四类模型作为对比。受篇幅所限,具体代码不再罗列。

训练过程

训练过程如下:

def train(net,loss,optimizer,train_iter,test_iter,index2sentence):
    net = net.to(device)
    num_epochs = 31
    score_log = []
    for epoch in range(num_epochs):
        for x, y in train_iter:
            #print(x.shape,y.shape)
            yhat = net(x)
            yhat = yhat.view(len(yhat), -1)
            l = loss(yhat, y.long().squeeze().to(device))
            optimizer.zero_grad()
            l.backward()
            optimizer.step()
        #loss_train = calculate(net, train_iter, loss)
        loss_test = calculate(net, test_iter, loss)
        acc_train,prec_tr, recall_tr, f1_tr,report_tr,yhat_list,label_list  = evaluate(net, train_iter)
        acc_test,prec_te,recall_te,f1_te,report_te,yhat_list_te,label_list_te  = evaluate(net, test_iter,output=True)
        print("epoch",epoch,"*test*  ","f1:",f1_te,"loss:", loss_test,"precision:",prec_te,"recall:",recall_te,"acc(hand):",acc_test)
        print("train f1:",f1_tr)
        print(report_te)
        score_log.append([f1_tr, f1_te])

特别地,代码支持结果画图错误样本甄别

if epoch!=0 and epoch% 10 == 0:
                import matplotlib
                matplotlib.use('Agg')
                from matplotlib import pyplot as plt
                plt.figure(figsize=(10, 5), dpi=300)
                import numpy as np
                score_log2 = np.array(score_log)
                print(score_log2)
                plt.plot(score_log2[:,0], linewidth=2,c='green',label="train set")
                plt.plot(score_log2[:,1], linewidth=2,c='red',label="test set")
                plt.legend()
                plt.title('F1-socre')
                plt.xlabel('epoch')
                import os
                print(os.getcwd())
                # save result
                plt.savefig(os.getcwd() + "/result/this.png")
                #plt.show()
        if epoch == 1000:
            print("epoch",epoch,report_te)
            wrong_items = []
            encoder = {'neural': 0, 'happy': 1, 'angry': 2, 'sad': 3, 'fear': 4, 'surprise': 5}
            decoder = dict(map(reversed, encoder.items()))
            for i in range(len(yhat_list_te)):
                if yhat_list_te[i] != label_list_te[i]:
                    wrong_items.append(
                        [index2sentence[i], decoder[yhat_list_te[i].item()], decoder[label_list_te[i].item()]])
            for item in wrong_items:
                print(item[0], "prediction:", item[1], "label:", item[2])

评估

在评估阶段,我们采用sklearn.metrics中提供的混淆矩阵宏F1值等计算函数,对比模型输出和标签,评估模型性能。

from sklearn.metrics import precision_recall_fscore_support,classification_report
p_class, r_class, f_class, support_micro = precision_recall_fscore_support(labels_list,yhat_list,average='macro')
report = classification_report(labels_list, yhat_list, labels=[0, 1, 2, 3, 4, 5])
return p_class,r_class,f_class,report,yhat_list,labels_list

实验结果

模型评估

模型宏-F1值宏-精确率宏-召回率
RNN0.33580.34150.3389
GRU0.39490.40860.3921
LSTM0.41660.45580.4160
Transformer0.42150.44700.4379
Bi-LSTM-attention0.49040.50890.4827

其中,Bi-LSTM组的最优结果截图如下:
在这里插入图片描述
训练集和测试集的宏F1值变化如下:
在这里插入图片描述
训练过程中的超参数如下:

参数取值
初始学习率1e-4
L_2正则化(weight_decay)0.8*1e-4
学习率衰减步长10
学习率衰减率0.5
batch size32

结论和反思

实验结果表明:在给定的文本分类任务下,我们构建的Bi-LSTM-attention模型在测试集上达到了0.4904的宏F1值,优于RNN、GRU、LSTM和Transfomer模型。

实验过程中,我们发现了以下问题和值得思考的点:

(1)过拟合现象严重:训练集和测试集在情感上的分布大体相似,但是在词汇上具有较大的差异。对此,在不使用任何预训练模型的前提下,我们尽可能地引入了多种策略以增加模型的泛化性能,包括:添加Dropout层、L_2正则化、提前终止训练、学习率衰减等。

(2)样本不均衡问题:在调试过程中,我们每次将分类错误(mis-classified)的样本进行输出,发现错误集中于小类别的样本数据。因此,我们尝试了数据增广策略;此外,实验表明,适当减少batchsize有助于对此类数据的学习。

(3)小数据集的预处理问题:相比于工业界用于预训练任务的数据集,给定的数据集规模较小。因此,预处理过程一方面会让数据变得更为精细;另一方面,过多的处理会造成信息流失,使得模型可学习的信息量减少,最终“无物可学”。因此,需考虑预处理的适度问题。

实验心得体会

在本科时,数据挖掘课的文本分类作业是通过WEKA软件“调包”实现的,当时便对文本挖掘产生了巨大的兴趣,但没有机会动手“造轮子”,也不明白梯度下降等算法的原理,不理解为什么代码可以work。

在暑期学期中,通过智能计算数学基础、深度学习两门课程,我初步建立了较为完善的知识体系。在完成了四次实验以及最终的大作业之后,我终于对曾经感到非常困惑的深度学习领域产生了较为清晰的认知。

在大作业的选题时,我决定再次选择文本分类的题目。在完成作业的过程中,我直观地感受到了自身的成长:从对公式的不解到手动推导公式、从调包完成作业到动手实现、从对待不同类型数据的恐慌到能够熟练运用词嵌入、序列采样等方法进行处理……之前道听途说的各种技巧,成为亲手写下的代码时,会觉得心里少了许多浮躁,多了一分踏实。

同时,数据科学精彩、丰富的一面逐渐映入眼帘。疫情数据的生活性、数据处理的艺术性、优化方法的严密性、偏差和误差的偶然性,交融着理性的光芒和感性的色彩,使我对这门学科产生了更深层的理解和敬意。

非常幸运,能在这个暑假修读万老师主讲的《深度学习》课程。希望在以后的学习生活中,也能够一直做到理论结合实践,做到知行合一,止于至善。

参考文献

[1] 清博大数据-SMP2020微博情绪分类评测汇报
[2] 炬火-SMP2020-微博情绪分类评测汇报
[3] 文本数据增强——回译:https://github.com/dongrixinyu/JioNLP
[4] Transformer原理以及文本分类实战:https://blog.csdn.net/qq_36618444/article/details/106472126
[5] Attention 扫盲:注意力机制及其 PyTorch 应用实现:
https://blog.csdn.net/fengdu78/article/details/103849711
[6] pytorch中RNN参数的详细解释: https://blog.csdn.net/lwgkzl/article/details/88717678
[7] NLP:基于jieba和gensim的疫情微博情绪分类:
https://blog.csdn.net/sunny_1219/article/details/110239752
[8] Chris Clifto. Cross-Industry Standard Process for Data Mining

  人工智能 最新文章
2022吴恩达机器学习课程——第二课(神经网
第十五章 规则学习
FixMatch: Simplifying Semi-Supervised Le
数据挖掘Java——Kmeans算法的实现
大脑皮层的分割方法
【翻译】GPT-3是如何工作的
论文笔记:TEACHTEXT: CrossModal Generaliz
python从零学(六)
详解Python 3.x 导入(import)
【答读者问27】backtrader不支持最新版本的
上一篇文章      下一篇文章      查看所有文章
加:2021-09-24 10:34:02  更:2021-09-24 10:34:07 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 12:43:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码