GPTモデルに技術ブログを書いてもらおう

![a16c4880-a501-11ea-9e8f-646cf611702e.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/cff71386-6ba2-2f65-90e7-9c2e90c89fb1.png)
## 前書き
皆さん技術ブログ書いてらしゃいますでしょうか:relaxed:
私は定期的に技術ブログ書かなきゃと思ってはいますが、いざ書こうと思ってもいいネタが無かったり、出来が悪くて公開できるようなものでなかったりして、日々苦しんでます:sweat_smile:
もしAIに書いてもらえればどうなるのかを試してみたくなったので、その手順を記録します:writing_hand_tone1:

## 使用モデル
GPT-2モデルがベースになってるrinna社の日本語特化モデル[japanese-gpt-1b](https://huggingface.co/rinna/japanese-gpt-1b)を使用します。
選んだ理由としては、商用可能なライセンスとパラメーターの多さです。
![rinna.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/15a91852-922b-edbe-24a8-49bb32d37b6c.png)
基本的な使い方は公式ページにも書いてありますように、
提供されたサンプルコードを実行するだけで、与えられた文章の続きを作ってくれます。

```python
import torch
from transformers import T5Tokenizer, AutoModelForCausalLM

tokenizer = T5Tokenizer.from_pretrained("rinna/japanese-gpt-1b")
model = AutoModelForCausalLM.from_pretrained("rinna/japanese-gpt-1b")

if torch.cuda.is_available():
    model = model.to("cuda")

text = "西田幾多郎は、"
token_ids = tokenizer.encode(text, add_special_tokens=False, return_tensors="pt")

with torch.no_grad():
    output_ids = model.generate(
        token_ids.to(model.device),
        max_length=100,
        min_length=100,
        do_sample=True,
        top_k=500,
        top_p=0.95,
        pad_token_id=tokenizer.pad_token_id,
        bos_token_id=tokenizer.bos_token_id,
        eos_token_id=tokenizer.eos_token_id,
        bad_word_ids=[[tokenizer.unk_token_id]]
    )

output = tokenizer.decode(output_ids.tolist()[0])
print(output) 

# sample output: 西田幾多郎は、その主著の「善の研究」などで、人間の内面に自然とその根源があると指摘し、その根源的な性格は、この西田哲学を象徴しているとして、カントの「純粋理性批判」と「判断力批判」を対比して捉えます。それは、「人が理性的存在であるかぎりにおいて、人はその当人に固有な道徳的に自覚された善悪の基準を持っている」とするもので、この理性的な善悪の観念を否定するのがカントの
```

## 転移学習

`japanese-gpt-1b`モデルはデフォルトの状態でも文章は作れますが、
技術ブログ風の文書を書いてもらうために、転移学習を行う必要があります。

学習させる用のデータを用意する必要があります。
人に迷惑かけない程度にクローリングで集め、データの前処理はしておいた方が良いです。

```訓練用データ.txt
記事本文1個目...
記事本文2個目...
記事本文3個目...
...
```
# 実装
`Google Colab`を使用します。`rinna/japanese-gpt-1b`はかなり巨大なモデルのため、colabの無料版ではおそらくGPUのメモリが足りない可能性があります。
もしメモリが足りなくなったら、`rinna/japanese-gpt2-medium`など少し小さいサイズのモデルで試してください。

## ライブラリのインストール
モデルが含まれるライブラリ`transformers`、単語分割のためのライブラリ`sentencepiece`をインストールします。
```shell
!pip install transformers
!pip install sentencepiece
```

`rinna/japanese-gpt2-1b`モデルをロードします。

```python
from transformers import T5Tokenizer, AutoModelForCausalLM

tokenizer = T5Tokenizer.from_pretrained("rinna/japanese-gpt2-1b")
model = AutoModelForCausalLM.from_pretrained("rinna/japanese-gpt2-1b")
```

用意できたデータファイルをアップロード、
google colabでファイルをアップロードする際に、下記のコードを実行すればできます。

```python
from google.colab import files

uploaded = files.upload()
file_path_original = list(uploaded.keys())[0]  # ファイルパス取得
print(file_path_original)
```

## モデルの訓練

`rinna/japanese-gpt2-1b`モデルに訓練を行います。
まずは、訓練のための各設定を行います。

```python
from transformers import TextDataset, DataCollatorForLanguageModeling
from transformers import Trainer, TrainingArguments

# データセットの設定
train_dataset = TextDataset(
        tokenizer=tokenizer,
        file_path=file_path_original,
        block_size=128
        )

# データの入力に関する設定
data_collator = DataCollatorForLanguageModeling(
    tokenizer=tokenizer,
    mlm=False  # データをマスクするかどうか
    )

# 訓練に関する設定
training_args = TrainingArguments(
    output_dir="./output",  # 関連ファイルを保存するパス
    overwrite_output_dir=True,  # ファイルを上書きするかどうか
    num_train_epochs=3,  # エポック数
    per_device_train_batch_size=8,  # バッチサイズ
    logging_steps=100,  # 途中経過を表示する間隔
    save_steps=800  # モデルを保存する間隔
    )

# トレーナーの設定
trainer = Trainer(
    model=model,
    args=training_args,
    data_collator=data_collator,
    train_dataset=train_dataset,
)
```
トレーナーの`train()`メソッドにより、訓練が開始されます。

```python
trainer.train()
```
訓練には少し時間がかかります、筆者の場合は1時間ほどかかりました。

## 記事生成用の関数作成

入力文章から続きの記事を生成する関数を作ります。
```python
def getarate_sentences(seed_sentence):
    x = tokenizer.encode(seed_sentence, return_tensors="pt", add_special_tokens=False)  # 入力
    x = x.cuda()  # GPU対応
    y = model.generate(x, #　入力
                       min_length=50,  # 文章の最小長
                       max_length=100,  # 文章の最大長
                       do_sample=True,   # 次の単語を確率で選ぶ
                       top_k=50, # Top-Kサンプリング
                       top_p=0.95,  # Top-pサンプリング
                       temperature=1.2,  # 確率分布の調整
                       num_return_sequences=3,  # 生成する文章の数
                       pad_token_id=tokenizer.pad_token_id,  # パディングのトークンID
                       bos_token_id=tokenizer.bos_token_id,  # テキスト先頭のトークンID
                       eos_token_id=tokenizer.eos_token_id,  # テキスト終端のトークンID
                       )  
    generated_sentences = tokenizer.batch_decode(y, skip_special_tokens=True)  # 特殊トークンをスキップして文章に変換
    return generated_sentences
```
記事の生成、冒頭を関数に渡して、学習済みのモデルに記事を書いてもらいます。

```python
seed_sentence = "Reactのテストにおいて"  # 記事の冒頭
generated_sentences = getarate_sentences(seed_sentence)  # 生成された記事
for sentence in generated_sentences:
    print(sentence)

# ReactのテストにおいてデータベースをJSON形式にしている場合、下記のようなリクエストが行われます。 この場合、変数に代入されたパラメータは以下の順番で... 続きを読む » » ReactでJSONを扱う方法(公開鍵) →... 続きを読む » Reactで画像を表示する方法は? PHPからJSONを扱う方法(Akismet) →... 続きを読む » ReactでMySQLにデータの保管場所を設定する(There are Into Behavior Picture) →... 続きを読む
# Reactのテストにおいてあるブラウザの挙動を検証。基本的な部分はなるべく同じ方法で検証できるし、違いもわかりやすい。ただし、JSのフロントエンドのサンプルコードがJSPのコードページには登録されないので、英語ドキュメントを調べる必要がある。また、Stylish PEでも一部機能に対応していないらしい。ソースをパースし、翻訳するのも一苦労そうだ。ChromeにはJSブラウザも。Hyper-V環境で試験もできそうだ。ただ日本語
# Reactのテストにおいて、CDnsのリストはcursesのlist値を参照して、そのリスト名を検証しています。以下はその例です。これをrootで実行してみましょう。結果は?340行目から32行目までがCDnsのテストです。よって、このテストが成功したことを示しています。これは特に重い結果ではありません。もし、重いテストの場合、これを無効にすることを忘れないようにしましょう。root権限のみでCDnsのデバッグも可能です。詳細については
```

記事ぽいものは作られましたね、
トレーニング済みのモデルを保存するには下記にコード実行すればできます。

```python
from transformers import T5Tokenizer, AutoModelForCausalLM

model = AutoModelForCausalLM.from_pretrained("./output/checkpoint-3000")
model.save_pretrained("model")
```


## 最後に

訓練ずみモデルであれば、ある程度記事ぽい文章を作ってくれますが、
正直出来が良いとは言えません、でも可能性を感じたので、何かの改善を施せば、よりよくなる気がします。