Skip to content

Latest commit

 

History

History
387 lines (233 loc) · 15.7 KB

chapter2.md

File metadata and controls

387 lines (233 loc) · 15.7 KB
title description prev next type id
第2章: spaCyによる大量データの解析
この章では、大量のテキストから特定の情報を抽出する方法をみていきます。 spaCyのデータ構造の作成方法と、テキスト解析のために機械学習パイプラインとルールベースパイプラインを効率的に組み合わせる方法を学びます。
/chapter1
/chapter3
chapter
2

パート1

  • nlp.vocab.stringsから「ネコ」のハッシュ値を取得してください。
  • 逆に、ハッシュ値から文字列を取得してください。
  • 文字列のデータベースnlp.vocab.stringsは、Pythonの辞書のように使うことができます。 例えば、nlp.vocab.strings["ユニコーン"]とすればハッシュ値を取得でき、逆にハッシュ値を使うと "ユニコーン"を再取得することができます。

パート2

  • nlp.vocab.stringsから「PERSON」ラベルのハッシュ値を取得してください。
  • ハッシュ値から文字列を取得してください。
  • 文字列のデータベースnlp.vocab.stringsは、Pythonの辞書のように使うことができます。 例えば、nlp.vocab.strings["ユニコーン"]とすればハッシュ値を取得でき、逆にハッシュ値を使うと "ユニコーン"を再取得することができます。

さて、なぜこのコードはエラーとなるでしょうか?

import spacy

# 日本語とドイツ語のnlpオブジェクトを作る
nlp = spacy.blank("ja")
nlp_de = spacy.blank("de")

# 「ボウイ」のIDを取得
bowie_id = nlp.vocab.strings["ボウイ"]
print(bowie_id)

# vocabから、IDを用いて「ボウイ」を取得
print(nlp_de.vocab.strings[bowie_id])

ハッシュ値は復号できません。そのため、テキストを処理したり、文字列をルックアップしたり、同じvocabオブジェクトを使ってハッシュ値から文字列を取得します。

いかなる文字列もハッシュ化できます。

nlpという名前はただの慣習です。コード中でnlpの代わりにnlp_deを用いると、vocabオブジェクトも含めてnlpが上書きされてしまいます。

では、Docオブジェクトをゼロから作ってみましょう。

パート1

  • Docクラスをspacy.tokensからインポートしてください。
  • Docオブジェクトをwordsspacesから作成します。vocabオブジェクトを渡すのを忘れないでください!

Docクラスは3つの引数をとります。1つめは通常nlp.vocabで表される共有語彙データ、 2つめはwordsのリスト、3つめは単語間のスペースの有無をブール値で表したspacesのリストです。

パート2

  • Docクラスをspacy.tokensからインポートしてください。
  • Docオブジェクトをwordsspacesから作成します。vocabオブジェクトを渡すのを忘れないでください!

出力したいテキストの各単語を見て、それがスペースに続いているかどうかを確認します。 もしそうならばspacesTrueを、そうでないならばFalseを追加してください。

パート3

  • Docクラスをspacy.tokensからインポートしてください。
  • Docオブジェクトをwordsspacesから作成してください。

各トークンを注意深くみてください。 spaCyが普段どのように文字列をトークン化しているかを見るには、試しに nlp("本当ですか?!") のトークンをプリントしてみましょう。

この演習では、DocSpanを手動で作り、固有表現を登録してみましょう。 これはspaCyが普段裏側でやっていることです。 共有nlpオブジェクトはすでに作られています。

  • DocSpanクラスをspacy.tokensからインポートしてください。
  • Docクラスから直接、単語とスペースリストを用いてdocオブジェクトを作ってください。
  • 「デヴィッド・ボウイ」のSpanオブジェクトをdocから作り、"PERSON"ラベルをつけてください。
  • doc.entsを「デヴィッド・ボウイ」のspanからなる固有表現のリストで上書きしてください。
  • Docクラスは3つの引数で初期化します。1つめは例えばnlp.vocabで表される共有語彙データ、 2つめはwordsのリスト、3つめは単語間のスペースの有無をブール値で表したspacesのリストです。
  • Spanクラスは4つの引数をとります。1つめはdocへの参照、2つめは開始トークンのインデックス、3つめは終了トークンのインデックス、4つめはオプショナルで、固有表現ラベルです。
  • doc.entsプロパティは、任意のSpanからなるイテレート可能タイプ(iterable)を書き込むことができます。

この例では、テキストを解析し、動詞が続く固有名詞を全て抽出しようとしています。

import spacy

nlp = spacy.load("ja_core_news_sm")
doc = nlp("ベルリンはいい街だと思う")

# 全てのトークンと品詞タグを取得
token_texts = [token.text for token in doc]
pos_tags = [token.pos_ for token in doc]

for index, pos in enumerate(pos_tags):
    # 現在のトークンが固有名詞かどうかをチェックする
    if pos == "PROPN":
        # 次のトークンが設置詞(Adposition)かどうかを調べる
        if pos_tags[index + 1] == "ADP":
            result = token_texts[index]
            print("設置詞の前の固有名詞が見つかりました:", result)

パート1

このコードはなぜよくないでしょうか?

文字列をTokenオブジェクトに変換し直す必要はありません。ただこれ以降も文字列以外の情報を使う必要があるのならば、 トークンを文字列にしないほうが賢明でしょう。

結果を文字列として出力するのはなるべく後にし、一貫性を保つためにネイティブなトークン属性を使うのが良いです。

.pos_属性は粗視化された品詞タグを返し、"PROPN"は固有名詞をチェックするための正しいタグです。

パート2

  • 2つのリストtoken_textspos_tagsを使う代わりに、トークンのネイティブ属性を使ってコードを書き直してください。
  • docに入っているそれぞれのtokenについてループし、token.pos_属性をチェックしてください。
  • doc[token.i+1]を使って次のトークンを取得し、その.pos_属性をチェックしてください。
  • 動詞の前に固有名詞が見つかったら、そのtoken.textをプリントしてください。
  • 事前に文字列を取得する必要はないので、token_textspos_tagsを削除してください。
  • pos_tagsをイテレートする代わりに、docに入っている各tokenをループし、token.pos_属性を取得してください。
  • 次のトークンが動詞かどうかをチェックするために、doc[token.i + 1].pos_を確認してください。

この演習では、35,000個の単語ベクトルが含まれている中サイズの日本語パイプラインを使います。 このパイプラインは既にインストールされています。

  • 単語ベクトルの入っている中サイズパイプライン"ja_core_news_md"をロードしてください。
  • token.vector属性を使って、"バナナ"の単語ベクトルをプリントしてください。
  • spacy.load関数を呼びだし、機械学習パイプラインをロードしてください。
  • docに含まれるトークンを取得するには、インデックスを使ってください。例えばdoc[4]とします。

この演習では、spaCyのsimilarityメソッドを使って、DocTokenSpanオブジェクトの比較をし、類似度を算出していきます。

パート1

  • doc.similarityメソッドを使って、doc1doc2を比較し、結果をプリントしてください。

doc.similarityメソッドは引数を1つとります。比較対象のオブジェクトです。

パート2

  • token.similarityメソッドを使って、token1token2を比較し、結果を出力してください。
  • token.similarityメソッドは引数を1つとります。比較対象のオブジェクトです。

パート3

  • 「素晴らしいレストラン」と「とても素敵なバー」のスパンを作ってください。
  • span.similarityを使ってこれらを比較し、結果をプリントしてください。

なぜこのパターンはdoc中の「Silicon Valley」にマッチしないでしょうか?

pattern = [{'LOWER': 'silicon'}, {'TEXT': ' '}, {'LOWER': 'valley'}]
doc = nlp("Silicon Valleyの労働者は内部からビッグテックを抑制することができるか?")

パターン中の"LOWER"属性は、トークンを小文字化したらその値にマッチする、ということを示しています。 つまり、{"LOWER": "valley"}は「Valley」や「VALLEY」、「valley」等にマッチします。

トークナイザは既にスペースを区切っており、それぞれの辞書はトークンについて表しています。

パターン中のトークンは、デフォルトで一度だけマッチします。 演算子は、この挙動を変えたいときだけ用います。例えば、0回以上マッチさせたいときなどです。

この演習中のパターンは両方とも誤っており、期待した通りには動きません。 正しく修正できますか? もし躓いてしまったら、docのトークンを全てプリントしてみて、トークンがどのように分割されているかを確認し、 パターン中の各辞書が1つのトークンに対応するように調整してみましょう。

  • pattern1を修正し、大文字小文字によらず"Amazon"にマッチし、また、タイトルケースの固有名詞にマッチするようにしてください。
  • pattern2を修正し、大文字小文字によらない"ad-free"と、名詞の組にマッチするようにしてください。
  • nlpオブジェクトにマッチする文字列を処理してみてください。例えば、[token.text for token in nlp("ad-free視聴")]のように。
  • トークンを検査し、パターン内の各辞書が1つのトークンを正しく記述していることを確認してください。

個々のトークンを記述するパターンを書くよりも、正確な文字列をマッチさせた方が効率的な場合もあります。これは特に、世界のすべての国のような数に限りのある場合に当てはまります。 すでに国のリストがあるので、これを使って情報抽出スクリプトを作りましょう。 国名のリストは、COUNTRIES変数に入っています。

  • PhraseMatcherをインポートし、共有のvocabで初期化し、matcher変数に格納してください。
  • フレーズのパターンを追加し、matcherをdocに対して呼び出してください。

共有のvocabは、nlp.vocabにあります。

前の演習では、PhraseMatcherを使って国名を抽出するスクリプトを作りました。 長いテキストで国別マッチツールを使用し、構文を分析して、一致した国でdocの固有表現を更新してみましょう。

  • マッチの結果をイテレートし、ラベル "GPE" (地理的実体) を持つ Span を作成します。
  • doc.ents を上書きし、マッチしたスパンを追加します。
  • マッチしたスパンのルートのヘッドトークンを取得します。
  • そのヘッドトークンとスパンのテキストをプリントします。
  • テキストは変数 text として利用できます。
  • スパンのルートトークンは span.root で取得できます。トークンのヘッドは token.head 属性で取得できます。