1# Copyright 2016 The TensorFlow Authors. All Rights Reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14# ============================================================================== 15"""TensorFlow composable models used as building blocks for estimators (deprecated). 16 17This module and all its submodules are deprecated. See 18[contrib/learn/README.md](https://www.tensorflow.org/code/tensorflow/contrib/learn/README.md) 19for migration instructions. 20""" 21 22from __future__ import absolute_import 23from __future__ import division 24from __future__ import print_function 25 26import math 27import re 28 29import six 30 31from tensorflow.contrib import layers 32from tensorflow.contrib.framework import list_variables 33from tensorflow.contrib.framework import load_variable 34from tensorflow.contrib.layers.python.layers import feature_column_ops 35from tensorflow.python.framework import ops 36from tensorflow.python.ops import clip_ops 37from tensorflow.python.ops import gradients 38from tensorflow.python.ops import nn 39from tensorflow.python.ops import partitioned_variables 40from tensorflow.python.ops import variable_scope 41from tensorflow.python.summary import summary 42from tensorflow.python.util.deprecation import deprecated 43 44 45class _ComposableModel(object): 46 """ABC for building blocks that can be used to create estimators. 47 48 Subclasses need to implement the following methods: 49 - build_model 50 - _get_optimizer 51 See below for the required signatures. 52 _ComposableModel and its subclasses are not part of the public tf.learn API. 53 """ 54 55 @deprecated(None, "Please use model_fns in tf.estimator.") 56 def __init__(self, 57 num_label_columns, 58 optimizer, 59 gradient_clip_norm, 60 num_ps_replicas, 61 scope, 62 trainable=True): 63 """Common initialization for all _ComposableModel objects. 64 65 Args: 66 num_label_columns: The number of label columns. 67 optimizer: An instance of `tf.Optimizer` used to apply gradients to 68 the model. If `None`, will use a FTRL optimizer. 69 gradient_clip_norm: A float > 0. If provided, gradients are clipped 70 to their global norm with this clipping ratio. See 71 tf.clip_by_global_norm for more details. 72 num_ps_replicas: The number of parameter server replicas. 73 scope: Scope for variables created in this model. 74 trainable: True if this model contains variables that can be trained. 75 False otherwise (in cases where the variables are used strictly for 76 transforming input labels for training). 77 """ 78 self._num_label_columns = num_label_columns 79 self._optimizer = optimizer 80 self._gradient_clip_norm = gradient_clip_norm 81 self._num_ps_replicas = num_ps_replicas 82 self._scope = scope 83 self._trainable = trainable 84 self._feature_columns = None 85 86 def get_scope_name(self): 87 """Returns the scope name used by this model for variables.""" 88 return self._scope 89 90 def build_model(self, features, feature_columns, is_training): 91 """Builds the model that can calculate the logits. 92 93 Args: 94 features: A mapping from feature columns to tensors. 95 feature_columns: An iterable containing all the feature columns used 96 by the model. All items in the set should be instances of 97 classes derived from `FeatureColumn`. 98 is_training: Set to True when training, False otherwise. 99 100 Returns: 101 The logits for this model. 102 """ 103 raise NotImplementedError 104 105 def get_train_step(self, loss): 106 """Returns the ops to run to perform a training step on this estimator. 107 108 Args: 109 loss: The loss to use when calculating gradients. 110 111 Returns: 112 The ops to run to perform a training step. 113 """ 114 my_vars = self._get_vars() 115 if not (self._get_feature_columns() or my_vars): 116 return [] 117 118 grads = gradients.gradients(loss, my_vars) 119 if self._gradient_clip_norm: 120 grads, _ = clip_ops.clip_by_global_norm(grads, self._gradient_clip_norm) 121 return [self._get_optimizer().apply_gradients(zip(grads, my_vars))] 122 123 def _get_feature_columns(self): 124 if not self._feature_columns: 125 return None 126 feature_column_ops.check_feature_columns(self._feature_columns) 127 return sorted(set(self._feature_columns), key=lambda x: x.key) 128 129 def _get_vars(self): 130 if self._get_feature_columns(): 131 return ops.get_collection(self._scope) 132 return [] 133 134 def _get_optimizer(self): 135 if (self._optimizer is None or isinstance(self._optimizer, 136 six.string_types)): 137 optimizer = self._get_default_optimizer(self._optimizer) 138 elif callable(self._optimizer): 139 optimizer = self._optimizer() 140 else: 141 optimizer = self._optimizer 142 return optimizer 143 144 def _get_default_optimizer(self, optimizer_name=None): 145 raise NotImplementedError 146 147 148class LinearComposableModel(_ComposableModel): 149 """A _ComposableModel that implements linear regression. 150 151 THIS CLASS IS DEPRECATED. See 152 [contrib/learn/README.md](https://www.tensorflow.org/code/tensorflow/contrib/learn/README.md) 153 for general migration instructions. 154 155 Instances of this class can be used to build estimators through the use 156 of composition. 157 """ 158 159 def __init__(self, 160 num_label_columns, 161 optimizer=None, 162 _joint_weights=False, 163 gradient_clip_norm=None, 164 num_ps_replicas=0, 165 scope=None, 166 trainable=True): 167 """Initializes LinearComposableModel objects. 168 169 Args: 170 num_label_columns: The number of label columns. 171 optimizer: An instance of `tf.Optimizer` used to apply gradients to 172 the model. If `None`, will use a FTRL optimizer. 173 _joint_weights: If True use a single (possibly partitioned) variable 174 to store all weights in this model. Faster, but requires that all 175 feature columns are sparse and have the 'sum' combiner. 176 gradient_clip_norm: A float > 0. If provided, gradients are clipped 177 to their global norm with this clipping ratio. See 178 tf.clip_by_global_norm for more details. 179 num_ps_replicas: The number of parameter server replicas. 180 scope: Optional scope for variables created in this model. If scope 181 is not supplied, it will default to 'linear'. 182 trainable: True if this model contains variables that can be trained. 183 False otherwise (in cases where the variables are used strictly for 184 transforming input labels for training). 185 """ 186 scope = "linear" if not scope else scope 187 super(LinearComposableModel, self).__init__( 188 num_label_columns=num_label_columns, 189 optimizer=optimizer, 190 gradient_clip_norm=gradient_clip_norm, 191 num_ps_replicas=num_ps_replicas, 192 scope=scope, 193 trainable=trainable) 194 self._joint_weights = _joint_weights 195 196 def get_weights(self, model_dir): 197 """Returns weights per feature of the linear part. 198 199 Args: 200 model_dir: Directory where model parameters, graph and etc. are saved. 201 202 Returns: 203 The weights created by this model (without the optimizer weights). 204 """ 205 all_variables = [name for name, _ in list_variables(model_dir)] 206 values = {} 207 optimizer_regex = r".*/" + self._get_optimizer().get_name() + r"(_\d)?$" 208 for name in all_variables: 209 if (name.startswith(self._scope + "/") and 210 name != self._scope + "/bias_weight" and 211 not re.match(optimizer_regex, name)): 212 values[name] = load_variable(model_dir, name) 213 if len(values) == 1: 214 return values[list(values.keys())[0]] 215 return values 216 217 def get_bias(self, model_dir): 218 """Returns bias of the model. 219 220 Args: 221 model_dir: Directory where model parameters, graph and etc. are saved. 222 223 Returns: 224 The bias weights created by this model. 225 """ 226 return load_variable(model_dir, name=(self._scope + "/bias_weight")) 227 228 def build_model(self, features, feature_columns, is_training): 229 """See base class.""" 230 self._feature_columns = feature_columns 231 partitioner = partitioned_variables.min_max_variable_partitioner( 232 max_partitions=self._num_ps_replicas, min_slice_size=64 << 20) 233 with variable_scope.variable_scope( 234 self._scope, values=features.values(), 235 partitioner=partitioner) as scope: 236 if self._joint_weights: 237 logits, _, _ = layers.joint_weighted_sum_from_feature_columns( 238 columns_to_tensors=features, 239 feature_columns=self._get_feature_columns(), 240 num_outputs=self._num_label_columns, 241 weight_collections=[self._scope], 242 trainable=self._trainable, 243 scope=scope) 244 else: 245 logits, _, _ = layers.weighted_sum_from_feature_columns( 246 columns_to_tensors=features, 247 feature_columns=self._get_feature_columns(), 248 num_outputs=self._num_label_columns, 249 weight_collections=[self._scope], 250 trainable=self._trainable, 251 scope=scope) 252 return logits 253 254 def _get_default_optimizer(self, optimizer_name=None): 255 if optimizer_name is None: 256 optimizer_name = "Ftrl" 257 default_learning_rate = 1. / math.sqrt(len(self._get_feature_columns())) 258 default_learning_rate = min(0.2, default_learning_rate) 259 return layers.OPTIMIZER_CLS_NAMES[optimizer_name]( 260 learning_rate=default_learning_rate) 261 262 263class DNNComposableModel(_ComposableModel): 264 """A _ComposableModel that implements a DNN. 265 266 THIS CLASS IS DEPRECATED. See 267 [contrib/learn/README.md](https://www.tensorflow.org/code/tensorflow/contrib/learn/README.md) 268 for general migration instructions. 269 270 Instances of this class can be used to build estimators through the use 271 of composition. 272 """ 273 274 def __init__(self, 275 num_label_columns, 276 hidden_units, 277 optimizer=None, 278 activation_fn=nn.relu, 279 dropout=None, 280 gradient_clip_norm=None, 281 num_ps_replicas=0, 282 scope=None, 283 trainable=True): 284 """Initializes DNNComposableModel objects. 285 286 Args: 287 num_label_columns: The number of label columns. 288 hidden_units: List of hidden units per layer. All layers are fully 289 connected. 290 optimizer: An instance of `tf.Optimizer` used to apply gradients to 291 the model. If `None`, will use a FTRL optimizer. 292 activation_fn: Activation function applied to each layer. If `None`, 293 will use `tf.nn.relu`. 294 dropout: When not None, the probability we will drop out 295 a given coordinate. 296 gradient_clip_norm: A float > 0. If provided, gradients are clipped 297 to their global norm with this clipping ratio. See 298 tf.clip_by_global_norm for more details. 299 num_ps_replicas: The number of parameter server replicas. 300 scope: Optional scope for variables created in this model. If not scope 301 is supplied, one is generated. 302 trainable: True if this model contains variables that can be trained. 303 False otherwise (in cases where the variables are used strictly for 304 transforming input labels for training). 305 """ 306 scope = "dnn" if not scope else scope 307 super(DNNComposableModel, self).__init__( 308 num_label_columns=num_label_columns, 309 optimizer=optimizer, 310 gradient_clip_norm=gradient_clip_norm, 311 num_ps_replicas=num_ps_replicas, 312 scope=scope, 313 trainable=trainable) 314 self._hidden_units = hidden_units 315 self._activation_fn = activation_fn 316 self._dropout = dropout 317 318 def get_weights(self, model_dir): 319 """Returns the weights of the model. 320 321 Args: 322 model_dir: Directory where model parameters, graph and etc. are saved. 323 324 Returns: 325 The weights created by this model. 326 """ 327 return [ 328 load_variable( 329 model_dir, name=(self._scope + "/hiddenlayer_%d/weights" % i)) 330 for i, _ in enumerate(self._hidden_units) 331 ] + [load_variable( 332 model_dir, name=(self._scope + "/logits/weights"))] 333 334 def get_bias(self, model_dir): 335 """Returns the bias of the model. 336 337 Args: 338 model_dir: Directory where model parameters, graph and etc. are saved. 339 340 Returns: 341 The bias weights created by this model. 342 """ 343 return [ 344 load_variable( 345 model_dir, name=(self._scope + "/hiddenlayer_%d/biases" % i)) 346 for i, _ in enumerate(self._hidden_units) 347 ] + [load_variable( 348 model_dir, name=(self._scope + "/logits/biases"))] 349 350 def _add_hidden_layer_summary(self, value, tag): 351 # TODO(zakaria): Move this code to tf.learn and add test. 352 summary.scalar("%s/fraction_of_zero_values" % tag, nn.zero_fraction(value)) 353 summary.histogram("%s/activation" % tag, value) 354 355 def build_model(self, features, feature_columns, is_training): 356 """See base class.""" 357 self._feature_columns = feature_columns 358 359 input_layer_partitioner = ( 360 partitioned_variables.min_max_variable_partitioner( 361 max_partitions=self._num_ps_replicas, min_slice_size=64 << 20)) 362 with variable_scope.variable_scope( 363 self._scope + "/input_from_feature_columns", 364 values=features.values(), 365 partitioner=input_layer_partitioner) as scope: 366 net = layers.input_from_feature_columns( 367 features, 368 self._get_feature_columns(), 369 weight_collections=[self._scope], 370 trainable=self._trainable, 371 scope=scope) 372 373 hidden_layer_partitioner = ( 374 partitioned_variables.min_max_variable_partitioner( 375 max_partitions=self._num_ps_replicas)) 376 for layer_id, num_hidden_units in enumerate(self._hidden_units): 377 with variable_scope.variable_scope( 378 self._scope + "/hiddenlayer_%d" % layer_id, 379 values=[net], 380 partitioner=hidden_layer_partitioner) as scope: 381 net = layers.fully_connected( 382 net, 383 num_hidden_units, 384 activation_fn=self._activation_fn, 385 variables_collections=[self._scope], 386 trainable=self._trainable, 387 scope=scope) 388 if self._dropout is not None and is_training: 389 net = layers.dropout(net, keep_prob=(1.0 - self._dropout)) 390 self._add_hidden_layer_summary(net, scope.name) 391 392 with variable_scope.variable_scope( 393 self._scope + "/logits", 394 values=[net], 395 partitioner=hidden_layer_partitioner) as scope: 396 logits = layers.fully_connected( 397 net, 398 self._num_label_columns, 399 activation_fn=None, 400 variables_collections=[self._scope], 401 trainable=self._trainable, 402 scope=scope) 403 self._add_hidden_layer_summary(logits, "logits") 404 return logits 405 406 def _get_default_optimizer(self, optimizer_name=None): 407 if optimizer_name is None: 408 optimizer_name = "Adagrad" 409 return layers.OPTIMIZER_CLS_NAMES[optimizer_name](learning_rate=0.05) 410