日本語の文章を単語に分割する(分かち書きをする)

文章を単語単位に分割したいと思う時があります。英語ならば単語がスペースで区切られているため比較的簡単にできそうですが、日本語ではどうやったらいいのか見当もつきません。

調べてみると、文章を単語で分割して記述することを「分かち書き」と言うそうです。そして、分かち書きを行うには、「形態素解析」を行えばよいようです。形態素解析とは、文章を意味のある最小の言葉(形態素)に分割し、それらの品詞を判別することです。

こんなに難しそうなことは素人の手には負えませんので、簡単にできそうな方法を探してみました。まず形態素解析を行う方法ですが、これは次の2つの方法しか見つかりませんでした。

  1. 形態素解析ツールをインストールして使う
  2. Webサービスを使う

形態素解析ツールとして有名なものには、KAKASHIChaSen(茶筌)MeCab(和布蕪)などがあります。この内MeCabについては、その機能を簡単に呼び出すことができる.NETのライブラリがいくつか公開されています。私が調べた限りでは、次のようなものがありました。

Webサービスによる方法というのは、Yahoo! Japanの日本語形態素解析Webサービスのことです。これ以外に形態素解析を行うWebサービスがあるかは、分かりません。

これらの方法は、外部のアプリケーション、外部のWebサービスを使わなければならないという大きな足かせがあります。しかし、それ以外の方法は見つかりませんでした。

次に分かち書きだけを行う方法ですが、以下のような方法が見つかりました。

  1. 公開されている分かち書きを行うコード(TinySegmenter)を参考にする
  2. IWordBreakerを使う

ここではこの2つの方法を順番に紹介します。なお、形態素解析については、次回以降に紹介します。

TinySegmenterを移植する

JavaScriptだけで日本語分かち書きを行う、TinySegmenterというソフトウェアがあります。非常にコンパクトで、辞書を使用しません。ライセンスは、修正BSDライセンスです。

TinySegmenterは様々な言語に移植されていますが、C#やVB.NETへの移植版は見つかりませんでした。TinySegmenterのコードを見たところ、JavaScriptの知識がそれほどでもない私でもなんとか理解できそうな位シンプルでしたので、C#とVB.NETに移植してみました。かなりの行数のコードになってしまいましたのでここには載せませんが、以下の場所で公開します。使い方なども、そちらで説明しています。

なお、他の言語への移植版には、以下のようなものがありました。

IWordBreakerを使用する

WindowsのIndex Serviceで使用されているIWordBreakerを使用すれば、分かち書きができます。これは、Windows 2000以降でサポートされているようです。

IWordBreakerで分かち書きをする方法はちょっと難しいですが、Andrew Cenciniさんのブログ「Part 1: Testing Full-Text Wordbreakers」(元のページが消滅しているため、Internet Archiveへのリンク)で説明されています。また、以下のページでも詳しく説明されています。ほとんどのページは、「Testing Full-Text Wordbreakers」で紹介されているコードが基になっています。

すでにこれだけの記事がありますので、ここで紹介する必要もないとは思いますが、元サイトが消えているということもありますので、「Testing Full-Text Wordbreakers」に掲載されているコードを以下に紹介させていただきます(ちょっとだけ書き換えていますが、ほぼ同じです)。また、VB.NETに書き換えたコードも示します(コード変換には「Snippet Converter for .NET 4.0」を使用しました)。

まずは、WordBreaker.csです。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//===============================================================
// WordBreaker.cs
//===============================================================
using System;
using System.Runtime.InteropServices;
 
namespace StemText
{
    //===============================================================
    // Wordbreaker stuff
    //===============================================================
    [Flags]
    public enum WORDREP_BREAK_TYPE
    {
        WORDREP_BREAK_EOW = 0,
        WORDREP_BREAK_EOS = 1,
        WORDREP_BREAK_EOP = 2,
        WORDREP_BREAK_EOC = 3
    }
 
    [ComImport]
    [Guid("CC907054-C058-101A-B554-08002B33B0E6")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IWordSink
    {
        void PutWord([MarshalAs(UnmanagedType.U4)] int cwc,
              [MarshalAs(UnmanagedType.LPWStr)] string pwcInBuf,
              [MarshalAs(UnmanagedType.U4)] int cwcSrcLen,
              [MarshalAs(UnmanagedType.U4)] int cwcSrcPos);
        void PutAltWord([MarshalAs(UnmanagedType.U4)] int cwc,
              [MarshalAs(UnmanagedType.LPWStr)] string pwcInBuf,
              [MarshalAs(UnmanagedType.U4)] int cwcSrcLen,
              [MarshalAs(UnmanagedType.U4)] int cwcSrcPos);
        void StartAltPhrase();
        void EndAltPhrase();
        void PutBreak(WORDREP_BREAK_TYPE breakType);
    }
 
    [ComImport]
    [Guid("CC906FF0-C058-101A-B554-08002B33B0E6")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IPhraseSink
    {
        void PutSmallPhrase([MarshalAs(UnmanagedType.LPWStr)] string pwcNoun,
              [MarshalAs(UnmanagedType.U4)] int cwcNoun,
              [MarshalAs(UnmanagedType.LPWStr)] string pwcModifier,
              [MarshalAs(UnmanagedType.U4)] int cwcModifier,
              [MarshalAs(UnmanagedType.U4)] int ulAttachmentType);
        void PutPhrase([MarshalAs(UnmanagedType.LPWStr)] string pwcPhrase,
              [MarshalAs(UnmanagedType.U4)] int cwcPhrase);
    }
 
    public class CWordSink : IWordSink
    {
        public void PutWord(int cwc, string pwcInBuf, int cwcSrcLen, int cwcSrcPos)
        {
            Console.WriteLine("PutWord buffer: " + pwcInBuf.Substring(0, cwc));
        }
 
        public void PutAltWord(int cwc, string pwcInBuf, int cwcSrcLen, int cwcSrcPos)
        {
            Console.WriteLine("PutAltWord buffer: " + pwcInBuf.Substring(0, cwc));
        }
 
        public void StartAltPhrase()
        {
            Console.WriteLine("StartAltPhrase");
        }
 
        public void EndAltPhrase()
        {
            Console.WriteLine("EndAltPhrase");
        }
 
        public void PutBreak(StemText.WORDREP_BREAK_TYPE breakType)
        {
            string strBreak;
            switch (breakType)
            {
                case WORDREP_BREAK_TYPE.WORDREP_BREAK_EOC:
                    strBreak = "EOC";
                    break;
                case WORDREP_BREAK_TYPE.WORDREP_BREAK_EOP:
                    strBreak = "EOP";
                    break;
                case WORDREP_BREAK_TYPE.WORDREP_BREAK_EOS:
                    strBreak = "EOS";
                    break;
                case WORDREP_BREAK_TYPE.WORDREP_BREAK_EOW:
                    strBreak = "EOW";
                    break;
                default:
                    strBreak = "ERROR";
                    break;
            }
            Console.WriteLine("PutBreak : " + strBreak);
        }
    }
 
    public class CPhraseSink : IPhraseSink
    {
        public void PutSmallPhrase(string pwcNoun, int cwcNoun, string pwcModifier,
            int cwcModifier, int ulAttachmentType)
        {
            Console.WriteLine("PutSmallPhrase: " + pwcNoun.Substring(0, cwcNoun)
                 + " , " + pwcModifier.Substring(0, cwcModifier));
        }
 
        public void PutPhrase(string pwcPhrase, int cwcPhrase)
        {
            Console.WriteLine("PutPhrase: " + pwcPhrase.Substring(0, cwcPhrase));
        }
    }
 
    [StructLayout(LayoutKind.Sequential)]
    public struct TEXT_SOURCE
    {
        [MarshalAs(UnmanagedType.FunctionPtr)]
        public delFillTextBuffer pfnFillTextBuffer;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string awcBuffer;
        [MarshalAs(UnmanagedType.U4)]
        public int iEnd;
        [MarshalAs(UnmanagedType.U4)]
        public int iCur;
    }
 
    // used to fill the buffer for TEXT_SOURCE
    public delegate uint delFillTextBuffer(
        [MarshalAs(UnmanagedType.Struct)] ref TEXT_SOURCE pTextSource);
 
    [ComImport]
    [Guid("D53552C8-77E3-101A-B552-08002B33B0E6")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IWordBreaker
    {
        void Init([MarshalAs(UnmanagedType.Bool)] bool fQuery,
              [MarshalAs(UnmanagedType.U4)] int maxTokenSize,
              [MarshalAs(UnmanagedType.Bool)] out bool pfLicense);
        void BreakText([MarshalAs(UnmanagedType.Struct)] ref TEXT_SOURCE pTextSource,
              [MarshalAs(UnmanagedType.Interface)] IWordSink pWordSink,
              [MarshalAs(UnmanagedType.Interface)] IPhraseSink pPhraseSink);
        void GetLicenseToUse([MarshalAs(UnmanagedType.LPWStr)] out string ppwcsLicense);
    }
 
    //HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\ContentIndex\Language
    //の Japanese_Default の WBreakerClass の値を調べてGuidを書き換える
    [ComImport]
    //Windows 2000
    //[Guid("80A3E9B0-A246-11D3-BB8C-0090272FA362")]
    //Windows XP
    //[Guid("BE41F4E6-9EAD-498f-A473-F3CA66F9BE8B")]
    //Windows Vista, 7
    [Guid("E1E8F15E-8BEC-45DF-83BF-50FF84D0CAB5")]
    public class CWordBreaker
    {
    }
}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
'===============================================================
' WordBreaker.vb
'===============================================================
Imports System.Runtime.InteropServices
 
Namespace StemText
    '===============================================================
    ' Wordbreaker stuff
    '===============================================================
    <Flags()> _
    Public Enum WORDREP_BREAK_TYPE
        WORDREP_BREAK_EOW = 0
        WORDREP_BREAK_EOS = 1
        WORDREP_BREAK_EOP = 2
        WORDREP_BREAK_EOC = 3
    End Enum
 
    <ComImport()> _
    <Guid("CC907054-C058-101A-B554-08002B33B0E6")> _
    <InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> _
    Public Interface IWordSink
        Sub PutWord(<MarshalAs(UnmanagedType.U4)> ByVal cwc As Integer, _
                    <MarshalAs(UnmanagedType.LPWStr)> ByVal pwcInBuf As String, _
                    <MarshalAs(UnmanagedType.U4)> ByVal cwcSrcLen As Integer, _
                    <MarshalAs(UnmanagedType.U4)> ByVal cwcSrcPos As Integer)
        Sub PutAltWord(<MarshalAs(UnmanagedType.U4)> ByVal cwc As Integer, _
                       <MarshalAs(UnmanagedType.LPWStr)> ByVal pwcInBuf As String, _
                       <MarshalAs(UnmanagedType.U4)> ByVal cwcSrcLen As Integer, _
                       <MarshalAs(UnmanagedType.U4)> ByVal cwcSrcPos As Integer)
        Sub StartAltPhrase()
        Sub EndAltPhrase()
        Sub PutBreak(ByVal breakType As WORDREP_BREAK_TYPE)
    End Interface
 
    <ComImport()> _
    <Guid("CC906FF0-C058-101A-B554-08002B33B0E6")> _
    <InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> _
    Public Interface IPhraseSink
        Sub PutSmallPhrase(<MarshalAs(UnmanagedType.LPWStr)> ByVal pwcNoun As String, _
                           <MarshalAs(UnmanagedType.U4)> ByVal cwcNoun As Integer, _
                           <MarshalAs(UnmanagedType.LPWStr)> ByVal pwcModifier As String, _
                           <MarshalAs(UnmanagedType.U4)> ByVal cwcModifier As Integer, _
                           <MarshalAs(UnmanagedType.U4)> ByVal ulAttachmentType As Integer)
        Sub PutPhrase(<MarshalAs(UnmanagedType.LPWStr)> ByVal pwcPhrase As String, _
                      <MarshalAs(UnmanagedType.U4)> ByVal cwcPhrase As Integer)
    End Interface
 
    Public Class CWordSink
        Implements IWordSink
        Public Sub PutWord(ByVal cwc As Integer, _
                           ByVal pwcInBuf As String, _
                           ByVal cwcSrcLen As Integer, _
                           ByVal cwcSrcPos As Integer) _
                       Implements IWordSink.PutWord
            Console.WriteLine("PutWord buffer: " & pwcInBuf.Substring(0, cwc))
        End Sub
 
        Public Sub PutAltWord(ByVal cwc As Integer, _
                              ByVal pwcInBuf As String, _
                              ByVal cwcSrcLen As Integer, _
                              ByVal cwcSrcPos As Integer) _
                          Implements IWordSink.PutAltWord
            Console.WriteLine("PutAltWord buffer: " & pwcInBuf.Substring(0, cwc))
        End Sub
 
        Public Sub StartAltPhrase() Implements IWordSink.StartAltPhrase
            Console.WriteLine("StartAltPhrase")
        End Sub
 
        Public Sub EndAltPhrase() Implements IWordSink.EndAltPhrase
            Console.WriteLine("EndAltPhrase")
        End Sub
 
        Public Sub PutBreak(ByVal breakType As StemText.WORDREP_BREAK_TYPE) _
            Implements IWordSink.PutBreak
            Dim strBreak As String
            Select Case breakType
                Case WORDREP_BREAK_TYPE.WORDREP_BREAK_EOC
                    strBreak = "EOC"
                    Exit Select
                Case WORDREP_BREAK_TYPE.WORDREP_BREAK_EOP
                    strBreak = "EOP"
                    Exit Select
                Case WORDREP_BREAK_TYPE.WORDREP_BREAK_EOS
                    strBreak = "EOS"
                    Exit Select
                Case WORDREP_BREAK_TYPE.WORDREP_BREAK_EOW
                    strBreak = "EOW"
                    Exit Select
                Case Else
                    strBreak = "ERROR"
                    Exit Select
            End Select
            Console.WriteLine("PutBreak : " & strBreak)
        End Sub
    End Class
 
    Public Class CPhraseSink
        Implements IPhraseSink
        Public Sub PutSmallPhrase(ByVal pwcNoun As String, _
                                  ByVal cwcNoun As Integer, _
                                  ByVal pwcModifier As String, _
                                  ByVal cwcModifier As Integer, _
                                  ByVal ulAttachmentType As Integer) _
                              Implements IPhraseSink.PutSmallPhrase
            Console.WriteLine("PutSmallPhrase: " & pwcNoun.Substring(0, cwcNoun) & _
                              " , " & pwcModifier.Substring(0, cwcModifier))
        End Sub
 
        Public Sub PutPhrase(ByVal pwcPhrase As String, _
                             ByVal cwcPhrase As Integer) _
                         Implements IPhraseSink.PutPhrase
            Console.WriteLine("PutPhrase: " & pwcPhrase.Substring(0, cwcPhrase))
        End Sub
    End Class
 
    <StructLayout(LayoutKind.Sequential)> _
    Public Structure TEXT_SOURCE
        <MarshalAs(UnmanagedType.FunctionPtr)> _
        Public pfnFillTextBuffer As delFillTextBuffer
        <MarshalAs(UnmanagedType.LPWStr)> _
        Public awcBuffer As String
        <MarshalAs(UnmanagedType.U4)> _
        Public iEnd As Integer
        <MarshalAs(UnmanagedType.U4)> _
        Public iCur As Integer
    End Structure
 
    ' used to fill the buffer for TEXT_SOURCE
    Public Delegate Function delFillTextBuffer( _
        <MarshalAs(UnmanagedType.Struct)> ByRef pTextSource As TEXT_SOURCE) As UInteger
 
    <ComImport()> _
    <Guid("D53552C8-77E3-101A-B552-08002B33B0E6")> _
    <InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> _
    Public Interface IWordBreaker
        Sub Init(<MarshalAs(UnmanagedType.Bool)> ByVal fQuery As Boolean, _
                 <MarshalAs(UnmanagedType.U4)> ByVal maxTokenSize As Integer, _
                 <MarshalAs(UnmanagedType.Bool)> ByRef pfLicense As Boolean)
        Sub BreakText(<MarshalAs(UnmanagedType.Struct)> ByRef pTextSource As TEXT_SOURCE, _
                      <MarshalAs(UnmanagedType.[Interface])> ByVal pWordSink As IWordSink, _
                      <MarshalAs(UnmanagedType.[Interface])> ByVal pPhraseSink As IPhraseSink)
        Sub GetLicenseToUse(<MarshalAs(UnmanagedType.LPWStr)> ByVal ppwcsLicense As String)
    End Interface
 
    'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\ContentIndex\Language
    'の Japanese_Default の WBreakerClass の値を調べてGuidを書き換える
    'Windows 2000
    '<Guid("80A3E9B0-A246-11D3-BB8C-0090272FA362")>
    'Windows XP
    '<Guid("BE41F4E6-9EAD-498f-A473-F3CA66F9BE8B")>
    'Windows Vista, 7
    <ComImport()> _
    <Guid("E1E8F15E-8BEC-45DF-83BF-50FF84D0CAB5")> _
    Public Class CWordBreaker
    End Class
End Namespace

最後のCWordBreakerクラスの部分ですが、GuidがOSによって変わるようです。コメントに書いたように、レジストリの「HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\ContentIndex\Language\Japanese_Default」の「WBreakerClass」の値を調べて書き換えてください。コメントにOS別の値みたいなものを書いておきましたが、これが本当に合っているかは保証できません。

次に分かち書きを実行する部分です。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
//==============================================================
// Main.cs
//==============================================================
using System;
using System.Runtime.InteropServices;
 
namespace StemText
{
    //==============================================================
    // Main class
    //==============================================================
    class MainClass
    {
        static uint pfnFillTextBuffer(ref TEXT_SOURCE pTextSource)
        {
            // return WBREAK_E_END_OF_TEXT
            return 0x80041780;
        }
 
        [STAThread]
        static void Main(string[] args)
        {
            //分かち書きをする文章
            string tokStr = "今日はいい天気ですね。";
 
            try
            {
                CWordBreaker wb = new CWordBreaker();
                IWordBreaker iwb = (IWordBreaker)wb;
 
                CWordSink cws = new CWordSink();
                IWordSink iws = (IWordSink)cws;
                CPhraseSink cps = new CPhraseSink();
                IPhraseSink ips = (IPhraseSink)cps;
 
                bool pfLicense = true;
                iwb.Init(true, 1000, out pfLicense);
 
                //string tokStr = args[0];
 
                TEXT_SOURCE pTextSource = new TEXT_SOURCE();
                pTextSource.pfnFillTextBuffer =
                    new delFillTextBuffer(pfnFillTextBuffer);
                pTextSource.awcBuffer = tokStr;
                pTextSource.iCur = 0;
                pTextSource.iEnd = tokStr.Length;
 
                iwb.BreakText(ref pTextSource, iws, ips);
 
            }
            catch (System.Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
 
            Console.ReadLine();
        }
    }
 
}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
'==============================================================
' Main.vb
'==============================================================
Imports System.Runtime.InteropServices
 
Namespace StemText
    '==============================================================
    ' Main Module
    '==============================================================
    Module MainModule
        Function pfnFillTextBuffer(ByRef pTextSource As TEXT_SOURCE) As UInteger
            ' return WBREAK_E_END_OF_TEXT
            Return &H80041780UI
        End Function
 
        <STAThread()> _
        Sub Main(ByVal args As String())
            '分かち書きをする文章
            Dim tokStr As String = "今日はいい天気ですね。"
 
            Try
                Dim wb As New CWordBreaker()
                Dim iwb As IWordBreaker = DirectCast(wb, IWordBreaker)
 
                Dim cws As New CWordSink()
                Dim iws As IWordSink = DirectCast(cws, IWordSink)
                Dim cps As New CPhraseSink()
                Dim ips As IPhraseSink = DirectCast(cps, IPhraseSink)
 
                Dim pfLicense As Boolean = True
                iwb.Init(True, 1000, pfLicense)
 
                'Dim tokStr As String = args(0)
 
                Dim pTextSource As New TEXT_SOURCE()
                pTextSource.pfnFillTextBuffer = _
                    New delFillTextBuffer(AddressOf pfnFillTextBuffer)
                pTextSource.awcBuffer = tokStr
                pTextSource.iCur = 0
                pTextSource.iEnd = tokStr.Length
 
 
                iwb.BreakText(pTextSource, iws, ips)
            Catch ex As System.Exception
                Console.WriteLine(ex.Message)
            End Try
 
            Console.ReadLine()
        End Sub
    End Module
End Namespace

このコードを実行すると、以下のように出力されます。

PutWord buffer: 今日
PutWord buffer: は
PutWord buffer: いい
PutWord buffer: 天気
PutWord buffer: です
PutWord buffer: ね
PutBreak : EOP

TinySegmenterとIWordBreakerの結果の比較

TinySegmenterとIWordBreakerの結果の比較が、「COM で分かち書き IWordBreaker for C++」などで紹介されています。これらによると、IWordBreakerはTinySegmenterと比較して、より大きな塊で分ける傾向があるようです。また、IWordBreakerでは記号がなくなってしまうようです。

予告

次回は、形態素解析について紹介する予定です。

コメント



ページ情報
[ トップ ]   [ 新規 | 子ページ作成 | 一覧 | 単語検索 | 最終更新 | ヘルプ ]