chooblarin’s blog

NSLinguistic​Taggerで遊ぶ

August 14, 2016

  • #Swift

"Natural Language Processing with Swift"という素晴らしいトークを拝見しました.トークの中では,ナイーブベイズ分類器を用いた簡易のスパムフィルタの実装が説明されています. その中でNSLinguistic​Taggerという便利なクラスを知り,気になったので触ってみました.(環境は Xcode 7.3.1, Swift 2.2 です.)

初期化する

NSLinguistic​Taggerの初期にはtagSchemesoptionsを渡します. 型はそれぞれ[String]Intです.

NSLinguistic​Taggerはなかなか多機能なやつで,tagSchemesには用途に応じたパラメータを渡すことになります.これは後ほど調べるとして,今はとにかくテキトーに初期化してみます.

let tagger = NSLinguisticTagger(tagSchemes: [NSLinguisticTagSchemeLexicalClass], options: 0)

tagSchemesは LexicalClass,optionsは特に指定せずにインスタンスを取得出来ました.

試す

先ほど初期化したNSLinguistic​Taggerの簡単な使用例を見ていきます.

let sentence = "I'm Spider-man."
let range = NSRange(location: 0, length: sentence.characters.count)

tagger.string = sentence

tagger.enumerateTagsInRange(
    range,
    scheme: NSLinguisticTagSchemeLexicalClass,
    options: .OmitWhitespace) { tag, tokenRange, _, _ in
        let start = sentence.startIndex.advancedBy(tokenRange.location)
        let end = sentence.startIndex.advancedBy(tokenRange.location + tokenRange.length)

        let token = sentence.substringWithRange(start ..< end)
        print("\(token) (\(tag))")
    }

出力結果は以下の通り.()の中身がtaggerによって付加された tag です.

I (Pronoun)
'm (Verb)
Spider (Noun)
- (Dash)
man (Noun)
. (SentenceTerminator)

このように文章の品詞分解が出来ました.

Tagging Schemes

Scheme の指定を変更することで様々な結果を得ることが出来ます.(上記の例では LexicalClass を指定しました.)

英語のテキストに対して指定可能な Scheme の一覧は以下のように取得出来ます.

let availableSchemes = NSLinguisticTagger.availableTagSchemesForLanguage("en")
// => ["TokenType", "Language", "Script", "Lemma", "LexicalClass", "NameType", "NameTypeOrLexicalClass"]

試しに別な Scheme を指定して,先ほどのコードを実行してみます.(結果のみ)

TokenType

I (Word)
'm (Word)
Spider (Word)
- (Punctuation)
man (Word)
. (Punctuation)

NameType

I (OtherWord)
'm (OtherWord)
Spider (OtherWord)
- (Dash)
man (OtherWord)
. (SentenceTerminator)

Language

I (en)
'm (en)
Spider (en)
- ()
man (en)
. ()

と,このように異なる tagging がされていることが分かります.

Tag Schemes の一覧は公式ドキュメントを確認して下さい.

Options

上記の例では OmitWhitespace を指定していたので空白の除去が行われていました.

Options の一覧は公式ドキュメントを確認して下さい.

句読点の除去を行う OmitPunctuation や"New York"などを一単語として扱う JoinNames などがあります.

複数の option を指定したい場合は or 演算|を行います.

let optionsRawValue: UInt =
NSLinguisticTaggerOptions.OmitWhitespace.rawValue | NSLinguisticTaggerOptions.JoinNames.rawValue

let optionsInt = Int(optionsRawValue)
let options = NSLinguisticTaggerOptions(rawValue: optionsRawValue)

遊ぶ

では最後にサンプルコードを掲載します.Playground でそのまま実行出来るはずです.

import Foundation

extension NSRange {
    func rangeForString(string: String) -> Range<String.Index>? {
        guard location != NSNotFound else { return nil }
        let start = string.startIndex.advancedBy(location)
        let end = string.startIndex.advancedBy(location + length)
        return start ..< end
    }
}

let sentence1 = "With great power comes great responsibility."
let sentence2 = "In critical moments, men sometimes see exactly what they wish to see."
let sentence3 = "He can make the choice that no one else can make, the right choice."

let availableSchemes = NSLinguisticTagger.availableTagSchemesForLanguage("en")
let optionsRawValue: UInt =
    NSLinguisticTaggerOptions.OmitWhitespace.rawValue | NSLinguisticTaggerOptions.JoinNames.rawValue

let tagger = NSLinguisticTagger(tagSchemes: availableSchemes, options: Int(optionsRawValue))

for sentence in [sentence1, sentence2, sentence3] {
    tagger.string = sentence

    let range = NSRange(location: 0, length: sentence.characters.count)
    tagger.enumerateTagsInRange(
        range,
        scheme: NSLinguisticTagSchemeNameTypeOrLexicalClass,
        options: NSLinguisticTaggerOptions(rawValue: optionsRawValue)) {
            tag, tokenRange, sentenceRange, stop in
            let token = sentence.substringWithRange(tokenRange.rangeForString(sentence)!)
            print("\(tag): \(token)")
    }
    print("\n")
}

参考