☰
从N-gram到大语言模型:零基础入门与避坑指南
2026/11/3 23:13:11 网站建设 项目流程

简介:这份PDF面向大语言模型零基础学习者与准备入门语言模型课程的读者,系统梳理LLM相关背景与核心概念,帮助建立从统计语言模型到神经语言模型、预训练语言模型再到大语言模型的知识脉络。整包仅含1个PDF文件,约3.24MB,篇幅集中、便于通读,适合作为概念速查与课堂补充材料。内容围绕语言模型的定义、句子概率计算、评估指标及技术演进展开,并涉及N-gram的马尔可夫假设、数据稀疏与OOV问题,以及神经网络语言模型的输入输出结构和softmax预测思路。还提及BERT、GPT、ChatGPT、GLM、BLOOM、LLaMA等代表模型,可用于理解参数规模、训练范式与AIGC阶段的变化。已有129人学习,适合希望用较短时间搭起LLM入门框架的读者。

1. 别急着装环境:语言模型到底是什么

很多新手第一次听到「大语言模型」,脑子里立刻浮现一堆看不懂的论文、显卡型号、几十 GB 的权重文件,然后还没开始就先不敢动。把那些吓人的词全剥掉,它干的事其实很朴素:给一段文字,猜下一个词最可能是什么;猜得足够准,看起来就像「会说话」。你在搜索框里刚打半句它自动补全,你写邮件它帮你接下半句,背后都是这个动作在反复执行。

这份资料讲的就是从「猜下一个词」这个最小动作,一路走到大语言模型(LLM)的完整脉络:语言模型是什么、概率怎么算、N-gram 怎么遇到瓶颈、神经网络怎么补救、预训练模型怎么一步步变成今天的大模型。它适合完全零基础的人当成第一张地图来看——读完你不一定立刻会训模型,但你一定知道每块路牌指向哪、下一步该往哪走。

2. 先算清一件事:语言模型算的是「这句话像不像人话」

零基础入门最容易被劝退的地方,是一上来就去背 Transformer 的结构图。其实没必要。所有后面那些复杂结构,都是在解决同一个问题的路上被逼出来的:怎么给一个句子打分。这一节只做一件事——把「打分」这件事讲到你可以在纸上算出来。

2.1 一个中文小例子:P(S) 到底在算什么

先假设我们的词典只有几个词,V = {我、想、去、打、篮球、晚饭、学习}。一个句子 S 就是从词典里挑词排成一行,比如 S = {我, 想, 去, 打, 篮球}。语言模型要做的事,就是给这个句子一个概率 P(S),P 大于等于 0。

通俗理解就是:判断这句话像不像人话。如果给定的词序列符合语用习惯,就给高概率;不符合,就给低概率。所以「我 想 去 打 篮球」会拿到一个高概率,「篮球 我 学习 打 想」会拿到一个很低的概率。

提示:概率高不等于语法正确,概率低也不等于语法错误。语言模型建模的是「这种说法在真实语料里有多常见」,它是个排序器,不是语法检查器。这一点新手最容易理解偏,后面第 5 章还会再说一次。

2.2 链式法则:把一句话的概率拆成一步一步

一个句子可能有七八个词,直接给整句打分很难。概率论里有个现成的拆法叫链式法则,把整句概率拆成一串条件概率相乘:

P(W1, W2, …, Wn) = P(W1) × P(W2|W1) × P(W3|W1,W2) × … × P(Wn|W1,…,Wn-1)

每一项的意思都很直白——「在已经说了前面这些词的情况下,下一个词说这个词的概率是多少」。就像接力赛,每一棒都接着上一棒的位置跑。

# 假设条件概率已经通过统计得到了,这里直接给定,先看链式法则怎么用 p_first = 0.19 # P(我):一句话以「我」开头的概率 p_second = 0.75 # P(想|我) p_third = 0.75 # P(去|我想) p_fourth = 0.75 # P(打|我想去) p_fifth = 0.667 # P(篮球|我想去打) # 链式法则:整句概率 = 每一步条件概率连乘 p_sentence = p_first * p_second * p_third * p_fourth * p_fifth print(f"P(我想去打篮球) = {p_sentence:.6f}")

跑出来大约是 0.0534。这里的每个参数都对应一个「下一步该说什么」的判断,乘法代表这些判断要同时成立。你会注意到一件事:句子越长,连乘的项越多,最终数值越小。所以看到 0.05 这种小数不要慌,它不代表模型很差,只代表概率被拆细了。真正有意义的用法是拿两个候选句子比大小。

比如现在想问:我想去打【mask】,mask 应该是「篮球」还是「晚饭」?前四项完全一样,区别只在最后一项:P(篮球|我想去打) = 0.667,P(晚饭|我想去打) = 0.333。谁大选谁,答案就是篮球。这就是「预测下一个词」的全部秘密,剩下的工作都是怎么把这一项估得更准。

2.3 最直觉的做法:数一数出现几次,然后发现行不通

既然要算概率,最朴素的办法就是计数:假设我们有一个包含 N 个句子的数据集,统计句子 S 在其中出现了 n 次,那么 P(S) = n / N。这个思路非常自然,可惜几乎没法用,原因有两个。

第一是数据稀疏。只要这个句子没在语料库里原样出现过,概率就是 0。你去网上随便找一段正常的中文,里面绝大多数句子都是「没被记录过」的新组合。模型给出 0,等于斩钉截铁地说「这不是人话」,显然不合理。第二是参数空间太大:条件概率 P(Wn|W1,…,Wn-1) 要枚举的可能性多到无法估算,而且很多组合压根没用。

这两个问题,一个叫数据稀疏严重,一个叫参数空间过大,它们贯穿了整个语言模型的发展史。后面每一代方法——N-gram、神经网络、预训练模型——本质上都是在想办法绕开这两座山。

3. N-gram 入门:用「只看前几个词」把概率算出来

上一节留下的问题是:条件概率 P(Wn|W1,…,Wn-1) 的前缀太长了,估不出来。N-gram 的思路很务实——既然看全部前文估不出来,那就少看一点。这一节会带你手搓一个能跑的小例子,跑完你对「语言模型」的感觉就从抽象变成具体的了。

3.1 马尔科夫假设:不往前看全部,只看最近 N-1 个词

N-gram 引入的核心假设叫马尔科夫假设:随便一个词出现的概率,只与它前面出现的有限几个词有关,再往前的就不管了。按「看几个词」来分,就有了不同的名字:

  • 一个词的出现与它周围的词独立,叫 unigram,也就是一元语言模型;
  • 只依赖前面出现的一个词,叫 bigram,二元语言模型;
  • 只依赖前面出现的两个词,叫 trigram,三元语言模型。

一般来说,N 元模型就是假设当前词的出现概率只与它前面的 N-1 个词有关。这套思路解释力不算强,但胜在能算:这些概率参数都可以基于大规模语料库统计出来,用极大似然估计就行。实践中用得最多的是 bigram 和 trigram,因为 N 再往上,参数量就爆炸了。

3.2 第一个能跑的例子:手搓一张 bigram 概率表

下面用一个极小的语料库,把 bigram 模型的参数算出来,再用它做两件事:算整句概率、预测下一个词。这段代码不需要装任何深度学习框架,标准库就够。

from collections import Counter # 一个极小的语料库,每行一句,词之间用空格隔开 corpus = [ "我 想 去 打 篮球", "我 想 去 打 晚饭", "我 想 学习", "你 想 去 学习", "我 去 打 篮球", ] unigram = Counter() # 统计单个词出现的次数,记为 C(Wi-1) bigram = Counter() # 统计相邻词对出现的次数,记为 C(Wi-1, Wi) for line in corpus: words = line.split() for w in words: unigram[w] += 1 # zip(words, words[1:]) 把句子拆成所有相邻词对,比如 (我,想) (想,去) for a, b in zip(words, words[1:]): bigram[(a, b)] += 1 def prob(b, a): """条件概率 P(b|a) = C(a,b) / C(a),分母为 0 时直接返回 0""" if unigram[a] == 0: return 0.0 return bigram[(a, b)] / unigram[a] print("P(想|我) =", prob("想", "我")) # 3/4 print("P(篮球|打) =", round(prob("篮球", "打"), 3)) # 2/3 print("P(晚饭|打) =", round(prob("晚饭", "打"), 3)) # 1/3

运行结果:P(想|我) = 0.75,P(篮球|打) ≈ 0.667,P(晚饭|打) ≈ 0.333。这三个数怎么来的?「我」在语料里出现 4 次,其中后面接「想」的有 3 次,所以是 3/4;「打」出现 3 次,后面接「篮球」2 次、接「晚饭」1 次,所以分别是 2/3 和 1/3。就这么点事。

拿到这张概率表,就可以算整句概率了。注意句子第一个词没有前文,通常用它的词频占比代替:

def sentence_prob(sentence): """用 bigram 近似整句概率:P(W1) * P(W2|W1) * P(W3|W2) * ...""" words = sentence.split() total = sum(unigram.values()) p = unigram[words[0]] / total # 首词退化成词频占比 for a, b in zip(words, words[1:]): p *= prob(b, a) # 后续每一步都乘条件概率 return p print(round(sentence_prob("我 想 去 打 篮球"), 4)) # 约 0.0534 print(round(sentence_prob("我 想 去 打 晚饭"), 4)) # 约 0.0267

两句话只差最后一个词,概率差了整整一倍,篮球胜出。把这两段代码连起来看,你其实已经完整走了一遍语言模型的工作流程:统计计数、算出条件概率、连乘得到句子概率、比大小选出下一个词。今天的百亿参数模型,输入输出和这个流程是对应的,只是中间那一步从「查表」换成了「神经网络」。

注意:这个例子里「P(学习|想)」是 1/4 而不是 1,因为语料里「想」后面还接过「去」。语料越小,估出来的概率越粗糙,这就是后面要讲的数据稀疏问题在具体例子里的样子。

3.3 优点和翻车点:n 是不是越大越好

N-gram 的优点很实在:采用极大似然估计,参数容易训练;完全包含了前 n-1 个词的全部信息;可解释性强,每一项概率都能对应回语料里的具体计数,是名副其实的「白盒」,不是黑匣子。

缺点也同样实在。先看参数规模,假设词典大小是 10000:

模型类型依赖前文参数量级实际可用性
unigram0 个词约 10^4太弱,几乎不建模顺序
bigram1 个词约 10^8可用,最常用之一
trigram2 个词约 10^12常用上限,再大存不下
4-gram 及以上3 个以上词10^16 起存储和稀疏都无法承受

参数量随 N 呈指数增长,这是第一个瓶颈。第二个瓶颈是数据稀疏:随着 N 增大,大量词对在语料里根本没出现过,一算就是 0;还会遇到 OOV(未登录词),也就是词典里压根没有的词。第三个瓶颈最致命——它只能建模到前 N-1 个词,超出的信息被直接丢掉,长距离依赖完全抓不住。

我第一次接触这块时的血泪经验是:别急着调大 N 看效果,先把 N=2 和 N=3 的结果摆一起对比。很多时候 N 从 2 加到 3,困惑度是降了,但你抽几条生成结果出来看,会发现句子开头几个词之后就开始跑偏——因为它的「记忆」就只有两个词。

4. 从神经网络到 Transformer:词向量、预训练和大模型

N-gram 靠查表,泛化能力差,见到没见过的组合就傻眼。比如它知道「打篮球」常见,也知道「踢足球」常见,但它不知道这两个短语在做同一类事,因为「篮球」和「足球」在表里是两个毫不相干的格子。这一章讲的是怎么把「格子」换成「向量」,以及这个替换如何一路通向大语言模型。

4.1 词向量:把词变成一个能参与计算的数组

神经网络语言模型做的第一件事,是给每个词配一个向量,叫词向量,记作 C(w)。这个词向量是模型自己学出来的:语义和用法相近的词,向量在空间里的方向也接近。

import numpy as np # 假设词向量已经学好(真实模型通常几百维,这里简化成 3 维) vectors = { "篮球": np.array([0.8, 0.1, 0.6]), "足球": np.array([0.7, 0.2, 0.5]), "晚饭": np.array([0.1, 0.9, 0.2]), } def cos(a, b): """余弦相似度:两个向量的夹角,越接近 1 说明方向越一致""" return float(a @ b / (np.linalg.norm(a) * np.linalg.norm(b))) print("篮球 vs 足球:", round(cos(vectors["篮球"], vectors["足球"]), 3)) # 0.992 print("篮球 vs 晚饭:", round(cos(vectors["篮球"], vectors["晚饭"]), 3)) # 0.311

「篮球」和「足球」的相似度是 0.992,「篮球」和「晚饭」只有 0.311。这意味着即使语料里从没出现过「打乒乓球」这个词组,模型也能靠向量距离推出它和「打篮球」是一类结构。N-gram 那种「没出现过就是 0 概率」的死板,在这里被绕开了,这就是泛化能力的来源。

4.2 网络的三层:拼接、激活、softmax

有了词向量,网络结构本身其实只有三层,一层层看并不复杂。假设要预测第 t 个词 wt,已知前 n-1 个词:

第一层,把 C(wt-n+1) 到 C(wt-1) 这 n-1 个词向量首尾拼接起来,形成一个 (n-1)×m 大小的向量,记作 x。这一步只是把散装的信息排成一排。

第二层,过一个全连接层,结果再用 tanh 激活函数处理。这一层负责从拼接向量里提取组合特征。

第三层,输出一共 V 个节点(V 是语料词汇总数),本质上是另一个全连接层。每个输出节点 yi 表示下一个词是第 i 个词的未归一化分数(logits)。最后用 softmax 把 y 归一化成概率,概率最大的那个就是预测结果。

import numpy as np # 网络最后一层吐出来的原始分数(logits),还没归一化 logits = np.array([2.0, 1.0, 0.1]) words = ["篮球", "晚饭", "学习"] def softmax(x): # 减去最大值是为了防止 exp 溢出,数学上结果完全等价 e = np.exp(x - np.max(x)) return e / e.sum() probs = softmax(logits) for w, p in zip(words, probs): print(w, round(float(p), 3))

输出是篮球 0.659、晚饭 0.242、学习 0.099,三者加起来正好是 1。softmax 的作用就是「把任意实数分数压成一组和为 1 的概率」,这样才好和真实分布对比。选词的时候可以选概率最大的(贪心),也可以按概率随机抽(采样),后者生成的内容更多样,这个选择在你以后调生成效果时会反复出现。

神经网络语言模型的优点相比 N-gram 是明显的:泛化能力更好,数据稀疏带来的问题被缓解了。缺点也存在:对长序列的建模能力有限,训练时可能出现梯度消失,这些就是后面 Transformer 要解决的事。

4.3 预训练和微调:两个绕不开的词

到这里,思路该升级了。与其为每个任务单独训一个模型,不如先在大规模通用文本上把语言表示学一遍,再用到具体任务上。这套做法叫预训练加微调,是大语言模型时代最重要的工程范式,也是新手最容易念错意思的两个词。

预训练:在大规模数据集上事先训练神经网络,让它学通用的特征表示和知识。类比就是广泛涉猎,先建立一个通识底子。

微调:在具体的下游任务上,用预训练好的模型做迁移学习,拿一小批带标签的数据再训一小会儿,效果通常比从零训练好得多。类比就是入职后的针对性岗前培训。

当时的预训练模型大致分两条路线:一条偏「理解」,训练目标是挖空让模型填(MLM)加上判断两句是否相邻(NSP),用双向上下文;另一条偏「生成」,训练目标就是老老实实猜下一个词,自回归地从左往右写。这两条路线的差异,会直接影响模型擅长什么——一个擅长分类、抽取,一个擅长续写、对话。

预训练模型的优点是泛化能力更强、语义表示更丰富、能有效防止过拟合。缺点是计算资源需求大、可解释性差。可解释性这一条,新手要有心理准备:你很难指着某个参数说「它学到了什么」。

如果不上手敲代码,一次微调的最小流程可以按这五步理解:

  1. 准备带标签的小数据集,并按任务定义清楚输入输出格式;
  2. 加载预训练权重,作为初始参数;
  3. 换掉输出层,让它输出你任务的类别数或标签空间;
  4. 用小学习率训练几轮,避免把预训练学到的表示冲掉;
  5. 在留出的验证集上看指标,而不是在训练集上看损失。

4.4 大模型「大」在哪:参数量的三级跳

当预训练模型的参数一路涨上去,性能也跟着涨。业界目前的一个通俗界线是:参数量超过 10B(百亿级)的模型,就可以叫大语言模型。它可以处理多种自然语言任务,比如文本分类、问答、翻译、对话。

回顾整个演进,大致分三个阶段:

第一阶段,核心工作是设计一系列自监督训练目标(MLM、NSP 等),设计出 Transformer 这个新架构,并确立预训练加微调的范式。代表是各种双向理解型模型。

第二阶段,思路变成逐步扩大模型参数和训练语料规模,同时探索不同类型的架构,编码器-解码器混合结构、纯解码器结构都在这个阶段被反复试过。

第三阶段,走向 AIGC 时代。参数规模进一步抬升,架构收敛到自回归,模型走向对话式、生成式、多模态,并且格外强调与人类交互的对齐,目标是可靠、安全、无毒的内容输出。

这个阶段最值得一提的能力变化是:模型规模上去之后,可以做到不调整权重、只依据用户给出的任务示例就完成新任务。你自己完全没有写训练代码,只是在输入框里给两个例子,它就能照做。这种「不改参数也能适应新任务」的特性,是把大模型当工具用的基础。

当然,大语言模型的缺点也要一并知道:参数量大、算力要求高、训练时间长,并且可能生成部分有害的、有偏见的内容。这不是道德说教,是你在实际使用时必须做的预期管理。

5. 新手最容易踩的坑:五条血泪经验

前面都是往前推,这一章往回看。下面这五条,是零基础入门阶段最容易卡住的地方,每条按「现象、原因、解决」写清楚。看完这一章,你至少能少走一个月的弯路。

5.1 概念上的三个坑

坑一:以为语言模型在「判断句子对不对」。

现象:你拿一个语序完全乱的句子去算,发现概率不是 0,于是怀疑自己算错了。

原因:语言模型建模的是概率分布,它只对「这种说法有多常见」排序,不做语法判断。乱句子只要由高频词对构成,概率就不会太低。

解决:永远用比较的方式看输出——两个候选谁的概率高,而不是盯绝对值是多少。绝对值在不同模型、不同语料之间根本不可比。

坑二:以为参数越大效果一定越好。

现象:拿两个不同规模的模型比,发现小模型在你的任务上反而更好,觉得哪里出错了。

原因:参数量只是规模。训练数据质量、训练目标、对齐方式、推理参数都会影响最终表现,规模只是其中一个变量。

解决:先固定一个评测集和评测指标,再去比较模型。没有固定评测集的「感觉这个好点」,基本都是玄学。

坑三:把 N-gram 的 N 当成「模型的记忆长度」。

现象:以为 trigram 能记住整段话的开头,结果生成的内容一过三个词就开始跑偏。

原因:马尔科夫假设决定了它只看前 N-1 个词,更早的信息被直接丢弃,不是忘了,是压根没进模型。

解决:需要长距离依赖就别在 N-gram 上死磕,该换神经网络或注意力结构了。N-gram 更适合当入门教学工具和基线。

5.2 评估和动手上的两个坑

在说坑之前,先把评估指标交代清楚,因为这两个坑都跟它有关。语言模型的评估指标大致分两类。

一类是内在指标,最常见的是困惑度(perplexity)。它衡量的是模型对下一个词的预测有多不确定,数值越小越好,计算方式是把句子概率开 n 次方再取倒数。直觉化的说法:如果每一步的平均条件概率是 1/20,困惑度就是 20,相当于模型每一步都像在 20 个候选词里犹豫。

另一类是外在指标,也就是把模型接到具体任务上,看准确率、F1 这类任务级数字。内在指标好算,外在指标才贴近你真正关心的事。

坑四:概率连乘,算着算着变成 0 或者变成 1e-300。

现象:句子稍微长一点,整句概率直接打印出 0.0;或者没有到 0,但是小到浮点数存不下,后面全乱。

原因:两个原因叠加。一是数据稀疏,某个词对在语料里没出现过,条件概率是 0,一乘全毁;二是数值下溢,几十个小于 1 的数连乘,很快就超出浮点数的表示范围。

解决:一是加平滑,给没见过的词对分配一个小正数,别让它直接归零;二是取对数,把连乘变成连加。

import math # 五个条件概率,最后一项因为没在语料里出现过,概率为 0 probs = [0.19, 0.75, 0.75, 0.75, 0.0] # 直接连乘:结果是 0.0,看不出是哪一步出的问题 print("连乘结果:", math.prod(probs)) # 取对数后连乘变连加:哪一步有问题一眼看穿 log_sum = sum(math.log(p) if p > 0 else float("-inf") for p in probs) print("对数结果:", log_sum) # -inf,说明第 5 项是 0

这段代码的重点不在输出,而在于告诉你一个习惯:只要涉及概率连乘,先取对数。真实训练里所有的损失函数都是在对数空间算的,不是没有原因的。

坑五:只盯困惑度,不看实际效果。

现象:调了一轮参数,困惑度确实降了,但人工抽几条生成结果看,反而更差、更啰嗦、更容易跑题。

原因:困惑度衡量的是「预测下一个词有多不确定」,它和「生成的内容是否有用、是否安全、是否符合任务要求」不是一回事。一个模型可以在困惑度上表现很好,同时在对话里答非所问。

解决:至少配一个任务级指标,再固定一小批样本做人工抽检。别嫌抽检土,它是唯一能让你发现「指标涨了但东西变差了」的手段。

6. 下一步学什么:一条能走通的自学路径

如果你已经读到这里,说明概念关基本过了。接下来最怕的是「知道很多名词但一个都跑不起来」,所以我建议按下面的顺序推进,每个阶段都要求有一个能看见的产出:

阶段学什么阶段产出
第一周链式法则、条件概率、概率连乘与对数用 Python 手算三句话的概率并比较大小
第二周N-gram、马尔科夫假设、平滑自己换一个语料库,跑通 bigram 预测下一个词
第三周词向量、softmax、余弦相似度用现成的向量文件算几组词的相似度并排序
第四周预训练与微调的区别、常见任务形式完整说清一个「输入什么、输出什么」的任务定义

四个阶段里,第三周最容易卡住,因为要开始接触外部文件和数据格式。我的建议是先用一小份词向量跑通流程,别一上来就下载几十 GB 的东西,否则下载失败的挫败感会直接劝退。

三个适合零基础的小练习,按顺序做:

  1. 把第 3 章那个语料库换成你自己写的二十句中文,重新算一遍 P(篮球|打),看看数字变化有多大。这个练习的目的是让你对「语料决定一切」有体感。
  2. 给上面的 bigram 代码加一个最简单的平滑:所有计数先加 1 再算概率。跑完对比一下没平滑时的结果,你会立刻明白平滑是干什么的。
  3. 找一段你自己写的文字,用 bigram 模型算它的困惑度;再找一段乱码文字算一遍。两个数字的差距,就是你第一次亲手量出来的「语言模型」。

还有一个具体技巧值得单独说:读任何一篇大模型相关的材料时,先找它的「输入是什么、输出是什么、用什么指标衡量」。这三件事定不下来,后面所有细节都会变成噪音。我见过太多人一头扎进结构图,看了两周还不知道自己训出来的模型要干嘛。

我第一次系统学这块的时候,犯过一个特别低级的错误:把所有时间花在推导公式上,代码一行没跑。等到真正要动手,连「概率连乘要用对数」这种常识都要现查。从那以后我每次学一个新概念,都强制走一遍「用最小代码量跑出一个数字」这一步,跑不出来就说明我根本没懂。希望这份入门路径能帮到你,从今天先跑通那二十行 bigram 开始。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询