❄️

Sudachi同義語辞書をElasticsearchで使う(暫定方法)

2021/01/26に公開

TL;DR

  • Sudachi同義語辞書を「Solr Synonyms形式」に変換して使う
  • あくまで暫定的な使い方: 本来は形態素解析結果を元に厳密に展開されるべき
  • ちゃんとしたフィルタープラグインは、徳島のSudachi公式がもうすぐ公開してくれるはず

Sudachi同義語辞書とは

同義語が単に羅列されているわけではなく、詳細化した同義関係が付与されています。

そして、この言語資源は定期的に専門家によりメンテナンス、更新されています。例えば、以下のような語も2020年7月のアップデートなどで追加されています;

...

023538,1,0,1,0,0,0,(医療),新型コロナウイルス感染症,,
023538,1,0,1,2,0,0,(医療),COVID-19,,

023539,1,0,1,0,0,0,(医療),コロナウイルス,,
023539,1,0,1,0,0,2,(医療),コロナウィルス,,
023539,1,0,1,0,0,1,(医療),Coronavirus,,
023539,1,1,1,0,2,0,(医療),コロナ,,

023540,1,0,1,0,0,0,(医療),SARS-CoV-2,,
023540,1,0,1,1,0,0,(医療),Severe Acute Respiratory Syndrome Coronavirus 2,,
023540,1,1,1,2,0,0,(医療),新型コロナウイルス,,
023540,1,1,1,2,2,0,(医療),新型コロナ,,

023541,1,0,1,0,0,0,(医療),重症急性呼吸器症候群,,
023541,1,0,1,0,1,0,(医療),SARS,,
023541,1,0,1,0,1,2,(医療),サーズ,,
023541,1,0,1,1,0,0,(医療),Severe Acute Respiratory Syndrome,,

023542,1,0,1,0,0,0,(医療),中東呼吸器症候群,,
023542,1,0,1,0,1,0,(医療),MERS,,
023542,1,0,1,0,1,2,(医療),マーズ,,
023542,1,0,1,1,0,0,(医療),Middle East Respiratory Syndrome,,

023543,1,0,1,0,0,0,(),ソーシャルディスタンス,,
023543,1,0,1,0,0,1,(),social distance,,
023543,1,0,2,0,0,0,(),社会的距離,,

023545,1,0,1,0,0,0,(医療),空気感染,,
023545,1,0,2,0,0,0,(医療),空気伝染,,
023545,1,0,3,0,0,0,(医療),エアロゾル感染,,

023546,1,0,1,0,0,0,(医療),二次感染,,
023546,1,0,2,0,0,0,(医療),続発感染,,

023547,1,0,1,0,0,0,(医療),無症状感染,,
023547,1,0,2,0,0,0,(医療),不顕性感染,,

023548,1,0,1,0,0,0,(),不活化,,
023548,1,0,2,0,0,0,(),不活性化,,

023549,1,0,1,0,0,0,(医療/人),重症者,,
023549,1,0,2,0,0,0,(医療/人),重症患者,,

023550,1,0,1,0,0,0,(医療),集中治療室,,
023550,1,0,1,0,1,0,(医療),ICU,,
023550,1,0,1,1,0,0,(医療),intensive care unit,,

 ...

「Solr Synonyms形式」へ変換する

Sudachiによる形態素解析結果を元に厳密な形で同義語辞書を使うことは(現状では)できないのですが、元の言語資源をSolr Synonyms形式に変換することで、Elasticsearchで活用することができます。

Elasticsearchの同義語フィルターやSolr Synonyms形式については、公式ドキュメントをご覧ください; Synonym token filter | Elasticsearch Reference | Elastic

変換スクリプト

ssyn2es.py (from Pull Request #65 · WorksApplications/elasticsearch-sudachi)

#!/usr/bin/env python

import argparse
import fileinput

def main():
    parser = argparse.ArgumentParser(prog="ssyn2es.py", description="convert Sudachi synonyms to ES")
    parser.add_argument('files', metavar='FILE', nargs='*', help='files to read, if empty, stdin is used')
    parser.add_argument('-p', '--output-predicate', action='store_true', help='output predicates')
    args = parser.parse_args()

    synonyms = {}
    with fileinput.input(files = args.files) as input:
        for line in input:
            line = line.strip()
            if line == "":
                continue
            entry = line.split(",")[0:9]
            if entry[2] == "2" or (not args.output_predicate and entry[1] == "2"):
                continue
            group = synonyms.setdefault(entry[0], [[], []])
            group[1 if entry[2] == "1" else 0].append(entry[8])

    for groupid in sorted(synonyms):
        group = synonyms[groupid]
        if not group[1]:
            if len(group[0]) > 1:
                print(",".join(group[0]))
        else:
            if len(group[0]) > 0 and len(group[1]) > 0:
                print(",".join(group[0]) + "=>" + ",".join(group[0] + group[1]))


if __name__ == "__main__":
    main() 
$ python ssyn2es.py SudachiDict/src/main/text/synonyms.txt > synonym.txt

$ head synonym.txt
曖昧,あいまい,不明確,あやふや,不明瞭,不確か
宛て先,あて先,宛先,送り先,送付先,届け先,発送先,配送先
粗筋,あらすじ,荒筋,概略,大略,概要,大要,要約,要旨,梗概,サマリー,サマリ,summary,レジュメ,レジメ,résumé,シノプシス,synopsis,アウトライン,outline=>粗筋,あらすじ,荒筋,概略,大略,概要,大要,要約,要旨,梗概,サマリー,サマリ,summary,レジュメ,レジメ,résumé,シノプシス,synopsis,アウトライン,outline,resume,概ね,あらまし,アブストラクト,abstract
経緯,いきさつ,事情,経過,事由=>経緯,いきさつ,事情,経過,事由,理由,訳
閉店,店仕舞い=>閉店,店仕舞い,クローズ,close
開店,始業,営業開始=>開店,始業,営業開始,店開き,オープン,open
お喋り,おしゃべり,話,はなし,トーク,talk,会話,カンバセーション,conversation
支払,決済=>支払,決済,支払い,勘定,精算,会計,御愛想,愛想
特別会計,特会
御出掛け,おでかけ,お出かけ,お出掛け,外出

展開抑制の扱い

上記の変換は、Solr Synonyms形式での => という表記を使って、展開方向の抑制も考慮されています。

例えば、以下の同義語エントリがあったとします;

アイスクリーム,ice cream,ice=>アイスクリーム,ice cream,ice,アイス

この時「アイスクリーム」は、 アイスクリーム, ice cream, ice, アイス に展開されます。

他方で 「アイス」は、アイス としか出力されず、同義語展開されません

変換した同義語辞書を使う

変換したSolr Synonyms形式のファイルは、Elasticsearchのデフォルトのフィルター Synonym token filterSynonym graph filter で使うことができます。

句読点記号の扱い

といった記号を取り除く必要があります。取り除かないと "term: € was completely eliminated by analyzer" といったエラーがでます。

取り除くには "discard_punctuation": true と analyzer を設定します。他の方法としては、 "lenient": true と設定し、 synonym filter が例外を無視するというのもあります。

これらの記号は Punctuation として設定されています。詳細は SudachiTokenizer.javaの該当箇所 をご覧ください。

Elasticsearchの設定例

{
    "settings": {
        "analysis": {
            "filter": {
                "sudachi_synonym": {
                    "type": "synonym_graph",
                    "synonyms_path": "sudachi/synonym.txt"
                }
            },
            "tokenizer": {
                "sudachi_tokenizer": {
                    "type": "sudachi_tokenizer"
                }
            },
            "analyzer": {
                "sudachi_synonym_analyzer": {
                    "type": "custom",
                    "tokenizer": "sudachi_tokenizer",
                    "filter": [
                        "sudachi_synonym"
                    ]
                }
            }
        }
    }
}

ここでは、変換した同義語ファイルが $ES_PATH_CONF/sudachi/synonym.txt にあることを想定しています。

また、もし sudachi_split フィルターも利用したい場合は、同義語フィルターの にセットしてください。前にある場合 term: 不明確 analyzed to a token (不) with position increment != 1 (got: 0) というようなエラーが発生します。

解析例

ケース 1.

リクエスト

{
  "analyzer": "sudachi_synonym_analyzer",
  "text": "アイスクリーム"
}

レスポンス

{
    "tokens": [
        {
            "token": "アイスクリーム",
            "start_offset": 0,
            "end_offset": 7,
            "type": "SYNONYM",
            "position": 0
        },
        {
            "token": "ice cream",
            "start_offset": 0,
            "end_offset": 7,
            "type": "SYNONYM",
            "position": 0
        },
        {
            "token": "ice",
            "start_offset": 0,
            "end_offset": 7,
            "type": "SYNONYM",
            "position": 0
        },
        {
            "token": "アイス",
            "start_offset": 0,
            "end_offset": 7,
            "type": "SYNONYM",
            "position": 0
        }
    ]
}

ケース 2.

リクエスト

{
    "analyzer": "sudachi_synonym_analyzer",
    "text": "アイス"
}

レスポンス

{
    "tokens": [
        {
            "token": "アイス",
            "start_offset": 0,
            "end_offset": 3,
            "type": "word",
            "position": 0
        }
    ]
}

関連文献

詳細化した同義関係をもつ同義語辞書の作成


( img from https://twitter.com/sorami/status/1239848113273069569 )

詳細化した語彙関係をもつ同義語辞書を用いた日本語のRelation Embedding学習への取り組み


( img from https://twitter.com/sorami/status/1308581126617796608 )

わからないことや要望があったら

公式Slackに投稿したり、GitHubレポジトリでissueを立てると良いです; Sudachi公式Slack参加リンク

Enjoy Full Text Search!

Discussion