2025, Oct 07 23:34
Keras मल्टी-आउटपुट इमेज क्लासिफायर में y_true/y_pred संरचना असंगति का समाधान
जानें क्यों Keras मल्टी-आउटपुट इमेज क्लासिफायर में y_true और y_pred की संरचना असंगति होती है और इसे कैसे ठीक करें: लेबल वेक्टर को आउटपुट हेड dict में बदलें.
Keras में मल्टी-आउटपुट इमेज क्लासिफायर को ट्रेन करते समय एक अप्रत्याशित असंगति सामने आ सकती है: मॉडल आउटपुट्स की एक सूची देता है, जबकि आपका डेटासेट प्रति सैंपल एक ही वेक्टर लौटाता है। नतीजा — संरचना का मेल न बैठना, जो ट्रेनिंग रोक देता है। नीचे स्पष्ट रूप से समझाया गया है कि ऐसा क्यों होता है और लेबल्स को कैसे व्यवस्थित करें, ताकि Keras हर आउटपुट हेड के साथ उन्हें ठीक से मिला सके।
लक्षण
ट्रेनिंग शुरू करते ही प्रक्रिया इस त्रुटि के साथ रुक जाती है:
ValueError: y_true and y_pred have different structures.
y_true: *
y_pred: ['*', '*', '*', '*']
समस्या का सेटअप: न्यूनतम उदाहरण
डेटासेट हर छवि के लिए एक इमेज और श्रेणीबद्ध लक्ष्यों का 4-तत्वों वाला वेक्टर देता है। मॉडल में चार हेड हैं और हर हेड के लिए SparseCategoricalCrossentropy का उपयोग होता है।
def fetch_targets(p):
    p = p.numpy().decode("utf-8")
    k = os.path.basename(p)[:9]
    if k not in target_map:
        print("Missing key:", k)
        raise ValueError("Missing label key.")
    return tf.convert_to_tensor(target_map[k], dtype=tf.uint8)
def load_frame(p):
    raw = tf.io.read_file(p)
    pic = tf.io.decode_jpeg(raw, channels=3)
    return tf.image.resize_with_crop_or_pad(pic, 360, 360)
def make_sample(file_p):
    y = tf.py_function(func=fetch_targets, inp=[file_p], Tout=tf.uint8)
    y.set_shape([4])
    x = tf.py_function(func=load_frame, inp=[file_p], Tout=tf.uint8)
    x.set_shape([360, 360, 3])
    return x, y
# target_map की उदाहरण सामग्री
# 'Img_00001': [0, 1, 0, 1], 'Img_00002': [2, 0, 4, 1], 'Img_00003': [2, 0, 1, 0],
# 'Img_00004': [4, 1, 2, 1], 'Img_00005': [3, 1, 3, 1], 'Img_00006': [1, 1, 5, 1]
split_count = int(file_list_ds.cardinality().numpy() * 0.2)
train_ds = file_list_ds \
  .skip(split_count) \
  .map(make_sample, num_parallel_calls=tf.data.AUTOTUNE) \
  .cache() \
  .batch(100) \
  .prefetch(buffer_size=tf.data.AUTOTUNE)
val_ds = file_list_ds \
  .take(split_count) \
  .map(make_sample, num_parallel_calls=tf.data.AUTOTUNE) \
  .cache() \
  .batch(100) \
  .prefetch(buffer_size=tf.data.AUTOTUNE)
input_tensor = tf.keras.layers.Input(shape=(360, 360, 3))
feats = tf.keras.layers.Rescaling(1./255)(input_tensor)
feats = tf.keras.layers.Conv2D(32, (3, 3), activation='relu', padding='same')(feats)
feats = tf.keras.layers.MaxPooling2D((2, 2))(feats)
feats = tf.keras.layers.Conv2D(64, (3, 3), activation='relu', padding='same')(feats)
feats = tf.keras.layers.MaxPooling2D((2, 2))(feats)
feats = tf.keras.layers.Conv2D(128, (3, 3), activation='relu', padding='same')(feats)
feats = tf.keras.layers.Flatten()(feats)
feats = tf.keras.layers.Dense(128, activation='relu')(feats)
head_label = tf.keras.layers.Dense(len(label_classes))(feats)
head_cellshape = tf.keras.layers.Dense(len(cellshape_classes))(feats)
head_nucleusshape = tf.keras.layers.Dense(len(nucleusshape_classes))(feats)
head_cytovacuole = tf.keras.layers.Dense(len(cytovacuole_classes))(feats)
net = tf.keras.Model(
    inputs=input_tensor,
    outputs=[head_label, head_cellshape, head_nucleusshape, head_cytovacuole]
)
net.compile(
  optimizer=tf.keras.optimizers.Adam(),
  loss={
    "head_label": tf.keras.losses.SparseCategoricalCrossentropy(),
    "head_cellshape": tf.keras.losses.SparseCategoricalCrossentropy(),
    "head_nucleusshape": tf.keras.losses.SparseCategoricalCrossentropy(),
    "head_cytovacuole": tf.keras.losses.SparseCategoricalCrossentropy()
  },
  metrics={
    "head_label": ["sparse_categorical_accuracy"],
    "head_cellshape": ["sparse_categorical_accuracy"],
    "head_nucleusshape": ["sparse_categorical_accuracy"],
    "head_cytovacuole": ["sparse_categorical_accuracy"]
  }
)
history = net.fit(
  train_ds,
  validation_data=val_ds,
  epochs=10,
  batch_size=100,
  validation_steps=1
)
त्रुटि का कारण क्या है
मॉडल चार आउटपुट देता है—हर क्लासिफिकेशन हेड के लिए एक। Keras चाहता है कि लक्ष्य (targets) की संरचना भविष्यवाणियों की संरचना का प्रतिबिंब हो। चार अलग-अलग लक्ष्यों (हर हेड के लिए एक) की जगह डेटासेट एक ही वेक्टर में चार पूर्णांक देता है। यही असंगति “different structures” त्रुटि को जन्म देती है, क्योंकि Keras हर loss फ़ंक्शन को उसके संबंधित लक्ष्य हिस्से से जोड़ नहीं पाता।
समाधान: आउटपुट नामों को कुंजियों के रूप में लेकर लेबल का dict दें
हर सैंपल एक मैपिंग लौटाए: आउटपुट नाम से उसकी क्लास इंडेक्स तक। इससे फ्रेमवर्क y_true के हर हिस्से को सही हेड और loss तक पहुंचा पाता है। एक सिंगल-सैंपल का लक्ष्य इस तरह होना चाहिए:
example_target = {
    "head_label": tf.Tensor([0]),
    "head_cellshape": tf.Tensor([1]),
    "head_nucleusshape": tf.Tensor([0]),
    "head_cytovacuole": tf.Tensor([1]),
}
और 5 के बैच के लिए:
example_batch = {
    "head_label": tf.Tensor([0, 2, 3, 1, 0]),
    "head_cellshape": tf.Tensor([3, 2, 3, 2, 0]),
    "head_nucleusshape": tf.Tensor([2, 2, 3, 4, 1]),
    "head_cytovacuole": tf.Tensor([1, 2, 3, 1, 1]),
}
इनपुट पाइपलाइन में एकमात्र बदलाव यह है कि 4-तत्वों वाले वेक्टर को मॉडल के आउटपुट नामों से कुंजीबद्ध dict में बदला जाए।
सुधारे गए लेबल्स के साथ कार्यशील पाइपलाइन
नीचे समायोजित सैंपल-तैयारी फ़ंक्शन दिया है; बाकी फ्लो वही रहता है। मॉडल और compile वाले हिस्से समस्या सेटअप जैसे ही हैं—केवल लेबल्स की संरचना अलग है।
def fetch_targets(p):
    p = p.numpy().decode("utf-8")
    k = os.path.basename(p)[:9]
    if k not in target_map:
        print("Missing key:", k)
        raise ValueError("Missing label key.")
    return tf.convert_to_tensor(target_map[k], dtype=tf.uint8)
def load_frame(p):
    raw = tf.io.read_file(p)
    pic = tf.io.decode_jpeg(raw, channels=3)
    return tf.image.resize_with_crop_or_pad(pic, 360, 360)
def make_sample(file_p):
    y_vec = tf.py_function(func=fetch_targets, inp=[file_p], Tout=tf.uint8)
    y_vec.set_shape([4])
    x = tf.py_function(func=load_frame, inp=[file_p], Tout=tf.uint8)
    x.set_shape([360, 360, 3])
    y_dict = {
        "head_label": y_vec[0],
        "head_cellshape": y_vec[1],
        "head_nucleusshape": y_vec[2],
        "head_cytovacuole": y_vec[3],
    }
    return x, y_dict
split_count = int(file_list_ds.cardinality().numpy() * 0.2)
train_ds = file_list_ds \
  .skip(split_count) \
  .map(make_sample, num_parallel_calls=tf.data.AUTOTUNE) \
  .cache() \
  .batch(100) \
  .prefetch(buffer_size=tf.data.AUTOTUNE)
val_ds = file_list_ds \
  .take(split_count) \
  .map(make_sample, num_parallel_calls=tf.data.AUTOTUNE) \
  .cache() \
  .batch(100) \
  .prefetch(buffer_size=tf.data.AUTOTUNE)
input_tensor = tf.keras.layers.Input(shape=(360, 360, 3))
feats = tf.keras.layers.Rescaling(1./255)(input_tensor)
feats = tf.keras.layers.Conv2D(32, (3, 3), activation='relu', padding='same')(feats)
feats = tf.keras.layers.MaxPooling2D((2, 2))(feats)
feats = tf.keras.layers.Conv2D(64, (3, 3), activation='relu', padding='same')(feats)
feats = tf.keras.layers.MaxPooling2D((2, 2))(feats)
feats = tf.keras.layers.Conv2D(128, (3, 3), activation='relu', padding='same')(feats)
feats = tf.keras.layers.Flatten()(feats)
feats = tf.keras.layers.Dense(128, activation='relu')(feats)
head_label = tf.keras.layers.Dense(len(label_classes))(feats)
head_cellshape = tf.keras.layers.Dense(len(cellshape_classes))(feats)
head_nucleusshape = tf.keras.layers.Dense(len(nucleusshape_classes))(feats)
head_cytovacuole = tf.keras.layers.Dense(len(cytovacuole_classes))(feats)
net = tf.keras.Model(
    inputs=input_tensor,
    outputs=[head_label, head_cellshape, head_nucleusshape, head_cytovacuole]
)
net.compile(
  optimizer=tf.keras.optimizers.Adam(),
  loss={
    "head_label": tf.keras.losses.SparseCategoricalCrossentropy(),
    "head_cellshape": tf.keras.losses.SparseCategoricalCrossentropy(),
    "head_nucleusshape": tf.keras.losses.SparseCategoricalCrossentropy(),
    "head_cytovacuole": tf.keras.losses.SparseCategoricalCrossentropy()
  },
  metrics={
    "head_label": ["sparse_categorical_accuracy"],
    "head_cellshape": ["sparse_categorical_accuracy"],
    "head_nucleusshape": ["sparse_categorical_accuracy"],
    "head_cytovacuole": ["sparse_categorical_accuracy"]
  }
)
history = net.fit(
  train_ds,
  validation_data=val_ds,
  epochs=10,
  batch_size=100,
  validation_steps=1
)
यह क्यों महत्वपूर्ण है
मल्टी-हेड मॉडल आउटपुट्स, लॉसेस और टार्गेट्स के एक-से-एक मैपिंग पर निर्भर करते हैं। अगर डेटासेट सारे टार्गेट्स को एक ही वेक्टर में समेट देता है, तो फ्रेमवर्क यह नहीं समझ पाता कि किस हिस्से को किस हेड से जोड़ना है। संरचनाओं को संरेखित रखने से पेचीदा ट्रेनिंग बग्स से बचाव होता है, मेट्रिक्स सही हेड्स से जुड़े रहते हैं, और विफलता के कारण तुरंत समझ में आते हैं।
मुख्य निष्कर्ष
अपने डेटासेट के लेबल्स में मॉडल के आउटपुट की संरचना का प्रतिबिंब रखें। अलग-अलग SparseCategoricalCrossentropy लॉस के साथ मल्टी-आउटपुट क्लासिफिकेशन में, उन टार्गेट्स का dict पास करें जिनकी कुंजियाँ मॉडल के हेड नामों से मेल खाती हों। यही छोटा सा बदलाव संरचना के असंगति वाले एरर को खत्म करता है और Keras को हर y_true को उसके संबंधित y_pred और loss फ़ंक्शन से बिना अटकलों के जोड़ने देता है।