Key Findings
  • 使用公開的約 90 萬筆文言文–白話文平行語料[4],搭配 Qwen2.5-7B + QLoRA 微調,可在 Google Colab 免費 T4 GPU 上完成訓練
  • QLoRA 只更新不到 1% 的參數,記憶體需求從 28 GB 壓縮到約 8 GB,完全塞得進 T4 的 16 GB VRAM
  • 微調後的模型能將「項莊舞劍,意在沛公」流暢翻譯成「項莊表面上是在舞劍助興,實際上想刺殺劉邦」
  • 2026 年的當下,前沿大模型(GPT-4o、Claude、Qwen2.5-72B)已能零樣本翻譯大部分文言文,RAG + 典籍辭典的方案在專業場景更有優勢

一、為什麼要微調一個「文言文翻譯機」?

先承認一件事:2026 年的大型語言模型,直接丟一段史記給它,多半都能翻得八九不離十。那我們為什麼還要自己微調?

三個理由:

  1. 學習 LLM 微調的最佳練習場——文言文翻白話文是一個完美的 seq2seq 任務:輸入明確、輸出明確、有大量平行語料、結果好不好一眼就看得出來
  2. 成本控制——如果你要大量翻譯古籍(例如建一個古文數位典藏平台),每次都呼叫 GPT-4o API 是很貴的。微調一個 7B 模型,自己部署,長期成本低得多
  3. 可控性——你可以決定翻譯風格(信達雅的比例)、處理特殊術語的方式、輸出格式等,這些是通用大模型難以精確控制的

而司馬遷的《史記》[10]是我們今天的練功場——全書一百三十篇、五十二萬字,涵蓋帝王本紀、諸侯世家、名人列傳,文體多樣、難度適中,是測試翻譯品質的理想材料。

二、整體流程概覽

先看大圖再動手:

  1. 取得平行語料:從 Hugging Face 載入約 90 萬筆文言文↔白話文的句對
  2. 選擇基座模型:Qwen2.5-7B-Instruct(目前中文表現最佳的開源 7B 級模型之一)
  3. 格式化訓練資料:轉成 instruction-tuning 格式(「請將以下文言文翻譯成白話文:⋯⋯」)
  4. QLoRA 微調:4-bit 量化 + LoRA adapter,在 T4 GPU 上跑得動
  5. 測試翻譯效果:拿史記原文來試試

三、在 Google Colab 上動手做

打開 Google Colab,執行階段切換到 T4 GPU(必須,CPU 跑不動微調)。

3.1 安裝依賴

我們使用 Unsloth[6]——它能讓微調速度加快 2 倍、記憶體省 60%,而且跟 Hugging Face 生態完全相容:

# 安裝 Unsloth(已整合 bitsandbytes、peft、trl 等)
!pip install unsloth -q
!pip install datasets -q
import torch
from unsloth import FastLanguageModel

print(f"PyTorch: {torch.__version__}")
print(f"CUDA available: {torch.cuda.is_available()}")
if torch.cuda.is_available():
    print(f"GPU: {torch.cuda.get_device_name(0)}")
    print(f"VRAM: {torch.cuda.get_device_properties(0).total_mem / 1024**3:.1f} GB")

3.2 載入基座模型(4-bit 量化)

# ★ 載入 Qwen2.5-7B-Instruct,4-bit 量化 ★
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="unsloth/Qwen2.5-7B-Instruct-bnb-4bit",
    max_seq_length=2048,
    dtype=None,            # 自動偵測
    load_in_4bit=True,     # QLoRA 的關鍵:4-bit 量化
)

print(f"模型載入完成!參數量: {model.num_parameters():,}")

Qwen2.5-7B-Instruct[3] 是阿里巴巴通義千問團隊的開源模型,中文理解能力在 7B 級距中屬於頂尖。4-bit 量化後大約只佔 4–5 GB VRAM,留給訓練足夠的空間。

3.3 添加 LoRA Adapter

# ★ 設定 LoRA adapter ★
model = FastLanguageModel.get_peft_model(
    model,
    r=16,                          # LoRA rank(越大越有表達力,但越吃記憶體)
    target_modules=[               # 要微調的模組
        "q_proj", "k_proj", "v_proj", "o_proj",
        "gate_proj", "up_proj", "down_proj",
    ],
    lora_alpha=16,
    lora_dropout=0,                # Unsloth 建議設 0(已內建 regularization)
    bias="none",
    use_gradient_checkpointing="unsloth",  # 省 30% 記憶體
    random_state=42,
)

# 看看可訓練參數量
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
total = sum(p.numel() for p in model.parameters())
print(f"可訓練參數: {trainable:,} / {total:,} ({trainable/total*100:.2f}%)")

LoRA[2] 的核心概念:不動原本的 7B 參數,而是在特定層旁邊插入一個小小的「旁路」(低秩矩陣),只訓練這個旁路。通常可訓練參數不到原模型的 1%——但效果幾乎跟全量微調一樣好[1]

3.4 載入文言文–白話文平行語料

# ★ 載入公開的文言文-白話文平行語料 ★
from datasets import load_dataset

# raynardj 的 wenyanwen 語料:約 90 萬筆文言文-白話文句對
dataset = load_dataset(
    "raynardj/wenyanwen-ancient-translate-to-modern",
    split="train"
)

print(f"總筆數: {len(dataset):,}")
print(f"欄位: {dataset.column_names}")

# 看幾筆範例
for i in range(3):
    print(f"\n--- 範例 {i+1} ---")
    print(f"文言文: {dataset[i]['ancient']}")
    print(f"白話文: {dataset[i]['modern']}")

這個資料集[4]收錄了大量經典古籍的平行翻譯,包含史記、左傳、資治通鑑、論語等。每一筆都是一個文言文句子對應一個白話文翻譯。

3.5 格式化成 Instruction-Tuning 資料

# ★ 轉換成 Qwen 的 ChatML 格式 ★
SYSTEM_PROMPT = "你是一位精通古典文學的翻譯專家。請將使用者提供的文言文準確翻譯成流暢的現代白話文,保留原文的意思和語氣。"

def format_conversation(example):
    """將平行語料轉成 ChatML instruction-tuning 格式"""
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": f"請將以下文言文翻譯成白話文:\n\n{example['ancient']}"},
        {"role": "assistant", "content": example['modern']},
    ]
    text = tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=False
    )
    return {"text": text}

# 取子集訓練(全量 90 萬筆在 Colab 上要跑很久)
# 建議:先用 10,000 筆試跑,確認沒問題後再加大
TRAIN_SIZE = 10000

dataset_subset = dataset.shuffle(seed=42).select(range(TRAIN_SIZE))
formatted_dataset = dataset_subset.map(format_conversation)

print(f"訓練集大小: {len(formatted_dataset)}")
print(f"\n格式化範例(前 500 字元):\n{formatted_dataset[0]['text'][:500]}")

3.6 開始微調

# ★ 設定 SFTTrainer 開始微調 ★
from trl import SFTTrainer
from transformers import TrainingArguments

trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    train_dataset=formatted_dataset,
    dataset_text_field="text",
    max_seq_length=2048,
    dataset_num_proc=2,
    packing=True,               # 把短句子打包在一起,提高 GPU 利用率
    args=TrainingArguments(
        per_device_train_batch_size=2,
        gradient_accumulation_steps=4,    # 等效 batch_size = 8
        warmup_steps=50,
        num_train_epochs=3,
        learning_rate=2e-4,
        fp16=not torch.cuda.is_bf16_supported(),
        bf16=torch.cuda.is_bf16_supported(),
        logging_steps=25,
        optim="adamw_8bit",
        weight_decay=0.01,
        lr_scheduler_type="linear",
        seed=42,
        output_dir="outputs",
        report_to="none",
    ),
)

# 開始訓練!
print("開始微調...")
stats = trainer.train()

print(f"\n訓練完成!")
print(f"  總步數: {stats.global_step}")
print(f"  訓練時間: {stats.metrics['train_runtime']:.0f} 秒")
print(f"  最終 loss: {stats.metrics['train_loss']:.4f}")

在 T4 GPU 上,10,000 筆資料、3 個 epoch 大約需要 20–40 分鐘。你會看到 loss 從一開始的 2.x 逐漸降到 1.x 甚至更低——這代表模型越來越會翻譯了。

四、測試翻譯效果

訓練完了,來拿史記的原文測試:

# ★ 測試:翻譯史記名段 ★
FastLanguageModel.for_inference(model)

test_passages = [
    # 史記·項羽本紀
    "項莊舞劍,意在沛公。",
    # 史記·陳涉世家
    "燕雀安知鴻鵠之志哉!",
    # 史記·廉頗藺相如列傳
    "完璧歸趙。",
    # 史記·太史公自序
    "究天人之際,通古今之變,成一家之言。",
    # 史記·刺客列傳
    "風蕭蕭兮易水寒,壯士一去兮不復還。",
    # 較長段落:史記·項羽本紀·鴻門宴
    "沛公旦日從百餘騎來見項王,至鴻門,謝曰:臣與將軍戮力而攻秦,將軍戰河北,臣戰河南,然不自意能先入關破秦,得復見將軍於此。今者有小人之言,令將軍與臣有郤。",
]

def translate(text):
    """用微調後的模型翻譯文言文"""
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": f"請將以下文言文翻譯成白話文:\n\n{text}"},
    ]
    inputs = tokenizer.apply_chat_template(
        messages,
        tokenize=True,
        add_generation_prompt=True,
        return_tensors="pt"
    ).to("cuda")

    outputs = model.generate(
        input_ids=inputs,
        max_new_tokens=512,
        temperature=0.3,         # 低溫度:更忠實於原文
        top_p=0.9,
        repetition_penalty=1.1,
    )

    # 只取生成的部分
    response = tokenizer.decode(outputs[0][inputs.shape[-1]:], skip_special_tokens=True)
    return response.strip()

# 逐段翻譯
for passage in test_passages:
    print(f"【文言文】{passage}")
    translation = translate(passage)
    print(f"【白話文】{translation}")
    print("-" * 60)

你應該會看到類似這樣的翻譯結果:

【文言文】燕雀安知鴻鵠之志哉!
【白話文】燕子和麻雀怎麼能夠知道天鵝的志向呢!

【文言文】究天人之際,通古今之變,成一家之言。
【白話文】探究天道與人事之間的關係,通曉從古到今的歷史變化,形成自己獨到的見解。

4.1 跟原始模型做對照

# 對照實驗:看看微調前後的差異
# 載入原始模型(不帶 adapter)
from peft import PeftModel

# 暫時停用 adapter
model.disable_adapter_layers()

print("=== 原始 Qwen2.5-7B(未微調)===")
test_text = "項莊舞劍,意在沛公。"
print(f"文言文: {test_text}")
print(f"翻譯:   {translate(test_text)}")

# 重新啟用 adapter
model.enable_adapter_layers()

print(f"\n=== 微調後 ===")
print(f"文言文: {test_text}")
print(f"翻譯:   {translate(test_text)}")

微調後的模型通常在翻譯的「信」和「達」上都會有明顯提升——用詞更準確、句子更通順,特別是在處理典故和專有名詞時。

五、儲存模型

# ★ 儲存 LoRA adapter ★
model.save_pretrained("wenyanwen-translator-lora")
tokenizer.save_pretrained("wenyanwen-translator-lora")
print("LoRA adapter 已儲存!")

# 如果要上傳到 Hugging Face Hub:
# model.push_to_hub("your-username/wenyanwen-translator-lora")
# tokenizer.push_to_hub("your-username/wenyanwen-translator-lora")

# 如果要合併成完整模型(方便部署):
# model.save_pretrained_merged("wenyanwen-translator-merged", tokenizer)

LoRA adapter 通常只有幾十 MB——比起完整的 7B 模型(14 GB),小得可以忽略。你可以輕鬆下載到本機或上傳到 Hugging Face Hub。

六、完整程式碼(一鍵複製版)

# === LLM 微調文言文翻譯 完整版 ===
# 環境:Google Colab + T4 GPU

# Step 1: 安裝
!pip install unsloth datasets -q

# Step 2: 載入模型(4-bit 量化)
import torch
from unsloth import FastLanguageModel

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="unsloth/Qwen2.5-7B-Instruct-bnb-4bit",
    max_seq_length=2048,
    dtype=None,
    load_in_4bit=True,
)

# Step 3: 添加 LoRA
model = FastLanguageModel.get_peft_model(
    model, r=16,
    target_modules=["q_proj","k_proj","v_proj","o_proj","gate_proj","up_proj","down_proj"],
    lora_alpha=16, lora_dropout=0, bias="none",
    use_gradient_checkpointing="unsloth",
)

# Step 4: 載入平行語料
from datasets import load_dataset
dataset = load_dataset("raynardj/wenyanwen-ancient-translate-to-modern", split="train")

SYSTEM_PROMPT = "你是一位精通古典文學的翻譯專家。請將使用者提供的文言文準確翻譯成流暢的現代白話文,保留原文的意思和語氣。"

def format_conversation(example):
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": f"請將以下文言文翻譯成白話文:\n\n{example['ancient']}"},
        {"role": "assistant", "content": example['modern']},
    ]
    return {"text": tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=False)}

formatted = dataset.shuffle(seed=42).select(range(10000)).map(format_conversation)

# Step 5: 微調
from trl import SFTTrainer
from transformers import TrainingArguments

trainer = SFTTrainer(
    model=model, tokenizer=tokenizer,
    train_dataset=formatted,
    dataset_text_field="text",
    max_seq_length=2048, packing=True,
    args=TrainingArguments(
        per_device_train_batch_size=2,
        gradient_accumulation_steps=4,
        warmup_steps=50, num_train_epochs=3,
        learning_rate=2e-4,
        fp16=not torch.cuda.is_bf16_supported(),
        bf16=torch.cuda.is_bf16_supported(),
        logging_steps=25, optim="adamw_8bit",
        output_dir="outputs", report_to="none",
    ),
)
trainer.train()

# Step 6: 測試
FastLanguageModel.for_inference(model)

def translate(text):
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": f"請將以下文言文翻譯成白話文:\n\n{text}"},
    ]
    inputs = tokenizer.apply_chat_template(messages, tokenize=True, add_generation_prompt=True, return_tensors="pt").to("cuda")
    outputs = model.generate(input_ids=inputs, max_new_tokens=512, temperature=0.3)
    return tokenizer.decode(outputs[0][inputs.shape[-1]:], skip_special_tokens=True).strip()

print(translate("燕雀安知鴻鵠之志哉!"))

# Step 7: 儲存
model.save_pretrained("wenyanwen-translator-lora")
tokenizer.save_pretrained("wenyanwen-translator-lora")

七、拆解黑箱:微調到底改了什麼?

你可能好奇:LoRA 只動了不到 1% 的參數,為什麼翻譯品質就能提升這麼多?

這要從 LLM 的「知識」和「能力」說起:

  • 知識(Knowledge):存在模型的 FFN 層中——Qwen2.5 預訓練時已經讀過大量中文古典文獻,它「知道」文言文的語法和詞彙
  • 能力(Capability):存在 Attention 層的投影矩陣中——模型「知道」但不一定「擅長」特定格式的輸出

LoRA 微調[2]做的事情,本質上是校準模型的注意力模式:讓它在看到「請翻譯文言文」的指令時,知道要把注意力集中在文言文的語法結構、虛詞用法、古今詞義差異上,然後輸出通順的白話文。模型本來就有這個能力,微調只是把它「啟動」了。

QLoRA[1] 更進一步:把基座模型壓縮到 4-bit(原本每個參數用 16-bit 儲存),但 LoRA adapter 本身還是用 16-bit 訓練。這樣既省記憶體,又不犧牲微調精度。

八、2026 年的當下,有更好的方法嗎?

8.1 零樣本翻譯:直接問大模型

老實說,2026 年的前沿大模型已經很強了。如果你的需求是「偶爾翻譯幾段古文」,直接問就好:

# 用 Qwen2.5-72B(或 GPT-4o、Claude)直接翻譯
# 這裡示範用 Hugging Face Inference API
!pip install huggingface_hub -q

from huggingface_hub import InferenceClient

client = InferenceClient("Qwen/Qwen2.5-72B-Instruct")

response = client.chat_completion(
    messages=[
        {"role": "system", "content": "你是文言文翻譯專家,請準確翻譯為白話文。"},
        {"role": "user", "content": "請翻譯:沛公旦日從百餘騎來見項王,至鴻門,謝曰:臣與將軍戮力而攻秦,將軍戰河北,臣戰河南。"}
    ],
    max_tokens=512,
    temperature=0.3,
)

print(response.choices[0].message.content)

72B 級的模型翻譯品質通常非常好——但每次推論都要付 API 費用,而且你無法控制翻譯風格。

8.2 RAG + 古典辭典:專業場景的最佳方案

如果你要翻譯的是充滿典故、通假字、專有名詞的高難度古文,單靠 LLM 可能會出錯。TongGu[7] 提出的 CCU-RAG 方案是更好的選擇:

# RAG 增強翻譯的概念示意
# 1. 建立古典辭典向量資料庫
# 2. 翻譯前先檢索相關條目
# 3. 將檢索結果作為上下文提供給 LLM

"""
概念流程:
輸入: "完璧歸趙"

Step 1 - RAG 檢索:
  → 查到「完璧歸趙」典故出處:史記·廉頗藺相如列傳
  → 查到「璧」= 和氏璧(一種珍貴的玉器)
  → 查到「趙」= 趙國

Step 2 - 增強 Prompt:
  "參考以下背景知識:
   - 完璧歸趙:出自《史記·廉頗藺相如列傳》
   - 璧:指和氏璧
   - 趙:戰國時期的趙國
   請翻譯:完璧歸趙"

Step 3 - LLM 翻譯:
  → "將完好無缺的和氏璧歸還給趙國。
     比喻將物品完好地歸還給原主。"
"""
print("RAG 增強翻譯能將典故準確率提升 7 個百分點(TongGu, 2024)")
print("多義詞解析準確率提升 24 個百分點")

TongGu 的實驗顯示,RAG 增強能將典故翻譯準確率提升 7 個百分點、多義詞解析提升 24 個百分點[7]。如果你的目標是學術級的翻譯品質,這是目前的最佳路線。

8.3 多代理人協作翻譯:最新研究方向

Zhang et al. 在 2025 年發表的研究[8]提出了一個有趣的做法:用多個 LLM Agent 協作翻譯,每個 Agent 負責不同的任務——

  • Agent 1(字詞專家):逐字解釋文言文的詞義
  • Agent 2(翻譯者):根據詞義生成段落級白話文
  • Agent 3(審校者):從信、達、雅三個維度審查翻譯品質,提出修改建議

這種分工模式讓翻譯品質顯著超越單一模型。在實務上,你可以用 LangChain 或 LangGraph 來實現這個多 Agent 架構。

8.4 WenyanGPT:專為古文打造的大模型

如果你不想自己微調,WenyanGPT[5] 是一個已經微調好的專用模型,專攻古典中文的斷句和翻譯。它在翻譯任務上的 BLEU-1 分數達到 0.47,超過第二名 0.06 個百分點。值得關注的是,它對古文斷句(為沒有標點符號的原始古文加標點)也有很好的表現——這是翻譯前很重要的預處理步驟。

8.5 方案選擇一覽

方案適合場景翻譯品質成本可控性
QLoRA 微調 7B大量翻譯、自建平台低(自部署)最高
零樣本 72B API偶爾翻譯、快速驗證中(按量付費)
RAG + 辭典學術研究、高難度古文最高中高
多 Agent 協作出版級翻譯品質最高高(多次推論)
WenyanGPT開箱即用、斷句 + 翻譯

九、進階調校建議

如果你跑出了基本結果,想要更好的翻譯品質,可以試試以下調整:

  1. 增加訓練資料量:從 10,000 筆加到 50,000 甚至 100,000 筆。語料品質比數量重要,但在這個量級內,多就是好
  2. 過濾史記專屬語料:如果你特別想翻譯史記,可以從 中國哲學書電子化計劃 (ctext.org)[9] 取得史記全文,再搭配白話文譯本做對齊
  3. 調整 LoRA rankr=16 是一個好的起點。如果翻譯太「死板」,可以試 r=32r=64(但記憶體需求會增加)
  4. 多輪對話微調:加入「先解釋關鍵字詞,再翻譯全文」的多輪對話格式,讓模型學會先理解再翻譯
  5. 評估指標:除了人工看,也可以用 BLEU、ROUGE 等自動指標做量化評估。但中文翻譯的評估一直是個難題——同一句話可以有很多種正確的翻譯方式

十、常見問題 FAQ

Q:Colab 免費版的 T4 GPU 夠用嗎?

夠。Qwen2.5-7B 4-bit 量化約佔 4–5 GB,LoRA 訓練額外需要 3–4 GB,總共約 8 GB,T4 的 16 GB VRAM 綽綽有餘。但 Colab 免費版的 GPU 時間有限,建議一次跑完別中斷。

Q:為什麼選 Qwen2.5 而不是 LLaMA 3?

因為中文。Qwen2.5 的預訓練語料中有大量高品質中文(包含古典文獻),中文理解能力遠勝 LLaMA 3[3]。其他好的選擇還有 Yi-1.5、ChatGLM、Baichuan、DeepSeek。

Q:訓練資料只有 10,000 筆夠嗎?

作為 demo 夠了。但如果你要真正好的翻譯品質,建議用 50,000 筆以上。90 萬筆全用的話,在 T4 上大約要跑 6–8 小時——可以考慮用 Colab Pro 或者半夜掛著跑。

Q:微調後的模型可以處理史記以外的文言文嗎?

可以。因為訓練語料來自多部古籍(不只史記),所以微調後的模型對大部分文言文都有不錯的翻譯能力。但如果遇到特別冷門的典故或通假字,可能還是會出錯——這時候 RAG + 辭典的方案就派上用場了[7]

Q:能不能反過來,把白話文翻譯成文言文?

可以。raynardj 同時也提供了白話文→文言文的語料和模型[4]。用同樣的流程,把 ancientmodern 欄位對調就好。不過白話文翻文言文的難度更高——畢竟,模仿司馬遷的文筆可不是一件容易的事。

十一、結語:讓兩千年前的文字活過來

司馬遷在《報任安書》裡說自己寫史記的目的是「究天人之際,通古今之變,成一家之言」[10]。兩千年後的今天,我們用 AI 來翻譯他的文字,某種意義上也是在「通古今之變」——用現代的語言,把古人的智慧傳遞給更多人。

技術層面的收穫也不少:你在這篇教學裡學到了 QLoRA 微調的完整流程、instruction-tuning 的資料格式、Unsloth 的加速技巧,以及如何在免費的 Colab T4 上跑完整個訓練。這些技能可以直接遷移到任何其他的 LLM 微調任務——客服對話、程式碼生成、醫療問答、法律摘要,流程都是一樣的。

打開 Colab,把程式碼貼進去,讓 AI 試著讀懂司馬遷吧。第一次看到模型把「項莊舞劍,意在沛公」翻成流暢的白話文時,你大概會跟我一樣覺得:這真的滿酷的。