mirror of
https://github.com/wassname/PSPNet-Keras-tensorflow.git
synced 2026-09-12 12:01:22 +08:00
Made commit of original caffe-tensorflow converter
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
# ImageNet Examples
|
||||
|
||||
This folder contains two examples that demonstrate how to use converted networks for
|
||||
image classification. Also included are sample converted models and helper scripts.
|
||||
|
||||
## 1. Image Classification
|
||||
|
||||
`classify.py` uses a GoogleNet trained on ImageNet, converted to TensorFlow, for classifying images.
|
||||
|
||||
The architecture used is defined in `models/googlenet.py` (which was auto-generated). You will need
|
||||
to download and convert the weights from Caffe to run the example. The download link for the
|
||||
corresponding weights can be found in Caffe's `models/bvlc_googlenet/` folder.
|
||||
|
||||
You can run this example like so:
|
||||
|
||||
$ ./classify.py /path/to/googlenet.npy ~/pics/kitty.png ~/pics/woof.jpg
|
||||
|
||||
You should expect to see an output similar to this:
|
||||
|
||||
Image Classified As Confidence
|
||||
----------------------------------------------------------------------
|
||||
kitty.png Persian cat 99.75 %
|
||||
woof.jpg Bernese mountain dog 82.02 %
|
||||
|
||||
|
||||
## 2. ImageNet Validation
|
||||
|
||||
`validate.py` evaluates a converted model against the ImageNet (ILSVRC12) validation set. To run
|
||||
this script, you will need a copy of the ImageNet validation set. You can run it as follows:
|
||||
|
||||
$ ./validate.py alexnet.npy val.txt imagenet-val/ --model AlexNet
|
||||
|
||||
The validation results specified in the main readme were generated using this script.
|
||||
|
||||
## Helper Scripts
|
||||
|
||||
In addition to the examples above, this folder includes a few additional files:
|
||||
|
||||
- `dataset.py` : helper script for loading, pre-processing, and iterating over images
|
||||
- `models/` : contains converted models (auto-generated)
|
||||
- `models/helper.py` : describes how the data should be preprocessed for each model
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python
|
||||
import argparse
|
||||
import numpy as np
|
||||
import tensorflow as tf
|
||||
import os.path as osp
|
||||
|
||||
import models
|
||||
import dataset
|
||||
|
||||
|
||||
def display_results(image_paths, probs):
|
||||
'''Displays the classification results given the class probability for each image'''
|
||||
# Get a list of ImageNet class labels
|
||||
with open('imagenet-classes.txt', 'rb') as infile:
|
||||
class_labels = map(str.strip, infile.readlines())
|
||||
# Pick the class with the highest confidence for each image
|
||||
class_indices = np.argmax(probs, axis=1)
|
||||
# Display the results
|
||||
print('\n{:20} {:30} {}'.format('Image', 'Classified As', 'Confidence'))
|
||||
print('-' * 70)
|
||||
for img_idx, image_path in enumerate(image_paths):
|
||||
img_name = osp.basename(image_path)
|
||||
class_name = class_labels[class_indices[img_idx]]
|
||||
confidence = round(probs[img_idx, class_indices[img_idx]] * 100, 2)
|
||||
print('{:20} {:30} {} %'.format(img_name, class_name, confidence))
|
||||
|
||||
|
||||
def classify(model_data_path, image_paths):
|
||||
'''Classify the given images using GoogleNet.'''
|
||||
|
||||
# Get the data specifications for the GoogleNet model
|
||||
spec = models.get_data_spec(model_class=models.GoogleNet)
|
||||
|
||||
# Create a placeholder for the input image
|
||||
input_node = tf.placeholder(tf.float32,
|
||||
shape=(None, spec.crop_size, spec.crop_size, spec.channels))
|
||||
|
||||
# Construct the network
|
||||
net = models.GoogleNet({'data': input_node})
|
||||
|
||||
# Create an image producer (loads and processes images in parallel)
|
||||
image_producer = dataset.ImageProducer(image_paths=image_paths, data_spec=spec)
|
||||
|
||||
with tf.Session() as sesh:
|
||||
# Start the image processing workers
|
||||
coordinator = tf.train.Coordinator()
|
||||
threads = image_producer.start(session=sesh, coordinator=coordinator)
|
||||
|
||||
# Load the converted parameters
|
||||
print('Loading the model')
|
||||
net.load(model_data_path, sesh)
|
||||
|
||||
# Load the input image
|
||||
print('Loading the images')
|
||||
indices, input_images = image_producer.get(sesh)
|
||||
|
||||
# Perform a forward pass through the network to get the class probabilities
|
||||
print('Classifying')
|
||||
probs = sesh.run(net.get_output(), feed_dict={input_node: input_images})
|
||||
display_results([image_paths[i] for i in indices], probs)
|
||||
|
||||
# Stop the worker threads
|
||||
coordinator.request_stop()
|
||||
coordinator.join(threads, stop_grace_period_secs=2)
|
||||
|
||||
def main():
|
||||
# Parse arguments
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('model_path', help='Converted parameters for the GoogleNet model')
|
||||
parser.add_argument('image_paths', nargs='+', help='One or more images to classify')
|
||||
args = parser.parse_args()
|
||||
|
||||
# Classify the image
|
||||
classify(args.model_path, args.image_paths)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,178 @@
|
||||
'''Utility functions and classes for handling image datasets.'''
|
||||
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import tensorflow as tf
|
||||
|
||||
|
||||
def process_image(img, scale, isotropic, crop, mean):
|
||||
'''Crops, scales, and normalizes the given image.
|
||||
scale : The image wil be first scaled to this size.
|
||||
If isotropic is true, the smaller side is rescaled to this,
|
||||
preserving the aspect ratio.
|
||||
crop : After scaling, a central crop of this size is taken.
|
||||
mean : Subtracted from the image
|
||||
'''
|
||||
# Rescale
|
||||
if isotropic:
|
||||
img_shape = tf.to_float(tf.shape(img)[:2])
|
||||
min_length = tf.minimum(img_shape[0], img_shape[1])
|
||||
new_shape = tf.to_int32((scale / min_length) * img_shape)
|
||||
else:
|
||||
new_shape = tf.pack([scale, scale])
|
||||
img = tf.image.resize_images(img, new_shape[0], new_shape[1])
|
||||
# Center crop
|
||||
# Use the slice workaround until crop_to_bounding_box supports deferred tensor shapes
|
||||
# See: https://github.com/tensorflow/tensorflow/issues/521
|
||||
offset = (new_shape - crop) / 2
|
||||
img = tf.slice(img, begin=tf.pack([offset[0], offset[1], 0]), size=tf.pack([crop, crop, -1]))
|
||||
# Mean subtraction
|
||||
return tf.to_float(img) - mean
|
||||
|
||||
|
||||
class ImageProducer(object):
|
||||
'''
|
||||
Loads and processes batches of images in parallel.
|
||||
'''
|
||||
|
||||
def __init__(self, image_paths, data_spec, num_concurrent=4, batch_size=None, labels=None):
|
||||
# The data specifications describe how to process the image
|
||||
self.data_spec = data_spec
|
||||
# A list of full image paths
|
||||
self.image_paths = image_paths
|
||||
# An optional list of labels corresponding to each image path
|
||||
self.labels = labels
|
||||
# A boolean flag per image indicating whether its a JPEG or PNG
|
||||
self.extension_mask = self.create_extension_mask(self.image_paths)
|
||||
# Create the loading and processing operations
|
||||
self.setup(batch_size=batch_size, num_concurrent=num_concurrent)
|
||||
|
||||
def setup(self, batch_size, num_concurrent):
|
||||
# Validate the batch size
|
||||
num_images = len(self.image_paths)
|
||||
batch_size = min(num_images, batch_size or self.data_spec.batch_size)
|
||||
if num_images % batch_size != 0:
|
||||
raise ValueError(
|
||||
'The total number of images ({}) must be divisible by the batch size ({}).'.format(
|
||||
num_images, batch_size))
|
||||
self.num_batches = num_images / batch_size
|
||||
|
||||
# Create a queue that will contain image paths (and their indices and extension indicator)
|
||||
self.path_queue = tf.FIFOQueue(capacity=num_images,
|
||||
dtypes=[tf.int32, tf.bool, tf.string],
|
||||
name='path_queue')
|
||||
|
||||
# Enqueue all image paths, along with their indices
|
||||
indices = tf.range(num_images)
|
||||
self.enqueue_paths_op = self.path_queue.enqueue_many([indices, self.extension_mask,
|
||||
self.image_paths])
|
||||
# Close the path queue (no more additions)
|
||||
self.close_path_queue_op = self.path_queue.close()
|
||||
|
||||
# Create an operation that dequeues a single path and returns a processed image
|
||||
(idx, processed_image) = self.process()
|
||||
|
||||
# Create a queue that will contain the processed images (and their indices)
|
||||
image_shape = (self.data_spec.crop_size, self.data_spec.crop_size, self.data_spec.channels)
|
||||
processed_queue = tf.FIFOQueue(capacity=int(np.ceil(num_images / float(num_concurrent))),
|
||||
dtypes=[tf.int32, tf.float32],
|
||||
shapes=[(), image_shape],
|
||||
name='processed_queue')
|
||||
|
||||
# Enqueue the processed image and path
|
||||
enqueue_processed_op = processed_queue.enqueue([idx, processed_image])
|
||||
|
||||
# Create a dequeue op that fetches a batch of processed images off the queue
|
||||
self.dequeue_op = processed_queue.dequeue_many(batch_size)
|
||||
|
||||
# Create a queue runner to perform the processing operations in parallel
|
||||
num_concurrent = min(num_concurrent, num_images)
|
||||
self.queue_runner = tf.train.QueueRunner(processed_queue,
|
||||
[enqueue_processed_op] * num_concurrent)
|
||||
|
||||
def start(self, session, coordinator, num_concurrent=4):
|
||||
'''Start the processing worker threads.'''
|
||||
# Queue all paths
|
||||
session.run(self.enqueue_paths_op)
|
||||
# Close the path queue
|
||||
session.run(self.close_path_queue_op)
|
||||
# Start the queue runner and return the created threads
|
||||
return self.queue_runner.create_threads(session, coord=coordinator, start=True)
|
||||
|
||||
def get(self, session):
|
||||
'''
|
||||
Get a single batch of images along with their indices. If a set of labels were provided,
|
||||
the corresponding labels are returned instead of the indices.
|
||||
'''
|
||||
(indices, images) = session.run(self.dequeue_op)
|
||||
if self.labels is not None:
|
||||
labels = [self.labels[idx] for idx in indices]
|
||||
return (labels, images)
|
||||
return (indices, images)
|
||||
|
||||
def batches(self, session):
|
||||
'''Yield a batch until no more images are left.'''
|
||||
for _ in xrange(self.num_batches):
|
||||
yield self.get(session=session)
|
||||
|
||||
def load_image(self, image_path, is_jpeg):
|
||||
# Read the file
|
||||
file_data = tf.read_file(image_path)
|
||||
# Decode the image data
|
||||
img = tf.cond(
|
||||
is_jpeg,
|
||||
lambda: tf.image.decode_jpeg(file_data, channels=self.data_spec.channels),
|
||||
lambda: tf.image.decode_png(file_data, channels=self.data_spec.channels))
|
||||
if self.data_spec.expects_bgr:
|
||||
# Convert from RGB channel ordering to BGR
|
||||
# This matches, for instance, how OpenCV orders the channels.
|
||||
img = tf.reverse(img, [False, False, True])
|
||||
return img
|
||||
|
||||
def process(self):
|
||||
# Dequeue a single image path
|
||||
idx, is_jpeg, image_path = self.path_queue.dequeue()
|
||||
# Load the image
|
||||
img = self.load_image(image_path, is_jpeg)
|
||||
# Process the image
|
||||
processed_img = process_image(img=img,
|
||||
scale=self.data_spec.scale_size,
|
||||
isotropic=self.data_spec.isotropic,
|
||||
crop=self.data_spec.crop_size,
|
||||
mean=self.data_spec.mean)
|
||||
# Return the processed image, along with its index
|
||||
return (idx, processed_img)
|
||||
|
||||
@staticmethod
|
||||
def create_extension_mask(paths):
|
||||
|
||||
def is_jpeg(path):
|
||||
extension = osp.splitext(path)[-1].lower()
|
||||
if extension in ('.jpg', '.jpeg'):
|
||||
return True
|
||||
if extension != '.png':
|
||||
raise ValueError('Unsupported image format: {}'.format(extension))
|
||||
return False
|
||||
|
||||
return [is_jpeg(p) for p in paths]
|
||||
|
||||
def __len__(self):
|
||||
return len(self.image_paths)
|
||||
|
||||
|
||||
class ImageNetProducer(ImageProducer):
|
||||
|
||||
def __init__(self, val_path, data_path, data_spec):
|
||||
# Read in the ground truth labels for the validation set
|
||||
# The get_ilsvrc_aux.sh in Caffe's data/ilsvrc12 folder can fetch a copy of val.txt
|
||||
gt_lines = open(val_path).readlines()
|
||||
gt_pairs = [line.split() for line in gt_lines]
|
||||
# Get the full image paths
|
||||
# You will need a copy of the ImageNet validation set for this.
|
||||
image_paths = [osp.join(data_path, p[0]) for p in gt_pairs]
|
||||
# The corresponding ground truth labels
|
||||
labels = np.array([int(p[1]) for p in gt_pairs])
|
||||
# Initialize base
|
||||
super(ImageNetProducer, self).__init__(image_paths=image_paths,
|
||||
data_spec=data_spec,
|
||||
labels=labels)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
from helper import *
|
||||
@@ -0,0 +1,19 @@
|
||||
from kaffe.tensorflow import Network
|
||||
|
||||
class AlexNet(Network):
|
||||
def setup(self):
|
||||
(self.feed('data')
|
||||
.conv(11, 11, 96, 4, 4, padding='VALID', name='conv1')
|
||||
.lrn(2, 2e-05, 0.75, name='norm1')
|
||||
.max_pool(3, 3, 2, 2, padding='VALID', name='pool1')
|
||||
.conv(5, 5, 256, 1, 1, group=2, name='conv2')
|
||||
.lrn(2, 2e-05, 0.75, name='norm2')
|
||||
.max_pool(3, 3, 2, 2, padding='VALID', name='pool2')
|
||||
.conv(3, 3, 384, 1, 1, name='conv3')
|
||||
.conv(3, 3, 384, 1, 1, group=2, name='conv4')
|
||||
.conv(3, 3, 256, 1, 1, group=2, name='conv5')
|
||||
.max_pool(3, 3, 2, 2, padding='VALID', name='pool5')
|
||||
.fc(4096, name='fc6')
|
||||
.fc(4096, name='fc7')
|
||||
.fc(1000, relu=False, name='fc8')
|
||||
.softmax(name='prob'))
|
||||
@@ -0,0 +1,19 @@
|
||||
from kaffe.tensorflow import Network
|
||||
|
||||
class CaffeNet(Network):
|
||||
def setup(self):
|
||||
(self.feed('data')
|
||||
.conv(11, 11, 96, 4, 4, padding='VALID', name='conv1')
|
||||
.max_pool(3, 3, 2, 2, padding='VALID', name='pool1')
|
||||
.lrn(2, 2e-05, 0.75, name='norm1')
|
||||
.conv(5, 5, 256, 1, 1, group=2, name='conv2')
|
||||
.max_pool(3, 3, 2, 2, padding='VALID', name='pool2')
|
||||
.lrn(2, 2e-05, 0.75, name='norm2')
|
||||
.conv(3, 3, 384, 1, 1, name='conv3')
|
||||
.conv(3, 3, 384, 1, 1, group=2, name='conv4')
|
||||
.conv(3, 3, 256, 1, 1, group=2, name='conv5')
|
||||
.max_pool(3, 3, 2, 2, padding='VALID', name='pool5')
|
||||
.fc(4096, name='fc6')
|
||||
.fc(4096, name='fc7')
|
||||
.fc(1000, relu=False, name='fc8')
|
||||
.softmax(name='prob'))
|
||||
@@ -0,0 +1,188 @@
|
||||
from kaffe.tensorflow import Network
|
||||
|
||||
class GoogleNet(Network):
|
||||
def setup(self):
|
||||
(self.feed('data')
|
||||
.conv(7, 7, 64, 2, 2, name='conv1_7x7_s2')
|
||||
.max_pool(3, 3, 2, 2, name='pool1_3x3_s2')
|
||||
.lrn(2, 2e-05, 0.75, name='pool1_norm1')
|
||||
.conv(1, 1, 64, 1, 1, name='conv2_3x3_reduce')
|
||||
.conv(3, 3, 192, 1, 1, name='conv2_3x3')
|
||||
.lrn(2, 2e-05, 0.75, name='conv2_norm2')
|
||||
.max_pool(3, 3, 2, 2, name='pool2_3x3_s2')
|
||||
.conv(1, 1, 64, 1, 1, name='inception_3a_1x1'))
|
||||
|
||||
(self.feed('pool2_3x3_s2')
|
||||
.conv(1, 1, 96, 1, 1, name='inception_3a_3x3_reduce')
|
||||
.conv(3, 3, 128, 1, 1, name='inception_3a_3x3'))
|
||||
|
||||
(self.feed('pool2_3x3_s2')
|
||||
.conv(1, 1, 16, 1, 1, name='inception_3a_5x5_reduce')
|
||||
.conv(5, 5, 32, 1, 1, name='inception_3a_5x5'))
|
||||
|
||||
(self.feed('pool2_3x3_s2')
|
||||
.max_pool(3, 3, 1, 1, name='inception_3a_pool')
|
||||
.conv(1, 1, 32, 1, 1, name='inception_3a_pool_proj'))
|
||||
|
||||
(self.feed('inception_3a_1x1',
|
||||
'inception_3a_3x3',
|
||||
'inception_3a_5x5',
|
||||
'inception_3a_pool_proj')
|
||||
.concat(3, name='inception_3a_output')
|
||||
.conv(1, 1, 128, 1, 1, name='inception_3b_1x1'))
|
||||
|
||||
(self.feed('inception_3a_output')
|
||||
.conv(1, 1, 128, 1, 1, name='inception_3b_3x3_reduce')
|
||||
.conv(3, 3, 192, 1, 1, name='inception_3b_3x3'))
|
||||
|
||||
(self.feed('inception_3a_output')
|
||||
.conv(1, 1, 32, 1, 1, name='inception_3b_5x5_reduce')
|
||||
.conv(5, 5, 96, 1, 1, name='inception_3b_5x5'))
|
||||
|
||||
(self.feed('inception_3a_output')
|
||||
.max_pool(3, 3, 1, 1, name='inception_3b_pool')
|
||||
.conv(1, 1, 64, 1, 1, name='inception_3b_pool_proj'))
|
||||
|
||||
(self.feed('inception_3b_1x1',
|
||||
'inception_3b_3x3',
|
||||
'inception_3b_5x5',
|
||||
'inception_3b_pool_proj')
|
||||
.concat(3, name='inception_3b_output')
|
||||
.max_pool(3, 3, 2, 2, name='pool3_3x3_s2')
|
||||
.conv(1, 1, 192, 1, 1, name='inception_4a_1x1'))
|
||||
|
||||
(self.feed('pool3_3x3_s2')
|
||||
.conv(1, 1, 96, 1, 1, name='inception_4a_3x3_reduce')
|
||||
.conv(3, 3, 208, 1, 1, name='inception_4a_3x3'))
|
||||
|
||||
(self.feed('pool3_3x3_s2')
|
||||
.conv(1, 1, 16, 1, 1, name='inception_4a_5x5_reduce')
|
||||
.conv(5, 5, 48, 1, 1, name='inception_4a_5x5'))
|
||||
|
||||
(self.feed('pool3_3x3_s2')
|
||||
.max_pool(3, 3, 1, 1, name='inception_4a_pool')
|
||||
.conv(1, 1, 64, 1, 1, name='inception_4a_pool_proj'))
|
||||
|
||||
(self.feed('inception_4a_1x1',
|
||||
'inception_4a_3x3',
|
||||
'inception_4a_5x5',
|
||||
'inception_4a_pool_proj')
|
||||
.concat(3, name='inception_4a_output')
|
||||
.conv(1, 1, 160, 1, 1, name='inception_4b_1x1'))
|
||||
|
||||
(self.feed('inception_4a_output')
|
||||
.conv(1, 1, 112, 1, 1, name='inception_4b_3x3_reduce')
|
||||
.conv(3, 3, 224, 1, 1, name='inception_4b_3x3'))
|
||||
|
||||
(self.feed('inception_4a_output')
|
||||
.conv(1, 1, 24, 1, 1, name='inception_4b_5x5_reduce')
|
||||
.conv(5, 5, 64, 1, 1, name='inception_4b_5x5'))
|
||||
|
||||
(self.feed('inception_4a_output')
|
||||
.max_pool(3, 3, 1, 1, name='inception_4b_pool')
|
||||
.conv(1, 1, 64, 1, 1, name='inception_4b_pool_proj'))
|
||||
|
||||
(self.feed('inception_4b_1x1',
|
||||
'inception_4b_3x3',
|
||||
'inception_4b_5x5',
|
||||
'inception_4b_pool_proj')
|
||||
.concat(3, name='inception_4b_output')
|
||||
.conv(1, 1, 128, 1, 1, name='inception_4c_1x1'))
|
||||
|
||||
(self.feed('inception_4b_output')
|
||||
.conv(1, 1, 128, 1, 1, name='inception_4c_3x3_reduce')
|
||||
.conv(3, 3, 256, 1, 1, name='inception_4c_3x3'))
|
||||
|
||||
(self.feed('inception_4b_output')
|
||||
.conv(1, 1, 24, 1, 1, name='inception_4c_5x5_reduce')
|
||||
.conv(5, 5, 64, 1, 1, name='inception_4c_5x5'))
|
||||
|
||||
(self.feed('inception_4b_output')
|
||||
.max_pool(3, 3, 1, 1, name='inception_4c_pool')
|
||||
.conv(1, 1, 64, 1, 1, name='inception_4c_pool_proj'))
|
||||
|
||||
(self.feed('inception_4c_1x1',
|
||||
'inception_4c_3x3',
|
||||
'inception_4c_5x5',
|
||||
'inception_4c_pool_proj')
|
||||
.concat(3, name='inception_4c_output')
|
||||
.conv(1, 1, 112, 1, 1, name='inception_4d_1x1'))
|
||||
|
||||
(self.feed('inception_4c_output')
|
||||
.conv(1, 1, 144, 1, 1, name='inception_4d_3x3_reduce')
|
||||
.conv(3, 3, 288, 1, 1, name='inception_4d_3x3'))
|
||||
|
||||
(self.feed('inception_4c_output')
|
||||
.conv(1, 1, 32, 1, 1, name='inception_4d_5x5_reduce')
|
||||
.conv(5, 5, 64, 1, 1, name='inception_4d_5x5'))
|
||||
|
||||
(self.feed('inception_4c_output')
|
||||
.max_pool(3, 3, 1, 1, name='inception_4d_pool')
|
||||
.conv(1, 1, 64, 1, 1, name='inception_4d_pool_proj'))
|
||||
|
||||
(self.feed('inception_4d_1x1',
|
||||
'inception_4d_3x3',
|
||||
'inception_4d_5x5',
|
||||
'inception_4d_pool_proj')
|
||||
.concat(3, name='inception_4d_output')
|
||||
.conv(1, 1, 256, 1, 1, name='inception_4e_1x1'))
|
||||
|
||||
(self.feed('inception_4d_output')
|
||||
.conv(1, 1, 160, 1, 1, name='inception_4e_3x3_reduce')
|
||||
.conv(3, 3, 320, 1, 1, name='inception_4e_3x3'))
|
||||
|
||||
(self.feed('inception_4d_output')
|
||||
.conv(1, 1, 32, 1, 1, name='inception_4e_5x5_reduce')
|
||||
.conv(5, 5, 128, 1, 1, name='inception_4e_5x5'))
|
||||
|
||||
(self.feed('inception_4d_output')
|
||||
.max_pool(3, 3, 1, 1, name='inception_4e_pool')
|
||||
.conv(1, 1, 128, 1, 1, name='inception_4e_pool_proj'))
|
||||
|
||||
(self.feed('inception_4e_1x1',
|
||||
'inception_4e_3x3',
|
||||
'inception_4e_5x5',
|
||||
'inception_4e_pool_proj')
|
||||
.concat(3, name='inception_4e_output')
|
||||
.max_pool(3, 3, 2, 2, name='pool4_3x3_s2')
|
||||
.conv(1, 1, 256, 1, 1, name='inception_5a_1x1'))
|
||||
|
||||
(self.feed('pool4_3x3_s2')
|
||||
.conv(1, 1, 160, 1, 1, name='inception_5a_3x3_reduce')
|
||||
.conv(3, 3, 320, 1, 1, name='inception_5a_3x3'))
|
||||
|
||||
(self.feed('pool4_3x3_s2')
|
||||
.conv(1, 1, 32, 1, 1, name='inception_5a_5x5_reduce')
|
||||
.conv(5, 5, 128, 1, 1, name='inception_5a_5x5'))
|
||||
|
||||
(self.feed('pool4_3x3_s2')
|
||||
.max_pool(3, 3, 1, 1, name='inception_5a_pool')
|
||||
.conv(1, 1, 128, 1, 1, name='inception_5a_pool_proj'))
|
||||
|
||||
(self.feed('inception_5a_1x1',
|
||||
'inception_5a_3x3',
|
||||
'inception_5a_5x5',
|
||||
'inception_5a_pool_proj')
|
||||
.concat(3, name='inception_5a_output')
|
||||
.conv(1, 1, 384, 1, 1, name='inception_5b_1x1'))
|
||||
|
||||
(self.feed('inception_5a_output')
|
||||
.conv(1, 1, 192, 1, 1, name='inception_5b_3x3_reduce')
|
||||
.conv(3, 3, 384, 1, 1, name='inception_5b_3x3'))
|
||||
|
||||
(self.feed('inception_5a_output')
|
||||
.conv(1, 1, 48, 1, 1, name='inception_5b_5x5_reduce')
|
||||
.conv(5, 5, 128, 1, 1, name='inception_5b_5x5'))
|
||||
|
||||
(self.feed('inception_5a_output')
|
||||
.max_pool(3, 3, 1, 1, name='inception_5b_pool')
|
||||
.conv(1, 1, 128, 1, 1, name='inception_5b_pool_proj'))
|
||||
|
||||
(self.feed('inception_5b_1x1',
|
||||
'inception_5b_3x3',
|
||||
'inception_5b_5x5',
|
||||
'inception_5b_pool_proj')
|
||||
.concat(3, name='inception_5b_output')
|
||||
.avg_pool(7, 7, 1, 1, padding='VALID', name='pool5_7x7_s1')
|
||||
.fc(1000, relu=False, name='loss3_classifier')
|
||||
.softmax(name='prob'))
|
||||
@@ -0,0 +1,81 @@
|
||||
import sys
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
|
||||
# Add the kaffe module to the import path
|
||||
sys.path.append(osp.realpath(osp.join(osp.dirname(__file__), '../../../')))
|
||||
|
||||
from googlenet import GoogleNet
|
||||
from vgg import VGG16
|
||||
from alexnet import AlexNet
|
||||
from caffenet import CaffeNet
|
||||
from nin import NiN
|
||||
from resnet import ResNet50, ResNet101, ResNet152
|
||||
|
||||
|
||||
class DataSpec(object):
|
||||
'''Input data specifications for an ImageNet model.'''
|
||||
|
||||
def __init__(self,
|
||||
batch_size,
|
||||
scale_size,
|
||||
crop_size,
|
||||
isotropic,
|
||||
channels=3,
|
||||
mean=None,
|
||||
bgr=True):
|
||||
# The recommended batch size for this model
|
||||
self.batch_size = batch_size
|
||||
# The image should be scaled to this size first during preprocessing
|
||||
self.scale_size = scale_size
|
||||
# Whether the model expects the rescaling to be isotropic
|
||||
self.isotropic = isotropic
|
||||
# A square crop of this dimension is expected by this model
|
||||
self.crop_size = crop_size
|
||||
# The number of channels in the input image expected by this model
|
||||
self.channels = channels
|
||||
# The mean to be subtracted from each image. By default, the per-channel ImageNet mean.
|
||||
# The values below are ordered BGR, as many Caffe models are trained in this order.
|
||||
# Some of the earlier models (like AlexNet) used a spatial three-channeled mean.
|
||||
# However, using just the per-channel mean values instead doesn't affect things too much.
|
||||
self.mean = mean if mean is not None else np.array([104., 117., 124.])
|
||||
# Whether this model expects images to be in BGR order
|
||||
self.expects_bgr = True
|
||||
|
||||
|
||||
def alexnet_spec(batch_size=500):
|
||||
'''Parameters used by AlexNet and its variants.'''
|
||||
return DataSpec(batch_size=batch_size, scale_size=256, crop_size=227, isotropic=False)
|
||||
|
||||
|
||||
def std_spec(batch_size, isotropic=True):
|
||||
'''Parameters commonly used by "post-AlexNet" architectures.'''
|
||||
return DataSpec(batch_size=batch_size, scale_size=256, crop_size=224, isotropic=isotropic)
|
||||
|
||||
# Collection of sample auto-generated models
|
||||
MODELS = (AlexNet, CaffeNet, GoogleNet, NiN, ResNet50, ResNet101, ResNet152, VGG16)
|
||||
|
||||
# The corresponding data specifications for the sample models
|
||||
# These specifications are based on how the models were trained.
|
||||
# The recommended batch size is based on a Titan X (12GB).
|
||||
MODEL_DATA_SPECS = {
|
||||
AlexNet: alexnet_spec(),
|
||||
CaffeNet: alexnet_spec(),
|
||||
GoogleNet: std_spec(batch_size=200, isotropic=False),
|
||||
ResNet50: std_spec(batch_size=25),
|
||||
ResNet101: std_spec(batch_size=25),
|
||||
ResNet152: std_spec(batch_size=25),
|
||||
NiN: std_spec(batch_size=500),
|
||||
VGG16: std_spec(batch_size=25)
|
||||
}
|
||||
|
||||
|
||||
def get_models():
|
||||
'''Returns a tuple of sample models.'''
|
||||
return MODELS
|
||||
|
||||
|
||||
def get_data_spec(model_instance=None, model_class=None):
|
||||
'''Returns the data specifications for the given network.'''
|
||||
model_class = model_class or model_instance.__class__
|
||||
return MODEL_DATA_SPECS[model_class]
|
||||
@@ -0,0 +1,22 @@
|
||||
from kaffe.tensorflow import Network
|
||||
|
||||
class NiN(Network):
|
||||
def setup(self):
|
||||
(self.feed('data')
|
||||
.conv(11, 11, 96, 4, 4, padding='VALID', name='conv1')
|
||||
.conv(1, 1, 96, 1, 1, name='cccp1')
|
||||
.conv(1, 1, 96, 1, 1, name='cccp2')
|
||||
.max_pool(3, 3, 2, 2, name='pool1')
|
||||
.conv(5, 5, 256, 1, 1, name='conv2')
|
||||
.conv(1, 1, 256, 1, 1, name='cccp3')
|
||||
.conv(1, 1, 256, 1, 1, name='cccp4')
|
||||
.max_pool(3, 3, 2, 2, padding='VALID', name='pool2')
|
||||
.conv(3, 3, 384, 1, 1, name='conv3')
|
||||
.conv(1, 1, 384, 1, 1, name='cccp5')
|
||||
.conv(1, 1, 384, 1, 1, name='cccp6')
|
||||
.max_pool(3, 3, 2, 2, padding='VALID', name='pool3')
|
||||
.conv(3, 3, 1024, 1, 1, name='conv4-1024')
|
||||
.conv(1, 1, 1024, 1, 1, name='cccp7-1024')
|
||||
.conv(1, 1, 1000, 1, 1, name='cccp8-1024')
|
||||
.avg_pool(6, 6, 1, 1, padding='VALID', name='pool4')
|
||||
.softmax(name='prob'))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
from kaffe.tensorflow import Network
|
||||
|
||||
class VGG16(Network):
|
||||
def setup(self):
|
||||
(self.feed('data')
|
||||
.conv(3, 3, 64, 1, 1, name='conv1_1')
|
||||
.conv(3, 3, 64, 1, 1, name='conv1_2')
|
||||
.max_pool(2, 2, 2, 2, name='pool1')
|
||||
.conv(3, 3, 128, 1, 1, name='conv2_1')
|
||||
.conv(3, 3, 128, 1, 1, name='conv2_2')
|
||||
.max_pool(2, 2, 2, 2, name='pool2')
|
||||
.conv(3, 3, 256, 1, 1, name='conv3_1')
|
||||
.conv(3, 3, 256, 1, 1, name='conv3_2')
|
||||
.conv(3, 3, 256, 1, 1, name='conv3_3')
|
||||
.max_pool(2, 2, 2, 2, name='pool3')
|
||||
.conv(3, 3, 512, 1, 1, name='conv4_1')
|
||||
.conv(3, 3, 512, 1, 1, name='conv4_2')
|
||||
.conv(3, 3, 512, 1, 1, name='conv4_3')
|
||||
.max_pool(2, 2, 2, 2, name='pool4')
|
||||
.conv(3, 3, 512, 1, 1, name='conv5_1')
|
||||
.conv(3, 3, 512, 1, 1, name='conv5_2')
|
||||
.conv(3, 3, 512, 1, 1, name='conv5_3')
|
||||
.max_pool(2, 2, 2, 2, name='pool5')
|
||||
.fc(4096, name='fc6')
|
||||
.fc(4096, name='fc7')
|
||||
.fc(1000, relu=False, name='fc8')
|
||||
.softmax(name='prob'))
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python
|
||||
'''Validates a converted ImageNet model against the ILSVRC12 validation set.'''
|
||||
|
||||
import argparse
|
||||
import numpy as np
|
||||
import tensorflow as tf
|
||||
import os.path as osp
|
||||
|
||||
import models
|
||||
import dataset
|
||||
|
||||
|
||||
def load_model(name):
|
||||
'''Creates and returns an instance of the model given its class name.
|
||||
The created model has a single placeholder node for feeding images.
|
||||
'''
|
||||
# Find the model class from its name
|
||||
all_models = models.get_models()
|
||||
lut = {model.__name__: model for model in all_models}
|
||||
if name not in lut:
|
||||
print('Invalid model index. Options are:')
|
||||
# Display a list of valid model names
|
||||
for model in all_models:
|
||||
print('\t* {}'.format(model.__name__))
|
||||
return None
|
||||
NetClass = lut[name]
|
||||
|
||||
# Create a placeholder for the input image
|
||||
spec = models.get_data_spec(model_class=NetClass)
|
||||
data_node = tf.placeholder(tf.float32,
|
||||
shape=(None, spec.crop_size, spec.crop_size, spec.channels))
|
||||
|
||||
# Construct and return the model
|
||||
return NetClass({'data': data_node})
|
||||
|
||||
|
||||
def validate(net, model_path, image_producer, top_k=5):
|
||||
'''Compute the top_k classification accuracy for the given network and images.'''
|
||||
# Get the data specifications for given network
|
||||
spec = models.get_data_spec(model_instance=net)
|
||||
# Get the input node for feeding in the images
|
||||
input_node = net.inputs['data']
|
||||
# Create a placeholder for the ground truth labels
|
||||
label_node = tf.placeholder(tf.int32)
|
||||
# Get the output of the network (class probabilities)
|
||||
probs = net.get_output()
|
||||
# Create a top_k accuracy node
|
||||
top_k_op = tf.nn.in_top_k(probs, label_node, top_k)
|
||||
# The number of images processed
|
||||
count = 0
|
||||
# The number of correctly classified images
|
||||
correct = 0
|
||||
# The total number of images
|
||||
total = len(image_producer)
|
||||
|
||||
with tf.Session() as sesh:
|
||||
coordinator = tf.train.Coordinator()
|
||||
# Load the converted parameters
|
||||
net.load(data_path=model_path, session=sesh)
|
||||
# Start the image processing workers
|
||||
threads = image_producer.start(session=sesh, coordinator=coordinator)
|
||||
# Iterate over and classify mini-batches
|
||||
for (labels, images) in image_producer.batches(sesh):
|
||||
correct += np.sum(sesh.run(top_k_op,
|
||||
feed_dict={input_node: images,
|
||||
label_node: labels}))
|
||||
count += len(labels)
|
||||
cur_accuracy = float(correct) * 100 / count
|
||||
print('{:>6}/{:<6} {:>6.2f}%'.format(count, total, cur_accuracy))
|
||||
# Stop the worker threads
|
||||
coordinator.request_stop()
|
||||
coordinator.join(threads, stop_grace_period_secs=2)
|
||||
print('Top {} Accuracy: {}'.format(top_k, float(correct) / total))
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
# Parse arguments
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('model_path', help='Path to the converted model parameters (.npy)')
|
||||
parser.add_argument('val_gt', help='Path to validation set ground truth (.txt)')
|
||||
parser.add_argument('imagenet_data_dir', help='ImageNet validation set images directory path')
|
||||
parser.add_argument('--model', default='GoogleNet', help='The name of the model to evaluate')
|
||||
args = parser.parse_args()
|
||||
|
||||
# Load the network
|
||||
net = load_model(args.model)
|
||||
if net is None:
|
||||
exit(-1)
|
||||
|
||||
# Load the dataset
|
||||
data_spec = models.get_data_spec(model_instance=net)
|
||||
image_producer = dataset.ImageNetProducer(val_path=args.val_gt,
|
||||
data_path=args.imagenet_data_dir,
|
||||
data_spec=data_spec)
|
||||
|
||||
# Evaluate its performance on the ILSVRC12 validation set
|
||||
validate(net, args.model_path, image_producer)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,36 @@
|
||||
### LeNet Example
|
||||
|
||||
_Thanks to @Russell91 for this example_
|
||||
|
||||
This example showns you how to finetune code from the [Caffe MNIST tutorial](http://caffe.berkeleyvision.org/gathered/examples/mnist.html) using Tensorflow.
|
||||
First, you can convert a prototxt model to tensorflow code:
|
||||
|
||||
$ ./convert.py examples/mnist/lenet.prototxt --code-output-path=mynet.py
|
||||
|
||||
This produces tensorflow code for the LeNet network in `mynet.py`. The code can be imported as described below in the Inference section. Caffe-tensorflow also lets you convert `.caffemodel` weight files to `.npy` files that can be directly loaded from tensorflow:
|
||||
|
||||
$ ./convert.py examples/mnist/lenet.prototxt --caffemodel examples/mnist/lenet_iter_10000.caffemodel --data-output-path=mynet.npy
|
||||
|
||||
The above command will generate a weight file named `mynet.npy`.
|
||||
|
||||
#### Inference:
|
||||
|
||||
Once you have generated both the code weight files for LeNet, you can finetune LeNet using tensorflow with
|
||||
|
||||
$ ./examples/mnist/finetune_mnist.py
|
||||
|
||||
At a high level, `finetune_mnist.py` works as follows:
|
||||
|
||||
```python
|
||||
# Import the converted model's class
|
||||
from mynet import MyNet
|
||||
|
||||
# Create an instance, passing in the input data
|
||||
net = MyNet({'data':my_input_data})
|
||||
|
||||
with tf.Session() as sesh:
|
||||
# Load the data
|
||||
net.load('mynet.npy', sesh)
|
||||
# Forward pass
|
||||
output = sesh.run(net.get_output(), ...)
|
||||
```
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
# Import the converted model's class
|
||||
import numpy as np
|
||||
import random
|
||||
import tensorflow as tf
|
||||
from tensorflow.examples.tutorials.mnist import input_data
|
||||
|
||||
from mynet import LeNet as MyNet
|
||||
|
||||
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
|
||||
batch_size = 32
|
||||
|
||||
def gen_data(source):
|
||||
while True:
|
||||
indices = range(len(source.images))
|
||||
random.shuffle(indices)
|
||||
for i in indices:
|
||||
image = np.reshape(source.images[i], (28, 28, 1))
|
||||
label = source.labels[i]
|
||||
yield image, label
|
||||
|
||||
def gen_data_batch(source):
|
||||
data_gen = gen_data(source)
|
||||
while True:
|
||||
image_batch = []
|
||||
label_batch = []
|
||||
for _ in range(batch_size):
|
||||
image, label = next(data_gen)
|
||||
image_batch.append(image)
|
||||
label_batch.append(label)
|
||||
yield np.array(image_batch), np.array(label_batch)
|
||||
|
||||
|
||||
images = tf.placeholder(tf.float32, [batch_size, 28, 28, 1])
|
||||
labels = tf.placeholder(tf.float32, [batch_size, 10])
|
||||
net = MyNet({'data': images})
|
||||
|
||||
ip2 = net.layers['ip2']
|
||||
pred = tf.nn.softmax(ip2)
|
||||
|
||||
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(ip2, labels), 0)
|
||||
opt = tf.train.RMSPropOptimizer(0.001)
|
||||
train_op = opt.minimize(loss)
|
||||
|
||||
with tf.Session() as sess:
|
||||
# Load the data
|
||||
sess.run(tf.initialize_all_variables())
|
||||
net.load('mynet.npy', sess)
|
||||
|
||||
data_gen = gen_data_batch(mnist.train)
|
||||
for i in range(1000):
|
||||
np_images, np_labels = next(data_gen)
|
||||
feed = {images: np_images, labels: np_labels}
|
||||
|
||||
np_loss, np_pred, _ = sess.run([loss, pred, train_op], feed_dict=feed)
|
||||
if i % 10 == 0:
|
||||
print('Iteration: ', i, np_loss)
|
||||
@@ -0,0 +1,129 @@
|
||||
name: "LeNet"
|
||||
layer {
|
||||
name: "data"
|
||||
type: "Input"
|
||||
top: "data"
|
||||
input_param { shape: { dim: 64 dim: 1 dim: 28 dim: 28 } }
|
||||
}
|
||||
layer {
|
||||
name: "conv1"
|
||||
type: "Convolution"
|
||||
bottom: "data"
|
||||
top: "conv1"
|
||||
param {
|
||||
lr_mult: 1
|
||||
}
|
||||
param {
|
||||
lr_mult: 2
|
||||
}
|
||||
convolution_param {
|
||||
num_output: 20
|
||||
kernel_size: 5
|
||||
stride: 1
|
||||
weight_filler {
|
||||
type: "xavier"
|
||||
}
|
||||
bias_filler {
|
||||
type: "constant"
|
||||
}
|
||||
}
|
||||
}
|
||||
layer {
|
||||
name: "pool1"
|
||||
type: "Pooling"
|
||||
bottom: "conv1"
|
||||
top: "pool1"
|
||||
pooling_param {
|
||||
pool: MAX
|
||||
kernel_size: 2
|
||||
stride: 2
|
||||
}
|
||||
}
|
||||
layer {
|
||||
name: "conv2"
|
||||
type: "Convolution"
|
||||
bottom: "pool1"
|
||||
top: "conv2"
|
||||
param {
|
||||
lr_mult: 1
|
||||
}
|
||||
param {
|
||||
lr_mult: 2
|
||||
}
|
||||
convolution_param {
|
||||
num_output: 50
|
||||
kernel_size: 5
|
||||
stride: 1
|
||||
weight_filler {
|
||||
type: "xavier"
|
||||
}
|
||||
bias_filler {
|
||||
type: "constant"
|
||||
}
|
||||
}
|
||||
}
|
||||
layer {
|
||||
name: "pool2"
|
||||
type: "Pooling"
|
||||
bottom: "conv2"
|
||||
top: "pool2"
|
||||
pooling_param {
|
||||
pool: MAX
|
||||
kernel_size: 2
|
||||
stride: 2
|
||||
}
|
||||
}
|
||||
layer {
|
||||
name: "ip1"
|
||||
type: "InnerProduct"
|
||||
bottom: "pool2"
|
||||
top: "ip1"
|
||||
param {
|
||||
lr_mult: 1
|
||||
}
|
||||
param {
|
||||
lr_mult: 2
|
||||
}
|
||||
inner_product_param {
|
||||
num_output: 500
|
||||
weight_filler {
|
||||
type: "xavier"
|
||||
}
|
||||
bias_filler {
|
||||
type: "constant"
|
||||
}
|
||||
}
|
||||
}
|
||||
layer {
|
||||
name: "relu1"
|
||||
type: "ReLU"
|
||||
bottom: "ip1"
|
||||
top: "ip1"
|
||||
}
|
||||
layer {
|
||||
name: "ip2"
|
||||
type: "InnerProduct"
|
||||
bottom: "ip1"
|
||||
top: "ip2"
|
||||
param {
|
||||
lr_mult: 1
|
||||
}
|
||||
param {
|
||||
lr_mult: 2
|
||||
}
|
||||
inner_product_param {
|
||||
num_output: 10
|
||||
weight_filler {
|
||||
type: "xavier"
|
||||
}
|
||||
bias_filler {
|
||||
type: "constant"
|
||||
}
|
||||
}
|
||||
}
|
||||
layer {
|
||||
name: "prob"
|
||||
type: "Softmax"
|
||||
bottom: "ip2"
|
||||
top: "prob"
|
||||
}
|
||||
Binary file not shown.
Reference in New Issue
Block a user