5.3 获取数据
幸运的是,在得到了CC Wiki的许可后,stackoverflow 的幕后团队提供了stackoverflow 所在的StackExchange 域中的大多数数据。在本书撰写之时,最新的数据可以在http://www.clearbits.net/torrents/2076-aug-2012 找到。这个页面很可能包含一个指向更新后的转存数据的链接。
下载并解压之后,我们得到了大约37 GB的XML格式的数据。大致如下表所示:
文件 | 大小(MB) | 描述 |
badges.xml | 309 | 用户的徽章 |
comments.xml | 3225 | 问题或答案的评论 |
posthistory.xml | 18 370 | 编辑历史 |
posts.xml | 12 272 | 问题和答案——这是我们需要的 |
users.xml | 319 | 用户的一般性信息 |
votes.xml | 2200 | 投票信息 |
由于这些文件差不多都是独立的,我们可以把除posts.xml 以外的其他文件都删掉;posts.xml 包含了所有问题和答案,它们位于root标签posts 下的row 标签中。参考如下代码:
<?xml version="1.0" encoding="utf-8"?> <posts> <row Id="4572748" PostTypeId="2" ParentId="4568987" CreationDate="2011-01-01T00:01:03.387" Score="4" ViewCount="" Body="<p>IANAL, but <a href="http://support.apple.com/kb/HT2931" rel="nofollow">this</a> indicates tme that you cannot use the loops in your application:</p> <blockquote> <p>...however, individual audiloops may not be commercially or otherwise distributed on a standalone basis, nor may they be repackaged in whole or in part as audisamples, sound effects or music beds."</p> <p>Sdon't worry, you can make commercial music with GarageBand, you just can't distribute the loops as loops.</p> </blockquote> " OwnerUserId="203568" LastActivityDate="2011-01- 01T00:01:03.387" CommentCount="1" />
名字 | 类型 | 描述 |
Id | Integer | 这是唯一标识 |
PostType | Integer | 这个描述了帖子的类型。我们对下面这些类型感兴趣: 问题 答案 |
ParentId | Integer | 这是答案所属问题的唯一标识(一些问题可能缺失) |
CreationDate | DateTime | 这是提交的日期 |
Score | Integer | 这是帖子的分数 |
ViewCount | Integer or empty | 这个告诉我们该帖子的用户浏览数 |
Body | String | 这是帖子的全部内容,编码在HTML文本中 |
OwnerUserId | Id | 这个是发帖者的唯一标识。如果是1,那这是一个wiki问题 |
Title | String | 这是问题的标题(答案没有标题) |
AcceptedAnswerId | Id | 这是被接受答案的ID(一些答案可能缺失) |
CommentCount | Integer | 这个告诉我们帖子的评论数 |
5.3.1 将数据消减到可处理的程度
为了加快实验进程,我们不应该在一个12 GB的文件上评估分类算法。相反,我们应该想想如何把数据缩小,以便能够在快速验证想法的同时,仍然使数据具有代表性。如果把XML中创建日期(CreationDate )为2011或者之后的row 标签过滤掉,我们最后还拥有超过600万的帖子(2 323 184个问题和4 055 999个答案)。目前,这些训练数据应该已经足够多了。我们不能在XML格式的数据上进行操作,因为这样会让处理速度变得很慢。数据格式越简单越好。这就是我们要用Python的cElementTree 来解析其余的XML,并把它写入一个以tab符分隔的文件的原因。
5.3.2 对属性进行预选择和处理
我们还应该仅保留那些我们认为有助于分类器从一般答案中区分出优质答案的属性。当然,我们需要与识别有关的属性,来赋予问题正确的解答。阅读一下下面这些属性。
例如PostType 属性只能用于区分问题和答案。然后通过检查ParentId 可以进一步对它们进行区分。所以,为了区分出问题,我们要把这个属性保留下来,并设为1 。
CreationDate 属性对于确定提出问题和发表解答之间的时间间隔是有意义的,所以我们也把它保留下来。
Score 属性当然也很重要,它是社区评价的风向标。
ViewCount 属性,相反,很可能对我们的任务一点用处也没有。即使它能帮助分类器区分答案的好坏,我们却无法在答案提交时获得这个信息。所以我们把它忽略。
Body 属性明显包含了最重要的信息。由于它编码在HTML中,我们需要把它解码成纯文本。
OwnerUserId 属性只有当我们把用户相关的特征考虑进去的时候才会有用处。但我们不会考虑。尽管在这里需要把它扔掉,我们仍然鼓励你使用它(或许与users.xml 有关联)来构建一个更好的分类器。
Title 属性在这里也可以忽略,尽管它可以提供更多关于问题的信息。
CommentCount 属性也可以忽略。类似于ViewCount ,它在帖子发表了一段时间之后才会对分类器有所帮助(更多的评论等于更多模棱两可的帖子),但在答案刚发出的时候却无裨益。
AcceptedAnswerId 属性类似于Score 属性。它是帖子质量的指示器。由于每个答案都涉及这个属性,我们要创建一个新属性,IsAccepted ,而不是保留原来的属性。对于答案来说这个新属性的值是0 或者1 ,而对于问题(ParentId=1 ),它将被忽略。
我们最后得到如下的格式:
Id <TAB> ParentId <TAB> IsAccepted <TAB> TimeToAnswer <TAB> Score <TAB> Text
关于具体的解析细节,请参考so_xml_to_tsv.py 和choose_instance.py 。为了加速这个过程,我们将数据切分到两个文件中。在meta.json 里,我们存储了一个从帖子id映射到其他数据的字典(除了JSON格式的Text ),便于我们用正确的格式读取数据。例如,一个帖子的分数可以放在meta[id][Score] 里。在data.tsv 里,我们存储了Id 和Text 。用下列方法很容易对它们进行读取:
def fetch_posts(): for line in open("data.tsv", "r"): post_id, text = line.split("\t") yield int(post_id), text.strip()
5.3.3 定义什么是优质答案
在开始训练分类器区分好坏答案之前,我们需要构造训练样本。到目前为止,我们只有一堆数据。我们仍需要确定样本的标签。
当然,我们可以简单地用IsAccepted 属性作为标签。毕竟,它标识出了解答了问题的答案。然而,这只是提问者的意见。随着时间的推进,有更多的答案提交上来,其中一些往往会比已接受的答案更好。然而,提问者却很少再回顾这个问题,并改变他/她的主意。所以最后我们会得到很多包含被接受答案的问题,而这些答案的分数并不是最高的。
另一个极端是,我们可以把每个问题中最好和最差的答案当做正负样本。但是,对于只有好答案的问题我们该怎么办呢,比如一个给2分,另一个给4分?我们真的应该把2分的答案当做负样本吗?
我们需要在两个极端之间寻求一个解决方案。如果把所有大于0分的答案当做正例,把所有小于等于0分的答案当作负例,我们就会得到一个比较合理的标签,如下所示:
>>> all_answers = [q for q,v in meta.iteritems() if v['ParentId']!=-1] >>> Y = np.asarray([meta[aid]['Score']>0 for aid in all_answers])
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论