Sequential Labeling (系列ラベリング) は
系列データの入力に対して対応するラベルを付与するタスクのこと。
NLPでは POS Tagging (品詞タグ付け)、NER (固有表現抽出) などの
単語や文字に対してラベルを付与するタスクが該当する。
NER の評価方法はコードが公開されているが、 形態素解析や POS tagging といった
他のタスクでも同じ評価方法でいいのか不安になったので調べてまとめる。
NER の評価
系列ラベリングで調べるとまず出てくるのが NER。
huggingface のチュートリアルでも Token Classification というセクションで WNUT 17 dataset を使っている。
このチュートリアルで評価に使われているのが seqeval なので、このパッケージが一般的に使われていると考えてよいだろう。
seqeval について深掘りする。
seqeval と conlleval
seqeval は conlleval に基づいて実装されている。
これは CoNLL 向けに作成された perl スクリプト (元のコード)。
タスク紹介のページ を見ると
2000年のChunkingタスク、2002年のNERタスクのいずれでも同じコードを使って評価している。
また、output.html の説明によると、
正しいスパンかつ正しいラベルのときに TruePositive となるので、完全一致の評価ロジックだということがわかる。
評価に用いるデータ形式も確認しておく。
以下は CoNLL2002 データセットの esp.train.gz
からの引用。
Melbourne B-LOC
( O
Australia B-LOC
) O
, O
25 O
may O
( O
EFE B-ORG
) O
. O
- O
El O
Abogado B-PER
General I-PER
del I-PER
Estado I-PER
, O
Daryl B-PER
Williams I-PER
, O
1行に単語とスペース、IOB format) のラベルが含まれる。
空行がデータの区切り、文末を意味する。
一方 seqeval は Python で書かれており、
入力は IOB のラベルだけ受け取る。
>>> from seqeval.metrics import f1_score
>>> y_true = [['O', 'O', 'O', 'B-MISC', 'I-MISC', 'I-MISC', 'O'], ['B-PER', 'I-PER', 'O']]
>>> y_pred = [['O', 'O', 'B-MISC', 'I-MISC', 'I-MISC', 'I-MISC', 'O'], ['B-PER', 'I-PER', 'O']]
>>> f1_score(y_true, y_pred)
0.50
ほかの seqeval の差分としては、CLI には対応していない点がある。
また、IOB 以外の形式 (IOE, IOBES, BILOU) にも対応しているので、
conlleval と厳密に同じロジックではない (と思われる) 。
seqeval の評価方法
seqeval のコードをみながら、評価方法を確認する。
最新 seqeval-1.2.2
を google colab 上で動作確認した。
評価には上記データのラベルを利用。
LOC, ORG, PERの3種類の entity を予測するタスク。
y_true= [
['B-LOC', 'O', 'B-LOC', 'O', 'O', 'O', 'O', 'O', 'B-ORG', 'O', 'O'],
['O'],
['O', 'B-PER', 'I-PER', 'I-PER', 'I-PER', 'O', 'B-PER', 'I-PER', 'O'],
]
完全一致の判定方法
conlleval と同様、seqeval の評価は完全一致を TruePositive としている。
完全一致かどうかの判定には、 get_entities()
で sequence から BI 部分を 1つの entity として抽出する。
from seqeval.metrics.sequence_labeling import get_entities
for row in y_true:
print(row, get_entities(row))
>>
['B-LOC', 'O', 'B-LOC', 'O', 'O', 'O', 'O', 'O', 'B-ORG', 'O', 'O'] [('LOC', 0, 0), ('LOC', 2, 2), ('ORG', 8, 8)]
['O'] []
['O', 'B-PER', 'I-PER', 'I-PER', 'I-PER', 'O', 'B-PER', 'I-PER', 'O'] [('PER', 1, 4), ('PER', 6, 7)]
上の例以外で entity の取得を確認してみる。
先頭の1文字目が BI
以外の場合は無視される。 (IOEとIOBESにも対応しているので正確には ES
も扱える)
スタートが B
でなく I
の場合、形式としては
不正ではあるものの、 entity として扱われている。
y_invalid_samples = [
[],
["O", "A-LOC", "B-LOC"],
["O", "I-LOC", "O"],
["B-ORG", "I-PER", "O"],
["B-ORG", "B-ORG", "O"],
["B-ORG", "B-PER", "O"],
["I-ORG", "I-ORG", "O"],
]
for row in y_invalid_samples:
print(row, get_entities(row))
>>>
[] []
['O', 'A-LOC', 'B-LOC'] [('LOC', 2, 2)]
['O', 'I-LOC', 'O'] [('LOC', 1, 1)]
['B-ORG', 'I-PER', 'O'] [('ORG', 0, 0), ('PER', 1, 1)]
['B-ORG', 'B-ORG', 'O'] [('ORG', 0, 0), ('ORG', 1, 1)]
['B-ORG', 'B-PER', 'O'] [('ORG', 0, 0), ('PER', 1, 1)]
['I-ORG', 'I-ORG', 'O'] [('ORG', 0, 1)]
TruePositive のカウントは extract_tp_actual_correct
の実装の通り。
entity の種類ごとに TruePositive をカウントしている。
https://github.com/chakki-works/seqeval/blob/6fc76acf89a0b7905d04e98ec517c0488cd25bce/seqeval/metrics/v1.py#L291-L308
entity の種類は指定しなければタグの -
に続く文字列を使う。
e.g. I-LOC
-> LOC
, B-PER
-> PER
スコアの算出方法: case study
適当に y_pred
を作ってスコアがどうなるか確認。
case1: 1つ予測できない
最後のラベル 'B-PER', 'I-PER'
を 'O', 'O'
に置き換え。
正解より予測の方が entity が少ない場合。
from seqeval.metrics import accuracy_score, f1_score, classification_report, precision_score, recall_score
y_pred_miss = [
['B-LOC', 'O', 'B-LOC', 'O', 'O', 'O', 'O', 'O', 'B-ORG', 'O', 'O'],
['O'],
['O', 'B-PER', 'I-PER', 'I-PER', 'I-PER', 'O', 'O', 'O', 'O']
]
print("accuracy:\t", accuracy_score(y_true, y_pred_miss))
print("precision:\t", precision_score(y_true, y_pred_miss))
print("recall:\t ", recall_score(y_true, y_pred_miss))
print("f1_score:\t", f1_score(y_true, y_pred_miss))
print(classification_report(y_true, y_pred_miss))
>>
accuracy: 0.9047619047619048
precision: 1.0
recall: 0.8
f1_score: 0.888888888888889
precision recall f1-score support
LOC 1.00 1.00 1.00 2
ORG 1.00 1.00 1.00 1
PER 1.00 0.50 0.67 2
micro avg 1.00 0.80 0.89 5
macro avg 1.00 0.83 0.89 5
weighted avg 1.00 0.80 0.87 5
accuracy_score だけ get_entities()
を使わずラベル単位での一致率を求めている。
ラベルが2つだけ異なるので 19/21 = 0.9047619047619048
。
precision_score, recall_score, f1_score はデフォルトで average = 'micro'
のスコア。
全ての entity の TruePositive (TP) をまとめてカウントしてスコアを計算する。
この例の場合、一致していない entity は1つだけなので TP は 4。
- precision
- y_pred_miss で予測している entity は4つ
4/4 = 1.0
- recall
- y_true に entity は5つ
4/5 = 0.8
case2: 部分一致
最後のデータのラベル 'B-PER', 'I-PER', 'I-PER', 'I-PER'
を
'B-PER', 'I-PER', 'O', 'O'
にして評価。
ラベルが部分一致している場合。
y_pred_partial = [
['B-LOC', 'O', 'B-LOC', 'O', 'O', 'O', 'O', 'O', 'B-ORG', 'O', 'O'],
['O'],
['O', 'B-PER', 'I-PER', 'O', 'O', 'O', 'B-PER', 'I-PER', 'O']
]
print("accuracy:\t", accuracy_score(y_true, y_pred_partial))
print("precision:\t", precision_score(y_true, y_pred_partial))
print("recall:\t ", recall_score(y_true, y_pred_partial))
print("f1_score:\t", f1_score(y_true, y_pred_partial))
print(classification_report(y_true, y_pred_partial))
>>>
accuracy: 0.9047619047619048
precision: 0.8
recall: 0.8
f1_score: 0.8000000000000002
precision recall f1-score support
LOC 1.00 1.00 1.00 2
ORG 1.00 1.00 1.00 1
PER 0.50 0.50 0.50 2
micro avg 0.80 0.80 0.80 5
macro avg 0.83 0.83 0.83 5
weighted avg 0.80 0.80 0.80 5
完全一致のときのみ TP なので case1 と同じく TP は4つ。
- precision
- y_pred_partialで予測している entity は5つ
4/5 = 0.8
- recall
- y_true に entity は5つ
4/5 = 0.8
(case1 と同じ)
case3: 余計なラベル
最初のデータの 'O'
の1つを 'I-ORG'
に変更。
entity を正解より多く予測している場合。
y_pred_extra = [
['B-LOC', 'O', 'B-LOC', 'O', 'I-ORG', 'O', 'O', 'O', 'B-ORG', 'O', 'O'],
['O'],
['O', 'B-PER', 'I-PER', 'I-PER', 'I-PER', 'O', 'B-PER', 'I-PER', 'O']
]
print("accuracy:\t", accuracy_score(y_true, y_pred_extra))
print("precision:\t", precision_score(y_true, y_pred_extra))
print("recall:\t ", recall_score(y_true, y_pred_extra))
print("f1_score:\t", f1_score(y_true, y_pred_extra))
print(classification_report(y_true, y_pred_extra))
>>
accuracy: 0.9523809523809523
precision: 0.8333333333333334
recall: 1.0
f1_score: 0.9090909090909091
precision recall f1-score support
LOC 1.00 1.00 1.00 2
ORG 0.50 1.00 0.67 1
PER 1.00 1.00 1.00 2
micro avg 0.83 1.00 0.91 5
macro avg 0.83 1.00 0.89 5
weighted avg 0.90 1.00 0.93 5
1つ余計に予測しており、他は y_true と一致しているため TP は5。
- precision
- y_pred_extra で予測している entity は6つ
5/6 = 0.8333333333333334
- recall
- y_true の entity は5つ
5/5 = 1.0
seqeval の評価方法、注意すべき点
- seqeval は
entity
単位で評価
- entity の種類と位置いずれも一致で TruePositive
O
タグは評価に含めない (accuracy以外)
- デフォルトで micro スコア
IOB
系の schema 以外は評価不可
続く
seqeval だけで長くなってしまった。
もともと形態素解析など、日本語の系列ラベリングについて調査するのが目的だったので
別の記事にこれらの情報をまとめる予定。