├── .gitignore ├── README.md ├── cat.jpg ├── dataset.py ├── dog.jpg ├── tf1_cnn.ipynb ├── tf1_requirements.txt ├── tf2_cnn.ipynb └── tf2_requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | data/ 2 | .ipynb_checkpoints/ 3 | __pycache__/ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tensorflow Image Classification 2 | CNN for multi-class image recognition in tensorflow 3 | 4 | Notebook converted from Hvass-Labs' tutorial in order to work with custom datasets, flexible image dimensions, 3-channel images, training over epochs, early stopping, and a deeper network. An updated version of the notebook for TensorFlow 2 is also included, along with a separate requirements file for that TensorFlow version. 5 | 6 | This example uses Kaggle's cats vs. dogs dataset. 7 | -------------------------------------------------------------------------------- /cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdcolema/tensorflow-image-classification/d9ca52018208a9cfbb7c74b95fab9d36d3f36d7a/cat.jpg -------------------------------------------------------------------------------- /dataset.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | import numpy as np 4 | import cv2 5 | from sklearn.utils import shuffle 6 | 7 | 8 | def load_train(train_path, image_size, classes): 9 | images = [] 10 | labels = [] 11 | ids = [] 12 | cls = [] 13 | 14 | print('Reading training images') 15 | for fld in classes: # assuming data directory has a separate folder for each class, and that each folder is named after the class 16 | index = classes.index(fld) 17 | print('Loading {} files (Index: {})'.format(fld, index)) 18 | path = os.path.join(train_path, fld, '*g') 19 | files = glob.glob(path) 20 | for fl in files: 21 | image = cv2.imread(fl) 22 | image = cv2.resize(image, (image_size, image_size), cv2.INTER_LINEAR) 23 | images.append(image) 24 | label = np.zeros(len(classes)) 25 | label[index] = 1.0 26 | labels.append(label) 27 | flbase = os.path.basename(fl) 28 | ids.append(flbase) 29 | cls.append(fld) 30 | images = np.array(images) 31 | labels = np.array(labels) 32 | ids = np.array(ids) 33 | cls = np.array(cls) 34 | 35 | return images, labels, ids, cls 36 | 37 | 38 | def load_test(test_path, image_size): 39 | path = os.path.join(test_path, '*g') 40 | files = sorted(glob.glob(path)) 41 | 42 | X_test = [] 43 | X_test_id = [] 44 | print("Reading test images") 45 | for fl in files: 46 | flbase = os.path.basename(fl) 47 | img = cv2.imread(fl) 48 | img = cv2.resize(img, (image_size, image_size), cv2.INTER_LINEAR) 49 | X_test.append(img) 50 | X_test_id.append(flbase) 51 | 52 | ### because we're not creating a DataSet object for the test images, normalization happens here 53 | X_test = np.array(X_test, dtype=np.uint8) 54 | X_test = X_test.astype('float32') 55 | X_test = X_test / 255 56 | 57 | return X_test, X_test_id 58 | 59 | 60 | class DataSet(object): 61 | 62 | def __init__(self, images, labels, ids, cls): 63 | """Construct a DataSet. one_hot arg is used only if fake_data is true.""" 64 | 65 | self._num_examples = images.shape[0] 66 | 67 | # Convert shape from [num examples, rows, columns, depth] 68 | # to [num examples, rows*columns] (assuming depth == 1) 69 | # Convert from [0, 255] -> [0.0, 1.0]. 70 | 71 | images = images.astype(np.float32) 72 | images = np.multiply(images, 1.0 / 255.0) 73 | 74 | self._images = images 75 | self._labels = labels 76 | self._ids = ids 77 | self._cls = cls 78 | self._epochs_completed = 0 79 | self._index_in_epoch = 0 80 | 81 | @property 82 | def images(self): 83 | return self._images 84 | 85 | @property 86 | def labels(self): 87 | return self._labels 88 | 89 | @property 90 | def ids(self): 91 | return self._ids 92 | 93 | @property 94 | def cls(self): 95 | return self._cls 96 | 97 | @property 98 | def num_examples(self): 99 | return self._num_examples 100 | 101 | @property 102 | def epochs_completed(self): 103 | return self._epochs_completed 104 | 105 | def next_batch(self, batch_size): 106 | """Return the next `batch_size` examples from this data set.""" 107 | start = self._index_in_epoch 108 | self._index_in_epoch += batch_size 109 | 110 | if self._index_in_epoch > self._num_examples: 111 | # Finished epoch 112 | self._epochs_completed += 1 113 | 114 | # # Shuffle the data (maybe) 115 | # perm = np.arange(self._num_examples) 116 | # np.random.shuffle(perm) 117 | # self._images = self._images[perm] 118 | # self._labels = self._labels[perm] 119 | # Start next epoch 120 | 121 | start = 0 122 | self._index_in_epoch = batch_size 123 | assert batch_size <= self._num_examples 124 | end = self._index_in_epoch 125 | 126 | return self._images[start:end], self._labels[start:end], self._ids[start:end], self._cls[start:end] 127 | 128 | 129 | def read_train_sets(train_path, image_size, classes, validation_size=0): 130 | class DataSets(object): 131 | pass 132 | 133 | data_sets = DataSets() 134 | 135 | images, labels, ids, cls = load_train(train_path, image_size, classes) 136 | images, labels, ids, cls = shuffle(images, labels, ids, cls) # shuffle the data 137 | 138 | if isinstance(validation_size, float): 139 | validation_size = int(validation_size * images.shape[0]) 140 | 141 | validation_images = images[:validation_size] 142 | validation_labels = labels[:validation_size] 143 | validation_ids = ids[:validation_size] 144 | validation_cls = cls[:validation_size] 145 | 146 | train_images = images[validation_size:] 147 | train_labels = labels[validation_size:] 148 | train_ids = ids[validation_size:] 149 | train_cls = cls[validation_size:] 150 | 151 | data_sets.train = DataSet(train_images, train_labels, train_ids, train_cls) 152 | data_sets.valid = DataSet(validation_images, validation_labels, validation_ids, validation_cls) 153 | 154 | return data_sets 155 | 156 | 157 | def read_test_set(test_path, image_size): 158 | images, ids = load_test(test_path, image_size) 159 | return images, ids 160 | -------------------------------------------------------------------------------- /dog.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdcolema/tensorflow-image-classification/d9ca52018208a9cfbb7c74b95fab9d36d3f36d7a/dog.jpg -------------------------------------------------------------------------------- /tf1_requirements.txt: -------------------------------------------------------------------------------- 1 | # Created with anaconda environment, non-conda requirements may differ 2 | # tensorflow-gpu compiled with cuda 9.0 and cudnn 7.6 3 | 4 | matplotlib==3.3.1 5 | nb_conda==2.2.1 6 | numpy==1.22.0 7 | pandas==0.25.0 8 | scikit-learn==1.5.0 9 | tensorflow==2.12.1 10 | 11 | -------------------------------------------------------------------------------- /tf2_requirements.txt: -------------------------------------------------------------------------------- 1 | # Created with anaconda environment, non-conda requirements may differ 2 | # tensorflow-gpu compiled with cuda 10.1 and cudnn 7.6 3 | 4 | matplotlib==3.3.1 5 | nb_conda==2.2.1 6 | numpy==1.22.0 7 | pandas==1.1.0 8 | scikit-learn==1.5.0 9 | tensorflow==2.12.1 10 | --------------------------------------------------------------------------------