1. 程式人生 > >python資料分析:內容資料化運營(中)——基於潛在狄利克雷分配(LDA)的內容主體挖掘

python資料分析:內容資料化運營(中)——基於潛在狄利克雷分配(LDA)的內容主體挖掘

案例背景

本案例是從一堆新聞檔案中建立相應的主題模型,然後得到不同模型的主題特點,並通過對新文字資料集的預測得到其可能的主題分類。

相關知識

TF-IDF

TF-IDF(term frequency–inverse document frequency)是一種針對關鍵字的統計分析方法,用來評估關鍵字或詞語對於文件、語料庫和檔案集合的重要程度。關鍵字的重要程度跟它在文件中出現的次數成正比,但同時跟它出現的頻率呈反比。這種加權的形式能有效避免常用詞對於關鍵字搜尋結果的影響,提高搜尋結果與關鍵字之間的相關性。
TF-IDF的主要思想是:如果某個關鍵字在一篇文件中出現的頻率(TF, Term Frequ-ency)高,並且在其他文件中很少出現,那麼認為該關鍵字具有良好的區分不同文件的能力,也就越重要。

詞性標註

所謂詞性標註是給每個詞語確定一個詞性分類。例如,“我愛Python資料分析與資料化運營”分詞後的結果可能是“我|愛|Python|資料分析|與|資料|化|運營”,其中每個詞都有專屬的分類:

  • 我:代詞
  • 愛:動詞
  • Python:英語
  • 資料分析:習用語
  • 與:介詞
  • 資料:名詞
  • 化:名詞
  • 運營:名動詞,具有名詞功能的動詞

在運營中,有很多場景需要做詞性標註,然後基於標註的詞性可以做進一步應用。例如統計競爭對手新聞稿的主要詞語分佈、分詞結果篩選和過濾、配合文章標籤的提取等。

案例資料

案例資料來自搜狐新聞2012年6月到7月的部分新聞資料,涵蓋了新聞、家居、體育等主題頻道。壓縮包中有10個新聞檔案,每個檔案中包含多條新聞,每條新聞的格式相同,包含新聞URL、頁面ID、頁面標題和頁面內容4部分。以下是檔案內容格式示例:

<doc>
<url>頁面URL</url>
<docno>頁面ID</docno>
<contenttitle>頁面標題</contenttitle>
<content>頁面內容</content>
</doc>

python實現

匯入模組

# tar壓縮包庫
import tarfile
import os
# 帶詞性標註的分詞模組
import jieba.posseg as pseg
# gensim的詞頻統計和主題建模模組
from gensim import
corpora, models # 用於處理xml格式檔案 from bs4 import BeautifulSoup

解壓檔案

#### 解壓縮檔案 #####
if not os.path.exists('./news_data'):  # 如果不存在資料目錄,則先解壓資料檔案
    tar = tarfile.open('news_data.tar.gz')  # 開啟tar.gz壓縮包物件
    names = tar.getnames()  # 獲得壓縮包內的每個檔案物件的名稱
    for name in names:  # 迴圈讀出每個檔案
        tar.extract(name, path='./')  # 將檔案解壓到指定目錄
    tar.close()  # 關閉壓縮包物件

內容彙總

# 全形轉半形
def str_convert(content):
    '''
    將內容中的全形字元,包含英文字母、數字鍵、符號等轉換為半形字元
    :param content: 要轉換的字串內容
    :return: 轉換後的半形字串
    '''
    new_str = ''
    for each_char in content:  # 迴圈讀取每個字元
        code_num = ord(each_char)  # 讀取字元的ASCII值或Unicode值
        if code_num == 12288:  # 全形空格直接轉換
            code_num = 32
        elif (code_num >= 65281 and code_num <= 65374):  # 全形字元(除空格)根據關係轉化
            code_num -= 65248
        new_str += chr(code_num)
    return new_str

# 解析檔案內容
def data_parse(data):
    '''
    從原始檔案中解析出文字內容資料
    :param data: 包含程式碼的原始內容
    :return: 文字中的所有內容,列表型
    '''
    raw_code = BeautifulSoup(data, "lxml")  # 建立BeautifulSoup物件
    content_code = raw_code.find_all('content')  # 從包含文字的程式碼塊中找到content標籤
    content_list = []  # 建立空列表,用來儲存每個content標籤的內容
    for each_content in content_code:  # 迴圈讀出每個content標籤
        if len(each_content) > 0:  # 如果content標籤的內容不為空
            raw_content = each_content.text  # 獲取原始內容字串
            convert_content = str_convert(raw_content)  # 將全形轉換為半形
            content_list.append(convert_content)  # 將content文字內容加入列表
    return content_list

# 彙總所有內容
all_content = []  # 總列表,用於儲存所有檔案的文字內容
for root, dirs, files in os.walk('./news_data'):  # 分別讀取遍歷目錄下的根目錄、子目錄和檔案列表
    for file in files:  # 讀取每個檔案
        file_name = os.path.join(root, file)  # 將目錄路徑與檔名合併為帶有完整路徑的檔名
        with open(file_name, encoding='utf-8') as f:  # 以只讀方式開啟檔案
            data = f.read()  # 讀取檔案內容
        all_content.extend(data_parse(data))  # 從檔案內容中獲取文字並將結果追加到總列表

使用jieba對內容進行分詞

耗時比較長

# 中文分詞
def jieba_cut(text):
    '''
    將輸入的文字句子根據詞性標註做分詞
    :param text: 文字句子,字串型
    :return: 符合規則的分詞結果
    '''
    rule_words = ['z', 'vn', 'v', 't', 'nz', 'nr', 'ns', 'n', 'l', 'i', 'j', 'an',
                  'a']  # 只保留狀態詞、名動詞、動詞、時間詞、其他名詞、人名、地名、名詞、習用語、簡稱略語、成語、形容詞、名形詞
    words = pseg.cut(text)  # 分詞
    seg_list = []  # 列表用於儲存每個檔案的分詞結果
    for word in words:  # 迴圈得到每個分詞
        if word.flag in rule_words:
            seg_list.append(word.word)  # 將分詞追加到列表
    return seg_list

# 獲取每條內容的分詞結果
words_list = []  # 分詞列表,用於儲存所有檔案的分詞結果
for each_content in all_content:  # 迴圈讀出每個文字內容
    words_list.append(list(jieba_cut(each_content)))  # 將檔案內容的分詞結果以列表的形式追加到列表

建立主題模型

通過LDA進行主題建模,分為3個主題

# 文字預處理
def text_pro(words_list, tfidf_object=None, training=True):
    '''
    gensim主題建模預處理過程,包含分詞類別轉字典、生成語料庫和TF-IDF轉換
    :param words_list: 分詞列表,列表型
    :param tfidf_object: TF-IDF模型物件,該物件在訓練階段生成
    :param training: 是否訓練階段,用來針對訓練和預測兩個階段做預處理
    :return: 如果是訓練階段,返回詞典、TF-IDF物件和TF-IDF向量空間資料;如果是預測階段,返回TF-IDF向量空間資料
    '''
    # 分詞列表轉字典
    dic = corpora.Dictionary(words_list)  # 將分詞列表轉換為字典形式
    print ('{:*^60}'.format('token & word mapping review:'))
    for i in range(5):  # 迴圈讀出字典前5條的每個key和value,對應的是索引值和分詞
        print ('token:%s -- word:%s' % (i, dic[i]))
    # 生成語料庫
    corpus = []  # 建立一個用於儲存語料庫的列表
    for words in words_list:  # 讀取每個分詞列表
        corpus.append(dic.doc2bow(words))  # 將每個分詞列表轉換為語料庫詞袋(bag of words)形式的列表
    print ('{:*^60}'.format('bag of words review:'))
    print (corpus[0])  # 列印輸出第一條語料庫
    # TF-IDF轉換
    if training == True:
        tfidf = models.TfidfModel(corpus)  # 建立TF-IDF模型物件
        corpus_tfidf = tfidf[corpus]  # 得到TF-IDF向量稀疏矩陣
        print ('{:*^60}'.format('TF-IDF model review:'))
        for doc in corpus_tfidf:  # 迴圈讀出每個向量
            print (doc ) # 列印第一條向量
            break  # 跳出迴圈
        return dic, corpus_tfidf, tfidf
    else:
        return tfidf_object[corpus]

# 建立主題模型
print ('train topic model...')
dic, corpus_tfidf, tfidf = text_pro(words_list, tfidf_object=None, training=True)  # 訓練集的文字預處理
num_topics = 3  # 設定主題個數
lda = models.LdaModel(corpus_tfidf, id2word=dic, num_topics=num_topics)  # 通過LDA進行主題建模
print ('{:*^60}'.format('topic model review:'))
for i in range(num_topics):  # 輸出每一類主題的結果
    print (lda.print_topic(i))  # 輸出對應主題

結果如下:

****************token & word mapping review:****************
token:0 -- word:仇恨
token:1 -- word:侮辱
token:2 -- word:侵害
token:3 -- word:凶殺
token:4 -- word:危害
********************bag of words review:********************
[(0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (6, 1), (7, 1), (8, 1), (9, 1), (10, 1), (11, 1), (12, 1), (13, 1), (14, 1), (15, 1), (16, 2), (17, 1), (18, 1), (19, 2), (20, 1), (21, 1), (22, 1), (23, 1), (24, 1), (25, 2), (26, 1), (27, 1), (28, 1), (29, 1), (30, 1), (31, 1)]
********************TF-IDF model review:********************
[(0, 0.16762633852828174), (1, 0.16660204914253687), (2, 0.1643986382302142), (3, 0.168282481745965), (4, 0.16197667368712637), (5, 0.14602961468426073), (6, 0.16282320045073903), (7, 0.10154448591145282), (8, 0.12365275311464316), (9, 0.12399080729729553), (10, 0.16703117734810868), (11, 0.163124879458702), (12, 0.16844765669812112), (13, 0.16409043499326897), (14, 0.1662290891913951), (15, 0.1685028172752526), (16, 0.332245916102828), (17, 0.16383481532598135), (18, 0.16681622559479037), (19, 0.30849126342177313), (20, 0.1677351934753784), (21, 0.16778969587205647), (22, 0.15736459689355045), (23, 0.15266091940783724), (24, 0.11609101090194619), (25, 0.2636835311342954), (26, 0.14576561774317554), (27, 0.16762633852828174), (28, 0.16751768276692697), (29, 0.1653853043789113), (30, 0.16501988564410103), (31, 0.16833748902827933)]
********************topic model review:*********************
0.005*"散佈" + 0.004*"民族" + 0.004*"穩定" + 0.004*"標題" + 0.002*"謠言" + 0.002*"賭博" + 0.002*"教唆" + 0.002*"封建迷信" + 0.002*"邪教" + 0.002*"凶殺"
0.002*"比賽" + 0.002*"是" + 0.001*"搜狐" + 0.001*"小區" + 0.001*"有" + 0.001*"編號" + 0.001*"時間" + 0.001*"北京" + 0.001*"歐洲盃" + 0.001*"人"
0.003*"登入" + 0.002*"刪除" + 0.001*"企業" + 0.001*"訪問" + 0.001*"房源" + 0.001*"房價" + 0.001*"存在" + 0.001*"市場" + 0.001*"土地" + 0.001*"個人" 

新文字歸類

#### 新文字歸類###
with open('article.txt', encoding='utf-8') as f:  # 開啟新的文字
    text_new = f.read()  # 讀取文字資料
text_content = data_parse(data)  # 解析新的文字
words_list_new = jieba_cut(text_new)  # 將文字轉換為分詞列表
corpus_tfidf_new = text_pro([words_list_new], tfidf_object=tfidf, training=False)  # 新文字資料集的預處理
corpus_lda_new = lda[corpus_tfidf_new]  # 獲取新的分詞列表(文件)的主題概率分佈
print ('{:*^60}'.format('topic forecast:'))
print (list(corpus_lda_new))

結果如下:

****************token & word mapping review:****************
token:0 -- word:一鳴驚人
token:1 -- word:三劍客
token:2 -- word:上演
token:3 -- word:不敗
token:4 -- word:專業培訓
********************bag of words review:********************
[(0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (5, 3), (6, 1), (7, 1), (8, 1), (9, 1), (10, 2), (11, 1), (12, 1), (13, 1), (14, 1), (15, 2), (16, 2), (17, 1), (18, 1), (19, 3), (20, 1), (21, 1), (22, 1), (23, 1), (24, 1), (25, 1), (26, 1), (27, 2), (28, 3), (29, 2), (30, 1), (31, 1), (32, 1), (33, 1), (34, 1), (35, 2), (36, 1), (37, 2), (38, 1), (39, 1), (40, 2), (41, 2), (42, 1), (43, 1), (44, 1), (45, 1), (46, 1), (47, 1), (48, 1), (49, 1), (50, 2), (51, 1), (52, 1), (53, 1), (54, 2), (55, 3), (56, 1), (57, 1), (58, 1), (59, 2), (60, 1), (61, 1), (62, 2), (63, 1), (64, 1), (65, 1), (66, 1), (67, 1), (68, 1), (69, 1), (70, 2), (71, 1), (72, 1), (73, 4), (74, 1), (75, 1), (76, 1), (77, 7), (78, 5), (79, 2), (80, 1), (81, 1), (82, 1), (83, 1), (84, 1), (85, 1), (86, 1), (87, 1), (88, 1), (89, 4), (90, 4), (91, 1), (92, 1), (93, 7), (94, 1), (95, 1), (96, 2), (97, 3), (98, 1), (99, 2), (100, 2), (101, 1), (102, 1), (103, 1), (104, 1), (105, 1), (106, 8), (107, 1), (108, 3), (109, 1), (110, 1), (111, 3), (112, 2), (113, 1), (114, 1), (115, 1), (116, 2), (117, 1), (118, 1), (119, 1), (120, 7), (121, 2), (122, 4), (123, 1), (124, 1), (125, 1), (126, 1), (127, 1), (128, 1), (129, 17), (130, 1), (131, 4), (132, 1), (133, 1), (134, 1), (135, 1), (136, 1), (137, 1), (138, 1), (139, 2), (140, 2), (141, 2), (142, 1), (143, 1), (144, 1), (145, 2), (146, 1), (147, 2), (148, 1), (149, 2), (150, 1), (151, 1), (152, 1), (153, 1), (154, 1), (155, 4), (156, 1), (157, 1), (158, 2), (159, 2), (160, 1), (161, 1), (162, 5), (163, 1), (164, 2), (165, 1), (166, 6), (167, 1), (168, 1), (169, 1), (170, 1), (171, 2), (172, 1), (173, 1), (174, 1), (175, 1), (176, 2), (177, 1), (178, 1), (179, 3), (180, 1), (181, 1), (182, 1), (183, 3), (184, 1), (185, 1), (186, 2)]
**********************topic forecast:***********************
[[(0, 0.1855535), (1, 0.6704695), (2, 0.14397693)]]

根據概率來看應該歸屬於第二個主題

結論

從主題建模得到的結果看,三個主題是:

  1. 0.005*“散佈” + 0.004*“民族” + 0.004*“穩定” + 0.004*“標題” + 0.002*“謠言” + 0.002*“賭博” + 0.002*“教唆” + 0.002*“封建迷信” + 0.002*“邪教” + 0.002*“凶殺”
  2. 0.002*“比賽” + 0.002*“是” + 0.001*“搜狐” + 0.001*“小區” + 0.001*“有” + 0.001*“編號” + 0.001*“時間” + 0.001*“北京” + 0.001*“歐洲盃” + 0.001*“人”
  3. 0.003*“登入” + 0.002*“刪除” + 0.001*“企業” + 0.001*“訪問” + 0.001*“房源” + 0.001*“房價” + 0.001*“存在” + 0.001*“市場” + 0.001*“土地” + 0.001*“個人”

可見:

  • 主題一中,權重比較高的詞語是散步、民族、穩定、標題等,權重在0.004以上,其次是賭博、教唆等,因此大體可以判斷該主體與政治、社會、新聞等主題相關。
  • 主題二中,權重高的詞語為比賽、是,其次是歐洲盃、編號等因此大體可以判斷該主題是與比賽、體育相關。
  • 主題三中,權重高的詞語有登入、企業、房源、房價之類,因此大體可以判斷該主題是與房地產相關。

對新主題的預測上,通過結果可以發現,該資料集屬於第一、二、三主題的概率是18.6%,67%,14.4%。因此新資料集更偏向於第二主題,即比賽、體育類話題。讀者可檢視原始檔,該檔案確實屬於該內容方向。

案例應用

對於此類以文字挖掘為主的分析類案例,後期的主要應用方向包括:

  • 從各個類別的主題中提取關鍵字,並將關鍵字作為各個主題的SEO關鍵字優化主題;
  • 不斷增加新的文字資料,然後將每次的主題關鍵字做對比,查詢分析新出現的主題關鍵字,建立對於特定主題的分析和後續內容運營機制;
  • 基於歷史主題建模結果做對比,發現各個主題中的新詞、新趨勢和新話題點;
  • 基於主題關鍵字,建立或優化主題頁的自動關鍵字和自動摘要資訊;
  • 基於具有顯著性的關鍵字(較高權重),將所有文章進行重新類別劃分或優化,使主題間的關聯性和話題緊密型更強。

補充

  • 在做預測集應用時,所有的文字預處理過程都需要重新做一遍,但TF-IDF模型不需要重新做,只需要應用訓練好的模型即可。
  • 主題類別的劃分,嚴重依賴於中文分詞的結果。本案例中,只保留具有特定意義的詞語,而“有意義”的認知依賴於具體應用環境;通常情況下,名詞、動詞等型別的詞語在主題提煉中更具有顯著性意義。
  • 新的預測集在做文字預處理時,需要保持跟訓練集相同的格式(此案例中都是巢狀列表)。
  • 如果有大量的文字資料集,可以將訓練好的LDA(以及其他模型)永續性儲存到硬碟,使用save和load方法可以實現。這樣可以避免所有資料物件都儲存在記憶體中,由於資料計算容量的增加導致記憶體溢位等問題。
  • 案例中的結巴分詞模組,對於大文字下的分詞效率比較低。結巴分詞支援並行分詞,它可以將目標文字按行分隔後,把各行文字分配到多個Python程序並行分詞然後歸併結果,從而獲得分詞速度的提升。並行分詞基於Python自帶的multiprocessing模組,但是目前暫不支援Windows。
  • 主題模型中關於主題個數的確定,跟KMeans演算法中的K非常類似,如果是針對新的文字集,在沒有任何先驗經驗的前提下要獲得有意義的主題是比較困難的,這需要讀者通過多次實驗獲的。

參考:

《python資料分析與資料化運營》 宋天龍