物以类聚人以群分,通过GensimLda文本聚类构建人工智能个性化推荐系统(Python3.10)

众所周知,个性化推荐系统能够根据用户的兴趣、偏好等信息向用户推荐相关内容,使得用户更感兴趣,从而提升用户体验,提高用户粘度,之前我们曾经使用协同过滤算法构建过个性化推荐系统,但基于显式反馈的算法就会有一定的局限性,本次我们使用无监督的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帮我们进行聚类看看结果:

可以看到,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

posted @ 2023-01-09 15:10  刘悦的技术博客  阅读(621)  评论(0编辑  收藏  举报