diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..f3d5fcf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,28 @@ +--- +name: Bug report +about: Create a bug report +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +__Snippet of your code__ + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Logs** +If applicable, add error message to help explain your problem. + +**Environment (please complete the following information):** + - OS: [e.g. iOS] + - Python [e.g. Python 2, Python 3] + - Version [e.g. v0.1.11] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..24e8dd7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,14 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Feature request** +A clear and concise description of what the problem is. Ex. My layer hasn't supported yet [...] + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/LICENSE b/LICENSE index af461f4..4d7d753 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 Grigory Malivenko +Copyright (c) 2019 Grigory Malivenko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..0385206 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include LICENSE +include README.md +include requirements.txt \ No newline at end of file diff --git a/README.md b/README.md index b184683..400cc45 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,14 @@ # pytorch2keras -[![Build Status](https://travis-ci.com/nerox8664/pytorch2keras.svg?branch=master)](https://travis-ci.com/nerox8664/pytorch2keras) +[![Build Status](https://travis-ci.com/gmalivenko/pytorch2keras.svg?branch=master)](https://travis-ci.com/gmalivenko/pytorch2keras) +[![GitHub License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) +[![Python Version](https://img.shields.io/badge/python-2.7%2C3.6-lightgrey.svg)](https://github.com/gmalivenko/pytorch2keras) +[![Downloads](https://pepy.tech/badge/pytorch2keras)](https://pepy.tech/project/pytorch2keras) +![PyPI](https://img.shields.io/pypi/v/pytorch2keras.svg) +[![Readthedocs](https://img.shields.io/readthedocs/pytorch2keras.svg)](https://pytorch2keras.readthedocs.io/en/latest/) -Pytorch to Keras model convertor. Still beta for now. + +PyTorch to Keras model converter. ## Installation @@ -12,44 +18,107 @@ pip install pytorch2keras ## Important notice -In that moment the only PyTorch 0.2 (deprecated) and PyTorch 0.4 (latest stable) are supported. - To use the converter properly, please, make changes in your `~/.keras/keras.json`: -``` +```json ... "backend": "tensorflow", "image_data_format": "channels_first", ... ``` -From the latest releases, multiple inputs is also supported. +## Tensorflow.js + +For the proper conversion to a tensorflow.js format, please use the new flag `names='short'`. +Here is a short instruction how to get a tensorflow.js model: -## Tensorflow.js +1. First of all, you have to convert your model to Keras with this converter: -For the proper convertion to the tensorflow.js format, please use a new flag `short_names=True`. +```python +k_model = pytorch_to_keras(model, input_var, [(10, 32, 32,)], verbose=True, names='short') +``` +2. Now you have Keras model. You can save it as h5 file and then convert it with `tensorflowjs_converter` but it doesn't work sometimes. As alternative, you may get Tensorflow Graph and save it as a frozen model: -## How to build the latest PyTorch +```python +# Function below copied from here: +# https://stackoverflow.com/questions/45466020/how-to-export-keras-h5-to-tensorflow-pb +def freeze_session(session, keep_var_names=None, output_names=None, clear_devices=True): + """ + Freezes the state of a session into a pruned computation graph. + + Creates a new computation graph where variable nodes are replaced by + constants taking their current value in the session. The new graph will be + pruned so subgraphs that are not necessary to compute the requested + outputs are removed. + @param session The TensorFlow session to be frozen. + @param keep_var_names A list of variable names that should not be frozen, + or None to freeze all the variables in the graph. + @param output_names Names of the relevant graph outputs. + @param clear_devices Remove the device directives from the graph for better portability. + @return The frozen graph definition. + """ + from tensorflow.python.framework.graph_util import convert_variables_to_constants + graph = session.graph + with graph.as_default(): + freeze_var_names = \ + list(set(v.op.name for v in tf.global_variables()).difference(keep_var_names or [])) + output_names = output_names or [] + output_names += [v.op.name for v in tf.global_variables()] + input_graph_def = graph.as_graph_def() + if clear_devices: + for node in input_graph_def.node: + node.device = "" + frozen_graph = convert_variables_to_constants(session, input_graph_def, + output_names, freeze_var_names) + return frozen_graph + + +from keras import backend as K +import tensorflow as tf +frozen_graph = freeze_session(K.get_session(), + output_names=[out.op.name for out in k_model.outputs]) + +tf.train.write_graph(frozen_graph, ".", "my_model.pb", as_text=False) +print([i for i in k_model.outputs]) -Please, follow [this guide](https://github.com/pytorch/pytorch#from-source) to compile the latest version. +``` -## How to use +3. You will see the output layer name, so, now it's time to convert `my_model.pb` to tfjs model: -It's a convertor of pytorch graph to a Keras (Tensorflow backend) graph. +```bash +tensorflowjs_converter \ + --input_format=tf_frozen_model \ + --output_node_names='TANHTObs/Tanh' \ + my_model.pb \ + model_tfjs +``` -Firstly, we need to load (or create) pytorch model: +4. Thats all! +```js +const MODEL_URL = `model_tfjs/tensorflowjs_model.pb`; +const WEIGHTS_URL = `model_tfjs/weights_manifest.json`; +const model = await tf.loadFrozenModel(MODEL_URL, WEIGHTS_URL); ``` + +## How to use + +It's the converter of PyTorch graph to a Keras (Tensorflow backend) model. + +Firstly, we need to load (or create) a valid PyTorch model: + +```python class TestConv2d(nn.Module): - """Module for Conv2d convertion testing + """ + Module for Conv2d testing """ def __init__(self, inp=10, out=16, kernel_size=3): super(TestConv2d, self).__init__() - self.conv2d = nn.Conv2d(inp, out, stride=(inp % 3 + 1), kernel_size=kernel_size, bias=True) + self.conv2d = nn.Conv2d(inp, out, stride=1, kernel_size=kernel_size, bias=True) def forward(self, x): x = self.conv2d(x) @@ -61,84 +130,97 @@ model = TestConv2d() # model.load_state_dict(torch.load(path_to_weights.pth)) ``` -The next step - create a dummy variable with correct shapes: +The next step - create a dummy variable with correct shape: -``` +```python input_np = np.random.uniform(0, 1, (1, 10, 32, 32)) input_var = Variable(torch.FloatTensor(input_np)) ``` -We're using dummy-variable in order to trace the model. +We use the dummy-variable to trace the model (with jit.trace): -``` -from converter import pytorch_to_keras +```python +from pytorch2keras import pytorch_to_keras # we should specify shape of the input tensor k_model = pytorch_to_keras(model, input_var, [(10, 32, 32,)], verbose=True) ``` -That's all! If all is ok, the Keras model is stores into the `k_model` variable. +You can also set H and W dimensions to None to make your model shape-agnostic (e.g. fully convolutional netowrk): -## Supported layers +```python +from pytorch2keras.converter import pytorch_to_keras +# we should specify shape of the input tensor +k_model = pytorch_to_keras(model, input_var, [(10, None, None,)], verbose=True) +``` -Layers: +That's all! If all the modules have converted properly, the Keras model will be stored in the `k_model` variable. -* Linear -* Conv2d -* Conv3d -* ConvTranspose2d -* MaxPool2d -* MaxPool3d -* AvgPool2d -* Global average pooling (as special case of AdaptiveAvgPool2d) -* Embedding -* UpsamplingNearest2d -Reshape: +## API -* View -* Reshape (only with 0.4) -* Transpose (only with 0.4) +Here is the only method `pytorch_to_keras` from `pytorch2keras` module. -Activations: +```python +def pytorch_to_keras( + model, args, input_shapes=None, + change_ordering=False, verbose=False, name_policy=None, +): +``` + +Options: -* ReLU -* LeakyReLU -* PReLU (only with 0.2) -* SELU (only with 0.2) -* Tanh -* Softmax -* Softplus (only with 0.2) -* Softsign (only with 0.2) -* Sigmoid +* `model` - a PyTorch model (nn.Module) to convert; +* `args` - a list of dummy variables with proper shapes; +* `input_shapes` - (experimental) list with overrided shapes for inputs; +* `change_ordering` - (experimental) boolean, if enabled, the converter will try to change `BCHW` to `BHWC` +* `verbose` - boolean, detailed log of conversion +* `name_policy` - (experimental) choice from [`keep`, `short`, `random`]. The selector set the target layer naming policy. + +## Supported layers -Element-wise: +* Activations: + + ReLU + + LeakyReLU + + SELU + + Sigmoid + + Softmax + + Tanh -* Addition -* Multiplication -* Subtraction +* Constants -Misc: +* Convolutions: + + Conv2d + + ConvTrsnpose2d + +* Element-wise: + + Add + + Mul + + Sub + + Div + +* Linear -* reduce sum ( .sum() method) +* Normalizations: + + BatchNorm2d + + InstanceNorm2d -## Unsupported parameters +* Poolings: + + MaxPool2d + + AvgPool2d + + Global MaxPool2d (adaptive pooling to shape [1, 1]) -* Pooling: count_include_pad, dilation, ceil_mode -* Convolution: group ## Models converted with pytorch2keras -* ResNet18 -* ResNet34 -* ResNet50 -* SqueezeNet (with ceil_mode=False) -* DenseNet +* ResNet* +* VGG* +* PreResNet* +* DenseNet* * AlexNet -* Inception (v4 only) -* SeNet +* Mobilenet v2 ## Usage Look at the `tests` directory. ## License -This software is covered by MIT License. \ No newline at end of file +This software is covered by MIT License. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..6be292a --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,11 @@ +site_name: pytorch2keras documentation +theme: readthedocs + +pages: +- Home: index.md +- Installation: installation.md +- Getting started: getting_started.md +- Supported layers and models: supported_layers_and_models.md +- API: api.md +- Known problems: known_problems.md +- License: license.md \ No newline at end of file diff --git a/pytorch2keras/__init__.py b/pytorch2keras/__init__.py index e69de29..326dfc6 100644 --- a/pytorch2keras/__init__.py +++ b/pytorch2keras/__init__.py @@ -0,0 +1,3 @@ +from .converter import pytorch_to_keras + +__all__ = ['pytorch_to_keras'] \ No newline at end of file diff --git a/pytorch2keras/converter.py b/pytorch2keras/converter.py index 65cf333..931c71b 100644 --- a/pytorch2keras/converter.py +++ b/pytorch2keras/converter.py @@ -1,221 +1,75 @@ """ -The Pytorch2Keras converter module over JIT-trace. +The PyTorch2Keras converter interface """ +from onnx2keras import onnx_to_keras import torch -import torch.jit -import torch.autograd -import torch.serialization -import contextlib -from torch.jit import _unique_state_dict - -from .layers import AVAILABLE_CONVERTERS - - -@contextlib.contextmanager -def set_training(model, mode): - """ - A context manager to temporarily set the training mode of 'model' - to 'mode', resetting it when we exit the with-block. A no-op if - mode is None. - """ - if mode is None: - yield - return - old_mode = model.training - if old_mode != mode: - model.train(mode) - try: - yield - finally: - if old_mode != mode: - model.train(old_mode) - - -def _optimize_graph(graph, aten): - # run dce first to eliminate dead parts of the graph that might have been - # left behind by things like symbolic_override - torch._C._jit_pass_dce(graph) - torch._C._jit_pass_lint(graph) - - torch._C._jit_pass_peephole(graph) - torch._C._jit_pass_lint(graph) - graph = torch._C._jit_pass_onnx(graph, aten) - torch._C._jit_pass_lint(graph) - torch._C._jit_pass_onnx_peephole(graph) - torch._C._jit_pass_lint(graph) - torch._C._jit_pass_dce(graph) - torch._C._jit_pass_lint(graph) - graph = torch._C._jit_pass_canonicalize(graph) - torch._C._jit_pass_lint(graph) - return graph - - -def get_node_id(node): - import re - node_id = re.search(r"[\d]+", node.__str__()) - return node_id.group(0) +import onnx +import io +import logging def pytorch_to_keras( - model, args, input_shapes, - change_ordering=False, training=False, verbose=False, short_names=False, + model, args, input_shapes=None, + change_ordering=False, verbose=False, name_policy=None, + do_constant_folding=False ): """ - By given pytorch model convert layers with specified convertors. + By given PyTorch model convert layers with ONNX. Args: model: pytorch model args: pytorch model arguments input_shapes: keras input shapes (using for each InputLayer) change_ordering: change CHW to HWC - training: switch model to training mode verbose: verbose output - short_names: use shorn names for keras layers + name_policy: use short names, use random-suffix or keep original names for keras layers Returns: model: created keras model. """ + logger = logging.getLogger('pytorch2keras') + + if verbose: + logging.basicConfig(level=logging.DEBUG) - # PyTorch JIT tracing - if isinstance(args, torch.autograd.Variable): - args = (args, ) + logger.info('Converter is called.') - # Workaround for previous versions - if isinstance(input_shapes, tuple): + if name_policy: + logger.warning('Name policy isn\'t supported now.') + + if input_shapes: + logger.warning('Custom shapes isn\'t supported now.') + + if input_shapes and not isinstance(input_shapes, list): input_shapes = [input_shapes] - orig_state_dict_keys = _unique_state_dict(model).keys() + if not isinstance(args, list): + args = [args] - with set_training(model, training): - trace, torch_out = torch.jit.get_trace_graph(model, tuple(args)) + args = tuple(args) - if orig_state_dict_keys != _unique_state_dict(model).keys(): - raise RuntimeError("state_dict changed after running the tracer; " - "something weird is happening in your model!") + dummy_output = model(*args) - # _optimize_trace(trace, False) - trace.set_graph(_optimize_graph(trace.graph(), False)) + if isinstance(dummy_output, torch.autograd.Variable): + dummy_output = [dummy_output] - if verbose: - print(trace.graph()) + input_names = ['input_{0}'.format(i) for i in range(len(args))] + output_names = ['output_{0}'.format(i) for i in range(len(dummy_output))] - if verbose: - print(list(trace.graph().outputs())) + logger.debug('Input_names:') + logger.debug(input_names) - # Get all graph nodes - nodes = list(trace.graph().nodes()) + logger.debug('Output_names:') + logger.debug(output_names) - # Collect graph outputs - graph_outputs = [n.uniqueName() for n in trace.graph().outputs()] - print('Graph outputs:', graph_outputs) + stream = io.BytesIO() + torch.onnx.export(model, args, stream, do_constant_folding=do_constant_folding, verbose=verbose, input_names=input_names, output_names=output_names) - # Collect model state dict - state_dict = _unique_state_dict(model) - if verbose: - print('State dict:', list(state_dict)) - - import re - import keras - from keras import backend as K - K.set_image_data_format('channels_first') - - layers = dict() - keras_inputs = [] - for i in range(len(args)): - layers['input{0}'.format(i)] = keras.layers.InputLayer( - input_shape=input_shapes[i], name='input{0}'.format(i) - ).output - keras_inputs.append(layers['input{0}'.format(i)]) - - outputs = [] - - input_index = 0 - for node in nodes: - node_inputs = list(node.inputs()) - node_input_names = [] - for node_input in node_inputs: - if node_input.node().scopeName(): - node_input_names.append(get_node_id(node_input.node())) - - if len(node_input_names) == 0: - node_input_names.append('input{0}'.format(input_index)) - input_index += 1 - - node_type = node.kind() - # print(dir(node)) - - node_scope_name = node.scopeName() - node_id = get_node_id(node) - node_weights_name = '.'.join( - re.findall(r'\[([\w\d.]+)\]', node_scope_name) - ) - node_attrs = {k: node[k] for k in node.attributeNames()} - - node_outputs = list(node.outputs()) - node_outputs_names = [] - for node_output in node_outputs: - if node_output.node().scopeName(): - node_outputs_names.append(node_output.node().scopeName()) - - if verbose: - print(' ____ ') - print('graph node:', node_scope_name) - print('type:', node_type) - print('inputs:', node_input_names) - print('outputs:', node_outputs_names) - print('name in state_dict:', node_weights_name) - print('attrs:', node_attrs) - print('is_terminal:', node_id in graph_outputs) - AVAILABLE_CONVERTERS[node_type]( - node_attrs, - node_weights_name, node_id, - node_input_names, - layers, state_dict, - short_names - ) - if node_id in graph_outputs: - outputs.append(layers[node_id]) - - model = keras.models.Model(inputs=keras_inputs, outputs=outputs) - - if change_ordering: - import numpy as np - conf = model.get_config() - for layer in conf['layers']: - if layer['config'] and 'batch_input_shape' in layer['config']: - layer['config']['batch_input_shape'] = \ - tuple(np.reshape(np.array( - [ - [None] + - list(layer['config']['batch_input_shape'][2:][:]) + - [layer['config']['batch_input_shape'][1]] - ]), -1 - )) - if layer['config'] and 'target_shape' in layer['config']: - layer['config']['target_shape'] = \ - tuple(np.reshape(np.array( - [ - list(layer['config']['target_shape'][1:][:]), - layer['config']['target_shape'][0] - ]), -1 - )) - if layer['config'] and 'data_format' in layer['config']: - layer['config']['data_format'] = 'channels_last' - if layer['config'] and 'axis' in layer['config']: - layer['config']['axis'] = 3 - - K.set_image_data_format('channels_last') - model_tf_ordering = keras.models.Model.from_config(conf) - - # from keras.utils.layer_utils import convert_all_kernels_in_model - # convert_all_kernels_in_model(model) - - for dst_layer, src_layer in zip( - model_tf_ordering.layers, model.layers - ): - dst_layer.set_weights(src_layer.get_weights()) - - model = model_tf_ordering - - return model + stream.seek(0) + onnx_model = onnx.load(stream) + + k_model = onnx_to_keras(onnx_model=onnx_model, input_names=input_names, + input_shapes=input_shapes, name_policy=name_policy, + verbose=verbose, change_ordering=change_ordering) + return k_model diff --git a/pytorch2keras/layers.py b/pytorch2keras/layers.py deleted file mode 100644 index fae2360..0000000 --- a/pytorch2keras/layers.py +++ /dev/null @@ -1,1107 +0,0 @@ -import keras.layers -import numpy as np -import random -import string - - -def random_string(length): - return ''.join(random.choice(string.ascii_letters) for m in range(length)) - - -def convert_conv(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert convolution layer. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - short_names: use short names - """ - print('Converting convolution ...') - - if short_names: - tf_name = 'C' + random_string(7) - else: - tf_name = w_name + str(random.random()) - - bias_name = '{0}.bias'.format(w_name) - weights_name = '{0}.weight'.format(w_name) - input_name = inputs[0] - - if len(weights[weights_name].numpy().shape) == 5: # 3D conv - W = weights[weights_name].numpy().transpose(2, 3, 4, 1, 0) - height, width, channels, n_layers, n_filters = W.shape - print(W.shape) - - if bias_name in weights: - biases = weights[bias_name].numpy() - has_bias = True - else: - biases = None - has_bias = False - - if params['pads'][0] > 0 or params['pads'][1] > 0: - padding_name = tf_name + '_pad' - padding_layer = keras.layers.ZeroPadding3D( - padding=(params['pads'][0], - params['pads'][1], - params['pads'][2]), - name=padding_name - ) - layers[padding_name] = padding_layer(layers[input_name]) - input_name = padding_name - - weights = None - if has_bias: - weights = [W, biases] - else: - weights = [W] - - print(len(weights), len(weights[0]), len(weights[0][0]), - len(weights[0][0][0]), len(weights[0][0][0][0]), - len(weights[0][0][0][0][0])) - conv = keras.layers.Conv3D( - filters=n_filters, - kernel_size=(channels, height, width), - strides=(params['strides'][0], - params['strides'][1], - params['strides'][2]), - padding='valid', - weights=weights, - use_bias=has_bias, - activation=None, - dilation_rate=params['dilations'][0], - name=tf_name - ) - layers[scope_name] = conv(layers[input_name]) - elif len(weights[weights_name].numpy().shape) == 4: # 2D conv - W = weights[weights_name].numpy().transpose(2, 3, 1, 0) - height, width, channels, n_filters = W.shape - - if bias_name in weights: - biases = weights[bias_name].numpy() - has_bias = True - else: - biases = None - has_bias = False - - if params['pads'][0] > 0 or params['pads'][1] > 0: - padding_name = tf_name + '_pad' - padding_layer = keras.layers.ZeroPadding2D( - padding=(params['pads'][0], params['pads'][1]), - name=padding_name - ) - layers[padding_name] = padding_layer(layers[input_name]) - input_name = padding_name - - weights = None - if has_bias: - weights = [W, biases] - else: - weights = [W] - - conv = keras.layers.Conv2D( - filters=n_filters, - kernel_size=(height, width), - strides=(params['strides'][0], params['strides'][1]), - padding='valid', - weights=weights, - use_bias=has_bias, - activation=None, - dilation_rate=params['dilations'][0], - name=tf_name - ) - layers[scope_name] = conv(layers[input_name]) - else: # 1D conv - W = weights[weights_name].numpy().transpose(2, 1, 0) - width, channels, n_filters = W.shape - - if bias_name in weights: - biases = weights[bias_name].numpy() - has_bias = True - else: - biases = None - has_bias = False - - padding_name = tf_name + '_pad' - padding_layer = keras.layers.ZeroPadding1D( - padding=params['pads'][0], - name=padding_name - ) - layers[padding_name] = padding_layer(layers[inputs[0]]) - input_name = padding_name - - weights = None - if has_bias: - weights = [W, biases] - else: - weights = [W] - - conv = keras.layers.Conv1D( - filters=n_filters, - kernel_size=width, - strides=params['strides'][0], - padding='valid', - weights=weights, - use_bias=has_bias, - activation=None, - dilation_rate=params['dilations'][0], - name=tf_name - ) - layers[scope_name] = conv(layers[input_name]) - - -def convert_convtranspose(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert transposed convolution layer. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting transposed convolution ...') - - if short_names: - tf_name = 'C' + random_string(7) - else: - tf_name = w_name + str(random.random()) - - bias_name = '{0}.bias'.format(w_name) - weights_name = '{0}.weight'.format(w_name) - - if len(weights[weights_name].numpy().shape) == 4: - W = weights[weights_name].numpy().transpose(2, 3, 1, 0) - height, width, n_filters, channels = W.shape - - if bias_name in weights: - biases = weights[bias_name].numpy() - has_bias = True - else: - biases = None - has_bias = False - - assert(params['pads'][0] == 0) - assert(params['pads'][1] == 0) - - input_name = inputs[0] - - weights = None - if has_bias: - weights = [W, biases] - else: - weights = [W] - - conv = keras.layers.Conv2DTranspose( - filters=n_filters, - kernel_size=(height, width), - strides=(params['strides'][0], params['strides'][1]), - padding='valid', - weights=weights, - use_bias=has_bias, - activation=None, - dilation_rate=params['dilations'][0], - name=tf_name - ) - layers[scope_name] = conv(layers[input_name]) - else: - raise AssertionError('Layer is not supported for now') - - -def convert_flatten(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert reshape(view). - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Conerting reshape ...') - if short_names: - tf_name = 'R' + random_string(7) - else: - tf_name = w_name + str(random.random()) - - # TODO: check if the input is already flattened - reshape = keras.layers.Flatten(name=tf_name) - layers[scope_name] = reshape(layers[inputs[0]]) - - -def convert_gemm(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert Linear. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting Linear ...') - - if short_names: - tf_name = 'FC' + random_string(6) - else: - tf_name = w_name + str(random.random()) - - bias_name = '{0}.bias'.format(w_name) - weights_name = '{0}.weight'.format(w_name) - - W = weights[weights_name].numpy().transpose() - input_channels, output_channels = W.shape - - keras_weights = [W] - has_bias = False - if bias_name in weights: - bias = weights[bias_name].numpy() - keras_weights = [W, bias] - has_bias = True - - dense = keras.layers.Dense( - output_channels, - weights=keras_weights, use_bias=has_bias, name=tf_name - ) - - layers[scope_name] = dense(layers[inputs[0]]) - - -def convert_avgpool(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert Average pooling. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting pooling ...') - - if short_names: - tf_name = 'P' + random_string(7) - else: - tf_name = w_name + str(random.random()) - - height, width = params['kernel_shape'] - stride_height, stride_width = params['strides'] - padding_h, padding_w, _, _ = params['pads'] - - input_name = inputs[0] - padding = 'valid' - if padding_h > 0 and padding_w > 0: - if padding_h == height // 2 and padding_w == width // 2: - padding = 'same' - else: - raise AssertionError('Custom padding isnt supported') - - pooling = keras.layers.AveragePooling2D( - pool_size=(height, width), - strides=(stride_height, stride_width), - padding=padding, - name=tf_name - ) - - layers[scope_name] = pooling(layers[input_name]) - - -def convert_maxpool(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert Max pooling. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - - print('Converting pooling ...') - - if short_names: - tf_name = 'P' + random_string(7) - else: - tf_name = w_name + str(random.random()) - - if 'kernel_shape' in params: - height, width = params['kernel_shape'] - else: - height, width = params['kernel_size'] - - if 'strides' in params: - stride_height, stride_width = params['strides'] - else: - stride_height, stride_width = params['stride'] - if 'pads' in params: - padding_h, padding_w, _, _ = params['pads'] - else: - padding_h, padding_w = params['padding'] - input_name = inputs[0] - if padding_h > 0 and padding_w > 0: - padding_name = tf_name + '_pad' - padding_layer = keras.layers.ZeroPadding2D( - padding=(padding_h, padding_w), - name=padding_name - ) - layers[padding_name] = padding_layer(layers[inputs[0]]) - input_name = padding_name - - # Pooling type - pooling = keras.layers.MaxPooling2D( - pool_size=(height, width), - strides=(stride_height, stride_width), - padding='valid', - name=tf_name - ) - - layers[scope_name] = pooling(layers[input_name]) - - -def convert_maxpool3(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert 3d Max pooling. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - - print('Converting pooling ...') - - if short_names: - tf_name = 'P' + random_string(7) - else: - tf_name = w_name + str(random.random()) - - if 'kernel_shape' in params: - height, width, depth = params['kernel_shape'] - else: - height, width, depth = params['kernel_size'] - - if 'strides' in params: - stride_height, stride_width, stride_depth = params['strides'] - else: - stride_height, stride_width, stride_depth = params['stride'] - if 'pads' in params: - padding_h, padding_w, padding_d, _, _ = params['pads'] - else: - padding_h, padding_w, padding_d = params['padding'] - input_name = inputs[0] - if padding_h > 0 and padding_w > 0 and padding_d > 0: - padding_name = tf_name + '_pad' - padding_layer = keras.layers.ZeroPadding3D( - padding=(padding_h, padding_w, padding_d), - name=padding_name - ) - layers[padding_name] = padding_layer(layers[inputs[0]]) - input_name = padding_name - - # Pooling type - pooling = keras.layers.MaxPooling3D( - pool_size=(height, width, depth), - strides=(stride_height, stride_width, stride_depth), - padding='valid', - name=tf_name - ) - - layers[scope_name] = pooling(layers[input_name]) - - -def convert_dropout(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert dropout. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting dropout ...') - - if short_names: - tf_name = 'DO' + random_string(6) - else: - tf_name = w_name + str(random.random()) - - dropout = keras.layers.Dropout(rate=params['ratio'], name=tf_name) - layers[scope_name] = dropout(layers[inputs[0]]) - - -def convert_batchnorm(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert batch normalization layer. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting batchnorm ...') - - if short_names: - tf_name = 'BN' + random_string(6) - else: - tf_name = w_name + str(random.random()) - - bias_name = '{0}.bias'.format(w_name) - weights_name = '{0}.weight'.format(w_name) - mean_name = '{0}.running_mean'.format(w_name) - var_name = '{0}.running_var'.format(w_name) - - if bias_name in weights: - beta = weights[bias_name].numpy() - - if weights_name in weights: - gamma = weights[weights_name].numpy() - - mean = weights[mean_name].numpy() - variance = weights[var_name].numpy() - - eps = params['epsilon'] - momentum = params['momentum'] - - if weights_name not in weights: - bn = keras.layers.BatchNormalization( - axis=1, momentum=momentum, epsilon=eps, - center=False, scale=False, - weights=[mean, variance], - name=tf_name - ) - else: - bn = keras.layers.BatchNormalization( - axis=1, momentum=momentum, epsilon=eps, - weights=[gamma, beta, mean, variance], - name=tf_name - ) - layers[scope_name] = bn(layers[inputs[0]]) - - -def convert_elementwise_add( - params, w_name, scope_name, inputs, layers, weights, short_names -): - """ - Convert elementwise addition. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting elementwise_add ...') - model0 = layers[inputs[0]] - model1 = layers[inputs[1]] - - if short_names: - tf_name = 'A' + random_string(7) - else: - tf_name = w_name + str(random.random()) - - add = keras.layers.Add(name=tf_name) - layers[scope_name] = add([model0, model1]) - - -def convert_elementwise_mul( - params, w_name, scope_name, inputs, layers, weights, short_names -): - """ - Convert elementwise multiplication. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting elementwise_mul ...') - model0 = layers[inputs[0]] - model1 = layers[inputs[1]] - - if short_names: - tf_name = 'M' + random_string(7) - else: - tf_name = w_name + str(random.random()) - - mul = keras.layers.Multiply(name=tf_name) - layers[scope_name] = mul([model0, model1]) - - -def convert_elementwise_sub( - params, w_name, scope_name, inputs, layers, weights, short_names -): - """ - Convert elementwise subtraction. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting elementwise_sub ...') - model0 = layers[inputs[0]] - model1 = layers[inputs[1]] - - if short_names: - tf_name = 'S' + random_string(7) - else: - tf_name = w_name + str(random.random()) - - sub = keras.layers.Subtract(name=tf_name) - layers[scope_name] = sub([model0, model1]) - - -def convert_sum( - params, w_name, scope_name, inputs, layers, weights, short_names -): - """ - Convert sum. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting Sum ...') - - def target_layer(x): - return keras.backend.sum(x) - - lambda_layer = keras.layers.Lambda(target_layer) - layers[scope_name] = lambda_layer(layers[inputs[0]]) - - -def convert_concat(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert concatenation. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting concat ...') - concat_nodes = [layers[i] for i in inputs] - - if short_names: - tf_name = 'CAT' + random_string(5) - else: - tf_name = w_name + str(random.random()) - - cat = keras.layers.Concatenate(name=tf_name, axis=params['axis']) - layers[scope_name] = cat(concat_nodes) - - -def convert_relu(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert relu layer. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting relu ...') - - if short_names: - tf_name = 'RELU' + random_string(4) - else: - tf_name = w_name + str(random.random()) - - relu = keras.layers.Activation('relu', name=tf_name) - layers[scope_name] = relu(layers[inputs[0]]) - - -def convert_lrelu(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert leaky relu layer. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting lrelu ...') - - if short_names: - tf_name = 'lRELU' + random_string(3) - else: - tf_name = w_name + str(random.random()) - - leakyrelu = \ - keras.layers.LeakyReLU(alpha=params['alpha'], name=tf_name) - layers[scope_name] = leakyrelu(layers[inputs[0]]) - - -def convert_sigmoid(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert sigmoid layer. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting sigmoid ...') - - if short_names: - tf_name = 'SIGM' + random_string(4) - else: - tf_name = w_name + str(random.random()) - - sigmoid = keras.layers.Activation('sigmoid', name=tf_name) - layers[scope_name] = sigmoid(layers[inputs[0]]) - - -def convert_softmax(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert softmax layer. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting softmax ...') - - if short_names: - tf_name = 'SMAX' + random_string(4) - else: - tf_name = w_name + str(random.random()) - - softmax = keras.layers.Activation('softmax', name=tf_name) - layers[scope_name] = softmax(layers[inputs[0]]) - - -def convert_tanh(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert tanh layer. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting tanh ...') - - if short_names: - tf_name = 'TANH' + random_string(4) - else: - tf_name = w_name + str(random.random()) - - tanh = keras.layers.Activation('tanh', name=tf_name) - layers[scope_name] = tanh(layers[inputs[0]]) - - -def convert_selu(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert selu layer. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting selu ...') - - if short_names: - tf_name = 'SELU' + random_string(4) - else: - tf_name = w_name + str(random.random()) - - selu = keras.layers.Activation('selu', name=tf_name) - layers[scope_name] = selu(layers[inputs[0]]) - - -def convert_transpose(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert transpose layer. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting transpose ...') - if params['perm'][0] != 0: - # raise AssertionError('Cannot permute batch dimension') - print('!!! Cannot permute batch dimension. Result may be wrong !!!') - layers[scope_name] = layers[inputs[0]] - else: - if short_names: - tf_name = 'PERM' + random_string(4) - else: - tf_name = w_name + str(random.random()) - permute = keras.layers.Permute(params['perm'][1:], name=tf_name) - layers[scope_name] = permute(layers[inputs[0]]) - - -def convert_reshape(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert reshape layer. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting reshape ...') - - if short_names: - tf_name = 'RESH' + random_string(4) - else: - tf_name = w_name + str(random.random()) - - if len(inputs) > 1: - reshape = keras.layers.Reshape(layers[inputs[1]][1:], name=tf_name) - layers[scope_name] = reshape(layers[inputs[0]]) - else: - reshape = keras.layers.Reshape(params['shape'][1:], name=tf_name) - layers[scope_name] = reshape(layers[inputs[0]]) - - -def convert_matmul(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert matmul layer. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting matmul ...') - - if short_names: - tf_name = 'MMUL' + random_string(4) - else: - tf_name = w_name + str(random.random()) - - if len(inputs) == 1: - weights_name = '{0}.weight'.format(w_name) - - W = weights[weights_name].numpy().transpose() - input_channels, output_channels = W.shape - - keras_weights = [W] - - dense = keras.layers.Dense( - output_channels, - weights=keras_weights, use_bias=False, name=tf_name - ) - layers[scope_name] = dense(layers[inputs[0]]) - elif len(inputs) == 2: - weights_name = '{0}.weight'.format(w_name) - - W = weights[weights_name].numpy().transpose() - input_channels, output_channels = W.shape - - keras_weights = [W] - - dense = keras.layers.Dense( - output_channels, - weights=keras_weights, use_bias=False, name=tf_name - ) - layers[scope_name] = dense(layers[inputs[0]]) - else: - raise AssertionError('Cannot convert matmul layer') - - -def convert_gather(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert gather (embedding) layer. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting embedding ...') - - if short_names: - tf_name = 'EMBD' + random_string(4) - else: - tf_name = w_name + str(random.random()) - - weights_name = '{0}.weight'.format(w_name) - - W = weights[weights_name].numpy() - input_channels, output_channels = W.shape - - keras_weights = [W] - - dense = keras.layers.Embedding( - input_channels, - weights=keras_weights, output_dim=output_channels, name=tf_name - ) - layers[scope_name] = dense(layers[inputs[0]]) - - -def convert_reduce_sum(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert reduce_sum layer. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting reduce_sum ...') - - keepdims = params['keepdims'] > 0 - axis = np.array(params['axes']) - - def target_layer(x, keepdims=keepdims, axis=axis): - return keras.backend.sum(x, keepdims=keepdims, axis=axis) - - lambda_layer = keras.layers.Lambda(target_layer) - layers[scope_name] = lambda_layer(layers[inputs[0]]) - - -def convert_constant(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert constant layer. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting constant ...') - - # def target_layer(x, params=params): - # return keras.backend.constant(np.float32(params['value'])) - - # lambda_layer = keras.layers.Lambda(target_layer) - # layers[scope_name] = lambda_layer(layers[inputs[0]]) - layers[scope_name] = np.float32(params['value']) - - -def convert_upsample(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert upsample_bilinear2d layer. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting upsample...') - - if params['mode'] != 'nearest': - raise AssertionError('Cannot convert non-nearest upsampling') - - if short_names: - tf_name = 'UPSL' + random_string(4) - else: - tf_name = w_name + str(random.random()) - - scale = (params['height_scale'], params['width_scale']) - upsampling = keras.layers.UpSampling2D( - size=scale, name=tf_name - ) - layers[scope_name] = upsampling(layers[inputs[0]]) - - -def convert_padding(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert padding layer. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting padding...') - - if params['mode'] != 'constant': - raise AssertionError('Cannot convert non-constant padding') - - if params['value'] != 0.0: - raise AssertionError('Cannot convert non-zero padding') - - if short_names: - tf_name = 'PADD' + random_string(4) - else: - tf_name = w_name + str(random.random()) - - # Magic ordering - padding_name = tf_name - padding_layer = keras.layers.ZeroPadding2D( - padding=((params['pads'][2], params['pads'][6]), (params['pads'][3], params['pads'][7])), - name=padding_name - ) - - layers[scope_name] = padding_layer(layers[inputs[0]]) - - - -def convert_adaptive_avg_pool2d(params, w_name, scope_name, inputs, layers, weights, short_names): - """ - Convert adaptive_avg_pool2d layer. - - Args: - params: dictionary with layer parameters - w_name: name prefix in state_dict - scope_name: pytorch scope name - inputs: pytorch node inputs - layers: dictionary with keras tensors - weights: pytorch state_dict - short_names: use short names for keras layers - """ - print('Converting adaptive_avg_pool2d...') - - if short_names: - tf_name = 'APOL' + random_string(4) - else: - tf_name = w_name + str(random.random()) - - global_pool = keras.layers.GlobalAveragePooling2D() - layers_global_pool = global_pool(layers[inputs[0]]) - - def target_layer(x): - return keras.backend.expand_dims(x) - - lambda_layer = keras.layers.Lambda(target_layer) - layers[scope_name] = lambda_layer(layers_global_pool) - - -AVAILABLE_CONVERTERS = { - 'onnx::Conv': convert_conv, - 'onnx::ConvTranspose': convert_convtranspose, - 'onnx::Flatten': convert_flatten, - 'onnx::Gemm': convert_gemm, - 'onnx::MaxPool': convert_maxpool, - 'max_pool2d': convert_maxpool, - 'aten::max_pool3d': convert_maxpool3, - 'onnx::AveragePool': convert_avgpool, - 'onnx::Dropout': convert_dropout, - 'onnx::BatchNormalization': convert_batchnorm, - 'onnx::Add': convert_elementwise_add, - 'onnx::Mul': convert_elementwise_mul, - 'onnx::Sub': convert_elementwise_sub, - 'onnx::Sum': convert_sum, - 'onnx::Concat': convert_concat, - 'onnx::Relu': convert_relu, - 'onnx::LeakyRelu': convert_lrelu, - 'onnx::Sigmoid': convert_sigmoid, - 'onnx::Softmax': convert_softmax, - 'onnx::Tanh': convert_tanh, - 'onnx::Selu': convert_selu, - 'onnx::Transpose': convert_transpose, - 'onnx::Reshape': convert_reshape, - 'onnx::MatMul': convert_matmul, - 'onnx::Gather': convert_gather, - 'onnx::ReduceSum': convert_reduce_sum, - 'onnx::Constant': convert_constant, - 'onnx::Upsample': convert_upsample, - 'onnx::Pad': convert_padding, - 'aten::adaptive_avg_pool2d': convert_adaptive_avg_pool2d, -} diff --git a/readthedocs.yml b/readthedocs.yml new file mode 100644 index 0000000..f52cac7 --- /dev/null +++ b/readthedocs.yml @@ -0,0 +1,6 @@ +# .readthedocs.yml + +build: + image: latest + +requirements_file: docs/requirements.txt \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 068d5df..917f0b4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,7 @@ keras -numpy \ No newline at end of file +tensorflow +numpy +torch +torchvision +onnx +onnx2keras \ No newline at end of file diff --git a/setup.py b/setup.py index b441ed6..a6767b2 100644 --- a/setup.py +++ b/setup.py @@ -1,33 +1,37 @@ from setuptools import setup, find_packages -try: # for pip >= 10 - from pip._internal.req import parse_requirements -except ImportError: # for pip <= 9.0.3 - from pip.req import parse_requirements +def parse_requirements(filename): + """ load requirements from a pip requirements file """ + lineiter = (line.strip() for line in open(filename)) + return [line for line in lineiter if line and not line.startswith("#")] -# parse_requirements() returns generator of pip.req.InstallRequirement objects -install_reqs = parse_requirements('requirements.txt', session='null') - - -# reqs is a list of requirement -# e.g. ['django==1.5.1', 'mezzanine==1.4.6'] -reqs = [str(ir.req) for ir in install_reqs] +reqs = parse_requirements('requirements.txt') with open('README.md') as f: - long_description = f.read() + long_description = f.read() setup(name='pytorch2keras', - version='0.1.3', - description='The deep learning models convertor', + version='0.2.4', + description='The deep learning models converter', long_description=long_description, long_description_content_type='text/markdown', - url='https://github.com/nerox8664/pytorch2keras', + url='https://github.com/gmalivenko/pytorch2keras', author='Grigory Malivenko', - author_email='nerox8664@gmail.com', + author_email='', + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: MIT License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Scientific/Engineering :: Image Recognition', + ], + keywords='machine-learning deep-learning pytorch keras neuralnetwork vgg resnet ' + 'densenet drn dpn darknet squeezenet mobilenet', license='MIT', packages=find_packages(), install_requires=reqs, diff --git a/tests/avg_pool.py b/tests/avg_pool.py deleted file mode 100644 index 84cbac6..0000000 --- a/tests/avg_pool.py +++ /dev/null @@ -1,46 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class AvgPool(nn.Module): - """Module for MaxPool conversion testing - """ - - def __init__(self, inp=10, out=16, kernel_size=3, bias=True): - super(AvgPool, self).__init__() - self.conv2d = nn.Conv2d(inp, out, kernel_size=kernel_size, padding=3, bias=bias) - self.pool = nn.AvgPool2d(kernel_size=kernel_size, count_include_pad=True) - - def forward(self, x): - x = self.conv2d(x) - x = self.pool(x) - return x - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - kernel_size = np.random.randint(1, 7) - inp = np.random.randint(kernel_size + 1, 100) - out = np.random.randint(1, 100) - - model = AvgPool(inp, out, kernel_size, inp % 2) - - input_np = np.random.uniform(0, 1, (1, inp, inp, inp)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (inp, inp, inp,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/bn.py b/tests/bn.py deleted file mode 100644 index 02d9274..0000000 --- a/tests/bn.py +++ /dev/null @@ -1,48 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class TestConv2d(nn.Module): - """Module for BatchNorm2d conversion testing - """ - - def __init__(self, inp=10, out=16, kernel_size=3, bias=True): - super(TestConv2d, self).__init__() - self.conv2d = nn.Conv2d(inp, out, kernel_size=kernel_size, bias=bias) - self.bn = nn.BatchNorm2d(out) - - def forward(self, x): - x = self.conv2d(x) - x = self.bn(x) - return x - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - kernel_size = np.random.randint(1, 7) - inp = np.random.randint(kernel_size + 1, 100) - out = np.random.randint(1, 100) - - model = TestConv2d(inp, out, kernel_size, inp % 2) - for m in model.modules(): - m.training = False - - input_np = np.random.uniform(0, 1, (1, inp, inp, inp)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (inp, inp, inp,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/concat_many.py b/tests/concat_many.py deleted file mode 100644 index 64f7990..0000000 --- a/tests/concat_many.py +++ /dev/null @@ -1,50 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class TestConcatMany(nn.Module): - """Module for Concatenation (2 or many layers) testing - """ - - def __init__(self, inp=10, out=16, kernel_size=3, bias=True): - super(TestConcatMany, self).__init__() - self.conv2_1 = nn.Conv2d(inp, out, kernel_size=kernel_size, bias=bias) - self.conv2_2 = nn.Conv2d(inp, out, kernel_size=kernel_size, bias=bias) - self.conv2_3 = nn.Conv2d(inp, out, kernel_size=kernel_size, bias=bias) - - def forward(self, x): - x = torch.cat([ - self.conv2_1(x), - self.conv2_2(x), - self.conv2_3(x) - ], dim=1) - return x - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - kernel_size = np.random.randint(1, 7) - inp = np.random.randint(kernel_size + 1, 100) - out = np.random.randint(1, 100) - - model = TestConcatMany(inp, out, kernel_size, inp % 2) - - input_np = np.random.uniform(0, 1, (1, inp, inp, inp)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (inp, inp, inp,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/const.py b/tests/const.py deleted file mode 100644 index 0b16a8a..0000000 --- a/tests/const.py +++ /dev/null @@ -1,43 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class TestConst(nn.Module): - """Module for Const conversion testing - """ - - def __init__(self, inp=10, out=16, bias=True): - super(TestConst, self).__init__() - self.linear = nn.Linear(inp, out, bias=False) - - def forward(self, x): - x = self.linear(x) * 2.0 - return x - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - inp = np.random.randint(1, 100) - out = np.random.randint(1, 100) - model = TestConst(inp, out, inp % 2) - - input_np = np.random.uniform(0, 1, (1, inp)) - input_var = Variable(torch.FloatTensor(input_np)) - - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (inp,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/conv2d.py b/tests/conv2d.py deleted file mode 100644 index 4aa3d59..0000000 --- a/tests/conv2d.py +++ /dev/null @@ -1,44 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class TestConv2d(nn.Module): - """Module for Conv2d conversion testing - """ - - def __init__(self, inp=10, out=16, kernel_size=3, bias=True): - super(TestConv2d, self).__init__() - self.conv2d = nn.Conv2d(inp, out, kernel_size=kernel_size, bias=bias) - - def forward(self, x): - x = self.conv2d(x) - return x - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - kernel_size = np.random.randint(1, 7) - inp = np.random.randint(kernel_size + 1, 100) - out = np.random.randint(1, 100) - - model = TestConv2d(inp, out, kernel_size, inp % 2) - - input_np = np.random.uniform(0, 1, (1, inp, inp, inp)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (inp, inp, inp,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/conv2d_channels_last.py b/tests/conv2d_channels_last.py deleted file mode 100644 index a3d59ea..0000000 --- a/tests/conv2d_channels_last.py +++ /dev/null @@ -1,44 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class TestConv2d(nn.Module): - """Module for Conv2d conversion testing - """ - - def __init__(self, inp=10, out=16, kernel_size=3, bias=True): - super(TestConv2d, self).__init__() - self.conv2d = nn.Conv2d(inp, out, stride=1, kernel_size=kernel_size, bias=bias) - - def forward(self, x): - x = self.conv2d(x) - return x - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - kernel_size = np.random.randint(1, 10) - inp = np.random.randint(kernel_size + 1, 100) - out = np.random.randint(1, 2) - - model = TestConv2d(inp + 2, out, kernel_size, inp % 2) - - input_np = np.random.uniform(0, 1, (1, inp + 2, inp, inp)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (inp + 2, inp, inp,), change_ordering=True, verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np.transpose(0, 2, 3, 1)) - - error = np.max(pytorch_output - keras_output.transpose(0, 3, 1, 2)) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/conv2d_dilation.py b/tests/conv2d_dilation.py deleted file mode 100644 index 10d2f6c..0000000 --- a/tests/conv2d_dilation.py +++ /dev/null @@ -1,45 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class TestConv2d(nn.Module): - """Module for Conv2d conversion testing - """ - - def __init__(self, inp=10, out=16, kernel_size=3, dilation=1, bias=True): - super(TestConv2d, self).__init__() - self.conv2d = nn.Conv2d(inp, out, kernel_size=kernel_size, bias=bias, dilation=dilation) - - def forward(self, x): - x = self.conv2d(x) - return x - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - kernel_size = np.random.randint(1, 7) - dilation = np.random.randint(1, kernel_size + 1) - inp = np.random.randint(kernel_size + 1, 100) - out = np.random.randint(1, 100) - - model = TestConv2d(inp, out, kernel_size, dilation, inp % 2) - - input_np = np.random.uniform(0, 1, (1, inp, inp, inp)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (inp, inp, inp,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/conv3d.py b/tests/conv3d.py deleted file mode 100644 index 027b18c..0000000 --- a/tests/conv3d.py +++ /dev/null @@ -1,46 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class TestConv3d(nn.Module): - """Module for Conv2d conversion testing - """ - - def __init__(self, inp=10, out=16, kernel_size=3, bias=True): - super(TestConv3d, self).__init__() - self.conv3d = nn.Conv3d(inp, out, kernel_size=kernel_size, bias=bias) - - def forward(self, x): - x = self.conv3d(x) - return x - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - kernel_size = np.random.randint(1, 7) - inp = np.random.randint(kernel_size + 1, 30) - out = np.random.randint(1, 30) - - model = TestConv3d(inp, out, kernel_size, inp % 2) - - input_var = Variable(torch.randn(1, inp, inp, inp, inp)) - - output = model(input_var) - - k_model = pytorch_to_keras(model, - input_var, - (inp, inp, inp, inp,), - verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_var.numpy()) - error = np.max(pytorch_output - keras_output) - print("iteration: {}, error: {}".format(i, error)) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/convtranspose2d.py b/tests/convtranspose2d.py deleted file mode 100644 index 151f048..0000000 --- a/tests/convtranspose2d.py +++ /dev/null @@ -1,44 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class TestConvTranspose2d(nn.Module): - """Module for ConvTranspose2d conversion testing - """ - - def __init__(self, inp=10, out=16, kernel_size=3, padding=1, bias=True): - super(TestConvTranspose2d, self).__init__() - self.conv2d = nn.ConvTranspose2d(inp, out, kernel_size=kernel_size, bias=bias, stride=padding) - - def forward(self, x): - x = self.conv2d(x) - return x - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - kernel_size = np.random.randint(1, 7) - inp = np.random.randint(kernel_size + 1, 100) - out = np.random.randint(1, 100) - - model = TestConvTranspose2d(inp, out, kernel_size, 2, inp % 3) - - input_np = np.random.uniform(0, 1, (1, inp, inp, inp)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (inp, inp, inp,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/dense.py b/tests/dense.py deleted file mode 100644 index cd2dcbf..0000000 --- a/tests/dense.py +++ /dev/null @@ -1,43 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class TestDense(nn.Module): - """Module for Dense conversion testing - """ - - def __init__(self, inp=10, out=16, bias=True): - super(TestDense, self).__init__() - self.linear = nn.Linear(inp, out, bias=False) - - def forward(self, x): - x = self.linear(x) - return x - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - inp = np.random.randint(1, 100) - out = np.random.randint(1, 100) - model = TestDense(inp, out, inp % 2) - - input_np = np.random.uniform(0, 1, (1, inp)) - input_var = Variable(torch.FloatTensor(input_np)) - - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (inp,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/densenet.py b/tests/densenet.py deleted file mode 100644 index b1994b7..0000000 --- a/tests/densenet.py +++ /dev/null @@ -1,29 +0,0 @@ -import numpy as np -import torch -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras -import torchvision - - -if __name__ == '__main__': - max_error = 0 - for i in range(10): - model = torchvision.models.DenseNet() - for m in model.modules(): - m.training = False - - input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/droupout.py b/tests/droupout.py deleted file mode 100644 index d722fa1..0000000 --- a/tests/droupout.py +++ /dev/null @@ -1,47 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class TestDropout(nn.Module): - """Module for Dropout conversion testing - """ - - def __init__(self, inp=10, out=16, p=0.5, bias=True): - super(TestDropout, self).__init__() - self.linear = nn.Linear(inp, out, bias=bias) - self.dropout = nn.Dropout(p=p) - - def forward(self, x): - x = self.linear(x) - x = self.dropout(x) - return x - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - inp = np.random.randint(1, 100) - out = np.random.randint(1, 100) - p = np.random.uniform(0, 1) - model = TestDropout(inp, out, inp % 2, p) - model.eval() - - input_np = np.random.uniform(-1.0, 1.0, (1, inp)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (inp,), verbose=True) - - keras_output = k_model.predict(input_np) - - pytorch_output = output.data.numpy() - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - # not implemented yet diff --git a/tests/embedding.py b/tests/embedding.py deleted file mode 100644 index bafbb53..0000000 --- a/tests/embedding.py +++ /dev/null @@ -1,36 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class TestEmbedding(nn.Module): - def __init__(self, input_size): - super(TestEmbedding, self).__init__() - self.embedd = nn.Embedding(input_size, 100) - - def forward(self, input): - return self.embedd(input) - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - input_np = np.random.randint(0, 10, (1, 1, 4)) - input = Variable(torch.LongTensor(input_np)) - - simple_net = TestEmbedding(1000) - output = simple_net(input) - - k_model = pytorch_to_keras(simple_net, input, (1, 4), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output[0]) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/__init__.py b/tests/layers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/layers/activations/hard_tanh.py b/tests/layers/activations/hard_tanh.py new file mode 100644 index 0000000..f092949 --- /dev/null +++ b/tests/layers/activations/hard_tanh.py @@ -0,0 +1,75 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + + +class LayerTest(nn.Module): + def __init__(self, min_val, max_val): + super(LayerTest, self).__init__() + self.htanh = nn.Hardtanh(min_val=min_val, max_val=max_val) + + def forward(self, x): + x = self.htanh(x) + return x + + +class FTest(nn.Module): + def __init__(self, min_val, max_val): + super(FTest, self).__init__() + self.min_val = min_val + self.max_val = max_val + + def forward(self, x): + from torch.nn import functional as F + return F.hardtanh(x, min_val=self.min_val, max_val=self.max_val) + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for i in range(10): + import random + vmin = random.random() - 1.0 + vmax = vmin + 2.0 * random.random() + model = LayerTest(vmin, vmax) + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + for i in range(10): + vmin = random.random() - 1.0 + vmax = vmin + 2.0 * random.random() + model = FTest(vmin, vmax) + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/activations/lrelu.py b/tests/layers/activations/lrelu.py new file mode 100644 index 0000000..5be0a2b --- /dev/null +++ b/tests/layers/activations/lrelu.py @@ -0,0 +1,70 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + + +class LayerTest(nn.Module): + def __init__(self, negative_slope): + super(LayerTest, self).__init__() + self.relu = nn.LeakyReLU(negative_slope=negative_slope) + + def forward(self, x): + x = self.relu(x) + return x + + +class FTest(nn.Module): + def __init__(self, negative_slope): + super(FTest, self).__init__() + self.negative_slope = negative_slope + + def forward(self, x): + from torch.nn import functional as F + return F.leaky_relu(x, self.negative_slope) + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for i in range(10): + import random + model = LayerTest(negative_slope=random.random() - 0.5) + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + for i in range(10): + model = FTest(negative_slope=random.random() - 0.5) + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/activations/relu.py b/tests/layers/activations/relu.py new file mode 100644 index 0000000..985c03d --- /dev/null +++ b/tests/layers/activations/relu.py @@ -0,0 +1,68 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + + +class LayerTest(nn.Module): + def __init__(self): + super(LayerTest, self).__init__() + self.relu = nn.ReLU() + + def forward(self, x): + x = self.relu(x) + return x + + +class FTest(nn.Module): + def __init__(self): + super(FTest, self).__init__() + + def forward(self, x): + from torch.nn import functional as F + return F.relu(x) + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for i in range(10): + model = LayerTest() + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + for i in range(10): + model = FTest() + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/activations/selu.py b/tests/layers/activations/selu.py new file mode 100644 index 0000000..92e249e --- /dev/null +++ b/tests/layers/activations/selu.py @@ -0,0 +1,68 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + + +class LayerTest(nn.Module): + def __init__(self): + super(LayerTest, self).__init__() + self.selu = nn.SELU() + + def forward(self, x): + x = self.selu(x) + return x + + +class FTest(nn.Module): + def __init__(self): + super(FTest, self).__init__() + + def forward(self, x): + from torch.nn import functional as F + return F.selu(x) + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for i in range(10): + model = LayerTest() + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + for i in range(10): + model = FTest() + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/activations/sigmoid.py b/tests/layers/activations/sigmoid.py new file mode 100644 index 0000000..f5b71aa --- /dev/null +++ b/tests/layers/activations/sigmoid.py @@ -0,0 +1,68 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + + +class LayerTest(nn.Module): + def __init__(self): + super(LayerTest, self).__init__() + self.sigmoid = nn.Sigmoid() + + def forward(self, x): + x = self.sigmoid(x) + return x + + +class FTest(nn.Module): + def __init__(self): + super(FTest, self).__init__() + + def forward(self, x): + from torch.nn import functional as F + return F.sigmoid(x) + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for i in range(10): + model = LayerTest() + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + for i in range(10): + model = FTest() + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/activations/softmax.py b/tests/layers/activations/softmax.py new file mode 100644 index 0000000..bb91abe --- /dev/null +++ b/tests/layers/activations/softmax.py @@ -0,0 +1,70 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + + +class LayerTest(nn.Module): + def __init__(self, dim): + super(LayerTest, self).__init__() + self.softmax = nn.Softmax(dim=dim) + + def forward(self, x): + x = self.softmax(x) + return x + + +class FTest(nn.Module): + def __init__(self, dim): + super(FTest, self).__init__() + self.dim = dim + + def forward(self, x): + from torch.nn import functional as F + return F.softmax(x, dim=self.dim) + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for i in range(50): + import random + model = LayerTest(dim=np.random.randint(0, 3)) + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + for i in range(50): + model = FTest(dim=np.random.randint(0, 3)) + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/activations/tanh.py b/tests/layers/activations/tanh.py new file mode 100644 index 0000000..231ce6c --- /dev/null +++ b/tests/layers/activations/tanh.py @@ -0,0 +1,68 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + + +class LayerTest(nn.Module): + def __init__(self): + super(LayerTest, self).__init__() + self.tanh = nn.Tanh() + + def forward(self, x): + x = self.tanh(x) + return x + + +class FTest(nn.Module): + def __init__(self): + super(FTest, self).__init__() + + def forward(self, x): + from torch.nn import functional as F + return F.tanh(x) + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for i in range(10): + model = LayerTest() + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + for i in range(10): + model = FTest() + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/alexnet.py b/tests/layers/constants/constant.py similarity index 51% rename from tests/alexnet.py rename to tests/layers/constants/constant.py index 4041d64..4df515a 100644 --- a/tests/alexnet.py +++ b/tests/layers/constants/constant.py @@ -1,13 +1,34 @@ import numpy as np import torch +import torch.nn as nn from torch.autograd import Variable from pytorch2keras.converter import pytorch_to_keras -import torchvision + + +class FTest(nn.Module): + def __init__(self): + super(FTest, self).__init__() + + def forward(self, x): + return x + torch.FloatTensor([1.0]) + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + if __name__ == '__main__': max_error = 0 + for i in range(10): - model = torchvision.models.AlexNet() + model = FTest() model.eval() input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) @@ -16,11 +37,7 @@ k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) + error = check_error(output, k_model, input_np) if max_error < error: max_error = error diff --git a/tests/layers/convolutions/conv1d.py b/tests/layers/convolutions/conv1d.py new file mode 100644 index 0000000..a265761 --- /dev/null +++ b/tests/layers/convolutions/conv1d.py @@ -0,0 +1,56 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + + +class LayerTest(nn.Module): + def __init__(self, inp, out, kernel_size=3, padding=1, stride=1, bias=False, dilation=1): + super(LayerTest, self).__init__() + self.conv = nn.Conv1d(inp, out, kernel_size=kernel_size, padding=padding, \ + stride=stride, bias=bias, dilation=dilation) + + def forward(self, x): + x = self.conv(x) + return x + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for kernel_size in [1, 3, 5]: + for padding in [0, 1, 3]: + for stride in [1, 2]: + for bias in [True, False]: + for dilation in [1, 2, 3]: + # ValueError: strides > 1 not supported in conjunction with dilation_rate > 1 + if stride > 1 and dilation > 1: + continue + + ins = np.random.choice([1, 3, 7]) + model = LayerTest(ins, np.random.choice([1, 3, 7]), \ + kernel_size=kernel_size, padding=padding, stride=stride, bias=bias, dilation=dilation) + model.eval() + + input_np = np.random.uniform(0, 1, (1, ins, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + print(output.size()) + k_model = pytorch_to_keras(model, input_var, (ins, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/convolutions/conv2d.py b/tests/layers/convolutions/conv2d.py new file mode 100644 index 0000000..8261b82 --- /dev/null +++ b/tests/layers/convolutions/conv2d.py @@ -0,0 +1,56 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + + +class LayerTest(nn.Module): + def __init__(self, inp, out, kernel_size=3, padding=1, stride=1, bias=False, dilation=1, groups=1): + super(LayerTest, self).__init__() + self.conv = nn.Conv2d(inp, out, kernel_size=kernel_size, padding=padding, \ + stride=stride, bias=bias, dilation=dilation, groups=groups) + + def forward(self, x): + x = self.conv(x) + return x + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for kernel_size in [1, 3, 5]: + for padding in [0, 1, 3]: + for stride in [1, 2]: + for bias in [True, False]: + for dilation in [1, 2, 3]: + for groups in [1, 3]: + # ValueError: strides > 1 not supported in conjunction with dilation_rate > 1 + if stride > 1 and dilation > 1: + continue + + model = LayerTest(3, groups, \ + kernel_size=kernel_size, padding=padding, stride=stride, bias=bias, dilation=dilation, groups=groups) + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/convolutions/convtranspose2d.py b/tests/layers/convolutions/convtranspose2d.py new file mode 100644 index 0000000..44d52f2 --- /dev/null +++ b/tests/layers/convolutions/convtranspose2d.py @@ -0,0 +1,52 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + + +class LayerTest(nn.Module): + def __init__(self, inp, out, kernel_size=3, padding=1, stride=1, bias=False): + super(LayerTest, self).__init__() + self.conv = nn.ConvTranspose2d(inp, out, kernel_size=kernel_size, padding=padding, \ + stride=stride, bias=bias) + + def forward(self, x): + x = self.conv(x) + return x + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for kernel_size in [1, 3, 5]: + for padding in [0, 1, 3]: + for stride in [1, 2]: + for bias in [True, False]: + outs = np.random.choice([1, 3, 7]) + + model = LayerTest(3, outs, \ + kernel_size=kernel_size, padding=padding, stride=stride, bias=bias) + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/elementwise/add.py b/tests/layers/elementwise/add.py new file mode 100644 index 0000000..be943fa --- /dev/null +++ b/tests/layers/elementwise/add.py @@ -0,0 +1,46 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + + +class FTest(nn.Module): + def __init__(self): + super(FTest, self).__init__() + def forward(self, x, y): + x = x + y + return x + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + + for i in range(10): + model = FTest() + model.eval() + + input_np1 = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_np2 = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var1 = Variable(torch.FloatTensor(input_np1)) + input_var2 = Variable(torch.FloatTensor(input_np2)) + output = model(input_var1, input_var2) + + k_model = pytorch_to_keras(model, [input_var1, input_var2], [(3, 224, 224,), (3, 224, 224,)], verbose=True) + + error = check_error(output, k_model, [input_np1, input_np2]) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/elementwise/div.py b/tests/layers/elementwise/div.py new file mode 100644 index 0000000..55c5f36 --- /dev/null +++ b/tests/layers/elementwise/div.py @@ -0,0 +1,46 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + + +class FTest(nn.Module): + def __init__(self): + super(FTest, self).__init__() + def forward(self, x, y): + x = x / y + return x + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + + for i in range(10): + model = FTest() + model.eval() + + input_np1 = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_np2 = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var1 = Variable(torch.FloatTensor(input_np1)) + input_var2 = Variable(torch.FloatTensor(input_np2)) + output = model(input_var1, input_var2) + + k_model = pytorch_to_keras(model, [input_var1, input_var2], [(3, 224, 224,), (3, 224, 224,)], verbose=True) + + error = check_error(output, k_model, [input_np1, input_np2]) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/elementwise/mul.py b/tests/layers/elementwise/mul.py new file mode 100644 index 0000000..4194ef4 --- /dev/null +++ b/tests/layers/elementwise/mul.py @@ -0,0 +1,46 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + + +class FTest(nn.Module): + def __init__(self): + super(FTest, self).__init__() + def forward(self, x, y): + x = x * y + return x + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + + for i in range(10): + model = FTest() + model.eval() + + input_np1 = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_np2 = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var1 = Variable(torch.FloatTensor(input_np1)) + input_var2 = Variable(torch.FloatTensor(input_np2)) + output = model(input_var1, input_var2) + + k_model = pytorch_to_keras(model, [input_var1, input_var2], [(3, 224, 224,), (3, 224, 224,)], verbose=True) + + error = check_error(output, k_model, [input_np1, input_np2]) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/elementwise/sub.py b/tests/layers/elementwise/sub.py new file mode 100644 index 0000000..b046961 --- /dev/null +++ b/tests/layers/elementwise/sub.py @@ -0,0 +1,46 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + + +class FTest(nn.Module): + def __init__(self): + super(FTest, self).__init__() + def forward(self, x, y): + x = x - y + return x + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + + for i in range(10): + model = FTest() + model.eval() + + input_np1 = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_np2 = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var1 = Variable(torch.FloatTensor(input_np1)) + input_var2 = Variable(torch.FloatTensor(input_np2)) + output = model(input_var1, input_var2) + + k_model = pytorch_to_keras(model, [input_var1, input_var2], [(3, 224, 224,), (3, 224, 224,)], verbose=True) + + error = check_error(output, k_model, [input_np1, input_np2]) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/embeddings/embedding.py b/tests/layers/embeddings/embedding.py new file mode 100644 index 0000000..5201e87 --- /dev/null +++ b/tests/layers/embeddings/embedding.py @@ -0,0 +1,49 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + + +class LayerTest(nn.Module): + def __init__(self, input_size, embedd_size): + super(LayerTest, self).__init__() + self.embedd = nn.Embedding(input_size, embedd_size) + + def forward(self, x): + x = self.embedd(x) + return x + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + + for i in range(10): + emb_size = np.random.randint(10, 1000) + inp_size = np.random.randint(10, 1000) + + model = LayerTest(inp_size, emb_size) + model.eval() + + input_np = np.random.uniform(0, 1, (1, 1, inp_size)) + input_var = Variable(torch.LongTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, [(1, inp_size)], verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/linears/linear.py b/tests/layers/linears/linear.py new file mode 100644 index 0000000..71e3c8f --- /dev/null +++ b/tests/layers/linears/linear.py @@ -0,0 +1,46 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + + +class LayerTest(nn.Module): + def __init__(self, inp, out, bias=False): + super(LayerTest, self).__init__() + self.fc = nn.Linear(inp, out, bias=bias) + + def forward(self, x): + x = self.fc(x) + return x + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for bias in [True, False]: + ins = np.random.choice([1, 3, 7]) + model = LayerTest(ins, np.random.choice([1, 3, 7]), bias=bias) + model.eval() + + input_np = np.random.uniform(0, 1, (1, ins)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + print(output.size()) + k_model = pytorch_to_keras(model, input_var, (ins,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/multiple_inputs.py b/tests/layers/multiple_inputs.py new file mode 100644 index 0000000..a9805be --- /dev/null +++ b/tests/layers/multiple_inputs.py @@ -0,0 +1,47 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + +class FTest(nn.Module): + def __init__(self): + super(FTest, self).__init__() + + def forward(self, x, y, z): + from torch.nn import functional as F + return F.relu(x) + F.relu(y) + F.relu(z) + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for i in range(10): + model = FTest() + model.eval() + + input_np1 = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var1 = Variable(torch.FloatTensor(input_np1)) + + input_np2 = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var2 = Variable(torch.FloatTensor(input_np2)) + output = model(input_var1, input_var2, input_var2) + + k_model = pytorch_to_keras(model, [input_var1, input_var2, input_var2], [(3, 224, 224,), (3, 224, 224,), (3, 224, 224,)], verbose=True) + + error = check_error(output, k_model, [input_np1, input_np2, input_np2]) + if max_error < error: + max_error = error + + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/normalizations/bn2d.py b/tests/layers/normalizations/bn2d.py new file mode 100644 index 0000000..7653f00 --- /dev/null +++ b/tests/layers/normalizations/bn2d.py @@ -0,0 +1,48 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras +import random + + +class LayerTest(nn.Module): + def __init__(self, out, eps, momentum): + super(LayerTest, self).__init__() + self.bn = nn.BatchNorm2d(out, eps=eps, momentum=momentum) + + def forward(self, x): + x = self.bn(x) + return x + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for i in range(10): + inp_size = np.random.randint(10, 100) + + model = LayerTest(inp_size, random.random(), random.random()) + model.eval() + + input_np = np.random.uniform(0, 1, (1, inp_size, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (inp_size, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/normalizations/do.py b/tests/layers/normalizations/do.py new file mode 100644 index 0000000..81a1bfb --- /dev/null +++ b/tests/layers/normalizations/do.py @@ -0,0 +1,49 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras +import random + + +class LayerTest(nn.Module): + def __init__(self, p): + super(LayerTest, self).__init__() + self.do = nn.Dropout2d(p=p) + + def forward(self, x): + x = x + 0 # To keep the graph + x = self.do(x) + return x + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for i in range(10): + inp_size = np.random.randint(10, 100) + + model = LayerTest(random.random()) + model.eval() + + input_np = np.random.uniform(0, 1, (1, inp_size, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (inp_size, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/normalizations/in2d.py b/tests/layers/normalizations/in2d.py new file mode 100644 index 0000000..3919626 --- /dev/null +++ b/tests/layers/normalizations/in2d.py @@ -0,0 +1,48 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras +import random + + +class LayerTest(nn.Module): + def __init__(self, out, eps, momentum): + super(LayerTest, self).__init__() + self.in2d = nn.InstanceNorm2d(out, eps=eps, momentum=momentum) + + def forward(self, x): + x = self.in2d(x) + return x + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for i in range(10): + inp_size = np.random.randint(10, 100) + + model = LayerTest(inp_size, random.random(), random.random()) + model.eval() + + input_np = np.random.uniform(0, 1, (1, inp_size, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (inp_size, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/poolings/avgpool2d.py b/tests/layers/poolings/avgpool2d.py new file mode 100644 index 0000000..a750096 --- /dev/null +++ b/tests/layers/poolings/avgpool2d.py @@ -0,0 +1,51 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + + +class LayerTest(nn.Module): + def __init__(self, kernel_size=3, padding=1, stride=1): + super(LayerTest, self).__init__() + self.pool = nn.AvgPool2d(kernel_size=kernel_size, padding=padding, stride=stride) + + def forward(self, x): + x = self.pool(x) + return x + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for kernel_size in [1, 3, 5, 7]: + for padding in [0, 1, 3]: + for stride in [1, 2, 3, 4]: + # RuntimeError: invalid argument 2: pad should be smaller than half of kernel size, but got padW = 1, padH = 1, kW = 1, + if padding > kernel_size / 2: + continue + + model = LayerTest(kernel_size=kernel_size, padding=padding, stride=stride) + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/poolings/global_avgpool2d.py b/tests/layers/poolings/global_avgpool2d.py new file mode 100644 index 0000000..954c6c2 --- /dev/null +++ b/tests/layers/poolings/global_avgpool2d.py @@ -0,0 +1,46 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + + +class LayerTest(nn.Module): + def __init__(self): + super(LayerTest, self).__init__() + self.pool = nn.AdaptiveAvgPool2d((1,1)) + + def forward(self, x): + x = self.pool(x) + return x + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for i in range(10): + + model = LayerTest() + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/poolings/global_maxpool2d.py b/tests/layers/poolings/global_maxpool2d.py new file mode 100644 index 0000000..9a5bf9f --- /dev/null +++ b/tests/layers/poolings/global_maxpool2d.py @@ -0,0 +1,46 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + + +class LayerTest(nn.Module): + def __init__(self): + super(LayerTest, self).__init__() + self.pool = nn.AdaptiveMaxPool2d((1,1)) + + def forward(self, x): + x = self.pool(x) + return x + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for i in range(10): + + model = LayerTest() + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/poolings/maxpool2d.py b/tests/layers/poolings/maxpool2d.py new file mode 100644 index 0000000..9bb24ca --- /dev/null +++ b/tests/layers/poolings/maxpool2d.py @@ -0,0 +1,51 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + + +class LayerTest(nn.Module): + def __init__(self, kernel_size=3, padding=1, stride=1): + super(LayerTest, self).__init__() + self.pool = nn.MaxPool2d(kernel_size=kernel_size, padding=padding, stride=stride) + + def forward(self, x): + x = self.pool(x) + return x + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for kernel_size in [1, 3, 5, 7]: + for padding in [0, 1, 3]: + for stride in [1, 2, 3, 4]: + # RuntimeError: invalid argument 2: pad should be smaller than half of kernel size, but got padW = 1, padH = 1, kW = 1, + if padding > kernel_size / 2: + continue + + model = LayerTest(kernel_size=kernel_size, padding=padding, stride=stride) + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/upsample_nearest.py b/tests/layers/upsample_nearest.py similarity index 100% rename from tests/upsample_nearest.py rename to tests/layers/upsample_nearest.py diff --git a/tests/layers/upsamplings/upsampling_bilinear.py b/tests/layers/upsamplings/upsampling_bilinear.py new file mode 100644 index 0000000..b96069b --- /dev/null +++ b/tests/layers/upsamplings/upsampling_bilinear.py @@ -0,0 +1,68 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + + +class LayerTest(nn.Module): + def __init__(self, scale_factor=2): + super(LayerTest, self).__init__() + self.up = nn.UpsamplingBilinear2d(scale_factor=scale_factor) + + def forward(self, x): + x = self.up(x) + return x + + +class FTest(nn.Module): + def __init__(self): + super(FTest, self).__init__() + + def forward(self, x): + from torch.nn import functional as F + return F.upsample_bilinear(x, scale_factor=2) + + +def check_error(output, k_model, input_np, epsilon=1e-4): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for scale_factor in [1, 2, 3, 4]: + model = LayerTest(scale_factor) + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + for i in range(10): + model = FTest() + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/layers/upsamplings/upsampling_nearest.py b/tests/layers/upsamplings/upsampling_nearest.py new file mode 100644 index 0000000..59f780e --- /dev/null +++ b/tests/layers/upsamplings/upsampling_nearest.py @@ -0,0 +1,68 @@ +import numpy as np +import torch +import torch.nn as nn +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + + +class LayerTest(nn.Module): + def __init__(self, scale_factor=2): + super(LayerTest, self).__init__() + self.up = nn.UpsamplingNearest2d(scale_factor=scale_factor) + + def forward(self, x): + x = self.up(x) + return x + + +class FTest(nn.Module): + def __init__(self): + super(FTest, self).__init__() + + def forward(self, x): + from torch.nn import functional as F + return F.upsample_nearest(x, scale_factor=2) + + +def check_error(output, k_model, input_np, epsilon=1e-4): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for scale_factor in [1, 2, 3, 4]: + model = LayerTest(scale_factor) + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + for i in range(10): + model = FTest() + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/lrelu.py b/tests/lrelu.py deleted file mode 100644 index c0aa4f4..0000000 --- a/tests/lrelu.py +++ /dev/null @@ -1,44 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class TestLeakyRelu(nn.Module): - """Module for LeakyReLu conversion testing - """ - - def __init__(self, inp=10, out=16, bias=True): - super(TestLeakyRelu, self).__init__() - self.linear = nn.Linear(inp, out, bias=True) - self.relu = nn.LeakyReLU(inplace=True) - - def forward(self, x): - x = self.linear(x) - x = self.relu(x) - return x - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - inp = np.random.randint(1, 100) - out = np.random.randint(1, 100) - model = TestLeakyRelu(inp, out, inp % 2) - - input_np = np.random.uniform(0, 1, (1, inp)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (inp,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/max_pool.py b/tests/max_pool.py deleted file mode 100644 index 288e8ab..0000000 --- a/tests/max_pool.py +++ /dev/null @@ -1,46 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class MaxPool(nn.Module): - """Module for MaxPool conversion testing - """ - - def __init__(self, inp=10, out=16, kernel_size=3, bias=True): - super(MaxPool, self).__init__() - self.conv2d = nn.Conv2d(inp, out, kernel_size=kernel_size, bias=bias) - self.pool = nn.MaxPool2d(kernel_size=3, padding=1) - - def forward(self, x): - x = self.conv2d(x) - x = self.pool(x) - return x - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - kernel_size = np.random.randint(1, 7) - inp = np.random.randint(kernel_size + 1, 100) - out = np.random.randint(1, 100) - - model = MaxPool(inp, out, kernel_size, inp % 2) - - input_np = np.random.uniform(0, 1, (1, inp, inp, inp)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (inp, inp, inp,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/max_pool3d.py b/tests/max_pool3d.py deleted file mode 100644 index 53d4cf1..0000000 --- a/tests/max_pool3d.py +++ /dev/null @@ -1,46 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class MaxPool(nn.Module): - """Module for MaxPool conversion testing - """ - - def __init__(self, inp=10, out=16, kernel_size=3, bias=True): - super(MaxPool, self).__init__() - self.conv3d = nn.Conv3d(inp, out, kernel_size=kernel_size, bias=bias) - self.pool3d = nn.MaxPool3d(kernel_size=3, padding=1) - - def forward(self, x): - x = self.conv3d(x) - x = self.pool3d(x) - return x - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - kernel_size = np.random.randint(1, 7) - inp = np.random.randint(kernel_size + 1, 30) - out = np.random.randint(1, 30) - - model = MaxPool(inp, out, kernel_size, inp % 2) - - input_np = np.random.uniform(0, 1, (1, inp, inp, inp, inp)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (inp, inp, inp, inp,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/models/__init__.py b/tests/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/models/alexnet.py b/tests/models/alexnet.py new file mode 100644 index 0000000..ef551a0 --- /dev/null +++ b/tests/models/alexnet.py @@ -0,0 +1,46 @@ +import numpy as np +import torch +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras +import torchvision + + +class AlexNet(torchvision.models.AlexNet): + def __init__(self): + super(AlexNet, self).__init__() + + def forward(self, x): + x = self.features(x) + x = x.view([int(x.size(0)), 256 * 6 * 6]) # << important fix + x = self.classifier(x) + return x + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for i in range(100): + model = AlexNet() + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/models/drn.py b/tests/models/drn.py new file mode 100644 index 0000000..668aad8 --- /dev/null +++ b/tests/models/drn.py @@ -0,0 +1,347 @@ +import numpy as np +import torch +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras +import torch.nn as nn + + +# Code below copied from DRN repo +import math +BatchNorm = nn.BatchNorm2d + + +def conv3x3(in_planes, out_planes, stride=1, padding=1, dilation=1): + return nn.Conv2d( + in_planes, out_planes, kernel_size=3, stride=stride, padding=padding, bias=False, dilation=dilation + ) + + +class BasicBlock(nn.Module): + expansion = 1 + + def __init__(self, inplanes, planes, stride=1, downsample=None, + dilation=(1, 1), residual=True): + super(BasicBlock, self).__init__() + self.conv1 = conv3x3(inplanes, planes, stride, + padding=dilation[0], dilation=dilation[0]) + self.bn1 = BatchNorm(planes) + self.relu = nn.ReLU(inplace=True) + self.conv2 = conv3x3(planes, planes, + padding=dilation[1], dilation=dilation[1]) + self.bn2 = BatchNorm(planes) + self.downsample = downsample + self.stride = stride + self.residual = residual + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + residual = self.downsample(x) + if self.residual: + out += residual + out = self.relu(out) + + return out + + +class Bottleneck(nn.Module): + expansion = 4 + + def __init__(self, inplanes, planes, stride=1, downsample=None, + dilation=(1, 1), residual=True): + super(Bottleneck, self).__init__() + self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) + self.bn1 = BatchNorm(planes) + self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, + padding=dilation[1], bias=False, + dilation=dilation[1]) + self.bn2 = BatchNorm(planes) + self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False) + self.bn3 = BatchNorm(planes * 4) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class DRN(nn.Module): + + def __init__(self, block, layers, num_classes=1000, + channels=(16, 32, 64, 128, 256, 512, 512, 512), + out_map=False, out_middle=False, pool_size=28, arch='D'): + super(DRN, self).__init__() + self.inplanes = channels[0] + self.out_map = out_map + self.out_dim = channels[-1] + self.out_middle = out_middle + self.arch = arch + + if arch == 'C': + self.conv1 = nn.Conv2d(3, channels[0], kernel_size=7, stride=1, + padding=3, bias=False) + self.bn1 = BatchNorm(channels[0]) + self.relu = nn.ReLU(inplace=True) + + self.layer1 = self._make_layer( + BasicBlock, channels[0], layers[0], stride=1) + self.layer2 = self._make_layer( + BasicBlock, channels[1], layers[1], stride=2) + elif arch == 'D': + self.layer0 = nn.Sequential( + nn.Conv2d(3, channels[0], kernel_size=7, stride=1, padding=3, + bias=False), + BatchNorm(channels[0]), + nn.ReLU(inplace=True) + ) + + self.layer1 = self._make_conv_layers( + channels[0], layers[0], stride=1) + self.layer2 = self._make_conv_layers( + channels[1], layers[1], stride=2) + + self.layer3 = self._make_layer(block, channels[2], layers[2], stride=2) + self.layer4 = self._make_layer(block, channels[3], layers[3], stride=2) + self.layer5 = self._make_layer(block, channels[4], layers[4], + dilation=2, new_level=False) + self.layer6 = None if layers[5] == 0 else \ + self._make_layer(block, channels[5], layers[5], dilation=4, + new_level=False) + + if arch == 'C': + self.layer7 = None if layers[6] == 0 else \ + self._make_layer(BasicBlock, channels[6], layers[6], dilation=2, + new_level=False, residual=False) + self.layer8 = None if layers[7] == 0 else \ + self._make_layer(BasicBlock, channels[7], layers[7], dilation=1, + new_level=False, residual=False) + elif arch == 'D': + self.layer7 = None if layers[6] == 0 else \ + self._make_conv_layers(channels[6], layers[6], dilation=2) + self.layer8 = None if layers[7] == 0 else \ + self._make_conv_layers(channels[7], layers[7], dilation=1) + + if num_classes > 0: + self.avgpool = nn.AvgPool2d(pool_size) + self.fc = nn.Conv2d(self.out_dim, num_classes, kernel_size=1, + stride=1, padding=0, bias=True) + for m in self.modules(): + if isinstance(m, nn.Conv2d): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.data.normal_(0, math.sqrt(2. / n)) + elif isinstance(m, BatchNorm): + m.weight.data.fill_(1) + m.bias.data.zero_() + + def _make_layer(self, block, planes, blocks, stride=1, dilation=1, + new_level=True, residual=True): + assert dilation == 1 or dilation % 2 == 0 + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2d(self.inplanes, planes * block.expansion, + kernel_size=1, stride=stride, bias=False), + BatchNorm(planes * block.expansion), + ) + + layers = list() + layers.append(block( + self.inplanes, planes, stride, downsample, + dilation=(1, 1) if dilation == 1 else ( + dilation // 2 if new_level else dilation, dilation), + residual=residual)) + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes, residual=residual, + dilation=(dilation, dilation))) + + return nn.Sequential(*layers) + + def _make_conv_layers(self, channels, convs, stride=1, dilation=1): + modules = [] + for i in range(convs): + modules.extend([ + nn.Conv2d(self.inplanes, channels, kernel_size=3, + stride=stride if i == 0 else 1, + padding=dilation, bias=False, dilation=dilation), + BatchNorm(channels), + nn.ReLU(inplace=True)]) + self.inplanes = channels + return nn.Sequential(*modules) + + def forward(self, x): + y = list() + + if self.arch == 'C': + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + elif self.arch == 'D': + x = self.layer0(x) + + x = self.layer1(x) + y.append(x) + x = self.layer2(x) + y.append(x) + + x = self.layer3(x) + y.append(x) + + x = self.layer4(x) + y.append(x) + + x = self.layer5(x) + y.append(x) + + if self.layer6 is not None: + x = self.layer6(x) + y.append(x) + + if self.layer7 is not None: + x = self.layer7(x) + y.append(x) + + if self.layer8 is not None: + x = self.layer8(x) + y.append(x) + + if self.out_map: + x = self.fc(x) + else: + x = self.avgpool(x) + x = self.fc(x) + x = x.view(x.size(0), -1) + + if self.out_middle: + return x, y + else: + return x + + +class DRN_A(nn.Module): + + def __init__(self, block, layers, num_classes=1000): + self.inplanes = 64 + super(DRN_A, self).__init__() + self.out_dim = 512 * block.expansion + self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, + bias=False) + self.bn1 = nn.BatchNorm2d(64) + self.relu = nn.ReLU(inplace=True) + self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) + self.layer1 = self._make_layer(block, 64, layers[0]) + self.layer2 = self._make_layer(block, 128, layers[1], stride=2) + self.layer3 = self._make_layer(block, 256, layers[2], stride=1, + dilation=2) + self.layer4 = self._make_layer(block, 512, layers[3], stride=1, + dilation=4) + self.avgpool = nn.AvgPool2d(28, stride=1) + self.fc = nn.Linear(512 * block.expansion, num_classes) + + for m in self.modules(): + if isinstance(m, nn.Conv2d): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.data.normal_(0, math.sqrt(2. / n)) + elif isinstance(m, BatchNorm): + m.weight.data.fill_(1) + m.bias.data.zero_() + + # for m in self.modules(): + # if isinstance(m, nn.Conv2d): + # nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') + # elif isinstance(m, nn.BatchNorm2d): + # nn.init.constant_(m.weight, 1) + # nn.init.constant_(m.bias, 0) + + def _make_layer(self, block, planes, blocks, stride=1, dilation=1): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2d(self.inplanes, planes * block.expansion, + kernel_size=1, stride=stride, bias=False), + nn.BatchNorm2d(planes * block.expansion), + ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, downsample)) + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes, + dilation=(dilation, dilation))) + + return nn.Sequential(*layers) + + def forward(self, x): + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + x = self.maxpool(x) + + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + x = self.layer4(x) + + x = self.avgpool(x) + x = x.view(x.size(0), -1) + x = self.fc(x) + + return x + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for i in range(100): + model = DRN(BasicBlock, [1, 1, 2, 2, 2, 2, 1, 1], arch='C') + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/models/menet.py b/tests/models/menet.py new file mode 100644 index 0000000..1381ba7 --- /dev/null +++ b/tests/models/menet.py @@ -0,0 +1,342 @@ +import numpy as np +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + +""" + MENet, implemented in PyTorch. + Original paper: 'Merging and Evolution: Improving Convolutional Neural Networks for Mobile Applications' +""" + +import torch +import torch.nn as nn +import torch.nn.functional as F +import torch.nn.init as init + + +def depthwise_conv3x3(channels, + stride): + return nn.Conv2d( + in_channels=channels, + out_channels=channels, + kernel_size=3, + stride=stride, + padding=1, + groups=channels, + bias=False) + + +def group_conv1x1(in_channels, + out_channels, + groups): + return nn.Conv2d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, + groups=groups, + bias=False) + + +def channel_shuffle(x, + groups): + """Channel Shuffle operation from ShuffleNet [arxiv: 1707.01083] + Arguments: + x (Tensor): tensor to shuffle. + groups (int): groups to be split + """ + batch, channels, height, width = x.size() + channels_per_group = channels // groups + x = x.view(batch, groups, channels_per_group, height, width) + x = torch.transpose(x, 1, 2).contiguous() + x = x.view(batch, channels, height, width) + return x + + +class ChannelShuffle(nn.Module): + + def __init__(self, + channels, + groups): + super(ChannelShuffle, self).__init__() + if channels % groups != 0: + raise ValueError('channels must be divisible by groups') + self.groups = groups + + def forward(self, x): + return channel_shuffle(x, self.groups) + + +class ShuffleInitBlock(nn.Module): + + def __init__(self, + in_channels, + out_channels): + super(ShuffleInitBlock, self).__init__() + + self.conv = nn.Conv2d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=3, + stride=2, + padding=1, + bias=False) + self.bn = nn.BatchNorm2d(num_features=out_channels) + self.activ = nn.ReLU(inplace=True) + self.pool = nn.MaxPool2d( + kernel_size=3, + stride=2, + padding=1) + + def forward(self, x): + x = self.conv(x) + x = self.bn(x) + x = self.activ(x) + x = self.pool(x) + return x + + +def conv1x1(in_channels, + out_channels): + return nn.Conv2d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, + bias=False) + + +def conv3x3(in_channels, + out_channels, + stride): + return nn.Conv2d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=3, + stride=stride, + padding=1, + bias=False) + + +class MEModule(nn.Module): + + def __init__(self, + in_channels, + out_channels, + side_channels, + groups, + downsample, + ignore_group): + super(MEModule, self).__init__() + self.downsample = downsample + mid_channels = out_channels // 4 + + if downsample: + out_channels -= in_channels + + # residual branch + self.compress_conv1 = group_conv1x1( + in_channels=in_channels, + out_channels=mid_channels, + groups=(1 if ignore_group else groups)) + self.compress_bn1 = nn.BatchNorm2d(num_features=mid_channels) + self.c_shuffle = ChannelShuffle( + channels=mid_channels, + groups=(1 if ignore_group else groups)) + self.dw_conv2 = depthwise_conv3x3( + channels=mid_channels, + stride=(2 if self.downsample else 1)) + self.dw_bn2 = nn.BatchNorm2d(num_features=mid_channels) + self.expand_conv3 = group_conv1x1( + in_channels=mid_channels, + out_channels=out_channels, + groups=groups) + self.expand_bn3 = nn.BatchNorm2d(num_features=out_channels) + if downsample: + self.avgpool = nn.AvgPool2d(kernel_size=3, stride=2, padding=1) + self.activ = nn.ReLU(inplace=True) + + # fusion branch + self.s_merge_conv = conv1x1( + in_channels=mid_channels, + out_channels=side_channels) + self.s_merge_bn = nn.BatchNorm2d(num_features=side_channels) + self.s_conv = conv3x3( + in_channels=side_channels, + out_channels=side_channels, + stride=(2 if self.downsample else 1)) + self.s_conv_bn = nn.BatchNorm2d(num_features=side_channels) + self.s_evolve_conv = conv1x1( + in_channels=side_channels, + out_channels=mid_channels) + self.s_evolve_bn = nn.BatchNorm2d(num_features=mid_channels) + + def forward(self, x): + identity = x + # pointwise group convolution 1 + x = self.activ(self.compress_bn1(self.compress_conv1(x))) + x = self.c_shuffle(x) + # merging + y = self.s_merge_conv(x) + y = self.s_merge_bn(y) + y = self.activ(y) + # depthwise convolution (bottleneck) + x = self.dw_bn2(self.dw_conv2(x)) + # evolution + y = self.s_conv(y) + y = self.s_conv_bn(y) + y = self.activ(y) + y = self.s_evolve_conv(y) + y = self.s_evolve_bn(y) + y = F.sigmoid(y) + x = x * y + # pointwise group convolution 2 + x = self.expand_bn3(self.expand_conv3(x)) + # identity branch + if self.downsample: + identity = self.avgpool(identity) + x = torch.cat((x, identity), dim=1) + else: + x = x + identity + x = self.activ(x) + return x + + +class MENet(nn.Module): + + def __init__(self, + block_channels, + side_channels, + groups, + num_classes=1000): + super(MENet, self).__init__() + input_channels = 3 + block_layers = [4, 8, 4] + + self.features = nn.Sequential() + self.features.add_module("init_block", ShuffleInitBlock( + in_channels=input_channels, + out_channels=block_channels[0])) + + for i in range(len(block_channels) - 1): + stage = nn.Sequential() + in_channels_i = block_channels[i] + out_channels_i = block_channels[i + 1] + for j in range(block_layers[i]): + stage.add_module("unit_{}".format(j + 1), MEModule( + in_channels=(in_channels_i if j == 0 else out_channels_i), + out_channels=out_channels_i, + side_channels=side_channels, + groups=groups, + downsample=(j == 0), + ignore_group=(i == 0 and j == 0))) + self.features.add_module("stage_{}".format(i + 1), stage) + + self.features.add_module('final_pool', nn.AvgPool2d(kernel_size=7)) + + self.output = nn.Linear( + in_features=block_channels[-1], + out_features=num_classes) + + self._init_params() + + def _init_params(self): + for name, module in self.named_modules(): + if isinstance(module, nn.Conv2d): + init.kaiming_uniform_(module.weight) + if module.bias is not None: + init.constant_(module.bias, 0) + + def forward(self, x): + x = self.features(x) + x = x.view(x.size(0), -1) + x = self.output(x) + return x + + +def get_menet(first_block_channels, + side_channels, + groups, + pretrained=False, + **kwargs): + if first_block_channels == 108: + block_channels = [12, 108, 216, 432] + elif first_block_channels == 128: + block_channels = [12, 128, 256, 512] + elif first_block_channels == 160: + block_channels = [16, 160, 320, 640] + elif first_block_channels == 228: + block_channels = [24, 228, 456, 912] + elif first_block_channels == 256: + block_channels = [24, 256, 512, 1024] + elif first_block_channels == 348: + block_channels = [24, 348, 696, 1392] + elif first_block_channels == 352: + block_channels = [24, 352, 704, 1408] + elif first_block_channels == 456: + block_channels = [48, 456, 912, 1824] + else: + raise ValueError("The {} of `first_block_channels` is not supported".format(first_block_channels)) + + if pretrained: + raise ValueError("Pretrained model is not supported") + + net = MENet( + block_channels=block_channels, + side_channels=side_channels, + groups=groups, + **kwargs) + return net + + +def menet108_8x1_g3(**kwargs): + return get_menet(108, 8, 3, **kwargs) + + +def menet128_8x1_g4(**kwargs): + return get_menet(128, 8, 4, **kwargs) + + +def menet160_8x1_g8(**kwargs): + return get_menet(160, 8, 8, **kwargs) + + +def menet228_12x1_g3(**kwargs): + return get_menet(228, 12, 3, **kwargs) + + +def menet256_12x1_g4(**kwargs): + return get_menet(256, 12, 4, **kwargs) + + +def menet348_12x1_g3(**kwargs): + return get_menet(348, 12, 3, **kwargs) + + +def menet352_12x1_g8(**kwargs): + return get_menet(352, 12, 8, **kwargs) + + +def menet456_24x1_g3(**kwargs): + return get_menet(456, 24, 3, **kwargs) + + +if __name__ == '__main__': + max_error = 0 + for i in range(10): + model = menet228_12x1_g3() + for m in model.modules(): + m.training = False + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print(error) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/models/mobilinet.py b/tests/models/mobilinet.py new file mode 100644 index 0000000..18a6bb9 --- /dev/null +++ b/tests/models/mobilinet.py @@ -0,0 +1,140 @@ +import numpy as np +import torch +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras +import torch.nn as nn +import math + + +def conv_bn(inp, oup, stride): + return nn.Sequential( + nn.Conv2d(inp, oup, 3, stride, 1, bias=False), + nn.BatchNorm2d(oup), + nn.ReLU6(inplace=True) + ) + + +def conv_1x1_bn(inp, oup): + return nn.Sequential( + nn.Conv2d(inp, oup, 1, 1, 0, bias=False), + nn.BatchNorm2d(oup), + nn.ReLU6(inplace=True) + ) + + +class InvertedResidual(nn.Module): + def __init__(self, inp, oup, stride, expand_ratio): + super(InvertedResidual, self).__init__() + self.stride = stride + assert stride in [1, 2] + + self.use_res_connect = self.stride == 1 and inp == oup + + self.conv = nn.Sequential( + # pw + nn.Conv2d(inp, inp * expand_ratio, 1, 1, 0, bias=False), + nn.BatchNorm2d(inp * expand_ratio), + nn.ReLU6(inplace=True), + # dw + nn.Conv2d(inp * expand_ratio, inp * expand_ratio, 3, stride, 1, groups=inp * expand_ratio, bias=False), + nn.BatchNorm2d(inp * expand_ratio), + nn.ReLU6(inplace=True), + # pw-linear + nn.Conv2d(inp * expand_ratio, oup, 1, 1, 0, bias=False), + nn.BatchNorm2d(oup), + ) + + def forward(self, x): + if self.use_res_connect: + return x + self.conv(x) + else: + return self.conv(x) + + +class MobileNetV2(nn.Module): + def __init__(self, n_class=1000, input_size=224, width_mult=1.): + super(MobileNetV2, self).__init__() + # setting of inverted residual blocks + self.interverted_residual_setting = [ + # t, c, n, s + [1, 16, 1, 1], + [6, 24, 2, 2], + [6, 32, 3, 2], + [6, 64, 4, 2], + [6, 96, 3, 1], + [6, 160, 3, 2], + [6, 320, 1, 1], + ] + + # building first layer + assert input_size % 32 == 0 + input_channel = int(32 * width_mult) + self.last_channel = int(1280 * width_mult) if width_mult > 1.0 else 1280 + self.features = [conv_bn(3, input_channel, 2)] + # building inverted residual blocks + for t, c, n, s in self.interverted_residual_setting: + output_channel = int(c * width_mult) + for i in range(n): + if i == 0: + self.features.append(InvertedResidual(input_channel, output_channel, s, t)) + else: + self.features.append(InvertedResidual(input_channel, output_channel, 1, t)) + input_channel = output_channel + # building last several layers + self.features.append(conv_1x1_bn(input_channel, self.last_channel)) + self.features.append(nn.AvgPool2d(input_size//32)) + # make it nn.Sequential + self.features = nn.Sequential(*self.features) + + # building classifier + self.classifier = nn.Sequential( + nn.Dropout(), + nn.Linear(self.last_channel, n_class), + ) + + # self._initialize_weights() + + def forward(self, x): + x = self.features(x) + x = x.view(-1, self.last_channel) + x = self.classifier(x) + return x + + def _initialize_weights(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.data.normal_(0, math.sqrt(2. / n)) + if m.bias is not None: + m.bias.data.zero_() + elif isinstance(m, nn.BatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + elif isinstance(m, nn.Linear): + n = m.weight.size(1) + m.weight.data.normal_(0, 0.01) + m.bias.data.zero_() + + +if __name__ == '__main__': + max_error = 0 + for i in range(10): + model = MobileNetV2() + for m in model.modules(): + m.training = False + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print(error) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/models/preresnet18.py b/tests/models/preresnet18.py new file mode 100644 index 0000000..730bec0 --- /dev/null +++ b/tests/models/preresnet18.py @@ -0,0 +1,738 @@ +""" +Model from https://github.com/osmr/imgclsmob/tree/master/pytorch/models +""" + +import numpy as np +import torch +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + +import os +import torch.nn as nn +import torch.nn.init as init + + +class PreResConv(nn.Module): + """ + PreResNet specific convolution block, with pre-activation. + + Parameters: + ---------- + in_channels : int + Number of input channels. + out_channels : int + Number of output channels. + kernel_size : int or tuple/list of 2 int + Convolution window size. + stride : int or tuple/list of 2 int + Strides of the convolution. + padding : int or tuple/list of 2 int + Padding value for convolution layer. + """ + def __init__(self, + in_channels, + out_channels, + kernel_size, + stride, + padding): + super(PreResConv, self).__init__() + self.bn = nn.BatchNorm2d(num_features=in_channels) + self.activ = nn.ReLU(inplace=True) + self.conv = nn.Conv2d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + bias=False) + + def forward(self, x): + x = self.bn(x) + x = self.activ(x) + x_pre_activ = x + x = self.conv(x) + return x, x_pre_activ + + +def conv1x1(in_channels, + out_channels, + stride): + """ + Convolution 1x1 layer. + + Parameters: + ---------- + in_channels : int + Number of input channels. + out_channels : int + Number of output channels. + stride : int or tuple/list of 2 int + Strides of the convolution. + """ + return nn.Conv2d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, + stride=stride, + padding=0, + bias=False) + + +def preres_conv1x1(in_channels, + out_channels, + stride): + """ + 1x1 version of the PreResNet specific convolution block. + + Parameters: + ---------- + in_channels : int + Number of input channels. + out_channels : int + Number of output channels. + stride : int or tuple/list of 2 int + Strides of the convolution. + """ + return PreResConv( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, + stride=stride, + padding=0) + + +def preres_conv3x3(in_channels, + out_channels, + stride): + """ + 3x3 version of the PreResNet specific convolution block. + + Parameters: + ---------- + in_channels : int + Number of input channels. + out_channels : int + Number of output channels. + stride : int or tuple/list of 2 int + Strides of the convolution. + bn_use_global_stats : bool + Whether global moving statistics is used instead of local batch-norm for BatchNorm layers. + """ + return PreResConv( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=3, + stride=stride, + padding=1) + + +class PreResBlock(nn.Module): + """ + Simple PreResNet block for residual path in ResNet unit. + + Parameters: + ---------- + in_channels : int + Number of input channels. + out_channels : int + Number of output channels. + stride : int or tuple/list of 2 int + Strides of the convolution. + """ + def __init__(self, + in_channels, + out_channels, + stride): + super(PreResBlock, self).__init__() + self.conv1 = preres_conv3x3( + in_channels=in_channels, + out_channels=out_channels, + stride=stride) + self.conv2 = preres_conv3x3( + in_channels=out_channels, + out_channels=out_channels, + stride=1) + + def forward(self, x): + x, x_pre_activ = self.conv1(x) + x, _ = self.conv2(x) + return x, x_pre_activ + + +class PreResBottleneck(nn.Module): + """ + PreResNet bottleneck block for residual path in PreResNet unit. + + Parameters: + ---------- + in_channels : int + Number of input channels. + out_channels : int + Number of output channels. + stride : int or tuple/list of 2 int + Strides of the convolution. + conv1_stride : bool + Whether to use stride in the first or the second convolution layer of the block. + """ + def __init__(self, + in_channels, + out_channels, + stride, + conv1_stride): + super(PreResBottleneck, self).__init__() + mid_channels = out_channels // 4 + + self.conv1 = preres_conv1x1( + in_channels=in_channels, + out_channels=mid_channels, + stride=(stride if conv1_stride else 1)) + self.conv2 = preres_conv3x3( + in_channels=mid_channels, + out_channels=mid_channels, + stride=(1 if conv1_stride else stride)) + self.conv3 = preres_conv1x1( + in_channels=mid_channels, + out_channels=out_channels, + stride=1) + + def forward(self, x): + x, x_pre_activ = self.conv1(x) + x, _ = self.conv2(x) + x, _ = self.conv3(x) + return x, x_pre_activ + + +class PreResUnit(nn.Module): + """ + PreResNet unit with residual connection. + + Parameters: + ---------- + in_channels : int + Number of input channels. + out_channels : int + Number of output channels. + stride : int or tuple/list of 2 int + Strides of the convolution. + bottleneck : bool + Whether to use a bottleneck or simple block in units. + conv1_stride : bool + Whether to use stride in the first or the second convolution layer of the block. + """ + def __init__(self, + in_channels, + out_channels, + stride, + bottleneck, + conv1_stride): + super(PreResUnit, self).__init__() + self.resize_identity = (in_channels != out_channels) or (stride != 1) + + if bottleneck: + self.body = PreResBottleneck( + in_channels=in_channels, + out_channels=out_channels, + stride=stride, + conv1_stride=conv1_stride) + else: + self.body = PreResBlock( + in_channels=in_channels, + out_channels=out_channels, + stride=stride) + if self.resize_identity: + self.identity_conv = conv1x1( + in_channels=in_channels, + out_channels=out_channels, + stride=stride) + + def forward(self, x): + identity = x + x, x_pre_activ = self.body(x) + if self.resize_identity: + identity = self.identity_conv(x_pre_activ) + x = x + identity + return x + + +class PreResInitBlock(nn.Module): + """ + PreResNet specific initial block. + + Parameters: + ---------- + in_channels : int + Number of input channels. + out_channels : int + Number of output channels. + """ + def __init__(self, + in_channels, + out_channels): + super(PreResInitBlock, self).__init__() + self.conv = nn.Conv2d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=7, + stride=2, + padding=3, + bias=False) + self.bn = nn.BatchNorm2d(num_features=out_channels) + self.activ = nn.ReLU(inplace=True) + self.pool = nn.MaxPool2d( + kernel_size=3, + stride=2, + padding=1) + + def forward(self, x): + x = self.conv(x) + x = self.bn(x) + x = self.activ(x) + x = self.pool(x) + return x + + +class PreResActivation(nn.Module): + """ + PreResNet pure pre-activation block without convolution layer. It's used by itself as the final block. + + Parameters: + ---------- + in_channels : int + Number of input channels. + """ + def __init__(self, + in_channels): + super(PreResActivation, self).__init__() + self.bn = nn.BatchNorm2d(num_features=in_channels) + self.activ = nn.ReLU(inplace=True) + + def forward(self, x): + x = self.bn(x) + x = self.activ(x) + return x + + +class PreResNet(nn.Module): + """ + PreResNet model from 'Identity Mappings in Deep Residual Networks,' https://arxiv.org/abs/1603.05027. + + Parameters: + ---------- + channels : list of list of int + Number of output channels for each unit. + init_block_channels : int + Number of output channels for the initial unit. + bottleneck : bool + Whether to use a bottleneck or simple block in units. + conv1_stride : bool + Whether to use stride in the first or the second convolution layer in units. + in_channels : int, default 3 + Number of input channels. + num_classes : int, default 1000 + Number of classification classes. + """ + def __init__(self, + channels, + init_block_channels, + bottleneck, + conv1_stride, + in_channels=3, + num_classes=1000): + super(PreResNet, self).__init__() + + self.features = nn.Sequential() + self.features.add_module("init_block", PreResInitBlock( + in_channels=in_channels, + out_channels=init_block_channels)) + in_channels = init_block_channels + for i, channels_per_stage in enumerate(channels): + stage = nn.Sequential() + for j, out_channels in enumerate(channels_per_stage): + stride = 1 if (i == 0) or (j != 0) else 2 + stage.add_module("unit{}".format(j + 1), PreResUnit( + in_channels=in_channels, + out_channels=out_channels, + stride=stride, + bottleneck=bottleneck, + conv1_stride=conv1_stride)) + in_channels = out_channels + self.features.add_module("stage{}".format(i + 1), stage) + self.features.add_module('post_activ', PreResActivation(in_channels=in_channels)) + self.features.add_module('final_pool', nn.AvgPool2d( + kernel_size=7, + stride=1)) + + self.output = nn.Linear( + in_features=in_channels, + out_features=num_classes) + + self._init_params() + + def _init_params(self): + for name, module in self.named_modules(): + if isinstance(module, nn.Conv2d): + init.kaiming_uniform_(module.weight) + if module.bias is not None: + init.constant_(module.bias, 0) + + def forward(self, x): + x = self.features(x) + x = x.view(int(x.size(0)), -1) + x = self.output(x) + return x + + +def get_preresnet(blocks, + conv1_stride=True, + width_scale=1.0, + model_name=None, + pretrained=False, + root=os.path.join('~', '.torch', 'models'), + **kwargs): + """ + Create PreResNet model with specific parameters. + + Parameters: + ---------- + blocks : int + Number of blocks. + conv1_stride : bool + Whether to use stride in the first or the second convolution layer in units. + width_scale : float + Scale factor for width of layers. + model_name : str or None, default None + Model name for loading pretrained model. + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + + if blocks == 10: + layers = [1, 1, 1, 1] + elif blocks == 12: + layers = [2, 1, 1, 1] + elif blocks == 14: + layers = [2, 2, 1, 1] + elif blocks == 16: + layers = [2, 2, 2, 1] + elif blocks == 18: + layers = [2, 2, 2, 2] + elif blocks == 34: + layers = [3, 4, 6, 3] + elif blocks == 50: + layers = [3, 4, 6, 3] + elif blocks == 101: + layers = [3, 4, 23, 3] + elif blocks == 152: + layers = [3, 8, 36, 3] + elif blocks == 200: + layers = [3, 24, 36, 3] + else: + raise ValueError("Unsupported ResNet with number of blocks: {}".format(blocks)) + + init_block_channels = 64 + + if blocks < 50: + channels_per_layers = [64, 128, 256, 512] + bottleneck = False + else: + channels_per_layers = [256, 512, 1024, 2048] + bottleneck = True + + channels = [[ci] * li for (ci, li) in zip(channels_per_layers, layers)] + + if width_scale != 1.0: + channels = [[int(cij * width_scale) for cij in ci] for ci in channels] + init_block_channels = int(init_block_channels * width_scale) + + net = PreResNet( + channels=channels, + init_block_channels=init_block_channels, + bottleneck=bottleneck, + conv1_stride=conv1_stride, + **kwargs) + + if pretrained: + if (model_name is None) or (not model_name): + raise ValueError("Parameter `model_name` should be properly initialized for loading pretrained model.") + import torch + from .model_store import get_model_file + net.load_state_dict(torch.load(get_model_file( + model_name=model_name, + local_model_store_dir_path=root))) + + return net + + +def preresnet10(**kwargs): + """ + PreResNet-10 model from 'Identity Mappings in Deep Residual Networks,' https://arxiv.org/abs/1603.05027. + It's an experimental model. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_preresnet(blocks=10, model_name="preresnet10", **kwargs) + + +def preresnet12(**kwargs): + """ + PreResNet-12 model from 'Identity Mappings in Deep Residual Networks,' https://arxiv.org/abs/1603.05027. + It's an experimental model. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_preresnet(blocks=12, model_name="preresnet12", **kwargs) + + +def preresnet14(**kwargs): + """ + PreResNet-14 model from 'Identity Mappings in Deep Residual Networks,' https://arxiv.org/abs/1603.05027. + It's an experimental model. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_preresnet(blocks=14, model_name="preresnet14", **kwargs) + + +def preresnet16(**kwargs): + """ + PreResNet-16 model from 'Identity Mappings in Deep Residual Networks,' https://arxiv.org/abs/1603.05027. + It's an experimental model. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_preresnet(blocks=16, model_name="preresnet16", **kwargs) + + +def preresnet18_wd4(**kwargs): + """ + PreResNet-18 model with 0.25 width scale from 'Identity Mappings in Deep Residual Networks,' + https://arxiv.org/abs/1603.05027. It's an experimental model. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_preresnet(blocks=18, width_scale=0.25, model_name="preresnet18_wd4", **kwargs) + + +def preresnet18_wd2(**kwargs): + """ + PreResNet-18 model with 0.5 width scale from 'Identity Mappings in Deep Residual Networks,' + https://arxiv.org/abs/1603.05027. It's an experimental model. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_preresnet(blocks=18, width_scale=0.5, model_name="preresnet18_wd2", **kwargs) + + +def preresnet18_w3d4(**kwargs): + """ + PreResNet-18 model with 0.75 width scale from 'Identity Mappings in Deep Residual Networks,' + https://arxiv.org/abs/1603.05027. It's an experimental model. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_preresnet(blocks=18, width_scale=0.75, model_name="preresnet18_w3d4", **kwargs) + + +def preresnet18(**kwargs): + """ + PreResNet-18 model from 'Identity Mappings in Deep Residual Networks,' https://arxiv.org/abs/1603.05027. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_preresnet(blocks=18, model_name="preresnet18", **kwargs) + + +def preresnet34(**kwargs): + """ + PreResNet-34 model from 'Identity Mappings in Deep Residual Networks,' https://arxiv.org/abs/1603.05027. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_preresnet(blocks=34, model_name="preresnet34", **kwargs) + + +def preresnet50(**kwargs): + """ + PreResNet-50 model from 'Identity Mappings in Deep Residual Networks,' https://arxiv.org/abs/1603.05027. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_preresnet(blocks=50, model_name="preresnet50", **kwargs) + + +def preresnet50b(**kwargs): + """ + PreResNet-50 model with stride at the second convolution in bottleneck block from 'Identity Mappings in Deep + Residual Networks,' https://arxiv.org/abs/1603.05027. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_preresnet(blocks=50, conv1_stride=False, model_name="preresnet50b", **kwargs) + + +def preresnet101(**kwargs): + """ + PreResNet-101 model from 'Identity Mappings in Deep Residual Networks,' https://arxiv.org/abs/1603.05027. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_preresnet(blocks=101, model_name="preresnet101", **kwargs) + + +def preresnet101b(**kwargs): + """ + PreResNet-101 model with stride at the second convolution in bottleneck block from 'Identity Mappings in Deep + Residual Networks,' https://arxiv.org/abs/1603.05027. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_preresnet(blocks=101, conv1_stride=False, model_name="preresnet101b", **kwargs) + + +def preresnet152(**kwargs): + """ + PreResNet-152 model from 'Identity Mappings in Deep Residual Networks,' https://arxiv.org/abs/1603.05027. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_preresnet(blocks=152, model_name="preresnet152", **kwargs) + + +def preresnet152b(**kwargs): + """ + PreResNet-152 model with stride at the second convolution in bottleneck block from 'Identity Mappings in Deep + Residual Networks,' https://arxiv.org/abs/1603.05027. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_preresnet(blocks=152, conv1_stride=False, model_name="preresnet152b", **kwargs) + + +def preresnet200(**kwargs): + """ + PreResNet-200 model from 'Identity Mappings in Deep Residual Networks,' https://arxiv.org/abs/1603.05027. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_preresnet(blocks=200, model_name="preresnet200", **kwargs) + + +def preresnet200b(**kwargs): + """ + PreResNet-200 model with stride at the second convolution in bottleneck block from 'Identity Mappings in Deep + Residual Networks,' https://arxiv.org/abs/1603.05027. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_preresnet(blocks=200, conv1_stride=False, model_name="preresnet200b", **kwargs) + + +if __name__ == '__main__': + max_error = 0 + for i in range(10): + model = preresnet18() + for m in model.modules(): + m.training = False + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print(error) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/models/resnet18.py b/tests/models/resnet18.py new file mode 100644 index 0000000..522e2f4 --- /dev/null +++ b/tests/models/resnet18.py @@ -0,0 +1,56 @@ +import numpy as np +import torch +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras +import torchvision + + +class ResNet(torchvision.models.resnet.ResNet): + def __init__(self, *args, **kwargs): + super(ResNet, self).__init__(*args, **kwargs) + + def forward(self, x): + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + x = self.maxpool(x) + + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + x = self.layer4(x) + + x = self.avgpool(x) + x = x.view(int(x.size(0)), -1) # << This fix again + x = self.fc(x) + return x + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for i in range(100): + model = ResNet(torchvision.models.resnet.BasicBlock, [2, 2, 2, 2]) + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/models/resnet18_channels_last.py b/tests/models/resnet18_channels_last.py new file mode 100644 index 0000000..216357e --- /dev/null +++ b/tests/models/resnet18_channels_last.py @@ -0,0 +1,56 @@ +import numpy as np +import torch +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras +import torchvision + + +class ResNet(torchvision.models.resnet.ResNet): + def __init__(self, *args, **kwargs): + super(ResNet, self).__init__(*args, **kwargs) + + def forward(self, x): + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + x = self.maxpool(x) + + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + x = self.layer4(x) + + x = self.avgpool(x) + x = x.view(int(x.size(0)), -1) # << This fix again + x = self.fc(x) + return x + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for i in range(100): + model = ResNet(torchvision.models.resnet.BasicBlock, [2, 2, 2, 2]) + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True, change_ordering=True) + + error = check_error(output, k_model, input_np.transpose(0, 2, 3, 1)) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/models/resnet34.py b/tests/models/resnet34.py new file mode 100644 index 0000000..9a93876 --- /dev/null +++ b/tests/models/resnet34.py @@ -0,0 +1,56 @@ +import numpy as np +import torch +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras +import torchvision + + +class ResNet(torchvision.models.resnet.ResNet): + def __init__(self, *args, **kwargs): + super(ResNet, self).__init__(*args, **kwargs) + + def forward(self, x): + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + x = self.maxpool(x) + + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + x = self.layer4(x) + + x = self.avgpool(x) + x = x.view(int(x.size(0)), -1) # << This fix again + x = self.fc(x) + return x + + +def check_error(output, k_model, input_np, epsilon=1e-3): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for i in range(100): + model = ResNet(torchvision.models.resnet.BasicBlock, [3, 4, 6, 3]) + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/models/resnet50.py b/tests/models/resnet50.py new file mode 100644 index 0000000..21c0c16 --- /dev/null +++ b/tests/models/resnet50.py @@ -0,0 +1,56 @@ +import numpy as np +import torch +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras +import torchvision + + +class ResNet(torchvision.models.resnet.ResNet): + def __init__(self, *args, **kwargs): + super(ResNet, self).__init__(*args, **kwargs) + + def forward(self, x): + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + x = self.maxpool(x) + + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + x = self.layer4(x) + + x = self.avgpool(x) + x = x.view(int(x.size(0)), -1) # << This fix again + x = self.fc(x) + return x + + +def check_error(output, k_model, input_np, epsilon=1e-3): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for i in range(100): + model = ResNet(torchvision.models.resnet.Bottleneck, [3, 4, 6, 3]) + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/senet.py b/tests/models/senet.py similarity index 97% rename from tests/senet.py rename to tests/models/senet.py index 5a5382a..e261eb5 100644 --- a/tests/senet.py +++ b/tests/models/senet.py @@ -19,8 +19,10 @@ def __init__(self, channel, reduction=16): def forward(self, x): b, c, _, _ = x.size() - y = self.avg_pool(x).view(b, c) - y = self.fc(y).view(b, c, 1, 1) + y = self.avg_pool(x) + y = y.view([int(b), -1]) + y = self.fc(y) + y = y.view([int(b), int(c), 1, 1]) return x * y @@ -230,7 +232,7 @@ def forward(self, x): x = self.layer3(x) x = self.avgpool(x) - x = x.view(x.size(0), -1) + x = x.view([int(x.size(0)), -1]) x = self.fc(x) return x @@ -252,7 +254,7 @@ def forward(self, x): x = self.relu(x) x = self.avgpool(x) - x = x.view(x.size(0), -1) + x = x.view([int(x.size(0)), -1]) x = self.fc(x) diff --git a/tests/squeezenet.py b/tests/models/squeezenet.py similarity index 98% rename from tests/squeezenet.py rename to tests/models/squeezenet.py index 27f6073..0658566 100644 --- a/tests/squeezenet.py +++ b/tests/models/squeezenet.py @@ -103,7 +103,7 @@ def __init__(self, version=1.0, num_classes=1000): def forward(self, x): x = self.features(x) x = self.classifier(x) - return x.view(x.size(0), self.num_classes) + return x.view([int(x.size(0)), self.num_classes]) if __name__ == '__main__': diff --git a/tests/models/squeezenext.py b/tests/models/squeezenext.py new file mode 100644 index 0000000..e78b9f4 --- /dev/null +++ b/tests/models/squeezenext.py @@ -0,0 +1,404 @@ +""" +Model from https://github.com/osmr/imgclsmob/tree/master/pytorch/models +""" + + +import numpy as np +import torch +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras + +import os +import torch.nn as nn +import torch.nn.init as init + + +class SqnxtConv(nn.Module): + """ + SqueezeNext specific convolution block. + + Parameters: + ---------- + in_channels : int + Number of input channels. + out_channels : int + Number of output channels. + kernel_size : int or tuple/list of 2 int + Convolution window size. + stride : int or tuple/list of 2 int + Strides of the convolution. + padding : int or tuple/list of 2 int, default (0, 0) + Padding value for convolution layer. + """ + def __init__(self, + in_channels, + out_channels, + kernel_size, + stride, + padding=(0, 0)): + super(SqnxtConv, self).__init__() + self.conv = nn.Conv2d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding) + self.bn = nn.BatchNorm2d(num_features=out_channels) + self.activ = nn.ReLU(inplace=True) + + def forward(self, x): + x = self.conv(x) + x = self.bn(x) + x = self.activ(x) + return x + + +class SqnxtUnit(nn.Module): + """ + SqueezeNext unit. + + Parameters: + ---------- + in_channels : int + Number of input channels. + out_channels : int + Number of output channels. + stride : int or tuple/list of 2 int + Strides of the convolution. + """ + def __init__(self, + in_channels, + out_channels, + stride): + super(SqnxtUnit, self).__init__() + if stride == 2: + reduction_den = 1 + self.resize_identity = True + elif in_channels > out_channels: + reduction_den = 4 + self.resize_identity = True + else: + reduction_den = 2 + self.resize_identity = False + + self.conv1 = SqnxtConv( + in_channels=in_channels, + out_channels=(in_channels // reduction_den), + kernel_size=1, + stride=stride) + self.conv2 = SqnxtConv( + in_channels=(in_channels // reduction_den), + out_channels=(in_channels // (2 * reduction_den)), + kernel_size=1, + stride=1) + self.conv3 = SqnxtConv( + in_channels=(in_channels // (2 * reduction_den)), + out_channels=(in_channels // reduction_den), + kernel_size=(1, 3), + stride=1, + padding=(0, 1)) + self.conv4 = SqnxtConv( + in_channels=(in_channels // reduction_den), + out_channels=(in_channels // reduction_den), + kernel_size=(3, 1), + stride=1, + padding=(1, 0)) + self.conv5 = SqnxtConv( + in_channels=(in_channels // reduction_den), + out_channels=out_channels, + kernel_size=1, + stride=1) + + if self.resize_identity: + self.identity_conv = SqnxtConv( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, + stride=stride) + self.activ = nn.ReLU(inplace=True) + + def forward(self, x): + if self.resize_identity: + identity = self.identity_conv(x) + else: + identity = x + identity = self.activ(identity) + x = self.conv1(x) + x = self.conv2(x) + x = self.conv3(x) + x = self.conv4(x) + x = self.conv5(x) + x = x + identity + x = self.activ(x) + return x + + +class SqnxtInitBlock(nn.Module): + """ + SqueezeNext specific initial block. + + Parameters: + ---------- + in_channels : int + Number of input channels. + out_channels : int + Number of output channels. + """ + def __init__(self, + in_channels, + out_channels): + super(SqnxtInitBlock, self).__init__() + self.conv = SqnxtConv( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=7, + stride=2, + padding=1) + self.pool = nn.MaxPool2d( + kernel_size=3, + stride=2, + ceil_mode=True) + + def forward(self, x): + x = self.conv(x) + x = self.pool(x) + return x + + +class SqueezeNext(nn.Module): + """ + SqueezeNext model from 'SqueezeNext: Hardware-Aware Neural Network Design,' https://arxiv.org/abs/1803.10615. + + Parameters: + ---------- + channels : list of list of int + Number of output channels for each unit. + init_block_channels : int + Number of output channels for the initial unit. + final_block_channels : int + Number of output channels for the final block of the feature extractor. + in_channels : int, default 3 + Number of input channels. + num_classes : int, default 1000 + Number of classification classes. + """ + def __init__(self, + channels, + init_block_channels, + final_block_channels, + in_channels=3, + num_classes=1000): + super(SqueezeNext, self).__init__() + + self.features = nn.Sequential() + self.features.add_module("init_block", SqnxtInitBlock( + in_channels=in_channels, + out_channels=init_block_channels)) + in_channels = init_block_channels + for i, channels_per_stage in enumerate(channels): + stage = nn.Sequential() + for j, out_channels in enumerate(channels_per_stage): + stride = 2 if (j == 0) and (i != 0) else 1 + stage.add_module("unit{}".format(j + 1), SqnxtUnit( + in_channels=in_channels, + out_channels=out_channels, + stride=stride)) + in_channels = out_channels + self.features.add_module("stage{}".format(i + 1), stage) + self.features.add_module('final_block', SqnxtConv( + in_channels=in_channels, + out_channels=final_block_channels, + kernel_size=1, + stride=1)) + in_channels = final_block_channels + self.features.add_module('final_pool', nn.AvgPool2d( + kernel_size=7, + stride=1)) + + self.output = nn.Linear( + in_features=in_channels, + out_features=num_classes) + + self._init_params() + + def _init_params(self): + for name, module in self.named_modules(): + if isinstance(module, nn.Conv2d): + init.kaiming_uniform_(module.weight) + if module.bias is not None: + init.constant_(module.bias, 0) + + def forward(self, x): + x = self.features(x) + x = x.view(x.size(0), -1) + x = self.output(x) + return x + + +def get_squeezenext(version, + width_scale, + model_name=None, + pretrained=False, + root=os.path.join('~', '.torch', 'models'), + **kwargs): + """ + Create SqueezeNext model with specific parameters. + + Parameters: + ---------- + version : str + Version of SqueezeNet ('23' or '23v5'). + width_scale : float + Scale factor for width of layers. + model_name : str or None, default None + Model name for loading pretrained model. + pretrained : bool, default False + Whether to load the pretrained weights for model. + ctx : Context, default CPU + The context in which to load the pretrained weights. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + + init_block_channels = 64 + final_block_channels = 128 + channels_per_layers = [32, 64, 128, 256] + + if version == '23': + layers = [6, 6, 8, 1] + elif version == '23v5': + layers = [2, 4, 14, 1] + else: + raise ValueError("Unsupported SqueezeNet version {}".format(version)) + + channels = [[ci] * li for (ci, li) in zip(channels_per_layers, layers)] + + if width_scale != 1: + channels = [[int(cij * width_scale) for cij in ci] for ci in channels] + init_block_channels = int(init_block_channels * width_scale) + final_block_channels = int(final_block_channels * width_scale) + + net = SqueezeNext( + channels=channels, + init_block_channels=init_block_channels, + final_block_channels=final_block_channels, + **kwargs) + + if pretrained: + if (model_name is None) or (not model_name): + raise ValueError("Parameter `model_name` should be properly initialized for loading pretrained model.") + import torch + from .model_store import get_model_file + net.load_state_dict(torch.load(get_model_file( + model_name=model_name, + local_model_store_dir_path=root))) + + return net + + +def sqnxt23_w1(**kwargs): + """ + 1.0-SqNxt-23 model from 'SqueezeNext: Hardware-Aware Neural Network Design,' https://arxiv.org/abs/1803.10615. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_squeezenext(version="23", width_scale=1.0, model_name="sqnxt23_w1", **kwargs) + + +def sqnxt23_w3d2(**kwargs): + """ + 0.75-SqNxt-23 model from 'SqueezeNext: Hardware-Aware Neural Network Design,' https://arxiv.org/abs/1803.10615. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_squeezenext(version="23", width_scale=1.5, model_name="sqnxt23_w3d2", **kwargs) + + +def sqnxt23_w2(**kwargs): + """ + 0.5-SqNxt-23 model from 'SqueezeNext: Hardware-Aware Neural Network Design,' https://arxiv.org/abs/1803.10615. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_squeezenext(version="23", width_scale=2.0, model_name="sqnxt23_w2", **kwargs) + + +def sqnxt23v5_w1(**kwargs): + """ + 1.0-SqNxt-23v5 model from 'SqueezeNext: Hardware-Aware Neural Network Design,' https://arxiv.org/abs/1803.10615. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_squeezenext(version="23v5", width_scale=1.0, model_name="sqnxt23v5_w1", **kwargs) + + +def sqnxt23v5_w3d2(**kwargs): + """ + 0.75-SqNxt-23v5 model from 'SqueezeNext: Hardware-Aware Neural Network Design,' https://arxiv.org/abs/1803.10615. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_squeezenext(version="23v5", width_scale=1.5, model_name="sqnxt23v5_w3d2", **kwargs) + + +def sqnxt23v5_w2(**kwargs): + """ + 0.5-SqNxt-23v5 model from 'SqueezeNext: Hardware-Aware Neural Network Design,' https://arxiv.org/abs/1803.10615. + + Parameters: + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.torch/models' + Location for keeping the model parameters. + """ + return get_squeezenext(version="23v5", width_scale=2.0, model_name="sqnxt23v5_w2", **kwargs) + + +if __name__ == '__main__': + max_error = 0 + for i in range(10): + model = sqnxt23_w1() + for m in model.modules(): + m.training = False + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print(error) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/models/vgg11.py b/tests/models/vgg11.py new file mode 100644 index 0000000..f9da7c7 --- /dev/null +++ b/tests/models/vgg11.py @@ -0,0 +1,46 @@ +import numpy as np +import torch +from torch.autograd import Variable +from pytorch2keras.converter import pytorch_to_keras +import torchvision + + +class VGG(torchvision.models.vgg.VGG): + def __init__(self, *args, **kwargs): + super(VGG, self).__init__(*args, **kwargs) + + def forward(self, x): + x = self.features(x) + x = x.view([int(x.size(0)), -1]) + x = self.classifier(x) + return x + + +def check_error(output, k_model, input_np, epsilon=1e-5): + pytorch_output = output.data.numpy() + keras_output = k_model.predict(input_np) + + error = np.max(pytorch_output - keras_output) + print('Error:', error) + + assert error < epsilon + return error + + +if __name__ == '__main__': + max_error = 0 + for i in range(100): + model = VGG(torchvision.models.vgg.make_layers(torchvision.models.vgg.cfg['A'], batch_norm=True)) + model.eval() + + input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) + input_var = Variable(torch.FloatTensor(input_np)) + output = model(input_var) + + k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) + + error = check_error(output, k_model, input_np) + if max_error < error: + max_error = error + + print('Max error: {0}'.format(max_error)) diff --git a/tests/mul.py b/tests/mul.py deleted file mode 100644 index ca2b0f2..0000000 --- a/tests/mul.py +++ /dev/null @@ -1,46 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class TestMul(nn.Module): - """Module for Element-wise multiplication conversion testing - """ - - def __init__(self, inp=10, out=16, kernel_size=3, bias=True): - super(TestMul, self).__init__() - self.conv2d_1 = nn.Conv2d(inp, out, stride=(inp % 3 + 1), kernel_size=kernel_size, bias=bias) - self.conv2d_2 = nn.Conv2d(inp, out, stride=(inp % 3 + 1), kernel_size=kernel_size, bias=bias) - - def forward(self, x): - x1 = self.conv2d_1(x) - x2 = self.conv2d_2(x) - return (x1 * x2).sum() - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - kernel_size = np.random.randint(1, 7) - inp = np.random.randint(kernel_size + 1, 100) - out = np.random.randint(1, 100) - - model = TestMul(inp, out, kernel_size, inp % 2) - - input_np = np.random.uniform(0, 1, (1, inp, inp, inp)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (inp, inp, inp,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/multiple_inputs.py b/tests/multiple_inputs.py deleted file mode 100644 index dfff736..0000000 --- a/tests/multiple_inputs.py +++ /dev/null @@ -1,45 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class TestMultipleInputs(nn.Module): - """Module for Conv2d conversion testing - """ - - def __init__(self, inp=10, out=16, kernel_size=3, bias=True): - super(TestMultipleInputs, self).__init__() - self.conv2d = nn.Conv2d(inp, out, kernel_size=kernel_size, bias=bias) - - def forward(self, x, y, z): - return self.conv2d(x) + self.conv2d(y) + self.conv2d(z) - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - kernel_size = np.random.randint(1, 7) - inp = np.random.randint(kernel_size + 1, 100) - out = np.random.randint(1, 100) - - model = TestMultipleInputs(inp, out, kernel_size, inp % 2) - - input_np = np.random.uniform(0, 1, (1, inp, inp, inp)) - input_var = Variable(torch.FloatTensor(input_np)) - input_var2 = Variable(torch.FloatTensor(input_np)) - input_var3 = Variable(torch.FloatTensor(input_np)) - output = model(input_var, input_var2, input_var3) - - k_model = pytorch_to_keras(model, [input_var, input_var2, input_var3], [(inp, inp, inp,), (inp, inp, inp,), (inp, inp, inp,)], verbose=True) - k_model.summary() - pytorch_output = output.data.numpy() - keras_output = k_model.predict([input_np, input_np, input_np]) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/relu.py b/tests/relu.py deleted file mode 100644 index 69ad413..0000000 --- a/tests/relu.py +++ /dev/null @@ -1,44 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class TestRelu(nn.Module): - """Module for ReLu conversion testing - """ - - def __init__(self, inp=10, out=16, bias=True): - super(TestRelu, self).__init__() - self.linear = nn.Linear(inp, out, bias=True) - self.relu = nn.ReLU(inplace=True) - - def forward(self, x): - x = self.linear(x) - x = self.relu(x) - return x - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - inp = np.random.randint(1, 100) - out = np.random.randint(1, 100) - model = TestRelu(inp, out, inp % 2) - - input_np = np.random.uniform(0, 1, (1, inp)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (inp,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/resnet18.py b/tests/resnet18.py deleted file mode 100644 index 7ebf78a..0000000 --- a/tests/resnet18.py +++ /dev/null @@ -1,29 +0,0 @@ -import numpy as np -import torch -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras -import torchvision - - -if __name__ == '__main__': - max_error = 0 - for i in range(10): - model = torchvision.models.resnet18() - for m in model.modules(): - m.training = False - - input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/resnet18_channels_last.py b/tests/resnet18_channels_last.py deleted file mode 100644 index 03f18db..0000000 --- a/tests/resnet18_channels_last.py +++ /dev/null @@ -1,31 +0,0 @@ -import numpy as np -import torch -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras -import torchvision - - -if __name__ == '__main__': - max_error = 0 - for i in range(10): - model = torchvision.models.resnet18() - for m in model.modules(): - m.training = False - - input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True, change_ordering=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np.transpose(0, 2, 3, 1)) - - print(pytorch_output.shape, keras_output.shape) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/resnet34.py b/tests/resnet34.py deleted file mode 100644 index 6cc458d..0000000 --- a/tests/resnet34.py +++ /dev/null @@ -1,29 +0,0 @@ -import numpy as np -import torch -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras -import torchvision - - -if __name__ == '__main__': - max_error = 0 - for i in range(10): - model = torchvision.models.resnet34() - for m in model.modules(): - m.training = False - - input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/resnet50.py b/tests/resnet50.py deleted file mode 100644 index 959f6bc..0000000 --- a/tests/resnet50.py +++ /dev/null @@ -1,29 +0,0 @@ -import numpy as np -import torch -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras -import torchvision - - -if __name__ == '__main__': - max_error = 0 - for i in range(10): - model = torchvision.models.resnet50() - for m in model.modules(): - m.training = False - - input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/sigmoid.py b/tests/sigmoid.py deleted file mode 100644 index 4ed247c..0000000 --- a/tests/sigmoid.py +++ /dev/null @@ -1,44 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class TestSigmoid(nn.Module): - """Module for Sigmoid activation conversion testing - """ - - def __init__(self, inp=10, out=16, bias=True): - super(TestSigmoid, self).__init__() - self.linear = nn.Linear(inp, out, bias=True) - self.sigmoid = nn.Sigmoid() - - def forward(self, x): - x = self.linear(x) - x = self.sigmoid(x) - return x - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - inp = np.random.randint(1, 100) - out = np.random.randint(1, 100) - model = TestSigmoid(inp, out, inp % 2) - - input_np = np.random.uniform(-1.0, 1.0, (1, inp)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (inp,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/softmax.py b/tests/softmax.py deleted file mode 100644 index 934828a..0000000 --- a/tests/softmax.py +++ /dev/null @@ -1,44 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class TestSoftmax(nn.Module): - """Module for Softmax activation conversion testing - """ - - def __init__(self, inp=10, out=16, bias=True): - super(TestSoftmax, self).__init__() - self.linear = nn.Linear(inp, out, bias=True) - self.softmax = nn.Softmax() - - def forward(self, x): - x = self.linear(x) - x = self.softmax(x) - return x - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - inp = np.random.randint(1, 100) - out = np.random.randint(1, 100) - model = TestSoftmax(inp, out, inp % 2) - - input_np = np.random.uniform(-1.0, 1.0, (1, inp)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (inp,), verbose=True, change_ordering=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/sub.py b/tests/sub.py deleted file mode 100644 index 5baf91a..0000000 --- a/tests/sub.py +++ /dev/null @@ -1,46 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class TestSub(nn.Module): - """Module for Element-wise subtaction conversion testing - """ - - def __init__(self, inp=10, out=16, kernel_size=3, bias=True): - super(TestSub, self).__init__() - self.conv2d_1 = nn.Conv2d(inp, out, stride=(inp % 3 + 1), kernel_size=kernel_size, bias=bias) - self.conv2d_2 = nn.Conv2d(inp, out, stride=(inp % 3 + 1), kernel_size=kernel_size, bias=bias) - - def forward(self, x): - x1 = self.conv2d_1(x) - x2 = self.conv2d_2(x) - return x1 - x2 - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - kernel_size = np.random.randint(1, 7) - inp = np.random.randint(kernel_size + 1, 100) - out = np.random.randint(1, 100) - - model = TestSub(inp, out, kernel_size, inp % 2) - - input_np = np.random.uniform(0, 1, (1, inp, inp, inp)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (inp, inp, inp,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/sum.py b/tests/sum.py deleted file mode 100644 index 0b5208c..0000000 --- a/tests/sum.py +++ /dev/null @@ -1,36 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class TestSum(nn.Module): - def __init__(self, input_size): - super(TestSum, self).__init__() - self.embedd = nn.Embedding(input_size, 100) - - def forward(self, input): - return self.embedd(input).sum(dim=0) - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - input_np = np.random.randint(0, 10, (1, 1, 4)) - input = Variable(torch.LongTensor(input_np)) - - simple_net = TestSum(1000) - output = simple_net(input) - - k_model = pytorch_to_keras(simple_net, input, (1, 4), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output[0]) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/tanh.py b/tests/tanh.py deleted file mode 100644 index a746d29..0000000 --- a/tests/tanh.py +++ /dev/null @@ -1,44 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class TestTanh(nn.Module): - """Module for Tanh activation conversion testing - """ - - def __init__(self, inp=10, out=16, bias=True): - super(TestTanh, self).__init__() - self.linear = nn.Linear(inp, out, bias=True) - self.tanh = nn.Tanh() - - def forward(self, x): - x = self.linear(x) - x = self.tanh(x) - return x - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - inp = np.random.randint(1, 100) - out = np.random.randint(1, 100) - model = TestTanh(inp, out, inp % 2) - - input_np = np.random.uniform(-1.0, 1.0, (1, inp)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (inp,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/transpose.py b/tests/transpose.py deleted file mode 100644 index b02e32a..0000000 --- a/tests/transpose.py +++ /dev/null @@ -1,46 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class TestTranspose(nn.Module): - """Module for Transpose conversion testing - """ - - def __init__(self, inp=10, out=16, kernel_size=3, bias=True): - super(TestTranspose, self).__init__() - self.conv2d = nn.Conv2d(inp, out, kernel_size=kernel_size, bias=bias) - - def forward(self, x): - x = self.conv2d(x) - x = torch.transpose(x, 2, 3) - x = torch.nn.Tanh()(x) - return x - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - kernel_size = np.random.randint(1, 7) - inp = np.random.randint(kernel_size + 1, 100) - out = np.random.randint(1, 100) - - model = TestTranspose(inp, out, kernel_size, inp % 2) - - input_np = np.random.uniform(0, 1, (1, inp, inp, inp)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (inp, inp, inp,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/vgg11.py b/tests/vgg11.py deleted file mode 100644 index c7e39fc..0000000 --- a/tests/vgg11.py +++ /dev/null @@ -1,29 +0,0 @@ -import numpy as np -import torch -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras -import torchvision - - -if __name__ == '__main__': - max_error = 0 - for i in range(10): - model = torchvision.models.vgg11_bn() - for m in model.modules(): - m.training = False - - input_np = np.random.uniform(0, 1, (1, 3, 224, 224)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error)) diff --git a/tests/view.py b/tests/view.py deleted file mode 100644 index 630bdaf..0000000 --- a/tests/view.py +++ /dev/null @@ -1,46 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from torch.autograd import Variable -from pytorch2keras.converter import pytorch_to_keras - - -class TestView(nn.Module): - """Module for View conversion testing - """ - - def __init__(self, inp=10, out=16, kernel_size=3, bias=True): - super(TestView, self).__init__() - self.conv2d = nn.Conv2d(inp, out, kernel_size=kernel_size, bias=bias) - - def forward(self, x): - x = self.conv2d(x) - x = x.view([x.size(0), -1, 2, 1, 1, 1, 1, 1]) - x = torch.nn.Tanh()(x) - return x - - -if __name__ == '__main__': - max_error = 0 - for i in range(100): - kernel_size = np.random.randint(1, 7) - inp = 2 * np.random.randint(kernel_size + 1, 10) - out = 2 * np.random.randint(1, 10) - - model = TestView(inp, out, kernel_size, inp % 2) - - input_np = np.random.uniform(0, 1, (1, inp, inp, inp)) - input_var = Variable(torch.FloatTensor(input_np)) - output = model(input_var) - - k_model = pytorch_to_keras(model, input_var, (inp, inp, inp,), verbose=True) - - pytorch_output = output.data.numpy() - keras_output = k_model.predict(input_np) - - error = np.max(pytorch_output - keras_output) - print(error) - if max_error < error: - max_error = error - - print('Max error: {0}'.format(max_error))