Skip to content

Commit 43b209d

Browse files
authored
Merge pull request #27 from Yoctol/freeze_graph
we can freeze graph before saving
2 parents e7794a6 + a35afc3 commit 43b209d

9 files changed

Lines changed: 300 additions & 99 deletions

File tree

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ before_install:
1313
- sudo apt-get install pandoc -y
1414
install:
1515
- pip install pipenv==2018.10.13
16-
- make install
16+
- make install-dev
1717
- pipenv run pip install tensorflow
1818
script:
1919
- pipenv run make lint

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
.PHONY: install
44
install:
5+
pipenv install
6+
7+
.PHONY: install-dev
8+
install-dev:
59
pipenv install --dev
610

711
.PHONY: lint
@@ -21,7 +25,7 @@ testall:
2125
docker-compose rm -f
2226

2327
.PHONY: all
24-
all: install testall lint
28+
all: install-dev lint testall
2529

2630
.PHONY: clean
2731
clean:

README.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,19 @@
1313

1414
Some python utilities when using tensorflow-serving.
1515

16+
1617
## Installation
1718

19+
Prepare an environment with python version >= 3.6
20+
1821
From PYPI:
1922
1. Manually install tensorflow CPU or GPU version.
2023
2. `pip install serving-utils`
2124

2225
From Github repository:
2326
1. `git clone git@github.com:Yoctol/serving-utils.git`
24-
2. `make install`
27+
2. Manually install tensorflow CPU or GPU version.
28+
3. `make install`
2529

2630

2731
## Usage
@@ -41,6 +45,7 @@ saver = Saver(
4145
outputs={'output': tf.Tensor...},
4246
)
4347
},
48+
freeze=True, # (default: True) Frozen graph will be saved if True.
4449
)
4550
saver.save(...)
4651
```
@@ -60,6 +65,15 @@ client.predict(
6065
await client.async_predict(...)
6166
```
6267

68+
3. Freeze graph
69+
```python
70+
from serving_utils.freeze_graph import freeze_graph, create_session_from_graphdef
71+
72+
frozen_graph_def = freeze_graph(session, output_op_names)
73+
new_session = create_session_from_graphdef(frozen_graph_def)
74+
```
75+
76+
6377
## Test
6478

6579
Run the following commands:
@@ -68,8 +82,9 @@ make lint
6882
make test
6983
```
7084

85+
7186
## Dev
7287

7388
```
74-
make install
89+
make install-dev
7590
```

serving_utils/freeze_graph.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import tensorflow as tf
2+
from tensorflow.python.framework import graph_util
3+
4+
5+
def freeze_graph(session, output_op_names):
6+
"""Freeze graph
7+
8+
Freeze graph accroding to input session and
9+
output operation names
10+
11+
ref: https://github.com/tensorflow/tensorflow/blob/master/tensorflow/
12+
python/tools/freeze_graph.py
13+
14+
Args:
15+
session: an instance of tf.Session
16+
output_op_names (strs): a list of operation names
17+
18+
Return:
19+
a tf.GraphDef object
20+
21+
"""
22+
operations_in_graph(graph_def=session.graph_def, op_names=output_op_names)
23+
frozen_graph_def = graph_util.convert_variables_to_constants(
24+
sess=session,
25+
input_graph_def=session.graph_def,
26+
output_node_names=output_op_names, # node name == op name
27+
)
28+
return frozen_graph_def
29+
30+
31+
def operations_in_graph(graph_def, op_names):
32+
"""
33+
Check whether all operations are in graph or not
34+
35+
Args:
36+
graph_def (tf.GraphDef): a tf.GraphDef object
37+
op_names (strs): a list of operation names
38+
39+
Return:
40+
KeyError or None
41+
42+
"""
43+
all_node_names = [node.name for node in graph_def.node]
44+
for op_name in op_names:
45+
if op_name not in all_node_names:
46+
raise KeyError(f"{op_name} is not in this graph.")
47+
48+
49+
def create_session_from_graphdef(graph_def):
50+
"""
51+
Create new session from given tf.GraphDef object
52+
53+
Arg:
54+
graph_def (tf.GraphDef): a tf.GraphDef object
55+
56+
Return:
57+
session (tf.Session): a new session with given graph_def
58+
59+
"""
60+
new_sess = tf.Session(graph=tf.Graph())
61+
with new_sess.graph.as_default():
62+
tf.import_graph_def(graph_def, name="")
63+
return new_sess

serving_utils/saver.py

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from typing import Dict
22
import pathlib
3-
43
import tensorflow as tf
54

5+
from .freeze_graph import freeze_graph, create_session_from_graphdef
6+
67

78
class Saver:
89

@@ -11,35 +12,71 @@ def __init__(
1112
session: tf.Session,
1213
output_dir: str,
1314
signature_def_map: Dict[str, tf.saved_model.signature_def_utils.predict_signature_def],
15+
freeze: bool = True,
1416
) -> None:
1517
self.session = session
1618
self.signature_def_map = signature_def_map
1719
self.output_dir = pathlib.Path(output_dir)
1820
self.output_dir.mkdir(parents=True, exist_ok=True)
21+
self.freeze = freeze
1922

20-
@staticmethod
21-
def _get_next_version(
22-
path: pathlib.PosixPath,
23-
) -> pathlib.PosixPath:
24-
candidate_paths = path.glob('**/*')
25-
dirs = [x.name for x in candidate_paths if x.is_dir()]
26-
versions = [int(dir_name) for dir_name in dirs if dir_name.isdigit()]
27-
new_version = max(versions) + 1 if versions else 0
28-
return path / str(new_version)
23+
def save(self, **kwargs) -> str:
24+
"""Save model given session and signature
25+
26+
Save frozen model if self.freeze is True.
27+
28+
Return:
29+
output_version_dir
30+
31+
"""
32+
if self.freeze:
33+
output_op_names = self._get_op_names()
34+
frozen_graphdef = freeze_graph(self.session, output_op_names)
35+
session_to_be_saved = create_session_from_graphdef(frozen_graphdef)
36+
else:
37+
session_to_be_saved = self.session
2938

30-
def save(
31-
self,
32-
**kwargs
33-
) -> str:
3439
output_version_dir = str(self._get_next_version(self.output_dir))
35-
with self.session.graph.as_default():
40+
with session_to_be_saved.graph.as_default():
3641
builder = tf.saved_model.builder.SavedModelBuilder(
3742
export_dir=output_version_dir,
3843
)
3944
builder.add_meta_graph_and_variables(
40-
sess=self.session,
45+
sess=session_to_be_saved,
4146
tags=[tf.saved_model.tag_constants.SERVING],
4247
signature_def_map=self.signature_def_map,
4348
)
4449
builder.save()
50+
51+
if self.freeze:
52+
session_to_be_saved.close()
53+
4554
return output_version_dir
55+
56+
@staticmethod
57+
def _get_next_version(path: pathlib.PosixPath) -> pathlib.PosixPath:
58+
candidate_paths = path.glob('**/*')
59+
dirs = [x.name for x in candidate_paths if x.is_dir()]
60+
versions = [int(dir_name) for dir_name in dirs if dir_name.isdigit()]
61+
new_version = max(versions) + 1 if versions else 0
62+
return path / str(new_version)
63+
64+
def _get_op_names(self):
65+
"""
66+
Extract output operation names from signature
67+
68+
Return:
69+
output_op_names (strs): a list of operation names
70+
for output recorded in signature.
71+
72+
"""
73+
output_op_names = []
74+
for signature_value in self.signature_def_map.values():
75+
for _, tensor_info in signature_value.outputs.items():
76+
tensor_name = tensor_info.name
77+
if ':' in tensor_name:
78+
op_name = tensor_name.split(':')[0]
79+
else:
80+
op_name = tensor_name
81+
output_op_names.append(op_name)
82+
return output_op_names

serving_utils/tests/mock_model.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,9 @@ def fit(self, x, y, epochs):
6464
self.y_place: y,
6565
},
6666
)
67-
self.save_tf_serve()
6867

69-
def save_tf_serve(self):
70-
pass
68+
def evaluate(self, x, y):
69+
return self.sess.run(self.loss.name, feed_dict={self.x_place.name: x, self.y_place.name: y})
7170

7271
def define_signature(self):
7372
signature_def_map = {

serving_utils/tests/test_client.py

Lines changed: 0 additions & 2 deletions
This file was deleted.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import pytest
2+
import numpy as np
3+
4+
from ..freeze_graph import freeze_graph, create_session_from_graphdef
5+
from .mock_model import MockModel
6+
7+
8+
@pytest.fixture
9+
def x_test():
10+
return np.random.rand(5, 10).astype('float32')
11+
12+
13+
@pytest.fixture
14+
def y_test(x_test):
15+
batch_size, dims = x_test.shape
16+
return np.random.rand(batch_size, dims).astype('float32')
17+
18+
19+
@pytest.fixture
20+
def model(x_test):
21+
return MockModel(maxlen=x_test.shape[1])
22+
23+
24+
def test_create_session_from_graphdef(model):
25+
old_graph_def = model.sess.graph_def
26+
new_sess = create_session_from_graphdef(model.sess.graph_def)
27+
assert old_graph_def == new_sess.graph_def
28+
29+
30+
def test_freeze_graph_will_not_change_loss(x_test, y_test, model):
31+
old_loss = model.evaluate(x_test, y_test)
32+
graph_def = freeze_graph(model.sess, [model.loss.op.name])
33+
34+
# model get new sess created from frozen graph def
35+
new_sess = create_session_from_graphdef(graph_def)
36+
model.sess = new_sess
37+
new_loss = model.evaluate(x_test, y_test)
38+
39+
assert old_loss == new_loss
40+
41+
42+
def test_op_not_in_graph(model):
43+
with pytest.raises(KeyError):
44+
freeze_graph(model.sess, ['not_in_graph'])

0 commit comments

Comments
 (0)