上一篇博客用词袋模型, 包括词频矩阵, Tf-Idf 矩阵, LSA 和 n-gram 构造文本特征, 做了 Kaggle 上的电影评论情感分类题.
这篇博客还是关于文本特征工程的, 用词嵌入的方法来构造文本特征, 也就是用 word2vec 词向量和 glove 词向量进行文本表示, 训练随机森林分类器.
一, 训练 word2vec 词向量
Kaggle 情感分析题给出了三个数据集, 一个是带标签的训练集, 共 25000 条评论, 一个是测试集, 无标签的, 用来做预测并提交结果, 这两个数据集是上一篇文章里我们用过的.
此外还有一个无标签的数据集, 有 50000 条评论, 不用太可惜了. 我们可以想到, 用无标签的数据可以训练 word2vec 词向量, 进行词嵌入. 与词袋模型相比, word2vec 词向量能解决文本表示维度过高的问题, 并且把单词之间的位置信息考虑进去了. 或许, 用 word2vec 词向量进行文本表示, 能取得更好的预测结果.
下面我们先用 gensim 训练 word2vec 词向量.
首先导入所需要的库.
- import os,re
- import numpy as np
- import pandas as pd
- from bs4 import BeautifulSoup
- from gensim.models import word2vec
接着读取有标签的训练数据和无标签的数据, 把影评合并到一个列表中.
- """读取数据, 包括有标签的和无标签的数据"""
- # 定义读取数据的函数
- def load_dataset(name, nrows=None):
- datasets = {
- 'unlabeled_train': 'unlabeledTrainData.tsv',
- 'labeled_train': 'labeledTrainData.tsv',
- 'test': 'testData.tsv'
- }
- if name not in datasets:
- raise ValueError(name)
- data_file = os.path.join('..', 'data', datasets[name])
- df = pd.read_csv(data_file, sep='\t', escapechar='\\', nrows=nrows)
- print('Number of reviews: {}'.format(len(df)))
- return df
- # 读取有标签和无标签的数据
- df_labeled = load_dataset('labeled_train')
- df_unlabeled = load_dataset('unlabeled_train')
- sentences = []
- for s in df_labeled['review']:
- sentences.append(s)
- for s in df_unlabeled['review']:
- sentences.append(s)
- print("一共加载了",len(sentences),"条评论.")
- Number of reviews: 25000
- Number of reviews: 50000
一共加载了 75000 条评论.
接着进行数据预处理, 处理成 gensim 所需要的格式. 这里非常关键, 我还摸索了一阵, 才知道什么输入格式是正确的.
其实输入格式是这样的, 假设有两篇文本, 那么处理成 [ ['with', 'all', 'this', 'stuff', 'going',...], ['movie', 'but', 'mj', 'and', 'most',...]]的格式, 每篇文本是一个列表, 列表元素为单个单词. 这个很容易做到, 因为英文不需要进行分词, 用 text.split()按照空格进行切分就行.
由于 word2vec 依赖于上下文, 而上下文有可能就是停词, 所以这里选择不去停用词.
- """数据预处理, 去 html 标签, 去非字母的字符"""
- eng_stopwords = {}.fromkeys([ line.rstrip() for line in open('../stopwords.txt')])
- # 可以选择是否去停用词, 由于 word2vec 依赖于上下文, 而上下文有可能就是停词.
- # 因此对于 word2vec, 我们可以不用去停词.
- def clean_text(text, remove_stopwords=False):
- text = BeautifulSoup(text,'html.parser').get_text()
- text = re.sub(r'[^a-zA-Z]', ' ', text)
- words = text.lower().split()
- if remove_stopwords:
- words = [w for w in words if w not in eng_stopwords]
- return words
- sentences = [clean_text(s) for s in sentences]
- # 这里可以说是最关键的, gensim 需要的格式就是把每条评论弄成 ['with', 'all', 'this', 'stuff', 'going',...] 的格式.
- # 再次强调, 这里最关键, 格式不对则没法学习.
现在就可以输入进去训练词向量了.
- """打印日志信息"""
- import logging
- logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
- """"设定词向量训练的参数, 开始训练词向量"""
- num_features = 300 # 词向量取 300 维
- min_word_count = 40 # 词频小于 40 个单词就去掉
- num_workers = 4 # 并行运行的线程数
- context = 10 # 上下文滑动窗口的大小
- model_ = 0 # 使用 CBOW 模型进行训练
- model_name = '{}features_{}minwords_{}context.model'.format(num_features, min_word_count, context)
- print('Training model...')
- model = word2vec.Word2Vec(sentences, workers=num_workers, \
- size=num_features, min_count = min_word_count, \
- Windows = context, sg=model_)
- # 保存模型
- model.save(os.path.join('..', 'models', model_name))
检验一下模型训练的效果, 查看和 man 这个单词最相关的词, 可以看到, 结果还不错.
- model.wv.most_similar("man")
- [('woman', 0.6039960384368896),
- ('lady', 0.5690498948097229),
- ('lad', 0.5434065461158752),
- ('guy', 0.4913134276866913),
- ('person', 0.4771265387535095),
- ('monk', 0.47647857666015625),
- ('widow', 0.47423964738845825),
- ('millionaire', 0.4719209671020508),
- ('soldier', 0.4717007279396057),
- ('men', 0.46545034646987915)]
二, 用 word2vec 和 glove 词向量进行文本表示
好, 下面分别用 word2vec 和 glove 词向量做电影评论的文本表示, 再次训练随机森林分类器, 看哪种词向量的效果更好.
首先导入所需要的库.
- import os
- import re
- import numpy as np
- import pandas as pd
- from bs4 import BeautifulSoup
- from nltk.corpus import stopwords
- from gensim.models.word2vec import Word2Vec
- from sklearn.ensemble import RandomForestClassifier
- from sklearn import metrics
读取训练集数据.
- """读取训练集数据"""
- def load_dataset(name, nrows=None):
- datasets = {
- 'unlabeled_train': 'unlabeledTrainData.tsv',
- 'labeled_train': 'labeledTrainData.tsv',
- 'test': 'testData.tsv'
- }
- if name not in datasets:
- raise ValueError(name)
- data_file = os.path.join('..', 'data', datasets[name])
- df = pd.read_csv(data_file, sep='\t', escapechar='\\', nrows=nrows)
- print('Number of reviews: {}'.format(len(df)))
- return df
- df = load_dataset('labeled_train')
读取训练好的 word2vec 词向量, 和预训练的 glove 词向量(需要先下载 glove 词向量), 备用.
- """读取训练好的 word2vec 模型"""
- model_name = '300features_40minwords_10context.model'
- word2vec_embedding = Word2Vec.load(os.path.join('..', 'models', model_name))
- """读取 glove 词向量"""
- glove_embedding = {}
- f = open('../glove.6B/glove.6B.300d.txt', encoding='utf-8')
- for line in f:
- values = line.split()
- Word = values[0]
- coefs = np.asarray(values[1:], dtype='float32')
- glove_embedding[Word] = coefs
- f.close()
将训练集中的每条电影评论用向量表示, 首先要得到每条评论中每个单词的词向量, 然后把所有单词的词向量做平均, 当作是句子或文本的向量表示.
于是得到电影评论的 word2vec 表示和 golve 表示.
- """数据预处理, 得到单词的词向量, 并得到句子的向量"""
- # 编码方式有一点粗暴, 简单说来就是把这句话中的词的词向量做平均
- eng_stopwords = set(stopwords.words('english'))
- # 清洗文本数据
- def clean_text(text, remove_stopwords=False):
- text = BeautifulSoup(text, 'html.parser').get_text()
- text = re.sub(r'[^a-zA-Z]', ' ', text)
- words = text.lower().split()
- if remove_stopwords:
- words = [w for w in words if w not in eng_stopwords]
- return words
- # 取 word2vec 词向量, 或者 glove 词向量
- def to_review_vector(review,model='word2vec'):
- words = clean_text(review, remove_stopwords=True)
- if model == 'word2vec':
- array = np.asarray([word2vec_embedding[w] for w in words if w in word2vec_embedding],dtype='float32')
- elif model == 'glove':
- array = np.asarray([glove_embedding[w] for w in words if w in glove_embedding],dtype='float32')
- else:
- raise ValueError('请输入: word2vec 或 glove')
- return array.mean(axis=0)
- """word2vec 表示的样本"""
- train_data_word2vec = [to_review_vector(text,'word2vec') for text in df['review']]
- """用 glove 表示的样本"""
- train_data_glove = [to_review_vector(text,'glove') for text in df['review']]
用 word2vec 表示的样本训练随机森林模型, 并用包外估计作为泛化误差的评估指标.
从结果可以看到, 包外估计为 0.83568, 之前用词频矩阵训练的模型包外估计为 0.84232, 所以比之前用词袋模型训练的效果差一点.
- def model_eval(train_data):
- print("1, 混淆矩阵为:\n")
- print(metrics.confusion_matrix(df.sentiment, forest.predict(train_data)))
- print("\n2, 准确率, 召回率和 F1 值为:\n")
- print(metrics.classification_report(df.sentiment,forest.predict(train_data)))
- print("\n3, 包外估计为:\n")
- print(forest.oob_score_)
- print("\n4,AUC Score 为:\n")
- y_predprob = forest.predict_proba(train_data)[:,1]
- print(metrics.roc_auc_score(df.sentiment, y_predprob))
- """用 word2vec 词向量表示训练模型和评估模型"""
- forest = RandomForestClassifier(oob_score=True,n_estimators = 200, random_state=42)
- forest = forest.fit(train_data_word2vec, df.sentiment)
- print("\n==================== 评估以 word2vec 为文本表示训练的模型 ==================\n")
- model_eval(train_data_word2vec)
再用 glove 词向量表示的训练集进行模型训练. 很不幸, 包外估计为 0.78556, 泛化性能比较差.
- """用 glove 词向量表示训练模型和评估模型"""
- forest = RandomForestClassifier(oob_score=True,n_estimators = 200, random_state=42)
- forest = forest.fit(train_data_glove, df.sentiment)
- print("\n==================== 评估以 glove 为文本表示训练的模型 ==================\n")
- model_eval(train_data_glove)
三, 后记
之前就用 gensim 训练过中文词向量, 一段时间不用, 连输入格式都忘记了, 这次正好巩固一下.
从上面的结果可以看到, 至少在这个任务中, word2vec 的表现比 glove 要优秀.
来源: https://www.cnblogs.com/Luv-GEM/p/10890010.html