Skip to content

Commit 0fce6fb

Browse files
author
Jakub Kaczmarzyk
committed
Merge remote-tracking branch 'upstream/master' into add/conv3d
2 parents bfeb268 + 9fd6ed9 commit 0fce6fb

File tree

10 files changed

+1702
-17
lines changed

10 files changed

+1702
-17
lines changed

.npmignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
.DS_Store
66
.babelrc
77
.idea/
8+
src/**/*_test.ts
89
demo/
910
docs/
1011
scripts/

README.md

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -171,25 +171,32 @@ the fetch() method.
171171

172172
Currently TensorFlow.js only supports a limited set of TensorFlow Ops. See the
173173
[full list](./docs/supported_ops.md).
174-
If your model uses an unsupported ops, the `tensorflowjs_converter` script will fail and
174+
If your model uses unsupported ops, the `tensorflowjs_converter` script will fail and
175175
produce a list of the unsupported ops in your model. Please file issues to let us
176176
know what ops you need support with.
177177

178-
## Loading the weights only
178+
## Manual forward pass and direct weights loading
179179

180-
If you prefer to load the weights only, you can use follow code snippet.
180+
If you want to manually write the forward pass with the ops API, you can load the weights directly as a map from weight names to tensors:
181181

182-
```typescript
182+
```js
183183
import * as tf from '@tensorflow/tfjs';
184184

185185
const modelUrl = "https://example.org/model/model.json";
186186

187-
const model = await fetch(modelUrl);
188-
this.weightManifest = (await model.json())['weightsManifest'];
187+
const response = await fetch(modelUrl);
188+
this.weightManifest = (await response.json())['weightsManifest'];
189189
const weightMap = await tf.io.loadWeights(
190190
this.weightManifest, "https://example.org/model");
191191
```
192192

193+
`weightMap` maps a weight name to a tensor. You can use it to manually implement the forward pass of the model:
194+
195+
```js
196+
const input = tf.tensor(...);
197+
tf.matMul(weightMap['fc1/weights'], input).add(weightMap['fc1/bias']);
198+
```
199+
193200
## FAQ
194201

195202
1. What TensorFlow models does the converter currently support?
@@ -221,6 +228,20 @@ tensorflowjs_converter \
221228

222229
The time of first call also includes the compilation time of WebGL shader programs for the model. After the first call the shader programs are cached, which makes the subsequent calls much faster. You can warm up the cache by calling the predict method with an all zero inputs, right after the completion of the model loading.
223230

231+
6. I have a model converted with a previous version of TensorFlow.js converter (0.15.x), that is in .pb format. How do I convert it to the new JSON format?
232+
233+
You can use the built-in migration tool to convert the models generated by previous versions. Here are the steps:
234+
235+
```
236+
git clone git@github.com:tensorflow/tfjs-converter.git
237+
cd tfjs-converter
238+
yarn
239+
yarn ts-node tools/pb2json_converter.ts pb_model_directory/ json_model_directory/
240+
```
241+
242+
`pb_model_directory` is the directory where the model generated by previous version is located.
243+
`json_model_directory` is the destination directory for the converted model.
244+
224245
## Development
225246

226247
To build **TensorFlow.js converter** from source, we need to clone the project and prepare
@@ -234,7 +255,7 @@ $ yarn # Installs dependencies.
234255

235256
We recommend using [Visual Studio Code](https://code.visualstudio.com/) for
236257
development. Make sure to install
237-
[TSLint VSCode extension](https://marketplace.visualstudio.com/items?itemName=eg2.tslint)
258+
[TSLint VSCode extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-typescript-tslint-plugin)
238259
and the npm [clang-format](https://github.com/angular/clang-format) `1.2.2` or later
239260
with the
240261
[Clang-Format VSCode extension](https://marketplace.visualstudio.com/items?itemName=xaver.clang-format)

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tensorflow/tfjs-converter",
3-
"version": "1.0.0",
3+
"version": "1.0.2",
44
"description": "Tensorflow model converter for javascript",
55
"main": "dist/src/index.js",
66
"jsnext:main": "dist/tf-converter.esm.js",
@@ -14,10 +14,10 @@
1414
},
1515
"license": "Apache-2.0",
1616
"peerDependencies": {
17-
"@tensorflow/tfjs-core": "1.0.0"
17+
"@tensorflow/tfjs-core": "1.0.2"
1818
},
1919
"devDependencies": {
20-
"@tensorflow/tfjs-core": "1.0.0",
20+
"@tensorflow/tfjs-core": "1.0.2",
2121
"@types/jasmine": "~2.8.6",
2222
"@types/long": "~3.0.32",
2323
"@types/node-fetch": "1.6.9",

python/tensorflowjs/converters/BUILD

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ py_test(
5353
],
5454
)
5555

56+
py_test(
5657
name = "tf_saved_model_conversion_v2_test",
5758
srcs = ["tf_saved_model_conversion_v2_test.py"],
5859
srcs_version = "PY2AND3",
@@ -68,10 +69,11 @@ py_library(
6869
data = ["//tensorflowjs:op_list_jsons"],
6970
srcs_version = "PY2AND3",
7071
deps = [
71-
":tf_saved_model_conversion",
7272
"//tensorflowjs:expect_numpy_installed",
7373
"//tensorflowjs:expect_tensorflow_installed",
74+
"//tensorflowjs:version",
7475
"//tensorflowjs:write_weights",
76+
"//tensorflowjs/converters:common",
7577
],
7678
)
7779

python/tensorflowjs/converters/keras_h5_conversion.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ def _check_version(h5file):
101101
raise ValueError(
102102
'Expected Keras version 2; got Keras version %s' % keras_version)
103103

104+
104105
def _initialize_output_dictionary(h5file):
105106
"""Prepopulate required fields for all data foramts.
106107
@@ -127,6 +128,36 @@ def _ensure_json_dict(item):
127128
return item if isinstance(item, dict) else json.loads(as_text(item))
128129

129130

131+
# https://github.com/tensorflow/tfjs/issues/1255, b/124791387
132+
# In tensorflow version 1.13 and some alpha and nightly-preview versions,
133+
# the following layers have different class names in their serialization.
134+
# This issue should be fixed in later releases. But we include the logic
135+
# to translate them anyway, for users who use those versions of tensorflow.
136+
_CLASS_NAME_MAP = {
137+
'BatchNormalizationV1': 'BatchNormalization',
138+
'UnifiedGRU': 'GRU',
139+
'UnifiedLSTM': 'LSTM'
140+
}
141+
142+
143+
def translate_class_names(input_object):
144+
"""Perform class name replacement.
145+
146+
Beware that this method modifies the input object in-place.
147+
"""
148+
if not isinstance(input_object, dict):
149+
return
150+
for key in input_object:
151+
value = input_object[key]
152+
if key == 'class_name' and value in _CLASS_NAME_MAP:
153+
input_object[key] = _CLASS_NAME_MAP[value]
154+
elif isinstance(value, dict):
155+
translate_class_names(value)
156+
elif isinstance(value, (tuple, list)):
157+
for item in value:
158+
translate_class_names(item)
159+
160+
130161
def h5_merged_saved_model_to_tfjs_format(h5file, split_by_layer=False):
131162
"""Load topology & weight values from HDF5 file and convert.
132163
@@ -156,6 +187,7 @@ def h5_merged_saved_model_to_tfjs_format(h5file, split_by_layer=False):
156187

157188
model_json['model_config'] = _ensure_json_dict(
158189
h5file.attrs['model_config'])
190+
translate_class_names(model_json['model_config'])
159191
if 'training_config' in h5file.attrs:
160192
model_json['training_config'] = _ensure_json_dict(
161193
h5file.attrs['training_config'])

python/tensorflowjs/converters/keras_h5_conversion_test.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,54 @@ def testSavedModelRaisesErrorIfArtifactsDirExistsAsAFile(self):
459459
ValueError, r'already exists as a file'):
460460
conversion.save_keras_model(model, artifacts_dir)
461461

462+
def testTranslateBatchNormalizationV1ClassName(self):
463+
# The config JSON of a model consisting of a "BatchNormalizationV1" layer.
464+
# pylint: disable=line-too-long
465+
json_object = json.loads(
466+
'{"class_name": "Sequential", "keras_version": "2.2.4-tf", "config": {"layers": [{"class_name": "Dense", "config": {"kernel_initializer": {"class_name": "GlorotUniform", "config": {"dtype": "float32", "seed": null}}, "name": "dense", "kernel_constraint": null, "bias_regularizer": null, "bias_constraint": null, "dtype": "float32", "activation": "relu", "trainable": true, "kernel_regularizer": null, "bias_initializer": {"class_name": "Zeros", "config": {"dtype": "float32"}}, "units": 10, "batch_input_shape": [null, 3], "use_bias": true, "activity_regularizer": null}}, {"class_name": "BatchNormalizationV1", "config": {"beta_constraint": null, "gamma_initializer": {"class_name": "Ones", "config": {"dtype": "float32"}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {"dtype": "float32"}}, "name": "batch_normalization_v1", "dtype": "float32", "trainable": true, "moving_variance_initializer": {"class_name": "Ones", "config": {"dtype": "float32"}}, "beta_initializer": {"class_name": "Zeros", "config": {"dtype": "float32"}}, "scale": true, "axis": [1], "epsilon": 0.001, "gamma_constraint": null, "gamma_regularizer": null, "beta_regularizer": null, "momentum": 0.99, "center": true}}, {"class_name": "Dense", "config": {"kernel_initializer": {"class_name": "GlorotUniform", "config": {"dtype": "float32", "seed": null}}, "name": "dense_1", "kernel_constraint": null, "bias_regularizer": null, "bias_constraint": null, "dtype": "float32", "activation": "linear", "trainable": true, "kernel_regularizer": null, "bias_initializer": {"class_name": "Zeros", "config": {"dtype": "float32"}}, "units": 1, "use_bias": true, "activity_regularizer": null}}], "name": "sequential"}, "backend": "tensorflow"}')
467+
# pylint: enable=line-too-long
468+
conversion.translate_class_names(json_object)
469+
# Some class names should not have been changed be translate_class_names().
470+
self.assertEqual(json_object['class_name'], 'Sequential')
471+
self.assertEqual(json_object['keras_version'], '2.2.4-tf')
472+
self.assertEqual(json_object['config']['layers'][0]['class_name'], 'Dense')
473+
# The translation should have happend:
474+
# BatchNormalizationV1 --> BatchNormalization.
475+
self.assertEqual(
476+
json_object['config']['layers'][1]['class_name'], 'BatchNormalization')
477+
self.assertEqual(json_object['config']['layers'][2]['class_name'], 'Dense')
478+
479+
# Assert that converted JSON can be reconstituted as a model object.
480+
model = keras.models.model_from_json(json.dumps(json_object))
481+
self.assertTrue(isinstance(model, keras.Sequential))
482+
self.assertEqual(model.input_shape, (None, 3))
483+
self.assertEqual(model.output_shape, (None, 1))
484+
self.assertEqual(model.layers[0].units, 10)
485+
self.assertEqual(model.layers[2].units, 1)
486+
487+
def testTranslateUnifiedGRUAndLSTMClassName(self):
488+
# The config JSON of a model consisting of a "UnifiedGRU" layer
489+
# and a "UnifiedLSTM" layer.
490+
# pylint: disable=line-too-long
491+
json_object = json.loads(
492+
'{"class_name": "Sequential", "keras_version": "2.2.4-tf", "config": {"layers": [{"class_name": "UnifiedGRU", "config": {"recurrent_activation": "sigmoid", "dtype": "float32", "trainable": true, "recurrent_initializer": {"class_name": "Orthogonal", "config": {"seed": null, "gain": 1.0}}, "use_bias": true, "bias_regularizer": null, "return_state": false, "unroll": false, "activation": "tanh", "bias_initializer": {"class_name": "Zeros", "config": {}}, "units": 10, "batch_input_shape": [null, 4, 3], "activity_regularizer": null, "recurrent_dropout": 0.0, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "kernel_constraint": null, "time_major": false, "dropout": 0.0, "stateful": false, "reset_after": true, "recurrent_regularizer": null, "name": "unified_gru", "bias_constraint": null, "go_backwards": false, "implementation": 1, "kernel_regularizer": null, "return_sequences": true, "recurrent_constraint": null}}, {"class_name": "UnifiedLSTM", "config": {"recurrent_activation": "sigmoid", "dtype": "float32", "trainable": true, "recurrent_initializer": {"class_name": "Orthogonal", "config": {"seed": null, "gain": 1.0}}, "use_bias": true, "bias_regularizer": null, "return_state": false, "unroll": false, "activation": "tanh", "bias_initializer": {"class_name": "Zeros", "config": {}}, "units": 2, "unit_forget_bias": true, "activity_regularizer": null, "recurrent_dropout": 0.0, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "kernel_constraint": null, "time_major": false, "dropout": 0.0, "stateful": false, "recurrent_regularizer": null, "name": "unified_lstm", "bias_constraint": null, "go_backwards": false, "implementation": 1, "kernel_regularizer": null, "return_sequences": false, "recurrent_constraint": null}}], "name": "sequential"}, "backend": "tensorflow"}')
493+
# pylint: enable=line-too-long
494+
conversion.translate_class_names(json_object)
495+
# Some class names should not have been changed be translate_class_names().
496+
self.assertEqual(json_object['class_name'], 'Sequential')
497+
self.assertEqual(json_object['keras_version'], '2.2.4-tf')
498+
# The translation should have happend:
499+
# UnifiedGRU --> GRU.
500+
# UnifiedLSTM --> LSTM.
501+
self.assertEqual(json_object['config']['layers'][0]['class_name'], 'GRU')
502+
self.assertEqual(json_object['config']['layers'][1]['class_name'], 'LSTM')
503+
504+
# Assert that converted JSON can be reconstituted as a model object.
505+
model = keras.models.model_from_json(json.dumps(json_object))
506+
self.assertTrue(isinstance(model, keras.Sequential))
507+
self.assertEqual(model.input_shape, (None, 4, 3))
508+
self.assertEqual(model.output_shape, (None, 2))
509+
462510

463511
if __name__ == '__main__':
464512
unittest.main()

python/tensorflowjs/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# @license See the LICENSE file.
22

33
# This code is auto-generated, do not modify this file!
4-
version = '1.0.0'
4+
version = '1.0.2'

src/version.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/** @license See the LICENSE file. */
22

33
// This code is auto-generated, do not modify this file!
4-
const version = '1.0.0';
4+
const version = '1.0.2';
55
export {version};

0 commit comments

Comments
 (0)