大语言模型基础

一、快速启动

大语言模型(LLM)可以使用 Ollama 工具进行安装和使用。Ollama 是一个工具集合,专门用于部署和管理语言模型。要快速启动大语言模型,可以使用以下步骤:

安装 Ollama:可以从官方源(Download Ollama on Windows)获取 Ollama 并进行安装。点击下载然后安装即可。如果下载太慢,可以访问百度网盘链接,其中的OllamaSetup.exe是我在20240812下载的安装包。

直接下一步即可逐步完成安装。

安装完成后桌面会有一个小图标:

此时打开命令行输入ollama出现以下内容表明安装成功。

运行模型:使用 Ollama 提供的命令行工具或 API 接口来启动和调用模型。

使用以下指令启动千问模型。

ollama run qwen

显示以下进度条表明正在下载安装。

安装完成后在这个命令行界面就可以使用大语言模型了。

二、概述

2.1 发展历程

统计语言模型(Statistical Language Model):统计语言模型依赖于统计方法来估计词序列的概率分布。最早的语言模型基于 n-gram 方法,即通过分析 n 个词的联合出现概率来预测下一个词。

神经语言模型(Neural Language Model):神经语言模型使用神经网络来建模词序列。这类模型通过嵌入层将词转换为稠密的向量表示,然后通过 RNN、LSTM 或 GRU 等网络结构来捕捉词序列的上下文信息。

预训练语言模型(Pre-trained Language Model):预训练语言模型是在大规模文本数据上进行预训练,然后可以针对特定任务进行微调(Fine-tuning)。这些模型在预训练阶段学习到通用的语言知识和表示能力,如 BERT 和 GPT 系列。

大语言模型(Large Language Model, LLM):大语言模型是规模较大的预训练语言模型,具有显著的模型容量和参数规模。LLM 通常在巨大的数据集上进行训练,能够生成高质量的文本,处理复杂的语言任务。

2.2 涌现能力

大语言模型的涌现能力指的是“在小型模型中不存在但在大模型中出现的能力”,即当模型规模扩大到一定程度时,其在特定任务上的性能会突然显著提升,远超过随机水平。

具体而言,这包括三种典型的涌现能力:

上下文学习(In-context Learning, ICL):模型可以通过自然语言提示和多个任务示例进行学习,而无需显式的训练或梯度更新,仅依靠输入的文本序列生成预期的输出。

指令遵循(Instruction Following):大语言模型能根据自然语言指令执行对应的任务,表现出高效的任务处理能力。

逐步推理(Step-by-step Reasoning):在处理如数学应用题等复杂任务时,小型模型难以进行推理,但大模型可以利用思维链(Chain-of-Thought, CoT)来显著提升推理性能。

三、Transformer

当前主流的大语言模型大多基于 Transformer 模型进行设计。

Transformer 最核心的两个部分,多头注意力机制和位置编码,弄懂这两个部分基本就把Transformer弄懂了。

3.1 多层多头自注意力(Multi-head Self-attention)

3.1.1 自注意力机制

自注意力机制(Self-Attention Mechanism)是一种在自然语言处理任务中常用的机制,允许模型在处理每个词时考虑到序列中所有其他词的影响,从而捕捉长距离依赖关系。

在传统的循环神经网络(RNN)中,每个词的表示都是通过前面的词的隐藏状态进行计算得到的。这种方式存在一个问题,即无论序列有多长,模型都必须通过一连串的隐藏状态来传递信息,从而导致信息的损失和累积误差。而自注意力机制则能够避免这个问题。

自注意力机制通过在输入序列中的每个词上计算注意力权重,来决定每个词对其他词的关注程度。这样,模型可以根据计算得到的注意力权重来对每个词的表示进行加权求和,从而捕捉到序列上的全局依赖关系。

具体来说,自注意力机制将输入序列的每个词分别进行线性映射得到查询(Query)、键(Key)和值(Value)向量。然后,通过计算查询向量和所有键向量之间的相似度,得到注意力权重。注意力权重表示了每个词对其他词的重要程度。最后,通过将注意力权重和值向量进行加权求和,得到每个词的最终表示。

相比于传统的循环神经网络,自注意力机制具有以下优势:
1. 并行计算:每个词的表示都可以独立地进行计算,因此可以并行处理整个序列,加快模型训练和推理的速度。
2. 长距离依赖:自注意力机制可以捕捉到序列中的长距离依赖关系,不需要通过一系列隐藏状态来传递信息。这对于处理长文本或有长距离依赖的任务非常有用。
3. 上下文感知:每个词的表示都可以考虑到序列中所有其他词的影响,从而更好地理解每个词的上下文语义。

自注意力机制在机器翻译、文本摘要、语言建模等任务中取得了很好的效果。同时,它也是许多现代神经网络模型(如Transformer)的基础,为自然语言处理任务的进一步发展提供了重要的思路和方法。

示例代码如下:

import torch
import torch.nn as nn
import torch.nn.functional as F
import math

class ScaledDotProductAttention(nn.Module):
    def __init__(self, embed_dim, key_size, value_size):
        """
        初始化 ScaledDotProductAttention 模块。

        Args:
            embed_dim (int): 输入嵌入向量的维度。
            key_size (int): 注意力机制中键的维度。
            value_size (int): 注意力机制中值的维度。
        """
        super().__init__()
        # 定义查询、键和值的线性变换
        self.W_q = nn.Linear(embed_dim, key_size, bias=False)
        self.W_k = nn.Linear(embed_dim, key_size, bias=False)
        self.W_v = nn.Linear(embed_dim, value_size, bias=False)

    def forward(self, x, attn_mask=None):
        """
        执行前向传播计算注意力。

        Args:
            x (Tensor): 输入序列,形状为 (N, L, embed_dim),其中 N 是批次大小,L 是序列长度,embed_dim 是嵌入维度。
            attn_mask (Tensor, optional): 注意力掩码,形状为 (N, L, L),用于掩盖注意力矩阵中的某些位置,避免计算其注意力权重。
        
        Returns:
            Tensor: 注意力加权的输出,形状为 (N, L, value_size)。
        """
        # 计算查询、键和值
        query = self.W_q(x)  # (N, L, key_size)
        key = self.W_k(x)    # (N, L, key_size)
        value = self.W_v(x)  # (N, L, value_size)

        # 计算注意力分数
        scores = torch.matmul(query, key.transpose(1, 2)) / math.sqrt(query.size(-1))  # (N, L, L)

        if attn_mask is not None:
            # 掩码注意力分数(通常将掩码位置设置为非常小的负数,以便在 softmax 后变为零)
            scores = scores.masked_fill(attn_mask == 0, float('-inf'))  # 注意:这里要填充为 -inf,而不是 0,以使 softmax 后为 0

        # 计算注意力权重
        attn_weights = F.softmax(scores, dim=-1)  # (N, L, L)

        # 加权求和得到输出
        output = torch.matmul(attn_weights, value)  # (N, L, value_size)

        return output

if __name__ == "__main__":
    # 设置随机种子以确保结果可重复
    torch.manual_seed(0)

    # 示例输入数据
    N, L, embed_dim, key_size, value_size = 2, 3, 4, 5, 6
    x = torch.randn(N, L, embed_dim)
    attn_mask = torch.ones(N, L, L)  # 这里的掩码全部为 1,表示没有掩盖任何位置

    # 创建 ScaledDotProductAttention 实例
    attention = ScaledDotProductAttention(embed_dim, key_size, value_size)

    # 执行前向传播
    output = attention(x, attn_mask)

    # 打印输入和输出
    print("输入数据 x:")
    print(x)
    print("\n注意力掩码 attn_mask:")
    print(attn_mask)
    print("\n注意力输出:")
    print(output)

3.1.2 多头注意力机制

多头注意力机制是一种在自然语言处理任务中广泛应用的模型,旨在增强模型对输入序列的理解能力。在传统的自注意力机制中,只使用了一个注意力头,将输入序列中所有位置的信息整合为一个注意力模式。然而,这种方式可能无法捕捉到输入序列中不同位置之间的复杂依赖关系。

相比之下,多头注意力机制引入了多个独立的注意力头,同时对输入序列进行并行计算,每个注意力头可以学习到不同的注意力模式,提高了模型的表示能力。每个注意力头都有自己的权重矩阵,用于生成注意力分数。通过将每个注意力头生成的注意力分数与其对应的查询、键和值进行加权求和,得到多头注意力的输出。

多头注意力机制的优势在于,不同的注意力头可以捕捉到不同的关注点。例如,在机器翻译任务中,一个注意力头可以关注源语言句子的语法结构,而另一个注意力头可以关注句子中的词汇信息。这种并行计算不同的注意力模式,使得模型能够以多个视角来理解输入序列,增强了模型的表达能力。

此外,多头注意力机制还可以提高模型的鲁棒性和泛化能力。通过多个独立的注意力头计算不同的注意力模式,模型能够对输入序列中的不同特征进行分别编码,从而减少了对特定特征的依赖。这样,即使某个注意力头出现问题或无法正确捕捉到某些特征,其他注意力头仍能提供有效的信息,保证了模型的稳定性和准确性。

综上所述,多头注意力机制通过并行计算多个独立的注意力头,能够提高模型的表示能力、鲁棒性和泛化能力。在很多自然语言处理任务中,使用多头注意力机制可以帮助模型更好地理解输入序列,提高任务性能

示例代码如下:

import torch
import torch.nn as nn
import torch.nn.functional as F
import math

class MultiHeadSelfAttention(nn.Module):
    def __init__(self, embed_dim, num_heads, key_size, value_size, bias=False):
        """
        初始化多头自注意力机制
        :param embed_dim: 嵌入维度,即每个词的表示大小
        :param num_heads: 注意力头的数量
        :param key_size: 键的维度
        :param value_size: 值的维度
        :param bias: 是否使用偏置
        """
        super().__init__()
        self.embed_dim = embed_dim
        self.num_heads = num_heads
        self.q_head_dim = key_size // num_heads
        self.k_head_dim = key_size // num_heads
        self.v_head_dim = value_size // num_heads
        
        # 定义用于生成查询、键和值的线性层
        self.W_q = nn.Linear(embed_dim, key_size, bias=bias)  # 查询的线性变换
        self.W_k = nn.Linear(embed_dim, key_size, bias=bias)  # 键的线性变换
        self.W_v = nn.Linear(embed_dim, value_size, bias=bias)  # 值的线性变换

        # 定义用于投影的线性层
        self.q_proj = nn.Linear(key_size, key_size, bias=bias)
        self.k_proj = nn.Linear(key_size, key_size, bias=bias)
        self.v_proj = nn.Linear(value_size, value_size, bias=bias)
        self.out_proj = nn.Linear(value_size, embed_dim, bias=bias)

    def forward(self, x):
        """
        前向传播
        :param x: 输入张量,形状为 (N, L, embed_dim),
                  其中 N 是批次大小,L 是序列长度,embed_dim 是嵌入维度
        :return: 输出张量,形状为 (N, L, embed_dim)
        """
        query = self.W_q(x)  # 通过查询线性层,形状为 (N, L, key_size)
        key = self.W_k(x)    # 通过键线性层,形状为 (N, L, key_size)
        value = self.W_v(x)  # 通过值线性层,形状为 (N, L, value_size)
        
        # 通过投影层计算实际的 q, k, v
        q, k, v = self.q_proj(query), self.k_proj(key), self.v_proj(value)
        N, L, _ = v.size()

        # 重新调整形状以适应多头注意力
        q = q.reshape(N, L, self.num_heads, self.q_head_dim).transpose(1, 2)
        k = k.reshape(N, L, self.num_heads, self.k_head_dim).transpose(1, 2)
        v = v.reshape(N, L, self.num_heads, self.v_head_dim).transpose(1, 2)

        # 计算注意力分数
        att = torch.matmul(q, k.transpose(-1, -2)) / math.sqrt(self.k_head_dim)
        att = F.softmax(att, dim=-1)
        
        # 计算输出
        output = torch.matmul(att, v)
        output = output.transpose(1, 2).reshape(N, L, -1)
        output = self.out_proj(output)
        
        return output

if __name__ == "__main__":
    # 示例用法
    embed_dim = 64  # 嵌入维度
    num_heads = 8  # 注意力头数量
    key_size = 64  # 键的维度
    value_size = 64  # 值的维度
    batch_size = 2  # 批次大小
    seq_length = 10  # 序列长度

    # 创建 MultiHeadSelfAttention 类的实例
    model = MultiHeadSelfAttention(embed_dim, num_heads, key_size, value_size)

    # 生成随机输入张量,形状为 (N, L, embed_dim)
    x = torch.randn(batch_size, seq_length, embed_dim)

    # 执行前向传播
    output = model(x)

    print("输入张量的形状:", x.shape)
    print("输出张量的形状:", output.shape)

3.2 位置编码(Positional Encoding)

位置编码是一种用于为Transformer模型引入词的位置信息的技术。由于Transformer模型本身不具备序列建模的能力,在不考虑词的位置顺序的情况下,无法理解词在序列中的具体位置。位置编码通过为每个位置赋予一个独特的编码向量,使得Transformer模型能够区分不同位置的词,从而能够建模序列信息。

位置编码通常采用正弦函数和余弦函数来生成。假设输入序列的长度为L,每个位置对应一个d维的位置编码向量,其中d是Transformer模型的隐藏单元维度。对于位置编码,每个维度上的数值是根据正弦和余弦函数定制的,并且依赖于位置和维度。具体而言,对于位置p和维度i,位置编码的数值可以表示为:

PE_{pos,2i} = \sin \frac{pos}{10000^{\frac{2i}{d_{model}}}}

PE_{pos,2i+1} = \cos \frac{pos}{10000^{\frac{2i}{d_{model}}}}

其中PE(p, 2i)和PE(p, 2i+1)分别表示位置p和维度i上的位置编码值。

生成位置编码后,将其与输入序列的词嵌入向量相加,得到包含位置信息的输入向量。这样,Transformer模型在进行自注意力机制和前馈神经网络操作时,就能够区分不同位置的词,并更好地捕捉到序列中的顺序信息。

需要注意的是,位置编码不对词的语义特征做任何调整,仅仅是引入了位置信息。在模型训练和推理过程中,位置编码是固定的,不会被更新。这保证了位置编码能够提供一致的位置信息。

通过引入位置编码,Transformer模型能够更好地处理序列数据,提高了模型在自然语言处理等任务中的性能

import torch
import torch.nn as nn

class SinPositionEncoding(nn.Module):
    def __init__(self, max_sequence_length, d_model, base=10000):
        """
        初始化位置编码类
        
        :param max_sequence_length: 最大序列长度
        :param d_model: 嵌入维度
        :param base: 位置编码的基数(默认为10000)
        """
        super().__init__()
        self.max_sequence_length = max_sequence_length  # 序列长度
        self.d_model = d_model  # 嵌入维度
        self.base = base  # 基数

    def forward(self):
        """
        计算位置编码
        
        :return: 位置编码矩阵
        """
        # 创建一个全零矩阵,大小为 (max_sequence_length, d_model)
        pe = torch.zeros(self.max_sequence_length, self.d_model, dtype=torch.float)
        
        # 初始化一半维度,sin位置编码的维度被分为了两部分
        exp_1 = torch.arange(self.d_model // 2, dtype=torch.float)
        exp_value = exp_1 / (self.d_model / 2)

        # 计算 alpha 值
        alpha = 1 / (self.base ** exp_value)
        
        # 计算位置编码矩阵的每个位置的编码值
        out = torch.arange(self.max_sequence_length, dtype=torch.float)[:, None] @ alpha[None, :]
        
        # 计算正弦和余弦编码
        embedding_sin = torch.sin(out)
        embedding_cos = torch.cos(out)

        # 将奇数位置设置为sin值,偶数位置设置为cos值
        pe[:, 0::2] = embedding_sin
        pe[:, 1::2] = embedding_cos
        
        return pe

# 主函数部分,用于测试
if __name__ == "__main__":
    # 创建 SinPositionEncoding 实例
    encoding = SinPositionEncoding(max_sequence_length=10, d_model=4, base=10000)
    
    # 计算位置编码
    pe = encoding.forward()
    
    # 打印结果
    print("位置编码矩阵:")
    print(pe)

    # 输入示例
    # max_sequence_length=10, d_model=4
    # 输出矩阵的大小为 (10, 4),其中每一行代表一个位置的编码

四、指令微调

4.1 指令微调的必要性

指令微调(Instruction Tuning) 是指在预训练后对大语言模型进行进一步的训练,以优化其在特定任务或指令下的表现。该方法的关键步骤包括:

  • 数据收集:获取包含自然语言指令和对应目标输出的数据集。
  • 微调过程:使用这些数据对模型进行微调,使模型能够更好地理解和执行给定的指令。

这种技术在 2022 年由谷歌研究员在 ICLR 论文中提出,也被称为有监督微调(Supervised Fine-tuning)或多任务提示训练(Multitask Prompted Training)。

4.2 指令微调的数据集构建

4.2.1 基于现有的NLP任务数据集构建

4.2.2 基于日常对话数据构建

4.2.3 基于合成数据构建

五、人类对齐

5.1 人类对齐的必要性

尽管大语言模型在许多任务中表现优异,但它们可能会出现以下问题:

  • 错误和不准确:模型有时可能生成不准确或错误的信息。
  • 有害和误导性内容:模型可能生成有害、误导性或带有偏见的内容。

为解决这些问题,研究者们致力于改进模型的对齐技术,以确保其输出符合预期的伦理和安全标准。例如下面的输入输出,不使用人类对齐的话,会答出农村地区更适合发展污染较重的产业,但是经过人类对齐之后,回答会跟符合人类的道德认知。

5.2 人类对齐的方法

通常使用人类反馈的强化学习(Reinforcement Learning from Human Feedback, RLHF)来进行人类对齐。

5.3 强化学习训练方式

5.3.1 策略梯度算法

策略梯度(Policy Gradient)是一种基础的强化学习算法,训练策略模型在与外部环境交互的过程中学习到较好的更新策略。

策略梯度算法存在训练慢且不稳定的问题,因此实践中一般使用PPO算法。

5.3.2 PPO算法

PPO算法是一种近端策略优化算法,它通过优势估计、重要性采样、基于梯度裁剪的目标函数和基于KL散度的目标函数等关键点来提高策略梯度算法的效果。

首先,PPO算法使用优势估计来更准确地评估决策轨迹能获得的奖励。优势估计通过估计每个动作的优势值,即当前策略相对于基准策略的优势程度,来更好地指导策略更新。

其次,PPO算法利用重要性采样来进行离线策略训练。重要性采样可以在在线采样和离线训练之间建立联系,通过利用历史数据进行离线训练,提高样本利用效率。

另外,PPO算法使用基于梯度裁剪的目标函数来控制策略更新的步长和方向。梯度裁剪可以防止策略更新过于剧烈,避免策略在训练过程中发生较大的变化,从而增强算法的稳定性。

最后,PPO算法还使用基于KL散度的目标函数来约束策略更新的幅度。KL散度可以衡量两个概率分布之间的差异,通过控制KL散度的大小,可以限制策略更新的幅度,避免策略更新过于剧烈,进一步提升算法的稳定性。

综上所述,PPO算法在强化学习中具有重要的应用价值,通过优势估计、重要性采样、基于梯度裁剪的目标函数和基于KL散度的目标函数等关键点,可以提高策略梯度算法的训练效果。

六、RAG(Retrieval-Augmented Generation)

6.1 RAG的必要性

大语言模型在生成文本时可能会出现“幻觉”的情况。这意味着模型可能会生成看似连贯但事实上不准确或不可靠的信息。这是因为大语言模型缺乏对事实的准确性和可靠性的验证。

RAG的出现是为了解决这个问题。通过引入检索模型,RAG可以从可靠和相关的文本数据中提取信息,然后使用生成模型将其转化为自然语言文本。检索模型可以帮助RAG选择合适的上下文信息,并确保生成的文本更加准确和可靠。

因此,RAG的出现是为了提高大语言模型在生成文本时的准确性和可靠性。它可以帮助避免“幻觉”,并生成更具实际意义和可信度的文本。

6.2 RAG的原理

RAG(检索增强生成)是结合了检索和生成的模型架构。它的基本思想是将信息检索过程与生成模型相结合:

  • 检索步骤:从外部知识库或数据库中检索相关的信息。
  • 生成步骤:利用检索到的信息来生成更准确和上下文相关的回答。

这种方法可以帮助模型在处理信息密集型任务时更好地利用外部知识。

七、提示学习

提示学习(Prompt Learning),也被称为 提示工程(Prompt Engineering),是指通过设计和调整输入提示来引导模型产生期望的输出。关键点包括:

  • 提示设计:创建有效的提示语句,以引导模型生成所需的回答或行为。
  • 提示优化:通过实验和调整提示的形式来改进模型的表现。

提示学习是提高大语言模型性能的一种重要方法,尤其在特定任务或应用场景中能够显著提升模型的效果。

八、智能体

大语言模型智能体的构建通常涉及以下三个基本组件:

  1. 记忆组件(Memory):存储模型在交互过程中获得的信息和上下文,用于长期知识的积累和使用。

  2. 规划组件(Planning):负责制定和优化行动计划,以完成特定的任务或目标。

  3. 执行组件(Execution):执行计划并进行实际的操作或交互。

通过这三个组件的协作,智能体能够有效地感知环境、制定策略并执行任务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值