Skip to content

Commit 07102f2

Browse files
committed
add new doc for structured parameter
Signed-off-by: reiase <[email protected]>
1 parent 0acd212 commit 07102f2

File tree

2 files changed

+192
-0
lines changed

2 files changed

+192
-0
lines changed

docs/structured_parameter.md

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
Structured Parameter
2+
====================
3+
4+
The main idea of `HyperParameter` is to organize the parameters and parameter accesses into a tree structure, by which we can refer to each parameter with a unique name and identify each access to the parameters. Then we can modify each parameter and control the access to the parameters.
5+
6+
Nested Parameters and Parameter Path
7+
------------------------------------
8+
9+
The parameters are stored in a nested dict, a very common solution for python programs, and can be easily serialized into JSON or YAML format. For example:
10+
11+
```python
12+
cfg = {
13+
"param1": 1, # unique name: param1
14+
"obj2": {
15+
"param3": "value4", # unique name: obj2.param3
16+
"param5": 6, # unique name: obj2.param5
17+
},
18+
}
19+
```
20+
21+
We can directly refer to the value `value4` by `cfg["obj2"]["param3"]`. But we also need to check whether the parameter is missing from the cfg, and handle the default value.
22+
23+
`HyperParameter` offers a tiny DSL to access the parameters. The DSL syntax is very similar to `jsonpath`, but compatible with python syntax so that the interpreter and code editor can check for syntax errors. (I found this feature saves me a lot of time.). The first thing to use the DSL is converting the cfg into `HyperParameter`, and then we can use the DSL:
24+
25+
```python
26+
# convert a nested dict into HyperParameter
27+
hp = HyperParameter(**cfg)
28+
29+
# or create the HyperParameter object from scratch
30+
hp = HyperParameter(param1=1, obj2={"param3": "value4"})
31+
32+
# the DSL for access the parameter
33+
param = hp().obj2.param3(default="undefined")
34+
```
35+
36+
`hp().obj2.param3(default="undefined")` is the inline DSL for reading parameter from `HyperParameter` object. It looks like a `jsonpath` expression but has support for default values.
37+
38+
Best Practice for Structure Parameters with Parameter Path
39+
-----------------------------------------------
40+
41+
### A Quick Example of Recommendation Model
42+
43+
Suppose we are building a wide&deep model with `keras`.
44+
45+
```python
46+
class WideAndDeepModel(keras.Model):
47+
def __init__(self, units=30, activation="relu", **kwargs):
48+
super().__init__(**kwargs)
49+
self.hidden1 = keras.layers.Dense(units, activation=activation)
50+
self.hidden2 = keras.layers.Dense(units, activation=activation)
51+
self.main_output = keras.layers.Dense(1)
52+
self.aux_output = keras.layers.Dense(1)
53+
54+
def call(self, inputs):
55+
input_A , input_B = inputs
56+
hidden1 = self.hidden1(input_B)
57+
hidden2 = self.hidden2(hidden1)
58+
concat = keras.layers.concatenate([input_A, hidden2])
59+
main_output = self.main_output()(concat)
60+
aux_output = self.aux_output()(hidden2)
61+
return main_outputi, aux_output
62+
```
63+
64+
The model is straightforward and does not support many parameters. If we want to add batch normalization, dropout, and leaky-relu tricks to the model, we have to modify the code as follows:
65+
66+
```python
67+
class WideAndDeepModel(keras.Model):
68+
def __init__(self,
69+
units=[30, 30, 30],
70+
activation="relu",
71+
use_bn=False,
72+
bn_momentum=0.99,
73+
bn_epsilon=0.001,
74+
bn_center=True,
75+
bn_scale=True,
76+
bn_beta_initializer="zeros",
77+
bn_gamma_initializer="ones",
78+
bn_moving_mean_initializer="zeros",
79+
bn_moving_variance_initializer="ones",
80+
use_dropout=False,
81+
...):
82+
83+
...
84+
self.bn1 = keras.layers.BatchNormalization(
85+
momentum=bn_momentum,
86+
epsilon=bn_epsilon,
87+
center=bn_center,
88+
scale=bn_scale,
89+
beta_initializer=bn_beta_initializer,
90+
gamma_initializer=bn_gamma_initializer,
91+
...,
92+
)
93+
```
94+
95+
The code becomes too complicated, having dozens of parameters to handle, most of which are not used.
96+
97+
### A Fast Trial of Structured Parameter
98+
99+
We can simplify the code with `auto_param`, which automatically converts the parameters into a parameter tree. And then, we can specify the parameters by name:
100+
101+
```python
102+
# add parameter support for custom functions with a decorator
103+
@auto_param("myns.rec.rank.dropout")
104+
class dropout:
105+
def __init__(self, ratio=0.5):
106+
...
107+
108+
# add parameter support for library functions
109+
wrapped_bn = auto_param("myns.rec.rank.bn")(keras.layers.BatchNormalization)
110+
```
111+
112+
`myns.rec.rank` is the namespace for my project, and `myns.rec.rank.dropout` refers to the function defined in our code. We can refer to the keyword arguments (e.g. `ratio=0.5`) with the path `hp().myns.rec.rank.dropout`.
113+
114+
After making the building block configurable, we can simplify the model:
115+
```python
116+
class WideAndDeepModel(keras.Model):
117+
def __init__(self,
118+
units=[30, 30, 30],
119+
activation="relu",
120+
...):
121+
122+
...
123+
self.bn1 = wrapped_bn()
124+
self.dropout1 = dropout()
125+
```
126+
And we can change the parameters of the `BN` layers with `param_scope`:
127+
128+
```python
129+
with param_scope(**{
130+
"myns.rec.rank.dropout.ratio": 0.6,
131+
"myns.rec.rank.bn.center": False,
132+
...
133+
}):
134+
model = WideAndDeepModel()
135+
```
136+
137+
Or read the parameters from a JSON file:
138+
139+
```python
140+
with open("model.cfg.json") as f:
141+
cfg = json.load(f)
142+
with param_scope(**cfg):
143+
model = WideAndDeepModel()
144+
```
145+
146+
### Fine-grained Control of Structured Parameters
147+
148+
In the last section, we have introduced how to structure the parameters with `auto_param` and modify them with `param_scope` by their path.
149+
However, we may also need to access the same parameter in different places in our code, e.g., different layers in a DNN model.
150+
151+
In such situation, we can break our code into named scopes. And then, we can identify each access to the parameters and set a value for each access.
152+
153+
To add named scopes to our code, we can use `param_scope`:
154+
155+
```python
156+
class WideAndDeepModel(keras.Model):
157+
def __init__(self,
158+
units=[30, 30, 30],
159+
activation="relu",
160+
...):
161+
162+
...
163+
with param_scope["layer1"]():
164+
self.bn1 = wrapped_bn()
165+
self.dropout1 = dropout()
166+
with param_scope["layer2"]():
167+
self.bn2 = wrapped_bn()
168+
self.dropout2 = dropout()
169+
...
170+
171+
with param_scope["wdmodel"]():
172+
model = WideAndDeepModel()
173+
```
174+
175+
`param_scope["layer1"]` creates a named scope called `layer1`. Since the scope is created inside another named scope `param_scope["wdmodel"]`, its full path should be `wdmodel.layer1`. We can specify different values of a parameter according to its path. For example:
176+
177+
```python
178+
with param_scope["wdmodel"](**{
179+
"myns.rec.rank.dropout.ratio@wdmodel#layer1": 0.6,
180+
"myns.rec.rank.dropout.ratio@wdmodel#layer2": 0.7,
181+
}):
182+
model = WideAndDeepModel()
183+
```
184+
185+
With the above code, we get a drop ratio of 0.6 for `layer1` and 0.7 for `layer2`.

mkdocs.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,18 @@ plugins:
2424
nav:
2525
- Home: index.md
2626
- Quick Start: quick_start.md
27+
- Best Practice: structured_parameter.md
2728
- Reference: reference.md
2829

2930
watch:
3031
- hyperparameter
3132

33+
markdown_extensions:
34+
- pymdownx.highlight:
35+
anchor_linenums: true
36+
use_pygments: true
37+
- pymdownx.superfences
38+
3239
extra:
3340
alternate:
3441
- name: English

0 commit comments

Comments
 (0)