Appearance
章节5:模块综合实战
学习目标
- 综合运用 jieba、Word2Vec、BERT 完成商品评论情感分析系统
- 运用 TF‑IDF、FastText、意图识别完成智能客服意图分类模型
- 掌握从数据预处理到模型训练再到部署的完整 NLP 项目流程
- 学会评估与对比不同方法的优劣
实战一:商品评论情感分析系统
项目概览
输入: "这个手机拍照效果非常好,电池续航也很给力!"
输出: {"情感": "正面", "置信度": 0.97}方案一:jieba + Word2Vec + 分类器(传统方法)
python
import jieba
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report
from gensim.models import Word2Vec
# ==================== 1. 准备数据 ====================
reviews = [
"这个手机拍照效果非常好,电池续航也很给力", # 正面
"质量太差了,用了三天就坏了", # 负面
"价格实惠,性价比很高,推荐购买", # 正面
"物流很慢,包装破损,不满意", # 负面
"屏幕显示效果惊艳,颜色很正", # 正面
"客服态度恶劣,完全不解决问题", # 负面
"手感很好,轻薄便携,续航也不错", # 正面
"经常死机,系统优化太差", # 负面
"外观时尚,拍照清晰,非常满意", # 正面
"收到就是坏的,退换货流程复杂", # 负面
]
labels = [1, 0, 1, 0, 1, 0, 1, 0, 1, 0]
# ==================== 2. 分词 ====================
def tokenize(text: str) -> list:
return jieba.lcut(text)
corpus_tokens = [tokenize(r) for r in reviews]
# ==================== 3. 训练 Word2Vec ====================
w2v_model = Word2Vec(
sentences=corpus_tokens,
vector_size=100,
window=5,
min_count=1,
sg=0, # CBOW
epochs=50
)
# ==================== 4. 文档向量化(均值池化) ====================
def doc_vector(tokens: list, model: Word2Vec) -> np.ndarray:
vectors = [model.wv[w] for w in tokens if w in model.wv]
if not vectors:
return np.zeros(model.vector_size)
return np.mean(vectors, axis=0)
X = np.array([doc_vector(tokens, w2v_model) for tokens in corpus_tokens])
y = np.array(labels)
# ==================== 5. 训练分类器 ====================
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42
)
clf = LogisticRegression(max_iter=1000)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
print("\n=== 方案一:Word2Vec + LR 结果 ===")
print(f"准确率: {accuracy_score(y_test, y_pred):.4f}")
# ==================== 6. 预测新评论 ====================
def predict_v1(text: str) -> dict:
tokens = tokenize(text)
vec = doc_vector(tokens, w2v_model).reshape(1, -1)
prob = clf.predict_proba(vec)[0]
label = "正面" if clf.predict(vec)[0] == 1 else "负面"
return {"text": text, "情感": label, "置信度": max(prob):.4f}
print(predict_v1("性价比超高的好产品"))方案二:BERT 微调(深度学习方法)
python
from transformers import (BertTokenizer, BertForSequenceClassification,
Trainer, TrainingArguments)
from datasets import Dataset
import torch
# ==================== 1. 准备数据 ====================
# 扩充数据量(实际项目应使用 > 10,000 条)
all_reviews = reviews * 5 # 简单增强
all_labels = labels * 5
# ==================== 2. 加载模型 ====================
model_name = "bert-base-chinese"
tokenizer = BertTokenizer.from_pretrained(model_name)
model = BertForSequenceClassification.from_pretrained(model_name, num_labels=2)
# ==================== 3. 编码 ====================
def encode(batch):
return tokenizer(batch["text"], padding="max_length",
truncation=True, max_length=128)
dataset = Dataset.from_dict({
"text": all_reviews,
"label": all_labels
}).map(encode, batched=True)
# ==================== 4. 微调 ====================
training_args = TrainingArguments(
output_dir="./bert_sentiment_model",
per_device_train_batch_size=8,
num_train_epochs=3,
learning_rate=2e-5,
save_strategy="no",
logging_steps=10,
report_to="none",
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=dataset,
)
trainer.train()
# ==================== 5. 保存与推理 ====================
model.save_pretrained("./bert_sentiment_model")
tokenizer.save_pretrained("./bert_sentiment_model")
def predict_v2(text: str) -> dict:
inputs = tokenizer(text, return_tensors="pt",
truncation=True, max_length=128)
with torch.no_grad():
outputs = model(**inputs)
probs = torch.nn.functional.softmax(outputs.logits, dim=-1)
label = "正面" if probs[0][1] > 0.5 else "负面"
return {"text": text, "情感": label, "置信度": probs[0][1].item():.4f}
print(predict_v2("这个产品太令人失望了"))两种方案对比
| 维度 | Word2Vec + LR | BERT 微调 |
|---|---|---|
| 训练速度 | ⚡ 秒级 | 🐢 分钟~小时 |
| 推理速度 | ⚡ 毫秒 | ⚡ 毫秒 |
| 数据需求 | 百级可用 | 千级以上效果明显 |
| 语义理解 | 一般(静态向量) | 优秀(上下文动态) |
| 部署体积 | 小(约 50MB) | 大(约 400MB) |
| 可解释性 | 较高 | 较低 |
| 最佳场景 | 快速原型、资源受限 | 精度优先、数据充足 |
实战二:智能客服意图分类模型
项目概览
输入: "我想查询一下我的订单什么时候发货"
输出: {"意图": "查询订单", "置信度": 0.95}方案一:TF‑IDF + FastText + 集成分类
python
import jieba
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, accuracy_score
from gensim.models import FastText
# ==================== 1. 准备数据 ====================
queries = [
# 查询订单
"我的订单到哪了", "查一下快递", "什么时候发货",
"物流信息怎么查看", "订单号 10086 的物流状态",
# 退换货
"我要退货", "怎么申请退款", "商品坏了可以换吗",
"退货运费谁承担", "退款多久到账",
# 产品咨询
"这个手机支持5G吗", "电池容量多大", "有没有红色款",
"保修期多久", "支不支持无线充电",
# 账户问题
"密码忘记了", "怎么修改手机号", "账号被盗了",
"怎么注销账号", "登录不了",
]
intents = (
["查询订单"] * 5 +
["退换货"] * 5 +
["产品咨询"] * 5 +
["账户问题"] * 5
)
# ==================== 2. 特征工程 ====================
# 方法 A:TF-IDF 特征
tfidf_vec = TfidfVectorizer(tokenizer=jieba.lcut, max_features=500)
X_tfidf = tfidf_vec.fit_transform(queries).toarray()
# 方法 B:FastText 文档向量
corpus_tokens = [jieba.lcut(q) for q in queries]
ft_model = FastText(
sentences=corpus_tokens,
vector_size=50,
window=5,
min_count=1,
min_n=2,
max_n=4,
epochs=100
)
def ft_doc_vector(tokens: list) -> np.ndarray:
vectors = [ft_model.wv[w] for w in tokens if w in ft_model.wv]
if not vectors:
return np.zeros(ft_model.vector_size)
return np.mean(vectors, axis=0)
X_ft = np.array([ft_doc_vector(tokens) for tokens in corpus_tokens])
# 方法 C:特征融合
X_combined = np.concatenate([X_tfidf, X_ft], axis=1)
print(f"融合特征维度: {X_combined.shape[1]}")
# ==================== 3. 标签编码 ====================
le = LabelEncoder()
y = le.fit_transform(intents)
# ==================== 4. 训练 ====================
X_train, X_test, y_train, y_test = train_test_split(
X_combined, y, test_size=0.3, random_state=42
)
clf = RandomForestClassifier(n_estimators=100, random_state=42)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
print("\n=== 客服意图分类结果 ===")
print(f"准确率: {accuracy_score(y_test, y_pred):.4f}")
print("\n分类报告:")
print(classification_report(y_test, y_pred, target_names=le.classes_))
# ==================== 5. 在线预测 ====================
def classify_intent(text: str) -> dict:
tokens = jieba.lcut(text)
tfidf_feat = tfidf_vec.transform([text]).toarray()
ft_feat = ft_doc_vector(tokens).reshape(1, -1)
feat = np.concatenate([tfidf_feat, ft_feat], axis=1)
proba = clf.predict_proba(feat)[0]
pred_idx = clf.predict(feat)[0]
intent = le.inverse_transform([pred_idx])[0]
return {"text": text, "意图": intent, "置信度": max(proba):.4f}
# 测试
tests = [
"查一下我的快递到哪了",
"手机屏幕碎了能换吗",
"这个笔记本内存多大",
"我忘记登录密码了"
]
for t in tests:
print(classify_intent(t))方案二:意图识别 + 槽位填充的 NLU 管道
python
import re
from dataclasses import dataclass, asdict
from typing import Optional
@dataclass
class IntentResult:
intent: str
confidence: float
slots: dict
raw_text: str
class IntentNLU:
"""基于规则的意图识别 + 槽位填充(可作为模型冷启动替代方案)"""
def __init__(self):
# 意图 → 触发词列表
self.intent_patterns = {
"查询订单": ["订单", "快递", "物流", "发货", "到哪", "配送"],
"退换货": ["退货", "退款", "退换", "换货", "坏了", "退货"],
"产品咨询": ["参数", "支持", "多大", "颜色", "功能", "配置", "内存"],
"账户问题": ["密码", "账号", "登录", "手机号", "注销", "绑定"],
}
def extract_intent(self, text: str) -> tuple:
"""统计匹配的触发词数量,返回最高分意图"""
scores = {}
for intent, keywords in self.intent_patterns.items():
score = sum(1 for kw in keywords if kw in text)
if score > 0:
scores[intent] = score
if not scores:
return "闲聊", 0.0
best_intent = max(scores, key=scores.get)
total = sum(scores.values())
confidence = scores[best_intent] / total
return best_intent, confidence
def extract_slots(self, text: str, intent: str) -> dict:
"""根据意图类型提取对应槽位"""
slots = {}
# 通用槽位:订单号
order_match = re.search(r"(\d{5,})", text)
if order_match:
slots["订单号"] = order_match.group(1)
# 产品咨询:提取产品名
product_match = re.search(r"(手机|电脑|笔记本|平板|耳机|充电器)", text)
if product_match:
slots["产品"] = product_match.group(1)
# 账户问题:提取操作类型
if intent == "账户问题":
if "密码" in text:
slots["操作"] = "密码管理"
elif "手机号" in text:
slots["操作"] = "修改手机号"
elif "注销" in text:
slots["操作"] = "注销账号"
return slots
def __call__(self, text: str) -> IntentResult:
intent, confidence = self.extract_intent(text)
slots = self.extract_slots(text, intent)
return IntentResult(
intent=intent,
confidence=confidence,
slots=slots,
raw_text=text
)
# 测试
nlu = IntentNLU()
test_cases = [
"帮我查一下订单 12345 的物流信息",
"我买的手机想退货",
"这个笔记本内存多大",
"我忘记密码了怎么办"
]
for case in test_cases:
result = nlu(case)
print(asdict(result))部署为一个简单的 API
python
from flask import Flask, request, jsonify
app = Flask(__name__)
nlu = IntentNLU() # 或使用训练好的模型
@app.route("/api/nlu", methods=["POST"])
def nlu_endpoint():
data = request.get_json()
text = data.get("text", "")
if not text:
return jsonify({"error": "text is required"}), 400
result = nlu(text)
return jsonify(asdict(result))
@app.route("/api/sentiment", methods=["POST"])
def sentiment_endpoint():
data = request.get_json()
text = data.get("text", "")
if not text:
return jsonify({"error": "text is required"}), 400
# 加载保存的 BERT 模型
# result = predict_v2(text)
return jsonify({"text": text, "message": "模型加载后返回"})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=False)综合对比与选型指南
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 快速原型、数据量小 | jieba + TF‑IDF + LR | 无需 GPU,几分钟出结果 |
| 中等规模、精度优先 | Word2Vec/FastText + 深度分类器 | 语义泛化能力优于 TF‑IDF |
| 大规模、最高精度 | BERT / RoBERTa 微调 | 上下文动态表示,SOTA |
| 冷启动、无标注数据 | 规则系统(正则 + 触发词) | 零标注成本,快速上线 |
| 生产系统 | 规则 + 模型结合 | 规则兜底,模型提精度 |
项目总结
通过本模块的两个综合实战,我们完成了:
商品评论情感分析系统
- 使用 jieba + Word2Vec + LogisticRegression 快速实现基线
- 使用 BERT 微调 实现高精度情感分类
- 对比了传统方法与深度学习方法在不同场景下的优劣势
智能客服意图分类模型
- 使用 TF‑IDF + FastText 特征融合 + RandomForest 实现多分类
- 实现基于规则的 NLU 管道作为冷启动方案
- 部署为可调用的 RESTful API
课后练习
- 在电商评论数据集()上分别用 Word2Vec + LR 和 BERT 微调训练情感模型,对比它们在测试集上的准确率和 F1 值。
- 为智能客服系统增加"投诉建议"意图类别,收集10条对应语料,重新训练并评估。
- 将 BERT 情感分析模型导出为 ONNX 格式,对比导出前后的推理速度。
- 设计一个"规则 + 模型"的混合 NLU 系统:当模型置信度 < 0.6 时,使用规则结果;否则使用模型结果。
- 挑战题: 将本模块两个实战合并为一个完整的"智能客服 + 评论分析"系统,前端提供聊天界面,用户输入评论时自动做情感分析,输入问题时做意图分类。可使用 Gradio 或 Streamlit 搭建 UI。