1. 程式人生 > >大眾點評----評論情感分析

大眾點評----評論情感分析

一、資料獲取

1、爬蟲評論一次之後就被遮蔽(好像是網站被一個IP頻繁訪問會讓你輸驗證碼),解決辦法:先試了用代理IP,大眾點評好像不能用代理IP訪問,然後加入了time.sleep(random.uniform(1,10)),讓它訪問不要太頻繁。
2、爬完資料寫入csv檔案亂碼問題:out = codecs.open(’./data/Stu_csv.csv’, ‘a’, encoding=“gbk”)
3、爬下來的評論詳情,顯示亂七八糟:蠻久之座,提周預週末位,都。次國慶,估計隊出遊了,然易到了。發現,大眾點評上的評論是字和圖片混著來的,而且也不能複製。資料不好,後期做出來效果肯定也不好,於是去github上找了個數據集。
4、csv檔案用excel開啟亂碼,原因:csv用UTF-8編碼,而excel預設編碼是ANSI,解決辦法:將csv檔案用TXT開啟,另存為ANSI格式,再用excel開啟就好了。

二、資料預處理

1、資料檢視

import pandas as pd
def loaddata():
    #顯示所有列
    pd.set_option('display.max_columns', None)
    pd.set_option('display.width',200)
    data=pd.read_csv('data/data.csv')
    print(data.head())

結果:
在這裡插入圖片描述
檢視資料集的規模:data.shape:(32483, 14)
也可以檢視資料集的資訊:data.info()
在這裡插入圖片描述
可以看到,沒有缺失值(其實貝葉斯對缺失值沒有很敏感)。
因為是對評論進行情感分析,因此我們用到的資料也就是cus_comment(特徵)和stars(類別)兩列資料,這裡的stars是評論的類別,1、2是差評,3是中評,4、5是好評,我們把1-2記為0,4-5記為1,3為中評,對我們的情感分析作用不大,丟棄掉這部分資料,但是可以用來作為語料。我們的情感評分可以轉化為標籤值為1的概率值,這樣我們就把情感分析問題轉為文字分類問題了。

def getLabel(score): 
    if score > 3: 
        return 1 
    elif score < 3:
        return 0 
    else: 
        return None
def createLabel(data):
    data['target'] = data['stars'].map(lambda x:getLabel(x))
    dataSelect=data[['cus_comment','target']]#選擇評論列和類別列。 
    return dataSelect.
dropna()#刪除掉中評的資料

現在我們還剩21691條資料。
將資料集切分為訓練集和測試集:

from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(data_model['cus_comment'], data_model['target'], random_state=3, test_size=0.25)#random_state是隨機數的種子,不同的種子會造成不同的隨機取樣結果,相同的種子取樣結果相同。
data['target'].value_counts()

輸出:

1.0    14920
0.0     1348

從輸出結果可以看出,訓練集的樣本分佈不平衡。正樣本是14920條,而負樣本只有1348條。

在樣本不均衡的情況下,我們應該選擇合適的評估標準,比如ROC或者F1,而不是準確度(accuracy)。處理樣本不均衡問題的方法,首先可以選擇調整閾值,使得模型對於較少的類別更為敏感,或者另外一種方法就是通過取樣(sampling)來調整資料的不平衡。其中欠取樣拋棄了大部分正例資料,從而弱化了其影響,可能會造成偏差很大的模型,同時,資料總是寶貴的,拋棄資料是很奢侈的。另外一種是過取樣。
這裡我們用過取樣的方式來解決樣本不平衡的問題。但如果是單純複製反例,因此會過分強調已有的反例。如果其中部分點標記錯誤或者是噪音,那麼錯誤也容易被成倍的放大。容易造成對反例的過擬合,因此,我們採用SMOTE演算法來人工合成數據實現過取樣。

後續我們會加入過取樣。

三、特徵工程

接下來對訓練集進行文字特徵處理,首先用jieba分詞進行中文分詞:

def fenci(data):
    data=data.apply(lambda x:' '.join(jieba.cut(x)))
    return data

載入停用詞:

def load_stopwords():
    stop_file=open("data/stopwords.txt", encoding='utf-8')
    stopwords_list=stop_file.readlines()
    stopwords=[x.strip() for x in stopwords_list]
    return stopwords

文字轉向量,有詞庫表示法、TF-IDF、word2vec等, 這裡我們使用sklearn庫的TF-IDF工具進行文字特徵提取:

def feature_extraction(data,stopwords):
    #TF-IDF構建文字向量    sklearn庫中可以指定stopwords
    tf = TfidfVectorizer(stop_words=stopwords, max_features=3000)
    tf.fit(data)
    return tf #返回一個介面卡

四、模型訓練

特徵和標籤準備好之後,接下來就是建模了。這裡我們使用文字分類的經典演算法樸素貝葉斯演算法,而且樸素貝葉斯演算法的計算量較少。特徵值是評論文字經過TF-IDF處理的向量,標籤值評論的分類共兩類,好評是1,差評是0。情感評分為分類器預測分類1的概率值,概率值越大,情感評分越高。

#模型訓練--貝葉斯分類器
def train_model(x_train,y_train,x_test,y_test):
    classifier=MultinomialNB()
    classifier.fit(x_train,y_train)
    print(classifier.score(x_test,y_test))
    return classifier #返回模型

對測試集同樣要進行分詞、去停用詞、轉TF-IDF,然後測試模型的準確率、ROC值:

if __name__ == '__main__':
    data=loaddata()
    data=preprocess(data)
    x_train,x_test,y_train,y_test=train_test_split(data['cus_comment'],data['target'],random_state=3,test_size=0.25)
    stopwords=load_stopwords()
    x_train_fenci=fenci(x_train)
    tf=feature_extraction(x_train_fenci, stopwords)
    model=train_model(tf.transform(x_train_fenci), y_train,tf.transform(fenci(x_test)),y_test)

輸出:

accuracy 0.9302968836437396
roc-score 0.5657246489975608

可以看出來,雖然準確率可以,但是ROC卻很低。
從大眾點評網找兩條評論來測試一下

def predict(model,comment,tf):
    return model.predict_proba(tf.transform(fenci((comment))))# 返回預測屬於某標籤的概率

comment1="一如既往的好。已經快成了陸家嘴上班的我的食堂了。滿減活動非常給力,上次叫了八樣東西,折扣下來居然就六十左右,吃得好爽好爽。南瓜吃過幾次,就一次不夠酥爛,其他幾次都很好。烤麩非常入味,適合上海人。魚香肉絲有點辣,下飯剛好。那個蔬菜每次都點。總體很好吃。"
comment2="糯米外皮不綿滑,豆沙餡粗躁,沒有香甜味。12元一碗不值。"
print(predict(model,pd.Series([comment1]) , tf))
print(predict(model,pd.Series([comment2]), tf))

輸出:

[[0.01838771 0.98161229]]
[[0.17337369 0.82662631]]

predict_proba返回的是一個 n 行 k 列的陣列, 第 i 行 第 j 列上的數值是模型預測 第 i 個預測樣本為某個標籤的概率,並且每一行的概率和為1。

可看到,5星好評模型預測出來了,1星差評卻預測錯誤。這種情況就是樣本不平衡,我們檢視一下混淆矩陣

 y_predict=model.predict(tf.transform(fenci((x_test))))
 print(confusion_matrix(y_test,y_predict))

輸出:

[[  57  374]
 [   4 4988]]

第一行是負樣本,第二行是正樣本,負類樣本中有57個被預測為正類(15%),這是由於資料的不平衡導致的,模型的預設閾值為輸出值的中位數。

SMOTE演算法實現過取樣:
原理:對少量類別的每一個樣本o計算其K近鄰,根據取樣倍率N從其K近鄰中隨機選取N個樣本x,根據公式: x n e w = x + r a n d ( 0 , 1 ) × ( x o ) x_{new}=x+rand(0,1)\times(x-o) 計算新樣本。
程式碼:

import random
from sklearn.neighbors import NearestNeighbors
import numpy as np
class Smote:
    def __init__(self,samples,N=10,k=5):
        self.n_samples,self.n_attrs=samples.shape
        self.N=N
        self.k=k
        self.samples=samples
        self.newindex=0
       # self.synthetic=np.zeros((self.n_samples*N,self.n_attrs))

    def over_sampling(self):
        N=int(self.N/100)
        self.synthetic = np.zeros((self.n_samples * N, self.n_attrs))
        neighbors=NearestNeighbors(n_neighbors=self.k).fit(self.samples)
        print("neighbors",neighbors)
        for i in range(len(self.samples)):
            nnarray=neighbors.kneighbors(self.samples[i].reshape(1,-1),return_distance=False)[0]
            self._populate(N,i,nnarray)
        return self.synthetic


    # for each minority class samples,choose N of the k nearest neighbors and generate N synthetic samples.
    def _populate(self,N,i,nnarray):
        for j in range(N):
            nn=random.randint(0,self.k-1)
            dif=self.samples[nnarray[nn]]-self.samples[i]
            gap=random.random()
            self.synthetic[self.newindex]=self.samples[i]+gap*dif
            self.newindex+=1

訓練集中的樣本分佈:

1.0    14920
0.0     1348

負樣本只有1348條,所以利用SMOTE演算法根據這1348條資料人工合成為原來的10倍,加入到訓練集中進行模型的訓練。

#下面的程式碼是為了解決引用同目錄下py檔案中的類時報錯的問題。
current_dir = os.path.abspath(os.path.dirname(__file__))
sys.path.append(current_dir)
sys.path.append("..")
import smote
#將負樣本中的詞向量傳入SMOTE演算法進行過取樣
x_train_tf=tf.transform(x_train_fenci).toarray()
samples0=[]
for i, label in enumerate(y_train):
     if label==0:
         samples0.append(x_train_tf[i])
s=smote.Smote(np.array(samples0),N=100)
over_samplings_x=s.over_sampling()
#下面是矩陣的合併,組成新的訓練集
total_samplings_x=np.row_stack((x_train_tf,over_samplings_x))
total_samplings_y=np.concatenate((y_train,np.zeros(len(over_samplings_x))),axis=0)

再次訓練模型:

取樣倍率N=100(多一倍)
accuracy 0.9382260741287111
roc-score 0.6410470209411625
[[0.03223564 0.96776436]]
[[0.30446218 0.69553782]]

取樣倍率N=500(多5倍)
accuracy 0.9280840862990964
roc-score 0.7828688314295913
[[0.0898476 0.9101524]]
[[0.59244416 0.40755584]]

取樣倍率N=600(多6倍)
accuracy 0.9203392955928453
roc-score 0.8041004818847046
[[0.08742767 0.91257233]]
[[0.60816768 0.39183232]]

因為取樣倍率再大的話np好像有MemoryError的錯誤,所以先到6倍,但是結果已經優化了好多,ROC值提升了很多,差評也能預測出來。

這個專案基本已經結束,通過SMOTE演算法也實現了不錯的效果。後續優化方向:

使用更復雜的機器學習模型如神經網路、支援向量機等
模型的調參
行業詞庫的構建
增加資料量
優化情感分析的演算法
增加標籤提取等

過程總結:
1、簡單的爬蟲操作,爬取點評網站上的評論資料。
2、怎麼對資料進行分析、預處理。用pandas的info或describe方法或其他方法對資料做統計分析,發現數據的缺失值,樣本的平衡性,並使用過取樣(SMOTE演算法)解決這個問題。
3、特徵處理。這裡就是基本的文字特徵處理:分詞–>去停用詞–>轉向量
4、標籤類別處理。將幾個評分類別轉化為二分類問題。
5、模型訓練。使用sklearn的貝葉斯進行訓練並使用準確率和ROC來評估模型分類效果。
6、使用混淆矩陣檢視預測情況,對模型做出評價。

知識點總結:

  • TF-IDF的引數,呼叫,原理
  • 樸素貝葉斯的原理
  • 樸素貝葉斯的呼叫。
  • smote演算法的基本思想
  • 模型評價指標:PR、ROC、混淆矩陣