Creates a :class:`SoundClassifier` model.

    Parameters
    ----------
    dataset : SFrame
        Input data. The column named by the 'feature' parameter will be
        extracted for modeling.

    target : string or int
        Name of the column containing the target variable. The values in this
        column must be of string or integer type.

    feature : string, optional
        Name of the column containing the feature column. This column must
        contain audio data or deep audio features.
        Audio data is represented as dicts with key 'data' and 'sample_rate',
        see `turicreate.load_audio(...)`.
        Deep audio features are represented as a list of numpy arrays, each of
        size 12288, see `turicreate.sound_classifier.get_deep_features(...)`.

    max_iterations : int, optional
        The maximum number of allowed passes through the data. More passes over
        the data can result in a more accurately trained model. Consider
        increasing this (the default value is 10) if the training accuracy is
        low.

    custom_layer_sizes : list of ints
        Specifies the architecture of the custom neural network. This neural
        network is made up of a series of dense layers. This parameter allows
        you to specify how many layers and the number of units in each layer.
        The custom neural network will always have one more layer than the
        length of this list. The last layer is always a soft max with units
        equal to the number of classes.

    verbose : bool, optional
        If True, prints progress updates and model details.

    validation_set : SFrame, optional
        A dataset for monitoring the model's generalization performance. The
        format of this SFrame must be the same as the training dataset. By
        default, a validation set is automatically sampled. If `validation_set`
        is set to None, no validataion is used. You can also pass a validation
        set you have constructed yourself.

    batch_size : int, optional
        If you are getting memory errors, try decreasing this value. If you
        have a powerful computer, increasing this value may improve performance.

def create(dataset, target, feature, max_iterations=10,
           custom_layer_sizes=[100, 100], verbose=True,
           validation_set='auto', batch_size=64):
    '''
    Creates a :class:`SoundClassifier` model.

    Parameters
    ----------
    dataset : SFrame
        Input data. The column named by the 'feature' parameter will be
        extracted for modeling.

    target : string or int
        Name of the column containing the target variable. The values in this
        column must be of string or integer type.

    feature : string, optional
        Name of the column containing the feature column. This column must
        contain audio data or deep audio features.
        Audio data is represented as dicts with key 'data' and 'sample_rate',
        see `turicreate.load_audio(...)`.
        Deep audio features are represented as a list of numpy arrays, each of
        size 12288, see `turicreate.sound_classifier.get_deep_features(...)`.

    max_iterations : int, optional
        The maximum number of allowed passes through the data. More passes over
        the data can result in a more accurately trained model. Consider
        increasing this (the default value is 10) if the training accuracy is
        low.

    custom_layer_sizes : list of ints
        Specifies the architecture of the custom neural network. This neural
        network is made up of a series of dense layers. This parameter allows
        you to specify how many layers and the number of units in each layer.
        The custom neural network will always have one more layer than the
        length of this list. The last layer is always a soft max with units
        equal to the number of classes.

    verbose : bool, optional
        If True, prints progress updates and model details.

    validation_set : SFrame, optional
        A dataset for monitoring the model's generalization performance. The
        format of this SFrame must be the same as the training dataset. By
        default, a validation set is automatically sampled. If `validation_set`
        is set to None, no validataion is used. You can also pass a validation
        set you have constructed yourself.

    batch_size : int, optional
        If you are getting memory errors, try decreasing this value. If you
        have a powerful computer, increasing this value may improve performance.
    '''
    import time
    from .._mxnet import _mxnet_utils
    import mxnet as mx

    from ._audio_feature_extractor import _get_feature_extractor

    start_time = time.time()

    # check parameters
    if len(dataset) == 0:
        raise _ToolkitError('Unable to train on empty dataset')
    if feature not in dataset.column_names():
        raise _ToolkitError("Audio feature column '%s' does not exist" % feature)
    if not _is_deep_feature_sarray(dataset[feature]) and not _is_audio_data_sarray(dataset[feature]):
        raise _ToolkitError("'%s' column is not audio data." % feature)
    if target not in dataset.column_names():
        raise _ToolkitError("Target column '%s' does not exist" % target)
    if not _tc.util._is_non_string_iterable(custom_layer_sizes) or len(custom_layer_sizes) == 0:
        raise _ToolkitError("'custom_layer_sizes' must be a non-empty list.")
    for i in custom_layer_sizes:
        if not isinstance(i, int):
            raise _ToolkitError("'custom_layer_sizes' must contain only integers.")
    if not (isinstance(validation_set, _tc.SFrame) or validation_set == 'auto' or validation_set is None):
        raise TypeError("Unrecognized value for 'validation_set'")
    if isinstance(validation_set, _tc.SFrame):
        if feature not in validation_set.column_names() or target not in validation_set.column_names():
            raise ValueError("The 'validation_set' SFrame must be in the same format as the 'dataset'")
    if batch_size < 1:
        raise ValueError('\'batch_size\' must be greater than or equal to 1')

    classes = list(dataset[target].unique().sort())
    num_labels = len(classes)
    feature_extractor_name = 'VGGish'
    feature_extractor = _get_feature_extractor(feature_extractor_name)
    class_label_to_id = {l: i for i, l in enumerate(classes)}

    # create the validation set
    if not isinstance(validation_set, _tc.SFrame) and validation_set == 'auto':
        if len(dataset) >= 100:
            print ( "Creating a validation set from 5 percent of training data. This may take a while.\n"
                    "\tYou can set ``validation_set=None`` to disable validation tracking.\n")
            dataset, validation_set = dataset.random_split(0.95, exact=True)
        else:
            validation_set = None

    encoded_target = dataset[target].apply(lambda x: class_label_to_id[x])

    if _is_deep_feature_sarray(dataset[feature]):
        train_deep_features = dataset[feature]
    else:
        # do the preprocess and VGGish feature extraction
        train_deep_features = get_deep_features(dataset[feature], verbose=verbose)

    train_data = _tc.SFrame({'deep features': train_deep_features, 'labels': encoded_target})
    train_data = train_data.stack('deep features', new_column_name='deep features')
    train_data, missing_ids = train_data.dropna_split(columns=['deep features'])

    if len(missing_ids) > 0:
        _logging.warning("Dropping %d examples which are less than 975ms in length." % len(missing_ids))

    if validation_set is not None:
        if verbose:
            print("Preparing validataion set")
        validation_encoded_target = validation_set[target].apply(lambda x: class_label_to_id[x])

        if _is_deep_feature_sarray(validation_set[feature]):
            validation_deep_features = validation_set[feature]
        else:
            validation_deep_features = get_deep_features(validation_set[feature], verbose=verbose)

        validation_data = _tc.SFrame({'deep features': validation_deep_features, 'labels': validation_encoded_target})
        validation_data = validation_data.stack('deep features', new_column_name='deep features')
        validation_data = validation_data.dropna(columns=['deep features'])

        validation_batch_size = min(len(validation_data), batch_size)
        validation_data = mx.io.NDArrayIter(validation_data['deep features'].to_numpy(),
                                             label=validation_data['labels'].to_numpy(),
                                             batch_size=validation_batch_size)
    else:
        validation_data = []

    if verbose:
        print("\nTraining a custom neural network -")

    training_batch_size = min(len(train_data), batch_size)
    train_data = mx.io.NDArrayIter(train_data['deep features'].to_numpy(),
                                    label=train_data['labels'].to_numpy(),
                                    batch_size=training_batch_size, shuffle=True)

    custom_NN = SoundClassifier._build_custom_neural_network(feature_extractor.output_length, num_labels, custom_layer_sizes)
    ctx = _mxnet_utils.get_mxnet_context()
    custom_NN.initialize(mx.init.Xavier(), ctx=ctx)

    trainer = mx.gluon.Trainer(custom_NN.collect_params(), 'nag', {'learning_rate': 0.01, 'momentum': 0.9})

    if verbose:
        # Setup progress table
        row_ids = ['epoch', 'train_accuracy', 'time']
        row_display_names = ['Epoch', 'Training Accuracy (%)', 'Elapsed Time (seconds)']
        if validation_data:
            row_ids.insert(2, 'validation_accuracy')
            row_display_names.insert(2, 'Validation Accuracy (%)')
        table_printer = _tc.util._ProgressTablePrinter(row_ids, row_display_names)

    train_metric = mx.metric.Accuracy()
    if validation_data:
        validation_metric = mx.metric.Accuracy()
    softmax_cross_entropy_loss = mx.gluon.loss.SoftmaxCrossEntropyLoss()
    for i in range(max_iterations):
        # TODO: early stopping

        for batch in train_data:
            data = mx.gluon.utils.split_and_load(batch.data[0], ctx_list=ctx, batch_axis=0, even_split=False)
            label = mx.gluon.utils.split_and_load(batch.label[0], ctx_list=ctx, batch_axis=0, even_split=False)

            # Inside training scope
            with mx.autograd.record():
                for x, y in zip(data, label):
                    z = custom_NN(x)
                    # Computes softmax cross entropy loss.
                    loss = softmax_cross_entropy_loss(z, y)
                    # Backpropagate the error for one iteration.
                    loss.backward()
            # Make one step of parameter update. Trainer needs to know the
            # batch size of data to normalize the gradient by 1/batch_size.
            trainer.step(batch.data[0].shape[0])
        train_data.reset()

        # Calculate training metric
        for batch in train_data:
            data = mx.gluon.utils.split_and_load(batch.data[0], ctx_list=ctx, batch_axis=0, even_split=False)
            label = mx.gluon.utils.split_and_load(batch.label[0], ctx_list=ctx, batch_axis=0, even_split=False)
            outputs = [custom_NN(x) for x in data]
            train_metric.update(label, outputs)
        train_data.reset()

        # Calculate validataion metric
        for batch in validation_data:
            data = mx.gluon.utils.split_and_load(batch.data[0], ctx_list=ctx, batch_axis=0, even_split=False)
            label = mx.gluon.utils.split_and_load(batch.label[0], ctx_list=ctx, batch_axis=0, even_split=False)
            outputs = [custom_NN(x) for x in data]
            validation_metric.update(label, outputs)

        # Get metrics, print progress table
        _, train_accuracy = train_metric.get()
        train_metric.reset()
        printed_row_values = {'epoch': i, 'train_accuracy': train_accuracy}
        if validation_data:
            _, validataion_accuracy = validation_metric.get()
            printed_row_values['validation_accuracy'] = validataion_accuracy
            validation_metric.reset()
            validation_data.reset()
        if verbose:
            printed_row_values['time'] = time.time()-start_time
            table_printer.print_row(**printed_row_values)


    state = {
        '_class_label_to_id': class_label_to_id,
        '_custom_classifier': custom_NN,
        '_feature_extractor': feature_extractor,
        '_id_to_class_label': {v: k for k, v in class_label_to_id.items()},
        'classes': classes,
        'custom_layer_sizes': custom_layer_sizes,
        'feature': feature,
        'feature_extractor_name': feature_extractor.name,
        'num_classes': num_labels,
        'num_examples': len(dataset),
        'target': target,
        'training_accuracy': train_accuracy,
        'training_time': time.time() - start_time,
        'validation_accuracy': validataion_accuracy if validation_data else None,
    }
    return SoundClassifier(state)