07. 自然语言处理 NLP

NLP 核心技术:文本预处理、词向量 Word2Vec/GloVe、BERT 微调、文本分类与命名实体识别实战

自然语言处理 (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-BaseEncoder110M12 层,768 维
BERT-LargeEncoder340M24 层,1024 维
RoBERTaEncoder125M更优训练策略
ALBERTEncoder12M参数共享,轻量
ELECTRADiscriminator-判别式预训练
DeBERTaEncoder-增强注意力

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 模型显存速度质量损失
FP3232 bit28 GB基准无
FP1616 bit14 GB1.8×几乎无
INT88 bit7 GB2.5×< 1%
INT4 (GPTQ)4 bit3.5 GB3×2~5%
NF4 (QLoRA)4 bit3.5 GB3×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 推理加速框架

框架特点适用
vLLMPagedAttention 高效 KV Cache 管理高吞吐服务化部署
TensorRT-LLMNVIDIA GPU 极致优化生产环境
llama.cppCPU 推理,量化到极致边缘设备
ONNX Runtime跨平台,图优化通用加速

11. NLP 评估指标

任务指标说明
分类Accuracy / F1 / AUC关注不平衡时用 F1
NERspan-level F1实体级别匹配
机器翻译BLEUn-gram 精确率
摘要ROUGEn-gram 召回率
生成Perplexity困惑度,越低越好
语义检索Recall@K / MRRTop-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、分词、POSBiLSTM-CRF / BERT-CRF
语义相似度问答匹配、去重Sentence-BERT
文本生成摘要、对话GPT/BART/T5
语义搜索文档检索Dense Passage Retrieval

NLP 工程实践要点:

  1. 中文处理:分词质量直接影响下游任务,Jieba/HanLP 或直接用字符级 BERT
  2. 预训练模型:优先使用领域相关的预训练权重(如医疗、法律)
  3. 数据增强:同义词替换、回译、EDA(Easy Data Augmentation)
  4. 长文本:BERT 的 512 限制可用滑窗、Longformer/BigBird 解决
  5. 推理优化:使用 ONNX Runtime / TensorRT 加速部署

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「ai」更多文章

  1. 排序学习与搜索召回排序系统
  2. 数据版本控制与血缘:DVC 与 LakeFS
  3. 模型可解释性:SHAP、LIME 与注意力归因