自然语言处理入门7——注意力机制

注意力机制是一个非常伟大的机制,说它是现代人工智能的基石也不为过。因为现在大语言模型,如chatgpt,deepseek这种,底层用到了Transformer,而Transformer是一种部件级别的基础网络模型,类似于卷积神经网络CNN和循环神经网络RNN的地位。Transformer正是基于注意力机制提出的,在一篇举世闻名的论文《Attention is all you need》中首次被提出。这次我们来看看注意力机制的原理,并用一个例子来说明。

关于注意力机制的文章我也写过几篇了,之前介绍的不够通俗,《深度学习进阶:自然语言处理》这本书中的介绍我觉得很通俗,也很清楚,所以写出来和大家分享一下。

我们上一篇文章介绍了序列到序列的生成模型Seq2seq,Seq2seq底层是两个LSTM组合的,一个成为编码器(Encoder),一个成为解码器(Decoder),训练时候叫做解码器,实际使用的时候就叫做生成器(generator)。结构如下:

编码器先对输入进行Embedding,把单词表示成向量,然后通过LSTM,得到一个隐藏信息输出h。解码器把输入单词Embedding后,把得到的单词向量合并编码器输出的隐藏信息h,一起输入到解码器的LSTM中,输出的隐藏信息再输出到分类层affine,最后通过softmax得到输出单词的概率。从而计算损失值。

但是这种解码器,只有解码器的第一个节点获取到了编码器输出隐藏信息,可能造成信息的丢失,所以前一篇文章中有一种改进方法叫做:Peeky。

Peeky把编码器输出的隐藏信息分别传递到解码器的每一个节点中,使得每个节点都可以获取到编码器的完整隐藏信息,其余结构完全相同,该方法效果得到了明显提升。

注意力机制,其实就是在此基础上进一步的改进。之前的模型只使用了编码器输出隐藏信息hs的最后一行,现在把整个hs都进行输出,进行某种计算。简单来说,它把编码器输出的隐藏信息和解码器的LSTM输出的隐藏信息做了某种计算,把这个结果信息输入到了分类层affine中,再进行分类。示意图如下:

这个"某种计算"就是注意力机制Attention。

注意力机制的计算分为两部分,首先计算Attention Weight,再进行Weight Sum计算。其实就是将hs和h去求一个相似度,这个相似度就是a,然后根据a去对hs求一个加权和,得到weight sum。书中的例子特别好:

如果编码器输出的hs是表示几个单词的向量,那么如果有个向量a可以表示每个单词的重要性,那么就可以就可以用hs和a求一个加权和,得到的结果就是上下文向量,用这个上下文向量去进行分类,会比较好,因为他采用了上下文信息,并且根据不同的权重进行了考虑。

好了,现在的问题是怎么求得这个重要性向量a,最简单的方法就是直接求相似度,编码器隐藏向量hs和解码器LSTM输出的隐藏信息h直接的相似度,用内积实现即可。背后的意义就是用数值表示这个h在多大的程度上和hs的各个单词向量"相似",越相似则权重越高。

对内积结果用softmax计算一下,把结果转换成0到1之间的概率值。就是我们前面要的向量a了。

真正的结构就是这样:

它把编码器输出的隐藏信息和解码器的LSTM输出的隐藏信息求了一个相似度,得到了两个向量的相似度,然后用这个相似度来编码器的隐藏信息hs做加权和,得到上下文的权重,把这个信息输入到了分类层affine中,再进行分类。

下面我们来实现一下代码,首先是AttentionWeight,输入编码器隐藏信息hs,以及解码器LSTM输出的隐藏信息h。

python 复制代码
class AttentionWeight:
    def __init__(self):
        self.params, self.grads = [], []
        self.softmax = Softmax()
        self.cache = None
        
    def forward(self, hs, h):
        N,T,H = hs.shape
        hr = h.reshape(N,1,H).repeat(T,axis=1)
        # hr = h.reshape(N,1,H) # 也可以用广播机制实现
        t = hs*hr
        s = np.sum(t,axis=2)
        a = self.softmax.forward(s)
        self.cache = (hs,hr)
        return a
    
    def backward(self, da):
        hs, hr = self.cache
        N,T,H = hs.shape
        ds = self.softmax.backward(da)
        dt = ds.reshape(N,T,1).repeat(H, axis=2)
        dhs = dt*hr
        dhr = dt*hs
        dh = np.sum(dhr,axis=1)
        return dhs,dh

WeightSum根据AttentionWeight得到的相似度a,以及编码器输出hs,来计算上下文向量。

python 复制代码
class WeightSum:
    def __init__(self):
        self.params, self.grads = [], []
        self.cache = []
        
    def forward(self, hs, a):
        N,T,H = hs.shape
        ar = a.reshape(N,T,1).repeat(H, axis=2)
        t = hs*ar
        c = np.sum(t, axis=1)
        self.cache = (hs, ar)
        return c
    
    def backward(self, dc):
        hs, ar = self.cache
        N,T,H = hs.shape
        # sum的反向传播
        dt = dc.reshape(N,1,H).repeat(T,axis=1) 
        dar = dt*hs
        dhs = dt*ar
        # repeat的反向传播
        da = np.sum(dar,axis=2) 
        return dhs,da

这里面就是要注意,sum运算的反向传播是repeat,repeat运算的反向传播是sum。下面是把WeightSum和AttentionWeight合并为一个Attention的层的代码。

python 复制代码
class Attention:
    def __init__(self):
        self.params, self.gards = [], []
        self.attention_weight_layer = AttentionWeight()
        self.weight_sum_layer = WeightSum()
        self.attention_weight = None
        
    def forward(self, hs, h):
        a = self.attention_weight_layer.forward(hs,h)
        out = self.weight_sum_layer.forward(hs,a)
        self.attention_weight = a
        return out
    
    def backward(self, dout):
        dhs0,da = self.weight_sum_layer.backward(dout)
        dhs1,dh = self.attention_weight_layer.backward(da)
        dhs = dhs0+dhs1
        return dhs,dh

TimeAttention就是在时间序列上做Attention。

python 复制代码
class TimeAttention:
    def __init__(self):
        self.params, self.grads = [], []
        self.layers = None
        self.attention_weights = None
        
    def forward(self, hs_enc, hs_dec):
        N,T,H = hs_dec.shape
        out = np.empty_like(hs_dec)
        self.layers = []
        self.attention_weights = []
        for t in range(T):
            layer = Attention()
            out[:,t,:] = layer.forward(hs_enc, hs_dec[:,t,:])
            self.layers.append(layer)
            self.attention_weights.append(layer.attention_weight)
        return out
    
    def backward(self, dout):
        N,T,H = dout.shape
        dhs_enc = 0
        dhs_dec = np.empty_like(dout)
        for t in range(T):
            layer = self.layers[t]
            dhs,dh = layer.backward(dout[:,t,:])
            dhs_enc += dhs
            dhs_dec[:,t,:] = dh
        return dhs_enc,dhs_dec    

最后利用TimeAttention构建编码器和解码器,最终合并编码器和解码器得到基于注意力机制的Seq2seq模型。其他如LSTM等模块和原来的Seq2seq模型一样的。

编码器和之前的编码器几乎一致,只是返回的时候返回了整个hs,而原来只是返回了hs的最后一行。

python 复制代码
class AttentionEncoder(Encoder):
    def forward(self, xs):
        xs = self.embed.forward(xs)
        hs = self.lstm.forward(xs)
        return hs
    
    def backward(self, dhs):
        dout = self.lstm.backward(dhs)
        dout = self.embed.backward(dout)
        return dout

解码器和生成器,解码器结合了注意力层Attention。

python 复制代码
class AttentionDecoder:
    def __init__(self, vocab_size, wordvec_size, hidden_size):
        V,D,H = vocab_size, wordvec_size, hidden_size
        rn = np.random.randn
        # 初始化权重和偏置
        embed_W = (rn(V,D)/100).astype('f')
        lstm_Wx = (rn(D,4*H)/np.sqrt(D)).astype('f')
        lstm_Wh = (rn(H,4*H)/np.sqrt(H)).astype('f')
        lstm_b = np.zeros(4*H).astype('f')
        affine_W = (rn(2*H,V)/np.sqrt(2*H)).astype('f')
        affine_b = np.zeros(V).astype('f')
        # 模型的每层
        self.embed = TimeEmbedding(embed_W)
        self.lstm = TimeLSTM(lstm_Wx,lstm_Wh,lstm_b,stateful=True)
        self.attention = TimeAttention()
        self.affine = TimeAffine(affine_W, affine_b)
        layers = [self.embed,self.lstm,self.attention,self.affine]
        # 初始化参数和梯度
        self.params, self.grads = [], []
        for layer in layers:
            self.params += layer.params
            self.grads += layer.grads
            
    def forward(self, xs, enc_hs):
        h = enc_hs[:,-1]
        self.lstm.set_state(h)

        out = self.embed.forward(xs)
        dec_hs = self.lstm.forward(out)
        c = self.attention.forward(enc_hs, dec_hs)
        out = np.concatenate((c,dec_hs), axis=2)
        score = self.affine.forward(out)
        return score

    def backward(self, dscore):
        dout = self.affine.backward(dscore)
        N, T, H2 = dout.shape
        H = H2 // 2
        dc, ddec_hs0 = dout[:,:,:H], dout[:,:,H:]
        denc_hs, ddec_hs1 = self.attention.backward(dc)
        ddec_hs = ddec_hs0 + ddec_hs1
        dout = self.lstm.backward(ddec_hs)
        dh = self.lstm.dh
        denc_hs[:, -1] += dh
        self.embed.backward(dout)
        return denc_hs


    def generate(self, enc_hs, start_id, sample_size):
        sampled = []
        sample_id = start_id
        h = enc_hs[:, -1]
        self.lstm.set_state(h)
        for _ in range(sample_size):
            x = np.array([sample_id]).reshape((1, 1))
            out = self.embed.forward(x)
            dec_hs = self.lstm.forward(out)
            c = self.attention.forward(enc_hs, dec_hs)
            out = np.concatenate((c, dec_hs), axis=2)
            score = self.affine.forward(out)
            sample_id = np.argmax(score.flatten())
            sampled.append(sample_id)

        return sampled

两者结合在一起构成了AttentionSeq2seq模型。

python 复制代码
class AttentionSeq2seq(Seq2seq):
    def __init__(self, vocab_size, wordvec_size, hidden_size):
        args = vocab_size, wordvec_size, hidden_size
        self.encoder = AttentionEncoder(*args)
        self.decoder = AttentionDecoder(*args)
        self.softmax = TimeSoftmaxWithLoss()
        self.params = self.encoder.params + self.decoder.params
        self.grads = self.encoder.grads + self.decoder.grads

下面做了一个实验,用于将各种日期格式转变成标准日期格式来测试一下AttentionSeq2seq的效果。这里的输入就是各种格式的日期数据,输出就是标准的日期格式,编码器将输入数据进行编码,得到的结果通过解码器进行解码,训练10个epoch,在训练2个epoch后,精度就达到了99.9%了。

对于训练过后的模型,我们可以可视化注意力机制,横轴表示输入的信息,纵轴表述输出的标签,高亮由训练后的模型Attention来决定。

可以看到,在这个测试数据中,输入的是"FRIDAY, AUGUST 26, 1983",FRIDAY没有与之对应的单词,所以亮色显示在横线-处,AUGUEST显示最亮的地方对应的纵轴正是8,26的高亮部分对应的纵轴也是26,1983的高亮部分对应到纵轴上也是1983,可以看到注意力都被正确的表示出来了。

相关推荐
ybdesire10 分钟前
Jinja2模板引擎SSTI漏洞
网络·人工智能·安全·web安全·大模型·漏洞·大模型安全
cnbestec1 小时前
3D 视觉赋能仓储精准高效:ID Logistics 与 Stereolabs 的创新合作之旅
人工智能·3d
AORO_BEIDOU1 小时前
遨游科普:三防平板除了三防特性?还能实现什么功能?
大数据·人工智能·科技·智能手机·电脑·信息与通信
CPPAlien2 小时前
Python for MLOps - 第一阶段学习笔记
python
AI大模型顾潇2 小时前
[特殊字符] AI 大模型的 Prompt Engineering 原理:从基础到源码实践
运维·人工智能·spring·自然语言处理·自动化·大模型·prompt
C灿灿数模2 小时前
2025mathorcup妈妈杯数学建模挑战赛C题:汽车风阻预测,详细思路,模型,代码更新中
人工智能·算法·ffmpeg
喵~来学编程啦2 小时前
【模块化编程】Python文件路径检查、跳转模块
开发语言·python
Tester_孙大壮2 小时前
OCR技术与视觉模型技术的区别、应用及展望
人工智能·ai·ocr
果冻人工智能2 小时前
关于AI:记忆、身份和锁死
人工智能
小研学术2 小时前
AI文生图工具推荐
人工智能·ai·文生图·多模态·deepseek·ai生图