情感分析实战:从 IMDb 电影评论到词袋模型与逻辑回归(Python Machine Learning 第 8 章全解)
机器学习教程【免费下载链接】python-machine-learning-bookThe Python Machine Learning (1st edition) book code repository and info resource项目地址https://gitcode.com/gh_mirrors/py/python-machine-learning-book点击查看免费下载本指南以《Python Machine Learning第 1 版》第 8 章“Applying Machine Learning to Sentiment Analysis”为核心完整梳理一条从 IMDb 原始影评数据到可部署情感分类器的技术链路先通过aclImdb数据集构建movie_data.csv再依次掌握词袋模型bag-of-words、TF-IDF 特征加权、文本清洗与分词最后分别用逻辑回归Pipeline GridSearchCV与在线学习HashingVectorizer SGDClassifier完成文档级情感分类。读完本文你将能够复现第 8 章全部实验并理解这些代码在仓库 ch08.ipynb 与 ch08.py 中的完整实现脉络为第 9 章将模型嵌入 Flask Web 应用打下基础。图为movie_data.csv的样例第一列review为影评原文第二列sentiment为情感标签0负面1正面这正是后续全部特征工程与模型训练的数据起点。一、获取 IMDb 电影评论数据集1.1 数据集来源与解压第 8 章使用的“Large Movie Review Dataset v1.0”来自斯坦福大学 AI 实验室http://ai.stanford.edu/~amaas/data/sentiment/仓库没有直接存放原始aclImdb压缩包而是提供了处理好的 CSV 版本数据说明说明该数据集在第 8、9 章使用CSV 共两列——review文本与sentiment0负面1正面前 25,000 行为训练样本、后 25,000 行为测试样本数据文件movie_data.csv.zip。若希望从原始aclImdb_v1.tar.gz自行构建按第 8 章 notebook 的步骤操作Linux / macOS在终端进入下载目录后执行tar -zxf aclImdb_v1.tar.gzWindows使用 7-Zip 等解压工具解包。1.2 编码兼容性注意事项notebook 作者特别给出编码提示文本读取时若 Python 默认编码不是utf-8可通过sys.getdefaultencoding()检查必要时先执行export PYTHONIOENCODINGutf8或显式在所有文件读写处指定encodingutf-8。仓库脚本 ch08.py 已在open(...)中显式传入encodingutf-8即采用了推荐的写法。1.3 组装 DataFrame读取原始aclImdb目录train/pos、train/neg、test/pos、test/neg四个子目录共 50,000 个 txt 文件的代码如下import pyprind import pandas as pd import os basepath ./aclImdb labels {pos: 1, neg: 0} pbar pyprind.ProgBar(50000) df pd.DataFrame() for s in (test, train): for l in (pos, neg): path os.path.join(basepath, s, l) for file in os.listdir(path): with open(os.path.join(path, file), r, encodingutf-8) as infile: txt infile.read() df df.append([[txt, labels[l]]], ignore_indexTrue) pbar.update() df.columns [review, sentiment]要点说明labels字典完成目录名到数值标签的映射pos→1neg→0pyprind.ProgBar(50000)用于显示 5 万条影评的读取进度需pip install pyprind随后用np.random.seed(0); df df.reindex(np.random.permutation(df.index))打乱顺序保证训练/测试样本的类别分布随机化最后df.to_csv(./movie_data.csv, indexFalse)持久化为 CSV。若跳过此步也可直接使用仓库提供的 movie_data.csv.zip。二、词袋模型把文本变成数值特征向量2.1 三句话示例词袋模型的核心思想忽略词序与语法仅统计每个词在文档中出现的次数将文档表示为“词→计数”的向量。第 8 章用如下三句话做最小演示import numpy as np from sklearn.feature_extraction.text import CountVectorizer count CountVectorizer() docs np.array([ The sun is shining, The weather is sweet, The sun is shining, the weather is sweet, and one and one is two]) bag count.fit_transform(docs)调用fit_transform即完成两件事fit构建词汇表vocabularytransform把三句话转换成稀疏特征向量。打印词汇表可看到“词 → 整数索引”的映射字典print(count.vocabulary_)例如and映射到索引 0is映射到索引 1以此类推。打印稠密化后的特征矩阵print(bag.toarray())每个索引位置对应词汇表中的一个词位置 0 是and的计数仅第 3 句出现位置 1 是is的计数三句都出现。这些计数值即原始词频tf(t,d)——词 t 在文档 d 中出现的次数。注意CountVectorizer默认会对文本做小写化并基于空白分词因此The与the会被合并为同一个词。2.2 稀疏矩阵的意义影评语料词汇量可达数万级若用稠密矩阵存储50,000 篇文档 × 数十万词维度的矩阵会占用海量内存。fit_transform返回的是 scipy 稀疏矩阵仅记录非零元素这是后续大数据量处理能够进行的前提。三、用 TF-IDF 评估词的区分度3.1 动机高频词往往无信息量在跨类文档中都频繁出现的词如is、the通常不携带判别信息。TF-IDF词频-逆文档频率的作用就是降低这类高频词的权重$$\text{tf-idf}(t,d)\text{tf}(t,d)\times \text{idf}(t,d)$$其中逆文档频率的教科书定义为$$\text{idf}(t,d) \log\frac{n_d}{1\text{df}(d, t)}$$n_d为文档总数df(d,t)为包含词 t 的文档数。分母加 1 是为了让出现在所有训练样本中的词也能得到非零权重取对数则是避免过低文档频率被赋予过大的权重。3.2 scikit-learn 的实现差异平滑 IDF 与 1 偏移直接用TfidfTransformer得到的结果与教科书公式略有不同因为 scikit-learn 实际实现的是带平滑项的版本from sklearn.feature_extraction.text import TfidfTransformer tfidf TfidfTransformer(use_idfTrue, norml2, smooth_idfTrue) print(tfidf.fit_transform(count.fit_transform(docs)).toarray())scikit-learn 内部使用的公式为$$\text{idf}(t,d) \log\frac{1 n_d}{1 \text{df}(d, t)}$$$$\text{tf-idf}(t,d) \text{tf}(t,d) \times (\text{idf}(t,d)1)$$并默认直接对tf-idf 结果做 L2 归一化norml2$$v_{\text{norm}} \frac{v}{||v||_2} \frac{v}{\sqrt{v_1^2 v_2^2 \dots v_n^2}}$$3.3 手算验证以第 3 句中的is为例tf3df3三句都含该词$$\text{idf}(is, d_3) \log\frac{13}{13} 0$$$$\text{tf-idf}(is, d_3) 3 \times (01) 3$$对第 3 句全部词重复计算得到未归一化向量[3.39, 3.0, 3.39, 1.29, 1.29, 1.29, 2.0, 1.69, 1.29]再除以 L2 范数得到归一化向量[0.5, 0.45, 0.5, 0.19, 0.19, 0.19, 0.3, 0.25, 0.19]其中is的 tf-idf 由原本的最大词频降为 0.45——这正是 TF-IDF 压制高频无信息词的直观体现。notebook 中还有一段验证代码先用normNone取出原始 tf-idf再手动做 L2 归一化结果与TfidfTransformer默认输出完全一致见 ch08.ipynb 第 44–45 个代码单元。四、清洗文本数据去 HTML 与保留表情符号原始影评含有 HTML 标签如br /与大量标点。第 8 章定义了一个preprocessor函数完成三项工作import re def preprocessor(text): text re.sub([^]*, , text) emoticons re.findall((?::|;|)(?:-)?(?:\)|\(|D|P), text) text re.sub([\W], , text.lower()) \ .join(emoticons).replace(-, ) return text逐行解读re.sub([^]*, , text)删除所有 HTML 标签re.findall((?::|;|)(?:-)?(?:\)|\(|D|P), text)用正则捕获:)、:-(、;D等表情符号。正则可拆解为眼睛:/;/ 可选鼻子- 嘴型)/(/D/P最后将文本小写化、把非单词字符\W替换为空格再把捕获到的表情符号拼接回文本末尾并将表情中的-去掉使:-(变为:(。例如preprocessor(/aThis :) is :( a test :-)!)输出为this is a test :) :( :(。验证通过后对整个review列应用df[review] df[review].apply(preprocessor)清洗能显著减少后续 GridSearch 的特征维度从而加速调参。五、文档分词Porter 词干提取与停用词5.1 词干提取利用 NLTK 的 PorterStemmer 把词形变化归并到词干例如runners、running、runs都归并为runfrom nltk.stem.porter import PorterStemmer porter PorterStemmer() def tokenizer(text): return text.split() def tokenizer_porter(text): return [porter.stem(word) for word in text.split()]对比输出tokenizer(runners like running and thus they run)保持原形而tokenizer_porter(...)会输出[runner, like, run, and, thu, they, run]注意thus被过度归并成了thu这是词干提取的常见副作用。5.2 停用词过滤is、and、the这类词在情感判别上几乎无价值使用 NLTK 提供的英文停用词表过滤import nltk nltk.download(stopwords) from nltk.corpus import stopwords stop stopwords.words(english) [w for w in tokenizer_porter(a runner likes running and runs a lot)[-10:] if w not in stop]输出只保留[runner, like, run]等有实际语义的词干。注意停用词过滤与词干提取的顺序会影响结果——先提取词干再去停用词表比对因为 Porter 词干可能改变拼写如thu需要与停用词表形态对应。六、训练逻辑回归文档分类器6.1 划分训练集与测试集将前 25,000 条作为训练集、后 25,000 条作为测试集X_train df.loc[:25000, review].values y_train df.loc[:25000, sentiment].values X_test df.loc[25000:, review].values y_test df.loc[25000:, sentiment].values6.2 Pipeline GridSearchCV 联合调参把特征提取TfidfVectorizer与分类器LogisticRegression串成 Pipeline再交给 GridSearchCV 做 5 折交叉验证from sklearn.pipeline import Pipeline from sklearn.linear_model import LogisticRegression from sklearn.feature_extraction.text import TfidfVectorizer # scikit-learn 0.18 前后 GridSearchCV 的导入路径不同 from sklearn.model_selection import GridSearchCV tfidf TfidfVectorizer(strip_accentsNone, lowercaseFalse, preprocessorNone) param_grid [{vect__ngram_range: [(1, 1)], vect__stop_words: [stop, None], vect__tokenizer: [tokenizer, tokenizer_porter], clf__penalty: [l1, l2], clf__C: [1.0, 10.0, 100.0]}, {vect__ngram_range: [(1, 1)], vect__stop_words: [stop, None], vect__tokenizer: [tokenizer, tokenizer_porter], vect__use_idf: [False], vect__norm: [None], clf__penalty: [l1, l2], clf__C: [1.0, 10.0, 100.0]}, ] lr_tfidf Pipeline([(vect, tfidf), (clf, LogisticRegression(random_state0))]) gs_lr_tfidf GridSearchCV(lr_tfidf, param_grid, scoringaccuracy, cv5, verbose1, n_jobs-1)关键参数解读Pipeline 命名步骤名vect与clf是 GridSearch 参数前缀vect__、clf__的依据__分隔步骤名与参数名ngram_range本实验限定为(1, 1)仅用单词扩展到(1, 2)可引入二元词组特征但维度与计算量会上升stop_words对比启用/停用停用词过滤tokenizer对比普通分词与 Porter 词干分词penalty与C逻辑回归的 L1/L2 正则化及正则强度C越小正则越强第二个参数字典关闭 IDFuse_idfFalse并去掉归一化normNone用于验证纯词频特征的表现n_jobs-1并行使用全部 CPU 核心加速。notebook 特别提示Windows 上多进程偶发问题参见 GitHub issue #50若报错可将n_jobs改为 1版本兼容仓库代码用LooseVersion判断 scikit-learn 版本0.18 之前从sklearn.grid_search导入GridSearchCV0.18 及以后从sklearn.model_selection导入见 ch08.py。6.3 训练、评估与 best_score_ 的含义gs_lr_tfidf.fit(X_train, y_train) print(Best parameter set: %s % gs_lr_tfidf.best_params_) print(CV Accuracy: %.3f % gs_lr_tfidf.best_score_) clf gs_lr_tfidf.best_estimator_ print(Test Accuracy: %.3f % clf.score(X_test, y_test))重要澄清best_score_是 5 折交叉验证的平均准确率不是测试集准确率。notebook 专门用一段伪数据25 个随机整数标签 噪声特征演示手动构造 5 折索引cross_val_score返回 5 个折的准确率再交给GridSearchCV(LogisticRegression(), {}, cvcv5_idx)后best_score_恰等于这 5 个分数的均值——证明GridSearchCV的best_score_本质上就是 k 折交叉验证的平均分最终模型性能应以clf.score(X_test, y_test)的测试集结果为准。七、处理更大规模数据在线学习与 out-of-core当数据量远超内存时例如数千万条影评无法一次性fit全部样本。第 8 章最后给出解决方案流式读取 HashingVectorizer SGDClassifier 的增量在线学习。7.1 流式读取与小批量生成def stream_docs(path): with open(path, r, encodingutf-8) as csv: next(csv) # skip header for line in csv: text, label line[:-3], int(line[-2]) yield text, label def get_minibatch(doc_stream, size): docs, y [], [] try: for _ in range(size): text, label next(doc_stream) docs.append(text) y.append(label) except StopIteration: return None, None return docs, ystream_docs是生成器逐行读取 CSV跳过头行每次 yield 一条(text, label)line[:-3]截取评论文本、int(line[-2])读取情感标签这种切片方式依赖movie_data.csv的列分隔格式get_minibatch每次取size条组成小批量数据耗尽时返回(None, None)作为停止信号。7.2 HashingVectorizer无内存词典的哈希技巧from sklearn.feature_extraction.text import HashingVectorizer vect HashingVectorizer(decode_errorignore, n_features2**21, preprocessorNone, tokenizertokenizer)与CountVectorizer的关键区别HashingVectorizer不保存词汇表而是用哈希函数把每个词直接映射到固定维度这里n_features2**21≈ 209 万的特征向量位置。代价是无法反向还原词索引vocabulary_不存在、也无法做 IDF 加权好处是内存占用恒定、天然支持流式/分布式场景。decode_errorignore用于忽略无法解码的字符。注意这里的tokenizer是第 7 节开头重新定义的增强版——在preprocessor逻辑基础上额外过滤停用词见 ch08.py。有趣的是这套 tokenizer HashingVectorizer 组合在下一章被原样复用到 Web 应用里movieclassifier/vectorizer.py 与 movieclassifier_with_update/vectorizer.py 中的代码与第 8 章完全同源只是停用词表改为从pkl_objects/stopwords.pkl反序列化加载——这正说明本章沉淀的文本处理流程具有直接的生产复用价值。7.3 SGDClassifier 增量训练from sklearn.linear_model import SGDClassifier import pyprind clf SGDClassifier(losslog, random_state1, n_iter1) doc_stream stream_docs(path./movie_data.csv) pbar pyprind.ProgBar(45) classes np.array([0, 1]) for _ in range(45): X_train, y_train get_minibatch(doc_stream, size1000) if not X_train: break X_train vect.transform(X_train) clf.partial_fit(X_train, y_train, classesclasses) pbar.update()SGDClassifier(losslog, ...)用随机梯度下降优化对数损失等价于线性逻辑回归但支持增量更新partial_fit必须显式传入classes参数因为增量学习中分类器不知道完整的类别集合每轮取 1,000 条小批量做一次partial_fit共 45 轮覆盖 45,000 条训练数据配合pyprind.ProgBar显示进度注意n_iter1表示每个小批量只迭代一次——在线学习的“一遍过”特性正是其内存高效的原因。7.4 评估与收尾X_test, y_test get_minibatch(doc_stream, size5000) X_test vect.transform(X_test) print(Accuracy: %.3f % clf.score(X_test, y_test)) clf clf.partial_fit(X_test, y_test)用剩余 5,000 条做测试评估之后再次partial_fit(X_test, y_test)把测试数据也纳入训练——对生产系统而言任何可用数据都不应浪费。整个流程中任意时刻内存中只保留一个 1,000 条的小批量与固定 2^21 维的稀疏矩阵因此可轻松扩展到远超单机内存的数据集。八、小结第 8 章从数据到模型给出了一条完整的文本情感分类流水线数据IMDb 影评aclImdb组装为 movie_data.csvreviewsentiment两列前 25,000 训练 / 后 25,000 测试特征工程CountVectorizer构建词袋 →TfidfTransformer/TfidfVectorizer做 TF-IDF 加权注意 scikit-learn 的平滑 IDF 与 1 偏移公式与 L2 归一化 →preprocessor去 HTML、保留表情 → Porter 词干提取 停用词过滤分类Pipeline(TfidfVectorizer LogisticRegression)配合GridSearchCV5 折调参理解best_score_是 CV 平均分而非测试准确率规模化stream_docsget_minibatchHashingVectorizerSGDClassifier.partial_fit实现 out-of-core 在线学习内存恒定、可增量训练。本章产出的 tokenizer 与向量化配置在第 9 章被直接复用于 Flask Web 应用vectorizer.py读者若想继续把模型落地为可交互的服务可继续阅读第 9 章及其配套的 ch09 目录。如需在本地复现全部实验可直接运行 ch08.py 脚本或打开 ch08.ipynb 逐单元执行环境搭建与 Notebook 打开方式可参考 code/README.md 与 docs/running_jupyter_nb.pdf。赞分享机器学习教程【免费下载链接】python-machine-learning-bookThe Python Machine Learning (1st edition) book code repository and info resource项目地址https://gitcode.com/gh_mirrors/py/python-machine-learning-book点击查看免费下载相关推荐如何用 easytimer.js 构建企业级计时器应用从基础到实战如何用 easytimer.js 构建企业级计时器应用从基础到实战 easytimer.js 是一款功能强大且易于使用的 JavaScript 计时器库兼容开发工具1Remote如何用一个工具管理所有远程连接1Remote如何用一个工具管理所有远程连接 你是否曾经为管理不同类型的远程连接而感到困扰RDP、SSH、VNC、FTP……每个协议都需要不同的客户端配桌面应用网络产品评论情感分析pkuseg-python分词对模型效果的影响产品评论情感分析pkuseg python分词对模型效果的影响 引言情感分析中的分词痛点 你是否遇到过这样的困境使用通用分词工具处理电商评论时性价比高人工智能NLP创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考