Japanese morphological analyzer on .NET
2010年からOSDNで開発し公開してきたNMeCabですが、バージョン0.10.0からは、こちらGitHubで開発し公開していきます。
NMeCabは.NETで開発した日本語形態素解析エンジンです。
その名の通り、元々はMeCabというOSSの日本語形態素解析エンジンをC++からC#へ移植したものですが、独自の機能追加も行っています。
辞書データはMeCabに対応したものをそのまま使用できます。
NMeCabバージョン0.10.0では、.NET Standard 2.0ライブラリにしてあります。(これに対応するランタイムは、.NET 5 以上、.NET Core 2.0 以上、.NET Framework 4.6.1 以上、などです)
自然言語の文章を形態素(日本語では単語と同じ)に分割し、品詞・読み・活用型などの素性情報の付与も行うことです。
様々な自然言語処理の第一段階として使用されることを目的にしています。
プログラム言語コードのコンパイラのように、文字列を文法ベースで処理していると思われるかもしれませんが、実際にそのような処理を行っても日本語の文章にはほとんど通用しません。
MeCab/NMeCabの手法は、事前にコーパス(言語学的にまとめられた大量の資源)から統計的に抽出した情報(辞書)を使うものです。
コーパスベースの辞書引き(DoubleArrayTrieを活用)と、規則ベースの未知語抽出により、ラティスと呼ばれる候補の形態素の集合を作り、そこから統計的に最も確からしい形態素の並びを選び出す(Viterbiアルゴリズムを使用)、という流れで解析を行なっています。
パッケージ | NuGet ID | NuGet Status |
---|---|---|
NMeCabライブラリ | LibNMeCab | |
IPA辞書 | LibNMeCab.IpaDicBin |
辞書パッケージをNuGetでインストールすると、依存するNMeCabライブラリパッケージも同時にインストールされます。
以下は主にC#によるサンプルコードで説明していきます。
LibNMeCab.IpaDicBin
をNuGetでインストールするだけで、必要なライブラリと辞書ファイルが一括でプロジェクトに追加され、NMeCabを使う準備が整います。
サンプルコード(C#):
using System;
using NMeCab.Specialized;
class Program
{
static void Main()
{
using (var tagger = MeCabIpaDicTagger.Create()) // Taggerインスタンスを生成
{
var nodes = tagger.Parse("皇帝の新しい心"); // 形態素解析を実行
foreach (var node in nodes) // 形態素ノード配列を順に処理
{
Console.WriteLine($"表層形:{node.Surface}");
Console.WriteLine($"読み :{node.Reading}");
Console.WriteLine($"品詞 :{node.PartsOfSpeech}");
Console.WriteLine();
}
}
}
}
サンプルコード(F#):
open NMeCab.Specialized
[<EntryPoint>]
let main argv =
let sentence = "皇帝の新しい心"
use tagger = MeCabIpaDicTagger.Create()
sentence
|> tagger.Parse
|> Seq.iter (fun node ->
printfn $"表層形:{node.Surface}"
printfn $"読み :{node.Reading}"
printfn $"品詞 :{node.PartsOfSpeech}"
printfn "")
0
サンプルコード(C#)の説明:
まず using NMeCab.Specialized;
により、NMeCab.Specialized名前空間を参照します。
次に MeCabIpaDicTagger.Create()
により、形態素解析処理の起点となるTaggerインスタンス(MeCabTaggerBase継承クラスのインスタンス)を生成します。
このTaggerインスタンスはIDisposableインターフェースを実装しており、使用後に Dispose()
メソッドを呼び出す必要があります。
そのため、このサンプルでは using
ステートメントを記述しています。
Dispose()
メソッドです。(.NETプログラミングに慣れない方は注意して下さい)そのTaggerインスタンスを使い tagger.Parse("皇帝の新しい心")
のように任意の文字列をParseメソッドに渡すだけで、形態素解析を実行できます。
結果は、形態素ノード(MeCabNodeBase継承クラスのインスタンス)の配列の形で返却されるので、ここではforeachループで処理しています。
形態素解析により得られた情報が、形態素ノードのプロパティにより取得できます。
このサンプルでは、表層形( Surface
)、読み( Reading
)、品詞( PartsOfSpeech
)を取得しコンソールに出力しています。
サンプルコードの結果:
表層形:皇帝
読み :コウテイ
品詞 :名詞
表層形:の
読み :ノ
品詞 :助詞
表層形:新しい
読み :アタラシイ
品詞 :形容詞
表層形:心
読み :ココロ
品詞 :名詞
他にもプロパティで取得できる情報があります。 VisualStudioのIntelliSenseなどにより閲覧できるよう、XML文書化コメントに記載してあるので確認してみてください。
LibNMeCab
だけをNuGetでインストールし、辞書は自分で用意したものを使用することもできます。
NMeCabで使う辞書ファイルは、MeCabの mecab-dict-index
コマンドを使って「解析用バイナリ辞書」にしたものである必要があります。
最初から解析用バイナリ辞書の状態で配布されているものを入手できれば簡単です。
バイナリ化する前の辞書しか入手できないときや、自分で辞書を作成するときは、MeCabのサイトなどを参照してください。
なお、文字コードが選べるときは「utf-8」にしておくことが無難です。
結果として以下のファイルが必要になりますので、任意のディレクトリにまとめて配置してください。これ以外のファイルは、同じディレクトリにあってもなくてもNMeCabの動作には影響しません。
NMeCabでは辞書の素性フォーマット別にTaggerクラスを用意してあります。 それぞれ異なる形態素ノードクラスを使用し、異なる素性情報プロパティにアクセスできます。
素性フォーマット | 名前空間 | Taggarクラス | デフォルトの辞書ディレクトリ |
---|---|---|---|
汎用 | NMeCab | MeCabTagger | dic |
IPA辞書 | NMeCab.Specialized | MeCabIpaDicTagger | IpaDic |
UniDic ver 2.1.x | NMeCab.Specialized | MeCabUniDic21Tagger | UniDic |
UniDic ver 2.2.x | NMeCab.Specialized | MeCabUniDic22Tagger | UniDic |
MeCabTagger.create()
のように辞書のパスを指定しなかった場合に適用されます。パスのルートはNMeCabのDLLの配置先です。まずは、汎用のTaggerインスタンスを生成して使うサンプルを示します。
サンプルコード(C#):
using System;
using NMeCab;
class Program
{
static void Main()
{
var dicDir = @"C:\Program Files (x86)\MeCab\dic\ipadic"; // 辞書のパス
using (var tagger = MeCabTagger.Create(dicDir)) // 汎用のTaggerインスタンスを生成
{
var nodes = tagger.Parse("皇帝の新しい心"); // 形態素解析を実行
foreach (var node in nodes) // 形態素ノード配列を順に処理
{
Console.WriteLine($"表層形:{node.Surface}");
Console.WriteLine($"素性 :{node.Feature}"); // 全ての素性文字列
Console.WriteLine();
}
}
}
}
汎用のTaggerクラス「MeCabTagger」はNMeCab名前空間にあるので、 using NMeCab;
を記述しています。
MeCabTagger.Create(dicDir)
により辞書のあるディレクトリへのパスを指定して汎用のTaggerインスタンスを生成し、 tagger.Parse(~
で汎用の形態素ノード配列の形で形態素解析結果を取得しています。
次にIPA辞書用のTaggarインスタンスを生成して使うサンプルを示します。
サンプルコード(C#):
using System;
using NMeCab.Specialized;
class Program
{
static void Main()
{
var dicDir = @"C:\Program Files (x86)\MeCab\dic\ipadic"; // 辞書のパス
using (var tagger = MeCabIpaDicTagger.Create(dicDir)) // IPAdic形式用のTaggerインスタンスを生成
{
var nodes = tagger.Parse("皇帝の新しい心"); // 形態素解析を実行
foreach (var node in nodes) // 形態素ノード配列を順に処理
{
Console.WriteLine($"表層形:{node.Surface}");
Console.WriteLine($"読み :{node.Reading}"); // 個別の素性
Console.WriteLine($"品詞 :{node.PartsOfSpeech}"); // 〃
Console.WriteLine();
}
}
}
}
using NMeCab.Specialized;
で名前空間の使用を宣言し、 MeCabIpaDicTagger.Create(dicDir)
によって辞書のパスを指定しながらIPA辞書用Taggerインスタンスを生成し、 tagger.Parse(~
でIPA辞書用の形態素ノード配列の形で形態素解析結果を取得しています。
システム辞書は必ず必要ですが、システム辞書に含まれていない単語を追加したいとき、ユーザー辞書を使うことも可能です。
やはりMeCabで解析用バイナリ辞書にしたファイルを、システム辞書と同じディレクトリに配置してください。 ファイル名は任意です。 複数のユーザー辞書を使用することも可能です。 文字コードと素性フォーマットはシステム辞書と同一にしてください。
サンプルコード(C#):
var dicDir = @"C:\Program Files (x86)\MeCab\dic\ipadic"; // 辞書のパス
var userDics = new[] { "usr1.dic", "usr2.dic" }; // ユーザー辞書ファイル名の一覧
using (var tagger = MeCabIpaDicTagger.Create(dicDir, userDics)) // ユーザー辞書も指定してTaggerインスタンスを生成
{
Taggerインスタンス生成メソッドの第2引数にユーザー辞書ファイル名の配列を渡すと、システム辞書と同時にそれらのユーザー辞書が読み込まれます。
ここまでの解説では、Taggerインスタンスの Parse(string sentence)
メソッドにより、最も確からしい形態素解析結果だけを取得していました。
Taggerインスタンスの ParseNBest(string sentence)
メソッドを使うと、確からしい順番に複数の形態素解析結果を取得できます。
結果はイテレータで、内部の取得処理は遅延実行されます。
下のサンプルでは、LinqのTakeにより上位5件の結果を取得し表示しています。
サンプルコード(C#):
using System;
using System.Linq;
using NMeCab.Specialized;
class Program
{
static void Main()
{
using (var tagger = MeCabIpaDicTagger.Create())
{
var results = tagger.ParseNBest("皇帝の新しい心"); // Nベスト解を取得
foreach (var nodes in results.Take(5)) // 上位から5件までの解を処理
{
foreach (var node in nodes) // 形態素ノード配列を順に処理
{
Console.WriteLine($"表層形:{node.Surface}");
Console.WriteLine($"読み :{node.Reading}");
Console.WriteLine($"品詞 :{node.PartsOfSpeech}");
Console.WriteLine();
}
Console.WriteLine("----");
}
}
}
}
Taggerインスタンスの ParseSoftWakachi(string sentence, float theta)
メソッドでは、その文章に含まれる可能性がある形態素を洗いざらい取得できます。
また取得した形態素ノードの Prob
プロパティにより「その形態素の含まれる確率=周辺確率」も取得できます。
下のサンプルでは、周辺確率の大きな形態素だけを抽出して表示しています。
サンプルコード(C#):
using System;
using System.Linq;
using NMeCab.Specialized;
class Program
{
static void Main()
{
using (var tagger = MeCabIpaDicTagger.Create())
{
var theta = 1f / 800f / 2f; // 温度パラメータ
var nodes = tagger.ParseSoftWakachi("本部長", theta); // ソフトわかち解を取得
foreach (var node in nodes.Where(n => n.Prob > 0.1f)) // 周辺確率>0.1の形態素ノードだけを処理
{
Console.WriteLine($"表層形 :{node.Surface}");
Console.WriteLine($"読み :{node.Reading}");
Console.WriteLine($"品詞 :{node.PartsOfSpeech}");
Console.WriteLine($"周辺確率:{node.Prob}");
Console.WriteLine();
}
// さらに、周辺確率の上位から表層形の異なるものの5件までを取得
var searchWords = nodes.OrderByDescending(n => n.Prob)
.Select(n => n.Surface)
.Distinct()
.Take(5);
Console.WriteLine($"上位ワード:{string.Join(",", searchWords)}");
}
}
}
サンプルコードの結果:
表層形 :本部
読み :ホンブ
品詞 :名詞
周辺確率:0.5966396
表層形 :本
読み :ホン
品詞 :接頭詞
周辺確率:0.2812245
表層形 :部長
読み :ブチョウ
品詞 :名詞
周辺確率:0.3622776
表層形 :長
読み :チョウ
品詞 :名詞
周辺確率:0.5903029
上位ワード:本部,長,部長,本,部
引数 theta
は、周辺確率のなめらかさを指定する温度パラメータです。
温度パラメータを大きな値にすると、最も確からしい解の形態素の周辺確率が1または∞、その他は全て0となります。温度パラメータを0に近付けると、中間の周辺確率値も現れます。
Taggerインスタンスの ParseToLattice(string sentence)
メソッドでは、「解析情報の束=ラティス」のインスタンスを取得できます。
サンプルコード(C#):
using System;
using System.Linq;
using NMeCab.Specialized;
class Program
{
static void Main()
{
using (var tagger = MeCabIpaDicTagger.Create())
{
var prm = new MeCabParam()
{
LatticeLevel = MeCabLatticeLevel.Two,
Theta = 1f / 800f / 2f
};
var lattice = tagger.ParseToLattice("東京大学", prm); // ラティスを取得
// ラティスから、ベスト解を取得し処理
foreach (var node in lattice.GetBestNodes())
{
Console.Write(node.Surface);
Console.CursorLeft = 10;
Console.Write(node.Feature);
Console.WriteLine();
}
Console.WriteLine("--------");
// ラティスから、2番目と3番目のベスト解を取得し処理
foreach (var result in lattice.GetNBestResults().Skip(1).Take(2))
{
foreach (var node in result)
{
Console.Write(node.Surface);
Console.CursorLeft = 10;
Console.Write(node.Feature);
Console.WriteLine();
}
Console.WriteLine("----");
}
Console.WriteLine("--------");
// ラティスから、開始位置別の形態素を取得し処理
for (int i = 0; i < lattice.BeginNodeList.Length - 1; i++)
{
for (var node = lattice.BeginNodeList[i]; node != null; node = node.BNext)
{
if (node.Prob <= 0.001f) continue;
Console.CursorLeft = i * 2;
Console.Write(node.Surface);
Console.CursorLeft = 10;
Console.Write(node.Prob.ToString("F3"));
Console.CursorLeft = 16;
Console.Write(node.Feature);
Console.WriteLine();
}
}
Console.WriteLine("--------");
// ラティスから、最終的な累積コストのみを取得
Console.WriteLine(lattice.EosNode.Cost);
}
}
}
サンプルコードの結果:
東京大学 名詞,固有名詞,組織,*,*,*,東京大学,トウキョウダイガク,トーキョーダイガク
--------
東京 名詞,固有名詞,地域,一般,*,*,東京,トウキョウ,トーキョー
大学 名詞,一般,*,*,*,*,大学,ダイガク,ダイガク
----
東京 名詞,固有名詞,地域,一般,*,*,東京,トウキョウ,トーキョー
大 名詞,接尾,一般,*,*,*,大,ダイ,ダイ
学 名詞,接尾,一般,*,*,*,学,ガク,ガク
----
--------
東京大学 0.763 名詞,固有名詞,組織,*,*,*,東京大学,トウキョウダイガク,トーキョーダイガク
東京大 0.007 名詞,固有名詞,組織,*,*,*,東京大,トウキョウダイ,トーキョーダイ
東京 0.230 名詞,固有名詞,地域,一般,*,*,東京,トウキョウ,トーキョー
大学 0.005 名詞,固有名詞,地域,一般,*,*,大学,ダイガク,ダイガク
大学 0.004 名詞,固有名詞,人名,名,*,*,大学,ダイガク,ダイガク
大学 0.200 名詞,一般,*,*,*,*,大学,ダイガク,ダイガク
大 0.010 名詞,接尾,一般,*,*,*,大,ダイ,ダイ
大 0.001 接頭詞,名詞接続,*,*,*,*,大,オオ,オー
大 0.003 接頭詞,名詞接続,*,*,*,*,大,ダイ,ダイ
大 0.005 名詞,一般,*,*,*,*,大,ダイ,ダイ
学 0.021 名詞,接尾,一般,*,*,*,学,ガク,ガク
学 0.002 名詞,固有名詞,人名,名,*,*,学,マナブ,マナブ
学 0.004 名詞,一般,*,*,*,*,学,ガク,ガク
--------
3620
MeCabNodeBase
、 MeCabTaggerBase
を継承した独自のクラスをコーディングします。
このあたりの設計は、キャスト処理やリフレクション(new T)を排除し、利便性や実行パフォーマンスを向上させる目的から、ジェネリクスやラムダを多く使ったものとなっています。
MeCabNodeBase
を継承した形態素ノードクラスをコーディングします。この継承時には型引数で自クラスを指定してください。
そして、各々の素性情報の名称のプロパティをコーディングし、CSVである素性情報文字列の任意の列の値を、 GetFeatureAt(1)
のように継承元クラスのメソッドで取得するようにしてください。
using NMeCab;
/// <summary>
/// MyDicの形態素ノードです。
/// </summary>
public class MyDicNode : MeCabNodeBase<MyDicNode>
{
/// <summary>
/// 素性情報1
/// </summary>
public string Feature1
{
get { return this.GetFeatureAt(0); }
}
/// <summary>
/// 素性情報2
/// </summary>
public string Feature2
{
get { return this.GetFeatureAt(1); }
}
}
MeCabTaggerBase
を継承したTaggerクラスをコーディングします。
これは定型的なコードとなります。
下記をコピーして「MyDic」の部分を書き換えるだけでOKです。
using NMeCab;
/// <summary>
/// MyDicを使用する場合の形態素解析処理の起点を表します。
/// </summary>
public class MyDicTagger : MeCabTaggerBase<MyDicNode>
{
/// <summary>
/// コンストラクタ(非公開)
/// </summary>
private MyDicTagger()
{ }
/// <summary>
/// 形態素解析処理の起点を作成します。
/// </summary>
/// <param name="dicDir">使用する辞書のディレクトリへのパス</param>
/// <param name="userDics">使用するユーザー辞書のファイル名のコレクション</param>
/// <returns>形態素解析処理の起点</returns>
public static MyDicTagger Create(string dicDir = null,
string[] userDics = null)
{
return Create(dicDir,
userDics,
() => new MyDicTagger(), // Tagger生成関数
() => new MyDicNode(), // 形態素ノード生成関数
"MyDic"); // デフォルトの辞書ディレクトリ名
}
}
上の2クラスができたら、作成したTaggerクラスのCreateメソッドを呼び出して使用します。
using System;
class Program
{
static void Main()
{
var dicDir = @"C:\Program Files (x86)\MeCab\dic\mydic"; // 辞書のパス
using (var tagger = MyDicTagger.Create(dicDir)) // MyDic形式用のTaggerインスタンスを生成
{
var nodes = tagger.Parse("皇帝の新しい心"); // 形態素解析を実行
foreach (var node in nodes) // 形態素ノード配列を順に処理
{
Console.WriteLine($"素性情報1:{node.Feature1}");
Console.WriteLine($"素性情報2:{node.Feature2}");
}
}
}
}
NMeCabは GPL v2 / LGPL v2.1 のデュアルライセンスです。 2つのどちらか片方もしくは両方に基づいて使用できます。
Taku Kudo (@taku910) 氏のMeCabという素晴らしいソフトウェアの公開に感謝いたします。
Kouji Matsui (@kekyo) 氏の素晴らしい情報とコードの公開に感謝いたします。 LibNMeCab.IpaDicBin の辞書ファイルをNuGet/MSBuildで扱うコードは、 @kekyo氏によるGPL/LGPLデュアルライセンスのオープンソースをほぼそのまま使用させて頂いたものです。
メジャーバージョン番号を今でも0にしているのは、ベースにしたMeCabのバージョン番号を超えないようにするためです。