console.blog(self);

技術、読んだ本、いろいろ。

Cloud Natural Language APIを使ってみた、というか自然言語処理について調べてみた

12/24に書いているけど、Goodpatch Advent Calendar 2017の3日目の記事です!いろいろあるよね!

Cloud Natural Language APIはこれ。

最近ハードに英語を勉強していたら、品詞とかに詳しくなった。英語の主な品詞はこんな感じ。

  • 名詞
  • 代名詞
  • 動詞
  • 形容詞
  • 副詞
  • 前置詞
  • 接続詞
  • 間投詞

こちらが分かりやすかった。以下の語尾の部分などは記事からの引用。

名詞によくある語尾

  • -tion: information(情報)
  • -sion: decision (意思決定)
  • -ity: identity (アイデンティティ)
  • -ty: beauty (美しさ)
  • -th: health (健康)
  • -ment: supplement (サプリメント)
  • -ness: kindness (やさしさ)
  • -ance: announce (アナウンス)
  • -ence: entrance (入り口)
  • -cy: policy (ポリシー)
  • -er: employer (雇用主)
  • -or: doctor (博士)
  • -ee: employee (被雇用者)

動詞によくある語尾

  • -en: broaden (広げる)
  • -fy: notify (知らせる)
  • -ize: prioritize (優先する)
  • en(語頭): enpower (力を与える)

形容詞によくある語尾

  • -able/-ible: possible(可能な), accessible(アクセス可能な)
  • -al: financial(財務の)
  • -ant: brilliant(素晴らしい)
  • -ent: independent(独立した)
  • -ar: similar(似た)
  • -ed: limited(限定的な)
  • -ful: successful(成功した)
  • -ic/-ical: basic(基本的な), economical(経済的な)
  • -ive: massive(巨大な), active(活動的な)
  • -less: useless(使えない), helpless(希望のない)
  • -ory: contributory(一因となる)
  • -ous: enormous(巨大な)
  • -y: busy(忙しい)

副詞によくある語尾

副詞は、形容詞の語尾に-lyをつけるだけ

  • financial+”-ly”=financially (財務に)

ただ名詞に ly がついていると形容詞になる。

  • costly 形容詞

Cloud Natural Language API

こんなの人間が判断するのだるいので。

構文解析

トークンと文の抽出、品詞(PoS)の特定、各文の係り受け解析ツリーの作成が可能です。

エンティティ認識

エンティティとラベル(人、組織、場所、イベント、商品、メディアなど)を特定できます。

感情分析

テキストのブロック内で示されている全体的な感情を読み取ることができます。

コンテンツの分類

事前定義された 700 以上のカテゴリでドキュメントを分類できます。

多言語

さまざまな言語のテキストを簡単に分析できます。英語、スペイン語、日本語、中国語(簡体字および繁体字)、フランス語、ドイツ語、イタリア語、韓国語、ポルトガル語に対応しています。

自然言語処理

そもそも、自然言語処理とは。

自然言語処理(しぜんげんごしょり、英語: natural language processing、略称:NLP)は、人間が日常的に使っている自然言語をコンピュータに処理させる一連の技術であり、人工知能言語学の一分野である。

自然言語処理 - Wikipedia

形態素解析

文を形態素に分割する。

形態素解析(けいたいそかいせき、Morphological Analysis)とは、文法的な情報の注記の無い自然言語のテキストデータ(文)から、対象言語の文法や、辞書と呼ばれる単語の品詞等の情報にもとづき、形態素(Morpheme, おおまかにいえば、言語で意味を持つ最小単位)の列に分割し、それぞれの形態素の品詞等を判別する作業である。

形態素解析 - Wikipedia

形態素とは。

形態素(けいたいそ、英: morpheme)とは、言語学の用語で、意味をもつ表現要素の最小単位。ある言語においてそれ以上分解したら意味をなさなくなるところまで分割して抽出された、音素のまとまりの1つ1つを指す。形態素の一般的な性質や、形態素間の結びつきなどを明らかにする言語学の領域は、形態論と呼ばれる。

形態素 - Wikipedia

日本語はいろいろ大変。

わかち書き(わかちがき)とは、文章において語の区切りに空白を挟んで記述することである。分かち書き・別ち書きとも表記する。

わかち書き - Wikipedia

日本語形態素解析の裏側を覗く!MeCab はどのように形態素解析しているか - クックパッド開発者ブログ

構文解析

形態素間の関連を分析する。

構文解析(こうぶんかいせき、syntactic analysis あるいは parse)とは、文章、具体的にはマークアップなどの注記の入っていないベタの文字列を、自然言語であれば形態素に切分け、さらにその間の関連(修飾-被修飾など)といったような、統語論的(構文論的)な関係を図式化するなどして明確にする(解析する)手続きである。

構文解析 - Wikipedia

語義の曖昧性解消

いろんな意味があるので、それを解消する。

語義の曖昧性解消(ごぎのあいまいせいかいしょう、英語: Word-sense disambiguation)とは自然言語処理において、文中のある単語に出会ったとき、その単語がどの語義をあらわしているのかを判断する過程のこと。語義識別、語義判別、語義確定などともいう。

語義の曖昧性解消 - Wikipedia

やばい。「のめり込みそうである」って語義が来年の広辞苑の改訂で追加される。

①身に危険が迫るさま。あぶない。 「 - ・いぞ,逃げろ」 ②不都合が予想される。 「この成績では-・いな」 ③若者言葉で,すごい。自身の心情が,ひどく揺さぶられている様子についていう。 「この曲,-・いよ」 〔若者言葉では「格好良い」を意味する肯定的な文脈から,「困った」を意味する否定的文脈まで,広く感動詞的に用いられる〕

やばいとは - 日本語表現辞典 Weblio辞書

照応解析

「あれ」とかが何を示すのか推定する。

照応解析(しょうおうかいせき、英: reference resolution)とは、照応詞(代名詞や指示詞など)の指示対象を推定したり、省略された名詞句(ゼロ代名詞)を補完する処理のこと。照応は文と文の間にまたがった構造なので、照応解析は談話解析の一種である。

照応解析 - Wikipedia

Universal Dependencies

形態論と依存関係ツリー

形態論とは、単語の内部構造と、単語の形成および変更の仕組みについて研究したものです。形態論は、1 つの単語の構成要素(語幹、語素、接頭辞、接尾辞など)がどのように配置または変更されて、さまざまな意味を持つようになるかに注目しています。Natural Language API は、提供された単語について、形態素解析を利用して文法情報を推測します。

形態論は構文とは別のものであることに注意してください(相互に影響し合うことはあります)。たとえば、英語の未来形は「I will get my umbrella」のように、動詞の前に単語「will」を付加することで表現されます。一方、形態論的には「will」も「get」も単語自体は変化していないので、単語自体が未来形を示しているわけではなく、未来形は構文規則で示されています。ただし、言語によっては単語を直接変更して未来形動詞とするものもあります(数多くあります)。

https://cloud.google.com/natural-language/docs/morphology?hl=ja

Universal Dependencies

Universal Dependencies (UD) is a framework for cross-linguistically consistent grammatical annotation and an open community effort with over 200 contributors producing more than 100 treebanks in over 60 languages.

http://universaldependencies.org/

つかってみた

構文解析してみた。

This is really a good pen.

f:id:sadah:20171224111918p:plain

APIたたくだけだとこんな感じ。

sentences:<text:<content:"This is really a good pen." > >

tokens:<
  text:<content:"This" >
  part_of_speech:<tag:DET number:SINGULAR >
  dependency_edge:<head_token_index:1 label:NSUBJ >
  lemma:"This"
> 

tokens:<
  text:<content:"is" begin_offset:5 > 
  part_of_speech:<tag:VERB mood:INDICATIVE number:SINGULAR person:THIRD tense:PRESENT >
  dependency_edge:<head_token_index:1 label:ROOT > 
  lemma:"be"
> 

tokens:<
  text:<content:"really" begin_offset:8 > 
  part_of_speech:<tag:ADV > 
  dependency_edge:<head_token_index:1 label:ADVMOD > 
  lemma:"really"
> 

tokens:<
  text:<content:"a" begin_offset:15 > 
  part_of_speech:<tag:DET > 
  dependency_edge:<head_token_index:5 label:DET > 
  lemma:"a"
> 

tokens:<
  text:<content:"good" begin_offset:17 >
  part_of_speech:<tag:ADJ >
  dependency_edge:<head_token_index:5 label:AMOD >
  lemma:"good"
> 

tokens:<
  text:<content:"pen" begin_offset:22 > 
  part_of_speech:<tag:NOUN number:SINGULAR > 
  dependency_edge:<head_token_index:1 label:ATTR > 
  lemma:"pen"
> 

tokens:<
  text:<content:"." begin_offset:25 > 
  part_of_speech:<tag:PUNCT > 
  dependency_edge:<head_token_index:1 label:P > 
  lemma:"."
>

document_sentiment:<> language:"en" 

そのままだとさっぱりわからないので、頑張って日本語にした。

package main

import (
    language "cloud.google.com/go/language/apiv1"
    "fmt"
    "golang.org/x/net/context"
    languagepb "google.golang.org/genproto/googleapis/cloud/language/v1"
    "log"
    "strings"
)

func main() {
    ctx := context.Background()

    // Creates a client.
    client, err := language.NewClient(ctx)
    if err != nil {
        log.Fatalf("Failed to create client: %v", err)
    }

    // Sets the text to analyze.
    text := "This is really a good pen."
    //text := "At the end of the ball, Kennedy spoke to thank Sinatra on the festivities and his support of the Democratic Party throughout his life and the 1960 campaign, adding 'The happy relationship between the arts and politics which has characterized our long history I think reached culmination tonight.'"
    annotateTextResponse, _ := analyzeSyntax(ctx, client, text)
    sentences := annotateTextResponse.Sentences
    tokens := annotateTextResponse.Tokens

    for _, sentence := range sentences {
        fmt.Printf("%v\n\n", sentence)
    }

    for _, token := range tokens {
        fmt.Printf("%v\n", token.Text)
        fmt.Printf("%v\n", replacePartOfSpeech(fmt.Sprintf("%v", token.PartOfSpeech)))
        fmt.Printf("%v\n", token.DependencyEdge)
        fmt.Printf("\n")
    }
}

func analyzeSyntax(ctx context.Context, client *language.Client, text string) (*languagepb.AnnotateTextResponse, error) {
    return client.AnnotateText(ctx, &languagepb.AnnotateTextRequest{
        Document: &languagepb.Document{
            Source: &languagepb.Document_Content{
                Content: text,
            },
            Type: languagepb.Document_PLAIN_TEXT,
        },
        Features: &languagepb.AnnotateTextRequest_Features{
            ExtractSyntax: true,
        },
        EncodingType: languagepb.EncodingType_UTF8,
    })
}

// https://cloud.google.com/natural-language/docs/reference/rest/v1beta2/Token?hl=ja#Tag
func replacePartOfSpeech(str string) string {
    oldnewTag := []string{
        "NOUN", "名詞(一般名詞 or 固有名詞)",
        "VERB", "動詞",
        "ADJ", "形容詞",
        "ADV", "副詞",
        "PRON", "代名詞",
        "CONJ", "接続詞",
        "DET", "限定詞",
        "ADP", "接置詞",
        "UNKNOWN", "不明",
        "NUM", "数詞",
        "PRT", "接辞",
        "AFFIX", "接辞?",
        "PUNCT", "句読点",
        "X", "その他",
    }
    oldnewCase := []string{
        "CASE_UNKNOWN", "格不明",
        "ACCUSATIVE", "対格(直接目的語)",
        "ADVERBIAL", "副詞(形容詞の副詞形)",
        "COMPLEMENTIVE", "補語",
        "DATIVE", "与格(間接目的語または直接目的語)",
        "GENITIVE", "属格(所有格)",
        "INSTRUMENTAL", "具格(名詞が行為の手段であるかどうか)",
        "LOCATIVE", "処格(場所を推測する単語の使用)",
        "NOMINATIVE", "主格(動詞の主語)",
        "OBLIQUE", "限定的用法(動詞または前置詞に対する目的語)",
        "PARTITIVE", "部分詞",
        "PREPOSITIONAL", "前置詞の目的語",
        "REFLEXIVE_CASE", "再帰格",
        "RELATIVE_CASE", "動詞または形容詞と名詞を結びつける関係詞節の補部",
        "VOCATIVE", "呼格(相手に呼びかけるときに使用する名詞)",
    }
    oldnewPerson := []string{
        "PERSON_UNKNOWN", "人称不明",
        "FIRST", "一人称",
        "SECOND", "二人称",
        "THIRD", "三人称",
        "REFLEXIVE_PERSON", "再帰代名詞",
    }
    oldnewNumber := []string{
        "NUMBER_UNKNOWN", "数不明",
        "SINGULAR", "単数",
        "PLURAL", "複数",
        "DUAL", "2数",
    }
    oldnewTense := []string{
        "TENSE_UNKNOWN", "時制不明",
        "CONDITIONAL_TENSE", "条件法",
        "FUTURE", "未来形",
        "PAST", "過去形",
        "PRESENT", "現在形",
        "IMPERFECT", "過去進行形",
        "PLUPERFECT", "過去完了形",
    }
    oldnewAspect := []string{
        "ASPECT_UNKNOWN", "相不明",
        "PERFECTIVE", "完了相",
        "IMPERFECTIVE", "未完了相",
        "PROGRESSIVE", "進行相",
    }
    oldnewMood := []string{
        "MOOD_UNKNOWN", "法不明",
        "CONDITIONAL_MOOD", "条件法",
        "IMPERATIVE", "命令法",
        "INDICATIVE", "現実法",
        "INTERROGATIVE", "疑問法",
        "JUSSIVE", "命令法",
        "SUBJUNCTIVE", "接続法",
    }
    oldnewProper := []string{
        "PROPER_UNKNOWN", "PROPER不明",
        "PROPER", "固有名詞の一部",
        "NOT_PROPER", "固有名詞の一部ではない",
    }
    oldnew := append(oldnewTag, oldnewCase...)
    oldnew = append(oldnew, oldnewPerson...)
    oldnew = append(oldnew, oldnewNumber...)
    oldnew = append(oldnew, oldnewTense...)
    oldnew = append(oldnew, oldnewAspect...)
    oldnew = append(oldnew, oldnewMood...)
    oldnew = append(oldnew, oldnewProper...)
    r := strings.NewReplacer(oldnew...)
    return r.Replace(str)
}

こんな出力になる。

text:<content:"This is really a good pen." > 

content:"This" 
tag:限定詞 number:単数 
head_token_index:1 label:NSUBJ 

content:"is" begin_offset:5 
tag:動詞 mood:現実法 number:単数 person:三人称 tense:現在形 
head_token_index:1 label:ROOT 

content:"really" begin_offset:8 
tag:副詞 
head_token_index:1 label:ADVMOD 

content:"a" begin_offset:15 
tag:限定詞 
head_token_index:5 label:DET 

content:"good" begin_offset:17 
tag:形容詞 
head_token_index:5 label:AMOD 

content:"pen" begin_offset:22 
tag:名詞(一般名詞 or 固有名詞) number:単数 
head_token_index:1 label:ATTR 

content:"." begin_offset:25 
tag:句読点 
head_token_index:1 label:P 

f:id:sadah:20171224111949p:plain

nsubjとかを調べてみると、こんな感じのことが書いてあった。むずい。

nsubj: nominal subject:

nsubjは節の構文的な主語である名詞節を示す。この関係のガバナーは常に動詞とは限らない。もし動詞が継動詞(copular verb)である場合、その節のルート(根)はその継動詞の補語になる。

attr: attribute:

be動詞やseem、appear、becomeなどのようにイコールの関係で結ぶ動詞(これを継詞、連結詞などと呼ぶ)に対して、それの補語。 たとえば   "What is that?"   attr (is, What)

advmod: adverbial modifier:

ある語に対するadvmodは、(節でない)RBかADVPで、その語の意味を修飾する。 たとえば   "Genetically modied food"   advmod(modied, genetically)   "less often"   advmod(often, less)

det: determiner: determiner

はNPのヘッドとそれのdeterminerとの関係を示す。たとえば   "The man is here"   det(man, the)

amod: adjectival modifier:

NPに対する形容的な句で、NPの意味を修飾する。たとえば   "Sam eats red meat"   amod(meat, red)

punct: punctuation:

punctは、節の句読点のうち、もしその句読点がtyped dependencyの中で保持されているなら、それを表す。但しデフォルトでは、句読点では出力に保持されていない。   "Go home!"   punct(Go, !)


まとめ

自然言語処理言語学は違う。真面目に言語学に取り組むよりは、英語勉強したほうが楽。

参考文献

Cloud Natural Language API

Universal Dependencies

形態素解析 / 言語学

その他