├── .gitattributes ├── .gitignore ├── .ipynb_checkpoints └── Multiclass_classification-checkpoint.ipynb ├── README.md ├── datasets.zip ├── folder_tree.png ├── main.py ├── mc_preds.png ├── mc_results.png ├── requirements.txt └── utils ├── __init__.py └── helpers.py /.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-vendored 2 | *.py linguist-vendored=false 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | 162 | datasets/ 163 | archive/ 164 | archive.zip 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TensorFlow-Multiclass-Image-Classification-using-CNN-s 2 | This is a multiclass image classification project using Convolutional Neural Networks and PyTorch. If you want to have Tensorflow 1.0 version, take a look at **tensorflow1.0** branch. 3 | 4 | It is a ready-to-run code. 5 | 6 | ## Folder Tree 7 | 8 | ![folder_tree](folder_tree.png) 9 | 10 | ## Installing Dependencies & Running 11 | 12 | ```run.sh 13 | python3 -m venv venv 14 | source venv/bin/activate 15 | pip install -r requirements.txt 16 | python main.py 17 | ``` 18 | 19 | 20 | ## Data 21 | No MNIST or CIFAR-10. 22 | 23 | This is a repository containing datasets of 5200 training images of 4 classes and 1267 testing images.No problematic image. 24 | 25 | Just extract files from multiclass_datasets.rar. 26 | 27 | train_data_bi.npy is containing 5200 training photos with labels. 28 | 29 | test_data_bi.npy is containing 1267 testing photos with labels. 30 | 31 | Classes are chair & kitchen & knife & saucepan. Classes are equal(1300 glass - 1300 kitchen - 1300 knife- 1300 saucepan) on training data. 32 | 33 | Download pure data from [here](https://www.kaggle.com/mbkinaci/chair-kitchen-knife-saucepan). Warning 962 MB. 34 | 35 | ## Architecture 36 | 37 | AlexNet is used as architecture. 5 convolution layers and 3 Fully Connected Layers with 0.5 Dropout Ratio. 60 million Parameters. 38 | ![alt text](https://github.com/MuhammedBuyukkinaci/TensorFlow-Image-Classification-Convolutional-Neural-Networks/blob/master/alexnet_architecture.png) 39 | 40 | 41 | ## Results 42 | Accuracy score reached 87% on CV after just 5 epochs. 43 | ![alt text](https://github.com/MuhammedBuyukkinaci/TensorFlow-Multiclass-Image-Classification-using-CNN-s/blob/master/mc_results.png) 44 | 45 | ![folder_tree](mc_results.png) 46 | 47 | ## Predictions 48 | Predictions for first 64 testing images are below. Titles are the predictions of our Model. 49 | 50 | ![folder_tree](mc_preds.png) 51 | -------------------------------------------------------------------------------- /datasets.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MuhammedBuyukkinaci/TensorFlow-Multiclass-Image-Classification-using-CNN-s/1035219c3e75b5253f1eb1a86865f190d336a34c/datasets.zip -------------------------------------------------------------------------------- /folder_tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MuhammedBuyukkinaci/TensorFlow-Multiclass-Image-Classification-using-CNN-s/1035219c3e75b5253f1eb1a86865f190d336a34c/folder_tree.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os # dealing with directories 2 | 3 | import matplotlib.pyplot as plt # for visualizations 4 | import numpy as np # arrays 5 | import pandas as pd # for manipulating data 6 | import torch 7 | import torch.nn as nn 8 | import torch.nn.functional as F 9 | from PIL import Image 10 | from sklearn.metrics import roc_auc_score 11 | from torch.utils.data import DataLoader, Dataset 12 | 13 | from utils.helpers import (create_transform, prepare_train_valid_test, 14 | unzip_input_file) 15 | 16 | # HYPERPARAMETERS 17 | # our photos are in the size of (80,80,3) 18 | IMG_SIZE = 80 19 | IMG_SIZE_ALEXNET = 227 20 | SHOWN_IMAGE_COUNT = 64 21 | columns = 8 22 | rows = 8 23 | # hyperparameters 24 | hidden_size = 100 25 | num_epochs = 50 26 | batch_size = 32 27 | learning_rate = 3e-5 28 | UNZIP = False 29 | 30 | BASE_DIR = os.getcwd() 31 | 32 | # Current working directory 33 | 34 | # Our dataset class 35 | class CustomDataset(Dataset): 36 | def __init__(self, arr, transform=None) -> None: 37 | self.x = [Image.fromarray(i[0], "RGB") for i in arr] 38 | self.y = np.array([i[1].argmax() for i in arr]) 39 | self.transform = transform 40 | self.n_samples = len(self.x) 41 | 42 | def __getitem__(self, index): 43 | y_label = self.y[index] 44 | if self.transform: 45 | img = self.transform(self.x[index]) 46 | return img, y_label 47 | 48 | def __len__(self): 49 | return self.n_samples 50 | 51 | 52 | # Declaring model 53 | class AlexNet(nn.Module): 54 | def __init__(self) -> None: 55 | super().__init__() 56 | self.conv1 = nn.Conv2d( 57 | in_channels=3, out_channels=96, kernel_size=(11, 11), stride=4 58 | ) 59 | self.relu = nn.ReLU() 60 | self.pool1 = nn.MaxPool2d(kernel_size=(3, 3), stride=(2, 2)) 61 | self.conv2 = nn.Conv2d( 62 | in_channels=96, out_channels=256, kernel_size=(5, 5), stride=1, padding=2 63 | ) 64 | self.pool2 = nn.MaxPool2d(kernel_size=(3, 3), stride=(2, 2)) 65 | self.conv3 = nn.Conv2d( 66 | in_channels=256, out_channels=384, kernel_size=(3, 3), stride=1, padding=1 67 | ) 68 | self.conv4 = nn.Conv2d( 69 | in_channels=384, out_channels=384, kernel_size=(3, 3), stride=1, padding=1 70 | ) 71 | self.conv5 = nn.Conv2d( 72 | in_channels=384, out_channels=256, kernel_size=(3, 3), stride=1, padding=1 73 | ) 74 | self.pool3 = nn.MaxPool2d(kernel_size=(3, 3), stride=(2, 2)) 75 | self.dropout = nn.Dropout(p=0.5) 76 | self.fc1 = nn.Linear(in_features=256 * 6 * 6, out_features=4096) 77 | self.fc2 = nn.Linear(in_features=4096, out_features=4096) 78 | self.fc3 = nn.Linear(in_features=4096, out_features=4) 79 | 80 | def forward(self, x): 81 | x = self.pool1(self.relu(self.conv1(x))) 82 | x = self.pool2(self.relu(self.conv2(x))) 83 | x = self.relu(self.conv3(x)) 84 | x = self.relu(self.conv4(x)) 85 | x = self.pool3(self.relu(self.conv5(x))) 86 | x = self.dropout(x) 87 | x = x.reshape(-1, 256 * 6 * 6) 88 | x = self.relu(self.fc1(x)) 89 | x = self.dropout(x) 90 | x = self.relu(self.fc2(x)) 91 | x = self.fc3(x) 92 | return x 93 | 94 | 95 | def training( 96 | train_loader, 97 | device, 98 | model, 99 | optimizer, 100 | epoch, 101 | criterion, 102 | training_loss_list, 103 | num_steps, 104 | ): 105 | model.train() 106 | for i, (images, labels) in enumerate(train_loader): 107 | # moving input and output to device 108 | images = images.to(device) 109 | labels = labels.to(device) 110 | # forward 111 | outputs = model(images) 112 | loss = criterion(outputs, labels) 113 | training_loss_list.append(loss.item()) 114 | # set gradients to 0 first 115 | optimizer.zero_grad() 116 | # back propogate gradients 117 | loss.backward() 118 | # update weights via learning rate and gradients 119 | optimizer.step() 120 | 121 | if (i + 1) % num_steps == 0: 122 | print(f"train; epoch={epoch+1}, training loss = {np.round(loss.item(),4)}") 123 | 124 | 125 | def evaluation(model, device, loader, criterion, validation_loss_list, epoch=None, validation=True): 126 | model.eval() 127 | with torch.no_grad(): 128 | n_correct = 0 129 | n_samples = 0 130 | loss_all = 0 131 | for images, labels in loader: 132 | images = images.to(device) 133 | labels = labels.to(device) 134 | outputs = model(images) 135 | loss = criterion(outputs, labels) 136 | validation_loss_list.append(loss.item()) 137 | loss_all += loss.item() 138 | 139 | # value, index 140 | _, predictions = torch.max(outputs, 1) 141 | 142 | n_samples += labels.shape[0] 143 | n_correct += (predictions == labels).sum().item() 144 | 145 | acc = 100.0 * n_correct / n_samples 146 | avg_loss = loss_all / len(loader) 147 | if validation: 148 | print( 149 | f"valid; epoch={epoch+1}, valid loss = {round(float(avg_loss),4)}, accuracy = {np.round(acc,4)} \n" 150 | ) 151 | else: 152 | print( 153 | f"test scores, valid loss = {round(float(avg_loss),4)}, accuracy = {np.round(acc,4)} \n" 154 | ) 155 | 156 | 157 | def plot_loss_train_valid(training_loss_list, validation_loss_list): 158 | f, ax = plt.subplots(1, 2, figsize=(12, 3)) 159 | pd.Series(training_loss_list).rolling(50).mean().plot( 160 | kind="line", title="Accuracy on CV data", ax=ax[0] 161 | ) 162 | pd.Series(validation_loss_list).rolling(50).mean().plot( 163 | kind="line", title="Loss on CV data", ax=ax[1] 164 | ) 165 | plt.subplots_adjust(wspace=0.8) 166 | ax[0].set_title("Loss on train data") 167 | ax[1].set_title("Loss on CV data") 168 | plt.show() 169 | 170 | 171 | def get_test_preds(model, loader, device): 172 | model.eval() 173 | with torch.no_grad(): 174 | test_classes = [] 175 | test_preds = [] 176 | for images, labels in loader: 177 | images = images.to(device) 178 | labels = labels.to(device) 179 | outputs = model(images) 180 | _, predictions = torch.max(outputs, 1) 181 | test_classes.append(labels) 182 | test_preds.append(predictions) 183 | test_classes = np.hstack([x.cpu().numpy() for x in test_classes]) 184 | test_preds = np.hstack([x.cpu().numpy() for x in test_preds]) 185 | return test_preds 186 | 187 | 188 | def plot_some_preds(SHOWN_IMAGE_COUNT, columns, rows, test_preds, test_data): 189 | 190 | pred_labels = [] 191 | for i in range(SHOWN_IMAGE_COUNT): 192 | r = test_preds[i] 193 | if r == 0: 194 | pred_labels.append("chair") 195 | elif r == 1: 196 | pred_labels.append("kitchen") 197 | elif r == 2: 198 | pred_labels.append("knife") 199 | elif r == 3: 200 | pred_labels.append("saucepan") 201 | 202 | # First 64 images 203 | shown_images = [x[0] for x in test_data[:SHOWN_IMAGE_COUNT]] 204 | fig = plt.figure(figsize=(20, 20)) 205 | for m in range(1, columns * rows + 1): 206 | img = shown_images[m - 1].reshape([IMG_SIZE_ALEXNET, IMG_SIZE_ALEXNET, 3]) 207 | fig.add_subplot(rows, columns, m) 208 | plt.imshow(img) 209 | plt.title("Pred: " + pred_labels[m - 1]) 210 | plt.axis("off") 211 | plt.show() 212 | 213 | 214 | def main(): 215 | # Unzipping file 216 | if UNZIP: 217 | unzip_input_file("datasets.zip") 218 | 219 | # prepare data 220 | train, cv, test_data = prepare_train_valid_test( 221 | BASE_DIR, 222 | "datasets", 223 | "train_data_mc.npy", 224 | "test_data_mc.npy", 225 | IMG_SIZE_ALEXNET, 226 | train_size=4800, 227 | ) 228 | transform = create_transform() 229 | 230 | train_dataset = CustomDataset(train, transform) 231 | valid_dataset = CustomDataset(cv, transform) 232 | test_dataset = CustomDataset(test_data, transform) 233 | 234 | train_loader = DataLoader( 235 | dataset=train_dataset, batch_size=batch_size, shuffle=True 236 | ) 237 | valid_loader = DataLoader( 238 | dataset=valid_dataset, batch_size=batch_size, shuffle=False 239 | ) 240 | test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False) 241 | 242 | # setting device 243 | device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 244 | print(device) 245 | 246 | # move model to cuda 247 | model = AlexNet().to(device) 248 | 249 | # define loss function 250 | criterion = nn.CrossEntropyLoss() 251 | # define optimizer 252 | optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate) 253 | # setting step count 254 | num_steps = len(train_loader) 255 | # some empty dicts to monitor losses during training and testing 256 | training_loss_list = [] 257 | validation_loss_list = [] 258 | 259 | # training loop 260 | for epoch in range(num_epochs): 261 | # training phase 262 | training( 263 | train_loader, 264 | device, 265 | model, 266 | optimizer, 267 | epoch, 268 | criterion, 269 | training_loss_list, 270 | num_steps, 271 | ) 272 | # evaluation on valid 273 | evaluation( 274 | model=model, 275 | device=device, 276 | loader=valid_loader, 277 | criterion=criterion, 278 | validation_loss_list=validation_loss_list, 279 | epoch=epoch, 280 | validation=True, 281 | ) 282 | 283 | # evaluation on test 284 | # evaluation( 285 | # model=model, device=device, loader=test_loader, epoch=None, validation=False 286 | # ) 287 | 288 | plot_loss_train_valid(training_loss_list, validation_loss_list) 289 | 290 | # convert list to numpy array 291 | test_preds = get_test_preds(model=model, loader=test_loader, device=device) 292 | 293 | plot_some_preds(SHOWN_IMAGE_COUNT, columns, rows, test_preds, test_data) 294 | 295 | 296 | if __name__ == "__main__": 297 | main() 298 | -------------------------------------------------------------------------------- /mc_preds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MuhammedBuyukkinaci/TensorFlow-Multiclass-Image-Classification-using-CNN-s/1035219c3e75b5253f1eb1a86865f190d336a34c/mc_preds.png -------------------------------------------------------------------------------- /mc_results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MuhammedBuyukkinaci/TensorFlow-Multiclass-Image-Classification-using-CNN-s/1035219c3e75b5253f1eb1a86865f190d336a34c/mc_results.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | arrow==1.2.3 2 | binaryornot==0.4.4 3 | certifi==2022.9.24 4 | chardet==5.0.0 5 | charset-normalizer==2.1.1 6 | click==8.1.3 7 | contourpy==1.0.6 8 | cookiecutter==2.1.1 9 | cycler==0.11.0 10 | fonttools==4.38.0 11 | idna==3.4 12 | Jinja2==3.1.2 13 | jinja2-time==0.2.0 14 | joblib==1.2.0 15 | kiwisolver==1.4.4 16 | MarkupSafe==2.1.1 17 | matplotlib==3.6.2 18 | numpy==1.23.4 19 | nvidia-cublas-cu11==11.10.3.66 20 | nvidia-cuda-nvrtc-cu11==11.7.99 21 | nvidia-cuda-runtime-cu11==11.7.99 22 | nvidia-cudnn-cu11==8.5.0.96 23 | opencv-python==4.6.0.66 24 | packaging==21.3 25 | pandas==1.5.1 26 | Pillow==9.3.0 27 | pkg_resources==0.0.0 28 | pyparsing==3.0.9 29 | python-dateutil==2.8.2 30 | python-slugify==6.1.2 31 | pytz==2022.6 32 | PyYAML==6.0 33 | requests==2.28.1 34 | scikit-learn==1.1.3 35 | scipy==1.9.3 36 | six==1.16.0 37 | text-unidecode==1.3 38 | threadpoolctl==3.1.0 39 | torch==1.13.0 40 | torchaudio==0.13.0 41 | torchvision==0.14.0 42 | tqdm==4.64.1 43 | typing_extensions==4.4.0 44 | urllib3==1.26.12 45 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MuhammedBuyukkinaci/TensorFlow-Multiclass-Image-Classification-using-CNN-s/1035219c3e75b5253f1eb1a86865f190d336a34c/utils/__init__.py -------------------------------------------------------------------------------- /utils/helpers.py: -------------------------------------------------------------------------------- 1 | import os 2 | import zipfile 3 | 4 | import cv2 5 | import numpy as np 6 | import torchvision.transforms as transforms 7 | 8 | 9 | def unzip_input_file(file_name) -> None: 10 | with zipfile.ZipFile(file_name, "r") as zip_ref: 11 | zip_ref.extractall() 12 | 13 | 14 | # Reading .npy files 15 | def load_data(file_path: str): 16 | return np.load(file_path, allow_pickle=True) 17 | 18 | 19 | def resize_np_array(arr, resize_shape): 20 | for i in range(len(arr)): 21 | arr[i][0] = cv2.resize(arr[i][0], (resize_shape, resize_shape)) 22 | return arr 23 | 24 | 25 | def fix_test_data(test_data): 26 | for i in range(len(test_data)): 27 | test_data[i][1] = np.array(test_data[i][1]) 28 | return test_data 29 | 30 | 31 | def prepare_train_valid_test( 32 | BASE_DIR, folder_name, train_name, test_name, IMG_SIZE_ALEXNET, train_size=4800 33 | ): 34 | train_data = load_data(os.path.join(BASE_DIR, folder_name, train_name)) 35 | test_data = load_data(os.path.join(BASE_DIR, folder_name, test_name)) 36 | 37 | # fix the test_data by converting list to numpy array 38 | test_data = fix_test_data(test_data) 39 | 40 | # resize all_images in npy files 41 | 42 | # In order to implement ALEXNET, we are resizing them to (227,227,3) 43 | 44 | train_data = resize_np_array(train_data, IMG_SIZE_ALEXNET) 45 | test_data = resize_np_array(test_data, IMG_SIZE_ALEXNET) 46 | 47 | train = train_data[:train_size] 48 | cv = train_data[train_size:] 49 | return train, cv, test_data 50 | 51 | def create_transform(): 52 | # transform for input image 53 | transform = transforms.Compose( 54 | transforms=[ 55 | transforms.ToTensor(), 56 | transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)), 57 | ] 58 | ) 59 | return transform --------------------------------------------------------------------------------