使用 Unsloth 與 QLoRA 針對音訊任務微調 Gemma¶
本指南將引導您使用 Unsloth 在自訂音訊資料集上微調 Gemma,以執行音訊任務(產生熊本方言日語的轉錄)。
概觀與目標¶
- 基底模型:Gemma 4 PT 多模態基礎模型
- 資料集:Hugging Face 上的
federerjiang/dialect.kumamoto(1,300 段熊本方言日語的語音錄音) - 方法:透過 Unsloth 與 PEFT 進行 4-bit QLoRA,大幅降低記憶體需求,同時微調語言注意力層與 MLP 投影層
- 目標指標:文字相似度 > 80.0%
1. 環境設定與相依套件安裝¶
您將安裝:
unsloth:用於加速的 4-bit QLoRA 與記憶體最佳化- 來自 Hugging Face 的
transformers、trl、peft、accelerate soundfile、librosa、jiwer:用於音訊處理與字元錯誤率(CER)評估
# Install Unsloth and required dependencies
!pip install unsloth[colab-new] unsloth_zoo
!pip install -U torch torchvision torchaudio transformers trl peft accelerate datasets bitsandbytes soundfile librosa jiwer
2.(選用)Hugging Face 驗證¶
您需要有效的 Hugging Face Token 才能發布模型。如果您在 Google Colab 中執行,可以使用 Colab 密鑰安全地使用 Hugging Face Token;否則可以直接在 login 方法中設定 Token。請確保您的 Token 具有寫入權限,因為訓練期間會將模型推送到 Hub。
3. 硬體與 GPU 精度驗證¶
import torch
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Device: {device}")
if device == "cuda":
gpu_name = torch.cuda.get_device_name(0)
vram_gb = torch.cuda.get_device_properties(0).total_memory / (1024**3)
supports_bf16 = torch.cuda.is_bf16_supported()
print(f"GPU: {gpu_name} ({vram_gb:.2f} GB VRAM)")
print(f"Bfloat16 Supported: {supports_bf16}")
dtype = torch.bfloat16 if supports_bf16 else torch.float16
else:
raise RuntimeError("GPU acceleration is required. In Colab: Runtime -> Change runtime type -> T4 GPU / A100.")
4. 資料集載入與準備 (federerjiang/dialect.kumamoto)¶
您將直接從 Hugging Face 下載 federerjiang/dialect.kumamoto 資料集。
資料集包含:
audio:音訊波形與取樣率(16 kHz)sentence:熊本方言(Kumamoto-ben)的標準轉錄文字
您將把 1,300 個樣本分為:
- 訓練集:1,170 個樣本(90%)
- 測試集:130 個樣本(10%),使用固定隨機種子(
seed=42)
import os
import soundfile as sf
import numpy as np
from datasets import load_dataset
from tqdm import tqdm
print("Downloading dataset from Hugging Face...")
raw_dataset = load_dataset("federerjiang/dialect.kumamoto", split="train")
print(f"Loaded {len(raw_dataset)} total samples.")
# Create local audio storage directory
audio_dir = "./audios"
os.makedirs(audio_dir, exist_ok=True)
# Split into 90% train (1170) and 10% test (130) with fixed seed
split_ds = raw_dataset.train_test_split(test_size=0.1, seed=42)
train_raw = split_ds["train"]
test_raw = split_ds["test"]
print(f"Train samples: {len(train_raw)}, Test samples: {len(test_raw)}")
def process_and_cache(raw_split, split_name):
items = []
instruction_text = "音声を聞いて、文字起こしをしてください。"
for idx, sample in enumerate(tqdm(raw_split, desc=f"Processing {split_name}")):
sample_id = f"kumamoto_{split_name}_{idx:04d}"
wav_path = os.path.join(audio_dir, f"{sample_id}.wav")
# Extract audio array and sampling rate
audio_info = sample["audio"]
audio_array = np.array(audio_info["array"], dtype=np.float32)
sr = audio_info["sampling_rate"]
# Resample to 16kHz if not already
if sr != 16000:
import librosa
audio_array = librosa.resample(audio_array, orig_sr=sr, target_sr=16000)
sr = 16000
# Save audio file
sf.write(wav_path, audio_array, sr)
text = sample["sentence"].strip()
# Format into standard multi-turn conversation
messages = [
{
"role": "user",
"content": [
{"type": "audio", "url": wav_path},
{"type": "text", "text": instruction_text}
]
},
{
"role": "assistant",
"content": [
{"type": "text", "text": text}
]
}
]
items.append({
"id": sample_id,
"audio_path": wav_path,
"sentence": text,
"messages": messages
})
return items
train_items = process_and_cache(train_raw, "train")
test_items = process_and_cache(test_raw, "test")
import json
os.makedirs("./data", exist_ok=True)
with open("./data/train.json", "w", encoding="utf-8") as f:
json.dump(train_items, f, ensure_ascii=False, indent=2)
with open("./data/test.json", "w", encoding="utf-8") as f:
json.dump(test_items, f, ensure_ascii=False, indent=2)
print("Dataset prepared and saved to ./data/train.json and ./data/test.json!")
Downloading dataset from Hugging Face...
Loaded 1300 total samples.
Train samples: 1170, Test samples: 130
Processing train: 100%|██████████| 1170/1170 [00:23<00:00, 49.88it/s]
Processing test: 100%|██████████| 130/130 [00:02<00:00, 53.72it/s]
Dataset prepared and saved to ./data/train.json and ./data/test.json!
5. 使用 4-bit QLoRA 初始化模型¶
以下程式碼使用 Unsloth 以 4-bit 正規浮點數(NF4)量化載入 Gemma 模型與處理器。
主要調整:
- 音訊遮罩散佈修補:修補 PyTorch 的
masked_scatter,以在量化權重旁嵌入音訊表示時處理張量 dtype 不一致的問題。 - LoRA 轉接器設定:
- 秩(\(r\)):32
- Alpha(\(\alpha\)):64(\(2 \times r\))
- 目標模組:語言模型的注意力層(
q_proj、k_proj、v_proj、o_proj)與前饋 MLP 層(gate_proj、up_proj、down_proj)。 - 可訓練參數:約 48.3M(僅佔基底模型權重的 約 0.94%)。
# Patch torch masked_scatter for multimodal audio tensor compatibility in QLoRA
_original_masked_scatter = torch.Tensor.masked_scatter
def _patched_masked_scatter(self, mask, source):
if self.dtype != source.dtype:
source = source.to(self.dtype)
return _original_masked_scatter(self, mask, source)
torch.Tensor.masked_scatter = _patched_masked_scatter
import unsloth
from unsloth import FastModel
from unsloth.chat_templates import get_chat_template
model_name = "google/gemma-4-E2B"
max_seq_length = 2048
print(f"Loading {model_name} in 4-bit NF4 (QLoRA)...")
model, processor = FastModel.from_pretrained(
model_name=model_name,
max_seq_length=max_seq_length,
dtype=dtype,
load_in_4bit=True,
full_finetuning=False,
use_gradient_checkpointing="unsloth",
)
processor = get_chat_template(processor, chat_template="gemma-4")
print("Attaching LoRA adapters...")
model = FastModel.get_peft_model(
model,
finetune_vision_layers=False,
finetune_language_layers=True,
finetune_attention_modules=True,
finetune_mlp_modules=True,
r=32,
lora_alpha=64,
lora_dropout=0,
bias="none",
)
model.print_trainable_parameters()
🦥 Unsloth: Will patch your computer to enable 2x faster free finetuning.
🦥 Unsloth Zoo will now patch everything to make training faster!
Loading google/gemma-4-E2B in 4-bit NF4 (QLoRA)...
==((====))== Unsloth 2026.9.4: Fast Gemma4 patching. Transformers: 5.17.0.
\\ /| NVIDIA L4. Num GPUs = 1. Max memory: 22.034 GB. Platform: Linux.
O^O/ \_/ \ Torch: 2.14.0+cu130. CUDA: 8.9. CUDA Toolkit: 13.0. Triton: 3.8.0
\ / Bfloat16 = TRUE. FA [Xformers = None. FA2 = False]
"-____-" Free license: http://github.com/unslothai/unsloth
Unsloth: Fast downloading is enabled - ignore downloading bars which are red colored!
Loading weights: 0%| | 0/1951 [00:00<?, ?it/s]
Attaching LoRA adapters...
trainable params: 48,316,416 || all params: 5,152,613,920 || trainable%: 0.9377
6. 音訊轉錄 Collator 與損失遮罩¶
為確保模型只學習轉錄音訊,而非背誦提示詞元:
- 以下程式碼會在每個序列中解析
<|turn>model\n。 - 在
<|turn>model\n之前的所有詞元(包括音訊前綴標記與使用者指令)都會被遮罩為-100。 - 只有助手回應的詞元會計入交叉熵損失。
def get_collate_fn(processor):
def collate_fn(examples):
texts = []
audios = []
for example in examples:
messages = example["messages"]
full_text = processor.apply_chat_template(messages, add_generation_prompt=False, tokenize=False)
texts.append(full_text.strip())
audio_path = example.get("audio_path")
if not audio_path:
for m in messages:
for b in m.get("content", []):
if isinstance(b, dict) and b.get("type") == "audio":
audio_path = b.get("audio") or b.get("url")
audio_arr, sr = sf.read(audio_path, dtype="float32")
if audio_arr.ndim > 1:
audio_arr = audio_arr.mean(axis=1)
audios.append(audio_arr)
batch = processor(text=texts, audio=audios, return_tensors="pt", padding=True)
labels = batch["input_ids"].clone()
target_phrase = "<|turn>model\n"
target_tokens = processor.tokenizer.encode(target_phrase, add_special_tokens=False)
target_len = len(target_tokens)
for i in range(labels.size(0)):
row_tokens = batch["input_ids"][i].tolist()
assistant_start_idx = None
for idx in range(len(row_tokens) - target_len + 1):
if row_tokens[idx : idx + target_len] == target_tokens:
assistant_start_idx = idx + target_len
break
if assistant_start_idx is not None:
labels[i, :assistant_start_idx] = -100
else:
labels[i, labels[i] == processor.tokenizer.pad_token_id] = -100
labels[labels == processor.tokenizer.pad_token_id] = -100
batch["labels"] = labels
return batch
return collate_fn
collate_fn = get_collate_fn(processor)
print("Collate function defined successfully!")
7. 使用 TRL SFTTrainer 微調¶
訓練超參數¶
| 參數 | 值 |
|---|---|
| 學習率 | 2.0e-4,搭配餘弦衰減 |
| 暖機比例 | 總步數的 3%(約 18 步) |
| 訓練輪數 | 4 輪(588 個優化器步數) |
| 批次大小 | 每裝置 4 \(\times\) 2 梯度累積 = 有效批次 8 |
| 精度 | bfloat16(或退回 float16) |
| 檢查點策略 | 每輪儲存,並依 eval_loss 使用 load_best_model_at_end=True |
from trl import SFTTrainer, SFTConfig
from datasets import load_dataset as hf_load_dataset
train_dataset = hf_load_dataset("json", data_files="./data/train.json", split="train")
test_dataset = hf_load_dataset("json", data_files="./data/test.json", split="train")
output_dir = "./checkpoints"
os.makedirs(output_dir, exist_ok=True)
batch_size = 4
grad_accum = 2
epochs = 4
learning_rate = 2e-4
total_steps = (len(train_dataset) // (batch_size * grad_accum)) * epochs
warmup_steps = max(1, int(total_steps * 0.03))
sft_config = SFTConfig(
output_dir=output_dir,
per_device_train_batch_size=batch_size,
per_device_eval_batch_size=batch_size,
gradient_accumulation_steps=grad_accum,
num_train_epochs=epochs,
learning_rate=learning_rate,
lr_scheduler_type="cosine",
warmup_steps=warmup_steps,
bf16=supports_bf16,
fp16=not supports_bf16,
eval_strategy="epoch",
save_strategy="epoch",
save_total_limit=2,
load_best_model_at_end=True,
metric_for_best_model="eval_loss",
greater_is_better=False,
remove_unused_columns=False,
dataset_kwargs={"skip_prepare_dataset": True},
max_length=max_seq_length,
logging_steps=10,
report_to="none",
)
trainer = SFTTrainer(
model=model,
processing_class=processor,
train_dataset=train_dataset,
eval_dataset=test_dataset,
data_collator=collate_fn,
args=sft_config,
)
print(f"Starting QLoRA training ({total_steps} steps across {epochs} epochs)...")
train_result = trainer.train()
print(f"Training completed! Stats: {train_result}")
# Save final best model and processor
final_model_path = os.path.join(output_dir, "final_model")
trainer.save_model(final_model_path)
processor.save_pretrained(final_model_path)
print(f"Fine-tuned QLoRA adapter saved to {final_model_path}!")
Generating train split: 0 examples [00:00, ? examples/s]
Generating train split: 0 examples [00:00, ? examples/s]
The tokenizer has new PAD/BOS/EOS tokens that differ from the model config and generation config. The model config and generation config were aligned accordingly, being updated with the tokenizer's values. Updated tokens: {'bos_token_id': 2}.
Starting QLoRA training (584 steps across 4 epochs)...
==((====))== Unsloth - 2x faster free finetuning | Num GPUs used = 1
\\ /| Num examples = 1,170 | Num Epochs = 4 | Total steps = 588
O^O/ \_/ \ Batch size per device = 4 | Gradient accumulation steps = 2
\ / Data Parallel GPUs = 1 | Total batch size (4 x 2 x 1) = 8
"-____-" Trainable parameters = 48,316,416 of 5,152,613,920 (0.94% trained)
Unsloth: Not an error, but Gemma4ForConditionalGeneration does not accept `num_items_in_batch`.
Using gradient accumulation will be very slightly less accurate.
Read more on gradient accumulation issues here: https://unsloth.ai/blog/gradient
[588/588 18:18, Epoch 4/4]
| Epoch | Training Loss | Validation Loss |
|---|---|---|
| 1 | 0.635206 | 0.630127 |
| 2 | 0.279609 | 0.644313 |
| 3 | 0.069598 | 0.765895 |
| 4 | 0.014357 | 0.905066 |
Unsloth: Restored added_tokens_decoder metadata in ./checkpoints/checkpoint-147/tokenizer_config.json.
Unsloth: Restored added_tokens_decoder metadata in ./checkpoints/checkpoint-294/tokenizer_config.json.
Unsloth: Restored added_tokens_decoder metadata in ./checkpoints/checkpoint-441/tokenizer_config.json.
Unsloth: Restored added_tokens_decoder metadata in ./checkpoints/checkpoint-588/tokenizer_config.json.
Training completed! Stats: TrainOutput(global_step=588, training_loss=0.3260592963217067, metrics={'train_runtime': 1196.9343, 'train_samples_per_second': 3.91, 'train_steps_per_second': 0.491, 'total_flos': 1.816203342288845e+16, 'train_loss': 0.3260592963217067, 'epoch': 4.0})
Unsloth: Restored added_tokens_decoder metadata in ./checkpoints/final_model/tokenizer_config.json.
Fine-tuned QLoRA adapter saved to ./checkpoints/final_model!
8. 在未見過的測試集(130 個樣本)上評估¶
您將使用貪心解碼,在全部 130 段未見過的測試音訊錄音 上評估微調後的模型。
計算的指標:
- SequenceMatcher 相似度(
difflib.SequenceMatcher):字元子序列對齊比例。 - Levenshtein 相似度:正規化編輯距離比例(\(1 - \frac{\text{dist}}{\max(L_1, L_2)}\))。
- 字元正確率:\(1 - \text{CER}\),使用
jiwer.cer。 - 完全比對率:字元完全相符的百分比。
評估目標:達到 > 80.0% 的文字相似度。
import difflib
import jiwer
from tqdm import tqdm
def compute_levenshtein(s1, s2):
if len(s1) < len(s2):
return compute_levenshtein(s2, s1)
if len(s2) == 0:
return len(s1)
prev = range(len(s2) + 1)
for i, c1 in enumerate(s1):
curr = [i + 1]
for j, c2 in enumerate(s2):
ins = prev[j + 1] + 1
dels = curr[j] + 1
subs = prev[j] + (c1 != c2)
curr.append(min(ins, dels, subs))
prev = curr
return prev[-1]
# Load fine-tuned adapter for inference
print(f"Loading fine-tuned model for inference from {final_model_path}...")
eval_model, eval_processor = FastModel.from_pretrained(
model_name=final_model_path,
max_seq_length=max_seq_length,
dtype=dtype,
load_in_4bit=True,
)
eval_processor = get_chat_template(eval_processor, chat_template="gemma-4")
FastModel.for_inference(eval_model)
eval_device = next(eval_model.parameters()).device
similarities = []
lev_sims = []
char_accs = []
cers = []
exact_matches = 0
results = []
print(f"Evaluating {len(test_items)} test samples...")
for item in tqdm(test_items, desc="Evaluating"):
ref = item["sentence"].strip()
audio_path = item["audio_path"]
prompt_text = "音声を聞いて、文字起こしをしてください。"
audio_arr, sr = sf.read(audio_path, dtype="float32")
if audio_arr.ndim > 1:
audio_arr = audio_arr.mean(axis=1)
messages = [
{
"role": "user",
"content": [
{"type": "audio", "url": audio_path},
{"type": "text", "text": prompt_text}
]
}
]
prompt = eval_processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
inputs = eval_processor(text=[prompt], audio=[audio_arr], return_tensors="pt", padding=True)
inputs = {k: v.to(eval_device) if isinstance(v, torch.Tensor) else v for k, v in inputs.items()}
with torch.no_grad():
output_ids = eval_model.generate(
**inputs,
max_new_tokens=80,
do_sample=False,
pad_token_id=eval_processor.tokenizer.pad_token_id,
eos_token_id=[1, 106],
)
input_len = inputs["input_ids"].shape[1]
pred = eval_processor.tokenizer.decode(output_ids[0][input_len:], skip_special_tokens=True).strip()
pred = pred.split("<turn|>")[0].split("\n")[0].strip()
# Metrics
sim = difflib.SequenceMatcher(None, ref, pred).ratio() * 100.0
max_l = max(len(ref), len(pred))
dist = compute_levenshtein(ref, pred)
lev_sim = ((max_l - dist) / max_l * 100.0) if max_l > 0 else 100.0
try:
cer = jiwer.cer(ref, pred)
except Exception:
cer = 1.0
char_acc = max(0.0, 1.0 - cer) * 100.0
is_exact = (ref == pred)
if is_exact:
exact_matches += 1
similarities.append(sim)
lev_sims.append(lev_sim)
char_accs.append(char_acc)
cers.append(cer * 100.0)
results.append({
"id": item["id"],
"reference": ref,
"prediction": pred,
"similarity": round(sim, 2),
"exact_match": is_exact
})
mean_sim = sum(similarities) / len(similarities)
mean_lev = sum(lev_sims) / len(lev_sims)
mean_acc = sum(char_accs) / len(char_accs)
mean_cer = sum(cers) / len(cers)
exact_rate = (exact_matches / len(test_items)) * 100.0
print("\n" + "="*50)
print("QLoRA ASR EVALUATION SUMMARY")
print("="*50)
print(f"Total Test Samples: {len(test_items)}")
print(f"Mean Text Similarity: {mean_sim:.2f}%")
print(f"Mean Levenshtein Sim: {mean_lev:.2f}%")
print(f"Mean Character Accuracy: {mean_acc:.2f}%")
print(f"Mean CER: {mean_cer:.2f}%")
print(f"Exact Match Rate: {exact_rate:.2f}% ({exact_matches}/{len(test_items)})")
print(f"Target (> 80%) Achieved: {'YES 🎉' if mean_sim > 80.0 else 'NO'}")
print("="*50 + "\n")
Loading fine-tuned model for inference from ./checkpoints/final_model...
==((====))== Unsloth 2026.9.4: Fast Gemma4 patching. Transformers: 5.17.0.
\\ /| NVIDIA L4. Num GPUs = 1. Max memory: 22.034 GB. Platform: Linux.
O^O/ \_/ \ Torch: 2.14.0+cu130. CUDA: 8.9. CUDA Toolkit: 13.0. Triton: 3.8.0
\ / Bfloat16 = TRUE. FA [Xformers = None. FA2 = False]
"-____-" Free license: http://github.com/unslothai/unsloth
Unsloth: Fast downloading is enabled - ignore downloading bars which are red colored!
Loading weights: 0%| | 0/1951 [00:00<?, ?it/s]
Evaluating 130 test samples...
Evaluating: 100%|██████████| 130/130 [08:33<00:00, 3.95s/it]
==================================================
QLoRA ASR EVALUATION SUMMARY
==================================================
Total Test Samples: 130
Mean Text Similarity: 84.75%
Mean Levenshtein Sim: 81.83%
Mean Character Accuracy: 80.89%
Mean CER: 19.11%
Exact Match Rate: 9.23% (12/130)
Target (> 80%) Achieved: YES 🎉
==================================================
9. 互動式檢視與方言音訊示範¶
讓我們對照標準熊本方言語句檢視預測範例,並直接在 Colab 播放一段音訊範例。
from IPython.display import Audio, display
import pandas as pd
# Display top predictions in a clean table
df_results = pd.DataFrame(results[:15])
display(df_results[["id", "reference", "prediction", "similarity", "exact_match"]])
# Play an example audio
sample_to_play = test_items[0]
print(f"Playing sample {sample_to_play['id']}:")
print(f"Ground Truth: {sample_to_play['sentence']}")
matched_pred = next(r['prediction'] for r in results if r['id'] == sample_to_play['id'])
print(f"QLoRA Pred : {matched_pred}")
display(Audio(sample_to_play["audio_path"]))
| | id | reference | prediction | similarity | exact_match |
|--|--|--|--|--|--|
| 0 | kumamoto_test_0000 | カピバラに触らるっ場所もあっばい。親子で歩きよる姿に癒さるっけん。 | カピバラに触らるっ場所もあっばい。親子で歩きよる姿に癒さるっけん。 | 100.00 | True |
| 1 | kumamoto_test_0001 | 舗装もぴしゃっとしとらすけんあんま揺れんし,風ば受けながり土手ん上ば走るとは気持ちんよか(爽... | ホソーもぴしゃっとしとらすけんあんまよれんし、風邪ば受けながらどっでん上ば走るとは気持ちんよ... | 77.23 | False |
| 2 | kumamoto_test_0002 | だけん,祇園界隈な観光客でわんさかしとって,人気の店ん前には長蛇ん列のでくる. | だけん、気取らない観光客でわん探しとって、人気の店ん前にはちょうだん列のでくる。 | 70.89 | False |
| 3 | kumamoto_test_0003 | 飛行機の着陸する寸前に窓から下ば見っと、格子状ん畑の広がっとって帰ってきたなっちゅう感じのすっ。 | 飛行機の着陸する瞬間に窓から下ばみっと、合志城ん畑の広がっとって、帰ってきたなっちゅう感じのすっ。 | 86.60 | False |
| 4 | kumamoto_test_0004 | 次,料理ん本番ばい. | 次,両親ん本番ばい. | 80.00 | False |
| 5 | kumamoto_test_0005 | 透き通っとる白湯スープのラーメンで,たいぎゃあっさりとした味わいばい. | 好きとっとるパイタンスープのラーメンで、たいぎゃあっさりとした味ばい。 | 77.14 | False |
| 6 | kumamoto_test_0006 | 東京まぢ戻ってきたばってん,11時オープンてだけん,時間のちと早かごたる. | 東京マジ戻ってきたばってん、11時オープンてだけん、時間の落ちと早かごたる。 | 85.33 | False |
| 7 | kumamoto_test_0007 | 近くにゃレンゲ畑のあって、きれいかねって思いよりながら自転車ん乗っとったら、いがわさんつこけた。 | 近くにゃレンギバタケのあって、きれいかねって思いよりながら自転車ん乗っとったら、飯川さんつこけだ。 | 86.60 | False |
| 8 | kumamoto_test_0008 | 大人んなってくっと一人で過ごしたか時間の出てくるもんたい. | 大人になってくっと一人で過ごしたか時間の出てくるもんだい. | 93.10 | False |
| 9 | kumamoto_test_0009 | 近所ん豚舎んもんの友達におって、たまあにうまか豚バラん串焼きば食わせてもらいよった。 | キンジョン豚しゃんもんの友達におって、たまーにうまか豚バラん串焼きば作ってもらいよった。 | 79.07 | False |
| 10 | kumamoto_test_0010 | ばってん夏休みな結局ずっと部活しかしとらん. | ばってん夏休みは結局ずっと部活しとらん. | 90.48 | False |
| 11 | kumamoto_test_0011 | そぎゃん親切ばしてもらうと、旅の何倍でん楽しゅうなったい。 | そぎゃん新設ばしてもらうと、旅の何倍でん楽ちゅうなったい。 | 89.66 | False |
| 12 | kumamoto_test_0012 | またいろいろあると思ちょるばってんが,今ん状態じゃ,そんぐらいしか見つけきらんかった. | まあたいろいろあると思ちょるばってんが,今ん状態じゃそんくらいしか見つけきらんかった. | 95.35 | False |
| 13 | kumamoto_test_0013 | 飲み放題もついとったばってん,だっもいっちょん飲まんで,食ぶる食ぶる肉ばひたすら食べよらす. | 飲み放題もついとったばってん、だんもいっちょん飲まんで、食べぶる食べぶる、肉ばひたすら食べよらす。 | 88.42 | False |
| 14 | kumamoto_test_0014 | 桜んイメージのなかかもしれんばってん、一心行ん大桜は有名ばい。 | 桜んイメージのなかかも試練ばってん、一芯ぎょうん大桜は夢え。 | 72.13 | False |
Playing sample kumamoto_test_0000:
Ground Truth: カピバラに触らるっ場所もあっばい。親子で歩きよる姿に癒さるっけん。
QLoRA Pred : カピバラに触らるっ場所もあっばい。親子で歩きよる姿に癒さるっけん。
10.(選用)儲存並將 LoRA 轉接器推送到 Hugging Face Hub¶
您可以將微調後的 LoRA 權重直接推送到 Hugging Face Hub,方便分享或後續部署:
from huggingface_hub import whoami
hf_repo_id = whoami()["name"] + "/gemma-4-kumamoto-asr-qlora"
eval_model.push_to_hub(hf_repo_id, token=hf_token)
eval_processor.push_to_hub(hf_repo_id, token=hf_token)
print(f"Adapter successfully pushed to https://huggingface.co/{hf_repo_id}")
Processing Files (0 / 0) : | | 0.00B / 0.00B
New Data Upload : | | 0.00B / 0.00B
...adapter_model.safetensors: 8%|8 | 16.0MB / 193MB
Saved model to https://huggingface.co/bebechien/gemma-4-kumamoto-asr-qlora
Unsloth: Restored added_tokens_decoder metadata in /tmp/tmpx47_aabs/tokenizer_config.json.
Processing Files (0 / 0) : | | 0.00B / 0.00B
New Data Upload : | | 0.00B / 0.00B
...mpx47_aabs/tokenizer.json: 50%|####9 | 16.0MB / 32.2MB
Adapter successfully pushed to https://huggingface.co/bebechien/gemma-4-kumamoto-asr-qlora
摘要與後續步驟¶
本教學介紹了如何使用 Unsloth 與 QLoRA 微調 Gemma 模型。接下來請參閱以下文件: