作者:来自 Elastic Mark Harwood
戴上护目镜……
在电影《铁血战士》(Predator)中,外星人拥有先进的热成像系统,能通过观察人体与周围环境的热量差异,精准锁定他的人类猎物。
新的 “显著词汇聚合 - significant terms aggregation” 就像《铁血战士》的视角一样,能够识别出从背景中脱颖而出的有趣内容(不是通过观察热量差异,而是通过观察词频差异)。在结果集中,感兴趣的词汇像一个单音节的奥地利健美运动员在蕨类植物后面挥汗如雨的热信号一样,清晰地显现出来。
揭示“不寻常的常见”
显著词汇聚合的技巧在于发现那些在结果集中明显比其背景数据中更常见的词汇。你可以把它们称为“不寻常的常见词”,这些词汇带来的真实洞察包括:
“coil spring”(螺旋弹簧)一词被揭示为某款汽车故障报告中的重要原因(虽然 “the” 是报告中最常见的词,但并没有实际意义)
喜欢电影《Talladega Nights》(塔拉迪加之夜)的人也喜欢电影《Blades of Glory》(荣耀之刃)(他们最常喜欢的电影是《肖申克的救赎》,但这太普遍无意义)
报告亏损的信用卡显示曾向某个不知名网站有过付款(他们交易中最常见的收款方通常无意义,大型商家如 iTunes 在受害与未受害信用卡中都同样流行)
在以下章节中,我们将通过实际案例介绍该功能的一些有用应用:
检测地理异常
故障报告的根因分析
训练分类器
揭示错误分类内容
检测信用卡欺诈
产品推荐
用例:地理异常
这张 XKCD 漫画简洁地总结了传统映射分析形式存在的问题:
显著词汇聚合可以帮助解决这个问题。
我们先获取英国去年所有的犯罪数据,并使用 geohash_grid 聚合将报告按地理区域划分,然后结合一个简单的 terms 聚合,如下所示:
curl -XGET "http://localhost:9200/ukcrimes/_search" -d'
{
"query": {
"aggregations" : {
"map" : {
"geohash_grid" : {
"field":"location",
"precision":5,
},
"aggregations":{
"most_popular_crime_type":{"terms":{ "field" : "crime_type", "size" : 1}}
}
}
}
}'
最终我们得到了一张类似 XKCD 风格的地图,它实际展示的是人口分布情况,以及一个并不太有用的洞察:反社会行为在各地都是最常见的犯罪类型。
然而,如果我们使用 significant_terms
聚合,就能从数据中获得更有趣的洞察,揭示每个地点中不寻常的犯罪事件。
curl -XGET "http://localhost:9200/ukcrimes/_search" -d'
{
"query": {
"aggregations" : {
"map" : {
"geohash_grid" : {
"field":"location",
"precision":5,
},
"aggregations":{
"weirdCrimes":{"significant_terms":{"field" : "crime_type", "size":1}}
}
}
}
}'
如果我们只展示得分最高的区域,就能避开对人口最多区域和最常见犯罪的关注,开始发现数据中的异常点:
在这里,我们看到一个相对偏远的地区却有着异常多的“持有武器”类犯罪。如果我们放大地图,就能从空中看出原因——这是斯坦斯特德机场的位置,旅客在机场过境时会被例行安检。全国各地还有其他有趣的案例:某些田野在年度音乐节期间会出现毒品相关犯罪高峰;像剑桥这样的大学城全年都有大量自行车被盗;而在监狱中,似乎“罪犯对罪犯的犯罪”并不被真正视为犯罪,因此被登记为“其他”类型。
用例:根因分析
国家公路交通安全管理局(NHTSA)维护着一个汽车故障报告数据库,像许多故障报告系统一样,每条报告都包含一个产品 ID 和一段自由文本的描述。使用 significant_terms
聚合,可以通过分析每个产品的自由文本描述,识别出产品故障的常见原因。
示例查询如下:
curl -XGET "http://localhost:9200/nhtsa/_search" -d'
{
"aggregations" : {
"car_model":{
"terms":{"field" : "car_model", "size" : 20},
"aggregations":{
"reasons_for_failure" : {
"significant_terms":{"field" : "fault_description", "size" : 20}
}
}
}
}
}'
示例结果:
"aggregations": {
"car_model": {
"buckets": [
{
"key": "Taurus",
"doc_count": 3967,
"reasons_for_failure": {
"doc_count": 3967,
"buckets": [
{
"key": "coil",
"doc_count": 250,
"score": 0.544,
"bg_count": 1115
},
{
"key": "mounts",
"doc_count": 178,
"score": 0.3969,
"bg_count": 777
},
{
"key": "spring",
"doc_count": 261,
"score": 0.3668,
"bg_count": 1706
},
...
为了让这些关键词成为更易读的故障解释,一个有用的技巧是将关键词置于上下文中显示(这种技术通常被称为 KWIC,即 keywords in context 的缩写)。这需要从上面的结果中提取关键词,并构造一个带有高亮的 terms 查询。以下是一个实现该功能的 javascript 示例函数:
获取 “上下文中的关键词” 示例
function getKWIC(car_model,buckets){
var shouldClauses=[];
for(var i=0;i < buckets.length; i++)
{
//Get at least the top 5 significant keywords
if((shouldClauses.length > 5) || (buckets[i].score < 2)) {
shouldClauses.push( {"term" : { "fault_description" : {
"value" : buckets[i].key,
"boost" : buckets[i].score
} }});
}
}
var kwicQuery={
"query" :
{
"bool" : {
"should":shouldClauses,
"must":[{"terms":{"car_model":[car_model]}}]
}
},
"size":30,
"highlight": {
"pre_tags" : ["<span style="background-color: #f7f7a7;">"],
"post_tags" : ["</span>"],
"fields": {"fault_description":{"matched_fields": ["fault_description"] }}
}
};
dataForServer=JSON.stringify(kwicQuery);
var kwResultHtml="";
$.ajax({
type: "POST",
url: '/nhtsa/_search',
dataType: 'json',
async: false,
data: dataForServer,
success: function (data) {
var hits=data.hits.hits;
for (h in hits){
//format results as html table rows
var snippets=hits[h].highlight.fault_description;
kwResultHtml+="<tr><td>";
for(snippet in snippets){
kwResultHtml+="<span>"+snippets[snippet]+"...</span>";
}
kwResultHtml+="</td></tr>";
}
}
});
return kwResultHtml;
}
我们的根因分析结果可能如下所示:
. AS A RESULT OF THE SITUATION, I INCURRED EXPENSE TO REPLACE THE COIL SPRINGS, STRUTS AND UPPER MOUNTS; PLUS...AS I WAS BACKING UP THE FRONT DRIVERS SIDE COIL SPRING BROKE, PUNCTURING THE TIRE. IT IS THE SAME... 2001 FORD TAURUS (48302 ODOMETER) REAR COIL SPRING BROKE REPLACED SPRINGS WITH REAR STRUTS. *NM... WAS BROKE. FORD HAS HAD A HISTORY OF COIL SPRING FAILURES AND SHOULD ISSUE A RECALL ON ALL SPRINGS. *TR...WHILE GETTING A SCHEDULED OIL CHANGE, THE DEALER NOTICED MY COIL SPRING ON THE REAR PASSENGER SIDE... TRECALL CAMPAIGN 04V332000 CONCERNING COIL SPRINGS. THE COIL SPRING BROKE IN THREE PLACES. IT BLEW...
用例:训练分类器
许多系统通过分配标签或类别字段来对文档进行分类。分类文档可能是一个繁琐的手动过程,所以在这个例子中,我们将训练一个分类器,自动识别新文档中表明合适类别的关键词。
利用 The Movie Database (TMDB) 数据,我们可以搜索描述中包含 “vampire” 的电影:
示例查询:
curl -XGET "http://localhost:9200/tmdb/_search" -d'
{
"query" : {
"match" : {"overview":"vampire" }
},
"aggregations" : {
"keywords" : {"significant_terms" : {"field" : "overview"}}
}
}'
示例结果:
"aggregations": {
"keywords": {
"doc_count": 437,
"buckets": [
{
"key": "vampire",
"doc_count": 437,
"score": 3790.9405,
"bg_count": 437
},
{
"key": "helsing",
"doc_count": 17,
"score": 113.9480,
"bg_count": 22
},
{
"key": "dracula",
"doc_count": 33,
"score": 98.3565,
"bg_count": 96
},
{
"key": "harker",
"doc_count": 7,
"score": 42.5023,
"bg_count": 10
},
{
"key": "undead",
"doc_count": 15,
"score": 31.9717,
"bg_count": 61
},
{
"key": "buffy",
"doc_count": 4,
"score": 23.130071721937412,
"bg_count": 6
},
{
"key": "bloodsucking",
"doc_count": 4,
"score": 19.8244,
"bg_count": 7
},
{
"key": "fangs",
"doc_count": 5,
"score": 19.7094,
"bg_count": 11
}
这些关键词随后可以被精选出来,加入到使用 Percolate API 注册的新 terms 查询中,帮助识别可能应标记为 vampire 电影的新电影。注意,这样避免了大量选择有用关键词的猜测工作。
用例:使用 “Like this but not this” 模式查找错误分类的内容
对于拥有大量预分类内容的系统,识别数据库维护人员未正确分类的内容非常有用。在这个例子中,我们将从带有“acquisitions”(收购)标签的路透社新闻文章开始,使用 significant_terms
聚合学习一些相关关键词,例如:
curl -XGET "http://localhost:9200/reuters/_search" -d'
{
"query" : {
"match" : {"topics":"acq" }
},
"aggregations" : {
"keywords":{"significant_terms" : {"field" : "body", "size" : 20}},
}
}'
被揭示为与“acquisition”(收购)新闻类别相关的关键词如下:
{
"aggregations": {
"keywords": {
"doc_count": 2340,
"buckets": [
{
"key": "acquisition",
"doc_count": 469,
"score": 0.973,
"bg_count": 704
},
{
"key": "acquire",
"doc_count": 395,
"score": 0.927,
"bg_count": 535
},
{
"key": "shares",
"doc_count": 842,
"score": 0.820,
"bg_count": 2258
},
{
"key": "stake",
"doc_count": 363,
"score": 0.780,
"bg_count": 529
},
{
"key": "inc",
"doc_count": 1220,
"score": 0.752,
"bg_count": 4390
},
{
"key": "merger",
"doc_count": 298,
"score": 0.674,
"bg_count": 416
},
{
"key": "acquired",
"doc_count": 327,
"score": 0.643,
"bg_count": 513
},
...
下一步是构造一个 “like this but not this” 查询,通过:
将显著类别关键词添加到一个 should 的 terms 查询中
将原始类别字段条件添加到 mustNot 子句中
具体如下:
curl -XGET "http://localhost:9200/reuters/_search" -d'
{
"query" : {
"bool": {
"mustNot":[ {"match" : {"topics" : "acq" } }],
"should":[
{ "terms":{"body":["acquisition", "acquire","shares","stake","inc","merger"...]}}
]
}
}
}'
该查询的结果是一个按相关性排序的新闻文章列表,这些文章本应被标记为关于收购(acquisitions)的内容,但却未被正确标记。以下是一个未能记录“acq”主题标签的匹配示例:
Salomon Brothers Inc 表示已收购 Harcourt... Brace Jovanovich Inc 的 21,978 份可转换次级债券,称这些债券可转换为 21,978,000 股普通股。在一份文件中... 将其转换为股票。Salomon 表示基于 3,940 万股,持有 Harcourt 35.8% 股份。Harcourt 表示 Salomon 和纽约投资公司 Mutual Shares Corp 持有合计... 他们当前部分或全部股份通过市场或协商交易出售,
用例:检测信用卡欺诈
当银行客户打电话投诉账户上出现异常交易时,银行会进行常见妥协点分析。被发现的异常交易可能是客户未去过国家的酒店付款,但这只是根本问题的表象而非原因。在客户信用卡的支付历史中,某个商户可能故意窃取了他们的卡信息(例如加油站安装的盗刷设备),或者意外泄露了信息(例如某网站数据库被黑)。无论哪种情况,该商户都代表了一个常见妥协点,可能有大量卡信息被获取并在黑市出售。银行的目标是找出问题商户(或商户们)以及可能即将遭遇欺诈交易的客户。
起始查询是选择一批被盗用的信用卡,查看它们过去几个月的所有交易,并汇总它们支付给了哪些商户:
curl -XGET "http://localhost:9200/transactions/_search" -d'
{
"query": {
"terms": {"payer" : [59492167, 203701197, 365610456,....]}
},
"aggregations" : {
"payees":{
"significant_terms":{"field":"payee"},
"aggregations":{ "payers":{"terms":{"field":"payer"}}}
}
}
}'
查询中的付款方代表了我们的 “不开心客户”,因此匹配的交易会包含一些正常支付,但关键是导致问题的异常支付。通过在 payee 字段上使用 significant_terms
聚合,我们可以聚焦那些在这批可疑交易中出现频率远高于随机正常客户样本的商户。这有助于过滤掉那些在任何随机客户样本中都常见的热门商户,专注于可能的妥协点。对于选出的可疑商户,我们设置了一个 payers 的子聚合,这样可以看到有多少“不开心客户”与该商户交易,并可以将其可视化为社交网络图。
如果只用简单的 terms
聚合,我们往往会聚焦在热门商户上,真正的嫌疑商户则被隐藏在常见的热门收款方中,难以识别。
但当我们使用 significant_terms
聚合时,焦点转向那些“不寻常的常见”连接点,结果中的额外统计信息让我们能够报告该商户交易中,有多少百分比属于这批可疑交易:
现在嫌疑商户变得更加明显了。这里最可疑的商户在其总共 72 笔交易中,有 13 笔属于问题交易集,使他成为我们最有可能的嫌疑对象。另一家商户有 19 笔交易,其中 3 笔出现在问题集中,可能只是因为坏商户的客户也经常在这家邻近店铺购物。叠加地理和时间信息有助于此类调查,这可以通过在查询中添加额外的子聚合轻松实现。
用例:产品推荐
产品推荐通常基于 “喜欢这个的人也喜欢……” 类型的购买数据分析。最强大的推荐引擎使用复杂算法,考察数据的多个特征,但这里我们将用 significant_terms
聚合,利用简单数据快速给出合理结果。在这个例子中,我们使用公开的 “MovieLens” 数据。第一步是索引用户评分数据,使每个用户对应一个 JSON 文档,列出他们喜欢的所有电影 ID(评分 4 星及以上):
{
"user": 6785,
"movie": [12, 3245, 4657, 7567, 55276, 56367...]
}
现在,对于任意一部电影,我们可以查询所有喜欢这部电影的人,汇总他们还喜欢的其他电影:
{
"query": { "terms": { "movie": [46970]} },
"aggs": {
"significantMovies": { "significant_terms": { "field": "movie" }},
"popularMovies": { "terms": { "field": "movie" }}
}
}
上面的查询首先选取了所有喜欢电影 ID 为 46970(Talladega Nights)的人,然后使用 terms
聚合汇总他们最喜欢的电影,识别最受欢迎的电影,同时用 significant_terms
聚合找到更有洞察力的“不寻常的常见”电影。
结果如下:
|
|
---|---|
|
|
|
|
|
|
|
|
terms
聚合侧重于普遍流行的电影(可以说是无关紧要的),而 significant_terms
聚合则聚焦于 “Talladega Nights” 粉丝中特别受欢迎的电影。这里显示的前三个推荐电影都主演了《Talladega Nights》的主演 Will Ferrell。
结论
这篇文章展示了使用 significant_terms
能做的一些示例。我很期待看到大家通过这个新视角探索数据时获得的新洞察。告诉我们你是如何使用它的,帮助我们提升分析能力。祝你发现更多精彩!