GPT-2でツイッターのトレンドから論文タイトルを生成してみた！【後編】

# はじめに
こんにちは。
この記事は、沖縄高専 Advent Calender 2022 17日目の記事です。
また、[10日目の記事](https://qiita.com/4ytk3/items/35013cf7ac32792aeee8 "GPT-2でツイッターのトレンドから論文タイトルを生成してみた！【前編】")の後編となっています。

実は、記事が長くなってしまったことだけではなく、前編投稿時にはまだプログラムが未完成だったことが前編と後編に分けた理由です。先延ばしの先延ばしでした。
参加者の皆さん本当に申し訳ありません。

機械学習初心者＋Qiita初投稿のため所々意味不明かもしれませんが、温かく見守っていただければと思います。

## 前編の要約
前編では、GPT-2でツイッターのトレンドから論文タイトルを生成するためには、実際の論文のタイトルで日本語の事前学習モデルをファインチューニングすればいいのでは？という至極当たり前の考えで、データセットの作成を行いました。
また、試行錯誤の結果、入力したトレンドの単語に続くように論文タイトルっぽいデタラメな文章を生成する、**単純なテキストジェネレーター**を作る方針にしました。

データセットは、CiNiiでスクレイピングした論文タイトルを、タイトルの始まりと終わりを示す特殊トークン、`
`と`
`を付与した形で保存しています。

それに加えて、自前のGPUで学習を行うために必要なドライバーやライブラリのインストールを行いました。

## 後編の要約
この記事では、日本語の事前学習モデルをファインチューニングして、トレンドの単語から論文タイトルを生成・ツイートしていきたいと思います。

また、タスクスケジューラを用いた**定期ツイート**もしていきたいと思います。

# 今回やったこと
全体の流れは大きく分けて、
1. データセットの作成
2. 機械学習の準備
3. モデルの作成
4. トレンド取得とタイトル生成・ツイート

の4つで、今回は、**モデルの作成**と**トレンド取得とタイトル生成・ツイート**を行います。

## 3. モデルの作成
モデルは前述したとおり、日本語の事前学習モデルを、前編で作成したデータセットでファインチューニングすることで作ります。
日本語の事前学習モデルはrinnaの、[japanese-gpt-2-small](https://huggingface.co/rinna/japanese-gpt2-small)を使います(本当はmediumを使いたかったけど、3070TiのVRAMが8GBなので足りなかった...)。

### 1. ファインチューニングの準備
ファインチューニングにはいくつかのライブラリ等が必要で、まずはそれらをインストールしていきます。

Hugging Faceの**Transformers**リポジトリを作業ディレクトリにclone
```:..\Programs\fakepaperbot
git clone https://github.com/huggingface/transformers
```
また、必要なライブラリもそれぞれインストール
```
pip install transformers
pip install sentencepiece
pip install datasets
```
今回使うrinnaの日本語事前学習モデルでは、T5というTokenizerを使うため、作業ディレクトリにcloneしたTransformersの中のスクリプトファイル、**`run_clm.py`を少し修正する**必要があります。
深くは理解できていないのですが、[こちらの記事](https://qiita.com/Katsumata420/items/077de5d968eb7d600d36#fine-tuning%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%AE%E7%94%A8%E6%84%8F "日本語GPT-2を使って「小説家になろう」に掲載されていそうなタイトルを生成してみた")を参考にAutoTokenizer関連のプログラム
```python:..\fakepaperbot\transformers\examples\pytorch\language-modeling\run_clm.py (修正前)
from transformers import AutoTokenizer

# ...

    if model_args.tokenizer_name:
        tokenizer = AutoTokenizer.from_pretrained(model_args.tokenizer_name, **tokenizer_kwargs)
    elif model_args.model_name_or_path:
        tokenizer = AutoTokenizer.from_pretrained(model_args.model_name_or_path, **tokenizer_kwargs)
    else:
        raise ValueError(
            "You are instantiating a new tokenizer from scratch. This is not supported by this script."
            "You can do it from another script, save it, and load it from here, using --tokenizer_name."
        )
```
を、下のようにT5Tokenizerに書き換えています。
```python:..\fakepaperbot\transformers\examples\pytorch\language-modeling\run_clm.py (修正後)
from transformers import T5Tokenizer

# ...

    tokenizer = T5Tokenizer.from_pretrained(model_args.model_name_or_path)
    tokenizer.do_lower_case = True
```


### 2. ファインチューニング
以下のコマンドで、実際にファインチューニングを実行しました。
作業ディレクトリにあるtransformesの中の先ほど修正した`run_clm.py`を使って、rinnaのjapanese-gpt-2-smallに、前編で作成したトレーニングデータとテストデータでファインチューニングしています。

今回使用したのがmediumではなくsmallだったため、気持ちバッチサイズを多めに(ほんとに気持ち)、エポック数は10回で実行しました。
```
python ./transformers/examples/pytorch/language-modeling/run_clm.py --model_name_or_path=rinna/japanese-gpt2-small --train_file=train_title.txt --validation_file=test_title.txt --do_train --do_eval --num_train_epochs=10 --save_steps=10000 --save_total_limit=3 --per_device_train_batch_size=2 --per_device_eval_batch_size=2 --output_dir=output/ --use_fast_tokenizer=False
```
学習にかかった時間は約1.5時間ほどだったと思います。

### 3. タイトル生成
以下のプログラムでいくつかタイトルを生成できます。
論文タイトルの長さっぽく15文字以上で、ツイートに収まるように140文字以内で生成しています。
```python:generate.py
from transformers import T5Tokenizer, AutoModelForCausalLM

tokenizer = T5Tokenizer.from_pretrained("rinna/japanese-gpt2-small")
tokenizer.do_lower_case = True
model = AutoModelForCausalLM.from_pretrained("output/")

def generate_paper_title(word):
    input_text = word
    input = tokenizer.encode(input_text, add_special_tokens=False, return_tensors="pt")
    output = model.generate(input, do_sample=True, num_return_sequences=10, min_length=15, max_length=140)
    titles = '\n'.join(tokenizer.batch_decode(output)).replace('
', '')
    print(titles)
```
トレンドに『男性と育休』が入っていたので、試しに10件生成してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/b1c9966f-e7dc-4c0d-f88a-2ef54dfb4fb6.png)
それっぽい論文タイトルが生成できているのではないでしょうか。
CiNiiで同様のタイトルの論文があるか検索してみましたが見つかりませんでした。

また、最近チェンソーマンを読んでいたのでチェンソーマンでも生成してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/10580aa8-ffb9-f192-25a5-93948ea7cb7e.png)
それっぽい意味不明なタイトルが生成できていますね！

ただ、『くん』や『さん』などの敬称を含む人名ぽいものを入力すると...
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/d494e772-31a4-5f3f-9289-ac86e4902428.png)
rinnaの学習データに結構引っ張られてしまいます...
そこはトレンド取得の時点で避けようかと思います(人名で論文書くことなんてほとんどないですからね)


## 4. トレンドからタイトルを生成して定期ツイート
ツイッターのトレンドの取得やツイートには、[Twitter Developers](https://developer.twitter.com/)でのAPI申請が必要となります。
自分は何度かbotを作成したことがあったため手順は省略させていただきますが、[こちらの記事](https://www.itti.jp/web-direction/how-to-apply-for-twitter-api/ "2021年度版 Twitter API利用申請の例文からAPIキーの取得まで詳しく解説")が分かりやすくて参考になると思います。

### 1. トレンドの取得とツイート
以下のプログラムでトレンドの取得とツイートを行います。
今回は日本語の論文タイトルを学習させているため、英数字のみで構成されているトレンドは省くようにしています。
また、トレンド検索に引っかかるように#を、自然な文章にするためにゼロ幅スペースを付与しています。
```python:main.py
import re
import tweepy
from transformers import T5Tokenizer, AutoModelForCausalLM

CONSUMER_KEY = 'xxxxx'
CONSUMER_SECRET = 'xxxxx'
ACCESS_TOKEN = 'xxxxx'
ACCESS_SECRET = 'xxxxx'

auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
auth.set_access_token(ACCESS_TOKEN, ACCESS_SECRET)
api = tweepy.API(auth)

def get_trends():
    woeid = 23424856 #日本のトレンドID
    trends_data = api.get_place_trends(woeid)
    trends_list = trends_data[0]['trends']
    trends = [trend['name'].replace('#', '') for trend in trends_list]

    return trends

def generate_paper_title(word):
    tokenizer = T5Tokenizer.from_pretrained("rinna/japanese-gpt2-small")
    tokenizer.do_lower_case = True
    model = AutoModelForCausalLM.from_pretrained("output/")

    input = tokenizer.encode(word, add_special_tokens=False, return_tensors="pt")
    output = model.generate(input, do_sample=True, num_return_sequences=1, min_length=15, max_length=140)
    title = '\n'.join(tokenizer.batch_decode(output)).replace('
', '')

    return title

def main():
    trends = get_trends()
    for trend in trends:
        if re.match(r'^[a-zA-Z0-9!-/:-@¥[-`{-~\s]*$', trend) is None: #英数字・記号を含むかの判定
            if re.search('くん|さん|ちゃん', trend) is None: #人名敬称を含むかの判定
                title = generate_paper_title(trend)
                title = title.replace(trend, '#' + trend + '​') ##とゼロ幅スペースの付与
                api.update_status(title)
                break

if __name__ == "__main__":
    main()
```
[虚構論文bot](https://twitter.com/fakepaperbot)というbotを作ってテスト投稿してみました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/b29b30e8-7f47-d167-2c5f-e9c726e1339c.png)

### 2. 定期ツイート
定期ツイートは、Windowsに標準搭載されている定期実行アプリ、**タスクスケジューラ**で行います。
Unix系(Macなど)では、cronで代用できると思います。
仮想環境を使っていて少し手間取ったところもあったため、仮想環境を使った定期実行の手順を簡単に紹介します。

1. パスを通す
ユーザー環境変数のPathに、**anaconda3の仮想環境のパス**を指定します。
これをしないと仮想環境のPythonを使ってのプログラムの実行ができません。
今回は、fakepaperbotという仮想環境で開発していたため、そこのパスを通しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/5dbbd730-7a9b-40a4-12c9-630f0cb82959.png)

2. 基本タスクの作成
タスクスケジューラを開き、**操作**から**基本タスクの作成**に進みます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/2254bb64-7c67-a951-c75a-b7c0dd87bf11.png)
**トリガー**では、実行日や実行頻度を指定できます。
一旦1回限りに指定しておきます(後で変更します)。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/fcaa820e-c818-34bd-8d40-831c0adfc45c.png)
**プログラムの開始**を選択し、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/bfaf6086-f014-4293-191b-a5ac5ec01d03.png)
**プログラム/スクリプト**に、先ほどパスを通した仮想環境内のpython実行ファイルを指定
**引数の追加**には、実行するプログラム(今回は`main.py`)を指定
**開始**には、引数の追加で指定したプログラムがあるディレクトリを指定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/c884aa55-2b0f-1c83-bed5-b918e3e45634.png)
**完了**を押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/641057d6-d9b5-58c0-6afc-7a42aeb2a307.png)

3. トリガーの変更
**アクティブなタスク**から、先ほど作ったタスクを探し、開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/1666b72e-a991-eba3-583a-fef5bba694a5.png)
**プロパティ**から、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/c60e766f-d0af-bd30-e025-d95ef968d3b3.png)
**トリガーの編集**を押し、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/55171470-118f-16f4-7dac-238e09d0d37d.png)
**繰り返し間隔**にチェックを入れ、1時間に指定、**継続時間**を無期限にしました。
また、開始時刻は直近で一番近い切りのいい時刻に設定しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/0492bb22-1cb7-8edb-6a5f-2991e59a4ede.png)
これで1時間置きに定期ツイートが実行されます。
### 3. 結果
ちゃんと1時間置きに定期ツイートできていました！(今出先なので取り急ぎスマホのスクリーンショットでですが)
<img width="400" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/a271fb9f-7dc0-9604-7655-ba3bace24dfb.jpeg><img width="400" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/e3f0b3c2-85bf-7464-803b-942631f05227.jpeg>
#サトシ引退に関しての出力は少し怪しい(というか明らかに論文っぽくない)ものもありますが、他はそれっぽくできていると思います。
このままこのbotは定期ツイートし続けるので、良かったらフォローしてみてください！

# おわりに
今回は、GPT-2の日本語事前学習モデルに、CiNiiからスクレイピングして集めた論文タイトルでファインチューニングして、ツイッターのトレンドの単語から論文タイトルを生成してツイートするbotを作ってみました。
モチベが出た時にちまちま進めていたので、完成するのにすごく時間がかかりましたが、一応完成できてよかったです。
また、記事を書くのも初めてだったので、要領が掴めず、Advent Calenderの日程をずらしたり前編と後編に分けたりしちゃいましたが、ちゃんと書ききれてよかったです(これを記入しているのは記事公開日)。

今回はrinnaのsmallを利用しましたが、今度はmediumを、より多くの論文タイトルでファインチューニングできたらいいなと思っています。

[モデル](https://huggingface.co/4ytk3/fakepaperbot_gpt-2 "Hugging Face 4ytk3/fakepaperbot_gpt-2")と[虚構論文bot](https://twitter.com/fakepaperbot "虚構論文bot")はずっと公開しているので、ぜひ遊んでみてください。

次回、GPT-3でツイッターのトレンドから論文を生成してみた！

## 追記
たまに画像のように、英単語を含むトレンドに#とゼロ幅スペースを付与できないことがあります。
原因が分からないため、有識者の方が入れば教えていただきたいです。
よろしくお願いします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/2601b019-12f7-65c9-fce5-37895170721c.png)

## 参考記事
* [GPT-2で友達を再現して対話してみた](https://qiita.com/Yokohide/items/e74254f334e1335cd502)
* [論文タイトルから学会流行を知り，日本語GPT-2でタイトル生成してみよう!](https://qiita.com/dcm_sawayama/items/7c991d44ee57e96446c5)
* [日本語GPT-2を使って「小説家になろう」に掲載されていそうなタイトルを生成してみた](https://qiita.com/Katsumata420/items/077de5d968eb7d600d36)
* [2021年度版 Twitter API利用申請の例文からAPIキーの取得まで詳しく解説](https://www.itti.jp/web-direction/how-to-apply-for-twitter-api/)
* [pythonでtwitterのトレンドを取得する【サンプルコードあり】](https://python-man.club/python_twitter_trends/)
* [Pythonをwindowsタスクスケジューラで定期実行させる](https://note.com/kohaku935/n/n68092e2e1dd2)
* [タスクスケジューラのタスクを指定した間隔で定期的に実行する方法](https://cloud-si.com/taskschd-interval/)
* [【python3】仮想環境下で実行するpythonプログラムをタスクスケジューラに登録する](https://note.com/yucco72/n/ne20d603bb885)