Made commit of original caffe-tensorflow converter

This commit is contained in:
VladK
2017-07-13 11:11:01 +03:00
parent e150141dd4
commit d6cfad81d1
35 changed files with 10228 additions and 0 deletions
@@ -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
View File
@@ -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
View File
@@ -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()
+36
View File
@@ -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
View File
@@ -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"
}