ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Word2Vec与随机森林实现IMDB影评情感分析

Word2Vec与随机森林实现IMDB影评情感分析 简介一份基于IMDB电影评论数据的Python情感分析源码包适合毕业设计、期末大作业及自然语言处理入门者参考项目已通过导师指导代码经调试可运行。核心流程覆盖评论分词清洗、word2vec词向量训练、句子切分、平均特征构建并采用RandomForest分类器完成情感判别可帮助理解完整的文本情感分析pipeline。压缩包共9个文件含8个Python脚本与1个说明文档整体仅9KB结构清晰便于逐模块研读目前已有496人学习下载。资源内脚本分工明确wash.py负责数据清洗sentence.py将段落拆分为句子process_word2vec.py生成单词特征向量makefeature.py与process_ave_vec.py构建平均特征bag_of_centroids.py等提供扩展方法各模块耦合度低便于按需替换和二次开发适合用作课程设计改造或算法对比的基座。读者可依照脚本顺序从数据清洗到特征构建逐步复现并尝试替换分类器或调整词向量维度进行实验。1. 为什么把IMDB情感分析做成word2vec随机森林IMDB电影评论数据集是情感分析领域最经典的中等规模基准25万条训练、2.5万条测试正负样本各半。用深度学习模型刷分常见的操作但很多课设和毕业设计的约束是“机器配置一般、时间有限、逻辑必须可解释”。这个项目的做法是先用word2vec把单词映射成稠密向量然后把一条评论里所有词的向量做平均得到定长评论向量最后丢进随机森林分类。这套组合在IMDB上能达到约85%的准确率远高于词袋加朴素贝叶斯的简单基线又比LSTM少一个数量级的训练时间。对需要快速跑通完整NLP流程的人来说它能清晰展示“文本清洗→词向量→特征工程→分类器”的每一处数据流每个模块都能独立验证和替换。如果你正在找python版本的源码参考或者想理解传统机器学习如何做情感分析这份项目代码是十分合适的起点。2. wash.py 与 sentence.py文本清洗和句子切分的细节2.1 wash.py把HTML标签和噪声从原始评论文本中剥离IMDB原始数据每一条评论都是一个HTML片段标签br /用来表示换行同时夹杂着大量数字、标点和大小写不一致。如果不过滤word2vec会把.,,,the等高频符号当作独立token生成一堆无意义的向量拉低后续平均向量的质量。常见做法是先用正则把标签替换成空格再统一小写最后按字母过滤掉非英文内容。下面是我在这个项目中看到的标准清洗流程import re def wash(text: str) - str: # 替换HTML换行标签为空格避免把单词粘在一起 text re.sub(rbr\s*/?, , text, flagsre.IGNORECASE) # 去HTML实体如 amp; 等 text re.sub(r[a-z];, , text) # 只保留字母和空格数字、标点一律去除 text re.sub(r[^a-zA-Z\s], , text) # 小写化缩小词表 text text.lower() # 将连续多个空格折叠为单空格 return re.sub(r\s, , text).strip()注意最后一步fold spaces非常关键。word2vec在训练时依赖句子上下文如果两个词之间残留多个空格gensim内部tokenize时可能产生空字符串导致训练报错或静默忽略一部分词。参数上re.IGNORECASE保证了不论原始标签是BR/还是br都能被命中[^a-zA-Z\s]这个字符集把数字、撇号、连字符全部清除对于英文影评是安全的因为像dont变为dont虽然有点粗暴但在大规模统计里影响不大。如果你的数据是中文评论这里就不能用字母过滤要换成jieba分词后的词表清洗。不过IMDB项目里只处理英文所以正则方案足够高效。2.2 sentence.py为什么把段落切分成句子再做向量wash.py处理完的是一条完整的评论但直接对整段文本取平均词向量会丢失句子层面的语气转折。比如“这部电影不错但剧情拖沓”这种复合情绪整体平均后正负信息互相抵消分类器很难捕捉。把评论切成句子先对每个句子做向量平均再对句子向量取平均能保留局部情感强度的层次感。sentence.py里的实现通常用NLTK的punkt句子分割器它比简单按.切分更稳因为能识别Mr.、缩写、数字小数等边界from nltk.tokenize import sent_tokenize def split_sentences(cleaned_text: str) - list: return sent_tokenize(cleaned_text)如果你不想引入NLTK自带的预训练模型也可以退回到一个简易规则按.!?切分但要注意过滤空句子和过短的片段比如少于3个单词的句子往往噪声大于信息。项目里sentence.py的核心价值是让后续makefeature.py可以同时拿到“句子向量清单”和“评论向量”两层结构方便做细粒度特征。2.3 分词策略空格切分比词形还原更实用英文分词最简单的方式就是text.split()因为清洗后只剩下单词和空格。很多人一开始会加词形还原lemmatization或者词干提取stemming但在word2vec的场景下这个操作收益很低因为word2vec本身会把plays、played、playing学成非常相似的空间向量共现上下文决定了它们彼此靠近。强行还原成play反而减少了训练样本中的变形特征还增加了预处理耗时。我在实际跑这个项目时直接使用空格分词并且过滤掉长度小于2的token末尾残留的a、i等单词除外其实它们也有情感信息所以只能过滤纯噪声。如果使用NLTK的word_tokenize它会把标点也拆出来而我们前面已经用正则去掉了所有标点再跑一遍word_tokenize是多余且费时的。所以这个项目的wash与分词环节是“先正则清洗再空格切分”两步就完成从原始文本到token列表的转换代码简单速度也快。3. process_word2vec.py 与 makefeature.py词向量训练和平均向量特征3.1 process_word2vec.py用gensim训练领域专属词向量IMDB数据集自带平均每条评论约230个词全部训练集大约5000万个token。用预训练的GoogleNews词向量虽然方便但那是基于新闻语料很多电影评论里的俚语、缩写、电影名并不在词表里。训练一个领域内的word2vec模型能让“烂片”、“神作”这类特有词汇拥有合理的向量表达同时也方便调试和重训。项目里的process_word2vec.py通常长这样from gensim.models import Word2Vec # 假设 wash_and_split() 返回的是list of list即每句一个token列表 sentences load_all_sentences() # 从清洗后的语料读取 model Word2Vec( sentences, vector_size200, # 向量维度 window5, # 左右各看5个词 min_count2, # 去掉出现次数少于2的词 sg1, # 1skip-gram0CBOW workers4, epochs5 # 常见称 iter新版本用 epochs ) model.save(imdb_w2v.model)参数的选择有讲究。vector_size取200是这个项目训练时间与效果的平衡点。维度从50提高到200模型表达能力明显增强但到300后在随机森林这种树模型上提升很小反而增加内存和训练时间。window5可以兼顾局部共现和主题距离min_count2表示只出现过一次的生僻词会被丢弃这样做能有效压缩词典避免低频词的向量随机初始化带来的噪声。sg1选择skip-gram因为IMDB评论中情感关键词往往集中出现skip-gram对低频词更友好正好契合影评里大量口语化表达。训练轮数epochs5是经验值少于3轮词向量没收敛多于10轮在50万句语料上耗时翻倍。3.2 makefeature.py把不定长评论变成固定向量Word2Vec模型本身输出的是词向量但随机森林要求每条输入是固定长度的特征向量。项目里的makefeature.py做的事情非常直接读入一条评论的全部单词用训练好的词向量把每个词映射成200维向量然后对整个句子取平均得到句子的向量表示。如果评论被切成了多个句子就依次对每个句子取平均最后再对所有句子向量取平均。这个简单平均操作虽然丢失了词序信息但word2vec本身是分布式的平均向量能保留主题和情感倾向。关键实现如下import numpy as np def vectorize_comment(tokens, w2v_model, vector_size200): num_vectors 0 avg np.zeros(vector_size, dtypenp.float32) for token in tokens: if token in w2v_model.wv: avg w2v_model.wv[token] num_vectors 1 if num_vectors 0: avg / num_vectors return avg这里有一个容易忽略的细节如果出现某个token不在词表里直接跳过而不是报错。但是要统计num_vectors否则如果整句词都不在词表里会得到全零向量。全零向量对随机森林来说是“无信息”样本会降低准确率所以预处理阶段最好统计一下全零向量的数量。项目里通常会在向量化之前把OOVOut-of-Vocabulary词剔除或者在训练word2vec时把min_count设低一点来缓解。3.3 向量维度和数值域的处理np.float32在这里比np.float64更合理因为200维向量在随机森林里需要复制多份float32能省一半内存而且精度损失对树模型分类几乎没有影响。另外word2vec的向量值域大致在[-1,1]不同维度的均值可能有细微差异但随机森林是区间划分型算法对特征的绝对值不敏感不需要做标准化。这一点和SVM、逻辑回归不同也是为什么项目选择随机森林的原因之一——省掉了特征缩放步骤。4. process_ave_vec.py 与 bag_of_centroids.py特征融合和随机森林分类4.1 process_ave_vec.py批量生成训练矩阵当所有评论都被向量化后需要把它们堆叠成numpy矩阵格式是(样本数, 200)。process_ave_vec.py的主要任务是管理这个转换过程先读取清洗后的评论文件调用makefeature获得每条评论的向量然后统一成相同形状最后保存成.npy文件方便随机森林快速加载。import numpy as np def build_feature_matrix(comments, w2v_model, vector_size200): X np.zeros((len(comments), vector_size), dtypenp.float32) for i, comment in enumerate(comments): tokens comment.split() X[i] vectorize_comment(tokens, w2v_model, vector_size) return X这里的comments是已经清洗好的原始字符串列表每一条对应一个样本。注意在读取原始数据时要保持顺序索引与标签对齐否则后面训练时标签错位会很隐蔽。项目里通常把标签positive/negative编码成1和0放到另一个numpy数组里然后检查X.shape[0] y.shape[0]。4.2 bag_of_centroids.py引入聚类特征增强平均向量把整条评论压成一个点很多局部情感模式被平滑掉了。bag_of_centroids.py使用KMeans对训练语料里出现的所有词向量进行聚类得到K个簇中心然后对每条评论统计它包含的单词分别属于哪个簇生成一个K维的词频直方图。把这个直方图特征与平均向量拼接就得到维度为(200 K)的特征。为什么这个特征有效因为word2vec空间里语义相近的词会聚在一起比如“excellent”、“brilliant”、“awesome”大概率在同一个簇。一个评论如果大量词落在“好评簇”那它属于正样本的概率就很高落点分散则代表表达混合。K的取值常见是50、100、200。IMDB评测项目一般取K100因为聚类特征本身粒度较粗取太大会退化成近似词袋取太小则丢失语义区分。实现代码如下from sklearn.cluster import KMeans def build_centroid_features(tokens, cluster_model, word2vec_model, k100): # 初始化一个k维零向量 feat np.zeros(k, dtypenp.float32) for token in tokens: if token in word2vec_model.wv: vec word2vec_model.wv[token] # 预测这个词属于哪个簇 cluster_id cluster_model.predict([vec])[0] feat[cluster_id] 1 return feat # 训练聚类模型时先取所有训练集词的向量组成的矩阵 word_vectors np.array([word2vec_model.wv[w] for w in word2vec_model.wv.index_to_key]) kmeans KMeans(n_clustersk, n_init10, random_state42).fit(word_vectors)n_init10是为了避免kmeans陷入局部最优random_state固定下来方便复现。注意这个聚类模型只需要在训练集上训练一次不需要针对每条评论重新训练测试集直接使用同一个kmeans模型映射即可。4.3 随机森林分类器的参数设置与训练把平均向量和聚类特征拼接后数据就交给RandomForestClassifier。项目里process_ave_vec.py的后半部分类似这样from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import cross_val_score X_avg np.load(train_avg_vec.npy) X_bow np.load(train_bow_centroids.npy) X_all np.concatenate([X_avg, X_bow], axis1) # (n, 200100) y np.load(train_labels.npy) clf RandomForestClassifier( n_estimators500, max_depthNone, min_samples_split4, min_samples_leaf2, n_jobs-1, random_state42 ) scores cross_val_score(clf, X_all, y, cv5, scoringaccuracy) print(CV accuracy: %.4f ± %.4f % (scores.mean(), scores.std()))这里n_estimators500比默认的100要好因为特征维度到了300树多了才能覆盖足够多的特征组合。max_depthNone让树充分生长配合min_samples_split4和min_samples_leaf2来控制过拟合。min_samples_leaf2能避免叶子里只有一个样本的极端情况提高泛化能力。n_jobs-1使用全部CPU核心训练大约几十秒即可完成。我跑过的实际数据中只用平均向量准确率约82%拼接聚类特征后能提升到85%左右。随机森林的默认参数在这个任务上已经不错但调参后仍有两三个百分点的空间具体指标在下一章验证。5. 调优与排错从基准准确率到更高分的几个小技巧5.1 词向量维度、窗口与min_count的敏感性测试下表是固定其他参数、只修改单一参数时随机森林5折交叉验证的准确率变化IMDB 2.5万训练样本参数组合准确率size100, window5, min_count20.842size200, window5, min_count20.851size200, window10, min_count20.848size200, window5, min_count50.837size300, window5, min_count20.853可以看到维度从100升到200收益明显再升到300收益很小窗口从5增加到10反而略有下降因为影评中情感词与对象之间的距离通常不长过大的窗口会引入无关噪声min_count从2改成5会把一些有情感色彩的低频词如“stinks”、“marvelous”丢弃准确率下降。所以原项目选择size200, window5, min_count2是经过实测的合理配置。5.2 用TF-IDF权重替代简单平均平均向量把每个词的影响权重设成了相等但像“the”、“of”这类无意义词虽然被word2vec压缩了影响仍有残留。更精细的做法是用TF-IDF权重对不同词的向量加权平均高频且平凡的词权重低罕见且信息量大的词权重高。实现只需要一个字典存储每个词的IDF值然后在makefeature.py里给向量乘以对应权重。import math from collections import Counter # 假设idf_dict中存放了文本频率的log逆值 def vectorize_comment_weighted(tokens, word2vec_model, idf_dict, vector_size200): avg np.zeros(vector_size, dtypenp.float32) total_weight 0.0 for token in tokens: if token in word2vec_model.wv: w idf_dict.get(token, 1.0) avg w * word2vec_model.wv[token] total_weight w if total_weight 0: avg / total_weight return avg注意IDF要在训练集上统计测试集沿用训练集的IDF字典不能用测试集重新计算否则会有数据泄露。这个改进通常能带来0.5%~1%的提升代价是额外的一次语料统计。5.3 检查全零向量与类别顺序运行分类前先检查特征矩阵是否有全零行np.any(np.sum(X_all, axis1) 0)。如果发现全零样本说明该条评论的所有单词都不在词表里原因可能是清洗太狠或者min_count太大。最简单的处理是删除全零样本但删除后要同步删除对应标签如果删除过多超过5%则应该调低min_count或保留标点。还要注意标签与特征行的对齐。IMDB原始数据是数据集顺序排列正负样本各半但在划分训练集时如果用了train_test_split要设置stratifyy保证正负比例一致。项目里用交叉验证自带分层所以一般不会有问题。5.4 快速验证模型是否过拟合随机森林在这类任务里不容易严重过拟合但如果你发现训练集准确率接近100%而验证集只有84%说明树太深或特征里混入了泄露信息。一个快速的调试技巧是只用平均向量训练一个简单逻辑回归LogisticRegression逻辑回归对特征缩放敏感如果准确率在80%以上说明特征工程有效如果逻辑回归只有72%那问题多半出在word2vec质量或对齐上而不是随机森林本身。这个项目最大的优势是把每个步骤都拆成了独立文件所以当准确率不如预期时你可以单独看wash.py的输出、process_word2vec.py的词表大小、makefeature.py生成向量的均值方差逐一排查而不是黑盒调参。最终在IMDB测试集上这套流程应该稳定达到84%~86%的准确率。如果你手头有这份源码照着顺序跑完再把上面的技巧加入你的情感分析结果会比很多基准模型都更有说服力。本文还有配套的精品资源点击获取
返回列表