上篇文章链接:基于社交网络爬虫分析人物兴趣属性(一)
所有代码都已经发布在github上:https://github.com/smityliu/spider
本篇文章为介绍lda模型和从之前文章一海量数据中提取主题的方法。在开始介绍前要给读者说声抱歉,因为上篇图片不清晰的原因,有读者反映说影响了阅读,可能是因为上传文章的时候进行了压缩,这次作者会认真调试图片像素等,改善大家的阅读体验。
先看看最后的效果:每一个圆圈都代表从海量推文里提取出来的一个主题,圆圈之间的距离代表这些主题之间的相似程度,右边是每个主题的关键词,标为红色的则是最能代表主题特性的词语。
主题提取这个概念其实并不复杂,在现实生活中,很多时候我们面对一堆文字,看了几句话就可以大概理解文章到底讲什么(当然不排除那些特脑洞,结尾来个大转折的文章)。其实这个过程靠的是我们在阅读理解时,大脑自己不断归纳总结出现词频较高,相似度较大的词汇,自动的在我们大脑中构建出来一个关于这些词汇的主题。比如:
“我有一只调皮可爱的小狗。小狗长着一身洁白的毛,小狗一跑起来,就像一团雪球在滚动。一双圆溜溜又机灵的眼睛藏在长长的绒毛里,一对耳朵警惕地听着四周一切可疑的声音。小狗还长着一双锋利的爪子,尾巴翘得老高,显得非常傲慢。”
这段文字我们可以很明显的看到,嗯,是在说我家那只小狗的,那么如果我把文章中小狗这两个字全部去掉,大家再看看,还能不能提取出它讲的大致内容呢:
“它长着一身洁白的毛,一跑起来,就像一团雪球在滚动。一双圆溜溜又机灵的眼睛藏在长长的绒毛里,一对耳朵警惕地听着四周一切可疑的声音。它还长着一双锋利的爪子,尾巴翘得老高,显得非常傲慢。”
看到这段文字后,我们的大脑经历了什么呢,我们提取了这几个很关键的词语——“毛、绒毛、机灵、雪球、一双眼睛、一对耳朵、警惕、锋利、爪子、尾巴、翘的老高”,因为他们出现的频率高,而且相关性强。
虽然这段文字没有“小狗”这个确定的主题词语,大脑此时并不能给出一个完全确定的主题,但是根据这些形容词和我们平时的生活经验,我们是不是可以很容易的确定出来这个就是在说狗或者狼一类的动物呢,即使不能完全确定是在说的是哪个,但我们可以100%确定是在说一头动物,而且80%的可能性还就是狗。
当我们面对一堆海量文字,想要一眼就能看出类似“小狗”这么鲜明主题词,几乎是不可能办到的事情,但是我们又必须要能够大致归纳它在说什么。
这时就要用到机器学习,来模拟我们刚才人脑提取关键词的过程,之后再将提取的这些关键词分类,给出所能够提取出主题的各种可能性,再交给人们去判断最终我们要的主题是什么。当机器学习算法遇到了下面这段文字:
“每天我生活最多的地方就是我们美丽的校园。在那里,我们快乐地学习和成长;在那里,我们有快乐地午休、也有愉快的体育课、有有趣的大课间活动……多姿多彩的快乐的校园生活。但是最令我难忘的就是大课间的一次跳绳比赛。
那是十月中旬的一天,我们下午有一场激烈的比赛一跳绳比赛。老师先让我们分成四组,每组一个长绳,每组两个人来摇绳,其他的同学来跳绳。跳绳飞起来了,同学们个个精神饱满,经过一轮又一轮的比赛,终于轮到我们组了。我刚一上场,我的心就怦怦地跳了起来,手心也直往外冒汗。这时同学们手中的跳绳飞了起来,我也像一只欢快的小鸟一样在绳子上飞来飞去。突然,我身子一歪,飞到绳子外面去了,就这样,我飞下场了。为了胜利,同学们都尽了最大的努力。”
它将会提取出这几类:
1.“校园、我们、快乐、体育、比赛、同学、努力”
2.“跳绳、比赛、飞起来、精神饱满、小鸟、绳子、胜利”
3.“美丽、校园、我、上场、手心、往外冒汗、怦怦地跳、往外冒汗”
如果不看文章,
根据第一类词语,可以想到,这段话是在说我和一群同学一起在学校进行体育比赛;
根据第二类词语,可以想到,这段话是在说我们进行了一场跳绳比赛,而且很开心;
根据第一类词语,可以想到,这段话是在说我参加了一场学校的跳绳比赛,而且很努力的争取胜利,跳的满头大汗。
虽然有很多类,但是大致都在说,我们有一场比赛,而且这个比赛是一场跳绳比赛,这个就是机器学习所能做的主题提取,不是说它真的一定能够给出一个特别确定的主题,而是能“提取出足以归纳一个主题的所有要素”,这些要素可以被分成很多类,每一类提取主题的容易程度取决于训练效果的好坏。
主题提取有很多方法。目前最为常用的叫做隐含狄利克雷分布(Latent Dirichlet allocation),简称LDA。我们之前通过爬虫“基于社交网络爬虫分析人物兴趣属性(一)”爬取了一堆用户的tweets。如下图,这个是我利用前一篇文章的爬虫脚本爬取了某著名女歌手的tweets截图:
那么问题来了,我们如何用这些个数据做我们想要的那个主题提取呢。
LDA相关原理部分,涉及到过多的数学知识,篇幅有限,本文会重点讲解他的代码实现。下面我们先用Python来尝试实践一次主题抽取,如果你对原理感兴趣,不妨再做延伸阅读。
开始前先要安装一些必须的库
pip install jieba
pip install pyldavis
然后先把导入的库给大家理一下
import pandas as pd
import jieba
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
from sklearn.decomposition import LatentDirichletAllocation
import pyLDAvis
import pyLDAvis.sklearn
下面会一条条讲到
这里我们使用了数据预处理很著名的工具Pandas来读取数据,因为方便后面的处理
import pandas as pd
这行代码的最主要作用就是讲text.txt的数据读成n行*1列的表格:
df = pd.read_table("./text.txt", encoding='utf-8')
因为表格的列名一定要写,而且只能是一个单词,所以我们在第一篇的爬虫脚本中已经给数据集开头写了一个content列名,所以我们的text.txt数据集合就变成了如下形式
也可以答应df.shape这个变量来看看读取的表格结构,(n,1)就说明是对的。
有同学可能会问,这个不还是句子吗,哪里有关键词了,这个该怎么提取啊接下来?
不要着急,下面要做的这步就是分词。
导入分词库的语句是
import jieba
jieba分词是现在最常用的分词库,它可以识别几乎所有的英文,中文等词汇,并将其从句子中分隔开,不多说,我们先看看效果就更加了解了。
首先我们定义一个函数
def chinese_word_cut(mytext):
return " ".join(jieba.cut(mytext))
然后我们在主函数里面使用这个函数,由于是pandas读取,传参的时候需要用到
df.content.apply(chinese_word_cut)这个方法,结合上我们之前读取的代码就是
import pandas as pd
import jieba
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
from sklearn.decomposition import LatentDirichletAllocation
import pyLDAvis
import pyLDAvis.sklearn
def chinese_word_cut(mytext):
return " ".join(jieba.cut(mytext))
if __name__ == "__main__":
df = pd.read_table("./text.txt", encoding='utf-8')
df["content_cutted"] = df.content.apply(chinese_word_cut)
print(df["content_cutted"])
我们试着打印一下看看分词效果
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\a\AppData\Local\Temp\jieba.cache
Loading model cost 1.517 seconds.
Prefix dict has been built succesfully.
上面那段代码会卡住几秒,那就是正在分词的一个过程,然后print会显示如图:
可以看到每个句子都被分割成了一个个的单词,99%以上都是有意义的,分割效果还是很好的。是不是这样就可以直接投入训练呢,当然不是,因为计算机虽然得到了这一个个的词语,却没有办法理解他们和文本的关系,所以我们还要做一步工作。
大家不要被这个词语吓怕了,因为计算机是一个只认得1和0这两个数字的机器。我们需要做的,是把我们所分出来的单词,用1和0去表示它,这里当然不是二进制那种表示,而是为了让计算机理解文本是怎么由单词构成的,比如:
假如这里有两句话:
I am smity
I am a boy
那么我们就可以抽取出以下特征:
I
am
a
smity
boy
所以两句话的单词向量就是(1,1,0,1,0)和(1,1,1,0,1),化成表格形式就是下面这样,这样是不是看的更懂一些。
计算机只认识这样的表格,我们只有把单词向量化以后,计算机才知道这些单词的意义,才知道这些单词是根据某种规则组成了一个海量的文本,这个表格也叫做我们的词频矩阵。
接下来就是要导入的库了。
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
这个库带给我们两个函数——CountVectorizer 和 TfidfVectorizer
CountVectorizer会将文本的单词转换为单词向量并构成词频矩阵,这个函数忽略文本数量,只关心词汇的出现频率。
TfidfVectorizer则是在原CountVectorizer的基础上,结合文本数量和词汇出现频率,当文本较多的时候训练效果更为显著
因为我们分词出来的数量很多很多,这里不可能都将他们做为词频矩阵的一部分,所以我们必须设置一个n_features,来限制我们构成我们词频矩阵的词语数量,本文设置的是1000个。
n_features = 1000
tf_vectorizer=TfidfVectorizer(strip_accents='unicode',max_features=n_features,stop_words=stop_words,max_df=0.5,min_df=3)
tf = tf_vectorizer.fit_transform(df.content_cutted)
fit_transform()功能是对数据进行某种统一处理(比如标准化~N(0,1),将数据缩放(映射)到某个固定区间,归一化,正则化等),再进行词频矩阵的构建。
大家看到在TfidfVectorizer函数中还有一个参数叫做stop_words,这个参数是干嘛用的呢,在文章中,肯定会有一些介词,或者“你我他”这样的代词,他们的出现频率很高,总不能够说把这些作为我们的主题吧,大部分情况下,他们都不会是我们应该要提取的主题,因此我们必须在数据预处理的时候将他们过滤掉,这个就是停用词的作用。
具体停用词当然是有很多实验室都整理过了,比较著名的而是百度AI实验室和各个大学AI实验室所整理的停用词库,直接从网络上dump下来就可以了,放在同目录下。
而后面的max_df和min_df则是表示当词语在文本中出现频率大于多少和小于多少的时候直接忽略,不进行提取,一般来说当停用词不为None,我们会设置max_df=0.5。设为None且max_df∈[0.7, 1.0)将自动根据当前的语料库建立停用词表
#设置停用词,设为None不使用停用词,设为None且max_df∈[0.7, 1.0)将自动根据当前的语料库建立停用词表
stpwrdpath ="./stop_words.txt"
with open(stpwrdpath, 'r',encoding='utf-8') as fp:
stop_words = fp.read() # 提用词提取
stop_words = stop_words.splitlines()
#提取1000个关键词
n_features = 1000
tf_vectorizer=TfidfVectorizer(strip_accents='unicode',max_features=n_features,stop_words=stop_words,max_df=0.5,min_df=3)
tf = tf_vectorizer.fit_transform(df.content_cutted)
接下来就是我们重点中的重点了,LDA模型构建,也是我们输出最后主题的主要步骤,他需要导入的库如下:
from sklearn.decomposition import LatentDirichletAllocation
我们再简单的介绍一下LDA的代码:
LatentDirichletAllocation这个函数就是用于生成LDA模型,其中主题数量是由使用者规定的,这里n_topics就是主题数量,如何确定可以根据每次训练出来后的结果不断的进行调整,一般建议从5个开始调试,逐渐找出最容易解释的数量,太少了,会有过多关键词融合在一起,不利于主题归纳,太多会有很多主题重复也没有必要。
之后用lda.fit开始训练我们之前tf所构造的词频矩阵。
n_topics = 8
lda=LatentDirichletAllocation(n_components=n_topics,max_iter=50,learning_method='online',learning_offset=50,random_state=0)
lda.fit(tf)
主题没有一个确定的名称,而是用一系列关键词刻画的。我们定义以下的函数,把每个主题里面的前若干个关键词显示出来:
因此我们需要定义打印关键词的函数:
def print_top_words(model, feature_names, n_top_words):
for topic_idx, topic in enumerate(model.components_):
print("Topic #%d:" % topic_idx)
print(" ".join([feature_names[i]
for i in topic.argsort()[:-n_top_words - 1:-1]]))
print()
然后调用它:
n_top_words = 10
tf_feature_names = tf_vectorizer.get_feature_names()
print_top_words(lda, tf_feature_names, n_top_words)
然后我们看看效果:
到了这里其实我们的算法已经运行完毕了,至于结果好坏,就需要使用者自己尝试去修改停用词,max_df和min_df范围、主题数、关键词书这些具有一定弹性的参数,但是我们只是看这一堆文本肯定还是觉得不够直观,因此我们可以再做一步可视化的工作。
这里就是我们这两个带入库大显神威的时候了:
import pyLDAvis
import pyLDAvis.sklearn
具体代码如下
data = pyLDAvis.sklearn.prepare(lda, tf, tf_vectorizer)
pyLDAvis.show(data)
这段代码运行之后,会有一段时间的等待,后台会绘制主题提取之后的可视化结果,并弹出浏览器页面进行展示:
本文实验结果如图
如开头所讲,这里的每一个圆圈都代表者海量文本的一个主题,圆圈之间的距离则代表着他们的相似程度,距离越近则越相似。
如果你什么圆圈都不选择,那么它右边的图像显示的就是他综合考量后认为最合适的主题关键词,如果你选择了某个圆圈
那么他就会单独显示他这个主题中的词频情况,红色部分是它认为主要发挥作用的词语。
到了这里,LDA主题提取模型就给大家介绍完了,贴出整体代码:
import pandas as pd
import jieba
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
from sklearn.decomposition import LatentDirichletAllocation
import pyLDAvis
import pyLDAvis.sklearn
def chinese_word_cut(mytext):
return " ".join(jieba.cut(mytext))
def print_top_words(model, feature_names, n_top_words):
for topic_idx, topic in enumerate(model.components_):
print("Topic #%d:" % topic_idx)
print(" ".join([feature_names[i]
for i in topic.argsort()[:-n_top_words - 1:-1]]))
print()
if __name__ == "__main__":
#按表格形式读入数据
#一定要记得在数据最前面加一个标题content,不然数据格式会出错
df = pd.read_table("./text.txt", encoding='utf-8')
#print(df)
df["content_cutted"] = df.content.apply(chinese_word_cut)
#print(df["content_cutted"])
#设置停用词,设为None不使用停用词,设为None且max_df∈[0.7, 1.0)将自动根据当前的语料库建立停用词表
stpwrdpath ="./stop_words.txt"
with open(stpwrdpath, 'r',encoding='utf-8') as fp:
stop_words = fp.read() # 提用词提取
stop_words = stop_words.splitlines()
#提取1000个关键词
n_features = 1000
tf_vectorizer=TfidfVectorizer(strip_accents='unicode',max_features=n_features,stop_words=stop_words,max_df=0.5,min_df=3)
tf = tf_vectorizer.fit_transform(df.content_cutted)
n_topics = 8
lda=LatentDirichletAllocation(n_components=n_topics,max_iter=50,learning_method='online',learning_offset=50,random_state=0)
lda.fit(tf)
n_top_words = 10
tf_feature_names = tf_vectorizer.get_feature_names()
print_top_words(lda, tf_feature_names, n_top_words)
#浏览器绘图
data = pyLDAvis.sklearn.prepare(lda, tf, tf_vectorizer)
pyLDAvis.show(data)
print("over")
下一步我们就要做两个网络之间的数据融合和数据对齐,使得我们的结果更具有可信度。
合天网安实验室相关实验:《Python爬虫-进阶》
点击 链接 开始学习吧!