From bb28055a50b817b581fb8c4cb909dfa247fb005e Mon Sep 17 00:00:00 2001 From: Zeynep AKI <73976500+zeynepaki@users.noreply.github.com> Date: Tue, 26 Mar 2024 12:39:52 +0000 Subject: [PATCH 1/5] bugfixing condenser.py --- pre-preparation/condenser.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/pre-preparation/condenser.py b/pre-preparation/condenser.py index 2caef17..9e9ea3a 100644 --- a/pre-preparation/condenser.py +++ b/pre-preparation/condenser.py @@ -39,25 +39,31 @@ def condenser(spreadsheets, with_images): # Remove rows with all NaN values merged_data.dropna(how='all', axis=1, inplace=True) - if with_images: + if not with_images: + # If images are not requested, include rows with image paths containing 'images/wee/' and 'images/no-image2/' + df_without_images = pd.concat([merged_data[merged_data['image-id'].str.contains('images/wee/')], + merged_data[merged_data['image-id'].str.contains('images/no-image2/')]], + ignore_index=True) + + # Return the DataFrame without images + return df_without_images + + else: # If images are requested, filter rows with image paths containing 'images/wee/' - df_with_images = merged_data[merged_data['image-id'].str.contains('images/wee/')] + df_with_images = merged_data[ + merged_data['image-id'].str.contains('images/wee/') & ~merged_data['image-id'].str.contains( + 'images/wee/Fig')] - # Extract a cleaned-up ID from the 'image-id' column - df_with_images['cleaned_id'] = df_with_images['image-id'].str.extract(r'images/wee/(\d+_\d+)\.jpg') + # Extract the cleaned ID and extension from the 'image-id' column + df_with_images[['cleaned_id', 'extension']] = df_with_images['image-id'].str.extract( + r'images/wee/(\d+_\d+)_?\d*\.(jpg|tif|jpeg)') - # Drop the original 'image-id' column - df_with_images.drop(columns=['image-id'], inplace=True) + # Drop the original 'image-id' column and the 'extension' column + df_with_images.drop(columns=['image-id', 'extension'], inplace=True) # Return the DataFrame with images return df_with_images - # If images are not requested, filter rows with image paths containing 'images/no-image2' - df_without_images = merged_data[merged_data['image-id'].str.contains('images/no-image2')] - - # Return the DataFrame without images - return df_without_images - if __name__ == "__main__": # Set up command-line argument parser From acfad388cb34d49d3ee03480f48b6a0fb97a7a8d Mon Sep 17 00:00:00 2001 From: Zeynep AKI <73976500+zeynepaki@users.noreply.github.com> Date: Tue, 26 Mar 2024 13:00:38 +0000 Subject: [PATCH 2/5] temportaty fix to the nan.png bug --- pre-preparation/condenser.py | 8 +++++--- pre-preparation/reformatter.py | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pre-preparation/condenser.py b/pre-preparation/condenser.py index 9e9ea3a..6f45680 100644 --- a/pre-preparation/condenser.py +++ b/pre-preparation/condenser.py @@ -41,8 +41,10 @@ def condenser(spreadsheets, with_images): if not with_images: # If images are not requested, include rows with image paths containing 'images/wee/' and 'images/no-image2/' - df_without_images = pd.concat([merged_data[merged_data['image-id'].str.contains('images/wee/')], - merged_data[merged_data['image-id'].str.contains('images/no-image2/')]], + df_without_images = pd.concat([merged_data[merged_data['image-id'].str.contains('images/wee/Fig')], + merged_data[merged_data['image-id'].str.contains('images/no-image2/')], + merged_data[merged_data['image-id'].str.contains('to')], + merged_data[merged_data['image-id'].str.contains('and')]], ignore_index=True) # Return the DataFrame without images @@ -60,7 +62,7 @@ def condenser(spreadsheets, with_images): # Drop the original 'image-id' column and the 'extension' column df_with_images.drop(columns=['image-id', 'extension'], inplace=True) - + df_with_images = df_with_images[df_with_images['cleaned_id'] != ''] # Return the DataFrame with images return df_with_images diff --git a/pre-preparation/reformatter.py b/pre-preparation/reformatter.py index d4e2986..d6ffece 100644 --- a/pre-preparation/reformatter.py +++ b/pre-preparation/reformatter.py @@ -44,6 +44,7 @@ def data_reformatter(df, training_data): formatted_df[tag] = filtered_df.apply(lambda row: 1 if tag in row.values else 0, axis=1) formatted_df = duplicate_cleanser(formatted_df) + formatted_df = formatted_df[formatted_df['image_id'] != 'nan.png'] return formatted_df From a465c16b70b3d39faba97a5581774380ac45adfd Mon Sep 17 00:00:00 2001 From: Zeynep AKI <73976500+zeynepaki@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:43:56 +0000 Subject: [PATCH 3/5] initial working code --- requirements.txt | 5 ++- training.py | 83 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 training.py diff --git a/requirements.txt b/requirements.txt index 2bfa3d5..4118243 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,6 @@ pandas~=2.2.1 openpyxl~=3.1.2 -pillow~=10.2.0 \ No newline at end of file +pillow~=10.2.0 +numpy~=1.26.4 +tensorflow~=2.16.1 +scikit-learn~=1.4.1.post1 \ No newline at end of file diff --git a/training.py b/training.py new file mode 100644 index 0000000..97b5890 --- /dev/null +++ b/training.py @@ -0,0 +1,83 @@ +import sys + +sys.path.append('pre-preparation') +import reformatter +import condenser +import os +import numpy as np +from tensorflow.keras.preprocessing.image import img_to_array, load_img +from tensorflow.keras.models import Sequential +from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout +from sklearn.model_selection import train_test_split + +# Load your dataset +data = condenser.condenser('pre-preparation/dataset/unformatted/spreadsheets/*.xlsx', with_images=True) +data = reformatter.data_reformatter(data, True) + +# Define image processing parameters +image_size = (128, 128) # Example size, adjust to your needs + + +# Function to load and preprocess an image +def preprocess_image(image_path, target_size): + image = load_img(image_path, target_size=target_size) + image = img_to_array(image) + image /= 255.0 # Normalize to [0, 1] + return image + + +# Assuming your images are in a folder named 'images' in the same directory as your CSV +base_image_path = 'pre-preparation/dataset/formatted/images/' + +# Initialize lists to hold processed images and their corresponding labels +images = [] +valid_labels = [] + +# Preprocess all images and collect valid labels +for index, row in data.iterrows(): + image_id = row['image_id'] + img_path = os.path.join(base_image_path, image_id) + if os.path.exists(img_path): # Check if the image file exists + img = preprocess_image(img_path, image_size) + images.append(img) + valid_labels.append(row.drop('image_id').values) # Append label data, excluding the image_id + print(f"Image {image_id} processed") + else: + print(f"Image {image_id} not found, skipping...") + +# Convert lists of images and labels to numpy arrays +images = np.array(images) +valid_labels = np.array(valid_labels) +images = np.array(images).astype('float32') +valid_labels = np.array(valid_labels).astype('float32') + +model = Sequential([ + Conv2D(32, (3, 3), activation='relu', input_shape=(128, 128, 3)), + # Adjust input_shape based on your image size and channels + MaxPooling2D((2, 2)), + Conv2D(64, (3, 3), activation='relu'), + MaxPooling2D((2, 2)), + Conv2D(128, (3, 3), activation='relu'), + MaxPooling2D((2, 2)), + Flatten(), + Dense(128, activation='relu'), + Dropout(0.5), + Dense(valid_labels.shape[1], activation='sigmoid') # The output layer with a sigmoid activation, one per label +]) + +model.compile(optimizer='adam', + loss='binary_crossentropy', + metrics=['accuracy']) + +print(model.summary()) + +# Split the data +X_train, X_val, y_train, y_val = train_test_split(images, valid_labels, test_size=0.2, random_state=42) + +# Train the model +history = model.fit(X_train, y_train, epochs=75, validation_data=(X_val, y_val)) +train_accuracy_percentage = history.history['accuracy'][-1] * 100 +validation_accuracy_percentage = history.history['val_accuracy'][-1] * 100 + +print("Train Accuracy:", train_accuracy_percentage, "%") +print("Validation Accuracy:", validation_accuracy_percentage, "%") From e720ac27bfd87c80a0c6954a28c4b67132e5011f Mon Sep 17 00:00:00 2001 From: Zeynep AKI <73976500+zeynepaki@users.noreply.github.com> Date: Sat, 26 Oct 2024 12:59:02 +0100 Subject: [PATCH 4/5] switched to a pre-train model implementation --- training.py | 71 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/training.py b/training.py index 97b5890..d678bfa 100644 --- a/training.py +++ b/training.py @@ -1,35 +1,36 @@ import sys - -sys.path.append('pre-preparation') -import reformatter -import condenser import os import numpy as np from tensorflow.keras.preprocessing.image import img_to_array, load_img from tensorflow.keras.models import Sequential -from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout +from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout, GlobalAveragePooling2D +from tensorflow.keras.applications import VGG16 +from tensorflow.keras.optimizers import Adam from sklearn.model_selection import train_test_split +sys.path.append('pre-preparation') +import reformatter +import condenser + # Load your dataset data = condenser.condenser('pre-preparation/dataset/unformatted/spreadsheets/*.xlsx', with_images=True) data = reformatter.data_reformatter(data, True) # Define image processing parameters -image_size = (128, 128) # Example size, adjust to your needs +image_size = (128, 128) # Function to load and preprocess an image def preprocess_image(image_path, target_size): image = load_img(image_path, target_size=target_size) - image = img_to_array(image) - image /= 255.0 # Normalize to [0, 1] + image = img_to_array(image) / 255.0 # Normalize to [0, 1] return image -# Assuming your images are in a folder named 'images' in the same directory as your CSV +# Set the base path to the image folder base_image_path = 'pre-preparation/dataset/formatted/images/' -# Initialize lists to hold processed images and their corresponding labels +# Initialize lists for images and labels images = [] valid_labels = [] @@ -37,45 +38,55 @@ def preprocess_image(image_path, target_size): for index, row in data.iterrows(): image_id = row['image_id'] img_path = os.path.join(base_image_path, image_id) - if os.path.exists(img_path): # Check if the image file exists + if os.path.exists(img_path): img = preprocess_image(img_path, image_size) images.append(img) - valid_labels.append(row.drop('image_id').values) # Append label data, excluding the image_id + + # Ensure labels are binary (0 or 1) and convert to integers + labels = row.drop('image_id').values + labels = np.array(labels > 0.5, dtype=int) # Convert to 0 or 1 if binary + valid_labels.append(labels) print(f"Image {image_id} processed") else: print(f"Image {image_id} not found, skipping...") # Convert lists of images and labels to numpy arrays -images = np.array(images) -valid_labels = np.array(valid_labels) -images = np.array(images).astype('float32') -valid_labels = np.array(valid_labels).astype('float32') +images = np.array(images, dtype='float32') +valid_labels = np.array(valid_labels, dtype='int32') +# Split the data +X_train, X_val, y_train, y_val = train_test_split(images, valid_labels, test_size=0.2, random_state=42) + +# Check label structure for troubleshooting +print("Label Sample (before model):", y_train[:5]) + +# Use VGG16 for feature extraction, without the top layers +vgg_base = VGG16(weights='imagenet', include_top=False, input_shape=(128, 128, 3)) + +# Build the model using VGG16 as a feature extractor model = Sequential([ - Conv2D(32, (3, 3), activation='relu', input_shape=(128, 128, 3)), - # Adjust input_shape based on your image size and channels - MaxPooling2D((2, 2)), - Conv2D(64, (3, 3), activation='relu'), - MaxPooling2D((2, 2)), - Conv2D(128, (3, 3), activation='relu'), - MaxPooling2D((2, 2)), - Flatten(), + vgg_base, + GlobalAveragePooling2D(), Dense(128, activation='relu'), Dropout(0.5), - Dense(valid_labels.shape[1], activation='sigmoid') # The output layer with a sigmoid activation, one per label + Dense(valid_labels.shape[1], activation='sigmoid') # Change to softmax if single-label multi-class ]) -model.compile(optimizer='adam', - loss='binary_crossentropy', +# Unfreeze top layers of VGG16 for fine-tuning +for layer in vgg_base.layers[-4:]: + layer.trainable = True + +# Compile the model +model.compile(optimizer=Adam(learning_rate=0.00001), # Further reduced learning rate for fine-tuning + loss='binary_crossentropy', # Use categorical_crossentropy for multi-class metrics=['accuracy']) print(model.summary()) -# Split the data -X_train, X_val, y_train, y_val = train_test_split(images, valid_labels, test_size=0.2, random_state=42) - # Train the model history = model.fit(X_train, y_train, epochs=75, validation_data=(X_val, y_val)) + +# Calculate final accuracy train_accuracy_percentage = history.history['accuracy'][-1] * 100 validation_accuracy_percentage = history.history['val_accuracy'][-1] * 100 From 73e376bdf31c50deced3e8417c88750a16cef60c Mon Sep 17 00:00:00 2001 From: Zeynep AKI <73976500+zeynepaki@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:29:53 +0000 Subject: [PATCH 5/5] Update training.py --- training.py | 45 ++++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/training.py b/training.py index d678bfa..a2a1716 100644 --- a/training.py +++ b/training.py @@ -1,10 +1,10 @@ import sys import os import numpy as np +import matplotlib.pyplot as plt from tensorflow.keras.preprocessing.image import img_to_array, load_img from tensorflow.keras.models import Sequential -from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout, GlobalAveragePooling2D -from tensorflow.keras.applications import VGG16 +from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout from tensorflow.keras.optimizers import Adam from sklearn.model_selection import train_test_split @@ -42,7 +42,7 @@ def preprocess_image(image_path, target_size): img = preprocess_image(img_path, image_size) images.append(img) - # Ensure labels are binary (0 or 1) and convert to integers + # Ensure labels are binary (0 or 1) labels = row.drop('image_id').values labels = np.array(labels > 0.5, dtype=int) # Convert to 0 or 1 if binary valid_labels.append(labels) @@ -54,37 +54,40 @@ def preprocess_image(image_path, target_size): images = np.array(images, dtype='float32') valid_labels = np.array(valid_labels, dtype='int32') +# Visualize some samples +for i in range(5): + plt.imshow(images[i]) + plt.title(f"Label: {valid_labels[i]}") + plt.show() + # Split the data X_train, X_val, y_train, y_val = train_test_split(images, valid_labels, test_size=0.2, random_state=42) -# Check label structure for troubleshooting -print("Label Sample (before model):", y_train[:5]) +# Build a simpler model +model = Sequential([ + Conv2D(32, (3, 3), activation='relu', input_shape=(128, 128, 3)), + MaxPooling2D((2, 2)), + Dropout(0.3), -# Use VGG16 for feature extraction, without the top layers -vgg_base = VGG16(weights='imagenet', include_top=False, input_shape=(128, 128, 3)) + Conv2D(64, (3, 3), activation='relu'), + MaxPooling2D((2, 2)), + Dropout(0.3), -# Build the model using VGG16 as a feature extractor -model = Sequential([ - vgg_base, - GlobalAveragePooling2D(), + Flatten(), Dense(128, activation='relu'), Dropout(0.5), - Dense(valid_labels.shape[1], activation='sigmoid') # Change to softmax if single-label multi-class + Dense(valid_labels.shape[1], activation='sigmoid') ]) -# Unfreeze top layers of VGG16 for fine-tuning -for layer in vgg_base.layers[-4:]: - layer.trainable = True - -# Compile the model -model.compile(optimizer=Adam(learning_rate=0.00001), # Further reduced learning rate for fine-tuning - loss='binary_crossentropy', # Use categorical_crossentropy for multi-class +# Compile the model with a lower learning rate +model.compile(optimizer=Adam(learning_rate=1e-5), + loss='binary_crossentropy', metrics=['accuracy']) print(model.summary()) -# Train the model -history = model.fit(X_train, y_train, epochs=75, validation_data=(X_val, y_val)) +# Train the simpler model +history = model.fit(X_train, y_train, epochs=30, validation_data=(X_val, y_val), batch_size=32) # Calculate final accuracy train_accuracy_percentage = history.history['accuracy'][-1] * 100