1. 程式人生 > >推薦系統技術之文字相似性計算(三)

推薦系統技術之文字相似性計算(三)

前面說了兩篇了,分別介紹了TFIDF和向量空間的相關東西,然後介紹了主題模型,這一篇我們就來試試這兩個東西。詞向量就不在這篇試了,詞向量和這兩個關係不大,不好對比,不過我最後也給出了程式碼。

0. 工具準備

工欲善其事,必先利其器,那麼我們先來利其器,這裡我們使用的是python的gensim工具包,地址是:https://radimrehurek.com/gensim/index.html,這個工具包很強大,我就不一一介紹了,反正我們需要的功能都有,而且我們用得很簡單,它還可以分散式部署,感興趣可以去官網看具體介紹。

為什麼不自己寫?這個問題....呵呵.....呵呵....我寫不出來.....

至於安裝,需要先安裝python 2.6以上

(廢話),NumPy 1.3以上,SciPy 0.7以上,後兩個是python的科學計算的包。

easy_install很容易搞定,這裡就不廢話了,windows上安裝可能有點困難,但我很久沒用過windows了,我電腦上安裝很輕鬆,三四個命令搞定,可以去看gensim的官方文件,上面也有怎麼安裝,如果你裝都裝不上,那就google,百度,總有解決辦法。

除了gensim,還有個分詞的包需要裝一下,就是jieba分詞,這個也很容易裝。

1. 資料準備

資料準備可是個技術活,我的職業操守很高,沒有用公司的資料,那隻能自己找資料了,如果直接找網上的語料,顯得太Low了。於是我自己爬了一些資料。

首先,我瞄準了目前全國最火的全棧技術網站(就是SegmentFault啦),然後瞄準了一個汽車網站,於是開始爬資料,自己寫了個爬蟲開始爬資料,恩,我的爬蟲我覺得還可以,排程器+爬取器組成,爬取器外掛化了,可以使用任意語言做編寫,甚至可以直接對接chrome

爬取純JS單頁面網站爬取,也支援代理池,如果大家感興趣我也可以說說爬蟲相關的東西,分散式的哦,可以隨便加機器增加爬取能力。

這裡說個小故事,爬取沙發站(SegmentFault.com)的時候,我先擼了一遍所有文章的列表頁,本來我就想把標題爬下來就行了,結果發現SF站沒有反應,瞬間就爬下來了,於是大意了,在沒掛代理池的情況下,直接開始擼文章的詳情頁,想把文章都爬下來,結果臥槽,爬到6000多篇的時候把我的IP給封了。於是。。我現在只能掛VPN上SF站,掛VPN啊!!!去新加坡打個轉來上一個杭州的網站!!如果有管理員或者運營人員看到這篇文章,麻煩給解個封吧。我真沒有惡意,不然也不寫在這了。

並且,作為一個老程式設計師,還是個後端人員,跑到一個90後遍地的前端為主的技術社群寫後端東西,看的人本來就不多,應該在oschina,cnblog,csdn這種地方寫文章的。這是一種什麼精神??國際主義精神啊!因為SF的配色我實在是太喜歡了,而且markdown的渲染效果也很好看,並且手機上看效果也非常贊,於是就來了,看的人少就少吧,反正主要給自己看。。。。

好了,八卦完了,爬了兩個網站,可以開始幹活了,爬兩個型別的網站是為了說明後面LDA主題模型,大家就有個認識了。

2. 資料清理

資料爬下來後,要做的就是資料清洗工作了,我之前有一篇搞機器學習的技能說了,資料的清理是一個演算法工程師的必備技能,如果沒有好的資料,演算法怎麼好都沒用。

拿到資料以後,寫個指令碼

  • 首先把標題,作者,時間之類的提取出來,通過正則也好,xpath也好,都很容易把這些東西提取出來。

  • 然後把html標籤幹掉,一堆正則就行了,剩下的基本上就是正文了,另外,SF站的東西還特殊處理了一下,把中的內容幹掉了,一堆程式碼對我來說沒什麼用。

  • 最後,把標點符號幹掉,把特殊符號幹掉,調整一下格式,最後的每一篇文章都變成下面的樣子

ID(實際上是url)[TAB]TITLE(標題)[TAB]CONTENT(文章詳情)

一共有11628篇文章,其中汽車類大約6000,技術類(SF站)大約6000,好了,資料也基本上清洗好了。

4. 訓練資料

都覺得這一節才是重點,其實有jieba分詞和gensim以後,程式碼非常簡單,不超過50行,我們來一步一步玩。

4.1 分詞--建立詞典--準備數字語料

分詞是基礎,首先進行分詞

from gensim import corpora,models,similarities,utils
import jieba
import jieba.posseg as pseg
jieba.load_userdict( "user_dic.txt" ) #載入自定義的詞典,主要有一些計算機詞彙和汽車型號的詞彙
#定義原始語料集合
train_set=[]
f=open("./data/all.txt")
lines=f.readlines()
for line in lines:
    content = (line.lower()).split("\t")[2] + (line.lower()).split("\t")[1]
    #切詞,etl函式用於去掉無用的符號,cut_all表示非全切分
    word_list = filter(lambda x: len(x)>0,map(etl,jieba.cut(content,cut_all=False)))
    train_set.append(word_list)
f.close()

得到的tain_set就是原始語料了,然後對這些語料匯入到詞典中,建立一個詞典。

#生成字典
dictionary = corpora.Dictionary(train_set)
#去除極低頻的雜質詞
dictionary.filter_extremes(no_below=1,no_above=1,keep_n=None)
#將詞典儲存下來,方便後續使用
dictionary.save(output + "all.dic")

將語料匯入詞典後,每個詞實際上就已經被編號成1,2,3....這種編號了,這是向量化的第一步,然後把詞典儲存下來。
然後生成數字語料

corpus = [dictionary.doc2bow(text) for text in train_set]

這一句表示把每一條原始資料向量化成編號,這樣以後,corpus這個變數是個二維資料,每一行表示一個文件的每個詞的編號和詞頻,每一行像這樣

[(1,2),(2,4),(5,2)....] 表示編號為1的詞出現了2次,編號為2的詞出現了4次....

OK,前期準備OK了,原始文章通過切詞-->建立詞典-->生成語料後已經被我們數字化了,後面就簡單了。

4.1 TFIDF模型

有了數字語料以後,我們可以生成一個TFIDF模型

#使用數字語料生成TFIDF模型
tfidfModel = models.TfidfModel(corpus)
#儲存tfidfModel
tfidfModel.save(output + "allTFIDF.mdl")

這一句是關鍵,我們用了原始的數字語料,生成了一個TFIDF模型,這個模型能幹什麼呢?gensim過載了[]操作符,我們可以用類似[(1,2),(2,4),(5,2)....]的原始向量傳進去,變成一個tfidf的向量,像這樣[(1,0.98),(2,0.23),(5,0.56)....],這樣就說明編號1的詞的重要性比後面兩個詞都要大,這個向量可以作為後面LDA的原始向量輸入。

然後我們把所有的語料都TFIDF向量化,並作為一個索引資料存起來方便以後查詢的時候使用

#把全部語料向量化成TFIDF模式,這個tfidfModel可以傳入二維陣列
tfidfVectors = tfidfModel[corpus]
#建立索引並儲存
indexTfidf = similarities.MatrixSimilarity(tfidfVectors)
indexTfidf.save(output + "allTFIDF.idx")

好了,TFIDF部分完了,先記下來,我們生成了一個模型資料(allTFIDF.mdl),生成了一份全部語料的TFIDF向量的索引資料(allTFIDF.idx),加上上面的詞典資料(all.dic),我們現在有三份資料了,後面再說怎麼用,現在先繼續LDA部分。

4.2 LDA模型

LDA上一篇講了那麼多,在gensim看來就是下面幾行程式碼,而且使用了傳說中的機器學習哦。只能說gensim的程式碼封裝得太簡潔了。

#通過TFIDF向量生成LDA模型,id2word表示編號的對應詞典,num_topics表示主題數,我們這裡設定的50,主題太多時間受不了。
lda = models.LdaModel(tfidfVectors, id2word=dictionary, num_topics=50)
#把模型儲存下來
lda.save(output + "allLDA50Topic.mdl")
#把所有TFIDF向量變成LDA的向量
corpus_lda = lda[tfidfVectors]
#建立索引,把LDA資料儲存下來
indexLDA = similarities.MatrixSimilarity(corpus_lda)
indexLDA.save(output + "allLDA50Topic.idx")

雖然只有這三步,但是還是挺耗時的,在log開啟的情況下可以看到處理過程,我隨便截取了幾個,像下面一樣,很明顯,前面幾個主題都和汽車相關,後面幾個主題都和技術相關,看樣子還算比較靠譜的。

#38 (0.020): 0.003*新奇 + 0.003*駿 + 0.002*途安 + 0.002*配備 + 0.002*都市 + 0.001*除 + 0.001*昂科威
#27 (0.020): 0.003*配置 + 0.003*內飾 + 0.003*車型 + 0.002*氣囊 + 0.002*瑞風 + 0.002*萬元 + 0.002*逸緻 +
#0 (0.020): 0.004*奔騰 + 0.003*加速 + 0.003*嘉年華 + 0.002*油門 + 0.002*愛麗舍 + 0.002*秒
#49 (0.020): 0.004*瑞虎 + 0.004*紳寶 + 0.004*歐諾 + 0.002*雷克薩斯 + 0.002*車型 + 0.002*樂途 
#26 (0.020): 0.011*列表 + 0.009*流 + 0.007*快捷鍵 + 0.006*崩潰 + 0.002*大神 + 0.002*混淆 + 0.002*郵箱
#21 (0.020): 0.035*命令 + 0.018*瀏覽器 + 0.007*第三方 + 0.007*安裝 + 0.006*控制檯 
topic #25 (0.020): 0.064*檔案 + 0.004*約束 + 0.004*練習 + 0.003*複製到 + 0.003*就行了 + 0.003*反編譯

好了,LDA部分也完了,又多了兩個檔案allLDA50Topic.mdlallLDA50Topic.idx,加上前面的3個,一共5個檔案了,OK,休息一下,喝杯可樂,繼續下一步。

5. 驗證結果

好了,第四部分中不知不覺我們已經使用機器學習這麼高階的東西了,那現在要驗證一下這麼高階的東西到底效果如何了。

前面的TFIDF和LDA我們都儲存了模型和向量資料,那麼我們就用兩篇新的文章,來看看和這篇文章最相似的文章都有哪些來驗證這兩個模型靠譜不靠譜吧。

我隨便開啟一個汽車網站,選了一篇汽車的文章(寶馬的評測),再找了我之前的一篇技術的文章(講搜尋引擎的),並且只隨機截取了文章的一段進行測試。

看開頭這應該是一篇為全新寶馬X1 Li(下文簡稱新X1)洗地的文章,我想很多寶馬死忠、車神也已經準備移步評論........

一般情況下,搜尋引擎預設會認為索引是不會有太大的變化的,所以把索引分為全量索引和增量索引兩部分,全量索引一般是以天.......

好,文章選好了,先載入之前儲存的資料檔案

#載入字典
dictionary = corpora.Dictionary.load(output + "all.dic")
#載入TFIDF模型和索引
tfidfModel = models.TfidfModel.load(output+"allTFIDF.mdl")
indexTfidf = similarities.MatrixSimilarity.load(output + "allTFIDF.idx")
#載入LDA模型和索引
ldaModel = models.LdaModel.load(output + "allLDA50Topic.mdl")
indexLDA = similarities.MatrixSimilarity.load(output + "allLDA50Topic.idx")

然後把測試資料進行切詞TFIDF向量化找相似LDA向量化找相似

#query就是測試資料,先切詞
query_bow = dictionary.doc2bow(filter(lambda x: len(x)>0,map(etl,jieba.cut(query,cut_all=False))))
#使用TFIDF模型向量化
tfidfvect = tfidfModel[query_bow]
#然後LDA向量化,因為我們訓練時的LDA是在TFIDF基礎上做的,所以用itidfvect再向量化一次
ldavec = ldaModel[tfidfvect]
#TFIDF相似性
simstfidf = indexTfidf[tfidfvect]
#LDA相似性
simlda = indexLDA[ldavec]

好了,結束,所有程式碼就這麼些了。太簡單了。。。。我們來看看效果。

6 輸出結果

我們先看TFIDF的結果

  • 汽車的測試文章TFIDF結果(前10結果中隨機選3個)

優惠購車推薦寶馬x3優惠3.5-7萬元
保時捷macan競爭力分析寶馬x3 
寶馬2014年新車展望多達十餘款新車

  • 技術的測試文章TFIDF結果(前10結果中隨機選3個)

用golang寫一個搜尋引擎(0x06)
索引那點事
[搜尋引擎] sphinx 的介紹和原理探索

很明顯,結果基本都比較靠譜,第一個基本是說寶馬車的,第二個基本都在說搜尋引擎和索引。

我們再看看LDA的結果,LDA的主要功能是文字分類而不是關鍵詞的匹配,就是看測試文章分類分得對不對,我們這裡基本上是兩類文章,一類技術文章,一類汽車文章,所以我們通過找和測試文章最相似的文章,然後看看找出來最相似的文章是不是正好都是技術類的或者汽車類的,如果是,表示模型比較好。

  • 汽車的測試文章LDA結果(前10結果中隨機選3個)

編輯心中最美中級車一汽-大眾新cc
25萬時尚品質4款豪華緊湊車之賓士a級 
iphone手機html5上傳圖片方向問題解決

  • 技術的測試文章LDA結果(前10結果中隨機選3個)

java 多執行緒核心技術梳理(附原始碼)
springsession原理解析
併發中的鎖檔案模式

從結果看,基本比較靠譜,但汽車那個出現了一個badcaseiphone手機html5上傳圖片方向問題解決,這是篇技術文章,但是出現在了汽車類上面。

7. 結果分析

我們來分析一下這個結果,對於TFIDF模型,在現有資料集(12000篇文章)的情況下,推薦結果強相關,讓人覺得推薦結果很靠譜,這也是TFIDF這種演算法簡單有效的地方,他把文章中的關鍵詞很好的提取出來了,所以推薦的結果讓人覺得強相關,但是他也有自己的問題。

  • 對於比較短的文章(比如微博這類的),由於文字太短了,TFIDF比較難提取出重要的關鍵詞或者提取得不對,導致推薦結果不靠譜。

  • 單純以詞頻來說明這個詞的重要性感覺不全面,比如這篇文章,人類來看的話應該是文字相似性最重要,但有可能按TFIDF算出來是模型這個詞最重要。
    對於純文字的推薦系統來說,這種文字相關性的推薦可能比較適合垂直類的網站,比如像SegmentFault這種,看某篇文章的人很可能希望看到類似的文章,更深入的瞭解這個領域,這種演算法比較靠譜,不過據我觀察,SegmentFault是使用的標籤推薦,這種推薦效果更好,但人為因素更多點,要是我寫文章的時候隨便打標籤就比較麻煩了。

再來看看LDA模型,LDA主要用在文字聚類上,而且他的基礎是主題,如果把他作為推薦系統的演算法來使用,要看具體場景,他的推薦結果在資料樣本不太夠的情況下,可能看上去不太靠譜(即便樣本大可能也看上去不太好),顯得粒度很粗,但正因為很粗,所以比較適合做內容發現,比如我對數碼新聞感興趣,這種感興趣不僅僅是隻對iphone感興趣,只要是數碼這個主題的我都感興趣,所以用LDA可以很好的給我推薦數碼這個主題下的東西,這比正在看iphone的文章,下面全是iphone的文章要靠譜多了。

LDA出現上一節的哪種badcase的時候怎麼辦呢?因為基本不太可能改模型,那麼只能從幾個方面入手。

  • 如果只是偶爾的一兩個,可以選擇忍了。

  • 如果多的話,那隻能先調一調主題個數,然後LDA裡面有些個引數可以調調(演算法工程師的價值所在啊)

  • 還有一條路子就是把輸入的資料儘可能的清洗乾淨,把無用的雜質去掉(演算法工程師必備技能耐心和細心
    所以,不同的模型對於不同的場景是很重要的,根據你的場景選擇合適的模型才能達到合適的效果。

8. 寫在後面的話

這篇文章只是一個文字相似性的最最基本的文章,可以最直觀的瞭解一下TFIDF模型和LDA模型,同時,也使用了目前最熱的機器學習技術哦。

其實,像LDA,以及word2vec這種模型,已經是被數學抽象得很強的模型了,和實際場景基本上已經脫離了,已經完全數學化了,所以其實不一定要用在文字處理上,在流量分析,使用者行為分析上一樣有用,這就是演算法工程師要想的事情,一個好的演算法如何用在現有的場景中。

試想一下,如果我們想給我們的使用者分個類,看看哪些使用者興趣比較相似。我們其實可以這麼來做:

  • 首先,如果我們有一堆使用者的瀏覽行為資料,每一條資料記錄了使用者點選某個連結,或者點選了某個按鈕。

  • 把這些瀏覽行為按照使用者維度進行合併,那麼新資料中每一條資料就是一個使用者的操作記錄,按順序就是他幾點幾分有什麼行為。類似於使用者A :[瀏覽了a頁面,點選了b按鈕,瀏覽了c頁面....]

  • 好,如果我們發揮演算法工程師的必備技能之一----想象力,那麼我們把每個使用者的行為當成一篇文章,每個行為資料當成一個詞語,然後使用LDA.....呵呵
    這樣算出來的主題,是不是就是使用者的類別呢?有相似行為資料的使用者會出現在相同的主題下,那麼這樣就把這些使用者分類了,那麼是不是可以理解為同樣類別的下的使用者有著相似的愛好呢?如果你覺得可行,可以拿你公司的使用者資料試試,看看效果好不好:)

9. 後面的後面

最後,所有程式碼在github上,點選這裡可以看得到,程式碼相當簡單,整個不超過200行,核心的就是我上面列的那些,程式碼中也有word2vec的程式碼和使用,這篇文章就沒提了,另外,爬取的文章就不放上來了,太大了。

如果大家想要語料自己玩,可以上wiki百科,他們開放了他們的所有資料給全世界做語料分析,其中有中文的,地址是:https://dumps.wikimedia.org/zhwiki/latest/zhwiki-latest-pages-articles.xml.bz2,但維基上中文語料並不多,中文語料多的是百度百科,但看看百度百科,呵呵,不但不開放,防爬蟲跟防賊一樣,呵呵,不過我也給大家個地址,100G的百度百科原始頁面:http://pan.baidu.com/s/1i3wvfil ,接頭密碼:neqs,由亞洲第二爬蟲天王樑斌penny友情提供。

好了,今天的文章有點長,就到這了,後面會把演算法部分放一放,最近工作太忙,等這一段結束了,我會再說說演算法部分,因為現在工作中會有一些比較好玩的演算法要用,接下來的文章會主要寫寫系統架構方面的東西了,另外我自己的那個搜尋引擎目前太忙沒時間整,也要等一小段時間了,不好意思:)但放心,不會有頭無尾啦。