TF-IDF演算法-Python實現(附原始碼)
一、背景
TF-IDF演算法全稱 termfrequency–inverse document frequency,是一種用於資訊檢索與資訊探勘的常用加權技術。它的演算法複雜度並不高,但能很好的滿足搜尋高相關度文件的需求。由於它的高效性,TF-IDF 模型在搜尋引擎等實際應用中被廣泛使用。
以下是本人使用Python實現該演算法的思路,如有不當之處望各位大牛指導一二。
二、TF-IDF演算法概述
關於TF-IDF演算法的描述網上很多,我就不拾前人牙慧了,感興趣的筒子們可參考這篇通俗易懂的文章:TF-IDF模型的概率解釋。這篇文章中給出了很多數學公式,但數學的美妙在於其每個符號在現實中都是有著極其和諧的對應關係的。在下文中,我將用更通俗的方法闡述下個人的理解。
對於一篇文件來說,它與關鍵字 w[i] 的相關度取決於它包含的所有詞中該關鍵詞的頻率。這其實挺直觀的,一篇文件中包含關鍵詞w[i]越多,那麼它與關鍵字w[i]相關度也就越大。但是,如果僅僅取關鍵詞的頻數的話,那麼比較長的文件包含該關鍵詞的頻數很可能遠遠大於比較短的文件的。因而為了協調文件長度的影響,相關度的衡量應取關鍵詞w[i]佔文件總詞數的頻率。
那有多個關鍵詞的話該怎樣衡量一篇文件出現的情況該怎樣衡量文件的綜合相關度呢?最簡單的當然是把它們都加起來,但這樣一來新的問題又出現了。假設某個關鍵詞w[j]出現在很多篇文件裡,另一關鍵詞w[k]僅在一小部分文件(記為集合U[i])裡出現,那按照常理來說是不是匹配了更多關鍵詞的文件集U[k]與給出的搜尋關鍵詞w[k],w[j]相關度更大?鑑於這種情況,我們需要給每一篇文件包含的每一個關鍵詞的相關度加一個權值。《TF-IDF模型的概率解釋》這篇文章裡給出了該權值的推導過程。依本人的膚淺理解,這個權值為關鍵詞w在所有文件集中所蘊含的資訊熵。
這樣TF-IDF演算法的模型就出來了:
TF-IDF (q, d) = sum { i = 1..k | TF (w[i], d) *IDF(w[i]) }
IDF為逆向文件頻率:
IDF = log (n / docs (w, D))
TF表示詞條在文件d中出現的頻率:
TF (w,d)= count (w, d) / sum { i = 1..n| count (w, d[i]) }
三、演算法實現
1、文件預處理 獲取了足夠多的文件後,需要對文件進行預處理,以加快搜索的速度。=由於linux系統和windows系統的預設編碼不同,Python在處理中文文件時可能會出錯,所以也需要對不同編碼格式的文件預處理成同一編碼格式的文件。因而在文件預處理這一模組需要有以下幾個步驟:讀取文件 -> 刪除不需要的字元(如回車符\n、製表符\t、空格等)->
轉換成unicode格式 -> 對文件分詞 -> 轉換成utf-8格式寫入txt文件。
這些步驟的實現主要使用了以下幾個模組
- 字串修剪模組str_replace.py
這個模組就一個函式,程式碼如下:
- def str_replace(str_source,char,*words):
- str_temp=str_source
- for word in words:
- str_temp=str_temp.replace(word,char)
- return str_temp
str_replace(str_source,char,*words)函式接受兩個或兩個以上的引數,str_source是需要處理的字串,char是要替換的目標字元,words是可變字串的元組,對字串str_source中的每一個出現在words裡的字元均替換成統一字元char。
在主程式裡可以這樣使用str_replace(content_temp,"","\t","\n",""),即將content_temp裡的每一個"\t","\n",""字元都刪掉。
- 字串格式轉換模組StrToUni.py
這個模組主要有兩個函式StrToUni_try和StrToUni。
由於輸入字串的格式可能沒法實現知道,因而需要進行unicode解碼的嘗試,在解碼嘗試成功後再進行轉碼。這一過程分兩個步驟,StrToUni_try和StrToUni兩個函式分別完成。StrToUni_try函式主要負責判斷字串是不是某一格式,這個函式返回字串的正確編碼格式。StrToUni函式負責使用StrToUni_try返回的編碼格式將字串轉化成unicode格式。程式碼如下:- def StrToUni_try(str,type_1):
- try:
- str.decode(type_1)
- except UnicodeDecodeError:
- returnFalse
- else:
- returnTrue
- def StrToUni(str,*type_list):
- ifnot type_list:
- if StrToUni_try(str,'utf-8'):
- return str.decode('utf-8')
- else:
- print"輸入的原始檔的編碼格式不是utf-8"
- else:
- for type_2 in type_list:
- if StrToUni_try(str,type_2):
- return str.decode(type_2)
- else:
- if type_2==type_list[-1]:
- print"輸入的原始檔的編碼格式不在您提供的格式列表中"
StrToUni_try函式嘗試解碼的格式列表可以在GrobalParament.py模組裡設定。使用者需要單獨使用時可以手動輸入,如:
StrToUni(str,”utf-8”,”GBK”,”GB2312”)
- 分詞模組full_word_cut.py和half_word_cut.py
在本程式中使用結巴分詞對中文文件分詞。在本程式中有兩種搜尋模式,一是全文搜尋,二是關鍵詞搜尋。前一種的實現方法是使用結巴分詞將中文文件進行全文分詞再儲存,後者的實現方法是使用結巴分詞提取關鍵詞再儲存。由full_word_cut.py和half_word_cut.py模組分別實現這兩種分詞功能。
- Unicode格式轉字串模組UniToStr.py
這一模組也是由兩個函式組成,轉換的過程與StrToUni.py模組類似。程式碼如下:
- def UniToStr_try(str,type_1):
- try:
- str.encode(type_1)
- except LookupError:
- returnFalse
- else:
- returnTrue
- def UniToStr(str,*out_Format):
- ifnot out_Format:
- return str.encode('utf-8')
- else:
- for type_2 in out_Format:
- if UniToStr_try(str,type_2):
- return str.encode(type_2)
- else:
- if type_2==out_Format[-1]:
- print"輸入的目標編碼格式不正確"
該模組的目標編碼格式可以在GrobalParament.py模組裡設定,也可由使用者手動設定。需要注意的是,這個模組只會根據目標編碼格式列表裡的第一個正確的編碼格式進行編碼,也就是說允許目標編碼格式裡的格式名是錯誤的格式名。如果使用者不大確定某一編碼格式叫什麼名,可以多輸入幾個可能的組合作嘗試。如使用者不記得linux下的編碼格式的準確名,可以在GrobalParament.py模組裡修改OutputFormatList。
如OutputFormatList=[“utf-9”,”uft-8”,”utf-8”],事實上國際標準裡沒有“utf-9”、”uft-8”兩種編碼格式,但”utf-8”是正確的,程式就能正確地輸出”utf-8”格式的字串。這樣程式就有了一定的容錯性。
- 文件預處理頂層模組prepro_file.py
這個模組需要使用上文所提到的五個模組,主要功能是將一個目錄裡的所有文件都處理完後按照給定的編碼格式輸出到指定的文件中。主要程式碼如下:
- #這個函式用於預處理檔案處理過程中採用unicode編碼
- import os
- from str_replace import str_replace
- from TF_IDF.StrToUni import StrToUni
- import GrobalParament
- from full_word_cut import fullcut
- from half_word_cut import halfcut
- from UniToStr import UniToStr
- def prepro_file(fl_in_url,re_out_url,*wd_be_del):
- in_url=fl_in_url.replace('\\','/')
- out_url=re_out_url.replace('\\','/')
- try:
- try:
- fl_in=os.listdir(in_url)
- except WindowsError:
- print"您輸入的預處理文件目錄有誤"
- try:
- re_out=open(out_url,'w')
- except WindowsError:
- print"您輸入的結果文件輸出目錄有誤"
- except NameError:
- pass
- else:
- for file in fl_in:
- afile_url=fl_in_url+'/'+file
- if os.path.isfile(afile_url):
- afile=open(afile_url,"r")
- content_temp="".join(afile.readlines())
- ifnot wd_be_del:
- content=str_replace(content_temp,"","\t","\n"," ")#刪除某些特殊字元如\t,\n等以保證是一行的連續的
- else:
- content=str_replace(content_temp,'',*wd_be_del)
- con_unicode=StrToUni(content,*(GrobalParament.InputFormatList))
- if GrobalParament.pattern=="full":
- cut_result=fullcut(con_unicode)
- else:
- cut_result=halfcut(con_unicode)
- s_fl_Name=UniToStr(file,*(GrobalParament.OutputFormatList))
- re_out.write(s_fl_Name+'\t')
- key_word_out=[]
- for key_word in cut_result:
- s_key_word=UniToStr(key_word,*(GrobalParament.OutputFormatList))
- key_word_out.append(s_key_word)
- out_str=','.join(key_word_out)
- re_out.write(out_str)
- re_out.write('\n')
這一模組沒啥好說的,主要是將前文所講的模組統和到一起完成文件的預處理功能。需要注意的是,這裡使用正則表示式濾除了結巴分詞的結果集中只有一個字的詞,如”的”,”之”,”地”等用處不大的詞。
在本程式中,預處理的結果預設的輸出格式為:
檔名\t分詞結果\n
檔名\t分詞結果\n
⋯ ⋯
最後的結果儲存為:E:\\EclipseProjection\\Python\\TF_IDF\\test\\pro_res.txt
儲存路徑和儲存檔名可以GrobalParament.py模組裡設定,設定方法見下文。
2.TF-IDF演算法模組
文件預處理後,就是本文的主菜TF-IDF演算法模組了。以下我將詳細的闡述TF-IDF演算法的實現思路。1) 開啟前文得到的預處理文件pro_res.txt,並讀取一行字串(代表某一文件),賦給變數data;
2) 如果data不為空,則接步驟3);
3) 將data拆分,用file_name記錄文件名,列表data_temp_2為該文件的所有詞語的列表。data_temp_len為該文件詞語總數,同時代表文檔總數的變數files_num加1;
4) 對於每一個關鍵詞word,先判斷此時file_name代表的文件的所有詞語中是否有關鍵詞word,如果有的話接5);
5) 判斷word是否在其他文件裡出現,沒有出現過得話在字典word_in_allfiles_stat新增一元素,以該關鍵字為鍵名,並賦鍵值為1;否則的話將字典word_in_allfiles_stat裡鍵名為word對應的關鍵字的元素的鍵值加1;
6) 如果字典word_in_afile_stat沒有以此時file_name變數指向的文件名的元素,即當前文件包含已經遍歷過了的關鍵字,則新建一個元素,檔名為file_name變數指向的文件名,鍵值為一空字典;
7) 在6)中得到的空字典裡新建一列表,儲存file_name變數指向的文件中包含當前關鍵字的數量和當前文件的總詞語數量;
8) 重複以上2)~ 7)知道讀完整個預處理文件pro_res.txt;最後應得到兩個字典word_in_allfiles_stat和word_in_afile_stat。這兩個字典的結構分別如下:
word_in_allfiles_stat{A:n1,B:n2,⋯⋯}
word_in_afile_stat{a:{A:[aA,suma],B:[aB,suma],⋯⋯}, ⋯⋯}, b:{A:[bA,sumb],B:[bB,sumb],⋯⋯}, ⋯⋯},⋯⋯}
在以上兩個字典中集合{A,B,⋯⋯}代表搜尋關鍵字集,集合{a,b,⋯⋯}代表文件集,{aA,aB,⋯⋯}代表文件包含關鍵字的個數,{suma,sumb,⋯⋯}代表文件對應的總詞數。
例如,在word_in_afile_stat中a:{A:[aA,suma],B:[aB,suma],⋯⋯}代表文件a中包含關鍵字A的詞數為aA,包含關鍵字B的詞數為aB⋯⋯,檔案a的總詞數為suma,若a沒有關鍵詞B,則字典中不包含B這個鍵名。
在word_in_allfiles_stat中,代表共有n1個文件包含關鍵詞A,n2個文件包含關鍵詞B。
9) 最後格局字典word_in_allfiles_stat和word_in_afile_stat裡的內容計算最終結果,最終按照結果值的由高到低返回指定數目的文件名。
這部分的原始碼如下:
- from __future__ import division
- import math
- from TF_IDF import GrobalParament
- def TF_IDF_Compute(file_import_url_temp,file_export_url_temp,*words):
- file_import_url=file_import_url_temp.replace('\\','/')
- file_export_url=file_export_url_temp.replace('\\','/')
- data_source=open(file_import_url,'r')
- data=data_source.readline()
- word_in_afile_stat={}
- word_in_allfiles_stat={}
- files_num=0
- while(data!=""):
- data_temp_1=[]
- data_temp_2=[]
- data_temp_1=data.split("\t") #file name and key words of a file
- data_temp_2=data_temp_1[1].split(",")#key words of a file
- file_name=data_temp_1[0]
- data_temp_len=len(data_temp_2)
- files_num+=1
- for word in words: