SeqGANで自然文テキストを自動生成（PyTorch、英語・日本語データ）

# はじめに

テキストを生成するGANのPyTorch実装を動かした備忘録

自然言語処理分野において、自然文テキストを自動生成するGANはここ2、3年のホットトピックとなっていますが（例えば[小町先生による解説](https://www.slideshare.net/mamoruk/jsai2018gan4nlp)）、画像処理でのGANと比較して「動かした」系の記事があまり見つからなかったので、基本的なモデルであるSeqGAN（2017年）のサンプルコードを動かした記録を書きます。

# 環境

```
Anaconda: anaconda Command line client (version 1.7.2)
```

# ライブラリ

https://github.com/williamSYSU/TextGAN-PyTorch

公式（論文著者による実装）ではないですが、Google検索上位でStarも多かったのでこちらを使いました。

# セットアップ

基本的にはREADME.mdの通りに進めていきます。

## リポジトリのcloneとRequirementsのインストール

```
$ git clone https://github.com/williamSYSU/TextGAN-PyTorch.git
$ cd ./TextGAN-PyTorch
$ pip install -r requirements.txt
```

既に入っているパッケージもあったため、自分が動かした環境とRequirementsで差分があります。インストールパッケージについて、動作確認をした環境でのバージョンは以下の通りでした。

```
torch                              1.6.0
numpy                              1.14.5
nltk                               3.5
tqdm                               4.32.1
```

## KenLMのインストール

言語モデルをインストールします。

```
$ brew install boost; brew install bjam
$ wget http://kheafield.com/code/kenlm.tar.gz
$ tar xvfz kenlm.tar.gz
$ cd ./kenlm
$ mkdir -p build
$ cd ./build
$ cmake ..
$ make -j 4
$ pip install https://github.com/kpu/kenlm/archive/master.zip
```

# SeqGANサンプルの実行（英語データ）

まず、README.mdに書かれているサンプルをそのまま動かしてみます。テキスト生成モデル（SeqGAN、LeakGAN）などによって実行コマンドが分離されていますが、基本的な使い方は同じです。先ほどcloneしてきたTextGAN-PyTorchのリポジトリに移動して実行コマンドを打ち込みます。

```
$ cd ./run
$ python run_seqgan.py 0 0
```

引数の{0, 0}というのは、それぞれ{job_id, gpu_id}に該当します。job_idは、run_seqgan.pyのコードの中に書かれているサンプルデータのインデックスを指します。具体的には[こちら](https://github.com/williamSYSU/TextGAN-PyTorch/blob/master/run/run_seqgan.py#L45)に定義されているとおり

```
0 -> 'oracle'
1 -> 'image_coco'
2 -> 'emnlp_news'
```

と割り振られています。ここで、oracleは正解データもシミュレーションで生成するサンプルコードで、emnlp_newsは英語のニュースデータセットを正解データとするものとなっています。

gpu_idは、マシンのGPUのどれを動作させるかを指定する引数です。gpuを使わずcpuで動作させる場合、cudaの呼び出しをキャンセルするため、run_seqgan.pyの[こちら](https://github.com/williamSYSU/TextGAN-PyTorch/blob/master/run/run_seqgan.py#L37)の行を変更する必要があります。

```
CUDA = int(False)
```

ちなみに、oracleサンプルを動かすとき、上のようにCUDAをキャンセルした場合でもcudaが呼ばれてしまうことがあるので（[例](https://github.com/williamSYSU/TextGAN-PyTorch/blob/master/instructor/oracle_data/instructor.py#L237)）、このようなときは条件分岐を追加するとCUDAのない環境でもサンプルを動作させることができるようになります（CUDA環境で実行する場合は改修不要）。

```python
if cfg.CUDA:
    self.oracle.load_state_dict(torch.load(cfg.oracle_state_dict_path, map_location='cuda:{}'.format(cfg.device)))
else:
    self.oracle.load_state_dict(torch.load(cfg.oracle_state_dict_path, map_location=torch.device('cpu')))
```

一方、実データのemnlp_newsでサンプルを動作させるときは、README.mdのリンクから、あらかじめデータセットをダウンロードして配置する必要があります。
https://drive.google.com/drive/folders/1XvT3GqbK1wh3XhTgqBLWUtH_mLzGnKZP?usp=sharing

ダウンロードしたデータは、リポジトリのルートディレクトリにdataset, dataset/testdataを作成してそちらに置きます。データセットの配置はこちら。

```
TextGAN-PyTorch
├── dataset
│   ├── emnlp_news.txt
│   └── testdata
│       └── emnlp_news_test.txt
```

準備ができたらSeqGANの実行コマンドを動かします。GeneratorのMLE学習、Discriminatorの学習、Adversarial学習の順に処理が進みます。

```
$ python run_seqgan.py 2 0
```

# SeqGANの実行（日本語データ）

上記の仕組みを把握したので、日本語データでもSeqGANを動かしてみたいと思います。英語のtokenizeに該当するのが日本語の分かち書き（形態素解析）ですが、今回は分かち書き処理をされたデータを先に準備しておき、tokenizeでは空白で分割するだけの簡単な処理とします。

例として、青空文庫データをMeCabで分かち書きしたデータを準備しました。データを作る方法は[こちら](https://qiita.com/Yupine/items/92d75865a72c60ae7285)などを参考にしてください。ファイル内に空行があると実行時エラーとなります。

```text:aozora_wakati.txt
...
ある 日 の 暮方 の 事 で ある 。 一 人 の 下人 が 、 羅生門 の 下 で 雨 やみ を 待っ て い た 。
広い 門 の 下 に は 、 この 男 の ほか に 誰 も い ない 。 ただ 、 所々 丹塗 の 剥げ た 、 大きな 円柱 に 、 蟋蟀 が 一 匹 とまっ て いる 。 羅生門 が 、 朱雀 大路 に ある 以上 は 、 この 男 の ほか に も 、 雨 やみ を する 市 女 笠 や 揉 烏帽子 が 、 もう 二 三 人 は あり そう な もの で ある 。 それ が 、 この 男 の ほか に は 誰 も い ない 。
...
```

こちらを先ほどと同じようにdatasetディレクトリへと配置します。

```
TextGAN-PyTorch
├── dataset
│   ├── aozora_wakati.txt
│   └── testdata
│       └── aozora_wakati_test.txt
```

SeqGANコードでこちらのデータを扱うため、run_seqgan.py中のジョブ定義配列にaozora_wakati文字列を追加します。追加後のコードはこのようになります。

```python:run_seqgan.py
...
# ===Oracle  or Real===
if_real_data = [int(False), int(True), int(True), int(True)]
dataset = ['oracle', 'image_coco', 'emnlp_news', 'aozora_wakati']
vocab_size = [5000, 0, 0, 0]
...
```

また、tokenizeの関数を修正します。

```python:text_process.py
...
def get_tokenlized(file):
    """tokenlize the file"""
    tokenlized = list()
    with open(file) as raw:
        for text in raw:
            tokenlized.append(text.strip().split(" "))
    return tokenlized
...
```

このような準備によって、job_idにaozora_wakatiのインデックスを指定してSeqGANを実行することができるようになりました。

```
$ python run_seqgan.py 3 0
```

# SeqGANで生成された自然文テキストの例

各学習ステップ（MLE, ADV）でGeneratorが生成した自然文テキストのサンプルを確認することができます。

```
$ head -1 ../save/YYYYMMDD/aozora_wakati/seqgan_vanilla_dt-Ra_lt-rsgan_mt-ra_et-Ra_sl574_temp1_lfd0.0_T0201_0135_46/samples/samples_ADV_00000.txt
叔母 は とうとう 物識り の 旅人 を 知らせ た 。 襖 真率 の 匂 も 石 と 、 賑 か と 、 執っ て ゐる 春 か 云っ た 。 おおい 、 座敷 に 、 茫然 そう の つい た 。 彼 は すぐ に 素 する の 外 は か 、 妄り に 生きる 言葉 が 、 少時 天主 の 中 に 坐り ながら 、 慎太郎 と 、 あの 避暑 通る か の 、 僕 の 前 を 案内 し た 。 ことに まあ 目 の 高い 座敷 の 、 当年 空 の 微粒 なき を 分け て 、 恬然 た 沙漠 と 、 彼 と 、 人 を 龍 ん か と 、 ワルシ と 智識 不孝 見れ ば 蜘蛛 は 違い ず 、 移し た が 、 脚 に 神山 さん が 、 我々 を 感じ た 風 場 で あろ う と 覚え て い た 。 現に 今 にとって 、 黙然と 汁 を 下る の 出 な 鏡 が 、 西 は 作家 へ はいり 出し た 。 」
```

学習の初期のためまだ意味が通じていないところがありますが、日本語テキストの生成例が出力されていることが分かります。今回は動作確認のためこの時点のサンプルでいったん投稿しますが、敵対的学習が進んだサンプルが得られたら後日追記していきたいと思います。