上一篇博客用词袋模型,包括词频矩阵、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, \ window = 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)
重新用word2vec词向量表示的样本训练分类器,进行预测,并保存预测结果。
"""重新用word2vec向量表示的样本训练模型""" forest = RandomForestClassifier(oob_score=True,n_estimators = 200, random_state=42) forest = forest.fit(train_data_word2vec, df.sentiment) del df del train_data_word2vec del train_data_glove """进行预测,并保存预测结果""" df = load_dataset('test') test_data_word2vec = [to_review_vector(text,'word2vec') for text in df['review']] result = forest.predict(test_data_word2vec) output = pd.DataFrame({'id':df.id, 'sentiment':result}) # 保存 output.to_csv(os.path.join('..', 'data', 'word2vec_model.csv'), index=False)
三、后记
之前就用gensim训练过中文词向量,一段时间不用,连输入格式都忘记了,这次正好巩固一下。
从上面的结果可以看到,至少在这个任务中,word2vec的表现比glove要优秀。