Returns a structured description of the model, including (where relevant)
        the schema of the training data, description of the training data,
        training statistics, and model hyperparameters.

        Returns
        -------
        sections : list (of list of tuples)
            A list of summary sections.
              Each section is a list.
                Each item in a section list is a tuple of the form:
                  ('<label>','<field>')
        section_titles: list
            A list of section names.
              The order matches that of the 'sections' object.

def _get_summary_struct(self):
        """
        Returns a structured description of the model, including (where relevant)
        the schema of the training data, description of the training data,
        training statistics, and model hyperparameters.

        Returns
        -------
        sections : list (of list of tuples)
            A list of summary sections.
              Each section is a list.
                Each item in a section list is a tuple of the form:
                  ('<label>','<field>')
        section_titles: list
            A list of section names.
              The order matches that of the 'sections' object.
        """

        stats = self._list_fields()
        options = self._get_current_options()

        section_titles = []
        sections = []

        observation_columns = set(self.observation_data_column_names)
        not_needed = set([self.user_id,
                          self.item_id,
                          self.target])
        num_obs_fields = len(observation_columns.difference(not_needed))

        user_features = self.user_side_data_column_names
        item_features = self.item_side_data_column_names

        section_titles.append("Schema")
        schema_fields = [
            ('User ID', 'user_id'),
            ('Item ID', 'item_id'),
            ('Target', 'target'),
            ('Additional observation features', _precomputed_field(num_obs_fields)),
            ('User side features', _precomputed_field(user_features)),
            ('Item side features', _precomputed_field(item_features))]
        sections.append(schema_fields)

        data_fields = [
            ('Number of observations', 'num_observations'),
            ('Number of users', 'num_users'),
            ('Number of items', 'num_items')]
        section_titles.append("Statistics")
        sections.append(data_fields)

        training_fields = [
            ('Training time', 'training_time')]

        if 'data_load_elapsed_time' in stats:
            training_fields.append(('Data load time',
                                    'data_load_elapsed_time'))
        if 'validation_metrics_elapsed_time' in stats:
            training_fields.append(('Validation metrics time',
                                   'validation_metrics_elapsed_time'))
        section_titles.append("Training summary")
        sections.append(training_fields)

        # Remove any options that should not be shown under "Settings"
        to_ignore = ['random_seed',
                     'user_id',
                     'item_id',
                     'target']

        for k in to_ignore:
            if k in options:
                del options[k]

        def add_ordered_options(name, ordered_options, additional = []):
            option_fields = []

            for k, v in additional:
                option_fields.append((k, _precomputed_field(v)))

            for k in ordered_options:
                if k in options:
                    option_fields.append((k, _precomputed_field(options[k])))
                    del options[k]

            if option_fields:
                section_titles.append(name)
                sections.append(option_fields)

        # Put in a number of things in order, if applicable.

        # Model parameters
        model_parameter_options = [
            "only_top_k",
            "threshold",
            "num_factors",
            "binary_target",
            "side_data_factorization",
            "solver",
            "nmf",
            "max_iterations",
            "similarity_type",
            "training_method"]

        add_ordered_options("Model Parameters", model_parameter_options,
                            [("Model class", self.__class__.__name__)])

        # Regularization type options
        regularization_options = [
            "regularization",
            "regularization_type",
            "linear_regularization",
            "ranking_regularization",
            "unobserved_rating_value",
            "num_sampled_negative_examples",
            "ials_confidence_scaling_type",
            "ials_confidence_scaling_factor"]

        add_ordered_options("Regularization Settings", regularization_options)

        # Optimization stuff
        optimization_settings = [
            "init_random_sigma",
            "sgd_convergence_interval",
            "sgd_convergence_threshold",
            "sgd_max_trial_iterations",
            "sgd_sampling_block_size",
            "sgd_step_adjustment_interval",
            "sgd_step_size",
            "sgd_trial_sample_minimum_size",
            "sgd_trial_sample_proportion",
            "step_size_decrease_rate",
            "additional_iterations_if_unhealthy",
            "adagrad_momentum_weighting",
            "num_tempering_iterations",
            "tempering_regularization_start_value",
            "track_exact_loss"]

        add_ordered_options("Optimization Settings", optimization_settings)

        # clean up
        option_fields = []

        for k, v in _six.iteritems(options):
            option_fields.append((k, _precomputed_field(v)))

        if option_fields:
            section_titles.append("Other Settings")
            sections.append(option_fields)

        return (sections, section_titles)