究竟Self-Attention结构是怎样的?

一、self-attention概念详解
了解了模型大致原理,我们可以详细的看一下究竟self-attention结构是怎样的。其基本结构如下
对于self-attention来讲,q(query), k(key), v(value)三个矩阵均来自同一输入,首先我们要计算q与k之间的点乘,然后为了防止其结果过大,会除以一个尺度标度,其中为一个query和key向量的维度。再利用softmax操作将其结果归一化为概率分布,然后再乘以矩阵v就得到权重求和的表示。该操作可以表示为
这里可能比较抽象,我们来看一个具体的例子(图片来源于https://jalammar.github.io/illustrated-transformer/),该博客讲解的极其清晰,强烈推荐),假如我们要翻译一个词组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实现自定义网络层。需要实现以下三个方法:(注意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 sequencefrom keras.datasets import imdbfrom matplotlib import pyplot as pltimport pandas as pdfrom keras import backend as kfrom keras.engine.topology import layerclass 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): # 为该层创建一个可训练的权重 #inputs.shape = (batch_size, time_steps, seq_len) self.kernel = self.add_weight(name='kernel', shape=(3,input_shape[2], self.output_dim), initializer='uniform', trainable=true) super(self_attention, self).build(input_shape) # 一定要在最后调用它 def call(self, x): 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])) qk = qk / (64**0.5) qk = k.softmax(qk) print(qk.shape,qk.shape) v = k.batch_dot(qk,wv) return v 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]))qk = qk / (64**0.5)qk = k.softmax(qk)print(qk.shape,qk.shape)v = k.batch_dot(qk,wv)
这里qk = qk / (64**0.5) 是除以一个归一化系数,(64**0.5)是笔者自己定义的,其他文章可能会采用不同的方法。
三、训练网络
项目完整代码如下,这里使用的是keras自带的imdb影评数据集。
#%%from keras.preprocessing import sequencefrom keras.datasets import imdbfrom matplotlib import pyplot as pltimport pandas as pdfrom keras import backend as kfrom keras.engine.topology import layerclass 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): # 为该层创建一个可训练的权重 #inputs.shape = (batch_size, time_steps, seq_len) self.kernel = self.add_weight(name='kernel', shape=(3,input_shape[2], self.output_dim), initializer='uniform', trainable=true) super(self_attention, self).build(input_shape) # 一定要在最后调用它 def call(self, x): 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])) qk = qk / (64**0.5) qk = k.softmax(qk) print(qk.shape,qk.shape) v = k.batch_dot(qk,wv) return v def compute_output_shape(self, input_shape): return (input_shape[0],input_shape[1],self.output_dim)max_features = 20000print('loading data...')(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')#%%数据归一化处理maxlen = 64print('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)#%%batch_size = 32from keras.models import modelfrom keras.optimizers import sgd,adamfrom keras.layers import *from attention_keras import attention,position_embeddings_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)print(model.summary())# try using different optimizers and different optimizer configsopt = adam(lr=0.0002,decay=0.00001)loss = 'categorical_crossentropy'model.compile(loss=loss, optimizer=opt, metrics=['accuracy'])#%%print('train...')h = model.fit(x_train, y_train, batch_size=batch_size, epochs=5, 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)plt.legend()plt.show()#model.save(imdb.h5)
四、结果输出
(tf_gpu) d:filesdatasprjspython f_keras ransfromerdemo>c:/files/apps/ruanjian/miniconda3/envs/tf_gpu/python.exe d:/files/datas/prjs/python/tf_keras/transfromerdemo/train.1.pyusing tensorflow backend.loading data...25000 train sequences25000 test sequencespad sequences (samples x time)x_train shape: (25000, 64)x_test shape: (25000, 64)wq.shape (?, 64, 128)k.permute_dimensions(wk, [0, 2, 1]).shape (?, 128, 64)qk.shape (?, 64, 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=================================================================total params: 2,609,410trainable params: 2,609,410non-trainable params: 0_________________________________________________________________nonetrain...train on 25000 samples, validate on 25000 samplesepoch 1/525000/25000 [==============================] - 17s 693us/step - loss: 0.5244 - acc: 0.7514 - val_loss: 0.3834 - val_acc: 0.8278epoch 2/525000/25000 [==============================] - 15s 615us/step - loss: 0.3257 - acc: 0.8593 - val_loss: 0.3689 - val_acc: 0.8368epoch 3/525000/25000 [==============================] - 15s 614us/step - loss: 0.2602 - acc: 0.8942 - val_loss: 0.3909 - val_acc: 0.8303epoch 4/525000/25000 [==============================] - 15s 618us/step - loss: 0.2078 - acc: 0.9179 - val_loss: 0.4482 - val_acc: 0.8215epoch 5/525000/25000 [==============================] - 15s 619us/step - loss: 0.1639 - acc: 0.9368 - val_loss: 0.5313 - val_acc: 0.8106

运放的输入轨、输出轨及电源轨
传感网的主要应用及实例
一款全自动电饭煲系统的设计与实现
使用ATECLOUD电源测试系统测试启动延迟和上升/下降时间
什么是分流电阻_分流电阻的计算方法
究竟Self-Attention结构是怎样的?
加湿器水箱密封性防水测试是怎么做的
美国疫情愈演愈烈 苹果宣布向欧美医护人员捐赠900万个N95口罩
华为“新物种”一鸣惊人,五分钟便卖出三十万台
联发科子公司Gaintech一年内累积出售唯捷创芯6.66%股权
买5G手机必知的3个基本问题
软通动力“梧桐·招聘”入选中国首部大模型应用权威案例集
超低温线性NTC温度传感器(HN110)
使用超高速闪存微控制器实现快速内存传输
比特币上涨的多种理论解答
宁德新能源柔性电池 专为可穿戴设备设计
环形导轨,链节式环形导轨输送线,高频快节拍输送线,不用二次定位的环形输送线,用于自动化组装线的输
苹果今年新款旗舰或命名iPhone12S
罗姆BM1Z002FJ-EVK-001评估板测评
气象监测站:用科技感知气象变化