• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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