フィルタ

文字フィルタ(Character Filter)とトークンフィルタ(Token Filter)は、lindera-analysisのTokenizerパイプラインにおける前処理・後処理の2つの段階です。

  • 文字フィルタはセグメンテーションの前に入力テキストを変換します。バイトオフセットは自動的に補正されるため、変換後のテキストに対して生成されたトークンでも、元のフィルタ前のテキストにおける位置が正しく報告されます。
  • トークンフィルタはSegmenterが生成したトークンのリストをセグメンテーションの後に変換します。

どちらのフィルタも設定方法は共通で、kind文字列(CLIの--character-filter / --token-filterフラグでもkind:{"json": "args"}という形式で使用されます)と、フィルタ固有のパラメータを持つJSONのargsオブジェクトで構成されます。

文字フィルタ

文字フィルタはYAML設定ファイルのcharacter_filtersキーで設定します。各エントリは順番に適用され、あるフィルタの出力が次のフィルタの入力になります。

unicode_normalize

4種類の標準的なUnicode正規化形式のいずれかを使って入力テキストを正規化します。

パラメータ:

パラメータ型必須説明
kindstringはいnfc、nfd、nfkc、nfkdのいずれか

例:

{
  "kind": "unicode_normalize",
  "args": {
    "kind": "nfkc"
  }
}

japanese_iteration_mark

日本語の踊り字(繰り返し記号)である々、ゝ、ゞ、ヽ、ヾを、それぞれが繰り返す文字に置き換えて正規化します。ひらがな・カタカナの繰り返し記号については、必要に応じて濁点の付与・除去も行います。

パラメータ:

パラメータ型必須デフォルト説明
normalize_kanjiboolいいえfalse漢字の踊り字々を正規化する
normalize_kanaboolいいえfalseひらがな・カタカナの踊り字ゝ、ゞ、ヽ、ヾを正規化する

例:

{
  "kind": "japanese_iteration_mark",
  "args": {
    "normalize_kanji": true,
    "normalize_kana": true
  }
}

mapping(文字フィルタ)

mappingのキーに一致する部分を対応する値に置き換えます。入力テキスト全体に対して、Aho-Corasickオートマトンによる最長一致検索を行います。

パラメータ:

パラメータ型必須説明
mappingobject(string to string)はい置換対象の部分文字列と、その置換先の対応表

例:

{
  "kind": "mapping",
  "args": {
    "mapping": {
      "リンデラ": "Lindera"
    }
  }
}

regex

正規表現にマッチした箇所をすべて、リテラルな置換文字列で置き換えます。キャプチャグループの内容は置換文字列に展開されません。

パラメータ:

パラメータ型必須説明
patternstringはい正規表現(regexクレートの構文)
replacementstringはいpatternにマッチした箇所すべてを置き換えるリテラル文字列

例:

{
  "kind": "regex",
  "args": {
    "pattern": "\\s{2,}",
    "replacement": " "
  }
}

トークンフィルタ

トークンフィルタはYAML設定ファイルのtoken_filtersキーで設定します。各フィルタは、Segmenterが生成したトークンリストに対して順番に適用されます。

japanese_base_form

トークンの表層形を、辞書のbase_form(またはorthographic_base_form)フィールドに登録された原形(辞書形)に置き換えます。動詞・形容詞のレンマ化(見出し語化)として機能します。未知語処理によって生成されたトークン(token.word_id.is_unknown())は変更されません。

このフィルタに設定パラメータはありません。

例:

{
  "kind": "japanese_base_form"
}

japanese_compound_word

品詞タグがtagsのいずれかに一致する連続したトークンを、1つの複合語トークンに結合します。

結合後のトークンの詳細には、辞書スキーマから解決した品詞の位置に new_tag が入ります。それより前の品詞以外のフィールド(SudachiDict の display_surface)と残りのフィールドはすべて * になります。

パラメータ:

パラメータ型必須説明
tagsarray<string>はい結合対象となるトークンを示す品詞タグ(カンマ区切りで最大4階層)
new_tagstringいいえ結合後のトークンに付与する品詞タグ。省略した場合は複合語が付与される

例:

{
  "kind": "japanese_compound_word",
  "args": {
    "tags": [
      "名詞,数",
      "名詞,接尾,助数詞"
    ],
    "new_tag": "名詞,数"
  }
}

japanese_kana

トークンテキストをひらがなとカタカナの間で相互変換します。

パラメータ:

パラメータ型必須説明
kindstringはい"hiragana"はカタカナをひらがなに、"katakana"はひらがなをカタカナに変換する

例:

{
  "kind": "japanese_kana",
  "args": {
    "kind": "hiragana"
  }
}

japanese_katakana_stem

カタカナのトークンの末尾にある長音記号(ー、U+30FC)を除去します。ただし、トークンの文字数がminより大きい場合のみ除去されます。

パラメータ:

パラメータ型必須説明
min正の整数はい末尾の長音記号をステミングする対象となる、カタカナトークンの最小文字数

例:

{
  "kind": "japanese_katakana_stem",
  "args": {
    "min": 3
  }
}

japanese_keep_tags

品詞タグがtagsのいずれかに一致するトークンのみを保持し、それ以外を除去します。

タグは 4 階層のカンマ区切りに正規化され(不足分は * で補完)、各トークンの品詞詳細 4 つ(辞書スキーマで part_of_speech という名前のフィールドから始まる 4 つ。スキーマにそのフィールドが無い場合は先頭の詳細から)と完全一致で比較されます。IPADIC の助詞トークンは必ず 助詞,係助詞 のようにサブカテゴリを持つため、助詞 単独では一致しません。一方、助動詞はサブカテゴリを持たないため(助動詞,*,*,*)、助動詞 単独で一致します。この動作は japanese_stop_tags、japanese_compound_word、および japanese_number の tags も同様です。位置はスキーマ経由で解決されるため、SudachiDict の display_surface のように品詞の前に列があるスキーマでも同じ規則で一致します。

パラメータ:

パラメータ型必須説明
tagsarray<string>はい保持する品詞タグ(カンマ区切りで最大4階層)

例:

{
  "kind": "japanese_keep_tags",
  "args": {
    "tags": [
      "名詞,一般"
    ]
  }
}

japanese_number

トークンの表層テキストに含まれる日本語の数値表現(漢数字、大字、全角数字)をアラビア数字に変換します。

トークンは、その全ての文字が数詞である場合にのみ変換されます(Lucene の JapaneseNumberFilter と同じ規則)。そのため、数詞で始まるだけの通常の語は変換されません。一部は1部にならず一部のままです。

japanese_compound_wordの後段で使う場合は、名詞,数のトークンだけを結合してください。助数詞(名詞,接尾,助数詞)を数値トークンに結合すると、そのトークンは数詞ではなくなるため、一万円は変換されずそのまま残ります。

パラメータ:

パラメータ型必須説明
tagsarray<string>またはnullいいえ変換対象を限定する品詞タグ(カンマ区切りで最大4階層)。japanese_keep_tags と同じ規則で一致判定される。省略またはnullの場合はすべてのトークンが変換対象になる

例:

{
  "kind": "japanese_number",
  "args": {
    "tags": [
      "名詞,数"
    ]
  }
}

japanese_reading_form

トークンの表層テキストを、辞書のreadingフィールドに登録された読み(カタカナ)に置き換えます。未知語処理によって生成されたトークン(token.word_id.is_unknown())は変更されません。

このフィルタに設定パラメータはありません。

例:

{
  "kind": "japanese_reading_form"
}

japanese_stop_tags

品詞タグがtagsのいずれかに一致するトークンを除去します。

パラメータ:

パラメータ型必須説明
tagsarray<string>はい除去する品詞タグ(カンマ区切りで最大4階層)

例:

{
  "kind": "japanese_stop_tags",
  "args": {
    "tags": [
      "助詞,格助詞,一般",
      "助詞,係助詞",
      "助詞,連体化",
      "助動詞"
    ]
  }
}

keep_words

表層テキストがwordsのいずれかに完全一致するトークンのみを保持します。

パラメータ:

パラメータ型必須説明
wordsarray<string>はい保持する表層形の一覧

例:

{
  "kind": "keep_words",
  "args": {
    "words": [
      "すもも",
      "もも"
    ]
  }
}

korean_compound_word

最初の品詞タグがtagsのいずれかに一致する連続した韓国語トークンを、1つの複合語トークンに結合します。結合後のトークンは先頭トークンのbyte_startとposition、末尾トークンのbyte_endを持ち、position_lengthは結合したトークン数になります。隣接トークンが一致しない単独のトークンはそのままです。

ko-dicは漢数詞を形態素単位に分割するため、이/NR 천/NR 이/NR 십/NR 육/NRをkorean_numberで変換する前に1トークンへ戻すのがこのフィルタの役割です。

入力ko-dicのトークンkorean_compound_word適用後(tags: ["SN", "NR"]、new_tag: "NR")korean_number適用後
이천이십육년이/NR 천/NR 이/NR 십/NR 육/NR 년/NNBC이천이십육/NR 년/NNBC2026 년
10만10/SN 만/NR10만/NR100000
2천262/SN 천/NR 26/SN2천26/NR2026

パラメータ:

パラメータ型必須説明
tagsarray<string>はい結合対象となるトークンを示す品詞タグ(NRやSNなどko-dicの単一タグ)
new_tagstringいいえ結合後のトークンに付与する品詞タグ。他のdetailは*になる。省略した場合は복합어が付与されるが、これはko-dicのタグではないため、後続のkorean_numberやkorean_keep_tagsに結合後のトークンを渡す場合はNRなどのタグを指定する

例:

{
  "kind": "korean_compound_word",
  "args": {
    "tags": [
      "SN",
      "NR"
    ],
    "new_tag": "NR"
  }
}

既定の設定では、ko-dicは만 원や십만 원の만を数詞NRと判定するため、십만 원は십만/NR 원/NNBCになります。空白の読み飛ばしを無効にする(skip_whitespace: false)と、만の後ろの語は空白のノードに接続するため、ko-dicはそこでの만を助詞JXと判定し(만/JX 원/NNG)、["SN", "NR"]では結合されません。

korean_decompound

ko-dicの複合語・活用形・事前解析済み(Preanalysis)トークンを、expressionフィールドが示す形態素に分割します。Lucene noriのDecompoundModeと同じ動作で、隣接トークンを結合するkorean_compound_wordの逆操作にあたります。LinderaのMode::Decomposeは日本語の漢字向けに調整された長さペナルティであり、これらのフィールドを読まないため、このフィルタなしでは무궁화はどのモードでも分割されません。

入力ko-dicのトークンmode: "discard"mode: "mixed"
무궁화꽃이무궁화/NNG 꽃/NNG 이/JKS무궁 화 꽃 이무궁화 무궁 화 꽃 이
가곡역가곡역/NNP가곡 역가곡역 가곡 역

断片のオフセットはnoriに合わせています。自身のバイト範囲を持つのはCompoundの断片だけで、各断片は自身が表すテキストの範囲を持ちます。InflectとPreanalysisの断片はトークン全体の範囲を共有します。活用形の断片は元の表層形の部分文字列とは限らないためです(갔は가 + 았に分解されます)。

Compoundトークンを分割するのは、断片をつなげると表層形とちょうど一致する場合だけで、それ以外は範囲を共有します。これには、断片が表層形そのものの文字列ではない同梱ko-dicの12項目(안지랑역 = 안지 + 역、고춧값 = 고추 + 값)が含まれます。これらではnoriのオフセットが一部の断片を別の文字列に向けてしまいます。프린터3D = 프린터 + 쓰리디のようなユーザー辞書の項目や、先行フィルタが表層形を書き換えた場合も同様です。同梱辞書のそれ以外の項目ではオフセットはnoriと一致し、どのオフセットでテキストを切り出しても安全です。

discardでは断片が複合語自身の位置から連番で並び、mixedでは複合語がその位置にposition_lengthを断片数として残り、断片が同じ位置から続きます。展開後のトークンは増えた位置の分だけ後ろにずれ、それ以外はずれないため、korean_stop_tagsなど先行フィルタが残した位置の隙間は保たれます。

展開するのは位置を1つだけ占めるトークンに限るため、mixedが残した複合語が再び分割されることはありません。それでもフィルタはチェーンに一度だけ入れてください。mixedが残した断片1つの項目は位置を1つしか占めないためです。korean_compound_wordと組み合わせる場合は結合を先に置いてください。結合はバイト範囲の重なるトークンを繋がないため逆順でも壊れませんが、結合を先にするとセグメンターが出したトークンそのものを対象にできます。

断片は自身の品詞タグと意味分類を持ち、複合語自体を説明するフィールド(type、expression、first_part_of_speech、last_part_of_speech、reading、presence_absence)は空になります。読みを空にするのはnoriと同じ挙動で、noriのDecompoundToken.getReading()はnullを返します。korean_reading_formは*をスキップするため、断片は自身の表層形を保ちます。

スキーマにtype/expressionフィールドがない辞書に適用した場合、フィルタは設定されたフィルタごとに一度だけ警告を出し、すべてのトークンをそのまま通過させます。

パラメータ:

パラメータ型必須説明
modestringいいえ"discard"(デフォルト、noriと同じ)は複合語を断片で置き換え、"mixed"は複合語を残したうえで断片も出力します
typesarray<string>いいえ分解対象のtype値(大文字と小文字を区別)。デフォルトは["Compound", "Inflect", "Preanalysis"]。空のリストやそれ以外の値はエラーになります

例:

{
  "kind": "korean_decompound",
  "args": {
    "mode": "discard",
    "types": [
      "Compound",
      "Inflect",
      "Preanalysis"
    ]
  }
}

korean_keep_tags

最初の品詞タグがtagsのいずれかに一致する韓国語トークンのみを保持します。

パラメータ:

パラメータ型必須説明
tagsarray<string>はい保持する品詞タグ

例:

{
  "kind": "korean_keep_tags",
  "args": {
    "tags": [
      "NNG"
    ]
  }
}

korean_number

トークンの表層テキストに含まれる漢数詞由来の韓国語の数値表現(ハングル数字、漢数字、全角数字)をアラビア数字に変換します。固有語数詞(하나、둘、열、스물など)は位取りの構造を持たないため変換しません。

変換はトークンごとに独立して行われます。ko-dicは漢数詞を形態素単位に分割するため、1つの数を構成するトークン群が1つのアラビア数字にまとまるわけではありません。

入力ko-dicのトークンkorean_number適用後
이천이십육년이/NR 천/NR 이/NR 십/NR 육/NR 년/NNBC2 1000 2 10 6 년
10만10/SN 만/NR10 10000

IPADICに対するjapanese_compound_wordのように、変換前にトークンを結合するにはkorean_compound_wordをこのフィルタの前に置きます。結合後のトークンを変換対象にするため、new_tagにはこのフィルタのtagsに含まれるタグを指定します。

[
  {"kind": "korean_compound_word", "args": {"tags": ["SN", "NR"], "new_tag": "NR"}},
  {"kind": "korean_number", "args": {"tags": ["SN", "NR"]}}
]

このチェーンで이천이십육년は2026 년、10만は100000、2천26は2026になります。漢字表記の二千二十六/SHのように数値全体が1トークンの場合は、このフィルタ単体でそのまま変換されます。

パラメータ:

パラメータ型必須説明
tagsarray<string>またはnullいいえ変換対象を限定する品詞タグ。省略した場合は["SN", "NR"]、nullの場合はすべてのトークンが変換対象になる

トークンは、その全ての文字が数詞である場合にのみ変換されます(Lucene の KoreanNumberFilter と同じ規則)。これにより固有語数詞が保護されます。일곱(7)は漢数詞の일で始まるため、1文字ずつ変換すると1곱になってしまいます。

ハングルの数詞は他の形態素とも同形で(이は主格助詞、만は補助詞)、これらは1文字なので全文字一致の規則では区別できません。そのため既定では数詞の品詞タグに限定しています。

漢数字も変換する場合はSHを追加してください。対応は2つの意味で部分的です。ko-dicは漢字語全体をNNGと判定するため參加や萬歲は変換されず、一方で単語に結び付かない単独の漢字はSHとなるため、十字架は10 字 架、百貨店は100 貨 店になります。

例:

{
  "kind": "korean_number",
  "args": {
    "tags": [
      "SN",
      "NR"
    ]
  }
}

korean_reading_form

トークンの表層テキストを、辞書のreadingフィールドに登録された読みに置き換えます。未知語処理によって生成されたトークン(token.word_id.is_unknown())は変更されません。

このフィルタに設定パラメータはありません。

例:

{
  "kind": "korean_reading_form"
}

korean_stop_tags

最初の品詞タグがtagsのいずれかに一致する韓国語トークンを除去します。

パラメータ:

パラメータ型必須説明
tagsarray<string>はい除去する品詞タグ

例:

{
  "kind": "korean_stop_tags",
  "args": {
    "tags": [
      "EP",
      "EF",
      "JKG"
    ]
  }
}

length

表層テキストの文字数が[min, max]の範囲に収まるトークンのみを保持します。

パラメータ:

パラメータ型必須説明
min符号なし整数いいえ最小文字数(この値を含む)
max符号なし整数いいえ最大文字数(この値を含む)

例:

{
  "kind": "length",
  "args": {
    "min": 2,
    "max": 3
  }
}

lowercase

トークンの表層テキストを小文字に変換します。

このフィルタに設定パラメータはありません。

例:

{
  "kind": "lowercase"
}

mapping(トークンフィルタ)

mappingのキーに一致する部分を、各トークンの表層テキスト内で対応する値に置き換えます。Aho-Corasickオートマトンによる最長一致検索を使用します。文字フィルタのmappingのトークン版に相当します。

パラメータ:

パラメータ型必須説明
mappingobject(string to string)はい置換対象の部分文字列と、その置換先の対応表

例:

{
  "kind": "mapping",
  "args": {
    "mapping": {
      "籠": "篭"
    }
  }
}

remove_diacritical_mark

トークンの表層テキストからダイアクリティカルマーク(発音区別符号)を除去し、その後テキストの元のUnicode正規化形式を再適用します。

パラメータ:

パラメータ型必須デフォルト説明
japaneseboolいいえfalse日本語の濁点・半濁点の結合文字(分解済みの濁音・半濁音仮名に含まれるものなど)も除去する

例:

{
  "kind": "remove_diacritical_mark",
  "args": {
    "japanese": false
  }
}

stop_words

表層テキストがwordsのいずれかに完全一致するトークンを除去します。

パラメータ:

パラメータ型必須説明
wordsarray<string>はい除去する表層形の一覧

例:

{
  "kind": "stop_words",
  "args": {
    "words": [
      "も",
      "の"
    ]
  }
}

uppercase

トークンの表層テキストを大文字に変換します。

このフィルタに設定パラメータはありません。

例:

{
  "kind": "uppercase"
}

YAML設定

文字フィルタとトークンフィルタは、Segmenterと一緒に1つのYAMLファイルで設定します。ファイル全体の形式は設定を参照してください。関連する部分だけを抜粋すると次のようになります。

character_filters:
  - kind: "unicode_normalize"
    args:
      kind: "nfkc"
  - kind: "japanese_iteration_mark"
    args:
      normalize_kanji: true
      normalize_kana: true

token_filters:
  - kind: "japanese_stop_tags"
    args:
      tags:
        - "助詞,格助詞,一般"
        - "助詞,係助詞"
        - "助詞,連体化"
        - "助動詞"
  - kind: "japanese_katakana_stem"
    args:
      min: 3
  - kind: "lowercase"
  - kind: "length"
    args:
      min: 2

Rust API

文字フィルタとトークンフィルタは、プログラムから作成・適用することもできます。

use lindera::dictionary::load_dictionary;
use lindera::mode::Mode;
use lindera::segmenter::Segmenter;
use lindera::analysis::character_filter::BoxCharacterFilter;
use lindera::analysis::character_filter::unicode_normalize::{
    UnicodeNormalizeCharacterFilter, UnicodeNormalizeKind,
};
use lindera::analysis::token_filter::BoxTokenFilter;
use lindera::analysis::token_filter::japanese_stop_tags::JapaneseStopTagsTokenFilter;
use lindera::analysis::token_filter::japanese_katakana_stem::JapaneseKatakanaStemTokenFilter;
use lindera::analysis::tokenizer::Tokenizer;
use lindera::LinderaResult;

fn main() -> LinderaResult<()> {
    let dictionary = load_dictionary("embedded://ipadic")?;
    let segmenter = Segmenter::new(Mode::Normal, dictionary, None);

    let mut tokenizer = Tokenizer::new(segmenter);

    // 文字フィルタを追加
    let normalize_filter = UnicodeNormalizeCharacterFilter::new(UnicodeNormalizeKind::NFKC);
    tokenizer.append_character_filter(BoxCharacterFilter::from(normalize_filter));

    // トークンフィルタを追加
    let stop_tags_filter = JapaneseStopTagsTokenFilter::new(
        vec![
            "助詞,格助詞,一般".to_string(),
            "助詞,係助詞".to_string(),
            "助詞,連体化".to_string(),
            "助動詞".to_string(),
        ]
        .into_iter()
        .collect(),
    );
    tokenizer.append_token_filter(BoxTokenFilter::from(stop_tags_filter));

    let katakana_stem_filter =
        JapaneseKatakanaStemTokenFilter::new(std::num::NonZeroUsize::new(3).unwrap());
    tokenizer.append_token_filter(BoxTokenFilter::from(katakana_stem_filter));

    // フィルタを適用してトークナイズ
    let tokens = tokenizer.tokenize("Linderaは形態素解析エンジンです。")?;

    for token in tokens {
        println!(
            "token: {:?}, details: {:?}",
            token.surface, token.details
        );
    }

    Ok(())
}

append_character_filterとappend_token_filterメソッドは、フィルタを追加した順番で登録します。文字フィルタはセグメンテーション前のテキストに対して順次適用され、トークンフィルタはセグメンテーション後のトークンリストに対して順次適用されます。