第1关:Attention注意力机制
任务描述
本关任务:了解循环神经网络中的Attention注意力机制并回答相关问题
相关知识
为了完成本关任务,你需要掌握:1.Attention注意力机制基本知识
什么是Attention注意力机制
在计算能力有限的情况下,注意力机制(Attention Mechanism)作为一种资源分配方案,将有限的计算资源用来处理更加重要的信息,是解决信息超载问题的主要手段。当用神经网络来处理大量的输入信息时,也可以借鉴人脑的注意力机制,只选择一些关键的信息输入进行处理,来提高神经网络的效率。
在目前的神经网络模型中,我们可以将最大汇聚(Max Pooling)、门控(Gating)机制近似地看作自上而下的基于显著性的注意力机制。除此之外,自上而下的聚焦式注意力也是一种有效的信息选择方式。以阅读理解任务为例,给定一篇很长的文章,然后就此文章的内容进行提问。提出的问题只和段落中的一两个句子相关,其余部分都是无关的。为了减小神经网络的计算负担,只需要把相关的片段挑出来让后续的神经网络来处理,而不需要把所有的文字内容都输入给神经网络。
seq2seq中的Attention注意力机制
Attention注意力机制常用于seq2seq模型当中,下图为一个简单的Encoder-Decoder的seq2seq机器翻译模型,在传统的seq2seq模型中输出序列y对输入序列x1,x2,x3没有区分,没有辨识度。
由于没有引用引入注意力,模型对于输入的辨识度较低,我们在seq2seq机器翻译模型中引入Attention注意力,每个输出词y都受到输入x1,x2,x3影响的权重不同,这个权重是通过Attention注意力进行计算的,因此可以把Attention机制看作注意力分配系数,下图为引入注意力的seq2seq机器翻译模型。
下面将结合上图详细讲解Attension注意力实现过程 (1) 首先利用双向RNN结构得到隐层状态(h1, h2, …, hn) (2) 如当前已经decoder到隐层St-1,接下来计算每一个输入位置hj对当前位置i的影响
这里attention的计算方式可以有很多种,点乘形式、加权点乘形式或求和形式 (3) 对eij进行softmax将其normalization得到attention权重分布,如下所示
(4) 利用aij进行加权求和得到相应的context vector
(5) 计算最终输出
Soft Attention与Hard Attention
Soft atttention是参数化的,是可导的可嵌入到模型中直接训练,上述seq2seq中的即是Soft Attention;Hard Attention是一个随机的过程,为了实现梯度的反向传播,需要采用蒙特卡洛采样的方法来估计模块的梯度,目前更多的研究和应用还是更倾向于使用Soft Attention,因为其可以直接求导,进行梯度反向传播.Hard Attention的一个最大的缺点是基于最大采样的方式来选择信息,使得最终的损失函数与注意力分布之间的函数关系不可导,无法使用反向传播算法进行训练,因此,Hard Attention通常需要使用强化学习来进行训练。
Self-Attention Model
基于卷积或循环网络的序列编码都是一种局部的编码方式,只建模了输入信息的局部依赖关系,虽然循环网络理论上可以建立长距离依赖关系,但是由于信息传递的容量及梯度消失问题,实际上也只能建立短距离依赖关系。 如果想要建立输入序列之间的长距离依赖关系,可以使用以下两种方法:一种方法是增加网络的层数,通过一个深层网络来获取远距离的信息交互;另一种方法是使用全连接网络。全连接网络是一种非常直接的建模远距离依赖的模型,但是无法处理变长的输入序列。不同的输入长度,其连接权重的大小也是不同的。这时我们就可以利用注意力机制来“动态”地生成不同连接的权重,这就是自注意力模型(Self-Attention Model)
下图为机器翻译任务自注意力模型图
假如输入序列是"Thinking Machines",x1,x2就是对应地"Thinking"和"Machines"添加过位置编码之后的词向量,然后词向量通过三个权值矩阵WQ,WK,WV ,转变成为计算Attention值所需的Query,Keys,Values向量。
得到Q,K,V向量之后,接下来就是计算Attention值,下图为计算Attention的过程图
计算的步骤如下:
步骤1: 输入序列中每个单词之间的相关性得分,计算相关性得分可以使用点积法,就是用Q中每一个向量与K中每一个向量计算点积。
步骤2: 对于输入序列中每个单词之间的相关性得分进行归一化,归一化的目的主要是为了训练时梯度能够稳定。
步骤3: 通过softmax函数,将每个单词之间的得分向量转换成[0,1]之间的概率分布,同时更加凸显单词之间的关系。经过softmax后,score转换成一个值分布在[0,1]之间的概率分布矩阵。
步骤4: 根据每个单词之间的概率分布,然后乘上对应的Values值,α与V进行点积。
第1关任务——选择题
1、深度学习模型引入注意力机制的主要原因有: (A)
A、有利于将有限的计算资源用来处理更重要信息。
B、减少模型参数量。
C、防止梯度消失。
D、防止梯度爆炸。
2、Attention计算的形式有哪几种。 (ABC)
A、点乘形式
B、加权点乘形式
C、求和形式
D、求导形式
3、seq2seq机器翻译模型中引入Attention的原因是? (B)
A、建立词向量之间的长依赖关系。
B、使输入词向量更加有区分度、辨识度。
C、减少模型参数量
D、加快训练过程。
4、Self-Attention主要是为了解决什么样的问题? (C)
A、梯度消失问题
B、梯度爆炸问题
C、长距离依赖问题
D、收敛速度问题
5、机器翻译任务中的Self-Attention的核心思想是? (A)
A、利用词向量生成的查询向量Q建立与其他词向量生成的键向量K之间的权重关系,从而加权到值向量V形成词向量之间的注意力。
B、利用词向量生成的键向量K建立与其他词向量生成的值向量V之间的权重关系,从而加权到查询向量Q形成词向量之间的注意力。
C、利用词向量生成的查询向量Q建立与其他词向量生成的查询向量Q之间权重关系,形成词向量之前的注意力。
D、利用词向量生成的值向量Q建立与其他词向量生成的值向量Q之间权重关系,形成词向量之前的注意力。
第2关:Seq2Seq
任务描述
本关任务:使用pytorch框架编写一个Seq2Seq模型
相关知识
为了完成本关任务,你需要掌握:1.了解常见的Seq2Seq模型结构 2.如何使用pytorch框架编写一个简单Seq2Seq模型代码。
什么是Seq2Seq
Seq2Seq,全称Sequence to Sequence。它是一种通用的Encoder——Decoder框架,可用于机器翻译、文本摘要、会话建模、图像字幕等场景中。这里看看常见的encoder-decoder结构,基本思想就是利用两个RNN,一个RNN作为encoder,另一个RNN作为decoder。encoder负责将输入序列压缩成指定长度的向量,这个向量就可以看成是这个序列的语义,这个过程称为编码,如下图,获取语义向量最简单的方式就是直接将最后一个输入的隐状态作为语义向量C。也可以对最后一个隐含状态做一个变换得到语义向量,还可以将输入序列的所有隐含状态做一个变换得到语义变量。
而decoder则负责根据语义向量生成指定的序列,这个过程也称为解码,如下图,最简单的方式是将encoder得到的语义变量作为初始状态输入到decoder的RNN中,得到输出序列。可以看到上一时刻的输出会作为当前时刻的输入,而且其中语义向量C只作为初始状态参与运算,后面的运算都与语义向量C无关。
decoder处理方式还有另外一种,就是语义向量C参与了序列所有时刻的运算,如下图,上一时刻的输出仍然作为当前时刻的输入,但语义向量C会参与所有时刻的运算。
简单的Seq2Seq模型实现 :
略
第2关任务代码
平台会对你编写的代码进行测试:
测试输入:"highh"
预期输出:"low"
测试输入:"kingh"
预期输出:"queen"
提示: 模型讲解中各模块的定义。
开始你的任务吧,祝你成功!
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torch.autograd import Variable
dtype = torch.FloatTensor
char_list = [c for c in 'SEPabcdefghijklmnopqrstuvwxyz']
char_dic = {n: i for i, n in enumerate(char_list)}
seq_data = [['man', 'women'], ['black', 'white'], ['king', 'queen'], ['girl', 'boy'], ['up', 'down'], ['high', 'low']]
seq_len = 8
n_hidden = 128
n_class = len(char_list)
batch_size = len(seq_data)
##########Begin##########
#对数据进行编码部分
def make_batch(seq_data):
batch_size = len(seq_data)
input_batch,output_batch,target_batch = [],[],[]
for seq in seq_data:
for i in range(2):
seq[i] += 'P' * (seq_len - len(seq[i]))
input = [char_dic[n] for n in seq[0]]
output = [char_dic[n] for n in ('S' + seq[1])]
target = [char_dic[n] for n in (seq[1] + 'E')]
input_batch.append(np.eye(n_class)[input])
output_batch.append(np.eye(n_class)[output])
target_batch.append(target)
return Variable(torch.Tensor(input_batch)),Variable(torch.Tensor(output_batch)),Variable(torch.LongTensor(target_batch))
input_batch,output_batch,target_batch=make_batch(seq_data)
##########End##########
##########Begin##########
#模型类定义
class Seq2Seq(nn.Module):
def __init__(self):
super(Seq2Seq,self).__init__()
self.encoder = nn.RNN(input_size = n_class,hidden_size = n_hidden)
self.decoder = nn.RNN(input_size = n_class,hidden_size = n_hidden)
self.fc = nn.Linear(n_hidden,n_class)
def forward(self,enc_input,enc_hidden,dec_input):
enc_input = enc_input.transpose(0,1) #需要将向量的第一第二维度进行转换
dec_input = dec_input.transpose(0,1)
_,h_states = self.encoder(enc_input,enc_hidden)
outputs,_ = self.decoder(dec_input,h_states)
outputs = self.fc(outputs)
return outputs
##########End##########
model = Seq2Seq()
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
##########Begin##########
#模型训练过程
for epoch in range(5001):
hidden = Variable(torch.zeros(1,batch_size,n_hidden))
optimizer.zero_grad()
outputs = model(input_batch,hidden,output_batch)
outputs = outputs.transpose(0,1)
loss = 0
for i in range(batch_size):
loss += criterion(outputs[i],target_batch[i])
# if (epoch % 500) == 0:
# print('epoch:{},loss:{}'.format(epoch,loss))
loss.backward()
optimizer.step()
##########End##########
##########Begin##########
#模型验证过程函数
def translated(word):
input_batch,output_batch,_ = make_batch([[word,'P'*len(word)]])
hidden = Variable(torch.zeros(1,1,n_hidden))
outputs = model(input_batch,hidden,output_batch)
predict = outputs.data.max(2,keepdim=True)[1]
decode = [char_list[i] for i in predict]
end = decode.index('P')
translated = ''.join(decode[:end])
print(translated)
##########End##########
translated('highh')
translated('kingh')