GPT

利用transformer的decoder构建的语言模型

介绍

经典的语言模型是利用过去的信息,对当前的词进行表征,如何利用过去的信息是有技巧的,之前使用RNN模型,将之前时间步的信息利用隐藏层或者cell状态传递,现在我们可以利用transformer的decoder进行信息传递,因为GPT仍然是预训练语言模型,所以我们依然从预训练和fine tuning两个角度来介绍。
由于GPT的源码只有finetuning的部分,没有pretraing的部分,所以只能从finetuning的代码上面找到pretraning的蛛丝马迹。
finetuning使用的是一个rocstories数据集,是根据故事内容,判断两个结局里面到底是哪一个正确的。这里它是用内容和两个结局分辨拼出了两个长句子,然后对着两个句子做预测,然后根据正确的label构建损失函数。

预训练阶段

因为源码里面没有预训练的部分,只有一个finetuning的部分,因为finetuning的loss包含了语言模型的loss,所以大体上只能明白它是怎么利用语言模型的,但是细节上只能参考,实际上细节上和标准的语言模型差别不少。

# 1. input
X = tf.reshape(X, [-1, n_ctx, 2])

# 2. embedding
we = tf.get_variable("we", [n_vocab+n_special+n_ctx, n_embd], initializer=tf.random_normal_initializer(stddev=0.02))
we = dropout(we, embd_pdrop, train)
h = embed(X, we)

# 3. decoder of transformer
for layer in range(n_layer):
    h = block(h, 'h%d'%layer, train=train, scale=True)
    
# 4. loss
lm_h = tf.reshape(h[:, :-1], [-1, n_embd])
lm_logits = tf.matmul(lm_h, we, transpose_b=True)
lm_losses = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=lm_logits, labels=tf.reshape(X[:, 1:, 0], [-1]))
lm_losses = tf.reshape(lm_losses, [shape_list(X)[0], shape_list(X)[1]-1])
lm_losses = tf.reduce_sum(lm_losses*M[:, 1:], 1)/tf.reduce_sum(M[:, 1:], 1)

输入

对于其中的某一个句子,一般是需要在开头和结尾加上特殊符号,以下是某个句子的举例,输入的序列移除</s>,目标序列移除<s>

<s>,word1,word2,word3...word_n,</s>

从它的finetuning代码里面以及后续,GPT的finetuning语料没有移除这个位置标签,只是在语言模型的loss部分做了一些处理,那么输入就是[batch_size,2,n_ctx, 2] 即先对所有的句子分别进行encode,n_ctx是句子长度,第一个2是finetuning预料中拼接出来的两个长句子,第二个2分别代表的是句子中词的id,以及位置的id。
模型拿进来就先做将第一个2合并到batch_size里面了,方便求语言模型的loss

embedding

使用了一个shape为[n_vocab+n_special+n_ctx, n_embd]的矩阵作为词嵌入矩阵

def embed(X, we):
    we = convert_gradient_to_tensor(we)
    e = tf.gather(we, X)
    h = tf.reduce_sum(e, 2)
    return h

其中e = tf.gather(we, X)获取的是[-1,n_ctx,n_embd]维度的tensor,这个操作目的是从we中,切出x指定位置的向量并组合起来,x表示的是词的id和位置的id,所以可以获取词的embedding向量和位置的embedding向量,然后将这两个向量相加作为最终向量,最终embedding的输出就是[-1,n_ctx,n_embd],至于we的维度加上了n_special,应该是增加了n_special个特殊标记符号进去了,所以要增加词汇表数目。

transformer的decoder

def block(x, scope, train=False, scale=False):
    with tf.variable_scope(scope):
        nx = shape_list(x)[-1]
        a = attn(x, 'attn', nx, n_head, train=train, scale=scale)
        n = norm(x+a, 'ln_1')
        m = mlp(n, 'mlp', nx*4, train=train)
        h = norm(n+m, 'ln_2')
        return h

def attn(x, scope, n_state, n_head, train=False, scale=False):
    assert n_state%n_head==0
    with tf.variable_scope(scope):
        c = conv1d(x, 'c_attn', n_state*3, 1, train=train)
        q, k, v = tf.split(c, 3, 2)
        q = split_heads(q, n_head)
        k = split_heads(k, n_head, k=True)
        v = split_heads(v, n_head)
        a = _attn(q, k, v, train=train, scale=scale)
        a = merge_heads(a)
        a = conv1d(a, 'c_proj', n_state, 1, train=train)
        a = dropout(a, resid_pdrop, train)
        return a

def _attn(q, k, v, train=False, scale=False):
    w = tf.matmul(q, k)

    if scale:
        n_state = shape_list(v)[-1]
        w = w*tf.rsqrt(tf.cast(n_state, tf.float32))

    w = mask_attn_weights(w)
    w = tf.nn.softmax(w)

    w = dropout(w, attn_pdrop, train)

    a = tf.matmul(w, v)
    return a

def mask_attn_weights(w):
    n = shape_list(w)[-1]
    b = tf.matrix_band_part(tf.ones([n, n]), -1, 0)
    b = tf.reshape(b, [1, 1, n, n])
    w = w*b + -1e9*(1-b)
    return w

我们可以看到,这块的decoder代码和标准的transformer的decoder只缺少一个外部attention的结构,因为这里没有encoder的输出,也没有办法同encoder做attention,所以只需要做一个masked self-attention就可以了。
这里的mask_attn_weights里面的n就是指序列长度,b是一个下三角矩阵,用来防止信息泄露,w的shape是[batch_size,num_head,n_ctx,n_ctx],这样的话,w与b相乘,将未来序列信息权重设置为0。但是paddingmask在这里没用啊。。。。不过反正输入序列的</s>都没有去掉,所以这里就不纠结了,这里绝对不是标准的语言模型

loss

        lm_h = tf.reshape(h[:, :-1], [-1, n_embd])
        lm_logits = tf.matmul(lm_h, we, transpose_b=True)
        lm_losses = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=lm_logits, labels=tf.reshape(X[:, 1:, 0], [-1]))
        lm_losses = tf.reshape(lm_losses, [shape_list(X)[0], shape_list(X)[1]-1])
        lm_losses = tf.reduce_sum(lm_losses*M[:, 1:], 1)/tf.reduce_sum(M[:, 1:], 1)

在进行decoder之后,得到了各位置的编码信息h,shape为[-1,n_ctx,n_embd],
之后将h转成我们需要预测的,因为最后的位置</s>是不需要的,所以要移除该token并reshape成每个词的位置,另外利用原先embedding lookup table的转置作为构建词汇logit的权重矩阵,这个技巧还是用的不少的,但是在google的官方transformer里面是没有的这样用的。
之后构建标签序列,标签序列就是移除第一个token,也就是<s>,形成这种错位的预测形式,也就是根据之前词的信息预测下一个词。
最后将各词的loss损失重新reshape成[-1,n_ctx],这样就可以利用原先输入的mask矩阵M,移除padding部分的loss,至于为什么M要移除初始的<s>,这是因为实际我们预测标签是真实序列减1,只是需要移除一个实际位置,只是让padding为1的位置少一个就行了。
最后loss就是[batch_size,],每个值是这个序列真实值的loss的平均,实际上在最后的loss反向传播中,loss又做了一次平均值。
这样就可以构建语言模型了。
怎么利用模型进行finetuning呢?

        clf_h = tf.reshape(h, [-1, n_embd])
        pool_idx = tf.cast(tf.argmax(tf.cast(tf.equal(X[:, :, 0], clf_token), tf.float32), 1), tf.int32)
        clf_h = tf.gather(clf_h, tf.range(shape_list(X)[0], dtype=tf.int32)*n_ctx+pool_idx)

        clf_h = tf.reshape(clf_h, [-1, 2, n_embd])
        if train and clf_pdrop > 0:
            shape = shape_list(clf_h)
            shape[1] = 1
            clf_h = tf.nn.dropout(clf_h, 1-clf_pdrop, shape)
        clf_h = tf.reshape(clf_h, [-1, n_embd])
        clf_logits = clf(clf_h, 1, train=train)
        clf_logits = tf.reshape(clf_logits, [-1, 2])

        clf_losses = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=clf_logits, labels=Y)

这里是finetuning任务的loss计算,首先得到每个词语的embedding向量,然后得到pool_idx,这个pool_idx是每个长句子的clf_token的位置,最终得到的是[batch_size*2]的位置向量。之后我们要获取这些句子中clf_token位置的向量,得到[batch_size*2,n_embedding]。接下来要进行分类,因为两个长句子一起构成2分类,每个句子只需要得到一个维度为1的logits就行了,然后还原出原先的两个长句子,让这个两个长句子的logits构成一个二分类的logits,就可以拿去做二分类了。这里也算是涨了知识了。。。。
finetuning的loss是lm_loss和clf_loss的合并,加了个自定义的超参lm_coef,设定lm_loss的比例

train_loss = tf.reduce_mean(clf_losses) + lm_coef*tf.reduce_mean(lm_losses)

参考资料
GPT的finetuning

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354