最近系统刷完了吴恩达老师的 NLP 系列课程,结合平时在做文本分类、信息抽取、评论情感分析等项目中的落地经验,我整理了一份从入门到进阶的 NLP 学习笔记。
很多同学学 NLP 时会遇到同一个困惑:资料太多,不知道从哪里开始;要么直接上手 HuggingFace 跑模型,原理完全不理解;要么只啃理论,代码能力跟不上。吴恩达这套 NLP 课程的优势在于,它把整个知识体系拆成了从传统方法到深度学习的递进路线,每个模块都配有可运行的 Python 实验,非常适合用来建立完整的 NLP 知识骨架。
这篇文章会把课程的骨架、核心算法、配套代码和常见的“坑位”一起串起来。无论你是刚接触自然语言处理的新手,还是已经在做 NLP 项目但缺少系统梳理的开发者,都能从里面找到需要的内容。
1. NLP 是什么,为什么需要系统学习
1.1 NLP 要解决的核心问题
自然语言处理(Natural Language Processing,简称 NLP)是让计算机理解、处理和生成人类语言的技术。它不是一个单一算法,而是一整个技术栈,覆盖了很多子任务:
- 文本分类:垃圾邮件识别、评论情感分析、新闻分类。
- 信息抽取:从文本中抽取实体、关系、事件。
- 机器翻译:中英翻译、多语言翻译。
- 问答系统:基于文档的问答、知识库问答。
- 文本生成:摘要、对话、写作辅助。
- 语音识别后的文本处理:分词、纠错、语义理解。
这些任务有一个共同难点:人类语言充满歧义、省略、指代和上下文依赖。比如“苹果好吃”和“苹果发布了新手机”,同一个词在不同的上下文里含义完全不同。NLP 的核心工作,就是找到一种方式,让模型能够捕捉这些上下文信息。
1.2 规则、统计与深度学习的演进
NLP 的技术路线大体经历了三个阶段:
| 阶段 | 代表方法 | 特点 |
|---|---|---|
| 规则时代 | 人工编写语法规则、词典 | 可解释性强,但覆盖不了语言多样性 |
| 统计时代 | N-gram、HMM、CRF、SVM、朴素贝叶斯 | 靠人工设计特征,泛化能力有限 |
| 深度学习时代 | RNN、LSTM、Seq2Seq、Transformer、BERT | 自动学习特征,效果大幅提升 |
吴恩达这套 NLP 课程的设计思路,恰好就是按照这个演进顺序展开的。它不是一上来就讲大模型,而是先从词向量、朴素贝叶斯、逻辑回归这些基础组件讲起,再一步步过渡到 RNN、注意力机制、Transformer。这种讲法最大的好处是:你能清楚知道每个模型解决什么问题、为什么后来会被新模型替代。
1.3 为什么推荐跟着课程体系走一遍
现在很多初学者一上来就微调 BERT,虽然能用,但遇到效果不好时往往不知道如何优化,因为不清楚模型内部发生什么。而吴恩达课程强调“用 Python 从零实现关键算法”,比如手写逻辑回归、手写反向传播、实现注意力得分计算。这些基本功在工程排查中非常有用。
另外,这套课程覆盖了 NLP 开发全链路:数据预处理、特征表示、模型训练、评估调优,不是只讲论文模型。跟着走一遍,对项目落地也很有帮助。
2. 课程体系拆解与学习路线规划
2.1 吴恩达 NLP 系列课程的主要内容
这里以 deeplearning.ai 的自然语言处理专项课程为参考,整个体系通常被划分为四个大的模块:
文本分类与向量空间学习词频表示、TF-IDF、词向量、余弦相似度,以及朴素贝叶斯、逻辑回归在文本分类中的应用。
概率模型与语言模型学习 N-gram 语言模型、概率计算、文本生成,以及词性标注、隐马尔可夫模型(HMM)等经典序列标注方法。
序列模型学习 RNN、LSTM、GRU 的原理与实现,掌握如何用序列模型处理文本生成、命名实体识别等任务。
注意力模型与 Transformer学习 Seq2Seq 中的注意力机制、Transformer 架构,并进一步理解 BERT 等预训练语言模型的应用。
不同版本课程在章节安排上可能略有差异,但主线基本一致。学习时不用纠结版本号,重点是抓住这条“从词向量到注意力机制”的主线。
2.2 适合哪些人学习
- 刚入门 NLP 的同学:课程从最基础的文本表示讲起,不需要很强的机器学习基础。
- 会用框架但不懂原理的开发者:课程里的手写实现可以帮你补上原理短板。
- 准备做 NLP 工程项目的工程师:文本预处理、模型评估、误差分析等章节贴近真实项目。
如果你已经熟练使用 HuggingFace 微调各种大模型,那么这套课程的前半部分可以快速过一遍,重点看注意力机制和 Transformer 的原理推导。
2.3 推荐的学习节奏
以“边看边写代码”为原则,这里给出一份 12 周的学习计划表,你可以根据实际情况调整:
| 周次 | 学习内容 | 动手任务 |
|---|---|---|
| 第 1 周 | 文本表示:词袋、TF-IDF | 用 sklearn 实现文本向量化 |
| 第 2 周 | 朴素贝叶斯、逻辑回归 | 实现一个垃圾邮件分类器 |
| 第 3 周 | 词向量与 Word2Vec | 训练小型中文词向量并做相似度分析 |
| 第 4 周 | 向量空间与相似度检索 | 实现一个简单问答检索 |
| 第 5 周 | N-gram 语言模型 | 用 N-gram 生成中文短句 |
| 第 6 周 | HMM 与词性标注 | 完成一个简单词性标注实验 |
| 第 7 周 | RNN 原理与代码 | 用 NumPy 实现前向传播 |
| 第 8 周 | LSTM、GRU | 用 Keras 完成文本分类 |
| 第 9 周 | Seq2Seq 模型 | 实现日期格式转换等小任务 |
| 第 10 周 | 注意力机制 | 手动实现注意力得分计算 |
| 第 11 周 | Transformer 架构 | 理解多头自注意力代码 |
| 第 12 周 | 预训练模型 BERT | 使用 HuggingFace 微调一个分类模型 |
看课和写代码的时间最好保持 1:2,甚至写代码的时间更多。只看不写,两周后基本会忘掉。
3. 核心知识点拆解:从词向量到注意力
这一节把课程中最核心的内容做一次提炼,每部分都会解释原理、适用场景和常见误区。
3.1 文本表示:从词袋到词向量
计算机无法直接处理文字,所以第一步是把文本变成数字。最朴素的方式是词袋模型(Bag of Words):
- 统计每个词在文档中出现的次数。
- 忽略词序,只保留词频信息。
# 文件路径:demo_bow.py from sklearn.feature_extraction.text import CountVectorizer corpus = [ "我喜欢自然语言处理", "自然语言处理是人工智能的方向" ] vectorizer = CountVectorizer(token_pattern=r"(?u)\b\w+\b") X = vectorizer.fit_transform(corpus) print(vectorizer.get_feature_names_out()) print(X.toarray())输出结果中,每一行代表一个文档,每一列代表一个词,值是该词在文档中出现的次数。
但词袋模型有两个明显问题:
- 高频词(如“的”“是”)会被赋予过高的权重,但它们往往没有实际区分度。
- 丢失了词序信息,“我喜欢你”和“你喜欢我”在词袋表示下完全相同。
为了解决第一个问题,出现了 TF-IDF。它通过“词频”乘以“逆文档频率”来降低普遍出现词的权重。为了解决第二个问题,就需要引入词向量。
3.2 Word2Vec 与词向量的核心思想
Word2Vec 的核心思想是:一个词的含义由它周围的词决定。也就是“分布假说”。
Word2Vec 有两种训练方式:
- CBOW:用上下文词预测中心词。
- Skip-gram:用中心词预测上下文词。
训练完成后,每个词对应一个低维稠密向量。语义相近的词在向量空间中距离更近,“国王 - 男人 + 女人 ≈ 女王”这类类比关系也往往能成立。
# 文件路径:demo_word2vec.py from gensim.models import Word2Vec sentences = [ ["自然语言处理", "是", "人工智能", "的", "重要", "方向"], ["我", "喜欢", "研究", "自然语言处理"], ["词向量", "是", "自然语言处理", "的", "基础"] ] # 按实际 gensim 版本调整参数 model = Word2Vec(sentences, vector_size=64, window=3, min_count=1, sg=1) vector = model.wv["自然语言处理"] print(vector.shape) similar_words = model.wv.most_similar("自然语言处理", topn=3) print(similar_words)这里vector_size是词向量维度,window是上下文窗口大小,sg=1表示使用 Skip-gram 方式训练。在项目里,如果语料只有几万条,词向量效果一般不会太好;如果语料达到百万级,词向量的语义效果会明显提升。
3.3 RNN 与 LSTM:处理序列信息
词向量解决了词的表示问题,但没有解决“顺序”问题。对于“我不喜欢这家餐厅”和“这家餐厅我不喜欢”,词袋或词向量平均都无法体现否定词对情感的影响。
RNN(循环神经网络)让模型在处理当前词时,把前一个时刻的隐藏状态一起输入进来。这样模型就具备了“记忆”能力。
RNN 的数学表达可以简化为:
h_t = tanh(W_h * h_{t-1} + W_x * x_t + b)其中h_t是当前时刻的隐藏状态,x_t是当前时刻的输入。
但标准 RNN 存在梯度消失问题,难以捕捉长距离依赖。LSTM 通过引入“门控机制”来解决这个问题:
- 遗忘门:决定记住多少旧信息。
- 输入门:决定把多少新信息写入记忆。
- 输出门:决定输出多少记忆信息。
在实际项目中,LSTM 是处理序列数据的稳妥选择,但它的训练速度比 RNN 慢,而且很难并行化。这也是后来 Transformer 兴起的原因之一。
3.4 注意力机制与 Transformer
Seq2Seq 模型在编码器和解码器之间只通过一个固定长度的向量传递信息。句子太长时,这个向量装不下所有信息,效果会下降。注意力机制的核心改进是:解码每个词时,让模型“回头看”编码器输出的所有隐藏状态,并按照相关性加权求和。
注意力得分的计算通常有几种方式:
- 点积注意力:
score = q · k - 缩放点积注意力:
score = q · k / sqrt(d_k) - 加性注意力:
score = W * tanh(q + k)
Transformer 则更进一步,完全抛弃了循环结构,使用“自注意力”机制来处理序列。自注意力的意思是:句子中的每个词都与句子中的其他词计算相关性权重,从而直接建模全局依赖。
# 文件路径:demo_attention.py import numpy as np def scaled_dot_product_attention(query, key, value): d_k = key.shape[-1] scores = np.dot(query, key.T) / np.sqrt(d_k) weights = np.exp(scores - np.max(scores, axis=-1, keepdims=True)) weights /= weights.sum(axis=-1, keepdims=True) return np.dot(weights, value) # 模拟三个词的向量表示 q = np.random.randn(3, 8) k = np.random.randn(3, 8) v = np.random.randn(3, 8) output = scaled_dot_product_attention(q, k, v) print(output.shape)在吴恩达课程中,这部分会有更完整的推导。理解注意力机制之后,再学习 BERT、GPT 等预训练模型就容易很多:它们本质上都是基于 Transformer 的不同训练目标和结构变体。
4. 完整实战:从中文语料清洗到情感分类
前面讲了大量概念,下面我们把知识点串起来,完成一个完整的 NLP 项目。项目目标:训练一个中文文本情感分类模型,流程覆盖“语料准备 → 数据清洗 → 特征表示 → 模型训练 → 效果评估”。
4.1 项目结构与数据准备
先创建项目目录:
nlp_sentiment_demo/ ├── data/ │ ├── raw.txt ├── src/ │ ├── clean.py │ ├── train_traditional.py │ ├── train_lstm.py ├── requirements.txt在requirements.txt中写入依赖:
jieba==0.42.1 scikit-learn==1.3.0 pandas==2.0.0 tensorflow==2.13.0注意:依赖版本需要结合你的 Python 环境调整,这里以示例环境为准。如果安装的是更高版本,API 基本兼容,但个别参数可能需要微调。
为了方便演示,这里使用一段很小的示例数据,不要期待它达到生产级效果。真实项目中,数据量至少要在几万条以上。
# 文件路径:src/clean.py import re import jieba raw_texts = [ ("这个电影太精彩了,演员演技在线,强烈推荐", 1), ("剧情拖沓,看了半小时就想睡觉", 0), ("画面很美,但故事逻辑有很大问题", 0), ("非常喜欢这种轻松搞笑的风格", 1), ("一般般,没有宣传中那么好", 0), ] def clean_text(text: str) -> str: # 只保留中文字符和英文字母 text = re.sub(r"[^\u4e00-\u9fa5a-zA-Z0-9]", " ", str(text)) # 分词并用空格连接 words = jieba.lcut(text) return " ".join(words) def build_dataset(): texts, labels = [], [] for text, label in raw_texts: texts.append(clean_text(text)) labels.append(label) return texts, labels if __name__ == "__main__": texts, labels = build_dataset() for t, l in zip(texts, labels): print(t, "->", l)这里做了两件事:
- 正则清洗:去掉标点符号、特殊字符,避免噪声进入模型。
- 中文分词:jieba 把句子切成词,方便后续向量化。
4.2 使用 TF-IDF + 朴素贝叶斯搭建基线模型
第一个模型选择逻辑回归或朴素贝叶斯。这类模型训练快、可解释性强,适合作为项目的 baseline。后续如果深度学习模型效果不理想,也可以先回到这个基线做对比。
# 文件路径:src/train_traditional.py from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.naive_bayes import MultinomialNB from sklearn.pipeline import Pipeline from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report from clean import build_dataset texts, labels = build_dataset() X_train, X_test, y_train, y_test = train_test_split( texts, labels, test_size=0.2, random_state=42, stratify=labels ) pipeline = Pipeline([ ("tfidf", TfidfVectorizer(analyzer="char", ngram_range=(1, 2))), ("clf", MultinomialNB()), ]) pipeline.fit(X_train, y_train) y_pred = pipeline.predict(X_test) print(classification_report(y_test, y_pred))这里有一个细节:analyzer="char"表示按字符级别做向量化,ngram_range=(1, 2)表示同时保留单个字符和相邻两个字符的信息。在中文短文本任务里,字符级别的 n-gram 通常比纯词级别更稳定,因为它不会受到分词错误的干扰。
4.3 使用 Embedding + LSTM 搭建深度模型
基线模型跑通之后,再用 LSTM 做一版深度模型,体验一下从“特征工程”到“自动特征学习”的转变。
# 文件路径:src/train_lstm.py from tensorflow.keras.preprocessing.text import Tokenizer from tensorflow.keras.preprocessing.sequence import pad_sequences from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, LSTM, Dense from sklearn.model_selection import train_test_split from clean import build_dataset texts, labels = build_dataset() X_train, X_test, y_train, y_test = train_test_split( texts, labels, test_size=0.2, random_state=42, stratify=labels ) VOCAB_SIZE = 1000 MAX_LEN = 20 EMBED_DIM = 64 tokenizer = Tokenizer(num_words=VOCAB_SIZE) tokenizer.fit_on_texts(X_train) X_train_seq = tokenizer.texts_to_sequences(X_train) X_test_seq = tokenizer.texts_to_sequences(X_test) X_train_pad = pad_sequences(X_train_seq, maxlen=MAX_LEN, padding="post") X_test_pad = pad_sequences(X_test_seq, maxlen=MAX_LEN, padding="post") model = Sequential([ Embedding(input_dim=VOCAB_SIZE, output_dim=EMBED_DIM, input_length=MAX_LEN), LSTM(units=32), Dense(units=1, activation="sigmoid") ]) model.compile( optimizer="adam", loss="binary_crossentropy", metrics=["accuracy"] ) model.fit( X_train_pad, y_train, epochs=10, batch_size=16, validation_split=0.2, verbose=1 ) loss, acc = model.evaluate(X_test_pad, y_test, verbose=0) print(f"Test accuracy: {acc:.4f}")这个代码里有几个容易出错的地方:
Tokenizer的num_words并不是“实际词典大小”,而是“保留最高频词的数量”。设置太小时,低频词会被丢弃。pad_sequences的padding="post"表示不到最大长度的序列在后面补 0。- Embedding 层接收的是词的索引,不是词向量。索引 0 通常留给 padding,所以词典大小要预留一个位置。
4.4 模型评估与预测
评估不能只看准确率。当样本不平衡时,准确率会骗人。比如 95% 的样本都是负样本,模型全预测为负样本也能有 95% 的准确率。所以分类任务至少要同时看精确率、召回率和 F1。
# 继续在 train_lstm.py 中追加 import numpy as np probabilities = model.predict(X_test_pad) predictions = (probabilities >= 0.5).astype(int) from sklearn.metrics import precision_score, recall_score, f1_score, accuracy_score print(f"Accuracy: {accuracy_score(y_test, predictions):.4f}") print(f"Precision: {precision_score(y_test, predictions):.4f}") print(f"Recall: {recall_score(y_test, predictions):.4f}") print(f"F1: {f1_score(y_test, predictions):.4f}")如果出现“准确率高但召回率低”的情况,通常表示模型偏向预测多数类,需要调整阈值或使用加权损失函数。
4.5 预测一条新样本
模型训练完成后,可以封装一个简单预测函数:
# 文件路径:src/predict.py import re import jieba from tensorflow.keras.models import load_model from tensorflow.keras.preprocessing.sequence import pad_sequences import pickle def preprocess_new_text(text: str) -> str: text = re.sub(r"[^\u4e00-\u9fa5a-zA-Z0-9]", " ", str(text)) return " ".join(jieba.lcut(text)) def predict_sentiment(model_path, tokenizer_path, text): model = load_model(model_path) with open(tokenizer_path, "rb") as f: tokenizer = pickle.load(f) clean = preprocess_new_text(text) seq = tokenizer.texts_to_sequences([clean]) padded = pad_sequences(seq, maxlen=20, padding="post") prob = model.predict(padded)[0][0] return prob if __name__ == "__main__": result = predict_sentiment( "sentiment_model.h5", "tokenizer.pkl", "电影还不错,但结局有点仓促" ) print(f"Positive probability: {result:.4f}")这个函数展示了真实工程中常用的模型部署方式:把模型和 Tokenizer 序列化保存,预测时重新加载。注意Tokenizer必须在训练集上 fit,不能在预测时才新建。
5. 常见问题与排查思路
5.1 高频问题对照表
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 中文文本向量化结果全是 0 | 没有正确分词,或词表与文本不匹配 | 先打印预处理后的文本,确认分词结果 |
| 模型准确率很低 | 数据量太少 / 类别不平衡 / 数据噪声大 | 扩充数据,检查样本分布,清洗数据 |
| LSTM 训练很慢 | 序列过长、Embedding 维度太高 | 限制 max_len,减小 embedding 维度 |
| 模型过拟合 | 数据量小、模型参数多、训练轮数多 | 增加 dropout,减小 LSTM 隐层单元,使用早停 |
| 测试集效果好,线上效果差 | 训练集和线上数据分布不一致 | 采集更多线上真实数据,做领域适配 |
| 出现 out of vocabulary 词 | 词典太小,或者使用按词切分 | 设置更大词表,或改用字符级特征 |
| 预测结果全是某一类 | 样本不平衡 / 阈值设置不合理 | 调整阈值,使用 class_weight |
5.2 一个典型问题排查过程
比如训练完成后,发现所有测试样本都被预测为“正向”。排查思路如下:
- 检查 labels 是否打错,正向和负向的 label 是否颠倒。
- 打印验证集上的预测概率分布,看模型是否输出接近 0.5 的值。
- 统计训练集中正负样本比例。如果正样本占 90%,模型很可能学成“无脑预测正类”。
- 在
model.fit中加入class_weight,给少数类更高权重。 - 如果问题依旧,回到传统模型,用 TF-IDF + 逻辑回归训练,观察特征权重是否合理。
6. 最佳实践与工程建议
6.1 数据质量优先于模型复杂度
做 NLP 项目时,数据决定效果上限,模型只是逼近这个上限。课程里常见的是处理好的干净语料,但生产环境里 80% 的时间都要花在数据上。
具体来说,要重视以下几步:
- 去重:重复样本会导致训练集和验证集重叠,评估分数虚高。
- 清洗:统一大小写、去特殊字符、处理 emoji 和 URL。
- 标签一致性检查:人工抽检 100 条标注,计算标注一致性。
- 切分策略:按时间切分,或按用户切分,避免数据泄露。
6.2 构建高质量中文 NLP 语料库
中文语料库构建有几个容易踩坑的点:
- 分词不只是“把句子切成词”。领域不同,词典差异很大。金融文本里的“多头”“空头”和日常用语含义完全不一样。
- 不要盲目使用网上下载的停用词表,很多业务关键词会被误删。
- 如果做实体识别,标注规范比标注数量更重要。标注不一致会直接导致模型学错。
一个常见的清洗流程是:
- 原始数据 → 去HTML标签 → 去URL → 去重复标点。
- 繁体转简体。
- 根据业务规则去掉无效字段。
- 人工抽检,记录清洗规则。
- 切分训练/验证/测试集。
6.3 模型选型建议
不同任务适合不同模型,不必一上来就上大模型:
| 场景 | 推荐方案 |
|---|---|
| 短文本分类、垃圾过滤 | TF-IDF + 逻辑回归 / 朴素贝叶斯 |
| 中等规模数据集上的序列标注 | BiLSTM + CRF |
| 大规模文本理解 | 预训练模型微调,如 BERT 系列 |
| 长文本分类 | 分段处理 + 模型集成,或直接使用长文本模型 |
| 低资源场景 | 数据增强、预训练模型 + PEFT 微调 |
其实很多时候,用朴素贝叶斯、逻辑回归就能达到生产可用效果。直接上大模型反而会引入部署复杂度和推理成本。
6.4 训练与评估注意事项
- 设定固定随机种子,保证实验可复现。
- 使用验证集做模型选择,测试集只能评估一次。
- 保存每次实验的配置和评估结果,方便回溯。
- 深度学习模型的初始学习率非常重要。可以先跑 10 个 batch 观察 loss 变化,再调整策略。
- 文本分类中,如果类别差异大,优先关注 macro-F1,而不是准确率。
7. 总结与下一步学习路线
这套课程学下来,你至少应该掌握以下能力:
- 理解文本从字符到向量的完整转换过程。
- 掌握朴素贝叶斯、逻辑回归在 NLP 中的应用。
- 理解 RNN、LSTM 的动机和局限。
- 理解注意力机制和 Transformer 的基本原理。
- 能独立完成一个包含数据清洗、模型训练、评估的中文 NLP 小项目。
如果这些目标已经达成,下一步可以沿着两个方向继续深入:
- 预训练模型方向:学习 HuggingFace Transformers 库,掌握 BERT 微调、提示学习、LoRA 等高效微调方法。
- 工程落地方向:学习模型部署、推理优化、数据回流、在线学习,了解模型上线后的监控与迭代闭环。
如果还想补充理论深度,也可以配合《动手学深度学习》中的 NLP 章节一起看。吴恩达课程的代码风格偏算法实现,而工程项目更考验数据清洗和调优能力,两者结合效果最好。
NLP 是一个典型的“入门容易、精通难”的领域。跟着课程体系走一遍,再亲手完成一两个完整的实战项目,遇到问题能自己定位根因,这时候才算真正入了门。建议收藏这份笔记,学习过程中随时回来对照知识点查漏补缺。