自然語言處理(NLP)——LDA模型:對電商購物評論進行情感分析

一、2020數學建模美賽C題簡介

從提供的亞馬遜電商平臺的商品評價數據中識別關鍵模式、關係、度量和參數。

需求

  1. 以此告知陽光公司在線銷售策略
  2. 識別潛在的重要設計功能,以增強產品的滿意度
  3. 陽光公司對數據基於時間的模式特別感興趣

評價內容

  1. 個人評級,星級評價,1~5分
  2. 評論,文本信息
  3. 幫助評分, 其他用戶對“評論”的作用的評價

提供數據

tsv格式的數據, 如下圖
在這裏插入圖片描述

二、解題思路

使用LDA模型量化評論,再結合其他數據進行下一步數據挖掘。這裏主要討論LDA。

三、LDA簡介

LDA(Latent Dirichlet Allocation)是一種文檔主題生成模型,也稱爲一個三層貝葉斯概率模型,包含詞、主題和文檔三層結構。所謂生成模型,就是說,我們認爲:

  • 一篇文章以一定概率選擇了某個主題
  • 這個主題以一定概率選擇了某個詞語得到。
  • 文檔到主題服從多項式分佈,主題到詞服從多項式分佈。
  • 每一篇文檔代表了一些主題所構成的一個概率分佈,而每一個主題又代表了很多單詞所構成的一個概率分佈。

應用

  • LDA是一種非監督機器學習技術,可以用來識別大規模文檔集(document collection)或語料庫(corpus)中潛藏的主題信息。

使用了詞袋(bag of words)方法

  • 將每一篇文檔視爲一個詞頻向量,從而將文本信息轉化爲了易於建模的數字信息。
  • 但是詞袋方法沒有考慮詞與詞之間的順序,這簡化了問題的複雜性,同時也爲模型的改進提供了契機。

四、代碼實現

代碼頭部全局變量,方便理解後續的代碼:

import re
import nltk
import pandas as pd
from nltk.corpus import stopwords
from nltk.stem.wordnet import WordNetLemmatizer
from gensim import corpora, models

TOPIC_NUM = 1  # 主題數

lmtzr = WordNetLemmatizer()

m_files = [r"..\data\microwave.tsv",
           r"..\data\microwave_lda_1rmv_cols.tsv",
           r"..\data\microwave_lda_2dup_revs.tsv",
           r"..\data\microwave_lda_3rmv_invds.tsv",
           r"..\data\microwave_lda_4pos_revs.txt",
           r"..\data\microwave_lda_5neg_revs.txt",
           r"..\data\microwave_lda_6pos_rev_words.txt",  # 文本進行了處理
           r"..\data\microwave_lda_7neg_rev_words.txt",
           r"..\data\microwave_lda_8pos_topic.tsv",
           r"..\data\microwave_lda_9neg_topic.tsv",
           r"..\data\microwave_lda_10pos_topic_words.txt",
           r"..\data\microwave_lda_11neg_topic_words.txt",
           r"..\data\microwave_lda_12rev_words.tsv",
           r"..\data\microwave_lda_13rev_score.tsv"]

#  停用詞集合
stop_words = set(stopwords.words('english'))
stop_words = [word for word in stop_words if word not in ['not']]
# print(stop_words)
# 自定義停用詞
m_stop_words = ['would', 'br', 'microwave', 'use', 'get', 'old', 'new', 'look', 'work', 'could', 'oven',
                'purchase', 'take', 'make', 'buy', 'go', 'come', 'say', 'not', 'bought', 'even', 'ge',
                'also', 'ca', 'dry']
# 情感分析中重要的詞性
m_tags = ['MD', 'UH', 'VB', 'VBD', 'VBG', 'VBN', 'VBP', 'VBZ', 'RP', 'RB', 'RBR', 'RBS', 'JJ', 'JJR', 'JJS']
# 正則表達式過濾特殊符號用空格符佔位,雙引號、單引號、句點、逗號
pat_letter = re.compile(r'[^a-zA-Z \']+')
# 還原常見縮寫單詞
pat_is = re.compile("(it|he|she|that|this|there|here)(\'s)", re.I)
pat_s = re.compile("(?<=[a-zA-Z])\'s")  # 找出字母后面的字母
pat_s2 = re.compile("(?<=s)\'s?")
pat_not = re.compile("(?<=[a-zA-Z])n\'t")  # not的縮寫
pat_would = re.compile("(?<=[a-zA-Z])\'d")  # would的縮寫
pat_will = re.compile("(?<=[a-zA-Z])\'ll")  # will的縮寫
pat_am = re.compile("(?<=[I|i])\'m")  # am的縮寫
pat_are = re.compile("(?<=[a-zA-Z])\'re")  # are的縮寫
pat_ve = re.compile("(?<=[a-zA-Z])\'ve")  # have的縮寫

然後看下最後調用的函數代碼,瞭解一下順序:

# lda訓練,得到主題詞
def lda_step1():
    remove_cols()  # 剔除多餘列 file[0]->file[1]
    get_dup_revs()  # 獲取重複評論 file[1]->file[2]

def lda_step2():  # 需要查看step1中獲取的重複評論的信息
    invd_list = [1, 2]  # 無效評論的行號
    remvove_invds(*invd_list)  # 剔除無效評論 file[1]->file[1],使用了file[2]
    get_pos_neg_revs()  # 獲取消極、積極評論 file[1]->file[4,5]

def lda_step3():  # lda訓練
    write_selected_words()  # 預處理文本(歸一化,篩選詞性,去停詞表等) file[4]->file[6],file[5]->file[7]
    get_topic_words()  # file[6]->file[8]->file[10],file[7]->file[9]-file[11]

# lda_step1()
# lda_step2()
lda_step3()

1. 數據預處理

1.1剔除無用信息

1.1.1 剔除掉不需要的列
# 剔除冗餘的列
def remove_cols():
    data = pd.read_csv(m_files[0], sep='\t', encoding='utf-8')
    data = data.drop(['marketplace', 'product_category', 'product_parent', 'product_title'], axis=1)  # 剔除了多列
    data.to_csv(m_files[1], sep='\t', encoding='utf-8')
1.1.2 找出無效評論並剔除
  1. 首先找到重複的評論
# 獲取重複的評論
def get_dup_revs():
    m_df = pd.read_csv(m_files[1], index_col=0, sep='\t', encoding='utf-8')
    data_review = m_df['review_body']  # 獲取評論這一列
    # 計算數組有哪些不同的值,並計算每個值有多少個重複值,原值變成了行索引
    dup_df = pd.DataFrame(data_review.value_counts())
    m_review = dup_df.index.values.tolist()  # 獲取評論值列表
    m_num = dup_df['review_body'].values.tolist()  # 獲取原來評論的重複值
    #  新建一個df
    m_review_num = pd.DataFrame([m_review, m_num])
    m_review_num = pd.DataFrame(m_review_num.values.T)  # 轉置
    m_review_num.columns = ['review_body', 'num']
    #  篩選出重複的評論
    m_review_num = m_review_num[m_review_num['num'] > 1]
    m_review_num.to_csv(m_files[2], sep='\t', index=False, header=True, encoding='utf-8')
    # print(m_review_num)

結果:
在這裏插入圖片描述
2. 重複率過高的可能是系統自動評論
第一條可能爲惡意評論:

I received a Danby Microwave for Christmas 2011. Less than 4 months later it stop working I called the Danby 800# and was told what to do. I did this and have not heard anything back. I have attempted numerous times with no success on getting my refund. Loss to my family of $85.00

I will never buy another Danby product or recommend one.

第二條爲系統標記無效評論
其他評論較爲正常
3. 剔除掉被認定爲無參考意義的評論

#  去除無效評論
def remvove_invds(*invd_list):  # 參數爲無效評論在“重複評論”中的行號
    #print("remvove_invds", invd_list)
    m_df = pd.read_csv(m_files[1], sep='\t', encoding='utf-8')

    m_invds = pd.read_csv(m_files[2], sep='\t', encoding='utf-8')
    #print("m_invds",m_invds)
    m_invds = m_invds[m_invds.index.isin(invd_list)]

    m_invd_revs = m_invds['review_body'].values.tolist()
    # print("m_invd_revs:" + m_invd_revs)
    #  篩選出不在無效評論中的
    m_df = m_df[~m_df.review_body.isin(m_invd_revs)]
    m_df.to_csv(m_files[3], sep='\t', index=False, header=True, encoding='utf-8')

1.2 抽取評論

抽取1,2星和4,5星的評論分別作爲消極評論、積極評論的語料

#  抽取1、2,4、5星的評論
def get_pos_neg_revs():
    m_df = pd.read_csv(m_files[3], sep='\t', encoding='utf-8')
    m_neg_df = m_df[m_df.star_rating.isin([1, 2])]
    m_pos_df = m_df[m_df.star_rating.isin([4, 5])]
    m_neg_revs = m_neg_df['review_body']
    m_pos_revs = m_pos_df['review_body']
    m_neg_revs.to_csv(m_files[5], sep='\t', index=False, header=True, encoding='utf-8')
    m_pos_revs.to_csv(m_files[4], sep='\t', index=False, header=True, encoding='utf-8')

1.3 詞形還原

英語中同一個動詞有多種形態,獎其還原成原形

1.4 去除停用詞

去除無參考意義的詞,如:

{'to', 'there', 'nor', 'wouldn', 'shouldn', 'i', 'then', 'you', 'ain', "hasn't", 'she', 'not', 'such', 'those', 'so', 'over', 'the', 'y', 'd', 'most', 'm', 'should', 'both', 'weren', 'from', 'until', 'an', 'my', 'yours', 'in', 'here', 'them', 'have', 'didn', 'against', 'myself', 'of', 'her', 'had', "couldn't", "didn't", 'when', "should've", 'is', 'very', "don't", 'has', 'these', 'will', 're', 'now', "hadn't", 'were', 'again', 'same', 'itself', 'his', 'what', 'him', 'don', "you'll", 'how', 'couldn', 'other', 'doesn', 'out', 'no', 'while', 'your', 'do', 'this', 'if', "shouldn't", 'just', 'aren', 'shan', 'himself', 'on', 'further', 'themselves', 've', 'hers', 't', 'me', 's', 'that', 'and', 'which', 'or', 'our', "won't", 'above', 'off', 'we', "wasn't", "needn't", 'ours', 'who', 'all', 'wasn', 'through', 'be', 'ourselves', 'by', 'during', 'about', "mightn't", 'was', 'yourselves', 'before', 'because', 'ma', 'being', 'more', 'it', 'any', 'll', "weren't", 'between', 'why', 'he', 'herself', 'whom', "wouldn't", 'o', "that'll", "you'd", 'few', 'won', 'once', 'some', 'doing', "aren't", "you've", 'with', 'under', "mustn't", 'too', 'needn', 'isn', 'yourself', "haven't", 'up', 'below', 'am', 'after', "it's", 'as', 'hadn', 'into', 'own', "you're", 'its', 'theirs', 'their', "isn't", "shan't", 'only', 'mightn', 'hasn', 'mustn', 'does', 'a', 'each', 'having', 'haven', 'they', "she's", 'at', 'can', 'but', 'been', 'did', "doesn't", 'down', 'than', 'are', 'for', 'where'}

1.5 篩選詞性

去除掉情感分析中無參考意義的詞性, 保留有參考意義的詞性。
有參考意義的詞性:

m_tags = ['MD', 'UH', 'VB', 'VBD', 'VBG', 'VBN', 'VBP', 'VBZ', 'RP', 'RB', 'RBR', 'RBS', 'JJ', 'JJR', 'JJS']

1.3~1.5代碼

# 從文本抽取單詞
def extract_words(text, debug=False):
    text = replace_abbreviations(text)
    if debug:
        print('去除非字母符號:', text)
    m_words = nltk.word_tokenize(text)  # 分詞
    if debug:
        print('分詞:', m_words)
    m_word_tags = nltk.pos_tag(m_words)  # 獲取單詞詞性
    if debug:
        print('獲取詞性:', m_word_tags)
    m_words = [word for word, tag in m_word_tags if tag in m_tags]  # 過濾詞性
    if debug:
        print('過濾詞性後:', m_words)
    m_words = words_normalize(m_words)  # 歸一化
    if debug:
        print('歸一化後:', m_words)
    m_words = [word for word in m_words if word not in stop_words]  # 過濾停詞表
    m_words = [word for word in m_words if word not in m_stop_words]  # 過濾自定義停詞表
    if debug:
        print('過濾停詞表後:', m_words)
    return m_words

2. 使用LDA模型進行主題分析

抽取1,2星和4,5星的評論分別作爲消極評論、積極評論的語料
分別對兩份語料進行LDA訓練得到主題詞。

get_topics.py:

# 獲取文章主題, 使用預處理後的評論文本(已經進行了歸一化,篩選詞性,去停詞表等操作)
def get_topics2(input_file):
    fr = open(input_file, 'r', encoding='utf-8')
    words_list = []  # 二維單詞列表
    for line in fr.readlines():
        m_words = nltk.word_tokenize(line)
        # m_words = [word for word in m_words if word not in m_stop_words]
        words_list.append(m_words)
    # """構建詞頻矩陣,訓練LDA模型"""
    dictionary = corpora.Dictionary(words_list)
    # corpus[0]: [(0, 1), (1, 1), (2, 1), (3, 1), (4, 1),...]
    # corpus是把每條新聞ID化後的結果,每個元素是新聞中的每個詞語,在字典中的ID和頻率
    corpus = [dictionary.doc2bow(words) for words in words_list]  # text單篇文章
    lda = models.LdaModel(corpus=corpus, id2word=dictionary, num_topics=TOPIC_NUM)  # lda訓練
    topic_list = lda.print_topics(TOPIC_NUM)
    print(len(topic_list), "個主題的單詞分佈爲:\n")
    for topic in topic_list:
        print(topic)
    return topic_list

分析結果:

1 個主題的單詞分佈爲:(積極)
(0, '0.022*"great" + 0.019*"well" + 0.015*"small" + 0.014*"good" + 0.013*"easy" + 0.011*"fit" + 0.010*"love" + 0.010*"need" + 0.009*"little" + 0.008*"much"')

1 個主題的單詞分佈爲:(消極)
(0, '0.014*"replace" + 0.009*"last" + 0.008*"stop" + 0.008*"start" + 0.008*"back" + 0.008*"well" + 0.007*"never" + 0.007*"call" + 0.007*"turn" + 0.007*"open"')

['well', 'small', 'fit', 'good', 'great', 'easy', 'need', 'much', 'little', 'love']

['replace', 'well', 'turn', 'last', 'never', 'call', 'back', 'stop', 'open', 'start']

完整代碼

gitee項目地址:https://gitee.com/Meloor/LDATest
文件目錄:LDA/get_topics.py

附錄

參考博客:https://www.jianshu.com/p/4a0bd8498561

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章