计算趋势主题或标签的最佳方式是什么?

许多网站提供了一些统计资料,如“最近24小时最热的话题”。 例如,Topix.com在其“新闻趋势”部分显示了这一点。 在那里,你可以看到提及数量增长最快的话题。

我也想计算一个话题的“嗡嗡”。 我怎么能这样做? 该algorithm应该加权总是热的话题less。 通常(几乎)没有人提到的话题应该是最热门的话题。

Google提供“热门趋势”,topix.com显示“热门话题”,fav.or.it显示“关键字趋势” – 所有这些服务有一个共同点:他们只显示即将到来的exception热点即将到来的趋势。

像“布兰妮斯皮尔斯”,“天气”或“巴黎希尔顿”这样的词汇将不会出现在这些列表中,因为它们总是很热且频繁。 这篇文章称之为“小甜甜布兰妮问题”。

我的问题:如何编码algorithm或使用现有的algorithm来解决这个问题? 在过去的24小时内search关键字的列表,algorithm会显示10(例如)最热门的。

我知道,在上面的文章中,提到了某种algorithm。 我试图用PHP编写代码,但我不认为它会起作用。 它只是发现大多数,不是吗?

我希望你能帮助我(编码的例子会很棒)。

你需要一个algorithm来衡量一个主题的速度 – 换句话说,如果你想要以惊人的速度显示那些正在上升的图。

这是趋势线的第一个导数,作为整体计算的加权因子并不难。

规范化

你需要做的一个技巧是规范化所有的数据。 对于您所关注的每个主题,请保留一个定义该主题基准的非常低通滤镜。 现在,关于该主题的每个数据点都应该进行标准化 – 减去其基线,您将获得所有主题在0附近,尖峰高于和低于线。 您可能想要将信号除以其基线量级,这将使信号达到1.0左右 – 这不仅使所有信号相互一致(使基线正常化),还使峰值正常化。 一个布兰妮峰会比别人的高峰大得多,但这并不意味着你应该注意它 – 相对于她的基线来说,高峰可能是非常小的。

派生

一旦你规范了一切,找出每个主题的斜率。 连续两个点,并测量差异。 一个积极的区别是趋势上升,一个消极的区别是趋势下降。 那么你可以比较标准化的差异,找出哪些话题比其他话题更受欢迎 – 每个话题都适合自己的“正常”,这可能是与其他议题不同的大小。

这实际上是问题的第一步。 有更先进的技术,你需要使用(主要是上述与其他algorithm的组合,加权,以满足您的需求),但它应该足以让你开始。

关于文章

这篇文章是关于主题趋势的,但是不是关于如何计算热门和不热门的,而是关于如何处理像Lycos和Google这样的algorithm必须处理的大量信息。 每个主题都需要一定的空间和时间,并且在search过程中发现每个主题的柜台都是巨大的。 这篇文章是关于尝试这样一个任务时面临的挑战。 它确实提到了布兰妮效应,但它并没有谈到如何克服它。

Nixuz指出,这也被称为Z或标准分数 。

这个问题需要一个z分数或标准分数,这个分数会考虑到其他人所提到的历史平均值,也包括这个历史数据的标准偏差,使得它比仅使用平均值更加稳健。

在你的情况下,z分数是通过下面的公式计算的,其中趋势将是诸如views / day的比率。

z-score = ([current trend] - [average historic trends]) / [standard deviation of historic trends] 

当使用z分数时,z分数越高或越低,趋势越不正常,例如如果z分数高度正则则趋势exceptionboost,而如果是高度负值则exception下降。 所以,一旦你计算出所有候选趋势的Z分数,最高的10个Z分数将与最exception增加的Z分数相关。

请参阅维基百科了解更多关于z分数的信息。

 from math import sqrt def zscore(obs, pop): # Size of population. number = float(len(pop)) # Average population value. avg = sum(pop) / number # Standard deviation of population. std = sqrt(sum(((c - avg) ** 2) for c in pop) / number) # Zscore Calculation. return (obs - avg) / std 

示例输出

 >>> zscore(12, [2, 4, 4, 4, 5, 5, 7, 9]) 3.5 >>> zscore(20, [21, 22, 19, 18, 17, 22, 20, 20]) 0.0739221270955 >>> zscore(20, [21, 22, 19, 18, 17, 22, 20, 20, 1, 2, 3, 1, 2, 1, 0, 1]) 1.00303599234 >>> zscore(2, [21, 22, 19, 18, 17, 22, 20, 20, 1, 2, 3, 1, 2, 1, 0, 1]) -0.922793112954 >>> zscore(9, [1, 2, 0, 3, 1, 3, 1, 2, 9, 8, 7, 10, 9, 5, 2, 4, 1, 1, 0]) 1.65291949506 

笔记

  • 如果您不想考虑过多的历史数据,您可以使用这种方法(即最近30天),这将使短期趋势更加明显,并可以缩短处理时间。

  • 您也可以使用z值来查看值,例如从一天到下一天的视图更改,以查找每天增加/减less视图的exception值。 这就像使用每日视图的斜率或派生图。

  • 如果你跟踪目前的人口规模,目前的人口总数,以及目前总人口的x ^ 2,你不需要重新计算这些价值,只需要更新它们,因此你只需要保留这些历史logging的值,而不是每个数据值。 以下代码演示了这一点。

     from math import sqrt class zscore: def __init__(self, pop = []): self.number = float(len(pop)) self.total = sum(pop) self.sqrTotal = sum(x ** 2 for x in pop) def update(self, value): self.number += 1.0 self.total += value self.sqrTotal += value ** 2 def avg(self): return self.total / self.number def std(self): return sqrt((self.sqrTotal / self.number) - self.avg() ** 2) def score(self, obs): return (obs - self.avg()) / self.std() 
  • 使用这种方法你的工作stream程如下。 对于每个主题,标记或页面都会创build一个浮点字段,以获得数据库中的总天数,视图总和和视图总和的平方。 如果您有历史数据,请使用该数据初始化这些字段,否则将初始化为零。 在每天结束时,使用当天的视图数量来计算存储在三个数据库字段中的历史数据的z-分数。 具有最高X z分数的主题,标签或页面是当日的X“最热门趋势”。 最后用这一天的价值更新这三个领域的每一个,明天重复这个过程。

新增function

如上所述的正常z-分数没有考虑数据的顺序,因此观察到“1”或“9”的z-分数将具有与序列[1,1,1,1 ,9,9,9,9]。 显然,对于趋势发现来说,最新的数据应该比旧的数据更重要,因此我们希望“1”观察比“9”观察具有更大的幅度得分。 为了达到这个目的,我提出了一个浮动的平均Z值。 应该清楚的是,这种方法不能保证在统计上是合理的,但对于趋势发现或类似的应用是有用的。 标准z分数和浮动平均z分数之间的主要区别是使用浮动平均值来计算平均人口值和平均人口值平方。 请参阅代码了解详情:

 class fazscore: def __init__(self, decay, pop = []): self.sqrAvg = self.avg = 0 # The rate at which the historic data's effect will diminish. self.decay = decay for x in pop: self.update(x) def update(self, value): # Set initial averages to the first value in the sequence. if self.avg == 0 and self.sqrAvg == 0: self.avg = float(value) self.sqrAvg = float((value ** 2)) # Calculate the average of the rest of the values using a # floating average. else: self.avg = self.avg * self.decay + value * (1 - self.decay) self.sqrAvg = self.sqrAvg * self.decay + (value ** 2) * (1 - self.decay) return self def std(self): # Somewhat ad-hoc standard deviation calculation. return sqrt(self.sqrAvg - self.avg ** 2) def score(self, obs): if self.std() == 0: return (obs - self.avg) * float("infinity") else: return (obs - self.avg) / self.std() 

示例IO

 >>> fazscore(0.8, [1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9]).score(1) -1.67770595327 >>> fazscore(0.8, [1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9]).score(9) 0.596052006642 >>> fazscore(0.9, [2, 4, 4, 4, 5, 5, 7, 9]).score(12) 3.46442230724 >>> fazscore(0.9, [2, 4, 4, 4, 5, 5, 7, 9]).score(22) 7.7773245459 >>> fazscore(0.9, [21, 22, 19, 18, 17, 22, 20, 20]).score(20) -0.24633160155 >>> fazscore(0.9, [21, 22, 19, 18, 17, 22, 20, 20, 1, 2, 3, 1, 2, 1, 0, 1]).score(20) 1.1069362749 >>> fazscore(0.9, [21, 22, 19, 18, 17, 22, 20, 20, 1, 2, 3, 1, 2, 1, 0, 1]).score(2) -0.786764452966 >>> fazscore(0.9, [1, 2, 0, 3, 1, 3, 1, 2, 9, 8, 7, 10, 9, 5, 2, 4, 1, 1, 0]).score(9) 1.82262469243 >>> fazscore(0.8, [40] * 200).score(1) -inf 

更新

正如David Kemp正确指出的那样,如果给定一系列常数值,然后为观测值提供一个不同于其他值的zscore,则结果应该可能不为零。 实际上返回的值应该是无穷大的。 所以我改变了这一行,

 if self.std() == 0: return 0 

至:

 if self.std() == 0: return (obs - self.avg) * float("infinity") 

这一变化反映在fazscore解决scheme代码中。 如果一个人不想处理无穷大的值,那么可以接受的解决scheme就是将行改为:

 if self.std() == 0: return obs - self.avg 

乍得桦木和亚当戴维斯是正确的,你将不得不向后看build立一个基准。 你的问题,如措辞,表明你只想查看过去24小时的数据,这不会飞。

给数据一些内存而不必查询大量历史数据的一种方法是使用指数移动平均值。 这样做的好处是你可以每个周期更新一次,然后刷新所有的旧数据,所以你只需要记住一个值。 因此,如果您的时间段是一天,您必须为每个主题保持“每日平均”属性,您可以通过以下方式执行此操作:

 a_n = a_(n-1)*b + c_n*(1-b) 

其中a_n是第n天的移动平均数,b是0和1之间的某个常数(越接近1,记忆越长), c_n是第n天的命中n 。 如果你在第n天结束时执行这个更新,你可以刷新c_na_(n-1)

一个警告是,它会最初敏感,无论你select你的初始值a

编辑

如果有助于可视化这种方法,取n = 5a_0 = 1b = .9

假设新的值是5,0,0,1,4:

 a_0 = 1 c_1 = 5 : a_1 = .9*1 + .1*5 = 1.4 c_2 = 0 : a_2 = .9*1.4 + .1*0 = 1.26 c_3 = 0 : a_3 = .9*1.26 + .1*0 = 1.134 c_4 = 1 : a_4 = .9*1.134 + .1*1 = 1.1206 c_5 = 4 : a_5 = .9*1.1206 + .1*5 = 1.40854 

它看起来不像一个平均水平吗? 请注意,即使我们的下一个input是5,值仍然接近1。发生了什么? 如果你扩大math,你会得到什么:

 a_n = (1-b)*c_n + (1-b)*b*c_(n-1) + (1-b)*b^2*c_(n-2) + ... + (leftover weight)*a_0 

剩余体重是什么意思? 那么,平均来说,所有的权重都必须加1,如果n是无限的,那么……可以永远持续下去,那么所有的权重总和为1.但是如果n相对较小,那么剩下的权重就会很大在原始input。

如果你研究上述公式,你应该认识到这个用法的一些事情:

  1. 所有的数据永远都会为平均贡献一些东西 。 实际上,贡献确实是非常小的一点。
  2. 最近的值比旧的值贡献更多。
  3. b越高,新值越低,旧值越长。 然而,越高的数据,你需要越多的数据来降低a的初始值。

我认为前两个特点正是你正在寻找的。 给你一个简单的想法可以实现,这里是一个python实现(减去所有的数据库交互):

 >>> class EMA(object): ... def __init__(self, base, decay): ... self.val = base ... self.decay = decay ... print self.val ... def update(self, value): ... self.val = self.val*self.decay + (1-self.decay)*value ... print self.val ... >>> a = EMA(1, .9) 1 >>> a.update(10) 1.9 >>> a.update(10) 2.71 >>> a.update(10) 3.439 >>> a.update(10) 4.0951 >>> a.update(10) 4.68559 >>> a.update(10) 5.217031 >>> a.update(10) 5.6953279 >>> a.update(10) 6.12579511 >>> a.update(10) 6.513215599 >>> a.update(10) 6.8618940391 >>> a.update(10) 7.17570463519 

通常使用某种forms的指数/对数衰减机制来计算“嗡嗡声”。 有关Hacker News,Reddit和其他人如何以简单的方式处理这个问题的综述,请看这篇文章 。

这并没有完全解决那些一直受欢迎的问题。 你要找的东西似乎是Google的“ 热门趋势 ”function。 为此,您可以将当前值除以历史值,然后减去低于某个噪声阈值的值。

我认为他们需要注意的关键词是“exception”。 为了确定什么时候什么是“exception”,你必须知道什么是正常的。 也就是说,你将需要历史数据,你可以平均来找出一个特定查询的正常速度。 您可能希望排除平均计算中的exceptiondate,但是又需要有足够的数据,以便知道排除哪些日子。

从那里,你将不得不设定一个门槛(这将需要实验,我敢肯定),如果超出门槛,比正常多50%的search,你可以认为这是一个“趋势”。 或者,如果你想能够像你提到的那样find“Top X Trendiest”,你只需要按比例(远大于正常比例)来sorting。

例如,假设您的历史数据告诉你,布兰妮斯皮尔斯通常会得到10万次search,而帕丽斯·希尔顿通常是5万次。 如果你有一天的search量比正常的多一万次,那么你应该考虑巴黎比布兰妮“热”,因为她的search量比平时增加了20%,而布兰妮只有10%。

上帝,我不敢相信我刚刚写了一个比较小甜甜布兰妮和巴黎希尔顿的“热”的段落。 你对我做了什么?

我想知道在这种情况下是否可以使用常规物理加速公式?

 v2-v1/t or dv/dt 

我们可以认为v1在过去的24小时里,每小时的初始赞/票/评论数和v2是每小时当前的“速度”?

这更像是一个问题,而不是一个答案,但似乎只是工作。 任何加速度最高的内容将成为热门话题…

我相信这可能不会解决布兰妮斯皮尔斯问题:-)

可能是一个简单的主题频率梯度将起作用 – 大正梯度=快速增长的普及。

最简单的方法就是每天search的数量,所以你有类似的东西

 searches = [ 10, 7, 14, 8, 9, 12, 55, 104, 100 ] 

然后找出它每天变化多less:

 hot_factor = [ ba for a, b in zip(searches[:-1], searches[1:]) ] # hot_factor is [ -3, 7, -6, 1, 3, 43, 49, -4 ] 

只是应用某种门槛,以便增加50以上的日子被认为是“热”。 如果你愿意的话,你也可以做得更加复杂。 而不是绝对的差别,你可以采取相对的差异,以便从100到150被认为是热的,但1000到1050不是。 或者是一个更复杂的渐变,考虑到不止一天的趋势。

您可以使用对数似然比来比较当前date与上个月或上一年。 这在统计上是合理的(因为你的事件不是正态分布的,从你的问题中可以推断出来)。

只需按logLRsorting所有的条款,并select前十名。

 public static void main(String... args) { TermBag today = ... TermBag lastYear = ... for (String each: today.allTerms()) { System.out.println(logLikelihoodRatio(today, lastYear, each) + "\t" + each); } } public static double logLikelihoodRatio(TermBag t1, TermBag t2, String term) { double k1 = t1.occurrences(term); double k2 = t2.occurrences(term); double n1 = t1.size(); double n2 = t2.size(); double p1 = k1 / n1; double p2 = k2 / n2; double p = (k1 + k2) / (n1 + n2); double logLR = 2*(logL(p1,k1,n1) + logL(p2,k2,n2) - logL(p,k1,n1) - logL(p,k2,n2)); if (p1 < p2) logLR *= -1; return logLR; } private static double logL(double p, double k, double n) { return (k == 0 ? 0 : k * Math.log(p)) + ((n - k) == 0 ? 0 : (n - k) * Math.log(1 - p)); } 

PS,TermBag是一个无序的单词集合。 为每个文档创build一个术语包。 只要计算一下单词的出现次数。 然后,方法occurrences返回给定单词的出现次数,方法size返回单词总数。 最好是以某种方式将这些单词标准化,通常情况下, toLowerCase是足够好的。 当然,在上面的例子中,您将创build一个包含今天所有查询的文档,以及一个包含去年所有查询的文档。

我曾参与过一个项目,我的目标是从Live Twitter Stream中find趋势主题,并对趋势主题进行感伤分析(发现Trending Topic是否被正面/负面地谈论)。 我已经使用Storm来处理twitterstream。

我已经发表了我的报告作为博客: http : //sayrohan.blogspot.com/2013/06/finding-trending-topics-and-trending.html

我已经使用总数和Z分数的排名。

我使用的方法有点泛化,在讨论部分,我已经提到过我们如何扩展非Twitter应用程序的系统。

希望信息有帮助。

如果你只是看推文,或状态消息来获得你的主题,你会遇到很多噪音。 即使你删除所有的停用词。 获得主题候选者的一个更好的子集的一种方式是只关注共享URL的tweet /消息,并从这些网页的标题获得关键字。 并确保您应用POS标记来获取名词+名词短语。

网页标题通常更具描述性,并包含描述网页内容的单词。 另外,分享网页通常与分享新闻(即如果迈克尔·jackson这样的名人死了,你会得到很多人分享关于他的死亡的文章)相关联。

我已经跑了一些实验,只用标题中stream行的关键字,然后得到这些关键字在所有状态信息中的总计数,并且确实消除了很多噪音。 如果你这样做,你不需要一个复杂的algorithm,只是做一个关键字频率的简单sorting,而你在一半。

这个想法是跟踪这些事情,并注意到,当他们跳跃显着比自己的基准。

因此,对于具有一定门槛的查询,追踪每一个查询,当其变化到历史价值的某个数值(几乎是两倍)时,这是一个新的热门趋势。