ChromeDBとBeluuuuuuga/Japanese-Instruction-Linux-Command-169を使って質問に対する適切なLinuxコマンドを探すRAGを構築する

初めに

珍しい?データセットがあったので、こちらを使ってLinuxで操作したいことがあるけどコマンドがわからない場合に雑に質問ができるRAGシステムを構築します

huggingface.co

実行した環境は以下でColobのNoteを公開しています

colab.research.google.com

デモ

開発環境

Chromeとは

Chromaはオープンソースかつローカルで使える埋め込みデータベースです。

docs.trychroma.com

RAGシステムの構築

ライブラリの準備

まずは以下でライブラリを入れます

%pip install -Uq chromadb numpy datasets

DBの構築とデータの追加

DBを作成します

# Import Chroma and instantiate a client. The default Chroma client is ephemeral, meaning it will not save to disk.
import chromadb

client = chromadb.Client()

# Create a new Chroma collection to store the supporting evidence. We don't need to specify an embedding fuction, and the default will be used.
collection = client.create_collection("Japanese-Instruction-Linux-Command")

huggingfaceからデータを取得して追加します

# Get the SciQ dataset from HuggingFace
from datasets import load_dataset

dataset = load_dataset("Beluuuuuuga/Japanese-Instruction-Linux-Command-169", split="train")


print("Number of questions with support: ", len(dataset))

# Embed and store the first 100 supports for this demo
i = 0
for data in dataset:
  collection.add(
      ids=[str(i)],  # IDs are just strings
      documents=data['instruction'],
      metadatas=[{"output": data['output']}],
  )
  i = i + 1

質問をして回答を取得

以下で質問に対して近い回答を取得します

results = collection.query(query_texts=dataset["instruction"][0], n_results=3)
print("質問:", dataset["instruction"][0])

# distanceのしきい値を設定
threshold = 0.05

# 検索結果をdistanceでフィルタリング
filtered_results = [result for result in zip(results['distances'][0], results['metadatas'][0]) if result[0] <= threshold]

if filtered_results:
    print("回答:")
    for distance, metadata in filtered_results:
        print(f"  distance: {distance:.4f}, output: {metadata['output']}")
else:
    print("適切な回答が見つかりませんでした。")

この場合の回答は以下になります

質問: Linuxで現在のディレクトリの内容を表示するコマンドを教えてください。
回答:
  distance: 0.0000, output: ls
  distance: 0.0360, output: pwd

ここで全く関係のないことを聞いてみます

q = "今日はいい天気ですね!"
results = collection.query(query_texts=q, n_results=3)
print("質問:", q)

# distanceのしきい値を設定
threshold = 0.05

# 検索結果をdistanceでフィルタリング
filtered_results = [result for result in zip(results['distances'][0], results['metadatas'][0]) if result[0] <= threshold]

if filtered_results:
    print("回答:")
    for distance, metadata in filtered_results:
        print(f"  distance: {distance:.4f}, output: {metadata['output']}")
else:
    print("適切な回答が見つかりませんでした。")

この場合は、見つからないので以下のようになります

質問: 今日はいい天気ですね!
適切な回答が見つかりませんでした。

マルチGPUででLLMの学習時をする際の「NCCL communicator and retrieving ncclUniqueId」のエラーの対応

開発環境

nvidia-smi

+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.54.14              Driver Version: 550.54.14      CUDA Version: 12.4     |
|-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA RTX A5000               Off |   00000000:1C:00.0 Off |                  Off |
| 54%   79C    P2            215W /  230W |   17200MiB /  24564MiB |     94%      Default |
|                                         |                        |                  N/A |

nvcc

nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2023 NVIDIA Corporation
Built on Mon_Apr__3_17:16:06_PDT_2023
Cuda compilation tools, release 12.1, V12.1.105
Build cuda_12.1.r12.1/compiler.32688072_0

詳細

マルチGPUで学習する際に、以下のエラーが出ていました。

RuntimeError: [6] is setting up NCCL communicator and retrieving ncclUniqueId from [0] via c10d key-value store by key '0', but store->get('0') got error: Socket Timeout

この際に、NCCL_IB_DISABLE=1環境変数で設定します

export NCCL_IB_DISABLE=1

reazon-research/reazonspeech(tiny)の音声データをNISQAで音声品質と自然さの評価のデータ分析をする

初めに

NISQAを使ってreazon-research/reazonspeechのデータを分析します

環境

  • L4 GPU
  • ubuntu22.04

分析処理の方向性

reazonspeechのデータフォーマット

huggingfaceから取得したreazonspeechのデータセットを使いためには、以下の対応をする必要があります。

まず前提として リポジトリのReadMeよりデータフォーマットは以下のようになっています

Audio files are available in FLAC format, sampled at 16000 hz. Each audio file is accompanied with a transcription.

{
    'name': '000/0000000000000.flac',
    'audio': {
        'path': '/path/to/000/0000000000000.flac',
        'array': array([ 0.01000000,  ...], dtype=float32),
        'sampling_rate': 16000
    },
    'transcription': '今日のニュースをお伝えします。'
}

NISQAの実行方法について

NISQAはReadMe及びコードを見るところ、以下の条件があります * wavファイルのみ対象 * CLIから処理をする方法が一般的(関数にwavファイルを渡すなどは想定していない?)

一つのファイルを処理をする場合、以下のように実行します

python run_predict.py --mode predict_file --pretrained_model weights/nisqa.tar --deg /path/to/wav/file.wav --output_dir /path/to/dir/with/results

またディレクトリ内のファイル全てに対しては以下のように実行します

python run_predict.py --mode predict_dir --pretrained_model weights/nisqa.tar --data_dir /path/to/folder/with/wavs --num_workers 0 --bs 10 --output_dir /path/to/dir/with/results

分析の方向性

上記の二つより以下の対応をする必要があります 1. flacファイルからwavファイルに変換 2. ディレクトリ内にwavファイルを入れて、CLI上からNISQAの処理をする

前処理

reazonspeechのデータをflacに変換

linux上で datasets から以下の処理でデータを取得した場合は、以下のような形でキャッシュされています。(少なくとも手元の環境では)

from datasets import load_dataset
ds = load_dataset("reazon-research/reazonspeech", "all", trust_remote_code=True)

キャッシュのパスは 以下の通りです

~/.cache/huggingface/datasets/reazon-research___reazonspeech/tiny/0.0.0/e16d1ee2aae813b6ea960f564f4dc8481f58bfa6074be491eb4a6ddde66330bb

キャッシュは以下のようになっています

reazonspeech-train.arrow

そのため、arrowファイルからflacに一度変換をします

詳細は以下の記事を確認してください

ayousanz.hatenadiary.jp

flacファイルをwavファイルに変換

flacファイルができたので、以下で全てwavファイルに変換をします

詳細は以下をご確認ください

ayousanz.hatenadiary.jp

NISQAでwavファイルを分析

wavファイルに変換が終わったら、以下でNISQAを使って処理を実行します

python run_predict.py --mode predict_dir --pretrained_model weights/nisqa.tar --data_dir convert_wav/ --num_workers 0 --bs 2 --output_dir .

変換処理が終わったら、NISQA_results.csv というファイルが生成されます。内容は以下のようになっています(一部のみを記載しています)

deg mos_pred noi_pred dis_pred col_pred loud_pred model
1 2.3836768 1.8683524 3.9964635 3.6280687 3.5253642 NISQAv2
2 1.5204918 1.5145079 3.7368195 2.7644107 2.5888093 NISQAv2
3 3.1642735 2.5920422 3.9088361 3.5170329 3.714206 NISQAv2
4 2.9011464 1.65048 4.631492 4.208716 4.005361 NISQAv2
5 3.2035732 2.8596961 3.8604171 3.57778 3.71185 NISQAv2

CSVから分析

以下のコードを使って、CSVからデータを取得してヒストグラムとして表示していきます

import pandas as pd
import matplotlib.pyplot as plt

# CSVファイルを読み込む
data = pd.read_csv('NISQA_results.csv')
bins = 80

# ヒストグラムのプロット
plt.figure(figsize=(10, 6))

plt.subplot(2, 3, 1)
plt.hist(data['mos_pred'], bins=bins, edgecolor='black')
plt.xlabel('MOS Prediction')
plt.ylabel('Frequency')
plt.title('Histogram of MOS Prediction')

plt.subplot(2, 3, 2)
plt.hist(data['noi_pred'], bins=bins, edgecolor='black')
plt.xlabel('Noise Prediction')
plt.ylabel('Frequency')
plt.title('Histogram of Noise Prediction')

plt.subplot(2, 3, 3)
plt.hist(data['dis_pred'], bins=bins, edgecolor='black')
plt.xlabel('Distortion Prediction')
plt.ylabel('Frequency')
plt.title('Histogram of Distortion Prediction')

plt.subplot(2, 3, 4)
plt.hist(data['col_pred'], bins=bins, edgecolor='black')
plt.xlabel('Coloration Prediction')
plt.ylabel('Frequency')
plt.title('Histogram of Coloration Prediction')

plt.subplot(2, 3, 5)
plt.hist(data['loud_pred'], bins=bins, edgecolor='black')
plt.xlabel('Loudness Prediction')
plt.ylabel('Frequency')
plt.title('Histogram of Loudness Prediction')

plt.tight_layout()
plt.show()

ffmpegを使ってflacファイルをwavファイルにGNU Parallelを使って並列処理で変換をする

初めに

開発環境

  • cuda:12.2.0
  • ubuntu22.04

詳細

以下のコードにて、指定したフォルダ内にあるflacファイルをwavファイルに変換します。このとき -j $(nproc) でCPUの最大コア数を指定しているのため、必要に応じて変更してください

#!/bin/bash

# 引数から音声ファイルがあるパスを取得
input_dir="$1"

# 変換後のwavファイルを保存するフォルダを作成
mkdir -p convert_wav

# 並列処理の関数を定義
convert_file() {
  file="$1"
  output_file="convert_wav/$(basename "${file%.flac}.wav")"
  echo "Converting file: $file"
  ffmpeg -i "$file" -acodec pcm_s16le -ar 44100 -ac 2 "$output_file"
  if [ $? -eq 0 ]; then
    echo "Conversion successful: $output_file"
  else
    echo "Conversion failed: $file"
  fi
}

# GNU Parallelを使用して並列処理を実行
export -f convert_file
find "$input_dir" -maxdepth 1 -name "*.flac" | parallel -j $(nproc) convert_file {}

echo "Conversion complete."

上記は以下のように実行します

./convert.sh output_flac/

reazon-research/reazonspeech(tiny)のデータセットをflac及びwavファイルで個別に保存する

環境

  • L4 GPU
  • ubuntu22.04

準備

実行

pythonflacに変換

from datasets import load_dataset
import os

# データセットをロード
ds = load_dataset("reazon-research/reazonspeech", "tiny")

# 出力ディレクトリを作成
output_dir = "output_flac"
os.makedirs(output_dir, exist_ok=True)

# データセットの各サンプルを処理
for sample in ds["train"]:
    # 音声データのローカルパスを取得
    audio_path = sample["audio"]["path"]

    # 出力ファイルのパスを作成
    output_path = os.path.join(output_dir, os.path.basename(audio_path))

    # 音声データをコピー
    with open(audio_path, "rb") as src_file, open(output_path, "wb") as dst_file:
        dst_file.write(src_file.read())

print("変換が完了しました。")

pythonでwavに変換

from datasets import load_dataset
import os
import librosa
import soundfile as sf

# データセットをロード
ds = load_dataset("reazon-research/reazonspeech", "tiny")

# 出力ディレクトリを作成
output_dir = "output_wav"
os.makedirs(output_dir, exist_ok=True)

# データセットの各サンプルを処理
for sample in ds["train"]:
    # 音声データのローカルパスを取得
    audio_path = sample["audio"]["path"]
    
    # 音声データを読み込み
    audio, sr = librosa.load(audio_path, sr=None)
    
    # 出力ファイルのパスを作成
    output_path = os.path.join(output_dir, os.path.splitext(os.path.basename(audio_path))[0] + ".wav")
    
    # 音声データを保存
    sf.write(output_path, audio, sr, subtype='PCM_24')

print("変換が完了しました。")

変換したwavファイルの情報を確認

以下で変換したwavファイルをいくつか取得して情報を確認します

import os
import soundfile as sf

output_dir = "output_wav"

# 変換後のwavファイルを3つ選択
wav_files = [file for file in os.listdir(output_dir) if file.endswith(".wav")][:3]

for wav_file in wav_files:
    file_path = os.path.join(output_dir, wav_file)
    
    # wavファイルの情報を取得
    audio_info = sf.info(file_path)
    
    print(f"ファイル名: {wav_file}")
    print(f"サンプリングレート: {audio_info.samplerate} Hz")
    print(f"チャンネル数: {audio_info.channels}")
    print(f"ビット深度: {audio_info.subtype}")
    print(f"長さ: {audio_info.duration:.2f} 秒")
    print("------------------------")

以下のように表示されます

ファイル名: 6c52ba0a0ba57.wav
サンプリングレート: 16000 Hz
チャンネル数: 1
ビット深度: PCM_24
長さ: 5.63 秒
------------------------
ファイル名: ff76142fa5e77.wav
サンプリングレート: 16000 Hz
チャンネル数: 1
ビット深度: PCM_24
長さ: 1.62 秒
------------------------
ファイル名: f879dcb872a87.wav
サンプリングレート: 16000 Hz
チャンネル数: 1
ビット深度: PCM_24
長さ: 23.92 秒
------------------------

ffmpegflacからwavに変換

まずはGPUの並列処理をするために、以下のライブラリを入れます

sudo apt-get install parallel

以下が並列で処理をするためのコードです. -j $(nproc) にてCPUのシステムコア数の最大値を指定しているため、必要に応じて変更してください

#!/bin/bash

# 引数から音声ファイルがあるパスを取得
input_dir="$1"

# 変換後のwavファイルを保存するフォルダを作成
mkdir -p convert_wav

# 並列処理の関数を定義
convert_file() {
  file="$1"
  output_file="convert_wav/$(basename "${file%.flac}.wav")"
  echo "Converting file: $file"
  ffmpeg -i "$file" -acodec pcm_s16le -ar 44100 -ac 2 "$output_file"
  if [ $? -eq 0 ]; then
    echo "Conversion successful: $output_file"
  else
    echo "Conversion failed: $file"
  fi
}

# GNU Parallelを使用して並列処理を実行
export -f convert_file
find "$input_dir" -maxdepth 1 -name "*.flac" | parallel -j $(nproc) convert_file {}

echo "Conversion complete."

reazon-research/reazonspeech(tiny)の音声データをSpeechMOSで音声の自然さのデータ分析をする

初めに

前回は、WADR-SNRで分析をしました。今回は SpeechMOSを使って音声の品質を分析していきます

ayousanz.hatenadiary.jp

環境

準備

必要なライブラリを入れていきます

!pip install datasets
!pip install datasets librosa IPython
!pip install numpy scipy datasets
!pip install soundfile
!pip install librosa

データをダウンロードします

from datasets import load_dataset

# データセットをロード
ds = load_dataset("reazon-research/reazonspeech", "tiny")

SpeechMOSによるデータ分析

以下でSpeechMOSの値を計算して、jsonに保存していきます

import torch
import librosa
import json
import numpy as np
from datasets import load_dataset

# speech-mosの予測器を初期化
predictor = torch.hub.load("tarepan/SpeechMOS:v1.2.0", "utmos22_strong", trust_repo=True)

# データを前処理するための関数
def preprocess_audio(data):
    # データが整数型の場合、浮動小数点型に変換
    if data.dtype == np.int16:
        data = data.astype(np.float32) / np.iinfo(np.int16).max
    elif data.dtype == np.int32:
        data = data.astype(np.float32) / np.iinfo(np.int32).max

    # ステレオをモノラルに変換(必要があれば)
    if len(data.shape) == 2:
        data = data.mean(axis=1)

    return data

def process_audio_data(data):
    # 音声データを読み込む
    audio_data = data['audio']['array']
    sr = data['audio']['sampling_rate']

    # データを前処理
    audio_data = preprocess_audio(audio_data)

    # speech-mosを使用して数値を取得
    audio_data_tensor = torch.from_numpy(audio_data).unsqueeze(0).to(torch.float32)  # float32に変換
    score = predictor(audio_data_tensor.to(torch.float32), sr)  # 入力データをfloat32に変換

    # 結果を辞書に格納
    result = {
        "ファイル名": data['name'],
        "MOS値": float(score),
        "トランスクリプション": data['transcription']
    }

    # 不要な変数を削除してメモリを解放
    del audio_data, audio_data_tensor

    return result

def process_and_save_results(ds):
    for data in ds['train']:
        result = process_audio_data(data)
        yield result

# 結果を保存するジェネレータ関数
def save_results_to_json(ds):
    with open('audio_analysis_results.json', 'w') as f:
        f.write('[\n')
        for i, result in enumerate(process_and_save_results(ds)):
            print("ファイル名:" + result["ファイル名"] + ", MOS値:" + str(result["MOS値"]))
            print("トランスクリプション: ", result["トランスクリプション"])
            json.dump(result, f, ensure_ascii=False, indent=4)
            if i < len(ds['train']) - 1:
                f.write(',\n')
        f.write('\n]')

    print("JSONファイルが保存されました。")

# 結果を保存
save_results_to_json(ds)

またjsonからヒストグラムのグラフを表示します

import json
import matplotlib.pyplot as plt

# JSONファイルからデータをロード
file_path = 'audio_analysis_results.json'
with open(file_path, 'r') as file:
    data = json.load(file)


# SNR値のリストを抽出
snr_values = [item['MOS値'] for item in data]
# ヒストグラムを描画
plt.hist(snr_values, bins=200, edgecolor='black')  # binsは適宜調整してください
plt.xlabel('SNR value')
plt.ylabel('number of occurances')
plt.title('Histogram of Speech MOS values')
plt.grid(True)
plt.show()

ヒストグラムで表示した際は以下のようになります

1区切りで見たさいに当てはまるデータ数は以下のように計算します

# SNR値が100以上のデータの数をカウント
count_snr_above_1 = sum(1 for item in data if item['MOS値'] >= 1)
count_snr_above_2 = sum(1 for item in data if item['MOS値'] >= 2)
count_snr_above_3 = sum(1 for item in data if item['MOS値'] >= 3)

print(f"SNR値が1以上のデータの数: {count_snr_above_1}")
print(f"SNR値が2以上のデータの数: {count_snr_above_2}")
print(f"SNR値が3以上のデータの数: {count_snr_above_3}")

実際の数値は以下のようになります

SNR値が1以上のデータの数: 5323
SNR値が2以上のデータの数: 1058
SNR値が3以上のデータの数: 224

GaLoreを使って0.01Bモデル(EN)を作ってみる(モデルが保存できない)

初めに

LoRAよりもメモリ効率がよく学習ができる手法であるGaLoreで試してみます

論文のabstractの日本語訳は以下です(claude 3 opus を使用)

大規模言語モデル(LLM)の学習では、重みと最適化器の状態のサイズが増大するため、メモリに関する大きな課題があります。低ランク適応(LoRA)などの一般的なメモリ削減手法では、各層の凍結された事前学習済みの重みに、学習可能な低ランク行列を追加することで、学習可能なパラメータと最適化器の状態を削減します。しかし、このようなアプローチは、パラメータ探索を低ランク部分空間に制限し、学習のダイナミクスを変更するため、事前学習と微調整の両段階で、通常、フルランクの重みを用いた学習よりも性能が低下します。さらに、フルランクのウォームスタートが必要になる場合もあります。本研究では、フルパラメータ学習を可能にしながら、LoRAなどの一般的な低ランク適応手法よりもメモリ効率の良い学習戦略である、勾配低ランク射影(GaLore)を提案します。我々のアプローチは、最適化器の状態のメモリ使用量を最大で65.5%削減しながら、LLaMA 1Bと7Bのアーキテクチャを用いて最大19.7Bトークンを含むC4データセットで事前学習し、GLUEタスクでRoBERTaを微調整する際の効率とパフォーマンスを維持します。我々の8ビットGaLoreは、最適化器のメモリをさらに最大82.5%、BF16ベースラインと比較して学習メモリ全体を63.3%削減します。特筆すべきは、モデル並列化、チェックポイント、オフロード戦略を使用せずに、24GBメモリ(NVIDIA RTX 4090など)を搭載した民生用GPUで7Bモデルを事前学習できることを初めて実証したことです。

arxiv.org

環境環境

  • L4 GPU
  • ubuntu22.04

準備

まずはcloneします

git clone https://github.com/jiaweizzhao/GaLore

環境を作成します

python -m venv venv
source venv/bin/activate

ライブラリを入れます

pip install -e .

学習

まずは、パラメータを設定するために GaLore/configs/llama_10m.json に0.01B用のconfig.jsonを作成します。以下のパラメータは 0.02B及び0.04Bを参考にして0.01B用に書き換えています

{
    "architectures": [
        "LLaMAForCausalLM"
    ],
    "bos_token_id": 0,
    "eos_token_id": 1,
    "hidden_act": "silu",
    "hidden_size": 128,
    "intermediate_size": 344,
    "initializer_range": 0.02,
    "max_sequence_length": 1024,
    "model_type": "llama",
    "num_attention_heads": 2,
    "num_hidden_layers": 2,
    "pad_token_id": -1,
    "rms_norm_eps": 1e-06,
    "transformers_version": "4.28.1",
    "use_cache": true,
    "vocab_size": 32000
}

以下で学習をします。この時になるべく24GBのVRAMを使えるようにbatchサイズを調節しています(使用は13GBです)

torchrun --standalone --nproc_per_node 1 torchrun_main.py \
    --model_config configs/llama_10m.json \
    --lr 0.005 \
    --galore_scale 0.25 \
    --rank 1024 \
    --update_proj_gap 500 \
    --batch_size 256 \
    --total_batch_size 4096 \
    --activation_checkpointing \
    --num_training_steps 10000 \
    --warmup_steps 15000 \
    --weight_decay 0 \
    --grad_clipping 1.0 \
    --dtype bfloat16 \
    --eval_every 1000 \
    --single_gpu \
    --optimizer galore_adamw8bit_per_laye