大眾點評----評論情感分析
一、資料獲取
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,根據公式:
計算新樣本。
程式碼:
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、混淆矩陣