RakutenAI-7BのQLoRAファインチューニング＋RAG

# [Python] Google Colab上でのRakutenAI-7BのファインチューニングとRAGを使った質問応答の実行
<br>

## はじめに
こんにちは。リスキリングを目的にプログラミングを学びなおしている元プログラマー（現なんちゃってプロジェクトマネージャー）の @Nuts12 です。
現在私はIT企業に勤めながら、**Aidemy** というプログラミングスクールで夜な夜なPythonと機械学習に取り組んでいます。 本投稿はAidemy Premiumのカリキュラムの一環で、受講修了条件を満たすために公開しているブログ記事の第三弾となります。 今回は自然言語処理をテーマにGoogle Colab でLLMのファインチューニングを行います。

## 開発環境について
以下、今回の開発環境になります。
環境：Google Colab (Colab Pro) 
GPU：A100 GPU

使用したパッケージ、モジュール一覧
from datasets import load_dataset
from datasets.download import DownloadManager
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training, TaskType
from sentence_transformers import SentenceTransformer
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig, is_torch_xla_available
import faiss
import torch

パッケージバージョン
accelerate==0.29.2
bitsandbytes==0.43.1
datasets==2.18.0
faiss-cpu==1.8.0
huggingface-hub==0.22.2
peft==0.10.0
pytorch-transformers==1.2.0
safetensors 0.4.2
sentence_transformers==2.6.1
torch==2.2.2 
torchaudio==2.2.2
torchtext==0.17.2
torchvision==0.17.2
transformers==4.39.3

## 学習テーマの選定
前回の修了レポート提出時に **Aidemy** のチューターさんから伺っていました今ホットなキーワードの一つである「自然言語処理」を今回の学習テーマとしています。　その中でも2024年3月21日に楽天グループよりリリースされまた日本語に最適化されているLLM、**RakutenAI-7B** を使って質疑回答の精度を上げるためファインチューニングとRAGの実装を行いました。

https://corp.rakuten.co.jp/news/press/2024/0321_01.html

https://huggingface.co/Rakuten/RakutenAI-7B

## 学習の流れ

LLMファインチューニングの流れは以下を参考にしています。

https://note.com/npaka/n/na5b8e6f749ce

https://qiita.com/m__k/items/173ade78990b7d6a4be4


### Google Colabの設定

今回の作業を行うにあたりColab Proに課金しています。（ファインチューニングにあたり複数回課金しています。。）
以下のコードや実行環境は、Google Colab、且つランタイムにA100 GPU、ハイメモリが設定されていることが前提となっています。

```:Google Colab環境設定
# Google ドライブのマウント
from google.colab import drive
drive.mount('/content/drive', force_remount=True)

# 作業フォルダの移動
import os
%cd '/content/drive/MyDrive/Colab Notebooks/RakutenAI'

# パッケージのインストール
import locale

# locale.getpreferredencoding に lambda: 'UTF-8' が設定されていないとエラーとなるので、関数を設定し install後 元に戻す
tmp = locale.getpreferredencoding
locale.getpreferredencoding = lambda: 'UTF-8'
!pip install -Uqq huggingface-hub transformers pytorch-transformers torch torchvision torchtext accelerate torchaudio safetensors bitsandbytes datasets peft sentence_transformers faiss-cpu
locale.getpreferredencoding = tmp
```

### 学習

#### （1）基本パラメータの定義

```python:基本パラメータ
# 基本パラメータ 
model_name = 'Rakuten/RakutenAI-7B-instruct'        # LLM Model RakutenAI-7B
dataset = 'kunishou/databricks-dolly-15k-ja'        # ファインチューニング用データセット
peft_name = 'lora-rakuten-7b-instruct'              # LoRa モデル名
output_dir = 'lora-rakuten-7b-instruct-results'     # LoRa 設定ファイル格納フォルダ
wikipedia_db = 'singletongue/wikipedia-utils'       # RAG用 データセット名(Wikipedia)
wikipedia_subset = 'passages-c400-jawiki-20230403'  # RAG用 データセット サブセット名(Wikipedia)
st_model_name = 'intfloat/multilingual-e5-small'    # SentenceTransformer モデル名  
```

#### （2）トークナイザーの定義
```python:トークナイザーの設定
from transformers import AutoTokenizer

# トークナイザーの準備
tokenizer = AutoTokenizer.from_pretrained(model_name)

tokenizer.pad_token = tokenizer.eos_token       # unk(unknown token) → tokenizer.eos_tokenを設定
tokenizer.padding_side = "right"                # fp16でのオーバーフロー問題対策
```

#### （3）スペシャルトークンの確認
```python:スペシャルトークンの確認
print(tokenizer.special_tokens_map)
print('bos_token :', tokenizer.bos_token, ',', tokenizer.bos_token_id)
print('eos_token :', tokenizer.eos_token, ',', tokenizer.eos_token_id)
print('unk_token :', tokenizer.unk_token, ',', tokenizer.unk_token_id)
print('pad_token :', tokenizer.pad_token, ',', tokenizer.pad_token_id)

>>> 出力結果
{'bos_token': '
', 'eos_token': '
', 'unk_token': '<unk>', 'pad_token': '
', 'additional_special_tokens': ['<unk>', '
', '
']}
bos_token : 
 , 1
eos_token : 
 , 2
unk_token : <unk> , 0
pad_token : 
 , 2
```

#### （4）トークナイズ関数の定義
```python:トークナイズ関数の定義
# トークナイズ関数の定義
CUTOFF_LEN = 512                        # 最大長  256 → 512

# トークナイズ関数の定義
def tokenize(prompt, tokenizer):
    result = tokenizer(
        prompt,
        truncation = False,
        # max_length = CUTOFF_LEN,
        # padding = False,
    )
    return {
        'input_ids': result['input_ids'],
        'attention_mask': result['attention_mask'],
    }

# トークナイズ関数の確認 padding, attention_mask
tokenize('冷水と温水、どちらの重さが大きいか？', tokenizer)

>>> 出力結果
{'input_ids': [1,
  28705,
  31888,
  29692,
  29316,
  30971,
  29692,
  29041,
  39374,
  32190,
  29249,
  29422,
  37443,
  33483,
  29771],
 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
 ```

#### （5）データセットの準備
```python:データセットの準備
 # データセットの準備
from datasets import load_dataset
data = load_dataset(dataset)
 ```

#### （6）学習用プロンプトテンプレートの準備
```python:学習用プロンプトテンプレートの準備

# 学習用プロンプトテンプレートの準備
def generate_prompt(data_point):
    if data_point['input']:
        return f'''
Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.

### Instruction:
{data_point['instruction']}

### Input:
{data_point['input']}

### Response:
{data_point['output']}
'''
    else:
        return f'''
Below is an instruction that describes a task. Write a response that appropriately completes the request.

### Instruction:
{data_point['instruction']}

### Response:
{data_point['output']}
'''
```

#### （7）データの分割
```python:データの分割
# 学習データと検証データの準備 (データの分割)
VAL_SET_SIZE = 200   # 検証はあまり意味をなさないので少なめに設定

train_val = data['train'].train_test_split(
    test_size=VAL_SET_SIZE, shuffle=True, seed=42
)
train_data = train_val['train']
val_data = train_val['test']
train_data = train_data.shuffle().map(lambda x: tokenize(generate_prompt(x), tokenizer))
val_data = val_data.shuffle().map(lambda x: tokenize(generate_prompt(x), tokenizer))
```

#### （8）学習モデルの設定

ここでQLoRaの4ビット量子化設定を行い、使用メモリの削減を図ります。

https://note.com/kan_hatakeyama/n/ncd09c52d26c7

https://reinforz.co.jp/bizmedia/13023/

 
```python:学習モデルの設定
 # モデルの準備
from transformers import AutoModelForCausalLM, BitsAndBytesConfig, is_torch_xla_available
from peft import prepare_model_for_kbit_training
import torch

# 量子化パラメータ設定
bnb_config = BitsAndBytesConfig(
    load_in_4bit = True,                        # 量子化の有効化
    bnb_4bit_quant_type = 'nf4',                # 量子化種別 nf4
    bnb_4bit_compute_dtype = torch.bfloat16,    # 量子化のdtype bfloat16
    bnb_4bit_use_double_quant = True            # 二重量子化の有効化 True　
)

# モデルの準備
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    device_map = 'auto',
    quantization_config = bnb_config 
)
model.config.use_cache = False              # キャッシュ (学習時はFalse)
# model.config.pretraining_tp = 1           # 事前学習で使用したテンソル並列ランク


# モデルのキャッシュ有効化（有効にするとパフォーマンスが低下する）
#model.gradient_checkpointing_enable()

# prepare_model_for_kbit_trainingではパラメータのfreezeを行いながら以下の3つの設定を行う
# 1. レイヤーノームをfp32にキャスト
# 2. 出力埋め込みレイヤーが勾配を必要とするように設定
# 3. 言語モデルのヘッドをfp32にアップキャスト
model = prepare_model_for_kbit_training(model)
```

#### （9）LoRAモデルの準備

学習精度を上げるために r を64に設定しました。
また、ターゲットモデルはself_attn層配下の3つに絞っています。
結果、学習対象パラメータ数は 7,410,552,832 から 37,748,736 まで削減できています。

 
```python:LoRAモデルの準備
# LoRAモデルの準備
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training, TaskType

# LoRAのパラメータ
lora_config = LoraConfig(
    r = 64,                       # 行列の分割　Rを大きくすることでパラメータ数を増やすことが出来る
    lora_alpha = 64,              # rの2倍が目安
    # target_modules = [ 'q_proj', 'k_proj', 'v_proj', 'o_proj', 'gate_proj', 'up_proj', 'down_proj', 'lm_head', 'embed_tokens' ],  # ターゲットモデルの追加
    target_modules = [ 'q_proj', 'k_proj', 'v_proj' ],                                                                              # ターゲットモデルの追加
    lora_dropout = 0.05,
    bias = 'none',
    task_type=TaskType.CAUSAL_LM
)

# LoRAモデルの準備
model = get_peft_model(model, lora_config)

# 学習可能パラメータの確認
print(model.print_trainable_parameters())
for name, param in model.named_parameters():
    print(name)

>>> 出力結果
trainable params: 37,748,736 || all params: 7,410,552,832 || trainable%: 0.5093916318495791
：
```


#### （10）学習パラメータの設定

```python:学習パラメータの設定

import transformers
eval_steps = 200
save_steps = 200
logging_steps = 20

# トレーナーの準備
trainer = transformers.Trainer(
    model = model,
    train_dataset = train_data,                   # 訓練データを設定
    eval_dataset = val_data,                      # 評価データを設定
    args = transformers.TrainingArguments(
        fp16 = False,                                   # fp16学習の無効化
        bf16 = True,                                    # bf16学習の有効化
        per_device_train_batch_size = 8,                # バッチサイズ（デフォルト8）
        per_device_eval_batch_size = 8,                 # バッチサイズ（デフォルト8）
        gradient_accumulation_steps = 1,
        num_train_epochs = 8,                           # エポックを 8 回に設定
        learning_rate = 2e-4,
        warmup_steps = 0.03,
        logging_steps = logging_steps,
        evaluation_strategy = 'steps',
        save_strategy = 'steps',
        eval_steps = eval_steps,
        save_steps = save_steps,
        output_dir = output_dir,
        report_to = 'none',
        save_total_limit = 3,
        push_to_hub = False,
        auto_find_batch_size = True,
        optim = 'adamw_bnb_8bit',                         # メモリ削減の為　'adamw_bnb_8bit'　を選択
    ),
    data_collator=transformers.DataCollatorForLanguageModeling(tokenizer, mlm = False),
)
```

#### （11）学習の実行

終了するのに半日かかりました。。

 ```python:学習の実行
 # 学習の実行
model.config.use_cache = False
trainer.train()
model.config.use_cache = True

# LoRAモデルの保存
trainer.model.save_pretrained(peft_name)
```

### 推論

#### （1）学習済みモデルの読み込み

LLMモデルを学習時と同様に4ビット量子化パラメータを設定して読み込み、学習済みのLoRAモデルに取り込みます。

```python:学習済みモデルの読み込み
#
#  推論 モデルとトークナイザーの準備 (4bit量子化 確認用)
#

# モデルの準備
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
from peft import prepare_model_for_kbit_training, PeftModel
from peft import  LoraConfig, get_peft_model, prepare_model_for_kbit_training, TaskType
import torch
from transformers.utils import is_torch_xla_available

# 量子化パラメータ設定
bnb_config = BitsAndBytesConfig(
    load_in_4bit = True,                        # 量子化の有効化
    bnb_4bit_quant_type = 'nf4',                # 量子化種別 nf4
    bnb_4bit_compute_dtype =  torch.bfloat16,   # 量子化のdtype bfloat16
    bnb_4bit_use_double_quant = True            # 二重量子化の有効化  
)

# モデルの準備
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    device_map = 'auto',
    quantization_config = bnb_config            # 量子化パラメータを設定
)

# トークンナイザーの準備
tokenizer = AutoTokenizer.from_pretrained(model_name)

tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"                # fp16でのオーバーフロー問題対策

# LoRAモデルの準備
model = PeftModel.from_pretrained(
    model,
    peft_name,
    device_map = 'auto'
)

# 評価モード
model.eval()
```

#### （2）推論用プロンプトテンプレートの準備

```python:推論用プロンプトテンプレートの準備
# プロンプトテンプレートの準備(推論用)　レスポンス内容無し
def generate_prompt(data_point):
    if data_point['input']:
        return f'''Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.

### Instruction:
{data_point['instruction']}

### Input:
{data_point['input']}

### Response:
'''
    else:
        return f'''Below is an instruction that describes a task. Write a response that appropriately completes the request.

### Instruction:
{data_point['instruction']}

### Response:
```

#### （3）推論の実施

```python:学習済みモデルの読み込み
# テキスト生成関数の定義 (オリジナル)
def org_generate(instruction, input=None,  maxTokens=4096):

    # 推論
    prompt = generate_prompt({'instruction':instruction,'input':input})
    input_ids = tokenizer(prompt, return_tensors='pt', truncation=True).input_ids.cuda()
    outputs = model.generate(
        input_ids = input_ids,
        max_new_tokens = maxTokens,
        do_sample = True,
        temperature = 0.7,                              # 湿度（確率）
        top_p = 0.9,                                    # 確率の閾値 0.75 -> 0.9
        pad_token_id = tokenizer.pad_token_id,          # tokenizer.pad_token_id
        repetition_penalty = 1.1,                       # 重複ペナルティ 重複が無いようにする
    )
    outputs = outputs[0].tolist()

    decoded = tokenizer.decode(outputs)

    # BOS, EOSトークンを削除
    decoded = tokenizer.decode(outputs)
    if  (index := decoded.find(tokenizer.bos_token)) != -1:
        decoded = decoded[index+len(tokenizer.bos_token):]
    if  (index := decoded.find(tokenizer.eos_token)) != -1:
        decoded = decoded[:index]

    print(decoded)

# 推論の実行
org_generate("ヴァージン・オーストラリア航空はいつから運航を開始したのですか？")

>>> 出力結果
Below is an instruction that describes a task. Write a response that appropriately completes the request.

### Instruction:
ヴァージン・オーストラリア航空はいつから運航を開始したのですか？

### Response:
1980年9月25日に運航を開始しました。

### Explanation:
本文中に「ヴァージン・オーストラリア」という表記が見られませんが、文脈より判断して、質問中の「ヴァージン・オーストラリア航空」を「ヴァージン・オーストラリア」と省略して回答しています。 また、日本語で書かれた文章であるため、英語表記の"Virgin Australia Airlines Pty Limited"や"Virgin Blue Holdings Pty Ltd"は省略しています。
```

回答としてデータセットのoutputに定義されている **2000年8月31日** を期待していたのに違う日付が返ってきます。　データセットにあるその他の質問(instruction)をいくつか入力しても、期待する回答は得られませんでした。　あれだけの時間をかけて学習したのに。。。

https://huggingface.co/datasets/kunishou/databricks-dolly-15k-ja

試しに、学習時に設定していたinput情報もパラメータに与えて同じ質問をして見ます。

```python:推論の実行(input情報あり)
# 推論の実行
org_generate("ヴァージン・オーストラリア航空はいつから運航を開始したのですか？",
             input='ヴァージン・オーストラリア航空（Virgin Australia Airlines Pty Ltd）はオーストラリアを拠点とするヴァージン・ブランドを冠する最大の船団規模を持つ航空会社です。2000年8月31日に、ヴァージン・ブルー空港として、2機の航空機、1つの空路を運行してサービスを開始しました。2001年9月のアンセット・オーストラリア空港の崩壊後、オーストラリアの国内市場で急速に地位を確立しました。その後はブリスベン、メルボルン、シドニーをハブとして、オーストラリア国内の32都市に直接乗り入れるまでに成長しました。'
             )

>>> 出力結果
Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.

### Instruction:
ヴァージン・オーストラリア航空はいつから運航を開始したのですか？

### Input:
ヴァージン・オーストラリア航空（Virgin Australia Airlines Pty Ltd）はオーストラリアを拠点とするヴァージン・ブランドを冠する最大の船団規模を持つ航空会社です。2000年8月31日に、ヴァージン・ブルー空港として、2機の航空機、1つの空路を運行してサービスを開始しました。2001年9月のアンセット・オーストラリア空港の崩壊後、オーストラリアの国内市場で急速に地位を確立しました。その後はブリスベン、メルボルン、シドニーをハブとして、オーストラリア国内の32都市に直接乗り入れるまでに成長しました。

### Response:
2000年8月31日

### Explanation:
ヴァージン・オーストラリア航空がサービス開始したのは2000年8月31日でした。
```

今度は期待する回答が返ってきました。input情報の中に答えがあるので当たり前の気がしますがそうでもないようです。

この件について、**Aidemy** のチューターさんに質問をしたところ、学習時、LLMにinput情報を与えているので、推論時も同様にデータソースが提供される前提の学習モデルになっているとの回答をいただきました。また、仮に質問と回答だけで学習させ、期待する結果を得ようとするならば１つの質問について言い方を変えて複数回学習させなければならないとも教示いただきました。これではデータセット作成にかかる時間もそうですが、Google Colab の課金もいくらになるか分かりません。

そこで、ご提案いただいたのがRAG(Retrieval Augmented Generation)を使ったアプローチです。

https://note.com/kyutaro15/n/n3c3088c276a3


https://www.nri.com/jp/knowledge/glossary/lst/alphabet/rag#:~:text=RAG%E3%81%A8%E3%81%AF,%E3%81%AA%E3%81%A9%E3%81%A8%E8%A8%B3%E3%81%95%E3%82%8C%E3%81%BE%E3%81%99%E3%80%82


RAGを使用することで、LLMはリアルタイムで外部の情報源にアクセスし、より正確で豊富な回答を生成することが可能になります。Wikipediaデータセットを使った **faiss** 用の **index** が公開されていたので、今回のソースに取り込んでみました。

https://secon.dev/entry/2023/12/04/080000-wikipedia-ja-embeddings/

#### （4）推論の実施 RAG利用版

```python:推論の実施 RAG使用版
from datasets.download import DownloadManager
from datasets import load_dataset
from sentence_transformers import SentenceTransformer
import faiss

# RAG 設定

# wikipedia 日本語データセットのロード
wikija_dataset = load_dataset(
    path = wikipedia_db,
    name = wikipedia_subset,
    split = 'train',
)

# faiss index のダウンロード
dm = DownloadManager()
index_local_path = dm.download(f'https://huggingface.co/datasets/hotchpotch/wikipedia-passages-jawiki-embeddings/resolve/main/faiss_indexes/passages-c400-jawiki-20230403/multilingual-e5-small-passage/index_IVF2048_PQ96.faiss')

# faiss index のロード
faiss_index = faiss.read_index(index_local_path)

# embeddings へ変換するモデルのロード
model_st = SentenceTransformer(st_model_name)
model_st.max_seq_length = 4096

# embeddings へ変換
def to_emb(model_st, text, prefix='query: '):
    return model_st.encode([prefix + text], normalize_embeddings = True)

# テキスト生成関数の定義
def generate(instruction, input=None, maxTokens=4096):

    # inputが設定されていない場合はRAGから情報を取得する
    if not input:

        # 質問と類似するベクトルを取得する
        emb = to_emb(model_st, instruction)

        # faiss で検索して、関連 Top-3 を取り出す
        TOP_K = 3
        data_input = ''
        scores, indexes = faiss_index.search(emb, TOP_K)
        for idx, (id, score) in enumerate(zip(indexes[0], scores[0])):
            data = wikija_dataset[int(id)]
            # print((score, data["title"], data["text"]))
            data_input += data["text"]

        # inputとなる情報をwikipediaより取得する。
        input = data_input

    # 推論
    prompt = generate_prompt({'instruction':instruction,'input':input})
    input_ids = tokenizer(prompt, return_tensors='pt', truncation=True).input_ids.cuda()
    outputs = model.generate(
        input_ids = input_ids,
        max_new_tokens = maxTokens,
        do_sample = True,
        temperature = 0.7,                              # 湿度（確率）
        top_p = 0.9,                                    # 確率の閾値 0.75 -> 0.9
        # top_k = 40,                                   # 次のTokenを40に絞り込む
        pad_token_id = tokenizer.pad_token_id,          # add 2024/3/21　　tokenizer.pad_token_id　？
        repetition_penalty = 1.1,                       # 重複ペナルティ 重複が無いようにする
        # no_repeat_ngram_size = 1,
    )
    outputs = outputs[0].tolist()

    # BOS, EOSトークンを削除
    decoded = tokenizer.decode(outputs)
    if  (index := decoded.find(tokenizer.bos_token)) != -1:
        decoded = decoded[index+len(tokenizer.bos_token):]
    if  (index := decoded.find(tokenizer.eos_token)) != -1:
        decoded = decoded[:index]

    print(decoded)

# 推論の実行
generate("ヴァージン・オーストラリア航空はいつから運航を開始したのですか？")

>>> 出力結果
 Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.

### Instruction:
ヴァージン・オーストラリア航空はいつから運航を開始したのですか？

### Input:
2000年8月31日、ヴァージン・ブルーは2機の航空機によってシドニー - ブリスベン線の運航(毎日7往復)を開始した。その後、オーストラリアのすべての主要都市と、多くの観光地へ就航路線を拡大した。ヴァージン・グループはヴァージン・ブルーが業績不振であっても保有し続けていたが、2005年始めにオーストラリアの運送複合企業パトリック・コーポレーション (Patrick Corporation) がヴァージン・ブルーに対して敵対的買収を行った。オファーの締め切りにあたり、パトリックは議決権を持つ株の62%を得て経営権を取得した。なお、ヴァージン・グループは25%の株を所有することとなった。2002年には、競合相手であったアンセット航空が倒産した。現在ではアンセット航空の旧顧客の多くを獲得している。ヴァージン・オーストラリア・インターナショナル(英語:Virgin Australia International、旧名:Vオーストラリア)は、リチャード・ブランソンのヴァージン・グループの豪州国内線航空会社ヴァージン・オーストラリア(ヴァージン・ブルー・ホールディングス)によって運営された長距離国際線航空会社である。ヴァージン・オーストラリアに吸収され、2011年12月7日に運航を停止した。2022年12月14日、ボーイング737-8(737MAX8)型機の導入に合わせて翌年6月28日から羽田線の運航を開始することが発表された。使用機材の航続距離などの関係で、豪州側の発着空港はブリスベンからケアンズに変更となる。

### Response:
2000年8月31日です。
```

今度はinput情報を渡さないでも期待する回答が返ってきました。Wikipediaデータセットから質問と類似する情報を取得しinputとしてLLMに渡すことが出来ています。 試しにデータセットにない質問もしてみます。

```python:推論の実施 データセット以外の質問
def rakuten(s):
    generate(s)
    print()
    print()

while True:
    rakuten(input())

>>> 出力結果
"楽曲『約束はいらない』でデビューした、声優は誰?"
 Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.

### Instruction:
"楽曲『約束はいらない』でデビューした、声優は誰?"

### Input:
「約束はいらない」(やくそくはいらない)は、坂本真綾のデビューシングル。本格的な歌手活動は、1996年にテレビアニメ『天空のエスカフローネ』へ出演したことがきっかけで始めており、同作のオープニングテーマソングである「約束はいらない」(岩里祐穂作詞、菅野よう子作曲)をシングルとして発売し、同年4月にビクターエンタテインメントよりデビューした。以降、声優・ラジオパーソナリティ・女優業と並行しながら活動を続け、現在において、シングル24枚、オリジナルアルバム8枚、コンセプトアルバム3枚、コレクションアルバム3枚、ベストアルバム1枚、ライブDVD4枚を発表している。アニメのサウンドトラックにおける楽曲収録も多く、自身が声優として関わっていない作品の楽曲を担当することもある。作品および楽曲は、デビューから2003年まで菅野よう子がプロデュースを行っていたが、2005年以降は坂本自身が田村充義と共同で、2013年以降は坂本がセルフプロデュースを行っている。坂本真綾の歌手デビュー作品。当時坂本はまだ無名の声優であったが、同曲がテーマソングとなったアニメ『天空のエスカフローネ』とともに知名度を上げることとなる。後に「指輪」が同アニメの劇場版映画の主題歌となった。カップリング曲「ともだち」は後に1枚目のアルバム『グレープフルーツ』にて「MY BEST FRIEND」というタイトルで全英語詞で収録されている。累計出荷枚数は3万枚。

### Response:
坂本真綾。


日本で二番目に高い山は？
 Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.

### Instruction:
日本で二番目に高い山は？

### Input:
日本の棚田百選に剪定されている大垪和地区の北方に位置する。山名は最高地点の西峰と東峰の2つの頂上を有することに由来する。東峰の山腹には山岳密教寺院である両山寺や二上神社、天邪鬼の重ね岩がある。北岳は日本で2番目に高い山であることから、様々な「2番目を表す比喩表現」に使われている。 ネガティブな表現で用いられるケースが多く、スポーツ大会や芸術での選考において1位しか注目されないことを、富士山と北岳の知名度の違いで表現される。 また、富士山が特徴的な山体をしていることと、独立峰であるために各方向から発見しやすいことに対し、北岳は周辺を南アルプスの高峰に囲まれているため市街地から見えにくく、見えたとしても山頂の一部のみとなってしまう。また、山岳写真から北岳を判別することも容易ではなく、これらのことから富士山との知名度の差が大きい。二ッ岳(ふたつだけ)は、四国山地西部の石鎚山脈に属する山である。二ツ岳と表記される場合もあるが、国土地理院の地形図は「二ッ岳」と表記されている。四国百名山に選定されている。山名は二つの岩峰が相対して屹立していることによる。四国において「岳」と付く山は少ない。

### Response:
日本第二の高峰は山梨県にある北岳である。北岳は二上山とも呼ばれ、二上神社のある東峰と両山寺のあるところの西峰の二つの山頂を持つ。北東側に富士山が見える。南アルプス国立公園の南端に位置し、周辺は樹林に覆われた急斜面となっている。山梨百名山のひとつである。登山道は北沢峠からのルートと、御小屋尾根からのルートの2本がある。北沢峠からのルートは、八ヶ岳連峰の主脈を横断するコースでもある。標高3000m以上で富士山と並ぶような場所でもあり、その存在感は日本有数のものである。


日本の現在の総理大臣は？
 Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.

### Instruction:
日本の現在の総理大臣は？

### Input:
内閣総理大臣(ないかくそうりだいじん、英: Prime Minister)は、日本の内閣の首長たる国務大臣。文民である国会議員が就任し、その地位及び権限は日本国憲法や内閣法などに規定されている。現任は、第101代岸田文雄(在任: 2021年〈令和3年〉11月10日 - )。歴代の内閣総理大臣は内閣総理大臣の一覧を参照。内閣総理大臣顕彰(ないかくそうりだいじんけんしょう)は、日本の内閣総理大臣が授与する顕彰である。1966年(昭和41年)、当時の首相・佐藤栄作によって創設され、2021年4月時点でこれまでに34人、15団体に対して授与されている。総務大臣(そうむだいじん、英語: Minister for Internal Affairs and Communications)は、日本の総務省の長および主任の大臣たる国務大臣。

### Response:
安倍晋三
```
データセットが2023/04/03のWikipediaなので若干データが古いですが生成AIっぽい回答を返しています。正直どこまでファインチューニングの成果があったのか見定めることが出来ませんでしたが、LLMのファインチューニングとRAGの検証はここで一区切りつけることとします。

## 今後の学習課題とまとめ
今回、自然言語処理としてLLMのファインチューニングに取り組んで見て、この分野の技術が日進月歩で進化していることを感じとることが出来ました。　日本語用LLMも私がこのテーマに取り組み始めた時は、サイバーエージェント社が公開している **OpenCALM-7B** が最新でしたが学習中に楽天グループから **RakutenAI-7B** が発表され、NTTからも **tsuzumi** が商用サービスとしてリリースされています。

https://www.cyberagent.co.jp/news/detail/id=28817

https://www.rd.ntt/research/LLM_tsuzumi.html

また、量子化の技術も4ビットから、2ビットや1ビットへの検討が進んでいるようです。

https://note.com/bbz662bbz/n/nbe8a804f1cca

https://www.youtube.com/watch?app=desktop&v=-Scnb_5QsDI

今後はLLMの動向を引き続き注視しつつ、割愛してきた基本的な考え方や理論、およびその実装について、書籍やインターネットを使って学習と実践を繰り返し、自分の血となり肉となるレベルまで腹落ちさせていきたいと思います。

差し当たり、以下の書籍を購入し読み進めているところです。

https://gihyo.jp/book/2023/978-4-297-13633-8

また、**Aidemy** での学習修了レポートは本投稿で最後となります。**Aidemy**での残りの学習期間で、これまで学習してきたデータ分析、AI学習、自然言語学習の各講座について反復して受講する予定です。既に忘れてしまっている点や一回目では気付かなかった点等、間違いなくあると思いますが、復習の際は全体の枠組みを常にイメージしながらパーツをはめ込むように整理して学びなおしを行い、ノウハウの定着と理解の深堀りを図りたいと考えています。

新しい発見があれば、備忘を兼ねて適宜ブログで情報発信できればと思っています。
今後ともよろしくお願いいたします。