Home | History | Annotate | Download | only in canned
      1 # Copyright 2017 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 """Utils for testing linear estimators."""
     16 
     17 from __future__ import absolute_import
     18 from __future__ import division
     19 from __future__ import print_function
     20 
     21 import math
     22 import os
     23 import shutil
     24 import tempfile
     25 
     26 import numpy as np
     27 import six
     28 
     29 from tensorflow.core.example import example_pb2
     30 from tensorflow.core.example import feature_pb2
     31 from tensorflow.python.client import session as tf_session
     32 from tensorflow.python.estimator import estimator
     33 from tensorflow.python.estimator import run_config
     34 from tensorflow.python.estimator import warm_starting_util
     35 from tensorflow.python.estimator.canned import linear
     36 from tensorflow.python.estimator.canned import metric_keys
     37 from tensorflow.python.estimator.export import export
     38 from tensorflow.python.estimator.inputs import numpy_io
     39 from tensorflow.python.estimator.inputs import pandas_io
     40 from tensorflow.python.feature_column import feature_column as feature_column_lib
     41 from tensorflow.python.framework import dtypes
     42 from tensorflow.python.framework import ops
     43 from tensorflow.python.framework import sparse_tensor
     44 from tensorflow.python.ops import check_ops
     45 from tensorflow.python.ops import control_flow_ops
     46 from tensorflow.python.ops import data_flow_ops
     47 from tensorflow.python.ops import init_ops
     48 from tensorflow.python.ops import math_ops
     49 from tensorflow.python.ops import parsing_ops
     50 from tensorflow.python.ops import partitioned_variables
     51 from tensorflow.python.ops import state_ops
     52 from tensorflow.python.ops import variable_scope
     53 from tensorflow.python.ops import variables as variables_lib
     54 from tensorflow.python.platform import gfile
     55 from tensorflow.python.platform import test
     56 from tensorflow.python.summary.writer import writer_cache
     57 from tensorflow.python.training import checkpoint_utils
     58 from tensorflow.python.training import gradient_descent
     59 from tensorflow.python.training import input as input_lib
     60 from tensorflow.python.training import optimizer as optimizer_lib
     61 from tensorflow.python.training import queue_runner
     62 from tensorflow.python.training import saver
     63 from tensorflow.python.training import session_run_hook
     64 
     65 try:
     66   # pylint: disable=g-import-not-at-top
     67   import pandas as pd
     68   HAS_PANDAS = True
     69 except IOError:
     70   # Pandas writes a temporary file during import. If it fails, don't use pandas.
     71   HAS_PANDAS = False
     72 except ImportError:
     73   HAS_PANDAS = False
     74 
     75 # pylint rules which are disabled by default for test files.
     76 # pylint: disable=invalid-name,protected-access,missing-docstring
     77 
     78 # Names of variables created by model.
     79 AGE_WEIGHT_NAME = 'linear/linear_model/age/weights'
     80 HEIGHT_WEIGHT_NAME = 'linear/linear_model/height/weights'
     81 OCCUPATION_WEIGHT_NAME = 'linear/linear_model/occupation/weights'
     82 BIAS_NAME = 'linear/linear_model/bias_weights'
     83 LANGUAGE_WEIGHT_NAME = 'linear/linear_model/language/weights'
     84 
     85 
     86 def assert_close(expected, actual, rtol=1e-04, name='assert_close'):
     87   with ops.name_scope(name, 'assert_close', (expected, actual, rtol)) as scope:
     88     expected = ops.convert_to_tensor(expected, name='expected')
     89     actual = ops.convert_to_tensor(actual, name='actual')
     90     rdiff = math_ops.abs(expected - actual, 'diff') / math_ops.abs(expected)
     91     rtol = ops.convert_to_tensor(rtol, name='rtol')
     92     return check_ops.assert_less(
     93         rdiff,
     94         rtol,
     95         data=('Condition expected =~ actual did not hold element-wise:'
     96               'expected = ', expected, 'actual = ', actual, 'rdiff = ', rdiff,
     97               'rtol = ', rtol,),
     98         name=scope)
     99 
    100 
    101 def save_variables_to_ckpt(model_dir):
    102   init_all_op = [variables_lib.global_variables_initializer()]
    103   with tf_session.Session() as sess:
    104     sess.run(init_all_op)
    105     saver.Saver().save(sess, os.path.join(model_dir, 'model.ckpt'))
    106 
    107 
    108 def queue_parsed_features(feature_map):
    109   tensors_to_enqueue = []
    110   keys = []
    111   for key, tensor in six.iteritems(feature_map):
    112     keys.append(key)
    113     tensors_to_enqueue.append(tensor)
    114   queue_dtypes = [x.dtype for x in tensors_to_enqueue]
    115   input_queue = data_flow_ops.FIFOQueue(capacity=100, dtypes=queue_dtypes)
    116   queue_runner.add_queue_runner(
    117       queue_runner.QueueRunner(input_queue,
    118                                [input_queue.enqueue(tensors_to_enqueue)]))
    119   dequeued_tensors = input_queue.dequeue()
    120   return {keys[i]: dequeued_tensors[i] for i in range(len(dequeued_tensors))}
    121 
    122 
    123 def sorted_key_dict(unsorted_dict):
    124   return {k: unsorted_dict[k] for k in sorted(unsorted_dict)}
    125 
    126 
    127 def sigmoid(x):
    128   return 1 / (1 + np.exp(-1.0 * x))
    129 
    130 
    131 class CheckPartitionerVarHook(session_run_hook.SessionRunHook):
    132   """A `SessionRunHook` to check a partitioned variable."""
    133 
    134   def __init__(self, test_case, var_name, var_dim, partitions):
    135     self._test_case = test_case
    136     self._var_name = var_name
    137     self._var_dim = var_dim
    138     self._partitions = partitions
    139 
    140   def begin(self):
    141     with variable_scope.variable_scope(
    142         variable_scope.get_variable_scope()) as scope:
    143       scope.reuse_variables()
    144       partitioned_weight = variable_scope.get_variable(
    145           self._var_name, shape=(self._var_dim, 1))
    146       self._test_case.assertTrue(
    147           isinstance(partitioned_weight, variables_lib.PartitionedVariable))
    148       for part in partitioned_weight:
    149         self._test_case.assertEqual(self._var_dim // self._partitions,
    150                                     part.get_shape()[0])
    151 
    152 
    153 class BaseLinearRegressorPartitionerTest(object):
    154 
    155   def __init__(self, linear_regressor_fn):
    156     self._linear_regressor_fn = linear_regressor_fn
    157 
    158   def setUp(self):
    159     self._model_dir = tempfile.mkdtemp()
    160 
    161   def tearDown(self):
    162     if self._model_dir:
    163       writer_cache.FileWriterCache.clear()
    164       shutil.rmtree(self._model_dir)
    165 
    166   def testPartitioner(self):
    167     x_dim = 64
    168     partitions = 4
    169 
    170     def _partitioner(shape, dtype):
    171       del dtype  # unused; required by Fn signature.
    172       # Only partition the embedding tensor.
    173       return [partitions, 1] if shape[0] == x_dim else [1]
    174 
    175     regressor = self._linear_regressor_fn(
    176         feature_columns=(feature_column_lib.categorical_column_with_hash_bucket(
    177             'language', hash_bucket_size=x_dim),),
    178         partitioner=_partitioner,
    179         model_dir=self._model_dir)
    180 
    181     def _input_fn():
    182       return {
    183           'language':
    184               sparse_tensor.SparseTensor(
    185                   values=['english', 'spanish'],
    186                   indices=[[0, 0], [0, 1]],
    187                   dense_shape=[1, 2])
    188       }, [[10.]]
    189 
    190     hook = CheckPartitionerVarHook(self, LANGUAGE_WEIGHT_NAME, x_dim,
    191                                    partitions)
    192     regressor.train(input_fn=_input_fn, steps=1, hooks=[hook])
    193 
    194   def testDefaultPartitionerWithMultiplePsReplicas(self):
    195     partitions = 2
    196     # This results in weights larger than the default partition size of 64M,
    197     # so partitioned weights are created (each weight uses 4 bytes).
    198     x_dim = 32 << 20
    199 
    200     class FakeRunConfig(run_config.RunConfig):
    201 
    202       @property
    203       def num_ps_replicas(self):
    204         return partitions
    205 
    206     # Mock the device setter as ps is not available on test machines.
    207     with test.mock.patch.object(
    208         estimator,
    209         '_get_replica_device_setter',
    210         return_value=lambda _: '/cpu:0'):
    211       linear_regressor = self._linear_regressor_fn(
    212           feature_columns=(
    213               feature_column_lib.categorical_column_with_hash_bucket(
    214                   'language', hash_bucket_size=x_dim),),
    215           config=FakeRunConfig(),
    216           model_dir=self._model_dir)
    217 
    218       def _input_fn():
    219         return {
    220             'language':
    221                 sparse_tensor.SparseTensor(
    222                     values=['english', 'spanish'],
    223                     indices=[[0, 0], [0, 1]],
    224                     dense_shape=[1, 2])
    225         }, [[10.]]
    226 
    227       hook = CheckPartitionerVarHook(self, LANGUAGE_WEIGHT_NAME, x_dim,
    228                                      partitions)
    229       linear_regressor.train(input_fn=_input_fn, steps=1, hooks=[hook])
    230 
    231 
    232 # TODO(b/36813849): Add tests with dynamic shape inputs using placeholders.
    233 class BaseLinearRegressorEvaluationTest(object):
    234 
    235   def __init__(self, linear_regressor_fn):
    236     self._linear_regressor_fn = linear_regressor_fn
    237 
    238   def setUp(self):
    239     self._model_dir = tempfile.mkdtemp()
    240 
    241   def tearDown(self):
    242     if self._model_dir:
    243       writer_cache.FileWriterCache.clear()
    244       shutil.rmtree(self._model_dir)
    245 
    246   def test_evaluation_for_simple_data(self):
    247     with ops.Graph().as_default():
    248       variables_lib.Variable([[11.0]], name=AGE_WEIGHT_NAME)
    249       variables_lib.Variable([2.0], name=BIAS_NAME)
    250       variables_lib.Variable(
    251           100, name=ops.GraphKeys.GLOBAL_STEP, dtype=dtypes.int64)
    252       save_variables_to_ckpt(self._model_dir)
    253 
    254     linear_regressor = self._linear_regressor_fn(
    255         feature_columns=(feature_column_lib.numeric_column('age'),),
    256         model_dir=self._model_dir)
    257     eval_metrics = linear_regressor.evaluate(
    258         input_fn=lambda: ({'age': ((1,),)}, ((10.,),)), steps=1)
    259 
    260     # Logit is (1. * 11.0 + 2.0) = 13, while label is 10. Loss is 3**2 = 9.
    261     self.assertDictEqual({
    262         metric_keys.MetricKeys.LOSS: 9.,
    263         metric_keys.MetricKeys.LOSS_MEAN: 9.,
    264         ops.GraphKeys.GLOBAL_STEP: 100
    265     }, eval_metrics)
    266 
    267   def test_evaluation_batch(self):
    268     """Tests evaluation for batch_size==2."""
    269     with ops.Graph().as_default():
    270       variables_lib.Variable([[11.0]], name=AGE_WEIGHT_NAME)
    271       variables_lib.Variable([2.0], name=BIAS_NAME)
    272       variables_lib.Variable(
    273           100, name=ops.GraphKeys.GLOBAL_STEP, dtype=dtypes.int64)
    274       save_variables_to_ckpt(self._model_dir)
    275 
    276     linear_regressor = self._linear_regressor_fn(
    277         feature_columns=(feature_column_lib.numeric_column('age'),),
    278         model_dir=self._model_dir)
    279     eval_metrics = linear_regressor.evaluate(
    280         input_fn=lambda: ({'age': ((1,), (1,))}, ((10.,), (10.,))), steps=1)
    281 
    282     # Logit is (1. * 11.0 + 2.0) = 13, while label is 10.
    283     # Loss per example is 3**2 = 9.
    284     # Training loss is the sum over batch = 9 + 9 = 18
    285     # Average loss is the average over batch = 9
    286     self.assertDictEqual({
    287         metric_keys.MetricKeys.LOSS: 18.,
    288         metric_keys.MetricKeys.LOSS_MEAN: 9.,
    289         ops.GraphKeys.GLOBAL_STEP: 100
    290     }, eval_metrics)
    291 
    292   def test_evaluation_weights(self):
    293     """Tests evaluation with weights."""
    294     with ops.Graph().as_default():
    295       variables_lib.Variable([[11.0]], name=AGE_WEIGHT_NAME)
    296       variables_lib.Variable([2.0], name=BIAS_NAME)
    297       variables_lib.Variable(
    298           100, name=ops.GraphKeys.GLOBAL_STEP, dtype=dtypes.int64)
    299       save_variables_to_ckpt(self._model_dir)
    300 
    301     def _input_fn():
    302       features = {'age': ((1,), (1,)), 'weights': ((1.,), (2.,))}
    303       labels = ((10.,), (10.,))
    304       return features, labels
    305 
    306     linear_regressor = self._linear_regressor_fn(
    307         feature_columns=(feature_column_lib.numeric_column('age'),),
    308         weight_column='weights',
    309         model_dir=self._model_dir)
    310     eval_metrics = linear_regressor.evaluate(input_fn=_input_fn, steps=1)
    311 
    312     # Logit is (1. * 11.0 + 2.0) = 13, while label is 10.
    313     # Loss per example is 3**2 = 9.
    314     # Training loss is the weighted sum over batch = 9 + 2*9 = 27
    315     # average loss is the weighted average = 9 + 2*9 / (1 + 2) = 9
    316     self.assertDictEqual({
    317         metric_keys.MetricKeys.LOSS: 27.,
    318         metric_keys.MetricKeys.LOSS_MEAN: 9.,
    319         ops.GraphKeys.GLOBAL_STEP: 100
    320     }, eval_metrics)
    321 
    322   def test_evaluation_for_multi_dimensions(self):
    323     x_dim = 3
    324     label_dim = 2
    325     with ops.Graph().as_default():
    326       variables_lib.Variable(
    327           [[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]], name=AGE_WEIGHT_NAME)
    328       variables_lib.Variable([7.0, 8.0], name=BIAS_NAME)
    329       variables_lib.Variable(100, name='global_step', dtype=dtypes.int64)
    330       save_variables_to_ckpt(self._model_dir)
    331 
    332     linear_regressor = self._linear_regressor_fn(
    333         feature_columns=(feature_column_lib.numeric_column(
    334             'age', shape=(x_dim,)),),
    335         label_dimension=label_dim,
    336         model_dir=self._model_dir)
    337     input_fn = numpy_io.numpy_input_fn(
    338         x={
    339             'age': np.array([[2., 4., 5.]]),
    340         },
    341         y=np.array([[46., 58.]]),
    342         batch_size=1,
    343         num_epochs=None,
    344         shuffle=False)
    345     eval_metrics = linear_regressor.evaluate(input_fn=input_fn, steps=1)
    346 
    347     self.assertItemsEqual(
    348         (metric_keys.MetricKeys.LOSS, metric_keys.MetricKeys.LOSS_MEAN,
    349          ops.GraphKeys.GLOBAL_STEP), eval_metrics.keys())
    350 
    351     # Logit is
    352     #   [2., 4., 5.] * [1.0, 2.0] + [7.0, 8.0] = [39, 50] + [7.0, 8.0]
    353     #                  [3.0, 4.0]
    354     #                  [5.0, 6.0]
    355     # which is [46, 58]
    356     self.assertAlmostEqual(0, eval_metrics[metric_keys.MetricKeys.LOSS])
    357 
    358   def test_evaluation_for_multiple_feature_columns(self):
    359     with ops.Graph().as_default():
    360       variables_lib.Variable([[10.0]], name=AGE_WEIGHT_NAME)
    361       variables_lib.Variable([[2.0]], name=HEIGHT_WEIGHT_NAME)
    362       variables_lib.Variable([5.0], name=BIAS_NAME)
    363       variables_lib.Variable(
    364           100, name=ops.GraphKeys.GLOBAL_STEP, dtype=dtypes.int64)
    365       save_variables_to_ckpt(self._model_dir)
    366 
    367     batch_size = 2
    368     feature_columns = [
    369         feature_column_lib.numeric_column('age'),
    370         feature_column_lib.numeric_column('height')
    371     ]
    372     input_fn = numpy_io.numpy_input_fn(
    373         x={'age': np.array([20, 40]),
    374            'height': np.array([4, 8])},
    375         y=np.array([[213.], [421.]]),
    376         batch_size=batch_size,
    377         num_epochs=None,
    378         shuffle=False)
    379 
    380     est = self._linear_regressor_fn(
    381         feature_columns=feature_columns, model_dir=self._model_dir)
    382 
    383     eval_metrics = est.evaluate(input_fn=input_fn, steps=1)
    384     self.assertItemsEqual(
    385         (metric_keys.MetricKeys.LOSS, metric_keys.MetricKeys.LOSS_MEAN,
    386          ops.GraphKeys.GLOBAL_STEP), eval_metrics.keys())
    387 
    388     # Logit is [(20. * 10.0 + 4 * 2.0 + 5.0), (40. * 10.0 + 8 * 2.0 + 5.0)] =
    389     # [213.0, 421.0], while label is [213., 421.]. Loss = 0.
    390     self.assertAlmostEqual(0, eval_metrics[metric_keys.MetricKeys.LOSS])
    391 
    392 
    393 class BaseLinearRegressorPredictTest(object):
    394 
    395   def __init__(self, linear_regressor_fn):
    396     self._linear_regressor_fn = linear_regressor_fn
    397 
    398   def setUp(self):
    399     self._model_dir = tempfile.mkdtemp()
    400 
    401   def tearDown(self):
    402     if self._model_dir:
    403       writer_cache.FileWriterCache.clear()
    404       shutil.rmtree(self._model_dir)
    405 
    406   def test_1d(self):
    407     """Tests predict when all variables are one-dimensional."""
    408     with ops.Graph().as_default():
    409       variables_lib.Variable([[10.]], name='linear/linear_model/x/weights')
    410       variables_lib.Variable([.2], name=BIAS_NAME)
    411       variables_lib.Variable(100, name='global_step', dtype=dtypes.int64)
    412       save_variables_to_ckpt(self._model_dir)
    413 
    414     linear_regressor = self._linear_regressor_fn(
    415         feature_columns=(feature_column_lib.numeric_column('x'),),
    416         model_dir=self._model_dir)
    417 
    418     predict_input_fn = numpy_io.numpy_input_fn(
    419         x={'x': np.array([[2.]])},
    420         y=None,
    421         batch_size=1,
    422         num_epochs=1,
    423         shuffle=False)
    424     predictions = linear_regressor.predict(input_fn=predict_input_fn)
    425     predicted_scores = list([x['predictions'] for x in predictions])
    426     # x * weight + bias = 2. * 10. + .2 = 20.2
    427     self.assertAllClose([[20.2]], predicted_scores)
    428 
    429   def testMultiDim(self):
    430     """Tests predict when all variables are multi-dimenstional."""
    431     batch_size = 2
    432     label_dimension = 3
    433     x_dim = 4
    434     feature_columns = (feature_column_lib.numeric_column('x', shape=(x_dim,)),)
    435     with ops.Graph().as_default():
    436       variables_lib.Variable(  # shape=[x_dim, label_dimension]
    437           [[1., 2., 3.], [2., 3., 4.], [3., 4., 5.], [4., 5., 6.]],
    438           name='linear/linear_model/x/weights')
    439       variables_lib.Variable(  # shape=[label_dimension]
    440           [.2, .4, .6], name=BIAS_NAME)
    441       variables_lib.Variable(100, name='global_step', dtype=dtypes.int64)
    442       save_variables_to_ckpt(self._model_dir)
    443 
    444     linear_regressor = self._linear_regressor_fn(
    445         feature_columns=feature_columns,
    446         label_dimension=label_dimension,
    447         model_dir=self._model_dir)
    448 
    449     predict_input_fn = numpy_io.numpy_input_fn(
    450         # x shape=[batch_size, x_dim]
    451         x={'x': np.array([[1., 2., 3., 4.], [5., 6., 7., 8.]])},
    452         y=None,
    453         batch_size=batch_size,
    454         num_epochs=1,
    455         shuffle=False)
    456     predictions = linear_regressor.predict(input_fn=predict_input_fn)
    457     predicted_scores = list([x['predictions'] for x in predictions])
    458     # score = x * weight + bias, shape=[batch_size, label_dimension]
    459     self.assertAllClose([[30.2, 40.4, 50.6], [70.2, 96.4, 122.6]],
    460                         predicted_scores)
    461 
    462   def testTwoFeatureColumns(self):
    463     """Tests predict with two feature columns."""
    464     with ops.Graph().as_default():
    465       variables_lib.Variable([[10.]], name='linear/linear_model/x0/weights')
    466       variables_lib.Variable([[20.]], name='linear/linear_model/x1/weights')
    467       variables_lib.Variable([.2], name=BIAS_NAME)
    468       variables_lib.Variable(100, name='global_step', dtype=dtypes.int64)
    469       save_variables_to_ckpt(self._model_dir)
    470 
    471     linear_regressor = self._linear_regressor_fn(
    472         feature_columns=(feature_column_lib.numeric_column('x0'),
    473                          feature_column_lib.numeric_column('x1')),
    474         model_dir=self._model_dir)
    475 
    476     predict_input_fn = numpy_io.numpy_input_fn(
    477         x={'x0': np.array([[2.]]),
    478            'x1': np.array([[3.]])},
    479         y=None,
    480         batch_size=1,
    481         num_epochs=1,
    482         shuffle=False)
    483     predictions = linear_regressor.predict(input_fn=predict_input_fn)
    484     predicted_scores = list([x['predictions'] for x in predictions])
    485     # x0 * weight0 + x1 * weight1 + bias = 2. * 10. + 3. * 20 + .2 = 80.2
    486     self.assertAllClose([[80.2]], predicted_scores)
    487 
    488 
    489 class BaseLinearRegressorIntegrationTest(object):
    490 
    491   def __init__(self, linear_regressor_fn):
    492     self._linear_regressor_fn = linear_regressor_fn
    493 
    494   def setUp(self):
    495     self._model_dir = tempfile.mkdtemp()
    496 
    497   def tearDown(self):
    498     if self._model_dir:
    499       writer_cache.FileWriterCache.clear()
    500       shutil.rmtree(self._model_dir)
    501 
    502   def _test_complete_flow(self, train_input_fn, eval_input_fn, predict_input_fn,
    503                           input_dimension, label_dimension, prediction_length):
    504     feature_columns = [
    505         feature_column_lib.numeric_column('x', shape=(input_dimension,))
    506     ]
    507     est = self._linear_regressor_fn(
    508         feature_columns=feature_columns,
    509         label_dimension=label_dimension,
    510         model_dir=self._model_dir)
    511 
    512     # TRAIN
    513     # learn y = x
    514     est.train(train_input_fn, steps=200)
    515 
    516     # EVALUTE
    517     scores = est.evaluate(eval_input_fn)
    518     self.assertEqual(200, scores[ops.GraphKeys.GLOBAL_STEP])
    519     self.assertIn(metric_keys.MetricKeys.LOSS, six.iterkeys(scores))
    520 
    521     # PREDICT
    522     predictions = np.array(
    523         [x['predictions'] for x in est.predict(predict_input_fn)])
    524     self.assertAllEqual((prediction_length, label_dimension), predictions.shape)
    525 
    526     # EXPORT
    527     feature_spec = feature_column_lib.make_parse_example_spec(feature_columns)
    528     serving_input_receiver_fn = export.build_parsing_serving_input_receiver_fn(
    529         feature_spec)
    530     export_dir = est.export_savedmodel(tempfile.mkdtemp(),
    531                                        serving_input_receiver_fn)
    532     self.assertTrue(gfile.Exists(export_dir))
    533 
    534   def test_numpy_input_fn(self):
    535     """Tests complete flow with numpy_input_fn."""
    536     label_dimension = 2
    537     input_dimension = label_dimension
    538     batch_size = 10
    539     prediction_length = batch_size
    540     data = np.linspace(0., 2., batch_size * label_dimension, dtype=np.float32)
    541     data = data.reshape(batch_size, label_dimension)
    542 
    543     train_input_fn = numpy_io.numpy_input_fn(
    544         x={'x': data},
    545         y=data,
    546         batch_size=batch_size,
    547         num_epochs=None,
    548         shuffle=True)
    549     eval_input_fn = numpy_io.numpy_input_fn(
    550         x={'x': data},
    551         y=data,
    552         batch_size=batch_size,
    553         num_epochs=1,
    554         shuffle=False)
    555     predict_input_fn = numpy_io.numpy_input_fn(
    556         x={'x': data},
    557         y=None,
    558         batch_size=batch_size,
    559         num_epochs=1,
    560         shuffle=False)
    561 
    562     self._test_complete_flow(
    563         train_input_fn=train_input_fn,
    564         eval_input_fn=eval_input_fn,
    565         predict_input_fn=predict_input_fn,
    566         input_dimension=input_dimension,
    567         label_dimension=label_dimension,
    568         prediction_length=prediction_length)
    569 
    570   def test_pandas_input_fn(self):
    571     """Tests complete flow with pandas_input_fn."""
    572     if not HAS_PANDAS:
    573       return
    574 
    575     # Pandas DataFrame natually supports 1 dim data only.
    576     label_dimension = 1
    577     input_dimension = label_dimension
    578     batch_size = 10
    579     data = np.array([1., 2., 3., 4.], dtype=np.float32)
    580     x = pd.DataFrame({'x': data})
    581     y = pd.Series(data)
    582     prediction_length = 4
    583 
    584     train_input_fn = pandas_io.pandas_input_fn(
    585         x=x, y=y, batch_size=batch_size, num_epochs=None, shuffle=True)
    586     eval_input_fn = pandas_io.pandas_input_fn(
    587         x=x, y=y, batch_size=batch_size, shuffle=False)
    588     predict_input_fn = pandas_io.pandas_input_fn(
    589         x=x, batch_size=batch_size, shuffle=False)
    590 
    591     self._test_complete_flow(
    592         train_input_fn=train_input_fn,
    593         eval_input_fn=eval_input_fn,
    594         predict_input_fn=predict_input_fn,
    595         input_dimension=input_dimension,
    596         label_dimension=label_dimension,
    597         prediction_length=prediction_length)
    598 
    599   def test_input_fn_from_parse_example(self):
    600     """Tests complete flow with input_fn constructed from parse_example."""
    601     label_dimension = 2
    602     input_dimension = label_dimension
    603     batch_size = 10
    604     prediction_length = batch_size
    605     data = np.linspace(0., 2., batch_size * label_dimension, dtype=np.float32)
    606     data = data.reshape(batch_size, label_dimension)
    607 
    608     serialized_examples = []
    609     for datum in data:
    610       example = example_pb2.Example(features=feature_pb2.Features(
    611           feature={
    612               'x':
    613                   feature_pb2.Feature(float_list=feature_pb2.FloatList(
    614                       value=datum)),
    615               'y':
    616                   feature_pb2.Feature(float_list=feature_pb2.FloatList(
    617                       value=datum[:label_dimension])),
    618           }))
    619       serialized_examples.append(example.SerializeToString())
    620 
    621     feature_spec = {
    622         'x': parsing_ops.FixedLenFeature([input_dimension], dtypes.float32),
    623         'y': parsing_ops.FixedLenFeature([label_dimension], dtypes.float32),
    624     }
    625 
    626     def _train_input_fn():
    627       feature_map = parsing_ops.parse_example(serialized_examples, feature_spec)
    628       features = queue_parsed_features(feature_map)
    629       labels = features.pop('y')
    630       return features, labels
    631 
    632     def _eval_input_fn():
    633       feature_map = parsing_ops.parse_example(
    634           input_lib.limit_epochs(serialized_examples, num_epochs=1),
    635           feature_spec)
    636       features = queue_parsed_features(feature_map)
    637       labels = features.pop('y')
    638       return features, labels
    639 
    640     def _predict_input_fn():
    641       feature_map = parsing_ops.parse_example(
    642           input_lib.limit_epochs(serialized_examples, num_epochs=1),
    643           feature_spec)
    644       features = queue_parsed_features(feature_map)
    645       features.pop('y')
    646       return features, None
    647 
    648     self._test_complete_flow(
    649         train_input_fn=_train_input_fn,
    650         eval_input_fn=_eval_input_fn,
    651         predict_input_fn=_predict_input_fn,
    652         input_dimension=input_dimension,
    653         label_dimension=label_dimension,
    654         prediction_length=prediction_length)
    655 
    656 
    657 class BaseLinearRegressorTrainingTest(object):
    658 
    659   def __init__(self, linear_regressor_fn):
    660     self._linear_regressor_fn = linear_regressor_fn
    661 
    662   def setUp(self):
    663     self._model_dir = tempfile.mkdtemp()
    664 
    665   def tearDown(self):
    666     if self._model_dir:
    667       writer_cache.FileWriterCache.clear()
    668       shutil.rmtree(self._model_dir)
    669 
    670   def _mock_optimizer(self, expected_loss=None):
    671     expected_var_names = [
    672         '%s/part_0:0' % AGE_WEIGHT_NAME,
    673         '%s/part_0:0' % BIAS_NAME
    674     ]
    675 
    676     def _minimize(loss, global_step=None, var_list=None):
    677       trainable_vars = var_list or ops.get_collection(
    678           ops.GraphKeys.TRAINABLE_VARIABLES)
    679       self.assertItemsEqual(expected_var_names,
    680                             [var.name for var in trainable_vars])
    681 
    682       # Verify loss. We can't check the value directly, so we add an assert op.
    683       self.assertEquals(0, loss.shape.ndims)
    684       if expected_loss is None:
    685         if global_step is not None:
    686           return state_ops.assign_add(global_step, 1).op
    687         return control_flow_ops.no_op()
    688       assert_loss = assert_close(
    689           math_ops.to_float(expected_loss, name='expected'),
    690           loss,
    691           name='assert_loss')
    692       with ops.control_dependencies((assert_loss,)):
    693         if global_step is not None:
    694           return state_ops.assign_add(global_step, 1).op
    695         return control_flow_ops.no_op()
    696 
    697     mock_optimizer = test.mock.NonCallableMock(
    698         spec=optimizer_lib.Optimizer,
    699         wraps=optimizer_lib.Optimizer(use_locking=False, name='my_optimizer'))
    700     mock_optimizer.minimize = test.mock.MagicMock(wraps=_minimize)
    701 
    702     # NOTE: Estimator.params performs a deepcopy, which wreaks havoc with mocks.
    703     # So, return mock_optimizer itself for deepcopy.
    704     mock_optimizer.__deepcopy__ = lambda _: mock_optimizer
    705     return mock_optimizer
    706 
    707   def _assert_checkpoint(self,
    708                          expected_global_step,
    709                          expected_age_weight=None,
    710                          expected_bias=None):
    711     shapes = {
    712         name: shape
    713         for (name, shape) in checkpoint_utils.list_variables(self._model_dir)
    714     }
    715 
    716     self.assertEqual([], shapes[ops.GraphKeys.GLOBAL_STEP])
    717     self.assertEqual(expected_global_step,
    718                      checkpoint_utils.load_variable(self._model_dir,
    719                                                     ops.GraphKeys.GLOBAL_STEP))
    720 
    721     self.assertEqual([1, 1], shapes[AGE_WEIGHT_NAME])
    722     if expected_age_weight is not None:
    723       self.assertEqual(expected_age_weight,
    724                        checkpoint_utils.load_variable(self._model_dir,
    725                                                       AGE_WEIGHT_NAME))
    726 
    727     self.assertEqual([1], shapes[BIAS_NAME])
    728     if expected_bias is not None:
    729       self.assertEqual(expected_bias,
    730                        checkpoint_utils.load_variable(self._model_dir,
    731                                                       BIAS_NAME))
    732 
    733   def testFromScratchWithDefaultOptimizer(self):
    734     # Create LinearRegressor.
    735     label = 5.
    736     age = 17
    737     linear_regressor = self._linear_regressor_fn(
    738         feature_columns=(feature_column_lib.numeric_column('age'),),
    739         model_dir=self._model_dir)
    740 
    741     # Train for a few steps, and validate final checkpoint.
    742     num_steps = 10
    743     linear_regressor.train(
    744         input_fn=lambda: ({'age': ((age,),)}, ((label,),)), steps=num_steps)
    745     self._assert_checkpoint(num_steps)
    746 
    747   def testTrainWithOneDimLabel(self):
    748     label_dimension = 1
    749     batch_size = 20
    750     feature_columns = [feature_column_lib.numeric_column('age', shape=(1,))]
    751     est = self._linear_regressor_fn(
    752         feature_columns=feature_columns,
    753         label_dimension=label_dimension,
    754         model_dir=self._model_dir)
    755     data_rank_1 = np.linspace(0., 2., batch_size, dtype=np.float32)
    756     self.assertEqual((batch_size,), data_rank_1.shape)
    757 
    758     train_input_fn = numpy_io.numpy_input_fn(
    759         x={'age': data_rank_1},
    760         y=data_rank_1,
    761         batch_size=batch_size,
    762         num_epochs=None,
    763         shuffle=True)
    764     est.train(train_input_fn, steps=200)
    765     self._assert_checkpoint(200)
    766 
    767   def testTrainWithOneDimWeight(self):
    768     label_dimension = 1
    769     batch_size = 20
    770     feature_columns = [feature_column_lib.numeric_column('age', shape=(1,))]
    771     est = self._linear_regressor_fn(
    772         feature_columns=feature_columns,
    773         label_dimension=label_dimension,
    774         weight_column='w',
    775         model_dir=self._model_dir)
    776 
    777     data_rank_1 = np.linspace(0., 2., batch_size, dtype=np.float32)
    778     self.assertEqual((batch_size,), data_rank_1.shape)
    779 
    780     train_input_fn = numpy_io.numpy_input_fn(
    781         x={'age': data_rank_1,
    782            'w': data_rank_1},
    783         y=data_rank_1,
    784         batch_size=batch_size,
    785         num_epochs=None,
    786         shuffle=True)
    787     est.train(train_input_fn, steps=200)
    788     self._assert_checkpoint(200)
    789 
    790   def testFromScratch(self):
    791     # Create LinearRegressor.
    792     label = 5.
    793     age = 17
    794     # loss = (logits - label)^2 = (0 - 5.)^2 = 25.
    795     mock_optimizer = self._mock_optimizer(expected_loss=25.)
    796     linear_regressor = self._linear_regressor_fn(
    797         feature_columns=(feature_column_lib.numeric_column('age'),),
    798         model_dir=self._model_dir,
    799         optimizer=mock_optimizer)
    800     self.assertEqual(0, mock_optimizer.minimize.call_count)
    801 
    802     # Train for a few steps, and validate optimizer and final checkpoint.
    803     num_steps = 10
    804     linear_regressor.train(
    805         input_fn=lambda: ({'age': ((age,),)}, ((label,),)), steps=num_steps)
    806     self.assertEqual(1, mock_optimizer.minimize.call_count)
    807     self._assert_checkpoint(
    808         expected_global_step=num_steps,
    809         expected_age_weight=0.,
    810         expected_bias=0.)
    811 
    812   def testFromCheckpoint(self):
    813     # Create initial checkpoint.
    814     age_weight = 10.0
    815     bias = 5.0
    816     initial_global_step = 100
    817     with ops.Graph().as_default():
    818       variables_lib.Variable([[age_weight]], name=AGE_WEIGHT_NAME)
    819       variables_lib.Variable([bias], name=BIAS_NAME)
    820       variables_lib.Variable(
    821           initial_global_step,
    822           name=ops.GraphKeys.GLOBAL_STEP,
    823           dtype=dtypes.int64)
    824       save_variables_to_ckpt(self._model_dir)
    825 
    826     # logits = age * age_weight + bias = 17 * 10. + 5. = 175
    827     # loss = (logits - label)^2 = (175 - 5)^2 = 28900
    828     mock_optimizer = self._mock_optimizer(expected_loss=28900.)
    829     linear_regressor = self._linear_regressor_fn(
    830         feature_columns=(feature_column_lib.numeric_column('age'),),
    831         model_dir=self._model_dir,
    832         optimizer=mock_optimizer)
    833     self.assertEqual(0, mock_optimizer.minimize.call_count)
    834 
    835     # Train for a few steps, and validate optimizer and final checkpoint.
    836     num_steps = 10
    837     linear_regressor.train(
    838         input_fn=lambda: ({'age': ((17,),)}, ((5.,),)), steps=num_steps)
    839     self.assertEqual(1, mock_optimizer.minimize.call_count)
    840     self._assert_checkpoint(
    841         expected_global_step=initial_global_step + num_steps,
    842         expected_age_weight=age_weight,
    843         expected_bias=bias)
    844 
    845   def testFromCheckpointMultiBatch(self):
    846     # Create initial checkpoint.
    847     age_weight = 10.0
    848     bias = 5.0
    849     initial_global_step = 100
    850     with ops.Graph().as_default():
    851       variables_lib.Variable([[age_weight]], name=AGE_WEIGHT_NAME)
    852       variables_lib.Variable([bias], name=BIAS_NAME)
    853       variables_lib.Variable(
    854           initial_global_step,
    855           name=ops.GraphKeys.GLOBAL_STEP,
    856           dtype=dtypes.int64)
    857       save_variables_to_ckpt(self._model_dir)
    858 
    859     # logits = age * age_weight + bias
    860     # logits[0] = 17 * 10. + 5. = 175
    861     # logits[1] = 15 * 10. + 5. = 155
    862     # loss = sum(logits - label)^2 = (175 - 5)^2 + (155 - 3)^2 = 52004
    863     mock_optimizer = self._mock_optimizer(expected_loss=52004.)
    864     linear_regressor = self._linear_regressor_fn(
    865         feature_columns=(feature_column_lib.numeric_column('age'),),
    866         model_dir=self._model_dir,
    867         optimizer=mock_optimizer)
    868     self.assertEqual(0, mock_optimizer.minimize.call_count)
    869 
    870     # Train for a few steps, and validate optimizer and final checkpoint.
    871     num_steps = 10
    872     linear_regressor.train(
    873         input_fn=lambda: ({'age': ((17,), (15,))}, ((5.,), (3.,))),
    874         steps=num_steps)
    875     self.assertEqual(1, mock_optimizer.minimize.call_count)
    876     self._assert_checkpoint(
    877         expected_global_step=initial_global_step + num_steps,
    878         expected_age_weight=age_weight,
    879         expected_bias=bias)
    880 
    881 
    882 class BaseLinearClassifierTrainingTest(object):
    883 
    884   def __init__(self, linear_classifier_fn):
    885     self._linear_classifier_fn = linear_classifier_fn
    886 
    887   def setUp(self):
    888     self._model_dir = tempfile.mkdtemp()
    889 
    890   def tearDown(self):
    891     if self._model_dir:
    892       shutil.rmtree(self._model_dir)
    893 
    894   def _mock_optimizer(self, expected_loss=None):
    895     expected_var_names = [
    896         '%s/part_0:0' % AGE_WEIGHT_NAME,
    897         '%s/part_0:0' % BIAS_NAME
    898     ]
    899 
    900     def _minimize(loss, global_step):
    901       trainable_vars = ops.get_collection(ops.GraphKeys.TRAINABLE_VARIABLES)
    902       self.assertItemsEqual(
    903           expected_var_names,
    904           [var.name for var in trainable_vars])
    905 
    906       # Verify loss. We can't check the value directly, so we add an assert op.
    907       self.assertEquals(0, loss.shape.ndims)
    908       if expected_loss is None:
    909         return state_ops.assign_add(global_step, 1).op
    910       assert_loss = assert_close(
    911           math_ops.to_float(expected_loss, name='expected'),
    912           loss,
    913           name='assert_loss')
    914       with ops.control_dependencies((assert_loss,)):
    915         return state_ops.assign_add(global_step, 1).op
    916 
    917     mock_optimizer = test.mock.NonCallableMock(
    918         spec=optimizer_lib.Optimizer,
    919         wraps=optimizer_lib.Optimizer(use_locking=False, name='my_optimizer'))
    920     mock_optimizer.minimize = test.mock.MagicMock(wraps=_minimize)
    921 
    922     # NOTE: Estimator.params performs a deepcopy, which wreaks havoc with mocks.
    923     # So, return mock_optimizer itself for deepcopy.
    924     mock_optimizer.__deepcopy__ = lambda _: mock_optimizer
    925     return mock_optimizer
    926 
    927   def _assert_checkpoint(
    928       self, n_classes, expected_global_step, expected_age_weight=None,
    929       expected_bias=None):
    930     logits_dimension = n_classes if n_classes > 2 else 1
    931 
    932     shapes = {
    933         name: shape for (name, shape) in
    934         checkpoint_utils.list_variables(self._model_dir)
    935     }
    936 
    937     self.assertEqual([], shapes[ops.GraphKeys.GLOBAL_STEP])
    938     self.assertEqual(
    939         expected_global_step,
    940         checkpoint_utils.load_variable(
    941             self._model_dir, ops.GraphKeys.GLOBAL_STEP))
    942 
    943     self.assertEqual([1, logits_dimension],
    944                      shapes[AGE_WEIGHT_NAME])
    945     if expected_age_weight is not None:
    946       self.assertAllEqual(expected_age_weight,
    947                           checkpoint_utils.load_variable(
    948                               self._model_dir,
    949                               AGE_WEIGHT_NAME))
    950 
    951     self.assertEqual([logits_dimension], shapes[BIAS_NAME])
    952     if expected_bias is not None:
    953       self.assertAllEqual(expected_bias,
    954                           checkpoint_utils.load_variable(
    955                               self._model_dir, BIAS_NAME))
    956 
    957   def _testFromScratchWithDefaultOptimizer(self, n_classes):
    958     label = 0
    959     age = 17
    960     est = linear.LinearClassifier(
    961         feature_columns=(feature_column_lib.numeric_column('age'),),
    962         n_classes=n_classes,
    963         model_dir=self._model_dir)
    964 
    965     # Train for a few steps, and validate final checkpoint.
    966     num_steps = 10
    967     est.train(
    968         input_fn=lambda: ({'age': ((age,),)}, ((label,),)), steps=num_steps)
    969     self._assert_checkpoint(n_classes, num_steps)
    970 
    971   def testBinaryClassesFromScratchWithDefaultOptimizer(self):
    972     self._testFromScratchWithDefaultOptimizer(n_classes=2)
    973 
    974   def testMultiClassesFromScratchWithDefaultOptimizer(self):
    975     self._testFromScratchWithDefaultOptimizer(n_classes=4)
    976 
    977   def _testTrainWithTwoDimsLabel(self, n_classes):
    978     batch_size = 20
    979 
    980     est = linear.LinearClassifier(
    981         feature_columns=(feature_column_lib.numeric_column('age'),),
    982         n_classes=n_classes,
    983         model_dir=self._model_dir)
    984     data_rank_1 = np.array([0, 1])
    985     data_rank_2 = np.array([[0], [1]])
    986     self.assertEqual((2,), data_rank_1.shape)
    987     self.assertEqual((2, 1), data_rank_2.shape)
    988 
    989     train_input_fn = numpy_io.numpy_input_fn(
    990         x={'age': data_rank_1},
    991         y=data_rank_2,
    992         batch_size=batch_size,
    993         num_epochs=None,
    994         shuffle=True)
    995     est.train(train_input_fn, steps=200)
    996     self._assert_checkpoint(n_classes, 200)
    997 
    998   def testBinaryClassesTrainWithTwoDimsLabel(self):
    999     self._testTrainWithTwoDimsLabel(n_classes=2)
   1000 
   1001   def testMultiClassesTrainWithTwoDimsLabel(self):
   1002     self._testTrainWithTwoDimsLabel(n_classes=4)
   1003 
   1004   def _testTrainWithOneDimLabel(self, n_classes):
   1005     batch_size = 20
   1006 
   1007     est = linear.LinearClassifier(
   1008         feature_columns=(feature_column_lib.numeric_column('age'),),
   1009         n_classes=n_classes,
   1010         model_dir=self._model_dir)
   1011     data_rank_1 = np.array([0, 1])
   1012     self.assertEqual((2,), data_rank_1.shape)
   1013 
   1014     train_input_fn = numpy_io.numpy_input_fn(
   1015         x={'age': data_rank_1},
   1016         y=data_rank_1,
   1017         batch_size=batch_size,
   1018         num_epochs=None,
   1019         shuffle=True)
   1020     est.train(train_input_fn, steps=200)
   1021     self._assert_checkpoint(n_classes, 200)
   1022 
   1023   def testBinaryClassesTrainWithOneDimLabel(self):
   1024     self._testTrainWithOneDimLabel(n_classes=2)
   1025 
   1026   def testMultiClassesTrainWithOneDimLabel(self):
   1027     self._testTrainWithOneDimLabel(n_classes=4)
   1028 
   1029   def _testTrainWithTwoDimsWeight(self, n_classes):
   1030     batch_size = 20
   1031 
   1032     est = linear.LinearClassifier(
   1033         feature_columns=(feature_column_lib.numeric_column('age'),),
   1034         weight_column='w',
   1035         n_classes=n_classes,
   1036         model_dir=self._model_dir)
   1037     data_rank_1 = np.array([0, 1])
   1038     data_rank_2 = np.array([[0], [1]])
   1039     self.assertEqual((2,), data_rank_1.shape)
   1040     self.assertEqual((2, 1), data_rank_2.shape)
   1041 
   1042     train_input_fn = numpy_io.numpy_input_fn(
   1043         x={'age': data_rank_1, 'w': data_rank_2}, y=data_rank_1,
   1044         batch_size=batch_size, num_epochs=None,
   1045         shuffle=True)
   1046     est.train(train_input_fn, steps=200)
   1047     self._assert_checkpoint(n_classes, 200)
   1048 
   1049   def testBinaryClassesTrainWithTwoDimsWeight(self):
   1050     self._testTrainWithTwoDimsWeight(n_classes=2)
   1051 
   1052   def testMultiClassesTrainWithTwoDimsWeight(self):
   1053     self._testTrainWithTwoDimsWeight(n_classes=4)
   1054 
   1055   def _testTrainWithOneDimWeight(self, n_classes):
   1056     batch_size = 20
   1057 
   1058     est = linear.LinearClassifier(
   1059         feature_columns=(feature_column_lib.numeric_column('age'),),
   1060         weight_column='w',
   1061         n_classes=n_classes,
   1062         model_dir=self._model_dir)
   1063     data_rank_1 = np.array([0, 1])
   1064     self.assertEqual((2,), data_rank_1.shape)
   1065 
   1066     train_input_fn = numpy_io.numpy_input_fn(
   1067         x={'age': data_rank_1, 'w': data_rank_1}, y=data_rank_1,
   1068         batch_size=batch_size, num_epochs=None,
   1069         shuffle=True)
   1070     est.train(train_input_fn, steps=200)
   1071     self._assert_checkpoint(n_classes, 200)
   1072 
   1073   def testBinaryClassesTrainWithOneDimWeight(self):
   1074     self._testTrainWithOneDimWeight(n_classes=2)
   1075 
   1076   def testMultiClassesTrainWithOneDimWeight(self):
   1077     self._testTrainWithOneDimWeight(n_classes=4)
   1078 
   1079   def _testFromScratch(self, n_classes):
   1080     label = 1
   1081     age = 17
   1082     # For binary classifier:
   1083     #   loss = sigmoid_cross_entropy(logits, label) where logits=0 (weights are
   1084     #   all zero initially) and label = 1 so,
   1085     #      loss = 1 * -log ( sigmoid(logits) ) = 0.69315
   1086     # For multi class classifier:
   1087     #   loss = cross_entropy(logits, label) where logits are all 0s (weights are
   1088     #   all zero initially) and label = 1 so,
   1089     #      loss = 1 * -log ( 1.0 / n_classes )
   1090     # For this particular test case, as logits are same, the formular
   1091     # 1 * -log ( 1.0 / n_classes ) covers both binary and multi class cases.
   1092     mock_optimizer = self._mock_optimizer(
   1093         expected_loss=-1 * math.log(1.0/n_classes))
   1094 
   1095     est = linear.LinearClassifier(
   1096         feature_columns=(feature_column_lib.numeric_column('age'),),
   1097         n_classes=n_classes,
   1098         optimizer=mock_optimizer,
   1099         model_dir=self._model_dir)
   1100     self.assertEqual(0, mock_optimizer.minimize.call_count)
   1101 
   1102     # Train for a few steps, and validate optimizer and final checkpoint.
   1103     num_steps = 10
   1104     est.train(
   1105         input_fn=lambda: ({'age': ((age,),)}, ((label,),)), steps=num_steps)
   1106     self.assertEqual(1, mock_optimizer.minimize.call_count)
   1107     self._assert_checkpoint(
   1108         n_classes,
   1109         expected_global_step=num_steps,
   1110         expected_age_weight=[[0.]] if n_classes == 2 else [[0.] * n_classes],
   1111         expected_bias=[0.] if n_classes == 2 else [.0] * n_classes)
   1112 
   1113   def testBinaryClassesFromScratch(self):
   1114     self._testFromScratch(n_classes=2)
   1115 
   1116   def testMultiClassesFromScratch(self):
   1117     self._testFromScratch(n_classes=4)
   1118 
   1119   def _testFromCheckpoint(self, n_classes):
   1120     # Create initial checkpoint.
   1121     label = 1
   1122     age = 17
   1123     # For binary case, the expected weight has shape (1,1). For multi class
   1124     # case, the shape is (1, n_classes). In order to test the weights, set
   1125     # weights as 2.0 * range(n_classes).
   1126     age_weight = [[2.0]] if n_classes == 2 else (
   1127         np.reshape(2.0 * np.array(list(range(n_classes)), dtype=np.float32),
   1128                    (1, n_classes)))
   1129     bias = [-35.0] if n_classes == 2 else [-35.0] * n_classes
   1130     initial_global_step = 100
   1131     with ops.Graph().as_default():
   1132       variables_lib.Variable(age_weight, name=AGE_WEIGHT_NAME)
   1133       variables_lib.Variable(bias, name=BIAS_NAME)
   1134       variables_lib.Variable(
   1135           initial_global_step,
   1136           name=ops.GraphKeys.GLOBAL_STEP,
   1137           dtype=dtypes.int64)
   1138       save_variables_to_ckpt(self._model_dir)
   1139 
   1140     # For binary classifier:
   1141     #   logits = age * age_weight + bias = 17 * 2. - 35. = -1.
   1142     #   loss = sigmoid_cross_entropy(logits, label)
   1143     #   so, loss = 1 * -log ( sigmoid(-1) ) = 1.3133
   1144     # For multi class classifier:
   1145     #   loss = cross_entropy(logits, label)
   1146     #   where logits = 17 * age_weight + bias and label = 1
   1147     #   so, loss = 1 * -log ( soft_max(logits)[1] )
   1148     if n_classes == 2:
   1149       expected_loss = 1.3133
   1150     else:
   1151       logits = age_weight * age + bias
   1152       logits_exp = np.exp(logits)
   1153       softmax = logits_exp / logits_exp.sum()
   1154       expected_loss = -1 * math.log(softmax[0, label])
   1155 
   1156     mock_optimizer = self._mock_optimizer(expected_loss=expected_loss)
   1157 
   1158     est = linear.LinearClassifier(
   1159         feature_columns=(feature_column_lib.numeric_column('age'),),
   1160         n_classes=n_classes,
   1161         optimizer=mock_optimizer,
   1162         model_dir=self._model_dir)
   1163     self.assertEqual(0, mock_optimizer.minimize.call_count)
   1164 
   1165     # Train for a few steps, and validate optimizer and final checkpoint.
   1166     num_steps = 10
   1167     est.train(
   1168         input_fn=lambda: ({'age': ((age,),)}, ((label,),)), steps=num_steps)
   1169     self.assertEqual(1, mock_optimizer.minimize.call_count)
   1170     self._assert_checkpoint(
   1171         n_classes,
   1172         expected_global_step=initial_global_step + num_steps,
   1173         expected_age_weight=age_weight,
   1174         expected_bias=bias)
   1175 
   1176   def testBinaryClassesFromCheckpoint(self):
   1177     self._testFromCheckpoint(n_classes=2)
   1178 
   1179   def testMultiClassesFromCheckpoint(self):
   1180     self._testFromCheckpoint(n_classes=4)
   1181 
   1182   def _testFromCheckpointFloatLabels(self, n_classes):
   1183     """Tests float labels for binary classification."""
   1184     # Create initial checkpoint.
   1185     if n_classes > 2:
   1186       return
   1187     label = 0.8
   1188     age = 17
   1189     age_weight = [[2.0]]
   1190     bias = [-35.0]
   1191     initial_global_step = 100
   1192     with ops.Graph().as_default():
   1193       variables_lib.Variable(age_weight, name=AGE_WEIGHT_NAME)
   1194       variables_lib.Variable(bias, name=BIAS_NAME)
   1195       variables_lib.Variable(
   1196           initial_global_step,
   1197           name=ops.GraphKeys.GLOBAL_STEP,
   1198           dtype=dtypes.int64)
   1199       save_variables_to_ckpt(self._model_dir)
   1200 
   1201     # logits = age * age_weight + bias = 17 * 2. - 35. = -1.
   1202     # loss = sigmoid_cross_entropy(logits, label)
   1203     # => loss = -0.8 * log(sigmoid(-1)) -0.2 * log(sigmoid(+1)) = 1.1132617
   1204     mock_optimizer = self._mock_optimizer(expected_loss=1.1132617)
   1205 
   1206     est = linear.LinearClassifier(
   1207         feature_columns=(feature_column_lib.numeric_column('age'),),
   1208         n_classes=n_classes,
   1209         optimizer=mock_optimizer,
   1210         model_dir=self._model_dir)
   1211     self.assertEqual(0, mock_optimizer.minimize.call_count)
   1212 
   1213     # Train for a few steps, and validate optimizer and final checkpoint.
   1214     num_steps = 10
   1215     est.train(
   1216         input_fn=lambda: ({'age': ((age,),)}, ((label,),)), steps=num_steps)
   1217     self.assertEqual(1, mock_optimizer.minimize.call_count)
   1218 
   1219   def testBinaryClassesFromCheckpointFloatLabels(self):
   1220     self._testFromCheckpointFloatLabels(n_classes=2)
   1221 
   1222   def testMultiClassesFromCheckpointFloatLabels(self):
   1223     self._testFromCheckpointFloatLabels(n_classes=4)
   1224 
   1225   def _testFromCheckpointMultiBatch(self, n_classes):
   1226     # Create initial checkpoint.
   1227     label = [1, 0]
   1228     age = [17, 18.5]
   1229     # For binary case, the expected weight has shape (1,1). For multi class
   1230     # case, the shape is (1, n_classes). In order to test the weights, set
   1231     # weights as 2.0 * range(n_classes).
   1232     age_weight = [[2.0]] if n_classes == 2 else (
   1233         np.reshape(2.0 * np.array(list(range(n_classes)), dtype=np.float32),
   1234                    (1, n_classes)))
   1235     bias = [-35.0] if n_classes == 2 else [-35.0] * n_classes
   1236     initial_global_step = 100
   1237     with ops.Graph().as_default():
   1238       variables_lib.Variable(age_weight, name=AGE_WEIGHT_NAME)
   1239       variables_lib.Variable(bias, name=BIAS_NAME)
   1240       variables_lib.Variable(
   1241           initial_global_step,
   1242           name=ops.GraphKeys.GLOBAL_STEP,
   1243           dtype=dtypes.int64)
   1244       save_variables_to_ckpt(self._model_dir)
   1245 
   1246     # For binary classifier:
   1247     #   logits = age * age_weight + bias
   1248     #   logits[0] = 17 * 2. - 35. = -1.
   1249     #   logits[1] = 18.5 * 2. - 35. = 2.
   1250     #   loss = sigmoid_cross_entropy(logits, label)
   1251     #   so, loss[0] = 1 * -log ( sigmoid(-1) ) = 1.3133
   1252     #       loss[1] = (1 - 0) * -log ( 1- sigmoid(2) ) = 2.1269
   1253     # For multi class classifier:
   1254     #   loss = cross_entropy(logits, label)
   1255     #   where logits = [17, 18.5] * age_weight + bias and label = [1, 0]
   1256     #   so, loss = 1 * -log ( soft_max(logits)[label] )
   1257     if n_classes == 2:
   1258       expected_loss = (1.3133 + 2.1269)
   1259     else:
   1260       logits = age_weight * np.reshape(age, (2, 1)) + bias
   1261       logits_exp = np.exp(logits)
   1262       softmax_row_0 = logits_exp[0] / logits_exp[0].sum()
   1263       softmax_row_1 = logits_exp[1] / logits_exp[1].sum()
   1264       expected_loss_0 = -1 * math.log(softmax_row_0[label[0]])
   1265       expected_loss_1 = -1 * math.log(softmax_row_1[label[1]])
   1266       expected_loss = expected_loss_0 + expected_loss_1
   1267 
   1268     mock_optimizer = self._mock_optimizer(expected_loss=expected_loss)
   1269 
   1270     est = linear.LinearClassifier(
   1271         feature_columns=(feature_column_lib.numeric_column('age'),),
   1272         n_classes=n_classes,
   1273         optimizer=mock_optimizer,
   1274         model_dir=self._model_dir)
   1275     self.assertEqual(0, mock_optimizer.minimize.call_count)
   1276 
   1277     # Train for a few steps, and validate optimizer and final checkpoint.
   1278     num_steps = 10
   1279     est.train(
   1280         input_fn=lambda: ({'age': (age)}, (label)),
   1281         steps=num_steps)
   1282     self.assertEqual(1, mock_optimizer.minimize.call_count)
   1283     self._assert_checkpoint(
   1284         n_classes,
   1285         expected_global_step=initial_global_step + num_steps,
   1286         expected_age_weight=age_weight,
   1287         expected_bias=bias)
   1288 
   1289   def testBinaryClassesFromCheckpointMultiBatch(self):
   1290     self._testFromCheckpointMultiBatch(n_classes=2)
   1291 
   1292   def testMultiClassesFromCheckpointMultiBatch(self):
   1293     self._testFromCheckpointMultiBatch(n_classes=4)
   1294 
   1295 
   1296 class BaseLinearClassifierEvaluationTest(object):
   1297 
   1298   def __init__(self, linear_classifier_fn):
   1299     self._linear_classifier_fn = linear_classifier_fn
   1300 
   1301   def setUp(self):
   1302     self._model_dir = tempfile.mkdtemp()
   1303 
   1304   def tearDown(self):
   1305     if self._model_dir:
   1306       shutil.rmtree(self._model_dir)
   1307 
   1308   def _test_evaluation_for_simple_data(self, n_classes):
   1309     label = 1
   1310     age = 1.
   1311 
   1312     # For binary case, the expected weight has shape (1,1). For multi class
   1313     # case, the shape is (1, n_classes). In order to test the weights, set
   1314     # weights as 2.0 * range(n_classes).
   1315     age_weight = [[-11.0]] if n_classes == 2 else (
   1316         np.reshape(-11.0 * np.array(list(range(n_classes)), dtype=np.float32),
   1317                    (1, n_classes)))
   1318     bias = [-30.0] if n_classes == 2 else [-30.0] * n_classes
   1319 
   1320     with ops.Graph().as_default():
   1321       variables_lib.Variable(age_weight, name=AGE_WEIGHT_NAME)
   1322       variables_lib.Variable(bias, name=BIAS_NAME)
   1323       variables_lib.Variable(
   1324           100, name=ops.GraphKeys.GLOBAL_STEP, dtype=dtypes.int64)
   1325       save_variables_to_ckpt(self._model_dir)
   1326 
   1327     est = self._linear_classifier_fn(
   1328         feature_columns=(feature_column_lib.numeric_column('age'),),
   1329         n_classes=n_classes,
   1330         model_dir=self._model_dir)
   1331     eval_metrics = est.evaluate(
   1332         input_fn=lambda: ({'age': ((age,),)}, ((label,),)), steps=1)
   1333 
   1334     if n_classes == 2:
   1335       # Binary classes: loss = sum(corss_entropy(41)) = 41.
   1336       expected_metrics = {
   1337           metric_keys.MetricKeys.LOSS: 41.,
   1338           ops.GraphKeys.GLOBAL_STEP: 100,
   1339           metric_keys.MetricKeys.LOSS_MEAN: 41.,
   1340           metric_keys.MetricKeys.ACCURACY: 0.,
   1341           metric_keys.MetricKeys.PREDICTION_MEAN: 0.,
   1342           metric_keys.MetricKeys.LABEL_MEAN: 1.,
   1343           metric_keys.MetricKeys.ACCURACY_BASELINE: 1,
   1344           metric_keys.MetricKeys.AUC: 0.,
   1345           metric_keys.MetricKeys.AUC_PR: 0.5,
   1346       }
   1347     else:
   1348       # Multi classes: loss = 1 * -log ( soft_max(logits)[label] )
   1349       logits = age_weight * age + bias
   1350       logits_exp = np.exp(logits)
   1351       softmax = logits_exp / logits_exp.sum()
   1352       expected_loss = -1 * math.log(softmax[0, label])
   1353 
   1354       expected_metrics = {
   1355           metric_keys.MetricKeys.LOSS: expected_loss,
   1356           ops.GraphKeys.GLOBAL_STEP: 100,
   1357           metric_keys.MetricKeys.LOSS_MEAN: expected_loss,
   1358           metric_keys.MetricKeys.ACCURACY: 0.,
   1359       }
   1360 
   1361     self.assertAllClose(sorted_key_dict(expected_metrics),
   1362                         sorted_key_dict(eval_metrics), rtol=1e-3)
   1363 
   1364   def test_binary_classes_evaluation_for_simple_data(self):
   1365     self._test_evaluation_for_simple_data(n_classes=2)
   1366 
   1367   def test_multi_classes_evaluation_for_simple_data(self):
   1368     self._test_evaluation_for_simple_data(n_classes=4)
   1369 
   1370   def _test_evaluation_batch(self, n_classes):
   1371     """Tests evaluation for batch_size==2."""
   1372     label = [1, 0]
   1373     age = [17., 18.]
   1374     # For binary case, the expected weight has shape (1,1). For multi class
   1375     # case, the shape is (1, n_classes). In order to test the weights, set
   1376     # weights as 2.0 * range(n_classes).
   1377     age_weight = [[2.0]] if n_classes == 2 else (
   1378         np.reshape(2.0 * np.array(list(range(n_classes)), dtype=np.float32),
   1379                    (1, n_classes)))
   1380     bias = [-35.0] if n_classes == 2 else [-35.0] * n_classes
   1381     initial_global_step = 100
   1382     with ops.Graph().as_default():
   1383       variables_lib.Variable(age_weight, name=AGE_WEIGHT_NAME)
   1384       variables_lib.Variable(bias, name=BIAS_NAME)
   1385       variables_lib.Variable(
   1386           initial_global_step,
   1387           name=ops.GraphKeys.GLOBAL_STEP,
   1388           dtype=dtypes.int64)
   1389       save_variables_to_ckpt(self._model_dir)
   1390 
   1391     est = self._linear_classifier_fn(
   1392         feature_columns=(feature_column_lib.numeric_column('age'),),
   1393         n_classes=n_classes,
   1394         model_dir=self._model_dir)
   1395     eval_metrics = est.evaluate(
   1396         input_fn=lambda: ({'age': (age)}, (label)), steps=1)
   1397 
   1398     if n_classes == 2:
   1399       # Logits are (-1., 1.) labels are (1, 0).
   1400       # Loss is
   1401       #   loss for row 1: 1 * -log(sigmoid(-1)) = 1.3133
   1402       #   loss for row 2: (1 - 0) * -log(1 - sigmoid(1)) = 1.3133
   1403       expected_loss = 1.3133 * 2
   1404 
   1405       expected_metrics = {
   1406           metric_keys.MetricKeys.LOSS: expected_loss,
   1407           ops.GraphKeys.GLOBAL_STEP: 100,
   1408           metric_keys.MetricKeys.LOSS_MEAN: expected_loss / 2,
   1409           metric_keys.MetricKeys.ACCURACY: 0.,
   1410           metric_keys.MetricKeys.PREDICTION_MEAN: 0.5,
   1411           metric_keys.MetricKeys.LABEL_MEAN: 0.5,
   1412           metric_keys.MetricKeys.ACCURACY_BASELINE: 0.5,
   1413           metric_keys.MetricKeys.AUC: 0.,
   1414           metric_keys.MetricKeys.AUC_PR: 0.25,
   1415       }
   1416     else:
   1417       # Multi classes: loss = 1 * -log ( soft_max(logits)[label] )
   1418       logits = age_weight * np.reshape(age, (2, 1)) + bias
   1419       logits_exp = np.exp(logits)
   1420       softmax_row_0 = logits_exp[0] / logits_exp[0].sum()
   1421       softmax_row_1 = logits_exp[1] / logits_exp[1].sum()
   1422       expected_loss_0 = -1 * math.log(softmax_row_0[label[0]])
   1423       expected_loss_1 = -1 * math.log(softmax_row_1[label[1]])
   1424       expected_loss = expected_loss_0 + expected_loss_1
   1425 
   1426       expected_metrics = {
   1427           metric_keys.MetricKeys.LOSS: expected_loss,
   1428           ops.GraphKeys.GLOBAL_STEP: 100,
   1429           metric_keys.MetricKeys.LOSS_MEAN: expected_loss / 2,
   1430           metric_keys.MetricKeys.ACCURACY: 0.,
   1431       }
   1432 
   1433     self.assertAllClose(sorted_key_dict(expected_metrics),
   1434                         sorted_key_dict(eval_metrics), rtol=1e-3)
   1435 
   1436   def test_binary_classes_evaluation_batch(self):
   1437     self._test_evaluation_batch(n_classes=2)
   1438 
   1439   def test_multi_classes_evaluation_batch(self):
   1440     self._test_evaluation_batch(n_classes=4)
   1441 
   1442   def _test_evaluation_weights(self, n_classes):
   1443     """Tests evaluation with weights."""
   1444 
   1445     label = [1, 0]
   1446     age = [17., 18.]
   1447     weights = [1., 2.]
   1448     # For binary case, the expected weight has shape (1,1). For multi class
   1449     # case, the shape is (1, n_classes). In order to test the weights, set
   1450     # weights as 2.0 * range(n_classes).
   1451     age_weight = [[2.0]] if n_classes == 2 else (
   1452         np.reshape(2.0 * np.array(list(range(n_classes)), dtype=np.float32),
   1453                    (1, n_classes)))
   1454     bias = [-35.0] if n_classes == 2 else [-35.0] * n_classes
   1455     initial_global_step = 100
   1456     with ops.Graph().as_default():
   1457       variables_lib.Variable(age_weight, name=AGE_WEIGHT_NAME)
   1458       variables_lib.Variable(bias, name=BIAS_NAME)
   1459       variables_lib.Variable(
   1460           initial_global_step,
   1461           name=ops.GraphKeys.GLOBAL_STEP,
   1462           dtype=dtypes.int64)
   1463       save_variables_to_ckpt(self._model_dir)
   1464 
   1465     est = self._linear_classifier_fn(
   1466         feature_columns=(feature_column_lib.numeric_column('age'),),
   1467         n_classes=n_classes,
   1468         weight_column='w',
   1469         model_dir=self._model_dir)
   1470     eval_metrics = est.evaluate(
   1471         input_fn=lambda: ({'age': (age), 'w': (weights)}, (label)), steps=1)
   1472 
   1473     if n_classes == 2:
   1474       # Logits are (-1., 1.) labels are (1, 0).
   1475       # Loss is
   1476       #   loss for row 1: 1 * -log(sigmoid(-1)) = 1.3133
   1477       #   loss for row 2: (1 - 0) * -log(1 - sigmoid(1)) = 1.3133
   1478       #   weights = [1., 2.]
   1479       expected_loss = 1.3133 * (1. + 2.)
   1480       loss_mean = expected_loss / (1.0 + 2.0)
   1481       label_mean = np.average(label, weights=weights)
   1482       logits = [-1, 1]
   1483       logistics = sigmoid(np.array(logits))
   1484       predictions_mean = np.average(logistics, weights=weights)
   1485 
   1486       expected_metrics = {
   1487           metric_keys.MetricKeys.LOSS: expected_loss,
   1488           ops.GraphKeys.GLOBAL_STEP: 100,
   1489           metric_keys.MetricKeys.LOSS_MEAN: loss_mean,
   1490           metric_keys.MetricKeys.ACCURACY: 0.,
   1491           metric_keys.MetricKeys.PREDICTION_MEAN: predictions_mean,
   1492           metric_keys.MetricKeys.LABEL_MEAN: label_mean,
   1493           metric_keys.MetricKeys.ACCURACY_BASELINE: (
   1494               max(label_mean, 1-label_mean)),
   1495           metric_keys.MetricKeys.AUC: 0.,
   1496           metric_keys.MetricKeys.AUC_PR: 0.1668,
   1497       }
   1498     else:
   1499       # Multi classes: unweighted_loss = 1 * -log ( soft_max(logits)[label] )
   1500       logits = age_weight * np.reshape(age, (2, 1)) + bias
   1501       logits_exp = np.exp(logits)
   1502       softmax_row_0 = logits_exp[0] / logits_exp[0].sum()
   1503       softmax_row_1 = logits_exp[1] / logits_exp[1].sum()
   1504       expected_loss_0 = -1 * math.log(softmax_row_0[label[0]])
   1505       expected_loss_1 = -1 * math.log(softmax_row_1[label[1]])
   1506       loss_mean = np.average([expected_loss_0, expected_loss_1],
   1507                              weights=weights)
   1508       expected_loss = loss_mean * np.sum(weights)
   1509 
   1510       expected_metrics = {
   1511           metric_keys.MetricKeys.LOSS: expected_loss,
   1512           ops.GraphKeys.GLOBAL_STEP: 100,
   1513           metric_keys.MetricKeys.LOSS_MEAN: loss_mean,
   1514           metric_keys.MetricKeys.ACCURACY: 0.,
   1515       }
   1516 
   1517     self.assertAllClose(sorted_key_dict(expected_metrics),
   1518                         sorted_key_dict(eval_metrics), rtol=1e-3)
   1519 
   1520   def test_binary_classes_evaluation_weights(self):
   1521     self._test_evaluation_weights(n_classes=2)
   1522 
   1523   def test_multi_classes_evaluation_weights(self):
   1524     self._test_evaluation_weights(n_classes=4)
   1525 
   1526 
   1527 class BaseLinearClassifierPredictTest(object):
   1528 
   1529   def __init__(self, linear_classifier_fn):
   1530     self._linear_classifier_fn = linear_classifier_fn
   1531 
   1532   def setUp(self):
   1533     self._model_dir = tempfile.mkdtemp()
   1534 
   1535   def tearDown(self):
   1536     if self._model_dir:
   1537       shutil.rmtree(self._model_dir)
   1538 
   1539   def _testPredictions(self, n_classes, label_vocabulary, label_output_fn):
   1540     """Tests predict when all variables are one-dimensional."""
   1541     age = 1.
   1542 
   1543     # For binary case, the expected weight has shape (1,1). For multi class
   1544     # case, the shape is (1, n_classes). In order to test the weights, set
   1545     # weights as 2.0 * range(n_classes).
   1546     age_weight = [[-11.0]] if n_classes == 2 else (
   1547         np.reshape(-11.0 * np.array(list(range(n_classes)), dtype=np.float32),
   1548                    (1, n_classes)))
   1549     bias = [10.0] if n_classes == 2 else [10.0] * n_classes
   1550 
   1551     with ops.Graph().as_default():
   1552       variables_lib.Variable(age_weight, name=AGE_WEIGHT_NAME)
   1553       variables_lib.Variable(bias, name=BIAS_NAME)
   1554       variables_lib.Variable(100, name='global_step', dtype=dtypes.int64)
   1555       save_variables_to_ckpt(self._model_dir)
   1556 
   1557     est = self._linear_classifier_fn(
   1558         feature_columns=(feature_column_lib.numeric_column('age'),),
   1559         label_vocabulary=label_vocabulary,
   1560         n_classes=n_classes,
   1561         model_dir=self._model_dir)
   1562 
   1563     predict_input_fn = numpy_io.numpy_input_fn(
   1564         x={'age': np.array([[age]])},
   1565         y=None,
   1566         batch_size=1,
   1567         num_epochs=1,
   1568         shuffle=False)
   1569     predictions = list(est.predict(input_fn=predict_input_fn))
   1570 
   1571     if n_classes == 2:
   1572       scalar_logits = np.asscalar(
   1573           np.reshape(np.array(age_weight) * age + bias, (1,)))
   1574       two_classes_logits = [0, scalar_logits]
   1575       two_classes_logits_exp = np.exp(two_classes_logits)
   1576       softmax = two_classes_logits_exp / two_classes_logits_exp.sum()
   1577 
   1578       expected_predictions = {
   1579           'class_ids': [0],
   1580           'classes': [label_output_fn(0)],
   1581           'logistic': [sigmoid(np.array(scalar_logits))],
   1582           'logits': [scalar_logits],
   1583           'probabilities': softmax,
   1584       }
   1585     else:
   1586       onedim_logits = np.reshape(np.array(age_weight) * age + bias, (-1,))
   1587       class_ids = onedim_logits.argmax()
   1588       logits_exp = np.exp(onedim_logits)
   1589       softmax = logits_exp / logits_exp.sum()
   1590       expected_predictions = {
   1591           'class_ids': [class_ids],
   1592           'classes': [label_output_fn(class_ids)],
   1593           'logits': onedim_logits,
   1594           'probabilities': softmax,
   1595       }
   1596 
   1597     self.assertEqual(1, len(predictions))
   1598     # assertAllClose cannot handle byte type.
   1599     self.assertEqual(expected_predictions['classes'], predictions[0]['classes'])
   1600     expected_predictions.pop('classes')
   1601     predictions[0].pop('classes')
   1602     self.assertAllClose(sorted_key_dict(expected_predictions),
   1603                         sorted_key_dict(predictions[0]))
   1604 
   1605   def testBinaryClassesWithoutLabelVocabulary(self):
   1606     n_classes = 2
   1607     self._testPredictions(n_classes,
   1608                           label_vocabulary=None,
   1609                           label_output_fn=lambda x: ('%s' % x).encode())
   1610 
   1611   def testBinaryClassesWithLabelVocabulary(self):
   1612     n_classes = 2
   1613     self._testPredictions(
   1614         n_classes,
   1615         label_vocabulary=['class_vocab_{}'.format(i)
   1616                           for i in range(n_classes)],
   1617         label_output_fn=lambda x: ('class_vocab_%s' % x).encode())
   1618 
   1619   def testMultiClassesWithoutLabelVocabulary(self):
   1620     n_classes = 4
   1621     self._testPredictions(
   1622         n_classes,
   1623         label_vocabulary=None,
   1624         label_output_fn=lambda x: ('%s' % x).encode())
   1625 
   1626   def testMultiClassesWithLabelVocabulary(self):
   1627     n_classes = 4
   1628     self._testPredictions(
   1629         n_classes,
   1630         label_vocabulary=['class_vocab_{}'.format(i)
   1631                           for i in range(n_classes)],
   1632         label_output_fn=lambda x: ('class_vocab_%s' % x).encode())
   1633 
   1634 
   1635 class BaseLinearClassifierIntegrationTest(object):
   1636 
   1637   def __init__(self, linear_classifier_fn):
   1638     self._linear_classifier_fn = linear_classifier_fn
   1639 
   1640   def setUp(self):
   1641     self._model_dir = tempfile.mkdtemp()
   1642 
   1643   def tearDown(self):
   1644     if self._model_dir:
   1645       shutil.rmtree(self._model_dir)
   1646 
   1647   def _test_complete_flow(self, n_classes, train_input_fn, eval_input_fn,
   1648                           predict_input_fn, input_dimension, prediction_length):
   1649     feature_columns = [
   1650         feature_column_lib.numeric_column('x', shape=(input_dimension,))
   1651     ]
   1652     est = self._linear_classifier_fn(
   1653         feature_columns=feature_columns,
   1654         n_classes=n_classes,
   1655         model_dir=self._model_dir)
   1656 
   1657     # TRAIN
   1658     # learn y = x
   1659     est.train(train_input_fn, steps=200)
   1660 
   1661     # EVALUTE
   1662     scores = est.evaluate(eval_input_fn)
   1663     self.assertEqual(200, scores[ops.GraphKeys.GLOBAL_STEP])
   1664     self.assertIn(metric_keys.MetricKeys.LOSS, six.iterkeys(scores))
   1665 
   1666     # PREDICT
   1667     predictions = np.array(
   1668         [x['classes'] for x in est.predict(predict_input_fn)])
   1669     self.assertAllEqual((prediction_length, 1), predictions.shape)
   1670 
   1671     # EXPORT
   1672     feature_spec = feature_column_lib.make_parse_example_spec(feature_columns)
   1673     serving_input_receiver_fn = export.build_parsing_serving_input_receiver_fn(
   1674         feature_spec)
   1675     export_dir = est.export_savedmodel(tempfile.mkdtemp(),
   1676                                        serving_input_receiver_fn)
   1677     self.assertTrue(gfile.Exists(export_dir))
   1678 
   1679   def _test_numpy_input_fn(self, n_classes):
   1680     """Tests complete flow with numpy_input_fn."""
   1681     input_dimension = 4
   1682     batch_size = 10
   1683     prediction_length = batch_size
   1684     data = np.linspace(0., 2., batch_size * input_dimension, dtype=np.float32)
   1685     data = data.reshape(batch_size, input_dimension)
   1686     target = np.array([1] * batch_size)
   1687 
   1688     train_input_fn = numpy_io.numpy_input_fn(
   1689         x={'x': data},
   1690         y=target,
   1691         batch_size=batch_size,
   1692         num_epochs=None,
   1693         shuffle=True)
   1694     eval_input_fn = numpy_io.numpy_input_fn(
   1695         x={'x': data},
   1696         y=target,
   1697         batch_size=batch_size,
   1698         num_epochs=1,
   1699         shuffle=False)
   1700     predict_input_fn = numpy_io.numpy_input_fn(
   1701         x={'x': data},
   1702         y=None,
   1703         batch_size=batch_size,
   1704         num_epochs=1,
   1705         shuffle=False)
   1706 
   1707     self._test_complete_flow(
   1708         n_classes=n_classes,
   1709         train_input_fn=train_input_fn,
   1710         eval_input_fn=eval_input_fn,
   1711         predict_input_fn=predict_input_fn,
   1712         input_dimension=input_dimension,
   1713         prediction_length=prediction_length)
   1714 
   1715   def test_binary_classes_numpy_input_fn(self):
   1716     self._test_numpy_input_fn(n_classes=2)
   1717 
   1718   def test_multi_classes_numpy_input_fn(self):
   1719     self._test_numpy_input_fn(n_classes=4)
   1720 
   1721   def _test_pandas_input_fn(self, n_classes):
   1722     """Tests complete flow with pandas_input_fn."""
   1723     if not HAS_PANDAS:
   1724       return
   1725 
   1726     # Pandas DataFrame natually supports 1 dim data only.
   1727     input_dimension = 1
   1728     batch_size = 10
   1729     data = np.array([1., 2., 3., 4.], dtype=np.float32)
   1730     target = np.array([1, 0, 1, 0], dtype=np.int32)
   1731     x = pd.DataFrame({'x': data})
   1732     y = pd.Series(target)
   1733     prediction_length = 4
   1734 
   1735     train_input_fn = pandas_io.pandas_input_fn(
   1736         x=x, y=y, batch_size=batch_size, num_epochs=None, shuffle=True)
   1737     eval_input_fn = pandas_io.pandas_input_fn(
   1738         x=x, y=y, batch_size=batch_size, shuffle=False)
   1739     predict_input_fn = pandas_io.pandas_input_fn(
   1740         x=x, batch_size=batch_size, shuffle=False)
   1741 
   1742     self._test_complete_flow(
   1743         n_classes=n_classes,
   1744         train_input_fn=train_input_fn,
   1745         eval_input_fn=eval_input_fn,
   1746         predict_input_fn=predict_input_fn,
   1747         input_dimension=input_dimension,
   1748         prediction_length=prediction_length)
   1749 
   1750   def test_binary_classes_pandas_input_fn(self):
   1751     self._test_pandas_input_fn(n_classes=2)
   1752 
   1753   def test_multi_classes_pandas_input_fn(self):
   1754     self._test_pandas_input_fn(n_classes=4)
   1755 
   1756   def _test_input_fn_from_parse_example(self, n_classes):
   1757     """Tests complete flow with input_fn constructed from parse_example."""
   1758     input_dimension = 2
   1759     batch_size = 10
   1760     prediction_length = batch_size
   1761     data = np.linspace(0., 2., batch_size * input_dimension, dtype=np.float32)
   1762     data = data.reshape(batch_size, input_dimension)
   1763     target = np.array([1] * batch_size, dtype=np.int64)
   1764 
   1765     serialized_examples = []
   1766     for x, y in zip(data, target):
   1767       example = example_pb2.Example(features=feature_pb2.Features(
   1768           feature={
   1769               'x':
   1770                   feature_pb2.Feature(float_list=feature_pb2.FloatList(
   1771                       value=x)),
   1772               'y':
   1773                   feature_pb2.Feature(int64_list=feature_pb2.Int64List(
   1774                       value=[y])),
   1775           }))
   1776       serialized_examples.append(example.SerializeToString())
   1777 
   1778     feature_spec = {
   1779         'x': parsing_ops.FixedLenFeature([input_dimension], dtypes.float32),
   1780         'y': parsing_ops.FixedLenFeature([1], dtypes.int64),
   1781     }
   1782 
   1783     def _train_input_fn():
   1784       feature_map = parsing_ops.parse_example(serialized_examples, feature_spec)
   1785       features = queue_parsed_features(feature_map)
   1786       labels = features.pop('y')
   1787       return features, labels
   1788 
   1789     def _eval_input_fn():
   1790       feature_map = parsing_ops.parse_example(
   1791           input_lib.limit_epochs(serialized_examples, num_epochs=1),
   1792           feature_spec)
   1793       features = queue_parsed_features(feature_map)
   1794       labels = features.pop('y')
   1795       return features, labels
   1796 
   1797     def _predict_input_fn():
   1798       feature_map = parsing_ops.parse_example(
   1799           input_lib.limit_epochs(serialized_examples, num_epochs=1),
   1800           feature_spec)
   1801       features = queue_parsed_features(feature_map)
   1802       features.pop('y')
   1803       return features, None
   1804 
   1805     self._test_complete_flow(
   1806         n_classes=n_classes,
   1807         train_input_fn=_train_input_fn,
   1808         eval_input_fn=_eval_input_fn,
   1809         predict_input_fn=_predict_input_fn,
   1810         input_dimension=input_dimension,
   1811         prediction_length=prediction_length)
   1812 
   1813   def test_binary_classes_input_fn_from_parse_example(self):
   1814     self._test_input_fn_from_parse_example(n_classes=2)
   1815 
   1816   def test_multi_classes_input_fn_from_parse_example(self):
   1817     self._test_input_fn_from_parse_example(n_classes=4)
   1818 
   1819 
   1820 class BaseLinearLogitFnTest(object):
   1821 
   1822   def test_basic_logit_correctness(self):
   1823     """linear_logit_fn simply wraps feature_column_lib.linear_model."""
   1824     age = feature_column_lib.numeric_column('age')
   1825     with ops.Graph().as_default():
   1826       logit_fn = linear._linear_logit_fn_builder(units=2, feature_columns=[age])
   1827       logits = logit_fn(features={'age': [[23.], [31.]]})
   1828       bias_var = ops.get_collection(ops.GraphKeys.GLOBAL_VARIABLES,
   1829                                     'linear_model/bias_weights')[0]
   1830       age_var = ops.get_collection(ops.GraphKeys.GLOBAL_VARIABLES,
   1831                                    'linear_model/age')[0]
   1832       with tf_session.Session() as sess:
   1833         sess.run([variables_lib.global_variables_initializer()])
   1834         self.assertAllClose([[0., 0.], [0., 0.]], logits.eval())
   1835         sess.run(bias_var.assign([10., 5.]))
   1836         self.assertAllClose([[10., 5.], [10., 5.]], logits.eval())
   1837         sess.run(age_var.assign([[2.0, 3.0]]))
   1838         # [2 * 23 + 10, 3 * 23 + 5] = [56, 74].
   1839         # [2 * 31 + 10, 3 * 31 + 5] = [72, 98]
   1840         self.assertAllClose([[56., 74.], [72., 98.]], logits.eval())
   1841 
   1842   def test_compute_fraction_of_zero(self):
   1843     """Tests the calculation of sparsity."""
   1844     age = feature_column_lib.numeric_column('age')
   1845     occupation = feature_column_lib.categorical_column_with_hash_bucket(
   1846         'occupation', hash_bucket_size=5)
   1847     with ops.Graph().as_default():
   1848       cols_to_vars = {}
   1849       feature_column_lib.linear_model(
   1850           features={
   1851               'age': [[23.], [31.]],
   1852               'occupation': [['doctor'], ['engineer']]
   1853           },
   1854           feature_columns=[age, occupation],
   1855           units=3,
   1856           cols_to_vars=cols_to_vars)
   1857       cols_to_vars.pop('bias')
   1858       fraction_zero = linear._compute_fraction_of_zero(cols_to_vars)
   1859       age_var = ops.get_collection(ops.GraphKeys.GLOBAL_VARIABLES,
   1860                                    'linear_model/age')[0]
   1861       with tf_session.Session() as sess:
   1862         sess.run([variables_lib.global_variables_initializer()])
   1863         # Upon initialization, all variables will be zero.
   1864         self.assertAllClose(1, fraction_zero.eval())
   1865 
   1866         sess.run(age_var.assign([[2.0, 0.0, -1.0]]))
   1867         # 1 of the 3 age weights are zero, and all of the 15 (5 hash buckets
   1868         # x 3-dim output) are zero.
   1869         self.assertAllClose(16. / 18., fraction_zero.eval())
   1870 
   1871 
   1872 class BaseLinearWarmStartingTest(object):
   1873 
   1874   def __init__(self, _linear_classifier_fn, _linear_regressor_fn):
   1875     self._linear_classifier_fn = _linear_classifier_fn
   1876     self._linear_regressor_fn = _linear_regressor_fn
   1877 
   1878   def setUp(self):
   1879     # Create a directory to save our old checkpoint and vocabularies to.
   1880     self._ckpt_and_vocab_dir = tempfile.mkdtemp()
   1881 
   1882     # Make a dummy input_fn.
   1883     def _input_fn():
   1884       features = {
   1885           'age': [[23.], [31.]],
   1886           'age_in_years': [[23.], [31.]],
   1887           'occupation': [['doctor'], ['consultant']]
   1888       }
   1889       return features, [0, 1]
   1890 
   1891     self._input_fn = _input_fn
   1892 
   1893   def tearDown(self):
   1894     # Clean up checkpoint / vocab dir.
   1895     writer_cache.FileWriterCache.clear()
   1896     shutil.rmtree(self._ckpt_and_vocab_dir)
   1897 
   1898   def test_classifier_basic_warm_starting(self):
   1899     """Tests correctness of LinearClassifier default warm-start."""
   1900     age = feature_column_lib.numeric_column('age')
   1901 
   1902     # Create a LinearClassifier and train to save a checkpoint.
   1903     linear_classifier = self._linear_classifier_fn(
   1904         feature_columns=[age],
   1905         model_dir=self._ckpt_and_vocab_dir,
   1906         n_classes=4,
   1907         optimizer='SGD')
   1908     linear_classifier.train(input_fn=self._input_fn, max_steps=1)
   1909 
   1910     # Create a second LinearClassifier, warm-started from the first.  Use a
   1911     # learning_rate = 0.0 optimizer to check values (use SGD so we don't have
   1912     # accumulator values that change).
   1913     warm_started_linear_classifier = self._linear_classifier_fn(
   1914         feature_columns=[age],
   1915         n_classes=4,
   1916         optimizer=gradient_descent.GradientDescentOptimizer(learning_rate=0.0),
   1917         warm_start_from=linear_classifier.model_dir)
   1918 
   1919     warm_started_linear_classifier.train(input_fn=self._input_fn, max_steps=1)
   1920     for variable_name in warm_started_linear_classifier.get_variable_names():
   1921       self.assertAllClose(
   1922           linear_classifier.get_variable_value(variable_name),
   1923           warm_started_linear_classifier.get_variable_value(variable_name))
   1924 
   1925   def test_regressor_basic_warm_starting(self):
   1926     """Tests correctness of LinearRegressor default warm-start."""
   1927     age = feature_column_lib.numeric_column('age')
   1928 
   1929     # Create a LinearRegressor and train to save a checkpoint.
   1930     linear_regressor = self._linear_regressor_fn(
   1931         feature_columns=[age],
   1932         model_dir=self._ckpt_and_vocab_dir,
   1933         optimizer='SGD')
   1934     linear_regressor.train(input_fn=self._input_fn, max_steps=1)
   1935 
   1936     # Create a second LinearRegressor, warm-started from the first.  Use a
   1937     # learning_rate = 0.0 optimizer to check values (use SGD so we don't have
   1938     # accumulator values that change).
   1939     warm_started_linear_regressor = self._linear_regressor_fn(
   1940         feature_columns=[age],
   1941         optimizer=gradient_descent.GradientDescentOptimizer(learning_rate=0.0),
   1942         warm_start_from=linear_regressor.model_dir)
   1943 
   1944     warm_started_linear_regressor.train(input_fn=self._input_fn, max_steps=1)
   1945     for variable_name in warm_started_linear_regressor.get_variable_names():
   1946       self.assertAllClose(
   1947           linear_regressor.get_variable_value(variable_name),
   1948           warm_started_linear_regressor.get_variable_value(variable_name))
   1949 
   1950   def test_warm_starting_selective_variables(self):
   1951     """Tests selecting variables to warm-start."""
   1952     age = feature_column_lib.numeric_column('age')
   1953 
   1954     # Create a LinearClassifier and train to save a checkpoint.
   1955     linear_classifier = self._linear_classifier_fn(
   1956         feature_columns=[age],
   1957         model_dir=self._ckpt_and_vocab_dir,
   1958         n_classes=4,
   1959         optimizer='SGD')
   1960     linear_classifier.train(input_fn=self._input_fn, max_steps=1)
   1961 
   1962     # Create a second LinearClassifier, warm-started from the first.  Use a
   1963     # learning_rate = 0.0 optimizer to check values (use SGD so we don't have
   1964     # accumulator values that change).
   1965     warm_started_linear_classifier = self._linear_classifier_fn(
   1966         feature_columns=[age],
   1967         n_classes=4,
   1968         optimizer=gradient_descent.GradientDescentOptimizer(learning_rate=0.0),
   1969         # The provided regular expression will only warm-start the age variable
   1970         # and not the bias.
   1971         warm_start_from=warm_starting_util.WarmStartSettings(
   1972             ckpt_to_initialize_from=linear_classifier.model_dir,
   1973             vars_to_warm_start='.*(age).*'))
   1974 
   1975     warm_started_linear_classifier.train(input_fn=self._input_fn, max_steps=1)
   1976     self.assertAllClose(
   1977         linear_classifier.get_variable_value(AGE_WEIGHT_NAME),
   1978         warm_started_linear_classifier.get_variable_value(AGE_WEIGHT_NAME))
   1979     # Bias should still be zero from initialization.
   1980     self.assertAllClose(
   1981         [0.0] * 4, warm_started_linear_classifier.get_variable_value(BIAS_NAME))
   1982 
   1983   def test_warm_starting_with_vocab_remapping_and_partitioning(self):
   1984     """Tests warm-starting with vocab remapping and partitioning."""
   1985     vocab_list = ['doctor', 'lawyer', 'consultant']
   1986     vocab_file = os.path.join(self._ckpt_and_vocab_dir, 'occupation_vocab')
   1987     with open(vocab_file, 'w') as f:
   1988       f.write('\n'.join(vocab_list))
   1989     occupation = feature_column_lib.categorical_column_with_vocabulary_file(
   1990         'occupation',
   1991         vocabulary_file=vocab_file,
   1992         vocabulary_size=len(vocab_list))
   1993 
   1994     # Create a LinearClassifier and train to save a checkpoint.
   1995     partitioner = partitioned_variables.fixed_size_partitioner(num_shards=2)
   1996     linear_classifier = self._linear_classifier_fn(
   1997         feature_columns=[occupation],
   1998         model_dir=self._ckpt_and_vocab_dir,
   1999         n_classes=4,
   2000         optimizer='SGD',
   2001         partitioner=partitioner)
   2002     linear_classifier.train(input_fn=self._input_fn, max_steps=1)
   2003 
   2004     # Create a second LinearClassifier, warm-started from the first.  Use a
   2005     # learning_rate = 0.0 optimizer to check values (use SGD so we don't have
   2006     # accumulator values that change).  Use a new FeatureColumn with a
   2007     # different vocabulary for occupation.
   2008     new_vocab_list = ['doctor', 'consultant', 'engineer']
   2009     new_vocab_file = os.path.join(self._ckpt_and_vocab_dir,
   2010                                   'new_occupation_vocab')
   2011     with open(new_vocab_file, 'w') as f:
   2012       f.write('\n'.join(new_vocab_list))
   2013     new_occupation = feature_column_lib.categorical_column_with_vocabulary_file(
   2014         'occupation',
   2015         vocabulary_file=new_vocab_file,
   2016         vocabulary_size=len(new_vocab_list))
   2017     # We can create our VocabInfo object from the new and old occupation
   2018     # FeatureColumn's.
   2019     occupation_vocab_info = warm_starting_util.VocabInfo(
   2020         new_vocab=new_occupation.vocabulary_file,
   2021         new_vocab_size=new_occupation.vocabulary_size,
   2022         num_oov_buckets=new_occupation.num_oov_buckets,
   2023         old_vocab=occupation.vocabulary_file,
   2024         old_vocab_size=occupation.vocabulary_size,
   2025         # Can't use constant_initializer with load_and_remap.  In practice,
   2026         # use a truncated normal initializer.
   2027         backup_initializer=init_ops.random_uniform_initializer(
   2028             minval=0.39, maxval=0.39))
   2029     warm_started_linear_classifier = self._linear_classifier_fn(
   2030         feature_columns=[occupation],
   2031         n_classes=4,
   2032         optimizer=gradient_descent.GradientDescentOptimizer(learning_rate=0.0),
   2033         warm_start_from=warm_starting_util.WarmStartSettings(
   2034             ckpt_to_initialize_from=linear_classifier.model_dir,
   2035             var_name_to_vocab_info={
   2036                 OCCUPATION_WEIGHT_NAME: occupation_vocab_info
   2037             },
   2038             # Explicitly providing None here will only warm-start variables
   2039             # referenced in var_name_to_vocab_info (the bias will not be
   2040             # warm-started).
   2041             vars_to_warm_start=None),
   2042         partitioner=partitioner)
   2043 
   2044     warm_started_linear_classifier.train(input_fn=self._input_fn, max_steps=1)
   2045     # 'doctor' was ID-0 and still ID-0.
   2046     self.assertAllClose(
   2047         linear_classifier.get_variable_value(OCCUPATION_WEIGHT_NAME)[0, :],
   2048         warm_started_linear_classifier.get_variable_value(
   2049             OCCUPATION_WEIGHT_NAME)[0, :])
   2050     # 'consultant' was ID-2 and now ID-1.
   2051     self.assertAllClose(
   2052         linear_classifier.get_variable_value(OCCUPATION_WEIGHT_NAME)[2, :],
   2053         warm_started_linear_classifier.get_variable_value(
   2054             OCCUPATION_WEIGHT_NAME)[1, :])
   2055     # 'engineer' is a new entry and should be initialized with the
   2056     # backup_initializer in VocabInfo.
   2057     self.assertAllClose([0.39] * 4,
   2058                         warm_started_linear_classifier.get_variable_value(
   2059                             OCCUPATION_WEIGHT_NAME)[2, :])
   2060     # Bias should still be zero (from initialization logic).
   2061     self.assertAllClose(
   2062         [0.0] * 4, warm_started_linear_classifier.get_variable_value(BIAS_NAME))
   2063 
   2064   def test_warm_starting_with_naming_change(self):
   2065     """Tests warm-starting with a Tensor name remapping."""
   2066     age_in_years = feature_column_lib.numeric_column('age_in_years')
   2067 
   2068     # Create a LinearClassifier and train to save a checkpoint.
   2069     linear_classifier = self._linear_classifier_fn(
   2070         feature_columns=[age_in_years],
   2071         model_dir=self._ckpt_and_vocab_dir,
   2072         n_classes=4,
   2073         optimizer='SGD')
   2074     linear_classifier.train(input_fn=self._input_fn, max_steps=1)
   2075 
   2076     # Create a second LinearClassifier, warm-started from the first.  Use a
   2077     # learning_rate = 0.0 optimizer to check values (use SGD so we don't have
   2078     # accumulator values that change).
   2079     warm_started_linear_classifier = self._linear_classifier_fn(
   2080         feature_columns=[feature_column_lib.numeric_column('age')],
   2081         n_classes=4,
   2082         optimizer=gradient_descent.GradientDescentOptimizer(learning_rate=0.0),
   2083         # The 'age' variable correspond to the 'age_in_years' variable in the
   2084         # previous model.
   2085         warm_start_from=warm_starting_util.WarmStartSettings(
   2086             ckpt_to_initialize_from=linear_classifier.model_dir,
   2087             var_name_to_prev_var_name={
   2088                 AGE_WEIGHT_NAME: AGE_WEIGHT_NAME.replace('age', 'age_in_years')
   2089             }))
   2090 
   2091     warm_started_linear_classifier.train(input_fn=self._input_fn, max_steps=1)
   2092     self.assertAllClose(
   2093         linear_classifier.get_variable_value(
   2094             AGE_WEIGHT_NAME.replace('age', 'age_in_years')),
   2095         warm_started_linear_classifier.get_variable_value(AGE_WEIGHT_NAME))
   2096     # The bias is also warm-started (with no name remapping).
   2097     self.assertAllClose(
   2098         linear_classifier.get_variable_value(BIAS_NAME),
   2099         warm_started_linear_classifier.get_variable_value(BIAS_NAME))
   2100