Chapter3 大语言模型
Chapter3 大语言模型
1.文档阅读
1.1 语言模型与Transformer架构
-
从N-gram 到RNN
N-gram
-
N-gram模型是基于马尔可夫假设建立的语言模型。这里的 “N” 代表我们考虑的上下文窗口大小。
-
马尔可夫假设是为了解决概率的链式法则难以实现而提出的,其核心思想是:我们不必回溯一个词的全部历史,可以近似的认为一个词出现的概率只与它前面有限的n-1个词有关。
例子:
-
Bigram(当N=2时)
P ( w i ∣ w 1 , . . . , w i − 1 ) ≈ P ( w i ∣ w i − 1 ) P(w_i|w_1,...,w_{i-1})\approx P(w_i|w_{i-1}) P(wi∣w1,...,wi−1)≈P(wi∣wi−1)
-
Trigram(当N=3时)
P ( w i ∣ w 1 , . . . , w i − 1 ) ≈ P ( w i ∣ w i − 2 , w i − 1 ) P(w_i|w_1,...,w_{i-1})\approx P(w_i|w_{i-2},w_{i-1}) P(wi∣w1,...,wi−1)≈P(wi∣wi−2,wi−1)
这些概率可以通过最大似然估计(Maximum Likelihood Estimation,MLE)来计算。
例:
P ( w i ∣ w i − 1 ) = C o u n t ( w i − 1 , w i ) C o u n t ( w i − 1 ) P(w_i|w_{i-1})=\frac{Count(w{i-1},w_{i})}{Count(w_{i-1})} P(wi∣wi−1)=Count(wi−1)Count(wi−1,wi)
其中
- C o u n t ( w i − 1 , w i ) Count(w_{i-1},w_i) Count(wi−1,wi)表示词对 ( w i − 1 , w i ) (w_{i-1},w_i) (wi−1,wi)在语料库中连续出现的次数。
- C o u n t ( w i − 1 ) Count(w_{i-1}) Count(wi−1)表示单个词 w i − 1 w_{i-1} wi−1在语料库中出现的次数。
-
-
N-gram的缺陷:
- 数据稀疏性
- 泛化能力差
神经网络
N-gram 模型的根本缺陷在于它将词视为孤立、离散的符号。为了克服这个问题,研究者们转向了神经网络,并提出了一种思想:用连续的向量来表示词。
-
前馈神经网络语言模型 (Feedforward Neural Network Language Model)
核心思想:- 构建一个语义空间
- 学习从上下文到下一个词的映射
我们将词转换成了向量,所以可以用数学工具来度量它们之间的关系。最常用的方法是余弦相似度(Cosine Similarity)
s i m i l a r i t y ( a ⃗ , b ⃗ ) = cos ( θ ) = a ⃗ ⋅ b ⃗ ∣ a ⃗ ∣ ∣ b ⃗ ∣ similarity(\vec{a},\vec{b})=\cos(\theta)=\frac{\vec{a} \cdot \vec{b}}{|\vec{a}||\vec{b}|} similarity(a,b)=cos(θ)=∣a∣∣b∣a⋅b
-
循环神经网络(RNN):
提出:打破固定窗口的限制,核心思想是为网络增加“记忆能力”
缺点:长期依赖问题
-
长短时记忆网络(LSTM)
提出:解决长期依赖问题,核心创新在于引入了细胞状态 (Cell State) 和一套精密的门控机制 (Gating Mechanism)
-
-
Transformer架构解析
Transformer 完全抛弃了循环结构,依赖注意力机制来捕捉序列内的依赖关系,从而实现了真正意义上的并行计算。
2.1 Encoder-Decoder整体架构
编码器:任务是“理解”输入的整个句子。
解码器:任务是“生成”目标句子
2.2 注意力机制
自注意力(Self-Attention)机制允许模型在处理序列中的每个词时,都能兼顾句子中的所有其他词,并为这些词分配不同的注意力权重。权重越高的词,代表其与当前词的关联性越强,其信息也应该在当前词的表示中占据更大的比重。
为了实现上述过程,自注意力机制为每个输入的词元向量引入了三个可学习的角色
- 查询(Query,Q):代表当前词元,它正在主动的“查询”其他词元以获得信息。
- 键(Key,K):代表句子中可被查询的词元“标签”或“索引”。
- 值(Value,V):代表词元本身所携带的“内容”或“信息”。
这三个向量都是由原始的词嵌入向量乘以三个不同的、可学习的权重矩阵( W Q , W K , W V W^Q,W^K,W^V WQ,WK,WV)得到的。
计算过程公式表示:
A t t e n t i o n ( Q , K , V ) = s o f t m a x ( Q K T d k ) V Attention(Q,K,V)=softmax(\frac{QK^T}{\sqrt{d_k}})V Attention(Q,K,V)=softmax(dkQKT)V
只进行一次的注意力计算:单头注意力,多头注意力的思想:把一次做完然后分成几组,分开做,在合并。
2.3 前馈神经网络
在每个Encoder和Decoder层中,多头注意力子层之后都跟着一个逐位置前馈网络。这个前馈网络会独立的作用于序列中的每一个词元向量。
F F N ( x ) = m a x ( 0 , x W 1 + b 1 ) W 2 + b 2 FFN(x)=max(0,xW_1+b_1)W_2+b_2 FFN(x)=max(0,xW1+b1)W2+b2
2.4 残差连接与层归一化
在 Transformer 的每个编码器和解码器层中,所有子模块(如多头注意力和前馈网络)都被一个 Add & Norm 操作包裹。这个组合是为了保证 Transformer 能够稳定训练。
2.5 位置编码
为了解决自注意力机制计算不包括任何关于词元顺序或位置的信息,引入位置编码。
-
Decoder-only 架构
Decoder-only架构的工作模式被称为自回归(Autoregressive)描述的过程如下:
- 给模型一个起始文本(例如“Datawhale Agent is”)
- 模型预测出下一个最有可能的词(例如 “a”)
- 模型将自己刚刚生成的词“a”添加到输入文本的末尾,形成新的输入
- 模型基于这个新输入,再次预测下一个词
- 不断重复这个过程,直到生成完整的句子或达到停止条件
解码器通过掩码自注意力保证在预测第t个词时,不去偷看第t+1个词的答案。
在自注意力机制计算出注意力分数矩阵(即每个词对其他所有词的关注度得分)之后,但在进行 Softmax 归一化之前,模型会应用一个“掩码”。这个掩码会将所有位于当前位置之后(即目前尚未观测到)的词元对应的分数,替换为一个非常大的负数。当这个带有负无穷分数的矩阵经过 Softmax 函数时,这些位置的概率就会变为 0。这样一来,模
型在计算任何一个位置的输出时,都从数学上被阻止了去关注它后面的信息。这种机制保证了模型在预测下一个词时,能且仅能依赖它已经见过的、位于当前位置之前的所有信息,从而确保了预测的公平性和逻辑的连贯性。
1.2 与大语言模型交互
-
提示工程
研究如何设计精准的提示,从而引导模型产生我们期望输出的回复。
模型采样参数 :
大模型中经常有Temperature的可配置参数,其本质是通过调整模型对“概论分布”的采样策略,让输出匹配具体场景需求。
采样参数本质是在Softmax公式的基础上,根据不同策略“重新调整”或“截断”分布,从而改变大模型输出的下一个token。
- Temperture:温度是控制模型输出“随机性”与“确定性”的关键参数。其原理是引入温度系数
T
>
0
T\gt0
T>0,将Softmax改写为
p
i
(
T
)
=
e
z
i
/
T
∑
j
=
1
k
e
z
j
/
T
p_i^{(T)} = \frac{e^{z_i / T}}{\sum_{j=1}^k e^{z_j / T}}
pi(T)=∑j=1kezj/Tezi/T
- 低温度时输出更“精确、确定”。
- 中温度时输出“平衡、自然”。
- 高温度时输出“创新、发散”
- Top-k:其原理是将所有token按概率从高到低排序,取排名前k个token组成“候选集”,随后对筛选出的k个token的概率进行“归一化”
- 与温度采样的区别与联系:温度采样通过温度 T 调整所有 token 的概率分布(平滑或陡峭),不改变候选token 的数量(仍考虑全部 N 个)。Top-k 采样通过 k 值限制候选 token 的数量(只保留前 k 个高概率token),再从其中采样。当k=1时输出完全确定,退化为 “贪心采样”
- Top-p:其原理是将所有的token按概率从高到低排序,从排序后第一个token开始,逐步累加概率,直到累积和首次达到或超过阈值$$,此时累加过程中所有token组成“核集合”,最后对核集合进行归一化。
零样本、单样本与少样本
零样本提示 (Zero-shot Prompting) 这指的是我们不给模型任何示例,直接让它根据指令完成任务。这得益于模型在海量数据上预训练后获得的强大泛化能力。
单样本提示 (One-shot Prompting) 我们给模型提供一个完整的示例,向它展示任务的格式和期望的输出风格。我们给模型提供一个完整的示例,向它展示任务的格式和期望的输出风格。
少样本提示 (Few-shot Prompting) 我们提供多个示例,这能让模型更准确地理解任务的细节、边界和细微差别,从而获得更好的性能。
- Temperture:温度是控制模型输出“随机性”与“确定性”的关键参数。其原理是引入温度系数
T
>
0
T\gt0
T>0,将Softmax改写为
p
i
(
T
)
=
e
z
i
/
T
∑
j
=
1
k
e
z
j
/
T
p_i^{(T)} = \frac{e^{z_i / T}}{\sum_{j=1}^k e^{z_j / T}}
pi(T)=∑j=1kezj/Tezi/T
-
文本分词
将文本序列转换为数字序列的过程,叫做分词。分词器的作用 是定义一套规则,将原始文本切分成一个个最小的单元,称之为词元(Token)。
为了兼顾词表大小和语义表达,现代大语言模型普遍采用子词分词 (Subword Tokenization) 算法。它的核心思想是:将常见的词(如 “agent”)保留为完整的词元,同时将不常见的词(如 “Tokenization”)拆分成多个有意义的子词片段(如 “Token” 和 “ization”)。这样既控制了词表的大小,又能让模型通过组合子词来理解和生成新词。
- 字节对编码算法解析
字节对编码 (Byte-Pair Encoding, BPE) 是最主流的子词分词算法之一,GPT系列模型就采用了这种算法。其核心思想非常简洁,可以理解为一个“贪心”的合并过程:
- 初始化:将词表初始化为所有在语料库中出现过的基本字符。
- 迭代合并:在语料库上,统计所有相邻词元对的出现频率,找到频率最高的一对,将它们合并成一个新的词元,并加入词表。
- 重复:重复第 2 步,直到词表大小达到预设的阈值。
-
模型的选择
公开的基准测试排行榜,可以用来评估模型的综合能力
1.3 大语言模型的缩放法则与局限性
-
缩放法则
**缩放法则(Scaling Laws)**发现为大语言模型的持续发展提供了理论指导,阐明了增加资源投入能够系统性提升模型性能的底层逻辑。
缩放法则最令人惊奇的产物是**“能力的涌现”**。所谓能力涌现,是指当模型规模达到一定阈值后,会突然展现出在小规模模型中完全不存在或表现不佳的全新能力。
-
模型幻觉
**模型幻觉(Hallucination)**通常指的是大语言模型生成的内容与客观事实、用户输入或上下文信息相矛盾,或者生成了不存在的事实、实体或事件。
2.代码学习
2.1 N_gram
使用Bigram(N=2),每次考察连续的两个词
import collections
# 示例语料库,与上方案例讲解中的语料库保持一致
corpus = "datawhale agent learns datawhale agent works"
tokens = corpus.split()
total_tokens = len(tokens)
print(tokens) #['datawhale', 'agent', 'learns', 'datawhale', 'agent', 'works']
# --- 第一步:计算 P(datawhale) ---
count_datawhale = tokens.count('datawhale')
p_datawhale = count_datawhale / total_tokens
print(f"第一步: P(datawhale) = {count_datawhale}/{total_tokens} = {p_datawhale:.3f}")
# --- 第二步:计算 P(agent|datawhale) ---
# 先计算 bigrams 用于后续步骤
bigrams = zip(tokens, tokens[1:])
bigram_counts = collections.Counter(bigrams)
count_datawhale_agent = bigram_counts[('datawhale', 'agent')]
# count_datawhale 已在第一步计算
p_agent_given_datawhale = count_datawhale_agent / count_datawhale
print(f"第二步: P(agent|datawhale) = {count_datawhale_agent}/{count_datawhale} = {p_agent_given_datawhale:.3f}")
# --- 第三步:计算 P(learns|agent) ---
count_agent_learns = bigram_counts[('agent', 'learns')]
count_agent = tokens.count('agent')
p_learns_given_agent = count_agent_learns / count_agent
print(f"第三步: P(learns|agent) = {count_agent_learns}/{count_agent} = {p_learns_given_agent:.3f}")
# --- 最后:将概率连乘 ---
p_sentence = p_datawhale * p_agent_given_datawhale * p_learns_given_agent
print(f"最后: P('datawhale agent learns') ≈ {p_datawhale:.3f} * {p_agent_given_datawhale:.3f} * {p_learns_given_agent:.3f} = {p_sentence:.3f}")
2.2 Word_Embedding
一个著名的例子展示了词向量捕捉到的语义关系: vector('King') - vector('Man') + vector('Woman') 这个向量运算的结果,在向量空间中与 vector('Queen') 的位置惊人地接近。
代码如下:
import numpy as np
# 假设我们已经学习到了简化的二维词向量
embeddings = {
"king": np.array([0.9, 0.8]),
"queen": np.array([0.9, 0.2]),
"man": np.array([0.7, 0.9]),
"woman": np.array([0.7, 0.3])
}
def cosine_similarity(vec1, vec2):
dot_product = np.dot(vec1, vec2)
norm_product = np.linalg.norm(vec1) * np.linalg.norm(vec2)
return dot_product / norm_product
# king - man + woman
result_vec = embeddings["king"] - embeddings["man"] + embeddings["woman"]
# 计算结果向量与 "queen" 的相似度
sim = cosine_similarity(result_vec, embeddings["queen"])
print(f"king - man + woman 的结果向量: {result_vec}")
print(f"该结果与 'queen' 的相似度: {sim:.4f}")
2.3 Transformer
import torch
import torch.nn as nn
import math
import copy
class MultiHeadAttention(nn.Module):
"""
多头注意力机制模块
"""
def __init__(self, d_model, num_heads):
super(MultiHeadAttention, self).__init__()
"""
d_model: 输入和输出的特征维度
num_heads: 注意力头的数量
多头自注意力:
将原始的 Q, K, V 向量在维度上切分成 h 份(h 就是“头”数),
每一份都独立地进行一次单头注意力的计算。
这就好比让 h 个不同的“专家”从不同的角度去审视句子,
每个专家都能捕捉到一种不同的特征关系。
最后,将这 h 个专家的“意见”(即输出向量)拼接起来,
再通过一个线性变换进行整合,就得到了最终的输出。
"""
assert d_model % num_heads == 0, "d_model 必须能被 num_heads 整除"
self.d_model = d_model
self.num_heads = num_heads
self.d_k = d_model // num_heads
# 定义 Q, K, V 和输出的线性变换层
self.W_q = nn.Linear(d_model, d_model)
self.W_k = nn.Linear(d_model, d_model)
self.W_v = nn.Linear(d_model, d_model)
self.W_o = nn.Linear(d_model, d_model)
def scaled_dot_product_attention(self, Q, K, V, mask=None):
# 1. 计算注意力得分 (QK^T)
attn_scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
# 2. 应用掩码 (如果提供)
if mask is not None:
# 将掩码中为 0 的位置设置为一个非常小的负数,这样 softmax 后会接近 0
attn_scores = attn_scores.masked_fill(mask == 0, -1e9)
# 3. 计算注意力权重 (Softmax)
attn_probs = torch.softmax(attn_scores, dim=-1)
# 4. 加权求和 (权重 * V)
output = torch.matmul(attn_probs, V)
return output
def split_heads(self, x):
# 将输入 x 的形状从 (batch_size, seq_length, d_model)
# 变换为 (batch_size, num_heads, seq_length, d_k)
# 用transpose调整维度顺序以适应多头计算
batch_size, seq_length, d_model = x.size()
return x.view(batch_size, seq_length, self.num_heads, self.d_k).transpose(1, 2)
def combine_heads(self, x):
# 将输入 x 的形状从 (batch_size, num_heads, seq_length, d_k)
# 变回 (batch_size, seq_length, d_model)
batch_size, num_heads, seq_length, d_k = x.size()
return x.transpose(1, 2).contiguous().view(batch_size, seq_length, self.d_model)
def forward(self, Q, K, V, mask=None):
# 1. 对 Q, K, V 进行线性变换
Q = self.split_heads(self.W_q(Q))
K = self.split_heads(self.W_k(K))
V = self.split_heads(self.W_v(V))
# 2. 计算缩放点积注意力
attn_output = self.scaled_dot_product_attention(Q, K, V, mask)
# 3. 合并多头输出并进行最终的线性变换
output = self.W_o(self.combine_heads(attn_output))
return output
class PositionWiseFeedForward(nn.Module):
"""
位置前馈网络模块
"""
def __init__(self, d_model, d_ff, dropout=0.1):
super(PositionWiseFeedForward, self).__init__()
self.linear1 = nn.Linear(d_model, d_ff)
self.dropout = nn.Dropout(dropout)
self.linear2 = nn.Linear(d_ff, d_model)
self.relu = nn.ReLU()
def forward(self, x):
# x 形状: (batch_size, seq_len, d_model)
x = self.linear1(x)
x = self.relu(x)
x = self.dropout(x)
x = self.linear2(x)
# 最终输出形状: (batch_size, seq_len, d_model)
return x
class PositionalEncoding(nn.Module):
"""
为输入序列的词嵌入向量添加位置编码。
"""
def __init__(self, d_model: int, dropout: float = 0.1, max_len: int = 5000):
super().__init__()
self.dropout = nn.Dropout(p=dropout)
# 创建一个足够长的位置编码矩阵
position = torch.arange(max_len).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2) * (-math.log(10000.0) / d_model))
# pe (positional encoding) 的大小为 (max_len, d_model)
pe = torch.zeros(max_len, d_model)
# 偶数维度使用 sin, 奇数维度使用 cos
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
# 将 pe 注册为 buffer,这样它就不会被视为模型参数,但会随模型移动(例如 to(device))
self.register_buffer('pe', pe.unsqueeze(0))
def forward(self, x: torch.Tensor) -> torch.Tensor:
# x.size(1) 是当前输入的序列长度
# 将位置编码加到输入向量上
x = x + self.pe[:, :x.size(1)]
return self.dropout(x)
class EncoderLayer(nn.Module):
"""
编码器核心层
"""
def __init__(self, d_model, num_heads, d_ff, dropout):
super(EncoderLayer, self).__init__()
self.self_attn = MultiHeadAttention(d_model, num_heads)
self.feed_forward = PositionWiseFeedForward(d_model, d_ff, dropout)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x, mask):
# 1. 多头自注意力
attn_output = self.self_attn(x, x, x, mask)
x = self.norm1(x + self.dropout(attn_output))
# 2. 前馈网络
ff_output = self.feed_forward(x)
x = self.norm2(x + self.dropout(ff_output))
return x
class DecoderLayer(nn.Module):
"""
解码器核心层
"""
def __init__(self, d_model, num_heads, d_ff, dropout):
super(DecoderLayer, self).__init__()
self.self_attn = MultiHeadAttention(d_model, num_heads)
self.cross_attn = MultiHeadAttention(d_model, num_heads)
self.feed_forward = PositionWiseFeedForward(d_model, d_ff, dropout)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.norm3 = nn.LayerNorm(d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x, encoder_output, src_mask, tgt_mask):
# 1. 掩码多头自注意力 (对自己)
attn_output = self.self_attn(x, x, x, tgt_mask)
x = self.norm1(x + self.dropout(attn_output))
# 2. 交叉注意力 (对编码器输出)
cross_attn_output = self.cross_attn(x, encoder_output, encoder_output, src_mask)
x = self.norm2(x + self.dropout(cross_attn_output))
# 3. 前馈网络
ff_output = self.feed_forward(x)
x = self.norm3(x + self.dropout(ff_output))
return x
class Encoder(nn.Module):
def __init__(self, vocab_size, d_model, num_layers, num_heads, d_ff, dropout, max_len):
super(Encoder, self).__init__()
self.embedding = nn.Embedding(vocab_size, d_model)
self.pos_encoder = PositionalEncoding(d_model, dropout, max_len)
self.layers = nn.ModuleList([EncoderLayer(d_model, num_heads, d_ff, dropout) for _ in range(num_layers)])
self.norm = nn.LayerNorm(d_model)
def forward(self, x, mask):
x = self.embedding(x)
x = self.pos_encoder(x)
for layer in self.layers:
x = layer(x, mask)
return self.norm(x)
class Decoder(nn.Module):
def __init__(self, vocab_size, d_model, num_layers, num_heads, d_ff, dropout, max_len):
super(Decoder, self).__init__()
self.embedding = nn.Embedding(vocab_size, d_model)
self.pos_encoder = PositionalEncoding(d_model, dropout, max_len)
self.layers = nn.ModuleList([DecoderLayer(d_model, num_heads, d_ff, dropout) for _ in range(num_layers)])
self.norm = nn.LayerNorm(d_model)
def forward(self, x, encoder_output, src_mask, tgt_mask):
x = self.embedding(x)
x = self.pos_encoder(x)
for layer in self.layers:
x = layer(x, encoder_output, src_mask, tgt_mask)
return self.norm(x)
class Transformer(nn.Module):
def __init__(self, src_vocab_size, tgt_vocab_size, d_model, num_layers, num_heads, d_ff, dropout, max_len=5000):
super(Transformer, self).__init__()
self.encoder = Encoder(src_vocab_size, d_model, num_layers, num_heads, d_ff, dropout, max_len)
self.decoder = Decoder(tgt_vocab_size, d_model, num_layers, num_heads, d_ff, dropout, max_len)
self.final_linear = nn.Linear(d_model, tgt_vocab_size)
def generate_mask(self, src, tgt):
# src_mask: (batch_size, 1, 1, src_len)
src_mask = (src != 0).unsqueeze(1).unsqueeze(2)
# tgt_mask: (batch_size, 1, tgt_len, tgt_len)
tgt_pad_mask = (tgt != 0).unsqueeze(1).unsqueeze(2) # (batch_size, 1, 1, tgt_len)
tgt_len = tgt.size(1)
# 下三角矩阵,用于防止看到未来的 token
tgt_sub_mask = torch.tril(torch.ones((tgt_len, tgt_len), device=src.device)).bool() # (tgt_len, tgt_len)
tgt_mask = tgt_pad_mask & tgt_sub_mask
return src_mask, tgt_mask
def forward(self, src, tgt):
src_mask, tgt_mask = self.generate_mask(src, tgt)
encoder_output = self.encoder(src, src_mask)
decoder_output = self.decoder(tgt, encoder_output, src_mask, tgt_mask)
output = self.final_linear(decoder_output)
return output
# --- 演示如何使用模型 ---
if __name__ == "__main__":
# 1. 定义超参数
src_vocab_size = 5000
tgt_vocab_size = 5000
d_model = 512
num_layers = 6
num_heads = 8
d_ff = 2048
dropout = 0.1
max_len = 100
# 2. 实例化模型
model = Transformer(src_vocab_size, tgt_vocab_size, d_model, num_layers, num_heads, d_ff, dropout, max_len)
# 3. 创建模拟输入数据
# 假设 batch_size=2, src_seq_len=10, tgt_seq_len=12
src = torch.randint(1, src_vocab_size, (2, 10)) # (batch_size, seq_length)
tgt = torch.randint(1, tgt_vocab_size, (2, 12)) # (batch_size, seq_length)
# 4. 模型前向传播
output = model(src, tgt)
# 5. 打印输出形状
print("模型输出的形状:", output.shape)
# 预期输出: torch.Size([2, 12, 5000]) -> (batch_size, tgt_seq_len, tgt_vocab_size)
2.4 BPE
import re, collections
def get_stats(vocab):
"""统计词元对频率"""
pairs = collections.defaultdict(int)
for word, freq in vocab.items():
symbols = word.split()
for i in range(len(symbols)-1):
pairs[symbols[i],symbols[i+1]] += freq
return pairs
def merge_vocab(pair, v_in):
"""合并词元对"""
v_out = {}
bigram = re.escape(' '.join(pair))
p = re.compile(r'(?<!\S)' + bigram + r'(?!\S)')
for word in v_in:
w_out = p.sub(''.join(pair), word)
v_out[w_out] = v_in[word]
return v_out
# 准备语料库,每个词末尾加上</w>表示结束,并切分好字符
vocab = {'h u g </w>': 1, 'p u g </w>': 1, 'p u n </w>': 1, 'b u n </w>': 1}
num_merges = 4 # 设置合并次数
for i in range(num_merges):
pairs = get_stats(vocab)
if not pairs:
break
best = max(pairs, key=pairs.get)
vocab = merge_vocab(best, vocab)
print(f"第{i+1}次合并: {best} -> {''.join(best)}")
print(f"新词表(部分): {list(vocab.keys())}")
print("-" * 20)
2.5 Qwen
运行结果:

3.习题
-
自然语言处理中,语言模型经历了从统计到神经网络的模型演进。
-
请使用本章提供的迷你语料库(
datawhale agent learns,datawhale agent works),计算句子agent works在Bigram模型下的概率answer:同样的 N = 2 N=2 N=2
代码如下:
import collections # 示例语料库,与上方案例讲解中的语料库保持一致 corpus = "datawhale agent learns datawhale agent works" tokens = corpus.split() total_tokens = len(tokens) print(tokens) #['datawhale', 'agent', 'learns', 'datawhale', 'agent', 'works'] # --- 第一步:计算 P(agent) --- count_agent = tokens.count('agent') p_agent = count_agent / total_tokens print(f"第一步: P(agent) = {count_agent}/{total_tokens} = {p_agent:.3f}") # --- 第二步:计算 P(works|agent) --- # 先计算 bigrams 用于后续步骤 bigrams = zip(tokens, tokens[1:]) bigram_counts = collections.Counter(bigrams) count_agent_works = bigram_counts[('agent', 'works')] # count_agent 已在第一步计算 p_works_given_agent = count_agent_works / count_agent print(f"第二步: P(works|agent) = {count_agent_works}/{count_agent} = {p_works_given_agent:.3f}") # # --- 第三步:计算 P(learns|agent) --- # count_agent_learns = bigram_counts[('agent', 'learns')] # count_agent = tokens.count('agent') # p_learns_given_agent = count_agent_learns / count_agent # print(f"第三步: P(learns|agent) = {count_agent_learns}/{count_agent} = {p_learns_given_agent:.3f}") # --- 最后:将概率连乘 --- p_sentence = p_agent * p_works_given_agent # * p_learns_given_agent print(f"最后: P('agent works') ≈ {p_agent:.3f} * {p_works_given_agent:.3f} = {p_sentence:.3f}")
运行结果
-
N-gram模型的核心假设是马尔可夫假设。请解释这个假设的含义,以及N-gram模型存在哪些根本性局限?
answer:马尔可夫假设的含义:一个词出现的改了只与它前面有限的n-1个词有关。
N-gram模型的根本性局限:1. 数据稀疏性 2. 泛化能力差
-
神经网络语言模型(RNN/LSTM)和Transformer分别是如何克服N-gram模型局限的?它们各自的优势是什么?
answer:RNN:为网络增加记忆能力,打破固定窗口的限制
LSTM:引入细胞状态和门控机制,解决长期依赖的问题
Transformer:解决按顺序处理数据的问题,实现了并行计算。
-
-
Transformer架构[4]是现代大语言模型的基础。其中:
提示:可以结合本章3.1.2节的代码实现来辅助理解
-
自注意力机制(Self-Attention)的核心思想是什么?
A t t e n t i o n ( Q , K , V ) = s o f t m a x ( Q K T d k ) V Attention(Q,K,V)=softmax(\frac{QK^T}{\sqrt{d_k}})V Attention(Q,K,V)=softmax(dkQKT)V
-
为什么Transformer能够并行处理序列,而RNN必须串行处理?位置编码(Positional Encoding)在其中起什么作用?
answer:因为transformer可以同时计算整个序列,而RNN是有顺序依赖的。位置编码的作用是将语序纳入计算中。 -
Decoder-Only架构与完整的Encoder-Decoder架构有什么区别?为什么现在主流的大语言模型都采用Decoder-Only架构?
answer:Decoder-Only:只有Decoder,因为”言的核心任务,不就是预测下一个最有可能出现的词吗?“
-
-
文本子词分词算法是大语言模型的一项关键技术,负责将文本转换为模型可处理的 token 序列。那为什么不能直接以"字符"或"单词"作为模型的输入单元?BPE(Byte Pair Encoding)算法解决了什么问题?
answer:因为计算机本质上只能处理数字。BPE算法控制了词表的大小又能通过组合子词来理解和生成新词。 -
本章3.2.3节介绍了如何本地部署开源大语言模型。请完成以下实践和分析:
提示:这是一道动手实践题,建议实际操作
-
按照本章的指导,在本地部署一个轻量级的开源模型(推荐Qwen3-0.6B),并尝试调整采样参数并观察其对输出的影响
answer:generated_ids = model.generate( model_inputs.input_ids, max_new_tokens=512, # 调整参数 do_sample=True, top_p=0.9, temperature=0.7 )
-
选择一个具体任务(如文本分类、信息抽取、代码生成等),设计并对比以下不同的提示策略(如Zero-shot、Few-shot、Chain-of-Thought)对输出结果的效果差异
answer:任务选择:文本分类# 准备对话输入 messages = [ {"role": "system", "content": "You are a text classifier."}, {"role": "user", "content": "请帮我分类以下文本:\n\n'人工智能正在改变世界。'"}, ]Zero-shot:

Few-shot:
# 准备对话输入 messages = [ {"role": "system", "content": "You are a text classifier."}, {"role": "user", "content": "文本:'我爱自然语言处理' 分类:陈述句"}, {"role": "user", "content": "文本:'今天天气怎么样?' 分类:"}, ]
Chain-of-Thought:
# 准备对话输入 messages = [ {"role": "system", "content": "You are a text classifier."}, {"role": "user", "content": "文本:'我爱自然语言处理' 分类:陈述句"}, {"role": "user", "content": "文本:'今天天气怎么样?' 分类:疑问句"}, {"role": "user", "content": "文本:'请帮我写一首诗。' 分类:祈使句"}, {"role": "user", "content": "文本:'多喝热水!' 分类:感叹句"}, {"role": "user", "content": "文本:'机器学习是人工智能的一个分支' 分类:"}, ]
-
从性能、成本、可控性、隐私等维度比较闭源模型和开源模型
answer:维度 闭源模型 (Closed-source) 开源模型 (Open-source) 性能 天花板更高。通常在逻辑推理、多模态能力上领先 3-6 个月。 追赶速度极快。在特定任务和中等规模(7B-70B)上已并驾齐驱。 成本 按量计费 (OPEX)。初期投入极低,随使用量增加而线性增长。 硬件/运维成本 (CAPEX)。需采购 GPU 或租用算力,长期大规模使用更省钱。 可控性 较低。黑盒操作,依赖 Prompt 和厂商提供的微调 API。 极高。可修改架构、全量微调、量化剪枝,完全自定义输出行为。 隐私 信任机制。数据需传输至服务商云端(即使有企业版协议)。 主权受控。可本地/专网部署,数据全程不出内网。 -
如果你要构建一个企业级的客服智能体,你会选择哪种类型的模型?需要考虑哪些因素?
answer:我选择闭源模型
-
-
模型幻觉(Hallucination)[11]是大语言模型当前存在的关键局限性之一。本章介绍了缓解幻觉的方法(如检索增强生成、多步推理、外部工具调用)
-
请选择其中一种,说明其工作原理和适用场景
answer:RAG:RAG 系统通过在生成之前从外部知识库(如文档数据库、网页)中检索相关信息,然后将检索到的信息作为上下文,引导模型生成基于事实的回答。
-
调研前沿的研究和论文,是否还有其他的缓解模型幻觉的方法,他们又有哪些改进和优势?
answer:这篇论文的核心论点是:语言模型之所以产生幻觉,是因为其训练和评估程序奖励“猜测”,而非承认“不确定性”。
解决幻觉问题的关键,是一场自上而下的、针对评估体系的变革。通过引入明确的、带有惩罚机制的置信度目标,并将其实施于主流评估基准中,可以引导整个AI领域从“应试教育”模式转向培养更诚实、更可靠的AI模型。【引用: 全网最详细解读 OpenAI 最新论文:为什么大模型会存在“幻觉”? - 知乎】
-
-
假设你要设计一个论文辅助阅读智能体,它能够帮助研究人员快速阅读并理解学术论文,包括:总结论文研究的核心内容、回答关于论文的问题、提取关键信息、比较多篇不同论文的观点等。请回答:
-
你会选择哪个模型作为智能体设计时的基座模型?选择时需要考虑哪些因素?
answer:- 性能与能力:应该要选择擅长逻辑推理、总结归纳强的模型
- 上下文窗口:需要理解长文档、维持长期对话记忆的智能体。
-
如何设计提示词来引导模型更好地理解学术论文?学术论文通常很长,可能超过模型的上下文窗口限制,你会如何解决这个问题?
answer:给模型特定的身份:例如审稿人 ☺️
我会分段把论文喂给模型,再将分段得出的结论再喂给模型
-
学术研究是严谨的,这意味着我们需要确保智能体生成的信息是准确客观忠于原文的。你认为系统中加入哪些设计能够更好的实现这一需求?
answer:RAG检索
-
更多推荐


所有评论(0)