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

# はじめに
こんにちは。
この記事は、沖縄高専 Advent Calender 2022 10日目の記事です。

実は、2日目に投稿する予定だったのですが、**ポケモンSVのランクマッチ解禁**につき先延ばしにしてしまいました。
参加者の皆さん本当に申し訳ありません。
また、記事が長くなってしまったため、前編と後編([17日目公開](https://qiita.com/4ytk3/items/e7ce04a31f16c3e7eaf2 "GPT-2でツイッターのトレンドから論文タイトルを生成してみた！【後編】"))に分けて投稿しようと思います ~~(先延ばしの先延ばしではありません)~~ 。

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

## なぜやろうと思ったのか？
今年4月にゲーム用に3070Tiを購入したのですが、せっかくそれなりに良いGPUを持っているならゲーム以外のことにも使いたいなと思い、機械学習に手を出してみました。

なぜこのテーマにしたのかというと、ツイッターのトレンドから新規性を見出して論文にするbotとかいたら面白いかなって思ったからです。

学習にGoogle Colabを使っている記事がほとんどだったので、この記事では自前のGPUを使うための準備から書いていこうと思います。
PCの構成については、過去に[自作.com](https://jisaku.com/pc/RjbFmhYSZUiWqhkXjfNiyd "あえて黒で染めた逆張りNZXT")に書いた記事を読んでください。

## GPTとは？
GPTはOpenAIから提案されたモデルで、Generative Pre-trained Transformerの略です。Transformerという深層学習モデルをベースにしており、**事前学習**したモデルに**ファインチューニング**という追加学習を行うことで非常に高い精度を実現する、というものです。

例えば、rinna社が構築した日本語の事前学習モデル、[japanese-gpt-2-small](https://huggingface.co/rinna/japanese-gpt2-small)に、友達とのLINEのトーク履歴でファインチューニングして、[GPT-2で友達を作った友人](https://qiita.com/Yokohide/items/e74254f334e1335cd502 "GPT-2で友達を再現して対話してみた")もいます。

## なぜGPT-2？
自然言語処理の有名な事前学習モデルには、GPTやBERTなどがあります。
BERTは、予測したい単語の**前後**のテキストから間の単語を予測、GPTは、予測したい単語より**前**のテキストだけで次単語予測をするため、GPTの方が、**要約や対話生成などの文章生成を得意とする**らしいです。

また、GPTにはGPT-1、GPT-2、GPT-3があるのですが、基本的なモデルの仕組みは同じで、**パラメータの数**が違います。数字が大きい方がパラメータが多いので、GPT-3が一番**つよい**です。

ですが、GPT-3を使うにはAPI利用申請をしたり、ガイドラインをちゃんと読まないといけなさそうで難しかったため、思いつきで気軽に試すにはGPT-2が適正かなと思いこちらにしました。

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

の4つで、今回は、**データセットの作成**と**機械学習の準備**を行います。

## 1. データセットの作成
論文っぽいタイトルを作るのが目的なので、実際の論文のタイトルで日本語の事前学習モデルをファインチューニングすれば論文っぽいタイトルを生成できるのでは？と考えました。
そこで、 ~~論文の題材にされていそうな単語を集め、~~ CiNiiで論文タイトルを収集し、学習に適した形式で保存する、という形でデータセットを作成しました。

### 1. 論文タイトルのスクレイピング
論文のタイトルは[CiNii Research](https://cir.nii.ac.jp/opensearch/articles?q=論文&count=200&start=1&lang=ja)のRSSから、titleタグ部分を抜き出して取得しています。

~~検索単語と検索結果の論文タイトルでファインチューニングし、入力された単語を含む論文っぽいタイトルを生成するようにするため、`paper_subject.txt`の単語を1つ1つ検索し、検索単語と検索結果の論文タイトルをセットで保存する、というプログラムとなっています。~~ 
当初の予定では前述した[友人の記事](https://qiita.com/Yokohide/items/e74254f334e1335cd502 "GPT-2で友達を再現して対話してみた")を参考に、論文の題材として書かれていそうな単語を集め、検索単語を入力、検索結果の論文タイトルを出力として、セットで学習させる予定でした。
しかし、論文数が同程度の題材を集めるのが難しいこと、少ない方に合わせると学習が足りないこと、出力が集めた単語の分野に偏ってしまうということから、検索単語と検索結果の論文タイトルを学習させるのではなく、単純に論文タイトルを集め、**論文タイトルっぽい文法**を学習させる方針にしました。

何が言いたいかというと、入力に対して出力をする対話生成ではなく、入力したトレンドの単語に続くように論文タイトルっぽいデタラメな文章を生成する、**単純なテキストジェネレーター**にした、ということです。

CiNiiは検索ワード必須なので、**論文**と検索してタイトルを集めています。
**論文**での検索結果は200万件ほどありますが、1回の取得可能論文数が1万件だったため、2000年1月から月毎に1万件スクレイピングするという形にしています。


保存形式は少し特殊ですが、学習データの始まりと終わりを示す`
`と`
` ~~、入力と出力の境界を示す`[SEP]`、~~ を入れた形式で保存しています。

```python:paper_scraping.py
import requests
import datetime
from dateutil.relativedelta import relativedelta
from bs4 import BeautifulSoup

def get_title():
    #当初の予定では、paper_subject.txtに集めた単語を1つ1つ検索して論文タイトルを収集する予定だったためこのようなプログラムとなっています。今回はpaper_subject.txtの中身は『論文』という単語だけです。
    with open('paper_subject.txt', 'r', encoding='UTF-8') as f:
        subjects = f.readlines()
        subjects = [subject.rstrip('\n') for subject in subjects]

    #YYYYMM形式での検索となるため、その形式で年月をリストにしています。
    term = datetime.datetime(2000, 1, 1)
    terms = []
    while True:
        terms.append(term.strftime('%Y%m'))
        if term.year == 2022 and term.month == 12:
            break
        term += relativedelta(months=1)

    for subject in subjects: #ここのforも今回は検索単語が1単語のみなので意味はありません(一応残しています)。
        for term in terms:
            url = 'https://cir.nii.ac.jp/opensearch/articles?q={0}&count=200&from={1}&until={1}&lang=ja&format=rss'.format(subject, term)
            res = requests.get(url)
            soup = BeautifulSoup(res.content , features="xml")
            search_count_result = soup.find('opensearch:totalResults')
            search_count = int(search_count_result.string)

            #1ページあたり200件を最大50ページ分取得できるので、検索件数から何回スクレイピングするかを計算しています。
            if search_count // 200 == 0:
                times = 1
            elif search_count % 200 == 0:
                times = search_count // 200
            else:
                times = search_count // 200 + 1

            f = open('paper_title.txt', 'a', encoding='UTF-8')

            #雑誌などの、論文っぽいタイトルというコンセプトに沿わないタイトルによく含まれている記号と、連載記事などの重複を省くための単語を並べたリストで、これらを含む論文タイトルは省くようにしています。
            symbols = [':', '：', '(', '（', '―', ',', '，', '.', '．', '/', '／', '「', '?', '？', '!', '！', ' ', '　', '~', '<', '＜', '『', '【', '特集', '連載', 'CiNii Research articles'] 
            for time in range(times):
                count = 1 + 200 * time
                url ='https://cir.nii.ac.jp/opensearch/articles?q={0}&count=200&start={1}&from={2}&until={2}&lang=ja&format=rss'.format(subject, count, term)
                res = requests.get(url)
                soup = BeautifulSoup(res.content , features="xml")
                for title in soup.find_all('title'):
                    for symbol in symbols:
                        if symbol in title.string:
                            break
                        elif len(title.string) <= 10: #目次や表紙など、分割でアップロードされているものもあったため、10文字以下の論文は省くようにしています。
                            break
                    else:
                        #text = '
' + subject + '[SEP]' + title.string + '
'
                        text = '
' + title.string + '
'
                        f.write(text)
                        continue
            f.close()
```

### 2. トレーニングデータとテストデータに分割
scikit-learnの`train_test_split`という関数を使い、集めたタイトルをトレーニングデータとテストデータに分割します。
割合は、トレーニングデータ:テストデータ=`8:2`としました。
```python:title_split.py
from sklearn.model_selection import train_test_split

def split_title():
    with open("paper_title.txt", 'r', encoding='UTF-8') as f:
        title = f.read()
    #一旦
で分割してリストに
    title_list = title.split("
")
    title_list = [title + "
" for title in title_list]

    train, test = train_test_split(title_list, test_size=0.2)

    #それぞれのファイルに保存
    with open("train_title.txt", 'w', encoding='UTF-8') as f:
        f.write("".join(train))
    with open("test_title.txt", 'w', encoding='UTF-8') as f:
        f.write("".join(test))
```

## 2. 機械学習の準備
NVIDIA製GPUを使って学習するには、**ドライバー**と**CUDA**をインストールする必要があります。
また、Deep Learningに関連する計算を高速化してくれるライブラリ、**cuDNN**もインストールします。
環境によっては**Visual Studio**のダウンロードも必要らしいですが、自分は必要なかったためここは各々で調べてください(ごめんなさい)。

また、機械学習ライブラリは友人の個人的な趣味で**PyTorch**を使います。
自分は機械学習初心者なので良し悪しは分かりません！

### 1. ドライバーのインストール
**ドライバー**は、[公式ドライバー｜NVIDIA](https://www.nvidia.co.jp/Download/index.aspx?lang=jp '公式ドライバー｜NVIDIA')からダウンロードできます。
自分のGPUに対応したドライバーを選択してダウンロードしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/d4516de0-c6f9-08a4-fc80-8d8f3b26702d.png 'NVIDIAドライバーダウンロードページ')
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/7385d4d5-dfce-02c7-2cbd-5af0aed0dedd.png)

### 2. CUDAのインストール
**CUDA**は、[CUDA Toolkit Documentation](https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html#cuda-major-component-versions__table-cuda-toolkit-driver-versions 'CUDA Toolkit Documentation')からダウンロードできます。
**先ほどダウンロードしたドライバーに対応するCUDA**を入れてください。
バージョンの対応表も公式サイトに載っています。
※一部抜粋
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/6165691e-f272-1199-aff9-e60a6eac30ed.png 'NVIDIA CUDA Toolkit and Corresponding Driver Versions')
自分はドライバーバージョン527.37を入れたため、任意のCUDA入れることができます(と思っています)。
今回はCUDA 11.7をインストールしました。
また、システム環境変数にパスを通す必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/1d327b3c-94f4-abf1-27ed-f78b359ba5ec.png 'システム環境変数')

### 3. cuDNNのインストール
**cuDNN**は、[CUDA Deep Neural Network(cuDNN)｜NVIDIA Developer](https://developer.nvidia.com/cudnn 'CUDA Deep Neural Network(cuDNN)|NVIDIA Developer')からダウンロードできます。
cuDNNのダウンロードにはNVIDIAのアカウント作成が必要です。
また、**先ほどダウンロードしたCUDAに対応するcuDNN**を入れてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/6c63ddf0-c57f-7bf9-226c-c846c5e167ba.png)
自分はCUDA 11系統を入れたため、cuDNN v8.7.0 for CUDA 11.xをインストールしました。
cuDNNもCUDA同様に、システム環境変数にパスを通す必要があります。

### 4. PyTorchのインストール
**PyTorch**は、[PyTorchの公式サイト](https://pytorch.org/ 'PyTorch')からインストールできます。
公式サイトにある環境診断から自分の環境を選択し、インストールコマンドを入手します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617941/35f33234-5a3a-400d-8759-ad8dfed7a70b.png)

自分のPython環境はCondaで、CUDAはv11.7を入れたため、画像のようなコマンドでPyTorchをインストールしました。
Pythonで下記のコマンドを実行することで、PyTorchがGPUを認識しているかを確認することができます。

```python
import torch
torch.cuda.is_available()
#True
```

# おわりに
この記事では、データセットの作成と機械学習の準備について書きました。
機械学習用の環境構築は意外と簡単にできました。
思い出しながら書いているので所々大事なとこが抜けているかもしれません。改善点などあればぜひコメントしてください。

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

## 参考記事
* [GPT-2で友達を再現して対話してみた](https://qiita.com/Yokohide/items/e74254f334e1335cd502)
* [GPT-2をファインチューニングしてニュース記事のタイトルを条件付きで生成してみた。](https://qiita.com/m__k/items/36875fedf8ad1842b729)
* [論文タイトルから学会流行を知り，日本語GPT-2でタイトル生成してみよう!](https://qiita.com/dcm_sawayama/items/7c991d44ee57e96446c5)
* [日本語GPT-2を使って「小説家になろう」に掲載されていそうなタイトルを生成してみた](https://qiita.com/Katsumata420/items/077de5d968eb7d600d36)
* [スクレイピングしたデータをCSVに保存してみた！](https://qiita.com/ki_ttyomo/items/88dd2359d14850af612d)
* [Pytorch – GPU と対応するドライバ、CUDA、CuDNN のバージョン](https://pystyle.info/pytorch-relationship-between-gpu-and-driver-cuda-and-cudnn-versions/)