自然语言处理 (NLP) 是人工智能的核心领域,涵盖文本理解、生成与转换。从传统的词袋模型到 BERT 预训练,再到 GPT 生成,NLP 技术经历了革命性发展。本文系统讲解从预处理到实际任务的完整流程。
1. 文本预处理
1.1 基础处理
import re
import jieba
from collections import Counter
def preprocess_text(text):
# 1. 清洗
text = re.sub(r'[^一-鿿\w\s]', '', text) # 保留中文、英文、数字
text = re.sub(r'\s+', ' ', text).strip()
# 2. 分词(中文)
tokens = jieba.lcut(text)
# 3. 去停用词
stopwords = set(['的', '了', '在', '是', '和', '就', '都', '而', '及', '与'])
tokens = [t for t in tokens if t not in stopwords and len(t) > 1]
return tokens
# 英文预处理
import nltk
from nltk.tokenize import word_tokenize
from nltk.stem import WordNetLemmatizer
def preprocess_eng(text):
text = text.lower()
tokens = word_tokenize(text)
lemmatizer = WordNetLemmatizer()
tokens = [lemmatizer.lemmatize(t) for t in tokens if t.isalpha()]
return tokens
1.2 文本表示
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
# 词袋模型 (BoW)
count_vec = CountVectorizer(max_features=5000)
X_bow = count_vec.fit_transform(documents)
# TF-IDF(词频-逆文档频率)
tfidf_vec = TfidfVectorizer(max_features=5000, ngram_range=(1, 2))
X_tfidf = tfidf_vec.fit_transform(documents)
TF-IDF 公式:
$$TF-IDF(t, d) = TF(t, d) \times IDF(t) = \frac{f_{t,d}}{\sum f_{t’,d}} \times \log\frac{N}{1 + |{d: t \in d}|}$$
2. 词向量 (Word Embeddings)
2.1 Word2Vec
Mikolov 2013 年提出,通过上下文预测学习词向量。
CBOW:用上下文预测中心词
Skip-gram:用中心词预测上下文
from gensim.models import Word2Vec
sentences = [
['我', '喜欢', '机器学习'],
['深度', '学习', '是', '机器学习', '的', '分支'],
['自然语言', '处理', '属于', '人工智能'],
['人工智能', '改变', '世界']
]
model = Word2Vec(
sentences=sentences,
vector_size=100, # 词向量维度
window=5, # 上下文窗口
min_count=1, # 最小词频
sg=1, # 1=Skip-gram, 0=CBOW
workers=4,
epochs=100
)
# 相似词
similar = model.wv.most_similar('机器学习', topn=5)
print(similar)
# 词向量运算
result = model.wv.most_similar(
positive=['人工智能', '学习'],
negative=['人类'],
topn=1
)
2.2 GloVe
基于全局词-词共现统计,结合全局矩阵分解与局部上下文窗口。
import gensim.downloader as api
# 加载预训练词向量
glove_vectors = api.load("glove-wiki-gigaword-100")
# 相似度计算
glove_vectors.most_similar('computer', topn=5)
glove_vectors.similarity('king', 'queen')
# 类比推理: king - man + woman ≈ queen
result = glove_vectors.most_similar(
positive=['king', 'woman'],
negative=['man'],
topn=1
)
2.3 词向量特性
# 可视化
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
words = ['人工智能', '机器学习', '深度学习', '自然语言', '计算机', '人脑', '思维']
vectors = [model.wv[w] for w in words if w in model.wv]
pca = PCA(n_components=2)
reduced = pca.fit_transform(vectors)
plt.scatter(reduced[:, 0], reduced[:, 1])
for i, word in enumerate(words):
plt.annotate(word, (reduced[i, 0], reduced[i, 1]))
3. 预训练语言模型
3.1 模型演进
Word2Vec/GloVe → ELMo(上下文相关)→ GPT(Transformer Decoder)
→ BERT(Transformer Encoder,双向)→ RoBERTa/ALBERT/ELECTRA
→ GPT-2/3/4(生成式,规模扩大)
3.2 BERT 微调:文本分类
from transformers import BertTokenizer, BertForSequenceClassification
from transformers import Trainer, TrainingArguments
import torch
# 加载预训练模型和分词器
model_name = 'bert-base-chinese'
tokenizer = BertTokenizer.from_pretrained(model_name)
model = BertForSequenceClassification.from_pretrained(
model_name, num_labels=2)
# 数据准备
class TextDataset(torch.utils.data.Dataset):
def __init__(self, texts, labels, tokenizer, max_len=128):
self.texts = texts
self.labels = labels
self.tokenizer = tokenizer
self.max_len = max_len
def __len__(self):
return len(self.texts)
def __getitem__(self, idx):
text = str(self.texts[idx])
label = self.labels[idx]
encoding = self.tokenizer(
text,
truncation=True,
padding='max_length',
max_length=self.max_len,
return_tensors='pt'
)
return {
'input_ids': encoding['input_ids'].flatten(),
'attention_mask': encoding['attention_mask'].flatten(),
'labels': torch.tensor(label, dtype=torch.long)
}
# 训练配置
training_args = TrainingArguments(
output_dir='./results',
num_train_epochs=3,
per_device_train_batch_size=16,
per_device_eval_batch_size=32,
warmup_steps=500,
weight_decay=0.01,
logging_dir='./logs',
evaluation_strategy='epoch',
save_strategy='epoch',
load_best_model_at_end=True,
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=val_dataset,
)
trainer.train()
3.3 BERT 微调:命名实体识别 (NER)
from transformers import BertForTokenClassification
model = BertForTokenClassification.from_pretrained(
'bert-base-chinese',
num_labels=7 # B-PER, I-PER, B-LOC, I-LOC, B-ORG, I-ORG, O
)
# BIO 标注格式
# B-PER: 人名开头, I-PER: 人名中间, O: 非实体
labels_map = {'O': 0, 'B-PER': 1, 'I-PER': 2, 'B-LOC': 3,
'I-LOC': 4, 'B-ORG': 5, 'I-ORG': 6}
3.4 BERT 变体对比
| 模型 | 架构 | 参数 | 特点 |
|---|---|---|---|
| BERT-Base | Encoder | 110M | 12 层,768 维 |
| BERT-Large | Encoder | 340M | 24 层,1024 维 |
| RoBERTa | Encoder | 125M | 更优训练策略 |
| ALBERT | Encoder | 12M | 参数共享,轻量 |
| ELECTRA | Discriminator | - | 判别式预训练 |
| DeBERTa | Encoder | - | 增强注意力 |
4. 文本分类实战
import torch.nn as nn
from transformers import AutoModel, AutoTokenizer
class TextClassifier(nn.Module):
def __init__(self, model_name, num_classes, dropout=0.1):
super().__init__()
self.bert = AutoModel.from_pretrained(model_name)
self.dropout = nn.Dropout(dropout)
self.classifier = nn.Linear(self.bert.config.hidden_size, num_classes)
def forward(self, input_ids, attention_mask):
outputs = self.bert(
input_ids=input_ids,
attention_mask=attention_mask
)
# 取 CLS token 的隐藏状态
pooled = outputs.last_hidden_state[:, 0] # [CLS]
pooled = self.dropout(pooled)
return self.classifier(pooled)
# 训练
def train_epoch(model, dataloader, optimizer, criterion, device):
model.train()
total_loss = 0
for batch in dataloader:
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['labels'].to(device)
optimizer.zero_grad()
outputs = model(input_ids, attention_mask)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
total_loss += loss.item()
return total_loss / len(dataloader)
5. 文本生成
5.1 GPT-2 生成
from transformers import GPT2LMHeadModel, GPT2Tokenizer
model = GPT2LMHeadModel.from_pretrained('gpt2')
tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
prompt = "The rapid development of artificial intelligence"
inputs = tokenizer(prompt, return_tensors='pt')
# 贪婪解码
greedy = model.generate(**inputs, max_length=50)
print(tokenizer.decode(greedy[0]))
# Beam Search
beam = model.generate(**inputs, max_length=50, num_beams=5,
early_stopping=True)
# 采样解码(更自然)
sample = model.generate(**inputs, max_length=50,
do_sample=True, temperature=0.8,
top_k=50, top_p=0.95)
5.2 中文文本生成
from transformers import BertTokenizer, BartForConditionalGeneration
# 中文摘要
tokenizer = BertTokenizer.from_pretrained('fnlp/bart-base-chinese')
model = BartForConditionalGeneration.from_pretrained('fnlp/bart-base-chinese')
article = "这是一篇关于人工智能发展的长文..."
inputs = tokenizer([article], max_length=512, return_tensors='pt',
truncation=True)
summary_ids = model.generate(
inputs['input_ids'],
num_beams=4,
max_length=100,
early_stopping=True
)
summary = tokenizer.decode(summary_ids[0], skip_special_tokens=True)
6. 高级 NLP 技术
6.1 句向量表示
from sentence_transformers import SentenceTransformer
# 加载句向量模型
model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
sentences = [
"人工智能正在改变世界",
"机器学习是人工智能的分支",
"我今天吃了苹果"
]
embeddings = model.encode(sentences)
# 语义相似度
from sklearn.metrics.pairwise import cosine_similarity
sims = cosine_similarity(embeddings)
print(sims)
6.2 语义搜索
import faiss
# 构建索引
dimension = embeddings.shape[1]
index = faiss.IndexFlatIP(dimension) # 内积相似度(归一化后等价于余弦)
index.add(embeddings)
# 搜索
query = model.encode(["AI 的应用场景"])
D, I = index.search(query, k=3) # 返回 top-3
print(f"最相似的句子索引: {I[0]}")
6.3 文本摘要
from transformers import pipeline
# 抽取式摘要
summarizer = pipeline("summarization", model="facebook/bart-large-cnn")
article = """Long article text here..."""
summary = summarizer(article, max_length=130, min_length=30,
do_sample=False)
print(summary[0]['summary_text'])
7. Transformer 注意力机制详解
BERT 的成功源于 Transformer 的自注意力机制(Self-Attention)。理解 Attention 是掌握现代 NLP 的基础。
7.1 Self-Attention 计算流程
输入: X = [x1, x2, x3] (3 个 token, 每个 d 维)
WQ WK WV
│ │ │
▼ ▼ ▼
Q = XWQ K = XWK V = XWV
│ │ │
└───┬───┘ │
▼ │
QK^T / √dk │
│ │
softmax │
│ │
└───┬────────┘
▼
Attention(Q,K,V) = softmax(QK^T / √dk) · V
√dk 是缩放因子,防止点积结果过大导致 softmax 梯度消失。Q(Query)、K(Key)、V(Value)来自同一输入的三种线性投影,使模型能同时关注序列中任意位置的关联。
7.2 多头注意力(Multi-Head Attention)
将注意力切分为 h 个并行的"头",每个头独立学习不同的语义关系:
# 8 头注意力,每头 64 维(总 512 维)
# h1 可能关注语法角色,h2 关注共指关系,h3 关注语义相似...
# 最终拼接所有头的输出,经线性层融合
多头设计的直觉:不同语义层面的关联需要不同的投影空间。单头注意力被迫在一个空间内同时表示所有关系,表达能力受限。
7.3 位置编码
Transformer 本身没有循环或卷积,不知道 token 的顺序。位置编码为每个位置添加独特向量:
| 编码方式 | 公式特点 | 代表模型 |
|---|---|---|
| 绝对正弦 | PE(pos, 2i) = sin(pos/10000^(2i/d)) | 原始 Transformer |
| 可学习位置 Embedding | 与词嵌入同维度,联合训练 | BERT、GPT |
| 旋转位置编码 RoPE | 复数域旋转变换 | Llama、ChatGLM |
| ALiBi | 基于距离的线性偏置 | BLOOM |
RoPE 的优势是外推性好——训练时最大长度 2048,推理时可扩展到 4096 而无需额外训练。
8. 大模型微调技术(PEFT)
全参数微调 175B 参数模型成本高昂(需要数张 A100 和数周时间)。参数高效微调(PEFT)只训练少量参数即可获得接近全量微调的效果。
8.1 LoRA / QLoRA
LoRA(Low-Rank Adaptation):冻结预训练权重,在注意力层旁插入低秩矩阵进行训练。
from peft import LoraConfig, get_peft_model
lora_config = LoraConfig(
r=16, # 低秩维度,常用 8/16/32
lora_alpha=32, # 缩放因子
target_modules=["q_proj", "v_proj"], # 只改 Query/Value 投影
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
)
model = get_peft_model(base_model, lora_config)
model.print_trainable_parameters()
# 典型输出: trainable params: 20M || all params: 7B || trainable%: 0.28%
QLoRA:4-bit 量化基础模型 + LoRA 微调,单张 24GB 显卡可训练 65B 模型。
| 方法 | 显存需求 | 微调质量 | 适用场景 |
|---|---|---|---|
| 全参数微调 | 16× 模型参数 | 最高 | 数据量极大、充足算力 |
| LoRA | 原模型 + 2× r×d×层数 | 接近全量 | 通用首选 |
| QLoRA | ~1/4 原模型 + LoRA | 略低于 LoRA | 消费级显卡 |
| Prefix-tuning | 小 | 中 | NLG 生成任务 |
8.2 指令微调(Instruction Tuning)
让模型学会"按照指令回答问题":
{
"instruction": "将以下中文翻译成英文",
"input": "人工智能正在改变世界",
"output": "Artificial intelligence is changing the world."
}
常用数据集:Alpaca(52k)、Dolly、BELLE(中文)、COIG(中文)。指令微调的模板格式统一是模型能否正确跟随指令的关键。
9. RAG:检索增强生成
RAG(Retrieval-Augmented Generation)解决大模型"幻觉"(一本正经地胡说八道)和知识时效性问题。
9.1 RAG 流程
用户提问
├── 1. 向量化查询 → Embedding Model
├── 2. 向量检索 → 从知识库召回 Top-K 相关文档
├── 3. 构建 Prompt(上下文 + 问题)
└── 4. 大模型生成回答(基于检索到的上下文)
from langchain import OpenAIEmbeddings, FAISS, OpenAI
from langchain.chains import RetrievalQA
# 构建向量数据库
embeddings = OpenAIEmbeddings()
vectorstore = FAISS.from_documents(documents, embeddings)
# RAG 链
qa = RetrievalQA.from_chain_type(
llm=OpenAI(),
chain_type="stuff", # 或 map_reduce、refine
retriever=vectorstore.as_retriever(search_kwargs={"k": 3})
)
result = qa.run("公司的年假政策是什么?")
9.2 RAG 优化策略
| 优化点 | 策略 | 效果 |
|---|---|---|
| 文档切分 | 按语义段落切分,保持上下文完整 | 检索召回率提升 |
| 重排序(Rerank) | 先用向量召回 50 条,再用 Cross-Encoder 排序取 Top-5 | 精准度提升 |
| 查询改写 | 用大模型改写用户查询(Query Expansion) | 召回率提升 |
| 混合检索 | 向量检索 + 关键词 BM25 结合 | 覆盖不同语义粒度 |
| 引用溯源 | 回答中标注引用来源文档 | 可信度和可审计性 |
10. 模型量化与部署
10.1 量化技术对比
| 精度 | 每参数位数 | 7B 模型显存 | 速度 | 质量损失 |
|---|---|---|---|---|
| FP32 | 32 bit | 28 GB | 基准 | 无 |
| FP16 | 16 bit | 14 GB | 1.8× | 几乎无 |
| INT8 | 8 bit | 7 GB | 2.5× | < 1% |
| INT4 (GPTQ) | 4 bit | 3.5 GB | 3× | 2~5% |
| NF4 (QLoRA) | 4 bit | 3.5 GB | 3× | 2~3% |
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_use_double_quant=True, # 嵌套量化
)
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b",
quantization_config=bnb_config,
device_map="auto", # 自动分配到可用 GPU/CPU
)
10.2 推理加速框架
| 框架 | 特点 | 适用 |
|---|---|---|
| vLLM | PagedAttention 高效 KV Cache 管理 | 高吞吐服务化部署 |
| TensorRT-LLM | NVIDIA GPU 极致优化 | 生产环境 |
| llama.cpp | CPU 推理,量化到极致 | 边缘设备 |
| ONNX Runtime | 跨平台,图优化 | 通用加速 |
11. NLP 评估指标
| 任务 | 指标 | 说明 |
|---|---|---|
| 分类 | Accuracy / F1 / AUC | 关注不平衡时用 F1 |
| NER | span-level F1 | 实体级别匹配 |
| 机器翻译 | BLEU | n-gram 精确率 |
| 摘要 | ROUGE | n-gram 召回率 |
| 生成 | Perplexity | 困惑度,越低越好 |
| 语义检索 | Recall@K / MRR | Top-K 召回率 / 平均倒数排名 |
| RAG | 事实准确性 / 引用召回 | 人类评估为主 |
from sklearn.metrics import f1_score, classification_report
# 序列标注 F1
f1 = f1_score(y_true, y_pred, average='weighted')
# BLEU(机器翻译)
from nltk.translate.bleu_score import sentence_bleu
score = sentence_bleu([reference.split()], hypothesis.split())
总结
| 技术 | 适用场景 | 推荐方案 |
|---|---|---|
| 基础文本分类 | 情感分析、主题分类 | TF-IDF + SVM / BERT 微调 |
| 序列标注 | NER、分词、POS | BiLSTM-CRF / BERT-CRF |
| 语义相似度 | 问答匹配、去重 | Sentence-BERT |
| 文本生成 | 摘要、对话 | GPT/BART/T5 |
| 语义搜索 | 文档检索 | Dense Passage Retrieval |
NLP 工程实践要点:
- 中文处理:分词质量直接影响下游任务,Jieba/HanLP 或直接用字符级 BERT
- 预训练模型:优先使用领域相关的预训练权重(如医疗、法律)
- 数据增强:同义词替换、回译、EDA(Easy Data Augmentation)
- 长文本:BERT 的 512 限制可用滑窗、Longformer/BigBird 解决
- 推理优化:使用 ONNX Runtime / TensorRT 加速部署
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。