Skip to content

Commit 7e8d093

Browse files
committed
init commit
0 parents  commit 7e8d093

File tree

6 files changed

+370
-0
lines changed

6 files changed

+370
-0
lines changed

README.md

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Facial Expression Prediction
2+
3+
4+
This repository is to do car recognition by fine-tuning ResNet-152 with Cars Dataset from Stanford.
5+
6+
7+
## Dependencies
8+
9+
- [NumPy](http://docs.scipy.org/doc/numpy-1.10.1/user/install.html)
10+
- [Tensorflow](https://www.tensorflow.org/versions/r0.8/get_started/os_setup.html)
11+
- [Keras](https://keras.io/#installation)
12+
- [OpenCV](https://opencv-python-tutroals.readthedocs.io/en/latest/)
13+
14+
## Dataset
15+
16+
We use the Cars Dataset, which contains 16,185 images of 196 classes of cars. The data is split into 8,144 training images and 8,041 testing images, where each class has been split roughly in a 50-50 split.
17+
18+
![image](https://github.com/foamliu/Car-Recognition/raw/master/images/random.png)
19+
20+
You can get it from [Cars Dataset](https://ai.stanford.edu/~jkrause/cars/car_dataset.html), make sure cars_train.tgz and cars_test.tgz are is in mart folder.
21+
22+
## ImageNet Pretrained Models
23+
24+
Download [ResNet-152](https://drive.google.com/file/d/0Byy2AcGyEVxfeXExMzNNOHpEODg/view?usp=sharing) into imagenet_models folder.
25+
26+
## Usage
27+
28+
### Data Pre-processing
29+
Extract 8,041 training images, and split them by 80:20 rule (6,433 for training, 1,608 for validation):
30+
```bash
31+
$ python pre-process.py
32+
```
33+
When complete, folder structure looks like:
34+
35+
![image](https://github.com/foamliu/Car-Recognition/raw/master/images/data.png)
36+
37+
### Train
38+
```bash
39+
$ python train.py
40+
```
41+
![image](https://github.com/foamliu/Car-Recognition/raw/master/images/train.png)
42+
43+
If you want to visualize during training, run in your terminal:
44+
```bash
45+
$ tensorboard --logdir path_to_current_dir/logs
46+
```
47+
48+
### Analysis
49+
Use 8,041 testing images for result analysis.
50+
51+
52+
### Predict
53+
```bash
54+
$ python predict.py --i [image_path]
55+
```

Requirements.txt

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
numpy
2+
tensorflow-gpu
3+
keras
4+
pillow

app-what-car.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#from resnet_50 import resnet50_model
2+
from resnet_152 import resnet152_model
3+
from keras.preprocessing.image import ImageDataGenerator
4+
5+
IMG_WIDTH, IMG_HEIGHT = 227, 227
6+
TRAIN_DATA = 'mart/standford-cars-crop/train'
7+
VALID_DATA = 'mart/standford-cars-crop/valid'
8+
NUM_CLASSES = 196
9+
NB_TRAIN_SAMPLES = 6549
10+
NB_VALID_SAMPLES = 1595
11+
BATCH_SIZE = 16
12+
13+
# build a classifier model
14+
#model = resnet50_model(IMG_HEIGHT, IMG_WIDTH, 3, NUM_CLASSES)
15+
model = resnet152_model(IMG_HEIGHT, IMG_WIDTH, 3, NUM_CLASSES)
16+
17+
# prepare data augmentation configuration
18+
train_data_gen = ImageDataGenerator(rescale=1. / 255, zoom_range=0.2, rotation_range=20, width_shift_range=0.2, height_shift_range=0.2, horizontal_flip=True)
19+
valid_data_gen = ImageDataGenerator(rescale=1. / 255, zoom_range=0.2, rotation_range=20, width_shift_range=0.2, height_shift_range=0.2, horizontal_flip=True)
20+
21+
train_generator = train_data_gen.flow_from_directory(TRAIN_DATA, (IMG_WIDTH, IMG_HEIGHT), batch_size=BATCH_SIZE, class_mode='categorical')
22+
valid_generator = valid_data_gen.flow_from_directory(VALID_DATA, (IMG_WIDTH, IMG_HEIGHT), batch_size=BATCH_SIZE, class_mode='categorical')
23+
24+
# fine tune the model
25+
history = model.fit_generator(
26+
train_generator,
27+
steps_per_epoch=NB_TRAIN_SAMPLES // BATCH_SIZE,
28+
validation_data=valid_generator,
29+
validation_steps=NB_VALID_SAMPLES // BATCH_SIZE,
30+
epochs=80)
31+
32+
model.save_weights("model.h5")

custom_layers/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Python Package

custom_layers/scale_layer.py

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from keras.layers.core import Layer
2+
from keras.engine import InputSpec
3+
from keras import backend as K
4+
try:
5+
from keras import initializations
6+
except ImportError:
7+
from keras import initializers as initializations
8+
9+
class Scale(Layer):
10+
'''Learns a set of weights and biases used for scaling the input data.
11+
the output consists simply in an element-wise multiplication of the input
12+
and a sum of a set of constants:
13+
14+
out = in * gamma + beta,
15+
16+
where 'gamma' and 'beta' are the weights and biases larned.
17+
18+
# Arguments
19+
axis: integer, axis along which to normalize in mode 0. For instance,
20+
if your input tensor has shape (samples, channels, rows, cols),
21+
set axis to 1 to normalize per feature map (channels axis).
22+
momentum: momentum in the computation of the
23+
exponential average of the mean and standard deviation
24+
of the data, for feature-wise normalization.
25+
weights: Initialization weights.
26+
List of 2 Numpy arrays, with shapes:
27+
`[(input_shape,), (input_shape,)]`
28+
beta_init: name of initialization function for shift parameter
29+
(see [initializations](../initializations.md)), or alternatively,
30+
Theano/TensorFlow function to use for weights initialization.
31+
This parameter is only relevant if you don't pass a `weights` argument.
32+
gamma_init: name of initialization function for scale parameter (see
33+
[initializations](../initializations.md)), or alternatively,
34+
Theano/TensorFlow function to use for weights initialization.
35+
This parameter is only relevant if you don't pass a `weights` argument.
36+
'''
37+
def __init__(self, weights=None, axis=-1, momentum = 0.9, beta_init='zero', gamma_init='one', **kwargs):
38+
self.momentum = momentum
39+
self.axis = axis
40+
self.beta_init = initializations.get(beta_init)
41+
self.gamma_init = initializations.get(gamma_init)
42+
self.initial_weights = weights
43+
super(Scale, self).__init__(**kwargs)
44+
45+
def build(self, input_shape):
46+
self.input_spec = [InputSpec(shape=input_shape)]
47+
shape = (int(input_shape[self.axis]),)
48+
49+
# Compatibility with TensorFlow >= 1.0.0
50+
self.gamma = K.variable(self.gamma_init(shape), name='{}_gamma'.format(self.name))
51+
self.beta = K.variable(self.beta_init(shape), name='{}_beta'.format(self.name))
52+
#self.gamma = self.gamma_init(shape, name='{}_gamma'.format(self.name))
53+
#self.beta = self.beta_init(shape, name='{}_beta'.format(self.name))
54+
self.trainable_weights = [self.gamma, self.beta]
55+
56+
if self.initial_weights is not None:
57+
self.set_weights(self.initial_weights)
58+
del self.initial_weights
59+
60+
def call(self, x, mask=None):
61+
input_shape = self.input_spec[0].shape
62+
broadcast_shape = [1] * len(input_shape)
63+
broadcast_shape[self.axis] = input_shape[self.axis]
64+
65+
out = K.reshape(self.gamma, broadcast_shape) * x + K.reshape(self.beta, broadcast_shape)
66+
return out
67+
68+
def get_config(self):
69+
config = {"momentum": self.momentum, "axis": self.axis}
70+
base_config = super(Scale, self).get_config()
71+
return dict(list(base_config.items()) + list(config.items()))

resnet_152.py

+207
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from keras.optimizers import SGD
4+
from keras.layers import Input, Dense, Conv2D, MaxPooling2D, AveragePooling2D, ZeroPadding2D, Flatten, merge, Activation
5+
from keras.layers.normalization import BatchNormalization
6+
from keras.models import Model
7+
from keras import backend as K
8+
9+
from custom_layers.scale_layer import Scale
10+
11+
from sklearn.metrics import log_loss
12+
13+
import sys
14+
15+
sys.setrecursionlimit(3000)
16+
17+
18+
def identity_block(input_tensor, kernel_size, filters, stage, block):
19+
'''The identity_block is the block that has no conv layer at shortcut
20+
# Arguments
21+
input_tensor: input tensor
22+
kernel_size: defualt 3, the kernel size of middle conv layer at main path
23+
filters: list of integers, the nb_filters of 3 conv layer at main path
24+
stage: integer, current stage label, used for generating layer names
25+
block: 'a','b'..., current block label, used for generating layer names
26+
'''
27+
eps = 1.1e-5
28+
nb_filter1, nb_filter2, nb_filter3 = filters
29+
conv_name_base = 'res' + str(stage) + block + '_branch'
30+
bn_name_base = 'bn' + str(stage) + block + '_branch'
31+
scale_name_base = 'scale' + str(stage) + block + '_branch'
32+
33+
x = Conv2D(nb_filter1, (1, 1), name=conv_name_base + '2a', use_bias=False)(input_tensor)
34+
x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2a')(x)
35+
x = Scale(axis=bn_axis, name=scale_name_base + '2a')(x)
36+
x = Activation('relu', name=conv_name_base + '2a_relu')(x)
37+
38+
x = ZeroPadding2D((1, 1), name=conv_name_base + '2b_zeropadding')(x)
39+
x = Conv2D(nb_filter2, (kernel_size, kernel_size),
40+
name=conv_name_base + '2b', use_bias=False)(x)
41+
x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2b')(x)
42+
x = Scale(axis=bn_axis, name=scale_name_base + '2b')(x)
43+
x = Activation('relu', name=conv_name_base + '2b_relu')(x)
44+
45+
x = Conv2D(nb_filter3, (1, 1), name=conv_name_base + '2c', use_bias=False)(x)
46+
x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2c')(x)
47+
x = Scale(axis=bn_axis, name=scale_name_base + '2c')(x)
48+
49+
x = merge([x, input_tensor], mode='sum', name='res' + str(stage) + block)
50+
x = Activation('relu', name='res' + str(stage) + block + '_relu')(x)
51+
return x
52+
53+
54+
def conv_block(input_tensor, kernel_size, filters, stage, block, strides=(2, 2)):
55+
'''conv_block is the block that has a conv layer at shortcut
56+
# Arguments
57+
input_tensor: input tensor
58+
kernel_size: defualt 3, the kernel size of middle conv layer at main path
59+
filters: list of integers, the nb_filters of 3 conv layer at main path
60+
stage: integer, current stage label, used for generating layer names
61+
block: 'a','b'..., current block label, used for generating layer names
62+
Note that from stage 3, the first conv layer at main path is with subsample=(2,2)
63+
And the shortcut should have subsample=(2,2) as well
64+
'''
65+
eps = 1.1e-5
66+
nb_filter1, nb_filter2, nb_filter3 = filters
67+
conv_name_base = 'res' + str(stage) + block + '_branch'
68+
bn_name_base = 'bn' + str(stage) + block + '_branch'
69+
scale_name_base = 'scale' + str(stage) + block + '_branch'
70+
71+
x = Conv2D(nb_filter1, (1, 1), strides=strides,
72+
name=conv_name_base + '2a', use_bias=False)(input_tensor)
73+
x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2a')(x)
74+
x = Scale(axis=bn_axis, name=scale_name_base + '2a')(x)
75+
x = Activation('relu', name=conv_name_base + '2a_relu')(x)
76+
77+
x = ZeroPadding2D((1, 1), name=conv_name_base + '2b_zeropadding')(x)
78+
x = Conv2D(nb_filter2, (kernel_size, kernel_size),
79+
name=conv_name_base + '2b', use_bias=False)(x)
80+
x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2b')(x)
81+
x = Scale(axis=bn_axis, name=scale_name_base + '2b')(x)
82+
x = Activation('relu', name=conv_name_base + '2b_relu')(x)
83+
84+
x = Conv2D(nb_filter3, (1, 1), name=conv_name_base + '2c', use_bias=False)(x)
85+
x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2c')(x)
86+
x = Scale(axis=bn_axis, name=scale_name_base + '2c')(x)
87+
88+
shortcut = Conv2D(nb_filter3, (1, 1), strides=strides,
89+
name=conv_name_base + '1', use_bias=False)(input_tensor)
90+
shortcut = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '1')(shortcut)
91+
shortcut = Scale(axis=bn_axis, name=scale_name_base + '1')(shortcut)
92+
93+
x = merge([x, shortcut], mode='sum', name='res' + str(stage) + block)
94+
x = Activation('relu', name='res' + str(stage) + block + '_relu')(x)
95+
return x
96+
97+
98+
def resnet152_model(img_rows, img_cols, color_type=1, num_classes=None):
99+
"""
100+
Resnet 152 Model for Keras
101+
102+
Model Schema and layer naming follow that of the original Caffe implementation
103+
https://github.com/KaimingHe/deep-residual-networks
104+
105+
ImageNet Pretrained Weights
106+
Theano: https://drive.google.com/file/d/0Byy2AcGyEVxfZHhUT3lWVWxRN28/view?usp=sharing
107+
TensorFlow: https://drive.google.com/file/d/0Byy2AcGyEVxfeXExMzNNOHpEODg/view?usp=sharing
108+
109+
Parameters:
110+
img_rows, img_cols - resolution of inputs
111+
channel - 1 for grayscale, 3 for color
112+
num_classes - number of class labels for our classification task
113+
"""
114+
eps = 1.1e-5
115+
116+
# Handle Dimension Ordering for different backends
117+
global bn_axis
118+
if K.image_dim_ordering() == 'tf':
119+
bn_axis = 3
120+
img_input = Input(shape=(img_rows, img_cols, color_type), name='data')
121+
else:
122+
bn_axis = 1
123+
img_input = Input(shape=(color_type, img_rows, img_cols), name='data')
124+
125+
x = ZeroPadding2D((3, 3), name='conv1_zeropadding')(img_input)
126+
x = Conv2D(64, (7, 7), strides=(2, 2), name='conv1', use_bias=False)(x)
127+
x = BatchNormalization(epsilon=eps, axis=bn_axis, name='bn_conv1')(x)
128+
x = Scale(axis=bn_axis, name='scale_conv1')(x)
129+
x = Activation('relu', name='conv1_relu')(x)
130+
x = MaxPooling2D((3, 3), strides=(2, 2), name='pool1')(x)
131+
132+
x = conv_block(x, 3, [64, 64, 256], stage=2, block='a', strides=(1, 1))
133+
x = identity_block(x, 3, [64, 64, 256], stage=2, block='b')
134+
x = identity_block(x, 3, [64, 64, 256], stage=2, block='c')
135+
136+
x = conv_block(x, 3, [128, 128, 512], stage=3, block='a')
137+
for i in range(1, 8):
138+
x = identity_block(x, 3, [128, 128, 512], stage=3, block='b' + str(i))
139+
140+
x = conv_block(x, 3, [256, 256, 1024], stage=4, block='a')
141+
for i in range(1, 36):
142+
x = identity_block(x, 3, [256, 256, 1024], stage=4, block='b' + str(i))
143+
144+
x = conv_block(x, 3, [512, 512, 2048], stage=5, block='a')
145+
x = identity_block(x, 3, [512, 512, 2048], stage=5, block='b')
146+
x = identity_block(x, 3, [512, 512, 2048], stage=5, block='c')
147+
148+
x_fc = AveragePooling2D((7, 7), name='avg_pool')(x)
149+
x_fc = Flatten()(x_fc)
150+
x_fc = Dense(1000, activation='softmax', name='fc1000')(x_fc)
151+
152+
model = Model(img_input, x_fc)
153+
154+
if K.image_dim_ordering() == 'th':
155+
# Use pre-trained weights for Theano backend
156+
weights_path = 'imagenet_models/resnet152_weights_th.h5'
157+
else:
158+
# Use pre-trained weights for Tensorflow backend
159+
weights_path = 'imagenet_models/resnet152_weights_tf.h5'
160+
161+
model.load_weights(weights_path, by_name=True)
162+
163+
# Truncate and replace softmax layer for transfer learning
164+
# Cannot use model.layers.pop() since model is not of Sequential() type
165+
# The method below works since pre-trained weights are stored in layers but not in the model
166+
x_newfc = AveragePooling2D((7, 7), name='avg_pool')(x)
167+
x_newfc = Flatten()(x_newfc)
168+
x_newfc = Dense(num_classes, activation='softmax', name='fc8')(x_newfc)
169+
170+
model = Model(img_input, x_newfc)
171+
172+
# Learning rate is changed to 0.001
173+
sgd = SGD(lr=1e-3, decay=1e-6, momentum=0.9, nesterov=True)
174+
model.compile(optimizer=sgd, loss='categorical_crossentropy', metrics=['accuracy'])
175+
176+
return model
177+
178+
179+
if __name__ == '__main__':
180+
# Example to fine-tune on 3000 samples from Cifar10
181+
182+
img_rows, img_cols = 224, 224 # Resolution of inputs
183+
channel = 3
184+
num_classes = 10
185+
batch_size = 8
186+
epochs = 10
187+
188+
# Load Cifar10 data. Please implement your own load_data() module for your own dataset
189+
X_train, Y_train, X_valid, Y_valid = load_cifar10_data(img_rows, img_cols)
190+
191+
# Load our model
192+
model = resnet152_model(img_rows, img_cols, channel, num_classes)
193+
194+
# Start Fine-tuning
195+
model.fit(X_train, Y_train,
196+
batch_size=batch_size,
197+
epochs=epochs,
198+
shuffle=True,
199+
verbose=1,
200+
validation_data=(X_valid, Y_valid),
201+
)
202+
203+
# Make predictions
204+
predictions_valid = model.predict(X_valid, batch_size=batch_size, verbose=1)
205+
206+
# Cross-entropy loss score
207+
score = log_loss(Y_valid, predictions_valid)

0 commit comments

Comments
 (0)