自然言語のテキストを属性で区分する

NSLinguisticTaggerを用いると、**自然言語のテキストを品詞（名詞、動詞、代名詞）や「個人名」「地名」といった属性で区分**（トークンに分解）することができます。**日本語の形態素解析**も可能です。

![](http://f.cl.ly/items/0w2W0A3Z0q3Z0e2x0C3z/LinguisticTagger0_r1_c1.png)

使い方は非常にシンプルで、基本的な手順は

1. スキームを引数に渡してNSLinguisticTaggerオブジェクトを生成
2. 処理対象テキストをセット
3. トークン分解開始

の3ステップです。

```objc:objc
// スキーム
NSArray *schemes = @[NSLinguisticTagSchemeLexicalClass];

// NSLinguisticTaggerオブジェクトを生成
NSLinguisticTagger *tagger = [[NSLinguisticTagger alloc] initWithTagSchemes:schemes
                                                                    options:0];

// 処理対象テキスト
NSString *targetText = self.textView.text;
[tagger setString:targetText];

// トークンへの分解開始
[tagger enumerateTagsInRange:NSMakeRange(0, targetText.length)
                      scheme:NSLinguisticTagSchemeLexicalClass
                     options:0
                  usingBlock:
 ^(NSString *tag, NSRange tokenRange, NSRange sentenceRange, BOOL *stop) {
     
     NSString *subStr = [targetText substringWithRange:tokenRange];
     NSLog(@"%@ : %@", subStr, tag);
 }];
````

```swift:swift
let schemes = [NSLinguisticTagScheme.lexicalClass]
let tagger = NSLinguisticTagger(tagSchemes: schemes, options: 0)

tagger.string = orgStr
let attrStr = NSMutableAttributedString(string: orgStr)

guard let scheme = schemes.first else {fatalError()}
tagger.enumerateTags(in: NSMakeRange(0, orgStr.characters.count), scheme: scheme, options: NSLinguisticTagger.Options(rawValue: 0)) { (tag, tokenRange, sentenceRange, stop) in
    guard let tag = tag else {return}
    attrStr.addAttribute(.foregroundColor as NSAttributedStringKey, value: color(for: tag), range: tokenRange)
}

textView.attributedText = attrStr
```

スキームとは、トークン分解をどのような属性に基づいて行うかを指定するもので、上記サンプルで使用している`NSLinguisticTagSchemeLexicalClass`は品詞（「名詞」「動詞」etc）によってテキストを分解するスキームです。


`enumerateTagsInRange:scheme:options:usingBlock:`メソッドをコールするとトークン分解が開始され、トークンが見つかるごとに引数 usingBlock に渡されたBloksの処理が実行されます。

##品詞で区分する

名詞、動詞といった品詞でテキストを区分するには、スキームとして`NSLinguisticTagSchemeLexicalClass`を使用します。

`enumerateTagsInRange:scheme:options:usingBlock:`メソッドで取得できるタグの種類には、次のようなものがあります。（主なものを抜粋）

- NSLinguisticTagNoun：名詞
- NSLinguisticTagVerb：動詞
- NSLinguisticTagAdjective：形容詞
- NSLinguisticTagPronoun：代名詞
- NSLinguisticTagAdverb：副詞
- NSLinguisticTagConjunction：接続詞
- NSLinguisticTagPreposition：前置詞
- NSLinguisticTagParticle：助詞
- NSLinguisticTagDeterminer：限定詞

たとえば、品詞で区分して色分けする場合、この NSLinguisticTagger で取得できるタグに応じて色を決定するメソッドを実装しておき、


```objc
- (UIColor *)colorForAtteributeForLinguisticTag:(NSString *)linguisticTag {

    // 名詞
    if ([linguisticTag isEqualToString:NSLinguisticTagNoun]) {
        
        return [UIColor redColor];
    }
    // 動詞
    else if ([linguisticTag isEqualToString:NSLinguisticTagVerb]) {
        
        return [UIColor blueColor];
    }
    // 形容詞
    else if ([linguisticTag isEqualToString:NSLinguisticTagAdjective]) {
        
        return [UIColor magentaColor];
    }
    // 代名詞
    else if ([linguisticTag isEqualToString:NSLinguisticTagPronoun]) {
        
        return [UIColor orangeColor];
    }
    
    return [UIColor whiteColor];
}
```

次のように`enumerateTagsInRange:scheme:options:usingBlock:`メソッドのBlocks内でタグに応じた色分けを実施します。

```objc
// スキーム
NSArray *schemes = @[NSLinguisticTagSchemeLexicalClass];

// NSLinguisticTaggerオブジェクトを生成
NSLinguisticTagger *tagger = [[NSLinguisticTagger alloc] initWithTagSchemes:schemes
                                                                    options:0];

// 処理対象テキスト
NSString *targetText = self.textView.text;
[tagger setString:targetText];

// テキストに色をつけるためにNSMutableAttributedStringを生成
NSMutableAttributedString *formatted;
formatted = [[NSMutableAttributedString alloc] initWithString:targetText];

// トークンのタグを取得開始
[tagger enumerateTagsInRange:NSMakeRange(0, targetText.length)
                      scheme:NSLinguisticTagSchemeLexicalClass
                     options:0
                  usingBlock:
 ^(NSString *tag, NSRange tokenRange, NSRange sentenceRange, BOOL *stop) {
     
     // タグに応じた色分け
     UIColor *color = [self colorForAtteributeForLinguisticTag:tag];
     [formatted addAttribute:NSForegroundColorAttributeName
                       value:color
                       range:tokenRange];
 }];

// 色分けされたテキストを表示
self.textView.attributedText = formatted;
````

![品詞による色分け](http://f.cl.ly/items/3b2a0u3F2v3U1B402d0w/LinguisticTagger1_r1_c1.png)


##地名、個人名、組織名で区分する

スキームとして`NSLinguisticTagSchemeNameType`を使用します。他は品詞による区分の場合と同じ手順でOKです。

![名詞の種別による色分け](http://f.cl.ly/items/03332x3u2t0p2N1r0F2v/LinguisticTagger2_r1_c1.png)



##スキーム種別

上で使用した **`NSLinguisticTagSchemeLexicalClass`, `NSLinguisticTagSchemeNameType`** 以外に、次のようなスキームが用意されています。

- **NSLinguisticTagSchemeTokenType**：「単語」「区切り文字」「ホワイトスペース」「その他」で分解する
- **NSLinguisticTagSchemeLemma**：各単語の原形をタグとして取得する
- **NSLinguisticTagSchemeLanguage**：言語名（"ja", "en",...）をタグとして取得する
- **NSLinguisticTagSchemeScript**：書き文字種別（"Jpan", "Latn",...）をタグとして取得する
- **NSLinguisticTagSchemeNameTypeOrLexicalClass**：`NSLinguisticTagSchemeLexicalClass`または`NSLinguisticTagSchemeNameType`にある属性で分解する




##言語ごとの対応スキームを確認する

`availableTagSchemesForLanguage:`メソッドを使用すると、引数に指定した言語で利用できるスキームの配列（NSArray型）を取得することができます。

```objc
NSArray *schemes = [NSLinguisticTagger availableTagSchemesForLanguage:@"ja"];

NSLog(@"schemes:%@", schemes);
```

上記コードの実行結果は、

```objc
schemes:(
    TokenType,
    Language,
    Script
)
```

となりました。（iPhone 6.1 Simulatorで実行）

つまり、"ja"（日本語）で利用できるスキームは`NSLinguisticTagSchemeTokenType`, `NSLinguisticTagSchemeLanguage`, `NSLinguisticTagSchemeScript`の3種のみであることがわかります。

一方、引数に "en"（英語）を指定しての実行結果は、次のようになりました。

```objc
schemes:(
    TokenType,
    Language,
    Script,
    Lemma,
    LexicalClass,
    NameType,
    NameTypeOrLexicalClass
)
```

**英語ではすべてのスキームが利用可能**であることがわかります。

##日本語の形態素解析を行う

日本語は英語と違い、単語の間がスペースで区切られていません。そこで自然言語処理を行う際のベースとなる処理として、「形態素解析」という、文を形態素（言語で意味を持つ最小単位）に分解する処理が必要となります。

前項で`availableTagSchemesForLanguage:`メソッドを用いて調べたとおり、英語と比較すると日本語の対応スキームは少ないですが、`NSLinguisticTagSchemeTokenType`には対応しているので、単語への分解が可能です。すなわち、`NSLinguisticTagger`を用いて**日本語の形態素解析**を行うことができます。

スキームに`NSLinguisticTagSchemeTokenType`を指定し、`string`プロパティに日本語の文字列を渡すだけ、つまり英語の場合と実装方法は同じなので、詳細な説明は省略します。


##NSStringのカテゴリを利用する

NSLinguisticTagger.h には、NSString のカテゴリも定義されており、**NSLinguisticTaggerを使用せず NSStringだけ**でも同様の処理ができるようになっています。

NSStringに追加された`enumerateLinguisticTagsInRange:scheme:options:orthography:usingBlock:`というメソッドを使用します。使い方は`NSLinguisticTagger`とほぼ同様です。


```objc
NSString *targetText = self.textView.text;

[targetText enumerateLinguisticTagsInRange:NSMakeRange(0, targetText.length)
                              scheme:NSLinguisticTagSchemeLexicalClass
                             options:0
                         orthography:nil
                          usingBlock:
 ^(NSString *tag, NSRange tokenRange, NSRange sentenceRange, BOOL *stop) {
     
     // トークン取得時の処理
 }];
````

##サンプルコード

Githubにアップしてあります。

[LinguisticTaggerSample](https://github.com/shu223/LinguisticTaggerSample)

##参考資料

- [詳解iOS5プログラミング](http://www.amazon.co.jp/exec/obidos/ASIN/4798032034/22301-22/ref=nosim/)
- [NSLinguisticTagger Class Reference](http://developer.apple.com/library/ios/#documentation/cocoa/reference/NSLinguisticTagger_Class/Reference/Reference.html)