前言

當前nlp任務中關於信息抽取,主要集中在關鍵詞的抽取,一般基於TF-IDF的關鍵詞抽取、基於TextRank的關鍵詞抽取、基於Word2Vec詞聚類的關鍵詞抽取,以及多種演算法相融合的關鍵詞抽取等。這裡我們探討一個另一類nlp任務,技能詞抽取或者說領域詞抽取,很少有文章涉及相關並給出代碼。這裡我將結合這類nlp任務進行代碼實踐。這裡的實例將通過爬去的職位jd來抽取其中的技能詞。

方法

領域詞或者技能詞抽取常用的方法:互信息,文檔頻率,信息增益,卡方檢驗等。 這裡重點介紹兩種方法:卡方檢驗和信息增益來提取特徵詞。

原理

卡方檢驗的數學原理:卡方越大,則特徵越重要。

按照卡方的定義,若隨機變數 x_1,x_2,...,x_n相互獨立,且隨機變數 x_i 服從正態分布,則定義隨機變數 Q=sum_{i=0}^{n}{x_i} ,其分布服從 chi^{2} 分布。則當原假設(變數之間相互獨立)是正確的時候,卡方值越趨近於0,反之卡方越大,則拒絕原假設,變數相關。

由於實際當中數據的離散形式,原假設 H_{0} 為真,可認為樣本落入某個區間的頻率與概率應很接近,這裡考慮皮爾遜檢驗統計量:

chi^{2}=sum_{i=0}^{n}{frac{(f_{i}-np_{i})^{2}}{np_{i}}}

和其他文章的實例類似,我們統計出如下的數據,現在來設定原假設:深度學習這個技能詞與職位jd自然語言處理工程師不相關。

對於表格第一行第一列50(A),現在計算期望值 E_{00},注意到數字50表示包含深度學習的JD中有50個JD為"自然語言處理工程師",那麼對應的期望可理解為JD為"自然語言處理工程師"中有多少是包含深度學習的。

因此frac{A+B}{N} 表示所有JD數據中包含深度學習的頻率,若不相關,則 (A+C)frac{(A+B)}{N} 應該是自然語言處理工程師包含深度學習的期望值。

從而 N_{00}=frac{(A-E_{00})^2}{E_{00}} ,.

類似的理解可以計算出

N_{01}=frac{(A-E_{01})^2}{E_{01}} ,

N_{10}=frac{(A-E_{10})^2}{E_{10}} ,

N_{11}=frac{(A-E_{11})^2}{E_{11}}

最終可以計算出"深度學習"和"自然語言處理工程師"的卡方值:

chi^{2}=N_{00}+N_{01}+N_{10}+N_{11}=frac{N(AD?BC)^2}{(A+C)(A+B)(B+D)(C+D)} .

步驟如下:

  1. 從mysql中讀取各職位jd的數據表。
  2. 載入停用詞表(中文停用詞表.txt,哈工大停用詞表,txt四川大學機器智能實驗室停用詞庫.txt,百度停用詞表.txt)。
  3. 使用jieba進行分詞處理。
  4. 統計所有的單詞數、每個類別下的每一個單詞數以及每個類別包含的單詞數。
  5. 計算每一個類別每一個單詞的卡方。

給出關鍵代碼:

# 統計每一類別下單詞的數量
categories = ["nlp", "deep_learing", "kg", "machine_learning", "data_mining"]
words_count = dict([(c, {}) for c in categories])
# 統計所有的單詞數量情況
total_word_count = {}
for c in categories:
if c == "nlp":
for ele in nlp_job_desc_clean:
for word in ele:
words_count[c].setdefault(word, 0)
words_count[c][word] += 1
total_word_count.setdefault(word, 0)
total_word_count[word] += 1

if c == "deep_learing":
for ele in dl_job_desc_clean:
for word in ele:
words_count[c].setdefault(word, 0)
words_count[c][word] += 1
total_word_count.setdefault(word, 0)
total_word_count[word] += 1

if c == "kg":
for ele in kg_job_desc_clean:
for word in ele:
words_count[c].setdefault(word, 0)
words_count[c][word] += 1
total_word_count.setdefault(word, 0)
total_word_count[word] += 1

if c == "machine_learning":
for ele in ml_job_desc_clean:
for word in ele:
words_count[c].setdefault(word, 0)
words_count[c][word] += 1
total_word_count.setdefault(word, 0)
total_word_count[word] += 1

if c == "data_mining":
for ele in dm_job_desc_clean:
for word in ele:
words_count[c].setdefault(word, 0)
words_count[c][word] += 1
total_word_count.setdefault(word, 0)
total_word_count[word] += 1
# 計算每一類別下包含詞的數量
total_words_by_category = {}
for category in categories:
total_words_by_category[category] = sum(words_count[category].values())
# 計算每一個類別每一個單詞的卡方
chi_sq_by_category = dict([(c, {}) for c in categories])
for word in total_word_count.keys():
for category in categories:
# N11:表某一類別下包含某個詞的數量
N11 = words_count[category].get(word, 0)
# N10:表示其他類別包含某個詞的數量
N10 = total_word_count[word] - N11
# N01:表示此類別不包含這個詞的單詞數量
N01 = total_words_by_category[category] - N11
# N00:表示其他類別不包含這個單詞的單詞總數
N00 = sum(total_words_by_category[category] - N11 for c in categories if c != category)
N = N11 + N10 + N01 + N00
chi_sq = 1.0*N*(N11*N00-N10*N01)**2/((N11+N10)*(N11+N01)*(N00+N01)*(N00+N10))
chi_sq_by_category[category][word] = chi_sq
# 按照卡方的大小排序輸出到本地文件
with open("category_words.csv", "w", encoding="utf-8") as f:
writer = csv.writer(f)
for category in categories:
word_chi_list = list(reversed(sorted(chi_sq_by_category[category].items(), key= lambda x:x[1])))[:1000]
word_chi_cat = []
for word, chi in word_chi_list:
word_chi_cat.append([word, chi, category])
writer.writerows(word_chi_cat)

結果部分輸出如下:

數據,1934.6760870027745,nlp
自然語言,1464.8865979301315,nlp
NLP,1388.0876278390624,nlp
數據挖掘,1271.120149134985,nlp
文本,1203.2663612466956,nlp
分詞,1110.2437471563965,nlp
語義,1107.6088372648412,nlp
處理,893.8005019396106,nlp
數據挖掘,894.425714802237,machine_learning
分析,849.364072365675,machine_learning
經驗,685.4235591202735,machine_learning
數據,663.6835255954395,machine_learning
熟悉,564.9863796044659,machine_learning
工作,536.3657771984433,machine_learning
業務,525.0699906150004,machine_learning
Wecash,523.4344527862742,machine_learning
數據分析,479.5537323258305,machine_learning
挖掘,453.5459692097794,machine_learning
開發,450.23639496658603,machine_learning

信息增益的數學原理:信息增益越大,則特徵越重要。

對於一個隨機變數 X ,若取值為 x_{1},x_{2},...,x_{n} ,且相應的概率為 P_1,P_2,...,P_n,則 X 的信息熵定義為:

H(X)=-sum_{i=1}^{n}{P_{i}}*log_2P_i

衡量的是一個隨機變數取值越多,則包含的信息量就越大。

而信息增益衡量的是一個特徵詞對於JD的穩點程度,或者說是對類別(職位jd)的重要程度或者相關程度。即有沒有這個詞,對JD的影響度。即:

IG(C,f_{i})=H(C)-H(C|,f_{i}) 其中 H(C|,f_{i}) 為條件熵。

=-sum_{k=1}^{n}P({C_k})*log_2P({C_k})+P(ar{f_i})sum_{k=1}^{n}P(C_k|ar{f_i})log_2P(C_k|ar{f_i})+P({f_i})sum_{k=1}^{n}P(C_k|{f_i})log_2P(C_k|{f_i})

說明:

P(ar{f_i}) 表示所有文檔中沒有出現特徵 f_i 的頻率。

P(C_k|ar{f_i}) 表示第 k 類文檔中沒有出現特徵 f_i 在所有文檔中不出現特徵 f_i 的頻率。

P({f_i}) 表示所有文檔中出現特徵 f_i 的頻率。

P(C_k|{f_i}) 表示第 k 類文檔中出現特徵 f_i 在所有文檔中出現特徵 f_i 的頻率。

步驟如下:

  1. 這裡的數據處理階段相比上述進行了優化。對文檔分詞之後的word進行了去重操作。
  2. 構建辭彙表,方便構建映射,加快處理的速度。
  3. 統計詞頻,過濾低詞頻的word。
  4. 計算信息增益。

# 開始計算信息增益
def info_gain(x, y, k=None):
total_num = len(y)
num_class = {}
num_pos_per_class = {}
num_neg_per_class = {}
for xi, yi in zip(x, y):
num_class[yi] = num_class.get(yi, 0) + 1
for index, xii in enumerate(xi):
if index not in num_pos_per_class:
num_pos_per_class[index] = {}
num_neg_per_class[index] = {}
if yi not in num_pos_per_class[index]:
num_pos_per_class[index][yi] = 0
num_neg_per_class[index][yi] = 0
if xii != 0:
num_pos_per_class[index][yi] = num_pos_per_class[index].get(yi) + 1
else:
num_neg_per_class[index][yi] = num_neg_per_class[index].get(yi) + 1
# 上述就計算出來每一個辭彙在每一類的分布情況

# 對每一個單詞在對多個類別的數據進行加總
num_pos_word = {}
for w, dic in num_pos_per_class.items():
num_pos_word[w] = sum(num_pos_per_class[w].values())
num_neg_word = dict([(w, total_num - num) for w, num in num_pos_word.items()])

# 計算熵,先計算關於類別的熵值
HD = 0
for c, num in num_class.items():
p = float(num)/total_num
HD = HD - p*math.log(p, 2)

# 開始計算每個詞的信息增益
IG = {}
for w in num_pos_per_class.keys():
POS = 0
for yi, num in num_pos_per_class[w].items():
p = (float(num)+0.00001)/ (num_pos_word[w] + 0.00001*total_num)
POS = POS - p*math.log(p, 2)

NEG = 0
for yi, num in num_neg_per_class[w].items():
p = (float(num)+0.00001)/ (num_neg_word[w] + 0.00001*total_num)
NEG = NEG - p*math.log(p, 2)

p = float(num_pos_word[w])/total_num
IG[w] = round(HD - p*POS - (1 - p)*NEG, 4)
IG = sorted(IG.items(), key=lambda x:x[1], reverse=True)
if k == None:
return IG
else:
return IG[:k]

部分結果如下:

[自然語言, 分詞, 深度, 數據, nlp, 語義, 處理, 識別, 數據挖掘, 實體, 視覺, 數據分析, caffe, 詞性, 文本, 領域, 機器, sql, 圖譜, 中文, 情感, 問答, 學習, 圖像處理, cuda, cnn, 對話, 建模, 圖像, tensorflow, 業務, 模式識別, 標註, 論文, hive, hadoop, theano, 命名, 檢測, 發表, 新詞, sas, 用戶, 運營, 資料庫, spss, 數據倉庫, rnn, 行為, 挖掘, 句法分析, 統計學, 語音, 自動, 人工智慧, 基礎理論, 抽取, 知識, 碩士, torch, 會議, 機器翻譯, 物體, 圖像識別, 數據模型, 人臉, 研發, 研究, 知識庫,

總結:這裡提取技能詞分別使用了卡方檢驗和信息增益,事實上在提取技能詞的過程中使用了多種方法,例如會構建模板方法,熟悉**,掌握**等這樣的無監督方法,去提取高質量的技能詞,也會結合tfidf,crf等等。乾貨不成文,希望大家多關注我的知乎,歡迎點贊和評論。

參考:

1. 文本特徵詞提取演算法 2. 特徵提取 3. 特徵選擇之卡方檢驗 4. 特徵選擇之信息增益 5. 特徵選擇之信息增益

推薦閱讀:

相关文章