文章目录

  • 前言
  • 第一课 论文导读
  • 文本分类
  • 文本挖掘
  • 数据类型
  • 文本分类
  • 相关技术
  • 基于深度学习的文本分类 baseline涉及的三篇TC的论文
  • 分层注意网络
  • 前期知识储备
  • 第二课论文精读
  • 论文背景
  • 论文整体框架
  • 论文标题
  • 层次注意力网络
  • 基于GRU的词序列编码器:
  • 单词序列编码器:双向GRU
  • 单词注意力机制
  • 句子编码器
  • 句子注意力机制
  • 文档分类
  • 应用:
  • 实验和结果
  • 实验设置
  • 实验结果
  • 可视化分析
  • 总结
  • 推荐文献
  • 复现
  • 01层次文档分类数据处理
  • 02使用Pytorch实现HAN Attention模型
  • 03HAN Attention模型的训练和测试
  • 04总结


前言

本课程来自深度之眼deepshare.net,部分截图来自课程视频。

文章标题:Hierarchical Attention Network for Document Classification

原标题翻译:用于文本分类的层次注意力网络

作者:zichao Yang,Diyi Yang,Chris Dyer,Xiaodong He, Alex Smola,Eduard Hovy

单位:卡耐基梅隆大学,微软研究院

Carnegie Mellon University, Microsoft Research, Redmond

论文来源:NAACL 2016

在线LaTeX公式编辑器

NLP意图理解的完整过程 nlp理解层次图_文本分类


NLP意图理解的完整过程 nlp理解层次图_NLP意图理解的完整过程_02

第一课 论文导读

文本分类

文本挖掘

文本挖掘:是一个以半结构或者无结构的自然语言文本为对象的数据挖掘,是从大规模文本数据集中发现隐藏的、重要的、新颖的、潜在的、有用的规律的过程。
·文本分类是文本挖掘的一个常见任务。

数据类型

非结构化数据:没有固定结构的数据,直接整体进行存储,一般存储为二进制的数据格式。
半结构化数据:结构化数据的一种形式,它并不符合关系型数据库等形式关联起来的数据模型结构,但包含相关标记,用来分割语义元素以及对记录和字段进行分层。
结构化数据:能够用数据或统一的结构加以表示,如数字、符号。通常的形式是二维表。

文本分类

文本分类是自然语言处理的基本任务。目标是将非结构化文档(例如,评论,电子邮件,帖子,网站内容等)分配给一个或多个类。
文本分类流程:
文本数据获取
文本预处理
文本的向量表示
构造分类器
模型评估
常见的文本分类应用:
主题标记
·产品营销
·产品评论
·内容标记
情感分析:情感分析预测了基于文本分类的特定特征的情绪。

相关技术

信息检索(lR)所有文本分类问题的基础。IR基本算法:

词袋模型:按照词出现的频率表示文本

TF-IDF算法:按比率设置词频和逆向文件频率,通过出现的词的相关性来表示文本

N-gram模型:计算一组共同出现的词

其他早期无监督方法:

NLP意图理解的完整过程 nlp理解层次图_NLP意图理解的完整过程_03


(半)监督方法

NLP意图理解的完整过程 nlp理解层次图_编码器_04


NLP意图理解的完整过程 nlp理解层次图_结构化_05

基于深度学习的文本分类 baseline涉及的三篇TC的论文

深度之眼Paper带读笔记NLP.8:TextCNN.Baseline.04

NLP意图理解的完整过程 nlp理解层次图_NLP意图理解的完整过程_06


深度之眼Paper带读笔记NLP.9:基于CNN的词级别的文本分类.Baseline.05.

NLP意图理解的完整过程 nlp理解层次图_NLP意图理解的完整过程_07


深度之眼Paper带读笔记NLP.11:FASTTEXT.Baseline.06

NLP意图理解的完整过程 nlp理解层次图_文本分类_08


优点:

• 效果特别好,基本都是sota的结果。

• 不需要做特征工程。

忽视的问题:

• 文档中不同句子对于分类的重要性不同。(未考虑句子的重要性红色部分)

• 句子中不同单词对于分类的重要性也有所不同。(未考虑词的重要性蓝色部分)

NLP意图理解的完整过程 nlp理解层次图_文本分类_09


因此,需要分层注意网络来解决这两个问题。下面稍微介绍一下,后面详细讲。

分层注意网络

信息重要度:并非句子中的每个单词和文档中的每个句子对于理解文档的主要信息同样重要。

上下文语境:单词的含义需要根据不同的上下文做出不同的判断。

例如:pretty

·The bouquet of flowers is pretty.

·The food is pretty bad.

NLP意图理解的完整过程 nlp理解层次图_编码器_10


结论:并非文档中的所有部分都能从中获得同等重要的本质信息

前期知识储备

文本分类:了解文本分类任务的基本概念和应用种类,以及任务流程。
概率论:了解基本的概率论知识,掌握条件概率的概念和公式。
注意力机制:了解注意力机制的概念和种类,掌握注意力机制的计算方法。
循环神经网络:了解循环神经网络的结构,掌握它的基本工作原理。

第二课论文精读

本小节首先介绍了论文的整体框架,这部分我们根据论文结构和摘要理清作者的写研究和写作思路;然后讲解了层次注意力网络模型HAN,这部分我们讲解层次注意力网络的构成和计算方式。之后讲解了论文实验中的一些设置和并且分析了实验结果。最后我们进行了论文的讨论和总结,并展望该领域未来的发展方向。

论文背景

  1. 文本分类是自然语言处理的基础任务之一,近期的研究者逐渐开始使用基于深度学习的文本分类模型。
  2. 虽然基于深度学习的文本分类模型取得了非常好的效果,但是它们没有注意到文档的结构,并且没有注意到文档中不同部分对于分类的影响程度不一样。
  3. 为了解决这一问题,我们提出了一种层次注意力网络来学习文档的层次结构,并且使用两种注意力机制学习基于上下文的结构重要性。
  4. 我们的工作和前人工作的主要区别是我们使用上下文来区分句子或者单词的重要性,而不是仅仅使用单个句子或者单个的词。

Text classification is one of the fundamental task in Natural Language Processing. The goal is to assign labels to text. It has broad applications including topic labeling (Wang and Manning, 2012), sentiment classification (Maas et al., 2011; Pang and Lee, 2008), and spam detection (Sahami et al., 1998). Traditional approaches of text classification represent documents with sparse lexical features, such as n-grams, and then use a linear model or kernel methods on this representation (Wang and Manning, 2012; Joachims, 1998). More recent approaches used deep learning, such as convolutional neural networks (Blunsom et al., 2014) and recurrent neural networks based on long short-term memory (LSTM) (Hochreiter and Schmidhuber, 1997) to learn text representations.

论文整体框架

摘要

  1. 本文提出了一种层次注意力网络来做文档分类,它有两个特点。
    We propose a hierarchical attention network for document classification. (文章的主要工作)Our model has two distinctive characteristics:
  2. 第一个特点是这种层次结构对应着文档的层次结构。
    (i) it has a hierarchical structure that mirrors the hierarchical structure of documents;
  3. 第二个特点是它具有词级别和句子级别两种注意力机制,这使得网络能够区分文档中重要的部分,从而更好地生成文档表示。
    (ii) it has two levels of attention mechanisms applied at the wordand sentence-level, enabling it to attend differentially to more and less important content when constructing the document representation. (层次注意力模型的特点)
  4. 我们在六个大型数据集上的实验结果表明,我们的模型能够大幅度提高文档分类效果,并且可视化发现我们确实能够选择出文档中重要的句子和单词。
    experiments conducted on six large scale text classification tasks demonstrate that the proposed architecture outperform previous methods by a substantial margin. (模型在文本分类上的表现)
    Visualization of the attention layers illustrates that the model selects qualitatively informative words and sentences.(通过可视化,该模型可以选择出丰富语义的词和句子。)

论文标题

  1. Introduction
  2. Hierarchical Attention Networks
    2.1GPU-based sequence encoder
    2.2 Hierarchical Attention2.3Document Classification
  3. Experiments
    3.1Data sets
    3.2 Baselines
    3.2.1Linear methods
    3.2.2SVMs
    3.2.3 Neural Network methods
    3.3 Model configuration and training
    3.4 Results and analysis
    3.5 Context dependent attention weights
    3.6Visualization of attention
  4. Related Work
  5. Conclusion

层次注意力网络

NLP意图理解的完整过程 nlp理解层次图_NLP意图理解的完整过程_11


网络组成结构:

·word encoder:单词序列编码器

·word attention:单词注意力机制

·sentence encoder:句子编码器

·sentence attention:句子注意力机制

基于GRU的词序列编码器:

·RNN的一个变种,使用门机制来记录序列当前的状态。

·隐藏层状态的计算公式:NLP意图理解的完整过程 nlp理解层次图_结构化_12(这个公式表示新的状态保留多少,旧的状态保留多少。)

·更新门:NLP意图理解的完整过程 nlp理解层次图_编码器_13

·候选状态:NLP意图理解的完整过程 nlp理解层次图_文本分类_14

·重置(遗忘)门:NLP意图理解的完整过程 nlp理解层次图_结构化_15

GRU比LSTM少一个cell,用于记忆之前的信息

NLP意图理解的完整过程 nlp理解层次图_结构化_16

单词序列编码器:双向GRU

·NLP意图理解的完整过程 nlp理解层次图_NLP意图理解的完整过程_17:单词的词向量

·NLP意图理解的完整过程 nlp理解层次图_结构化_18:隐层状态

NLP意图理解的完整过程 nlp理解层次图_编码器_19

NLP意图理解的完整过程 nlp理解层次图_编码器_20是词向量矩阵,NLP意图理解的完整过程 nlp理解层次图_结构化_21是独热编码,两个相乘得到是词向量,t是时间步或者说第几个词,i代表当前第几个句子

NLP意图理解的完整过程 nlp理解层次图_编码器_22

上面是前向GRU

NLP意图理解的完整过程 nlp理解层次图_NLP意图理解的完整过程_23

上面是反向GRU

NLP意图理解的完整过程 nlp理解层次图_编码器_24

上面是前向和反向GRU的concat

NLP意图理解的完整过程 nlp理解层次图_结构化_25


首先假设一个文档有L个句子,每个句子NLP意图理解的完整过程 nlp理解层次图_结构化_26包含有NLP意图理解的完整过程 nlp理解层次图_NLP意图理解的完整过程_27个单词,句子的总长度为T

整个网络输入NLP意图理解的完整过程 nlp理解层次图_编码器_28表示第i个句子中的第t个单词(就是上图画红线部分)。然后利用矩阵NLP意图理解的完整过程 nlp理解层次图_编码器_20和独热编码NLP意图理解的完整过程 nlp理解层次图_结构化_21把单词embedding到向量中,得到单词的词向量NLP意图理解的完整过程 nlp理解层次图_NLP意图理解的完整过程_17(具体公式是上面第一个。)

然后通过双向GRU,可以将正向和反向的上下文信息结合起来获得隐藏层的输出。(具体公式是上面第2-4个)

单词注意力机制

第i个句子第t个词的输出为:
NLP意图理解的完整过程 nlp理解层次图_编码器_32
NLP意图理解的完整过程 nlp理解层次图_编码器_33
NLP意图理解的完整过程 nlp理解层次图_结构化_34
NLP意图理解的完整过程 nlp理解层次图_文本分类_35:隐层表示,对应的是上图中中间绿色正方形框框
NLP意图理解的完整过程 nlp理解层次图_编码器_36:注意力分数(权重),用随机初始化(在训练过程中也是不断更新的)的NLP意图理解的完整过程 nlp理解层次图_NLP意图理解的完整过程_37和隐层表示NLP意图理解的完整过程 nlp理解层次图_文本分类_35求相似度后进行softmax分类得到该权重。这里的NLP意图理解的完整过程 nlp理解层次图_NLP意图理解的完整过程_37是作者自己创建的注意力机制中的Query(随机初始化,并在梯度下降中进行优化),然后NLP意图理解的完整过程 nlp理解层次图_文本分类_35是Key,代表第i句话的所有词的集合。
NLP意图理解的完整过程 nlp理解层次图_文本分类_41:句子向量表示
不是所有的词对句子表示都很重要,因此使用注意力机制来实现提取词语对句子含义重要程度的信息,并将该信息汇总形成句子向量。

句子编码器

NLP意图理解的完整过程 nlp理解层次图_文本分类_42


NLP意图理解的完整过程 nlp理解层次图_文本分类_41:句子向量

NLP意图理解的完整过程 nlp理解层次图_结构化_44:隐层向量

NLP意图理解的完整过程 nlp理解层次图_NLP意图理解的完整过程_45

NLP意图理解的完整过程 nlp理解层次图_NLP意图理解的完整过程_46

NLP意图理解的完整过程 nlp理解层次图_结构化_47

由句子向量到文档向量和上面的流程一样

句子注意力机制

NLP意图理解的完整过程 nlp理解层次图_结构化_48
NLP意图理解的完整过程 nlp理解层次图_NLP意图理解的完整过程_49
NLP意图理解的完整过程 nlp理解层次图_NLP意图理解的完整过程_50
NLP意图理解的完整过程 nlp理解层次图_NLP意图理解的完整过程_51:隐层表示
NLP意图理解的完整过程 nlp理解层次图_编码器_36:注意力分数(权重),
NLP意图理解的完整过程 nlp理解层次图_文本分类_53:文档向量

文档分类

·文档向量v是文档的高级表示,可用作文档分类的功能:
NLP意图理解的完整过程 nlp理解层次图_NLP意图理解的完整过程_54
·损失函数:负对数似然
NLP意图理解的完整过程 nlp理解层次图_结构化_55

应用:

  1. 注意力机制在文本分类,以及其他分类和句子表示上大规模应用。
  2. 推动了注意力机制在非Seq2Seq场景下的应用。
  3. 层次结构对于文档级别数据的应用。

实验和结果

介绍论文中模型的实验设置和运行结果

论文中使用了六个数据集,Yelp reviews2013,2014,2015;IMDB reviews;Yahoo Answers;Amazon reviews

每个数据集合中80%的数据用作训练集合,10%的数据用作验证集合,剩余10%的集合用作测试集合

NLP意图理解的完整过程 nlp理解层次图_文本分类_56

实验设置

实验任务:六个数据集上的文本分类

对比模型:

·线性模型

·SVM

·word-based CNN(基于词的CNN)

·Character-based CNN(基于字符集的CNN Character-level Convolutional Networks for Text Classification)

NLP意图理解的完整过程 nlp理解层次图_文本分类_57

·ConV/LSTM-GRNN:Document Modeling with Gated Recurrent Neural Network for Sentiment Classification

NLP意图理解的完整过程 nlp理解层次图_结构化_58

实验结果

·HN-AVE使用平均来处理向量融合。

·HN-MAX使用求最大值处理向量融合。

·HN-ATT使用注意力机制处理向量融合。

·在各个数据集上均取得最优的结果。

模型参数:批次大小:64;动量值:0.9

NLP意图理解的完整过程 nlp理解层次图_结构化_59


无论数据集大小,结果都是最好。

可视化分析

单词“good”的权重分布情况,a是总体情况,b-f是good这个单词在好评度从差评过度到好评(1,2,3,4,5分)的不同分类中权重变化的情况,在f中很明显,good在好评中的权重很大。

NLP意图理解的完整过程 nlp理解层次图_NLP意图理解的完整过程_60


单词“bad”的权重分布情况

NLP意图理解的完整过程 nlp理解层次图_结构化_61


注意力机制可视化分析,红色是句子注意力,蓝色是词注意力。这里每句话进行了处理,句子不重要就不显示该句话中的重要的词。

NLP意图理解的完整过程 nlp理解层次图_文本分类_62

总结

A)提出一种用于文本分类的层次注意力网络
B)该网路首先构建句子的表示然后将它们聚合成文档表示
C)该网络在单词和句子级别应用了两个级别的注意机制
关键点
·之前的基于深度学习的文本分类没有关注到文档不同部分的信息重要性的不同。
·通过注意力机制可以学习到文档中各个部分对于分类的重要度。
·HAN Attention模型。
创新点
·提出了一种新的文本分类模型—HAN Attention模型
·HAN Attention模型通过两种级别的注意力机制同时学习文档中重要的句子和单词。
·在六个文本分类数据集上取得sota的结果。
启发点
·我们模型背后的直觉是文档不同部分对于问题的重要性不同,而且这些部分的重要性还取决于内部的
单词,而不仅仅是对这部分单独确定重要性。
The intuition underlying our model is that not all parts of a document are equally relevant for answering a query and that determining the relevant sections involves modeling the interactions of the words,not just their presence in isolation(Introduction P2)
·此外,单词和句子的重要性是上下文相关的,同样的词或者句子在不同的上下文情景下重要性也不同。
Moreover,the importance of words and sentences are highly context dependent,i.e.the same word or sentence may be differentially important in different context.(Introduction P3)

推荐文献

Tianyang Zhang et al.2018.Learning Structured Representation for Text Classification via Reinforcement Learning (清华团队出品)
Joulin et al.2017.Bag of Tricks for Efficient Text Classification (这个有带读笔记)
Johnson et al.2017.Deep Pyramid Convolutional Neural Networks for Text Categorization (DPCNN用于捕捉全局语义表示)
JacobDevlin et al.2019.BERT:Pre-training of Deep Bidirectional Transformers for Language Understanding(BERT)

复现

NLP意图理解的完整过程 nlp理解层次图_结构化_63


代码结构:

NLP意图理解的完整过程 nlp理解层次图_编码器_64


数据集下载

数据集: http://ir.hit.edu.cn/~dytang/paper/emnlp2015/emnlp-2015-data.7z

其他数据集可以看之前baseline里面有。

本次用的是影评数据imdb,前面两列估计是编号和外键,数字是类别,后面是评论。

NLP意图理解的完整过程 nlp理解层次图_编码器_65

01层次文档分类数据处理

层次文档分类的数据处理

# coding:utf-8
from torch.utils import data
import os
import torch
import nltk
import numpy as np
from gensim.models import KeyedVectors
import nltk


# 数据集加载
# 根据长度排序,这个之前讲过,按长度进行排序后,句子长度相近,就可以使得pad的效率比较高
# 创建word2id
# 分句
# 将数据转化成id
class IMDB_Data(data.DataLoader):
    def __init__(self, data_name, min_count, word2id=None, max_sentence_length=100, batch_size=64, is_pretrain=False):
        self.path = os.path.abspath(".")
        if "data" not in self.path:
            self.path += "/data"
        self.data_name = "/imdb/" + data_name
        self.min_count = min_count
        self.word2id = word2id
        self.max_sentence_length = max_sentence_length
        self.batch_size = batch_size
        self.datas, self.labels = self.load_data()
        if is_pretrain:
            self.get_word2vec()
        else:
            self.weight = None
        for i in range(len(self.datas)):
            self.datas[i] = np.array(self.datas[i])

    def load_data(self):
        datas = open(self.path + self.data_name, encoding="utf-8").read().splitlines()
        # -1代表最后一个字段就是文章列,2是label列,把两个东西放在一起,按句子长度排序,这样不会丢失句子和label的对应关系。
        datas = [data.split("		")[-1].split() + [data.split("		")[2]] for data in datas]
        # 按句子长度排序reverse=True代表从大到小,这样可以防止显存分配不足
        datas = sorted(datas, key=lambda x: len(x), reverse=True)
        # 获取label
        labels = [int(data[-1]) - 1 for data in datas]
        # 剥离label
        datas = [data[0:-1] for data in datas]
        if self.word2id == None:
            self.get_word2id(datas)
        # 分句
        for i, data in enumerate(datas):
            #数据集中是用<sssss>来进行分句的
            datas[i] = " ".join(data).split("<sssss>")
            for j, sentence in enumerate(datas[i]):
                datas[i][j] = sentence.split()
        datas = self.convert_data2id(datas)
        return datas, labels

    def get_word2id(self, datas):
        word_freq = {}
        for data in datas:
            for word in data:
                word_freq[word] = word_freq.get(word, 0) + 1
        word2id = {"<pad>": 0, "<unk>": 1}
        for word in word_freq:
            # 小于min_count设置为UNK词
            if word_freq[word] < self.min_count:
                continue
            else:
                word2id[word] = len(word2id)
        self.word2id = word2id

    # max_sentence_length 句子必须是一样长。
    # batch_size 代表每个batch_size,每个文档的句子一样多。
    def convert_data2id(self, datas):
        for i, document in enumerate(datas):
            if i % 10000 == 0:
                print(i, len(datas))
            for j, sentence in enumerate(document):
                for k, word in enumerate(sentence):
                    datas[i][j][k] = self.word2id.get(word, self.word2id["<unk>"])
                # 超长截断,过短pad补齐
                datas[i][j] = datas[i][j][0:self.max_sentence_length] + \
                              [self.word2id["<pad>"]] * (self.max_sentence_length - len(datas[i][j]))
        for i in range(0, len(datas), self.batch_size):
            max_data_length = max([len(x) for x in datas[i:i + self.batch_size]])
            for j in range(i, min(i + self.batch_size, len(datas))):
                datas[j] = datas[j] + [[self.word2id["<pad>"]] * self.max_sentence_length] * (
                            max_data_length - len(datas[j]))
                datas[j] = datas[j]
        return datas

    def get_word2vec(self):
        '''
        生成word2vec词向量
        :return: 根据词表生成的词向量
        '''
        print("Reading word2vec Embedding...")
        wvmodel = KeyedVectors.load_word2vec_format(self.path + "/imdb.model", binary=True)
        tmp = []
        for word, index in self.word2id.items():
            try:
                tmp.append(wvmodel.get_vector(word))
            except:
                pass
        mean = np.mean(np.array(tmp))
        std = np.std(np.array(tmp))
        print(mean, std)
        vocab_size = len(self.word2id)
        embed_size = 200
        np.random.seed(2)
        embedding_weights = np.random.normal(mean, std, [vocab_size, embed_size])  # 正太分布初始化方法
        for word, index in self.word2id.items():
            try:
                embedding_weights[index, :] = wvmodel.get_vector(word)
            except:
                pass
        self.weight = torch.from_numpy(embedding_weights).float()

    def __getitem__(self, idx):
        return self.datas[idx], self.labels[idx]

    def __len__(self):
        return len(self.labels)


if __name__ == "__main__":
    imdb_data = IMDB_Data(data_name="imdb-train.txt.ss", min_count=5, is_pretrain=True)
    training_iter = torch.utils.data.DataLoader(dataset=imdb_data,
                                                batch_size=64,
                                                shuffle=False,
                                                num_workers=0)
    for data, label in training_iter:
        print(np.array(data).shape)

02使用Pytorch实现HAN Attention模型

基于Pytorch的文本分类注意力机制实现以及HAN Attention模型实现

# -*- coding: utf-8 -*-
import torch
import torch.nn as nn
import numpy as np
from torch.nn import functional as F
from torch.autograd import Variable


class HAN_Model(nn.Module):
    def __init__(self, vocab_size, embedding_size, gru_size, class_num, is_pretrain=False, weights=None):
        super(HAN_Model, self).__init__()
        # 判断是否词向量是否预训练,不是预训练效果会差一点
        if is_pretrain:
            self.embedding = nn.Embedding.from_pretrained(weights, freeze=False)
        else:
            self.embedding = nn.Embedding(vocab_size, embedding_size)
        # 词的双向gru
        self.word_gru = nn.GRU(input_size=embedding_size, hidden_size=gru_size, num_layers=1,
                               bidirectional=True, batch_first=True)
        # 词attention的Query
        self.word_context = nn.Parameter(torch.Tensor(2 * gru_size, 1), requires_grad=True)
        self.word_dense = nn.Linear(2 * gru_size, 2 * gru_size)
        # 句子的双向gru,这里的输入shape是上一句的输出2*gru_size,
        self.sentence_gru = nn.GRU(input_size=2 * gru_size, hidden_size=gru_size, num_layers=1,
                                   bidirectional=True, batch_first=True)
        # 句attention的Query
        self.sentence_context = nn.Parameter(torch.Tensor(2 * gru_size, 1), requires_grad=True)
        self.sentence_dense = nn.Linear(2 * gru_size, 2 * gru_size)
        # class_num是最后文本分类的类别数量
        self.fc = nn.Linear(2 * gru_size, class_num)

    def forward(self, x, gpu=False):
        # 句子个数
        sentence_num = x.shape[1]
        # 句子长度
        sentence_length = x.shape[2]
        # x原来维度是bs*sentence_num*sentence_length,经过下面变化成二维的:(bs*sentence_num)*sentence_length
        x = x.view([-1, sentence_length])
        # 加了embedding维度:(bs*sentence_num)*sentence_length*embedding_size
        x_embedding = self.embedding(x)
        # word_outputs.shape:(bs*sentence_num)*sentence_length*(2*gru_size),这里因为是双向gru所以是2*gru_size
        word_outputs, word_hidden = self.word_gru(x_embedding)
        # attention_word_outputs.shape:(bs*sentence_num)*sentence_length*(2*gru_size),这里对应原文公式5
        attention_word_outputs = torch.tanh(self.word_dense(word_outputs))
        # weights.shape:(bs*sentence_num)*sentence_length*1,,这里和下面一句对应原文公式6
        weights = torch.matmul(attention_word_outputs, self.word_context)
        # weights.shape: (bs * sentence_num) * sentence_length * 1
        weights = F.softmax(weights, dim=1)
        # 这里对x加一个维度:(bs*sentence_num)*sentence_length*1
        x = x.unsqueeze(2)
        # 对x加的这个维度进行判断,如果这个维度上为0,表示这个地方是pad出来的,没有必要进行计算。
        # 如果为1,则按weight进行计算比例
        if gpu:
            weights = torch.where(x != 0, weights, torch.full_like(x, 0, dtype=torch.float).cuda())
        else:
            weights = torch.where(x != 0, weights, torch.full_like(x, 0, dtype=torch.float))

        # 这里由于忽略掉了pad为0的部分,所以要按维度重新计算分布
        weights = weights / (torch.sum(weights, dim=1).unsqueeze(1) + 1e-4)
        # bs*sentence_num*(2*gru_size)
        sentence_vector = torch.sum(word_outputs * weights, dim=1).view([-1, sentence_num, word_outputs.shape[-1]])
        # sentence_outputs.shape:bs*sentence_num*(2*gru_size)
        sentence_outputs, sentence_hidden = self.sentence_gru(sentence_vector)
        # 对应原文公式8:bs*sentence_num*(2*gru_size)
        attention_sentence_outputs = torch.tanh(self.sentence_dense(sentence_outputs))
        # bs*sentence_num*1
        weights = torch.matmul(attention_sentence_outputs, self.sentence_context)
        # bs*sentence_num*1
        weights = F.softmax(weights, dim=1)
        # bs * sentence_num * sentence_length
        x = x.view(-1, sentence_num, x.shape[1])
        # bs * sentence_num * 1
        x = torch.sum(x, dim=2).unsqueeze(2)
        if gpu:
            # bs * sentence_num * 1
            weights = torch.where(x != 0, weights, torch.full_like(x, 0, dtype=torch.float).cuda())
        else:
            weights = torch.where(x != 0, weights, torch.full_like(x, 0, dtype=torch.float))
        # bs * sentence_num * 1,对未计算的pad进行缩放
        weights = weights / (torch.sum(weights, dim=1).unsqueeze(1) + 1e-4)
        # bs *(2*gru)
        document_vector = torch.sum(sentence_outputs * weights, dim=1)
        # bs * class_num
        output = self.fc(document_vector)
        return output


if __name__ == "__main__":
    han_model = HAN_Model(vocab_size=30000, embedding_size=200, gru_size=50, class_num=4)
    x = torch.Tensor(np.zeros([64, 50, 100])).long()
    x[0][0][0:10] = 1
    output = han_model(x)
    print(output.shape)

03HAN Attention模型的训练和测试

基于Pytorch的HAN Attention模型的训练和测试

# -*- coding: utf-8 -*-
import torch
import torch.autograd as autograd
import torch.nn as nn
import torch.optim as optim
from model import HAN_Model
from data import IMDB_Data
import numpy as np
from tqdm import tqdm
import config as argumentparser

config = argumentparser.ArgumentParser()
torch.manual_seed(config.seed)

if config.cuda and torch.cuda.is_available():
    torch.cuda.set_device(config.gpu)


def get_test_result(data_iter, data_set):
    # 生成测试结果
    model.eval()
    true_sample_num = 0
    for data, label in data_iter:
        if config.cuda and torch.cuda.is_available():
            data = data.cuda()
            label = label.cuda()
        else:
            data = torch.autograd.Variable(data).long()
        if config.cuda and torch.cuda.is_available():
            out = model(data, gpu=True)
        else:
            out = model(data)
        true_sample_num += np.sum((torch.argmax(out, 1) == label).cpu().numpy())
    acc = true_sample_num / data_set.__len__()
    return acc

# 导入训练集,shuffle=False,不能是true,因为是按句子长度排序的
training_set = IMDB_Data("imdb-train.txt.ss", min_count=config.min_count,
                         max_sentence_length=config.max_sentence_length, batch_size=config.batch_size, is_pretrain=True)
training_iter = torch.utils.data.DataLoader(dataset=training_set,
                                            batch_size=config.batch_size,
                                            shuffle=False,
                                            num_workers=0)
test_set = IMDB_Data("imdb-test.txt.ss", min_count=config.min_count, word2id=training_set.word2id,
                     max_sentence_length=config.max_sentence_length, batch_size=config.batch_size)
test_iter = torch.utils.data.DataLoader(dataset=test_set,
                                        batch_size=config.batch_size,
                                        shuffle=False,
                                        num_workers=0)
if config.cuda and torch.cuda.is_available():
    training_set.weight = training_set.weight.cuda()
model = HAN_Model(vocab_size=len(training_set.word2id),
                  embedding_size=config.embedding_size,
                  gru_size=config.gru_size, class_num=config.class_num, weights=training_set.weight, is_pretrain=True)
if config.cuda and torch.cuda.is_available():
    model.cuda()
# CrossEntropyLoss自带softmax效果,无需额外做softmax
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=config.learning_rate)
loss = -1
for epoch in range(config.epoch):
    model.train()
    process_bar = tqdm(training_iter)
    for data, label in process_bar:
        if config.cuda and torch.cuda.is_available():
            data = data.cuda()
            label = label.cuda()
        else:
            data = torch.autograd.Variable(data).long()
        label = torch.autograd.Variable(label).squeeze()
        if config.cuda and torch.cuda.is_available():
            out = model(data, gpu=True)
        else:
            out = model(data)
        loss_now = criterion(out, autograd.Variable(label.long()))
        if loss == -1:
            loss = loss_now.data.item()
        else:
            loss = 0.95 * loss + 0.05 * loss_now.data.item()# 平滑处理
        process_bar.set_postfix(loss=loss_now.data.item())
        process_bar.update()
        optimizer.zero_grad()
        loss_now.backward()
        optimizer.step()
    test_acc = get_test_result(test_iter, test_set)
    print("The test acc is: %.5f" % test_acc)

04总结

总结HAN Attention模型以及实现

NLP意图理解的完整过程 nlp理解层次图_编码器_66