Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions final_project/data_prep.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# File to copy data into appropriate folders
# File created with help from ChatGPT for syntax and structure

import pandas as pd
from pathlib import Path
import shutil

#create paths
project_root = Path(__file__).resolve().parent
raw_root = project_root / "data" / "severstal-steel-defect-detection"
csv_path = raw_root / "train.csv"
train_images_path = raw_root / "train_images"
steel_defect_path = project_root / "data" / "steel_defect"
print(project_root)
print(raw_root)
print(csv_path)

# loading csv
df = pd.read_csv(csv_path)
print(df.shape)
print(df.columns)
print(df.head())
Comment on lines +8 to +22

# keep valid rows
defective = df[df["EncodedPixels"].notna()]
print(defective.shape)
print(defective["ClassId"].value_counts().sort_index())

# find raw training images
all_image_paths = list(train_images_path.glob("*.jpg"))
all_images = {path.name for path in all_image_paths}
print(f"Images found: {len(all_images)}")

# find no-defect images
defective_images = set(defective["ImageId"].unique())
no_defect_images = all_images - defective_images
print(f"All images: {len(all_images)}")
print(f"Defective images: {len(defective_images)}")
print(f"No-defect images: {len(no_defect_images)}")

# group defect classes
labels_by_image = defective.groupby("ImageId")["ClassId"].apply(
lambda values: tuple(sorted(values.unique()))
)
print(labels_by_image.head(20))
print(labels_by_image.apply(len).value_counts())

# put images in classes
label_counts = {
"no_defect": 0,
"defect_1": 0,
"defect_2": 0,
"defect_3": 0,
"defect_4": 0,
}

skipped = 0

# make folders
for folder_name in label_counts:
folder_path = steel_defect_path / folder_name
folder_path.mkdir(parents=True, exist_ok=True)

# sort images
for image_name in sorted(all_images):
if image_name in no_defect_images:
destination_folder = "no_defect"
else:
classes = labels_by_image.loc[image_name]

if len(classes) == 1:
class_id = classes[0]
destination_folder = f"defect_{class_id}"

else:
skipped += 1
continue

label_counts[destination_folder] += 1
source_path = train_images_path / image_name
destination_path = steel_defect_path / destination_folder / image_name
shutil.copy2(source_path, destination_path)

classified_total = sum(label_counts.values())

# check totals
print(label_counts)
print(f"Classified images: {classified_total}")
print(f"Multi-label images skipped: {skipped}")
print(f"Total accounted for: {classified_total + skipped}")

41 changes: 41 additions & 0 deletions final_project/docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Steel Surface Defect Classification

This project explored using a neural network to detect defects in steel manufacturing

## Project Overview

The project used a neural net and image processing pipelines to first train and test the model, and then display its results using streamlit app.

It classifies images into 4 different types of defects, and a category with no defects.

## Dataset Preparation and Splits

I used a custom script called data_prep.py to place the images into their correct folders for classification. This script used the original dataset and the train.csv file to do this.

I only used images with only 1 defect label, or none at all. This was for simplicity. I could not find a way to handle the multiple defect class of images without changing the core of the model.

The model uses stratification, a fixed random seed of 42, and 70%/15%/15% train/validation/test ratios.

## Data Preprocessing

The preprocessing had traning and validation pipelines. All images were resized to 256x256. In order to augement training, images were given a random flip probability and also a random brightness contrast. The flip and brightness were not added to validation, for the sake of reproducibility.

## Model Architecture

Model info: It has abou 103k params, and convolutional blocks with 32, 64, and 128 output channels.

## Training

The training loop used a CrossEntropyLoss loss function, an Adam optimizer, learning rate of 0.001, and a batch size of 32. I used onl a CPU for training and the model ran for 20 epochs.

Model checkpoints only happen when the model improves.

Total tranin gtime was about 4 hours for me, and epoch 17 had the best validation accuracy. My final validation accuracy was 0.791.

## Results and Inference

I was able to run the application in streamlitapp, with about 60ms of inference time. my final model accuracy was 79.1%.

## Challenges and Learnings

I enjoyed working with pytorch and seeing the inner workings of a NN. Deciding what to do with multi-label images was one of the more-difficult parts of this. Also, while CPU training took a long time, I was happy with the results.
Loading