📖 第1章:NLP基础概念¶
⚠️ 时效性说明:本章涉及前沿模型/价格/榜单等信息,可能随版本快速变化;请以论文原文、官方发布页和 API 文档为准。
学习时间:4小时 难度星级:⭐⭐ 前置知识:Python基础、机器学习基本概念 学习目标:理解NLP的定义与核心任务,了解NLP发展历史及应用场景
📋 目录¶
- 1. 什么是自然语言处理
- 2. NLP发展历史
- 3. NLP核心任务分类
- 4. NLP的技术栈全景
- 5. NLP的困难与挑战
- 6. NLP应用场景
- 7. NLP评估指标
- 8. 动手实验:初探NLP
- 9. 面试要点
- 10. 练习题
1. 什么是自然语言处理¶
1.1 定义¶
自然语言处理(Natural Language Processing, NLP) 是人工智能和计算语言学的交叉领域,致力于让计算机理解、解释和生成人类语言。
用更通俗的话说,NLP就是让机器"读懂"人话。
1.2 NLP的学科定位¶
NLP处于多个学科的交叉点:
- 计算机科学:提供算法和工程实现能力
- 语言学:提供语言规则和理论框架
- 数学/统计:提供建模工具(概率论、优化理论)
- 认知科学:理解人类语言处理机制
1.3 自然语言 vs 形式语言¶
| 特性 | 自然语言 | 形式语言(如Python) |
|---|---|---|
| 歧义性 | 大量歧义 | 无歧义 |
| 语法 | 灵活,可违反 | 严格,不可违反 |
| 上下文依赖 | 高度依赖 | 基本独立 |
| 隐含信息 | 大量隐含 | 显式表达 |
| 动态演变 | 不断变化 | 版本更新才变 |
这就是为什么NLP如此困难——自然语言充满了歧义、隐喻、省略和文化背景。
1.4 NLP的层次结构¶
从语言学角度,NLP可以分为以下层次:
┌─────────────────────────────────┐
│ 语用层(Pragmatics) │ 说话人的意图是什么?
├─────────────────────────────────┤
│ 语义层(Semantics) │ 句子的真正含义是什么?
├─────────────────────────────────┤
│ 句法层(Syntax) │ 句子的结构是什么?
├─────────────────────────────────┤
│ 词法层(Morphology) │ 词的内部结构是什么?
├─────────────────────────────────┤
│ 音韵层(Phonology) │ 语音的规则是什么?
└─────────────────────────────────┘
示例:分析"他把苹果吃了"
# 音韵层:tā bǎ píngguǒ chī le
# 词法层:他/把/苹果/吃/了
# 句法层:主语(他) + 把字句(把苹果) + 谓语(吃了)
# 语义层:动作执行者是"他",动作是"吃",对象是"苹果"
# 语用层:这是一个陈述事实的句子
2. NLP发展历史¶
2.1 规则与符号主义时代(1950s-1989)¶
核心思想:用手工编写的规则来处理语言
重要里程碑:
| 年份 | 事件 | 意义 |
|---|---|---|
| 1950 | 图灵提出"图灵测试" | 首次定义机器智能标准 |
| 1954 | Georgetown实验 | 首次机器翻译实验(俄→英) |
| 1957 | Chomsky形式语法 | 奠定计算语言学理论基础 |
| 1966 | ELIZA聊天机器人 | 首个对话系统(模式匹配) |
| 1971 | SHRDLU系统 | 受限域的自然语言理解 |
ELIZA的基本原理(模式匹配):
import re
def eliza_response(user_input):
"""简单的ELIZA风格对话系统"""
rules = [
(r"我觉得(.*)", ["为什么你觉得{0}?", "你能详细说说吗?"]),
(r"我是(.*)", ["你为什么说你是{0}?", "做{0}是什么感觉?"]),
(r"(.*)(难过|伤心|不开心)(.*)", ["很抱歉听到你不开心,能说说原因吗?"]),
(r"(.*)(高兴|开心|快乐)(.*)", ["真好!是什么让你这么高兴?"]),
(r"为什么(.*)", ["你觉得为什么呢?", "这个问题很好,你怎么想?"]),
(r"(.*)", ["请继续说", "嗯嗯,然后呢?", "有意思,能详细说说吗?"]),
]
import random
for pattern, responses in rules:
match = re.search(pattern, user_input) # re.search在字符串中搜索匹配模式
if match:
response = random.choice(responses)
# 填充匹配的内容
for i, group in enumerate(match.groups()): # enumerate同时获取索引和元素
response = response.replace(f"{{{i}}}", group if group else "")
return response
return "我不太明白,能换个说法吗?"
# 测试
test_inputs = [
"我觉得今天天气不错",
"我是一个学生",
"最近很难过",
"为什么要学NLP",
]
for inp in test_inputs:
print(f"用户:{inp}")
print(f"ELIZA:{eliza_response(inp)}")
print()
规则方法的局限: - 规则编写耗时耗力 - 难以覆盖所有情况 - 缺乏泛化能力 - 不同语言需要不同规则集
2.2 统计方法时代(1990s-2012)¶
核心思想:用数据驱动的统计模型取代手Craft规则
标志性事件:
| 年份 | 技术/模型 | 创新点 |
|---|---|---|
| 1990 | HMM在NLP中广泛应用 | 概率化的序列建模 |
| 1993 | IBM翻译模型 | 统计机器翻译的起点 |
| 2001 | 条件随机场(CRF) | 判别式序列标注 |
| 2002 | BLEU评测指标 | 标准化翻译评估 |
| 2003 | LDA主题模型 | 文档主题发现 |
| 2003 | 神经网络语言模型(Bengio) | 连续空间词表示的起点 |
统计方法的核心公式:
其中,贝叶斯公式是统计NLP的核心。以文本分类为例: - \(x\) = 文本特征 - \(y\) = 类别标签 - \(P(y|x)\) = 给定文本的类别概率
from collections import Counter, defaultdict
import math
class NaiveBayesClassifier:
"""朴素贝叶斯文本分类器 - 统计NLP时代的经典算法"""
def __init__(self):
self.class_counts = Counter() # Counter统计元素出现次数
self.word_counts = defaultdict(Counter) # defaultdict访问不存在的键时返回默认值
self.vocab = set()
self.total_docs = 0
def train(self, documents, labels):
"""训练朴素贝叶斯模型"""
self.total_docs = len(documents)
for doc, label in zip(documents, labels): # zip按位置配对
self.class_counts[label] += 1
words = doc.split()
for word in words:
self.word_counts[label][word] += 1
self.vocab.add(word)
def predict(self, document):
"""预测文档类别"""
words = document.split()
best_label = None
best_score = float('-inf')
for label in self.class_counts:
# log P(class)
score = math.log(self.class_counts[label] / self.total_docs)
# log P(word|class) with Laplace smoothing
total_words = sum(self.word_counts[label].values())
for word in words:
count = self.word_counts[label].get(word, 0)
score += math.log((count + 1) / (total_words + len(self.vocab)))
if score > best_score:
best_score = score
best_label = label
return best_label
# 示例:简单情感分类
docs = [
"这部电影太好看了 强烈推荐",
"非常喜欢 剧情很精彩",
"拍得真好 演技在线",
"太难看了 浪费时间",
"剧情太差 不推荐",
"很无聊 看了一半就关了",
]
labels = ["正面", "正面", "正面", "负面", "负面", "负面"]
nb = NaiveBayesClassifier()
nb.train(docs, labels)
test_docs = ["这部电影很好看", "太无聊了 不推荐"]
for doc in test_docs:
print(f"'{doc}' → {nb.predict(doc)}")
2.3 深度学习时代(2013-2017)¶
核心思想:用神经网络自动学习特征表示
里程碑事件:
| 年份 | 技术 | 论文/团队 | 影响 |
|---|---|---|---|
| 2013 | Word2Vec | Mikolov/Google | 开创词向量时代 |
| 2014 | GloVe | Stanford | 结合全局统计信息 |
| 2014 | Seq2Seq | Sutskever/Google | 序列到序列框架 |
| 2014 | TextCNN | Yoon Kim | CNN用于文本分类 |
| 2015 | Attention | Bahdanau | 注意力机制 |
| 2017 | Transformer | Vaswani/Google | 彻底改变NLP格局 |
import numpy as np
def softmax(x):
"""Softmax函数 - 注意力机制的核心组件"""
exp_x = np.exp(x - np.max(x))
return exp_x / exp_x.sum()
def self_attention(Q, K, V):
"""
自注意力机制简化实现
Q, K, V: shape = (seq_len, d_model)
"""
d_k = K.shape[-1] # [-1]负索引取最后元素
# 计算注意力分数
scores = np.dot(Q, K.T) / np.sqrt(d_k) # np.dot矩阵/向量点乘
# 归一化
attention_weights = np.array([softmax(s) for s in scores]) # np.array创建NumPy数组
# 加权求和
output = np.dot(attention_weights, V)
return output, attention_weights
# 示例:3个词,维度为4的自注意力
np.random.seed(42)
seq_len, d_model = 3, 4
X = np.random.randn(seq_len, d_model)
# 简化版:Q=K=V=X
output, weights = self_attention(X, X, X)
print("输入形状:", X.shape)
print("输出形状:", output.shape)
print("注意力权重:\n", np.round(weights, 3))
2.4 预训练模型时代(2018-2022)¶
核心思想:大规模预训练 + 下游任务微调
| 年份 | 模型 | 参数量 | 核心创新 |
|---|---|---|---|
| 2018.02 | ELMo | 94M | 上下文相关的词向量 |
| 2018.06 | GPT-1 | 117M | 单向Transformer预训练 |
| 2018.10 | BERT | 340M | 双向Transformer + MLM |
| 2019 | GPT-2 | 1.5B | 更大的语言模型 |
| 2020 | GPT-3 | 175B | In-Context Learning |
| 2022 | ChatGPT | ~175B* | RLHF对齐 |
*注:ChatGPT参数量未公开,估计基于GPT-3.5架构,约175B以上
2.5 大模型时代(2023至今)¶
核心思想:涌现能力、指令跟随、多模态融合
大模型时代的范式转变:
传统范式:预训练 → 微调 → 部署
大模型范式:预训练 → 对齐 → Prompt → 部署
传统NLP:一个任务一个模型
大模型NLP:一个模型解决所有任务
3. NLP核心任务分类¶
3.1 词法分析¶
分词(Tokenization / Word Segmentation)¶
将连续的文本切分为有意义的词语单元。
import jieba
# 中文分词
text = "自然语言处理是人工智能皇冠上的明珠"
words = jieba.lcut(text)
print("分词结果:", "/".join(words))
# 输出: 自然语言/处理/是/人工智能/皇冠/上/的/明珠
# 英文分词(相对简单)
en_text = "Natural language processing is a fascinating field."
en_words = en_text.split()
print("英文分词:", en_words)
词性标注(Part-of-Speech Tagging)¶
为每个词标注其语法类别(名词、动词、形容词等)。
import jieba.posseg as pseg
text = "小明在北京大学学习计算机科学"
words = pseg.lcut(text)
for word, flag in words:
print(f"{word}/{flag}", end=" ")
# 输出: 小明/nr 在/p 北京大学/nt 学习/v 计算机/n 科学/n
常见词性标签:
| 标签 | 含义 | 示例 |
|---|---|---|
| n | 名词 | 苹果、电脑 |
| v | 动词 | 吃、学习 |
| a | 形容词 | 漂亮、快速 |
| d | 副词 | 非常、已经 |
| nr | 人名 | 张三、李四 |
| ns | 地名 | 北京、上海 |
| nt | 机构名 | 清华大学 |
命名实体识别(Named Entity Recognition, NER)¶
识别文本中的实体(人名、地名、机构名、时间等)。
# 使用简单规则演示NER的基本概念
import re
def simple_ner(text):
"""简单的规则式NER"""
entities = []
# 日期识别
date_pattern = r'\d{4}年\d{1,2}月\d{1,2}日'
for match in re.finditer(date_pattern, text):
entities.append(("DATE", match.group(), match.start()))
# 金额识别
money_pattern = r'\d+(?:\.\d+)?[万亿]?元'
for match in re.finditer(money_pattern, text):
entities.append(("MONEY", match.group(), match.start()))
return entities
text = "2024年3月15日,阿里巴巴发布了一款价值299元的新产品"
entities = simple_ner(text)
for etype, value, pos in entities:
print(f"[{etype}] {value} (位置: {pos})")
3.2 句法分析¶
依存句法分析(Dependency Parsing)¶
分析句子中词语之间的依存关系。
# 使用SpaCy进行句法分析(需安装中文模型)
# pip install spacy
# python -m spacy download zh_core_web_sm
try: # try/except捕获异常
import spacy
nlp = spacy.load("zh_core_web_sm")
doc = nlp("自然语言处理技术发展迅速")
for token in doc:
print(f"{token.text} --{token.dep_}--> {token.head.text}")
except OSError:
print("请先安装中文SpaCy模型: python -m spacy download zh_core_web_sm")
成分句法分析(Constituency Parsing)¶
将句子分解为层次化的短语结构。
3.3 语义分析¶
语义角色标注(Semantic Role Labeling)¶
识别句子中"谁对谁做了什么"。
文本蕴含(Textual Entailment / NLI)¶
判断两个句子之间的逻辑关系。
# 文本蕴含示例
examples = [
{
"premise": "所有的猫都是动物",
"hypothesis": "我的猫是动物",
"label": "蕴含(Entailment)"
},
{
"premise": "今天下雨了",
"hypothesis": "今天是晴天",
"label": "矛盾(Contradiction)"
},
{
"premise": "他去了超市",
"hypothesis": "他买了很多东西",
"label": "中立(Neutral)"
},
]
for ex in examples:
print(f"前提: {ex['premise']}")
print(f"假设: {ex['hypothesis']}")
print(f"关系: {ex['label']}")
print("---")
3.4 文本生成任务¶
机器翻译(Machine Translation)¶
文本摘要(Text Summarization)¶
对话生成(Dialogue Generation)¶
3.5 信息检索与挖掘¶
文本分类(Text Classification)¶
# 文本分类的多种场景
classification_tasks = {
"情感分析": {
"输入": "这家餐厅的服务态度太差了",
"输出": "负面",
"类别": ["正面", "负面", "中性"]
},
"主题分类": {
"输入": "央行宣布下调存款准备金率0.5个百分点",
"输出": "财经",
"类别": ["财经", "科技", "体育", "娱乐", "政治"]
},
"意图识别": {
"输入": "帮我订一张明天去北京的机票",
"输出": "订票",
"类别": ["查询", "订票", "退票", "改签", "闲聊"]
},
"垃圾检测": {
"输入": "恭喜你中了100万大奖,点击链接领取",
"输出": "垃圾信息",
"类别": ["正常", "垃圾信息"]
},
}
for task_name, info in classification_tasks.items():
print(f"📌 {task_name}")
print(f" 输入: {info['输入']}")
print(f" 输出: {info['输出']}")
print(f" 候选类别: {info['类别']}")
print()
信息抽取(Information Extraction)¶
输入: "苹果公司CEO库克在WWDC 2024上发布了Apple Intelligence"
抽取结果:
- 实体: [苹果公司(ORG), 库克(PER), WWDC 2024(EVENT), Apple Intelligence(PROD)]
- 关系: (库克, CEO_of, 苹果公司), (库克, 发布, Apple Intelligence)
- 事件: 产品发布(时间=WWDC 2024, 发起人=库克, 对象=Apple Intelligence)
4. NLP的技术栈全景¶
4.1 传统NLP技术栈¶
传统NLP技术栈 = {
"特征提取": {
"词袋模型": "Bag of Words",
"TF-IDF": "词频-逆文档频率",
"N-gram": "N元语法模型",
},
"经典模型": {
"朴素贝叶斯": "文本分类",
"SVM": "文本分类、情感分析",
"HMM": "序列标注",
"CRF": "命名实体识别",
"LDA": "主题模型",
},
"工具库": ["NLTK", "jieba", "SnowNLP", "HanLP"],
}
4.2 深度学习NLP技术栈¶
深度学习NLP技术栈 = {
"词向量": {
"静态词向量": ["Word2Vec", "GloVe", "FastText"],
"上下文词向量": ["ELMo", "BERT embeddings"],
},
"基础模型": {
"CNN": "TextCNN (文本分类)",
"RNN/LSTM/GRU": "序列建模",
"Seq2Seq + Attention": "翻译、摘要",
"Transformer": "通用架构",
},
"预训练模型": {
"编码器": ["BERT", "RoBERTa", "ALBERT", "ERNIE"],
"解码器": ["GPT", "GPT-2", "GPT-3"],
"编码器-解码器": ["T5", "BART", "mT5"],
},
"工具框架": ["Transformers", "PyTorch", "SpaCy"],
}
4.3 大模型时代技术栈¶
大模型时代技术栈 = {
"模型": {
"闭源": ["GPT-4", "Claude", "Gemini"],
"开源": ["LLaMA", "Mistral", "ChatGLM", "Qwen"],
},
"技术": {
"Prompt Engineering": "设计高效提示词",
"RAG": "检索增强生成",
"Fine-tuning": ["LoRA", "QLoRA", "全参数微调"],
"RLHF/RLAIF": "人类反馈对齐",
"Agent": "智能体框架",
},
"工具": {
"模型服务": ["vLLM", "TGI", "Ollama"],
"应用框架": ["LangChain", "LlamaIndex", "Dify"],
"向量数据库": ["Milvus", "Chroma", "FAISS"],
},
}
5. NLP的困难与挑战¶
5.1 语言的歧义性¶
歧义是NLP最核心的挑战之一:
词汇歧义(Lexical Ambiguity)¶
# 同一个词在不同语境下含义不同
ambiguity_examples = {
"苹果": [
"我吃了一个苹果", # 水果
"苹果公司发布了新产品", # 公司名
],
"开": [
"他开了门", # 打开
"他开了一家店", # 创办
"花儿开了", # 绽放
"他开了一个玩笑", # 讲述
],
"打": [
"打篮球", # 进行运动
"打电话", # 拨打
"打酱油", # 买/不关心
"打雷了", # 发出
],
}
for word, examples in ambiguity_examples.items():
print(f"「{word}」的多义:")
for ex in examples:
print(f" - {ex}")
print()
句法歧义(Syntactic Ambiguity)¶
"用望远镜看到了戴帽子的人"
解读1: [用望远镜] [看到了] [戴帽子的人]
→ 我用望远镜看到了一个戴帽子的人
解读2: [看到了] [用望远镜的] [戴帽子的人]
→ 我看到一个用望远镜且戴帽子的人
"咬死了猎人的狗"
解读1: [咬死了] [猎人的狗]
→ (某物)咬死了属于猎人的那条狗
解读2: [咬死了猎人的] [狗]
→ 那条咬死了猎人的狗
语义歧义¶
5.2 语言的多样性¶
# 同一个意思的多种表达
same_meaning = [
"这部电影很好看",
"这片子真不错",
"强烈推荐这个电影",
"看完觉得值了",
"难得的好片",
"这是近几年最好的电影之一",
"五星好评 yyds",
"爆好看!!!",
"🔥🔥🔥",
]
print("以下表达都是正面情感:")
for expr in same_meaning:
print(f" ✓ {expr}")
5.3 上下文依赖¶
A: "你吃了吗?"
B: "吃了" ← 这里省略了"我"和"饭"
A: "明天几点的会?"
B: "十点" ← 需要理解"会"指什么,"十点"补全了时间
代词消解:
"小明告诉小红他很高兴" → "他"指谁?取决于上下文
5.4 知识和常识¶
"他端着一杯水走进来,然后把它放在桌上"
→ 机器需要知道"它"指的是水杯(不是桌子)
→ 机器需要知道水杯可以放在桌上(物理常识)
"餐厅里的牛排很老"
→ "老"指口感硬,不是指年龄大
→ 需要领域知识
5.5 中文NLP的特有挑战¶
中文特有挑战 = {
"分词问题": "中文没有天然的空格分隔 → '结婚的和尚未结婚的'",
"字词关系": "字和词的边界模糊 → '研究' vs '研' + '究'",
"新词发现": "网络新词层出不穷 → '内卷', 'YYDS', '摆烂'",
"简繁体": "简体和繁体的统一处理",
"拼音歧义": "同音字多 → shi可以对应几十个字",
"文言文": "古文和现代文的差异巨大",
"方言": "不同方言区的语言差异",
}
for challenge, description in 中文特有挑战.items():
print(f"📌 {challenge}: {description}")
5.6 工程挑战¶
| 挑战 | 描述 | 应对策略 |
|---|---|---|
| 数据稀缺 | 特定领域标注数据少 | 数据增强、迁移学习、Few-shot |
| 标注质量 | 标注不一致、错误多 | 多人标注、质量控制 |
| 模型效率 | 大模型推理慢 | 量化、蒸馏、剪枝 |
| 领域迁移 | 通用模型不适应特定领域 | 领域预训练、领域微调 |
| 冷启动 | 新任务没有训练数据 | Prompt Learning、零样本 |
6. NLP应用场景¶
6.1 搜索引擎¶
用户查询: "如何用Python处理文本"
│
▼
┌─────────────────────────┐
│ 查询理解 │
│ - 分词: 如何/用/Python/处理/文本 │
│ - 意图识别: 编程教程查询 │
│ - 查询改写/扩展 │
└─────────────────────────┘
│
▼
┌─────────────────────────┐
│ 语义匹配 │
│ - 文档检索 │
│ - 语义相似度计算 │
│ - 排序 │
└─────────────────────────┘
│
▼
┌─────────────────────────┐
│ 结果展示 │
│ - 摘要生成 │
│ - 高亮显示 │
│ - 相关推荐 │
└─────────────────────────┘
6.2 智能客服¶
# 智能客服系统的NLP模块示意
class SmartCustomerService:
def __init__(self):
self.intent_classifier = None # 意图分类
self.entity_recognizer = None # 实体识别
self.dialog_manager = None # 对话管理
self.response_generator = None # 回复生成
def process(self, user_input):
"""处理用户输入"""
# Step 1: 意图识别
intent = self.classify_intent(user_input)
# "我想退货" → intent: "退货申请"
# Step 2: 实体抽取
entities = self.extract_entities(user_input)
# "我昨天买的iPhone 15想退货" → {商品: "iPhone 15", 时间: "昨天"}
# Step 3: 对话管理
action = self.manage_dialog(intent, entities)
# 判断是否需要补充信息
# Step 4: 生成回复
response = self.generate_response(action)
return response
def classify_intent(self, text):
"""意图分类"""
intents = {
"退货": ["退货", "退款", "不想要了"],
"查询订单": ["订单", "到哪了", "快递"],
"投诉": ["投诉", "差评", "态度差"],
"咨询": ["多少钱", "有没有", "推荐"],
}
for intent, keywords in intents.items():
if any(kw in text for kw in keywords): # any()任一为True则返回True
return intent
return "其他"
def extract_entities(self, text):
return {}
def manage_dialog(self, intent, entities):
return {"intent": intent, "entities": entities}
def generate_response(self, action):
return "好的,我来帮您处理。"
# 测试
service = SmartCustomerService()
queries = [
"我想退货",
"我的订单到哪了",
"有什么好的手机推荐吗",
]
for q in queries:
intent = service.classify_intent(q)
print(f"用户: {q} → 意图: {intent}")
6.3 更多应用场景¶
应用场景 = {
"🔍 搜索推荐": ["查询理解", "语义匹配", "个性化推荐"],
"💬 智能对话": ["智能客服", "聊天机器人", "语音助手"],
"📝 内容创作": ["自动写作", "文本摘要", "标题生成"],
"🌍 机器翻译": ["文档翻译", "实时对话翻译", "字幕翻译"],
"📊 商业智能": ["舆情分析", "竞品分析", "用户画像"],
"⚖️ 法律": ["合同审查", "案例检索", "法律问答"],
"🏥 医疗": ["病历分析", "医学文献挖掘", "辅助诊断"],
"💰 金融": ["新闻情感分析", "研报摘要", "风控建模"],
"🎓 教育": ["作文评分", "智能批改", "知识问答"],
"🛡️ 安全": ["内容审核", "虚假新闻检测", "网络诈骗检测"],
}
for category, tasks in 应用场景.items():
print(f"{category}: {', '.join(tasks)}")
7. NLP评估指标¶
7.1 分类任务指标¶
import numpy as np
def compute_classification_metrics(y_true, y_pred):
"""计算分类指标"""
# 简化版:假设是二分类
tp = sum(1 for t, p in zip(y_true, y_pred) if t == 1 and p == 1)
fp = sum(1 for t, p in zip(y_true, y_pred) if t == 0 and p == 1)
fn = sum(1 for t, p in zip(y_true, y_pred) if t == 1 and p == 0)
tn = sum(1 for t, p in zip(y_true, y_pred) if t == 0 and p == 0)
accuracy = (tp + tn) / (tp + fp + fn + tn)
precision = tp / (tp + fp) if (tp + fp) > 0 else 0
recall = tp / (tp + fn) if (tp + fn) > 0 else 0
f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0
return {
"Accuracy": accuracy,
"Precision": precision,
"Recall": recall,
"F1-Score": f1,
}
# 示例
y_true = [1, 1, 1, 0, 0, 1, 0, 1, 1, 0]
y_pred = [1, 1, 0, 0, 0, 1, 1, 1, 0, 0]
metrics = compute_classification_metrics(y_true, y_pred)
for name, value in metrics.items():
print(f"{name}: {value:.4f}")
各指标含义:
7.2 序列标注指标¶
def compute_ner_metrics(true_entities, pred_entities):
"""
NER评估:基于实体级别的P/R/F1
true_entities: [("PER", 0, 2), ("LOC", 5, 7), ...]
pred_entities: [("PER", 0, 2), ("LOC", 5, 8), ...]
"""
true_set = set(true_entities)
pred_set = set(pred_entities)
tp = len(true_set & pred_set)
precision = tp / len(pred_set) if pred_set else 0
recall = tp / len(true_set) if true_set else 0
f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0
return {"Precision": precision, "Recall": recall, "F1": f1}
# 示例:NER结果 (类型, 起始位置, 结束位置)
true_entities = [("PER", 0, 2), ("LOC", 5, 7), ("ORG", 10, 14)]
pred_entities = [("PER", 0, 2), ("LOC", 5, 8), ("ORG", 10, 14)]
metrics = compute_ner_metrics(true_entities, pred_entities)
print("NER评估结果:")
for name, value in metrics.items():
print(f" {name}: {value:.4f}")
7.3 生成任务指标¶
BLEU分数¶
from collections import Counter
import math
def compute_bleu(reference, candidate, max_n=4):
"""BLEU分数简化计算"""
scores = []
for n in range(1, max_n + 1):
# 生成n-gram
ref_ngrams = Counter()
for i in range(len(reference) - n + 1):
ref_ngrams[tuple(reference[i:i+n])] += 1
cand_ngrams = Counter()
for i in range(len(candidate) - n + 1):
cand_ngrams[tuple(candidate[i:i+n])] += 1
# 计算匹配
match = 0
for ngram, count in cand_ngrams.items():
match += min(count, ref_ngrams.get(ngram, 0))
total = max(len(candidate) - n + 1, 1)
scores.append(match / total if total > 0 else 0)
# 几何平均
if min(scores) > 0:
log_avg = sum(math.log(s) for s in scores) / len(scores)
bleu = math.exp(log_avg)
else:
bleu = 0
# 简短惩罚
bp = min(1, math.exp(1 - len(reference) / max(len(candidate), 1)))
return bleu * bp
# 示例
reference = "the cat is on the mat".split()
candidate = "the cat is on the mat".split()
print(f"完美匹配 BLEU: {compute_bleu(reference, candidate):.4f}")
candidate2 = "the cat on mat".split()
print(f"部分匹配 BLEU: {compute_bleu(reference, candidate2):.4f}")
ROUGE分数¶
7.4 常用指标总结¶
| 任务类型 | 主要指标 | 说明 |
|---|---|---|
| 文本分类 | Accuracy, F1 | 多类别用Macro/Micro F1 |
| NER | Entity-level F1 | 实体边界和类型都要对 |
| 机器翻译 | BLEU | 机器翻译标准指标 |
| 文本摘要 | ROUGE-L | 基于最长公共子序列 |
| 阅读理解 | EM, F1 | EM=完全匹配 |
| 文本生成 | PPL, Human Eval | 困惑度+人工评估 |
8. 动手实验:初探NLP¶
8.1 实验一:构建简单的NLP流水线¶
"""
实验一:构建一个完整的NLP处理流水线
包含分词、词频统计、关键词提取
"""
import re
from collections import Counter
class SimpleNLPPipeline:
"""简单的NLP处理流水线"""
def __init__(self):
# 停用词列表(简化版)
self.stopwords = set([
"的", "了", "在", "是", "我", "有", "和", "就",
"不", "人", "都", "一", "一个", "上", "也", "很",
"到", "说", "要", "去", "你", "会", "着", "没有",
"看", "好", "自己", "这", "他", "她", "它",
])
def clean_text(self, text):
"""文本清洗"""
# 去除特殊字符
text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9\s]', '', text)
# 去除多余空格
text = re.sub(r'\s+', ' ', text).strip()
return text
def simple_tokenize(self, text):
"""简单的基于字符的分词(实际应用中使用jieba等工具)"""
# 这里使用简单的逐字分割(演示用)
# 实际项目中应使用jieba
tokens = list(text)
return [t for t in tokens if t.strip()]
def remove_stopwords(self, tokens):
"""去除停用词"""
return [t for t in tokens if t not in self.stopwords]
def word_frequency(self, tokens):
"""词频统计"""
return Counter(tokens)
def process(self, text):
"""完整处理流程"""
print(f"原始文本: {text[:50]}...") # 切片操作,取前n个元素
# Step 1: 清洗
cleaned = self.clean_text(text)
print(f"清洗后: {cleaned[:50]}...")
# Step 2: 分词
tokens = self.simple_tokenize(cleaned)
print(f"分词结果(前10): {tokens[:10]}")
# Step 3: 去停用词
filtered = self.remove_stopwords(tokens)
print(f"去停用词后(前10): {filtered[:10]}")
# Step 4: 词频统计
freq = self.word_frequency(filtered)
print(f"高频词Top10: {freq.most_common(10)}")
return {
"cleaned_text": cleaned,
"tokens": tokens,
"filtered_tokens": filtered,
"word_frequency": freq,
}
# 测试
pipeline = SimpleNLPPipeline()
text = """自然语言处理是人工智能领域中最重要的研究方向之一。
自然语言处理的目标是让计算机能够理解和生成人类语言。
近年来,随着深度学习技术的发展,自然语言处理取得了巨大进步。
特别是Transformer架构的提出,彻底改变了自然语言处理的研究范式。"""
result = pipeline.process(text)
8.2 实验二:情感分析初体验¶
"""
实验二:基于关键词的简单情感分析
"""
class SimpleSentimentAnalyzer:
"""基于词典的情感分析(baseline方法)"""
def __init__(self):
self.positive_words = set([
"好", "棒", "美", "喜欢", "开心", "满意", "优秀",
"推荐", "不错", "赞", "精彩", "完美", "出色",
"厉害", "高兴", "快乐", "感动", "温暖", "惊喜",
"有趣", "好看", "好听", "好吃", "舒服", "方便",
])
self.negative_words = set([
"差", "烂", "讨厌", "失望", "难过", "糟糕", "垃圾",
"难看", "难吃", "无聊", "坑", "恶心", "后悔",
"浪费", "差劲", "不好", "难受", "生气", "愤怒",
"可怕", "丑", "慢", "贵", "假", "骗",
])
self.negation_words = set([
"不", "没", "无", "别", "莫", "非", "未",
])
def analyze(self, text):
"""分析文本情感"""
positive_count = 0
negative_count = 0
chars = list(text)
for i, char in enumerate(chars):
# 检查是否有否定词在前面
is_negated = False
if i > 0 and chars[i-1] in self.negation_words:
is_negated = True
if char in self.positive_words:
if is_negated:
negative_count += 1
else:
positive_count += 1
elif char in self.negative_words:
if is_negated:
positive_count += 1
else:
negative_count += 1
# 也检查双字词
for i in range(len(chars) - 1):
word = chars[i] + chars[i+1]
if word in self.positive_words:
positive_count += 1
elif word in self.negative_words:
negative_count += 1
# 判断情感
if positive_count > negative_count:
sentiment = "正面 😊"
elif negative_count > positive_count:
sentiment = "负面 😞"
else:
sentiment = "中性 😐"
return {
"sentiment": sentiment,
"positive_count": positive_count,
"negative_count": negative_count,
"score": positive_count - negative_count,
}
# 测试
analyzer = SimpleSentimentAnalyzer()
test_texts = [
"这部电影太精彩了,强烈推荐!",
"服务态度太差了,非常失望",
"产品质量还行,价格有点贵",
"今天心情不错,天气也很好",
"这个餐厅不好吃,也不便宜",
]
for text in test_texts:
result = analyzer.analyze(text)
print(f"文本: {text}")
print(f" 情感: {result['sentiment']} (分数: {result['score']})")
print()
8.3 实验三:文本相似度计算¶
"""
实验三:基于词袋模型的文本相似度计算
"""
import math
from collections import Counter
def text_to_vector(text):
"""将文本转为词频向量"""
words = list(text) # 简单的按字分割
return Counter(words)
def cosine_similarity(vec1, vec2):
"""计算余弦相似度"""
# 获取所有词
all_words = set(vec1.keys()) | set(vec2.keys())
# 计算点积
dot_product = sum(vec1.get(w, 0) * vec2.get(w, 0) for w in all_words)
# 计算模长
norm1 = math.sqrt(sum(v**2 for v in vec1.values()))
norm2 = math.sqrt(sum(v**2 for v in vec2.values()))
if norm1 == 0 or norm2 == 0:
return 0.0
return dot_product / (norm1 * norm2)
# 测试文本相似度
texts = [
"自然语言处理是人工智能的重要分支",
"自然语言处理属于人工智能领域",
"深度学习推动了计算机视觉的发展",
"今天天气很好适合出去玩",
]
print("文本相似度矩阵:")
print("-" * 60)
for i in range(len(texts)):
for j in range(len(texts)):
vec_i = text_to_vector(texts[i])
vec_j = text_to_vector(texts[j])
sim = cosine_similarity(vec_i, vec_j)
print(f"{sim:.3f}", end=" ")
print()
print()
print("文本内容:")
for i, t in enumerate(texts):
print(f" [{i}] {t}")
9. 面试要点¶
🔑 面试高频考点
考点1:NLP的核心挑战是什么?¶
✅ 标准答案要点:
1. 歧义性:词汇歧义、句法歧义、语义歧义
2. 多样性:同一语义可以有多种表达
3. 上下文依赖:代词消解、省略恢复
4. 知识依赖:需要世界知识和常识推理
5. 数据稀缺:低资源语言和领域的数据不足
6. 评估困难:生成任务的质量难以自动评估
考点2:NLP的发展经历了哪些阶段?¶
✅ 标准答案要点:
1. 规则时代(1950s-1989):手写规则和模板
2. 统计方法时代(1990s-2012):HMM、CRF、SVM
3. 深度学习时代(2013-2017):Word2Vec、CNN、RNN、Transformer
4. 预训练模型时代(2018-2022):BERT、GPT、T5
5. 大模型时代(2023-今):ChatGPT、RLHF、Agent
关键转折点:Transformer(2017)和BERT(2018)
考点3:Precision和Recall的区别和trade-off?¶
✅ 标准答案要点:
- Precision(精确率):预测为正的样本中真正为正的比例
- Recall(召回率):所有正样本中被正确预测的比例
- Trade-off:提高Precision通常会降低Recall,反之亦然
- 应用选择:
- 垃圾邮件过滤:倾向高Precision(不要误伤正常邮件)
- 疾病诊断:倾向高Recall(不要漏诊)
- 平衡考虑:使用F1-Score
考点4:BLEU和ROUGE的区别?¶
✅ 标准答案要点:
- BLEU:面向精确率的指标,主要用于机器翻译
- 衡量候选译文中n-gram在参考译文中出现的比例
- 注重"生成的有多少是对的"
- ROUGE:面向召回率的指标,主要用于文本摘要
- 衡量参考摘要中n-gram在候选摘要中出现的比例
- 注重"参考答案中有多少被覆盖"
考点5:中文NLP和英文NLP的主要区别?¶
✅ 标准答案要点:
1. 分词:中文需要专门的分词步骤
2. 字符集:中文字符集远大于英文(几万 vs 26)
3. 形态学:英文有词形变化(时态、复数),中文没有
4. 句法:语序差异,中文主语可省略
5. 资源:英文NLP资源和工具更丰富
6. 预训练:中文预训练模型(ERNIE、MacBERT)考虑了中文特点
10. 练习题¶
📝 基础题¶
- 选择题:以下哪项不是NLP的基本任务?
- A. 词性标注
- B. 图像分割
- C. 命名实体识别
- D. 机器翻译
答案:B。图像分割属于计算机视觉(CV)任务,不属于NLP的基本任务。词性标注、命名实体识别和机器翻译都是经典的NLP任务。
- 简答题:请列举5种NLP的实际应用场景,并简述每种场景使用了哪些NLP技术。
答案:①智能客服/对话系统:使用意图识别、槽填充、对话管理等技术;②搜索引擎:使用查询理解、文本匹配、语义检索等技术;③机器翻译(如Google翻译):使用Seq2Seq、Transformer、注意力机制等技术;④情感分析/舆情监控:使用文本分类、情感词典、深度学习分类模型;⑤智能写作/文本生成(如AI摘要、辅助写作):使用语言模型、Seq2Seq、Prompt Engineering等技术。
- 简答题:解释词汇歧义和句法歧义的区别,各举一个中文的例子。
答案:词汇歧义指同一个词有多种含义,需根据上下文消歧。例如"苹果"可指水果,也可指苹果公司。句法歧义指同一个句子可以有不同的句法结构和解读。例如"咬死了猎人的狗"可理解为"狗咬死了猎人"或"猎人的狗被咬死了",两种句法分析都合法但语义不同。
💻 编程题¶
-
编程题:修改ELIZA对话系统代码,增加至少5条新的模式匹配规则,使其能够处理更多类型的对话。
-
编程题:实现一个简单的文本分类器,使用词频统计作为特征,对以下类别进行分类:体育、科技、娱乐。
-
编程题:编写一个函数,计算两段中文文本的Jaccard相似度。
def jaccard_similarity(text1, text2):
"""
计算两段文本的Jaccard相似度
提示:Jaccard = |A ∩ B| / |A ∪ B|
"""
# 你的代码
pass
# 测试
text1 = "自然语言处理是人工智能的核心"
text2 = "自然语言处理是人工智能的重要分支"
print(f"Jaccard相似度: {jaccard_similarity(text1, text2)}")
🔬 思考题¶
- 思考题:在大模型(如ChatGPT)时代,传统NLP任务(如分词、NER、文本分类)是否还有研究和工程价值?请给出你的分析。
答案:仍有重要价值。①工程价值:大模型推理成本高、延迟大,在高并发/低延迟场景(如搜索引擎分词、实时NER)中,轻量级专用模型更实用;②研究价值:传统任务是理解语言的基础,为大模型提供评测基准和训练数据;③互补关系:大模型擅长通用理解,但在垂直领域、低资源语言、边缘部署等场景,微调的小模型仍是最佳选择;④可解释性:传统模型结构清晰,更容易解释和调试。
- 思考题:如果要选择一个NLP方向深入研究(信息抽取、对话系统、文本生成),你会选哪个?为什么?
答案(参考):三个方向各有前景。文本生成是当前最热门的方向,大模型的核心能力就是生成,研究空间广阔(可控生成、幻觉消除、RLHF等)。对话系统应用场景最广(客服、助手、Agent),且与大模型结合最紧密,是产品化最直接的方向。信息抽取在知识图谱、数据治理、行业应用(金融、医疗、法律)中有刚需,且结构化输出是大模型的薄弱环节。建议根据个人兴趣和职业规划选择。
✅ 自我检查清单¶
□ 我能用自己的话解释什么是NLP
□ 我知道NLP的5个发展阶段和关键转折点
□ 我能列举至少8种NLP核心任务
□ 我理解NLP面临的主要挑战(歧义性、多样性等)
□ 我能区分Precision、Recall和F1-Score
□ 我知道BLEU和ROUGE分别用于什么任务
□ 我能运行本章的所有代码示例
□ 我完成了至少4道练习题
📚 延伸阅读¶
- Jurafsky & Martin - Speech and Language Processing (3rd edition)
- Stanford CS224N 课程主页
- ACL Anthology - NLP顶会论文库
- NLP Progress - NLP各任务SOTA追踪
- Papers With Code - NLP
下一篇:02-文本预处理 — 掌握NLP的第一步:数据清洗与预处理