赞
踩
转载自https://mp.weixin.qq.com/s/hMcJtB3Lss1NBalXRTGZlQ
加粗的部分和【】内的部分,均为自己后加上去的划重点标注~
......
用Python从海量文本抽取主题,翻了翻你自己的武器库,你发现了词云、情感分析和决策树。
然而,在帮你应对信息过载这件事儿上,上述武器好像都不大合适。
词云你打算做几个?全部文章只做一个的话,就会把所有文章的内容混杂起来,没有意义——因为你知道这些文章谈的就是数据科学啊!如果每一篇文章都分别做词云,1000多张图浏览起来,好像也没有什么益处。
你阅读数据科学类公众号文章是为了获得知识和技能,分析文字中蕴含的情感似乎于事无补。
决策树是可以用来做分类的,没错。可是它要求的输入信息是结构化的有标记数据,你手里握着的这一大堆文本,却刚好是非结构化的无标记数据。
全部武器都哑火了。
没关系。本文帮助你在数据科学武器库中放上一件新式兵器。它能够处理的,就是大批量的非结构无标记数据。在机器学习的分类里,它属于非监督学习(unsupervised machine learning)范畴。具体而言,我们需要用到的方法叫主题建模(topic model)或者主题抽取(topic extraction)。
既然要建模,我们就需要弄明白建立什么样的模型。
根据维基百科的定义,主题模型是指:
在机器学习和自然语言处理等领域是用来在一系列文档中发现抽象主题的一种统计模型。
这个定义本身好像就有点儿抽象,咱们举个例子吧。
还是维基百科上,对一条可爱的小狗有这样一段叙述。
阿博(Bo;2008年10月9日-) 是美国第44任总统巴拉克·奥巴马的宠物狗,也是奥巴马家族的成员之一。阿博是一只已阉割的雄性黑色长毛葡萄牙水犬。奥巴马一家本来没有养狗,因为他的大女儿玛丽亚对狗过敏。但为了延续白宫主人历年均有养狗的传统,第一家庭在入主白宫后,花了多个月去观察各种犬种,并特地选择了葡萄牙水犬这一种掉毛少的低敏狗。
我们来看看这条可爱的小狗照片:
问题来了,这篇文章的主题(topic)是什么?
你可能脱口而出,“狗啊!”
且慢,换个问法。假设一个用户读了这篇文章,很感兴趣。你想推荐更多他可能感兴趣的文章给他,以下2段文字,哪个选项更合适呢?
选项1:
阿富汗猎狗(Afghan Hound)是一种猎犬,也是最古老的狗品种。阿富汗猎狗外表厚实,细腻,柔滑,它的尾巴在最后一环卷曲。阿富汗猎狗生存于伊朗,阿富汗东部的寒冷山上,阿富汗猎狗最初是用来狩猎野兔和瞪羚。阿富汗猎狗其他名称包含巴尔赫塔子库奇猎犬,猎犬,俾路支猎犬,喀布尔猎犬,或非洲猎犬。
选项2:
1989年夏天,奥巴马在西德利·奥斯汀律师事务所担任暑期工读生期间,结识当时已是律师的米歇尔·鲁滨逊。两人于1992年结婚,现有两个女儿——大女儿玛丽亚在1999年于芝加哥芝加哥大学医疗中心出生,而小女儿萨沙在2001年于芝加哥大学医疗中心出生。
给你30秒,思考一下。
你的答案是什么?
我的答案是——不确定。
从前我们说“三百六十行”。随便拿出某个人来,我们就把他归入其中某一行。
现在不行了,反例就是所谓的“斜杠青年”。
主题这个事情,也同样不那么泾渭分明。介绍小狗Bo的文章虽然不长,但是任何单一主题都无法完全涵盖它。
如果用户是因为对小狗的喜爱,阅读了这篇文章,那么显然你给他推荐选项1会更理想;但是如果用户关注的是奥巴马的家庭,那么比起选项2来,选项1就显得不是那么合适了。
我们必须放弃用一个词来描述主题的尝试,转而用一系列关键词来刻画某个主题(例如“奥巴马”+“宠物“+”狗“+”第一家庭“)。
在这种模式下,以下的选项3可能会脱颖而出:
据英国《每日邮报》报道,美国一名男子近日试图绑架总统奥巴马夫妇的宠物狗博(Bo),不惜由二千多公里远的北达科他州驱车往华盛顿,但因为走漏风声,被特勤局人员逮捕。奥巴马夫妇目前养有博和阳光(Sunny)两只葡萄牙水犬。
讲到这里,你大概弄明白了主题抽取的目标了。
可是面对浩如烟海的文章,我们怎么能够把相似的文章聚合起来【第一个问题!】,并且提取描述聚合后主题的重要关键词【第二个问题!】呢?
主题抽取有若干方法。目前最为流行的叫做隐含狄利克雷分布(Latent Dirichlet allocation),简称LDA。
LDA相关原理部分,置于本文最后。下面我们先用Python来尝试实践一次主题抽取。如果你对原理感兴趣,不妨再做延伸阅读。
为了处理表格数据,我们依然使用数据框工具Pandas。先调用它。
import pandas as pd
然后读入我们的数据文件datascience.csv,注意它的编码是中文GB18030,不是Pandas默认设置的编码,所以此处需要显式指定编码类型,以免出现乱码错误。
df = pd.read_csv("datascience.csv", encoding='gb18030')
我们来看看数据框的头几行,以确认读取是否正确。
df.head()
显示结果如下:
没问题,头几行内容所有列都正确读入,文字显式正常。我们看看数据框的长度,以确认数据是否读取完整。
df.shape
执行的结果为:
(1024, 3)
行列数都与我们爬取到的数量一致,通过。
分词
下面我们需要做一件重要工作——分词。这是因为我们需要提取每篇文章的关键词。而中文本身并不使用空格在单词间划分。此处我们采用“结巴分词”工具。
我们首先调用jieba分词包。
import jieba
我们此次需要处理的,不是单一文本数据,而是1000多条文本数据,因此我们需要把这项工作并行化。这就需要首先编写一个函数,处理单一文本的分词。
- def chinese_word_cut(mytext):
- return " ".join(jieba.cut(mytext))
有了这个函数之后,我们就可以不断调用它来批量处理数据框里面的全部文本(正文)信息了。你当然可以自己写个循环来做这项工作。但这里我们使用更为高效的apply函数。如果你对这个函数有兴趣,可以点击这段教学视频查看具体的介绍。
下面这一段代码执行起来,可能需要一小段时间。请耐心等候。
df["content_cutted"] = df.content.apply(chinese_word_cut)
执行过程中可能会出现如下提示。没关系,忽略就好。
- Building prefix dict from the default dictionary ...
- Loading model from cache /var/folders/8s/k8yr4zy52q1dh107gjx280mw0000gn/T/jieba.cache
- Loading model cost 0.406 seconds.
- Prefix dict has been built succesfully.
执行完毕之后,我们需要查看一下,文本是否已经被正确分词。
df.content_cutted.head()
结果如下:
- 0 大 数据 产业 发展 受到 国家 重视 , 而 大 数据 已经 上升 为 国家 战略 , 未...
- 1 点击 上方 “ 硅谷 周边 ” 关注 我 , 收到 最新 的 文章 哦 ! 昨天 , Goo...
- 2 国务院 总理 李克强 当地 时间 20 日 上午 在 纽约 下榻 饭店 同 美国 经济 、 ...
- 3 2016 年 , 全峰 集团 持续 挖掘 大 数据 、 云 计算 、 “ 互联网 + ” 等...
- 4 贵州 理工学院 召开 大 数据分析 与 应用 专题 分享 会 借 “ 创响 中国 ” 贵...
- Name: content_cutted, dtype: object
单词之间都已经被空格区分开了。
文本的向量化
下面我们需要做一项重要工作,叫做文本的向量化。
不要被这个名称吓跑。它的意思其实很简单。因为计算机不但不认识中文,甚至连英文也不认识,它只认得数字。我们需要做的,是把文章中的关键词转换为一个个特征(列),然后对每一篇文章数关键词出现个数。
假如这里有两句话:
I love the game.
I hate the game.
那么我们就可以抽取出以下特征:
I
love
hate
the
game
然后上面两句话就转换为以下表格:
第一句表示为[1, 1, 0, 1, 1],第二句是[1, 0, 1, 1, 1]。这就叫向量化了。机器就能看懂它们了。
原理弄清楚了,让我们引入相关软件包吧。
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
处理的文本都是微信公众号文章,里面可能会有大量的词汇。我们不希望处理所有词汇。因为一来处理时间太长,二来那些很不常用的词汇对我们的主题抽取意义不大。所以这里做了个限定,只从文本中提取1000个最重要的特征关键词,然后停止。
n_features = 1000
下面我们开始关键词提取和向量转换过程:
- tf_vectorizer = CountVectorizer(strip_accents = 'unicode',
- max_features=n_features,
- stop_words='english',
- max_df = 0.5,
- min_df = 10)
- tf = tf_vectorizer.fit_transform(df.content_cutted)
用LDA方法进行主题抽取
到这里,似乎什么都没有发生。因为我们没有要求程序做任何输出。下面我们就要放出LDA这个大招了。
先引入软件包:
from sklearn.decomposition import LatentDirichletAllocation
然后我们需要人为设定主题的数量。这个要求让很多人大跌眼镜——我怎么知道这一堆文章里面多少主题?!
别着急。应用LDA方法,指定(或者叫瞎猜)主题个数是必须的。如果你只需要把文章粗略划分成几个大类,就可以把数字设定小一些;相反,如果你希望能够识别出非常细分的主题,就增大主题个数。
对划分的结果,如果你觉得不够满意,可以通过继续迭代,调整主题数量来优化。
这里我们先设定为5个分类试试。
- n_topics = 5lda = LatentDirichletAllocation(n_topics=n_topics, max_iter=50,
- learning_method='online',
- learning_offset=50.,
- random_state=0)
把我们的1000多篇向量化后的文章扔给LDA,让它欢快地找主题吧。
这一部分工作量较大,程序会执行一段时间,Jupyter Notebook在执行中可能暂时没有响应。等待一会儿就好,不要着急。
lda.fit(tf)
程序终于跑完了的时候,你会看到如下的提示信息:
- LatentDirichletAllocation(batch_size=128, doc_topic_prior=None,
- evaluate_every=-1, learning_decay=0.7,
- learning_method='online', learning_offset=50.0,
- max_doc_update_iter=100, max_iter=50, mean_change_tol=0.001,
- n_jobs=1, n_topics=5, perp_tol=0.1, random_state=0,
- topic_word_prior=None, total_samples=1000000.0, verbose=0)
可是,这还是什么输出都没有啊。它究竟找了什么样的主题?
主题没有一个确定的名称,而是用一系列关键词刻画的。我们定义以下的函数,把每个主题里面的前若干个关键词显示出来:
- 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()
定义好函数之后,我们暂定每个主题输出前20个关键词。
n_top_words = 20
以下命令会帮助我们依次输出每个主题的关键词表:
- tf_feature_names = tf_vectorizer.get_feature_names()
- print_top_words(lda, tf_feature_names, n_top_words)
执行效果如下:
- Topic #0:
- 学习 模型 使用 算法 方法 机器 可视化 神经网络 特征 处理 计算 系统 不同 数据库 训练 分类 基于 工具 一种 深度
- Topic #1:
- 这个 就是 可能 如果 他们 没有 自己 很多 什么 不是 但是 这样 因为 一些 时候 现在 用户 所以 非常 已经
- Topic #2:
- 企业 平台 服务 管理 互联网 公司 行业 数据分析 业务 用户 产品 金融 创新 客户 实现 系统 能力 产业 工作 价值
- Topic #3:
- 中国 2016 电子 增长 10 市场 城市 2015 关注 人口 检索 30 或者 其中 阅读 应当 美国 全国 同比 20
- Topic #4:
- 人工智能 学习 领域 智能 机器人 机器 人类 公司 深度 研究 未来 识别 已经 医疗 系统 计算机 目前 语音 百度 方面
- ()
在这5个主题里,可以看出主题0主要关注的是数据科学中的算法和技术,而主题4显然更注重数据科学的应用场景。
剩下的几个主题可以如何归纳?作为思考题,留给你花时间想一想吧。
到这里,LDA已经成功帮我们完成了主题抽取。但是我知道你不是很满意,因为结果不够直观。
那咱们就让它直观一些好了。
执行以下命令,会有有趣的事情发生。
- import pyLDAvisimport pyLDAvis.sklearn
- pyLDAvis.enable_notebook()
- pyLDAvis.sklearn.prepare(lda, tf, tf_vectorizer)
对,你会看到如下的一张图,而且还是可交互的动态图哦。
图的左侧,用圆圈代表不同的主题,圆圈的大小代表了每个主题分别包含文章的数量。
图的右侧,列出了最重要(频率最高)的30个关键词列表。注意当你没有把鼠标悬停在任何主题之上的时候,这30个关键词代表全部文本中提取到的30个最重要关键词。
如果你把鼠标悬停在1号上面:
右侧的关键词列表会立即发生变化,红色展示了每个关键词在当前主题下的频率。
以上是认为设定主题数为5的情况。可如果我们把主题数量设定为10呢?
你不需要重新运行所有代码,只需要执行下面这几行就可以了。
这段程序还是需要运行一段时间,请耐心等待。
- n_topics = 10lda = LatentDirichletAllocation(n_topics=n_topics, max_iter=50,
- learning_method='online',
- learning_offset=50.,
- random_state=0)
- lda.fit(tf)
- print_top_words(lda, tf_feature_names, n_top_words)
- pyLDAvis.sklearn.prepare(lda, tf, tf_vectorizer)
程序输出给我们10个主题下最重要的20个关键词。
- Topic #0:
- 这个 就是 如果 可能 用户 一些 什么 很多 没有 这样 时候 但是 因为 不是 所以 不同 如何 使用 或者 非常
- Topic #1:
- 中国 孩子 增长 市场 2016 学生 10 2015 城市 自己 人口 大众 关注 其中 教育 同比 没有 美国 投资 这个
- Topic #2:
- data 变量 距离 http 样本 com www 检验 方法 分布 计算 聚类 如下 分类 之间 两个 一种 差异 表示 序列
- Topic #3:
- 电子 采集 应当 或者 案件 保护 规定 信用卡 收集 是否 提取 设备 法律 申请 法院 系统 记录 相关 要求 无法
- Topic #4:
- 系统 检索 交通 平台 专利 智能 监控 采集 海量 管理 搜索 智慧 出行 视频 车辆 计算 实现 基于 数据库 存储
- Topic #5:
- 可视化 使用 工具 数据库 存储 hadoop 处理 图表 数据仓库 支持 查询 开发 设计 sql 开源 用于 创建 用户 基于 软件
- Topic #6:
- 学习 算法 模型 机器 深度 神经网络 方法 训练 特征 分类 网络 使用 基于 介绍 研究 预测 回归 函数 参数 图片
- Topic #7:
- 企业 管理 服务 互联网 金融 客户 行业 平台 实现 建立 社会 政府 研究 资源 安全 时代 利用 传统 价值 医疗
- Topic #8:
- 人工智能 领域 机器人 智能 公司 人类 机器 学习 未来 已经 研究 他们 识别 可能 计算机 目前 语音 工作 现在 能够
- Topic #9:
- 用户 公司 企业 互联网 平台 中国 数据分析 行业 产业 产品 创新 项目 2016 服务 工作 科技 相关 业务 移动 市场
- ()
附带的是可视化的输出结果:
你马上会发现当主题设定为10的时候,一些有趣的现象发生了——大部分的文章抱团出现在右上方,而2个小部落(8和10)似乎离群索居。我们查看一下这里的8号主题,看看它的关键词构成。
通过高频关键词的描述,我们可以猜测到这一主题主要探讨的是政策和法律法规问题,难怪它和那些技术、算法与应用的主题显得如此格格不入。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。