मुख्य सामग्री पर जाएँ

Ministral 3 का फाइन-ट्यूनिंग: X-ray वर्गीकरण के लिए विज़न-लैंग्वेज मॉडल ट्रेन करने की चरण-दर-चरण गाइड

LoRA, Hugging Face टूल्स, कस्टम प्रॉम्प्ट, और TRL का उपयोग करके X-ray इमेज वर्गीकरण के लिए Ministral 3 विज़न-लैंग्वेज मॉडल को फाइन-ट्यून करना सीखें।
· 10 मि॰ पढ़ें

AI के साथ खोजें

Ministral 3 एज-ऑप्टिमाइज़्ड मल्टीमॉडल मॉडलों का नया परिवार है, जो 3B, 8B, और 14B आकारों में उपलब्ध है। प्रत्येक में फुल विज़न सपोर्ट के साथ Base, Instruct, और Reasoning वेरिएंट मिलते हैं। 

फ्रंटियर-लेवल परफॉर्मेंस देने और स्थानीय व निजी डिप्लॉयमेंट के लिए हल्का बने रहने के लिए डिज़ाइन किया गया, Ministral 3 उन्नत क्षमताओं और हार्डवेयर सीमाओं के बीच सेतु का काम करता है। इसका फ्लैगशिप 14B Instruct मॉडल FP8 में 24GB VRAM में फिट हो जाता है और Mistral Small 3.2 24B जैसे बड़े सिस्टम्स की टक्कर देता है। इसमें 256k बड़ा कॉन्टेक्स्ट विंडो, सशक्त बहुभाषी कवरेज, नेटिव फंक्शन कॉलिंग, मजबूत सिस्टम-प्रॉम्प्ट अनुपालन और Apache 2.0 ओपन लाइसेंसिंग शामिल हैं। 

इस ट्यूटोरियल में, हम Ministral 3 14B Instruct मॉडल को बोन-फ्रैक्चर X-ray डेटासेट पर फाइन-ट्यून करेंगे ताकि यह फ्रैक्चर का पता लगाने और उनकी श्रेणियों को सटीक रूप से वर्गीकृत करने में बेहतर हो सके। 

आप सीखेंगे:

  1. Kaggle से डेटासेट लोड करना।
  2. उसे Hugging Face डेटासेट में बदलना।
  3. X-ray वर्गीकरण के लिए स्पष्ट ट्रेनिंग प्रॉम्प्ट बनाना।
  4. Ministral 3 14B विज़न-लैंग्वेज ट्रेनिंग पाइपलाइन सेट करना।
  5. मॉडल को फाइन-ट्यून करना और चेकपॉइंट सेव करना।
  6. इन्फरेंस के लिए प्रशिक्षित मॉडल अडॉप्टर को लोड करना।
  7. फाइन-ट्यूनिंग से पहले और बाद के परिणामों की तुलना करना।

यदि आप Mistral AI मॉडलों के बारे में और जानने के इच्छुक हैं, तो हमारा गाइड Mistral 3 और Mistral 3 Large ट्यूटोरियल देखें। 

अस्वीकरण: यह ट्यूटोरियल केवल शैक्षिक उद्देश्यों के लिए तकनीकी फाइन-ट्यूनिंग प्रक्रिया दर्शाता है। परिणामी मॉडल चिकित्सकीय या नैदानिक उपयोग के लिए अभिप्रेत नहीं है और निदान, निर्णय-निर्माण या रोगी देखभाल के लिए इस पर भरोसा नहीं किया जाना चाहिए।

1. Ministral 3 विज़न-लैंग्वेज फाइन-ट्यूनिंग के लिए पर्यावरण सेटअप

हालाँकि हम 14B मॉडल को फाइन-ट्यून कर रहे हैं, विज़न-लैंग्वेज ट्रेनिंग को केवल-टेक्स्ट फाइन-ट्यूनिंग की तुलना में काफी अधिक मेमोरी चाहिए। इमेज एन्कोडर GPU लोड बढ़ाता है, जिसका मतलब है कि इस प्रक्रिया को सुचारू रूप से चलाने के लिए हमें 80 GB VRAM वाले A100 GPU की आवश्यकता है। 

इस प्रोजेक्ट के लिए, मैं Runpod का उपयोग कर रहा हूँ और A100 पॉड लॉन्च कर रहा हूँ।

RunPod. A100 Pod.

पॉड लॉन्च करने के बाद, कंटेनर स्टोरेज को 50 GB तक बढ़ाएँ। फिर Kaggle और Hugging Face दोनों के API कीज़ के लिए एनवायरनमेंट वेरिएबल जोड़ें। 

Kaggle हमें बोन X-ray डेटासेट सीधे डाउनलोड करने देगा, और Hugging Face हमें अपने फाइन-ट्यून किए गए LoRA अडॉप्टर को मॉडल हब पर पुश करने की अनुमति देगा।

Runpod में एनवायरनमेंट वेरिएबल सेट करना

इसके बाद JupyterLab इंस्टेंस खोलें और एक नया नोटबुक बनाएँ। ट्रेनिंग से पहले, हमें सभी आवश्यक Python पैकेज इंस्टॉल करने होंगे। 

सुनिश्चित करें कि Transformers लाइब्रेरी नवीनतम संस्करण पर अपडेट हो, क्योंकि हाल में ही Ministral 3 सपोर्ट जोड़ा गया है और पुराने बिल्ड मॉडल को सही से लोड नहीं करेंगे।

! pip install --upgrade --quiet bitsandbytes datasets evaluate hf_transfer peft scikit-learn matplotlib Pillow kaggle ipywidgets
!pip install --quiet git+https://github.com/huggingface/transformers.git@bf3f0ae70d0e902efab4b8517fce88f6697636ce
!pip install --quiet --no-deps trl==0.22.2

सब कुछ इंस्टॉल हो जाने के बाद, आप डेटासेट लोड करने, कोलेटेर तैयार करने और फाइन-ट्यूनिंग शुरू करने के लिए तैयार हैं।

2. Kaggle से बोन फ्रैक्चर डेटासेट डाउनलोड करना

अगले चरण में, हम अपने डेटासेट के लिए एक रूट फोल्डर सेट करते हैं और Kaggle Python API का उपयोग करके Bone Break Classification Image डेटासेट डाउनलोड करते हैं। 

यह डेटासेट X-ray फॉर्मेट में विभिन्न बोन फ्रैक्चर श्रेणियाँ रखता है और इसे Ministral 3 को विज़ुअल क्लासिफिकेशन के लिए फाइन-ट्यून करने में इस्तेमाल किया जाएगा।

import os
import glob
from kaggle.api.kaggle_api_extended import KaggleApi

DATA_ROOT = "/bone-break"
os.makedirs(DATA_ROOT, exist_ok=True)

kaggle_dataset = "pkdarabi/bone-break-classification-image-dataset"

# Initialize and authenticate Kaggle API (it will read KAGGLE_USERNAME / KAGGLE_KEY)
api = KaggleApi()
api.authenticate()

# Download and unzip the dataset using the Python API
api.dataset_download_files(
    kaggle_dataset,
    path=DATA_ROOT,
    unzip=True,
)

आप Kaggle पर यहाँ डेटासेट देख सकते हैं:

Dataset URL: https://www.kaggle.com/datasets/pkdarabi/bone-break-classification-image-dataset

3. Hugging Face डेटासेट बनाना

डाउनलोड किया गया डेटासेट प्रचलित संरचना में व्यवस्थित नहीं है। प्रत्येक फ्रैक्चर श्रेणी के अंदर एक प्रशिक्षण फोल्डर और एक परीक्षण फोल्डर दोनों होते हैं। 

इसलिए पहले हमें हर क्लास फोल्डर पर लूप करने, दोनों स्प्लिट अलग करने और फिर साफ ट्रेन और टेस्ट मैपिंग के साथ संयुक्त Hugging Face डेटासेट बनाने की विधि चाहिए।

import glob, os
from typing import Any, Dict, List

from datasets import Dataset, DatasetDict, Features, ClassLabel, Image

inner_root = os.path.join(
    DATA_ROOT, "Bone Break Classification", "Bone Break Classification"
)

classes = []
train_records: List[Dict[str, Any]] = []
test_records: List[Dict[str, Any]] = []

for class_name in sorted(os.listdir(inner_root)):
    class_dir = os.path.join(inner_root, class_name)
    if not os.path.isdir(class_dir):
        continue

    classes.append(class_name)

    for split_name, split_key in [("Train", "train"), ("Test", "test")]:
        split_dir = os.path.join(class_dir, split_name)
        if not os.path.isdir(split_dir):
            continue

        for ext in ("*.png", "*.jpg", "*.jpeg"):
            for img_path in glob.glob(os.path.join(split_dir, ext)):
                rec = {"image": img_path, "label_name": class_name}
                if split_key == "train":
                    train_records.append(rec)
                else:
                    test_records.append(rec)

print("Classes:", classes)
print("Train samples:", len(train_records))
print("Test samples:", len(test_records))

हमारे पास कुल 10 वर्गीकरण हैं, जिनमें 989 प्रशिक्षण सैंपल और 140 परीक्षण सैंपल हैं।

Classes: ['Avulsion fracture', 'Comminuted fracture', 'Fracture Dislocation', 'Greenstick fracture', 'Hairline Fracture', 'Impacted fracture', 'Longitudinal fracture', 'Oblique fracture', 'Pathological fracture', 'Spiral Fracture']
Train samples: 989
Test samples: 140

अब हम Hugging Face डेटासेट के लिए लेबल एन्कोड करेंगे ताकि हर लेबल क्लास को क्लास लेबल समझा जाए और हर इमेज को इमेज फीचर माना जाए। 

हम दो डेटासेट बनाएँगे: एक ट्रेनिंग के लिए और एक टेस्टिंग के लिए। अंत में, हम इन्हें एकल डेटासेट में मिलाएँगे जिसमें दोनों सबसेट शामिल होंगे।

# Encode labels
classes = sorted(list(set(classes)))
label2id = {name: i for i, name in enumerate(classes)}

for rec in train_records:
    rec["label"] = label2id[rec["label_name"]]
for rec in test_records:
    rec["label"] = label2id[rec["label_name"]]

features = Features(
    {
        "image": Image(),
        "label": ClassLabel(names=classes),
    }
)

train_ds = Dataset.from_list(
    [{"image": r["image"], "label": r["label"]} for r in train_records],
    features=features,
)
test_ds = Dataset.from_list(
    [{"image": r["image"], "label": r["label"]} for r in test_records],
    features=features,
)

data = DatasetDict({"train": train_ds, "test": test_ds})
data

यहाँ डेटासेट का ओवरव्यू है। 

DatasetDict({
    train: Dataset({
        features: ['image', 'label'],
        num_rows: 989
    })
    test: Dataset({
        features: ['image', 'label'],
        num_rows: 140
    })
})

4. क्लास वितरण और नमूना X-ray इमेज का अन्वेषण

फाइन-ट्यूनिंग से पहले, यह समझना उपयोगी है कि डेटासेट कितना संतुलित है और इमेज कैसी दिखती हैं। हम प्रत्येक फ्रैक्चर श्रेणी के लिए कितने प्रशिक्षण सैंपल हैं, यह जाँचने से शुरू करेंगे।

from collections import Counter

label_names = data["train"].features["label"].names
label_ids = data["train"]["label"]

counts = Counter(label_ids)
print("Training Class counts:")
for lbl_id, cnt in counts.items():
    print(f"{lbl_id:2d} ({label_names[lbl_id]}): {cnt}")

FRACTURE_CLASSES = label_names

श्रेणियाँ काफ़ी हद तक संतुलित हैं, हालाँकि जटिल विज़न क्लासिफिकेशन कार्य के लिए कुल सैंपल आकार छोटा है। 

फिलहाल हम इसी डेटासेट के साथ आगे बढ़ेंगे, लेकिन सीमित सैंपल वॉल्यूम के कारण परिणामों की व्याख्या यथार्थवादी अपेक्षाओं के साथ करनी चाहिए।

Training Class counts:
 0 (Avulsion fracture): 109
 1 (Comminuted fracture): 134
 2 (Fracture Dislocation): 137
 3 (Greenstick fracture): 106
 4 (Hairline Fracture): 101
 5 (Impacted fracture): 75
 6 (Longitudinal fracture): 68
 7 (Oblique fracture): 69
 8 (Pathological fracture): 116
 9 (Spiral Fracture): 74

अब, डेटासेट को दृश्य रूप से बेहतर समझने के लिए कुछ रैंडम इमेज देखते हैं।

import matplotlib.pyplot as plt
from datasets import Image

# Cast the 'image' column to Hugging Face Image feature
dataset_images = data.cast_column("image", Image())

# Select random samples from the training set
samples = dataset_images["train"].shuffle(seed=420).select(range(4))

# Plot
fig, axes = plt.subplots(1, 4, figsize=(16, 5))

for i, sample in enumerate(samples):
    img_object = sample["image"]              # PIL Image
    label_id = sample["label"]               # int
    label_name = label_names[label_id]       # string

    axes[i].imshow(img_object)
    axes[i].set_title(f"Label: {label_name}")
    axes[i].axis("off")

plt.tight_layout()
plt.show()

नमूना इमेज देखकर, आकार, रेज़ोल्यूशन, ओरिएंटेशन और स्कैन गुणवत्ता में अंतर दिखता है। यह विज़न-लैंग्वेज मॉडल को फाइन-ट्यून करने को प्रभावित नहीं करता, लेकिन मॉडल प्रत्येक फ्रैक्चर श्रेणी को कितनी सुसंगतता से सीखता है, इस पर असर डाल सकता है। 

दृश्य रूप से फ्रैक्चर का पता लगाना अक्सर सीधा होता है, लेकिन इसे दस सूक्ष्म चिकित्सकीय फ्रैक्चर प्रकारों में वर्गीकृत करना यहाँ उपलब्ध डेटा की तुलना में अधिक संरचित डेटा की माँग करता है।

बोन फ्रैक्चर डेटासेट से 4 रैंडम सैंपल

5. बोन फ्रैक्चर क्लासिफिकेशन के लिए प्रॉम्प्ट डिज़ाइन

अब हमें तय करना है कि हम मॉडल से कैसा व्यवहार चाहते हैं। इस कार्य के लिए हमारा लक्ष्य काफ़ी सख्त है: एक X-ray इमेज दिए जाने पर, मॉडल को सिर्फ एक फ्रैक्चर क्लास का नाम और कुछ नहीं आउटपुट करना चाहिए। कोई व्याख्या नहीं, कोई मार्कडाउन नहीं, कोई अतिरिक्त टेक्स्ट नहीं।

हम एक स्पष्ट निर्देश-प्रॉम्प्ट लिखेंगे और फिर इसका उपयोग करके डेटासेट में messages कॉलम बनाएँगे। 

PROMPT = f"""
You are a radiology assistant specialised in bone fracture classification.

You must classify the X-ray image into exactly ONE of the following classes:
{", ".join(FRACTURE_CLASSES)}

Rules:
- Reply with ONLY the class name.
- No explanations, no extra words, no markdown, no punctuation.
- Output must be EXACTLY one of: {", ".join(FRACTURE_CLASSES)}
""".strip()


from typing import Dict, Any

def convert_to_conversation(example: Dict[str, Any]) -> Dict[str, Any]:
    conversation = [
        {
            "role": "user",
            "content": [
                {"type": "text",  "text": PROMPT},
                {"type": "image"},     # no image bytes here
            ],
        },
        {
            "role": "assistant",
            "content": [
                {"type": "text", "text": FRACTURE_CLASSES[example["label"]]},
            ],
        },
    ]
    example["messages"] = conversation
    return example




formatted_data = data["train"].map(convert_to_conversation)
formatted_data["messages"][0]

यह messages कॉलम बनाता है जहाँ प्रत्येक रिकॉर्ड में अब एक संपूर्ण मिनी बातचीत होती है: निर्देश और सही फ्रैक्चर क्लास। फाइन-ट्यूनिंग के दौरान विज़न-लैंग्वेज मॉडल ठीक यही देखेगा।

संवादी शैली में डेटासेट।

6. Ministral 3 विज़न-लैंग्वेज मॉडल लोड करना

अब हम Hugging Face Hub से Ministral-3 14B Instruct मॉडल का BF16 संस्करण और प्रोसेसर लोड करते हैं। प्रोसेसर टोकनाइज़र और इमेज प्री-प्रोसेसिंग पाइपलाइन को साथ बाँधता है, जो विज़न-लैंग्वेज इनपुट के लिए आवश्यक है।

import torch
from transformers import AutoProcessor, AutoModelForImageTextToText, BitsAndBytesConfig

model_id = "mistralai/Ministral-3-14B-Instruct-2512-BF16"


model_kwargs = dict(
    attn_implementation="eager",
    torch_dtype=torch.bfloat16,
    device_map="auto",
)

model = AutoModelForImageTextToText.from_pretrained(model_id, **model_kwargs)
processor = AutoProcessor.from_pretrained(model_id)

# Use right padding to avoid issues during training
processor.tokenizer.padding_side = "right"

7. कस्टम चैट टेम्पलेट सेट करना

Mistral AI द्वारा प्रदान किया गया मूल चैट टेम्पलेट लंबे, संवादात्मक उपयोग मामलों के लिए डिज़ाइन किया गया है। इस प्रोजेक्ट में यह समस्या बना। मॉडल लंबी रोल इंस्ट्रक्शन और सिस्टम टेक्स्ट पर केंद्रित होने लगा और उसने वास्तविक लक्ष्य—X-ray से एकल बोन फ्रैक्चर क्लास की भविष्यवाणी—को नज़रअंदाज़ कर दिया।

इसे ठीक करने के लिए, हम एक छोटा विज़न-लैंग्वेज टेम्पलेट परिभाषित करते हैं जो केवल ज़रूरी भाग रखता है: यूज़र का निर्देश और असिस्टेंट का उत्तर। इससे शोर घटता है और मॉडल को इमेज से क्लास लेबल तक साफ मैपिंग सीखने में मदद मिलती है।

SHORT_VLM_TEMPLATE = r"""{{ bos_token }}[INST]
{% for message in messages if message['role'] == 'user' -%}
  {%- for content in message['content'] -%}
    {%- if content['type'] == 'text' -%}
{{ content['text'] }}

    {%- elif content['type'] == 'image' -%}
[IMG]
    {%- endif -%}
  {%- endfor -%}
{%- endfor -%}
[/INST]{% if not add_generation_prompt %}
  {%- for message in messages if message['role'] == 'assistant' -%}
{{ message['content'][0]['text'] }}{{ eos_token }}
  {%- endfor -%}
{%- endif %}"""


# Attach to processor / tokenizer
processor.chat_template = SHORT_VLM_TEMPLATE
processor.tokenizer.chat_template = SHORT_VLM_TEMPLATE

8. Ministral 3 का परीक्षण (फाइन-ट्यूनिंग से पहले)

फाइन-ट्यूनिंग शुरू करने से पहले, बेसलाइन बनाने के लिए मूल Ministral 3 14B Instruct मॉडल का परीक्षण करना उपयोगी है। 

पहले, हम डेटासेट से एक रैंडम इमेज देते हैं और एक सामान्य प्रश्न पूछते हैं, ताकि देखें कि मॉडल X-ray की सामग्री को कैसे समझता है।

image = formatted_data[200]["image"]

messages = [
    {"role": "user", "content": [
        {"type": "text", "text": "What is the image about?"},
        {"type": "image"},
        
    ]}
]

input_text = processor.apply_chat_template(messages, add_generation_prompt = True)
inputs = processor(
    image,
    input_text,
    add_special_tokens = False,
    return_tensors = "pt",
).to(device="cuda", dtype=torch.bfloat16)

from transformers import TextStreamer
text_streamer = TextStreamer(processor, skip_prompt = True)
_ = model.generate(**inputs, streamer = text_streamer, max_new_tokens = 1000,
                   use_cache = True, temperature = 1.5, min_p = 0.1)

परिणामस्वरूप, मॉडल ने X-ray की सामान्य सामग्री समझ ली, लेकिन फिर ऐसे विवरणों में भटक गया जो चिकित्सकीय उपयोग के लिए विश्वसनीय नहीं थे।

अब हम अपने अंतिम उद्देश्य के और करीब कुछ आज़माते हैं। हम वही इमेज देते हैं, लेकिन इस बार खुले प्रश्न की जगह अपना सख्त फ्रैक्चर क्लासिफिकेशन प्रॉम्प्ट उपयोग करते हैं।

image = formatted_data[200]["image"]

messages = [
    {"role": "user", "content": [
        {"type": "text", "text": PROMPT},
        {"type": "image"},
        
    ]}
]

input_text = processor.apply_chat_template(messages, add_generation_prompt = True)
inputs = processor(
    image,
    input_text,
    add_special_tokens = False,
    return_tensors = "pt",
).to(device="cuda", dtype=torch.bfloat16)

from transformers import TextStreamer
text_streamer = TextStreamer(processor, skip_prompt = True)
_ = model.generate(**inputs, streamer = text_streamer, max_new_tokens = 1000,
                   use_cache = True, temperature = 1.5, min_p = 0.1

इस सैंपल के लिए, मॉडल ने उत्तर दिया:

Hairline Fracture</s>

अब हम इसी इमेज के वास्तविक ग्राउंड ट्रुथ लेबल की जाँच करते हैं।

FRACTURE_CLASSES[formatted_data[200]["label"]]

यह लौटाता है:

'Comminuted fracture'

तो, मॉडल ने निर्देश फॉर्मेट को सही से फॉलो किया और एकल क्लास नाम दिया, लेकिन वर्गीकरण गलत था। 

यही वजह है कि हमें फाइन-ट्यूनिंग की ज़रूरत है। बेस मॉडल की विज़न समझ मजबूत है, लेकिन इसे विशेष रूप से विस्तृत बोन फ्रैक्चर प्रकारों को अलग पहचानने के लिए प्रशिक्षित नहीं किया गया है। 

अगले चरणों में, हम इसे अपने फ्रैक्चर डेटासेट पर फाइन-ट्यून करेंगे ताकि यह व्यवहार बेहतर हो।

9. कुशल Ministral 3 फाइन-ट्यूनिंग के लिए LoRA कॉन्फ़िगर करना

अब हम LoRA सेट करते हैं ताकि Ministral 3 मॉडल को सभी पैरामीटर अपडेट किए बिना कुशलता से फाइन-ट्यून किया जा सके। यह कॉन्फ़िगरेशन Mistral-शैली के विज़न-लैंग्वेज मॉडल में प्रयुक्त मुख्य अटेंशन और MLP प्रोजेक्शन लेयर्स को लक्षित करता है।

from peft import LoraConfig, TaskType, get_peft_model

# LoRA config for a Mistral-style VLM used as a causal LM
peft_config = LoraConfig(
    task_type=TaskType.CAUSAL_LM,
    r=16,
    lora_alpha=32,
    lora_dropout=0.05,
    bias="none",
    target_modules=[
        "q_proj",
        "k_proj",
        "v_proj",
        "o_proj",
        "gate_proj",
        "up_proj",
        "down_proj",
    ],
    modules_to_save=None,
)

अब हम सुपरवाइज़्ड फाइन-ट्यूनिंग (SFT) ट्रेनिंग आर्ग्युमेंट्स परिभाषित करते हैं।

यह कॉन्फ़िगरेशन पूरे ट्रेनिंग प्रोसेस को नियंत्रित करता है, जिसमें बैच हैंडलिंग, मेमोरी ऑप्टिमाइज़ेशन, लर्निंग रेट का व्यवहार, लॉगिंग, सेविंग स्ट्रैटेजी और Hugging Face Hub के साथ इंटीग्रेशन शामिल है। 

from trl import SFTConfig

args = SFTConfig(
    output_dir="ministral-3-bone-fracture",            
    num_train_epochs=1,                       
    per_device_train_batch_size=2,                           
    per_device_eval_batch_size=2,                            
    gradient_accumulation_steps=4,                           
    gradient_checkpointing=True,                             
    optim="adamw_8bit",                               
    logging_steps=0.1,                                        
    save_strategy="epoch",                                                                                                             
    learning_rate=2e-4,                             
    bf16=True,
    fp16=False,
    max_grad_norm=0.3,                                       
    warmup_steps=25,                                       
    lr_scheduler_type="linear",                              
    push_to_hub=True,                                        
    report_to="none",
    gradient_checkpointing_kwargs={"use_reentrant": False},  
    dataset_kwargs={"skip_prepare_dataset": True},           
    remove_unused_columns = False,                           
    label_names=["labels"],                                  
)

10. ट्रेनिंग के लिए विज़न-लैंग्वेज डेटा कोलेटेर बनाना

Ministral 3 जैसे विज़न-लैंग्वेज मॉडल को फाइन-ट्यून करने के लिए हमें कस्टम डेटा कोलेटेर चाहिए जो इमेज और चैट-शैली टेक्स्ट को एक समान प्रारूप में बैच कर सके। 

नीचे दिया गया कोलेटेर इन बातों का ध्यान रखता है: टोकनाइज़र और पैड टोकन सेट करना, चैट टेम्पलेट लागू करना, टेक्स्ट और इमेज को संयुक्त रूप से टोकनाइज़ करना, सुरक्षित अधिकतम सीक्वेंस लंबाई लागू करना, और ऐसे लेबल बनाना जहाँ पैडिंग और इमेज टोकन (-100) से मास्क किए जाएँ ताकि वे लॉस को प्रभावित न करें। 

from typing import Any, Dict, List
import torch

# --- One-time processor setup ---
tokenizer = processor.tokenizer
tokenizer.padding_side = "right"

# Ensure we have a pad token
if tokenizer.pad_token is None:
    tokenizer.add_special_tokens({"pad_token": tokenizer.eos_token})

pad_token_id = tokenizer.pad_token_id
image_token_id = getattr(processor, "image_token_id", None)

# Hard cap for sequence length
MAX_SEQ_LEN = 2048 


def collate_fn(examples: List[Dict[str, Any]]) -> Dict[str, torch.Tensor]:
    """
    Collate function for VLM fine-tuning.

    Each example must contain:
      - "image": a PIL image
      - "messages": chat-style conversation (list[dict])
    """
    images = [ex["image"] for ex in examples]
    conversations = [ex["messages"] for ex in examples]

    # Build chat prompts for the whole batch
    chat_texts = processor.apply_chat_template(
        conversations,
        add_generation_prompt=False,
        tokenize=False,
    )

    # Tokenize + process images
    batch = processor(
        text=chat_texts,
        images=images,
        padding="longest", 
        truncation=True,
        max_length=MAX_SEQ_LEN,
        return_tensors="pt",
    )

    # Labels: same as input_ids but mask pad & image tokens
    labels = batch["input_ids"].clone()

    if pad_token_id is not None:
        labels[labels == pad_token_id] = -100

    if image_token_id is not None and image_token_id != pad_token_id:
        labels[labels == image_token_id] = -100

    batch["labels"] = labels
    return batch

11. TRL का उपयोग करके Ministral 3 को फाइन-ट्यून करना

LoRA अडॉप्टर, ट्रेनिंग कॉन्फ़िग और डेटा कोलेटेर तैयार होने के साथ, अब हम सब कुछ trl के SFTTrainer में लगाते हैं। 

यह ट्रेनर हमारे विज़न-लैंग्वेज Ministral 3 मॉडल के लिए पूरा सुपरवाइज़्ड फाइन-ट्यूनिंग लूप संभालता है, जिसमें हमारा फॉर्मेट किया हुआ डेटासेट, LoRA कॉन्फ़िग और इमेज–टेक्स्ट बैचों के लिए कस्टम कोलेट फंक्शन शामिल है।

from trl import SFTTrainer

trainer = SFTTrainer(
    model=model,
    args=args,
    train_dataset=formatted_data,
    peft_config=peft_config,
    processing_class=processor,
    data_collator=collate_fn,
)

ट्रेनिंग प्रक्रिया शुरू करने के लिए नीचे का कोड चलाएँ। 

trainer.train()

मेरे रन के दौरान, फाइन-ट्यूनिंग ने लगभग 57 GB VRAM का उपयोग किया, और कुछ अधिक मूल्यांकन चरणों के समय जो हर कुछ इटरेशन पर चलते हैं। व्यवहार में, इसका मतलब है कि आप 70 GB से कम VRAM वाले हार्डवेयर पर भी इसे आराम से फाइन-ट्यून कर सकते हैं।

Ministral 3 14B मॉडल के फाइन-ट्यूनिंग के दौरान Runpod टेलीमेट्री।

पूरी ट्रेनिंग में लगभग आधा घंटा लगा, और ट्रेनिंग लॉस समय के साथ लगातार घटती रही, जो संकेत देता है कि मॉडल डेटासेट से सीख रहा है।

Ministral 3 14B मॉडल के फाइन-ट्यूनिंग की ट्रेनिंग लॉस

ट्रेनिंग पूर्ण होने पर, हम अनुकूलित मॉडल को Hugging Face Hub पर सेव करते हैं। यह एक नया रिपॉज़िटरी बनाएगा और सभी आवश्यक फाइलें पुश करेगा ताकि आप किसी भी वातावरण से फाइन-ट्यून मॉडल को फिर से लोड कर चला सकें।

trainer.save_model()

आप मॉडल को यहाँ देख सकते हैं kingabzpro/ministral-3-bone-fracture। 

Hugging Face हब पर सेव किया गया फाइन-ट्यून मॉडल।

12. फाइन-ट्यून Ministral 3 मॉडल का मूल्यांकन

अब हम Hugging Face Hub से फाइन-ट्यून मॉडल और प्रोसेसर लोड करते हैं। अंदर ही अंदर, यह सेव किए गए LoRA अडॉप्टर को डाउनलोड करेगा, बेस Ministral 3 चेकपॉइंट लोड करेगा और अडॉप्टर को बेस मॉडल में मर्ज करेगा ताकि वह इन्फरेंस के लिए तैयार हो जाए।

import torch
from transformers import AutoProcessor, AutoModelForImageTextToText, BitsAndBytesConfig

model_id = "kingabzpro/ministral-3-bone-fracture"


model_kwargs = dict(
    attn_implementation="eager",
    dtype=torch.bfloat16,
    device_map="auto",
)

model = AutoModelForImageTextToText.from_pretrained(model_id, **model_kwargs)
processor = AutoProcessor.from_pretrained(model_id)

processor.tokenizer.padding_side = "left"

तुलना निष्पक्ष रखने के लिए, हम पहले वही इमेज (इंडेक्स 200) पुनः उपयोग करते हैं जिसे हमने फाइन-ट्यूनिंग से पहले टेस्ट किया था और सख्त क्लासिफिकेशन प्रॉम्प्ट के साथ इन्फरेंस पाइपलाइन से चलाते हैं।

image = formatted_data[200]["image"]

messages = [
    {"role": "user", "content": [
        {"type": "text", "text": PROMPT},
        {"type": "image"},
        
    ]}
]

input_text = processor.apply_chat_template(messages, add_generation_prompt = True)
inputs = processor(
    image,
    input_text,
    add_special_tokens = False,
    return_tensors = "pt",
).to(device="cuda", dtype=torch.bfloat16)

from transformers import TextStreamer
text_streamer = TextStreamer(processor, skip_prompt = True)
_ = model.generate(**inputs, streamer = text_streamer, max_new_tokens = 100)

हमने एकदम सही परिणाम हासिल किया। किसी अतिरिक्त पोस्ट-प्रोसेसिंग की ज़रूरत नहीं पड़ी। मॉडल ने प्रॉम्प्ट नियमों का पालन किया और सीधे सही क्लास दी।

Comminuted fracture</s>

अब हम डेटासेट से एक और रैंडम सैंपल आज़माते हैं ताकि पुष्टि हो सके कि यह व्यवहार केवल एक उदाहरण तक सीमित नहीं है।

image = formatted_data[400]["image"]

messages = [
    {"role": "user", "content": [
        {"type": "text", "text": PROMPT},
        {"type": "image"},
        
    ]}
]

input_text = processor.apply_chat_template(messages, add_generation_prompt = True)
inputs = processor(
    image,
    input_text,
    add_special_tokens = False,
    return_tensors = "pt",
).to(device="cuda", dtype=torch.bfloat16)

from transformers import TextStreamer
text_streamer = TextStreamer(processor, skip_prompt = True)
_ = model.generate(**inputs, streamer = text_streamer, max_new_tokens = 100)

मॉडल आउटपुट:

Greenstick fracture</s>

ग्राउंड ट्रुथ:

FRACTURE_CLASSES[formatted_data[400]["label"]]

आउटपुट:

'Greenstick fracture'

फिर से, भविष्यवाणी सही है और लेबल के पूरी तरह अनुरूप है।

इन जाँचों के साथ, हम कह सकते हैं कि हमारा Ministral 3 14B मॉडल बोन फ्रैक्चर X-ray डेटासेट पर सफलतापूर्वक फाइन-ट्यून हो गया है और अब निर्देश-प्रॉम्प्ट का पालन करते हुए इन उदाहरणों के लिए सटीक सिंगल-क्लास भविष्यवाणी दे सकता है।

अंतिम विचार

Ministral 3 को फाइन-ट्यून करना धैर्य और सतत समायोजन माँगता है। सच में, मुझे यह समझने में दो पूरे दिन लगे कि मॉडल अपेक्षित तरीके से ट्रेन क्यों नहीं हो रहा था और क्यों अपेक्षाकृत छोटा मॉडल होने के बावजूद यह मेरी GPU मेमोरी को खत्म कर रहा था। 

मुख्य कारण सरल था, पर अनदेखा करना आसान। यह सिर्फ एक लैंग्वेज मॉडल नहीं है। यह विज़न-लैंग्वेज मॉडल है, यानी इसमें एक इमेज एन्कोडर है जो अतिरिक्त मेमोरी उपयोग जोड़ता है। 

बड़ी इमेज रेज़ोल्यूशन सीधे-सीधे अधिक VRAM खपत की ओर ले जाती है। इमेज आकार कम करने या छोटी बैच साइज से शुरू करने से ट्रेनिंग कहीं अधिक स्थिर हो जाती है।

एक और समस्या जो आई, वह था डिफ़ॉल्ट चैट टेम्पलेट का उपयोग। इस तरह के कार्य के लिए वह बहुत लंबा और संरचित था। मॉडल टेम्पलेट फ़ॉर्मैटिंग को फिट करने में अधिक मेहनत कर रहा था, बजाय इसके कि वह इमेज पढ़ना और स्पष्ट फ्रैक्चर क्लासिफिकेशन देना सीखे। 

जैसे ही मैंने प्रॉम्प्ट को सीधे निर्देश प्रारूप में सरल किया, मॉडल ने अंततः दृश्य इनपुट पर सही से प्रतिक्रिया देना शुरू किया, न कि हर उत्तर को पूर्ण चैट प्रतिक्रिया की तरह गढ़ना।

मैंने यह भी मान लिया था कि चूँकि मॉडल की विज़न क्षमताएँ मजबूत हैं, यह स्वाभाविक रूप से मेडिकल स्कैन पर अच्छा प्रदर्शन करेगा। इस धारणा ने मुझे धीमा कर दिया। अपने मन में इसे “MedGemma” कहना मॉडल या प्रक्रिया के साथ न्याय नहीं था। यह बॉक्स से बाहर कोई विशेषीकृत मेडिकल मॉडल नहीं है, और बिना उचित प्रशिक्षण के उससे वैसा व्यवहार अपेक्षित करना मेरी गलती थी।

अंततः, मैंने हर बाधा पार की और जो सीखा उसे इस गाइड में बदला। उद्देश्य है कि आप वही भ्रम न झेलें और जल्दी सार्थक परिणाम पाएँ। 

“छोटी इमेज, सरल प्रॉम्प्ट और यथार्थवादी अपेक्षाओं के साथ, Ministral 3 को X-ray समझ के लिए कहीं अधिक सुगमता और विश्वसनीयता से फाइन-ट्यून किया जा सकता है।”

यदि आप फाइन-ट्यूनिंग में अधिक हाथ-पर अभ्यास करना चाहते हैं, तो मैं Fine-Tuning with Llama 3 कोर्स की सलाह देता/देती हूँ।

Abid Ali Awan

लेखक

Abid Ali Awan

एक प्रमाणित डेटा साइंटिस्ट के रूप में, मैं अत्याधुनिक तकनीक का उपयोग करके नवाचारी मशीन लर्निंग अनुप्रयोग बनाने के लिए उत्साहित रहता/रहती हूँ। स्पीच रिकॉग्निशन, डेटा विश्लेषण और रिपोर्टिंग, MLOps, संवादात्मक AI और NLP में मजबूत पृष्ठभूमि के साथ, मैंने ऐसे बुद्धिमान सिस्टम विकसित करने में अपनी विशेषज्ञता निखारी है जो वास्तविक प्रभाव डाल सकें। तकनीकी दक्षता के अलावा, मैं जटिल अवधारणाओं को स्पष्ट और संक्षिप्त भाषा में प्रस्तुत करने में भी निपुण हूँ। परिणामस्वरूप, मैं डेटा साइंस पर एक मांग में रहने वाला/वाली ब्लॉगर बन गया/गई हूँ, और डेटा प्रोफेशनलों के बढ़ते समुदाय के साथ अपने अनुभव और विचार साझा करता/करती हूँ। वर्तमान में, मैं कंटेंट निर्माण और संपादन पर केंद्रित हूँ, बड़े भाषा मॉडलों के साथ काम करते हुए ऐसा प्रभावशाली और आकर्षक कंटेंट विकसित कर रहा/रही हूँ जो व्यवसायों और व्यक्तियों दोनों को अपने डेटा का अधिकतम लाभ उठाने में मदद कर सके।

विषय