1. 什么是大语言模型? #
大语言模型(Large Language Model, LLM)是当前人工智能领域最热门的技术之一。你可能已经在使用 ChatGPT、文心一言、Kimi Chat 等 AI 助手时,注意到它们的回答不是一次性全部显示,而是一段一段地"流式"输出。这是为什么呢?
简单理解:
- 大语言模型就像一个非常聪明的"文字接龙"专家
- 它能够根据你输入的文字,预测并生成接下来的文字
- 它通过学习海量文本数据,掌握了语言的规律和世界的知识
为什么叫"大"语言模型?
- 参数量大:通常有数十亿甚至数千亿个参数(可以理解为模型的"记忆容量")
- 训练数据大:在数万亿字的文本数据上训练
- 能力强大:能够理解、生成、推理,完成各种语言任务
常见的大语言模型:
- ChatGPT:OpenAI 开发,最知名的对话模型
- GPT-4:OpenAI 的最新模型,能力更强
- 文心一言:百度开发的中文大模型
- 通义千问:阿里云开发的大模型
- Claude:Anthropic 开发的大模型
2. 大语言模型的基本工作原理 #
2.1 核心原理:文字接龙 #
大语言模型的本质工作就是做"文字接龙"。它并不是一次性生成完整答案,而是一个字一个字地逐步生成。
工作流程:
当你输入"什么是大语言模型"这个问题时:
- 第一步:模型分析你的问题,生成第一个字"大"
- 第二步:模型将原始输入"什么是大语言模型"和刚才生成的"大"一起,再次送入模型
- 第三步:模型接着生成第二个字"语"
- 第四步:继续这个过程,生成"言"、"模"、"型"等
- 第五步:直到生成结束标记,停止生成
为什么这样设计?
- 每个字的生成都基于前面的所有内容
- 这样可以保证生成的文本连贯、合理
- 就像人类说话一样,一个字一个字地说
3.2 大模型如何决定生成哪个字? #
概率模型的工作原理
大模型在生成下一个字时,并不是随机选择的,而是基于概率分布。具体过程如下:
- 计算概率:模型会计算所有可能字符的概率分布
- 选择输出:根据概率高低选择输出
- 随机性:通常概率越高的字符,被选中的机会越大,但也会给其他高概率字符一定的机会
示例说明
假设输入"今天天气很",模型会输出所有可能字符的概率:
- "好":概率 0.35(最高)
- "差":概率 0.25
- "热":概率 0.20
- 其他字符:概率更低
由于"好"的概率最高,模型通常会选择输出"好"。但需要注意的是,模型并不总是选择概率最高的字符,也会给其他高概率字符一定的输出机会,这就产生了随机性。因此,即使问同一个问题,大模型的回答也可能不完全相同。
温度参数(Temperature)
温度参数控制模型的随机性:
- 低温度(如 0.1):更确定,总是选择概率最高的字符
- 高温度(如 1.0):更随机,会给各种字符机会
4. Token 和词表(Vocab) #
4.1 什么是 Token? #
在大语言模型中,我们通常不说"字"或"词",而是说"Token"。Token 是大语言模型处理文本的基本单位。
Token 的定义
Token 是模型处理文本的最小单位,可以是一个字、一个词、或者词的一部分。
为什么使用 Token?
现在几乎所有大语言模型都按照 Token 来计费:
- GPT-4O:输入 100万 token = 5美元,输出 100万 token = 15美元
- DeepSeek(国内):输入 100万 token = 1元,输出 100万 token = 2元
Token 与字符的关系:
- 英文:1 个 token ≈ 0.75 个单词 ≈ 4 个字符
- 中文:1 个 token ≈ 1-2 个汉字
词表(Vocab)规模
所有可能的 Token 组成一个词表(Vocabulary)。不同模型的词表大小不同:
- GPT-3:约 5万个 Token
- 阿里千问2:约 15万个 Token
- LLaMA 3.1:约 12万个 Token
重要提示:词表中的 Token 并不一定对应一个完整的单词或汉字,可能是单词的一部分(子词)。
4.2 Token 是如何生成的?——分词(Tokenization) #
分词的作用
分词(Tokenization)是将输入的文本转换成一个个 Token 的过程:
- 输入阶段:文本 → Token 序列 → 模型
- 输出阶段:模型 → Token 序列 → 文本(逆分词)
为什么需要分词?
- 模型只能处理数字,不能直接处理文字
- 需要将文字转换为 Token ID(数字)
- 模型处理完后再转换回文字
4.3 为什么使用子词(Subword)作为 Token? #
Token 的三种选择
在构建词表时,有三种可能的选择:
- 单词(Word):如 "happy", "unhappy"
- 字符(Character):如 "h", "a", "p", "p", "y"
- 子词(Subword):如 "un", "happy"
为什么不用单词?
使用单词作为 Token 有两个主要缺点:
词表过大:每个单词有不同的时态、单复数等变化形式,导致词表数量巨大
- 例如:like, likes, liked, liking 等
- 词表过大增加模型训练难度
新词问题(OOV - Out of Vocabulary):
- 如果出现训练时未见过的词,会被标记为
<UNK>(未知词) - 子词可以通过组合多个 Token 来拼出新词,避免 OOV 问题
- 如果出现训练时未见过的词,会被标记为
为什么不用字符?
使用字符作为 Token 也有问题:
- 虽然词表很小,但字符本身缺乏语义信息
- 模型需要学习更长的序列才能理解语义,训练难度大
子词的优势
子词(Subword)是介于单词和字符之间的粒度,具有以下优势:
- 保留语义:如 "un" 表示否定,"happy" 表示快乐
- 组合灵活:可以通过 "un" + "like" 组成 "unlike"
- 词表适中:既不会太大,也不会太小
4.4 tiktokenizer #
4.4.1 什么是Tiktokenizer #
Tiktokenizer是一个用于分词(tokenize)的应用程序,能够将输入的文本按照语言规则划分成单个的标记(tokens),与GPT-4o模型相关。它通过简单的网页界面帮助用户快速理解文本在GPT模型中的分词方式,适用于开发者和对自然语言处理感兴趣的用户。
4.4.2 功能与用途 #
Tiktokenizer的核心功能是将文本分割成tokens,这是自然语言处理中的基础步骤。通过这种方式,用户可以直观地看到GPT模型如何解析文本,从而更好地调试和优化模型输入。例如,开发者可以通过它检查不同长度的文本在模型中的token数量,以优化API调用成本
4.4.3 网站与操作 #
Tiktokenizer的网站界面简洁易用,用户只需输入文本即可实时查看分词结果。网站地址为 https://tiktokenizer.vercel.app ,支持多种语言和文本类型,使用户能够快速上手。
4.5 tiktoken #
tiktoken 是 OpenAI 提供的一个高效文本分词(tokenization)库,能够将文本按照 GPT 等语言模型所使用的编码规则精准分割为 tokens。与常规字符分割方式不同,tiktoken 能根据不同模型(如 GPT-3.5、GPT-4)的内置编码方案,把输入文本准确转换为模型可以处理的 token 序列。
主要用途:
- 精准计算文本 token 数量,方便评估输入是否超出模型限制。
- 提前了解不同文本在不同模型下的 token 划分差异。
- 配合文本分割器调整 chunk size,使每段文本适合模型输入限制。
举例说明:
- 英文句子 "Hello, world!" 经过 tiktoken 编码会被分成几个 token,而不是按字母或空格分割。
- 中文句子 “今天天气很好” 也会采用模型专用的分词规则,按 token 编码后每个汉字通常为一个 token,但有些词组也可能被合并为单一 token。
tiktoken 的优势:
- 速度快,占用内存小。
- 支持多种 OpenAI 模型的编码方式(如 cl100k_base、p50k_base、r50k_base 等)。
- 便于与 langchain 等库结合,提高文本分割、上下文窗口管理的准确性。
下面我们通过具体代码示例,来演示如何用 tiktoken 对文本进行分词和统计 token 数量:
# 导入 tiktoken 库
import tiktoken
# 获取名为 "cl100k_base" 的编码器(GPT-4 使用的编码方式之一)
encoder = tiktoken.get_encoding("cl100k_base")
# 定义要编码的字符串
text = "Hello, world!"
# 使用编码器对文本进行编码,得到 token 列表
tokens = encoder.encode(text)
# 计算 token 的数量
token_count = len(tokens)
# 打印 token 的总数
print(f"Token数量: {token_count}")
# 打印每个 token 的编号(整数表示)
print(f"Tokens: {tokens}")
# 打印每个 token 对应的原始字节内容
print(f"Token对应文本: {[encoder.decode_single_token_bytes(token) for token in tokens]}")5. 过程 #
from collections import defaultdict, Counter
class SimpleTokenizer:
"""
分词器
功能:
1. 建立词汇表(单词 -> ID)
2. 学习每个词后面跟其他词的概率
3. 根据概率预测下一个词
"""
def __init__(self):
# 词汇表:单词 -> ID 的映射
self.vocab = {}
# 概率表:每个词后面跟其他词的概率
# 格式:{词: {下一个词: 概率}}
self.next_word_probs = {}
def train(self, texts):
"""
训练分词器
步骤:
1. 收集所有单词,建立词汇表
2. 统计每个词后面跟的词及其出现次数
3. 计算概率(次数 / 总次数)
"""
# ========== 第一步:建立词汇表 ==========
# 收集所有出现过的单词
all_words = set()
for text in texts:
all_words.update(text.split())
# 给每个单词分配一个ID(按字母顺序排序)
self.vocab = {word: i for i, word in enumerate(sorted(all_words))}
# ========== 第二步:统计词对出现次数 ==========
# 统计每个词后面跟的词出现了多少次
# 例如:"hello world" 会记录 "hello" 后面跟 "world" 出现1次
word_pair_counts = defaultdict(Counter)
for text in texts:
words = text.split()
# 遍历相邻的两个词
for i in range(len(words) - 1):
current_word = words[i]
next_word = words[i + 1]
# 记录:current_word 后面跟 next_word 出现1次
word_pair_counts[current_word][next_word] += 1
# ========== 第三步:计算概率 ==========
# 对于每个词,计算它后面跟其他词的概率
for word, next_word_counts in word_pair_counts.items():
# 计算这个词后面跟的所有词的总次数
total_count = sum(next_word_counts.values())
# 计算每个下一个词的概率 = 出现次数 / 总次数
self.next_word_probs[word] = {
next_word: count / total_count
for next_word, count in next_word_counts.items()
}
def predict(self, word):
"""
预测给定词的下一个词
参数:
word: 当前词
返回:
最可能的下一个词,如果词不在训练数据中则返回None
"""
# 如果这个词没有训练过,返回None
if word not in self.next_word_probs:
return None
# 获取这个词后面跟的所有词及其概率
probabilities = self.next_word_probs[word]
# 按概率从高到低排序
sorted_candidates = sorted(
probabilities.items(),
key=lambda x: x[1],
reverse=True
)
# 返回概率最高的词
most_likely_word = sorted_candidates[0][0]
return most_likely_word
# 创建分词器
tokenizer = SimpleTokenizer()
# 训练数据(更丰富)
texts = [
"hello world",
"hello world",
"hello world",
"hello python",
"hello python",
"hello hello"
]
# 训练模型
tokenizer.train(texts)
# 显示词汇表
print("词汇表(单词 -> ID):")
print(tokenizer.vocab)
# 显示概率分布
print("\n词的概率分布:")
for word, probs in tokenizer.next_word_probs.items():
print(f" '{word}' 后面可能是:")
# 按概率从高到低排序显示
for next_word, prob in sorted(probs.items(), key=lambda x: x[1], reverse=True):
print(f" {next_word}: {prob:.2f}")
# 测试预测功能
print("\n预测结果:")
test_word = "hello"
predicted_word = tokenizer.predict(test_word)
print(f" '{test_word}' 的下一个词: {predicted_word}")6. 增加参数 #
6.1 实现 #
from collections import defaultdict, Counter
+import random
class SimpleTokenizer:
"""
分词器
功能:
1. 建立词汇表(单词 -> ID)
2. 学习每个词后面跟其他词的概率
3. 根据概率预测下一个词
"""
def __init__(self):
# 词汇表:单词 -> ID 的映射
self.vocab = {}
# 概率表:每个词后面跟其他词的概率
# 格式:{词: {下一个词: 概率}}
self.next_word_probs = {}
def train(self, texts):
"""
训练分词器
步骤:
1. 收集所有单词,建立词汇表
2. 统计每个词后面跟的词及其出现次数
3. 计算概率(次数 / 总次数)
"""
# ========== 第一步:建立词汇表 ==========
# 收集所有出现过的单词
all_words = set()
for text in texts:
all_words.update(text.split())
# 给每个单词分配一个ID(按字母顺序排序)
self.vocab = {word: i for i, word in enumerate(sorted(all_words))}
# ========== 第二步:统计词对出现次数 ==========
# 统计每个词后面跟的词出现了多少次
# 例如:"hello world" 会记录 "hello" 后面跟 "world" 出现1次
word_pair_counts = defaultdict(Counter)
for text in texts:
words = text.split()
# 遍历相邻的两个词
for i in range(len(words) - 1):
current_word = words[i]
next_word = words[i + 1]
# 记录:current_word 后面跟 next_word 出现1次
word_pair_counts[current_word][next_word] += 1
# ========== 第三步:计算概率 ==========
# 对于每个词,计算它后面跟其他词的概率
for word, next_word_counts in word_pair_counts.items():
# 计算这个词后面跟的所有词的总次数
total_count = sum(next_word_counts.values())
# 计算每个下一个词的概率 = 出现次数 / 总次数
self.next_word_probs[word] = {
next_word: count / total_count
for next_word, count in next_word_counts.items()
}
+ def predict(self, word, temperature=1.0, top_p=1.0):
"""
预测给定词的下一个词
参数:
word: 当前词
+ temperature: 温度参数,控制随机性(0-1之间值越小越确定,越大越随机)
+ top_p: 核采样参数,只从累积概率达到top_p的候选中选择(0-1之间)
返回:
+ 根据概率分布采样的下一个词,如果词不在训练数据中则返回None
"""
# 如果这个词没有训练过,返回None
if word not in self.next_word_probs:
return None
# 获取这个词后面跟的所有词及其概率
probabilities = self.next_word_probs[word]
+ # ========== 第一步:应用 top_p 过滤 ==========
# 按概率从高到低排序
sorted_candidates = sorted(
probabilities.items(),
key=lambda x: x[1],
reverse=True
)
+
+ # 累积概率,只保留累积概率 <= top_p 的候选词
+ filtered_candidates = []
+ cumulative_prob = 0.0
+ for next_word, prob in sorted_candidates:
+ filtered_candidates.append((next_word, prob))
+ cumulative_prob += prob
+ if cumulative_prob >= top_p:
+ break
+
+ # 如果没有候选词,返回None
+ if not filtered_candidates:
+ return None
+
+ # ========== 第二步:应用 temperature 缩放 ==========
+ # 将概率进行温度缩放:prob^(1/temperature)
+ # temperature 越小,概率差异越大(更确定)
+ # temperature 越大,概率差异越小(更随机)
+ scaled_probs = []
+ for next_word, prob in filtered_candidates:
+ # 避免除零错误
+ if temperature > 0:
+ scaled_prob = prob ** (1.0 / temperature)
+ else:
+ # temperature = 0 时,只选择概率最高的
+ scaled_prob = prob if prob == max(p[1] for p in filtered_candidates) else 0
+ scaled_probs.append((next_word, scaled_prob))
+
+ # 重新归一化概率(使所有概率之和为1)
+ total_scaled_prob = sum(prob for _, prob in scaled_probs)
+ if total_scaled_prob > 0:
+ normalized_probs = [
+ (word, prob / total_scaled_prob)
+ for word, prob in scaled_probs
+ ]
+ else:
+ # 如果所有概率都是0,则均匀分布
+ normalized_probs = [
+ (word, 1.0 / len(scaled_probs))
+ for word, _ in scaled_probs
+ ]
+
+ # ========== 第三步:根据概率分布采样 ==========
+ # 生成0-1之间的随机数
+ rand = random.random()
+ # 累积概率,找到随机数落在哪个区间
+ cumulative = 0.0
+ for next_word, prob in normalized_probs:
+ cumulative += prob
+ if rand <= cumulative:
+ return next_word
+
+ # 如果由于浮点数精度问题没有返回,返回最后一个词
+ return normalized_probs[-1][0]
# 创建分词器
tokenizer = SimpleTokenizer()
# 训练数据(更丰富)
texts = [
"hello world",
"hello world",
"hello world",
"hello python",
"hello python",
"hello hello"
]
# 训练模型
tokenizer.train(texts)
# 显示词汇表
print("词汇表(单词 -> ID):")
print(tokenizer.vocab)
# 显示概率分布
print("\n词的概率分布:")
for word, probs in tokenizer.next_word_probs.items():
print(f" '{word}' 后面可能是:")
# 按概率从高到低排序显示
for next_word, prob in sorted(probs.items(), key=lambda x: x[1], reverse=True):
print(f" {next_word}: {prob:.2f}")
# 测试预测功能
print("\n预测结果:")
test_word = "hello"
+# 测试不同参数
+print(f"\n测试词: '{test_word}'")
+print("\n1. 默认参数(temperature=1.0, top_p=1.0):")
+for i in range(5):
+ predicted_word = tokenizer.predict(test_word)
+ print(f" 第{i+1}次: {predicted_word}")
+
+print("\n2. 低温度(temperature=0.1,更确定):")
+for i in range(5):
+ predicted_word = tokenizer.predict(test_word, temperature=0.1)
+ print(f" 第{i+1}次: {predicted_word}")
+
+print("\n3. 高温度(temperature=2.0,更随机):")
+for i in range(5):
+ predicted_word = tokenizer.predict(test_word, temperature=2.0)
+ print(f" 第{i+1}次: {predicted_word}")
+
+print("\n4. top_p=0.5(只从累积概率前50%的候选中选择):")
+for i in range(5):
+ predicted_word = tokenizer.predict(test_word, top_p=0.5)
+ print(f" 第{i+1}次: {predicted_word}")
+
+print("\n5. 组合参数(temperature=1.5, top_p=0.8):")
+for i in range(5):
+ predicted_word = tokenizer.predict(test_word, temperature=1.5, top_p=0.8)
+ print(f" 第{i+1}次: {predicted_word}")6.2 temperature #
temperature 参数(温度采样)
- 作用:控制输出的随机性
- 实现:对概率进行温度缩放
prob^(1/temperature) - 效果:
temperature < 1.0:概率差异放大,更倾向于高概率词(更确定)temperature = 1.0:保持原始概率分布temperature > 1.0:概率差异缩小,分布更均匀(更随机)temperature = 0:只选择概率最高的词(完全确定)
6.3 top_p #
top_p 参数(核采样/Nucleus Sampling)
- 作用:只从累积概率达到
top_p的候选中选择 - 实现:
- 按概率从高到低排序
- 累积概率,只保留累积概率 ≤
top_p的候选词 - 过滤低概率候选词
- 效果:
top_p = 1.0:考虑所有候选词top_p < 1.0:只考虑高概率候选词,过滤低概率词
6.4 采样流程 #
- 应用
top_p过滤:保留累积概率 ≤top_p的候选词 - 应用
temperature缩放:对概率进行温度缩放并归一化 - 概率采样:根据归一化后的概率分布进行随机采样
6.5 测试代码 #
添加了 5 个测试场景,展示不同参数组合的效果:
- 默认参数(temperature=1.0, top_p=1.0)
- 低温度(temperature=0.1,更确定)
- 高温度(temperature=2.0,更随机)
- top_p=0.5(只从累积概率前50%的候选中选择)
- 组合参数(temperature=1.5, top_p=0.8)