NLP100本ノック第4節おわりましたのでまとめます

第4章 形態素解析

形態素解析やからむずいやろとおもってた

準備

この章では、形態素解析済ファイルを作成する必要がありますが、毎回対象ファイルをダウンロード、解析して解いています。 ここではいつもどおり HashMap を利用するため ANALYZED_MECAB_KEYS を作成してこれをキーにします。 またよく利用する品詞を enum で定義しておき、変換関数 inspect を作成します。

const ANALYZED_MECAB_KEYS: [&str; 9] = ["pos", "pos1", "pos2", "pos3", "a", "b", "base", "read", "speech"];
enum PartOfSpeech {
    VERB,
    NOUN,
    PARTICLE,
}

use PartOfSpeech::*;

fn inspect(val: PartOfSpeech) -> String {
    match val {
        VERB => "動詞",
        NOUN => "名詞",
        PARTICLE => "助詞",
    }.to_string()
}

30. 形態素解析結果の読み込み

Map を使えと指定があるので素直に利用します

fn feature(node: &Node) -> HashMap<String, String> {
    let mut h: HashMap<String, String> = HashMap::new();
    let surface: String = (&(node.surface)[..node.length as usize]).to_string();
    h.insert("surface".to_string(), surface);
    let values: Vec<String> = node.feature.split(",").map(|m| m.to_string()).collect();
    for (a, b) in ANALYZED_MECAB_KEYS.iter().zip(values.iter()) {
        h.insert(a.to_string(), b.to_string());
    }
    h
}

fn main() {
    let url = "http://www.cl.ecei.tohoku.ac.jp/nlp100/data/neko.txt".to_string();
    let neco: Vec<String> = NLP100::get(url).split("\n").filter(|f| f.ne(&"")).map(|m| m.to_string()).collect();
    let mut morph = Vec::new();

    for line in neco {
        let mut tagger: Tagger = mecab::Tagger::new("");
        let nodes: Node = tagger.parse_to_node(line);

        let mut mecabu: Vec<HashMap<String, String>> = Vec::new();
        for node in nodes.iter_next() {
            match node.stat as i32 {
                mecab::MECAB_BOS_NODE => (),
                mecab::MECAB_EOS_NODE => (),
                _ => {
                    mecabu.push(feature(&node));
                }
            }
        }
        morph.push(mecabu);
    }
    for morph in morphs {
        for mecab in morph {
            println!("{}", format!("surface: {}, base: {}, pos: {}, pos1: {}", mecab["surface"], mecab["base"], mecab["pos"], mecab["pos1"]));
        }
        println!("");
    }
}

31. 動詞

動詞だけ抽出するので以下のコードで抽出し、表層形("surface")を取得する

fn verb(nodes: Vec<HashMap<String, String>>) -> Vec<HashMap<String, String>> {
    nodes.iter().filter(|m| m["pos"] == inspect(VERB)).map(|hm| hm.clone()).collect()
}

32. 動詞の原形

動詞だけ抽出するので上記のコードで抽出し、原形("base")を取得する

33. サ変名詞

サ変接続を抽出

fn sa_noun(nodes: Vec<HashMap<String, String>>) -> Vec<HashMap<String, String>>{
    noun(nodes).iter().filter(|node| node["pos1"] == "サ変接続").map(|hm| hm.clone()).collect()
}

34. 「AのB」

「の」を挾んでいる名詞を抽出

fn between_noun(node: &Node) -> Option<String> {
    let mecab = feature(node);
    if mecab["surface"] == "の" && mecab["pos"] == inspect(PARTICLE) && mecab["pos1"] == "連体化" {
        let prev = feature(&node.prev().unwrap());
        let next = feature(&node.next().unwrap());

        Some(format!("{}{}{}", &prev["surface"], &mecab["surface"], &next["surface"]))
    } else {
        None
    }
}

35. 名詞の連接

連続した名詞を抽出するが、mecab でうまく関数化できなかったので割愛(あとでうかんだら追記)します。

36. 単語の出現頻度

単純に頻度をまとめ、 sort するとよい

fn word_histgram(nodes: Vec<HashMap<String, String>>) -> HashMap<String, u64> {
    let mut results: HashMap<String, u64> = HashMap::new();
    for node in nodes {
        let base = &node["base"];
        *results.entry(base.to_string()).or_insert(0) += 1;
    }
    results
}

37. 頻度上位10語

上記の結果より .take(10) するだけです。

38. ヒストグラム

37 と違いがわからずおわり。

39. Zipfの法則

単純に両対数グラフ化でおわり。

おわり

おわり