【GPT-2, Octoparse 8】粗品さんのポストを学習して文章生成してみた

# 実行環境
* Google Colaboratory
* Python 3.10.12
* Octoparse 8

# はじめに
こんにちは。私はプログラミング未経験の会社員です。最近流行りのAIについて学習するため、Aidemyさんに入会しました。今回は実践練習として、チューターさんの協力を得ながら芸人の粗品さん風のポストを生成するAIを作成してみました。また、以下の記事を大変参考にさせて頂きました。

https://zenn.dev/robes/articles/24ee45dd81636a

# 本記事の概要
この記事で分かることとしては、以下の２点です。
* Xのポストデータを収集する方法
* それをGPT-2をベースにファインチューニングさせる方法

# Xのポストデータの取得
まずは粗品さんのポストデータを取得します。XはAPIを利用しないスクレイピングが禁止されていますが、XのAPIは月額1万円程度かかります。そこで今回は、初回14日間は無料でXのデータを取得できるOctoparseというソフトウェアを利用しました。
詳しい説明や利用方法については以下を参照してください。

https://www.octoparse.jp/blog/how-to-scrape-and-download-tweets-for-free

今回の記事では、粗品さんの直近１年間のポストをcsv形式で取得しました。
以下のように取得できます。Content列にポストが格納されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3704233/be0d7372-7b58-a8fe-1887-2a925689a12b.png)


# ポストデータの学習プログラム
#### 必要なライブラリのインストール
transformerとsentencepieceをインストールします。transformerはバージョンを指定してください。


```python
!pip install transformers==4.20.1
!pip install sentencepiece
```

#### 日本語用の事前学習済みモデル
日本語の事前学習モデルとして、**rinna/japanease-gpt2-small**を使用します。

```python
from transformers import T5Tokenizer,AutoModelForCausalLM
tokenizer = T5Tokenizer.from_pretrained("rinna/japanese-gpt2-small")
model = AutoModelForCausalLM.from_pretrained("rinna/japanese-gpt2-small")
```
# テキストデータの準備
先ほどOctoparseで取得した粗品さんのポストを、1ポスト1行となるように成形した状態で準備します。
そしてそれをファイルにまとめます。

```python
import pandas as pd
df = pd.read_csv("/content/drive/MyDrive/soshina_posts.csv")
posts = []
for i in df["Content"].values:
  if type(i) == str:
    posts.append(i.replace("\n", "") + "\n")

train_data_path = "train.text"
with open(train_data_path,"w") as f:
    f.writelines(posts)
```

# 訓練の準備
ファインチューニングを行うためのモデル側の準備をします。

* データセットの設定
* データの入力に関する設定
* 訓練に関する設定
* トレーナーの設定

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

#データセットの設定
train_dataset = TextDataset(
    tokenizer = tokenizer,
    file_path = "/content/train.text",
    block_size = 128 #文章の長さを揃える必要がある
)

#データの入力に関する設定
data_collator = DataCollatorForLanguageModeling(
    tokenizer=tokenizer,
    mlm= False
)

# 訓練に関する設定
training_args = TrainingArguments(
    output_dir="./gpt2-ft",  # 関連ファイルを保存するパス
    overwrite_output_dir=True,  # ファイルを上書きするかどうか
    num_train_epochs=100,  # エポック数
    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
)
```

# ファインチューニング（訓練）を実行

```python
%%time
trainer.train()
```
Training lossなどの指標は以下の通りでした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3704233/eae973d7-ac2d-175e-1fde-f8c98eb96fce.png)



# 文章を生成する関数
ファインチューニングしたモデルを使って、文章を生成する関数を作成します。
細かい仕様はコメントの通りです。

```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=5,  # 文章の最小長
                       max_length=30,  # 文章の最大長
                       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
                       bad_word_ids=[[tokenizer.unk_token_id]]  # 生成が許可されないトークンID
                       )
    generated_sentences = tokenizer.batch_decode(y, skip_special_tokens=True)  # 特殊トークンをスキップして文章に変換
    return generated_sentences
```

# 文章の生成
seed_sentenceにワードを入れると、それに続く文章をモデルが生成します。
上段の設定で、生成する文章を3としているので、ここでは3つの文章が生成されます。
今回は「ギャンブルが」と入れてみました。

```python
seed_sentence = "ギャンブルが"
generated_sentences = getarate_sentences(seed_sentence)  # 生成された文章
for sentence in generated_sentences:
    print(sentence)
```

# 出力結果
```python
ギャンブルが大好きで毎日大体6~7本は遊んでいる。 大体毎週金曜日19時からアメフトのネオスさんのライブを
ギャンブルが生活に与える影響は大きい。 例えば、競馬で2400万円勝ったとしましょう。1万円払ったとします。 あなた
ギャンブルが趣味の方は必見です! 競馬で毎日24万円儲けました 競馬で1000万円勝ったら全て税金払われるん
```

粗品みは感じるのですが、日本語としてはあまり成立していないですね。

# おわりに
まずは、Aidemyのチューター様、参考にさせて頂いたブログ主様に謝辞を述べさせてください。
本当にありがとうございました。
これからは、このプログラムの精度を上げるにはどうしたらよいのかについて勉強していきたいと思っています。

:::note
このブログはAidemy Premiumのカリキュラムの一環で、受講修了条件を満たすために公開しています
:::