Press "Enter" to skip to content

[深度应用]·Keras实现Self-Attention文本分类(机器如何读懂人心)

本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.

笔者在 [深度概念]·Attention 机制概念学习笔记 博文中,讲解了 Attention 机制的概念与技术细节,本篇内容配合讲解,使用 Keras 实现 Self-Attention 文本分类,来让大家更加深入理解 Attention 机制。

 

作为对比,可以访问 [TensorFlow 深度学习深入] 实战三 · 分别使用 DNN,CNN 与 RNN(LSTM) 做文本情感分析 ,查看不同网络区别与联系。

 

一、Self-Attention 概念详解

 

了解了模型大致原理,我们可以详细的看一下究竟 Self-Attention 结构是怎样的。其基本结构如下

 

 

对于 self-attention 来讲,Q(Query), K(Key), V(Value) 三个矩阵均来自同一输入,首先我们要计算 Q 与 K 之间的点乘,然后为了防止其结果过大,会除以一个尺度标度 ,其中  为一个 query 和 key 向量的维度。再利用 Softmax 操作将其结果归一化为概率分布,然后再乘以矩阵 V 就得到权重求和的表示。该操作可以表示为 

 

这里可能比较抽象,我们来看一个具体的例子(图片来源于 jalammar.github.io/illustrated… ,该博客讲解的极其清晰,强烈推荐),假如我们要翻译一个词组 Thinking Machines,其中 Thinking 的输入的 embedding vector 用  表示,Machines 的 embedding vector 用  表示。

 

 

当我们处理 Thinking 这个词时,我们需要计算句子中所有词与它的 Attention Score,这就像将当前词作为搜索的 query,去和句子中所有词(包含该词本身)的 key 去匹配,看看相关度有多高。我们用 代表 Thinking 对应的 query vector,  及  分别代表 Thinking 以及 Machines 对应的 key vector,则计算 Thinking 的 attention score 的时候我们需要计算  与  的点乘,同理,我们计算 Machines 的 attention score 的时候需要计算 与  的点乘。如上图中所示我们分别得到了 与  的点乘积,然后我们进行尺度缩放与 softmax 归一化,如下图所示:

 

 

显然,当前单词与其自身的 attention score 一般最大,其他单词根据与当前单词重要程度有相应的 score。然后我们在用这些 attention score 与 value vector 相乘,得到加权的向量。

 

 

如果将输入的所有向量合并为矩阵形式,则所有 query, key, value 向量也可以合并为矩阵形式表示

 

 

其中 是我们模型训练过程学习到的合适的参数。上述操作即可简化为矩阵形式

 

 

二、Self_Attention 模型搭建

 

笔者使用 Keras 来实现对于 Self_Attention 模型的搭建,由于网络中间参数量比较多,这里采用自定义网络层的方法构建 Self_Attention,关于如何自定义 Keras 可以参看这里: 编写你自己的 Keras 层

 

Keras 实现自定义网络层。需要实现以下三个方法:( 注意 input_shape是包含batch_size项的

build(input_shape) : 这是你定义权重的地方。这个方法必须设  self.built = True ,可以通过调用  super([Layer], self).build()  完成。
call(x) : 这里是编写层的功能逻辑的地方。你只需要关注传入  call  的第一个参数:输入张量,除非你希望你的层支持 masking。
compute_output_shape(input_shape) : 如果你的层更改了输入张量的形状,你应该在这里定义形状变化的逻辑,这让 Keras 能够自动推断各层的形状。

实现代码如下:

 

from keras.preprocessing import sequence
from keras.datasets import imdb
from matplotlib import pyplot as plt
from keras import backend as K
from keras.engine.topology import Layer
class Self_Attention(Layer):
def __init__(self, output_dim, **kwargs):
        self.output_dim = output_dim
        super(Self_Attention, self).__init__(**kwargs)
def build(self, input_shape):
        self.kernel = self.add_weight(name='kernel',
                                      shape=(3,input_shape[2], self.output_dim),
        super(Self_Attention, self).build(input_shape)  
        WQ = K.dot(x, self.kernel[0])
        WK = K.dot(x, self.kernel[1])
        WV = K.dot(x, self.kernel[2])
        print("WQ.shape",WQ.shape)
        print("K.permute_dimensions(WK, [0, 2, 1]).shape",K.permute_dimensions(WK, [0, 2, 1]).shape)
        QK = K.batch_dot(WQ,K.permute_dimensions(WK, [0, 2, 1]))
        print("QK.shape",QK.shape)
def compute_output_shape(self, input_shape):
return (input_shape[0],input_shape[1],self.output_dim)

 

这里可以对照一中的概念讲解来理解代码

 

如果将输入的所有向量合并为矩阵形式,则所有 query, key, value 向量也可以合并为矩阵形式表示

 

 

上述内容对应

 

WQ = K.dot(x, self.kernel[0])
WK = K.dot(x, self.kernel[1])
WV = K.dot(x, self.kernel[2])

 

其中 是我们模型训练过程学习到的合适的参数。上述操作即可简化为矩阵形式

 

 

上述内容对应( 为什幺使用 batch_dot 呢?这是由于 input_shape是包含batch_size项的

 

QK = K.batch_dot(WQ,K.permute_dimensions(WK, [0, 2, 1]))
print("QK.shape",QK.shape)

 

这里 QK = QK / (64 0.5) 是除以一个归一化系数,(64 0.5) 是笔者自己定义的,其他文章可能会采用不同的方法。

 

三、训练网络

 

项目完整代码如下,这里使用的是 Keras 自带的 imdb 影评数据集

 

from keras.preprocessing import sequence
from keras.datasets import imdb
from matplotlib import pyplot as plt
from keras import backend as K
from keras.engine.topology import Layer
class Self_Attention(Layer):
def __init__(self, output_dim, **kwargs):
        self.output_dim = output_dim
        super(Self_Attention, self).__init__(**kwargs)
def build(self, input_shape):
        self.kernel = self.add_weight(name='kernel',
                                      shape=(3,input_shape[2], self.output_dim),
        super(Self_Attention, self).build(input_shape)  
        WQ = K.dot(x, self.kernel[0])
        WK = K.dot(x, self.kernel[1])
        WV = K.dot(x, self.kernel[2])
        print("WQ.shape",WQ.shape)
        print("K.permute_dimensions(WK, [0, 2, 1]).shape",K.permute_dimensions(WK, [0, 2, 1]).shape)
        QK = K.batch_dot(WQ,K.permute_dimensions(WK, [0, 2, 1]))
        print("QK.shape",QK.shape)
def compute_output_shape(self, input_shape):
return (input_shape[0],input_shape[1],self.output_dim)
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)
y_train, y_test = pd.get_dummies(y_train),pd.get_dummies(y_test)
print(len(x_train), 'train sequences')
print(len(x_test), 'test sequences')
print('Pad sequences (samples x time)')
x_train = sequence.pad_sequences(x_train, maxlen=maxlen)
x_test = sequence.pad_sequences(x_test, maxlen=maxlen)
print('x_train shape:', x_train.shape)
print('x_test shape:', x_test.shape)
from keras.models import Model
from keras.optimizers import SGD,Adam
from keras.layers import *
from Attention_keras import Attention,Position_Embedding
S_inputs = Input(shape=(64,), dtype='int32')
embeddings = Embedding(max_features, 128)(S_inputs)
O_seq = Self_Attention(128)(embeddings)
O_seq = GlobalAveragePooling1D()(O_seq)
O_seq = Dropout(0.5)(O_seq)
outputs = Dense(2, activation='softmax')(O_seq)
model = Model(inputs=S_inputs, outputs=outputs)
opt = Adam(lr=0.0002,decay=0.00001)
loss = 'categorical_crossentropy'
h = model.fit(x_train, y_train,
         validation_data=(x_test, y_test))
plt.plot(h.history["loss"],label="train_loss")
plt.plot(h.history["val_loss"],label="val_loss")
plt.plot(h.history["acc"],label="train_acc")
plt.plot(h.history["val_acc"],label="val_acc")

 

四、结果输出

 

 

(TF_GPU) D:\Files\DATAs\prjs\python\tf_keras\transfromerdemo>C:/Files/APPs/RuanJian/Miniconda3/envs/TF_GPU/python.exe d:/Files/DATAs/prjs/python/tf_keras/transfromerdemo/train.1.py
Using TensorFlow backend.
Pad sequences (samples x time)
x_train shape: (25000, 64)
x_test shape: (25000, 64)
K.permute_dimensions(WK, [0, 2, 1]).shape (?, 128, 64)
_________________________________________________________________
Layer (type)                 Output Shape              Param 
=================================================================
input_1 (InputLayer)         (None, 64)                0
_________________________________________________________________
embedding_1 (Embedding)      (None, 64, 128)           2560000
_________________________________________________________________
self__attention_1 (Self_Atte (None, 64, 128)           49152
_________________________________________________________________
global_average_pooling1d_1 ( (None, 128)               0
_________________________________________________________________
dropout_1 (Dropout)          (None, 128)               0
_________________________________________________________________
dense_1 (Dense)              (None, 2)                 258
=================================================================
Trainable params: 2,609,410
_________________________________________________________________
Train on 25000 samples, validate on 25000 samples
25000/25000 [==============================] - 17s 693us/step - loss: 0.5244 - acc: 0.7514 - val_loss: 0.3834 - val_acc: 0.8278
25000/25000 [==============================] - 15s 615us/step - loss: 0.3257 - acc: 0.8593 - val_loss: 0.3689 - val_acc: 0.8368
25000/25000 [==============================] - 15s 614us/step - loss: 0.2602 - acc: 0.8942 - val_loss: 0.3909 - val_acc: 0.8303
25000/25000 [==============================] - 15s 618us/step - loss: 0.2078 - acc: 0.9179 - val_loss: 0.4482 - val_acc: 0.8215
25000/25000 [==============================] - 15s 619us/step - loss: 0.1639 - acc: 0.9368 - val_loss: 0.5313 - val_acc: 0.8106

 

五、Reference

 

1. zhuanlan.zhihu.com/p/47282410

 

欢迎大家关注小宋公众号**《极简 AI》**带你学深度学习:

 

基于深度学习的理论学习与应用开发技术分享,笔者会经常分享深度学习干货内容,大家在学习或者应用深度学习时,遇到什幺问题也可以与我在上面交流知无不答。

 

出自 CSDN 博客专家 & 知乎深度学习专栏作家 @小宋是呢

Be First to Comment

发表评论

邮箱地址不会被公开。 必填项已用*标注