NSLinguisticTaggerを用いると、自然言語のテキストを品詞(名詞、動詞、代名詞)や「個人名」「地名」といった属性で区分(トークンに分解)することができます。日本語の形態素解析も可能です。
使い方は非常にシンプルで、基本的な手順は
- スキームを引数に渡してNSLinguisticTaggerオブジェクトを生成
- 処理対象テキストをセット
- トークン分解開始
の3ステップです。
// スキーム
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);
 }];
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 で取得できるタグに応じて色を決定するメソッドを実装しておき、
- (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内でタグに応じた色分けを実施します。
// スキーム
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;
地名、個人名、組織名で区分する
スキームとしてNSLinguisticTagSchemeNameTypeを使用します。他は品詞による区分の場合と同じ手順でOKです。
スキーム種別
上で使用した NSLinguisticTagSchemeLexicalClass, NSLinguisticTagSchemeNameType 以外に、次のようなスキームが用意されています。
- NSLinguisticTagSchemeTokenType:「単語」「区切り文字」「ホワイトスペース」「その他」で分解する
- NSLinguisticTagSchemeLemma:各単語の原形をタグとして取得する
- NSLinguisticTagSchemeLanguage:言語名("ja", "en",...)をタグとして取得する
- NSLinguisticTagSchemeScript:書き文字種別("Jpan", "Latn",...)をタグとして取得する
- 
NSLinguisticTagSchemeNameTypeOrLexicalClass:NSLinguisticTagSchemeLexicalClassまたはNSLinguisticTagSchemeNameTypeにある属性で分解する
言語ごとの対応スキームを確認する
availableTagSchemesForLanguage:メソッドを使用すると、引数に指定した言語で利用できるスキームの配列(NSArray型)を取得することができます。
NSArray *schemes = [NSLinguisticTagger availableTagSchemesForLanguage:@"ja"];
NSLog(@"schemes:%@", schemes);
上記コードの実行結果は、
schemes:(
    TokenType,
    Language,
    Script
)
となりました。(iPhone 6.1 Simulatorで実行)
つまり、"ja"(日本語)で利用できるスキームはNSLinguisticTagSchemeTokenType, NSLinguisticTagSchemeLanguage, NSLinguisticTagSchemeScriptの3種のみであることがわかります。
一方、引数に "en"(英語)を指定しての実行結果は、次のようになりました。
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とほぼ同様です。
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にアップしてあります。



