1 介紹 此前我們寫了系列文章和notebook,通過分析共詞關系來分析文本的語義聚類。在《社區發現算法Girvan-Newman(GN)是否能應用于共詞矩陣?》,我們嘗試了使用Girvan-Newman算法,期望更直觀地發現語義聚集。但是,我們遇到了難題:長文本中共詞矩陣十分稠密,直接使用Girvan-Newman算法效率太低。本篇作為《對共詞關系求協方差矩陣后是否有更好的社會網絡分析結果?》的延伸,先經過社會網絡圖裁剪以后再進行社區發現,期望能提升分析效果。
在《共詞分析中的共詞關系是怎么得到的?》一篇我們講解了共詞關系描述了什么,同時也提到,如果為了衡量詞與詞之間在文檔中的分布規律是否相似,還有其他一些度量方法。前面,通過《用networkx和python編程可視化分析共詞關系圖》這篇notebook,我們學會了怎樣用社會網絡圖分析和觀察共詞關系,通過《用MST(minimum or maximum spanning tree)算法簡化共詞關系圖》和《設置邊權重閾值裁剪共詞關系圖》學會了兩種簡化圖的方法,這兩種方法提供了兩個不同的視圖去觀察共詞關系。 這些分析同樣的可以用在co-word之外的co-auther, co-cited, co-reference等等社會網絡分析中。用co-word進行演練有個好處:借助于GooSeeker分詞和情感分析軟件,一系列數據集唾手可得,而且可以很有意思地緊跟熱點,想研究二舅就研究二舅,想研究糖水爺爺就研究糖水爺爺。 本notebook準備使用協方差矩陣來描述共詞關系,同共詞矩陣相比,這可以看作是更加細膩的考察,因為通過去中心化計算,詞在文檔中的分布規律不再是非負數描述的,而是有正有負,可以看作是有漲有跌,這樣,如果兩個詞有相同的漲跌,那么他們的協方差就會比較大,同時跌雖然都是負值,兩個負數相乘變成正數,為協方差的最終結果給予正向的貢獻。 2 使用方法 操作順序是: - 在GooSeeker分詞和文本分析軟件上創建文本分析任務并導入包含待分析內容的excel,分析完成后導出選詞矩陣表。選詞矩陣表在NLP和機器學習領域也叫feature matrix
- 將導出的excel表放在本notebook的data/raw文件夾中
- 從頭到尾執行本notebook的單元
注意:GooSeeker發布的每個notebook項目目錄都預先規劃好了,具體參看Jupyter Notebook項目目錄規劃參考。如果要新做一個分析項目,把整個模板目錄拷貝一份給新項目,然后編寫notebook目錄下的ipynb文件。 3 修改歷史 2022-10-20:第一版發布 4 版權說明 本notebook是GooSeeker大數據分析團隊開發的,所分析的源數據是GooSeeker分詞和文本分析軟件生成的,本notebook中的代碼可自由共享使用,包括轉發、復制、修改、用于其他項目中。 5 準備運行環境 5.1 引入需要用到的庫 # -*- coding: utf-8 -*- ? import os import numpy as np import pandas as pd import networkx as nx import matplotlib.pyplot as plt import pylab ? from networkx.algorithms import community ? %xmode Verbose import warnings # 軟件包之間配套時可能會使用過時的接口,把這類告警忽略掉可以讓輸出信息簡練一些 warnings.filterwarnings("ignore", category=DeprecationWarning) # 把RuntimeWarning忽略掉,不然畫圖的時候有太多告警了 warnings.filterwarnings("ignore", category=RuntimeWarning)
5.2 設置中文字體 因為含有中文,plt畫圖會顯示下面的錯誤信息: C:\ProgramData\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:238: RuntimeWarning: Glyph 32993 missing from current font. font.set_text(s, 0.0, flags=flags) 為了防止plt顯示找不到字體的問題,先做如下設置。 參看glyph-23130-missing-from-current-font #plt.rcParams['font.sans-serif']=['SimHei'] # 上面一行在macOS上沒有效果,所以,使用下面的字體 plt.rcParams['font.sans-serif'] = ['Arial Unicode MS'] plt.rcParams['axes.unicode_minus']=False
5.3 常量和配置 在我們發布的一系列Jupyter Notebook中,凡是處理GooSeeker分詞軟件導出的結果文件的,都給各種導出文件起了固定的名字。為了方便大家使用,只要把導出文件放在data/raw文件夾,notebook就會找到導出文件,賦值給對應的文件名變量。下面羅列了可能用到的文件名變量: file_all_word:詞頻表 file_chosen_word: 選詞結果表 file_seg_effect: 分詞效果表 file_word_occurrence_matrix: 選詞矩陣表(是否出現) file_word_frequency_matrix: 文檔詞頻對應矩陣 file_word_document_match: 選詞匹配表 file_co_word_matrix: 共詞矩陣表
pd.set_option('display.width', 1000) # 設置字符顯示寬度 pd.set_option('display.max_rows', None) # 設置顯示最大 # np.set_printoptions(threshold=np.inf) # threshold 指定超過多少使用省略號,np.inf代表無限大 ? # 存原始數據的目錄 raw_data_dir = os.path.join(os.getcwd(), '../../data/raw') # 存處理后的數據的目錄 processed_data_dir = os.path.join(os.getcwd(), '../../data/processed') filename_temp = pd.Series(['詞頻','分詞效果','選詞矩陣','選詞匹配','選詞結果','共詞矩陣']) file_all_word = '' file_seg_effect = '' file_word_occurrence_matrix = '' file_word_frequency_matrix = '' file_word_document_match = '' file_chosen_word = '' file_co_word_matrix = ''
5.4 檢測data\raw目錄下是否有GooSeeker分詞結果表 在本notebook使用選詞矩陣表,不再是共詞矩陣表,因為選詞矩陣表含有更豐富的內容:每個詞在每篇文檔中出現的次數。下面的代碼將檢查data/raw中有沒有這個表,如果沒有會報錯,后面的程序就沒法執行了。 # 0:'詞頻', 1:'分詞效果', 2:'選詞矩陣', 3:'選詞匹配', 4:'選詞結果', 5:'共詞矩陣' print(raw_data_dir + '\r\n') ? for item_filename in os.listdir(raw_data_dir): if filename_temp[2] in item_filename: file_word_frequency_matrix = item_filename continue ? if file_word_frequency_matrix: print("選詞矩陣表:", "data/raw/", file_word_frequency_matrix) else: print("選詞矩陣表:不存在")
輸出結果如下: C:\Users\work\notebook\社區發現算法Girvan-Newman(GN)學習-對比\notebook\eda\../../data/raw 選詞矩陣表: data/raw/ 選詞矩陣-知乎-二舅.xlsx 6 讀取選詞矩陣表并存入矩陣 讀入過程不展開講解,具體參看《共詞分析中的共詞關系是怎么得到的?》 6.1 用pandas dataframe讀入選詞矩陣 df_word_frequency_matrix = pd.read_excel(os.path.join(raw_data_dir, file_word_frequency_matrix)) df_word_frequency_matrix.head(2)

6.2 提取字段名 將用于給graph的node命名 coword_names = df_word_frequency_matrix.columns.values[2:] print("There are ", len(coword_names), " words") coword_names
輸出結果: There are 100 words array(['苦難', '精神', '內耗', '故事', '問題', '社會', '時代', '人生', '時候', '世界', '作者', '殘疾', '中國', '作品', '農村', '城市', '現實', '電影', '命運', '人民', '東西', '底層', '媒體', '文化', '年輕人', '人們', '事情', '感覺', '觀眾', '普通人', '孩子', '經歷', '一生', '價值', '內容', '編劇', '雞湯', '能力', '年代', '時間', '原因', '能量', '意義', '老人', '評論', '勵志', '文案', '村里', '資本', '醫生', '流量', '文藝創作', '國家', '藝術', '個人', '情緒', '內心', '大眾', '朋友', '農民', '母親', '悲劇', '觀點', '大學', '機會', '思想', '殘疾人', '文藝', '壓力', '力量', '角度', '心理', '父母', '分鐘', '方式', '人物', '老師', '環境', '態度', '物質', '關系', '個體', '條件', '歷史', '情況', '群眾', '窮人', '房子', '回村', '本質', '辦法', '官方', '平臺', '想法', '視角', '生命', '熱度', '地方', '醫療', '身體'], dtype=object) 6.3 生成矩陣數據結構 # 使用astype函數對數據類型進行轉換,否則,下面畫圖的時候可能會報錯 array_word_frequence_matrix = df_word_frequency_matrix.values[:, 2:].astype(float) array_word_frequence_matrix

7 求協方差矩陣 在《共詞分析中的共詞關系是怎么得到的?》我們說過,如果選詞矩陣稱為R,那么,RTR(R的轉置乘以R)就是共詞矩陣(這里假定R中只有0和1兩個值表示是否出現這個詞,我們下面的計算所用的不只是1,而是>=1的數值表示詞頻,原理一樣)。如果先把R去中心化得到矩陣B(轉換成mean deviation form),那么,BTB(B的轉置乘以B)就是協方差矩陣。 covariance = np.cov(array_word_frequence_matrix, rowvar = False) covariance

協方差矩陣是一個對稱矩陣,對角線上的值是每個詞的方差(variance),為了畫圖的時候不畫環回的邊,我們把對角線的所有值賦0 np.fill_diagonal(covariance, 0) covariance

8 生成圖并進行探索 8.1 從NumPy數組生成networkx圖 參看networkx文檔,有專門的函數從其他數據結構直接生成graph graph_covariance = nx.from_numpy_array(covariance) print(nx.info(graph_covariance)) #graph_covariance.edges(data=True)
輸出結果: Name: Type: Graph Number of nodes: 100 Number of edges: 4950 Average degree: 99.0000 8.2 給node加上label 對程序代碼的解釋參看《用networkx和python編程可視化分析共詞關系圖》,不再贅述。 coword_labels = {} for idx, node in enumerate(graph_covariance.nodes()): print("idx=", idx, "; node=", node) coword_labels[node] = coword_names[idx] graph_covariance = nx.relabel_nodes(graph_covariance, coword_labels) sorted(graph_covariance)

8.3 畫圖 figure函數的使用方法參看pyplot官網 。其他參考資料: 由于是一個全連接圖,就沒有畫的必要了,下面的代碼都注釋掉了。 #pos = nx.spring_layout(graph_covariance) #plt.figure(1,figsize=(120,120)) #nx.draw(graph_covariance, pos, node_size=10, with_labels=True, font_size=22, font_color="red") #plt.show()
9 定義畫社區圖的函數 # 用這個函數,可以最多畫4種顏色不同的社區 def plot_communities(G, group_array): color_map = [] for node in G: found = False for idx, group in enumerate(group_array): if node in group: found = True if idx == 0: color_map.append('blue') elif idx == 1: color_map.append('green') elif idx == 2: color_map.append('orange') elif idx == 3: color_map.append('red') else: color_map.append('purple') if found == False: color_map.append('black') ? pos = nx.spring_layout(G) plt.figure(1,figsize=(15,15)) #nx.draw(G, pos, node_size=100, font_size=22, node_color=color_map, with_labels=True, font_color='white') nx.draw(G, pos, node_color=color_map, with_labels=True, font_color='white') plt.show()
10 用MST(maximum spanning tree)刪減邊 10.1 MST計算 graph_covariance_mst = nx.maximum_spanning_tree(graph_covariance) print(nx.info(graph_covariance_mst)) # graph_covariance_mst.edges(data=True)
輸出結果: Name: Type: Graph Number of nodes: 100 Number of edges: 99 Average degree: 1.9800 10.2 畫MST后的圖 #pos = nx.circular_layout(graph_covariance_mst) pos = nx.spring_layout(graph_covariance_mst) plt.figure(2,figsize=(15,15)) nx.draw(graph_covariance_mst, pos, node_size=50, with_labels=True, font_size=15, font_color="red") plt.show()

10.3 社區發現 10.3.1 分拆社區 comm_gen = community.girvan_newman(graph_covariance_mst) top_level_co_word = sorted(map(sorted, next(comm_gen))) top_level_co_word

second_level_co_word = sorted(map(sorted, next(comm_gen))) second_level_co_word

third_level_co_word = sorted(map(sorted, next(comm_gen))) third_level_co_word

10.3.2 畫社區圖 前面我們做了三層分解,那么就用這三層分解的結果分別畫圖,以示對比 plot_communities(graph_covariance_mst, top_level_co_word)

plot_communities(graph_covariance_mst, second_level_co_word)

plot_communities(graph_covariance_mst, third_level_co_word)

11 設定閾值刪減邊 11.1 選擇閾值 刪掉多少邊比較好呢?我們先看看這些位置上的邊權重是多少: coword_median = np.median(covariance) coword_median
輸出結果: 0.008842583189465455 coword_max = np.max(covariance) coword_max
輸出結果: 2.4578551843200986 coword_min = np.min(covariance) coword_min
輸出結果: -0.060885718154429454 coword_per10 = np.percentile(covariance, 90) coword_per10
輸出結果: 0.0691976828814191 coword_per2 = np.percentile(covariance, 98) coword_per2
輸出結果: 0.22641354388815757 11.2 刪除權重小于2%分位的邊 我們不挨個嘗試刪減度了,直接實驗重度刪減后的效果 graph_covariance_per2 = graph_covariance.copy() graph_covariance_per2.remove_edges_from([(n1, n2) for n1, n2, w in graph_covariance_per2.edges(data="weight") if w < coword_per2]) pos = nx.spring_layout(graph_covariance_per2) plt.figure(1,figsize=(15,15)) nx.draw(graph_covariance_per2, pos, node_size=10, with_labels=True, font_size=15, font_color="red") plt.show()

11.3 刪除基于2%分位裁剪的圖的孤立點 graph_covariance_per2.remove_nodes_from(list(nx.isolates(graph_covariance_per2))) #pos = nx.circular_layout(graph_covariance_per2) pos = nx.spring_layout(graph_covariance_per2) plt.figure(1,figsize=(15,15)) nx.draw(graph_covariance_per2, pos, node_size=10, with_labels=True, font_size=15, font_color="blue") plt.show()

11.4 社區發現 11.4.1 分拆社區 comm_gen = community.girvan_newman(graph_covariance_per2) top_level_cov_per2 = sorted(map(sorted, next(comm_gen))) top_level_cov_per2

second_level_cov_per2 = sorted(map(sorted, next(comm_gen))) second_level_cov_per2

third_level_cov_per2 = sorted(map(sorted, next(comm_gen))) third_level_cov_per2

11.4.2 畫社區圖 plot_communities(graph_covariance_per2, top_level_cov_per2)

plot_communities(graph_covariance_per2, second_level_cov_per2)

plot_communities(graph_covariance_per2, third_level_cov_per2)

12 總結 本文使用中介中心度作為Girvan-Newman算法的指標,看起來先進行MST計算得到的效果更好。其實不難分析,因為MST得到的是一棵樹,只要裁掉一條邊,就變成了兩棵不連通的樹(樹也是圖)。另外,已經用MST生成了語義骨干,再用Girvan-Newman算法結合上色顯示,僅僅是提升了一下顯示效果。相對來說,將Girvan-Newman算法用于根據閾值裁剪的圖的作用更大,因為即使做了大幅度裁剪,連通關系還是很復雜,使用Girvan-Newman算法可以進一步聚類一下。 13 下載源代碼 點擊下載Jupyter Notebook源代碼:社區發現算法Girvan-Newman(GN)學習-對比.zip |