物以類聚人以群分,透過GensimLda文字聚類構建人工智慧個性化推薦系統(Python3.10)

劉悅的技術部落格 發表於 2023-01-09
人工智慧 Python

眾所周知,個性化推薦系統能夠根據使用者的興趣、偏好等資訊向使用者推薦相關內容,使得使用者更感興趣,從而提升使用者體驗,提高使用者粘度,之前我們曾經使用協同過濾演算法構建過個性化推薦系統,但基於顯式反饋的演算法就會有一定的侷限性,本次我們使用無監督的Lda文字聚類方式來構建文字的個性化推薦系統。

推薦演算法:協同過濾/Lda聚類

我們知道,協同過濾演算法是一種基於使用者的歷史行為來推薦物品的演算法。協同過濾演算法利用使用者之間的相似性來推薦物品,如果兩個使用者對某些物品的評分相似,則協同過濾演算法會將這兩個使用者視為相似的,並向其中一個使用者推薦另一個使用者喜歡的物品。

說白了,它基於使用者的顯式反饋,什麼是顯式反饋?舉個例子,本如本篇文章,使用者看了之後,可能會點贊,也可能會瘋狂點踩,或者寫一些關於文字的評論,當然評論內容可能是負面、正面或者中性,所有這些使用者給出的行為,都是顯式反饋,但如果使用者沒有反饋出這些行為,就只是看了看,協同過濾演算法的效果就會變差。

LDA聚類是一種文字聚類演算法,它透過對文字進行主題建模來聚類文字。LDA聚類演算法在聚類文字時,不考慮使用者的歷史行為,而是根據文字的內容和主題來聚類。

說得通俗一點,協同過濾是一種主動推薦,系統根據使用者歷史行為來進行內容推薦,而LDA聚類則是一種被動推薦,在使用者還沒有產生使用者行為時,就已經開始推薦動作。

LDA聚類的主要目的是將文字分為幾類,使得每類文字的主題儘可能相似。

LDA聚類演算法的工作流程大致如下:

1.對文字進行預處理,去除停用詞等。

2.使用LDA模型對文字進行主題建模,得到文字的主題分佈。

3.將文字按照主題分佈相似性進行聚類。

4.將聚類結果作為類標籤,對文字進行分類。

大體上,LDA聚類演算法是一種自動將文字分類的演算法,它透過對文字進行主題建模,將文字按照主題相似性進行聚類,最終實現文字的分類。

Python3.10實現

實際應用層面,我們需要做的是讓主題模型能夠識別在文字里的主題,並且挖掘文字資訊中隱式資訊,並且在主題聚合、從非結構化文字中提取資訊。

首先安裝分詞以及聚類模型庫:

pip3 install jieba  
pip3 install gensim

隨後進行分詞操作,這裡以筆者的幾篇文章為例子:

import jieba  
import pandas as pd  
import numpy as np  
title1="乾坤大挪移,如何將同步阻塞(sync)三方庫包轉換為非同步非阻塞(async)模式?Python3.10實現。"  
title2="Generator(生成器),入門初基,Coroutine(原生協程),登峰造極,Python3.10併發非同步程式設計async底層實現"  
title3="週而復始,往復迴圈,遞迴、尾遞迴演算法與無限極層級結構的探究和使用(Golang1.18)"  
title4="彩虹女神躍長空,Go語言進階之Go語言高效能Web框架Iris專案實戰-JWT和中介軟體(Middleware)的使用EP07"  
content = [title1,title2, title3,title4]  
  
  
#分詞  
content_S = []  
all_words = []  
for line in content:  
    current_segment = [w for w in jieba.cut(line) if len(w)>1]  
    for x in current_segment:  
        all_words.append(x)  
    if len(current_segment) > 1 and current_segment != '\r\t':  
        content_S.append(current_segment)  
#分詞結果轉為DataFrame  
df_content = pd.DataFrame({'content_S':content_S})  
  
print(all_words)

可以看到,這裡透過四篇文章標題構建分詞列表,最後列印分詞結果:

['乾坤', '挪移', '如何', '同步', '阻塞', 'sync', '三方', '庫包', '轉換', '非同步', '阻塞', 'async', '模式', 'Python3.10', '實現', 'Generator', '生成器', '入門', '初基', 'Coroutine', '原生', '協程', '登峰造極', 'Python3.10', '併發', '非同步', '程式設計', 'async', '底層', '實現', '週而復始', '往復', '迴圈', '遞迴', '遞迴', '演算法', '無限極', '層級', '結構', '探究', '使用', 'Golang1.18', '彩虹', '女神', '長空', 'Go', '語言', '進階', 'Go', '語言', '高效能', 'Web', '框架', 'Iris', '專案', '實戰', 'JWT', '中介軟體', 'Middleware', '使用', 'EP07']

接著就可以針對這些詞進行聚類操作,我們可以先讓ChatGPT幫我們進行聚類看看結果:

物以類聚人以群分,透過GensimLda文字聚類構建人工智慧個性化推薦系統(Python3.10)

可以看到,ChatGPT已經幫我們將分詞結果進行聚類操作,分為兩大類:Python和Golang。

嚴謹起見,我們可以針對分詞結果進行過濾操作,過濾內容是停用詞,停用詞是在文字分析、自然語言處理等應用中,用來過濾掉不需要的詞的。通常來說,停用詞是指在英文中的介詞、代詞、連線詞等常用詞,在中文中的助詞、介詞、連詞等常用詞:

———  
》),  
)÷(1-  
”,  
)、  
=(  
:  
→  
℃   
&  
*  
一一  
~~~~  
’  
.   
『  
.一  
./  
--   
』  
=″  
【  
[*]  
}>  
[⑤]]  
[①D]  
c]  
ng昉  
*  
//  
[  
]  
[②e]  
[②g]  
={  
}  
,也   
‘  
A  
[①⑥]  
[②B]   
[①a]  
[④a]  
[①③]  
[③h]  
③]  
1.   
--   
[②b]  
’‘   
×××   
[①⑧]  
0:2   
=[  
[⑤b]  
[②c]   
[④b]  
[②③]  
[③a]  
[④c]  
[①⑤]  
[①⑦]  
[①g]  
∈[   
[①⑨]  
[①④]  
[①c]  
[②f]  
[②⑧]  
[②①]  
[①C]  
[③c]  
[③g]  
[②⑤]  
[②②]  
一.  
[①h]  
.數  
[]  
[①B]  
數/  
[①i]  
[③e]  
[①①]  
[④d]  
[④e]  
[③b]  
[⑤a]  
[①A]  
[②⑧]  
[②⑦]  
[①d]  
[②j]  
〕〔  
][  
://  
′∈  
[②④  
[⑤e]  
12%  
b]  
...  
...................  
…………………………………………………③  
ZXFITL  
[③F]  
」  
[①o]  
]∧′=[   
∪φ∈  
′|  
{-  
②c  
}  
[③①]  
R.L.  
[①E]  
Ψ  
-[*]-  
↑  
.日   
[②d]  
[②  
[②⑦]  
[②②]  
[③e]  
[①i]  
[①B]  
[①h]  
[①d]  
[①g]  
[①②]  
[②a]  
f]  
[⑩]  
a]  
[①e]  
[②h]  
[②⑥]  
[③d]  
[②⑩]  
e]  
〉  
】  
元/噸  
[②⑩]  
2.3%  
5:0    
[①]  
::  
[②]  
[③]  
[④]  
[⑤]  
[⑥]  
[⑦]  
[⑧]  
[⑨]   
……  
——  
?  
、  
。  
“  
”  
《  
》  
!  
,  
:  
;  
?  
.  
,  
.  
'  
?   
·  
———  
──  
?   
—  
<  
>  
(  
)  
〔  
〕  
[  
]  
(  
)  
-  
+  
~  
×  
/  
/  
①  
②  
③  
④  
⑤  
⑥  
⑦  
⑧  
⑨  
⑩  
Ⅲ  
В  
"  
;  
#  
@  
γ  
μ  
φ  
φ.  
×   
Δ  
■  
▲  
sub  
exp   
sup  
sub  
Lex   
#  
%  
&  
'  
+  
+ξ  
++  
-  
-β  
<  
<±  
<Δ  
<λ  
<φ  
<<  
=  
=  
=☆  
=-  
>  
>λ  
_  
~±  
~+  
[⑤f]  
[⑤d]  
[②i]  
≈   
[②G]  
[①f]  
LI  
㈧   
[-  
......  
〉  
[③⑩]  
第二  
一番  
一直  
一個  
一些  
許多  
種  
有的是  
也就是說  
末##末  
啊  
阿  
哎  
哎呀  
哎喲  
唉  
俺  
俺們  
按  
按照  
吧  
吧噠  
把  
罷了  
被  
本  
本著  
比  
比方  
比如  
鄙人  
彼  
彼此  
邊  
別  
別的  
別說  
並  
並且  
不比  
不成  
不單  
不但  
不獨  
不管  
不光  
不過  
不僅  
不拘  
不論  
不怕  
不然  
不如  
不特  
不惟  
不問  
不只  
朝  
朝著  
趁  
趁著  
乘  
衝  
除  
除此之外  
除非  
除了  
此  
此間  
此外  
從  
從而  
打  
待  
但  
但是  
當  
當著  
到  
得  
的  
的話  
等  
等等  
地  
第  
叮咚  
對  
對於  
多  
多少  
而  
而況  
而且  
而是  
而外  
而言  
而已  
爾後  
反過來  
反過來說  
反之  
非但  
非徒  
否則  
嘎  
嘎登  
該  
趕  
個  
各  
各個  
各位  
各種  
各自  
給  
根據  
跟  
故  
故此  
固然  
關於  
管  
歸  
果然  
果真  
過  
哈  
哈哈  
呵  
和  
何  
何處  
何況  
何時  
嘿  
哼  
哼唷  
呼哧  
乎  
譁  
還是  
還有  
換句話說  
換言之  
或  
或是  
或者  
極了  
及  
及其  
及至  
即  
即便  
即或  
即令  
即若  
即使  
幾  
幾時  
己  
既  
既然  
既是  
繼而  
加之  
假如  
假若  
假使  
鑑於  
將  
較  
較之  
叫  
接著  
結果  
借  
緊接著  
進而  
盡  
儘管  
經  
經過  
就  
就是  
就是說  
據  
具體地說  
具體說來  
開始  
開外  
靠  
咳  
可  
可見  
可是  
可以  
況且  
啦  
來  
來著  
離  
例如  
哩  
連  
連同  
兩者  
了  
臨  
另  
另外  
另一方面  
論  
嘛  
嗎  
慢說  
漫說  
冒  
麼  
每  
每當  
們  
莫若  
某  
某個  
某些  
拿  
哪  
哪邊  
哪兒  
哪個  
哪裡  
哪年  
哪怕  
哪天  
哪些  
哪樣  
那  
那邊  
那兒  
那個  
那會兒  
那裡  
那麼  
那麼些  
那麼樣  
那時  
那些  
那樣  
乃  
乃至  
呢  
能  
你  
你們  
您  
寧  
寧可  
寧肯  
寧願  
哦  
嘔  
啪達  
旁人  
呸  
憑  
憑藉  
其  
其次  
其二  
其他  
其它  
其一  
其餘  
其中  
起  
起見  
起見  
豈但  
恰恰相反  
前後  
前者  
且  
然而  
然後  
然則  
讓  
人家  
任  
任何  
任憑  
如  
如此  
如果  
如何  
如其  
如若  
如上所述  
若  
若非  
若是  
啥  
上下  
尚且  
設若  
設使  
甚而  
甚麼  
甚至  
省得  
時候  
什麼  
什麼樣  
使得  
是  
是的  
首先  
誰  
誰知  
順  
順著  
似的  
雖  
雖然  
雖說  
雖則  
隨  
隨著  
所  
所以  
他  
他們  
他人  
它  
它們  
她  
她們  
倘  
倘或  
倘然  
倘若  
倘使  
騰  
替  
透過  
同  
同時  
哇  
萬一  
往  
望  
為  
為何  
為了  
為什麼  
為著  
喂  
嗡嗡  
我  
我們  
嗚  
嗚呼  
烏乎  
無論  
無寧  
毋寧  
嘻  
嚇  
相對而言  
像  
向  
向著  
噓  
呀  
焉  
沿  
沿著  
要  
要不  
要不然  
要不是  
要麼  
要是  
也  
也罷  
也好  
一  
一般  
一旦  
一方面  
一來  
一切  
一樣  
一則  
依  
依照  
矣  
以  
以便  
以及  
以免  
以至  
以至於  
以致  
抑或  
因  
因此  
因而  
因為  
喲  
用  
由  
由此可見  
由於  
有  
有的  
有關  
有些  
又  
於  
於是  
於是乎  
與  
與此同時  
與否  
與其  
越是  
云云  
哉  
再說  
再者  
在  
在下  
我們  
我們們  
則  
怎  
怎麼  
怎麼辦  
怎麼樣  
怎樣  
咋  
照  
照著  
者  
這  
這邊  
這兒  
這個  
這會兒  
這就是說  
這裡  
這麼  
這麼點兒  
這麼些  
這麼樣  
這時  
這些  
這樣  
正如  
吱  
之  
之類  
之所以  
之一  
只是  
只限  
只要  
只有  
至  
至於  
諸位  
著  
著呢  
自  
自從  
自個兒  
自各兒  
自己  
自家  
自身  
綜上所述  
總的來看  
總的來說  
總的說來  
總而言之  
總之  
縱  
縱令  
縱然  
縱使  
遵照  
作為  
兮  
呃  
唄  
咚  
咦  
喏  
啐  
喔唷  
嗬  
嗯  
噯

這裡使用哈工大的停用詞列表。

首先載入停用詞列表,然後進行過濾操作:

#去除停用詞  
def drop_stopwords(contents,stopwords):  
    contents_clean = []  
    all_words = []  
    for line in contents:  
        line_clean = []  
        for word in line:  
            if word in stopwords:  
                continue  
            line_clean.append(word)  
            all_words.append(word)  
        contents_clean.append(line_clean)  
    return contents_clean,all_words  
  
#停用詞載入  
stopwords = pd.read_table('stop_words.txt',names = ['stopword'],quoting = 3)  
contents = df_content.content_S.values.tolist()  
  
contents_clean,all_words = drop_stopwords(contents,stopwords)

接著交給Gensim進行聚類操作:



from gensim import corpora,models,similarities  
import gensim

dictionary = corpora.Dictionary(contents_clean)  
corpus = [dictionary.doc2bow(sentence) for sentence in contents_clean]  
lda = gensim.models.ldamodel.LdaModel(corpus=corpus,id2word=dictionary,num_topics=2,random_state=3)  
  
#print(lda.print_topics(num_topics=2, num_words=4))  
  
for e, values in enumerate(lda.inference(corpus)[0]):  
    print(content[e])  
    for ee, value in enumerate(values):  
        print('\t分類%d推斷值%.2f' % (ee, value))


這裡使用LdaModel模型進行訓練,分類設定(num_topics)為2種,隨機種子(random_state)為3,在訓練機器學習模型時,很多模型的訓練過程都會涉及到隨機數的生成,例如隨機梯度下降法(SGD)就是一種隨機梯度下降的最佳化演算法。在訓練過程中,如果不設定random_state引數,則每次訓練結果可能都不同。而設定random_state引數後,每次訓練結果都會相同,這就方便了我們在調參時對比模型的效果。如果想要讓每次訓練的結果都隨機,可以將random_state引數設定為None。

程式返回:

[['乾坤', '挪移', '同步', '阻塞', 'sync', '三方', '庫包', '轉換', '非同步', '阻塞', 'async', '模式', 'Python3.10', '實現'], ['Generator', '生成器', '入門', '初基', 'Coroutine', '原生', '協程', '登峰造極', 'Python3.10', '併發', '非同步', '程式設計', 'async', '底層', '實現'], ['週而復始', '往復', '迴圈', '遞迴', '遞迴', '演算法', '無限極', '層級', '結構', '探究', '使用', 'Golang1.18'], ['彩虹', '女神', '長空', 'Go', '語言', '進階', 'Go', '語言', '高效能', 'Web', '框架', 'Iris', '專案', '實戰', 'JWT', '中介軟體', 'Middleware', '使用', 'EP07']]  
乾坤大挪移,如何將同步阻塞(sync)三方庫包轉換為非同步非阻塞(async)模式?Python3.10實現。  
        分類0推斷值0.57  
        分類1推斷值14.43  
Generator(生成器),入門初基,Coroutine(原生協程),登峰造極,Python3.10併發非同步程式設計async底層實現  
        分類0推斷值0.58  
        分類1推斷值15.42  
週而復始,往復迴圈,遞迴、尾遞迴演算法與無限極層級結構的探究和使用(Golang1.18)  
        分類0推斷值12.38  
        分類1推斷值0.62  
彩虹女神躍長空,Go語言進階之Go語言高效能Web框架Iris專案實戰-JWT和中介軟體(Middleware)的使用EP07  
        分類0推斷值19.19  
        分類1推斷值0.81

可以看到,結果和ChatGPT聚類結果一致,前兩篇為一種分類,後兩篇為另外一種分類。

隨後可以將聚類結果儲存為模型檔案:

lda.save('mymodel.model')

以後有新的文章釋出,直接對新的文章進行分類推測即可:

from gensim.models import  ldamodel  
import pandas as pd  
import jieba  
from gensim import corpora  
  
doc0="巧如範金,精比琢玉,一分鐘高效打造精美詳實的Go語言技術簡歷(Golang1.18)"  
# 載入模型  
lda = ldamodel.LdaModel.load('mymodel.model')  
  
content = [doc0]  
  
#分詞  
content_S = []  
for line in content:  
    current_segment = [w for w in jieba.cut(line) if len(w)>1]  
    if len(current_segment) > 1 and current_segment != '\r\t':  
        content_S.append(current_segment)  
#分詞結果轉為DataFrame  
df_content = pd.DataFrame({'content_S':content_S})  
  
  
#去除停用詞  
def drop_stopwords(contents,stopwords):  
    contents_clean = []  
    all_words = []  
    for line in contents:  
        line_clean = []  
        for word in line:  
            if word in stopwords:  
                continue  
            line_clean.append(word)  
            all_words.append(word)  
        contents_clean.append(line_clean)  
    return contents_clean,all_words  
  
#停用詞載入  
stopwords = pd.read_table('stop_words.txt',names = ['stopword'],quoting = 3)  
contents = df_content.content_S.values.tolist()  
  
contents_clean,all_words = drop_stopwords(contents,stopwords)  
  
  
dictionary = corpora.Dictionary(contents_clean)  
  
word = [w for w in jieba.cut(doc0)]  
  
bow = dictionary.doc2bow(word)  
print(lda.get_document_topics(bow))

程式返回:

➜  nlp_chinese /opt/homebrew/bin/python3.10 "/Users/liuyue/wodfan/work/nlp_chinese/new_text.py"  
Building prefix dict from the default dictionary ...  
Loading model from cache /var/folders/5x/gpftd0654bv7zvzyv39449rc0000gp/T/jieba.cache  
Loading model cost 0.264 seconds.  
Prefix dict has been built successfully.  
[(0, 0.038379338), (1, 0.9616206)]

這裡顯示文章推斷結果為分類2,也就是Golang型別的文章。

完整呼叫邏輯:

import jieba  
import pandas as pd  
import numpy as np  
from gensim.models import  ldamodel  
from gensim import corpora,models,similarities  
import gensim  
  
  
class LdaRec:  
  
    def __init__(self,cotent:list) -> None:  
          
        self.content = content  
        self.contents_clean = []  
        self.lda = None  
  
    def test_text(self,content:str):  
  
        self.lda = ldamodel.LdaModel.load('mymodel.model')  
        self.content = [content]  
  
        #分詞  
        content_S = []  
        for line in self.content:  
            current_segment = [w for w in jieba.cut(line) if len(w)>1]  
            if len(current_segment) > 1 and current_segment != '\r\t':  
                content_S.append(current_segment)  
        #分詞結果轉為DataFrame  
        df_content = pd.DataFrame({'content_S':content_S})  
  
        contents = df_content.content_S.values.tolist()  
  
        dictionary = corpora.Dictionary(contents)  
  
        word = [w for w in jieba.cut(content)]  
  
        bow = dictionary.doc2bow(word)  
        print(self.lda.get_document_topics(bow))  
  
  
    # 訓練  
    def train(self,num_topics=2,random_state=3):  
  
        dictionary = corpora.Dictionary(self.contents_clean)  
        corpus = [dictionary.doc2bow(sentence) for sentence in self.contents_clean]  
        self.lda = gensim.models.ldamodel.LdaModel(corpus=corpus,id2word=dictionary,num_topics=num_topics,random_state=random_state)  
  
        for e, values in enumerate(self.lda.inference(corpus)[0]):  
            print(self.content[e])  
            for ee, value in enumerate(values):  
                print('\t分類%d推斷值%.2f' % (ee, value))  
  
  
    # 過濾停用詞  
    def drop_stopwords(self,contents,stopwords):  
        contents_clean = []  
        for line in contents:  
            line_clean = []  
            for word in line:  
                if word in stopwords:  
                    continue  
                line_clean.append(word)  
            contents_clean.append(line_clean)  
        return contents_clean  
  
    def cut_word(self) -> list:  
        #分詞  
        content_S = []  
        for line in self.content:  
            current_segment = [w for w in jieba.cut(line) if len(w)>1]  
            if len(current_segment) > 1 and current_segment != '\r\t':  
                content_S.append(current_segment)  
  
        #分詞結果轉為DataFrame  
        df_content = pd.DataFrame({'content_S':content_S})  
  
        # 停用詞列表  
        stopwords = pd.read_table('stop_words.txt',names = ['stopword'],quoting = 3)  
  
        contents = df_content.content_S.values.tolist()  
        stopwords = stopwords.stopword.values.tolist()  
  
        self.contents_clean = self.drop_stopwords(contents,stopwords)  
  
  
if __name__ == '__main__':  
      
    title1="乾坤大挪移,如何將同步阻塞(sync)三方庫包轉換為非同步非阻塞(async)模式?Python3.10實現。"  
    title2="Generator(生成器),入門初基,Coroutine(原生協程),登峰造極,Python3.10併發非同步程式設計async底層實現"  
    title3="週而復始,往復迴圈,遞迴、尾遞迴演算法與無限極層級結構的探究和使用(Golang1.18)"  
    title4="彩虹女神躍長空,Go語言進階之Go語言高效能Web框架Iris專案實戰-JWT和中介軟體(Middleware)的使用EP07"  
    content = [title1,title2, title3,title4]  
  
    lr = LdaRec(content)  
  
    lr.cut_word()  
  
    lr.train()  
  
    lr.lda.save('mymodel.model')  
  
    lr.test_text("巧如範金,精比琢玉,一分鐘高效打造精美詳實的Go語言技術簡歷(Golang1.18)")

至此,基於聚類的推薦系統構建完畢,每一篇文章只需要透過既有分類模型進行訓練,推斷分類之後,給使用者推送同一分類下的文章即可,截止本文釋出,該分類模型已經在本站進行落地實踐:

物以類聚人以群分,透過GensimLda文字聚類構建人工智慧個性化推薦系統(Python3.10)

結語

金無足赤,LDA聚類演算法也不是萬能的,LDA聚類演算法有許多超引數,包括主題個數、學習率、迭代次數等,這些引數的設定對結果有很大影響,但是很難確定最優引數,同時聚類演算法的時間複雜度是O(n^2)級別的,在處理大規模文字資料時,計算速度較慢,反之,在樣本資料較少的情況下,模型的泛化能力較差。最後,奉上專案地址,與君共觴:https://github.com/zcxey2911/Lda-Gensim-Recommended-System-Python310