当前位置:   article > 正文

NLP之预训练语言模型GPT

早期的预训练语言模型是为了获取词的表示向量,比如:word2vec、glove等

887d86eea61ccf5a823b2e4095591cf0.png

来源:信息网络工程研究中心

本文约4000,建议阅读8分钟

本文将围绕 ScienceAdvances 的一篇论文,介绍如何利用机器学习,对燃煤电厂的胺排放量进行预测。

目录

引言

在自然语言处理领域中,预训练模型通常指代的是预训练语言模型。广义上的预训练语言模型可以泛指提前经过大规模数据训练的语言模型,包括早期的以Word2vec、GloVe为代表的静态词向量模型,以及基于上下文建模的CoVe、ELMo等动态词向量模型。

在2018年,以GPT和BERT为代表的基于深层Transformer的表示模型出现后,预训练语言模型这个词才真正被大家广泛熟知。因此,目前在自然语言处理领域中提到的预训练语言模型大多指此类模型。预训练语言模型的出现使得自然语言处理进入新的时代,也被认为是近些年来自然语言处理领域中的里程碑事件。

OpenAI 公司在2018年提出了一种生成式预训练(Generative Pre-Training,GPT)模型用来提升自然语言理解任务的效果,正式将自然语言处理带入“预训练”时代。“预训练”时代意味着利用更大规模的文本数据以及更深层的神经网络模型学习更丰富的文本语义表示。同时,GPT的出现打破了自然语言处理各个任务之间的壁垒,使得搭建一个面向特定任务的自然语言处理模型不再需要了解非常多的任务背景,只需要根据任务的输入输出形式应用这些预训练语言模型,就能够达到一个不错的效果。因此,GPT提出了“生成式预训练+判别式任务精调”的自然语言处理新范式,使得自然语言处理模型的搭建变得不再复杂。

所以以往大家的工作模式是:

  • 1.各个公司会自己去github下载代码,

  • 2.然后公司出钱捞数据,打标,

  • 3.工程师用自己公司的打标数据去训练来完成业务的需求

但是随着huggingface的成立,现在大家的工作模式是:

  • 1.算法工程师打开huggingface网页,搜业务相关的预训练模型(这些模型都是大厂基于大量的数据训练好的模型),下载

  • 2.算法工程师自己收集或者标记少量的数据;

  • 3.微调下载的模型

公认的很好地讲解GPT的是jalammar的gpt2;http://jalammar.github.io/illustrated-gpt2/。

GPT

GPT的论文内容及结构还是挺简单的。


c9b157913c9e2cf381b05f3224098a89.png

如GPT-1: Improving Language Understanding by Generative Pre-Training关于论文的翻译,如下截图(懒得打字)。

2bf94dcb4fc2b6e734b2115956c5feaa.png
653d0f0f1192aa85811cb6b0e9c4ead2.png

其实缩写就是:

GPT的阶段描述
预训练阶段以语言建模作为训练目标,将Transformer的Decoder部分用于无监督预训练任务,
微调阶段将训练好的Decoder参数固定,接上一层线性层,通过有监督训练任务微调线性层的参数,从而进行预测


适配下游任务

不同任务之间的输入形式各不相同,应如何根据不同任务适配GPT的输入形式成为一个问题。典型的任务在GPT中的输入输出形式,其中包括:单句文本分类、文本蕴含、相似度计算和选择型阅读理解:

4cb7016a64d12326f9c20b57e177033f.png


GPT代码

我们参考莫凡的代码来分析,这里莫凡用的是encoder,而论文说用decoder,看GPT模型介绍并且使用pytorch实现一个小型GPT中文闲聊系统的实现名称叫decoder,其实也是transformer的encoder部分。


原因就是:用着Decoder的 Future Mask (Look Ahead Mask),但结构上又更像Encoder。

首先基于transformer编写encoder,假设下述代码在transformer.py中

  1. import tensorflow as tf
  2. from tensorflow import keras
  3. import numpy as np
  4. import utils # this refers to utils.py in my [repo](https://github.com/MorvanZhou/NLP-Tutorials/)
  5. import time
  6. import pickle
  7. import os
  8. MODEL_DIM = 32
  9. MAX_LEN = 12
  10. N_LAYER = 3
  11. N_HEAD = 4
  12. DROP_RATE = 0.1
  13. self.wv = keras.layers.Dense(n_head * self.head_dim) # [n, step, h*h_dim]
  14. # 输出为
  15. self.o_dense = keras.layers.Dense(model_dim)
  16. self.o_drop = keras.layers.Dropout(rate=drop_rate)
  17. self.attention = None
  18. def call(self, q, k, v, mask, training):
  19. # 先基于给定的q,k,v计算https://www.cnblogs.com/shouhuxianjian/p/16165451.html#3-multiheadedattention 图中下面的linear部分
  20. _q = self.wq(q) # [n, q_step, h*h_dim]
  21. _k, _v = self.wk(k), self.wv(v) # [n, step, h*h_dim]
  22. # 将q k v进行切分为了后面算多头
  23. _q = self.split_heads(_q) # [n, h, q_step, h_dim]
  24. _k, _v = self.split_heads(_k), self.split_heads(_v) # [n, h, step, h_dim]
  25. # 将多头concat起来
  26. context = self.scaled_dot_product_attention(_q, _k, _v, mask) # [n, q_step, h*dv]
  27. # https://www.cnblogs.com/shouhuxianjian/p/16165451.html#3-multiheadedattention 图中上面的linear
  28. o = self.o_dense(context) # [n, step, dim]
  29. o = self.o_drop(o, training=training)
  30. return o
  31. def split_heads(self, x):
  32. x = tf.reshape(x, (x.shape[0], x.shape[1], self.n_head, self.head_dim)) # [n, step, h, h_dim]
  33. return tf.transpose(x, perm=[0, 2, 1, 3]) # [n, h, step, h_dim]
  34. def scaled_dot_product_attention(self, q, k, v, mask=None):
  35. dk = tf.cast(k.shape[-1], dtype=tf.float32)
  36. score = tf.matmul(q, k, transpose_b=True) / (tf.math.sqrt(dk) + 1e-8) # [n, h_dim, q_step, step]
  37. if mask is not None:
  38. score += mask * -1e9
  39. self.attention = tf.nn.softmax(score, axis=-1) # [n, h, q_step, step]
  40. context = tf.matmul(self.attention, v) # [n, h, q_step, step] @ [n, h, step, dv] = [n, h, q_step, dv]
  41. context = tf.transpose(context, perm=[0, 2, 1, 3]) # [n, q_step, h, dv]
  42. context = tf.reshape(context, (context.shape[0], context.shape[1], -1)) # [n, q_step, h*dv]
  43. return context
  44. #建立逐位置的前向网络
  45. class PositionWiseFFN(keras.layers.Layer):
  46. def __init__(self, model_dim):
  47. super().__init__()
  48. dff = model_dim * 4
  49. self.l = keras.layers.Dense(dff, activation=keras.activations.relu)
  50. self.o = keras.layers.Dense(model_dim)
  51. def call(self, x):
  52. o = self.l(x)
  53. o = self.o(o)
  54. return o # [n, step, dim]
  55. # 建立encoder层,以方便后面进行重复
  56. class EncodeLayer(keras.layers.Layer):
  57. def __init__(self, n_head, model_dim, drop_rate):
  58. super().__init__()
  59. self.ln = [keras.layers.LayerNormalization(axis=-1) for _ in range(2)] # only norm z-dim
  60. self.mh = MultiHead(n_head, model_dim, drop_rate)
  61. self.ffn = PositionWiseFFN(model_dim)
  62. self.drop = keras.layers.Dropout(drop_rate)
  63. def call(self, xz, training, mask):
  64. attn = self.mh.call(xz, xz, xz, mask, training) # [n, step, dim]
  65. o1 = self.ln[0](attn + xz)
  66. ffn = self.drop(self.ffn.call(o1), training)
  67. o = self.ln[1](ffn + o1) # [n, step, dim]
  68. return o
  69. #将encoderlayer进行重复,获取整个编码器
  70. class Encoder(keras.layers.Layer):
  71. def __init__(self, n_head, model_dim, drop_rate, n_layer):
  72. super().__init__()
  73. # 重复n_layer
  74. self.ls = [EncodeLayer(n_head, model_dim, drop_rate) for _ in range(n_layer)]
  75. def call(self, xz, training, mask):
  76. for l in self.ls:
  77. xz = l.call(xz, training, mask)
  78. return xz # [n, step, dim]

然后基于上述Encoder编写gpt

  1. import tensorflow as tf
  2. from tensorflow import keras
  3. import utils # this refers to utils.py in my [repo](https://github.com/MorvanZhou/NLP-Tutorials/)
  4. import time
  5. from transformer import Encoder
  6. import pickle
  7. import os
  8. class GPT(keras.Model):
  9. def __init__(self, model_dim, max_len, n_layer, n_head, n_vocab, lr, max_seg=3, drop_rate=0.1, padding_idx=0):
  10. super().__init__()
  11. self.padding_idx = padding_idx
  12. self.n_vocab = n_vocab
  13. self.max_len = max_len
  14. # I think task emb is not necessary for pretraining,
  15. # because the aim of all tasks is to train a universal sentence embedding
  16. # the body encoder is the same across all tasks,
  17. # and different output layer defines different task just like transfer learning.
  18. # finetuning replaces output layer and leaves the body encoder unchanged.
  19. # self.task_emb = keras.layers.Embedding(
  20. # input_dim=n_task, output_dim=model_dim, # [n_task, dim]
  21. # embeddings_initializer=tf.initializers.RandomNormal(0., 0.01),
  22. # )
  23. self.word_emb = keras.layers.Embedding(
  24. input_dim=n_vocab, output_dim=model_dim, # [n_vocab, dim]
  25. embeddings_initializer=tf.initializers.RandomNormal(0., 0.01),
  26. )
  27. self.segment_emb = keras.layers.Embedding(
  28. input_dim=max_seg, output_dim=model_dim, # [max_seg, dim]
  29. embeddings_initializer=tf.initializers.RandomNormal(0., 0.01),
  30. )
  31. self.position_emb = self.add_weight(
  32. name="pos", shape=[1, max_len, model_dim], dtype=tf.float32, # [1, step, dim]
  33. initializer=keras.initializers.RandomNormal(0., 0.01))
  34. self.encoder = Encoder(n_head, model_dim, drop_rate, n_layer)
  35. self.task_mlm = keras.layers.Dense(n_vocab)
  36. self.task_nsp = keras.layers.Dense(2)
  37. self.cross_entropy = keras.losses.SparseCategoricalCrossentropy(from_logits=True, reduction="none")
  38. self.opt = keras.optimizers.Adam(lr)
  39. def call(self, seqs, segs, training=False):
  40. embed = self.input_emb(seqs, segs) # [n, step, dim]
  41. # 需要增加mask
  42. z = self.encoder(embed, training=training, mask=self.mask(seqs)) # [n, step, dim]
  43. mlm_logits = self.task_mlm(z) # [n, step, n_vocab]
  44. nsp_logits = self.task_nsp(tf.reshape(z, [z.shape[0], -1])) # [n, n_cls]
  45. return mlm_logits, nsp_logits
  46. def step(self, seqs, segs, seqs_, nsp_labels):
  47. with tf.GradientTape() as tape:
  48. mlm_logits, nsp_logits = self.call(seqs, segs, training=True)
  49. pad_mask = tf.math.not_equal(seqs_, self.padding_idx)
  50. pred_loss = tf.reduce_mean(tf.boolean_mask(self.cross_entropy(seqs_, mlm_logits), pad_mask))
  51. nsp_loss = tf.reduce_mean(self.cross_entropy(nsp_labels, nsp_logits))
  52. loss = pred_loss + 0.2 * nsp_loss
  53. grads = tape.gradient(loss, self.trainable_variables)
  54. self.opt.apply_gradients(zip(grads, self.trainable_variables))
  55. return loss, mlm_logits
  56. def input_emb(self, seqs, segs):
  57.         return self.word_emb(seqs) + self.segment_emb(segs) + self.position_emb  # [n, step, dim]
  58.         
  59. def mask(self, seqs):
  60. """
  61. abcd--
  62. a011111
  63. b001111
  64. c000111
  65. d000011
  66. -000011
  67. -000011
  68. force head not to see afterward. eg.
  69. a is a embedding for a---
  70. b is a embedding for ab--
  71. c is a embedding for abc-
  72. later, b embedding will + b another embedding from previous residual input to predict c
  73. """
  74. mask = 1 - tf.linalg.band_part(tf.ones((self.max_len, self.max_len)), -1, 0)
  75. pad = tf.math.equal(seqs, self.padding_idx)
  76. mask = tf.where(pad[:, tf.newaxis, tf.newaxis, :], 1, mask[tf.newaxis, tf.newaxis, :, :])
  77. return mask # (step, step)
  78. @property
  79. def attentions(self):
  80. attentions = {
  81. "encoder": [l.mh.attention.numpy() for l in self.encoder.ls],
  82. }
  83. return attentions
  84. def train(model, data, step=10000, name="gpt"):
  85. t0 = time.time()
  86. for t in range(step):
  87. seqs, segs, xlen, nsp_labels = data.sample(16)
  88. loss, pred = model.step(seqs[:, :-1], segs[:, :-1], seqs[:, 1:], nsp_labels)
  89. if t % 100 == 0:
  90. pred = pred[0].numpy().argmax(axis=1)
  91. t1 = time.time()
  92. print(
  93. "\n\nstep: ", t,
  94. "| time: %.2f" % (t1 - t0),
  95. "| loss: %.3f" % loss.numpy(),
  96. "\n| tgt: ", " ".join([data.i2v[i] for i in seqs[0, 1:][:xlen[0].sum()+1]]),
  97. "\n| prd: ", " ".join([data.i2v[i] for i in pred[:xlen[0].sum()+1]]),
  98. )
  99. t0 = t1
  100. os.makedirs("./visual/models/%s" % name, exist_ok=True)
  101. model.save_weights("./visual/models/%s/model.ckpt" % name)
  102. def export_attention(model, data, name="gpt"):
  103. model.load_weights("./visual/models/%s/model.ckpt" % name)
  104. # save attention matrix for visualization
  105. seqs, segs, xlen, nsp_labels = data.sample(32)
  106. model.call(seqs[:, :-1], segs[:, :-1], False)
  107. data = {"src": [[data.i2v[i] for i in seqs[j]] for j in range(len(seqs))], "attentions": model.attentions}
  108. path = "./visual/tmp/%s_attention_matrix.pkl" % name
  109. os.makedirs(os.path.dirname(path), exist_ok=True)
  110. with open(path, "wb") as f:
  111. pickle.dump(data, f)
  112. if __name__ == "__main__":
  113. utils.set_soft_gpu(True)
  114. MODEL_DIM = 256
  115. N_LAYER = 4
  116. LEARNING_RATE = 1e-4
  117.   
  118. d = utils.MRPCData("./MRPC", 2000)
  119. print("num word: ", d.num_word)
  120. m = GPT(
  121. model_dim=MODEL_DIM, max_len=d.max_len - 1, n_layer=N_LAYER, n_head=4, n_vocab=d.num_word,
  122. lr=LEARNING_RATE, max_seg=d.num_seg, drop_rate=0.2, padding_idx=d.pad_id)
  123. train(m, d, step=5000, name="gpt")
  124.     eport_attention(m, d, name="gpt")

例子

调用huggingface的模型

如果只是使用openai的gpt,那就相当简单了,直接调用huggingface的模型就好:

  1. from transformers import pipeline, set_seed
  2. generator = pipeline('text-generation', model='openai-gpt')
  3. set_seed(42)
  4. generator("Hello, I'm a language model,", max_length=30, num_return_sequences=5)

微调用在其他任务上

参考文献:

《自然语言处理:基于预训练模型的方法》
莫凡GPT 单向语言模型
GPT-1: Improving Language Understanding by Generative Pre-Training
GPT-2没什么神奇的,PyTorch 就可以复现代码
GPT模型介绍并且使用pytorch实现一个小型GPT中文闲聊系统

编辑:王菁

校对:林亦霖344f3ecd7eb337df9340d451683dc984.png

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/354379
推荐阅读
相关标签
  

闽ICP备14008679号