自然语言处理基础
大约 13 分钟约 3797 字
自然语言处理基础
简介
自然语言处理(NLP)是 AI 的重要分支,研究如何让计算机理解、生成和处理人类语言。从传统的规则方法到统计学习,再到深度学习和大语言模型,NLP 技术已广泛应用于搜索引擎、机器翻译、智能客服等场景。
NLP 的发展经历了几个重要阶段:规则驱动时代(基于正则和语法规则)、统计学习时代(基于 HMM、CRF 等概率模型)、深度学习时代(基于 CNN、RNN、Transformer)以及大模型时代(基于 GPT、BERT 等预训练模型)。每个阶段都在处理语言的核心挑战:歧义消解、上下文理解、知识推理和长距离依赖。
特点
文本预处理
基础预处理
import re
import jieba
# 英文文本处理
def preprocess_english(text):
text = text.lower() # 小写化
text = re.sub(r'[^a-zA-Z\s]', '', text) # 去除特殊字符
tokens = text.split() # 分词
return tokens
# 中文分词
def preprocess_chinese(text):
# jieba 分词
words = jieba.lcut(text)
# 去除停用词
stopwords = {'的', '了', '是', '在', '我', '有', '和', '就'}
words = [w for w in words if w.strip() and w not in stopwords]
return words
text = "自然语言处理是人工智能的重要分支"
print(jieba.lcut(text))
# ['自然语言', '处理', '是', '人工智能', '的', '重要', '分支']高级文本清洗
import re
import unicodedata
class TextCleaner:
"""生产级文本清洗器——处理各类脏数据"""
def __init__(self, language='zh'):
self.language = language
self.patterns = {
'url': re.compile(r'https?://\S+|www\.\S+'),
'email': re.compile(r'\S+@\S+\.\S+'),
'phone': re.compile(r'1[3-9]\d{9}'),
'html': re.compile(r'<[^>]+>'),
'emoji': re.compile(
"["
"\U0001F600-\U0001F64F"
"\U0001F300-\U0001F5FF"
"\U0001F680-\U0001F6FF"
"\U0001F1E0-\U0001F1FF"
"\U00002702-\U000027B0"
"\U000024C2-\U0001F251"
"]+",
flags=re.UNICODE
),
'whitespace': re.compile(r'\s+'),
'special_chars': re.compile(r'[^\u4e00-\u9fa5a-zA-Z0-9\s,.!?;:,。!?;:、()()【】\[\]{}]'),
}
def clean(self, text: str) -> str:
"""执行完整清洗流程"""
if not text or not isinstance(text, str):
return ""
# Unicode 标准化
text = unicodedata.normalize('NFKC', text)
# 去除 HTML 标签
text = self.patterns['html'].sub('', text)
# 去除 URL
text = self.patterns['url'].sub(' ', text)
# 去除邮箱
text = self.patterns['email'].sub(' ', text)
# 去除特殊字符
text = self.patterns['special_chars'].sub(' ', text)
# 合并空白字符
text = self.patterns['whitespace'].sub(' ', text).strip()
return text
cleaner = TextCleaner()
dirty_texts = [
"Visit https://example.com for more info!!!",
"联系电话:13812345678,邮箱test@example.com",
"这是一段<div>包含HTML</div>的文本 ",
]
for t in dirty_texts:
print(f"清洗前: {t}")
print(f"清洗后: {cleaner.clean(t)}\n")TF-IDF 特征提取
from sklearn.feature_extraction.text import TfidfVectorizer
documents = [
"机器学习是人工智能的核心技术",
"深度学习推动了计算机视觉的发展",
"自然语言处理让机器理解人类语言",
"机器学习和深度学习都属于人工智能"
]
# TF-IDF 向量化
vectorizer = TfidfVectorizer(max_features=100)
tfidf_matrix = vectorizer.fit_transform(documents)
print(f"词汇表大小: {len(vectorizer.vocabulary_)}")
print(f"矩阵形状: {tfidf_matrix.shape}")
# 查看关键词
feature_names = vectorizer.get_feature_names_out()
for i, doc in enumerate(documents):
scores = zip(feature_names, tfidf_matrix[i].toarray()[0])
top_words = sorted(scores, key=lambda x: x[1], reverse=True)[:3]
print(f"文档{i+1} 关键词: {top_words}")高级 TF-IDF 配置
from sklearn.feature_extraction.text import TfidfVectorizer
import numpy as np
# 中文 TF-IDF(需要先分词)
def chinese_tfidf(documents, stopwords=None):
"""中文 TF-IDF:先分词再向量化"""
tokenized_docs = []
for doc in documents:
words = jieba.lcut(doc)
if stopwords:
words = [w for w in words if w not in stopwords and w.strip()]
tokenized_docs.append(' '.join(words))
vectorizer = TfidfVectorizer(
max_features=5000,
min_df=2, # 至少在2个文档中出现
max_df=0.95, # 最多在95%的文档中出现(过滤停用词)
ngram_range=(1, 2), # 使用 unigram + bigram
sublinear_tf=True, # 使用对数 TF:1 + log(tf)
)
tfidf_matrix = vectorizer.fit_transform(tokenized_docs)
return vectorizer, tfidf_matrix
# 演示不同 ngram 范围的效果
docs = ["机器学习入门", "深度学习与机器学习", "自然语言处理基础"]
for ngram in [(1, 1), (1, 2), (2, 2)]:
v = TfidfVectorizer(ngram_range=ngram)
m = v.fit_transform(docs)
print(f"ngram_range={ngram}: 词汇表大小={len(v.vocabulary_)}")词向量与文本表示
Word2Vec 词向量
from gensim.models import Word2Vec
# 训练 Word2Vec
sentences = [
["自然", "语言", "处理", "是", "人工智能", "的", "分支"],
["机器", "学习", "推动", "了", "人工智能", "发展"],
["深度", "学习", "是", "机器", "学习", "的", "子领域"],
["自然", "语言", "处理", "技术", "越来越", "成熟"],
["计算机", "视觉", "也", "属于", "人工智能"],
]
model = Word2Vec(sentences, vector_size=100, window=5, min_count=1, workers=4, epochs=100)
# 查看词向量
print(f"'人工智能' 向量维度: {model.wv['人工智能'].shape}")
# 计算相似度
similar = model.wv.most_similar("人工智能", topn=5)
print(f"与 '人工智能' 最相似的词: {similar}")
# 词向量运算
result = model.wv.most_similar(positive=["机器", "学习"], negative=["深度"], topn=3)
print(f"机器学习 - 深度 ≈ {result}")文本嵌入(Text Embedding)
from sentence_transformers import SentenceTransformer
# 加载预训练模型
model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
sentences = [
"机器学习是AI的核心技术",
"人工智能依赖机器学习方法",
"今天天气真好适合出门"
]
embeddings = model.encode(sentences)
print(f"嵌入维度: {embeddings.shape}")
# 计算语义相似度
from sklearn.metrics.pairwise import cosine_similarity
similarities = cosine_similarity(embeddings)
for i in range(len(sentences)):
for j in range(i+1, len(sentences)):
print(f"相似度 {similarities[i][j]:.4f}: '{sentences[i]}' ↔ '{sentences[j]}'")BERT 文本表示
from transformers import BertTokenizer, BertModel
import torch
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
bert_model = BertModel.from_pretrained('bert-base-chinese')
text = "自然语言处理是人工智能的重要分支"
inputs = tokenizer(text, return_tensors='pt', padding=True, truncation=True, max_length=128)
with torch.no_grad():
outputs = bert_model(**inputs)
# CLS token 作为句子表示
cls_embedding = outputs.last_hidden_state[:, 0, :]
print(f"CLS 嵌入维度: {cls_embedding.shape}")
# 平均池化
attention_mask = inputs['attention_mask']
token_embeddings = outputs.last_hidden_state
input_mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()
sum_embeddings = torch.sum(token_embeddings * input_mask_expanded, dim=1)
sum_mask = torch.clamp(input_mask_expanded.sum(dim=1), min=1e-9)
mean_embedding = sum_embeddings / sum_mask
print(f"平均池化嵌入维度: {mean_embedding.shape}")情感分析
基于词典的情感分析
from collections import Counter
positive_words = {'好', '优秀', '棒', '喜欢', '推荐', '满意', '快速', '方便'}
negative_words = {'差', '糟糕', '慢', '难用', '退货', '失望', '问题', '故障'}
def sentiment_analyze(text):
words = set(jieba.lcut(text))
pos_count = len(words & positive_words)
neg_count = len(words & negative_words)
if pos_count > neg_count:
return 'positive', pos_count / (pos_count + neg_count + 1)
elif neg_count > pos_count:
return 'negative', neg_count / (pos_count + neg_count + 1)
else:
return 'neutral', 0.5
reviews = [
"这个产品非常好用,推荐购买",
"质量太差了,很失望",
"一般般吧,没什么特别"
]
for review in reviews:
sentiment, score = sentiment_analyze(review)
print(f"[{sentiment}] {score:.2f} - {review}")基于深度学习的情感分析
import torch
import torch.nn as nn
class TextCNN(nn.Module):
"""
TextCNN 情感分类模型
使用多种尺寸的卷积核捕捉不同 n-gram 特征
"""
def __init__(self, vocab_size, embed_dim=128, num_classes=3, num_filters=100):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim)
# 卷积核尺寸:2, 3, 4(捕捉 bigram, trigram, 4-gram)
self.convs = nn.ModuleList([
nn.Conv1d(embed_dim, num_filters, kernel_size=k)
for k in [2, 3, 4]
])
self.dropout = nn.Dropout(0.5)
self.fc = nn.Linear(num_filters * 3, num_classes)
def forward(self, x):
# x: (batch, seq_len)
embed = self.embedding(x) # (batch, seq_len, embed_dim)
embed = embed.permute(0, 2, 1) # (batch, embed_dim, seq_len)
conv_outputs = []
for conv in self.convs:
c = torch.relu(conv(embed)) # (batch, num_filters, seq_len - k + 1)
c = torch.max(c, dim=2)[0] # 最大池化 (batch, num_filters)
conv_outputs.append(c)
cat = torch.cat(conv_outputs, dim=1) # (batch, num_filters * 3)
cat = self.dropout(cat)
return self.fc(cat)
model = TextCNN(vocab_size=10000, num_classes=3)
dummy_input = torch.randint(0, 10000, (4, 50))
output = model(dummy_input)
print(f"TextCNN 输出: {output.shape}") # (4, 3)基于 BERT 的情感分析
from transformers import pipeline
# 使用预训练 BERT 模型
classifier = pipeline('sentiment-analysis',
model='uer/roberta-base-finetuned-jd-binary-chinese')
results = classifier([
"这个产品非常好用,强烈推荐",
"质量太差了,浪费钱",
"还不错,性价比可以"
])
for result in results:
print(f"标签: {result['label']}, 置信度: {result['score']:.4f}")BERT 微调情感分类
from transformers import BertForSequenceClassification, BertTokenizer, AdamW
from torch.utils.data import Dataset, DataLoader
class SentimentDataset(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):
encoding = self.tokenizer(
self.texts[idx],
truncation=True,
padding='max_length',
max_length=self.max_len,
return_tensors='pt'
)
return {
'input_ids': encoding['input_ids'].squeeze(),
'attention_mask': encoding['attention_mask'].squeeze(),
'label': torch.tensor(self.labels[idx])
}
# 训练函数
def train_bert_classifier(model, train_loader, val_loader, epochs=3, lr=2e-5):
"""BERT 微调训练流程"""
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = model.to(device)
optimizer = AdamW(model.parameters(), lr=lr)
criterion = nn.CrossEntropyLoss()
for epoch in range(epochs):
model.train()
total_loss = 0
for batch in train_loader:
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['label'].to(device)
optimizer.zero_grad()
outputs = model(input_ids, attention_mask=attention_mask)
loss = criterion(outputs.logits, labels)
loss.backward()
optimizer.step()
total_loss += loss.item()
# 验证
model.eval()
correct = 0
total = 0
with torch.no_grad():
for batch in val_loader:
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['label'].to(device)
outputs = model(input_ids, attention_mask=attention_mask)
pred = outputs.logits.argmax(dim=1)
correct += (pred == labels).sum().item()
total += labels.size(0)
val_acc = correct / total
print(f"Epoch {epoch+1}/{epochs}, Loss: {total_loss/len(train_loader):.4f}, Val Acc: {val_acc:.4f}")
print("BERT 微调训练流程已定义")文本分类
完整的分类流程
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import classification_report
# 新闻分类示例
categories = ['科技', '体育', '财经', '娱乐']
texts = [
"苹果发布新款芯片性能提升显著",
"国足世预赛取得关键胜利",
"A股市场今日大幅上涨",
"新电影票房突破十亿",
] * 25 # 模拟更多数据
labels = [0, 1, 2, 3] * 25
# 构建Pipeline
from sklearn.pipeline import Pipeline
text_clf = Pipeline([
('tfidf', TfidfVectorizer(max_features=500)),
('clf', MultinomialNB())
])
X_train, X_test, y_train, y_test = train_test_split(
texts, labels, test_size=0.2, random_state=42
)
text_clf.fit(X_train, y_train)
y_pred = text_clf.predict(X_test)
print(classification_report(y_test, y_pred, target_names=categories))深度学习文本分类
class BiLSTMClassifier(nn.Module):
"""BiLSTM 文本分类模型"""
def __init__(self, vocab_size, embed_dim=128, hidden_dim=256, num_classes=4):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
self.lstm = nn.LSTM(
embed_dim, hidden_dim,
num_layers=2, bidirectional=True,
batch_first=True, dropout=0.3
)
self.dropout = nn.Dropout(0.5)
self.fc = nn.Linear(hidden_dim * 2, num_classes)
def forward(self, x):
embed = self.embedding(x)
lstm_out, (hidden, _) = self.lstm(embed)
# 拼接最后时刻的前向和后向隐藏状态
hidden_cat = torch.cat([hidden[-2], hidden[-1]], dim=1)
out = self.dropout(hidden_cat)
return self.fc(out)
model = BiLSTMClassifier(vocab_size=10000, num_classes=4)
dummy = torch.randint(0, 10000, (8, 50))
out = model(dummy)
print(f"BiLSTM 分类输出: {out.shape}")文本相似度
语义相似度计算
from sklearn.metrics.pairwise import cosine_similarity
# 基于TF-IDF的相似度
def text_similarity(text1, text2, vectorizer):
vec1 = vectorizer.transform([text1])
vec2 = vectorizer.transform([text2])
return cosine_similarity(vec1, vec2)[0][0]
# 基于词向量的相似度(使用 sentence-transformers)
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
sentences = [
"机器学习是AI的核心技术",
"人工智能依赖机器学习方法",
"今天天气真好适合出门"
]
embeddings = model.encode(sentences)
similarities = cosine_similarity(embeddings)
for i in range(len(sentences)):
for j in range(i+1, len(sentences)):
print(f"相似度 {similarities[i][j]:.4f}: '{sentences[i]}' ↔ '{sentences[j]}'")批量相似度搜索
import numpy as np
def batch_similarity_search(query_embedding, corpus_embeddings, top_k=5):
"""高效批量相似度搜索"""
# 归一化后用点积等价于余弦相似度
query_norm = query_embedding / np.linalg.norm(query_embedding)
corpus_norm = corpus_embeddings / np.linalg.norm(corpus_embeddings, axis=1, keepdims=True)
# 计算所有相似度
similarities = np.dot(corpus_norm, query_norm)
# 返回 top-k
top_indices = np.argsort(similarities)[::-1][:top_k]
return [(idx, similarities[idx]) for idx in top_indices]
print("批量相似度搜索函数已定义")文本生成
import torch
import torch.nn as nn
class SimpleLanguageModel(nn.Module):
"""简单的语言模型——理解文本生成的基本原理"""
def __init__(self, vocab_size, embed_dim=128, hidden_dim=256):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim)
self.lstm = nn.LSTM(embed_dim, hidden_dim, batch_first=True)
self.fc = nn.Linear(hidden_dim, vocab_size)
def forward(self, x):
embed = self.embedding(x)
lstm_out, _ = self.lstm(embed)
logits = self.fc(lstm_out)
return logits
def generate(self, start_token, max_len=50, temperature=1.0):
"""自回归文本生成"""
self.eval()
current = start_token
generated = [current]
for _ in range(max_len):
x = torch.tensor([[current]])
with torch.no_grad():
logits = self.forward(x)[:, -1, :]
logits = logits / temperature
probs = nn.functional.softmax(logits, dim=-1)
current = torch.multinomial(probs, num_samples=1).item()
generated.append(current)
return generated
model = SimpleLanguageModel(vocab_size=1000)
print("简单语言模型已定义")NLP 任务全景
| 任务类型 | 输入 | 输出 | 典型方法 | 应用场景 |
|---|---|---|---|---|
| 文本分类 | 文本 | 类别标签 | BERT, TextCNN | 情感分析、垃圾邮件检测 |
| 序列标注 | 文本 | 每个token的标签 | BiLSTM-CRF | NER、词性标注 |
| 文本生成 | 提示/上下文 | 新文本 | GPT, T5 | 机器翻译、摘要生成 |
| 问答系统 | 问题+上下文 | 答案 | BERT, RAG | 智能客服、知识问答 |
| 文本匹配 | 两个文本 | 相似度分数 | Siamese BERT | 搜索排序、推荐 |
| 信息抽取 | 文本 | 结构化数据 | GPT, UIE | 关系抽取、事件抽取 |
优点
缺点
总结
NLP 核心流程:文本预处理(分词/清洗)→ 特征提取(TF-IDF/词向量)→ 模型训练(分类/序列标注)。传统方法用 TF-IDF + 朴素贝叶斯,效果好且速度快。深度学习方法用 BERT 等预训练模型,精度高但需要 GPU。文本相似度可用 sentence-transformers 计算语义向量。中文 NLP 首选 jieba 分词,专业场景可用 pkuseg 或 LTP。现代 NLP 正在向大模型驱动的范式转变,但基础概念仍是理解上层应用的关键。
关键知识点
- 先分清模型能力边界、数据边界和工程边界。
- 任何 AI 主题都不只看效果,还要看延迟、成本、可解释性和安全性。
- 评估方式和失败样例往往比"换哪个模型"更重要。
- 词向量的质量直接决定了下游任务的效果上限。
- 预训练-微调范式已成为 NLP 的主流开发方式。
项目落地视角
- 给数据来源、Prompt 模板、Embedding 版本、评估集和实验结果做版本管理。
- 上线前准备兜底策略,例如拒答、回退、人工审核或缓存降级。
- 观察错误类型时,区分数据问题、召回问题、提示词问题和模型问题。
常见误区
- 只关注 Demo 效果,不考虑线上稳定性和可复现性。
- 没有评估集就频繁调参,最后无法解释为什么变好或变差。
- 忽略权限、审计、隐私和模型输出的安全边界。
- 直接使用英文 NLP 工具处理中文,忽略中文特有的分词和编码问题。
进阶路线
- 继续补齐训练、推理、评估、MLOps 和治理链路。
- 把主题放回真实业务流程,思考谁提供数据、谁消费结果、谁负责兜底。
- 把 PoC 逐步升级到可观测、可回滚、可演进的生产方案。
- 深入学习 Transformer 架构、预训练策略和大规模语言模型。
适用场景
- 当你准备把《自然语言处理基础》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合企业知识问答、内容生成、分类抽取和智能助手等场景。
- 当需求同时关注效果、时延、成本和安全边界时,这类主题最有价值。
落地建议
- 先定义评估集、成功标准和失败样例,再开始调模型或调提示。
- 把数据来源、分块方式、Embedding 版本和 Prompt 模板纳入版本管理。
- 上线前准备兜底策略,例如拒答、回退、人工审核或检索降级。
排错清单
- 先判断问题出在数据、检索、Prompt、模型还是后处理。
- 检查上下文是否过长、分块是否过碎或召回是否偏题。
- 对错误回答做分类,区分幻觉、事实过时、指令误解和格式错误。
复盘问题
- 如果把《自然语言处理基础》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《自然语言处理基础》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《自然语言处理基础》最大的收益和代价分别是什么?
