【C#】NMeCabによる形態素解析とユーザー辞書登録のクラスを作りました

当ページのリンクには広告が含まれています。

【C#】NMeCabで形態素解析!ユーザー辞書の登録も詳しく解説」の記事では、 MeCab をC#で使う方法について解説しました。

今回はC#から簡単に形態素解析とユーザー辞書登録を行えるようにしたヘルパークラスを作りましたので、紹介したいと思います。

NMeCab を解説しているサイトはいくつかありますが、クラス化しているものとか、ユーザー辞書の登録について詳しく解説しているサイトは見つからなかったので、それらを網羅した記事になっています。

サンプルプログラムとソースも公開していますので、皆さん適宜必要な個所を修正のうえ、お使いください。

目次

サンプルプログラムの動作

今回作成したサンプルプログラムの動作について簡単に解説します。

一番上のテキストボックスに解析したいテキストを入力し、「形態素解析」ボタンをクリックすると、中央のDataGird部分に解析結果が表示されます。

最下部のテキストボックスが辞書を登録する部分です。辞書に登録したいキーワードを列挙し、「辞書登録」ボタンをクリックすることで、ユーザー辞書が生成されます。

辞書を登録後に「形態素解析」ボタンをクリックすると、最初は「超」「便利」「な」「ドラッグ」「&」「ドロップ」という風に分割されていたものが、「超便利」と「ドラッグ&ドロップ」それぞれ単語として認識されていることが分かると思います。

プロジェクトのダウンロード

プロジェクト一式は下記からダウンロードできます。

尚、NMecabを入れるとファイルが巨大化するので、プロジェクト一式には含んでいません。

ビルドする場合は必ずNMecabをインストール(後述)してから行ってください。

NMeCabのインストール方法

NMeCabのインストール方法は NuGetを使って行います。

Visual Studio の NuGet で ”NMeCab”というキーワードで検索すると、以下の様にNMeCabが表示されますので、プロジェクト欄にチェックを入れて「インストール」をクリックしてください。

NuGetの詳しい解説がお知りになりたい方は、こちらをご覧ください。

C#からユーザー辞書を登録するにはMeCabのインストールが必要

NMeCabは直接ユーザー辞書を作成する機能をサポートしていません。

ユーザー辞書を登録する場合、MeCabが扱える辞書ファイル形式にコンパイルする必要があるのですが、これは本家のMeCabに添付されている、mecab-dict-index.exe を使うことになります。

今回のサンプルプログラムでも、C#からこのファイルを呼び出しています。本家のMeCabはこちらの公式サイトからダウンロード&インストールして下おいてください。

公式サイトを表示すると「ダウンロード」のリンクがありますので、これをクリックすると、ページ中央のダウンロードリンクの位置までジャンプします。

続けて、以下のダウンロードリンクをクリックすると、インストーラーがダウンロードできます。

ソースコード解説

今回はWPFを使ってサンプルを作成していますが、NMeCabを扱うクラスは WindowsForm や Console においても共通で使用できます。

では、XAMLの解説から始めましょう。

画面のソースコード

ボタンが2つ、テキストボックスが2つ、DataGridが1つというシンプルな構成です。

WPFではありますが、MVVMではなくWindowsFormと同じくイベントドリブンでベタ書きしているので、WindowsForm技術者の方でも理解しやすいかと思います。

<Window x:Class="MecabTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MecabTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="451.829" Width="831.098">
    <Grid>
        <TextBox x:Name="uxSource" AcceptsReturn="True" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Height="105" Margin="27,35,28,0" TextWrapping="Wrap" VerticalAlignment="Top"/>
        <Button x:Name="uxAnalyze" Content="形態素解析" HorizontalAlignment="Right" Margin="0,10,59,0" VerticalAlignment="Top" Width="79" Click="uxAnalyze_Click"/>
        <Button x:Name="uxDicEntry" Content="辞書登録" HorizontalAlignment="Right" Margin="0,0,59,126" VerticalAlignment="Bottom" Width="79" Click="uxDicEntry_Click"/>
        <DataGrid x:Name="uxResult" CanUserAddRows="False" ItemsSource="{Binding}" Margin="27,145,28,151"/>
        <TextBox x:Name="uxKeywords" AcceptsReturn="True" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Margin="27,0,28,12" TextWrapping="Wrap" Height="109" VerticalAlignment="Bottom"/>

    </Grid>
</Window>

次にC#のソースコードを見ていきます。

C#のソースコード

こちらもWindowsFormと同じく、イベントハンドラに処理を記述しています。

ソースコードの中央(70行目当たり)に public class MecabUtil という記述がありますが、これが NMeCabを簡単に使うためのクラスです。 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Diagnostics;
using System.IO;
using System.Data;
using NMeCab;

namespace MecabTest
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// MecabUtilのインスタンス生成
        /// </summary>
        private NMecabUtil _mecabUtil =  new NMecabUtil() ;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();
        }


        /// <summary>
        /// 形態素解析ボタンクリック処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void uxAnalyze_Click(object sender, RoutedEventArgs e)
        {
            var result = _mecabUtil.Analyze(uxSource.Text);
            
            DataTable dt = new DataTable();
            dt.Columns.Add("キーワード");
            Enumerable.Range(0, 10).Select(i => dt.Columns.Add("項目"+i.ToString())).ToArray();
            Enumerable.Range(0, result.Count).Select(i => dt.Rows.Add(dt.NewRow().ItemArray = result[i])).ToArray();
            uxResult.DataContext = dt;
        }

        /// <summary>
        /// 辞書登録ボタンクリック処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void uxDicEntry_Click(object sender, RoutedEventArgs e)
        {
            //uxKeyword.Textの中身を改行コードで分割し、辞書登録メソッドの引数に渡す
            _mecabUtil.CreateUserDic("MyDic.Dic",uxKeywords.Text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None));
        }

    }

    /// <summary>
    /// NMecabを簡単に使うためのクラス
    /// </summary>
    public class NMecabUtil
    {
        /// <summary>
        /// 辞書フォルダ
        /// </summary>
        public string DicDir { get; set; } = @"dic\ipadic";

        /// <summary>
        /// 形態素解析
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        public List<string[]> Analyze(string text)
        {
            //解析器の生成
            var mecab = MeCabTagger.Create();

            //最初のノードを取得
            var node = mecab.ParseToNode(text);

            //戻り値の List変数を定義
            List<string[]> result = new List<string[]>();

            //ループで node に格納された情報を抽出
            while (node != null)
            {
                //形態素が空でなく、ヘッダ・フッダ行でない場合、words へ追加
                if (node.Surface.Trim() != "" && !node.Feature.StartsWith("BOS/EOS"))
                {
                    //解析結果を文字列配列に格納
                    var item = new List<string>();
                    item.Add(node.Surface);
                    item.AddRange(node.Feature.Split(','));

                    //文字列配列を戻り値のLISTに追加
                    result.Add(item.ToArray());
                }

                //次のノードへ
                node = node.Next;
            }

            return result;
        }

        /// <summary>
        /// 辞書の作成
        /// </summary>
        /// <param name="dicPath"></param>
        /// <param name="keyWords"></param>
        /// <returns></returns>
        public string[] CreateUserDic(string dicName,string[] keyWords)
        {
            //一時ファイル名の取得
            var tempfile = System.IO.Path.GetTempFileName();

            //キーワードをMecabの辞書形式に変換
            var lines = keyWords.Select(i => i + ",,,10,名詞,一般,*,*,*,*,独自辞書").ToArray();

            //ファイルに保存
            File.WriteAllLines(tempfile, lines, Encoding.GetEncoding("shift-jis"));

            //DicDirフォルダに指定された辞書名で辞書を作成する
            var result = CreateUserDic(System.IO.Path.Combine(DicDir, dicName),tempfile);

            //一時ファイルの削除
            File.Delete(tempfile);

            return result;
        }

        /// <summary>
        /// 辞書の作成
        /// </summary>
        /// <param name="dicPath"></param>
        /// <param name="csvPath"></param>
        /// <returns></returns>
        public string[] CreateUserDic(string dicPath,string csvPath)
        {
            // Mecabの辞書登録コマンドの呼び出し
            return ProcessCall(@"C:\Program Files (x86)\MeCab\bin\mecab-dict-index", 
                string.Format(" -d \"C:\\Program Files (x86)\\MeCab\\dic\\ipadic\" -u \"{0}\" -f shift-jis -t utf-8 \"{1}\"", 
                dicPath, csvPath)).ToArray();
        }


        /// <summary>
        /// プロセスの実行
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public string[] ProcessCall(string program, string args = "")
        {
            ProcessStartInfo psInfo = new ProcessStartInfo();

            // 実行するファイルをセット
            psInfo.FileName = program;

            //引数があればセット
            psInfo.Arguments = (args == "") ? "" : args;

            // コンソール・ウィンドウを開かない
            psInfo.CreateNoWindow = true;

            // シェル機能を使用しない
            psInfo.UseShellExecute = false;

            // 標準出力をリダイレクトする
            psInfo.RedirectStandardOutput = true;

            // プロセスを開始
            Process p = Process.Start(psInfo);

            // アプリの出力を受け取るLIST
            List<string> result = new List<string>();

            // アプリのコンソール出力結果を全て受け取る
            string line;
            while ((line = p.StandardOutput.ReadLine()) != null)
            {
                result.Add(line);
            }

            return result.ToArray();
        }
    }
}

辞書を参照するためのApp.config

ユーザー辞書をNMecabから参照するための方法として、App.config に記述する方法と、MeCabParamクラスで辞書を指定する方法の2通りが有ります。

どちらでも良いのですが、私の場合 MeCabParam を使って辞書を登録すると例外エラーが発生してしまいましたので、App.config に記述する方法を採用しています。

ユーザー辞書を参照する場合、<configSections>タグと、<applicationSettings>タグを App.configに挿入する必要がありますが、ポイントは11行目の<value>~</value>の間に、ユーザー辞書を記述するところです。

ユーザー辞書が複数ある場合は、<value>MyDic1.dic,MyDic2.dic,MyDic3.dic<value> の様に、カンマ区切りで辞書名を羅列します。

    <configSections>
      <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
        <section name="NMeCab.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false"/>
      </sectionGroup>
    </configSections>
    <applicationSettings>
      <NMeCab.Properties.Settings>
        <setting name="UserDic" serializeAs="String">
          <value>ここにユーザー辞書をカンマ区切りで列挙</value>
        </setting>
      </NMeCab.Properties.Settings>
    </applicationSettings>

App.config に この編集を行った後で、列記した辞書が所定フォルダ(後述)に存在しない場合、実行時にエラーになります

あらかじめ、記述した名前でユーザー辞書を作成しておいて下さい。

所定のフォルダとはプログラム実行フォルダに存在する dic フォルダ⇒ iPadicフォルダの事です。

NMeCabの場合、このフォルダが辞書の場所として初期設定されていて、ここからの相対パスによって UserDicを探します。

MecabUtilのCreateUserDicメソッドを使うと簡単に辞書が作成できますので、プログラム起動時に辞書を作るようにしてもらえれば良いかと思います。

辞書を作成する際、1つでも良いので何らかの単語を CreateUserDicに指定してあげる必要があります。というのも、MeCabの仕様上、空のユーザー辞書を作成することが出来ないからです。

では、実際に App.config に手を加えてみましょう。MecabTestプロジェクトのApp.config の初期状態は次の様になっていました。

【変更前】

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
  </startup>
</configuration>

<configuration>タグの中に、<configSections>タグと<applicationSettings>を入れればよいので、次のようになります。

この時、<startup>タグの直前に入れるようにしてください。<startup>タグより後ろに入れると、実行時にエラーになります。

【変更後】

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

    <configSections>
      <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
        <section name="NMeCab.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false"/>
      </sectionGroup>
    </configSections>
    <applicationSettings>
      <NMeCab.Properties.Settings>
        <setting name="UserDic" serializeAs="String">
          <value>MyDic.dic</value>
        </setting>
      </NMeCab.Properties.Settings>
    </applicationSettings>

  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
  </startup>
  </configuration>

MeCabUtilの使い方

では、使い方について解説しておきます。

MeCabUtilのリファレンス

MeCabUtilには1つのプロパティと4つのメソッドが実装されています。

DicDirはユーザー辞書を置く場所を指定します。

MeCabUtilで作成したユーザー辞書は、DicDirで指定されたフォルダにコピーされます。

NMecabの辞書が置かれているフォルダ(相対指定で ”dic\ipadic”)と同じ場所を指定しないといけないので、初期値はそのようになっています。

属性リファレンス
プロパティDicDir
ユーザー辞書のコピー先フォルダ。
初期値はNMeCabのデフォルトの辞書フォルダである "dic\ipadic"
特に問題が無い限り、このまま使用する。
メソッドList<string[]> Analyze(string test)
形態素解析を実行し、結果の配列をLISTで返す。
通常、MeCabは解析結果を1単語(形態素)毎に、タブとカンマ区切りの1行として返してくる。

 <MeCabの出力例>
 今日\t名詞,副詞可能,*,*,*,*,今日,キョウ,キョー
 
これをタブとカンマで分解して配列に格納し、全ての形態素をLIST形でまとめて戻り値にしている。
メソッドstring[] CreateUserDic(string dicName,string[] keyword
keywordで指定された文字列を使って dicName の名前で辞書を作成する。
既に存在する場合は上書きされる。
メソッドstring[] CreateUserDic(string dicPath,string csvPath)
上記と同じだが、辞書を記述したCSVファイルを使って、dicPathで指定したパスに辞書を作成する。
メソッドstring[] ProcessCall(string program, string args = "")
C#からコンソールプログラムを呼び出すためのメソッド。
戻り値としてコンソールプログラムの出力結果を配列形式で返す。
C#からmecab-dict-index.exe を呼び出すために使用している。

MeCabUtilの使い方

では、形態素解析の方法とユーザー辞書の方法について、順に解説していきたいと思います。

形態素解析の実行

NMecabUtilのインスタンスを生成し、Analyzeメソッドに解析したい文字列を渡せば、結果がList形式で返ってきます。

var util = new NMecabUtil();
var words = mecabUtil.Analyze(text);
foreach(var word in words)
{
    Console.WriteLine("{0}",String.Join("/".word));
} 

出力イメージは次の様になります。

辞書 名詞 一般 * * * * 辞書 ジショ ジショ
を 助詞 格助詞 一般 * * * を ヲ ヲ
記述 名詞 サ変接続 * * * * 記述 キジュツ キジュツ
し 動詞 自立 * * サ変・スル 連用形 する シ シ
た 助動詞 * * * 特殊・タ 基本形 た タ タ
CSV 名詞 一般 * * * * *
ファイル 名詞 一般 * * * * ファイル ファイル ファイル
を 助詞 格助詞 一般 * * * を ヲ ヲ

各項目(列)の意味については、こちらで詳しく解説していますので、ご参照下さい。

ユーザー辞書の登録

辞書を登録する場合は、CreateUserDicメソッドを使います。

辞書を登録すると言っても、既存のユーザー辞書に追加するのではなく、呼び出される度に新規作成していることにご注意ください。

既存のユーザー辞書に新しい単語を追加したい場合、過去に登録した単語を含んだ文字列配列を、第2引数に指定する必要があります。

var keywords = string[]{"ユーザー辞書","CSVファイル","形態素解析"};

var util = new NMecabUtil();
util.CreateUserDic("MyDic.dic",keywords);

MeCabに辞書を登録する場合、フォーマットが決まっていますので、登録したい単語だけだとエラーになってしまいます。

登録したい単語の後ろに、品詞や読み仮名などの情報も一緒に付け加える必要があります。

しかし、幸いなことに省略できる項目も多く、単に形態素解析を行うだけであれば、登録したい単語に続けて",,,10,名詞,一般,,,,,独自辞書"などの固定文字列を付加してあげれば辞書が作成できます。

例えば、CSVファイル という単語をユーザー辞書に登録したい場合、次の様になります。

CSVファイル,,,10,名詞,一般,*,*,*,*,独自辞書

CreateUserDicメソッドは、第2引数で指定されたキーワード(登録したい単語)に対して、この固定文字を付加したものを mecab-dict-index.exe に渡して辞書を作成しています。

// Mecabの辞書登録コマンドの呼び出し
return ProcessCall(@"C:\Program Files (x86)\MeCab\bin\mecab-dict-index", 
       string.Format(" -d \"C:\\Program Files (x86)\\MeCab\\dic\\ipadic\" -u \"{0}\" -f shift-jis -t utf-8 \"{1}\"",       dicPath, csvPath)).ToArray();

mecab-dict-index.exe で指定している引数の意味は次の通りです。

引数意味
-dフォルダ名
-uユーザー辞書名
-fCSVの文字コード
-tユーザー辞書の文字コード

今回 -d オプションに指定しているフォルダは "C:\Program Files (x86)\MeCab\dic\ipadic\" にしています。

実行プログラムのフォルダにある "dic\ipadic"を指定しても良いのかもしれませんが、そうなるとプロジェクト毎に修正する必要が生じるので、本家のMeCabのシステム辞書フォルダを指定しています。

もし、異なった場所にインストールしている場合、この部分を適宜修正して下さい。

まとめ

以上でNMeCabのヘルパークラスの解説は終了です。

クラス化しているため、その部分だけコピペしてご自身のプロジェクトで再利用ができます。

あとは、皆さんのニーズに合わせてカスタマイズして頂ければ幸いです。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次