nikkie-ftnextの日記

イベントレポートや読書メモを発信

transformersのドキュメントの中からLukeForEntitySpanClassificationで固有表現認識する例を触る

はじめに

スカイウォーカー! nikkieです。

えぬえるぴーやな素振り記事です。
るうく(LUKE)なるものが面妖なので触っていきます

目次

動作環境

  • Python 3.10.9
  • pip install 'transformers[torch,sentencepiece]'で以下が入りました(主要なものをリストアップ)
    • numpy==1.25.2
    • sentencepiece==0.1.99
    • tokenizers==0.13.3
    • torch==2.0.1
    • transformers==4.33.1

LUKEって何よ

この記事の執筆時点では全然説明できません。
Language Understanding with Knowledge-based Embeddingsの略でLUKE。
エンティティの情報もマスクして学習してる言語モデルみたいなんですが、今はこれ以上は説明できないですね...

説明できるようになれそうな積ん読

新しい概念を論文や解説スライドから理解するのは得意ではないので、得意分野のコーディングに引きずり込んで触ってみることにしました。

transformersのドキュメントの例

LukeForEntitySpanClassification
https://huggingface.co/docs/transformers/v4.33.0/en/model_doc/luke#transformers.LukeForEntitySpanClassification

conll2003データセットでファインチューニングされたLUKEを読み込んで

「Beyoncé lives in Los Angeles」(ビヨンセはロサンゼルスに住んでいます)を固有表現認識します。

対話モードでの例を、スクリプト化しました。

スクリプトを実行すると

Beyoncé PER
Los Angeles LOC

Beyoncé(人名)、Los Angeles(地名)が認識できていますね!

深掘り

スクリプト-iオプション付きで実行して、気になる箇所を見ていきます。

テキスト中の単語に関するインデックス

text = "Beyoncé lives in Los Angeles"
word_start_positions = [0, 8, 14, 17, 21]
word_end_positions = [7, 13, 16, 20, 28]

単語の開始と終了のインデックスです。
終了のインデックスは単語に含まないので、英文中の空白文字を指していますね

>>> for start_pos, end_pos in zip(word_start_positions, word_end_positions):
...   print(text[start_pos:end_pos])
...
Beyoncé
lives
in
Los
Angeles

EntitySpan

EntitySpanなるものを扱います。

entity_spans = []
for i, start_pos in enumerate(word_start_positions):
    for end_pos in word_end_positions[i:]:
        entity_spans.append((start_pos, end_pos))

作られたentity_spansの中身を見ると

>>> len(entity_spans)
15
>>> for span in entity_spans:
...   print(text[span[0]:span[1]])
...
Beyoncé
Beyoncé lives
Beyoncé lives in
Beyoncé lives in Los
Beyoncé lives in Los Angeles
lives
lives in
lives in Los
lives in Los Angeles
in
in Los
in Los Angeles
Los
Los Angeles
Angeles

複数の単語を1つのスパンにまとめています。
「Beyoncé lives in Los Angeles」で取りうるスパンを全列挙した形になっています。

モデルはEntitySpanを分類

inputs = tokenizer(text, entity_spans=entity_spans, return_tensors="pt")
outputs = model(**inputs)
logits = outputs.logits
predicted_class_indices = logits.argmax(-1).squeeze().tolist()
>>> model.config.id2label  # EntitySpanを5クラス分類
{0: 'NIL', 1: 'MISC', 2: 'PER', 3: 'ORG', 4: 'LOC'}

>>> inputs["input_ids"]  # 12674は' Beyon'、12695は'cé'
tensor([[    0, 12674, 12695,  1074,    11,  1287,  1422,     2]])
>>> inputs["entity_start_positions"]  # torch.Size([1, 15])
tensor([[1, 1, 1, 1, 1, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6]])
>>> inputs["entity_end_positions"]
tensor([[2, 3, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 5, 6, 6]])

>>> logits.size()  # 15はすべてのEntitySpan
torch.Size([1, 15, 5])
>>> predicted_class_indices  # スコア最大のクラス(のインデックス)
[2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0]

LukeForEntitySpanClassificationという名前の通り、EntitySpanを分類しました。

  • 「Beyoncé」というEntitySpanはクラス2(PER)
  • 「Los Angeles」というEntitySpanはクラス4(LOC)

感想(終わりにに代えて)

書籍『大規模言語モデル入門』では、BERT-CRFを使って固有表現認識しました。

そのときはトークン単位でBIOのラベルを付与しました。
Bのトークンとその後のIのトークンを拾って、1つの固有表現とします。

それに対して、今回触ったLUKEはEntitySpanを分類します。
BIOのラベルは不要で、上で見たような5クラスのみ。
「これでうまくいくんだ〜」とか「モデルの中は何をやっているんだろう?」と興味は尽きない感じです。
また素振りしてみよう