Google ColaboratoryでGINZAを使って固有表現抽出しよう

[Google Colaboratory](https://colab.research.google.com/?hl=ja)でGINZAを利用する際の手順と最新(2023年6月)のGINZAにおける固有表現抽出の仕方についての記事です。
# GINZAとは
[GINZA](https://megagonlabs.github.io/ginza/)は日本語の自然言語処理タスクを支援するために開発されたPythonのライブラリです。GiNZAは、形態素解析、品詞タグ付け、係り受け解析などの言語解析タスクを処理するためのモデルとツールを提供しています。

GiNZAは、MeCabやJuman++などの形態素解析エンジンをベースにしており、SpaCyと統合された形で提供されています。SpaCyは、Pythonの自然言語処理ライブラリであり、GiNZAはその拡張パッケージとして機能しています。GiNZAを使用することで、日本語のテキストデータを効率的に解析し、さまざまな言語処理タスクを実行することができます。

[GiNZAの公式ウェブサイト](https://megagonlabs.github.io/ginza/)では、ライブラリの詳細な情報や使用方法、ドキュメント、コードのサンプルなどが提供されています。こちらのウェブサイトを参照することで、GiNZAのさまざまな機能や活用方法について詳細を知ることができます。

# 固有表現抽出とは
固有表現抽出（Named Entity Recognition）とは、自然言語処理の一分野であり、テキスト内から特定の種類の固有表現（人名、組織名、地名、日付など）を検出および分類するタスクです。固有表現は、文章内の特定の要素が個別の実体を指し示している場合に使用されます。

# GINZAを利用する前準備
2023年6月時点では[Google Colaboratory](https://colab.research.google.com/?hl=ja)上でのpythonのバージョンは`Python 3.10.12`でした。

今回はja-ginzaとja-ginza-electraを両方のモデルインストールしました。利用する際は必要なモデルだけで大丈夫です。ja-ginza-electraモデルでは従来のja-ginzaに比べて精度が向上したようです。ただし、ja-ginzaに比べて処理速度が遅いので、利用目的ごとに利用するモデルを選択してください。
```
!pip install -U ginza ja_ginza_electra
!pip install -U ginza ja-ginza
```
GiNZAのインストール後に、以下のコードを入力して、パッケージの再読み込みを行います。
```python
import pkg_resources, imp
imp.reload(pkg_resources)
```
2023年6月時点では以下のバージョンがインストールされました。
```
ginza                         5.1.2
ginza-transformers            0.4.2
ja-ginza                      5.1.2
ja-ginza-electra              5.1.2
```
# GINZAの基本的な使い方（形態素解析をしてみる）
日本語のテキストをトークン（単語や句読点などの単位）に分割し、それぞれのトークンに関する情報を表示しましょう。

```python
nlp = spacy.load('ja_ginza_electra')
doc = nlp('文書の形態素解析をしてみたよ')
for sent in doc.sents:
    for token in sent:
        print(
            token.i, #インデックス
            token.orth_, #表層形
            token.lemma_, #基本形
            token.norm_, #正規化形
            token.morph.get("Reading"), #読み
            token.pos_, #品詞
            token.morph.get("Inflection"),#活用形
            token.tag_, #品詞タグ
            token.dep_, #係り受け関係
            token.head.i, #係り受けの親のインデックス
        )

```
```:出力
0 桃佳 桃佳 桃佳 ['モモカ'] PROPN [] 名詞-固有名詞-人名-名 compound 1
1 ちゃん ちゃん ちゃん ['チャン'] NOUN [] 接尾辞-名詞的-一般 nsubj 10
2 は は は ['ハ'] ADP [] 助詞-係助詞 case 1
3 天王寺 天王寺 天王寺 ['テンノウジ'] PROPN [] 名詞-固有名詞-地名-一般 compound 4
4 動物園 動物園 動物園 ['ドウブツエン'] NOUN [] 名詞-普通名詞-一般 obl 10
5 に に に ['ニ'] ADP [] 助詞-格助詞 case 4
6 キリン キリン キリン ['キリン'] NOUN [] 名詞-普通名詞-一般 obj 8
7 を を を ['ヲ'] ADP [] 助詞-格助詞 case 6
8 見 見る 見る ['ミ'] VERB ['上一段-マ行;連用形-一般'] 動詞-非自立可能 advcl 10
9 に に に ['ニ'] ADP [] 助詞-格助詞 case 8
10 行き 行く 行く ['イキ'] VERB ['五段-カ行;連用形-一般'] 動詞-非自立可能 ROOT 10
11 ます ます ます ['マス'] AUX ['助動詞-マス;終止形-一般'] 助動詞 aux 10
12 。 。 。 ['。'] PUNCT [] 補助記号-句点 punct 10
```
以下は、コードの処理内容の概要です
1. ja_ginza_electraというモデルを使用して、spacy.load関数によってSpaCyのNLPパイプラインを作成します。このモデルは、日本語の形態素解析と品詞タグ付けに特化しています。
2. 解析対象の日本語テキストをnlpオブジェクトに渡します。この場合、テキストは「文書の形態素解析をしてみたよ」という文字列です。
3. docオブジェクトのsents属性を使用して、文を取得します。日本語の文は通常、句読点（「。」や「！」など）で区切られます。
4. 各文に対して、sentオブジェクト内のトークンを順に取得します。
5. トークンの各属性（インデックス、表層形、基本形、正規化形、読み、品詞、活用形、品詞タグ、係り受け関係、係り受けの親のインデックス）を表示します。これにより、トークンごとの情報が確認できます。

# 固有表現抽出とをする
docオブジェクトのents属性を使用して、テキスト内の固有表現を抽出します。
```python
import spacy
nlp = spacy.load('ja_ginza_electra')
doc = nlp('桃佳ちゃんは天王寺動物園にキリンを見に行きます。')

# 固有表現抽出
for ent in doc.ents:
    print(
        ent.text + ', '+             # 固有表現のテキスト自体
        ent.label_ + ',' +           # 固有表現のラベル（人名、地名、組織名など）
        str(ent.start_char) + ',' +  # 固有表現の開始位置（文字数）
        str(ent.end_char))           # 固有表現の終了位置（文字数）

```
```:出力
桃佳, Person,0,2
ちゃん, Title_Other,2,5
天王寺動物園, Geological_Region_Other,6,12
キリン, Mammal,13,16
```
また抽出結果を可視化することもできます。
```python
from spacy import displacy
displacy.render(doc, style='ent', jupyter=True)
```
出力
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984456/fff3c9fd-da1d-d439-1829-f5edaa709e47.png)

桃佳が「人名 (Person)」、ちゃんが「称号名＿その他 (Title_Other)」、天王寺動物園が「地形名＿その他 (Geological_Region_Other)」、キリンが「哺乳類名 (Mammal)」となっています。
また固有表現のラベルは[拡張固有表現階層　定義](http://liat-aip.sakura.ne.jp/ene/ene8/definition_jp/html/enetree.html)を参考にします。

# Wikipediaのデータで再学習する
[Wikipediaを用いた日本語の固有表現抽出データセット](https://github.com/stockmarkteam/ner-wikipedia-dataset)を用いて再学習してみましょう。[Wikipediaを用いた日本語の固有表現抽出データセット](https://github.com/stockmarkteam/ner-wikipedia-dataset)から`ner.json`をダウンロードして利用してください。
固有表現抽出モデルをトレーニングするための関数train_nerを定義しています。最終的に、トレーニングされたモデル（nlpオブジェクト）が返されます。
```python
import random
from spacy.training import Example

# 固有抽出表現の学習
def train_ner(train_data, epoch):
    # 日本語の空モデルの生成
    nlp = spacy.blank('ja')

    # 固有表現抽出のパイプの追加
    if 'ner' not in nlp.pipe_names:
        ner=nlp.add_pipe('ner')

    # ラベルの追加
    for _, annotations in train_data:
        for ent in annotations.get('entities'):
            ner.add_label(ent[2])

    # 固有表現抽出のみ学習
    other_pipes = [pipe for pipe in nlp.pipe_names if pipe != 'ner']
    with nlp.disable_pipes(*other_pipes):
        optimizer = nlp.begin_training()

        # 学習ループ
        for itn in range(epoch):
            # シャッフル
            random.shuffle(train_data)

            # 学習
            losses = {}
            for batch in spacy.util.minibatch(train_data, size=50):
              for text, annotations in batch:
                  # create Example
                  doc = nlp.make_doc(text)
                  example = Example.from_dict(doc, annotations)
                  # Update the model
                  nlp.update([example], losses=losses, drop=0.3)
            print('iteration'+str(itn)+': '+str(losses['ner']))
    return nlp
```
事前定義されたラベル（labels）とJSON形式の[Wikipediaを用いた日本語の固有表現抽出データセット](https://github.com/stockmarkteam/ner-wikipedia-dataset)（ner.json）を使用して、固有表現抽出モデルをトレーニングし、そのモデルをディスクに保存する手順を示します。トレーニングエポック数は50回とし、最後に`ner_model`として保存しています。学習には2~3時間程度かかります。
```python
labels = {
    '人名':           'Person',
    '法人名':         'Juridical_Person',
    '政治的組織名':   'Political_Organization',
    'その他の組織名': 'Organization_Other',
    '地名':           'Location',
    '施設名':         'Facility',
    '製品名':         'Product',
    'イベント名':     'Event',
}

json_data = json.load(open('ner.json', 'r'))
train_data = []
for data in json_data:
    text = data['text']
    entities = data['entities']
    value = []
    for entity in entities:
        span = entity['span']
        label = labels[entity['type']]
        value.append((span[0], span[1], label))
    train_data.append((text, {'entities': value}))


# 固有表現抽出の学習
nlp = train_ner(train_data, 50)

# 固有表現抽出モデルの保存
nlp.to_disk('ner_model')

```
先ほどと同じ文の出力結果は以下になります。桃佳ちゃんが「製品名 (Product)」になっていますが、天王寺動物園は「施設名 (Facility)」になりました。しかし、キリンは固有表現抽出できませんでした。エポック数が足りなかった可能性があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984456/c07ca2f9-b1e1-4a1f-a838-a906e9405d58.png)



# 参考にしたサイト
>[GiNZA入門 (2) - 固有表現抽出](https://note.com/npaka/n/n8c3dd573b167)
>[2020-04-28GiNZAの固有表現抽出とElasticsearchを使って自動でタグ検索](https://acro-engineer.hatenablog.com/entry/2020/04/28/120000)
>[GiNZA v5のTransformerモデルによる固有表現抽出](https://www.ai-shift.co.jp/techblog/2504)
>[【自然言語処理】GoogleColaboratoryでGiNZAがロードできない時の解決策](https://zenn.dev/robes/articles/7ee135605bdb8e)
>[spaCy入門 (1) - 事始め](https://note.com/npaka/n/nc608b9392300)