Alpaca-loraを日本語タスクでファインチューニングする

[Alpaca-LoRA](https://github.com/tloen/alpaca-lora)という家庭用GPUでも大規模言語モデルのFineTuningが可能なモデルが発表されました。

本記事では、[livedoorニュースコーパス](https://www.rondhuit.com/download.html)を使用してAlpaca-LoRAをFineTuningしてニュースのタイトルを考えさせるというタスクに挑戦してみます。

# 技術の概要
## Alpacaとは
[Alpaca](https://github.com/tatsu-lab/stanford_alpaca)とは、先日Metaが発表したLLaMa 7Bをtext-davinci-003によるself-instructで生成されたデータを使用してFineTuningした言語モデル。
生成したデータは52K個で生成コストは500ドル以下と低コストです。

人間による予備評価では7Bという比較的小さなモデルにも関わらず、text-davinci-003に似た挙動を示すという報告があげられています。

## Alpaca-LoRAとは
Alpaca-LoRAとはAlpacaで作成したデータセットを使用してLLaMa 7Bにlow-rank adaptation(LoRA)を使用してモデルをFineTuningすることで低コストでAlpacaと同等の出力を得ることができる言語モデルです。

学習はRTX4090一台で5時間以内に完了すると報告されています。また、13b、30b、65bなどのモデルにも容易に拡張が可能なようです。
また、推論はRaspberry Pi(4GB)でも動作することが確認されています。

以下は、Alpaca-LoRAのリポジトリに上がっていた、text-davinci-003、Alpaca、Alpaca-LoRAの出力結果を比較した例をDeepLにより日本語訳したものです。

```:入力
アルパカについて教えてください。
```

```:Alpaca-LoRA
アルパカはラクダ科の動物で、南米のアンデス山脈に生息しています。南米アンデス山脈に生息し、柔らかく高級感のあるフリースで知られ、衣類や毛布などの材料として使われています。アルパカは草食動物で、草などを食べています。社会性のある動物で、最大20頭ほどの群れで生活しています。
```

```:Alpaca
アルパカはラクダやリャマに似た小型でふわふわした動物です。ペルーとボリビアが原産で、約5,000年前に初めて家畜化されました。ニットなどの衣料品に使われる上質で柔らかいフリースが主な目的で飼われています。アルパカは群れで生活する動物で、年配の男性を中心とした小さな家族集団で暮らしています。社会性の高い動物で、飼い主と強い絆で結ばれることもあります。
```

```:text-davinci-003
アルパカは、南米のラクダ科の動物で、ラマに似た家畜の一種です。南米のアンデス山脈に生息し、柔らかく高級な毛を持つことから家畜として飼われています。アルパカは社会性のある動物で、最大20頭の群れで生活しています。一般的には2～5頭の小さな群れで飼育され、比較的世話がしやすいと言われています。アルパカは草食動物で、草や干し草などの植物を食べます。また、穏やかで人懐っこい性格で知られており、ペットとしても人気があります。
```

# 実装
本来はAlpacaで使用したデータを日本語に翻訳してFineTuningを行い日本語モデルを作成したかったのですが、翻訳コストの問題からデータが用意できませんでした。

今回はお試しということで冒頭にも述べたとおりlivedoorニュースコーパスを使用してニュースの内容からタイトルを考えるというタスクに特化した学習を行っていきます。

筆者の使用GPUはRTX3080一台です。バッチサイズを調整すればGPUメモリが12GB以上であれば単一のGPUでFineTuningが可能だと思います。

## livedoorニュースコーパスの前処理
Alpacaのデータは次のようにinstruct、input、outputの3つの要素からできています。

```:Alpacaのデータ例
{
    "instruction": "Give three tips for staying healthy.",
    "input": "",
    "output": "1.Eat a balanced diet and make sure to include plenty of fruits and vegetables. \n2. Exercise regularly to keep your body active and strong. \n3. Get enough sleep and maintain a consistent sleep schedule."
}
```

前処理としてlivedoorニュースコーパスのデータを上記の形に置き換えます。

今回は次のようにinstructionは常に「次のニュースのタイトルを考えて」で固定し、inputをニュースの内容、outputをタイトルとしていきます。

```:前処理を行ったlivedoorニュースコーパスのデータ例
{
    "instruction": "次のニュースのタイトルを考えて",
    "input": "吉川ひなのさん、マリエさん、長谷川京子さん、新垣結衣さんらが所属している芸能プロダクションの株式会社レプロエンタテインメントが、「感謝を込めて皆様にハッピーをお返しする」チャリティープロジェクト「レプロ・ハピチャリ・プロジェクト」を開始いたしました。昨年より企画準備していたという同プロジェクトは、今回の大災害が発生したことにより、企画内容を変更し計画を早急に進めたとのことで、所属タレント・スタッフ一同による総額合計3543万9896円の義援金による支援や、所属タレントとスタッフが地方各地を廻り、中長期的なチャリティー活動などを実施する予定だそうです。■レプロ・ハピチャリ・プロジェクト概要1.被災者の皆様へ義援金を寄付レプロエンタテインメントより2000万円、所属タレント・スタッフ一同より1543万9896円、合計3543万9896円を日本赤十字社を通じて寄付致します。2.所属タレント、スタッフによるチャリティー活動の実施所属タレントとスタッフが地方各地を廻り、中長期的なチャリティー活動を実施します。同時に被災地の1日でも早い復興を願って「レプロ募金」を開設し、広く義援金を募ります。3.ユニクロとのチャリティーコラボ商品発売本プロジェクトに賛同して下さる企業と協力し、本プロジェクトの理念や想いを広く伝えていきます。募金・収益金は、日本赤十字社、社団法人セーブ・ザ・チルドレン・ジャパンなどに寄付させて頂きます。活動の詳細が確定次第、随時公式サイト等にてお伝えしてまいります。■関連リンク・株式会社レプロエンタテインメント-公式サイト",
    "output": "吉川ひなの＆マリエらによるレプロ・ハピチャリ・プロジェクト"
}
```

前処理用のコードは以下です。
```python:create_alpaca_format.py
import os
import json


def parse_file(file_path):
    with open(file_path, 'r') as f:
        title = ''
        news = ''

        for i, line in enumerate(f.readlines()):
            if i < 2:
                # urlと日付は無視
                continue
            elif i == 2:
                title = line.strip()
            else:
                news += line.strip()

        return title, news


def main():
    DATADIR = 'data/ldcc-20140209/text'
    LICENCE_FILENAME = 'LICENSE.txt'
    learn_datas = []
    news_list = []
    
    for item in os.listdir(DATADIR):
        if not os.path.isdir(os.path.join(DATADIR, item)):
            # ディレクトリ以外は無視
            continue

        for filename in os.listdir(f'{DATADIR}/{item}'):
            learn_data = {
                "instruction": "次のニュースのタイトルを考えて",
                "input": "",
                "output": ""
            }

            if filename == LICENCE_FILENAME:
                continue

            title, news = parse_file(f'{DATADIR}/{item}/{filename}')

            news_list.append(news)
            learn_data["input"] = news
            learn_data["output"] = title

            learn_datas.append(learn_data)

    json_learn_data = json.dumps(learn_datas, indent=4, ensure_ascii=False)
    with open('news_data_ja.json', 'w', encoding="utf-8") as f:
        f.write(json_learn_data)
```

## Alpaca-LoRAによるFineTuning
Alpaca-LoRAのリポジトリからとってきたfinetune.pyのハイパーパラメータと入力データだけを変更し、それ以外は元のコードから変更していません。

下記はRTX3080に合わせて調整したパラメータになります。
CUTOFF_LENだけlivedoorニュースコーパスでは2200ほどにするのが適切でしたがGPUにのらないため短めなサイズを設定しています。

```python:finetune.py
MICRO_BATCH_SIZE = 1
BATCH_SIZE = 64
GRADIENT_ACCUMULATION_STEPS = BATCH_SIZE // MICRO_BATCH_SIZE
EPOCHS = 5
LEARNING_RATE = 3e-4
CUTOFF_LEN = 512
LORA_R = 8
LORA_ALPHA = 16
LORA_DROPOUT = 0.05
VAL_SET_SIZE = 24 
```

# 出力結果
Alpaca-LoRAでFineTuningしたモデルとChatGPTの出力結果を比較していきます。

入力のニュースの内容は以下より入力長の都合で一部を抜粋しています。
https://news.yahoo.co.jp/articles/ad7a110b769c6d9154bb5c146d20095a805b8814

```:入力
次のニュースのタイトルを考えて

ほのぼのと平和な内容が毎週楽しめるアニメ『サザエさん』ですが、意外と泥棒に入られるという物騒なエピソードが多いのはご存じでしょうか？　1992年発売の本『磯野家の謎』によると、原作マンガ68巻のうちで、磯野家が泥棒に入られた回数は18回にものぼるとのことです。磯野家の間取りを見る そんな原作に沿って、アニメでも泥棒は定期的にサザエさんたちの家にやってきます。その回数の異様な多さに、ネットでは「定番エピソードにしても多すぎる」と、心配する声まで出てしまいました。いかにおおらかな昭和世代の話といえど、さすがに防犯意識の低さを指摘したくなります。 そもそも、サザエさん宅は泥棒を寄せ付けない造りにはなっていません。
```

```Alpaca-LoRA
泥棒に入られるサザエさんのエピソードが多すぎる
```

```chatGPT
『サザエさん』には意外な物騒なエピソードが!? 泥棒が頻繁に訪れる磯野家に防犯意識の低さを指摘する声も
```

chatGPTには少し劣るようにも感じますが、Alpaca-LoRAの出力も悪くなく日本語を理解しているように感じます。

# 最後に
今回使用したコードは以下のリポジトリにあげています。
https://github.com/tosiyuki/alpaca-lora-create-news-title

今後、このように大規模言語モデルがローカル環境でもより簡単に動かせるようになる気がするので楽しみです。