You are a Python expert who can provide clear, concise, high-quality code.
"
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
import cv2
import random
from keras.models import load_model
import tensorflow as tf
from tensorflow.keras.optimizers import Adam, SGD, RMSprop, AdamW, Adadelta, Adagrad, Adamax, Adafactor, Nadam, Ftrl
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tqdm import tqdm
import os
from sklearn.utils import shuffle
from sklearn.model_selection import train_test_split
from tensorflow.keras.models import Sequential, Model
from tensorflow.keras.layers import GlobalAveragePooling2D, Dropout, Dense, Conv2D, MaxPooling2D, Flatten, Dropout, BatchNormalization, Activation, concatenate, Conv2DTranspose, Input, Reshape, UpSampling2D
from tensorflow.python.ops.gen_nn_ops import FusedBatchNormGradV2
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, TensorBoard, ModelCheckpoint
from sklearn.metrics import classification_report, confusion_matrix
import ipywidgets as widgets
import io
from PIL import Image
from IPython.display import display, clear_output
from warnings import filterwarnings
from google.colab import drive

drive.mount('/content/gdrive')
# Set image size
image_size = 224

# Define labels
labels = ['bridge', 'excess', 'good']

# Create data arrays
X_train = []  # Train data
y_train_class = []  # Train classification label
y_train_seg = []  # Train segmentation label

X_val = []  # Validation data
y_val_class = []  # Validation classification label
y_val_seg = []  # Validation segmentation label

X_test = []  # Test data
y_test_class = []  # Test classification label
y_test_seg = []  # Test segmentation label

# Load training data
for label in labels:
    folderPath = os.path.join('/content/gdrive/MyDrive/FYP/5 Dataset Ratio 20 20 60 TP 10 to 90/jit012_p0.1-0.9/jit0_p0.5/f_dip/train', label)
    for filename in tqdm(os.listdir(folderPath)):
        if filename.endswith('.jpg'):
            img = cv2.imread(os.path.join(folderPath, filename))
            img = cv2.resize(img, (image_size, image_size))
            X_train.append(img)
            y_train_class.append(label)
            seg_filename = filename.split('.')[0] + '.png'
            seg_img = cv2.imread(os.path.join(folderPath, seg_filename), 0)
            seg_img = cv2.resize(seg_img, (image_size, image_size))
            seg_img = np.where(seg_img > 0, 1, 0)  # Convert segmentation mask to binary
            y_train_seg.append(seg_img)

# Load validation data
for label in labels:
    folderPath = os.path.join('/content/gdrive/MyDrive/FYP/5 Dataset Ratio 20 20 60 TP 10 to 90/jit012_p0.1-0.9/jit0_p0.5/f_dip/val', label)
    for filename in tqdm(os.listdir(folderPath)):
        if filename.endswith('.jpg'):
            img = cv2.imread(os.path.join(folderPath, filename))
            img = cv2.resize(img, (image_size, image_size))
            X_val.append(img)
            y_val_class.append(label)
            seg_filename = filename.split('.')[0] + '.png'
            seg_img = cv2.imread(os.path.join(folderPath, seg_filename), 0)
            seg_img = cv2.resize(seg_img, (image_size, image_size))
            seg_img = np.where(seg_img > 0, 1, 0)  # Convert segmentation mask to binary
            y_val_seg.append(seg_img)

# Load test data
for label in labels:
    folderPath = os.path.join('/content/gdrive/MyDrive/FYP/5 Dataset Ratio 20 20 60 TP 10 to 90/jit012_p0.1-0.9/jit0_p0.5/f_dip/test', label)
    for filename in tqdm(os.listdir(folderPath)):
        if filename.endswith('.jpg'):
            img = cv2.imread(os.path.join(folderPath, filename))
            img = cv2.resize(img, (image_size, image_size))
            X_test.append(img)
            y_test_class.append(label)
            seg_filename = filename.split('.')[0] + '.png'
            seg_img = cv2.imread(os.path.join(folderPath, seg_filename), 0)
            seg_img = cv2.resize(seg_img, (image_size, image_size))
            seg_img = np.where(seg_img > 0, 1, 0)  # Convert segmentation mask to binary
            y_test_seg.append(seg_img)
# Convert to numpy arrays
X_train = np.array(X_train)
y_train_class = np.array(y_train_class)
y_train_seg = np.array(y_train_seg)

X_val = np.array(X_val)
y_val_class = np.array(y_val_class)
y_val_seg = np.array(y_val_seg)

X_test = np.array(X_test)
y_test_class = np.array(y_test_class)
y_test_seg = np.array(y_test_seg)

# Shuffle the data
X_train, y_train_class, y_train_seg = shuffle(X_train, y_train_class, y_train_seg, random_state=101)

# One Hot Encoding for classification labels
y_train_class_new = []
for label in y_train_class:
    y_train_class_new.append(labels.index(label))
y_train_class = y_train_class_new
y_train_class = tf.keras.utils.to_categorical(y_train_class)

y_val_class_new = []
for label in y_val_class:
    y_val_class_new.append(labels.index(label))
y_val_class = y_val_class_new
y_val_class = tf.keras.utils.to_categorical(y_val_class)

y_test_class_new = []
for label in y_test_class:
    y_test_class_new.append(labels.index(label))
y_test_class = y_test_class_new
y_test_class = tf.keras.utils.to_categorical(y_test_class)

# Print the counts
print("Number of train images:", len(X_train))
print("Number of train binary masks:", len(y_train_seg))
print("Number of validation images:", len(X_val))
print("Number of validation binary masks:", len(y_val_seg))
print("Number of test images:", len(X_test))
print("Number of test binary masks:", len(y_test_seg))
# Define U-Net model
input_shape = (image_size, image_size, 3)
num_classes = len(labels)
inputs = Input(input_shape)

num_filter = 32 # 16/32 best, 8: best classification but no segment

# Encoder (Done)
conv1 = Conv2D(num_filter * 1, 3, activation='linear', padding='same', strides=1)(inputs)
bn1 = BatchNormalization()(conv1)
relu1 = Activation('relu')(bn1)
conv2 = Conv2D(num_filter * 1, 3, activation='linear', padding='same', strides=1)(relu1)
bn2 = BatchNormalization()(conv2)
relu2 = Activation('relu')(bn2)

down1 = MaxPooling2D(pool_size=(2, 2), strides=2)(relu2)
conv3 = Conv2D(num_filter * 2, 3, activation='linear', padding='same', strides=1)(down1)
bn3 = BatchNormalization()(conv3)
relu3 = Activation('relu')(bn3)
conv4 = Conv2D(num_filter * 2, 3, activation='linear', padding='same', strides=1)(relu3)
bn4 = BatchNormalization()(conv4)
relu4 = Activation('relu')(bn4)

down2 = MaxPooling2D(pool_size=(2, 2), strides=2)(relu4)
conv5 = Conv2D(num_filter * 4, 3, activation='linear', padding='same', strides=1)(down2)
bn5 = BatchNormalization()(conv5)
relu5 = Activation('relu')(bn5)
conv6 = Conv2D(num_filter * 4, 3, activation='linear', padding='same', strides=1)(relu5)
bn6 = BatchNormalization()(conv6)
relu6 = Activation('relu')(bn6)

down3 = MaxPooling2D(pool_size=(2, 2), strides=2)(relu6)
conv7 = Conv2D(num_filter * 8, 3, activation='linear', padding='same', strides=1)(down3)
bn7 = BatchNormalization()(conv7)
relu7 = Activation('relu')(bn7)
conv8 = Conv2D(num_filter * 8, 3, activation='linear', padding='same', strides=1)(relu7)
bn8 = BatchNormalization()(conv8)
relu8 = Activation('relu')(bn8)

# Middle
down4 = MaxPooling2D(pool_size=(2, 2), strides=2)(relu8)
conv9 = Conv2D(num_filter * 16, 3, activation='linear', padding='same', strides=1)(down4)
bn9 = BatchNormalization()(conv9)
relu9 = Activation('relu')(bn9)
conv10 = Conv2D(num_filter * 16, 3, activation='linear', padding='same', strides=1)(relu9)
bn10 = BatchNormalization()(conv10)
relu10 = Activation('relu')(bn10)
up1 = UpSampling2D(size=(2, 2), interpolation='bilinear')(relu10)

# Decoder (Done)
concat1 = concatenate([up1, relu8], axis=-1) #, axis=3
conv11 = Conv2D(num_filter * 8, 3, activation='linear', padding='same', strides=1)(concat1)
bn11 = BatchNormalization()(conv11)
relu11 = Activation('relu')(bn11)
conv12 = Conv2D(num_filter * 8, 3, activation='linear', padding='same', strides=1)(relu11)
bn12 = BatchNormalization()(conv12)
relu12 = Activation('relu')(bn12)
up2 = UpSampling2D(size=(2, 2), interpolation='bilinear')(relu12)

concat2 = concatenate([up2, relu6], axis=-1) #, axis=3
conv13 = Conv2D(num_filter * 4, 3, activation='linear', padding='same', strides=1)(concat2)
bn13 = BatchNormalization()(conv13)
relu13 = Activation('relu')(bn13)
conv14 = Conv2D(num_filter * 4, 3, activation='linear', padding='same', strides=1)(relu13)
bn14 = BatchNormalization()(conv14)
relu14 = Activation('relu')(bn14)
up3 = UpSampling2D(size=(2, 2), interpolation='bilinear')(relu14)

concat3 = concatenate([up3, relu4], axis=-1) #, axis=3
conv15 = Conv2D(num_filter * 2, 3, activation='linear', padding='same', strides=1)(concat3)
bn15 = BatchNormalization()(conv15)
relu15 = Activation('relu')(bn15)
conv16 = Conv2D(num_filter * 2, 3, activation='linear', padding='same', strides=1)(relu15)
bn16 = BatchNormalization()(conv16)
relu16 = Activation('relu')(bn16)
up4 = UpSampling2D(size=(2, 2), interpolation='bilinear')(relu16)

concat4 = concatenate([up4, relu2], axis=-1) #, axis=3
conv17 = Conv2D(num_filter * 1, 3, activation='linear', padding='same', strides=1)(concat4)
bn17 = BatchNormalization()(conv17)
relu17 = Activation('relu')(bn17)
conv18 = Conv2D(num_filter * 1, 3, activation='linear', padding='same', strides=1)(relu17)
bn18 = BatchNormalization()(conv18)
relu18 = Activation('relu')(bn18)

# Segmentation branch
segmentation_output = Conv2D(1, 1, activation='sigmoid', name='segmentation_output')(relu18) #original

# Classification branch (Not done)
gap1 = GlobalAveragePooling2D()(relu8)
gap2 = GlobalAveragePooling2D()(relu10)
gap3 = GlobalAveragePooling2D()(relu12)

conv20 = Conv2D(16, 3, activation='linear', padding='same', strides=1)(segmentation_output)
bn20 = BatchNormalization()(conv20)
relu20 = Activation('relu')(bn20)
down5 = MaxPooling2D(pool_size=(4, 4), strides=4)(relu20)

conv21 = Conv2D(32, 3, activation='linear', padding='same', strides=1)(down5) 
bn21 = BatchNormalization()(conv21)
relu21 = Activation('relu')(bn21)
down6 = MaxPooling2D(pool_size=(4, 4), strides=4)(relu21)

conv22 = Conv2D(64, 3, activation='linear', padding='same', strides=1)(down6)
bn22 = BatchNormalization()(conv22)
relu22 = Activation('relu')(bn22)
down7 = MaxPooling2D(pool_size=(4, 4), strides=4)(relu22)
flatten1 = Flatten()(down7)
concat5 = concatenate([gap1, gap2, gap3, flatten1], axis = -1) # axis = -1

# FC layers
fc1 = Dense(1024, activation='relu')(concat5) # 512, 256 match with num_filter = 8
dropout1 = Dropout(0.5)(fc1)
fc2 = Dense(1024, activation='relu')(dropout1) # 512,256
dropout2 = Dropout(0.5)(fc2)
classification_output = Dense(num_classes, activation='softmax', name='classification_output')(dropout2)

# Define the model
model = Model(inputs=inputs, outputs=[classification_output, segmentation_output])
model.summary()
# Compile model
def segmentation_loss(y_true, y_pred):
    y_true = tf.cast(y_true, tf.float32)
    y_pred = tf.cast(y_pred, tf.float32)
    # Binary cross-entropy loss
    bce_loss = tf.keras.losses.binary_crossentropy(y_true, y_pred)
    # Dice loss
    smooth = 1e-6
    intersection = tf.reduce_sum(y_true * y_pred)
    union = tf.reduce_sum(y_true) + tf.reduce_sum(y_pred)
    dice_loss = 1.0 - 2.0 * (intersection + smooth) / (union + smooth)
    # Combine losses
    segmentation_loss = bce_loss + 1 * dice_loss
    return segmentation_loss

model.compile(optimizer=Adam(lr=0.001),
              loss={'classification_output': 'categorical_crossentropy', 'segmentation_output': segmentation_loss}, # binary_crossentropy
              metrics={'classification_output': 'accuracy', 'segmentation_output': 'accuracy'},
              loss_weights={'classification_output': 1, 'segmentation_output': 1})
# Define the callbacks
checkpoint = ModelCheckpoint("multitask_best_weights.h5", monitor="val_classification_output_accuracy", save_best_only=True, mode="max", verbose=1)
reduce_lr = ReduceLROnPlateau(monitor="val_classification_output_accuracy", factor=0.3, patience=2, min_delta=0.001, mode='auto', verbose=1)
tensorboard = TensorBoard(log_dir='logs')
history = model.fit(X_train, {'classification_output': y_train_class, 'segmentation_output': y_train_seg}, validation_data=(X_val, {'classification_output': y_val_class, 'segmentation_output': y_val_seg}), epochs=50, verbose=1, batch_size=16, callbacks=[checkpoint,reduce_lr,tensorboard]) # reduce_lr
    # Evaluate the model
    with tf.keras.utils.custom_object_scope({'segmentation_loss': segmentation_loss}):
    # Load the model
        best_model = load_model('multitask_best_weights.h5')

        # Evaluate the model on test data
        test_loss, test_class_loss, test_seg_loss, test_class_acc, test_seg_acc = best_model.evaluate(X_test, {'classification_output': y_test_class, 'segmentation_output': y_test_seg})
        print("Test Classification Loss:", test_class_loss)
        print("Test Segmentation Loss:", test_seg_loss)
        print("Test Classification Accuracy:", test_class_acc)
        print("Test Segmentation Accuracy:", test_seg_acc)

        # Evaluate the model on validation data
        val_loss, val_class_loss, val_seg_loss, val_class_acc, val_seg_acc = best_model.evaluate(X_val, {'classification_output': y_val_class, 'segmentation_output': y_val_seg})
        print("Validation Classification Loss:", val_class_loss)
        print("Validation Segmentation Loss:", val_seg_loss)
        print("Validation Classification Accuracy:", val_class_acc)
        print("Validation Segmentation Accuracy:", val_seg_acc)

        # Evaluate the model on training data
        train_loss, train_class_loss, train_seg_loss, train_class_acc, train_seg_acc = best_model.evaluate(X_train, {'classification_output': y_train_class, 'segmentation_output': y_train_seg})
        print("Train Classification Loss:", train_class_loss)
        print("Train Segmentation Loss:", train_seg_loss)
        print("Train Classification Accuracy:", train_class_acc)
        print("Train Segmentation Accuracy:", train_seg_acc)

    # Plot performance
    classification_train_accuracy = history.history['classification_output_accuracy']
    classification_train_loss = history.history['classification_output_loss']
    classification_val_accuracy = history.history['val_classification_output_accuracy']
    classification_val_loss = history.history['val_classification_output_loss']
    segmentation_train_accuracy = history.history['segmentation_output_accuracy']
    segmentation_train_loss = history.history['segmentation_output_loss']
    segmentation_val_accuracy = history.history['val_segmentation_output_accuracy']
    segmentation_val_loss = history.history['val_segmentation_output_loss']

    # Plot classification accuracy
    plt.figure(figsize=(7, 3))
    plt.plot(classification_train_accuracy, label='Training Accuracy')
    plt.plot(classification_val_accuracy, label='Validation Accuracy')
    plt.title('Classification Accuracy')
    plt.xlabel('Epochs')
    plt.ylabel('Accuracy')
    plt.legend()
    plt.show()

    # Plot segmentation accuracy
    plt.figure(figsize=(7, 3))
    plt.plot(segmentation_train_accuracy, label='Training Accuracy')
    plt.plot(segmentation_val_accuracy, label='Validation Accuracy')
    plt.title('Segmentation Accuracy')
    plt.xlabel('Epochs')
    plt.ylabel('Accuracy')
    plt.legend()
    plt.show()

    # Plot classification loss
    plt.figure(figsize=(7, 3))
    plt.plot(classification_train_loss,'b', label='Training Loss')
    plt.plot(classification_val_loss,'r', label='Validation Loss')
    plt.title('Classification Loss')
    plt.xlabel('Epochs')
    plt.ylabel('Loss')
    plt.legend()
    plt.show()

    # Plot segmentation loss
    plt.figure(figsize=(7, 3))
    plt.plot(segmentation_train_loss,'b', label='Training Loss')
    plt.plot(segmentation_val_loss,'r', label='Validation Loss')
    plt.title('Segmentation Loss')
    plt.xlabel('Epochs')
    plt.ylabel('Loss')
    plt.legend()
    plt.show()
"

The above is the Python code with Keras to do multi-task learning with binary segmentation and classification using one mult-task learning model. Modify the code so that it can run 5 independent runs to train the model and take the average test accuracies of the 5 runs. Also, remove the save best model function.