GPT-2(Transformer Decoder)的TensorFlow实现(附源码)

news2024/11/18 10:48:21

文章目录

  • 一、GPT2实现步骤
  • 二、源码

一、GPT2实现步骤

  机器学习模型的开发实现步骤一般都包含以下几个部分:
  1. 遵照模型的网络架构,实现每一层(Layer/Block)的函数;
  2. 将第1步中的函数组合在一起,形成完整的Model;
  3. 定义模型的单步训练(train_step)函数,损失计算函数,优化器,metric函数(准确率度量函数)等,以完成单步的模型训练;
  4. 定义循环训练函数(train_loop),循环调用第3步的函数,完成多轮次(epoch),多批次(batch)的训练;
  5. 定义评估函数(evaluate),测试并评估模型训练结果是否符合预期;
  6. 构造训练数据,包括train、validation、test等,输入到模型并开始训练;

二、源码

  GPT网络结构如下图:
在这里插入图片描述
  上图的详细分析参照之前的文章(https://blog.csdn.net/liuqiker/article/details/130782918?spm=1001.2014.3001.5501),此处不再赘述。

源码如下:
  导入包

import tensorflow as tf
import tensorflow.keras as keras
import matplotlib.pyplot as plt
import numpy as np
import urllib.request
import zipfile
from IPython import display
import time

  位置编码函数

def positional_encoding(length, depth):
  depth = depth/2

  positions = np.arange(length)[:, np.newaxis]     # (seq, 1)
  depths = np.arange(depth)[np.newaxis, :]/depth   # (1, depth)

  angle_rates = 1 / (10000**depths)         # (1, depth)
  angle_rads = positions * angle_rates      # (pos, depth)

  pos_encoding = np.concatenate(
      [np.sin(angle_rads), np.cos(angle_rads)],
      axis=-1) 

  return tf.cast(pos_encoding, dtype=tf.float32)

  定义PositionalEmbedding层

class PositionalEmbedding(tf.keras.layers.Layer):
  def __init__(self, vocab_size, d_model, max_seq_len=100, dropout_rate=0.1):
    super().__init__()
    self.d_model = d_model
    # vocab_size is the size of whole vocab, input length should not be longger than vocab_size
    self.embedding = tf.keras.layers.Embedding(vocab_size, d_model, mask_zero=True) 
    self.pos_encoding = positional_encoding(length=max_seq_len, depth=d_model)
    self.dropout = tf.keras.layers.Dropout(dropout_rate)

  def compute_mask(self, *args, **kwargs):
    return self.embedding.compute_mask(*args, **kwargs)

  def call(self, x):
    length = tf.shape(x)[1] # x.shape is [None, length]
    x = self.embedding(x)
    # This factor sets the relative scale of the embedding and positonal_encoding.
    x *= tf.math.sqrt(tf.cast(self.d_model, tf.float32))
    x = x + self.pos_encoding[tf.newaxis, :length, :]

    x = self.dropout(x)
    return x

  定义Multi-Head Attention Layer + Add & Norm层

class CausalSelfAttention(tf.keras.layers.Layer):
  def __init__(self, **kwargs):
    super().__init__()
    self.mha = tf.keras.layers.MultiHeadAttention(**kwargs)
    self.layernorm = tf.keras.layers.LayerNormalization()
    self.add = tf.keras.layers.Add()

  def call(self, x):
    attn_output = self.mha(
        query=x,
        value=x,
        key=x,
        use_causal_mask = True)
    x = self.add([x, attn_output]) # 残差
    x = self.layernorm(x)
    return x

  定义Feed Forward Block + Add & Norm层

class FeedForward(tf.keras.layers.Layer):
  # dff : depth of feed-forward layer
  def __init__(self, d_model, dff, dropout_rate=0.1):
    super().__init__()
    self.seq = tf.keras.Sequential([
      tf.keras.layers.Dense(dff, activation='relu'),
      tf.keras.layers.Dense(d_model),
      tf.keras.layers.Dropout(dropout_rate)
    ])
    self.add = tf.keras.layers.Add()
    self.layer_norm = tf.keras.layers.LayerNormalization()

  def call(self, x):
    x = self.add([x, self.seq(x)]) # 残差
    x = self.layer_norm(x) 
    return x

  定义DecoderBlock

class DecoderBlock(tf.keras.layers.Layer):
  def __init__(self,*, d_model, num_heads, dff, dropout_rate=0.1):
    super().__init__()

    self.self_attention = CausalSelfAttention(
        num_heads=num_heads,
        key_dim=d_model,
        dropout=dropout_rate)

    self.ffn = FeedForward(d_model, dff) # depth of feed-forward layer

  def call(self, x):
    x = self.self_attention(x)
    x = self.ffn(x)
    return x

  定义Decoder

class Decoder(tf.keras.layers.Layer):
  def __init__(self, num_layers, d_model, num_heads,
               dff, vocab_size, max_seq_len=100, dropout_rate=0.1):
    super().__init__()

    self.d_model = d_model
    self.num_layers = num_layers

    self.pos_embedding = PositionalEmbedding(
        vocab_size=vocab_size, d_model=d_model, max_seq_len=max_seq_len)

    self.dec_layers = [
        DecoderBlock(d_model=d_model,
                     num_heads=num_heads,
                     dff=dff, # depth of feed-forward layer
                     dropout_rate=dropout_rate)
        for _ in range(num_layers)]
    self.dropout = tf.keras.layers.Dropout(dropout_rate)

  def call(self, x):
    x = self.pos_embedding(x)  # Shape `(batch_size, seq_len, d_model)`.

    # Add dropout.
    x = self.dropout(x)

    for i in range(self.num_layers):
      x = self.dec_layers[i](x)

    return x  # Shape `(batch_size, seq_len, d_model)`.

  创建GPT模型

class GPT(tf.keras.Model):
  def __init__(self, num_layers, d_model, num_heads, dff, 
               vocab_size,
               max_seq_len,
               fine_tuning_class_num,
               dropout_rate=0.1):
    super().__init__()
    self.decoder = Decoder(num_layers, d_model, num_heads, dff, vocab_size, max_seq_len)
    self.final_layer = tf.keras.layers.Dense(vocab_size)
    self.fine_tuning_layer = tf.keras.layers.Dense(fine_tuning_class_num)

  def call(self, targets):
    decode_out = self.decoder(targets)
    final_out = self.final_layer(decode_out)
    # fine_tuning_out = self.fine_tuning_layer(tf.keras.layers.Flatten()(final_out)) 对于GPT2,不需要fine_tune输出,GPT1需要

    return final_out

  定义train_step、loss函数、optimizer

num_layers = 4
d_model = 128
dff = num_layers * d_model
num_heads = 8
target_vocab_size = tokenizer_title.vocab_size + 2
max_seq_len = MAX_LENGTH
dropout_rate = 0.1

# 自定义learning_rate,来自于https://www.tensorflow.org/text/tutorials/transformer
class CustomSchedule(tf.keras.optimizers.schedules.LearningRateSchedule):
  def __init__(self, d_model, warmup_steps=4000):
    super().__init__()

    self.d_model = d_model
    self.d_model = tf.cast(self.d_model, tf.float32)

    self.warmup_steps = warmup_steps

  def __call__(self, step):
    step = tf.cast(step, dtype=tf.float32)
    arg1 = tf.math.rsqrt(step)
    arg2 = step * (self.warmup_steps ** -1.5)

    return tf.math.rsqrt(self.d_model) * tf.math.minimum(arg1, arg2)

# 定义优化器
learning_rate = CustomSchedule(d_model)
optimizer = tf.keras.optimizers.Adam(learning_rate, beta_1=0.9, beta_2=0.98, epsilon=1e-9)

# 定义目标函数和评估指标,from_logits=True代表先做softmax再计算
loss_object = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True, reduction='none')
train_loss = tf.keras.metrics.Mean(name='train_loss')
train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='train_accuracy')

# 损失函数
def loss_fun(y_ture, y_pred):
    mask = tf.math.logical_not(tf.math.equal(y_ture, 0))  # 为0掩码标1
    loss_ = loss_object(y_ture, y_pred)

    mask = tf.cast(mask, dtype=loss_.dtype)
    loss_ *= mask
    return tf.reduce_mean(loss_)

# 初始化模型
gpt2 = GPT(num_layers, d_model, num_heads, dff,
            target_vocab_size,
            max_seq_len, 
            dropout_rate)

checkpoint_path = '/usr/data/checkpoint/train_gpt2_exp1'
ckpt = tf.train.Checkpoint(gpt2=gpt2,
                          optimizer=optimizer)
# ckpt管理器
ckpt_manager = tf.train.CheckpointManager(ckpt, checkpoint_path, max_to_keep=3)

if ckpt_manager.latest_checkpoint:
    ckpt.restore(ckpt_manager.latest_checkpoint)
    print('last checkpoit restore')

def train_step(targets):
    tar_inp = targets[:, :-1]
    tar_real = targets[:, 1:]

    with tf.GradientTape() as tape:
        predictions = gpt2(tar_inp)
        loss = loss_fun(tar_real, predictions)
        
    # 求梯度
    gradients = tape.gradient(loss, gpt2.trainable_variables)
    
    # 反向传播
    optimizer.apply_gradients(zip(gradients, gpt2.trainable_variables))

    # 记录loss和准确率
    train_loss(loss)
    train_accuracy(tar_real, predictions)

  定义train_loop

EPOCHS = 20
step_list = []
loss_list = []
step = 0

for epoch in range(EPOCHS):
    start = time.time()

    # 重置记录项
    train_loss.reset_states()
    train_accuracy.reset_states()

    for batch, all_inputs in enumerate(train_dataset):
        
        # 训练
        train_step(all_inputs)

        if batch % 100 == 0:
            loss = train_loss.result()
            print('epoch {}, batch {}, loss:{:.4f}, acc:{:.4f}'.format(
                epoch+1, batch, loss, train_accuracy.result()
            ))
            step_list.append(step)
            loss_list.append(loss)
        step += 1

    if (epoch + 1) % 2 == 0:
        ckpt_save_path = ckpt_manager.save()
        print('epoch {}, save model at {}'.format(
        epoch+1, ckpt_save_path
        ))

    print('epoch {}, loss:{:.4f}, acc:{:.4f}'.format(
        epoch+1, train_loss.result(), train_accuracy.result()
    ))

    print('time in 1 epoch:{} secs\n'.format(time.time()-start))
    
plt.plot(step_list, loss_list)
plt.xlabel('train step')
plt.ylabel('loss')

  定义评估输出函数

def evaluate(inp_sentence):
    start_token = [tokenizer_title.vocab_size]
    end_token = [tokenizer_title.vocab_size + 1]
    
    # 增加开始和结束标记
    inp_sentence = start_token + tokenizer_title.encode(inp_sentence) + end_token
    encoder_input = tf.expand_dims(inp_sentence, 0)

    decoder_input = [tokenizer_title.vocab_size]
    output = tf.expand_dims(decoder_input, 0)

    for i in range(MAX_LENGTH):
        predictions = gpt2(encoder_input)

        # 从 seq_len 维度选择最后一个词(选择最优解,如果需要生成内容更随机,可以修改此处的选择逻辑)
        predictions = predictions[: ,-1:, :]  # (batch_size, 1, vocab_size)

        predicted_id = tf.cast(tf.argmax(predictions, axis=-1), tf.int32)

        # 如果 predicted_id 等于结束标记,就返回结果
        if predicted_id == tokenizer_title.vocab_size + 1:
            return tf.squeeze(encoder_input, axis=0)

        # 连接 predicted_id 与输出,作为解码器的输入传递到解码器。
        encoder_input = tf.concat([encoder_input, predicted_id], axis=-1)
        output = tf.concat([output, predicted_id], axis=-1)
    
    return tf.squeeze(encoder_input, axis=0)
    
def translate(sentence, plot=''):
    result = evaluate(sentence)

    predicted_sentence = tokenizer_title.decode([i for i in result if i < tokenizer_title.vocab_size]) 
    predicted_sentence = predicted_sentence.replace(" ", "")
    sentence = sentence.replace(" ", "")

    print('输入: {}'.format(sentence))
    print('预测输出: {}'.format(predicted_sentence))

  至此GPT模型已经构建完毕,只需要自行下载训练数据,并做好相应的分词(使用jieba分词)和token处理(使用tfds.features.text.Tokenizer)即可开始模型的训练了!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/554593.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微信小程序nodejs+vue校园快递代拿系统uniapp校园互助系统

语言 node.js 框架&#xff1a;Express 前端:Vue.js 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat 开发软件&#xff1a;VScode 平台旨在解决目前大学生找人帮忙&#xff0c;难&#xff0c;慢&#xff0c;不可靠以及想兼职同学找不到好的平台的问题。对于招人帮忙的…

应急演练脚本编写的几个步骤

应急演练是一项非常重要的活动&#xff0c;对于保障企业的安全和稳定运行至关重要。而一个完整的应急演练需要编写一个详细的脚本来指导演练过程。以下是应急演练脚本编写的几个步骤。 定义演练场景 首先&#xff0c;需要定义演练场景&#xff0c;这将决定演练的目标和方向。在…

美国原装二手 SR560 低噪声电压前置放大器

Stanford Research SR560低噪声电压前置放大器 ​Stanford Research SR560 是一款高性能、低噪声前置放大器&#xff0c;适用于各种应用&#xff0c;包括低温测量、光学检测和音频工程。 SR560 具有一个具有 4 nV/√Hz 输入噪声和 100 MΩ 输入阻抗的差分前端。完整的噪声系数…

三招教你图片文字转语音怎么转

随着数字化时代的到来&#xff0c;人们对于数字信息的获取和处理需求越来越大&#xff0c;而图片文字转语音技术正是为了满足这一需求而诞生的。这项技术不仅可以辅助视力障碍者&#xff0c;让他们能更轻松地获取信息和理解内容&#xff0c;而且也可以帮助正在学习外语的人们练…

Menards EDI对接流程

Menards是一家美国的家居建材零售商&#xff0c;成立于1962年&#xff0c;总部位于美国威斯康星州的伊甸谷市。该公司经营各种家居建材产品&#xff0c;包括木材、地板、墙纸、厨房卫浴用品等&#xff0c;并拥有超过300家门店&#xff0c;分布在美国中西部和北部地区。Menards的…

2023智能座舱新趋势洞察

两年一度的上海车展于4月底正式落幕&#xff0c;怿星科技市场总监老崔率团队奔赴考察&#xff0c;经过多日分析整理&#xff0c;围绕车展发布车型为核心&#xff0c;制作了怿星科技2023智能座舱趋势洞察报告&#xff0c;现将报告分享如下。 01 车展简述 汽车行业进入新时代 本…

DVB-S中卫星通信系统的基带仿真(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 ​数字视频广播(DVB)在卫星通信数字多媒体业务领域应用广泛,其一般采用MPEG-2编码、数字传输和纠错处理等通用技术,然而,当第三方…

LeetCode 117. 填充每个节点的下一个右侧节点指针 II

117. 填充每个节点的下一个右侧节点指针 II 描述 给定一个二叉树&#xff1a; struct Node {int val;Node *left;Node *right;Node *next; }填充它的每个 next 指针&#xff0c;让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点&#xff0c;则将 next 指针设置为 …

csgo搬砖人必知:未来csgo饰品会一路走低吗?市场回暖到底还要多久?

csgo搬砖人必知&#xff1a;未来csgo饰品会一路走低吗&#xff1f;市场回暖到底还要多久&#xff1f; 最后一届巴黎major终于落下帷幕&#xff0c;Vitality小蜜蜂2-0战胜GL成功赢下本次Major冠军&#xff0c;也是首次夺得Major冠军&#xff01;有人欢喜有人忧啊&#xff0c;cs…

Phaser笔记文档阅读-Working with Phaser, TypeScript and webpack – step 1

首先得安装好npm和node.js。 创建一个空文件夹如&#xff1a;phaserwebpack&#xff0c;这里我使用webstorm打开&#xff0c;打开终端&#xff1a; 运行&#xff1a; npm init -y 执行上面的命令成功后会自动创建package.json文件。随后安装phaser&#xff1a; npm install…

常用性能测试工具选择所需要考虑的因素

在软件开发和应用中&#xff0c;性能问题是一个非常普遍的问题。进行性能测试已经成为了软件开发和应用必不可少的一步。而性能测试工具就是进行性能测试的关键。市面上有许多种不同类型的性能测试工具&#xff0c;如何选择合适的工具呢&#xff1f;以下是一些常用性能测试工具…

SpringCloud(注册中心)

分布式架构与微服&#xff1a;【restfu分格&#xff08;入参的分格&#xff09;---rest分格&#xff08;请求的分格&#xff09;】 微服务&#xff1a; 单体架构的应用场景&#xff1a; 微服务的应用场景&#xff1a; 上百个服务---服务于服务之间是有依赖关系的 什么是spring…

Shell运维实战1-核心与数值计算

目录 Shell 初步入门Shell 分类幻数注释 Shell 核心与实践变量引号输出特殊变量特殊状态变量bash 内置变量命令变量子串特殊扩展变量 Shell 变量数值计算实践基本算术运算符双小括号letexprbcawkread Shell 初步入门 Shell 分类 对于 Unix/Linux 两种系统&#xff0c;shell 主…

导入/导出 OpenAPI 不再是问题,这个开源管理工具可以!

导入 OpenAPI 插件 支持导入 OpenAPI 3.0 版本的文件&#xff0c;如果您使用 Swagger1.0/2.0&#xff0c;可以访问这个在线地址 转换为 3.0 再导入。 使用 导入功能有多个入口&#xff0c;你可以在 API 分组处点击加号导入 API&#xff1a; 也可以在设置页面导入 导出 OpenAP…

第17章_触发器

第17章_触发器 在实际开发中&#xff0c;我们经常会遇到这样的情况&#xff1a;有 2 个或者多个相互关联的表&#xff0c;如商品信息和库存信息分别存放在 2 个不同的数据表中&#xff0c;我们在添加一条新商品记录的时候&#xff0c;为了保证数据的完整性&#xff0c;必须同时…

Android和iOS双端赞奇超级云盘APP公测版正式上线!

赞奇云工作站自发布以来&#xff0c;经过层层迭代和升级&#xff0c;以云工作站、赞奇超级云盘、软件中心、云渲染等功能&#xff0c;更高效地整合打通各行设计业务全流程&#xff0c;实现云上数字内容创作的完美呈现&#xff0c;取得了广大用户的喜爱和认可。 现在&#xff0…

一个月节省40万核,企业级云资源分析与成本优化平台

Crane简介 Crane是一个基于 FinOps 的云资源分析与成本优化平台&#xff0c;是在保证客户应用运行质量的前提下实现极致的降本。 Crane 已经在腾讯内部自研业务实现了大规模落地&#xff0c;部署数百个 K8s 集群、管控 CPU 核数达百万&#xff0c;在降本增效方面取得了阶段性成…

数据驱动运营增长

利用数据来分析、优化和提升产品或服务的各个方面&#xff0c;从而实现业务目标的方法叫数据驱动运营增长。用好数据&#xff0c;在运营中能精准地定位用户需求、痛点、偏好和行为&#xff0c;细分用户群体以提供个性化的产品或服务&#xff0c;精细化地管理和优化每一个环节和…

操作系统原理 —— 什么是管程? 管程的概念以及作用(十六)

上一个章节中&#xff0c;我们讲了什么是信号量&#xff0c;如何用信号量来实现进程之间的同步、互斥。 但是吧&#xff0c;用信号量来实现的话&#xff0c;好麻烦哟&#xff0c;在各个进程之间都要大量的 PV 操作&#xff0c;而且操作不当一不小心就死锁了&#xff0c;为了锻…

00后薪资比老油条高,简直无地自容了...

00后带来的压力 公司一位工作3年的老油条工资还没有刚来的00后高&#xff0c;她心中不平&#xff0c;对这件事情有不小的怨气&#xff0c;她觉得自己来公司三年了&#xff0c;三年内迟到次数都不超过5次&#xff0c;每天勤勤恳恳&#xff0c;要加班的时候也愿意加班&#xff0…