Skip to content

Commit

Permalink
READMEと評価スクリプト追加
Browse files Browse the repository at this point in the history
  • Loading branch information
hoOttOo committed Mar 6, 2019
1 parent 67d5f73 commit 9dbdbdf
Show file tree
Hide file tree
Showing 1,584 changed files with 115,182 additions and 0 deletions.
95 changes: 95 additions & 0 deletions Classification/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# (2019/01/23更新)結果公開と評価

## 評価スクリプトの使用方法
### 各スクリプトファイルの説明
* poliinfo_createGS_formal_classification.py
* トピックごとに複数あるtestGSファイルを評価スクリプト用に一つにまとめます.
* poliinfo_eval_formal_classification.py
* 上記スクリプトで作成したファイルを用いて,推定結果の自動評価を行います.

### 評価までの手順
1. 各トピックのtestGSファイルを評価用に適した形式に変換する

    ```
    python3 poliinfo_createGS_formal_classification.py -i testGS/Classification-2018-JA-FormalRun-Test-02-カジノを含む統合型リゾートを推進するべきである-365-* > new_gs.json
    ```

1. 上記で作成したデータを用いて,結果データを評価する.

```
python3 poliinfo_eval_formal_classification.py -a new_gs.json -o output_directory -i input_file1 [input_file2 ...]
```

* ```-o```オプションで指定したディレクトリにはインスタンスごとの結果と混同行列表が出力されます.
* 標準出力には```-i```オプションで指定したすべての入力データの総合評価結果が出力されます.

Formal Runの提出データと評価結果を本リポジトリで公開しています.
各ディレクトリ,ファイルの内容は以下の通りです.

* ```evals```ディレクトリ(各トピック番号のサブディレクトリがあります)
* ```ConfusionMatrix-Classification-2018-JA-FormalRun-Test-[TOPIC]_[GROUP_ID]-[PRIORITY].txt```
* ```[GROUP_ID]``````[PRIORITY]```提出における混同行列表をタブ区切りテキスト形式で表したものです.
* 各行が正解,各列が提出結果の出力値を表しています.
* 行における```10```, ```20```は,正解がそれぞれ```1``````0``````2``````0```両方あることを表します.
* 行における```-```は,正解が存在しないことを表します.この行に対応するインスタンスは評価値の計算対象から除外されます.
* Classificationタスクでは正解のみなし方が複数あるため,表もその分の数だけあります.
* 正解のみなし方の名前は,各表の1行目部分,```正解[正解のみなし方名]```(例:正解N1)に記載があります.各正解のみなし方は後述します.
* ```Result-Classification-2018-JA-FormalRun-Test-[TOPIC]_[GROUP_ID]-[PRIORITY].txt```
* ```[GROUP_ID]``````[PRIORITY]```提出における各インスタンスの正誤結果をタブ区切りテキスト形式で表したものです.
* ```submits```ディレクトリ(各トピック番号のサブディレクトリがあります)
* 本タスクにて提出されたすべてのAnswerSheetデータを配置しています.
* ```eval-Classification-2018-JA-Formal-[TOPIC_NUM].xlsx```
* ```[TOPIC_NUM]```のトピックにおける全提出データを対象とした評価結果一覧です.
* 各列は```[正解のみなし方]_[評価対象]_[評価指標]```という形式です.
* ```Acc```: Accuracy
* ```P[i]```: ラベル```i```におけるPrecision
* ```R[i]```: ラベル```i```におけるRecall

## 略語の意味
* 正解のみなし方
* ```[アルファベット1文字]```: アルファベットは作成者IDを表す.その作成者による成果情報を用いたGold Standard.
* ```N1```: 作成者が1人でも付与したラベルを正解とみなす.
* ```N2```: 作成者が2人以上付与したラベルを正解とみなす.
* ```N3```: 作成者が3人以上付与したラベルを正解とみなす.3人以上付与したラベルがない場合は正解なしとして,評価対象から除外する.
* ```SC```: 作成者が何人付与したかを正解スコアの重みとして用いる,(例えば,```0```が2名,```1```が1名の場合,出力```0```に対しては2点,出力```1```に対しては1点という評価をする)
* 評価対象
* ```Rl```: Relevance
* ```FC```: Fact-checkability
* ```St```: Stance
* ```Cl```: Class


# Formal Run提出データの書式チェッカースクリプトについて

Formal Run提出データ(Answer Sheet)の書式をチェックするPythonスクリプトです.
結果を提出する際は,本スクリプトによるチェックをパスしたファイルを提出してください.

## 動作環境

* Python 3.5以上
* Windows, macOS, Linux環境で動作確認済み

## 使用方法

```bash
python3 poliinfo_check_format2.py [-h] -t {seg,sum,cls} json_file
```

* 必須引数:
* ```json_file```: 対象JSONファイル(指定しない場合は標準入力から読み込みます)
* ```-t {seg,sum,cls}, --type {seg,sum,cls}```: 対象ファイルの種別を指定し
* (```seg```:Segmentation, ```sum```:Summarization, ```cls```:Classification)
* オプション引数:
* ```-h, --help```: ヘルプ表示

| 実行結果 | 意味 |
|:--------|:----|
| チェックOK | 書式チェックをすべてパスしたことを表します. |
| [JSON全体の文法性] ...エラーあり | ファイルがJSONの書式を満たしていないことを表します.エラーの内容と箇所も併せて表示されます. |
| 先頭行は "[" のみでなければなりません. | 本提出データの先頭行は ```[``` のみとしてください. |
| 末尾行は "]" のみでなければなりません. | 本提出データの末尾行は ```]``` のみとしてください. |
| [line *n*] エラー,行が1つのオブジェクトになっていません. | (ファイルの*n*行目について)1行につき1つのオブジェクト表記としてください. |
| [line *n*] エラー,出力値 "xxx" が必要です. | (ファイルの*n*行目について)出力値として必須である ```xxx``` 項目が欠如しています. |
| [line *n*] エラー,識別子 "ID" が必要です. | (ファイルの*n*行目について)識別子として必須である ```ID``` 項目が欠如しています. |
| [line *n*] すでに出力済みの識別子を持つ行です. | (ファイルの*n*行目について)このIDのインスタンスは以前の行で出力済みです. |
| [line *n*] エラー,同名のフィールドが複数存在します.. | (ファイルの*n*行目について)同名の項目が2つ以上存在してはいけません. |
128 changes: 128 additions & 0 deletions Classification/poliinfo_check_format2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""NTCIR-14 QA Lab PoliInfoタスクのOutputとなるJSONファイルの書式チェックを行うプログラム.
Formal Runフォーマット修正版.
更新:2018.09.28
作成者:乙武 北斗
"""

import sys
import argparse
import fileinput
import json


id_fields = {
'seg': {'ID'},
'sum': {'ID'},
'cls': {'ID'}
}

output_fields = {
'seg': {'QuestionStartingLine', 'QuestionEndingLine', 'AnswerStartingLine', 'AnswerEndingLine'},
'sum': {'Summary'},
'cls': {'Relevance', 'Fact-checkability', 'Stance', 'Class'}
}


def construct_dict(pairs):
assert len(pairs) == len(set(pair[0]
for pair in pairs)), '同名のフィールドが複数存在します.'
return dict(pairs)


def get_args():
parser = argparse.ArgumentParser(description="""NTCIR-14 QA Lab PoliInfoタスクのOutputとなるJSONファイルの書式チェックを行います.
タスクを[-t|--type]オプションにて指定の上,ご利用ください.""")

# 標準入力がない場合は,ファイル指定必須
if sys.stdin.isatty():
parser.add_argument('json_file', help='対象JSONファイル(指定しない場合は標準入力から読み込みます)')

parser.add_argument('-t', '--type',
choices=['seg', 'sum', 'cls'],
nargs=1,
required=True,
help='対象ファイルの種別を指定します(seg:Segmentation, sum:Summarization, cls:Classification)')

return parser.parse_args()


def main():
args = get_args()

# JSON読み込み
src = '-' if not hasattr(args, 'json_file') else args.json_file
lines = [line.rstrip() for line in fileinput.input(
src, openhook=fileinput.hook_encoded('utf-8'))]

# JSON全体の文法チェック
try:
print('[JSON全体の文法性] ...', end='')
json.loads('\n'.join(lines))
except json.JSONDecodeError as e:
print('エラーあり')
print(e)
exit(1)
else:
print('OK')

# 先頭・末尾行のチェック
print('[先頭行・末尾行] ...', end='')
if lines[0] != '[':
print('エラーあり')
print('先頭行は "[" のみでなければなりません.')
exit(1)
if lines[-1] != ']':
print('エラーあり')
print('末尾行は "]" のみでなければなりません.')
exit(1)
print('OK')

# 各レコードのチェック
id_set = set()
for i, line in enumerate(lines[1:-1]): # type: int, str
# 行末のカンマを取り除く
line = line.rstrip(',')

# 1行がJSONオブジェクトになっているかチェック
try:
obj = json.loads(
line, object_pairs_hook=construct_dict) # type: dict
# 1行が1オブジェクトかどうか
if type(obj) != dict:
print('[line {0}] エラー,行が1つのオブジェクトになっていません.'.format(i+2))
exit(1)
# 識別子の存在チェック
for id_key in id_fields[args.type[0]]:
if id_key not in obj:
print('[line {0}] エラー,識別子 "{1}" が必要です.'.format(
i+2, id_key))
exit(1)
# 出力値の存在チェック
for out_key in output_fields[args.type[0]]:
if out_key not in obj:
print('[line {0}] エラー,出力値 "{1}" が必要です.'.format(
i+2, out_key))
exit(1)
# id重複チェック
id_tuple = tuple([obj[k] for k in id_fields[args.type[0]]])
if id_tuple in id_set:
print('[line {0}] すでに出力済みの識別子を持つ行です.'.format(i+2))
exit(1)
id_set.add(id_tuple)

except json.JSONDecodeError as e:
print('[line {0}] 文法エラー,行が1オブジェクトになっていることを確認してください.'.format(i+2))
print(e)
exit(1)
except AssertionError as e:
print('[line {0}] エラー,{1}'.format(i+2, e))
exit(1)
print('チェック成功')


if __name__ == '__main__':
main()
100 changes: 100 additions & 0 deletions Classification/poliinfo_createGS_formal_classification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""NTCIR-14 QA Lab PoliInfo ClassificationタスクのGSデータ作成スクリプト(Formal Run版).
1 3人が一致したsupport/againstをつけた場合support/against、それ以外other
2 2人が(以下略)
3 1人が(以下略)
4 作業者AをGSDとした場合
5 作業者Bを(以下略)
6 作業者Cを(以下略)
7 support/againstをつけた数がスコア
このうち,4〜6はGSがすでに存在するので,このスクリプトでは作成対象外とする.
更新:2018.11.19
作成者:乙武 北斗
"""

import sys
import argparse
import json
import math
from collections import Counter
from pathlib import Path
from typing import List, Dict, Tuple


def get_args():
parser = argparse.ArgumentParser(
description="""NTCIR-14 QA Lab PoliInfo ClassificationタスクのGSデータ作成スクリプト(Formal Run版).""")

parser.add_argument('-i', '--input-gs-files',
nargs='+',
required=True,
help='ベースとなるGSデータをすべて指定する')

return parser.parse_args()


def load_gs(files: List[str]) -> Tuple[List[dict], Dict[str, Dict[str, dict]]]:
"""
2つの値を返す:
[0]: 出力値以外の要素を持ったインスタンスを出現順に記録したリスト
[1]: IDをキーとして,出力値を各アノテーターごとに記録したdict
"""
l = []
gs_map = {}
for i, file in enumerate(files):
p = Path(file)
annotator = p.stem.split('-')[-1]
with open(file) as f:
json_array = json.load(f)

for ins in json_array:
# 最初のGSからは出力値以外を抽出して記録しておく
if i == 0:
l.append({
'ID': ins['ID'],
'Topic': ins['Topic'],
'Utterance': ins['Utterance']
})

idstr = ins['ID'].split('-')[-1]
if idstr not in gs_map:
gs_map[idstr] = {}
gs_map[idstr][annotator] = {
'Relevance': ins['Relevance'],
'Fact-checkability': ins['Fact-checkability'],
'Stance': ins['Stance'],
'Class': ins['Class']
}

return l, gs_map


def main():
args = get_args()

# GS読み込み
gs_list, gs_map = load_gs(args.input_gs_files)
num = len(gs_list)

print('[')
for i, ins in enumerate(gs_list):
idstr = ins['ID'].split('-')[-1]

d_m = {
'Relevance': {k: v['Relevance'] for k, v in gs_map[idstr].items()},
'Fact-checkability': {k: v['Fact-checkability'] for k, v in gs_map[idstr].items()},
'Stance': {k: v['Stance'] for k, v in gs_map[idstr].items()},
'Class': {k: v['Class'] for k, v in gs_map[idstr].items()}
}

print(json.dumps(dict(d_m, **ins), ensure_ascii=False), end='')
print(',') if i < num - 1 else print('')

print(']')


if __name__ == '__main__':
main()
Loading

0 comments on commit 9dbdbdf

Please sign in to comment.