LoginSignup
14
4

More than 3 years have passed since last update.

ElasticsearchのSignigicant Terms AggregationのCustom Scoreによる特徴語抽出

Last updated at Posted at 2020-12-21

概要

株式会社RevCommにて音声認識と全文検索を担当するk_ishiというものです。
この記事は 2020 年の RevComm アドベントカレンダー 21 日目の記事です。 20 日目は @rhoboro さんの「たった1行から始めるPythonのAST(抽象構文木)入門」でした。

ElasticsearchにはSignificant Terms Aggregationという、テキストフィールドに含まれる特徴語を抽出する機能が搭載されています。Significant Terms Aggregationは、デフォルトではJLH Scoreという指標で特徴語の抽出を行うのですが、どうもPainlessスクリプトを利用して記述するCustom Scoreによる特徴語抽出も可能となっているようです。このCustom Score機能のことがうっすらと気になっていたのですが、どうもネット上に情報が少ないような・・・と思ったので、本稿ではCustom Score機能について調査してまとめてみたいと思います。

特徴語とは

特徴語とは、ある属性をもつテキストに偏って出現する単語のことで、Twitterのトレンドのキーワードのようなものをイメージしていただくとわかりやすいと思います。
例えば、2020年12月のテキストには「クリスマス」や「アドベントカレンダー」などの単語が偏って出現するので、これらの単語は2020年12月のテキストの特徴語であると言えます。
ElasticsearchのSignificant Terms Aggregationでは、デフォルトでJLH Scoreというスコアの高い単語を特徴語として抽出します。
JLH Scoreは、下記の通り、指定範囲のドキュメントと全体のドキュメントでの、単語の出現割合を比較してスコアを算出するものです。

JLH = 絶対割合変化 × 相対割合変化
絶対割合変化 = 指定範囲のドキュメントでの出現割合 - 全体のドキュメントでの出現割合
相対割合変化 = 指定範囲のドキュメントでの出現割合 ÷ 全体のドキュメントでの出現割合

基本的には、全体であまり出現しなかった単語が、指定範囲で多めに出現していれば、その単語のスコアが高くなり、特徴語として抽出されます。

利用ソフトウェアなど

ソフトウェア バージョン URL
Elasticsearch 7.10.1 https://www.elastic.co/jp/downloads/elasticsearch
Python Elasticsearch Client 7.10.1 https://elasticsearch-py.readthedocs.io/en/7.10.0/
Citation-Network v1 https://cn.aminer.org/citation

データセットの準備

まずは、特徴語抽出の対象とするデータセットをElasticsearchに入れて準備します。
今回はデータセットとして、 https://cn.aminer.org/citation のCitation-Network v1を使います。
まず、Elasticsearchをダウンロードし、ローカルで立ち上げてください。
そして、下記のコマンドでindexを作ります。
Citation-Network v1は国際会議論文のタイトル、著者、国際会議名、開催年、アブストラクト、参考文献の情報が入っているデータセットです。
今回はアブストラクトから特徴語を抽出したいので、 abstractfielddatatrue にしています。
(Significant Terms Aggregationによる特徴語抽出は、fielddata:trueのフィールドでしか使えません)


curl -H "Content-Type: application/json" -XPUT 'http://localhost:9200/paper' -d '{
    "mappings": {
        "properties": {  
            "title": {
                "type": "text"
            },
            "authors": {
                "type": "text"
            },
            "venue": {
                "type": "text"
            },
            "abstract": {
                "type": "text",
                "fielddata": true
            },
            "year": {
                "type": "text"
            },
            "references": {
                "type": "text"
            }           
        }
    }
}'

次に、Citation-Network v1のデータをElasticsearchにインデクシングします。
今回は、下記のスクリプトで1万件だけインデクシングしました。
https://gist.github.com/ken57/6a299e67c90fac8a027a600daeeda352

JLH Scoreによる特徴語抽出

Significant Terms AggregationのJLH Scoreによる特徴語抽出

ElasticsearchのSignificant Terms Aggregationでは、JLHスコアを利用して特徴語抽出ができます。
下記のクエリにて、JLH Scoreにより、2003年論文のabstractの特徴語が抽出できます。
yearのところを"2004" に変えれば、2004年論文のabstractの特徴語が抽出できます。


curl -H 'Content-Type: application/json' localhost:9200/_search -d '{
    "size": 0,
    "query": {
        "term": {
            "year": "2003"
        }
    },
    "aggs": {
        "significant_terms_result": {
            "significant_terms": {
                "size": 5,
                "min_doc_count": 3,
                "field": "abstract"
            }
        }
    }
}'

2003年JLHスコアにより抽出された論文の特徴語は下記の通りでした。

{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 748,
      "relation": "eq"
    },
    "max_score": null,
    "hits": []
  },
  "aggregations": {
    "significant_terms_result": {
      "doc_count": 748,
      "bg_count": 10000,
      "buckets": [
        {
          "key": "preplogic",
          "doc_count": 10,
          "score": 0.16536074809116647,
          "bg_count": 10
        },
        {
          "key": "2003",
          "doc_count": 27,
          "score": 0.15273559065358508,
          "bg_count": 69
        },
        {
          "key": "cd",
          "doc_count": 26,
          "score": 0.1201397428198309,
          "bg_count": 78
        },
        {
          "key": "pass",
          "doc_count": 12,
          "score": 0.10651474979880143,
          "bg_count": 21
        },
        {
          "key": "joggers",
          "doc_count": 8,
          "score": 0.10369184134519144,
          "bg_count": 10
        }
      ]
    }
  }
}

2004年JLHスコアにより抽出された論文の特徴語は下記の通りでした。

{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 802,
      "relation": "eq"
    },
    "max_score": null,
    "hits": []
  },
  "aggregations": {
    "significant_terms_result": {
      "doc_count": 802,
      "bg_count": 10000,
      "buckets": [
        {
          "key": "geometric",
          "doc_count": 14,
          "score": 0.07488439032397091,
          "bg_count": 33
        },
        {
          "key": "cad",
          "doc_count": 6,
          "score": 0.06248095472043083,
          "bg_count": 8
        },
        {
          "key": "mesh",
          "doc_count": 9,
          "score": 0.041249743471744586,
          "bg_count": 24
        },
        {
          "key": "boundary",
          "doc_count": 10,
          "score": 0.037683323667341985,
          "bg_count": 31
        },
        {
          "key": "subdivision",
          "doc_count": 4,
          "score": 0.03647158081521052,
          "bg_count": 6
        }
      ]
    }
  }
}

2003年のトップの preplogic というのはちょっとよくわからないですね。
調べてみたところ、ネットワーク関係の会社名(?)のようでした。
2004年のトップは、 geometric とのことで、幾何学とかが流行ってたんでしょうか・・・

手計算によるJLHスコア算出

たいして難しい計算でもないので、手計算で geometric のスコアを出してみます。

2004年のgeometricの件数

curl -H 'Content-Type: application/json' localhost:9200/_count -d '{
    "query": {
        "bool": {
            "must": [
                {
                    "term": {
                        "year": "2004"
                    }
                },
                {
                    "match": {
                        "abstract": "geometric"
                    }
                }
            ]
        }
    }
}'

結果は14件

2004年のドキュメント件数

curl -H 'Content-Type: application/json' localhost:9200/_count -d '{
    "query": {
        "bool": {
            "must": [
                {
                    "term": {
                        "year": "2004"
                    }
                }
            ]
        }
    }
}'

結果は802件

ドキュメント全体でのgeometricの件数

curl -H 'Content-Type: application/json' localhost:9200/_count -d '{
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "abstract": "geometric"
                    }
                }
            ]
        }
    }
}'

結果は33件

全体のドキュメント件数は10,000件です。

geometric のJLH Scoreは
JLH Score(geometric) = (14/802 - 33/10000) × (14/802/(33/10000)) = 0.07488439
となり、Significant Terms Aggregationの結果と一致します。

Custom Scoreによる特徴語抽出

ElasticsearchのSignificant Terms Aggregationでは、JLHスコアの他にも Mutual Information、Chi square、Google normalized distance、Percentage を利用して特徴語抽出ができますが、ここではPlainless Scriptを利用した Custom Scoreでの特徴語抽出を試してみます。
まずは、Painless ScriptでJLH Scoreを追実装してみます。
Custom Scoreでは下記のパラメータが利用できます。

  • params._subset_size: 指定範囲のドキュメント数
  • params._superset_size: 全体でのドキュメント数
  • params._subset_freq: 指定範囲での単語の出現数
  • params._superset_freq: 全体での単語の出現数
long sbs = params._subset_size.longValue();
long sps = params._superset_size.longValue();
long sbf = params._subset_freq.longValue();
long spf = params._superset_freq.longValue();
if (sbs > 0 && sps > 0 && sbf > 0 && spf > 0) {
  double sbr = sbf / (double) sbs;
  double spr = spf / (double) sps;
  return (sbr - spr) * (sbr / spr);
} else {
  return 0.0;
}

これを一行にまとめてjlh-scoreというIDでElasticsearchに保存します・・・。保存すると、Elasticsearch内でスクリプトがコンパイルされます。(複数行のまま保存する方法ないんだろうか)

curl -XPUT -H 'Content-Type: application/json' localhost:9200/_scripts/jlh-score -d '{
    "script": {
    "lang": "painless",
    "source": "long sbs = params._subset_size.longValue();long sps = params._superset_size.longValue();long sbf = params._subset_freq.longValue();long spf = params._superset_freq.longValue();if ( sbs > 0 && sps > 0 && sbf > 0 && spf > 0) {  double sbr = sbf / (double) sbs;  double spr = spf / (double) sps;  return  (sbr - spr) * (sbr / spr);} else {  return 0.0;}"
  }
}'

下記で登録したスクリプトを利用して特徴語抽出ができます。

curl -H 'Content-Type: application/json' localhost:9200/_search -d '{
    "size": 0,
    "query": {
        "term": {
            "year": "2004"
        }
    },
    "aggs": {
        "significant_terms_result": {
            "significant_terms": {
                "size": 5,
                "field": "abstract",
                "script_heuristic": {
                    "script": {
                        "stored": "jlh-score"
                    }
                }
            }
        }
    }
}'

JLHスコアで特徴語抽出したときと同じ結果がレスポンスされます。で・・・オリジナルのスコアを考えたいのですが・・・良い案も思いつかないので、今回は適当に下記としてみます・・・。

# 変数αは外から与える
# Custom Score = pow(指定範囲のドキュメントの出現数, α) × 絶対割合変化 × 相対割合変化

long sbs = params._subset_size.longValue();
long sps = params._superset_size.longValue();
long sbf = params._subset_freq.longValue();
long spf = params._superset_freq.longValue();
if (sbs > 0 && sps > 0 && sbf > 0 && spf > 0) {
  double sbr = sbf / (double) sbs;
  double spr = spf / (double) sps;
  return Math.pow(sbf, params.alpha) * (sbr - spr) * (sbr / spr);
} else {
  return 0.0;
}

ID: custom-scoreで保存します。

curl -XPUT -H 'Content-Type: application/json' localhost:9200/_scripts/custom-score -d '{
    "script": {
    "lang": "painless",
    "source": "long sbs = params._subset_size.longValue();long sps = params._superset_size.longValue();long sbf = params._subset_freq.longValue();long spf = params._superset_freq.longValue();if ( sbs > 0 && sps > 0 && sbf > 0 && spf > 0) {  double sbr = sbf / (double) sbs;  double spr = spf / (double) sps;  return  Math.pow(sbf, params.alpha) * (sbr - spr) * (sbr / spr);} else {  return 0.0;}"
  }
}'

alphaに2を指定しつつ、custom-scoreを利用した特徴語を実行するときの例です。
下記のalphaの値を大きくすると、指定範囲での頻度が大きい単語が上位に上がりやすくなります。

curl -H 'Content-Type: application/json' localhost:9200/_search -d '{
    "size": 0,
    "query": {
        "term": {
            "year": "2004"
        }
    },
    "aggs": {
        "significant_terms_result": {
            "significant_terms": {
                "size": 5,
                "field": "abstract",
                "script_heuristic": {
                    "script": {
                        "params": {
                            "alpha": 2
                        },
                        "stored": "custom-score"
                    }
                }
            }
        }
    }
}'
{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 802,
      "relation": "eq"
    },
    "max_score": null,
    "hits": []
  },
  "aggregations": {
    "significant_terms_result": {
      "doc_count": 802,
      "bg_count": 10000,
      "buckets": [
        {
          "key": "you",
          "doc_count": 42,
          "score": 15.12774174289961,
          "bg_count": 450
        },
        {
          "key": "geometric",
          "doc_count": 14,
          "score": 14.6773405034983,
          "bg_count": 33
        },
        {
          "key": "guide",
          "doc_count": 29,
          "score": 8.038082622472649,
          "bg_count": 286
        },
        {
          "key": "3d",
          "doc_count": 13,
          "score": 5.050820197613765,
          "bg_count": 57
        },
        {
          "key": "boundary",
          "doc_count": 10,
          "score": 3.768332366734198,
          "bg_count": 31
        }
      ]
    }
  }
}

まとめ

以上がElasticsearchのSignigicant Terms AggregationのCustom Scoreによる特徴語抽出の例でした。
スクリプトの外部からパラメータを与えたりもできるようなので、結構色々なことができそうな感じもしますね。
予め用意している指標で満足のいく結果が得られないときは、Custom Scoreを利用してみるのもありかも知れません。

明日は、@mpayu2さんの記事になります。お楽しみに!

14
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
4