前言

SimBert是由苏剑林开发的模型,基于UniLM思路做成的,具体可以参考:https://kexue.fm/archives/7427

SimBert可以做相似句生成&句子相似度判断
比如生成句子:
gen_synonyms(“我和吴彦祖比谁更帅”)

['我和吴彦祖比谁更帅?', 
 '我和吴彦祖比较谁更帅', 
 '我和吴彦祖比谁更帅一些', 
 '我和吴彦祖谁更帅', 
 '我和吴彦祖谁更帅?']

本文主要介绍SimBert的张量矩阵运算原理,以及其shape的变换。

对于UniLM,可以认真看下苏神写的《从语言模型到Seq2Seq:Transformer如戏,全靠Mask》

原理

在上面苏神写的文章中,有张图特别的形象,横坐标是原句子(y_true),纵坐标是预测的句子(y_pred),通俗理解就是:原句子的“[CLS]”预测出“你”,原句子的“你”预测出“想”…以此方式进行下去…
用单向语言模型的方式做Seq2Seq
设计更适合的Mask做Seq2Seq

UniLM最重要的实现是对mask的设置,原理苏神已经在博客中写的很清楚,但是怎么实现一个字一个字预测下去的,具体的实现方式还是不太清楚,
用mask来做,那mask具体长什么样?怎么生成这种mask?生成的mask后面怎么发生作用?
出现了种种这些问题无法解决,于是自己认认真真的研究了下源码:
在bert4keras的models.py这个文件中的unilm_mask就可以解决第一、二个问题。

mask矩阵实现及运算

def unilm_mask(s): # s:Tensor("Input-Segment:0", shape=(?, ?), dtype=float32)
    idxs = K.cumsum(s, axis=1) # (?,?) 在一个维度上累加
    mask = idxs[:, None, :] <= idxs[:, :, None] # (?,?,?)   (?,1,?), (?,?,1) None会加上一维
    mask = K.cast(mask, K.floatx())
    return mask[:, None] # (?,1,?,?)

对这个函数可以细细的研究和品位,因为做法实在是太优雅了,两行代码就实现了主要的功能。
拆分讲解:

1、输入s:是segment_ids,shape=(btz, seq_len)
eg:

[[0 0 0 ... 0 1 1 ... 1],
 [0 0 0 ... 0 0 1 ... 1],
 ...
 [0 0 0 ... 0 1 1 ... 1]]

2、idxs = K.cumsum(s, axis=1) # (?,?) 在一个维度上累加
cumsum函数的作用就是当前位置的值为前面值的累加,比如[1 2 3 4 5],经过cumsum后就是[1 3 6 10 15]
而这里的累加是为了s中为1的第二个句子的预测做准备,因为第二个句子是一个一个的预测出来
比如:

 [[0 0 0 ... 0 1 2 3 4],
  [0 0 0 ... 1 2 3 4 5],
   ...
  [0 0 0 ... 0 1 2 3 4]]

3、mask = idxs[:, None, :] <= idxs[:, :, None]
首先要分别看idxs[:, None, :]和idxs[:, :, None] 都长什么样

  • idxs[:, None, :]:在None的位置加一维=>shape=(btz,1,seg_len)
    注意看这个矩阵和2中不太一样,多了一个[],也就是多了一维
    eg:
[[[0 0 0 ... 0 1 2 3 4]],
 [[0 0 0 ... 1 2 3 4 5]],
 ...
 [[0 0 0 ... 0 1 2 3 4]]]
  • idxs[:, :, None]:同理 shape=(btz,seq_len, 1)
[[[0]
  [0]
  [0]
  [1]
  [2]
  [3]
  [4]]
 ...
  [[0]
   [0]
   [0]
   [1]
   [2]
   [3]
   [4]]]

最后得出mask的shape=(btz,seq_len,seq_len)
大概长这样(这里和1)、 2)不一一对应):

[[[T T T F F F]
  [T T T F F F]
  [T T T F F F]
  [T T T T F F]
  [T T T T T F]
  [T T T T T T]]]

计算方法就是拿出2)中的每一行去和1)中的每一行做比较,最后得出的shape=(btz,seq_len,seq_len)

4、输出mask[:, None],此时shape=(btz, 1, seq_len, seq_len)

为什么要加一维后再输出呢?因为这是为后续做多头注意力做准备,在多头注意力中,q和k相乘后得到a的shape=(btz, heads, seq_len, seq_len),可以直接和mask做运算。
多头注意力的矩阵运算详解可见:【终于全面理解多维矩阵运算】 – 基于keras的MultiAttention实现及实例

我是卓师叔,欢迎关注,GitHub地址为:bert4keras_note

Logo

尧米是由西云算力与CSDN联合运营的AI算力和模型开源社区品牌,为基于DaModel智算平台的AI应用企业和泛AI开发者提供技术交流与成果转化平台。

更多推荐