Commited minutiae
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
"""Code for FineNet in paper "Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge" at ICB 2018
|
||||
https://arxiv.org/pdf/1712.09401.pdf
|
||||
|
||||
If you use whole or partial function in this code, please cite paper:
|
||||
|
||||
@inproceedings{Nguyen_MinutiaeNet,
|
||||
author = {Dinh-Luan Nguyen and Kai Cao and Anil K. Jain},
|
||||
title = {Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge},
|
||||
booktitle = {The 11th International Conference on Biometrics, 2018},
|
||||
year = {2018},
|
||||
}
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
from keras.models import Model
|
||||
from keras.layers import Activation, AveragePooling2D, BatchNormalization, Concatenate, Conv2D, Dense, GlobalAveragePooling2D
|
||||
from keras.layers import Input, Lambda, MaxPooling2D
|
||||
from keras.applications.imagenet_utils import _obtain_input_shape
|
||||
from keras import backend as K
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import itertools
|
||||
|
||||
def preprocess_input(x):
|
||||
"""Preprocesses a numpy array encoding a batch of images.
|
||||
|
||||
"""
|
||||
return keras.applications.imagenet_utils.preprocess_input(x, mode='tf')
|
||||
|
||||
|
||||
def conv2d_bn(x,
|
||||
filters,
|
||||
kernel_size,
|
||||
strides=1,
|
||||
padding='same',
|
||||
activation='relu',
|
||||
use_bias=False,
|
||||
name=None):
|
||||
"""Utility function to apply conv + BN.
|
||||
|
||||
"""
|
||||
x = Conv2D(filters,
|
||||
kernel_size,
|
||||
strides=strides,
|
||||
padding=padding,
|
||||
use_bias=use_bias,
|
||||
name=name)(x)
|
||||
if not use_bias:
|
||||
bn_axis = 1 if K.image_data_format() == 'channels_first' else 3
|
||||
bn_name = None if name is None else name + '_bn'
|
||||
x = BatchNormalization(axis=bn_axis, scale=False, name=bn_name)(x)
|
||||
if activation is not None:
|
||||
ac_name = None if name is None else name + '_ac'
|
||||
x = Activation(activation, name=ac_name)(x)
|
||||
return x
|
||||
|
||||
|
||||
def inception_resnet_block(x, scale, block_type, block_idx, activation='relu'):
|
||||
"""Inception-ResNet block.
|
||||
|
||||
"""
|
||||
if block_type == 'block35':
|
||||
branch_0 = conv2d_bn(x, 32, 1)
|
||||
branch_1 = conv2d_bn(x, 32, 1)
|
||||
branch_1 = conv2d_bn(branch_1, 32, 3)
|
||||
branch_2 = conv2d_bn(x, 32, 1)
|
||||
branch_2 = conv2d_bn(branch_2, 48, 3)
|
||||
branch_2 = conv2d_bn(branch_2, 64, 3)
|
||||
branches = [branch_0, branch_1, branch_2]
|
||||
elif block_type == 'block17':
|
||||
branch_0 = conv2d_bn(x, 192, 1)
|
||||
branch_1 = conv2d_bn(x, 128, 1)
|
||||
branch_1 = conv2d_bn(branch_1, 160, [1, 7])
|
||||
branch_1 = conv2d_bn(branch_1, 192, [7, 1])
|
||||
branches = [branch_0, branch_1]
|
||||
elif block_type == 'block8':
|
||||
branch_0 = conv2d_bn(x, 192, 1)
|
||||
branch_1 = conv2d_bn(x, 192, 1)
|
||||
branch_1 = conv2d_bn(branch_1, 224, [1, 3])
|
||||
branch_1 = conv2d_bn(branch_1, 256, [3, 1])
|
||||
branches = [branch_0, branch_1]
|
||||
else:
|
||||
raise ValueError('Unknown Inception-ResNet block type. '
|
||||
'Expects "block35", "block17" or "block8", '
|
||||
'but got: ' + str(block_type))
|
||||
|
||||
block_name = block_type + '_' + str(block_idx)
|
||||
channel_axis = 1 if K.image_data_format() == 'channels_first' else 3
|
||||
mixed = Concatenate(axis=channel_axis, name=block_name + '_mixed')(branches)
|
||||
up = conv2d_bn(mixed,
|
||||
K.int_shape(x)[channel_axis],
|
||||
1,
|
||||
activation=None,
|
||||
use_bias=True,
|
||||
name=block_name + '_conv')
|
||||
|
||||
x = Lambda(lambda inputs, scale: inputs[0] + inputs[1] * scale,
|
||||
output_shape=K.int_shape(x)[1:],
|
||||
arguments={'scale': scale},
|
||||
name=block_name)([x, up])
|
||||
if activation is not None:
|
||||
x = Activation(activation, name=block_name + '_ac')(x)
|
||||
return x
|
||||
|
||||
def FineNetmodel(num_classes = 2, pretrained_path = None, input_shape = None):
|
||||
"""Create FineNet architecture.
|
||||
|
||||
"""
|
||||
# Determine proper input shape
|
||||
input_shape = _obtain_input_shape(
|
||||
input_shape,
|
||||
default_size=299,
|
||||
min_size=139,
|
||||
data_format=K.image_data_format(),
|
||||
require_flatten=False,
|
||||
weights=pretrained_path)
|
||||
|
||||
|
||||
img_input = Input(shape=input_shape)
|
||||
|
||||
# Stem block: 35 x 35 x 192
|
||||
x = conv2d_bn(img_input, 32, 3, strides=2, padding='valid')
|
||||
x = conv2d_bn(x, 32, 3, padding='valid')
|
||||
x = conv2d_bn(x, 64, 3)
|
||||
x = MaxPooling2D(3, strides=2)(x)
|
||||
x = conv2d_bn(x, 80, 1, padding='valid')
|
||||
x = conv2d_bn(x, 192, 3, padding='valid')
|
||||
x = MaxPooling2D(3, strides=2)(x)
|
||||
|
||||
# Mixed 5b (Inception-A block): 35 x 35 x 320
|
||||
branch_0 = conv2d_bn(x, 96, 1)
|
||||
branch_1 = conv2d_bn(x, 48, 1)
|
||||
branch_1 = conv2d_bn(branch_1, 64, 5)
|
||||
branch_2 = conv2d_bn(x, 64, 1)
|
||||
branch_2 = conv2d_bn(branch_2, 96, 3)
|
||||
branch_2 = conv2d_bn(branch_2, 96, 3)
|
||||
branch_pool = AveragePooling2D(3, strides=1, padding='same')(x)
|
||||
branch_pool = conv2d_bn(branch_pool, 64, 1)
|
||||
branches = [branch_0, branch_1, branch_2, branch_pool]
|
||||
channel_axis = 1 if K.image_data_format() == 'channels_first' else 3
|
||||
x = Concatenate(axis=channel_axis, name='mixed_5b')(branches)
|
||||
|
||||
# 10x block35 (Inception-ResNet-A block): 35 x 35 x 320
|
||||
for block_idx in range(1, 11):
|
||||
x = inception_resnet_block(x,
|
||||
scale=0.17,
|
||||
block_type='block35',
|
||||
block_idx=block_idx)
|
||||
|
||||
# Mixed 6a (Reduction-A block): 17 x 17 x 1088
|
||||
branch_0 = conv2d_bn(x, 384, 3, strides=2, padding='valid')
|
||||
branch_1 = conv2d_bn(x, 256, 1)
|
||||
branch_1 = conv2d_bn(branch_1, 256, 3)
|
||||
branch_1 = conv2d_bn(branch_1, 384, 3, strides=2, padding='valid')
|
||||
branch_pool = MaxPooling2D(3, strides=2, padding='valid')(x)
|
||||
branches = [branch_0, branch_1, branch_pool]
|
||||
x = Concatenate(axis=channel_axis, name='mixed_6a')(branches)
|
||||
|
||||
# 20x block17 (Inception-ResNet-B block): 17 x 17 x 1088
|
||||
for block_idx in range(1, 21):
|
||||
x = inception_resnet_block(x,
|
||||
scale=0.1,
|
||||
block_type='block17',
|
||||
block_idx=block_idx)
|
||||
|
||||
# Mixed 7a (Reduction-B block): 8 x 8 x 2080
|
||||
branch_0 = conv2d_bn(x, 256, 1)
|
||||
branch_0 = conv2d_bn(branch_0, 384, 3, strides=2, padding='valid')
|
||||
branch_1 = conv2d_bn(x, 256, 1)
|
||||
branch_1 = conv2d_bn(branch_1, 288, 3, strides=2, padding='valid')
|
||||
branch_2 = conv2d_bn(x, 256, 1)
|
||||
branch_2 = conv2d_bn(branch_2, 288, 3)
|
||||
branch_2 = conv2d_bn(branch_2, 320, 3, strides=2, padding='valid')
|
||||
branch_pool = MaxPooling2D(3, strides=2, padding='valid')(x)
|
||||
branches = [branch_0, branch_1, branch_2, branch_pool]
|
||||
x = Concatenate(axis=channel_axis, name='mixed_7a')(branches)
|
||||
|
||||
# 10x block8 (Inception-ResNet-C block): 8 x 8 x 2080
|
||||
for block_idx in range(1, 10):
|
||||
x = inception_resnet_block(x,
|
||||
scale=0.2,
|
||||
block_type='block8',
|
||||
block_idx=block_idx)
|
||||
x = inception_resnet_block(x,
|
||||
scale=1.,
|
||||
activation=None,
|
||||
block_type='block8',
|
||||
block_idx=10)
|
||||
|
||||
# Final convolution block: 8 x 8 x 1536
|
||||
x = conv2d_bn(x, 1536, 1, name='conv_7b')
|
||||
|
||||
# Classification block
|
||||
x = GlobalAveragePooling2D(name='avg_pool')(x)
|
||||
x = Dense(num_classes, activation='softmax', name='predictions')(x)
|
||||
|
||||
|
||||
inputs = img_input
|
||||
|
||||
# Create model
|
||||
model = Model(inputs, x, name='FineNet')
|
||||
|
||||
# Load weights
|
||||
if pretrained_path != None:
|
||||
print 'Loading FineNet weights from %s'%(pretrained_path)
|
||||
model.load_weights(pretrained_path)
|
||||
|
||||
return model
|
||||
|
||||
def plot_confusion_matrix(cm, classes,
|
||||
normalize=False,
|
||||
title='Confusion matrix',
|
||||
cmap=plt.cm.Blues):
|
||||
"""
|
||||
This function prints and plots the confusion matrix.
|
||||
Normalization can be applied by setting `normalize=True`.
|
||||
"""
|
||||
plt.imshow(cm, interpolation='nearest', cmap=cmap)
|
||||
plt.title(title)
|
||||
plt.colorbar()
|
||||
tick_marks = np.arange(len(classes))
|
||||
plt.xticks(tick_marks, classes, rotation=45)
|
||||
plt.yticks(tick_marks, classes)
|
||||
|
||||
if normalize:
|
||||
cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
|
||||
print("Normalized confusion matrix")
|
||||
else:
|
||||
print('Confusion matrix, without normalization')
|
||||
|
||||
print(cm)
|
||||
|
||||
thresh = cm.max() / 2.
|
||||
for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
|
||||
plt.text(j, i, cm[i, j],
|
||||
horizontalalignment="center",
|
||||
color="white" if cm[i, j] > thresh else "black")
|
||||
|
||||
plt.tight_layout()
|
||||
plt.ylabel('True label')
|
||||
plt.xlabel('Predicted label')
|
||||
plt.show()
|
||||
@@ -0,0 +1,172 @@
|
||||
"""Code for FineNet in paper "Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge" at ICB 2018
|
||||
https://arxiv.org/pdf/1712.09401.pdf
|
||||
|
||||
If you use whole or partial function in this code, please cite paper:
|
||||
|
||||
@inproceedings{Nguyen_MinutiaeNet,
|
||||
author = {Dinh-Luan Nguyen and Kai Cao and Anil K. Jain},
|
||||
title = {Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge},
|
||||
booktitle = {The 11th International Conference on Biometrics, 2018},
|
||||
year = {2018},
|
||||
}
|
||||
"""
|
||||
|
||||
import sys,os
|
||||
sys.path.append(os.path.realpath('../FineNet'))
|
||||
|
||||
from keras.optimizers import Adam
|
||||
from keras.callbacks import ModelCheckpoint, LearningRateScheduler, TensorBoard
|
||||
from keras.callbacks import ReduceLROnPlateau
|
||||
from keras.preprocessing.image import ImageDataGenerator
|
||||
from FineNet_model import FineNetmodel, plot_confusion_matrix
|
||||
|
||||
import numpy as np
|
||||
import os
|
||||
from sklearn.metrics import confusion_matrix
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
os.environ["CUDA_VISIBLE_DEVICES"] = '2'
|
||||
os.environ['KERAS_BACKEND'] = 'tensorflow'
|
||||
|
||||
|
||||
output_dir = '../output_FineNet/'+datetime.now().strftime('%Y%m%d-%H%M%S')
|
||||
|
||||
# Prepare model model saving directory.
|
||||
save_dir = os.path.join(os.getcwd(), output_dir)
|
||||
log_dir = os.path.join(os.getcwd(), output_dir + '/logs')
|
||||
|
||||
# Training parameters
|
||||
batch_size = 32
|
||||
epochs = 200
|
||||
num_classes = 2
|
||||
|
||||
# Subtracting pixel mean improves accuracy
|
||||
subtract_pixel_mean = True
|
||||
|
||||
# Model size, patch
|
||||
model_type = 'patch224batch32'
|
||||
|
||||
|
||||
# =============== DATA loading ========================
|
||||
|
||||
train_path = '../Dataset/train/'
|
||||
test_path = '../Dataset/validate/'
|
||||
|
||||
input_shape = (224, 224, 3)
|
||||
|
||||
# Using data augmentation technique for training
|
||||
datagen = ImageDataGenerator(
|
||||
# set input mean to 0 over the dataset
|
||||
featurewise_center=False,
|
||||
# set each sample mean to 0
|
||||
samplewise_center=False,
|
||||
# divide inputs by std of dataset
|
||||
featurewise_std_normalization=False,
|
||||
# divide each input by its std
|
||||
samplewise_std_normalization=False,
|
||||
# apply ZCA whitening
|
||||
zca_whitening=False,
|
||||
# randomly rotate images in the range (deg 0 to 180)
|
||||
rotation_range=180,
|
||||
# randomly shift images horizontally
|
||||
width_shift_range=0.5,
|
||||
# randomly shift images vertically
|
||||
height_shift_range=0.5,
|
||||
# randomly flip images
|
||||
horizontal_flip=True,
|
||||
# randomly flip images
|
||||
vertical_flip=True)
|
||||
|
||||
train_batches = datagen.flow_from_directory(train_path, target_size=(input_shape[0], input_shape[1]), classes=['minu', 'non_minu'], batch_size=batch_size)
|
||||
# Feed data from directory into batches
|
||||
test_gen = ImageDataGenerator()
|
||||
test_batches = test_gen.flow_from_directory(test_path, target_size=(input_shape[0], input_shape[1]), classes=['minu', 'non_minu'], batch_size=batch_size)
|
||||
|
||||
|
||||
# =============== end DATA loading ========================
|
||||
|
||||
|
||||
|
||||
def lr_schedule(epoch):
|
||||
"""Learning Rate Schedule
|
||||
"""
|
||||
lr = 0.5e-2
|
||||
if epoch > 180:
|
||||
lr *= 0.5e-3
|
||||
elif epoch > 150:
|
||||
lr *= 1e-3
|
||||
elif epoch > 60:
|
||||
lr *= 5e-2
|
||||
elif epoch > 30:
|
||||
lr *= 5e-1
|
||||
print('Learning rate: ', lr)
|
||||
return lr
|
||||
|
||||
|
||||
|
||||
|
||||
#============== Define model ==================
|
||||
|
||||
model = FineNetmodel(num_classes = num_classes,
|
||||
pretrained_path = '../Models/FineNet.h5',
|
||||
input_shape=input_shape)
|
||||
|
||||
# Save model architecture
|
||||
#plot_model(model, to_file='./modelFineNet.pdf',show_shapes=True)
|
||||
|
||||
model.compile(loss='categorical_crossentropy',
|
||||
optimizer=Adam(lr=lr_schedule(0)),
|
||||
metrics=['accuracy'])
|
||||
#model.summary()
|
||||
|
||||
#============== End define model ==============
|
||||
|
||||
|
||||
#============== Other stuffs for loging and parameters ==================
|
||||
model_name = 'FineNet_%s_model.{epoch:03d}.h5' % model_type
|
||||
if not os.path.isdir(save_dir):
|
||||
os.makedirs(save_dir)
|
||||
if not os.path.isdir(log_dir):
|
||||
os.makedirs(log_dir)
|
||||
|
||||
filepath = os.path.join(save_dir, model_name)
|
||||
|
||||
|
||||
# Show in tensorboard
|
||||
tensorboard = TensorBoard(log_dir=log_dir, histogram_freq=0, write_graph=True, write_images=False)
|
||||
|
||||
# Prepare callbacks for model saving and for learning rate adjustment.
|
||||
checkpoint = ModelCheckpoint(filepath=filepath,
|
||||
monitor='val_acc',
|
||||
verbose=1,
|
||||
save_best_only=True)
|
||||
|
||||
lr_scheduler = LearningRateScheduler(lr_schedule)
|
||||
|
||||
lr_reducer = ReduceLROnPlateau(factor=np.sqrt(0.1),
|
||||
cooldown=0,
|
||||
patience=5,
|
||||
min_lr=0.5e-6)
|
||||
|
||||
callbacks = [checkpoint, lr_reducer, lr_scheduler, tensorboard]
|
||||
|
||||
#============== End other stuffs ==================
|
||||
|
||||
# Begin training
|
||||
model.fit_generator(train_batches,
|
||||
validation_data=test_batches,
|
||||
epochs=epochs, verbose=1,
|
||||
callbacks=callbacks)
|
||||
|
||||
|
||||
|
||||
# Plot confusion matrix
|
||||
score = model.evaluate_generator(test_batches)
|
||||
print 'Test accuracy:', score[1]
|
||||
predictions = model.predict_generator(test_batches)
|
||||
test_labels = test_batches.classes[test_batches.index_array]
|
||||
|
||||
cm = confusion_matrix(test_labels, np.argmax(predictions,axis=1))
|
||||
cm_plot_labels = ['minu','non_minu']
|
||||
plot_confusion_matrix(cm, cm_plot_labels, title='Confusion Matrix')
|
||||
Reference in New Issue
Block a user