日本語自然言語処理のData Augmentationライブラリdaajaを作りました

概要

こんにちは@kajyuuenです。 日本語自然言語処理のData Augmentationライブラリdaajaを作成しました。 この記事ではdaajaが実装しているData Augmentation手法についての解説とその使い方について紹介します。

ソースコードは以下のリポジトリで公開しています。

github.com

また、このライブラリはPyPIに公開しているのでpip install daajaでインストールが可能です。

はじめに

Data Augmentationとは

Data Augmentationとは元のデータから新しいデータを生成し、データ数を増やす手法です。 日本語ではデータ拡張という名前で知られています。 ラベル付きデータを擬似的に増やすことによって、アノテーションコストを必要とせずにモデルの汎化性能や精度の向上が期待できます。

対応している手法

現在daajaは、次の2つの論文で紹介されているData Augmentation手法を実装しています。

EDAが文書分類タスク向け、SDAが固有表現抽出向けのData Augmentation手法です。それぞれの手法について、詳しく説明していきます。

EDA: Easy Data Augmentation Techniques for Boosting Performance on Text Classification Tasks

この論文では自然言語処理の分類タスクに使える4つのData Augmentation手法について紹介しています。それぞれの手法について、詳細とdaajaを使った呼び出し方を記載します。

各手法について

Synonym Replacement

これは文章中のストップワードを除いた$N$つの単語を同義語に置き換える手法です。ハイパーパラメータ$\alpha$と文章中の単語数$l$によって、この$N$は定まり、$N=l\alpha$と計算されます。$N$はこのあと紹介する手法のRandom Insertion, Synonym Replacementでも同じように算出されます。

from daaja.augmentors.sentence.synonym_replace_augmentor import \
    SynonymReplaceAugmentor
augmentor = SynonymReplaceAugmentor(alpha=0.1)
text = "日本語でデータ拡張を行う"
print(augmentor.augment(text)) # => 日本語でデータ拡張をする

Random Insertion

文章中のストップワードを除いた$N$つの単語の同義語をランダムに挿入する手法です。

from daaja.augmentors.sentence.randam_insert_augmentor import \
    RandamInsertAugmentor
augmentor = RandamInsertAugmentor(alpha=0.1)
text = "日本語でデータ拡張を行う"
print(augmentor.augment(text)) # => 日本語でデータ拡張押し広げるを行う

Random Swap

文章中の2つの単語をランダムに選び、入れ替える処理を$N$回行う手法です。

from daaja.augmentors.sentence.randam_swap_augmentor import RandamSwapAugmentor
augmentor = RandamSwapAugmentor(alpha=0.1)
text = "日本語でデータ拡張を行う"
print(augmentor.augment(text)) # => データで日本語拡張を行う

Random Deletion

文章中の各単語を確率$p$でランダムに削除する手法です。この$p$はハイパーパラメータで、論文中では$p=\alpha$としています。

from daaja.augmentors.sentence.randam_delete_augmentor import \
    RandamDeleteAugmentor
augmentor = RandamDeleteAugmentor(p=0.1)
text = "日本語でデータ拡張を行う"
print(augmentor.augment(text)) # => 日本語でデータを行う

EasyDataAugmentor

EasyDataAugmentorはそれぞれの手法のハイパーパラメータ$\alpha$と生成する文章数$n$を指定し、今まで紹介した4つの手法を一度に実行するクラスです。

from daaja.methods.eda.easy_data_augmentor import EasyDataAugmentor
augmentor = EasyDataAugmentor(alpha_sr=0.1, alpha_ri=0.1, alpha_rs=0.1, p_rd=0.1, num_aug=4)
text = "日本語でデータ拡張を行う"
print(augmentor.augments(text))
# => ['日本語でを拡張データ行う', '日本語でデータ押広げるを行う', '日本語でデータ拡張を行う', '日本語で智見拡張を行う', '日本語でデータ拡張を行う']

An Analysis of Simple Data Augmentation for Named Entity Recognition

この論文では自然言語処理の固有表現抽出タスクに使える4つのData Augmentation手法について紹介しています。

各手法について

Label-wise token replacement (LwTR)

文章中の各単語を二項分布${\rm Bin}(p)$に基づいて別の単語に置き換えます。 置き換え先の単語はデータセット中の同じラベルを持つ他の単語から選択されます。

from daaja.augmentors.sequence_labeling.labelwise_token_replacement_augmentor import \
    LabelwiseTokenReplacementAugmentor
from daaja.augmentors.sequence_labeling.utils import get_token2prob_in_label

tokens_list = [
    ["私", "は", "田中", "と", "いい", "ます"],
]
labels_list = [
    ["O", "O", "B-PER", "O", "O", "O"],
]

augmentor = LabelwiseTokenReplacementAugmentor(get_token2prob_in_label(tokens_list, labels_list), p=0.1)
target_tokens = ["君", "は", "吉田", "さん", "かい"]
target_labels = ["O", "O", "B-PER", "O", "O"]
print(augmentor.augment(target_tokens, target_labels))
# => (['私', 'は', '田中', 'さん', 'かい'], ['O', 'O', 'B-PER', 'O', 'O'])

Synonym replacement

LwTRと同様に文章中の各単語を二項分布${\rm Bin}(p)$に基づいて別の単語に置き換えます。 置き換え先の単語は置き換え元の単語の同義語から選ばれます。

from daaja.augmentors.sequence_labeling.synonym_replacement_augmentor import \
    SynonymReplacementAugmentor

augmentor = SynonymReplacementAugmentor(p=0.5)
target_tokens = ["君", "は", "吉田", "さん", "かい"]
target_labels = ["O", "O", "B-PER", "O", "O"]
print(augmentor.augment(target_tokens, target_labels)
# => (['雇い主', 'は', '田中', '君', 'かい'], ['O', 'O', 'B-PER', 'O', 'O'])

Mention replacement

文中の各固有表現を二項分布${\rm Bin}(p)$に基づいて、同じタイプの固有表現に置き換えます。 LwTRがラベル単位(B-LOCI-LOCなど)の置き換えだったのに対して、こちらはタイプ単位(LOCなど)で置き換えが発生します。 置き換わる単語はデータセット中の同じタイプの固有表現から選ばれます。

from daaja.augmentors.sequence_labeling.mention_replacement_augmentor import \
    MentionReplacementAugmentor
from daaja.augmentors.sequence_labeling.utils import get_entity_dict

tokens_list = [
    ["私", "は", "田中", "太郎", "です"],
]
labels_list = [
    ["O", "O", "B-PER", "I-PER", "O"],
]

entity_dict = get_entity_dict(tokens_list, labels_list)
augmentor = MentionReplacementAugmentor(entity_dict, p=1)
target_tokens = ["君", "は", "吉田", "さん", "かい"]
target_labels = ["O", "O", "B-PER", "O", "O"]
print(augmentor.augment(target_tokens, target_labels))
# => (['君', 'は', '田中', '太郎', 'さん', 'かい'], ['O', 'O', 'B-PER', 'I-PER', 'O', 'O'])

Shuffle within segments

文章を固有表現のタイプで区切り、各区切りについてシャッフルするか二項分布${\rm Bin}(p)$に基づいて決定します。

from daaja.augmentors.sequence_labeling.shuffle_within_segments_augmentor import \
    ShuffleWithinSegmentsAugmentor

augmentor = ShuffleWithinSegmentsAugmentor(p=1)
target_tokens = ["君", "が", "東京", "出身", "の", "田中", "君", "かい"]
target_labels = ["O", "O", "B-LOC", "O", "O", "B-PER", "O", "O"]

print(augmentor.augment(target_tokens, target_labels))
# => (['が', '君', '東京', '出身', 'の', '田中', '君', 'かい'], ['O', 'O', 'B-LOC', 'O', 'O', 'B-PER', 'O', 'O'])

SimpleDataAugmentationforNER

SimpleDataAugmentationforNERはそれぞれの手法のハイパーパラメータと生成する文章数を指定、今まで紹介した4つの手法を一度に実行するクラスです。

from daaja.methods.ner_sda.simple_data_augmentation_for_ner import \
    SimpleDataAugmentationforNER

tokens_list = [
    ["私", "は", "田中", "と", "いい", "ます"],
    ["筑波", "大学", "に", "所属", "して", "ます"],
]
labels_list = [
    ["O", "O", "B-PER", "O", "O", "O"],
    ["B-ORG", "I-ORG", "O", "O", "O", "O"],
]
augmentor = SimpleDataAugmentationforNER(tokens_list=tokens_list, labels_list=labels_list, p_power=1, p_lwtr=1, p_mr=1, p_sis=1, p_sr=1, num_aug=4)
tokens = ["吉田", "さん", "は", "株式", "会社", "A", "に", "出張", "予定", "だ"]
labels = ["B-PER", "O", "O", "B-ORG", "I-ORG", "I-ORG", "O", "O", "O", "O"]
augmented_tokens_list, augmented_labels_list = augmentor.augments(tokens, labels)
print(augmented_tokens_list)
# => [
# ['田中', 'さん', 'ます', '筑波', '大学', '大学', '所属', 'は', 'ます', 'に'],
# ['吉田', 'さん', 'は', 'ストック', '企業', 'A', 'に', '出張', '心積り', 'だ'],
# ['田中', 'さん', 'は', '筑波', '大学', 'に', '出張', '予定', 'だ'],
# ['吉田', 'さん', 'は', '会社', 'A', '株式', '出張', 'に', '予定', 'だ'],
# ['吉田', 'さん', 'は', '株式', '会社', 'A', 'に', '出張', '予定', 'だ']]
print(augmented_labels_list)
# => [
# ['B-PER', 'O', 'O', 'B-ORG', 'I-ORG', 'I-ORG', 'O', 'O', 'O', 'O'],
# ['B-PER', 'O', 'O', 'B-ORG', 'I-ORG', 'I-ORG', 'O', 'O', 'O', 'O'],
# ['B-PER', 'O', 'O', 'B-ORG', 'I-ORG', 'O', 'O', 'O', 'O'],
# ['B-PER', 'O', 'O', 'I-ORG', 'I-ORG', 'B-ORG', 'O', 'O', 'O', 'O'],
# ['B-PER', 'O', 'O', 'B-ORG', 'I-ORG', 'I-ORG', 'O', 'O', 'O', 'O']]

おわりに

今回は日本語向けのData Augmentationライブラリdaajaを紹介しました。 手元でEDAについて軽く実験したところ、性能の向上がみられたので、SDAの実験も終わり次第別記事で公開したいと思います。

参考

ライブラリと実験の実装時に参考にさせていただきました。ありがとうございます。