クリスマス暇なので、AIにクリスマスソングを作らせた話

この記事は[WINC(早稲田コンピュータ研究会) Advent Calendar 2022](https://adventar.org/calendars/8102)の24日目の記事です。


こんにちは、クリスマスイブですね🎄
皆さんはサンタさんに何を頼みましたか？僕は今年就活生ということで、内定をお願いしました。

ところで、クリスマスといえば、クリスマスソングですよね。クリスマスソングに出てくる
***Romantic***なセリフってどうやったら思いつくのでしょうか。年齢的に、そろそろいい感じのセリフを生成できるようにならねばという焦燥感に駆られ、今回はAIにクリスマスソングを作らせてみようと思います。

尚、筆者はひよっこエンジニアですので、何か間違っていることを言っていた場合は優しく教えていただけるととても嬉しいです。

:::note warn
こちらの記事はクリスマスソングの歌詞の自動生成を行うもので，作曲をさせるわけではありません
:::

# やったこと
- クリスマスソングの歌詞データを収集する
- rinnaの日本語GPT-2モデルをファインチューニングして、クリスマスソングの自動生成を行う
- 結果を眺める

早く結果が見てみたいという方は[こちら](#結果)


# GPT-2とは
ここ最近は自然言語処理界隈が盛り上がっていて、特にOpenAIが開発した対話特化の大規模言語モデルである「ChatGPT」は騒がれていますよね。OpenAIは以前からさまざまなモデルを開発していて、今回利用するGPT-2は、2019年2月に発表されたものです。GPT-2はTransformerをベースとしたテキスト生成モデルで、与えられたテキストの単語を元に、逐次的に次の単語を予測しています。

アルゴリズムから細かく知りたいという方は、[こちらの記事](http://jalammar.github.io/illustrated-transformer/)を読んでみてください。

# データセットの収集
[歌ネット](https://www.uta-net.com/)から歌詞データをスクレイピングして集めます。

今回はクリスマスソングを集めたいので、歌詞検索で「クリスマス」という文字を含む歌を検索します。1354件がヒットしました。歌ネットの歌詞検索ではページ移動でURLが変化しないので全件取得には少しコツがいります。seleniumを使ってブラウザを自動操作するか，検索結果一覧表示件数を変更したときのurlパラメータをいい感じにいじってあげる必要があります。あとは、いい感じにスクレイピングをするだけです。コードは[こちらの記事](https://qiita.com/q-tyl/items/2da4680917784eb91f49)を参考にさせていただきました。以下、収集したデータの抜粋です。

|Song  |Artist  |Lyricist  |Composer  |Lyric  |
|---|---|---|---|---|
|あなたはサンタクロース  |宍戸留美  |田中ヤマネコ  |田中ヤマネコ  |眠くて 眠くて 寝ちゃいそう 今年は会えると思ってた もう少し もう少し 起きてたら ギリギリ...  |
|クリスマスラブ  |すとぷり  |HoneyWorks  |HoneyWorks  |今宵僕は君を選ぶだろう 白い吐息感じる距離で 愛を誓おう 溶けない愛を...  |
|カフェラテ  |ほのかりん  |ほのかりん  |ほのかりん  |思い出す体温は、気付いてた内存で、聴こえたの哀音、「君が好きだったよ」...  |
|Xmas with U  |LANA  |LANA  |LANA  |一番好きなイベント 今日は意味のある日なのでしょ？ だけどあなたがいないと意味がなくて Wanna see you it's a...  |
|クリスマスのよる  |あたらよ  |ひとみ  |ひとみ  |街の灯りがやけに眩しい夜 今年もこの季節か 街に溶け込むことが出来ない僕は イヤホンの音量を上げる...  |


# データの前処理① 英語→日本語 (断念)
歌詞の中で英語を混ぜてくるスタイルってオシャレですよね。ですが、分かち書きをする手前、英語を使うのはあまり良くないので今回は英語を日本語に翻訳して利用しようと思いました。しかし，データセットにピリオドがない関係で，部分的に翻訳がされないという問題が起こったのと，そもそも翻訳ではかなりカタコトになってしまうので，面倒になって断念しました。


**原文**

> "だけどあなたがいないと意味がなくて Wanna see you it's a “Merry Merry Xmas” Baby tonight, I wanna stay with you tonight ロウソク立ててお願いごと あなたと来年も祝えますように 今夜は2人で映画でも観て過ごそうよ Fall in love with each otherね again & again 今夜… だけどまたケンカするけど 今夜は噛みしめよ a happy “Merry Merry Xmas” 愛してるよ、誰よりも。 あなたとうちだけのクリスマスにしよ A happy happy “Merry Merry Merry Xmas” Candles and trees and kisses and presents You are my baby love Candles and trees and kisses and presents You are my baby love Candles and trees and kisses and presents You are my baby love Candles and trees and kisses and presents You are my baby love"

**翻訳後**

> だけどあなたがいないと意味がなくてメリー・メリー・クリスマス・ベイビー 今夜はあなたと一緒にいたいロウソク立ててお願いごとあなたと来年も祝えますように今夜は2人で映画でも観て過ごそうよそれぞれに恋をするotherねもう一度今夜…だけどまたケンカするけど今夜は噛みしめよ幸せな「メリーメリークリスマス」愛してるよ、誰よりも。あなたとうちだけのクリスマスにしよハッピー ハッピー メリー メリー メリー クリスマス キャンドルとツリーとキスとプレゼント You are my baby love キャンドルとツリーとキスとプレゼント You are my baby love キャンドルとツリーとキスとプレゼント You are my baby love キャンドルとツリーとキスとプレゼントプレゼント You are my baby lovelove


めちゃめちゃ違和感ありますね。

一応、ピリオドの問題は、[こちらの記事](https://qiita.com/sentencebird/items/a916cc8d874470c3b2fc)のようにピリオドの位置を推論することでなんとかできると思います。


# データの前処理② 英語，記号，空白の削除

英語の翻訳は断念しましたが，そのまま使うわけにはいかないので，英語は削除します。また，記号と空白も削除していきます。

```Python
def preprocess(raw_text: str) -> str:
    # 文字列を空白で区切ってリストに変換
    raw_text_list = raw_text.split()
    text_list: List[str] = []
    for text in raw_text_list:
        # 日本語文字を含まない場合
        if re.search('[一-龥ぁ-んァ-ン]+', text):
            # 半角記号の置換
            tmp = re.sub(r"[!-/:-@[-`{-~]", r" ", text)
            # 全角記号の置換 (ここでは0x25A0 - 0x266Fのブロックのみを除去)
            preprocessed_text = re.sub("[■-♯]", " ", tmp)
            text_list.append(preprocessed_text)
    return "".join(text_list)
```


最後に，前処理をした後のデータを一文ずつ格納したテキストファイルを作成し，transformersディレクトリと同じ階層に配置します。

```:train.txt
眠くて眠くて寝ちゃいそう今年は会えると思ってたもう少...
今宵僕は君を選ぶだろう白い吐息感じる距離で愛を誓おう溶けない愛を...
思い出す体温は、気付いてた内存で、聴こえたの哀音...
   :
   :
```


```
directory/
    ├ transformers/
    |  └ …
    ├ train.txt
     …

```


# 実装
データの前処理が終わったら，実装をしていきます。今回は，rinnaの日本語GPT-2モデルを利用します。

https://huggingface.co/rinna/japanese-gpt2-medium



また，実装といってもHuggingFaceのTransformersのコードを利用することで簡単にファインチューニングを行うことができます。

https://github.com/huggingface/transformers

(1) リポジトリのクローン

ファインチューニング用のコードをクローンします。transformers等とのバージョンを合わせないといけないので，ブランチを指定して持ってきます。

```
git clone https://github.com/huggingface/transformers -b v4.25-release
```

(2) パッケージのインストール

```bash
pip install transformers==4.25.1
pip install evaluate==0.4.0
pip install sentencepiece==0.1.97
```
パッケージでエラーを吐かれた場合は適宜pip install を行ってください。一応僕の環境でのパッケージ一覧を貼っておきます。重要なのは，上でクローンしてきたリポジトリのバージョンとtransformersのバージョンが合っていることです。

<details><summary>パッケージ一覧</summary>

```sh:requirements.txt
aiohttp==3.8.3
aiosignal==1.3.1
async-timeout==4.0.2
attrs==22.1.0
beautifulsoup4==4.11.1
bs4==0.0.1
certifi==2022.12.7
charset-normalizer==2.1.1
datasets==2.7.1
dill==0.3.6
evaluate==0.4.0
filelock==3.8.2
frozenlist==1.3.3
fsspec==2022.11.0
fugashi==1.2.1
huggingface-hub==0.11.1
idna==3.4
ipadic==1.0.0
joblib==1.2.0
multidict==6.0.3
multiprocess==0.70.14
numpy==1.24.0
packaging==22.0
pandas==1.5.2
protobuf==3.20.0
pyarrow==10.0.1
python-dateutil==2.8.2
pytz==2022.7
PyYAML==6.0
regex==2022.10.31
requests==2.28.1
responses==0.18.0
scikit-learn==1.2.0
scipy==1.9.3
sentencepiece==0.1.97
six==1.16.0
sklearn==0.0.post1
soupsieve==2.3.2.post1
threadpoolctl==3.1.0
tokenizers==0.13.2
torch==1.13.1
tqdm==4.64.1
transformers==4.25.1
typing_extensions==4.4.0
urllib3==1.26.13
xxhash==3.1.0
yarl==1.8.2
```
</details>

(3) ファインチューニングの実行

```bash
python3 ./transformers/examples/pytorch/language-modeling/run_clm.py \
    --model_name_or_path=rinna/japanese-gpt2-medium \
    --train_file=train.txt \
    --validation_file=train.txt \
    --do_train \
    --do_eval \
    --num_train_epochs=10 \
    --save_steps=10000 \
    --save_total_limit=3 \
    --per_device_train_batch_size=1 \
    --per_device_eval_batch_size=1 \
    --output_dir=output/
```


(4) 推論の実行

```python
from transformers import T5Tokenizer, AutoModelForCausalLM

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

input = tokenizer.encode("雪は", return_tensors="pt")
output = model.generate(input, do_sample=True, max_length=100, num_return_sequences=15)
print(tokenizer.batch_decode(output))
```

# 結果

試しに，「雪は」から始まる歌詞を生成してみます。

> '雪は
降らないよ。おやすみ。きみとの時間はいつもひとりきり。「おやすみ」と「さよなら」で つながったよ。きみと手をつないだり しっぽをふれ合ったりして はしゃいだね。「ありがとう」と 言ったよ。「メリークリスマス」 ふたりで過ごしたいと望むなら。きみとの 時間はいつもひとりきり。「さよなら」と「さよなら」で つながったよ'

きみとの時間はいつもひとりきり，，。なんだか切ないですね，，。

> '雪は
 白い<unk>空高くの彼方・・・雪は今夜・・・降るだろうな・・もうすぐ降りそそぐ雪の降り積もる景色を夢に見ながら・・・雪は・・・もう少し今夜のうちに降ってくれたらいいのにね 雪の日が待ちきれない夜は・・・雪の花に願いを込めて 星が舞い散るような・・雪に願いをこめて みんなの心'

「雪の花に願いを込めて」，「星が舞い散るような」おしゃれな言い回しです。

> '雪は
降りやまない さぁ!このあとお仕事へ行って来まーす!!やっぱりこれが私のオモチャだから ごーっぜ 雪は雪だろうが こつこつと お仕事しでかそうぜーーーーーー 雪はまだらだ かーーーーーーーーーーーーーーーーー こっち向いてよこで!こっち向いてろ!'

クリスマスソング，，？

>  '雪は
降り積もる 窓をひらけば 白い息 君はどこへ 行ってしまったの クリスマスツリー 灯りを消せば 誰かと過ごせるはず だけど ひとりぼっち クリスマス・キャロル ジングル・ベル 街の色は 少しづつ 雪 に染まって くすり がないと眠れない クリスマス・キャロル ジングル・ベル 街は にぎやかに 彩られる だけ', 

かなりクリスマスソングっぽいのではないでしょうか？！

> '雪は
 積もってる 寒いね 窓ガラス 白くなっちゃうよ サンタがこっちを見つめてる プレゼントを届けにきてくれてるよ 雪がつもるよ 街はにぎやかだね ほら サンタクロースがソリに乗ってやってきたよ ジングルベルが空にとけてゆけば...きっとトナカイもやって来るさ 












'

トナカイとサンタさんが別々にやってくるのでしょうか？笑

# おまけ
僕の大大大好きな「サイゼリヤ」から始まるクリスマスソングを生成してみました。

> 'サイゼリヤ
 ごはんがすすむ頃。 ふたりの願いはなんにもないけど今夜はすこし素敵な夜を ああ、あの頃の二人今となってはもう何の自慢にもならないけどま、いいかあんまり自慢にもならんけど確かに幸せは感じたしねでも今となっては「幸せ」なんて言葉さえもう二度と口にはしないだろうなあ 









'

恋人とサイゼリヤを食べながら，昔の２人のことを回想しているのでしょうか。今となっては，「幸せ」と口に出しては言わないけれど，確かにそれを感じていると，ふと思ったのでしょう。いろんな解釈ができるいい歌詞だと思いました。


# おわりに

今回は日本語GPT-2モデルをファインチューニングして，クリスマスソングの自動生成を行いました。
データの前処理をもっと工夫したり，データの数を増やすことでさらに自然な文章を生成できるようになると思うので，また今度挑戦したいと思います！

でも，，，こんなクリスマス・イブは嫌だっ！！

# 参考文献
https://note.com/npaka/n/n9f2f6aa0eb0b