title | description | prev | next | type | id |
---|---|---|---|---|---|
第2章: spaCyによる大量データの解析 |
この章では、大量のテキストから特定の情報を抽出する方法をみていきます。 spaCyのデータ構造の作成方法と、テキスト解析のために機械学習パイプラインとルールベースパイプラインを効率的に組み合わせる方法を学びます。 |
/chapter1 |
/chapter3 |
chapter |
2 |
nlp.vocab.strings
から「ネコ」のハッシュ値を取得してください。- 逆に、ハッシュ値から文字列を取得してください。
- 文字列のデータベース
nlp.vocab.strings
は、Pythonの辞書のように使うことができます。 例えば、nlp.vocab.strings["ユニコーン"]
とすればハッシュ値を取得でき、逆にハッシュ値を使うと"ユニコーン"
を再取得することができます。
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
オブジェクトをゼロから作ってみましょう。
Doc
クラスをspacy.tokens
からインポートしてください。Doc
オブジェクトをwords
とspaces
から作成します。vocabオブジェクトを渡すのを忘れないでください!
Doc
クラスは3つの引数をとります。1つめは通常nlp.vocab
で表される共有語彙データ、
2つめはwords
のリスト、3つめは単語間のスペースの有無をブール値で表したspaces
のリストです。
Doc
クラスをspacy.tokens
からインポートしてください。Doc
オブジェクトをwords
とspaces
から作成します。vocabオブジェクトを渡すのを忘れないでください!
出力したいテキストの各単語を見て、それがスペースに続いているかどうかを確認します。
もしそうならばspaces
にTrue
を、そうでないならばFalse
を追加してください。
Doc
クラスをspacy.tokens
からインポートしてください。Doc
オブジェクトをwords
とspaces
から作成してください。
各トークンを注意深くみてください。
spaCyが普段どのように文字列をトークン化しているかを見るには、試しに nlp("本当ですか?!")
のトークンをプリントしてみましょう。
この演習では、Doc
とSpan
を手動で作り、固有表現を登録してみましょう。
これはspaCyが普段裏側でやっていることです。
共有nlp
オブジェクトはすでに作られています。
Doc
とSpan
クラスを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)
このコードはなぜよくないでしょうか?
文字列をToken
オブジェクトに変換し直す必要はありません。ただこれ以降も文字列以外の情報を使う必要があるのならば、
トークンを文字列にしないほうが賢明でしょう。
結果を文字列として出力するのはなるべく後にし、一貫性を保つためにネイティブなトークン属性を使うのが良いです。
.pos_
属性は粗視化された品詞タグを返し、"PROPN"
は固有名詞をチェックするための正しいタグです。
- 2つのリスト
token_texts
とpos_tags
を使う代わりに、トークンのネイティブ属性を使ってコードを書き直してください。 doc
に入っているそれぞれのtoken
についてループし、token.pos_
属性をチェックしてください。doc[token.i+1]
を使って次のトークンを取得し、その.pos_
属性をチェックしてください。- 動詞の前に固有名詞が見つかったら、その
token.text
をプリントしてください。
- 事前に文字列を取得する必要はないので、
token_texts
とpos_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
メソッドを使って、Doc
とToken
とSpan
オブジェクトの比較をし、類似度を算出していきます。
doc.similarity
メソッドを使って、doc1
とdoc2
を比較し、結果をプリントしてください。
doc.similarity
メソッドは引数を1つとります。比較対象のオブジェクトです。
token.similarity
メソッドを使って、token1
とtoken2
を比較し、結果を出力してください。
token.similarity
メソッドは引数を1つとります。比較対象のオブジェクトです。
- 「素晴らしいレストラン」と「とても素敵なバー」のスパンを作ってください。
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
属性で取得できます。