サブロウ丸

Sabrou-mal サブロウ丸

主にプログラミングと数学

Transformerによる翻訳システム自作; part1 事前処理 & 学習の大枠

本稿では翻訳モデルを作成するにあたり必須な自然言語の事前処理について整理します。

用語集

コーパス (Corpus)

コーパスとは

"言語学において、自然言語処理の研究に用いるため、自然言語の文章を構造化し大規模に集積したもの"

コーパス」(2022年6月2日 (日) 11:34 UTCの版)『ウィキペディア日本語版』。

ということで大量のテキストデータです。翻訳タスクに用いるものであれば同一の文意が複数の言語で書かれたテキストデータのcorpusを用います(パラレルコーパスとも呼ばれます)。またテキストだけでなく構文構造などの情報が付与されたものもあります。

(注: 本稿以降ではテキストのみのコーパスを扱います。)

下記はGPT-3の訓練に使用されたコーパスの一覧です。GPT-3は1750億個のパラメタを持つOpenAIが訓練させた巨大な自然言語処理モデルです(詳細は調べてみてください)。 Brown, Tom, et al. "Language models are few-shot learners." Advances in neural information processing systems 33 (2020): 1877-1901.

Dataset テキスト量
Common Crawl (filtered) 4100億
WebText2 190億
Books1 120億
Books2 550億
Wikipedia 30億

(参考: OpenAI GPT-3: Everything You Need to Know) モデルの訓練に使用されるcorpusのサイズは年々大きくなっています。4100億のコーパスなんて想像もつかないですが...。

そのほか参考になる記事:

トークン (Token)

トークンとは自然言語の文やプログラミング言語において意味を持つ最小単位のものを指します。自然言語であれば単語や句読点、プログラミング言語であれば変数や演算子など。テキストをこのトークンに分割することはtokenize、それを行うものはtokenizerなどと呼ばれます。このトークン分割は形態素解析のような言語処理の知識に基づいて行われますが、コーパス(テキストデータ)の中にはあらかじめトークン分割された状態で提供されているものもあります。

コーパス読み込みプログラム

本稿では基本的に田中コーパスを用いてモデルの評価を行います。

DataLoader

DataLoaderとは学習時に用いるデータの取り出しのためのインターフェイスの総称です。一般に深層学習で用いられる確率的勾配降下法では、一部のデータを用いたパラメタの更新を繰り返すことで、目的に沿ったモデルの作成を目指します。

モデルのパラメタの更新は、深層学習ではデータを入力し出力を得る順伝播(forward)と、順伝播時のデータをもとにパラメタを更新するための勾配情報を取得するための逆伝播(backward)の2stepで行われます。このとき順伝播、逆伝播を行う際の入力データ単位をミニバッチ(mini batch)、その大きさをバッチサイズ(batch size)と呼びます。入力データをいくつかのミニバッチに分割し、それぞれのミニバッチに対し順伝播、逆伝播、パラメタの更新を順次行います。そしてミニバッチによるパラメタ更新が一巡する単位をエポック(epoch)と呼びます。通常、モデルの学習の際にはエポックを何回も繰り返し、また一つのエポックが終わるごとにデータのミニバッチ分割方法を変えて学習します。

DataLoaderはこの全てのデータからなるDatasetからミニバッチを取り出したり、ミニバッチの分割方法をシャッフルしたり、、という機能を持たせます。pytorchを用いて実装していなるなら、pytorchのDataLoaderを直接使ったり、継承してカスタマイズして使ったりします。

今回は自作ということで簡単に英日翻訳モデル用のDataset, DataLoaderを実装してみましょう。

Dataset

基本的に必要なのは dataset = Dataset(...) について dataset[i]でi番目のデータをとりだす機能やlen(dataset)でデータの個数が得られる機能があれば良いです。dataset[i]では日本語と対応する英語、学習用のtargetをまとめたData (namedtuple)を返すようにしました。

import collections

Data = collections.namedtuple("Data", "ja en")


class Dataset:
    def __init__(self, corpus_ja, corpus_en):
        assert len(corpus_ja) == len(corpus_en)
        self.corpus_ja = corpus_ja
        self.corpus_en = corpus_en

    def __iter__(self):
        for i in range(len(self)):
            yield self[i]

    def __getitem__(self, index):
        ja = self.corpus_ja[index]
        en = self.corpus_en[index]
        return Data(ja, en)

    def __len__(self):
        return len(self.corpus_ja)

DataLoader

そして、dataloader = DataLoader(...) としては for minibatch_data in dataloader でミニバッチデータの取り出しができて、全てのデータを使い終わったらループが終了。またforでループさせるときはミニバッチ分割がシャッフルされている、という要件を満たせば十分でしょう。ということで下記のコードです。ミニバッチの分割のシャッフルなどは`__iter__内で行なっています。(本当はiterator i のランダマイズの方がメモリ効率が良いですが、こちらの方が簡潔に書き得たのでご勘弁を)

import random

class DataLoader:
    def __init__(self, dataset, batch_size, shuffle=False, drop_last=False):
        self.dataset = dataset
        self.batch_size = batch_size
        self.shuffle = shuffle
        self.drop_last = drop_last

    def __iter__(self):
        iterator_data = list(self.dataset)
        if self.shuffle:
            random.shuffle(iterator_data)

        for i in range(0, len(iterator_data), self.batch_size):
            yield iterator_data[i : i + self.batch_size]

        if not self.drop_last and i < len(iterator_data) - 1:
            yield iterator_data[i:]

    def __len__(self):
        return len(self.dataset)

まとめ

corpus, tokenの用語説明と、DataLoader, Dasetおよび機械学習の大枠の説明を行いました。

他の記事