読者です 読者をやめる 読者になる 読者になる

Paradigm Shift Design

ISHITOYA Kentaro's blog.

Mecabで基本形の読みを取得する

C# 研究

いや、しかし、id:gi-chiさんにもピクルスさんにも同じことを言われてしまった。
えぇ、できれば「大学の研究」じゃなくて「研究」か「ビジネス」といわれるようなレベルにしたいんですが、今のところ全然ですね。がんばります。いやコクヨががんばってくれないかなw
相変わらず風呂敷広げっぱなしが趣味のようです。


さておき、掲題のMecabで基本形の読みを取得するという話。

盲点でした。現状では、基本形の読みは取得できません。
辞書の作成時に基本形の読みは作らず、すべて活用を展開した形での読みしか
作らないからです。

幸いにも mecab の辞書はCSVであればなんでもつっこめるので、自分で
ipadic から基本形の読みを取り出して、mecabCSV の最後のカラムに
突っ込めば情報を取り出せるようになります。

Re: 基本形の読みについて (mecab-users 184) - MeCab - SourceForge.JPと作者のくどうサンがおっしゃっていますので、Chasenと違って基本形の読みは取得できません。


というわけで辞書を変更せずに無理やり取得する方法です。
いや、要するに

  1. 形態素解析する
  2. 形態素の基本形をつなげた文字列を作る
  3. 形態素解析する
  4. マージする

という超アドホックな方法ですが、35000の形態素に対して行なって0.5秒とかそれくらいの時間で終わるので問題ないんじゃないかと思います。



まず準備として、Mecabをインストールする必要があります*1


で以下は私が使っているMecabのラッパです。そのまま使う場合は実行ディレクトリ以下にipadicをコピーするか、mecabOptionに場所を指定してください。

ipadic/dic.rcに下記フォーマットを記述

; tmboard
node-format-tmboard = %m,%f[0],%f[1],%f[2],%f[6],%f[7]\n
unk-format-tmboard  = %m,%f[0],%f[1],%f[2],%m,%m\n
eos-format-tmboard  = 

Mecab.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace com.semcode.tmboard.util
{
    public class Mecab
    {
        [DllImport("libmecab.dll")]
        extern static int mecab_new2(string arg);
        [DllImport("libmecab.dll")]
        extern static string mecab_sparse_tostr(int m, string str);
        [DllImport("libmecab.dll")]
        extern static void mecab_destroy(int m);

        public static List<MecabMorpheme> analyze(String target)
        {
            String mecabOption = "-d ipadic -O tmboard";
            int p = mecab_new2(mecabOption);
            if (p == 0)
            {
                Console.WriteLine("Mecab が見つかりません。");
                return new List<MecabMorpheme>();
            }
            String resultStr = mecab_sparse_tostr(p, target.Replace("\n", ""));
            mecab_destroy(p);

            Dictionary<String, MecabMorpheme> morphemes = new Dictionary<String, MecabMorpheme>();
            String[] splitted = resultStr.Split('\n');
            foreach (String line in splitted)
            {
                String[] data = line.Split(',');
                MecabMorpheme morpheme;
                if (data.Length == 6)
                {
                    morpheme = new MecabMorpheme(data[0], data[1], data[2], data[3], data[4], data[5]);
                }
                else if (data.Length == 3)
                {
                    morpheme = new MecabMorpheme(data[0], data[1], data[2]);
                }
                else
                {
                    continue;
                }
                if (morphemes.ContainsKey(morpheme.word))
                {
                    morphemes[morpheme.word].count++;
                }
                else
                {
                    morphemes.Add(morpheme.word, morpheme);
                }
            }

            return new List<MecabMorpheme>(morphemes.Values);
        }
    }
}

MecabMorpheme.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace com.semcode.tmboard.util
{
    public class MecabMorpheme
    {
        public static String CLASS_NOUN = "名詞";
        public static String SUBTYPE_NOUN_PROPER = "固有名詞";
        public static String SUBTYPE_NOUN_GENERAL = "一般";
        public static String SUBTYPE_NOUN_NUMERAL = "数";
        public static String CLASS_POSTPOSITION = "助詞";
        public static String CLASS_SYMBOL = "記号";


        protected String _word;
        public String word { get { return this._word; } set { this._word = value; } }
        protected String _wordClass;
        public String wordClass { get { return this._wordClass; } set { this._wordClass = value; } }
        protected String _subtype1;
        public String subtype1 { get { return this._subtype1; } set { this._subtype1 = value; } }
        protected String _subtype2;
        public String subtype2 { get { return this._subtype2; } set { this._subtype2 = value; } }
        protected double _score = 0;
        public double score { get { return this._score; } set { this._score = value; } }
        protected int _count = 0;
        public int count { get { return this._count; } set { this._count = value; } }
        protected String _yomi;
        public String yomi { get { return this._yomi; } set { this._yomi = value; } }
        protected String _original;
        public String original { get { return this._original; } set { this._original = value; } }

        public MecabMorpheme(String word, String wordClass, String subtype1)
        {
            this.word = word;
            this.wordClass = wordClass;
            this.subtype1 = subtype1;
        }

        public MecabMorpheme(String word, String wordClass, String subtype1, String subtype2, String original, String yomi)
        {
            this.word = word;
            this.wordClass = wordClass;
            this.subtype1 = subtype1;
            this.subtype2 = subtype2;
            this.yomi = yomi;
            this.original = original;
        }

        public bool isNoun()
        {
            return this.wordClass.Equals(CLASS_NOUN);
        }
        public bool isProperNoun()
        {
            return this.subtype1.Equals(SUBTYPE_NOUN_PROPER);
        }
        public bool isGeneralNoun()
        {
            return this.subtype1.Equals(SUBTYPE_NOUN_GENERAL);
        }
        public bool isSignificantNoun()
        {
            return this.isNoun() || (this.isProperNoun() || this.isGeneralNoun());
        }
        public bool isNumeralNoun()
        {
            return this.subtype1.Equals(SUBTYPE_NOUN_NUMERAL);
        }
        public bool isSymbol()
        {
            return this.wordClass.Equals(CLASS_SYMBOL);
        }
        public bool isPostPosition()
        {
            return this.wordClass.Equals(CLASS_POSTPOSITION);
        }
    }
}

でこれを使ってどうするかというと、ファイルを読み込んで助詞と記号を除いた形態素を原型で並び替えて、それに対する読みを出力しています。

        private void GenerateButton_Click(object sender, EventArgs e)
        {
            if (saveDictionaryDialog.ShowDialog() == DialogResult.OK)
            {
                List<MecabMorpheme> morphemes = new List<MecabMorpheme>();
                foreach (String filename in this.FileListBox.Items)
                {
                    morphemes.AddRange(this.ProcessFile(filename));
                }
                morphemes = this.CleanupList(morphemes);
            }
        }

        private List<MecabMorpheme> CleanupList(List<MecabMorpheme> morphemes)
        {
            Dictionary<String, MecabMorpheme> result = new Dictionary<String, MecabMorpheme>();
            foreach (MecabMorpheme morpheme in morphemes)
            {
                if (morpheme.isPostPosition() || morpheme.isSymbol())
                {
                    continue;
                }
                if (result.ContainsKey(morpheme.original))
                {
                    result[morpheme.original].count += morpheme.count;
                }
                else
                {
                    morpheme.word = morpheme.original;
                    result.Add(morpheme.original, morpheme);
                }
            }
            result = this.GetOriginalYomi(result);
            return new List<MecabMorpheme>(result.Values);
        }

        private Dictionary<String, MecabMorpheme> GetOriginalYomi(Dictionary<String, MecabMorpheme> morphemes)
        {
            String yomiStr = "";
            foreach (String original in morphemes.Keys)
            {
                yomiStr += original + " ";
            }
            List<MecabMorpheme> originals = Mecab.analyze(yomiStr);
            Dictionary<String, MecabMorpheme> yomis = new Dictionary<string, MecabMorpheme>();
            foreach (MecabMorpheme morpheme in originals)
            {
                if (yomis.ContainsKey(morpheme.original))
                {
                    continue;
                }
                yomis.Add(morpheme.original, morpheme);
            }

            foreach (MecabMorpheme morpheme in morphemes.Values)
            {
                if (yomis.ContainsKey(morpheme.original))
                {
                    morpheme.yomi = yomis[morpheme.original].yomi;
                }
            }
            return morphemes;
        }

        private List<MecabMorpheme> ProcessFile(String filename)
        {
            if (File.Exists(filename) == false)
            {
                Console.WriteLine(filename + " is not exists");
                return new List<MecabMorpheme>();
            }

            FileStream stream = new FileStream(filename, FileMode.Open, FileAccess.Read);
            byte[] bytes = null;
            if(stream.Length < 1024){
                bytes = new byte[stream.Length];
                stream.Read(bytes, 0, bytes.Length);
            }else{
                bytes = new byte[1024];
                stream.Read(bytes, 0, bytes.Length);
            }
            Encoding encoding = TextEncoder.GetCode(bytes);

            stream.Seek(0, SeekOrigin.Begin);
            StreamReader reader = new StreamReader(stream, encoding);
            List<MecabMorpheme> result = new List<MecabMorpheme>();
            String line = "";
            while (reader.Peek() > 0)
            {
                line = reader.ReadLine();
                //line = TextEncoder.ConvertEncoding(line, encoding, Encoding.Unicode);
                result.AddRange(Mecab.analyze(line));
            }
            return result;
        }

この例では原型だけが必要だったので、原型だけ抜き出していますが必要な場合は適当にソースを書き換えてください。
あまり参考にはならないかもしれませんが

        private Dictionary<String, MecabMorpheme> GetOriginalYomi(Dictionary<String, MecabMorpheme> morphemes)
        {
            String yomiStr = "";
            foreach (String original in morphemes.Keys)
            {
                yomiStr += original + " ";
            }
            List<MecabMorpheme> originals = Mecab.analyze(yomiStr);
            Dictionary<String, MecabMorpheme> yomis = new Dictionary<string, MecabMorpheme>();
            foreach (MecabMorpheme morpheme in originals)
            {
                if (yomis.ContainsKey(morpheme.original))
                {
                    continue;
                }
                yomis.Add(morpheme.original, morpheme);
            }

            foreach (MecabMorpheme morpheme in morphemes.Values)
            {
                if (yomis.ContainsKey(morpheme.original))
                {
                    morpheme.yomi = yomis[morpheme.original].yomi;
                }
            }
            return morphemes;
        }

この関数が、原型から読みを取得しているところです。

*1:ipadicが必要なので