├── images ├── accuracy.png └── metrics.png ├── README.md └── src ├── predict-binary.py ├── predict-multiclass.py ├── train-binary.py └── train-multiclass.py /images/accuracy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tatsuyah/CNN-Image-Classifier/HEAD/images/accuracy.png -------------------------------------------------------------------------------- /images/metrics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tatsuyah/CNN-Image-Classifier/HEAD/images/metrics.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CNN Image Classifier 2 | 3 | ## Overview 4 | 5 | A Simple Deep Neural Network to classify images made with Keras. This supports binary and multiclass classification. 6 | 7 | ## Requirements 8 | 9 | * Keras = 2.x (TensorFlow backend) 10 | * Numpy = 1.x 11 | 12 | ## Usage 13 | 14 | First, collect training and validation data and deploy it like this(for multiclass classification), 15 | ``` 16 | ./data/ 17 | train/ 18 | pizza/ 19 | pizza1.jpg 20 | pizza2.jpg 21 | ... 22 | poodle/ 23 | poodle1.jpg 24 | poodle2.jpg 25 | ... 26 | rose/ 27 | rose1.jpg 28 | rose2.jpg 29 | ... 30 | validation/ 31 | pizza/ 32 | pizza1.jpg 33 | pizza2.jpg 34 | ... 35 | poodle/ 36 | poodle1.jpg 37 | poodle2.jpg 38 | ... 39 | rose/ 40 | rose1.jpg 41 | rose2.jpg 42 | ... 43 | ``` 44 | 45 | and then run train script. 46 | 47 | ``` 48 | python src/train-multiclass.py 49 | ``` 50 | 51 | Train script makes model and weights file to `./output/`. 52 | 53 | To test another images, run 54 | 55 | ``` 56 | python src/predict-multiclass.py 57 | ``` 58 | 59 | After training, you'll have tensorboard log in `./tf-log/` 60 | So you can see the result 61 | 62 | ``` 63 | tensorboard --logdir=./tf-log 64 | ``` 65 | -------------------------------------------------------------------------------- /src/predict-binary.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | from keras.preprocessing.image import ImageDataGenerator, load_img, img_to_array 4 | from keras.models import Sequential, load_model 5 | 6 | img_width, img_height = 150, 150 7 | model_path = './models/model.h5' 8 | model_weights_path = './models/weights.h5' 9 | model = load_model(model_path) 10 | model.load_weights(model_weights_path) 11 | 12 | def predict(file): 13 | x = load_img(file, target_size=(img_width,img_height)) 14 | x = img_to_array(x) 15 | x = np.expand_dims(x, axis=0) 16 | array = model.predict(x) 17 | result = array[0] 18 | if result[0] > result[1]: 19 | print("Predicted answer: Pizza") 20 | answer = 'pizza' 21 | else: 22 | print("Predicted answer: Poodle") 23 | answer = 'poodle' 24 | 25 | return answer 26 | 27 | tp = 0 28 | tn = 0 29 | fp = 0 30 | fn = 0 31 | 32 | for i, ret in enumerate(os.walk('./test-data/poodle')): 33 | for i, filename in enumerate(ret[2]): 34 | if filename.startswith("."): 35 | continue 36 | print("Label: Poodle") 37 | result = predict(ret[0] + '/' + filename) 38 | if result == "poodle": 39 | tn += 1 40 | else: 41 | fp += 1 42 | 43 | for i, ret in enumerate(os.walk('./test-data/pizza')): 44 | for i, filename in enumerate(ret[2]): 45 | if filename.startswith("."): 46 | continue 47 | print("Label: Pizza") 48 | result = predict(ret[0] + '/' + filename) 49 | if result == "pizza": 50 | tp += 1 51 | else: 52 | fn += 1 53 | 54 | """ 55 | Check metrics 56 | """ 57 | print("True Positive: ", tp) 58 | print("True Negative: ", tn) 59 | print("False Positive: ", fp) # important 60 | print("False Negative: ", fn) 61 | 62 | precision = tp / (tp + fp) 63 | recall = tp / (tp + fn) 64 | print("Precision: ", precision) 65 | print("Recall: ", recall) 66 | 67 | f_measure = (2 * recall * precision) / (recall + precision) 68 | print("F-measure: ", f_measure) 69 | -------------------------------------------------------------------------------- /src/predict-multiclass.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | from keras.preprocessing.image import ImageDataGenerator, load_img, img_to_array 4 | from keras.models import Sequential, load_model 5 | 6 | img_width, img_height = 150, 150 7 | model_path = './models/model.h5' 8 | model_weights_path = './models/weights.h5' 9 | model = load_model(model_path) 10 | model.load_weights(model_weights_path) 11 | 12 | def predict(file): 13 | x = load_img(file, target_size=(img_width,img_height)) 14 | x = img_to_array(x) 15 | x = np.expand_dims(x, axis=0) 16 | array = model.predict(x) 17 | result = array[0] 18 | answer = np.argmax(result) 19 | if answer == 0: 20 | print("Label: Pizza") 21 | elif answer == 1: 22 | print("Labels: Poodle") 23 | elif answer == 2: 24 | print("Label: Rose") 25 | 26 | return answer 27 | 28 | pizza_t = 0 29 | pizza_f = 0 30 | poodle_t = 0 31 | poodle_f = 0 32 | rose_t = 0 33 | rose_f = 0 34 | 35 | for i, ret in enumerate(os.walk('./test-data/pizza')): 36 | for i, filename in enumerate(ret[2]): 37 | if filename.startswith("."): 38 | continue 39 | print("Label: Pizza") 40 | result = predict(ret[0] + '/' + filename) 41 | if result == 0: 42 | pizza_t += 1 43 | else: 44 | pizza_f += 1 45 | 46 | for i, ret in enumerate(os.walk('./test-data/poodle')): 47 | for i, filename in enumerate(ret[2]): 48 | if filename.startswith("."): 49 | continue 50 | print("Label: Poodle") 51 | result = predict(ret[0] + '/' + filename) 52 | if result == 1: 53 | poodle_t += 1 54 | else: 55 | poodle_f += 1 56 | 57 | for i, ret in enumerate(os.walk('./test-data/rose')): 58 | for i, filename in enumerate(ret[2]): 59 | if filename.startswith("."): 60 | continue 61 | print("Label: Rose") 62 | result = predict(ret[0] + '/' + filename) 63 | if result == 2: 64 | rose_t += 1 65 | else: 66 | rose_f += 1 67 | 68 | """ 69 | Check metrics 70 | """ 71 | print("True Pizza: ", pizza_t) 72 | print("False Pizza: ", pizza_f) 73 | print("True Poodle: ", poodle_t) 74 | print("False Poodle: ", poodle_f) 75 | print("True Rose: ", rose_t) 76 | print("False Rose: ", rose_f) 77 | -------------------------------------------------------------------------------- /src/train-binary.py: -------------------------------------------------------------------------------- 1 | """ 2 | First, you need to collect training data and deploy it like this. 3 | 4 | ./data/ 5 | train/ 6 | pizza/ 7 | pizza1.jpg 8 | pizza2.jpg 9 | ... 10 | poodle/ 11 | poodle1.jpg 12 | poodle2.jpg 13 | ... 14 | validation/ 15 | pizza/ 16 | pizza1.jpg 17 | pizza2.jpg 18 | ... 19 | poodle/ 20 | poodle1.jpg 21 | poodle2.jpg 22 | ... 23 | """ 24 | 25 | import sys 26 | import os 27 | from keras.preprocessing.image import ImageDataGenerator 28 | from keras import optimizers 29 | from keras.models import Sequential, Model 30 | from keras.layers import Dropout, Flatten, Dense, Activation 31 | from keras.layers.convolutional import Conv2D, MaxPooling2D 32 | from keras import callbacks 33 | 34 | DEV = False 35 | argvs = sys.argv 36 | argc = len(argvs) 37 | 38 | if argc > 1 and (argvs[1] == "--development" or argvs[1] == "-d"): 39 | DEV = True 40 | 41 | if DEV: 42 | epochs = 4 43 | else: 44 | epochs = 20 45 | 46 | 47 | train_data_dir = './data/train' 48 | validation_data_dir = './data/validation' 49 | 50 | img_width, img_height = 150, 150 51 | nb_train_samples = 2000 52 | nb_validation_samples = 800 53 | nb_filters1 = 32 54 | nb_filters2 = 64 55 | conv1_size = 3 56 | conv2_size = 2 57 | pool_size = 2 58 | classes_num = 2 59 | batch_size = 32 60 | lr = 0.0004 61 | 62 | model = Sequential() 63 | model.add(Conv2D(nb_filters1, (conv1_size, conv1_size), padding="same", input_shape=(img_width, img_height, 3))) 64 | model.add(Activation("relu")) 65 | model.add(MaxPooling2D(pool_size=(pool_size, pool_size))) 66 | 67 | model.add(Conv2D(nb_filters2, (conv2_size, conv2_size), padding="same")) 68 | model.add(Activation("relu")) 69 | model.add(MaxPooling2D(pool_size=(pool_size, pool_size), data_format='channels_first')) 70 | 71 | model.add(Flatten()) 72 | model.add(Dense(256)) 73 | model.add(Activation("relu")) 74 | model.add(Dropout(0.5)) 75 | model.add(Dense(classes_num, activation='softmax')) 76 | 77 | model.compile(loss='categorical_crossentropy', 78 | optimizer=optimizers.RMSprop(lr=lr), 79 | metrics=['accuracy']) 80 | 81 | train_datagen = ImageDataGenerator( 82 | rescale=1. / 255, 83 | shear_range=0.2, 84 | zoom_range=0.2, 85 | horizontal_flip=True) 86 | 87 | test_datagen = ImageDataGenerator( 88 | rescale=1. / 255) 89 | 90 | train_generator = train_datagen.flow_from_directory( 91 | train_data_dir, 92 | target_size=(img_height, img_width), 93 | batch_size=batch_size, 94 | class_mode='categorical') 95 | 96 | validation_generator = test_datagen.flow_from_directory( 97 | validation_data_dir, 98 | target_size=(img_height, img_width), 99 | batch_size=batch_size, 100 | class_mode='categorical') 101 | 102 | """ 103 | Tensorboard log 104 | """ 105 | log_dir = './tf-log/' 106 | tb_cb = callbacks.TensorBoard(log_dir=log_dir, histogram_freq=1) 107 | cbks = [tb_cb] 108 | 109 | model.fit_generator( 110 | train_generator, 111 | samples_per_epoch=nb_train_samples, 112 | epochs=epochs, 113 | validation_data=validation_generator, 114 | callbacks=cbks, 115 | validation_steps=nb_validation_samples) 116 | 117 | target_dir = './models/' 118 | if not os.path.exists(target_dir): 119 | os.mkdir(target_dir) 120 | model.save('./models/model.h5') 121 | model.save_weights('./models/weights.h5') 122 | -------------------------------------------------------------------------------- /src/train-multiclass.py: -------------------------------------------------------------------------------- 1 | """ 2 | First, you need to collect training data and deploy it like this. 3 | e.g. 3-classes classification Pizza, Poodle, Rose 4 | 5 | ./data/ 6 | train/ 7 | pizza/ 8 | pizza1.jpg 9 | pizza2.jpg 10 | ... 11 | poodle/ 12 | poodle1.jpg 13 | poodle2.jpg 14 | ... 15 | rose/ 16 | rose1.jpg 17 | rose2.jpg 18 | ... 19 | validation/ 20 | pizza/ 21 | pizza1.jpg 22 | pizza2.jpg 23 | ... 24 | poodle/ 25 | poodle1.jpg 26 | poodle2.jpg 27 | ... 28 | rose/ 29 | rose1.jpg 30 | rose2.jpg 31 | ... 32 | """ 33 | 34 | import sys 35 | import os 36 | from keras.preprocessing.image import ImageDataGenerator 37 | from keras import optimizers 38 | from keras.models import Sequential 39 | from keras.layers import Dropout, Flatten, Dense, Activation 40 | from keras.layers.convolutional import Convolution2D, MaxPooling2D 41 | from keras import callbacks 42 | 43 | DEV = False 44 | argvs = sys.argv 45 | argc = len(argvs) 46 | 47 | if argc > 1 and (argvs[1] == "--development" or argvs[1] == "-d"): 48 | DEV = True 49 | 50 | if DEV: 51 | epochs = 2 52 | else: 53 | epochs = 20 54 | 55 | train_data_path = './data/train' 56 | validation_data_path = './data/validation' 57 | 58 | """ 59 | Parameters 60 | """ 61 | img_width, img_height = 150, 150 62 | batch_size = 32 63 | samples_per_epoch = 1000 64 | validation_steps = 300 65 | nb_filters1 = 32 66 | nb_filters2 = 64 67 | conv1_size = 3 68 | conv2_size = 2 69 | pool_size = 2 70 | classes_num = 3 71 | lr = 0.0004 72 | 73 | model = Sequential() 74 | model.add(Convolution2D(nb_filters1, conv1_size, conv1_size, border_mode ="same", input_shape=(img_width, img_height, 3))) 75 | model.add(Activation("relu")) 76 | model.add(MaxPooling2D(pool_size=(pool_size, pool_size))) 77 | 78 | model.add(Convolution2D(nb_filters2, conv2_size, conv2_size, border_mode ="same")) 79 | model.add(Activation("relu")) 80 | model.add(MaxPooling2D(pool_size=(pool_size, pool_size), dim_ordering='th')) 81 | 82 | model.add(Flatten()) 83 | model.add(Dense(256)) 84 | model.add(Activation("relu")) 85 | model.add(Dropout(0.5)) 86 | model.add(Dense(classes_num, activation='softmax')) 87 | 88 | model.compile(loss='categorical_crossentropy', 89 | optimizer=optimizers.RMSprop(lr=lr), 90 | metrics=['accuracy']) 91 | 92 | train_datagen = ImageDataGenerator( 93 | rescale=1. / 255, 94 | shear_range=0.2, 95 | zoom_range=0.2, 96 | horizontal_flip=True) 97 | 98 | test_datagen = ImageDataGenerator(rescale=1. / 255) 99 | 100 | train_generator = train_datagen.flow_from_directory( 101 | train_data_path, 102 | target_size=(img_height, img_width), 103 | batch_size=batch_size, 104 | class_mode='categorical') 105 | 106 | validation_generator = test_datagen.flow_from_directory( 107 | validation_data_path, 108 | target_size=(img_height, img_width), 109 | batch_size=batch_size, 110 | class_mode='categorical') 111 | 112 | """ 113 | Tensorboard log 114 | """ 115 | log_dir = './tf-log/' 116 | tb_cb = callbacks.TensorBoard(log_dir=log_dir, histogram_freq=1) 117 | cbks = [tb_cb] 118 | 119 | model.fit_generator( 120 | train_generator, 121 | samples_per_epoch=samples_per_epoch, 122 | epochs=epochs, 123 | validation_data=validation_generator, 124 | callbacks=cbks, 125 | validation_steps=validation_steps) 126 | 127 | target_dir = './models/' 128 | if not os.path.exists(target_dir): 129 | os.mkdir(target_dir) 130 | model.save('./models/model.h5') 131 | model.save_weights('./models/weights.h5') 132 | --------------------------------------------------------------------------------