-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
heads.py
270 lines (233 loc) · 9.34 KB
/
heads.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# Copyright 2020 The AutoKeras Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Optional
import keras
import tensorflow as tf
import tree
from keras import activations
from keras import layers
from keras import losses
from autokeras import adapters
from autokeras import analysers
from autokeras import hyper_preprocessors as hpps_module
from autokeras import preprocessors
from autokeras.blocks import reduction
from autokeras.engine import head as head_module
from autokeras.utils import types
from autokeras.utils import utils
class ClassificationHead(head_module.Head):
"""Classification Dense layers.
Use sigmoid and binary crossentropy for binary classification and
multi-label classification. Use softmax and categorical crossentropy for
multi-class (more than 2) classification. Use Accuracy as metrics by
default.
The targets passing to the head would have to be tf.data.Dataset,
np.ndarray, pd.DataFrame or pd.Series. It can be raw labels, one-hot encoded
if more than two classes, or binary encoded for binary classification.
The raw labels will be encoded to one column if two classes were found,
or one-hot encoded if more than two classes were found.
# Arguments
num_classes: Int. Defaults to None. If None, it will be inferred from
the data.
multi_label: Boolean. Defaults to False.
loss: A Keras loss function. Defaults to use `binary_crossentropy` or
`categorical_crossentropy` based on the number of classes.
metrics: A list of Keras metrics. Defaults to use 'accuracy'.
dropout: Float. The dropout rate for the layers.
If left unspecified, it will be tuned automatically.
"""
def __init__(
self,
num_classes: Optional[int] = None,
multi_label: bool = False,
loss: Optional[types.LossType] = None,
metrics: Optional[types.MetricsType] = None,
dropout: Optional[float] = None,
**kwargs
):
self.num_classes = num_classes
self.multi_label = multi_label
self.dropout = dropout
if metrics is None:
metrics = ["accuracy"]
if loss is None:
loss = self.infer_loss()
super().__init__(loss=loss, metrics=metrics, **kwargs)
# Infered from analyser.
self._encoded = None
self._encoded_for_sigmoid = None
self._encoded_for_softmax = None
self._add_one_dimension = False
self._labels = None
def infer_loss(self):
if not self.num_classes:
return None
if self.num_classes == 2 or self.multi_label:
return losses.BinaryCrossentropy()
return losses.CategoricalCrossentropy()
def get_config(self):
config = super().get_config()
config.update(
{
"num_classes": self.num_classes,
"multi_label": self.multi_label,
"dropout": self.dropout,
}
)
return config
def build(self, hp, inputs=None):
inputs = tree.flatten(inputs)
utils.validate_num_inputs(inputs, 1)
input_node = inputs[0]
output_node = input_node
# Reduce the tensor to a vector.
if len(output_node.shape) > 2:
output_node = reduction.SpatialReduction().build(hp, output_node)
if self.dropout is not None:
dropout = self.dropout
else:
dropout = hp.Choice("dropout", [0.0, 0.25, 0.5], default=0)
if dropout > 0:
output_node = layers.Dropout(dropout)(output_node)
output_node = layers.Dense(self.shape[-1])(output_node)
if isinstance(self.loss, keras.losses.BinaryCrossentropy):
output_node = layers.Activation(
activations.sigmoid, name=self.name
)(output_node)
else:
output_node = layers.Softmax(name=self.name)(output_node)
return output_node
def get_adapter(self):
return adapters.ClassificationAdapter(name=self.name)
def get_analyser(self):
return analysers.ClassificationAnalyser(
name=self.name, multi_label=self.multi_label
)
def config_from_analyser(self, analyser):
super().config_from_analyser(analyser)
self.num_classes = analyser.num_classes
self.loss = self.infer_loss()
self._encoded = analyser.encoded
self._encoded_for_sigmoid = analyser.encoded_for_sigmoid
self._encoded_for_softmax = analyser.encoded_for_softmax
self._add_one_dimension = len(analyser.shape) == 1
self._labels = analyser.labels
def get_hyper_preprocessors(self):
hyper_preprocessors = []
if self._add_one_dimension:
hyper_preprocessors.append(
hpps_module.DefaultHyperPreprocessor(
preprocessors.AddOneDimension()
)
)
if self.dtype in [tf.uint8, tf.uint16, tf.uint32, tf.uint64]:
hyper_preprocessors.append(
hpps_module.DefaultHyperPreprocessor(
preprocessors.CastToInt32()
)
)
if not self._encoded and self.dtype != tf.string:
hyper_preprocessors.append(
hpps_module.DefaultHyperPreprocessor(
preprocessors.CastToString()
)
)
if self._encoded_for_sigmoid:
hyper_preprocessors.append(
hpps_module.DefaultHyperPreprocessor(
preprocessors.SigmoidPostprocessor()
)
)
elif self._encoded_for_softmax:
hyper_preprocessors.append(
hpps_module.DefaultHyperPreprocessor(
preprocessors.SoftmaxPostprocessor()
)
)
elif self.num_classes == 2:
hyper_preprocessors.append(
hpps_module.DefaultHyperPreprocessor(
preprocessors.LabelEncoder(self._labels)
)
)
else:
hyper_preprocessors.append(
hpps_module.DefaultHyperPreprocessor(
preprocessors.OneHotEncoder(self._labels)
)
)
return hyper_preprocessors
class RegressionHead(head_module.Head):
"""Regression Dense layers.
The targets passing to the head would have to be tf.data.Dataset,
np.ndarray, pd.DataFrame or pd.Series. It can be single-column or
multi-column. The values should all be numerical.
# Arguments
output_dim: Int. The number of output dimensions. Defaults to None.
If None, it will be inferred from the data.
multi_label: Boolean. Defaults to False.
loss: A Keras loss function. Defaults to use `mean_squared_error`.
metrics: A list of Keras metrics. Defaults to use `mean_squared_error`.
dropout: Float. The dropout rate for the layers.
If left unspecified, it will be tuned automatically.
"""
def __init__(
self,
output_dim: Optional[int] = None,
loss: types.LossType = "mean_squared_error",
metrics: Optional[types.MetricsType] = None,
dropout: Optional[float] = None,
**kwargs
):
if metrics is None:
metrics = ["mean_squared_error"]
super().__init__(loss=loss, metrics=metrics, **kwargs)
self.output_dim = output_dim
self.dropout = dropout
def get_config(self):
config = super().get_config()
config.update({"output_dim": self.output_dim, "dropout": self.dropout})
return config
def build(self, hp, inputs=None):
inputs = tree.flatten(inputs)
utils.validate_num_inputs(inputs, 1)
input_node = inputs[0]
output_node = input_node
if self.dropout is not None:
dropout = self.dropout
else:
dropout = hp.Choice("dropout", [0.0, 0.25, 0.5], default=0)
if dropout > 0:
output_node = layers.Dropout(dropout)(output_node)
output_node = reduction.Flatten().build(hp, output_node)
output_node = layers.Dense(self.shape[-1], name=self.name)(output_node)
return output_node
def config_from_analyser(self, analyser):
super().config_from_analyser(analyser)
self._add_one_dimension = len(analyser.shape) == 1
def get_adapter(self):
return adapters.RegressionAdapter(name=self.name)
def get_analyser(self):
return analysers.RegressionAnalyser(
name=self.name, output_dim=self.output_dim
)
def get_hyper_preprocessors(self):
hyper_preprocessors = []
if self._add_one_dimension:
hyper_preprocessors.append( # pragma: no cover
hpps_module.DefaultHyperPreprocessor(
preprocessors.AddOneDimension()
)
)
return hyper_preprocessors