Google Natural Language API を利用して固有表現とその付加情報(ナレッジグラフなど)を抽出する

本記事では、Googleが提供しているCloud Natural Language API のエンティティ分析を利用して、テキストから人名、地名などの固有表現とその付加情報(ナレッジグラフやWikipediaへのリンクなど)を抽出してみます。

【目次】

[1]はじめに

本記事では、Googleが提供しているCloud Natural Language API を利用したエンティティ分析(Entity Analysis)を取り上げます。

ここで、「エンティティ分析って何なの?」については、Googleのドキュメントに以下のように書かれています。
  • エンティティ分析は、指定されたテキストに既知のエンティティ(著名人、ランドマークなどの固有名詞)が含まれていないかどうかを調べて、それらのエンティティに関する情報を返します。

詳しくは以下のドキュメントを参照して下さい。

そもそも、「これは何に使えるのかな?」という素朴な疑問については、テキストからの情報抽出や情報検索などに重要な技術とされています。

また、今回のエンティティ分析を含むNatural Language AI サービスは、以前の記事で紹介したGoogle Document AI のビルディングブロックの1つでもあります。

個人的な興味としては、テキストに「何が書かれているか」のヒントが多く得られそうだという点です。固有名や数字、日付などの抽出ができて、内容によってはWikipediaやGoogleのナレッジグラフへのリンク情報も得られます。

実を言うと、この記事を書くきっかけになったのは、記事『Vision APIとナレッジグラフの検索で画像認識対象をより深く理解する(Google Knowledge Graph Search API)』を書いたことです。
この記事では、Vision APIの「ランドマーク検出」、「ロゴ検出」、「ラベル検出」では、Googleのナレッジグラフ検索に使える情報(エンティティID)が設定されますが、「テキスト検出(OCR)」では設定されない旨書きました。
考えてみれば、OCRでテキストを抽出したら、それは Natural Language AI で処理しなさい、ということではないかと考えて調べてみた、ということです(笑)。

正直なところ、私は自然言語処理について詳しくありませんので(初学者手前のレベル)、学術的なことは書けません。このため、素人の利用者側の立場から、Natural Language API の使い方と、具体的にどのような情報が得られるか、を中心に書きます。

なお、APIに興味は無く、具体的なエンティティ分析のイメージをつかみたいという方は、まず[5]エンティティ分析例をご覧いただくとよいと思います。

(参考)

専門家の方が本記事を読まれることはないと思いますので(笑)、少し関連情報を書きます。

まず「Natural Language」を直訳すれば自然言語で、英語や日本語のように普段使っている言語のことです。これを機械処理する技術を自然言語処理といいます。

今回取り上げるGoogleのエンティティ分析は、自然言語処理の一分野である固有表現 (Named Entity) に関するサービスだと思います。

Cloud Natural Language APIのエンティティ分析の機能が、その他の固有表現抽出エンジンと比べてどうなのか?は、残念ながら勉強不足でわかりません。

ただ、今回利用する Natural Language API は、学習済みモデルなので素人の私にはとっつきやすかったです(なお、Googleは AutoMLを使ってカスタム学習モデルを作ることもできるようです)。

また、Natural Language AIサービスには、エンティティ分析以外に感情分析、エンティティ感情分析、構文解析、コンテンツの分類の機能も提供されており、これらを一括して処理できるなど、商用サービスならではのメリットがあると思います。

[2]GCPプロジェクトの準備

Google Cloud Natural Language APIを利用するためには、まず、GCP(Google Cloud Platform)での準備作業が必要です。

(注意)
Cloud Natural Language API は有料サービスですので課金にご注意ください。但し、無料枠の設定がありますので、本ブログの内容程度の簡単なテストであれば、無料で試すことができると思います。

<GCPプロジェクトの準備の流れ>
  1. 利用するプロジェクトを決定する。
  2. 課金を有効化
    • プロジェクトの課金が有効になっていることを確認します。
  3. APIの有効化
    • Cloud Console 左側メニューの「APIとサービス/ライブラリ」を選択し、「Cloud Natural Language API」を検索して「Cloud Natural Language API」画面へ進みます。そして「有効にする」ボタンをクリックします。
  4. サービスアカウントの準備
    • サービスアカウントキーを作成して、JSONファイルをダウンロードします。

具体的な手順は以下を参照して下さい。

[3]APIの仕様

ここでは特定のプログラミング言語に依存しない Natural Language API の仕様をざっとみておきます。

特に、APIのレスポンスデータの構造を見ることで、Natural Language API からどのような情報が得られるのかが理解できます。

さて、Natural Language APIは、下図のように LanguageService で提供されており、サービスとクライアントは、 gRPCかREST方式で通信します。

Google Natural Language API/LanguageService

APIのリファレンスは以下にあります。

本記事では、Pythonのクライアントライブラリを利用したAPIの利用サンプルを見ていきますので、gRPCのリファレンス(v1)を中心に見ていきます。(Python クライアントライブラリは、gRPC のラッパーになっています。)

(1)LanguageService の利用

LanguageService には Natural Language API が提供する機能毎にメソッドが定義されています。
  • エンティティ分析:AnalyzeEntities
  • 感情分析:AnalyzeSentiment
  • エンティティ感情分析:AnalyzeEntitySentiment
  • 構文解析:AnalyzeSyntax
  • コンテンツ分類:ClassifyText

加えて、AnnotateText という上記のメソッドを同時に処理できるメソッドも提供されています。(引数のfeaturesフィールドで、利用したい機能を個別に指定できます。)

本記事ではエンティティ分析を取り上げますので、AnalyzeEntities メソッドを見ていきますが、AnnotateTextメソッドで引数の features.extract_entity_sentimentをtrueにして利用しても同じです。

(2)AnalyzeEntities メソッド

エンティティ分析を行うメソッドは AnalyzeEntities メソッドです。

AnalyzeEntitiesメソッドの定義
rpc AnalyzeEntities( AnalyzeEntitiesRequest ) returns ( AnalyzeEntitiesResponse )

考え方はとても簡単で、引数(AnalyzeEntitiesRequest)を渡すと、同期処理して戻り値(AnalyzeEntitiesResponse)を返してくれます。

以下では引数(AnalyzeEntitiesRequest)と戻り値(AnalyzeEntitiesResponse)の内容を簡単に見ていきます。

(3)引数:AnalyzeEntitiesRequest

AnalyzeEntitiesRequest は、AnalyzeEntities メソッドの引数となるデータで、解析対象となる元文書を指定します。

AnalyzeEntitiesRequest

いくつか補足します。
  • (必須)document.typeで、文書タイプを指定する必要があります。
    • PLAIN_TEXT
      • いわゆる通常の文字列だけで構成された文書。
    • HTML
      • HTML形式の文書。
    • Document.TypeにPLAIN_TEXTまたはHTMLを指定しない場合はエラーになります(INVALID_ARGUMENT例外がスローされます)。
  • 文書内容は以下のどちらか一方のフィールドで指定します。
    • document.content
      • 元文書の内容を直接contentに代入して送信します。
    • document.gcs_content_uri
      • もし元文書がGoogle Cloud Storage 上にある場合に、gs://bucket_name/object_name の形式で指定します。
  • encoding_type は、戻り値にあるbegin_offsetフィールドのような、元文書中の文字(開始)位置を特定するためのエンコーディング方法を指定します。(4)の項を参照して下さい。
  • (オプション)Document.language で元文書の言語を指定できます。
    • languageを指定しない場合は、言語が自動認識されます。
    • 明示的に言語を指定する場合、英語は「en」、日本語は「ja」のように指定します。指定できる言語コードは以下のサイトを参照して下さい。

(4)AnalyzeEntitiesRequest.encoding_type

後述しますが、戻りデータ(AnalyzeEntitiesResponse)には、begin_offsetという文字の開始位置を示すフィールドがあります。この文字の開始位置は、文書のエンコーディング方法に依存します。

例えば、JavaやPythonなどの文字列型(String)をもつ言語で、「私はテクノ大福です。」という文字列(String)があるとき、「テクノ大福」の開始位置は 2 となります。一方、「私はテクノ大福です。」という文字列をUTF-8で処理する場合は、開始位置が6となります。

ここでは文字エンコーディングに関する説明は割愛しますが、encoding_type フィールドは、Natural Language APIが、どのエンコーディングをもとに文字位置を計算するか、を指定するものです。
文書をプログラム言語の文字型を使って処理する場合、その言語の文字型の仕様によって設定する値が決まるものといえます。

encoding_typeには以下の値を指定できます。
  • NONE
    • begin_offset フィールドなどの文字位置を利用しない場合に設定します。NONEを指定した場合は、begin_offsetの値は常に-1が設定されます。
  • UTF8
    • UTF-8で文字を扱う場合に指定します。(C++など)
  • UTF16
    • UTF-16で文字を扱う場合に指定します。(Java、JavaScriptなど)
  • UTF32
    • UTF32で文字を扱う場合に指定します。(Pythonなど)
より詳細な説明は、APIリファレンスを参照して下さい。

(5)戻り値:AnalyzeEntitiesResponse

以下は、AnalyzeEntities メソッドの戻り値の概要です。

AnalyzeEntitiesResponse


少し補足します。
  • language
    • リクエストデータのDocument.languageで言語を指定した場合は、その値が設定されます。指定しなかった場合は、自動認識された言語コードが設定されます。(例えば、英語なら「en」、日本語なら「ja」など)
  • 抽出されたエンティティは、entitiesの下にEntity型でリストされます。
  • Entity.name
    • エンティティ名
  • Entity.type
    • PERSON、LOCATIONなどのエンティティのタイプ。(6)を参照して下さい。
  • Entity.salience
    • 顕著性のスコアを表し、0から1.0範囲の値を取ります。
  • Entity.metadata
    • エンティティに関する付加情報。(7)を参照して下さい。
  • Entity.mentions
    • 元文書内でエンティティに言及している位置などのリスト。(8)を参照して下さい。
  • Sentiment
    • APIのデータ構造定義には sentiment フィールドがありますが、エンティティ分析では設定されません。
    • エンティティ感情分析(AnalyzeEntitySentiment メソッドまたは引数の features.extract_entity_sentimentをtrueにしたAnnotateText メソッド)を利用する場合に設定されます。

(6)Entity.type

抽出されたエンティティのタイプが設定されます。Googleのドキュメントの説明では「エンティティのタイプ」と書かれているだけですが、固有表現の分類に対応するものと思います。

例えば、エンティティとして「伊藤博文」が抽出されると、そのタイプには「PERSON(人名)」が設定されます。

Googleのエンティティ分析APIでは以下のタイプが定義されています。
  • UNKNOWN
  • PERSON
  • LOCATION
  • ORGANIZATION
  • EVENT
  • WORK_OF_ART
  • CONSUMER_GOOD
  • OTHER
  • PHONE_NUMBER
  • ADDRESS
  • DATE
  • NUMBER
  • PRICE

各項目は直観的には分かりますが、少し考えると、いろいろと難しいものを感じます。。。

(参考)
Wikipediaによると、MUC・IREXの固有表現分類は以下の8種類ようです。
  • 組織名 (ORGANIZATION)
  • 人名 (PERSON)
  • 地名 (LOCATION)
  • 日付表現 (DATE)
  • 時間表現 (TIME)
  • 金額表現 (MONEY)
  • 割合表現 (PERCENT)
  • 固有物名 (ARTIFACT) 

Googleのタイプとは少し異なりますが、(良し悪しは私には分かりませんが)Googleのタイプには、EVENT、WORK_OF_ART、CONSUMER_GOOD、PHONE_NUMBER があるあたりに、学術的というより、実用重視の印象を受けます。

(7)Entity.metadata (mid,wikipedia_url他)

抽出されたエンティティに対する付加説明がキー・バリューのペア(map<string, string>)で得られます。

どのようなメタデータが得られるかは、Entity.typeによって異なるようです。
詳しくは、以下のTypeの説明をご参照下さい。
以下に、タイプ別に少し補足情報を書きます。
  • PHONE_NUMBER
    • キーとして、number、national_prefix、area_code、extension があり、値は半角数字で得られます。(ハイフンなどは取り除かれます)
    • 例えば、「携帯電話番号は090-XXXX-XXXXです。」という文章に対して、エンティティ「 090-XXXX-XXXX」がタイプPHONE_NUMBERで抽出されました。このメタデータは、national_prefix=0、number=9099999999 となっていました。
      • Xが9になっていますが、実際の電話番号を入れると、numberもその値になります。(numberの値とEntity.nameを比較すると9なのかXなのかの区別はつきます。)
  • ADDRESS
    • キーとして、street_number、locality、street_name、postal_code、country、broad_region、narrow_region、sublocalityがあります。
    • 本記事後半に例がありますので、参考にしてください。
  • DATE
    • キーとして、year、month、dayがあり、値は半角数字で得られます。
    • 「2021年8月」なら year=2021、month=8、「5月18日」なら month=5、day=18のように、年月日が揃っていない場合でも解釈できます。
    • 現状では和暦には対応していないように思えます。
  • NUMBER
    • value をキーとして、値が半角数字で得られます。
    • 試してみると、「私は十二歳です。」という文章から、「十二」がNUMBERとして認識され、value=12が得られました。
  • PRICE
    • キーとして value と currency があります。value は金額の値で、currencyは通貨です。
    • 試しに「そのお菓子は三百円です。」から、「三百円」がPRICEとして認識され、value=300.000000 と currency=JPY が得られました。
  • 上記以外のタイプ
    • wikipedia_url と mid が設定される場合があります。(下記参照)

①wikipedia_url

エンティティに関するWikipediaのページのURLが設定されます。
但し、英語版のページのURLのようです。

②mid

エンティティに関する Knowledge Graph MID が設定されます。

Google Knowledge Graph Search API を利用すれば、midに対応する情報が得られます。

例えば、GCPでKnowledge Graph Search APIを有効化して、以下のURLをブラウザのアドレスに入力すると、ナレッジグラフにある日本語と英語の情報を得ることができます。
([APIキー]は、GCPから取得したAPIキーです。)
https://kgsearch.googleapis.com/v1/entities:search?key=[APIキー]&ids=[midの値]&languages=ja&languages=en

Googleナレッジグラフについては、以下の記事も参考にしてください。

(8)Entity.mentions

元文書でエンティティに言及している位置などの情報が得られます。

元文書の位置情報は、TextSpan型のtextフィールドから得られます。
  • content
    • 本文中の該当文字列
    • 殆どの場合は、抽出されたエンティティの名前(Entity.name)と本文中に現れる文字列(content)は同じ値だと思いますが、異なる場合もあります。本記事の例にもエンティティ名とは異なる例があります。
  • begin_offset
    • 本文中のcontentの開始位置。
    • リクエストの AnalyzeEntitiesRequest.encoding_type フィールドの値に依存します。(4)を参照して下さい。

また、以下の値を取る typeフィールドもあります。
  • TYPE_UNKNOWN
  • PROPER
    • 固有名詞
  • COMMON
    • 普通名詞か複合名詞

[4]Pythonクライアントライブラリの利用

(1)準備

本記事では、Natural Language API のPython 言語のクライアントライブラリを利用します。

Pythonクライアントライブラリのドキュメントとソースコードは以下にあります。

クライアントライブラリを利用するためには、環境変数の設定とライブラリのインストールが必要です。

(参考)
本記事の動作確認は、Google Colaboratory で行いました。Colaboratory については以下の記事も参考にしてください。

①ライブラリのインストール

pipでインストールできます。
pip install --upgrade google-cloud-language

②環境変数(GOOGLE_APPLICATION_CREDENTIALS)の設定

先のGCPプロジェクトの準備でダウンロードしたキーファイル(JSONファイル)のパスを環境変数に設定します。

Colaboratoryでは、セルで以下のコードを実行します。
import os
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = 'JSONファイルのパス'

Linux環境では、exportコマンドでも設定できます。
export GOOGLE_APPLICATION_CREDENTIALS="JSONファイルのパス"

(2)エンティティ分析の実行と結果確認用コード

以下のコードは、分析したいテキストをtext_content引数に与えると、解析結果(AnalyzeEntitiesResponse)を返すサンプルコードです。
  • lang引数を指定しない場合は言語が自動認識されますが、明示的に言語を指定したい場合は、”en” や “ja” などを指定します。

<エンティティ分析を行うコード>
from google.cloud import language_v1

def analyze_entities_sample(text_content, lang=None):
    client = language_v1.LanguageServiceClient()

    req = {
        'document': {
            'content': text_content,
            'type_': language_v1.Document.Type.PLAIN_TEXT
            },
        'encoding_type': language_v1.EncodingType.UTF32
        }

    if lang is not None:
        req['document']['language'] = lang

    # デバッグ用
    print("request={}".format(req))

    response = client.analyze_entities(request = req)
    return response

続いて、analyze_entities_sample の結果(AnalyzeEntitiesResponse)を確認しやすくするために、HTMLのTABLE形式に変換する簡単なユーティリティ関数を用意します。

この関数では、PrettyTable を利用しますので、まずはpipでインストールしておきます。
pip install prettytable

<AnalyzeEntitiesResponseの結果をHTMLのテーブルに変換する関数例>
from prettytable import PrettyTable

def entities_to_htmltable(response, textout=False):
    # Entityのテーブル変換
    t = PrettyTable(['id', 'name', 'type', 'salience', 'metadata','mentions'])
    for i, entity in enumerate(response.entities):
        # メタデータをセルにまとめる
        metadata = '\n'.join(
            [ "{}={}".format(m_n, m_v)
                for m_n, m_v in entity.metadata.items()])
        # Mention をセルにまとめる
        mentions = '\n'.join(
            ["[{}]={}\n/offset={}\n/type={}".format(
                i,m.text.content, m.text.begin_offset, language_v1.EntityMention.Type(m.type_).name)
             for i, m in enumerate(entity.mentions)]
        )
        # 行の追加
        t.add_row(
            [i, entity.name, language_v1.Entity.Type(entity.type_).name, entity.salience,metadata,mentions])

    if textout:
        # コンソールにテーブルをテキスト表示(主にデバッグ用)
        print(t)

    return t.get_html_string()


本記事の実行結果は、以下の関数を定義してGoogle Colaboratory上で実行した結果をブログに貼り付けました。
def nlapi_test( text_content, textout=True ):
    # 解析実行
    response = analyze_entities_sample( text_content)
    # 言語表示
    print("language={}".format(response.language))
    # エンティティ表示
    html = entities_to_htmltable( response, textout)
    print( html.replace( "<table>\n", "<table border=1>\n" ) )

(参考)分析結果をJSONファイルに保存、再現する方法

エンティティ分析の結果をファイルなどに保存して、後で再利用したい場合もあると思います。ここでは簡単なコード例をメモしておきます。

下記コードの補足説明として、よろしければ以下の記事も参考にしてください。

①AnalyzeEntitiesResponseをJSON形式に変換してファイルに保存

# レスポンス(AnalyzeEntitiesResponse)をJSONファイルとしてファイルに保存する
def save_response_to_jsonfile( response, ofname ):
    js = response.__class__.to_json( response )
    with open(ofname, 'w') as f:
        f.write(js)

以下のように利用します。
save_response_to_jsonfile( response, "保存するファイル名”)

②JSONファイルの内容をAPIの戻り値(AnalyzeEntitiesResponse)に変換する

from google.cloud.language_v1 import AnalyzeEntitiesResponse

# 保存したJSONファイルをレスポンス(AnalyzeEntitiesResponse)として再現する
def load_response_from_jsonfile(ifname):
    with open(ifname) as f:
        js = f.read()
        return AnalyzeEntitiesResponse.from_json(js)

以下のように利用します。
response = load_response_from_jsonfile("JSONファイル名")

[5]エンティティ分析例

以下では、エンティティ分析のイメージをつかむため、簡単な例文を試していきます。
なお、処理結果の内容については、「[3]APIの仕様/(5)戻り値:AnalyzeEntitiesResponse」を参照して下さい。

(1)Wikipediaの固有表現抽出にある例

まずはWikipediaの固有表現抽出の具体例にある文で試してみます。

これによると、
太郎は5月18日の朝9時に花子に会いに行った。
という文に含まれる固有表現を抽出すると以下のようになる、とあります。
<PERSON>太郎</PERSON>は<DATE>5月18日</DATE>の<TIME>朝9時</TIME>に<PERSON>花子</PERSON>に会いに行った。

エンティティ分析ではどのようになるか実行してみます。
<実行>
nlapi_test("太郎は5月18日の朝9時に花子に会いに行った。")

<結果>
language=ja
id name type salience metadata mentions
0 太郎 PERSON 0.6322733759880066 [0]=太郎
/offset=0
/type=COMMON
1 花子 OTHER 0.367726594209671 [0]=花子
/offset=13
/type=COMMON
2 5月18日 DATE 0.0 day=18
month=5
[0]=5月18日
/offset=3
/type=TYPE_UNKNOWN
3 5 NUMBER 0.0 value=5 [0]=5
/offset=3
/type=TYPE_UNKNOWN
4 18 NUMBER 0.0 value=18 [0]=18
/offset=5
/type=TYPE_UNKNOWN
5 9 NUMBER 0.0 value=9 [0]=9
/offset=10
/type=TYPE_UNKNOWN

  • 日本語と自動認識されています。
  • 「太郎」はPERSONと認識されています。
  • 「5月18日」は「DATE」と認識され、さらに、month=5、day=18というメタデータが得られます。
  • 「朝9時」は認識されません。Googleのエンティティ分析のタイプを見るとDATEはありますが「TIME」は無いため、現状は時刻を認識できないと思います。
  • 「花子」も認識されていますが「PERSON」ではなく「OTHER」に分類されています。
  • 日付と時刻に現れる数字 5、18、9 がNUMBERとして認識されています。
    • 5と18については、文字範囲が「5月18日」に含まれているため、DATEを優先するなら5と18は無視できます。
時刻を除き、Wikipediaの解釈に近い結果が得られていると思います。

(2)英語の文:"California is a state."

Googleのサンプルコードにある英語の文("California is a state.")を試してみました。

<実行>
nlapi_test("California is a state.")

<結果>
language=en
id name type salience metadata mentions
0 California LOCATION 1.0 wikipedia_url=https://en.wikipedia.org/wiki/California
mid=/m/01n7q
[0]=California
/offset=0
/type=PROPER
[1]=state
/offset=16
/type=COMMON

  • 英語と自動認識されています。
  • 「California」がLOCATIONとして認識されています。
  • メタデータとして、Wikipediaの英語版URLと、ナレッジグラフのmidが得られます。
  • mentionが2つ抽出されています。固有名詞「California」はエンティティ名と同じですが、普通名詞「state」も認識されているのはなかなか奥が深いです。

(3)日本語の地名を含む例

国税庁法人番号公表サイトで調べた所在を文例にして試してみます。

<実行>
nlapi_test("衆議院の所在地は東京都千代田区永田町1丁目7-1です。")

<結果>
language=ja

id name type salience metadata mentions
0 衆議院 ORGANIZATION 0.2561890184879303 wikipedia_url=https://en.wikipedia.org/wiki/House_of_Representatives_(Japan)
mid=/m/01fw_b
[0]=衆議院
/offset=0
/type=PROPER
1 所在地 LOCATION 0.21020211279392242 [0]=所在地
/offset=4
/type=COMMON
2 千代田区 LOCATION 0.1954360157251358 wikipedia_url=https://en.wikipedia.org/wiki/Chiyoda,_Tokyo
mid=/m/01xhb_
[0]=千代田区
/offset=11
/type=PROPER
3 東京都 LOCATION 0.18612566590309143 mid=/m/07dfk
wikipedia_url=https://en.wikipedia.org/wiki/Tokyo
[0]=東京都
/offset=8
/type=PROPER
4 永田町 LOCATION 0.15204717218875885 wikipedia_url=https://en.wikipedia.org/wiki/Nagatachō
mid=/m/02225b
[0]=永田町
/offset=15
/type=PROPER
5 東京都千代田区永田町1丁目7-1 ADDRESS 0.0 locality=千代田区
country=JP
street_number=71
broad_region=東京都
[0]=東京都千代田区永田町1丁目7-1
/offset=8
/type=TYPE_UNKNOWN
6 NUMBER 0.0 value=1 [0]=1
/offset=18
/type=TYPE_UNKNOWN
7 NUMBER 0.0 value=7 [0]=7
/offset=21
/type=TYPE_UNKNOWN
8 -1 NUMBER 0.0 value=-1 [0]=-1
/offset=22
/type=TYPE_UNKNOWN

  • 日本語と自動認識されています。
  • 「衆議院」が、「ORGANIZATION」の固有名詞として認識され、WikipediaのURLとナレッジグラフのmidが得られます。
  • 「所在地」が「LOCATION」の普通名詞として抽出されています。
  • 「東京都千代田区永田町1丁目7-1」が「ADDRESS」として抽出されています。
    • さらに、メタデータでは、country=JP、broad_region=東京都、street_number=71、locality=千代田区、に分解されています。
    • 住所としては正しく認識されていますが、メタデータでは丁目地番以降の評価が微妙になるのかもしれません。
  • ADRESSの一部である「東京都」「千代田区」「永田町」が「LOCATION」の固有名詞としても抽出され、WikipediaのURLとナレッジグラフのmidが得られます。
  • ADRESSの一部である「1」「7」「-1」も「NUMBER」として抽出されます。
    • メタデータでは value として、それぞれ半角数字で 1,7,-1 と設定されています。

抽出されたエンティティを階層的にまとめてトップレベルのエンティティだけみると、「衆議院」「所在地」「東京都千代田区永田町1丁目7-1」の3つとなります。

ADDRESSがちゃんと認識できているのは凄いなと思いました。また、LOCATIONとADDRESSの違いが何となく理解できるような例になっていますが、「所在地」と「東京都」が同じLOCATIONに分類されると。。。

(4)異なる漢字だけど同じエンティティの例

記事『法人番号公表サイトのWeb-APIを利用した法人情報の取得と文字の取扱い』でとりあげた縮退文字の例文を作って試してみます。

少々意地悪な例?だとは思いますが、俗に「はしご高」といわれる「髙」(「高」の異体字)を使った例です。

<実行>
nlapi_test("法人番号公表サイトで髙島屋を検索すると高島屋と表示されました。")

<結果>
language=ja
id name type salience metadata mentions
0 法人番号公表サイト OTHER 0.37625449895858765 [0]=法人番号公表サイト
/offset=0
/type=COMMON
1 髙島屋 LOCATION 0.3392491340637207 wikipedia_url=https://en.wikipedia.org/wiki/Takashimaya
mid=/m/046k03
[0]=髙島屋
/offset=10
/type=PROPER
[1]=高島屋
/offset=19
/type=PROPER

  • 日本語と自動認識されています。
  • 「法人番号公表サイト」が固有表現として認識されています。但し、タイプはOTHERです。
  • はしご高の「髙島屋」がLOCATIONタイプで認識され、WikipediaのURLとナレッジグラフのmidも設定されています。されにmentionsに「髙島屋」と「高島屋」があり、同じものと認識されているようです。凄い!

しかし、髙島屋はLOCATIONなのでしょうか。。。

ところで、少し文章を変えると認識結果が変わるようです。以下のように「デパートの」を加えた文章の結果を見てみます。

<実行>
nlapi_test("法人番号公表サイトでデパートの髙島屋を検索すると高島屋と表示されました。")

<結果>
language=ja
id name type salience metadata mentions
0 法人番号公表サイト OTHER 0.27822035551071167 [0]=法人番号公表サイト
/offset=0
/type=COMMON
1 デパート OTHER 0.27822035551071167 [0]=デパート
/offset=10
/type=COMMON
2 髙島屋 LOCATION 0.23832997679710388 [0]=髙島屋
/offset=15
/type=COMMON
3 高島屋 LOCATION 0.20522929728031158 mid=/m/046k03
wikipedia_url=https://en.wikipedia.org/wiki/Takashimaya
[0]=高島屋
/offset=24
/type=PROPER

今度は「髙島屋」と「高島屋」が別のエンティティとして認識されました。タイプはどちらもLOCATIONです。さらに、Wkipediaとナレッジグラフの情報の内容は前回と同じですが、「高島屋」のメタデータにのみ設定されています。。。

とりあえず、今は深く考えず、次の例に進みます。

(5)OCRから抽出した表形式データの例

これまで単文の例ばかりでしたので、別の例を試してみます。

ここでは、Vision API などのOCRを使ってテキストを抽出して、そこからエンティティ分析をしてみます。
といっても、普通の文章では面白くないので、記事『Google Document AI で文書画像からフォーム要素(キー名とその値)を抽出する』で取り上げた表形式画像の例に、どのようなエンティティが抽出されるのか試してみます。

<元画像>

shoppinglist.jpg

この画像から、Document AI(OCR)で抽出されたテキストをそのまま試してみます。
<実行>
nlapi_test( """Shopping List
Price
Date Description
2021/2/1 Sweets
2021/2/1 Bento
2021/2/2 TechnoDaifukucho
Total
$300.00
$1,000.00
$0.00
$1,300.00""" )

<結果>
language=en
id name type salience metadata mentions
0 Shopping List Price Date Description WORK_OF_ART 1.0 [0]=Shopping List
Price
Date Description
/offset=0
/type=PROPER
1 2021/2/1 DATE 0.0 day=1
month=2
year=2021
[0]=2021/2/1
/offset=37
/type=TYPE_UNKNOWN
2 2021/2/1 DATE 0.0 month=2
year=2021
day=1
[0]=2021/2/1
/offset=53
/type=TYPE_UNKNOWN
3 2021/2/2 DATE 0.0 day=2
year=2021
month=2
[0]=2021/2/2
/offset=68
/type=TYPE_UNKNOWN
4 $300.00 PRICE 0.0 value=300.000000
currency=USD
[0]=$300.00
/offset=100
/type=TYPE_UNKNOWN
5 $1,000.00 PRICE 0.0 value=1000.000000
currency=USD
[0]=$1,000.00
/offset=108
/type=TYPE_UNKNOWN
6 $0.00 PRICE 0.0 currency=USD
value=0.000000
[0]=$0.00
/offset=118
/type=TYPE_UNKNOWN
7 $1,300.00 PRICE 0.0 value=1300.000000
currency=USD
[0]=$1,300.00
/offset=124
/type=TYPE_UNKNOWN
8 0.00 NUMBER 0.0 value=0.000000 [0]=0.00
/offset=119
/type=TYPE_UNKNOWN
9 2 NUMBER 0.0 value=2 [0]=2
/offset=73
/type=TYPE_UNKNOWN
10 1,000.00 NUMBER 0.0 value=1000.000000 [0]=1,000.00
/offset=109
/type=TYPE_UNKNOWN
11 2021 NUMBER 0.0 value=2021 [0]=2021
/offset=37
/type=TYPE_UNKNOWN
12 2021 NUMBER 0.0 value=2021 [0]=2021
/offset=53
/type=TYPE_UNKNOWN
13 2 NUMBER 0.0 value=2 [0]=2
/offset=58
/type=TYPE_UNKNOWN
14 2 NUMBER 0.0 value=2 [0]=2
/offset=42
/type=TYPE_UNKNOWN
15 2021 NUMBER 0.0 value=2021 [0]=2021
/offset=68
/type=TYPE_UNKNOWN
16 2 NUMBER 0.0 value=2 [0]=2
/offset=75
/type=TYPE_UNKNOWN
17 300.00 NUMBER 0.0 value=300.000000 [0]=300.00
/offset=101
/type=TYPE_UNKNOWN
18 1 NUMBER 0.0 value=1 [0]=1
/offset=60
/type=TYPE_UNKNOWN
19 1 NUMBER 0.0 value=1 [0]=1
/offset=44
/type=TYPE_UNKNOWN
20 1,300.00 NUMBER 0.0 value=1300.000000 [0]=1,300.00
/offset=125
/type=TYPE_UNKNOWN

  • 英語と自動認識されています。
  • 「Shopping List Price Date Description」が「WORK_OF_ART」と認識されています。。。
  • DATEとして「2021/2/1」「2021/2/1」「2021/2/2」が認識され、year,month,dayのメタデータも設定されています。
  • PRICEとして「$300.00」「$1,000.00」「$0.00」「$1,300.00」が認識され、currency=USD、valueも半角数字で設定されています。
  • それ以外は、DATEとPRICEの部分文字列がNUMBERとして抽出されたものです。

Document AIのフォーム解析結果と比べて違いを感じるのは、Natural Language APIのエンティティ分析では「Sweets」「Bento」「TechnoDaifukucho」「Total」がエンティティとして認識されないという事でした。

今回の例はOCRから抽出した文字列そのままを解析しており、文字の位置関係などは全く考慮していませんし、文章というより単語の並びなので、有意な情報を得るのは難しいのかも。。。(私でも画像を見ないで文字データだけ見ると、なんだかよく分からない気がします。)

また、今回の結果だけ見ると、エンティティ分析の結果がDocument AIのフォーム解析のキー候補になるとは限らないように見えますが、文字の配置関係を考慮して(少なくとも行レベルを揃えたりして)エンティティ分析を行うと結果が変わるかもしれません。

もっとも、エンティティ分析を固有表現抽出と考えると、Document AI のフォーム解析はその上位?の関係抽出レベルだし、エンティティ分析以外の情報も考慮されているので、関係はあるとしても、比べるものではないのかもしれません。

[6]最後に

試してみると、なかなか面白いものでした。

とはいえ、今回のブログには長くなるので書きませんでしたが、自分のブログをエンティティ分析したりして、長い文章を試してみると、かなり深い?です。
例えば、多くのエンティティが認識されたとき、何を取って何を捨てるか、salienceをどのように評価すればよいか、などの判断基準とか。。。
また、今回は一般的?な文書のみで試していますが、ドメイン特化したような文書はどうなんだろう、とか。

また、多くの最近のAI的な(機械学習系?)サービスに言えることですが、確立されたアルゴリズムや仕様に基づいて動作するものではない?ため、なぜそのような結果になるのか?がよく分からないことが多いです。(そこが面白いところでもあるのですが。。。)

ということで、かなり奥が深く、面白いことがわかりましたので、これをきっかけに、自然言語処理の勉強を始めてみようかな。。。

コメント

このブログの人気の投稿

VirtualBoxのスナップショット機能

Google Document AIで画像から表形式データを抽出する(Vision API OCRとの違い)

Vision API OCR事始め(1):TEXT_DETECTIONとDOCUMENT_TEXT_DETECTIONの違い