【深度学习】实验 — 动手实现 GPT【三】:LLM架构、LayerNorm、GELU激活函数

【深度学习】实验 — 动手实现 GPT【三】:LLM架构、LayerNorm、GELU激活函数

  • 模型定义
    • 编码一个大型语言模型(LLM)架构
  • 使用层归一化对激活值进行归一化
    • LayerNorm代码实现
    • scale和shift
  • 实现带有 GELU 激活的前馈网络
    • 测试

模型定义

编码一个大型语言模型(LLM)架构

  • 像 GPT 和 Llama 这样的模型是基于原始 Transformer 架构的解码器部分,按顺序生成词。
  • 因此,这些 LLM 通常被称为“类似解码器”的 LLM。
  • 与传统的深度学习模型相比,LLM 更大,主要原因在于其庞大的参数数量,而非代码量。
  • 我们会看到,在 LLM 架构中许多元素是重复的。

请添加图片描述

  • 我们考虑的嵌入和模型大小类似于小型 GPT-2 模型。

  • 我们将具体实现最小的 GPT-2 模型(1.24 亿参数)的架构,参考 Radford 等人发表的 Language Models are Unsupervised Multitask Learners(注意,最初报告中列出该模型参数量为 1.17 亿,但模型权重库后来更正为 1.24 亿)。

  • 后续部分将展示如何将预训练权重加载到我们的实现中,以支持 3.45 亿、7.62 亿和 15.42 亿参数的模型大小。

  • 1.24亿参数GPT-2型号的配置细节包括:

GPT_CONFIG_124M = {
    "vocab_size": 50257,    # Vocabulary size
    "context_length": 1024, # Context length
    "emb_dim": 768,         # Embedding dimension
    "n_heads": 12,          # Number of attention heads
    "n_layers": 12,         # Number of layers
    "drop_rate": 0.1,       # Dropout rate
    "qkv_bias": False       # Query-Key-Value bias
}
  • 我们使用简短的变量名,以避免代码中出现过长的行。
  • "vocab_size" 表示词汇表大小为 50,257,由 BPE 分词器支持。
  • "context_length" 表示模型的最大输入词元数量,由位置嵌入实现。
  • "emb_dim" 是输入词元的嵌入维度,将每个输入词元转换为 768 维向量。
  • "n_heads" 是多头注意力机制中的注意力头数。
  • "n_layers" 是模型中的 Transformer 块数量。
  • "drop_rate" 是 dropout 机制的强度,在第 3 章中讨论过;0.1 表示在训练过程中丢弃 10% 的隐藏单元,以减轻过拟合。
  • "qkv_bias" 决定多头注意力机制中的 Linear 层在计算查询(Q)、键(K)和值(V)张量时是否包含偏置向量;我们将禁用此选项,这是现代 LLM 的标准做法。

使用层归一化对激活值进行归一化

  • 层归一化(LayerNorm),也称为层归一化,Ba 等人,2016 提出,旨在将神经网络层的激活值中心化为 0 均值,并将其方差归一化为 1。
  • 这有助于稳定训练过程,并加快有效权重的收敛速度。
  • 层归一化在 Transformer 块内的多头注意力模块之前和之后应用,稍后我们会实现;此外,它也应用在最终输出层之前。
    请添加图片描述
  • 让我们通过一个简单的神经网络层传递一个小的输入样本,来看看层归一化的工作原理:
# create 2 training examples with 5 dimensions (features) each
batch_example = torch.randn(2, 5)

layer = nn.Sequential(nn.Linear(5, 6), nn.ReLU())
out = layer(batch_example)
print(out)

输出

tensor([[0.0000, 0.0000, 0.1504, 0.2049, 0.0694, 0.0000],
        [0.0000, 0.0000, 0.1146, 0.3098, 0.0939, 0.5742]],
       grad_fn=<ReluBackward0>)
  • 让我们计算上面2个输入中每一个的均值和方差:
mean = out.mean(dim=-1, keepdim=True)
var = out.var(dim=-1, keepdim=True)

print("Mean:\n", mean)
print("Variance:\n", var)
Mean:
 tensor([[0.3448],
        [0.2182]], grad_fn=<MeanBackward1>)
Variance:
 tensor([[0.0791],
        [0.2072]], grad_fn=<VarBackward0>)
  • 归一化独立应用于每个输入(行);使用 dim=-1 会在最后一个维度(此处为特征维度)上执行计算,而不是在行维度上执行。

请添加图片描述

  • 减去均值并除以方差(标准差)的平方根,使输入在列(特征)维度上具有 0 的均值和 1 的方差:
out_norm = (out - mean) / torch.sqrt(var)
print("Normalized layer outputs:\n", out_norm)

mean = out_norm.mean(dim=-1, keepdim=True)
var = out_norm.var(dim=-1, keepdim=True)
print("Mean:\n", mean)
print("Variance:\n", var)

输出

Normalized layer outputs:
 tensor([[ 1.9920, -0.1307, -0.3069, -0.7573, -0.2769, -0.5201],
        [-0.4793, -0.4793, -0.4793, -0.1003,  2.0176, -0.4793]],
       grad_fn=<DivBackward0>)
Mean:
 tensor([[-9.9341e-09],
        [ 4.5945e-08]], grad_fn=<MeanBackward1>)
Variance:
 tensor([[1.0000],
        [1.0000]], grad_fn=<VarBackward0>)
  • 每个输入都以 0 为中心,方差为 1;为了提高可读性,我们可以禁用 PyTorch 的科学计数法:
torch.set_printoptions(sci_mode=False)
print("Mean:\n", mean)
print("Variance:\n", var)

输出

Mean:
 tensor([[    -0.0000],
        [     0.0000]], grad_fn=<MeanBackward1>)
Variance:
 tensor([[1.0000],
        [1.0000]], grad_fn=<VarBackward0>)
  • 上面我们对每个输入的特征进行了归一化。
  • 现在,基于相同的思想,我们可以实现一个 LayerNorm 类:

LayerNorm代码实现

class LayerNorm(nn.Module):
    def __init__(self, emb_dim):
        super().__init__()
        self.eps = 1e-5
        self.scale = nn.Parameter(torch.ones(emb_dim))
        self.shift = nn.Parameter(torch.zeros(emb_dim))

    def forward(self, x):
        """
        args:
            x: torch.Tensor
                The input tensor
        returns:
            norm_x: torch.Tensor
                The normalized tensor
        Step:
            1. Compute the mean and variance separately
            2. Normalize the tensor
            3. Scale and shift the tensor
            4. Return the normalized tensor
        """
        # complete this section (3/10)
        # 1. 计算每个特征的均值和方差
        mean = x.mean(dim=-1,keepdim=True)
        variance = x.var(dim=-1,keepdim=True,unbiased=False)
        
        # 2. 对张量进行归一化处理
        x_normalized = (x - mean) / torch.sqrt(variance + self.eps)
        
        # 3. 缩放并平移张量
        norm_x = self.scale * x_normalized + self.shift
        
        # 4. 返回归一化后的张量
        return norm_x

scale和shift

  • 注意,除了通过减去均值并除以方差来执行归一化外,我们还添加了两个可训练的参数:scaleshift
  • 初始的 scale(乘以 1)和 shift(加 0)值不会产生任何效果;但是,scaleshift 是可训练的参数,LLM 会在训练期间自动调整它们,以提高模型在训练任务中的表现。
  • 这使得模型可以学习适合其处理数据的适当缩放和偏移。
  • 另外,在计算方差的平方根之前我们添加了一个较小的值(eps),以避免方差为 0 时的除零错误。

有偏方差

  • 在上述方差计算中,设置 unbiased=False 意味着使用公式 ∑ i ( x i − x ˉ ) 2 n \cfrac{\sum_i (x_i - \bar{x})^2}{n} ni(xixˉ)2 计算方差,其中 n 为样本大小(在这里为特征或列数);此公式不包含贝塞尔校正(其分母为 n-1),因此提供了方差的有偏估计。

  • 对于嵌入维度 n 很大的 LLM,使用 n 和 n-1 之间的差异可以忽略不计。

  • 然而,GPT-2 的归一化层是在有偏方差下训练的,因此为了与我们将在后续章节加载的预训练权重兼容,我们也采用了这种设置。

  • 现在让我们实际尝试 LayerNorm

ln = LayerNorm(emb_dim=5)
out_ln = ln(batch_example)
mean = out_ln.mean(dim=-1, keepdim=True)
var = out_ln.var(dim=-1, unbiased=False, keepdim=True)

print("Mean:\n", mean)
print("Variance:\n", var)

输出

Mean:
 tensor([[    -0.0000],
        [    -0.0000]], grad_fn=<MeanBackward1>)
Variance:
 tensor([[0.9999],
        [1.0000]], grad_fn=<VarBackward0>)

实现带有 GELU 激活的前馈网络

  • GELU(Hendrycks 和 Gimpel, 2016)可以通过多种方式实现;其精确版本定义为 GELU ( x ) = x ⋅ Φ ( x ) \text{GELU}(x) = x \cdot \Phi(x) GELU(x)=xΦ(x),其中 Φ ( x ) \Phi(x) Φ(x) 是标准高斯分布的累积分布函数。
  • 实际中,通常使用计算成本较低的近似实现: GELU ( x ) ≈ 0.5 ⋅ x ⋅ ( 1 + tanh ⁡ [ 2 π ⋅ ( x + 0.044715 ⋅ x 3 ) ] ) \text{GELU}(x) \approx 0.5 \cdot x \cdot \left(1 + \tanh\left[\sqrt{\frac{2}{\pi}} \cdot \left(x + 0.044715 \cdot x^3\right)\right]\right) GELU(x)0.5x(1+tanh[π2 (x+0.044715x3)])(原始的 GPT-2 模型也是在这种近似下训练的)。
class GELU(nn.Module):
    def __init__(self):
        super().__init__()

    def forward(self, x):
        """
        args:
            x: torch.Tensor
                The input tensor
        returns:
            torch.Tensor
                The output tensor
        """
        # Complete this section (4/10)
        # Approximate GELU using the tanh-based formula
        return 0.5 * x * (1 + torch.tanh((torch.sqrt(torch.tensor(2 / 3.1415)) * (x + 0.044715 * torch.pow(x, 3)))))

import matplotlib.pyplot as plt

gelu, relu = GELU(), nn.ReLU()

# Some sample data
x = torch.linspace(-3, 3, 100)
y_gelu, y_relu = gelu(x), relu(x)

plt.figure(figsize=(8, 3))
for i, (y, label) in enumerate(zip([y_gelu, y_relu], ["GELU", "ReLU"]), 1):
    plt.subplot(1, 2, i)
    plt.plot(x, y)
    plt.title(f"{label} activation function")
    plt.xlabel("x")
    plt.ylabel(f"{label}(x)")
    plt.grid(True)

plt.tight_layout()
plt.show()

输出请添加图片描述

  • 接下来,让我们实现一个小型神经网络模块 FeedForward,稍后将在 LLM 的 Transformer 块中使用:
class FeedForward(nn.Module):
    def __init__(self, cfg):
        super().__init__()
        """
        implement self.layers as a Sequential model with:
            1. Linear layer with input dimension cfg["emb_dim"] and output dimension 4*cfg["emb_dim"]
            2. GELU activation function
            3. Linear layer with input dimension 4*cfg["emb_dim"] and output dimension cfg["emb_dim"]
        """
        # complete this section (5/10)
        
        self.layers = nn.Sequential(
            nn.Linear(cfg["emb_dim"], 4 * cfg["emb_dim"]),  # 1. 线性层,输入维度 cfg["emb_dim"],输出 4*cfg["emb_dim"]
            GELU(),                                          # 2. 使用 GELU 激活函数
            nn.Linear(4 * cfg["emb_dim"], cfg["emb_dim"])    # 3. 线性层,输入维度 4*cfg["emb_dim"],输出 cfg["emb_dim"]
        )
        
    def forward(self, x):
        return self.layers(x)
print(GPT_CONFIG_124M["emb_dim"])

输出

768

请添加图片描述

测试

ffn = FeedForward(GPT_CONFIG_124M)

# input shape: [batch_size, num_token, emb_size]
x = torch.rand(2, 3, 768)
out = ffn(x)
print(out.shape)

输出

torch.Size([2, 3, 768])

请添加图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

《YOLO 目标检测》—— YOLO v4 详细介绍

文章目录 一、整体网络结构1. YOLO v4 网络结构图2.对之前版本改进创新的概括 二、对改进创新部分的具体介绍1. 输入端创新2. Backbone主干网络创新CSPDarknet53Mish激活函数Dropblock正则化 3. 特征融合创新SPP模块PAN结构 4. Prediction输出层创新&#xff08;未写完&#xf…

LinkedList 源码分析

1. 迭代器Iterator 在 Java 中&#xff0c;Iterator&#xff08;迭代器&#xff09;是一个用于遍历集合元素的接口。它提供了一种统一的方式来访问集合中的元素&#xff0c;而不需要了解集合的内部实现细节。 一、主要方法 hasNext()&#xff1a;判断是否还有下一个元素可遍历…

怎么安装行星减速电机才是正确的

行星减速电机由于其高效、精密的传动能力&#xff0c;广泛应用于自动化设备、机器人、机床以及其他需要精准控制的领域。正确的安装行星减速电机对于确保设备的性能与延长使用寿命至关重要。 一、前期准备 在进行行星减速电机的安装之前&#xff0c;必须做好充分的前期准备工作…

springcloud通过MDC实现分布式链路追踪

在DDD领域驱动设计中&#xff0c;我们使用SpringCloud来去实现&#xff0c;但排查错误的时候&#xff0c;通常会想到Skywalking&#xff0c;但是引入一个新的服务&#xff0c;增加了系统消耗和管理学习成本&#xff0c;对于大型项目比较适合&#xff0c;但是小的项目显得太过臃…

SAP RFC 用户安全授权

一、SAP 通讯用户 对于RFC接口的用户&#xff0c;使用五种用户类型之一的“通讯”类型&#xff0c;这种类型的用户没有登陆SAPGUI的权限。 二、对调用的RFC授权 在通讯用户内部&#xff0c;权限对象&#xff1a;S_RFC中&#xff0c;限制进一步可以调用的RFC函数授权&#xff…

Spring IoC——依赖注入

1. 依赖注入的介绍 DI&#xff0c;也就是依赖注入&#xff0c;在容器中建立的 bean &#xff08;对象&#xff09;与 bean 之间是有依赖关系的&#xff0c;如果直接把对象存在 IoC 容器中&#xff0c;那么就都是一个独立的对象&#xff0c;通过建立他们的依赖关系&#xff0c;…

IDEA连接EXPRESS版本的SQL server数据库

我安装的版本是SQL2019-SSEI-Expr.exe 也就是EXPRESS版本的SQL Server安排非常简单和快速。 但是默认没有启动sa用户。 启动sa用户名密码登录 默认安装完以后没有启用。 使用Miscrosoft SQL Server Management Studio 使用Windows身份连接后。 在安全性》登录名中找到sa并修改…

​Java面试经典 150 题.P13. 罗马数字转整数(012)​

本题来自&#xff1a;力扣-面试经典 150 题 面试经典 150 题 - 学习计划 - 力扣&#xff08;LeetCode&#xff09;全球极客挚爱的技术成长平台https://leetcode.cn/studyplan/top-interview-150/ 题解&#xff1a; class Solution {public int romanToInt(String s) {int sum…

大数据-204 数据挖掘 机器学习理论 - 混淆矩阵 sklearn 决策树算法评价

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

Windows11下将某个程序添加到鼠标右键快捷菜单

经常看log&#xff0c;最喜欢用的txt查看和编辑工具是EditPlus&#xff0c;好像是个韩国软件&#xff0c;最大的优势是打开大文件&#xff0c;好几G的log文件也很轻松&#xff0c;速度快&#xff0c;然后还有各种高亮设置查找文件等&#xff0c;非常方便。但是不知道为什么&…

宏处理将多个excel文件的指定sheet页合并到一个excel文件中

背景了解&#xff1a;有个同事问我&#xff1a;现在他要处理一千多个文件&#xff0c;每个excel文件都有3个sheet页签&#xff0c;想把所有的excel文件的第二个sheet页签复制一份放到一个新的excel文件中。如果是手动去操作一个个文件的复制&#xff0c;也没什么不可&#xff0…

什么是散度,什么是旋度,分别反映什么现象,磁场和静电场分别是什么场?

散度和旋度是矢量场中重要的微分运算概念&#xff0c;用来描述矢量场的局部特性&#xff0c;广泛应用于物理学、流体力学和电磁学中。 1. 散度&#xff08;Divergence&#xff09; 散度描述的是一个矢量场在某一点的“发散”或“汇聚”程度&#xff0c;简单来说就是在该点附近…

web——warmup——攻防世界

这道题还是没有做出来。。&#xff0c;来总结一下 1.ctrlU显示源码 2.看见body里有source.php 打开这个source.php 看见了源码 highlight_file(FILE); 这行代码用于高亮显示当前文件的源码&#xff0c;适合调试和学习&#xff0c;但在生产环境中通常不需要。 class emmm 定义…

vue3项目history模式部署404处理,使用 historyApiFallback 中间件支持单页面应用路由

vue3项目history模式部署404处理&#xff0c;使用 historyApiFallback 中间件支持单页面应用路由 在现代的 web 开发中&#xff0c;单页面应用&#xff08;SPA&#xff09;变得越来越流行。这类应用通常依赖于客户端路由来提供流畅的用户体验&#xff0c;但在服务器端&#xf…

跨境电商平台系统开发

随着全球化的不断深入&#xff0c;跨境电商作为新兴的商业模式&#xff0c;越来越受到企业和消费者的关注。跨境电商平台的系统开发不仅涉及技术层面的挑战&#xff0c;更涉及到法律、物流、支付等多方面的因素。商淘云将分享跨境电商平台系统开发的主要环节&#xff0c;包括需…

《Web性能权威指南》-WebRTC-读书笔记

本文是《Web性能权威指南》第四部分——WebRTC的读书笔记。 第一部分——网络技术概览&#xff0c;请参考网络技术概览&#xff1b; 第二部分——无线网络性能&#xff0c;请参考无线网络性能&#xff1b; 第三部分——HTTP&#xff0c;请参考HTTP&#xff1b; 第四部分——浏览…

.NET 8 Web API 中的身份验证和授权

本次介绍分为3篇文章&#xff1a; 1&#xff1a;.Net 8 Web API CRUD 操作.Net 8 Web API CRUD 操作-CSDN博客 2&#xff1a;在 .Net 8 API 中实现 Entity Framework 的 Code First 方法https://blog.csdn.net/hefeng_aspnet/article/details/143229912 3&#xff1a;.NET …

android定时器循环实现轮播图

说明&#xff1a; android定时器加for循环实现轮播图 效果&#xff1a; step1: package com.example.iosdialogdemo;import android.os.Bundle; import android.os.Handler; import android.widget.ImageView; import android.widget.TextView;import androidx.appcompat.ap…

基于Node.js+Vue+MySQL实现的(Web)图书管理系统

1 需求分析 本图书管理系统主要实现对图书馆的管理&#xff1a;图书、读者、管理员、借阅。由此&#xff0c;结构可分为&#xff1a;图书管理、读者管理、管理员管理、借还管理、罚单管理、还书信息。 1.1 需求定义 1.1.1 图书管理 可对图书信息进行浏览、编辑&#xff08;…

计算机网络803-(5)运输层

目录 一.运输层的两个主要协议&#xff1a;TCP 与 UDP 1.TCP/IP 的运输层有两个不同的协议&#xff1a; 2.端口号(protocol port number) &#xff08;1&#xff09;软件端口与硬件端口 &#xff08;2&#xff09;TCP 的端口 &#xff08;3&#xff09;三类端口 二.用户…