TF-IDF、TextRank、WordCount三種方法實現英文關鍵詞提取(python實現)


源碼:https://github.com/Cpaulyz/BigDataAnalysis/tree/master/Assignment2

數據預處理

進行關鍵詞提取之前,需要對源文件進行一系列預處理:

  • 提取PDF為TXT文件
  • 分句
  • 分詞(詞干提取、詞形還原)
  • 過濾數字、特殊字符等,大小寫轉換

提取PDF

使用Apache PDFBox工具對PDF文字進行提取

依賴如下:

<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.13</version>
</dependency>

提取工具類utils/PDFParser類代碼邏輯如下

try {
    // 讀取PDF文件夾,將PDF格式文件路徑存入一個Array中
    File dir = new File("src\\main\\resources\\ACL2020");
    ArrayList<String> targets = new ArrayList<String>();
    for(File file:dir.listFiles()){
        if(file.getAbsolutePath().endsWith(".pdf")){
            targets.add(file.getAbsolutePath());
        }
    }
    // readPdf為提取方法
    for(String path:targets){
        readPdf(path);
    }
} catch (Exception e) {
    e.printStackTrace();
}

至此,完成將PDF文件中的文字提取,並存入.txt文件中的操作,以便后續操作,示意圖如下。

image-20200918173250676

分句

使用python中的nltk庫進行分句

from nltk.tokenize import sent_tokenize
sens = sent_tokenize(str)

分句情況大致如下,可以看出分句情況較為准確

image-20200918173351972

分詞(詞干提取、詞形還原)

nltk提供了分詞工具,API如下

from nltk.stem import WordNetLemmatizer

wnl = WordNetLemmatizer()
print(wnl.lemmatize('ate', 'v'))
print(wnl.lemmatize('fancier', 'n'))

# 輸出為eat fancy

但是,這種分詞方法需要確定單詞在的詞性,好在nltk也為我們提供了方法來判斷句子的詞性,將其封裝為方法如下

# 獲取單詞的詞性
def get_wordnet_pos(tag):
    if tag.startswith('J'):
        return wordnet.ADJ
    elif tag.startswith('V'):
        return wordnet.VERB
    elif tag.startswith('N'):
        return wordnet.NOUN
    elif tag.startswith('R'):
        return wordnet.ADV
    else:
        return None

結合后進行調用,如下:

from nltk import word_tokenize, pos_tag
from nltk.corpus import wordnet
from nltk.stem import WordNetLemmatizer

tokens = word_tokenize(sentence)  # 分詞
tagged_sent = pos_tag(tokens)  # 獲取單詞詞性

wnl = WordNetLemmatizer()
lemmas_sent = []
for tag in tagged_sent:
    wordnet_pos = get_wordnet_pos(tag[1]) or wordnet.NOUN
    lemmas_sent.append(wnl.lemmatize(tag[0], pos=wordnet_pos))  # 詞形還原

結果如圖

image-20200918173456799

可以看出分詞后的效果還不錯,但仍存在問題為

  1. 沒有剔除掉;:.,等特殊符號

  2. 沒有剔除數字等

  3. 沒有剔除一些如a、the、of等介詞

過濾

問題1、2容易使用正則表達式進行剔除;

問題3我們通過nltk提供的英文停用詞列表、以及“不妨假設長度為4以下的字符串無效”來進行剔除。

import re
from nltk.corpus import stopwords

invalid_word = stopwords.words('english')

# 預處理,如果是False就丟掉
def is_valid(word):
    if re.match("[()\-:;,.0-9]+", word):
        return False
    elif len(word) < 4 or word in invalid_word:
        return False
    else:
        return True

方法1 TF-IDF

TF-IDF算法提取關鍵詞的結構化流程如下:

image-20200918184305196

1.1 分句分詞

同數據預處理,不再贅述

1.2 構造語料庫

由於IDF的計算需要語料庫的支持,我們在這里以全部文章構建一個語料庫,存儲在all_dic = {}

all_dict是一個map,存儲結構為(String 文章名,Map 詞頻<單詞,詞頻>)

一個示例如下

{
	'A Generative Model for Joint Natural Language Understanding and Generation.txt': 
		{'natural': 13, 
		'language': 24, 
		'understanding': 4,
		'andnatural': 1, 
		'generation': 9, 
		'twofundamental': 1,
		...
		},
	...
}

1.3 計算TF-IDF

(1)TF

詞頻 (term frequency, TF) 指的是某一個給定的詞語在該文件中出現的次數。這個數字通常會被歸一化(一般是詞頻除以文章總詞數), 以防止它偏向長的文件。(同一個詞語在長文件里可能會比短文件有更高的詞頻,而不管該詞語重要與否。)

image-20200918190354120

TF = article_dict[word] / article_word_counts

(2)IDF

逆向文件頻率 (inverse document frequency, IDF) IDF的主要思想是:如果包含詞條t的文檔越少, IDF越大,則說明詞條具有很好的類別區分能力。某一特定詞語的IDF,可以由總文件數目除以包含該詞語之文件的數目,再將得到的商取對數得到。

image-20200918190359938

            contain_count = 1  # 包含的文檔總數,因為要+1,干脆直接初始值為1來做
            for article1 in all_dic.keys():
                if word in all_dic[article1].keys():
                    contain_count += 1
            IDF = log(article_nums / contain_count)

(3)TF-IDF

image-20200918190404938

實現核心代碼如下:

def TFIDF():
    article_nums = len(all_dic)
    for article in all_dic.keys():
        article_dict: dict = all_dic[article]
        article_word_counts = 0
        for count in article_dict.values():
            article_word_counts += count
        local_dict = {}
        for word in article_dict:
            TF = article_dict[word] / article_word_counts
            contain_count = 1  # 包含的文檔總數,因為要+1,干脆直接初始值為1來做
            for article1 in all_dic.keys():
                if word in all_dic[article1].keys():
                    contain_count += 1
            IDF = log(article_nums / contain_count)
            local_dict[word] = TF * IDF
        all_dic[article] = local_dict  # 用TFIDF替代詞頻

1.4 輸出結果

值得一提的是,TF-IDF的基於語料庫的關鍵詞算法,我們在將ACL2020的全部文章作為語料庫進行提取,因此提取到的TF-IDF值是相對於文章內部的關鍵詞權重。

因此,通過這種方法,我們生成的是每篇文章的關鍵詞而非語料庫的關鍵詞。

在這里,我們選取每篇文章中TF-IDF最高的單詞及其權重輸出到method1_dict.txt中,權重表示的是TF-IDF值,排序為按照文章標題的字母排序。

unlabelled 0.03366690429509488
database 0.025963153344621098
triplet 0.06007324859328521
anaphor 0.054325239855360946
sparse 0.05140787295501171
dialog 0.02857688733696682
evaluator 0.047046849916043215
article 0.03181976626426247
dialogue 0.05009864522556742
false 0.05046963249913187
explanation 0.06756267918534663
keyphrases 0.07257334117762049
switch 0.02057258339292402
response 0.03487928535131968
hcvae 0.01490817643452481
response 0.01691069785427619
fragment 0.036740214670107636
concept 0.10144398960055125
node 0.026861943279698357
type 0.021568639909022032
hierarchy 0.04174740425673965
legal 0.09062083506033958
confidence 0.03208193690887942
question 0.018326715354972434
follow-up 0.0768915254934173
graph 0.030139792811985255
quarel 0.03142980753777034
instruction 0.04310656492734328
summary 0.023522349291620226
mutual 0.021794659657633334
malicious 0.03361252033133951
nucleus 0.03062106234461863
supervision 0.02716542294214428
relation 0.026017607441275774
calibrator 0.053113533081036744
centrality 0.06527959271708282
question 0.015813880735872966
slot 0.04442739804723785
graph 0.017963145985978687
taxonomy 0.05263359765861765
question 0.01694100733341999
transformer 0.019573842786351815
response 0.027652528223249546
topic 0.04541019920353925
paraphrase 0.024098507886884227

方法2 TextRank

TextRank算法提取關鍵詞的結構化流程如下

image-20200918173649653

2.1 分句

同預處理部分的分句處理,不再贅述

2.2 建立關系矩陣

建立關系矩陣Mn*n,其中n為單詞數量(相同單詞僅記一次),Mij表示j到i存在權重為Mij的關系。

關系的定義如下:

取窗口大小為win,則在每個分句中,去除停用詞、標點、無效詞后,每個單詞與距離為win以內的單詞存在聯系

為了方便表示關系矩陣,這里以一個(String word, Array relative_words)的Map來進行表示存在word→relative_words的關系,例子如下(來源網絡http://www.hankcs.com/nlp/textrank-algorithm-to-extract-the-keywords-java-implementation.html)

句分詞 = [程序員, 英文, 程序, 開發, 維護, 專業, 人員, 程序員, 分為, 程序, 設計, 人員, 程序, 編碼, 人員, 界限, 特別, 中國, 軟件, 人員, 分為, 程序員, 高級, 程序員, 系統, 分析員, 項目, 經理]

之后建立兩個大小為5的窗口,每個單詞將票投給它身前身后距離5以內的單詞:

{開發=[專業, 程序員, 維護, 英文, 程序, 人員],

軟件=[程序員, 分為, 界限, 高級, 中國, 特別, 人員],

程序員=[開發, 軟件, 分析員, 維護, 系統, 項目, 經理, 分為, 英文, 程序, 專業, 設計, 高級, 人員, 中國],

分析員=[程序員, 系統, 項目, 經理, 高級],

維護=[專業, 開發, 程序員, 分為, 英文, 程序, 人員],

系統=[程序員, 分析員, 項目, 經理, 分為, 高級],

項目=[程序員, 分析員, 系統, 經理, 高級],

經理=[程序員, 分析員, 系統, 項目],

分為=[專業, 軟件, 設計, 程序員, 維護, 系統, 高級, 程序, 中國, 特別, 人員],

英文=[專業, 開發, 程序員, 維護, 程序],

程序=[專業, 開發, 設計, 程序員, 編碼, 維護, 界限, 分為, 英文, 特別, 人員],

特別=[軟件, 編碼, 分為, 界限, 程序, 中國, 人員],

專業=[開發, 程序員, 維護, 分為, 英文, 程序, 人員],

設計=[程序員, 編碼, 分為, 程序, 人員],

編碼=[設計, 界限, 程序, 中國, 特別, 人員],

界限=[軟件, 編碼, 程序, 中國, 特別, 人員],

高級=[程序員, 軟件, 分析員, 系統, 項目, 分為, 人員],

中國=[程序員, 軟件, 編碼, 分為, 界限, 特別, 人員],

人員=[開發, 程序員, 軟件, 維護, 分為, 程序, 特別, 專業, 設計, 編碼, 界限, 高級, 中國]}

實現部分代碼如下

def add_to_dict(word_list, windows=5):
    valid_word_list = []  # 先進行過濾
    for word in word_list:
        word = str(word).lower()
        if is_valid(word):
            valid_word_list.append(word)
    # 根據窗口進行關系建立
    if len(valid_word_list) < windows:
        win = valid_word_list
        build_words_from_windows(win)
    else:
        index = 0
        while index + windows <= len(valid_word_list):
            win = valid_word_list[index:index + windows]
            index += 1
            build_words_from_windows(win)

# 根據小窗口,將關系建立到words中
def build_words_from_windows(win):
    for word in win:
        if word not in words.keys():
            words[word] = []
        for other in win:
            if other == word or other in words[word]:
                continue
            else:
                words[word].append(other)

2.3 迭代

TextRank的計算公式類似PageRank

image-20200918174031410

迭代的終止條件有以下兩種

  1. max_diff < 指定閾值,說明已收斂

  2. max_iter > 指定迭代次數,說明迭代次數達到上限

代碼實現如下

def text_rank(d=0.85, max_iter=100):
    min_diff = 0.05
    words_weight = {}  # {str,float)
    for word in words.keys():
        words_weight[word] = 1 / len(words.keys())
    for i in range(max_iter):
        n_words_weight = {}  # {str,float)
        max_diff = 0
        for word in words.keys():
            n_words_weight[word] = 1 - d
            for other in words[word]:
                if other == word or len(words[other]) == 0:
                    continue
                n_words_weight[word] += d * words_weight[other] / len(words[other])
            max_diff = max(n_words_weight[word] - words_weight[word], max_diff)
        words_weight = n_words_weight
        print('iter', i, 'max diff is', max_diff)
        if max_diff < min_diff:
            print('break with iter', i)
            break
    return words_weight

2.4 輸出結果

選取前30個關鍵詞,輸出結果如下,本方法中權重表示TextRank計算出來的值,保存在method2_dict.txt

 model 176.5304347133946
 question 85.40181168045564
 response 62.507994652932325
 data 60.65722815422958
 method 59.467011421798766
 result 58.625521805302576
 show 58.328949197586205
 graph 57.56085447050974
 answer 56.016412290514324
 generate 53.04744866326927
 example 52.68958963476476
 training 52.109756756305856
 also 51.35655567676399
 input 50.69980375572206
 word 50.52677865990237
 train 49.34118286080509
 representation 48.497427796293245
 sentence 48.21207111035171
 dataset 48.07840701700186
 work 47.57844139247928
 system 47.03771276235998
 propose 46.88347913956473
 task 46.518530285062205
 performance 45.70988317875179
 base 45.675096486932375
 different 44.92213315873288
 score 43.76950706001539
 test 42.996530025663326
 give 42.40794849944198
 information 42.39192128940212

方法3 WordCount

最后一種方法是朴素的詞頻計算法,思想很簡單,就是計算詞頻,認為出現次數越多,越可能是關鍵詞,結構化流程如下:

image-20200918174153676

3.1 分詞分句

同預處理部分,不再贅述

3.2 統計詞頻

使用一個Map來表示(單詞,詞頻)

dic = {}

def add_to_dict(word_list):
    for word in word_list:
        word = str(word).lower()
        if is_valid(word):
            if word in dic.keys():
                dic[word] += 1
            else:
                dic[word] = 1

3.3 輸出結果

選取前30個關鍵詞,輸出結果如下,本方法中權重表示詞頻,保存在method3_dict.txt

model 1742
question 813
response 579
graph 515
data 490
method 464
show 456
result 447
answer 445
representation 408
generate 398
example 394
training 393
word 387
dataset 377
sentence 368
input 365
propose 360
train 351
test 349
system 345
also 342
task 330
performance 327
score 325
different 315
work 312
document 304
base 294
information 293


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM