使用循環(huán)神經網絡(RNN)實現影評情感分類
創(chuàng)新互聯專業(yè)為企業(yè)提供臨沂網站建設、臨沂做網站、臨沂網站設計、臨沂網站制作等企業(yè)網站建設、網頁設計與制作、臨沂企業(yè)網站模板建站服務,十余年臨沂做網站經驗,不只是建網站,更提供有價值的思路和整體網絡服務。作為對循環(huán)神經網絡的實踐,我用循環(huán)神經網絡做了個影評情感的分類,即判斷影評的感情色彩是正面的,還是負面的。
選擇使用RNN來做情感分類,主要是因為影評是一段文字,是序列的,而RNN對序列的支持比較好,能夠“記憶”前文。雖然可以提取特征詞向量,然后交給傳統(tǒng)機器學習模型或全連接神經網絡去做,也能取得很好的效果,但只從端對端的角度來看的話,RNN無疑是最合適的。
以下介紹實現過程。
一、數據預處理
本文中使用的訓練數據集為https://www.cs.cornell.edu/people/pabo/movie-review-data/上的sentence polarity dataset v1.0,包含正負面評論各5331條??梢渣c擊進行下載。
數據下載下來之后需要進行解壓,得到rt-polarity.neg和rt-polarity.pos文件,這兩個文件是Windows-1252編碼的,先將它轉成unicode處理起來會更方便。
補充一下小知識,當我們打開一個文件,發(fā)現亂碼,卻又不知道該文件的編碼是什么的時候,可以使用python的chardet類庫進行判斷,這里的Windows-1252就是使用該類庫檢測出來的。
在數據預處理部分,我們要完成如下處理過程:
1.轉碼
即將文件轉為unicode編碼,方便我們后續(xù)操作。讀取文件,轉換編碼,重新寫入到新文件即可。不存在技術難點。
2.生成詞匯表
讀取訓練文件,提取出所有的單詞,并統(tǒng)計各個單詞出現的次數。為了避免低頻詞的干擾,同時減少模型參數,我們只保留部分高頻詞,比如這里我只保存出現次數前9999個,同時將低頻詞標識符
3.借助詞匯表將影評轉化為詞向量
單詞是沒法直接輸入給模型的,所以我們需要將詞匯表中的每個單詞對應于一個編號,將影評數據轉化成詞向量。方便后面生成詞嵌入矩陣。
4.填充詞向量并轉化為np數組
因為不同評論的長度是不同的,我們要組成batch進行訓練,就需要先將其長度統(tǒng)一。這里我選擇以最長的影評為標準,對其他較短的影評的空白部分進行填充。然后將其轉化成numpy的數組。
5.按比例劃分數據集
按照機器學習的慣例,數據集應被劃分為三份,即訓練集、開發(fā)集和測試集。當然,有時也會只劃分兩份,即只包括訓練集和開發(fā)集。
這里我劃分成三份,訓練集、開發(fā)集和測試集的占比為[0.8,0.1,0.1]。劃分的方式為輪盤賭法,在numpy中可以使用cumsum和searchsorted來簡潔地實現輪盤賭法。
6.打亂數據集,寫入文件
為了取得更好的訓練效果,將數據集隨機打亂。為了保證在訓練和模型調整的過程中訓練集、開發(fā)集、測試集不發(fā)生改變,將三個數據集寫入到文件中,使用的時候從文件中讀取。
下面貼上數據預處理的代碼,注釋寫的很細,就不多說了。
# -*- coding: utf-8 -*- # @Time : 18-3-14 下午2:28 # @Author : AaronJny # @Email : Aaron__7@163.com import sys reload(sys) sys.setdefaultencoding('utf8') import collections import settings import utils import numpy as np def create_vocab(): """ 創(chuàng)建詞匯表,寫入文件中 :return: """ # 存放出現的所有單詞 word_list = [] # 從文件中讀取數據,拆分單詞 with open(settings.NEG_TXT, 'r') as f: f_lines = f.readlines() for line in f_lines: words = line.strip().split() word_list.extend(words) with open(settings.POS_TXT, 'r') as f: f_lines = f.readlines() for line in f_lines: words = line.strip().split() word_list.extend(words) # 統(tǒng)計單詞出現的次數 counter = collections.Counter(word_list) sorted_words = sorted(counter.items(), key=lambda x: x[1], reverse=True) # 選取高頻詞 word_list = [word[0] for word in sorted_words] word_list = [''] + word_list[:settings.VOCAB_SIZE - 1] # 將詞匯表寫入文件中 with open(settings.VOCAB_PATH, 'w') as f: for word in word_list: f.write(word + '\n') def create_vec(txt_path, vec_path): """ 根據詞匯表生成詞向量 :param txt_path: 影評文件路徑 :param vec_path: 輸出詞向量路徑 :return: """ # 獲取單詞到編號的映射 word2id = utils.read_word_to_id_dict() # 將語句轉化成向量 vec = [] with open(txt_path, 'r') as f: f_lines = f.readlines() for line in f_lines: tmp_vec = [str(utils.get_id_by_word(word, word2id)) for word in line.strip().split()] vec.append(tmp_vec) # 寫入文件中 with open(vec_path, 'w') as f: for tmp_vec in vec: f.write(' '.join(tmp_vec) + '\n') def cut_train_dev_test(): """ 使用輪盤賭法,劃分訓練集、開發(fā)集和測試集 打亂,并寫入不同文件中 :return: """ # 三個位置分別存放訓練、開發(fā)、測試 data = [[], [], []] labels = [[], [], []] # 累加概率 rate [0.8,0.1,0.1] cumsum_rate [0.8,0.9,1.0] rate = np.array([settings.TRAIN_RATE, settings.DEV_RATE, settings.TEST_RATE]) cumsum_rate = np.cumsum(rate) # 使用輪盤賭法劃分數據集 with open(settings.POS_VEC, 'r') as f: f_lines = f.readlines() for line in f_lines: tmp_data = [int(word) for word in line.strip().split()] tmp_label = [1, ] index = int(np.searchsorted(cumsum_rate, np.random.rand(1) * 1.0)) data[index].append(tmp_data) labels[index].append(tmp_label) with open(settings.NEG_VEC, 'r') as f: f_lines = f.readlines() for line in f_lines: tmp_data = [int(word) for word in line.strip().split()] tmp_label = [0, ] index = int(np.searchsorted(cumsum_rate, np.random.rand(1) * 1.0)) data[index].append(tmp_data) labels[index].append(tmp_label) # 計算一下實際上分割出來的比例 print '最終分割比例', np.array([map(len, data)], dtype=np.float32) / sum(map(len, data)) # 打亂數據,寫入到文件中 shuffle_data(data[0], labels[0], settings.TRAIN_DATA) shuffle_data(data[1], labels[1], settings.DEV_DATA) shuffle_data(data[2], labels[2], settings.TEST_DATA) def shuffle_data(x, y, path): """ 填充數據,生成np數組 打亂數據,寫入文件中 :param x: 數據 :param y: 標簽 :param path: 保存路徑 :return: """ # 計算影評的大長度 maxlen = max(map(len, x)) # 填充數據 data = np.zeros([len(x), maxlen], dtype=np.int32) for row in range(len(x)): data[row, :len(x[row])] = x[row] label = np.array(y) # 打亂數據 state = np.random.get_state() np.random.shuffle(data) np.random.set_state(state) np.random.shuffle(label) # 保存數據 np.save(path + '_data', data) np.save(path + '_labels', label) def decode_file(infile, outfile): """ 將文件的編碼從'Windows-1252'轉為Unicode :param infile: 輸入文件路徑 :param outfile: 輸出文件路徑 :return: """ with open(infile, 'r') as f: txt = f.read().decode('Windows-1252') with open(outfile, 'w') as f: f.write(txt) if __name__ == '__main__': # 解碼文件 decode_file(settings.ORIGIN_POS, settings.POS_TXT) decode_file(settings.ORIGIN_NEG, settings.NEG_TXT) # 創(chuàng)建詞匯表 create_vocab() # 生成詞向量 create_vec(settings.NEG_TXT, settings.NEG_VEC) create_vec(settings.POS_TXT, settings.POS_VEC) # 劃分數據集 cut_train_dev_test()
另外有需要云服務器可以了解下創(chuàng)新互聯scvps.cn,海內外云服務器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務器、裸金屬服務器、高防服務器、香港服務器、美國服務器、虛擬主機、免備案服務器”等云主機租用服務以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應用場景需求。