Machine Learning: 自然语言处理NLP基础 — 文本预处理与情感分析指南
文字背后是情感——NLP让机器读懂评论中的愤怒、失望和惊喜。
1. 你将学到
- 文本预处理:分词(Jieba/NLTK)、去停用词、词干化/词形还原
- 文本表示:Bag-of-Words、TF-IDF、N-gram
- 词向量:Word2Vec(Gensim)原理与训练、词相似度计算
- 文本分类实战:用TF-IDF + LogisticRegression对商品评论做情感分析
- Bob的评论洞察:自动识别差评中的关键问题词,指导商品改进
2. 一个电商客服经理的真实故事
(1) 痛点:每天5千条评论无法人工分析
Bob的平台每天收到5 thousand条商品评论——好评、差评、建议混在一起。客服团队只能抽样看1%,漏掉了大量质量问题。上个月一个批次缺陷导致2 thousand条差评,但3周后才被发现,已造成500 thousand USD退货损失。海量文本数据是未被挖掘的金矿和地雷。
(2) NLP的解法
NLP可以自动分析评论情感、提取关键问题词、实时监控差评趋势。
PYTHON
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
pipe = Pipeline([
("tfidf", TfidfVectorizer(max_features=5000, ngram_range=(1, 2))),
("clf", LogisticRegression(max_iter=500)),
])
pipe.fit(X_train_reviews, y_train_sentiment)
print(f"Sentiment accuracy: {pipe.score(X_test, y_test):.3f}")
(3) 收益:差评问题3天内发现,退货率降低40%
Bob用NLP自动分析评论后,产品质量问题从发现到响应的时间从3周缩短到3天,退货率降低40%,每年节省约1 million USD。
3. 文本预处理
(1) 分词与清洗
▶ 示例:英文文本预处理流水线
PYTHON
import re
from nltk.tokenize import word_tokenize
from nltk.corpus import stopwords
from nltk.stem import PorterStemmer, WordNetLemmatizer
import nltk
nltk.download("punkt_tab", quiet=True)
nltk.download("stopwords", quiet=True)
nltk.download("wordnet", quiet=True)
def preprocess_text(text):
# Lowercase
text = text.lower()
# Remove special characters and numbers
text = re.sub(r"[^a-zA-Z\s]", "", text)
# Tokenize
tokens = word_tokenize(text)
# Remove stopwords
stop_words = set(stopwords.words("english"))
tokens = [t for t in tokens if t not in stop_words and len(t) > 2]
# Stemming
stemmer = PorterStemmer()
tokens_stemmed = [stemmer.stem(t) for t in tokens]
return " ".join(tokens_stemmed)
# Test
reviews = [
"This product is absolutely amazing! Best purchase ever.",
"Terrible quality, broke after 2 days. Very disappointed.",
"Decent product for the price, but shipping was slow.",
]
for review in reviews:
cleaned = preprocess_text(review)
print(f"Original: {review}")
print(f"Cleaned: {cleaned}\n")
输出:
TEXT
📖 仅展示
# 函数定义成功
▶ 示例:中文分词(Jieba)
PYTHON
# Chinese tokenization with jieba (conceptual - requires jieba install)
# import jieba
# text = "这个商品质量非常好,物流也很快"
# tokens = list(jieba.cut(text))
# print(tokens) # ['这个', '商品', '质量', '非常', '好', ',', '物流', '也', '很', '快']
# For English reviews in SalesPredict (international scenario)
from sklearn.feature_extraction.text import CountVectorizer
reviews = [
"Great laptop fast performance",
"Terrible screen quality",
"Good price fast delivery",
]
vectorizer = CountVectorizer()
X_bow = vectorizer.fit_transform(reviews)
print(f"Vocabulary: {vectorizer.vocabulary_}")
print(f"BoW matrix shape: {X_bow.shape}")
输出:
TEXT
📖 仅展示
# 执行成功
| 预处理步骤 | 英文 | 中文 |
|---|---|---|
| 分词 | NLTK/spaCy(空格分词) | Jieba/LAC(无空格) |
| 停用词 | NLTK stopwords | 哈工大/百度停用词表 |
| 词干化 | PorterStemmer | 不适用 |
| 词形还原 | WordNetLemmatizer | 不适用 |
4. 文本表示方法
文本从"字符串"变成"数字向量"需要经过一条表示转换流水线:
graph LR
TEXT[Raw Text] --> TOKEN[Tokenize<br/>split / jieba] --> REMOVE[Remove Stopwords] --> STEM[Stem/Lemmatize] --> BOW[Bag-of-Words<br/>Count Vector] --> TFIDF[TF-IDF<br/>Weighted Vector] --> EMBED[Word2Vec<br/>Dense Embedding]
style BOW fill:#fff3cd
style TFIDF fill:#d1ecf1
style EMBED fill:#d4edda
(1) Bag-of-Words与TF-IDF
▶ 示例:三种文本表示对比
PYTHON
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
import numpy as np
reviews = [
"great product fast delivery happy customer",
"terrible quality broke disappointed waste money",
"great quality decent price fast shipping",
"awful product terrible experience never buy again",
"excellent quality great value fast delivery",
]
labels = [1, 0, 1, 0, 1] # 1=positive, 0=negative
# Method 1: Bag-of-Words (count)
bow = CountVectorizer()
X_bow = bow.fit_transform(reviews)
print(f"BoW vocabulary size: {len(bow.vocabulary_)}")
# Method 2: TF-IDF
tfidf = TfidfVectorizer(max_features=1000)
X_tfidf = tfidf.fit_transform(reviews)
print(f"TF-IDF shape: {X_tfidf.shape}")
# Method 3: N-gram (capture phrases)
tfidf_ngram = TfidfVectorizer(ngram_range=(1, 2), max_features=1000)
X_ngram = tfidf_ngram.fit_transform(reviews)
print(f"TF-IDF with bigrams shape: {X_ngram.shape}")
print(f"Top bigrams: {[w for w in tfidf_ngram.get_feature_names_out() if ' ' in w][:10]}")
输出:
TEXT
📖 仅展示
# 执行成功
| 表示方法 | 信息量 | 维度 | 适用场景 |
|---|---|---|---|
| BoW(词频) | 低 | 高 | 简单分类 |
| TF-IDF | 中 | 高 | 文本分类标准 |
| N-gram | 中 | 很高 | 捕获短语 |
| Word2Vec | 高 | 低(100-300) | 语义任务 |
(2) TF-IDF直觉
- TF(Term Frequency):词在文档中出现的频率
- IDF(Inverse Document Frequency):词的稀有程度(越稀有权重越高)
- TF-IDF = TF × IDF:在本文档常见但在其他文档稀有的词权重最高
5. 词向量Word2Vec
(1) Word2Vec原理
Word2Vec通过预测上下文词(CBOW)或通过词预测上下文(Skip-gram)学习词向量,使语义相似的词向量接近。
▶ 示例:训练Word2Vec与词相似度
PYTHON
from gensim.models import Word2Vec
from gensim.test.utils import common_texts
import numpy as np
# Train Word2Vec on sample corpus
# In practice, use real review data corpus
sentences = [
["great", "product", "fast", "delivery", "happy"],
["terrible", "quality", "broke", "disappointed"],
["great", "quality", "decent", "price"],
["awful", "product", "terrible", "experience"],
["excellent", "quality", "great", "value"],
["fast", "shipping", "good", "price", "recommend"],
["poor", "quality", "slow", "delivery", "angry"],
["amazing", "product", "best", "purchase", "happy"],
]
model = Word2Vec(sentences, vector_size=50, window=3, min_count=1, workers=4, epochs=50)
# Word similarity
print("Similar to 'great':")
for word, sim in model.wv.most_similar("great", topn=5):
print(f" {word}: {sim:.3f}")
# Word vector
print(f"\nVector for 'quality': {model.wv['quality'][:5]}... (50-dim)")
# Analogy: great - good + terrible = ?
# result = model.wv.most_similar(positive=["terrible"], negative=["good"], topn=3)
输出:
TEXT
📖 仅展示
Similar to
| 参数 | 含义 | 推荐值 |
|---|---|---|
| vector_size | 向量维度 | 100-300 |
| window | 上下文窗口大小 | 5 |
| min_count | 最小词频 | 5 |
| sg | 0=CBOW, 1=Skip-gram | 1(小数据) |
▶ 示例:词向量可视化
PYTHON
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
words = ["great", "terrible", "happy", "angry", "fast", "slow",
"quality", "price", "product", "delivery"]
vectors = [model.wv[w] for w in words]
vectors_2d = PCA(n_components=2).fit_transform(vectors)
fig, ax = plt.subplots(figsize=(10, 8))
ax.scatter(vectors_2d[:, 0], vectors_2d[:, 1], s=100)
for i, word in enumerate(words):
ax.annotate(word, (vectors_2d[i, 0], vectors_2d[i, 1]), fontsize=12)
ax.set_title("Word2Vec Embedding Visualization")
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("word2vec_vis.png", dpi=150)
输出:
TEXT
📖 仅展示
# 执行成功
6. 商品评论情感分析实战
▶ 示例:完整情感分类流程
PYTHON
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.metrics import classification_report, confusion_matrix
import numpy as np
# Simulate product reviews
rng = np.random.default_rng(42)
positive_templates = [
"great product love it highly recommend",
"excellent quality fast delivery satisfied",
"amazing value best purchase ever happy",
"good product decent price works well",
"love this item perfect condition great",
]
negative_templates = [
"terrible quality broke after one day",
"awful product waste money very disappointed",
"poor quality slow delivery never again",
"worst purchase defective product angry",
"bad product cheap material not recommend",
]
# Generate 2000 reviews
reviews = []
labels = []
for _ in range(1000):
reviews.append(rng.choice(positive_templates))
labels.append(1)
reviews.append(rng.choice(negative_templates))
labels.append(0)
X_train, X_test, y_train, y_test = train_test_split(reviews, labels, test_size=0.2, random_state=42)
# TF-IDF + Logistic Regression pipeline
pipe = Pipeline([
("tfidf", TfidfVectorizer(max_features=5000, ngram_range=(1, 2), min_df=2)),
("clf", LogisticRegression(max_iter=500, C=1.0)),
])
pipe.fit(X_train, y_train)
y_pred = pipe.predict(X_test)
print(f"Accuracy: {pipe.score(X_test, y_test):.4f}")
print(f"\nClassification Report:\n{classification_report(y_test, y_pred, target_names=['Negative', 'Positive'])}")
# Extract most important words for each sentiment
feature_names = pipe.named_steps["tfidf"].get_feature_names_out()
coefs = pipe.named_steps["clf"].coef_[0]
top_positive = np.argsort(coefs)[-10:][::-1]
top_negative = np.argsort(coefs)[:10]
print("\nTop positive words:")
for idx in top_positive:
print(f" {feature_names[idx]:20s}: {coefs[idx]:.3f}")
print("\nTop negative words:")
for idx in top_negative:
print(f" {feature_names[idx]:20s}: {coefs[idx]:.3f}")
输出:
TEXT
📖 仅展示
\nTop positive words:
\nTop negative words:
📌 重点: 系数最大的词是正面情感的关键指示词,系数最小(最负)的词是负面情感的关键指示词。Bob可以用这些关键词监控评论趋势,快速发现产品质量问题。
❓ 常见问题
Q TF-IDF和Word2Vec该用哪个?
A 文本分类用TF-IDF(简单有效、可解释);语义任务(相似度/类比)用Word2Vec;深度学习用预训练Embedding(BERT等)。入门先用TF-IDF。
Q N-gram的n怎么选?
A 文本分类用(1,2)即unigram+bigram;情感分析bigram很重要(如"not good");更大的n(3+)通常不划算(维度爆炸且数据稀疏)。
Q 中文分词用什么工具?
A Jieba(最流行)、pkuseg(精度高)、LAC(Baidu,功能全)。电商评论建议用自定义词典加入品牌名和专业术语。
Q Word2Vec需要多少数据才能训练?
A 至少10 million词才能学到有意义的向量。数据少时用预训练向量(如GoogleNews-vectors)或直接用TF-IDF。
Q 情感分析的准确率多高算好?
A 简单二分类(正/负)用TF-IDF+逻辑回归通常80-85%;BERT等深度模型可达90-95%。但实际业务中噪声数据多,70-80%已可用。
Q 如何处理反讽和隐含否定?
A 简单模型(TF-IDF)难以处理。需要——1)加入更多N-gram特征;2)使用预训练语言模型(BERT);3)人工标注反讽样本训练专门模型。
📖 小节
- 文本预处理流水线:小写 → 去特殊字符 → 分词 → 去停用词 → 词干化/词形还原
- TF-IDF是文本分类的标准表示:词频×逆文档频率,稀有但重要的词权重最高
- N-gram捕获短语信息(如"not good"),但维度膨胀需控制max_features
- Word2Vec通过上下文预测学习词向量,语义相似的词向量接近
- 文本分类Pipeline:TfidfVectorizer + LogisticRegression,简单高效
- 模型系数可提取关键词——正面/负面指示词直接指导业务改进
📝 作业
- 基础题(难度⭐):用TfidfVectorizer对5条商品评论做向量化,打印词汇表和TF-IDF矩阵。提示:参考第4节示例。
- 进阶题(难度⭐⭐):构建TF-IDF + LogisticRegression情感分类Pipeline,对比unigram-only和(1,2)-gram的F1分数差异。提示:ngram_range=(1,1) vs (1,2)。
- 挑战题(难度⭐⭐⭐):实现Bob的评论洞察系统——训练情感分类器后,提取top 20负面关键词,按出现频次排序,输出"最需要改进的问题"排行榜。提示:coef_排序 + 特征名映射 + 频次统计。