GiNZA(9) - 固有表現抽出

固有表現抽出は、文章から「人名」「場所」「組織名」などの固有名詞や「日付」「時間」などの数値表現を抽出する処理です。

エンティティ分析と呼ばれることもあります。

固有表現抽出

固有表現抽出を行うソースコードは次のようになります。

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import spacy
from spacy import displacy
nlp = spacy.load('ja_ginza')
doc = nlp('武元さんと渋谷で夕食をご一緒しましょう。')

# 固有表現抽出
for ent in doc.ents:
print(
ent.text + ', '+ # テキスト
ent.label_ + ',' + # ラベル
str(ent.start_char) + ',' + # 開始位置
str(ent.end_char)) # 終了位置

# 固有表現抽出の強調表示
displacy.render(doc, style='ent', jupyter=True)

固有表現はEntityクラスとして取得します。

Entityクラスの主なプロパティは以下の通りです

  • ent.text
    テキスト
  • ent.label_
    ラベル
  • ent.start_char
    開始位置
  • ent.end_char
    終了位置

実行結果は以下の通りです。

[実行結果]

15行目の固有表現抽出の強調表示を実行することで少しグラフィカルに表示され、テキストとラベルの関係が見やすくなっています。

次回は、Wikipediaを用いた固有表現抽出データセットでの学習を行います。

GiNZA(8) - 単語係り受け解析

単語係り受け解析は、単語の係り受け関係を木構造で表現します。

各単語は木の頂点に対応し、ただ1つの親と複数の子を持ちます。

単語の係り受け解析

単語係り受け解析を行うソースコードは次の通りです。

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
10
11
import spacy
from spacy import displacy
nlp = spacy.load('ja_ginza')
doc = nlp('新橋で打ち合わせをしましょう。')

# 単語の係り受け解析
for token in doc:
print(token.text+' ← '+token.head.text+', '+token.dep_)

# グラフ表示
displacy.render(doc, style='dep', jupyter=True)

実行結果は以下の通りです。

[実行結果]

1
2
3
4
5
6
7
新橋 ← し, obl
で ← 新橋, case
打ち合わせ ← し, obj
を ← 打ち合わせ, case
し ← し, ROOT
ましょう ← し, aux
。 ← し, punct

各単語の係り受け関係をグラフで確認することができました。


11行目のグラフ表示処理で、options={‘compact’:True, ‘distance’: 90}を指定すると、グラフをシンプルに表示することができます。

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
10
11
import spacy
from spacy import displacy
nlp = spacy.load('ja_ginza')
doc = nlp('新橋で打ち合わせをしましょう。')

# 単語の係り受け解析
for token in doc:
print(token.text+' ← '+token.head.text+', '+token.dep_)

# シンプルなグラフ表示
displacy.render(doc, style='dep', jupyter=True, options={'compact':True, 'distance': 90})

実行結果は以下の通りです。

[実行結果]

次回は、固有表現抽出を行います。

GiNZA(7) - 係り受け解析

係り受け解析は、文章を文節または単語で分割した後、どの語がどの語を修飾しているかを判別する処理です。

構文解析と呼ばれることもあります。

文節の係り受け解析

文節の係り受け解析を行うソースコードは以下のようになります。

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
import spacy
import ginza
nlp = spacy.load('ja_ginza')
doc = nlp('新橋で打ち合わせをしましょう。')

# 文節の係り受け解析
for span in ginza.bunsetu_spans(doc):
for token in span.lefts:
print(str(ginza.bunsetu_span(token))+' → '+str(span))

実行結果は以下の通りです。

[実行結果]

1
2
新橋で → しましょう。
打ち合わせを → しましょう。

各文節がどの文節に係っているかを確認できました。


7行目のbunsetu_spans()の代わりにbunsetu_phrase_spans()を使うと、主辞(文節で一番重要な単語)を抽出することができます。

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
import spacy
import ginza
nlp = spacy.load('ja_ginza')
doc = nlp('新橋で打ち合わせをしましょう。')

# 文節の係り受け解析
for span in ginza.bunsetu_phrase_spans(doc):
for token in span.lefts:
print(str(ginza.bunsetu_span(token))+' → '+str(span))

実行結果は以下の通りです。

[実行結果]

1
2
新橋で → し
打ち合わせを → し

次回は、単語の係り受け解析を行います。

GiNZA(6) - 文節分割

文節分割は、文章を文節ごとに分解する処理です。

文節分割

日本語は、文節で分割した方が解析しやすくなることがよくあります。

文節分割を行うソースコードは以下のようになります。

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
import spacy
import ginza
nlp = spacy.load('ja_ginza')
doc = nlp('新橋でランチをご一緒しましょう。次の火曜日はどうですか。')

# 文節分割
for sent in doc.sents:
for span in ginza.bunsetu_spans(sent):
print(span)

実行結果は以下の通りです。

[実行結果]

1
2
3
4
5
6
新橋で
ランチを
ご一緒しましょう。
次の
火曜日は
どうですか。

文節で分解することができました。


またspanをforでループすることにより、文節を分割したトークンを取得することができます。

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
10
import spacy
import ginza
nlp = spacy.load('ja_ginza')
doc = nlp('新橋でランチをご一緒しましょう。次の火曜日はどうですか。')

# 文節分割+トークン化
for sent in doc.sents:
for span in ginza.bunsetu_spans(sent):
for token in span:
print(token)

実行結果は以下の通りです。

[実行結果]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
新橋

ランチ


一緒

ましょう



火曜日

どう
です


次回は、係り受け解析を行います。

GiNZA(5) - 文境界解析

文境界解析は、文章を文ごとに分解する処理です。

文の境界は「。」があるのでこれを検出すれば問題ないと思うかもしれませんが、「!」「?」で区切ることもあります。

また、「彼は『本当ですか。』とつぶやいた。」というように会話文が含まれていることもあり、文境界解析は意外と難しい処理になります。

文境界解析

文境界解析を行うソースコードは次のようになります。

[Google Colaboratory]

1
2
3
4
5
6
7
mport spacy
nlp = spacy.load('ja_ginza')
doc = nlp('新橋でランチをご一緒しましょう。次の火曜日はどうですか。')

# 文境界解析
for span in doc.sents:
print(span)

実行結果は以下の通りです。

[実行結果]

1
2
新橋でランチをご一緒しましょう。
次の火曜日はどうですか。

文ごとに分解することができました。


さらにspanをforでループすることにより、文を分割したトークンを取得することができます。

[Google Colaboratory]

1
2
3
4
5
6
7
8
import spacy
nlp = spacy.load('ja_ginza')
doc = nlp('新橋でランチをご一緒しましょう。次の火曜日はどうですか。')

# 文境界解析+トークン化
for span in doc.sents:
for token in span:
print(token)

実行結果は以下の通りです。

[実行結果]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
新橋

ランチ


一緒

ましょう



火曜日

どう
です



次回は、文節分割を行います。

GiNZA(4) - 形態素解析(レンマ化)

レンマ化は、トークンを辞書の見出し語に変換する処理です。

トークンを辞書の見出し語にそろえることで、異なる表記でも同じ単語であることを判別できるようになります。

レンマ化

レンマ化を行うソースコードは以下のようになります。

[Google Colaboratory]

1
2
3
4
5
6
7
8
import spacy
nlp = spacy.load('ja_ginza')
doc = nlp('新橋に行きます。')

for token in doc:
print(
token.text+', '+ # テキスト
token.lemma_) # レンマ化

実行結果は以下の通りです。

[実行結果]

1
2
3
4
5
新橋, 新橋
に, に
行き, 行く
ます, ます
。, 。

行きというトークンが、行くという見出し語に変換されています。

行くという単語であれば、辞書で確認できますね。


次回は、文境界解析を行います。

GiNZA(3) - 形態素解析(品詞タグ付け)

品詞タグ付けは、トークンの品詞を判別する処理です。

品詞タグ付け

トークンごとの品詞を確認するコードは以下の通りです。

品詞タグとして2種類表示します。

  • SudachiPyの品詞タグ
  • Universal Dependenciesの品詞タグ

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
import spacy
nlp = spacy.load('ja_ginza')
doc = nlp('新橋に行きます。')

for token in doc:
print(
token.text+', '+ # トークン
token.tag_+', '+ # SudachiPyの品詞タグ
token.pos_) # Universal Dependenciesの品詞タグ

実行結果は以下の通りです。

[実行結果]

1
2
3
4
5
新橋, 名詞-普通名詞-一般, NOUN
に, 助詞-格助詞, ADP
行き, 動詞-非自立可能, VERB
ます, 助動詞, AUX
。, 補助記号-句点, PUNCT

次回は、レンマ化を行います。

GiNZA(2) - 形態素解析(トークン化)

形態素解析

形態素解析とは、文章を形態素と呼ばれる言葉の最小単位に分割し形態素品詞見出し語を判定する処理です。

具体的には次のような処理に分けることができます。

  • トークン化
    文章を言葉の最小単位に分割する処理
  • 品詞タグ付け
    トークンの品詞を判別する処理
  • レンマ化
    トークンを辞書の見出し語に変換する処理

トークンの分割単位

GiNZAではトークン化の処理を行う場合、3種類の分割単位に切り替えて利用することができます。

分割単位を指定するためにはset_split_mode()を使います。


まずは分割単位Aでトークン化を行います。

[Google Colaboratory]

1
2
3
4
5
6
7
8
import spacy
import ginza
nlp = spacy.load('ja_ginza')
ginza.set_split_mode(nlp, 'A') # 分割単位A
doc = nlp('あの女性は国家公務員です')

for token in doc:
print(token)

[実行結果]

1
2
3
4
5
6
7
あの
女性

国家
公務

です

次に分割単位Bでトークン化を行います。

[Google Colaboratory]

1
2
3
4
5
6
7
8
import spacy
import ginza
nlp = spacy.load('ja_ginza')
ginza.set_split_mode(nlp, 'B') # 分割単位B
doc = nlp('あの女性は国家公務員です')

for token in doc:
print(token)

[実行結果]

1
2
3
4
5
6
あの
女性

国家
公務員
です

最後にに分割単位Cでトークン化を行います。

[Google Colaboratory]

1
2
3
4
5
6
7
8
import spacy
import ginza
nlp = spacy.load('ja_ginza')
ginza.set_split_mode(nlp, 'C') # 分割単位C
doc = nlp('あの女性は国家公務員です')

for token in doc:
print(token)

[実行結果]

1
2
3
4
5
あの
女性

国家公務員
です

分割単位によって「国家公務員」という単語の分け方が異なっていることが分かります。

  • 分割単位A
    国家/公務/員
  • 分割単位B
    国家/公務員
  • 分割単位C
    国家公務員

次回は、品詞のタグ付けを行います。

GiNZA(1) - インストール

自然言語処理を行うライブラリであるGiNZAを利用してみます。

GiNZAの概要

GiNZAは日本語の自然言語処理ライブラリで、次のような用途で使われます。

  • 情報抽出
    大量の自然言語の文章から、特定の条件に合致した情報を抽出します。
  • 自然言語理解
    発話文章から発話者がどんなタスクを要求しているのかを推測します。
  • 深層学習の前処理
    自然言語の文章を深層学習の入力データに変換します。
    自然言語処理の深層学習の推論で利用します。

インストール

GiNZAをインストールするには次のコマンドを実行します。

[Google Colaboratory]

1
2
# GiNZAのインストール
!pip install ginza==4.0.5

次のようなログが出力されていれば、インストールは成功しています。

[実行結果]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
Collecting ginza==4.0.5
Downloading ginza-4.0.5.tar.gz (20 kB)
Collecting spacy<3.0.0,>=2.3.2
Downloading spacy-2.3.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (10.4 MB)
|████████████████████████████████| 10.4 MB 5.5 MB/s
Collecting ja_ginza<4.1.0,>=4.0.0
Downloading ja_ginza-4.0.0.tar.gz (51.5 MB)
|████████████████████████████████| 51.5 MB 16 kB/s
Collecting SudachiPy>=0.4.9
Downloading SudachiPy-0.5.4.tar.gz (86 kB)
|████████████████████████████████| 86 kB 5.0 MB/s
Collecting SudachiDict-core>=20200330
Downloading SudachiDict-core-20210802.tar.gz (9.1 kB)
Collecting thinc<7.5.0,>=7.4.1
Downloading thinc-7.4.5-cp37-cp37m-manylinux2014_x86_64.whl (1.0 MB)
|████████████████████████████████| 1.0 MB 43.1 MB/s
Requirement already satisfied: blis<0.8.0,>=0.4.0 in /usr/local/lib/python3.7/dist-packages (from spacy<3.0.0,>=2.3.2->ginza==4.0.5) (0.4.1)
Requirement already satisfied: preshed<3.1.0,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from spacy<3.0.0,>=2.3.2->ginza==4.0.5) (3.0.5)
Requirement already satisfied: cymem<2.1.0,>=2.0.2 in /usr/local/lib/python3.7/dist-packages (from spacy<3.0.0,>=2.3.2->ginza==4.0.5) (2.0.5)
Requirement already satisfied: wasabi<1.1.0,>=0.4.0 in /usr/local/lib/python3.7/dist-packages (from spacy<3.0.0,>=2.3.2->ginza==4.0.5) (0.8.2)
Requirement already satisfied: srsly<1.1.0,>=1.0.2 in /usr/local/lib/python3.7/dist-packages (from spacy<3.0.0,>=2.3.2->ginza==4.0.5) (1.0.5)
Requirement already satisfied: numpy>=1.15.0 in /usr/local/lib/python3.7/dist-packages (from spacy<3.0.0,>=2.3.2->ginza==4.0.5) (1.19.5)
Requirement already satisfied: plac<1.2.0,>=0.9.6 in /usr/local/lib/python3.7/dist-packages (from spacy<3.0.0,>=2.3.2->ginza==4.0.5) (1.1.3)
Requirement already satisfied: requests<3.0.0,>=2.13.0 in /usr/local/lib/python3.7/dist-packages (from spacy<3.0.0,>=2.3.2->ginza==4.0.5) (2.23.0)
Requirement already satisfied: murmurhash<1.1.0,>=0.28.0 in /usr/local/lib/python3.7/dist-packages (from spacy<3.0.0,>=2.3.2->ginza==4.0.5) (1.0.5)
Requirement already satisfied: setuptools in /usr/local/lib/python3.7/dist-packages (from spacy<3.0.0,>=2.3.2->ginza==4.0.5) (57.4.0)
Requirement already satisfied: catalogue<1.1.0,>=0.0.7 in /usr/local/lib/python3.7/dist-packages (from spacy<3.0.0,>=2.3.2->ginza==4.0.5) (1.0.0)
Requirement already satisfied: tqdm<5.0.0,>=4.38.0 in /usr/local/lib/python3.7/dist-packages (from spacy<3.0.0,>=2.3.2->ginza==4.0.5) (4.62.3)
Requirement already satisfied: importlib-metadata>=0.20 in /usr/local/lib/python3.7/dist-packages (from catalogue<1.1.0,>=0.0.7->spacy<3.0.0,>=2.3.2->ginza==4.0.5) (4.8.1)
Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata>=0.20->catalogue<1.1.0,>=0.0.7->spacy<3.0.0,>=2.3.2->ginza==4.0.5) (3.6.0)
Requirement already satisfied: typing-extensions>=3.6.4 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata>=0.20->catalogue<1.1.0,>=0.0.7->spacy<3.0.0,>=2.3.2->ginza==4.0.5) (3.7.4.3)
Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests<3.0.0,>=2.13.0->spacy<3.0.0,>=2.3.2->ginza==4.0.5) (3.0.4)
Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests<3.0.0,>=2.13.0->spacy<3.0.0,>=2.3.2->ginza==4.0.5) (1.24.3)
Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests<3.0.0,>=2.13.0->spacy<3.0.0,>=2.3.2->ginza==4.0.5) (2.10)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests<3.0.0,>=2.13.0->spacy<3.0.0,>=2.3.2->ginza==4.0.5) (2021.5.30)
Collecting sortedcontainers~=2.1.0
Downloading sortedcontainers-2.1.0-py2.py3-none-any.whl (28 kB)
Collecting dartsclone~=0.9.0
Downloading dartsclone-0.9.0-cp37-cp37m-manylinux1_x86_64.whl (473 kB)
|████████████████████████████████| 473 kB 34.1 MB/s
Requirement already satisfied: Cython in /usr/local/lib/python3.7/dist-packages (from dartsclone~=0.9.0->SudachiPy>=0.4.9->ginza==4.0.5) (0.29.24)
Building wheels for collected packages: ginza, ja-ginza, SudachiDict-core, SudachiPy
Building wheel for ginza (setup.py) ... done
Created wheel for ginza: filename=ginza-4.0.5-py3-none-any.whl size=15895 sha256=05809609d54412282a3aca677bf262183e269cff4ea2eaa92b35fe485ed4104c
Stored in directory: /root/.cache/pip/wheels/ba/a9/a2/c1165c004f6dcb415b7a7d145aa4511b5024b5fb1f2eb0c0ea
Building wheel for ja-ginza (setup.py) ... done
Created wheel for ja-ginza: filename=ja_ginza-4.0.0-py3-none-any.whl size=51530813 sha256=f6d2a89bfa1850b356ef103dca344535c6472c5615cf8c7d3e0bf2f0bb964e42
Stored in directory: /root/.cache/pip/wheels/a8/f5/4a/5d4877342f912e0b7209d8a65e7ce39fe2c1a3c2511d59acfb
Building wheel for SudachiDict-core (setup.py) ... done
Created wheel for SudachiDict-core: filename=SudachiDict_core-20210802-py3-none-any.whl size=71418512 sha256=5d1f8362b682fef55a4ca03c8d4a590ed74a910a220d1d51947aac52c6d9a1c6
Stored in directory: /root/.cache/pip/wheels/91/e8/21/e80d212743835d87bb5e7eca81b6abef6d8cb67a294007a837
Building wheel for SudachiPy (setup.py) ... done
Created wheel for SudachiPy: filename=SudachiPy-0.5.4-cp37-cp37m-linux_x86_64.whl size=872116 sha256=de7e07689323107cc31919c53b64a7bad008113ae8e094daabcab14dbf5a8957
Stored in directory: /root/.cache/pip/wheels/6b/5b/8b/ce1f543c9e9af590fdc62e8344fda5a3950c60c0d21c83174e
Successfully built ginza ja-ginza SudachiDict-core SudachiPy
Installing collected packages: thinc, sortedcontainers, dartsclone, SudachiPy, spacy, SudachiDict-core, ja-ginza, ginza
Attempting uninstall: thinc
Found existing installation: thinc 7.4.0
Uninstalling thinc-7.4.0:
Successfully uninstalled thinc-7.4.0
Attempting uninstall: sortedcontainers
Found existing installation: sortedcontainers 2.4.0
Uninstalling sortedcontainers-2.4.0:
Successfully uninstalled sortedcontainers-2.4.0
Attempting uninstall: spacy
Found existing installation: spacy 2.2.4
Uninstalling spacy-2.2.4:
Successfully uninstalled spacy-2.2.4
Successfully installed SudachiDict-core-20210802 SudachiPy-0.5.4 dartsclone-0.9.0 ginza-4.0.5 ja-ginza-4.0.0 sortedcontainers-2.1.0 spacy-2.3.7 thinc-7.4.5

メニューから「ランタイム → ランタイムを再起動」を選択し、Google Colaboratoryを再起動しておきます。

トークン化

きちんとインストールできているかどうかを確認するために、トークン化を行ってみます。

[Google Colaboratory]

1
2
3
4
5
6
7
import spacy

nlp = spacy.load('ja_ginza')
doc = nlp('リモートワークできない職場なのでフリーランスになりました。')

for token in doc:
print(token)

実行結果は以下の通りです。

[実行結果]

1
2
3
4
5
6
7
8
9
10
11
12
13
リモートワーク
でき
ない
職場



フリーランス

なり
まし


きちんとトークン化できました。

次回からは、GiNZAを使って形態素解析を行います。

Transformers(16) - 要約④要約実行

学習したモデルを使って要約を行います。

要約

要約を行うコードは以下の通りです。

5行目で、日本語T5事前学習済みモデルのトークナイザーを読み込んでいます。

6行目では、前回ファインチューニングした要約モデルをoutputフォルダから読み込んでいます。

9行目に、要約対象の文章を設定しています。

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import torch
from transformers import pipeline, AutoTokenizer, AutoModelForSeq2SeqLM

# トークナイザーとモデルの準備
tokenizer = AutoTokenizer.from_pretrained('sonoisa/t5-base-japanese')
model = AutoModelForSeq2SeqLM.from_pretrained('output/')

# テキスト
text = "ぴちぴちのおねえさんが川でせんたくをしていると、ドンブラコ、ドンブラコと、大きな桃が流れてきました。おねえさんは大きな桃をひろいあげて、家に持ち帰りました。そして、ギャル男とおねえさんが桃を食べようと桃を切ってみると、なんと中から元気 の良いドランゴンの赤ちゃんが飛び出してきました。"

# テキストをテンソルに変換
input = tokenizer.encode(text, return_tensors='pt', max_length=512, truncation=True)

# 推論
model.eval()
with torch.no_grad():
summary_ids = model.generate(input)
print(tokenizer.decode(summary_ids[0]))

要約結果は以下の通りです。

[実行結果]

1
2
3
4
/usr/local/lib/python3.7/dist-packages/torch/_tensor.py:575: UserWarning: floor_divide is deprecated, and will be removed in a future version of pytorch. It currently rounds toward 0 (like the 'trunc' function NOT 'floor'). This results in incorrect rounding for negative values.
To keep the current behavior, use torch.div(a, b, rounding_mode='trunc'), or for actual floor division, use torch.div(a, b, rounding_mode='floor'). (Triggered internally at /pytorch/aten/src/ATen/native/BinaryOps.cpp:467.)
return torch.floor_divide(self, other)
<pad><extra_id_0>川でせんたくをしていると、ドンブラコ、ドンブラコと大きな桃が流れてきました。</s>

・・・要約と言えば要約されていますが、主語が全部とんでますし、中盤以降を全部省略とずいぶん大胆な要約となっています。

なんらかの改善が必要なのかもしれません。