HAN(层叠注意力)神经网络文本分类

原理讲解

HAN出处:论文Hierarchical Attention Networks for Document Classification

可以参见讲解文献阅读笔记:Hierarchical Attention Networks for Document Classification

这篇论文表示,对文档/较长文本进行分类的时候,仅仅对word粒度进行Attention是不够的,还需要对各个句子(短句)进行Attention的学习,不同句子也需要分配不同的权重,每个句子里的词语也分配不同的权重。

网络结构

  1. Word Encoder. 对词汇进行编码,建立词向量。接着用双向 RNN 从单词的两个方向汇总信息来获取单词的注释,因此将上下文信息合并到句子向量中。
  2. Word Attention. 对每句话的词语进行Attention操作,最后每句话都有一个特征向量,可以看做句向量
  3. Sentence Encoder. 与word encoder相似,对句子级别也使用双向 RNN 获取上下句的信息
  4. Sentence Attention. 与 word Attention相似,对所有句子进行Attention操作,获得一个每个句子加权平均作为整个输入的特征向量
  5. Document Classification. 常规的输出分类结果

本文实现

此处的 Attention 的实现使用了 FeedForwardAttention 的实现方式,与 TextAttBiRNN 中的 Attention 相同。

HAN 的网络结构:

此处使用了 TimeDistributed 包装器,希望 Embedding、Bidirectional RNN 和 Attention 层的参数能够在时间步维度上共享。

输入层

与其他的文本分类模型的输入不同,在这里输入层变成了句子数量×每句词语数量,在代码中对数据处理比较粗暴,直接将原来的一句话padding补全到句子数量×词语数量的长度,然后reshape到所需的维度。因此这里每个样本内容其实还是原来的一句话,所以在这个训练数据中,可以理解为进行了层次化的Attention。

word粒度Attention层

word粒度的操作,使用了keras中TimeDistributed层进行了封装,它的原理是将这个层中的操作对输入的最后一维进行使用,在这里就是该层中的Attention操作是对输入的最后一维,也就是词语这一维进行使用。举个例子,我们的数据输入是(32, 10, 16)维度,我们在TimeDistributed层中添加了一个Dense(16, 8)的全连接层,那么经过该TimeDistributed层的输出就是(32, 10, 8)维度。

Attention的代码与上一次Bi-LSTM+Attention代码一样,因此输出变成了16(句子数)×256(每句句子256维)

sentence粒度Attention层

这一层操作还是与word粒度Attention相同,对每句话进行了Attention的加权求和,得到这16句话最终加权的结果,也是256维。

输出层

依旧是接5分类的全连接层进行输出。

定义网络结构

首先定义Attention类,和上一篇博文text-attition-BiRNN中的定义一样。

from tensorflow.keras import backend as K
#from tensorflow.python.keras import backend as K
from tensorflow.keras import initializers, regularizers, constraints
from tensorflow.keras.layers import Layer
#from keras.engine.topology import Layerclass Attention(Layer):def __init__(self, step_dim,W_regularizer=None, b_regularizer=None,W_constraint=None, b_constraint=None,bias=True, **kwargs):"""Keras Layer that implements an Attention mechanism for temporal data.Supports Masking.Follows the work of Raffel et al. [https://arxiv.org/abs/1512.08756]# Input shape3D tensor with shape: `(samples, steps, features)`.# Output shape2D tensor with shape: `(samples, features)`.:param kwargs:Just put it on top of an RNN Layer (GRU/LSTM/SimpleRNN) with return_sequences=True.The dimensions are inferred based on the output shape of the RNN.Example:# 1model.add(LSTM(64, return_sequences=True))model.add(Attention())# next add a Dense layer (for classification/regression) or whatever...# 2hidden = LSTM(64, return_sequences=True)(words)sentence = Attention()(hidden)# next add a Dense layer (for classification/regression) or whatever..."""self.supports_masking = Trueself.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 = biasself.step_dim = step_dimself.features_dim = 0super(Attention, self).__init__(**kwargs)def build(self, input_shape):assert len(input_shape) == 3self.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 = Noneself.built = Truedef compute_mask(self, input, input_mask=None):# do not pass the mask to the next layersreturn Nonedef call(self, x, mask=None):features_dim = self.features_dimstep_dim = self.step_dime = 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.be = K.tanh(e)a = K.exp(e)# apply mask after the exp. will be re-normalized nextif mask is not None:# cast the mask to floatX to avoid float64 upcasting in theanoa *= K.cast(mask, K.floatx())# in some cases especially in the early stages of training the sum may be almost zero# and this results in NaN's. A workaround is to add a very small positive number ε to the sum.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 cdef compute_output_shape(self, input_shape):return input_shape[0], self.features_dim

然后定义HAN类。
注意这里的word级的attention和sentence级的attention不同,句子级的需要用TimeDistributed。

from tensorflow.keras import Input, Model
from tensorflow.keras.layers import Embedding, Dense, Dropout, Bidirectional, LSTM, TimeDistributedclass HAN(object):def __init__(self, maxlen_sentence, maxlen_word, max_features, embedding_dims,class_num=5,last_activation='softmax'):self.maxlen_sentence = maxlen_sentenceself.maxlen_word = maxlen_wordself.max_features = max_featuresself.embedding_dims = embedding_dimsself.class_num = class_numself.last_activation = last_activationdef get_model(self):# Word partinput_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)  # LSTM or GRUx_word = Attention(self.maxlen_word)(x_word)model_word = Model(input_word, x_word)# Sentence partinput = Input(shape=(self.maxlen_sentence, self.maxlen_word))x_sentence = TimeDistributed(model_word)(input)x_sentence = Bidirectional(LSTM(128, return_sequences=True))(x_sentence)  # LSTM or GRUx_sentence = Attention(self.maxlen_sentence)(x_sentence)output = Dense(self.class_num, activation=self.last_activation)(x_sentence)model = Model(inputs=input, outputs=output)return model

数据处理与训练

和之前的模型类似

from tensorflow.keras.preprocessing import sequence
import random
from sklearn.model_selection import train_test_split
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras.utils import to_categorical
import sys
sys.path.append('../data/lesson2_data')
from utils import *# 路径等配置
data_dir = "../data/lesson2_data/data"
vocab_file = "../data/lesson2_data/vocab/vocab.txt"
vocab_size = 40000# 神经网络配置
max_features = 40001
maxlen_sentence = 16
maxlen_word = 25
batch_size = 64
embedding_dims = 50
epochs = 10print('数据预处理与加载数据...')
# 如果不存在词汇表,重建
if not os.path.exists(vocab_file):  build_vocab(data_dir, vocab_file, vocab_size)
# 获得 词汇/类别 与id映射字典
categories, cat_to_id = read_category()
words, word_to_id = read_vocab(vocab_file)# 全部数据
x, y = read_files(data_dir)
data = list(zip(x,y))
del x,y
# 乱序
random.shuffle(data)
# 切分训练集和测试集
train_data, test_data = train_test_split(data)
# 对文本的词id和类别id进行编码
x_train = encode_sentences([content[0] for content in train_data], word_to_id)
y_train = to_categorical(encode_cate([content[1] for content in train_data], cat_to_id))
x_test = encode_sentences([content[0] for content in test_data], word_to_id)
y_test = to_categorical(encode_cate([content[1] for content in test_data], cat_to_id))print('对序列做padding')
x_train = sequence.pad_sequences(x_train, maxlen=maxlen_sentence * maxlen_word)
x_test = sequence.pad_sequences(x_test, maxlen=maxlen_sentence * maxlen_word)
x_train = x_train.reshape((len(x_train), maxlen_sentence, maxlen_word))
x_test = x_test.reshape((len(x_test), maxlen_sentence, maxlen_word))
print('x_train shape:', x_train.shape)
print('x_test shape:', x_test.shape)print('构建模型...')
model = HAN(maxlen_sentence, maxlen_word, max_features, embedding_dims).get_model()
model.compile('adam', 'categorical_crossentropy', metrics=['accuracy'])print('Train...')
early_stopping = EarlyStopping(monitor='val_accuracy', patience=2, mode='max')
history = model.fit(x_train, y_train,batch_size=batch_size,epochs=epochs,callbacks=[early_stopping],validation_data=(x_test, y_test))print('Test...')
result = model.predict(x_test)

NLP实战之HAN文本分类相关推荐

  1. NLP实战-中文新闻文本分类

    目录 1.思路 2.基于paddle的ERINE模型进行迁移学习训练 3.分步实现 3.1 获取数据 (1)数据解压 (2)将文本转成变量,这里为了好计算,我只选了新闻标题做文本分类 3.2 中文分词 ...

  2. 【NLP傻瓜式教程】手把手带你HAN文本分类(附代码)

    继续之前的文本分类系列 [NLP傻瓜式教程]手把手带你CNN文本分类(附代码) [NLP傻瓜式教程]手把手带你RNN文本分类(附代码) [NLP傻瓜式教程]手把手带你fastText文本分类(附代码) ...

  3. 【NLP】深度学习文本分类|模型代码技巧

    文本分类是NLP的必备入门任务,在搜索.推荐.对话等场景中随处可见,并有情感分析.新闻分类.标签分类等成熟的研究分支和数据集. 本文主要介绍深度学习文本分类的常用模型原理.优缺点以及技巧,是「NLP入 ...

  4. BERT模型实战之多文本分类(附源码)

    BERT模型也出来很久了,之前看了论文学习过它的大致模型(可以参考前些日子写的笔记NLP大杀器BERT模型解读),但是一直有杂七杂八的事拖着没有具体去实现过真实效果如何.今天就趁机来动手写一写实战,顺 ...

  5. 零基础入门天池NLP赛事之——新闻文本分类(5)

    基于深度学习的文本分类 一.学习目标: 学习Word2Vec的使用和基础原理 学习使用TextCNN.TextRNN进行文本表示 学习使用HAN网络结构完成文本分类 二.文本表示方法 Part3: 词 ...

  6. NLP入门之新闻文本分类竞赛——文本分类模型

    一.Word2Vec word2vec模型背后的基本思想是对出现在上下文环境里的词进行预测.对于每一条输入文本,我们选取一个上下文窗口和一个中心词,并基于这个中心词去预测窗口里其他词出现的概率.因此, ...

  7. 用最新NLP库Flair做文本分类

    摘要: Flair是一个基于PyTorch构建的NLP开发包,它在解决命名实体识别(NER).部分语音标注(PoS).语义消歧和文本分类等NLP问题达到了当前的最高水准.它是一个建立在PyTorch之 ...

  8. 用最新NLP库Flair做文本分类 1

    介绍 文本分类是一种监督机器学习方法,用于将句子或文本文档归类为一个或多个已定义好的类别.它是一个被广泛应用的自然语言处理方法,在垃圾邮件过滤.情感分析.新闻稿件分类以及与许多其它业务相关的问题中发挥 ...

  9. 【NLP基础理论】03 文本分类

    注: Unimelb Comp90042 NLP笔记 相关tutorial代码链接 Text Classification(文本分类) 目录 Text Classification(文本分类) 1 分 ...

最新文章

  1. UnicodeDecodeError: ‘ascii‘ codec can‘t decode byte 0xe6 in position 0: ordinal not in range(128)
  2. tensorflow版本问题导致的错误AttributeError: module ‘tensorflow‘ has no attribute ‘***‘
  3. leetcode343. 整数拆分(思路+详解)
  4. jdk 1.8 字符串+_JDK 9/10/11:Java字符串上+ =的副作用
  5. 软件测试用例_大话软件测试用例要素
  6. 实物贴图风格拟物图标素材,高逼格即显
  7. Git 分支相关操作
  8. [原创]项目管理知识体系指南之 13项目干系人管理思维导图
  9. Maven 梳理 - Maven中的dependencyManagement 意义
  10. 华为华三开启snmp服务
  11. 佛说剖腹产的孩子_选择好的剖腹产时间会改变孩子的命运吗?
  12. java o2o 源码_电子商务平台 b2b2c o2o java源码
  13. 最全面、最详细web前端面试题及答案总结
  14. 分享40个主机域名PHP源码,总有一款适合你
  15. dec是几进制(oct是几进制)
  16. SAMBA配置 “你可能没有权限访问网络资源”的问题解决方法
  17. python qq群文件_python 获取qq群成员列表数据
  18. 华为P30销量破千万有多少含金量?
  19. MATLAB演示元胞自动机算法
  20. A. chino with string(ac自动机+floyd矩阵快速幂)

热门文章

  1. EOJ Monthly 2019.1 3675. 唐纳德先生与假骰子
  2. 面向对象程序设计实验报告
  3. 微型计算机系统原理接口与EDA设计技术,微型计算机系统与接口
  4. 数据库第五次作业——查询数据
  5. C++排序算法利用EsayX实现过程可视化
  6. 云原生周报 | 2021下半年CNCF开源项目发展总结;Cilium 1.11发布;BFE Server及控制面更新
  7. Win11会触发部分游戏反作弊致使误封
  8. 什么是widget?widget是什么意思?
  9. NC17389-凤 凰(并查集)
  10. html代码多媒体播放器,6 个基于 HTML5 实现的多媒体播放器