WWDC2017のNatural Language Processing and your Appsで発表されたように、NLP APIが強化されているので試しにいろいろいじってみました。
NLP APIにおける自然言語処理の流れなどを知りたい場合はこちらの記事を御覧ください。
利用可能なタグスキームの取得
利用可能なタグスキームは以下のメソッドで取得することができます。language
のところにはja
などの言語コードを指定します。
class func availableTagSchemes(for unit: NSLinguisticTaggerUnit, language: String)
-> [NSLinguisticTagScheme]
ちなみに利用可能なスキームをユニット単位で知りたいのではなく、言語単位で知りたい場合は以下のメソッドを使うことができます。これから例に挙げるサンプルコードではわかりやすいようにtagSchemes
の引数にいちいち1個ずつスキームを設定していますが、上記メソッドを使えばある言語に対して利用可能なタグスキームを一気に設定することができます。
class func availableTagSchemes(forLanguage language: String) -> [NSLinguisticTagScheme]
言語判定
言語判定を行うことができるdominantLanguageはiOS 11から登場しました。言語が判定できれば言語コードのStringが、判定できなければund
という文字列が返ってきます。
let tagger = NSLinguisticTagger(tagSchemes: [.language], options: 0)
tagger.string = "Die Kleinen haben friedlich zusammen gespielt"
let language = tagger.dominantLanguage // -> "de"
let locale = Locale(identifier: "ja_JP")
locale.localizedString(forLanguageCode: language!)! // -> "ドイツ語"
このdominantLanguage
、返り値の型はString?なのでnilが返ってくることもあります。もはや言語とはいえない文字列をセットするとnilが返ってきました。
例:*;+
を設定
→nilが返ってきた
ここで意地悪をしてみます。Hello
という文字列は英語やドイツ語、その他の言語で使われている語です。これを単独で投入するとどのようなことが起きるでしょうか。
let tagger = NSLinguisticTagger(tagSchemes: [.language], options: 0)
tagger.string = "Hello"
let language = tagger.dominantLanguage // -> "it"
一番可能性が高いと判断された言語が返ってきています。2ヶ国語が混ざっている場合も、2つの言語が返ってくるわけではなく、可能性の高い1言語だけです。
例:NSLinguisticTagger provides text processing APIs.\n NSLinguisticTagger 是苹果的文字理处平台。
を設定。
→"en"で返ってきた
このようなことがあるので、WWDCのセッションでは「入力するテキストの言語が予めわかってるんだったら明示的に指定してあげて」と言っています。
ちなみにdominantLanguageはクラスメソッドも存在しているので、taggerを作成しなくてもいい場合はこちらを使っても良いです。
let hoge = NSLinguisticTagger.dominantLanguage(for: "NSLinguisticTagger provides text processing APIs.")
// -> "en"
トークン化
テキストをトークン単位で分割するにはNSLinguisticTagger
のtagSchemes
にtokenType
を指定します。
import Foundation
let tagger = NSLinguisticTagger(tagSchemes: [.tokenType], options: 0)
let text = "NSLinguisticTagger provides text processing APIs.\n NSLinguisticTagger 是苹果的文字理处平台。"
tagger.string = text
let range = NSRange(location: 0, length: text.utf16.count)
let options: NSLinguisticTagger.Options = [.omitPunctuation, .omitWhitespace]
tagger.enumerateTags(in: range, unit: .word, scheme: .tokenType, options: options) { tag, tokenRange, stop in
let token = (text as NSString).substring(with: tokenRange)
print(token)
}
// -> NSLinguisticTagger
// -> provides
// -> text
// -> processing
// -> APIs
// -> NSLinguisticTagger
// -> 是
// -> 苹果
// -> 的
// -> 文字
// -> 理
// -> 处
// -> 平台
日本語でやってみましょう。2つの文を投げてみました。精度としてはまあまあなのかなあというかんじです。
// -> 太陽
// -> フレア
// -> の
// -> せい
// -> で
// -> やる気
// -> が
// -> 出
// -> ない
// -> 今日
// -> もやっ
// -> て
// -> いく
品詞タグ付け
WWDCセッション内では説明がなかったのですが、lexicalClass
で品詞を抽出することができたのでその例を載せます。
let tagger = NSLinguisticTagger(tagSchemes: [.lexicalClass], options: 0)
let text = "Tim Cook is the CEO of Apple Inc. which is located in Cupertino, California"
tagger.string = text
let range = NSRange(location: 0, length: text.utf16.count)
tagger.enumerateTags(in: range, scheme: .lexicalClass, options: NSLinguisticTagger.Options(rawValue: 0))
{ tag, tokenRange, stop, _ in
let token = (text as NSString).substring(with: tokenRange)
print("\(tag!._rawValue)\t \(token)")
}
// -> Noun Tim
// -> Whitespace
// -> Noun Cook
// -> Whitespace
// -> Verb is
// -> Whitespace
// -> Determiner the
// -> Whitespace
// -> Noun CEO
// -> …(省略)
特定の品詞の単語だけ取り出したいときはこのような書き方もできます。動詞と名詞を取り出してみます。
let text = "Mary spent wonderful afternoon in America."
let tagger = NSLinguisticTagger(tagSchemes: [.lexicalClass], options: 0)
tagger.string = text
let options: NSLinguisticTagger.Options = [.omitPunctuation, .omitWhitespace, .joinNames]
let range = NSRange(location: 0, length: text.utf16.count)
let tags: [NSLinguisticTag] = [.verb, .noun]
tagger.enumerateTags(in: range, unit: .word, scheme: .lexicalClass, options: options) { tag, tokenRange, _ in
if let tag = tag, tags.contains(tag) {
let token = (text as NSString).substring(with: tokenRange)
print("token = \(token), tag = \(tag._rawValue)")
}
}
// -> token = Mary, tag = Noun
// -> token = spent, tag = Verb
// -> token = afternoon, tag = Noun
// -> token = America, tag = Noun
ダメ元で今日はいい天気
という日本語の文字列を入れてみたところ、出力はされましたが正しい結果ではありませんでした。
// -> OtherWord 今日
// -> OtherWord は
// -> OtherWord いい
// -> OtherWord 天気
レンマ化
テキストからレンマを抽出するにはNSLinguisticTagger
のtagSchemes
にlemma
を指定します。
import Foundation
let tagger = NSLinguisticTagger(tagSchemes: [.lemma], options: 0)
let text = "I spent wonderful afternoon in Marin Country."
tagger.string = text
let range = NSRange(location: 0, length: text.utf16.count)
let options: NSLinguisticTagger.Options = [.omitPunctuation, .omitWhitespace]
let tags: [NSLinguisticTag] = [.personalName, .placeName, .organizationName]
tagger.enumerateTags(in: range, unit: .word, scheme: .lemma, options: options) { tag, tokenRange, stop in
if let lemma = tag?.rawValue {
print(lemma)
}
}
// -> I
// -> spend
// -> wonderful
// -> afternoon
// -> in
// -> Marin
// -> country
過去形spent
が現在形spend
になっており、見出し語がちゃんと抽出できています。また、文頭の大文字W
は小文字になっていて、固有名詞Marin
の頭のM
は大文字のままなところがちゃんと仕事してる感あります。
こちらも日本語で試してみたいところなのですが、レンマ化、品詞タグ付け、固有表現抽出に関しては英語、フランス語、イタリア語、ドイツ語、スペイン語、ポルトガル語、ロシア語、トルコ語の8ヶ国語までしか対応していません。日本語をセットしてPlaygroundで動かしてみようとしましたが何も出力されませんでした。
固有表現抽出
テキストから固有表現を抽出するにはNSLinguisticTagger
のtagSchemes
にnameType
を指定します。
let text = "Tim Cook is the CEO of Apple Inc."
let tagger = NSLinguisticTagger(tagSchemes: [.nameType], options: 0)
tagger.string = text
let options: NSLinguisticTagger.Options = [.omitPunctuation, .omitWhitespace, .joinNames]
let range = NSRange(location: 0, length: text.utf16.count)
let tags: [NSLinguisticTag] = [.personalName, .placeName, .organizationName]
tagger.enumerateTags(in: range, unit: .word, scheme: .nameType, options: options)
{ tag, tokenRange, _ in
if let tag = tag, tags.contains(tag) {
let token = (text as NSString).substring(with: tokenRange)
print("token = \(token), tag = \(tag._rawValue)")
}
}
// -> token = Tim Cook, tag = PersonalName
// -> token = CEO, tag = PersonalName
// -> token = Apple Inc., tag = OrganizationName
joinNames
というオプションを設定しているので複数単語の固有表現があった場合は1つの塊として抽出することができます。
こちらも懲りずに日本語を設定してみましたが、何も出力されませんでした。