def evaluate_class_label_decoder(args, session, tensors, data):
    """ Evaluate class label decoder from loaded model.

    Args:
        args: Arguments from parser in train_grocerystore.py.
        session: Tensorflow session. 
        tensors: Tensors for prediction with class label decoder.
        data: The dataset for evaluation.

    Returns:
        The fine-grained accuracy, coarse-grained accuracy, and 
        the predicted labels for each sample. 

    """
    model_type = args.model_name.split('_')[0] # vcca or splitae
    # Get tensors
    x_tensor = tensors['x']
    labels_tensor = tensors['labels']
    scores_tensor = tensors['scores']
    accuracy_tensor = tensors['accuracy']

    # Get data
    features = data['features']
    labels = data['labels']
    n_classes = data['n_classes']
    finegrained_to_coarse = data['finegrained_to_coarse']
    n_coarse_classes = data['n_coarse_classes']
    coarse_labels = data['coarse_labels']

    batch_size = args.batch_size
    K = args.K

    n_examples = len(features)
    n_batches = int(np.ceil(n_examples/batch_size))
    predicted_labels = np.zeros(n_examples)
    accuracy = 0.

    for i in range(n_batches):
        start = i * batch_size
        end = start + batch_size
        if end > n_examples:
            end = n_examples

        x_batch = features[start:end]
        labels_batch = onehot_encode(labels[start:end], n_classes)
        feed_dict = {x_tensor: x_batch, labels_tensor: labels_batch}
        if model_type == 'vcca': # Add posterior samples K
            feed_dict[tensors['posterior_samples']] = K

        acc, predicted = session.run([accuracy_tensor, scores_tensor], feed_dict=feed_dict)
        accuracy += np.sum(acc)
        predicted_labels[start:end] = np.argmax(predicted, axis=1)
    accuracy = accuracy / n_batches

    predicted_coarse_labels = np.array([finegrained_to_coarse[c] for c in predicted_labels])
    accuracy_coarse = compute_accuracy(coarse_labels, predicted_coarse_labels)
    
    print("Accuracy: {:.3f} Coarse Accuracy: {:.3f}".format(accuracy, accuracy_coarse))
    return accuracy, accuracy_coarse, predicted_labels
def run_training_epoch(args, data, model, hyperparams, session, train_op=None, shuffle=False, mode='train'):
    """ Execute training epoch for Autoencoder.

    Args:
        args: Arguments from parser in train_grocerystore.py.
        data: Data used during epoch.
        model: Model used during epoch.
        hyperparams: Hyperparameters for training.
        session: Tensorflow session. 
        train_op: Op for computing gradients and updating parameters in model.
        shuffle: For shuffling data before epoch.
        mode: Training/validation mode.

    Returns:
        Metrics in python dictionary.

    """
    # Hyperparameters
    batch_size = hyperparams['batch_size']
    dropout_rate = hyperparams['dropout_rate']
    kl_weight = hyperparams['kl_weight']
    is_training = hyperparams['is_training']

    # Data
    features = data['features']
    labels = data['labels']
    captions = data['captions']
    iconic_image_path = data['iconic_image_paths']
    n_classes = data['n_classes']

    n_examples = len(features)
    n_batches = int(np.ceil(n_examples/batch_size))

    if shuffle:
        perm = np.random.permutation(n_examples)
        features = features[perm]
        iconic_image_path = iconic_image_path[perm]
        labels = labels[perm]
        
    total_loss = 0.
    x_loss = 0.
    i_loss = 0.
    w_loss = 0.
    clf_loss = 0.
    accuracy = 0.
    
    for i in range(n_batches):
        start = i * batch_size
        end = start + batch_size
        if end > n_examples:
            end = n_examples

        # Prepare batch and hyperparameters 
        x_batch = features[start:end]
        i_batch = load_iconic_images(iconic_image_path[start:end])
        captions_batch = load_captions(captions, labels[start:end])
        labels_batch = onehot_encode(labels[start:end], n_classes)
        feed_dict={model.x: x_batch, model.iconic_images: i_batch, model.captions: captions_batch, model.labels: labels_batch,
                    model.dropout_rate: dropout_rate, model.is_training: is_training}

        if mode == 'train':
            # Training step
            train_step_results = session.run([train_op] + model.log_var, feed_dict=feed_dict) 
            total_loss += train_step_results[1]
            x_loss += np.sum(train_step_results[2])
            i_loss += np.sum(train_step_results[3])
            w_loss += np.sum(train_step_results[4])
            clf_loss += np.sum(train_step_results[5])
            accuracy += np.sum(train_step_results[6])

        elif mode == 'val':
            # Validation step
            val_step_results = session.run(model.val_log_var, feed_dict=feed_dict)
            total_loss += val_step_results[0]
            x_loss += np.sum(val_step_results[1])
            i_loss += np.sum(val_step_results[2])
            w_loss += np.sum(val_step_results[3])
            clf_loss += np.sum(val_step_results[4])
            accuracy += np.sum(val_step_results[5])

        else:
            raise ValueError("Argument \'mode\' %s doesn't exist!" %mode)

    # Epoch finished, return results. clf_loss and accuracy are zero if args.classifier_head is False
    results = {'total_loss': total_loss / n_batches, 'x_loss': x_loss / n_examples,
                'i_loss': i_loss / n_examples, 'w_loss': w_loss / n_examples, 
                'clf_loss': clf_loss / n_examples, 'accuracy': accuracy / n_batches}
    return results