---
base_model: Qwen/Qwen3.5-4B-Base
library_name: transformers
language:
- id
license: apache-2.0
pipeline_tag: image-text-to-text
tags:
- image-captioning
- qwen3.5
- bahasa-indonesia
- lora
- lora-merged
- vlm
- multimodal
- json-output
datasets:
- custom
---
# Qwen3.5-4B ImCap — Indonesian Image Captioning (LoRA merged)
Model ini merupakan hasil fine-tuning (LoRA, sudah di-**merge** ke base weights) dari
[`Qwen/Qwen3.5-4B-Base`](https://huggingface.co/Qwen/Qwen3.5-4B-Base) untuk tugas **image captioning Bahasa Indonesia**.
---
## Deskripsi Singkat
| Atribut | Nilai |
|---|---|
| Base model | `Qwen/Qwen3.5-4B-Base` |
| Metode fine-tune | LoRA (rank/alpha sesuai config SFT) |
| Status adapter | **Merged** ke base weights |
| Bahasa output | Bahasa Indonesia 🇮🇩 |
| Format output | JSON — `{"caption": "..."}` |
| `enable_thinking` saat training | `False` |
| EOS token | `<\|im_end\|>` (+ `<\|endoftext\|>`) |
| Precision | bfloat16 |
---
## Cara Pakai (Inference)
### Minimal (bfloat16, GPU)
```python
import torch
from PIL import Image
from transformers import AutoProcessor, AutoModelForImageTextToText
REPO = "Adicandra/Qwen3.5-4B-ImageCaptioning-LoRA"
processor = AutoProcessor.from_pretrained(REPO)
model = AutoModelForImageTextToText.from_pretrained(
REPO,
torch_dtype=torch.bfloat16,
device_map="auto",
)
model.eval()
```
### System Prompt (WAJIB sama dengan training)
```python
SYSTEM_PROMPT = (
"Annotator dataset image captioning. Tulis caption Bahasa Indonesia yang deskriptif.\n\n"
"Aturan:\n"
"- Deskripsikan subjek utama, detail visual (warna, posisi, atribut), dan latar belakang.\n"
"- Jika gambar mengandung teks penting (meme, infografis, berita, poster), sertakan isi teksnya dalam caption.\n"
"- KHUSUS UNTUK GAMBAR MEME: Analisis dan jelaskan makna sarkasme, ironi, atau humor yang terkandung di dalamnya jika ada.\n"
"- Panjang caption fleksibel: 2-3 kalimat untuk gambar biasa, lebih panjang jika ada teks/informasi penting atau sarkasme.\n"
"- Hanya deskripsikan yang terlihat (serta konteks humor/sarkasme jika itu meme). Jangan tebak identitas/nama. Jangan awali dengan \"gambar ini menunjukkan\".\n"
'- Output: Harus berupa JSON valid dengan format: {"caption": "isi caption disini"}'
)
```
### Render Chat + Generate
```python
import json, re
USER_PROMPT = "Buatkan caption deskriptif untuk gambar ini."
MAX_IMAGE_SIZE = (560, 560)
# ── Load & resize gambar ──────────────────────────────────────────────────────
img = Image.open("path/to/image.jpg").convert("RGB")
img.thumbnail(MAX_IMAGE_SIZE, Image.Resampling.LANCZOS)
# ── Susun messages ────────────────────────────────────────────────────────────
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": [
{"type": "image"},
{"type": "text", "text": USER_PROMPT},
]},
]
# ── Render template (enable_thinking=False wajib) ────────────────────────────
text = processor.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
enable_thinking=False,
)
# ── Tokenize ──────────────────────────────────────────────────────────────────
inputs = processor(text=[text], images=[[img]], return_tensors="pt").to(model.device)
input_len = inputs.input_ids.shape[1]
# ── EOS tokens ───────────────────────────────────────────────────────────────
im_end_id = processor.tokenizer.convert_tokens_to_ids("<|im_end|>")
eot_id = processor.tokenizer.convert_tokens_to_ids("<|endoftext|>")
eos_ids = list({im_end_id, eot_id} - {-1})
# ── Generate — greedy (deterministik) ────────────────────────────────────────
with torch.no_grad():
out = model.generate(
**inputs,
max_new_tokens=256,
do_sample=False,
use_cache=True,
eos_token_id=eos_ids,
pad_token_id=processor.tokenizer.pad_token_id,
)
raw = processor.tokenizer.decode(out[0, input_len:], skip_special_tokens=True)
# ── Parse JSON output ─────────────────────────────────────────────────────────
def extract_caption(raw: str):
cleaned = re.sub(r".*?", "", raw, flags=re.DOTALL).strip()
try:
obj = json.loads(cleaned)
if isinstance(obj, dict) and "caption" in obj:
return obj["caption"], "valid"
except Exception:
pass
m = re.search(r"\{.*\}", cleaned, flags=re.DOTALL)
if m:
try:
obj = json.loads(m.group(0))
if isinstance(obj, dict) and "caption" in obj:
return obj["caption"], "recovered"
except Exception:
pass
return cleaned, "invalid"
caption, status = extract_caption(raw)
print(f"[{status}] {caption}")
```
### Generate dengan Sampling
```python
# Tambahkan argumen berikut ke model.generate() untuk sampling:
with torch.no_grad():
out = model.generate(
**inputs,
max_new_tokens=256,
do_sample=True,
temperature=0.7,
top_p=0.8,
top_k=20,
min_p=0.0,
repetition_penalty=1.2,
use_cache=True,
eos_token_id=eos_ids,
pad_token_id=processor.tokenizer.pad_token_id,
)
```
---
## Format Output
Model selalu menghasilkan JSON valid:
```json
{"caption": "Seekor kucing oranye sedang duduk di atas meja kayu berwarna cokelat, menatap ke arah kanan frame dengan mata setengah terpejam. Latar belakang berupa dinding putih yang sedikit buram."}
```
Jika gambar mengandung teks (meme, infografis, poster), teks tersebut akan disertakan dalam caption beserta konteks humor/sarkasme bila relevan.
---
## Diagnostik: Cek Apakah `` Muncul
Model di-train dengan `enable_thinking=False`. Pastikan argumen tersebut selalu diteruskan ke `apply_chat_template`. Jika token `` masih muncul di output:
1. Pastikan `enable_thinking=False` di `apply_chat_template`.
2. Gunakan `suppress_tokens=[think_id]` saat `generate()` sebagai fallback.
3. Fungsi `extract_caption()` di atas sudah mem-strip blok `...` secara otomatis.
---
## Keterbatasan & Catatan
- Model hanya menghasilkan caption **Bahasa Indonesia**; tidak dirancang untuk bahasa lain.
- Untuk input resolusi tinggi (> 560×560), model tetap berfungsi tetapi performa optimal pada thumbnail 560×560.
- **Jangan tebak identitas/nama orang** dari gambar — sesuai aturan system prompt.
- Evaluasi formal (CIDEr, BLEU, METEOR) belum tersedia; performa diukur secara kualitatif.
---
## Lisensi
Mengikuti lisensi base model: [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
---
## Citation
Jika menggunakan model ini dalam penelitian, silakan sitasi base model Qwen3.5 dan sebutkan repo ini sebagai fine-tune untuk image captioning Bahasa Indonesia.