从文档中提取关键字
想象一下你手上有數百萬(也許數十億)的文本文檔。無論是社交媒體數據還是社區論壇帖子。生成數據時沒有標記。給那些文件貼標簽真是費勁。
手工標注不實用;現有的標簽列表很快就會過時。雇用一家供應商公司來做標記工作太貴了。
你可能會說,為什么不使用機器學習呢?比如,普通網絡深度學習。但是,神經網絡首先需要一些訓練數據。并且要適合你數據集的訓練數據。
那么,有沒有一個解決方案可以讓我們滿足:
- 不需要訓練數據。
- 手動干擾最小,可自動運行。
- 自動捕獲新單詞和短語。
這篇文章記錄了我是如何在Python中提取關鍵字,并且它是如何工作的。
請注意,本文中的代碼是在Jupyter Notebook中運行和測試的。如果你運行一個代碼塊,但是遇到了缺少導入包的錯誤,那么這個包一定已經在前面某處導入過了。
核心思想
TF-IDF是一種廣泛使用的算法,用于評估單詞與文檔集合中文檔的相關性。
基于TF-IDF,那些獨特而重要的詞在文檔中應該具有較高的TF-IDF值。因此,理論上,我們應該能夠利用文本權重來提取文檔中最重要的單詞。
例如,一個關于sciket learn的文檔應該包含很多高密度的關鍵字scikit learn,而另一個關于“pandas”的文檔應該對pandas具有很高的TF-IDF值。
目標數據
我發現來自NLTK的Reuters文檔語料庫是一個很好的關鍵詞提取目標:https://www.nltk.org/
如果你不熟悉NLTK語料庫,本文可能有助于在不到一個小時的時間內開始NLTK,https://towardsdatascience.com/book-writing-pattern-analysis-625f7c47c9ad。
下載路透社語料庫。運行Python代碼:
import nltk nltk.download("reuters")列出我們剛剛下載的語料庫中的所有文檔ID。
from nltk.corpus import reuters reuters.fileids()檢查一個文檔的內容及其類別。
fileid = reuters.fileids()[202] print(fileid,"\n",reuters.raw(fileid),"\n",reuters.categories(fileid),"\n")路透社語料庫是由重疊的類別組成的。我們還可以按類別名稱獲取文檔。對于完整的NLTK語料庫操作,請查看這篇精彩的文章:訪問文本語料庫和詞匯資源:https://www.nltk.org/book/ch02.html#fig-inaugural2
生成停用詞列表
為了節省時間和計算資源,我們最好排除諸如“am”、“I”、“should”之類的停用詞。NLTK提供了一個很好的英語停用詞列表。
from nltk.corpus import stopwords ignored_words = list(stopwords.words('english'))你也可以用你自己的停用詞擴展這個列表,這些詞不包括在NLTK停用詞列表中。
ignored_words.extend( '''get see seeing seems back join excludes has have other that are likely like due since next 100 take based high day set ago still however long early much help sees would will say says said applying apply remark explain explaining '''.split())建立關鍵詞詞匯表-單字
在使用TF-IDF提取關鍵字之前,我將建立自己的詞匯表,包括單個單詞(例如“Python”)和兩個單詞(例如“white house”)。
在這里,我將使用scikit-learn中的CountVectorizer來執行單個單詞提取工作。
from sklearn.feature_extraction.text import CountVectorizer import pandas as pd count_vec = CountVectorizer(ngram_range = (1,1) #1,stop_words = ignored_words )text_set = [reuters.raw(fileid).lower() for fileid in reuters.fileids()] #2 tf_result = count_vec.fit_transform(text_set)tf_result_df = pd.DataFrame(tf_result.toarray(),columns=count_vec.get_feature_names()) #3the_sum_s = tf_result_df.sum(axis=0) #4the_sum_df = pd.DataFrame({ #5'keyword':the_sum_s.index,'tf_sum':the_sum_s.values })the_sum_df = the_sum_df[the_sum_df['tf_sum']>2 #6 ].sort_values(by=['tf_sum'],ascending=False)代碼#1,指定CountVectorizer只對單個單詞計數。你可能會問,為什么不使用ngram_range = (1,2),然后同時得到1gram和bigram呢?這是因為這里的捕獲bigram會得到“they are”、“I will”和“will be”這樣的短語。這些是連詞短語,通常不是文檔的關鍵字或關鍵短語。
另一個原因是為了節省內存資源,捕獲bigram短語在這個階段會由于太多的組合而占用大量內存。
代碼#2,使用Python生成式將路透社的所有文章放在一行代碼中。
代碼#3,將計數向量結果轉換為可讀的數據幀。
代碼#4,產生一個列表,包含關鍵字及其在語料庫中的總出現次數。
代碼#5,將序列轉換為數據幀,以便于讀取和數據操作。
代碼#6,選取只出現2次以上的單詞。
如果你查看由the_sum_df[:10]設置的前10個結果,你將看到那些最常用的詞:
最常見但毫無意義的是,我們可以通過Python切片輕松地按比例排除:
也可以刪除少于2個字符的單詞,如“vs”、“lt”等。
請注意,我使用的是.iloc而不是.loc。因為原始數據集是按TF(term frequency)值重新排序的。iloc將對索引的索引(或索引標簽的序列)進行切片。但loc將在索引標簽上切片。
建立關鍵詞詞匯表-bigram
在建立二元短語表時,不僅要考慮出現頻率,還要考慮其與相鄰詞的關系。
例如短語“they are”,多次出現在一起,但他們只能跟在有限的詞后面,就像他們是兄弟一樣,他們是好人,這些詞具有很高的內部粘性,但外部連接靈活性很低。
這通常可以用信息熵來度量。熵值越高,表示與其他詞一起使用的可能性越大。
而那些對我們大腦具有高內部粘性(計數頻率)和高外部熵的短語,我們稱之為“普通短語”,這些是我們想要添加到提取詞匯表中的。
NLTK提供了一個類似的解決方案來解決bigram短語提取問題。
from nltk.collocations import BigramAssocMeasures from nltk.collocations import BigramCollocationFinder from nltk.tokenize import word_tokenize text_set_words = [word_tokenize(reuters.raw(fileid).lower()) for fileid in reuters.fileids()] #1 bigram_measures = BigramAssocMeasures() finder = BigramCollocationFinder.from_documents(text_set_words) #2 finder.apply_freq_filter(3) #3 finder.apply_word_filter(lambda w: len(w) < 3 or len(w) > 15 or w.lower() in ignored_words) #4 phrase_result = finder.nbest(bigram_measures.pmi, 20000) #5 colloc_strings = [w1+' '+w2 for w1,w2 in phrase_result] #6代碼#1,在這個Python理解表達式中,我使用word_tokenize將文檔標記為單詞列表。輸出如下:
[['word1','word2',...,'wordn'], ['word1','word2',...,'wordn'],...['word1','word2',...,'wordn'] ]代碼#2,從標記化文檔列表啟動bigram查找器對象。還有另一個函數from_words()可以處理標記詞列表。
代碼#3,刪除頻率小于3的候選項。
編碼#4,刪除單詞長度小于3或大于15的候選詞。
代碼#5,使用BigramAssocMeasures中的pmi函數來測量2個單詞短語的可能性。此鏈接列出所有其他度量函數和源代碼:https://tedboy.github.io/nlps/generated/generated/nltk.BigramAssocMeasures.html#methods
代碼#6,將結果轉換為更可讀的格式。
通過將BigramAssocMeasures、BigramCollocationFinder替換為TrigramAssocMeasures和TrigramCollocationFinder,你將得到3個單詞的短語提取器。在路透社關鍵詞提取樣本中,我將跳過3個單詞的短語。我在這里發布了示例代碼,以防你需要它。
from nltk.collocations import TrigramAssocMeasures from nltk.collocations import TrigramCollocationFinder from nltk.tokenize import word_tokenize text_set_words = [word_tokenize(reuters.raw(fileid).lower()) for fileid in reuters.fileids()] trigram_measures = TrigramAssocMeasures() finder = TrigramCollocationFinder.from_documents(text_set_words) finder.apply_freq_filter(3) finder.apply_word_filter(lambda w: len(w) < 3 or len(w) > 15 or w.lower() in ignored_words) tri_phrase_result = finder.nbest(bigram_measures.pmi, 1000) tri_colloc_strings = [w1+' '+w2+' '+w3 for w1,w2,w3 in tri_phrase_result] tri_colloc_strings[:10]用TF-IDF測量關鍵詞權重
現在,讓我們將單字和雙字短語組合在一起,構建路透社定制的詞匯表。
my_vocabulary = [] my_vocabulary.extend(my_word_df['keyword'].tolist()) my_vocabulary.extend(colloc_strings)我們啟動吧。請注意,請找到一臺至少有16g RAM的機器來運行代碼。TF-IDF計算需要一段時間,可能會占用大量內存。
from sklearn.feature_extraction.text import TfidfVectorizer vec = TfidfVectorizer(analyzer ='word',ngram_range =(1, 2),vocabulary =my_vocabulary) text_set = [reuters.raw(fileid) for fileid in reuters.fileids()] tf_idf = vec.fit_transform(text_set) result_tfidf = pd.DataFrame(tf_idf.toarray(), columns=vec.get_feature_names()) #1將結果集轉換為代碼#1中的Dateframe后,result_tfidf將保存所有關鍵字的TF-IDF值:
看看結果
讓我們查看其中一篇文章,并將其與上面提取器提取的關鍵字進行比較,以驗證其有效性。
通過指定fileid索引輸出一個原始文檔。
返回fileid、raw及其categories(嗯,很多年前,美國和日本打了一場關稅戰)
test/15223 WHITE HOUSE SAYS JAPANESE TARRIFFS LIKELYThe White House said high U.S.Tariffs on Japanese electronic goods would likely be imposed asscheduled on April 17, despite an all-out effort by Japan toavoid them.Presidential spokesman Marlin Fitzwater made the remark oneday before U.S. And Japanese officials are to meet under theemergency provisions of a July 1986 semiconductor pact todiscuss trade and the punitive tariffs.Fitzwater said: "I would say Japan is applying thefull-court press...They certainly are putting both feet forwardin terms of explaining their position." But he added that "allindications are they (the tariffs) will take effect."['trade']打印出我們dataframe對象中的前10個關鍵字。
test_tfidf_row = result_tfidf.loc[file_index] keywords_df = pd.DataFrame({'keyword':test_tfidf_row.index,'tf-idf':test_tfidf_row.values }) keywords_df = keywords_df[keywords_df['tf-idf'] >0 ].sort_values(by=['tf-idf'],ascending=False) keywords_df[:10]十大關鍵詞:
看起來這里的white,house和white house是一樣的。我們需要刪除那些已經出現在兩個單詞短語中的單詞。
上面的代碼首先構建一個單詞集,其中包含來自2個單詞短語的單詞。然后,通過~xxxx.isin(xxxx)過濾出已經在兩個單詞短語中使用的單個單詞。
其他考慮因素
你擁有的文本語料庫越大,TF-IDF提取關鍵詞的效果就越好。路透社語料庫包含10788篇文章,結果表明它是有效的。我相信這個解決方案對于更大的文本數據庫會更好。
上面的代碼在我的macbookairm1中運行不到2分鐘,這意味著每日刷新結果集是可行的。
如果你有數百GB甚至TB大小的數據。你可能需要考慮重寫C/C++或GO中的邏輯,并且還可以利用GPU的功率來提高性能。
本文所描述的解決方案遠不是完美的,例如,我沒有過濾掉動詞和形容詞。解決方案的主干可以擴展到其他語言。
提取的關鍵字
讓我們再次打印出最終結果。
keywords_df_new[:10]關稅得到最高的TF-IDF值,其余關鍵字看起來很好地代表了路透社的這篇文章。達到目標!
參考文獻
- 從文檔中提取關鍵字
總結
- 上一篇: 双11大战:为什么细分品类车载冰箱火了?
- 下一篇: 乒乓操作(Ping-Pong)的理解:为