今天的课程主要学习了文本数据的处理方法。和身高、年龄、体重这类数据不同,评论、新闻、小说等文本本身不是数字,计算机不能直接理解一句话表达的意思。
例如:“这款手机外观很好看,运行速度也很快。”
在人类看来,这句话大概率是一条好评。但对机器学习模型来说,它只是一串字符。想让模型判断好评还是差评,首先要把文字转换成数字,这个过程就是文本向量化。
本节课程主要围绕以下内容展开:
- 文本数据清洗;
- 中文分词和停用词处理;
- 词袋模型和 TF-IDF;
- 使用
CountVectorizer转换词向量; - 使用朴素贝叶斯完成评论分类;
- 训练集、测试集和数据泄漏;
- 通过数据清洗处理结构化数据。
一、为什么要进行文本向量化
机器学习模型的输入通常是一个二维矩阵:
行:一条样本
列:一个特征
例如,预测房价时,特征可能是面积、楼层和房间数量:
| 面积 | 楼层 | 房间数 |
|---|---|---|
| 80 | 10 | 2 |
| 120 | 18 | 3 |
但是评论数据通常是这样的:
| 评论内容 | 标签 |
|---|---|
| 手机外观漂亮,运行速度很快 | 好评 |
| 电池续航太差,不推荐购买 | 差评 |
模型无法直接使用“外观漂亮”这种文字,因此需要先建立一个词语表,再统计每条评论中出现了哪些词。
假设词语表为:
外观、漂亮、运行、速度、电池、续航、差
第一条评论可以转换成:
[1, 1, 1, 1, 0, 0, 0]
第二条评论可以转换成:
[0, 0, 0, 0, 1, 1, 1]
这样,文本就变成了模型可以处理的数字矩阵。
二、数据清洗是文本分类的第一步
原始评论数据通常并不干净,里面可能包含:
空评论;
多余空格;
换行符;
HTML 标签;
表情符号;
特殊字符;
重复评论;
无意义的广告内容;
缺失标签。
如果不进行清洗,模型可能会把这些无关内容当成特征,影响最终分类效果。
首先读取评论数据:
import pandas as pd
data = pd.read_csv("苹果手机评论.csv")
print(data.head())
print(data.info())
print(data.isnull().sum())
假设数据中有两列:
content:评论内容
label:评论标签
可以删除评论内容或标签为空的数据:
data = data.dropna(subset=["content", "label"])
删除重复评论:
data = data.drop_duplicates(subset=["content"])
有些数据中,好评和差评可能不是数字,而是中文:
好评、差评
机器学习模型通常更适合使用数字标签,因此可以进行转换:
label_map = {
"差评": 0,
"好评": 1
}
data["label"] = data["label"].map(label_map)
如果标签本来就是 0 和 1,则不需要重复转换。
三、中文文本清洗与分词
英文单词之间通常使用空格分隔,例如:
this phone is good
但中文没有天然的空格:
这款手机运行速度很快
因此,中文文本需要先进行分词。Python 中常用的中文分词库是 jieba。
import jieba
text = "这款手机运行速度很快"
words = jieba.lcut(text)
print(words)
输出类似:
["这款", "手机", "运行", "速度", "很快"]
为了让分词结果更适合模型,还需要删除标点符号和停用词。
停用词是指“的、了、是、我、也”等出现频率很高,但区分能力较弱的词。可以准备一个停用词文件:
with open("StopwordsCN.txt", "r", encoding="utf-8") as f:
stopwords = set(line.strip() for line in f)
编写一个简单的文本清洗函数:
import re
import jieba
def clean_text(text):
text = str(text)
# 删除网址
text = re.sub(r"http\S+|www\S+", "", text)
# 只保留中文、英文、数字
text = re.sub(r"[^\u4e00-\u9fa5a-zA-Z0-9]", " ", text)
words = jieba.lcut(text)
result = []
for word in words:
word = word.strip()
if not word:
continue
if word in stopwords:
continue
result.append(word)
return result
处理一条评论:
print(clean_text("这款手机真的很好用!!!物流也很快"))
可能得到:
["这款", "手机", "真的", "好用", "物流", "很快"]
如果使用 CountVectorizer,通常需要把分词结果重新连接成空格分隔的字符串:
data["words"] = data["content"].apply(clean_text)
data["text_cut"] = data["words"].apply(lambda x: " ".join(x))
例如:
这款 手机 真的 好用 物流 很快
这样,向量化工具就可以正常识别每个词。
四、词袋模型:统计词语出现次数
词袋模型的思想很简单:不考虑词语在句子中的顺序,只统计词语出现了多少次。
例如有三句话:
texts = [
"手机 外观 漂亮",
"手机 运行 速度 快",
"电池 续航 时间 长"
]
使用 CountVectorizer:
from sklearn.feature_extraction.text import CountVectorizer
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(texts)
print(vectorizer.get_feature_names_out())
print(X.toarray())
输出的词表可能是:
["外观", "手机", "漂亮", "电池", "时间", "续航", "运行", "速度", "长", "快"]
每一行就是一条文本的词频向量。
1. max_features
课程代码中使用了:
vectorizer = CountVectorizer(
max_features=4000,
lowercase=False,
ngram_range=(1, 3)
)
max_features=4000 表示最多保留 4000 个特征词。词语数量太多时,会造成矩阵维度过高,训练速度变慢,因此可以限制词表大小。
2. lowercase
lowercase=False 表示不强制把英文转换为小写。中文数据中这个参数影响不大,但如果评论中包含型号、品牌或英文缩写,就可以根据实际需求决定是否统一大小写。
3. ngram_range
ngram_range=(1, 3)
表示同时使用:
一个词组成的一元词组;
两个词组成的二元词组;
三个词组成的三元词组。
例如:
手机 运行 速度 很快
除了“手机”“运行”等单个词,还可以生成:
手机 运行
运行 速度
速度 很快
手机 运行 速度
这样可以保留部分词语之间的组合关系。相比只使用单个词,分类效果通常会更好,但特征数量也会增加。
五、TF-IDF:降低常见词的影响
词频只能说明一个词在当前文本中出现了多少次,却不能说明这个词是否有区分度。
例如“手机”可能在所有评论中都出现,它虽然频率很高,但不能很好地区分好评和差评。“续航差”“屏幕清晰”等词语可能更有分类价值。
TF-IDF 由两部分组成:
1. TF:词频
表示某个词在当前文本中出现的频率。
TF = 某词在当前文本中出现的次数 / 当前文本总词数
2. IDF:逆文档频率
如果一个词在很多文档中都出现,那么它的区分能力较弱,IDF 就会较小。
IDF = log(文档总数 / 包含该词的文档数)
TF-IDF 就是:
TF-IDF = TF × IDF
使用 TfidfVectorizer:
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf = TfidfVectorizer(
max_features=4000,
ngram_range=(1, 2)
)
X = tfidf.fit_transform(data["text_cut"])
在课程中还通过《红楼梦》文本检索理解 TF-IDF。搜索“ 大观园 ”时,程序会计算查询词与各个章节之间的权重,权重越高,说明该章节与搜索内容越相关。
TF-IDF 常用于:
文本搜索;
关键词提取;
文档相似度计算;
新闻分类;
评论分类。
六、用多项式朴素贝叶斯识别好评和差评
文本分类中,朴素贝叶斯是一个非常经典的入门模型,尤其适合处理词频或 TF-IDF 特征。
它的核心思想是:根据一条评论中出现的词语,计算它属于不同类别的概率,最后选择概率更高的类别。
例如,评论中出现“漂亮、流畅、满意”等词,可能更接近好评;出现“卡顿、发热、失望”等词,可能更接近差评。
完整代码如下:
import pandas as pd
import jieba
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import classification_report
data = pd.read_csv("苹果手机评论.csv")
data = data.dropna(subset=["content", "label"])
data = data.drop_duplicates(subset=["content"])
def cut_text(text):
words = jieba.lcut(str(text))
return " ".join(words)
data["text_cut"] = data["content"].apply(cut_text)
x = data["text_cut"]
y = data["label"]
x_train, x_test, y_train, y_test = train_test_split(
x,
y,
test_size=0.25,
random_state=0,
stratify=y
)
vectorizer = CountVectorizer(
max_features=4000,
lowercase=False,
ngram_range=(1, 3)
)
x_train_vec = vectorizer.fit_transform(x_train)
x_test_vec = vectorizer.transform(x_test)
classifier = MultinomialNB(alpha=0.1)
classifier.fit(x_train_vec, y_train)
predicted = classifier.predict(x_test_vec)
print(classification_report(y_test, predicted))
这里最重要的是:
vectorizer.fit_transform(x_train)
vectorizer.transform(x_test)
词表必须只使用训练集建立。测试集只能使用训练集已经建立好的词表进行转换。
如果直接对全部数据执行:
vectorizer.fit_transform(x)
再划分训练集和测试集,测试集中的词语信息就提前参与了词表建立,这属于数据泄漏,会导致评估结果过于乐观。
七、如何理解分类报告
运行 classification_report 后,通常会看到:
precision recall f1-score support
0 1.00 0.98 0.99 54068
1 0.02 0.60 0.03 25
accuracy 0.98 54093
很多初学者看到准确率 0.98,会认为模型效果非常好。但从这份报告可以看出,类别 1 只有 25 条样本,F1 值只有 0.03,说明模型几乎不能稳定识别这个类别。
这就是类别不平衡问题。
假设测试集中有 1000 条评论,其中 990 条是好评,10 条是差评。模型即使把所有评论都预测为好评,也能获得 99% 的准确率,但它完全没有识别差评。
因此,评价评论分类模型时不能只看准确率,还要关注:
precision:预测为某类的样本中,有多少是真正的该类;
recall:真实属于某类的样本中,有多少被找出来;
f1-score:精确率和召回率的综合指标;
support:每个类别实际包含的样本数量。
当好评和差评数量差异很大时,可以考虑:
对多数类别进行下采样;
对少数类别进行过采样;
增加少数类别数据;
使用分层抽样;
重点观察少数类别的召回率和 F1 值。
八、结构化数据的数据清洗
课程后半部分还演示了矿物数据的清洗。虽然矿物数据不是文本,但处理机器学习数据时,基本流程是相通的。
首先读取 Excel 文件:
import pandas as pd
data = pd.read_excel("矿物数据.xls")
print(data.head())
print(data.shape)
删除不需要的类别,例如课程中删除了类别 E:
data = data[data["矿物类型"] != "E"]
查看每一列的缺失值:
null_num = data.isnull().sum()
print(null_num)
提取特征和标签:
X = data.drop(["序号", "矿物类型"], axis=1)
y = data["矿物类型"]
将分类标签转换成数字:
label_dict = {
"A": 0,
"B": 1,
"C": 2,
"D": 3
}
y = y.map(label_dict)
对于特征中的空值,可以使用中位数填充:
from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy="median")
X = imputer.fit_transform(X)
常见的缺失值处理方式还有:
使用均值填充;
使用中位数填充;
使用众数填充;
使用前一个有效值填充;
使用后一个有效值填充;
使用插值方法填充;
删除缺失比例过高的列或行。
选择哪种方法,需要结合数据分布。比如数据中存在极端值时,中位数通常比均值更稳定。
九、今天课程的完整流程
无论是苹果手机评论,还是矿物分类数据,都可以按照下面的流程处理:
读取数据
↓
检查缺失值和异常值
↓
清洗数据
↓
提取特征和标签
↓
划分训练集和测试集
↓
只用训练集建立特征转换器
↓
转换为数字矩阵
↓
训练机器学习模型
↓
使用测试集评估
对于文本数据,主要增加了中文分词和停用词处理:
原始评论
↓
去除网址、符号和空白
↓
中文分词
↓
删除停用词
↓
CountVectorizer 或 TF-IDF
↓
朴素贝叶斯分类
十、总结
今天学习的重点并不是某一个模型,而是理解“文本如何进入机器学习模型”。
一条评论要经过:
文字 → 清洗 → 分词 → 词表 → 数字向量 → 分类模型
CountVectorizer 通过统计词语出现次数建立词频向量,TfidfVectorizer 则会进一步降低常见词的权重。ngram_range=(1, 3) 可以加入二元词组和三元词组,让模型保留更多局部语义。
多项式朴素贝叶斯实现简单、训练速度快,是文本分类的优秀入门模型。但在实际项目中,不能只看准确率,还要检查类别是否平衡,并重点观察少数类别的精确率、召回率和 F1 值。
另外,数据清洗会直接影响模型效果。无论处理评论、新闻,还是矿物检测数据,都应该先检查空值、重复值、异常类别和标签格式,再进入模型训练阶段。只有数据质量可靠,后面的算法结果才有意义。

1050

被折叠的 条评论
为什么被折叠?



