Part 1、理論基礎(chǔ)
1.1 關(guān)于理論
其實這次不應(yīng)該叫“理論基礎(chǔ)”了,應(yīng)該叫“基礎(chǔ)理論”,本著簡單明了一看就懂的原則,就著這張越來越厚槍打不透的臉皮,我就“勉為其難”來講一講。
如果你之前沒聽說過TF-IDF,那我估計你現(xiàn)在看到TF-IDF也不會覺得特別陌生,你一定會感覺在哪見過,但又不是特別確切,那我再估計一下,你這模糊的記憶一定是TF-BOYS,那么TF-IDF和TF-BOYS到底是什么關(guān)系呢?我也不用跟你慢慢道來了,因為他倆的關(guān)系太簡單了,不,應(yīng)該是他四個的關(guān)系太簡單了,還不對,應(yīng)該是它一個和他三個的關(guān)系太簡單了,簡單到可以用一句話來總結(jié),那就是:沒有半毛錢關(guān)系,沒有!沒有!沒有!哈,扯了半天,下面正式開講。
首先來引用一下百度百科的解釋:
TF-IDF(term frequency–inverse document frequency,詞頻-逆向文件頻率)是一種用于信息檢索與數(shù)據(jù)挖掘的常用加權(quán)技術(shù)。
TF-IDF是一種統(tǒng)計方法,用以評估一字詞對于一個文件集或一個語料庫中的其中一份文件的重要程度。
TF-IDF的主要思想是:如果某個詞或短語在一篇文章中出現(xiàn)的頻率TF高,并且在其他文章中很少出現(xiàn),則認(rèn)為此詞或者短語具有很好的類別區(qū)分能力,適合用來分類。
再簡單一點講,它就是一種利用統(tǒng)計方法為詞加權(quán)的技術(shù),權(quán)值大小由兩部分決定,一是這個詞在這篇文檔中出現(xiàn)的頻率,頻率越高,權(quán)值越大,也就是詞頻(TF);二是整個文檔集中出現(xiàn)該詞的文檔數(shù)量,數(shù)量越多權(quán)值越小,也就是逆文件頻率(IDF)。寫成式子就是這樣:


加1是防止分母為0,這里的計算都已經(jīng)是最簡單的形式了,有興趣的可以對公式進行優(yōu)化,那么基礎(chǔ)理論也就講完了。
如果還不清楚,那就舉個簡單的例子:說文檔集一共有100篇文章,其中有一篇有1000個詞,并且“中國”、“北京”這兩個詞分別出現(xiàn)5次和10次,又知道出現(xiàn)過“中國”這個詞的文檔有50個,出現(xiàn)過“北京”這個詞的文檔有25個,那么有了以上信息我們就可以計算這篇文檔中的“中國”、“北京”兩個詞的TF-IDF值了,過程如下:

這個結(jié)果就是“中國”和“北京”在這篇文檔中的權(quán)重,從數(shù)值上來看,“北京”比“中國”更能代表這篇文檔的中心內(nèi)容,如果簡單的用“北京”和“中國”作為標(biāo)簽來對文檔進行分類,那么“北京”即可以作為這篇文檔的標(biāo)簽。
1.2 關(guān)于算法
有了上面的分析,設(shè)計算法就比較簡單了,只要清楚我們所需要的統(tǒng)計量,一切就好辦了,這里就簡單說明一下會用到統(tǒng)計量。
(1)輸入的文檔集。這里我使用的約900篇文章,每篇500字左右,每篇單獨一個txt文檔。
(2)每篇文章詳細的統(tǒng)計量。這里我使用的是一個嵌套的字典,在程序中命名為oneFileInfo,它形式是這樣的:

(3)包含某詞的文檔的數(shù)量。還是用一個字典來實現(xiàn),命名allFileInfo,形式:

(5)每篇文檔中詞的數(shù)量。
清楚了這幾個統(tǒng)計量,其他就是程序?qū)崿F(xiàn)的問題了,如果說還有其他什么需要注意的,在我看來就是中文文檔的編碼問題了,這里我使用的輸入文檔都是“utf-8”編碼的,程序中也會有相應(yīng)的decode和encode轉(zhuǎn)換函數(shù)
Part 2、算法實現(xiàn)
from __future__ import division
import re
import jieba
import math
import os
import sys
reload(sys)
sys.setdefaultencoding('utf8')
# part 1
# 一些可能用到的函數(shù)
OutTypeList = ['utf-8']
InTypeList = ['utf-8']
stopWord=[]
def toDecode(str1,type1): #自定義一個異常
try:
str1.decode(type1)
except UnicodeDecodeError:
return False
else:
return True
def beDecode(str1, *typeList): #decode函數(shù)
#星號表示收集參數(shù),即除了第一參數(shù),其余都認(rèn)為是typeList帶來的的參數(shù)
if not typeList:
if toDecode(str1,'utf_8'):
return str1.decode('utf-8')
else:
print "輸入的原文本格式不是utf-8"
else:
for type2 in typeList:
if toDecode(str1, type2):
return str1.decode(type2)
else:
if type2 == typeList[-1]:
print "輸入的源文件的編碼格式不在您提供的格式列表中"
def toEecode(str1,type1): #自定義一個異常
try:
str1.encode(type1)
except LookupError:
return False
else:
return True
def beEncode(str1, *outType): #encode函數(shù)
if not outType:
return str1.encode('utf-8')
else:
for type2 in outType:
if toEncode(str1, type2):
return str1.encode(type2)
else:
if type2 == outType[-1]:
print "輸入的目標(biāo)編碼格式不正確"
def ReplaceWord(SourceDocument,char,*words): #替換SourceDocument中的某些字符
DocAftReplace = SourceDocument
for word in words:
DocAftReplace = DocAftReplace.replace(word,char)
return DocAftReplace
# part 2
# 分詞函數(shù)
def fullcut(document): #分詞函數(shù),這里使用的是jieba分詞
WordList = []
DocumentAfterCut = jieba.cut(document, cut_all=False) #精確模式進行分詞
toWordList = list(DocumentAfterCut) #分詞結(jié)果轉(zhuǎn)為list(列表)類型
if not stopWord:
temp = '/'.join(toWordList)
r = r'[^/]{2,}'
WordList = re.findall(r, temp)
else: #如果stopWord不為空
for word in toWordList:
if word not in stopWord:
WordList.append(word)
return WordList
# part3
# 因為我的輸入文檔集中每個文檔是單獨的txt
# 所以這里使用一個函數(shù)將各文檔分詞后合并到一個文檔中。
def toGroupFile(FileFrom, resultDoc, *WordToDel):
FileUrl = FileFrom.replace('\\','/') #待處理文件目錄
resultUrl = resultDoc.replace('\\','/') #存放處理結(jié)果的文件的目錄
fileDic = os.listdir(FileUrl) #獲取所有待處理文件的文件名
openResult = open(resultUrl,'w')
i = 0
for files in fileDic: #遍歷fileDic(其實就是FileFrom)中的每個文件
i += 1
fileUrl = FileFrom + '/' + files #合成每個文檔的路徑
if os.path.isfile(fileUrl): #判斷該路徑是否為文件
#以下為對每個文件預(yù)處理,獲得預(yù)處理結(jié)果cutResult
afile = open(fileUrl,'r')
readyDoc = "".join(afile.readlines())
if not WordToDel: #如果WordToDel為空
document = ReplaceWord(readyDoc,"","\t","\n"," ")
#刪除某些特殊字符,如\t,\n以保證成為一行
else:
document = ReplaceWord(readyDoc,'',*WordToDel)
unicodeDoc = beDecode(document,*InTypeList)
cutResult = fullcut(unicodeDoc)
#以下為對每個文件進行處理,獲得處理結(jié)果lastResult
fileName = beEecode(files, *OutTypeList)
openResult.write(fileName + '\t')
keyWords = []
for everyWord in cutResult:
everyWord = beEecode(everyWord,*OutTypeList)
keyWords.append(everyWord)
lastResult = ','.join(keyWords)
#將結(jié)果寫入輸出文件中
openResult.write(lastResult)
openResult.write('\n')
# part4
# 計算TF-IDF值
def toTFIDF(fileFrom): #fileFrom就是待處理文檔的文件名字,也就是toGroupFile()函數(shù)處理的結(jié)果
oneFileInfo = {}
allFileInfo = {}
TFIDFInfo = []
fileNum = 0
#以下主要計算三個信息,oneFileInfo、allFileInfo、fileNum
fileUrl = fileFrom.replace('\\', '/')
openFile = open(fileUrl, 'r')
data = openFile.readline() #每次讀一行就是一個完整的文檔
while (data != ""):
wordInfo = {} #記錄每個文檔中出現(xiàn)的詞,不重復(fù)統(tǒng)計
wordInfo.clear()
DataStep1 = data.strip("\n").split("\t")
fileName = DataStep1[0]
DataStep2 = DataStep1[1].split(",")
fileLen = len(DataStep2)
fileNum += 1
for word in DataStep2:
if word not in allFileInfo:
allFileInfo[word] = 1
wordInfo[word] = 1
else:
if word not in wordInfo: # 如果這個單詞在這個文件中之前沒有出現(xiàn)過
allFileInfo[word] += 1
wordInfo[word] = 1
if not oneFileInfo.has_key(fileName):
oneFileInfo[fileName] = {}
if not oneFileInfo[fileName].has_key(word):
oneFileInfo[fileName][word] = []
oneFileInfo[fileName][word].append(DataStep2.count(word))
oneFileInfo[fileName][word].append(fileLen)
data = openFile.readline()
openFile.close()
#以下計算TFIDF值
if (oneFileInfo) and (allFileInfo) and (fileNum != 0):
for filenames in oneFileInfo.keys():
TFIDFstep1 = {}
TFIDFstep1.clear()
for word in oneFileInfo[filenames].keys():
wordNum = oneFileInfo[filenames][word][0]
wordSum = oneFileInfo[filenames][word][1]
filesNumOfWord = allFileInfo[word]
TFIDFstep1[word] = ((wordNum / wordSum)) * (math.log10(fileNum / filesNumOfWord))
# 將結(jié)果添加到TFIDFInfo
TFIDFstep2 = sorted(TFIDFstep1.iteritems(), key=lambda x: x[1], reverse=True) #按TF-IDF值降序排列
TFIDFInfo.append(filenames) #首先把文件名加入
TFIDFInfo.extend(TFIDFstep2[0:10]) #這里每個文檔只記錄了top10的數(shù)據(jù)
TFIDFInfo.append('\n')
#將最終結(jié)果寫入results.txt中
f = open("results.txt", "a+")
for infos in TFIDFInfo:
for i in infos:
f.write(str(i))
f.write("\n")
f.close()