Attention is all you need ---阅读笔记


     Transformer模型打破了传统的RNN的时序限制,引入自注意力机制,可以更好的对句子进行编码,关于模型的理论部分已经有很多介绍了,本文主要介绍transformer的各个部分在实现过程中遇到的问题,并展示具体的实现代码。Transformer模型的结构如下图所示:

transformer模型结构图

实验环境:

  • tensorflow == 1.13
  • python == 3.6

实验细节:

1.输入层编码

  • 无论是编码器还是解码器,输入层编码=word embedding + position embedding

  • word embedding可以是预训练好的词向量,也可以是随机初始化的向量

  • position embedding是对应的位置编码,transformer模型打破了时序的限制,
    可以进行并行计算,这样虽然提升了训练速度,但是如果仅利用词向量进行编码,不同位置的相同词的编码是一样,显然这样做是不合理的,因为在文本中不同位置的词往往对语义有不同的影响。因此,在该模型中引入了位置编码。

  • 位置编码的具体实现:

def get_position(seq_length, dim):
    """pe是一个seq_length*dim的矩阵,表明句子中每个单词每个维度的位置编码
    位置编码在句子中每个位置每个维度的编码一致
    pe是一个句子的位置编码"""
    pe = np.zeros((seq_length, dim))
    for i in range(seq_length):
        for j in range(dim):
            if j % 2 == 0:
                pe[i][j] = math.sin(i/(10000**(j/dim)))
            else:
                pe[i][j] = math.cos(i/(10000**((j-1)/dim)))
    tf.logging.info("*********************the position matrix have been created!!")
    tf.logging.info(pe)

    return pe   

2.多头注意力层

  • 注意力机制认为句子中每个单词对句子的编码的贡献(权重)不同,利用
    每个句子中每个单词之间的相似性作为权重,进行加权平均。
  • 具体实现过程:
    1. 利用输入层的输入生成q,k,v(相当于添加一个全连接层)
    2. 划分为多层注意力(增加不同语义空间)
    3. 考虑padding那部分的权重影响,padding主要包括输入本身mask的部分和句子mask的部分(解码器理论上t时刻的输出只能看到t时刻以前的结果,因此需要将t时刻以后的词语mask)
  • transformer模型中主要包括三种注意力:
    1. 编码器-编码器自注意力: 只需要考虑句子本身mask的那部分
    2. 编码器-解码器注意力:需要考虑句子本身mask的部分和解码器中t时刻以后的部分
    3. 解码器-解码器注意力:只需要考虑句子本身的mask部分
  • mask的技巧:利用一个特别小的数代替该位置的数值,这样在进行归一(softmax)的时候,得到的权重几乎趋近于0.
  • 具体实现过程:
def multi_attention(inputs, masks, num_head, type, encode_output=None):
    """
    inputs: [batch_size,seq_length,num_units]
    masks: [batch_size,seq_length]
    num_head: the number of head in model'
    type:encode,decode,encode-decode三种注意力类型
    return:  [batch_size,seq_length,num_units]
    """
    min_num = -2**32 + 1
    emb_dim = inputs.get_shape().as_list()
    print("*********emb_dim: ", emb_dim)
    # batch_size = emb_dim[0]
    seq_length = emb_dim[1]
    dim = emb_dim[2]
    if dim % num_head:
        raise ValueError('please make the dim/d_head is the int!! ')

    # 1.初始化q,k,v矩阵,相当于添加一个全连接层,将输入的维度由[batch_size,seq_length,dim]变为[batch_size,seq_length,dim//num_head]
    q = tf.layers.dense(inputs, units=dim)  # 全连接层,权重共享,对每个单词进行全连接,实现时所有位置单词是并行的
    k = tf.layers.dense(inputs, units=dim)
    v = tf.layers.dense(inputs, units=dim)
    tf.logging.info(q)

    # 只有编码器-解码器的权重的查询值与key,value不一样
    if type == 'encode-decode':
        k = encode_output  # 此时k和v是encode的输出,q是decode的输入
        v = encode_output

    # 切分为多头,q_:[batch_size*num_head,seq_length,dim/num_head]
    q_ = tf.concat(tf.split(q, num_head, axis=2), axis=0)
    k_ = tf.concat(tf.split(k, num_head, axis=2), axis=0)
    v_ = tf.concat(tf.split(v, num_head, axis=2), axis=0)
    tf.logging.info(q_)
    
    # scale q_
    depth = dim // num_head
    q_ *= (depth ** -0.5)

    # 计算q与k的相似度,sim:[batch_size*num_head,seq_length,seq_length]
    sim = tf.matmul(q_, k_, transpose_b=True)
    sim = sim / (k_.get_shape().as_list()[-1]**0.5)

    # 利用mask乘以一个特别小的数,避免填补的值对权重的影响。mask有padding mask和seq mask两种
    # 1.padding mask是句子长度不到固定长度的填补标记
    masks = tf.to_float(masks)
    pad_mask = tf.tile(tf.expand_dims(masks, axis=1), [1, seq_length, 1])  # [batch_size, seq_length, seq_length]
    multi_pad_mask = tf.tile(pad_mask, [num_head, 1, 1])  # [batch_size*num_head, seq_length, seq_length]
    tf.logging.info(multi_pad_mask)

    # 2.seq mask是解码器中需要把t时刻后的单词进行覆盖,生成一个下三角
    seq_mask = tf.ones_like(masks, dtype=tf.float32)
    seq_mask = tf.linalg.LinearOperatorLowerTriangular(seq_mask).to_dense()  # 将张量转换为下三角矩阵
    multi_seq_mask = tf.tile(tf.expand_dims(seq_mask, axis=1), [1, seq_length, 1])
    multi_seq_mask = tf.tile(multi_seq_mask, [num_head, 1, 1])

    # encode只需要padding mask, decode需要padding mask 和 seq
    padding = tf.ones_like(sim) * min_num  # 创造一个与sim具有相同形状的张量,元素全为最小数
    if type == 'encode' or type == 'encode-decode':
        sim = tf.where(tf.equal(multi_pad_mask, True), sim, padding)  # 如果为真,就保留权重,否则利用最小数代替
    elif type == 'decode':
        decode_mask = multi_pad_mask * multi_seq_mask
        sim = tf.where(tf.equal(decode_mask, True), sim, padding)
    else:
        raise ValueError("please input encode, decode or encode-decode!!!")

    # 归一化,确保权重之和为1
    sim = tf.nn.softmax(sim)
    
    # dropout, 添加
    if mode == 'train':
        sim = tf.layers.dropout(sim, rate=0.3, training=True)
        
    # 输出output
    output = tf.matmul(sim, v_)         # 输出output:[batch_size*num_head,seq_length,dim//num_head]
    output = tf.concat(tf.split(output, num_head, axis=0), axis=2)  # 输出output:[batch_size,seq_length,dim]
    
    # linear, 多头注意层的最后输出需要添加一个线性变换
    output = tf.layers.dense(output, units=dim, use_bias=False)

    return output

3.layer norm

  • layer norm:固定一层神经元的均值和方差

  • 为什么要进行layer norm?

    神经网络的权值高度依赖于前一层的输出,当这些输出高度相关时,难以快速收敛。
    
  • 计算公式:

    1. eplsion = 1e-8
    2. norm = (input - mean) / ((var + eplsion)**0.5)
    3. output = alpha * norm + beta
  • 具体实现过程:

def layer_norm(inputs, eplsion=1e-8):
    """
    layer norm
        inputs: [batch_size,seq_length,num_units]
        eplsion:防止分母为0
        return:  [batch_size,seq_length,num_units]
    """
    para = inputs.get_shape().as_list()[-1]
    alpha = tf.Variable(tf.ones(para))
    beta = tf.Variable(tf.zeros(para))
    mean, var = tf.nn.moments(inputs, axes=[-1], keep_dims=True)
    norm = (inputs - mean) / ((var + eplsion) ** 0.5)

    # 数组中*是按照对应位置相乘的
    output = alpha * norm + beta  # 相当于alpha乘以[batch_size, seq_length], 然后乘以norm:[batch_size,seq_length,dim]

    return output

4.残差连接

  • 计算公式:output = input + output(innput)
  • 确保反向传播求偏导时,梯度至少为1

5.全连接层(FFN)

  • FFN层相当于一个Relu激活层和一个线性激活层或两个大小为1的一维卷积层
  • 计算公式: FFN(x) = w_2*(max(0, w_1*x+b_1)) + b_2
  • 具体实现过程:
def forward_feed(inputs, num_units_list):
    """前馈全连接层FFN(x) = w_2*(max(0, w_1*x+b_1)) + b_2
    相当于两个大小为1的一维卷积层,二者选择一种方式实现即可
    inputs = word Embedding + position Embedding , [batch_size,seq_length,word_dim]
    num_units_list: contains two int number ,for example: [2048, 512]
    return: [batch_size,seq_length,word_dim]
    """
    # 1.全连接层实现
    inner = tf.layers.dense(inputs, units=num_units_list[0], activation=tf.nn.relu)
    output = tf.layers.dense(inner, units=num_units_list[1])  # 没有激活函数就是线性激活函数层

    # 2.卷积层实现
    # 相当于 [batch_size*sequence_length,num_units]*[num_units,ffn_dim],在reshape成[batch_size,sequence_length,num_units]
    # paras = {"inputs": inputs, "kernel_size": 1, "filter": num_units_list[0], "activation": tf.nn.relu}
    # inner = tf.layers.conv1d(**paras)
    # paras = {"inputs": inner, "kernel_size": 1, "filter": num_units_list[1], "activation": tf.nn.relu}
    # output = tf.layers.conv1d(**output)

    return output

6.标签平滑

  • 未经过平滑的标签在进行训练时,容易过拟合,为了增强模型的泛化能力,对标签进行平滑处理
  • 计算公式:output = (1-alpha) * input + alpha * (1/k),alpha一般取0.1,k表示类别的数目
  • 具体实现过程:
def label_smoothing(inputs, epsilon=0.1):
    """
    标签平滑
    """
    v = inputs.get_shape().as_list()[-1]
    output = (1-epsilon)*inputs + epsilon / v

    return output

常用函数

  • tf.tile(): 复制张量
  • tf.equal(): 逐个元素判断是否相等
  • tf.where(condition, x, y): condition是bool类型,与x,y具有相同的形状。
    如果condition中元素为True,利用x中对应位置的元素替换,否则,利用y中对应位置的元素替换
  • tf.expand_dims: 扩展张量维度,把(2,3,4)扩展为(2,1,3,4)
  • tf.shape():获取变量的维度信息,(包括变量的维度为None)

参考链接

    阅读transformer论文时借鉴了许多大神的博客,这里就不一一列举了。

       主要参考的transformer代码

多模态的自注意力机制是指在多模态数据中,每个模态内部使用自注意力机制来提取模态内部的信息,并使用跨模态的注意力机制来融合不同模态之间的信息。通过将多模态融合推迟到模型的后期,可以更充分地提取单个模态内部的信息,因为不同模态的数据结构和分布差异很大,使用相同的处理方式可能不合理。在单个模态内部,仍然使用原始的自注意力机制,但在跨模态的融合中,使用各个模态的部分信息来进行跨注意力。除此之外,还可以限制层内不同模态注意力的流动,通过引入一组潜在的融合单元,形成"注意力瓶颈",跨模态的交互必须通过这些单元进行。这样既可以降低计算量,处理部分冗余信息,也可以使模型集中处理每个模态中最相关的输入,并只与其他模态共享必要的输入。因此,多模态自注意力机制在模型中起到了重要的作用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [【多模态】《Attention Bottlenecks for Multimodal Fusion》论文阅读笔记](https://blog.csdn.net/qq_36643449/article/details/124968439)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [【论文阅读Attention Bottlenecks for Multimodal Fusion---多模态融合,音视频分类,注意力机制](https://blog.csdn.net/me_yundou/article/details/121070837)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [Attention is all you need:关于transformer中的self-attention](https://blog.csdn.net/hands_up_down/article/details/122022802)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值