所有的消息
使用KMeans群集143,000篇文章。
我最近策划这个数据集探索构成我们新闻的类别的一些算法近似,我在不同的时间读过和创造过这个东西。如果你有成千上万的文章,来自一个分散的渠道,似乎或多或少地代表了我们的国家新闻格局,你把它们变成结构化的数据,你把枪对着数据的头,强迫它成组,这些组会是什么?
我决定了最好的b一个简单有效的方法是使用无监督聚类方法,让数据自己排序,无论多么粗糙(而类别,无论它们来自什么算法,几乎总是粗糙的,因为媒体没有理由不能被无限小的分类)。出于各种原因(本地内存限制、能力、更有学问的人的建议),我选择通过KMeans运行一个单词包——换句话说,如果每个单词都成为自己的维度,而每篇文章都是单个数据点,那么将形成什么样的文章集群?如果你渴望跳到“那又怎样”和/或不关心代码,向下滚动,直到看到粗体字母告诉你不要这样做。代码在这里,如果有人想要同行审查,并告诉我是否/哪里我搞砸了和/或给我建议。
因为KMeans是不确定的,结果是不同的;集群在运行之间会发生一些变化,但是,在做了很多之后,我可以证明它们不会发生很大的变化。这里的结果或多或少与数据有关。
简要概述
这是数据的样子:
这些出版物的选择是基于一种完全不科学的笛卡尔式自省过程,我审视了自己的内心,得出了一个粗略的总结,我认为这是我们国家新闻格局的合理样本。抓取这些文章的方法差不多是这样的:
- 从无价的archive.org网站上获取每家刊物过去一年半的存档主页或RSS提要的链接(无价到后来我给了他们钱来表示感谢(如果有人正在阅读,谢谢他们的存在)。
- 从存档主页上的每个链接上刮下每一篇文章,使用非常非常拼凑的网页刮板,辅以BeautifulSoup。
- 无趣的东西的数据。
- 清理肮脏的数据。
- 等。
也就是说,这里的数据主要包括2016年初到2017年7月期间这些出版物的文章,以及在主页或RSS提要中推荐的文章——也就是说,这并不是对2016年8月13日存在的整个域的无休止的真空。
阻止语料库
以下是数据片段:
我做了一个判断(你可以判断这个判断),从语料库中删除专有名词。他们的想法是:A)类别太多了;B)它们不能告诉我们太多关于写作内容和方式的信息;C)当试图总结这些类别的本质时,它们是另一种形式的噪音。稍后您将看到,Python的NLTK是一个很棒的包,但它并不完美,在试图将一些专有名词删除之后,它们仍然保留在corupus中。
此外,我决定删除数字/数字。其中有一行是关于NLTK的分词器有时会在单词末尾留下句点的倾向的,我认为这是一个错误。
词干生成过程如下:
进口nltk
从NLTK导入pos_tag
nltk。干细胞导入PorterStemmer
从NLTK导入word_tokenize
由托收进口柜台
导入的时间抽梗机= PorterStemmer ()
记号赋予器= nltk.data.load(分词器/ punkt / english.pickle)Progress = 0 #用于跟踪函数的位置def茎(x):
结束= time.time ()
脏= word_tokenize (x)
令牌= []
word in dirty:
如果word.strip(' . ') == ": #处理错误
通过
Elif re.search(r ' \d{1,} ', word): #摆脱数字
通过
其他:
tokens.append (word.strip (' . '))
全球开始
全球进展
Tokens = pos_tag(Tokens) #
进步+ = 1
stem = ' ' .join(stemmer.stem(key.lower()) for key, value in tokens if value != ' NNP ') #去除专有名词
结束= time.time ()sys.stdout。写入(' \r{}百分比,{}位置,{}每秒'。格式(str(浮动(进展/ len(文章))),
Str (progress), (1 / (end - start)))) #让我们看看还剩下多少时间开始= time.time ()
返回茎开始= time.time ()
文章(“茎”)= articles.content。应用(λx:阀杆(x))
结果是文章从这个:
上周日,英国女王伊丽莎白二世(Queen Elizabeth II)近一个月来首次公开露面,缓解了外界对她健康状况的担忧。此前,由于白金汉宫所说的持续感冒,她错过了圣诞节和新年的教堂礼拜。女王将于今年4月年满91岁,她出席了桑德林厄姆的圣玛丽抹大拉教堂的仪式。
:
近一个月后,女王首次公开露面,减轻了人们对她缺席教堂礼拜后健康状况的担忧,因为她被形容为坚持感冒,她将在…
词干阻断大大减少了语料库的大小。“realize”和“implemented”被认为是不同的单词,并被赋予了各自的维度,它们被简化为共同的词干“realz”。这减少了噪音,这样算法就不会把重量放在,例如,一个出版物决定使用过去时而不是现在,或者复数名词不被认为是词汇表的不同部分而不是单数名词,等等。
创建一个词汇
现在我们来计数,计算整个语料库中的每一个词干,然后将其转换为一个数据框架,用于文档术语矩阵和词汇。
由托收进口柜台
all_words =计数器()
开始= time.time ()
进步= 0
def count_everything (x):
全球开始
全球all_words
全球进展
X = X .split(' ')
对于x中的单词:
all_words[词]+ = 1
进步+ = 1
结束= time.time ()
sys.stdout。写入(' \r{}百分比,{}位置,{}每秒'。格式((str(浮动(进展/ len(文章)))),
(progress), (1 / (end - start)))) .
开始= time.time ()对于item in articles.茎秆:
count_everything(项)
然后将其转移到一个新的数据框架中:
allwordsdf = pd。DataFrame(columns = [' words ', ' count '])
allwordsdf(“计数”)= pd.Series(列表(all_words.values ()))
allwordsdf['文字']= pd.Series(列表(all_words.keys ()))
allwordsdf。在dex = allwordsdf[‘words’]
这就得到了,在数据框架的头部:
Republican的收录是一个很好的例子,说明词性标签并没有完全抹掉专有名词。但是忽略这一点,语料库现在是一个数据框架,词汇表中的每个术语都是索引中的项,这在不久的将来会很有用。
处理来自互联网的文本数据的一个挑战是,非单词,如字符和符号的组合(例如,“@username”,“#hashtags”,与省略号“well…”连用的单词),出现的频率相对较高。我没有找到并清理每一个单词,而是决定只保留NLTK完整英语语料库中的单词。这个语料库到底有多完整还有待语言学家们的讨论,但它有236,736个单词,数量相当可观。我们将完成数据框架的最终精简,首先截取整个英语语料库,然后将其与我们自己的语料库进行比较:
nltk。语料库进口的话nltkstem = [stemmer.stem(word) for word in words.words()] #对NLTK语料库中的#单词进行词干处理,使它们等价于#the allwordsdf dataframe中的单词nltkwords = pd.DataFrame() #使用词根为#NLTK的单词创建一个新的数据框架nltkwords = nltkstems“单词”allwordsdf = allwordsdf[allwordsdf[' words '].isin(nltkwords[' words '])] #只保留词根NLTK语料库中的#those
这将总词汇量从89216减少到34527。它会处理词汇表中的每一点杂音,我花了几周时间才考虑这个解决方案。
Vectorizing的话
粗略地说,TfIdf(术语频率-反向文档频率)矢量器为每篇文章中的每个单词提供一个值,该值由整个语料库中的单词频率加权。反文档频率是由单词在整个数据集中的频率派生出来的分母。以“洞察力”(perspicacious)这个词为例,这个词在英语中有许多更高级的替代词,幸运的是,它是一个我们很少见到的扯淡词。由于这种稀缺性,它的反文档频率(或称演示频率)很低。如果它在一篇文章中出现了15次,那么它的Tf值或分子就会很高。所以它的TfIdf值应该是一个大的分子除以一个小的演示数,得到一个高的数。所以在我们的千维空间中,文章在“洞察力”维度上有一个值。(当然,这并不是说标准化向量和寻找TfIdf值所涉及的其他任务。)
在使用这种类型的矢量器时,包括停止词(算法可以忽略的单词列表)并不那么重要,因为不常见的单词会被赋予较低的值。尽管如此,它还是有用的,因为至少它降低了内存使用量,降低了已经非常高的空间维度。此外,创建一个单词层,确保在一篇文章中碰巧出现的非常罕见的单词不是偶然聚集在一起的。我选择了40分位数以上的单词。乍一看,这个数字似乎相当高,直到你看看这个分位数包含了什么:
allwordsdf [allwordsdf(“计数”)= = allwordsdf(“计数”).quantile(。4)][10]
因此,第40分位数包含的单词在整个语料库中只出现了9次——非常低,因此不倾向于提供信息。为什么不是第50或第60分位数?因为一定要选一个数字,也可能是这个。
继续创建停止词、矢量器词汇和矢量器。既写停顿词又写词汇可能是多余的;我添加这两个是为了更好的度量,而且因为我们稍后需要词汇表。
从sklearn.feature_extraction。文本导入TfidfVectorizerStopwords = list(allwordsdf[(allwordsdf[' count '] >= allwordsdf[' count '].quantile(.995)) | (allwordsdf[' count '] <= allwordsdf[' count '].quantile(.4))][' words '])Vecvocab = list(allwordsdf[(allwordsdf[' count '] < allwordsdf[' count '].quantile(.995)) & (allwordsdf[' count '] > allwordsdf[' count '].quantile(.4))][' words '])vec = TfidfVectorizer(stop_words = stopwords, vocabulary = vecvocab, tokenizer=None)
现在转换数据帧:
vec_matrix = vec.fit_transform(文章(“茎”))
生成的矩阵形状是(142570, 20193)
,约2万字。
降维
我们这个20193维的矩阵应该缩减到多少维是很难回答的。Sklearn的官方推荐“对于潜在语义分析(我们正在做的事情),建议值为100。”我用20193个维度对数据进行了聚类,我用100个维度对数据进行了聚类,我还用3个维度对数据进行了聚类,每一次,聚类似乎都与维度的多少无关。这最终归结为减少处理时间,因为创建包的人的智慧决定了100维,所以它就是100维。
导入截断svdpca = TruncatedSVD (n_components = 100)vec_matrix_pca = pca.fit_transform (vec_matrix)
聚类
更难回答的问题是要为数据分配多少个集群。使用KMeans,数据过于紧密地聚在一起,无法进行分层聚类或任何自行查找聚类的算法。如上所述,我选择了10作为起点。
从sklearn。集群进口KMeansclf10 = KMeans(n_clusters=10, verbose = 0)clf10.fit (vec_matrix_pca)
现在将我们刚刚创建的标签分配到原始数据框架中,用于分组、可视化和分析:
['标签']= clf10.labels_文章
我们可以看看每个出版物的文章被分配到每个标签的百分比:
labelsdf =文章。groupby([“出版”,'标签']).count ()
pubslist =列表(文章(“出版”).unique ())
labelsdf(“百分比”)= 0对于索引,在labelsdf.iterrows()中的行:
对于pubslist中的pub:
对于范围(10)中的标签:
试一试:
labelsdf。Loc [(pub, label), ' percentage '] = labelsdf。loc[(酒吧、标签),' id '] / labelsdf.loc[(酒吧),“id”].sum ()
除了:
通过Labelsdf = Labelsdf [['publication', 'labels', 'percentage']]
=
等等。
如果你向下滚动,这就是停止的地方
这些图表是用Plotly在RStudio中生成的。
这些是集群,每个集群中有前200个单词及其TfIdf值。单词越大,决定由什么组成集群就越重要。它们不会被政治结盟、语气或其他任何东西所分解;当数据聚类时,按类别进行分解。记住,我们选了10个;我们可以选择8个或20个,得到不同的星系团,但这些是最突出的。一个是关于俄罗斯丑闻,一个是关于外交政策,一个是关于教育等等。请随意探索。(Plotly允许用户放大,但文字不会变大;我已经通知有关部门去修了。)
下面是出版物如何按集群进行分类。(同样,对于Privacy Badger用户,这即是)。
还有最后一个viz,如果它的尺寸造成了问题,我很抱歉,但我认为它很重要。
它的名字(' articlestest ')应该表明我创建它时所处的准备阶段,它与最终的数据集略有不同——除了所有其他内容之外,它还包括来自Verge的长篇文章,我选择将其删除。这是将所有数据简化为三维的可视化,而不是20000多个维度(或上面主成分分析中使用的100个维度)。添加边缘的形状与去掉边缘的形状截然不同,我完全不确定这是为什么。但是如果您仔细查看它,您可以看到每个集群在三维空间中形成的位置,并且您可以双击右边的每个出版物名称来只保留该出版物。如果你点击所有的,你会看到几乎每一个都是一个无形的数据点星云,除了路透社,它形成了一个几乎完美的L。
一个散漫的结论
不同的人可能会对这些数据和图表有不同的理解,但我的理解如下:当被强制分组时,出版物会分类为路透社和其他所有内容。
我不认为这是对数据的过度推断。数据科学的一个主要哲学基础是,潜在的真相和联系是由表面现象引出的,一个人的语气或穿衣风格泄露的信息比我们愿意承认的要多。这些出版物中有一份在其涵盖的主题的基础上与其他出版物表现出如此不同的形式,这可以被合理地认为是它与数据集其他成员从根本上不同的原因。
这样的结论应该经得起纯粹反思的考验——当考虑到阅读这些出版物的经验时,它有意义吗?
再往下走几扇门,野生动物罗伊写了一篇文章Vice在夏洛茨维尔的部分似乎不可思议的病毒式传播。他提醒我们,Vice现在是另一个默多克资助的愤怒机器。我不同意Roy的观点,即视频的分享数如此轻易地归因于HBO的阴谋(因此暗示病毒式传播是可以预测的,但它是不可能的),但为了我们的目的,他在文章的结尾提出了一个有启发性的思考:
我们越是接受哗众取宠的事实,我们就越倒霉。只要我们让我们的蜥蜴大脑控制我们对世界的感知,我们就没有机会。如果我们一点都没有意识到我们所观看的节目背后的动机,那么疯子总是会赢。
这是一家价值数十亿美元的媒体,由拥有福克斯的同一个人资助。它把你带到下面的艺术完善了。记住,在一个我们每天花116分钟在广告赞助的社交媒体上的世界里,声音最大的人总是赢家.(重点)
除路透社外,本数据集中的所有出版物都有一些共同之处。它们的资金完全来自广告和/或订阅(Vox和BuzzFeed也有风险投资,但它们是基于广告的模式),它们的存在依赖于点击。相比之下,路透社的新闻产品只是一个庞大的信息集团的公众形象。也许更重要的是,它是一个新闻通讯社,它的报道包括我们的金融世界的事务,因此被赋予了与其他通讯社不同的使命——可以说比《纽约时报》更重要的是,它必须报道所有的新闻,而不是被困在人物驱动的电视真人秀中,而这个数据集的其他公民似乎都非常喜欢这样做。在所有这些事件中,路透社的声音往往保持在最温和的室内音量,没有任何一个全球事件能激起比现实生活更大的愤怒,如果路透社能激起愤怒的话。也许这是属于财经媒体、从宏观上分析世界的产物;非金融媒体的叙述没有给予伦敦银行间拆放款利率的变化和一个疯子的政策建议同等的重视,尽管可以说应该如此。这里的其他出版物似乎都带有乌托邦的暗示,它们内容的潜台词往往是,如果我们混合了食谱中正确的材料,一个完美的世界就会实现,而你愤怒的东西实际上是挡在我们和天堂之间的东西。以我作为读者的经验来看,我从来没有从路透社那里感受到过这种东西。
这不应被解释为断言《纽约时报》和布莱巴特新闻网站因此是相同的中风大锅。我读了设计精美作品今天的《时代》杂志讲述了深海生物中生物发光是多么常见。不用说,在Breitbart上找到这样一篇文章的可能性几乎为零,这是我对这一政治领域及其截然相反的反对者(如《谈话要点备忘录》)感到无比悲哀的事情之一。但这就是重点:向一个算法显示你写了多少篇关于深海生物的故事,它就会显示你是谁。在更好的分辨率下,我们可能会发现时报和福克斯新闻(Fox News)之间,或者NPR和纽约邮报(New York Post)之间存在分歧。看到上面的第三个集群了吗,所有的单词都被压缩了,TfIdf值很低,没有什么突出来的?它实际上是一个其他主题的丛林,你可以在这个集群上运行算法得到新的组和区别-和其中之一那些集群也将是不同类型故事的压缩,你可以在机器学习的分形中反复这样做。这不是唯一的区别,但从数据的角度来看,这是第一个区别。
作为一个创造内容的人,我在法律上有义务经营个人通讯。你可以报名参加在这里.