nl2sql-id-qlora-3b / README.md
qrizan's picture
Upload README.md
bb06acf verified
|
Raw
History Blame Contribute Delete
5.81 kB
---
base_model: unsloth/qwen2.5-coder-3b-instruct-bnb-4bit
library_name: peft
license: apache-2.0
language:
- id
tags:
- nl2sql
- text-to-sql
- indonesian
- sql
- qlora
- unsloth
- peft
pipeline_tag: text-generation
datasets:
- qrizan/nl2sql-id-schema
---
# NL2SQL-ID QLoRA - Indonesian Text-to-SQL (3B)
Model QLoRA adapter (r=16) hasil fine-tuning **Qwen2.5-Coder-3B-Instruct** untuk menerjemahkan pertanyaan bisnis **Bahasa Indonesia** menjadi SQL.
> **Proyek eksperimen.** Model dilatih khusus untuk satu skema e-commerce 5 tabel (customers, orders, order_items, products, payments). Bisa dipakai di database lain **hanya jika** skemanya persis sama. Untuk skema berbeda, perlu fine-tuning ulang dengan data baru.
---
## Hasil
3-way comparison pada 365 contoh eval_dev (nilai yang belum pernah dilihat model):
| Sistem | Exec Acc | Valid SQL | Error dominan |
|--------|:--------:|:---------:|---------------|
| Base (Qwen2.5-Coder-3B, zero-shot) | 0.27% | 3.0% | `invalid_sql` (354) |
| gpt-4o-mini (zero-shot) | 48.5% | 100% | `wrong_join` (99) |
| **Model ini** | **97.26%** | **100%** | `other` (10) |
Final pada eval_test (365 held-out, tidak pernah disentuh selama pengembangan):
| Metrik | Nilai |
|--------|-------|
| Execution Acc | **91.78%** |
| Valid SQL | 100% |
| Gap vs dev | 5.48pp (generalisasi baik) |
---
## Skema Database
Model dilatih dan hanya bekerja untuk skema 5 tabel berikut. Prompt harus selalu menyertakan DDL ini:
| Tabel | Kolom |
|-------|-------|
| `customers` | id, name, city |
| `orders` | id, customer_id, created_at, status |
| `order_items` | id, order_id, product_id, qty, price |
| `products` | id, name, category, price |
| `payments` | id, order_id, amount, paid_at, method |
Query yang didukung: `SELECT`, `JOIN` (max 4 tabel), `WHERE`, `GROUP BY`, `ORDER BY`, `SUM`/`COUNT`/`AVG`, `HAVING`, filter tanggal (`strftime`, `>= AND <`), `LIMIT`.
---
## Cara Pakai
### Load dari HF Hub
```python
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="qrizan/nl2sql-id-qlora-3b",
max_seq_length=768,
load_in_4bit=True,
)
FastLanguageModel.for_inference(model)
```
### Inference (self-contained, tanpa dependensi proyek)
```python
import re
# Salin skema database anda ke sini
SCHEMA_TEXT = """
customers(id, name, city)
orders(id, customer_id, created_at, status)
order_items(id, order_id, product_id, qty, price)
products(id, name, category, price)
payments(id, order_id, amount, paid_at, method)
"""
pertanyaan = "berapa total penjualan kategori elektronik bulan Januari 2025?"
# System prompt harus persis seperti saat training (src/prompts.py::get_system_prompt)
system = (
"Anda adalah asisten SQL yang menerjemahkan pertanyaan bisnis "
"Bahasa Indonesia menjadi query SQLite.\n\n"
f"Skema database:\n{SCHEMA_TEXT}\n\n"
"Aturan:\n"
"- Gunakan tabel dan kolom sesuai skema di atas.\n"
"- Tulis reasoning singkat dalam tag <think>...</think>:\n"
" Tabel: (tabel yang dipakai)\n"
" Join: (kondisi join)\n"
" Filter: (kondisi WHERE)\n"
" Agregasi: (fungsi agregasi, atau '-' jika tidak ada)\n"
"- Setelah </think>, tulis SQL tanpa markdown fence.\n"
"- Hanya SELECT, tanpa INSERT/UPDATE/DELETE."
)
messages = [
{"role": "system", "content": system},
{"role": "user", "content": pertanyaan},
]
prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
outputs = model.generate(**inputs, max_new_tokens=512, do_sample=False)
response = tokenizer.decode(outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
# Ekstrak SQL setelah </think>
m = re.search(r"</think>\s*(.*)", response, re.DOTALL | re.IGNORECASE)
sql = m.group(1).strip() if m else response.strip()
# Bersihkan markdown fence jika ada
sql = re.sub(r"```(?:sql)?\s*", "", sql, flags=re.IGNORECASE).replace("```", "").strip()
print(sql)
# SELECT SUM(oi.qty * oi.price) FROM order_items oi
# JOIN products p ON oi.product_id = p.id
# JOIN orders o ON oi.order_id = o.id
# WHERE p.category = 'elektronik'
# AND strftime('%Y-%m', o.created_at) = '2025-01'
```
### Format Output
Model menghasilkan reasoning chain diikuti SQL:
```
<think>
Tabel: order_items, products, orders
Join: oi.product_id = p.id, oi.order_id = o.id
Filter: p.category = 'elektronik', strftime('%Y-%m', o.created_at) = '2025-01'
Agregasi: SUM(qty * price)
</think>
SELECT SUM(oi.qty * oi.price) ...
```
SQL diekstrak setelah `</think>`. Baris `SCHEMA_TEXT` bisa diganti dengan DDL database anda - asal strukturnya sama dengan 5 tabel di atas.
---
## Training
| Parameter | Nilai |
|-----------|-------|
| Model base | Qwen2.5-Coder-3B-Instruct (4-bit) |
| Metode | QLoRA (r=16, alpha=16, dropout=0.05) |
| Data train | 820 contoh (pool A: elektronik/fashion/makanan/furnitur, Jan-Jun 2025) |
| Epoch | 5 |
| Learning rate | 2e-4 |
| Batch size | 16 (2 x grad_accum 8) |
| GPU | Colab T4 (~15 menit) |
| Trainable params | 29.9M (0.96%) |
| Adapter size | 115 MB |
Train dan eval menggunakan nilai berbeda (pool A vs pool B: kategori, bulan, kota) untuk menguji generalisasi, bukan hafalan. Hasil dev 97.26%, test held-out 91.78%.
---
## Batasan
- **Skema spesifik.** Hanya untuk 5 tabel di atas. Skema berbeda perlu fine-tuning ulang.
- Query `SELECT` saja - tidak mendukung `INSERT`/`UPDATE`/`DELETE`
- Bahasa Indonesia dengan kosakata bisnis, tidak diuji pada bahasa lain
- Maksimum ~768 token input
---
## Lisensi
Apache 2.0 - bebas dipakai, dimodifikasi, didistribusikan.
## Kode Lengkap
Pipeline training, evaluasi, demo: [github.com/qrizan/llm-posttraining-experiments](https://github.com/qrizan/llm-posttraining-experiments)