眾所周知,個性化推薦系統能夠根據使用者的興趣、偏好等資訊向用戶推薦相關內容,使得使用者更感興趣,從而提升使用者體驗,提高使用者粘度,之前我們曾經使用協同過濾演演算法構建過個性化推薦系統,但基於顯式反饋的演演算法就會有一定的侷限性,本次我們使用無監督的Lda文字聚類方式來構建文字的個性化推薦系統。
我們知道,協同過濾演演算法是一種基於使用者的歷史行為來推薦物品的演演算法。協同過濾演演算法利用使用者之間的相似性來推薦物品,如果兩個使用者對某些物品的評分相似,則協同過濾演演算法會將這兩個使用者視為相似的,並向其中一個使用者推薦另一個使用者喜歡的物品。
說白了,它基於使用者的顯式反饋,什麼是顯式反饋?舉個例子,本如本篇文章,使用者看了之後,可能會點贊,也可能會瘋狂點踩,或者寫一些關於文字的評論,當然評論內容可能是負面、正面或者中性,所有這些使用者給出的行為,都是顯式反饋,但如果使用者沒有反饋出這些行為,就只是看了看,協同過濾演演算法的效果就會變差。
LDA聚類是一種文字聚類演演算法,它通過對文字進行主題建模來聚類文字。LDA聚類演演算法在聚類文字時,不考慮使用者的歷史行為,而是根據文字的內容和主題來聚類。
說得通俗一點,協同過濾是一種主動推薦,系統根據使用者歷史行為來進行內容推薦,而LDA聚類則是一種被動推薦,在使用者還沒有產生使用者行為時,就已經開始推薦動作。
LDA聚類的主要目的是將文字分為幾類,使得每類文字的主題儘可能相似。
LDA聚類演演算法的工作流程大致如下:
1.對文字進行預處理,去除停用詞等。
2.使用LDA模型對文字進行主題建模,得到文字的主題分佈。
3.將文字按照主題分佈相似性進行聚類。
4.將聚類結果作為類標籤,對文字進行分類。
大體上,LDA聚類演演算法是一種自動將文字分類的演演算法,它通過對文字進行主題建模,將文字按照主題相似性進行聚類,最終實現文字的分類。
實際應用層面,我們需要做的是讓主題模型能夠識別在文字裡的主題,並且挖掘文字資訊中隱式資訊,並且在主題聚合、從非結構化文字中提取資訊。
首先安裝分詞以及聚類模型庫:
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幫我們進行聚類看看結果:
可以看到,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)")
至此,基於聚類的推薦系統構建完畢,每一篇文章只需要通過既有分類模型進行訓練,推斷分類之後,給使用者推播同一分類下的文章即可,截止本文釋出,該分類模型已經在本站進行落地實踐:
金無足赤,LDA聚類演演算法也不是萬能的,LDA聚類演演算法有許多超引數,包括主題個數、學習率、迭代次數等,這些引數的設定對結果有很大影響,但是很難確定最優引數,同時聚類演演算法的時間複雜度是O(n^2)級別的,在處理大規模文字資料時,計算速度較慢,反之,在樣本資料較少的情況下,模型的泛化能力較差。最後,奉上專案地址,與君共觴:https://github.com/zcxey2911/Lda-Gensim-Recommended-System-Python310