文本情感分析(二):基於word2vec、glove和fasttext詞向量的文本表示

上一篇博客用詞袋模型,包括詞頻矩陣、Tf-Idf矩陣、LSA和n-gram構造文本特徵,作了Kaggle上的電影評論情感分類題。html

這篇博客仍是關於文本特徵工程的,用詞嵌入的方法來構造文本特徵,也就是用word2vec、glove和fasttext詞向量進行文本表示,訓練隨機森林分類器。app

1、訓練word2vec和fasttext詞向量dom

Kaggle情感分析題給出了三個數據集,一個是帶標籤的訓練集,共25000條評論,一個是測試集,無標籤的,用來作預測並提交結果,這兩個數據集是上一篇文章裏咱們用過的。編輯器

此外還有一個無標籤的數據集,有50000條評論,不用太惋惜了。咱們能夠想到,用無標籤的數據能夠訓練word2vec詞向量,進行詞嵌入。與詞袋模型相比,word2vec詞向量能解決文本表示維度太高的問題,而且把單詞之間的位置信息考慮進去了。或許,用word2vec詞向量進行文本表示,能取得更好的預測結果。函數

另外,咱們也能夠訓練fasttext詞向量。fasttext這個模型就是爲了文本分類而造出來的,詞向量是其副產品,它的結構和word2vec的CBOW模型的結構相似,可是輸入是整篇文本而不是上下文信息,並且用字符級別的n-gram來獲得單詞的詞向量表示,捕捉有相同後綴的詞的語義關聯。性能

gensim中集成了訓練word2vec詞向量和fasttext詞向量的包,用法很是相似。不過貌似gensim中的fasttext包只能用來訓練詞向量,不能用來作fasttext文本分類。學習

首先導入所須要的庫。測試

import os,re import numpy as np import pandas as pd from bs4 import BeautifulSoup from gensim import models

接着讀取有標籤的訓練數據和無標籤的數據,把影評合併到一個列表中。this

"""讀取數據,包括有標籤的和無標籤的數據"""

# 定義讀取數據的函數
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',...]的格式。 # 再次強調,這裏最關鍵,格式不對則無法學習。

如今就能夠輸入進去訓練詞向量了。訓練好以後,有兩種保持模型的方式,一種是把訓練的模型自己保存下來,是一個二進制格式的文件,打開之後看不到單詞和詞向量,可是之後能夠繼續用更多的數據進行訓練,第二種是把單詞和對應的詞向量以txt的格式保存,不能再追加訓練,可是打開後能夠看到單詞和詞向量。下面的代碼以這兩種方式分別保存了模型。

"""打印日誌信息"""

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 = models.Word2Vec(sentences, workers=num_workers, \ size=num_features, min_count = min_word_count, \ window = context, sg=model_) # 保存模型 # 第一種方法保存的文件不能利用文本編輯器查看,可是保存了訓練的所有信息,能夠在讀取後追加訓練 # 後一種方法保存爲word2vec文本格式,可是保存時丟失了詞彙樹等部分信息,不能追加訓練
 model.save(os.path.join('..', 'models', model_name)) model.wv.save_word2vec_format(os.path.join('..','models','word2vec_txt.txt'),binary = False)

加載保存好的模型,並取出詞向量。

# 加載模型,根據保持時的格式不一樣,有兩種加載方式
 model = models.Word2Vec.load(os.path.join('..', 'models', model_name)) model_txt = models.KeyedVectors.load_word2vec_format(os.path.join('..','models','word2vec_txt.txt'),binary = False) # 能夠同時取出一個句子中單詞的詞向量
model.wv[['man','woman','guy']]

檢驗一下模型訓練的效果,查看和 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)]

接着訓練fasttext詞向量,輸入數據的格式要求和word2vec同樣,因此咱們直接用上面的數據開始訓練。

要吐槽一下,儘管聽說fasttext作文本分類賊快,但是訓練詞向量的過程很是慢,感受比word2vec慢多了,內存也時不時顯示99%,嚇死人。

model_name_2 = 'fasttext.model'

print('Training model...') model_2 = models.FastText(sentences, size=num_features, window=context, min_count=min_word_count,\ sg = model_, min_n = 2 , max_n = 3) # 保存模型 # 第一種方法保存的文件不能利用文本編輯器查看,可是保存了訓練的所有信息,能夠在讀取後追加訓練 # 後一種方法保存爲word2vec文本格式,可是保存時丟失了詞彙樹等部分信息,不能追加訓練 model_2.save(os.path.join('..', 'models', model_name_2)) model_2.wv.save_word2vec_format(os.path.join('..','models','fasttext.txt'),binary = False)

 查看和man這個單詞最相關的詞,仍是很是慢。從結果來看,和word2vec大有不一樣,找出來的詞後綴都是man。

model_2.wv.most_similar("man")
[('woman', 0.6353151798248291), ('boman', 0.6015676856040955), ('wolfman', 0.5951900482177734), ('wyman', 0.5888750553131104), ('snowman', 0.5807067155838013), ('madman', 0.5781949162483215), ('gunman', 0.5617127418518066), ('henchman', 0.5536723136901855), ('guffman', 0.5454517006874084), ('kidman', 0.5268094539642334)]

2、用word2vec、glove和fasttext詞向量進行文本表示

好,下面分別用word2vec、glove和fasttext詞向量作電影評論的文本表示,再次訓練隨機森林分類器,看哪一種詞向量的效果更好。

重開了一個jupyter notebook。首先導入所須要的庫。

import os import re import numpy as np import pandas as pd from bs4 import BeautifulSoup from nltk.corpus import stopwords from gensim import models 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詞向量),備用。怕內存受不了,先不加載fasttext詞向量。

"""讀取訓練好的word2vec模型""" model_name_w2v = '300features_40minwords_10context.model' word2vec_embedding = models.Word2Vec.load(os.path.join('..', 'models', model_name_w2v)) """讀取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') elif model == 'fasttext': array = np.asarray([fasttext_embedding[w] for w in words if w in fasttext_embedding],dtype='float32') else: raise ValueError('請輸入:word2vec、glove或fasttext') 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("一、混淆矩陣爲:\n") print(metrics.confusion_matrix(df.sentiment, forest.predict(train_data))) print("\n二、準確率、召回率和F1值爲:\n") print(metrics.classification_report(df.sentiment,forest.predict(train_data))) print("\n三、包外估計爲:\n") print(forest.oob_score_) print("\n四、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)

 

最後用fasttext詞向量表示的樣本訓練分類器。包外估計爲0.81112,比word2vec效果差很多。

del word2vec_embedding del glove_embedding del train_data_word2vec del train_data_glove del forest """讀取訓練好的fasttext模型""" model_name_fast = 'fasttext.model' fasttext_embedding = models.FastText.load(os.path.join('..', 'models', model_name_fast)) """fasttext表示的樣本""" train_data_fasttext = [to_review_vector(text,'fasttext') for text in df['review']] """用fasttext詞向量表示訓練模型和評估模型""" forest = RandomForestClassifier(oob_score=True,n_estimators = 200, random_state=42) forest = forest.fit(train_data_fasttext, df.sentiment) print("\n====================評估以fasttext爲文本表示訓練的模型==================\n") model_eval(train_data_fasttext)

3、後記

以前就用gensim訓練過中文詞向量,一段時間不用,連輸入格式都忘記了,此次正好鞏固一下。

從上面的結果能夠看到,至少在這個任務中,word2vec的表現比glove、fasttext要優秀。

相關文章
相關標籤/搜索