自然语言处理 (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. NLP 评估指标
| 任务 | 指标 | 说明 |
|---|---|---|
| 分类 | Accuracy / F1 / AUC | 关注不平衡时用 F1 |
| NER | span-level F1 | 实体级别匹配 |
| 机器翻译 | BLEU | n-gram 精确率 |
| 摘要 | ROUGE | n-gram 召回率 |
| 生成 | Perplexity | 困惑度,越低越好 |
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 加速部署
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。