1. 程式人生 > >情感分析系統(預測使用者評論積極或消極的概率)

情感分析系統(預測使用者評論積極或消極的概率)

1.資料預處理

本部分將要完成資料的預處理過程,包括資料的讀取,資料清洗,分詞,以及把文字轉換成tf-idf向量。在接下來的任務中,正面的情感我們標記為1, 負面的情感我們標記成0。

import re
import jieba
import numpy as np

def process_line(line):   
    new_line = re.sub('([a-zA-Z0-9])','',line)
    new_line = ''.join(e for e in new_line if e.isalnum())
    new_line = ','.join(jieba.cut(new_line))
    return new_line
    
def process_train(file_path):
    comments = []  # 用來儲存評論
    labels = []    # 用來儲存標籤(正/負),如果是train_positive.txt,則所有標籤為1, 否則0. 
    with open(file_path) as file:
        # TODO 提取每一個評論,然後利用process_line函式來做處理,並新增到comments。
        text = file.read().replace(' ','').replace('\n','')
        reg = '<reviewid=.*?</review>'
        result = re.findall(reg,text)
        for r in result:
            r = process_line(r)
            comments.append(r)
            if file_path == 'train.positive.txt':
                labels.append('1')
            else:
                labels.append('0')
    return comments, labels
    
    
def process_test(file_path):
    comments = []  # 用來儲存評論
    labels = []    # 用來儲存標籤(正/負).
    with open(file_path) as file:
        # TODO 提取每一個評論,然後利用process_line函式來做處理,並新增到
        # comments。
        text = file.read().replace(' ','').replace('\n','')
        reg = '<reviewid=.*?</review>'
        result = re.findall(reg,text)
        for r in result:
            
            label = re.findall('label="(\d)"',r)[0]
            labels.append(label)
            r = process_line(r)
            comments.append(r)
    return comments, labels
    

def read_file():
    """
    讀取所提供的.txt檔案,並把內容處理之後寫到list裡面。 這裡需要分別處理四個檔案,“train_positive.txt", "train_negative.txt",
    "test_combined.txt" 並把每一個檔案裡的內容儲存成列表。 
    """
    # 處理訓練資料,這兩個檔案的格式相同,請指定訓練檔案的路徑
    train_pos_comments, train_pos_labels = process_train("train.positive.txt")
    train_neg_comments, train_neg_labels = process_train("train.negative.txt")
    
    # TODO: train_pos_comments和train_neg_comments合併成train_comments, train_pos_labels和train_neg_labels合併成train_labels
    train_comments = train_pos_comments + train_pos_labels
    train_labels = train_pos_labels + train_neg_labels
    # 處理測試資料, 請指定測試檔案的路徑
    test_comments, test_labels = process_test("test.combined.txt")
    
    return train_comments, train_labels, test_comments, test_labels

讀取文字並校驗資料長度

# 讀取資料,並對文字進行處理
train_comments, train_labels, test_comments, test_labels = read_file()

# 檢視訓練資料與測試資料大小
print (len(train_comments), len(train_labels), len(test_comments), len(test_labels))

把每一個文字內容轉換成tf-idf向量

from sklearn.feature_extraction.text import TfidfVectorizer  # 匯入sklearn庫
# TODO: 利用TfidfVectorizer把train_comments轉換成tf-idf,把結果儲存在X_train, 這裡X_train是稀疏矩陣(Sparse Matrix) 
# 並把train_labels轉換成向量 y_train. 類似的,去建立X_test, y_test。 把文字轉換成tf-idf過程請參考TfidfVectorizer的說明
tfid_vec = TfidfVectorizer()
X_train = tfid_vec.fit_transform(train_comments)
y_train = np.array(train_labels)
X_test = tfid_vec.transform(test_comments)
y_test = np.array(test_labels)
# 檢視每個矩陣,向量的大小, 保證X_train和y_train, X_test和y_test的長度是一樣的。
print (np.shape(X_train), np.shape(y_train), np.shape(X_test), np.shape(y_test))

第二部分: 利用邏輯迴歸模型搭建情感分析引擎

在本部分將會利用羅迴歸模型(logistic regressiion)來搭建情感分析引擎。

from sklearn.linear_model import LogisticRegression
# TODO: 初始化模型model,並利用模型的fit函式來做訓練,暫時用預設的設定。
lr = LogisticRegression().fit(X_train,y_train)
# 列印在訓練資料上的準確率
print ("訓練資料上的準確率為:" + str(lr.score(X_train, y_train)))

# 列印在測試資料上的準確率
print ("測試資料上的準確率為: " + str(lr.score(X_test, y_test)))

# TODO: 利用自己提出的例子來做測試。隨意指定一個評論,接著利用process_line來做預處理,再利用之前構建好的TfidfVectorizer來把文字轉換
# 成tf-idf向量, 然後再利用構建好的model做預測(model.predict函式)
test_comment1 = "這個很好"
test_comment2 = "垃圾"
test_comment3 = "評論區說不爛都是騙人的,超讚"

a = []
a.append(process_line(test_comment1))
print(lr.predict(tfid_vec.transform(a)))

列印結果如下,能看到測試評論“這個很好”的預測值為1,即“積極”
在這裡插入圖片描述

第三部分: 利用決策樹,神經網路,SVM來訓練模型。

決策樹模型
from sklearn import tree
# TODO: 初始化決策樹模型,並利用模型的fit函式來做訓練並列印在訓練和測試資料上的準確率,利用決策樹預設的引數設定
dtc1 = tree.DecisionTreeClassifier().fit(X_train,y_train)
# 列印在訓練資料上的準確率
print ("訓練資料上的準確率為:" + str(dtc1.score(X_train, y_train)))

# 列印在測試資料上的準確率
print ("測試資料上的準確率為: " + str(dtc1.score(X_test, y_test)))

# TODO: 初始化決策樹模型,並利用模型的fit函式來做訓練並列印在訓練和測試資料上的準確率,設定max_depth引數為3
dtc2 = tree.DecisionTreeClassifier(max_depth=3).fit(X_train,y_train)
# 列印在訓練資料上的準確率
print ("訓練資料上的準確率為:" + str(dtc2.score(X_train, y_train)))

# 列印在測試資料上的準確率
print ("測試資料上的準確率為: " + str(dtc2.score(X_test, y_test)))

# TODO: 初始化決策樹模型,並利用模型的fit函式來做訓練並列印在訓練和測試資料上的準確率,設定max_depth引數為5
dtc3 = tree.DecisionTreeClassifier(max_depth=5).fit(X_train,y_train)
# 列印在訓練資料上的準確率
print ("訓練資料上的準確率為:" + str(dtc3.score(X_train, y_train)))

# 列印在測試資料上的準確率
print ("測試資料上的準確率為: " + str(dtc3.score(X_test, y_test)))
支援向量機(SMV)模型
from sklearn import svm

# TODO: 初始化SVM模型,並利用模型的fit函式來做訓練並列印在訓練和測試資料上的準確率,SVM模型的kernel設定成“rbf”核函式
svc = svm.SVC(kernel='rbf').fit(X_train,y_train)
# 列印在訓練資料上的準確率
print ("訓練資料上的準確率為:" + str(svc.score(X_train, y_train)))

# 列印在測試資料上的準確率
print ("測試資料上的準確率為: " + str(svc.score(X_test, y_test)))
線性支援向量機(LinearSVM)
from sklearn.svm import LinearSVC

# TODO: 初始化LinearSVC模型,並利用模型的fit函式來做訓練並列印在訓練和測試資料上的準確率,使用模型的預設引數。
clf = LinearSVC()
clf.fit(X_train,y_train)
# 列印在訓練資料上的準確率
print ("訓練資料上的準確率為:" + str(clf.score(X_train, y_train)))

# 列印在測試資料上的準確率
print ("測試資料上的準確率為: " + str(clf.score(X_test, y_test)))
神經網路模型
from sklearn.neural_network import MLPClassifier

# TODO: 初始化MLPClassifier模型,並利用模型的fit函式來做訓練並列印在訓練和測試資料上的準確率,設定為hidden_layer_sizes為100,
# 並使用"lbfgs" solver
mlp = MLPClassifier(solver='lbfgs',hidden_layer_sizes=100)
mlp.fit(X_train,y_train)
# 列印在訓練資料上的準確率
print ("訓練資料上的準確率為:" + str(mlp.score(X_train, y_train)))

# 列印在測試資料上的準確率
print ("測試資料上的準確率為: " + str(mlp.score(X_test, y_test)))

第四部分: 通過交叉驗證找出最好的超引數

呼叫一個sklearn模型本身很簡單,只需要2行程式碼即可以完成所需要的操作。但這裡的關鍵點在於怎麼去尋找最優的超引數(hyperparameter)。 比如對於邏輯迴歸
來說,我們可以設定一些引數的值如“penalty”, C等等,這些我們可以理解成是超引數。通常情況下,超引數對於整個模型的效果有著舉足輕重的作用,這就意味著
我們需要一種方式起來找到一個比較合適的引數。其中一個最常用的方法是grid search, 也就在一個去區間裡面做搜尋,然後找到最優的那個引數值。

舉個例子,對於邏輯迴歸模型,它擁有一個超引數叫做C,在文件裡面解釋叫做“Inverse of regularization strength“, 就是正則的權重,而且這種權重的取值
範圍可以認為通常是(0.01, 1000)區間。這時候,通過grid search的方式我們依次可以嘗試 0.01, 0.1, 1, 10, 100, 1000 這些值,然後找出使得
模型的準確率最高的引數。當然,如果計算條件資源允許的話,可以嘗試更多的值,比如0.01,0.05,0.1, 0.5, 1, 5, 10 …。 當我們嘗試越多值的時候,找到
最優引數的概率就會越大。

邏輯迴歸模型
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import KFold
params_c = np.logspace(-3,3,7)   # 對於引數 “C”,嘗試幾個不同的值
best_c = params_c[0]  # 儲存最好的C值
best_acc = 0
kf = KFold(n_splits=5,shuffle=False)
for c in params_c:
    # TODO: 編寫交叉驗證的過程,對於每一個c值,計算出在驗證集中的平均準確率。 在這裡,我們做5-fold交叉驗證。也就是,每一次把20%
    #   的資料作為驗證集來對待,然後準確率為五次的平均值。我們把這個準確率命名為 acc_avg
    avg = 0
    for train_index, test_index in kf.split(X_train):
        lr = LogisticRegression(C=c).fit(X_train[train_index],y_train[train_index])
        avg += lr.score(X_train[test_index],y_train[test_index])
    acc_avg = avg/5
    if acc_avg > best_acc:
        best_acc = acc_avg
        best_c = c

print ("最好的引數C值為: %f" % (best_c))
# TODO 我們需要在整個訓練資料上重新訓練模型,但這次利用最好的引數best_c值
#     提示: model = LogisticRegression(C=best_c).fit(X_train, y_train)
lr = LogisticRegression(C=best_c).fit(X_train, y_train)

# 列印在訓練資料上的準確率
print ("訓練資料上的準確率為:" + str(lr.score(X_train, y_train)))

# 列印在測試資料上的準確率
print ("測試資料上的準確率為: " + str(lr.score(X_test, y_test)))
神經網路模型

注意:這個模型訓練時間較久,在做交叉驗證的情況下計算時間可能長達幾十個小時

from sklearn.neural_network import MLPClassifier
import numpy as np

param_hidden_layer_sizes = np.linspace(10, 200, 20)  # 針對引數 “hidden_layer_sizes”, 嘗試幾個不同的值
param_alphas = np.logspace(-4,1,6)  # 對於引數 "alpha", 嘗試幾個不同的值

best_hidden_layer_size = param_hidden_layer_sizes[0]
best_alpha = param_alphas[0]

for size in param_hidden_layer_sizes:
    for val in param_alphas:
        # TODO 編寫交叉驗證的過程,需要做5-fold交叉驗證。
        avg = 0
        for train_index, test_index in kf.split(X_train, y_train):
            mlp = MLPClassifier(alpha=int(val),hidden_layer_sizes=int(size))
            mlp.fit(X_train[train_index],y_train[train_index])
            avg += mlp.score(X_train[test_index],y_train[test_index])
        acc_avg = avg/5
        if acc_avg > best_acc:
            best_acc = acc_avg
            best_hidden_layer_size = size
            best_alpha = val

print ("最好的引數hidden_layer_size值為: %f" % (best_hidden_layer_size))
print ("最好的引數alpha值為: %f" % (best_alpha))

# TODO 我們需要在整個訓練資料上重新訓練模型,但這次使用最好的引數hidden_layer_size和best_alpha
mlp = MLPClassifier(alpha=best_alpha,hidden_layer_sizes=best_hidden_layer_size).fit(X_train,y_train)
# 列印在訓練資料上的準確率
print ("訓練資料上的準確率為:" + str(mlp.score(X_train, y_train)))

# 列印在測試資料上的準確率
print ("測試資料上的準確率為: " + str(mlp.score(X_test, y_test)))            

完整程式碼及訓練資料已上傳至github,點選此處可直接檢視,有疑問的同學請提issues或部落格下方留言~