Rust分词库Lindera的使用:高效日语文本分析与处理的Rust自然语言处理工具

Rust分词库Lindera的使用:高效日语文本分析与处理的Rust自然语言处理工具

Lindera

一个Rust中的形态分析库。该项目从kuromoji-rs分叉而来。

Lindera旨在构建一个易于安装并为各种Rust应用程序提供简洁API的库。

分词示例

基本分词

在Cargo.toml中添加以下内容:

[dependencies]
lindera = { version = "0.44.1", features = ["ipadic"] }

此示例涵盖了Lindera的基本用法。

它将:

  • 在正常模式下创建分词器
  • 对输入文本进行分词
  • 输出分词结果
use lindera::dictionary::{load_dictionary_from_kind, DictionaryKind};
use lindera::mode::Mode;
use lindera::segmenter::Segmenter;
use lindera::tokenizer::Tokenizer;
use lindera::LinderaResult;

fn main() -> LinderaResult<()> {
    let mut config_builder = TokenizerConfigBuilder::new();
    config_builder.set_segmenter_dictionary_kind(&DictionaryKind::IPADIC);
    config_builder.set_segmenter_mode(&Mode::Normal);

    let dictionary = load_dictionary_from_kind(DictionaryKind::IPADIC)?;
    let segmenter = Segmenter::new(
        Mode::Normal,
        dictionary,
        None, // 假设未提供用户词典
    );

    // 创建分词器
    let tokenizer = Tokenizer::new(segmenter);

    // 对文本进行分词
    let text = "関西国際空港限定トートバッグ";
    let mut tokens = tokenizer.tokenize(text)?;

    // 打印文本和分词结果
    println!("text:\t{}", text);
    for token in tokens.iter_mut() {
        let details = token.details().join(",");
        println!("token:\t{}\t{}", token.text.as_ref(), details);
    }

    Ok(())
}

以上示例可以按以下方式运行:

% cargo run --features=ipadic --example=tokenize

您可以看到如下结果:

text:   関西国際空港限定トートバッグ
token:  関西国際空港    名詞,固有名詞,組織,*,*,*,関西国際空港,カンサイコクサイクウコウ,カンサイコクサイクーコー
token:  限定    名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
token:  トートバッグ    UNK

使用用户词典进行分词

您可以提供用户词典条目以及默认系统词典。用户词典应为CSV格式,格式如下:

<surface>,<part_of_speech>,<reading>

在Cargo.toml中添加以下内容:

[dependencies]
lindera = { version = "0.44.1", features = ["ipadic"] }

例如:

% cat ./resources/simple_userdic.csv
東京スカイツリー,カスタム名詞,トウキョウスカイツリー
東武スカイツリーライン,カスタム名詞,トウブスカイツリーライン
とうきょうスカイツリー駅,カスタム名詞,トウキョウスカイツリーエキ

使用用户词典时,Tokenizer将按以下方式创建:

use std::path::PathBuf;

use lindera::dictionary::{
    load_dictionary_from_kind, load_user_dictionary_from_csv, DictionaryKind,
};
use lindera::mode::Mode;
use lindera::segmenter::Segmenter;
use lindera::tokenizer::Tokenizer;
use lindera::LinderaResult;

fn main() -> LinderaResult<()> {
    let user_dict_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("../resources")
        .join("ipadic_simple_userdic.csv");

    let dictionary = load_dictionary_from_kind(DictionaryKind::IPADIC)?;
    let user_dictionary =
        load_user_dictionary_from_csv(DictionaryKind::IPADIC, PathBuf::from("./resources/ipadic_simple_userdic.csv").as_path())?;
    let segmenter = Segmenter::new(
        Mode::Normal,
        dictionary,
        Some(user_dictionary), // 假设未提供用户词典
    );

    // 创建分词器
    let tokenizer = Tokenizer::new(segmenter);

    // 对文本进行分词
    let text = "東京スカイツリーの最寄り駅はとうきょうスカイツリー駅です";
    let mut tokens = tokenizer.tokenize(text)?;

    // 打印文本和分词结果
    println!("text:\t{}", text);
    for token in tokens.iter_mut() {
        let details = token.details().join(",");
        println!("token:\t{}\t{}", token.text.as_ref(), details);
    }

    Ok(())
}

以上示例可以通过cargo run --example运行:

% cargo run --features=ipadic --example=tokenize_with_user_dict
text:   東京スカイツリーの最寄り駅はとうきょうスカイツリー駅です
token:  東京スカイツリー        カスタム名詞,*,*,*,*,*,東京スカイツリー,トウキョウスカイツリー,*
token:  の      助詞,連体化,*,*,*,*,の,ノ,ノ
token:  最寄り駅        名詞,一般,*,*,*,*,最寄り駅,モヨリエキ,モヨリエキ
token:  は      助詞,係助詞,*,*,*,*,は,ハ,ワ
token:  とうきょうスカイツリー駅        カスタム名詞,*,*,*,*,*,とうきょうスカイツリー駅,トウキョウスカイツリーエキ,*
token:  です    助動詞,*,*,*,特殊・デス,基本形,です,デス,デス

使用过滤器进行分词

在Cargo.toml中添加以下内容:

[dependencies]
lindera = { version = "0.44.1", features = ["ipadic"] }

此示例涵盖了Lindera分析框架的基本用法。

它将:

  • 应用字符过滤器进行Unicode规范化(NFKC)
  • 使用IPADIC对输入文本进行分词
  • 应用分词过滤器以移除停用标签(词性)和日语片假名词干过滤器
use std::collections::HashSet;

use lindera::character_filter::japanese_iteration_mark::JapaneseIterationMarkCharacterFilter;
use lindera::character_filter::unicode_normalize::{
    UnicodeNormalizeCharacterFilter, UnicodeNormalizeKind,
};
use lindera::character_filter::BoxCharacterFilter;
use lindera::dictionary::{load_dictionary_from_kind, DictionaryKind};
use lindera::mode::Mode;
use lindera::segmenter::Segmenter;
use lindera::token_filter::japanese_compound_word::JapaneseCompoundWordTokenFilter;
use lindera::token_filter::japanese_number::JapaneseNumberTokenFilter;
use lindera::token_filter::japanese_stop_tags::JapaneseStopTagsTokenFilter;
use lindera::token_filter::BoxTokenFilter;
use lindera::tokenizer::Tokenizer;
use lindera::LinderaResult;

fn main() -> LinderaResult<()> {
    let dictionary = load_dictionary_from_kind(DictionaryKind::IPADIC)?;
    let segmenter = Segmenter::new(
        Mode::Normal,
        dictionary,
        None, // 假设未提供用户词典
    );

    let unicode_normalize_char_filter =
        UnicodeNormalizeCharacterFilter::new(UnicodeNormalizeKind::NFKC);

    let japanese_iterration_mark_char_filter =
        JapaneseIterationMarkCharacterFilter::new(true, true);

    let japanese_compound_word_token_filter = JapaneseCompoundWordTokenFilter::new(
        DictionaryKind::IPADIC,
        vec!["名詞,数".to_string(), "名詞,接尾,助数詞".to_string()]
            .into_iter()
            .collect(),
        Some("複合語".to_string()),
    );

    let japanese_number_token_filter =
        JapaneseNumberTokenFilter::new(Some(vec!["名詞,数".to_string()].into_iter().collect()));

    let japanese_stop_tags_token_filter = JapaneseStopTagsTokenFilter::new(
        vec![
            "接続詞".to_string(),
            "助詞".to_string(),
            "助詞,格助詞".to_string(),
            "助詞,格助詞,一般".to_string(),
            "助詞,格助詞,引用".to_string(),
            "助詞,格助詞,連語".to_string(),
            "助詞,係助詞".to_string(),
            "助詞,副助詞".to_string(),
            "助詞,間投助詞".to_string(),
            "助詞,並立助詞".to_string(),
            "助詞,終助詞".to_string(),
            "助詞,副助詞/並立助詞/終助詞".to_string(),
            "助詞,連体化".to_string(),
            "助詞,副詞化".to_string(),
            "助詞,特殊".to_string(),
            "助動詞".to_string(),
            "記号".to_string(),
            "記号,一般".to_string(),
            "記号,読点".to_string(),
            "记號,句點".to_string(),
            "記号,空白".to_string(),
            "記号,括弧閉".to_string(),
            "その他,間投".to_string(),
            "フィラー".to_string(),
            "非言語音".to_string(),
        ]
        .into_iter()
        .collect(),
    );

    // 创建分词器
    let mut tokenizer = Tokenizer::new(segmenter);

    tokenizer
        .append_character_filter(BoxCharacterFilter::from(unicode_normalize_char_filter))
        .append_character_filter(BoxCharacterFilter::from(
            japanese_iterration_mark_char_filter,
        ))
        .append_token_filter(BoxTokenFilter::from(japanese_compound_word_token_filter))
        .append_token_filter(BoxTokenFilter::from(japanese_number_token_filter))
        .append_token_filter(BoxTokenFilter::from(japanese_stop_tags_token_filter));

    // 对文本进行分词
    let text = "Linderaは形態素解析エンジンです。ユーザー辞書も利用可能です。";
    let tokens = tokenizer.tokenize(text)?;

    // 打印文本和分词结果
    println!("text: {}", text);
    for token in tokens {
        println!(
            "token: {:?}, start: {:?}, end: {:?}, details: {:?}",
            token.text, token.byte_start, token.byte_end, token.details
        );
    }

    Ok(())
}

以上示例可以按以下方式运行:

% cargo run --features=ipadic --example=tokenize_with_filters

您可以看到如下结果:

text: Linderaは形態素解析エンジンです。ユーザー辞書も利用可能です。
token: "Lindera", start: 0, end: 21, details: Some(["UNK"])
token: "形態素", start: 24, end: 33, details: Some(["名詞", "一般", "*", "*", "*", "*", "形態素", "ケイタイソ", "ケイタイソ"])
token: "解析", start: 33, end: 39, details: Some(["名詞", "サ変接続", "*", "*", "*", "*", "解析", "カイセキ", "カイセキ"])
token: "エンジン", start: 39, end: 54, details: Some(["名詞", "一般", "*", "*", "*", "*", "エンジン", "エンジン", "エンジン"])
token: "ユーザー", start: 63, end: 75, details: Some(["名詞", "一般", "*", "*", "*", "*", "ユーザー", "ユーザー", "ユーザー"])
token: "辞書", start: 75, end: 81, details: Some(["名詞", "一般", "*", "*", "*", "*", "辞書", "ジショ", "ジショ"])
token: "利用", start: 84, end: 90, details: Some(["名詞", "サ変接続", "*", "*", "*", "*", "利用", "リヨウ", "リヨー"])
token: "可能", start: 90, end: 96, details: Some(["名詞", "形容動詞語幹", "*", "*", "*", "*", "可能", "カノウ", "カノー"])

配置文件

Lindera能够读取YAML格式的配置文件。 在环境变量LINDERA_CONFIG_PATH中指定以下文件的路径。您可以轻松使用它,而无需在Rust代码中编写分词器的行为。

segmenter:
  mode: "normal"
  dictionary:
    kind: "ipadic"
  user_dictionary:
    path: "./resources/ipadic_simple.csv"
    kind: "ipadic"

character_filters:
  - kind: "unicode_normalize"
    args:
      kind: "nfkc"
  - kind: "japanese_iteration_mark"
    args:
      normalize_kanji: true
      normalize_kana: true
  - kind: mapping
    args:
       mapping:
         リンデラ: Lindera

token_filters:
  - kind: "japanese_compound_word"
    args:
      kind: "ipadic"
      tags:
        - "名詞,数"
        - "名詞,接尾,助数詞"
      new_tag: "名詞,数"
  - kind: "japanese_number"
    args:
      tags:
        - "名詞,数"
  - kind: "japanese_stop_tags"
    args:
      tags:
        - "接続詞"
        - "助詞"
        - "助詞,格助詞"
        - "助詞,格助詞,一般"
        - "助詞,格助詞,引用"
        - "助詞,格助詞,連語"
        - "助詞,係助詞"
        - "助詞,副助詞"
        - "助詞,間投助詞"
        - "助詞,並立助詞"
        - "助詞,終助詞"
        - "助詞,副助詞/並立助詞/終助詞"
        - "助詞,連体化"
        - "助詞,副詞化"
        - "助詞,特殊"
        - "助動詞"
        - "記号"
        - "記号,一般"
        - "記号,読点"
        - "記号,句点"
        - "記号,空白"
        - "記号,括弧閉"
        - "その他,間投"
        - "フィラー"
        - "非言語音"
  - kind: "japanese_katakana_stem"
    args:
      min: 3
  - kind: "remove_diacritical_mark"
    args:
      japanese: false
% export LINDERA_CONFIG_PATH=./resources/lindera.yml
use std::path::PathBuf;

use lindera::tokenizer::TokenizerBuilder;
use lindera::LinderaResult;

fn main() -> LinderaResult<()> {
    // 创建一个新的`TokenizerConfigBuilder`实例
    // 如果设置了`LINDERA_CONFIG_PATH`环境变量,它将尝试从指定路径加载初始设置
    let builder = TokenizerBuilder::from_file(PathBuf::from("./resources/lindera.yml").as_path())?;

    let tokenizer = builder.build()?;

    // 对文本进行分词
    let text = "関西国際空港限定トートバッグ";
    let mut tokens = tokenizer.tokenize(text)?;

    // 打印文本和分词结果
    println!("text:\t{}", text);
    for token in tokens.iter_mut() {
        let details = token.details().join(",");
        println!("token:\t{}\t{}", token.text.as_ref(), details);
    }

    Ok(())
}

API参考

API参考可用。

完整示例代码

以下是一个完整的Lindera使用示例,展示了基本分词功能:

use lindera::dictionary::{load_dictionary_from_kind, DictionaryKind};
use lindera::mode::Mode;
use lindera::segmenter::Segmenter;
use lindera::tokenizer::Tokenizer;
use lindera::LinderaResult;

fn main() -> LinderaResult<()> {
    // 加载IPADIC词典
    let dictionary = load_dictionary_from_kind(DictionaryKind::IPADIC)?;
    
    // 创建分词器
    let segmenter = Segmenter::new(
        Mode::Normal,
        dictionary,
        None, // 不使用用户词典
    );
    
    let tokenizer = Tokenizer::new(segmenter);

    // 日语文本示例
    let text = "関西国際空港限定トートバッグ";
    
    // 进行分词
    let mut tokens = tokenizer.tokenize(text)?;

    // 输出结果
    println!("输入文本: {}", text);
    println!("分词结果:");
    for token in tokens.iter_mut() {
        let details = token.details().join(",");
        println!("- {}: {}", token.text.as_ref(), details);
    }

    Ok(())
}

要运行此代码,需要在Cargo.toml中添加依赖:

[dependencies]
lindera = { version = "0.44.1", features = ["ipadic"] }

然后使用以下命令运行:

cargo run

这将输出类似以下的结果:

输入文本: 関西国際空港限定トートバッグ
分词结果:
- 関西国際空港

1 回复

Rust分词库Lindera的使用:高效日语文本分析与处理的Rust自然语言处理工具

Lindera是一个专为日语文本设计的高效分词库,基于Rust语言实现。它提供快速的文本分割和词性标注功能,适用于自然语言处理任务,如搜索引擎、文本分析和机器学习预处理等。

主要特性

  • 高性能:利用Rust的零成本抽象和内存安全特性,实现快速分词
  • 支持多种词典:包括IPADIC和UniDic等标准日语词典
  • 词性标注:提供详细的词性信息
  • 自定义词典:支持用户添加自定义词汇

安装方法

在Cargo.toml中添加依赖:

[dependencies]
lindera = "0.12.0"

基本使用方法

use lindera::tokenizer::Tokenizer;
use lindera::mode::Mode;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 初始化分词器(使用IPADIC词典)
    let mut tokenizer = Tokenizer::new(Mode::Normal, "")?;
    
    // 待分词的日语文本
    let text = "日本語の自然言語処理は面白いです。";
    
    // 执行分词
    let tokens = tokenizer.tokenize(text)?;
    
    // 输出结果
    for token in tokens {
        println!("{:?}", token);
    }
    
    Ok(())
}

高级功能示例

使用自定义词典

use lindera::tokenizer::{Tokenizer, TokenizerConfig};
use lindera::mode::Mode;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = TokenizerConfig {
        dict_path: Some("path/to/your/custom/dict".to_string()),
        ..Default::default()
    };
    
    let mut tokenizer = Tokenizer::with_config(Mode::Normal, config)?;
    
    let text = "これはカスタム辞書のテストです";
    let tokens = tokenizer.tokenize(text)?;
    
    for token in tokens {
        println!("Text: {}, POS: {:?}", token.text, token.detail);
    }
    
    Ok(())
}

批量处理文本

use lindera::tokenizer::Tokenizer;
use lindera::mode::Mode;

fn batch_process(texts: Vec<&str>) -> Result<(), Box<dyn std::error::Error>> {
    let mut tokenizer = Tokenizer::new(Mode::Normal, "")?;
    
    for text in texts {
        let tokens = tokenizer.tokenize(text)?;
        println!("Original: {}", text);
        println!("Tokens: {}", tokens.iter().map(|t| t.text).collect::<Vec<_>>().join(" | "));
        println!("---");
    }
    
    Ok(())
}

性能优化建议

  1. 重用Tokenizer实例以避免重复初始化开销
  2. 对于大量文本处理,考虑使用并行处理
  3. 根据需求选择合适的词典(IPADIC用于一般用途,UniDic用于学术研究)

错误处理

use lindera::LinderaError;

fn safe_tokenize(text: &str) -> Result<(), LinderaError> {
    let mut tokenizer = Tokenizer::new(Mode::Normal, "")?;
    let tokens = tokenizer.tokenize(text)?;
    
    // 处理分词结果
    Ok(())
}

Lindera为Rust开发者提供了强大的日语文本处理能力,结合Rust的性能优势,使其成为处理日语NLP任务的理想选择。

完整示例demo

use lindera::tokenizer::{Tokenizer, TokenizerConfig};
use lindera::mode::Mode;
use lindera::LinderaError;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 示例1:基本分词功能
    println!("=== 基本分词示例 ===");
    basic_tokenization()?;
    
    // 示例2:批量处理文本
    println!("\n=== 批量处理示例 ===");
    let texts = vec![
        "今日は良い天気です",
        "自然言語処理が好きです",
        "Rustプログラミングは楽しい"
    ];
    batch_processing(texts)?;
    
    // 示例3:错误处理
    println!("\n=== 错误处理示例 ===");
    match safe_tokenization("テストテキスト") {
        Ok(_) => println!("分词成功"),
        Err(e) => println!("分词错误: {}", e),
    }
    
    Ok(())
}

/// 基本分词功能示例
fn basic_tokenization() -> Result<(), Box<dyn std::error::Error>> {
    // 初始化分词器,使用默认的IPADIC词典
    let mut tokenizer = Tokenizer::new(Mode::Normal, "")?;
    
    // 日语文本示例
    let text = "日本語の自然言語処理は面白いです。";
    
    // 执行分词
    let tokens = tokenizer.tokenize(text)?;
    
    // 输出分词结果
    println!("原始文本: {}", text);
    println!("分词结果:");
    for token in tokens {
        println!("  文本: {}, 词性: {:?}", token.text, token.detail);
    }
    
    Ok(())
}

/// 批量处理文本示例
fn batch_processing(texts: Vec<&str>) -> Result<(), Box<dyn std::error::Error>> {
    // 重用Tokenizer实例以提高性能
    let mut tokenizer = Tokenizer::new(Mode::Normal, "")?;
    
    for text in texts {
        let tokens = tokenizer.tokenize(text)?;
        println!("原始: {}", text);
        println!("分词: {}", tokens.iter()
            .map(|t| format!("{}[{:?}]", t.text, t.detail))
            .collect::<Vec<_>>()
            .join(" | "));
        println!("---");
    }
    
    Ok(())
}

/// 安全的错误处理示例
fn safe_tokenization(text: &str) -> Result<(), LinderaError> {
    let mut tokenizer = Tokenizer::new(Mode::Normal, "")?;
    let tokens = tokenizer.tokenize(text)?;
    
    // 处理分词结果
    println!("安全处理文本: {}", text);
    for token in tokens {
        println!("  分词: {}, 详细信息: {:?}", token.text, token.detail);
    }
    
    Ok(())
}

这个完整示例演示了Lindera库的主要功能:

  1. 基本分词功能:使用默认词典对日语文本进行分词和词性标注
  2. 批量处理:重用Tokenizer实例高效处理多个文本
  3. 错误处理:使用LinderaError进行安全的错误处理

要运行此示例,请确保在Cargo.toml中添加了正确的Lindera依赖,并根据需要配置适当的词典路径。

回到顶部