この記事は自然言語処理 #2 Advent Calendar2019の3日目の記事です。
はじめに
本記事では、spaCyのcommand-line interfaceを使用した文書のカテゴリ分類の学習を扱います。
最終的にはGiNZAを使用して日本語文書の分類も行いたいのですが、残念ながら力尽きてしまったので英語の文書のみ扱います。
なお、以前執筆した以下の記事ではCLIを使用せずに日本語文書の分類を行っています。
https://qiita.com/kyamamoto9120/items/cd36017be13529366767
動機
前述の記事にて、カテゴリ分類の学習は行えるものの非常に遅いことが問題でした。
原因はCPUを1コアしか使っておらず、GPUにも対応していなかったためです。
この問題に対応するために改めてドキュメントを眺めていたところ、spaCyで学習をする際に、ほとんどの目的においてCLIを使用することが最善の方法であると紹介されていることに気が付きました。
https://spacy.io/usage/training#spacy-train-cli
一方で、spaCyのCLIを使用して学習を行う方法についてはあまり多く語られておらず、日本語の情報は皆無に等しい状態です。
そこで、誰かの役に立てばと思って挑戦した次第です。
まさかり大歓迎ですのでよろしくお願いいたします。
前提条件
本記事はspaCy 2.2.3で検証しています。
実行環境にはgoogle ColabのGPUランタイムを使用しており、GPUはTesla P100です。
また、データセットにはscikit-learnのfetch_20newsgroupsを利用します。
20カテゴリのニュース記事で日本語でいうlivedoor ニュースコーパスに近いものなのかなと思っています。
データの準備
spaCyのCLIで学習をするためには、データを以下のようなJSON形式にする必要があります。
[{
"id": int, # ID of the document within the corpus
"paragraphs": [{ # list of paragraphs in the corpus
"raw": string, # raw text of the paragraph
"sentences": [{ # list of sentences in the paragraph
"tokens": [{ # list of tokens in the sentence
"id": int, # index of the token in the document
"dep": string, # dependency label
"head": int, # offset of token head relative to token index
"tag": string, # part-of-speech tag
"orth": string, # verbatim text of the token
"ner": string # BILUO label, e.g. "O" or "B-ORG"
}],
"brackets": [{ # phrase structure (NOT USED by current models)
"first": int, # index of first token
"last": int, # index of last token
"label": string # phrase label
}]
}],
"cats": [{ # new in v2.2: categories for text classifier
"label": string, # text category label
"value": float / bool # label applies (1.0/true) or not (0.0/false)
}]
}]
}]
出典:https://spacy.io/api/annotation#json-input
spaCyのDocから上記JSON形式への変換はgold.docs_to_json
を使用することで行うことができます。
以下はexampleを参考にfetch_20newsgroupsのデータセットを上記JSON形式に変換する処理です。
import spacy
import srsly
from spacy.gold import docs_to_json
from sklearn.datasets import fetch_20newsgroups
from sklearn.model_selection import train_test_split
def save_to_json(model, data, targets, target_names, output_file, n_texts=0):
def get_categories(target):
return dict([(key, int(target == i)) for i, key in enumerate(target_names)])
nlp = spacy.load(model)
nlp.disable_pipes(*nlp.pipe_names)
sentencizer = nlp.create_pipe("sentencizer")
nlp.add_pipe(sentencizer, first=True)
docs = []
count = 0
for i, doc in enumerate(nlp.pipe(data)):
doc.cats = get_categories(targets[i])
docs.append(doc)
if n_texts > 0 and count == n_texts:
break
count += 1
srsly.write_json(output_file, [docs_to_json(docs)])
return count
newsgroups_train = fetch_20newsgroups(subset='train')
newsgroups_test = fetch_20newsgroups(subset='test')
X_train, X_dev, y_train, y_dev = train_test_split(newsgroups_train.data, newsgroups_train.target, test_size=0.20, random_state=42)
save_to_json(
'en_core_web_sm',
X_train,
y_train,
newsgroups_train.target_names,
'train.json',
)
save_to_json(
'en_core_web_sm',
X_dev,
y_dev,
newsgroups_train.target_names,
'dev.json',
)
save_to_json(
'en_core_web_sm',
newsgroups_test.data,
newsgroups_test.target,
newsgroups_test.target_names,
'test.json',
)
学習
学習はpython -m spacy train
コマンドで行います。
ベースモデルの指定に-b
ではなく-v
を使用していますが、これは以下のIssueを参考にしています。
参考:https://github.com/explosion/spaCy/issues/4504
$ time python -m spacy train en output train.json dev.json -v en_core_web_sm -p textcat -ta simple_cnn -g 0
Training pipeline: ['textcat']
Starting with blank model 'en'
Loading vector from model 'en_core_web_sm'
Counting training words (limit=0)
tcmalloc: large alloc 2035253248 bytes == 0x65048000 @ 0x7fdf2bb321e7 0x5acc1b 0x7fdf2115d5db 0x7fdf2115dbf0 0x7fdf2115de36 0x7fdf2115b5c1 0x50abc5 0x50c549 0x7fdec8648b20 0x7fdec864d98f 0x7fdec86406c5 0x7fdec868fc47 0x7fdecb4f172a 0x7fdec867cfce 0x7fdecb4f172a 0x7fdec86a91e7 0x7fdecb4f172a 0x7fdec8670148 0x7fdec867a24b 0x594f4c 0x54a2c5 0x551761 0x5aa69c 0x50ab53 0x50d320 0x5081d5 0x5895e1 0x5a04ce 0x50d8f5 0x5081d5 0x50a020
Textcat evaluation score: F1-score macro-averaged across the labels
'alt.atheism, comp.graphics, comp.os.ms-windows.misc, comp.sys.ibm.pc.hardware,
comp.sys.mac.hardware, comp.windows.x, misc.forsale, rec.autos, rec.motorcycles,
rec.sport.baseball, rec.sport.hockey, sci.crypt, sci.electronics, sci.med,
sci.space, soc.religion.christian, talk.politics.guns, talk.politics.mideast,
talk.politics.misc, talk.religion.misc'
Itn Textcat Loss Textcat Token % CPU WPS GPU WPS
--- ------------ ------- ------- ------- -------
1 1300.851 45.507 100.000 34746 125805
2 210.326 62.555 100.000 34629 125121
3 155.350 71.460 100.000 35131 125583
4 120.228 76.617 100.000 35244 126553
5 104.154 78.516 100.000 35371 126499
6 64.690 80.724 100.000 35491 126734
7 57.947 82.024 100.000 35750 123946
8 49.666 82.662 100.000 35840 126354
9 38.450 83.410 100.000 35538 126965
10 36.798 83.496 100.000 35435 125634
11 24.351 83.933 100.000 35467 125578
12 24.935 83.989 100.000 35490 125223
13 26.003 84.009 100.000 35329 125631
14 16.517 84.013 100.000 35255 123643
15 16.210 84.367 100.000 35234 124040
16 16.640 84.601 100.000 35554 126235
17 17.365 84.494 100.000 35436 126539
18 18.860 84.866 100.000 35596 126301
19 11.555 84.910 100.000 35644 126256
20 17.691 84.916 100.000 35481 125505
21 16.697 85.556 100.000 35767 125510
22 13.508 84.815 100.000 35795 126961
23 12.806 84.610 100.000 36021 126313
24 6.776 84.754 100.000 36155 126526
25 10.806 85.122 100.000 35866 126933
26 10.999 85.007 100.000 35972 126758
27 8.463 85.224 100.000 36065 125702
28 7.601 84.920 100.000 35606 127107
29 10.220 85.088 100.000 36242 127223
30 7.911 84.889 100.000 36157 128428
✔ Saved model to output directory
output/model-final
✔ Created best model
output/model-best
real 97m9.901s
user 87m25.982s
sys 9m32.144s
雰囲気学習が行えている様子で、GPUも使っているようです。
実際、Tesla P100を使用するとCPUのみの場合に比べてだいぶ高速に学習を行うことができます。
K80だとCPUのみとそれほど差が出なかったので、Google Colabを使用する際にはGPUを厳選したほうが良いのかなと思います。
評価
学習はpython -m spacy evaluate
コマンドで行います。
こちらはハマりどころもなく、スムーズに実行することができました。
$ time python -m spacy evaluate output/model-best/ test.json -g 0
tcmalloc: large alloc 1610211328 bytes == 0x4ce4c000 @ 0x7fce167961e7 0x5acc1b 0x7fce0bdc15db 0x7fce0bdc1bf0 0x7fce0bdc1e36 0x7fce0bdbf5c1 0x50abc5 0x50c549 0x7fcdb32acb20 0x7fcdb32b198f 0x7fcdb32a46c5 0x7fcdb32f3c47 0x7fcdb615572a 0x7fcdb32e0fce 0x7fcdb615572a 0x7fcdb330d1e7 0x7fcdb615572a 0x7fcdb32d4148 0x7fcdb32de24b 0x594f4c 0x54a2c5 0x551761 0x5aa69c 0x50ab53 0x50c549 0x5081d5 0x5895e1 0x5a04ce 0x50d8f5 0x5081d5 0x50a020
tcmalloc: large alloc 1610211328 bytes == 0x4ce4c000 @ 0x7fce167961e7 0x5acc1b 0x7fce0bdc15db 0x7fce0bdc1bf0 0x7fce0bdc1e36 0x7fce0bdbf5c1 0x50abc5 0x50c549 0x7fcdb32acb20 0x7fcdb32b198f 0x7fcdb32a46c5 0x7fcdb32f3c47 0x7fcdb615572a 0x7fcdb32e0fce 0x7fcdb615572a 0x7fcdb330d1e7 0x7fcdb615572a 0x7fcdb32d4148 0x7fcdb32de3fe 0x594f4c 0x54a2c5 0x551761 0x5aa69c 0x50ab53 0x50c549 0x5081d5 0x5895e1 0x5a04ce 0x50d8f5 0x5081d5 0x50a020
================================== Results ==================================
Time 24.76 s
Words 3124908
Words/s 126185
TOK 100.00
POS 0.00
UAS 0.00
LAS 0.00
NER P 0.00
NER R 0.00
NER F 0.00
Textcat 70.99
real 1m22.570s
user 1m13.028s
sys 0m9.359s
学習時のスコアに比べると評価時のスコアがだいぶ低くなっています。
原因は調べられていないですが、この辺は実際のタスクに取り組む際の課題なのかなと思います。
今回は一切の前処理などを行っていないため、改善の余地は大いにあると感じています。
まとめ
本記事では、spaCyのCLIを使用してfetch_20newsgroupsというデータセットの分類を行いました。
拙い記事となってしまいましたが、誰かの参考になれば幸いです。
次は同様の手法で日本語文書のカテゴリ分類が行えたらと考えています。