[Python] Keras multilabel training dataset completeness

Discussão em 'Python' iniciado por Stack, Julho 21, 2025.

  1. Stack

    Stack Membro Participativo

    I am attempting to train a multilabel classification model with Keras. The application will be in an industrial setting to classify defect images routinely captured during manufacturing. The expected output would be to correctly identify defect "A" or defect "B". However it is possible both defect "A" and "B" can occur together, hence the need for multilabel classification. (In reality there will be over 10 different defect classifications and multiple defects could theoretically be found in one image, if unlikely.)

    I am new to ML but have followed tutorials/guides and been able to produce a multi-classification (single label) model that works well identifying whether the defect is "A" or "B". So far, so good. Now I want a model that can also identify "A" and "B".

    As a test for the model development, my training dataset consists of "squares" (200 images with white squares with a random rotation and position on a black background) and "triangles" (also 200 images of white triangles of random size/rotation on a black background). I intentionally do not have training images with BOTH triangles and squares since I want to replicate the defect image training where I will not have a sufficient number of images with multiple defects. Intuitively, it seems this should not matter. All the model should do is tell me whether it thinks there is a square in the image, regardless if there is also a triangle.

    However I am struggling with getting a multilabel classification model working. I feel like I'm missing something more high-level rather than just needing to tweak my model parameters, for example. The model is not correctly identifying test images with both squares and triangles, though it works well for when there is one or the other. I followed the various online guides to be sure the model has:

    • sigmoid final activation
    • binary_crossentropy loss
    • Adam optimizer
    • onehot multilabels produced with MultiLabelBinarizer()

    I'm using the following module versions on a linux mint 22.1 system:

    • python 3.12.3
    • pandas 2.2.3
    • keras 3.10.0
    • numpy 2.3.1
    • tensorflow 2.19.0
    • scikit-learn 1.7.1
    • pillow 11.1.0
    • opencv-python 4.12.0.88

    NOTE:

    • Images are converted to grayscale (single channel) for the training since this will reflect the application to the defect images which are also grayscale. Bonus that it saves on memory usage.

    CODE with extraneous comments removed


    from PIL import ImageDraw, Image
    import numpy as np

    # SmallerVGGNet modules
    import pandas as pd
    from keras.models import Sequential
    from keras.layers import BatchNormalization
    from keras.layers import Conv2D
    from keras.layers import MaxPooling2D
    from keras.layers import Activation
    from keras.layers import Flatten
    from keras.layers import Dropout
    from keras.layers import Dense
    from keras import backend as K

    # training modules
    from tensorflow.keras.preprocessing.image import ImageDataGenerator
    from keras.optimizers import Adam
    from keras.preprocessing.image import img_to_array
    from sklearn.preprocessing import MultiLabelBinarizer
    from sklearn.model_selection import train_test_split
    #from pyimagesearch.smallervggnet import SmallerVGGNet
    import matplotlib.pyplot as plt
    from imutils import paths
    import tensorflow as tf
    import numpy as np
    import argparse
    import random
    import pickle
    import cv2
    import os

    tf.compat.v1.enable_eager_execution() # enable eager execution

    class SmallerVGGNet:
    @staticmethod
    def build(width, height, depth, classes, finalAct="softmax"):
    model = Sequential()
    inputShape = (height, width, depth)
    chanDim = -1
    if K.image_data_format() == "channels_first":
    inputShape = (depth, height, width)
    chanDim = 1
    model.add(Conv2D(64, (2, 2), padding="same", input_shape=inputShape))
    model.add(Activation("relu"))
    model.add(MaxPooling2D(pool_size=(2, 2)))

    model.add(Conv2D(64, (2, 2), padding="same"))
    model.add(Activation("relu"))
    model.add(MaxPooling2D(pool_size=(2, 2)))

    model.add(Conv2D(128, (2, 2), padding="same"))
    model.add(Activation("relu"))
    model.add(MaxPooling2D(pool_size=(2, 2)))

    model.add(Conv2D(128, (2, 2), padding="same"))
    model.add(Activation("relu"))
    model.add(MaxPooling2D(pool_size=(2, 2)))

    # first (and only) set of FC => RELU layers
    model.add(Flatten())
    model.add(Dense(256))
    model.add(Activation("relu"))
    #model.add(BatchNormalization())
    model.add(Dropout(0.5))
    model.add(Dense(classes))
    model.add(Activation(finalAct))
    return model

    # initialize the number of epochs to train for, initial learning rate, batch size, and image dimensions
    EPOCHS = 50
    BS = 32
    IMAGE_DIMS = (200, 200, 1) # 1 channel for grayscale

    # initialize the data and labels

    data = []
    labels = []

    print("[INFO] loading images...")
    for item in ['squares','triangles']:
    image_path = f"Train/{item}"
    imagePaths = sorted(list(paths.list_images(image_path)))
    for imagePath in imagePaths:
    # load the image, pre-process it, and store it in the data list
    image = cv2.imread(imagePath, 0) # 0 reads as grayscale
    image = img_to_array(image)
    data.append(image)
    labels.append([item])

    # scale the raw pixel intensities to the range [0, 1]
    data = np.array(data, dtype="float") / 255.0
    labels = np.array(labels)
    print("[INFO] data matrix: {} images ({:.2f}MB)".format(len(data), data.nbytes / (1024 * 1000.0)))

    # binarize the labels using scikit-learn's special multi-label binarizer implementation
    print("[INFO] class labels:")
    mlb = MultiLabelBinarizer()
    labels = mlb.fit_transform(labels)
    # loop over each of the possible class labels and show them
    for (i, label) in enumerate(mlb.classes_):
    print("{}. {}".format(i + 1, label))

    # partition the data into training and testing splits using 80% of the data for training and the remaining 20% for testing
    (trainX, testX, trainY, testY) = train_test_split(data, labels, test_size=0.2, random_state=42)
    # construct the image generator for data augmentation
    aug = ImageDataGenerator(rotation_range=25, width_shift_range=0.1,
    height_shift_range=0.1, shear_range=0.2, zoom_range=0.2,
    horizontal_flip=True, fill_mode="nearest")

    print("[INFO] compiling model...")
    model = SmallerVGGNet.build(width=IMAGE_DIMS[1], height=IMAGE_DIMS[0],
    depth=IMAGE_DIMS[2], classes=len(mlb.classes_), finalAct="sigmoid")
    model.compile(loss="binary_crossentropy", optimizer=Adam(), metrics=["accuracy"])

    # train the network
    print("[INFO] training network...")
    H = model.fit(
    x=aug.flow(trainX, trainY, batch_size=BS),
    validation_data=(testX, testY),
    steps_per_epoch=len(trainX) // BS,
    epochs=EPOCHS, verbose=1)

    model.save("model_multilabel_gray.keras")
    with open('history_gray.pkl', 'wb') as file:
    pickle.dump(H.history, file)
    with open('classes.pkl', 'wb') as file:
    pickle.dump(mlb.classes_, file)


    I have tried changing various parameters in the model, but the resulting model only finds one of the shapes in the image (only the triangle but not the square when both shapes are present.)

    Continue reading...

Compartilhe esta Página