利用Doc2Vec对Quora问题标签聚类
引言
Quora是一个流行的知识分享平台,我常常在Quora上分享我的想法。这个平台是基于问答的形式,它因其简易的设计和平滑的用户体验而出名。
当新的问题被添加到Quora时,这些问题由机器人自动基于问题的上下文进行标记并稍后由用户进行编辑。这些标签反映了问题被归入的话题类别。如下是一个问题的基本概貌。
最近我在寻找合适的数据集,然后我偶然看到了Quora里的一个这个页面:Programming Challenges。我选择了这个叫做Answered的二分类问题挑战。其中,包括了近10000个问题(训练集和测试集总计)。每个问题和其话题标签以及其他的一些信息被以JSON格式储存。下图是一个JSON的示例。
示例问题JSON
动手干吧
第一个任务就是要从JSON文件中读取数据。训练集总共大约有9000个问题,而测试集总共约1000个问题。
import json
f=open("answered_data_10k.in").read().split("n")
train_set=f[1:9001]
test_set=f[9002:-1]
train=[json.loads(i)for i in train_set]
test=[json.loads(i)for i in test_set]
questions=train+test
接下来就要提取出所有数据集中的主题标签。在JSON文件中,主题存储在键"key"中。不同的问题有不同数量的主题标签。单个问题所允许存在的最大标签数为26。同时也存在没有关联主题标签的问题。
#Create the list of topics
topic_list=[]
for question in questions:
if len(question["topics"])>0:
for topic in question["topics"]:
topic_list=topic_list+[topic["name"]]
topic_list=list(set(topic_list))
print(len(topic_list))
在这个挑战所提供的数据中,一共有8762个主题标签。
在提取出主题标签之后,我们需要将具有相同标签的问题聚类。在动手之前,我们先对数据进行分析,因为如果直接对8762个进行聚类将会很困难而且聚类的质量也难以保证。
因此我们限定了每一个主题下的最小问题数来解决这个问题。拥有多于1个问题的主题有3275个。拥有5个问题的主题恰好有900个,这个数量相对更适合进行聚类。
最终,我们决定将主题下的最小问题数规定为5个,这主要有两个原因。首先是为了更好地用向量来表示主题,其次因为具有较少问题的主题大多数情况下是和无关的问题所关联的。
#Assigning question to topics.
question_list=[]
final_topic_list=[]
for topic in topic_list:
temp=[]
for question in questions:
context=[i["name"]for i in question["topics"]]
if topic in context:
temp.append(question['question_text'])
if len(temp)>=5:
question_list.append(temp)
final_topic_list.append(topic)
topic_list=final_topic_list
接下来,我们写一个函数,通过转换为小写、去除标点符号和停用词来正则化每个段落。每个话题下有五到多个问题。我们把每个话题下的问题的集合当做一个文档。
这样,我们先遍历话题标签,然后把问题聚集成段落,再把段落正则化化。然后我们把段落和段落的话题标签喂给Gensim的TaggedDocument函数,进行进一步的正则化。
from nltk import word_tokenize
from nltk.corpus import stopwords
from gensim import models
from gensim.models.doc2vec import TaggedDocument
#Function for normalizing paragraphs.
def normalize(string):
lst=word_tokenize(string)
lst=[word.lower()for word in lst if word.isalpha()]
lst=[w for w in lst if not w in stopwords.words('english')]
return(lst)
#Aggregate questions under each topic tag as a paragraph.
#Normalize the paragraph
#Feed the normalized paragraph along with the topic tag into Gensim's Tagged Document function.
#Append the return value to docs.
docs=[]
for index,item in enumerate(topic_list):
question="".join(question_list[index])
question=normalize(question)
docs.append(TaggedDocument(words=question,tags=[item]))
为Gensim的DocVec准备数据
接下来我们训练Doc2Vec模型。
应该调整vector_size和window,直到结果是最优的。
import gensim
model=gensim.models.Doc2Vec(vector_size=200,window=3,min_count=0,workers=4,epochs=40)
model.build_vocab(docs)
model.train(docs,total_examples=model.corpus_count,epochs=model.iter)
Doc2Vec Training
Doc2Vec模型训练好后,我们就用KMeans算法聚类了文档向量。簇的数量从100到50之间进行了检查。接近100的簇数会导致大簇被切分成小簇,而簇数等于50时会使得没有相关性的簇被组合成大簇。在仔细评估聚簇结果后,最后选择60作为簇数。
from sklearn.cluster import KMeans
from sklearn import metrics
import pylab as pl
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
kmeans_model=KMeans(n_clusters=60,init='k-means++',max_iter=100)
X=kmeans_model.fit(model.docvecs.doctag_syn0)
labels=kmeans_model.labels_.tolist()
l=kmeans_model.fit_predict(model.docvecs.doctag_syn0)
#map each centroid to its topic tag
word_centroid_map=dict(zip(model.docvecs.offset2doctag,l))
#Print Cluster List
for cluster in range(0,100):
print("nCluster%d"%cluster)
words=[]
for i in range(0,len(word_centroid_map.values())):
if(list(word_centroid_map.values())<i>==cluster):
words.append(list(word_centroid_map.keys())<i>)
print(words)
拟合KMeans模型并取回簇的列表
下面是一些簇的例子。
第一个簇里有相当多的“设计、UI/UX设计、软件开发、web开发和网站”。
第二个簇里有不少“在线广告、营销工具”
第三个簇里有许多像“生活、自我提升”这样的话题。
看到这个簇你就能明白数据库里大部分跟性有关的话题都是关于培养孩子和性-教育的方面。其他的热门关键词都没有出现。
这个簇是一个政-治和经济话题的大杂烩。同时,这也揭示了经济与政-治之间的内在联系,以及一个地区的经济因素是如何成为该地区发展的主要评价标准的。
影视、美术、书籍、音乐以及其他形式的创造性艺术。
这个簇里为啥有国际象棋(Chess)?
这个非常有趣,主要聚焦在宗-教-信-仰(Religion)上。有三件事我希望你们注意:假设问题(Hypothesis Questions)在这个簇里。伦-理(Ethics)在这个簇里。基于LGBTQ的问题跟宗-教-信-仰最相关,而(Homosexuality)和亲子(Parenting and Children)在一个簇里。
很清晰的是,簇形成的方式反映出了关于Quora上的话题之间互相关联的方式,以及问题被询问的方式。调查问题(Servey Questions)由于它的独特性,自己形成了一个簇,而政-治(Politics)有非常多的相关话题。
这是60个簇中我觉得值得一提的一些。你也可以提一下自己发现的有趣的簇。这个项目的代码可以在Github找到。