原文链接:Trending at Instagram
译者:杰微刊 -张迪
随着上周”Search and Explore”功能的发布,我们介绍了一种能力:在Instagram上,当有趣的瞬间发生的一刻,我们就可以轻松地发现它。Explore功能里的热门标签(trending hashtags)和热门地点,展现了来自世界各地最好的、最流行的内容,它们可能是从你并没有关注的地点和账号中检索出来的。创建一个每天分析2亿人的7千多万张新照片的系统,无疑是一个挑战。下面就来看看,我们如何实现识别、排名和呈现Instagram的最佳热门内容。
热门的定义 Definition of a Trend
直观地说,一个热门标签应该是一个比平常使用的更多的标签,是发生在某一时刻的特殊事件。例如,人们通常不会发送北极光的图片,但在我们发布功能的那天,一群人使用# northernlights标签分享了一些惊人的照片。你可以在下面的图片中看出,这个标签的使用量是如何随着时间的推移而增长的。
当我们写这篇博客时,#equality是Instagram上最热门的标签。
同样地,无论什么时候,当有大量的人分享在一个地方、于某一时刻拍摄的照片或视频时,这个地点就会成为热门地点。当我们写这篇文章的时候,美国最高法院也成为了热门地点,因为有数百人在那里分享其支持同性婚姻决策的游行活动。
通过上述事例,我们发现一个热门通常会包含三个要素:
① 人气(Popularity)——热门应该是让我们社会中的很多人都感兴趣的。
② 新奇度(Novelty)——热门应该是关于新的东西的。人们并没有在之前发布过,或是发布的数量不多。
③ 时效性(Timeliness)——当真实的事件发生的那一瞬间,热门就应该在Instagram上展现出来。
在这篇文章中,我们将讨论我们正在使用的、关于用于识别、排行和展示热门的算法和系统。
识别热门 Identifying a Trend
识别热门,要求我们量化,正在观察的活动(共享照片和视频的数量)与预期热门的活动之间的差异。通俗地讲,如果被观察的活动的热度大大高于预期的活动的热度,那么我们可以确定它是热门,然后我们可以根据预期值的差异进行热门排序。
让我们回到文章开始时#equality的例子。通常我们观察到,每小时内只有几张照片和视频使用这个标签。从07:00am PT(太平洋时区)开始,数以千计的人们开始使用#equality标签分享内容。这意味着#equality的活跃度高于我们的预期。相反,每天有超过十万张照片和视频用#love标记,因为它是一个非常受欢迎的标签。即使在一天内我们观察到有超过一万个#love,但鉴于其历史均值,这也不足以超出我们的预期。
对于每一个标签和地点,我们会储存一些数据:过去七天中,五分钟内,在多少个地方使用过这个标签或地点。为简单起见,现在我们只考虑标签,让我们假设C(h,t)是标签h在t时刻的计数(即,它是从t-5min到t这5分钟的时间段内,使用这个标签的数量)。因为随着时间的推移,这个计数在不同的标签之间有很大变化,我们使之规范化:计算标签h在时刻t时的概率P(h,t)。给定一个标签的历史计数(即时间序列(time series)),我们可以建立一个模型,预测观察所得的预期数量:C’(h,t);同样,计算预期的概率P’(h,t)。给定每个标签这两个值,一个共同的度量概率之间的差就是KL散度,在我们的案例中是这样计算的:
S(h, t) = P(h, t) * ln(P(h, t)/P’(h, t))
本质上,我们会考虑目前观察到的人气(popularity)和新奇度(novelty),人气是由概率P(h,t)体现的;新奇度的计算方式是,我们目前观测的数量比上预期数量P(h,t)/ P‘(h,t)。自然对数(LN)是用来中和新奇度的“强度(strength)”,使之与人气相当。时效性(timeliness)是由参数平衡的,通过查看在最近显示的窗口中出现的次数,热门被实时选出。
预测的问题 A Prediction Problem
如何基于过去的观察,计算出预期基线概率(the expected baseline probability)?
有几方面因素可以影响估计的准确性和计算的时间复杂度和空间复杂度(time-and-space complexity)。通常情况下,这些事情都不太好处理——你越想要准确,算法要求的2个复杂度就越高。我们尝试了不同的方案,比如使用上周相同时间内的数值,比如回归模型(regression models),甚至是神经网络(neural networks)。事实证明,别致的东西更准确,简单的东西效果更好,所以我们最终选择过去一周测量值的最大概率(maximal probability over the past week’s worth of measurements)。为什么这个好呢?
① 很容易计算,并且内存需求相对较低。
② 用高方差(high variance)来禁止非热门的发表很有效。
③ 快速识别新热门。
在这个解释中有两件事我们简单化了,所以让我们来改进模型吧。
首先,尽管一些标签非常受欢迎,但大多数的标签并不受欢迎,五分钟计数极低或为零。因此,对于以前的计数,我们保持以小时为单位作为时间间隔,因为计算基准概率时不需要五分钟法则。我们还要看看几个小时内的数据,因此我们可以尽量减少因随机使用高峰(random usage spikes)引起的“噪音(noise)”。我们注意到,在获得足够数据和快速发现热门事件之间,有一个平衡点——时间周期越长,我们获得的数据就越多,但确定一个热门的速度就越慢。
其次,如果预测基准P(h,t)仍然是零,甚至积累了几个小时后仍然是零,我们将不能够计算KL散度(因为分母为0)。因此,我们简单处理:如果我们在过去没有看到任何媒体对于一个给定标签的报道,且在这期间有三个这个标签标记的帖子,我们将记录它。为什么是三个?这让我们在存储数据时节省了大量的内存(> 90%),由于大多数的标签不能每个小时都有超过三个帖子,所以这部分标签的计数我们不保存,只保存每个小时至少有三个帖子使用了该标签的计数。
排名和综合排序 Ranking and Blending
下一个步骤是基于标签的热度进行排名,实现的方式是集合所有候选的某一国家/语言的标签(现在在美国是可行的),根据其KL散度得分,S(h,t)进行挑选。我们注意到,一些热门往往比其周围的兴趣消失得更快。例如,有大量文章使用的标签在那一时刻是热门,但这个热门的热度会自然而然的随着事件的结束而降低。因此,它的KL散度得分会快速降低,然后这个标签就不会在热了,即使人们通常喜欢在一个热门事件结束几小时后还看其照片和视频。
为了克服这些问题,我们使用指数衰减函数(exponential decay function)来定义先前热门的存活时间,来确定让它们再存活多久。我们跟踪每个热门的最大KL得分,即SM(h),时间Tmax,公式为S(h,Tmax)= Sm(h)。然后,我们还计算了那一刻每个候选标签的SM(h)的指数衰减值(exponential decayed value),最后我们可以用其最近的KL得分排序。
Sd(h, t) = SM(h) * (?)^((t - tmax)/half-life)
我们设置衰减参数half-life为两个小时,这意味着SM(h)每两小时减半。这样,如果一个标签或地点在几小时前是一个大热门,它可能仍然以热门的形式,与最新的热门并排显示。
相似热门分组 Grouping similar trends
人们往往用不同的标签来描述同一个事件,当这个事件很受欢迎时,描述这个事件的多个标签都可能是热门。由于显示描述同一个事件的多个标签可能是一个令人不喜的用户体验,我们把“概念”一样的标签组成一个组。
例如,上图说明了所有和# equality一起使用的标签。这表明,#equality总是和#lovewins, #love, #pride, #lgbt,还有许多很多其他热门标签一起使用。通过将这些标签组在一起,我们可以将#equality作为一个热门显示,并保留筛选所有其他不同标签的需要,直到获得另一个有趣的热门标签。
有两个重要的任务需要完成——第一,要弄清楚哪些标签讨论的是同一件事;第二,寻出一个最能代表这个组的标签。这里存在两个挑战——第一,我们需要捕捉到一些标签之间的相似概念;然后,我们使用“无监督的(unsupervised)”方式汇集它们,这意味着任何时刻,我们都不可能知道应该有多少集群(cluster)。
我们使用以下标签间的相似概念:
① 同时出现(Cooccurrences)——人们倾向于同时使用的一组标签,例如#fashionweek、#dress和 #model。同时出现的计算方式是看看最近的媒体报道,然后计数每个标签和其他标签一起出现的次数。
② 编辑距离(Edit distance)——同一个标签的不同拼写方法(或错误),例如# valentineday和#valentinesday,这些往往不会同时出现,是因为人们很少一起使用。使用字符串相似度算法来规避拼写差异的问题。
③话题分布(Topic distribution)——描述同一件事的标签,例如 #gocavs, #gowarriors有不同的拼写方式,很少同时出现。我们看了使用这些标签的标题,运行一个内部工具将它们分到一组预定义主题。对于每个标签,我们要看看主题分布(从出现过的所有媒体标题中收集),使用TF-IDF来加以规范。
我们的标签分组过程会计算两个热门标签的不同相似度,然后决定哪两个是足够相似的,以致可以被认为是相同的。在合并过程中,有一些标签集群(clusters of tags)出现,如果它们足够相似也会被合并。
现在我们已经讨论了关于确认Instagram热门标签的过程,让我们看看后台是如何将上述每个部分实现的。
系统的设计 System Design
热门标签的后台,被设计为一个具有四个节点的流处理应用,被连接在一个像装配线一样的链式结构中,如下图所示:
每一个节点都消耗和产生一个记录线(”log” lines)的流。入口点接收媒体创作事件的流,最后一个节点输出一个热门项目的排名(标签或地点)。每个节点都有如下的一个特定角色:
① 前置处理器(pre-processor)——掌握媒体原创事件内容和其创造者的元数据,在前置处理阶段,我们获取并附上所需的所有数据,以便在下一步中使用质量过滤器(quality filters)。
②解析器(parser)——提取照片或视频中的标签或地点,使用质量过滤器。如果一篇文章不符合我们的标准,它就不会被计入热门。
③ 计分器(scorer)——储存每个热门的时间聚集计数(time-aggregated counters)。我们的计分函数S(h,t)也在这里计算。每隔几分钟,S(h,t)的结果值就会被发布。
④ 排名器(ranker)——聚集和排列所有候选的热门及其热度分数。
我们的系统处理和存储大量的实时数据,它应该是高效的、并且具有容灾性的。这种流线型的架构(stream-lined architecture)使我们能够区分热门,每个节点上推出多个实例,这使得每个节点都能存储更少的数据量,而且能并行处理那些热门。此外,失败会被隔离在某个具体的分区,因此如果一个实例失败,热门计算不会完全失败。
到目前为止,我们讨论了热门标签的计算过程。下面的图表添加了一些组件,它们负责为app请求提供热门标签:
如图所示,对于来自Instagram应用程序的热门标签和地点的请求,需要在不向后台施加荷载的情况下被处理。因此,以防缓存丢失(cache miss),我们使用memcached的read-through缓存层(caching layer)和Postgres数据库。数据库通过定时任务填充从排名器中挑选出来的新热门,使用算法将相似的热门聚集在一起,再把结果存储到Postgres。通过这种方式,Instagram应用程序总是能从缓存层获取最新的热门标签,这可以使我们方便地扩展存储和信息流处理组件。
总结 Conclusion
当我们处理热门时,我们试图将它分解成一个个更小的问题,可以通过一个个特定功能组件来单独处理。因此,团队成员能够在转移到下一问题前专注处理一个具体的问题。我们希望用户能享受这个新功能,通过它更好地与世界产生联系,正如它已经发生的那样。
-------------------好久不见的分割线-------------------
如果您发现这篇译文的任何问题,可随时与杰微刊联系。
[转载请保留原文出处、译者和审校者。 可以不保留我们的链接]