Primeira vez aqui? Seja bem vindo e cheque o FAQ!
x

Como prever corretamente os dígitos da base de dados MNIST?

+1 voto
393 visitas
perguntada Jul 7, 2016 em Aprendizagem de Máquinas por Saulo (426 pontos)  

Implemente um perceptron multicamada com saída softmax para prever corretamente os dígitos corretos da base
http://yann.lecun.com/exdb/mnist/.
Lembre de usar regularização.

Compartilhe

2 Respostas

+1 voto
respondida Jul 7, 2016 por Saulo (426 pontos)  
editado Jul 7, 2016 por Saulo

A base de dados MNIST é uma base de dados contendo imagens de digitos de 0 a 9 escritos a mão. Ela é um subconjunto da base de dados NIST, e tem a grande vantagem dos dígitos já estarem normalizados em tamanho e centralizados em uma imagem 28x28.

Inicialmente, temos que baixar os dados de treinamento (arquivos train-images-idx3-ubyte.gz e train-labels-idx1-ubyte.gz) e de teste (arquivos t10k-images-idx3-ubyte.gz e t10k-labels-idx1-ubyte.gz), e descompactá-los.

Em seguida, vamos ler esses arquivos:

import os, struct

def load_mnist(dataset = "train", path = ".", digits=[]):

    if dataset is "train":
    fname_img = os.path.join(path, 'train-images.idx3-ubyte')
    fname_lbl = os.path.join(path, 'train-labels.idx1-ubyte')
    elif dataset is "test":
    fname_img = os.path.join(path, 't10k-images.idx3-ubyte')
    fname_lbl = os.path.join(path, 't10k-labels.idx1-ubyte')
    else:
    raise ValueError, "dataset must be 'train' or 'test'"

    from array import array as pyarray

    file_handle_lbl = open(fname_lbl, 'rb')
    magic_nr, size = struct.unpack(">II", file_handle_lbl.read(8))
    lbl = pyarray("b", file_handle_lbl.read())
    file_handle_lbl.close()

    file_handle_img = open(fname_img, 'rb')
    magic_nr, size, rows, cols = struct.unpack(">IIII", file_handle_img.read(16))
    img = pyarray("B", file_handle_img.read())
    file_handle_img.close()

    ind = [ k for k in xrange(size) if (len(digits)==0) or (len(digits)>0 and lbl[k] in digits) ]
    images =  np.zeros((len(ind), rows*cols))
    labels = np.zeros((len(ind), 1))
    for i in xrange(len(ind)):
    images[i, :] = img[ ind[i]*rows*cols : (ind[i]+1)*rows*cols ]
    labels[i] = lbl[ind[i]]

    return images, labels.ravel()

images_train, labels_as_digits_train = load_mnist('train', 'data')
images_train = np.multiply(images_train.astype(np.float), 1.0/255.0)
n_train = images_train.shape[0]

images_test, labels_as_digits_test = load_mnist('test', 'data')
images_test = np.multiply(images_test.astype(np.float), 1.0/255.0)
n_test = images_test.shape[0]

onde ''imagestrain'' é uma matriz \(N_{train} \times K\) com os valores dos pixels em escala de cinza normalizados entre 0 e 1, sendo \(N_{train}=60000\) a quantidade observações de treinamento e \(K= 28 \times 28 = 756\) a quantidade de entrada, e ''labelstrain'' é um vetor \(N_{train} \times 1\) contendo um dígito de 0 a 9 correspondente a cada observação. O mesmo vale para os dados de teste ''imagestest'' e ''labelstest'', mas com \(N_{test}=10000\) observações. No código acima, pressupõe-se que os arquivos de dados estão gravados no diretório "data".

Caso o leitor queira um exemplo de como plotar uma imagem, podemos fazer o seguinte para a imagem de índice 11 dos dados de teste:

import matplotlib.pyplot as plt
import matplotlib.cm as cm

image_size = images_test.shape[1]
image_width = image_height = np.ceil(np.sqrt(image_size)).astype(np.uint8)
def display(image, image_width, image_height, label):
    one_image = image.reshape(image_width,image_height)

    plt.axis('off')
    plt.title('Label: ' + label)
    plt.imshow(one_image, cmap=cm.binary)

IMAGE_TO_DISPLAY = 11
display(images_test[IMAGE_TO_DISPLAY], image_width, image_height, str(int(labels_as_digits_test[IMAGE_TO_DISPLAY])))

O próximo passo é transformar os dígitos de 0 a 9 em vetores \(y = (0, \ldots, 0, 1, 0, \ldots, 0)\), sendo que \(y\) é um vetor de zeros de tamanho 10 com a posição do dígito igual a 1. Por exemplo, o dígito 2 é representado pelo vetor \(y = (0, 0, 1, 0, 0, 0, 0, 0, 0, 0)\).

def digit_to_vector(labels_as_digits, num_classes):
    num_labels = labels_as_digits.shape[0]
    labels_as_vector = np.zeros((num_labels, num_classes))
    for k in range(num_labels):
    labels_as_vector[k, labels_as_digits[k]] = 1
    return labels_as_vector.astype(np.uint8)

labels_test = digit_to_vector(labels_as_digits_test, labels_count)
labels_train = digit_to_vector(labels_as_digits_train, labels_count)

Agora que temos os dados preparados, vamos partir para a solução do problema propriamente dita. Usaremos duas bibliotecas de redes neurais para fins de aprendizagem: pybrain e tensorflow. Em ambas, será aplicada a regularização \(L^2\).

A regularização \(L^2\), também chamada ''weight decay'', corresponde a adição do termo \( \frac{\lambda}{2} \| \mathbf{w} \|_2^2 \) na função custo, sendo \(\lambda \in [0, \infty)\) o parâmetro de regularização, que é uma penalização dada aos parâmetros do modelo. O referencial teórico sobre regularização pode ser obtido na seção 5.5 - "Regularization in Neural Networks" do livro "Pattern Recognition and Machine Learning" de Christopher M. Bishop, e no capítulo 7 - "Regularization for Deep Learning" do livro ainda não publicado "Deep Learning" de Ian Goodfellow, Yoshua Bengio e Aaron Courville.

Solução 1: Toolbox Pybrain

Inicialmente, vamos criar os datasets de treinamento e teste, que são classes específicas da biblioteca:

INPUT_LAYER_SIZE = 28*28
HIDDEN_LAYER_SIZE = 140
OUTPUT_LAYER_SIZE = 10

from pybrain.datasets import SupervisedDataSet
dataset_train = SupervisedDataSet(INPUT_LAYER_SIZE, OUTPUT_LAYER_SIZE)
dataset_train.setField('input', images_train)
dataset_train.setField('target', labels_train)

dataset_test = SupervisedDataSet(INPUT_LAYER_SIZE, OUTPUT_LAYER_SIZE)
dataset_test.setField('input', images_test)
dataset_test.setField('target', labels_test)

Em seguida, precisamos definir o modelo. Vamos usar uma camada escondida com 140 neurônios. Podemos criar a estrutura manualmente ou usar uma função pré-definida:

  • Modelo criando a estrutura de forma manual:

    from pybrain.structure import LinearLayer, SigmoidLayer
    from pybrain.structure.modules import SoftmaxLayer
    
    inLayer = LinearLayer(INPUT_LAYER_SIZE)
    hiddenLayer1 = SigmoidLayer(HIDDEN_LAYER_SIZE)
    outLayer = SoftmaxLayer(OUTPUT_LAYER_SIZE)
    
    from pybrain.structure import FeedForwardNetwork
    net = FeedForwardNetwork()
    net.addInputModule(inLayer)
    net.addModule(hiddenLayer1)
    net.addOutputModule(outLayer)
    
    from pybrain.structure import FullConnection
    theta1 = FullConnection(inLayer, hiddenLayer1)
    theta2 = FullConnection(hiddenLayer1, outLayer)
    
    net.addConnection(theta1)
    net.addConnection(theta2)
    
    net.sortModules()
    
  • Modelo criando a função pré-definida buildNetwork:

    from pybrain.structure import LinearLayer, SigmoidLayer
    from pybrain.structure.modules import SoftmaxLayer
    
    from pybrain.tools.shortcuts import buildNetwork
    net = buildNetwork(dataset_train.indim, HIDDEN_LAYER_SIZE, dataset_train.outdim, hiddenclass=SigmoidLayer, outclass=SoftmaxLayer)
    

Ambos modelos são equivalentes para uma camada escondida. Caso se deseje utilizar mais de uma camada escondida, deve-se optar pela primeira opção. Como vamos utilizar apenas uma camada escondida, vamos usar a segunda.

Agora vamos definir o método de treinamento. Usaremos a taxa de aprendizagem igual a 0.1, taxa de momento igual a 0 e parâmetro de regularização igual a \(1 \times 10^{-7}\).

Observação: O mesmo código apresentado nesta solução foi executado para uma lista de parâmetros de regularização. Foi gerado um gráfico relacionando os parâmetros de regularização e os erros de treinamento e teste:

A imagem será apresentada aqui.

A análise do gráfico sugere que o menor erro de ocorre em \(\lambda = 1 \times 10^{-7}\).

Finalmente, o treinamento da rede será dado por:

from pybrain.utilities import percentError
EPOCHS = 10

error_train, error_test = list(), list()
for i in range(EPOCHS):
    trainer.trainEpochs(1)
    y_train = net.activateOnDataset(dataset_train)
    percentError_train = percentError(y_train.argmax(axis=1), dataset_train['target'].argmax(axis=1))
    error_train.append(percentError_train)

    y_test = net.activateOnDataset(dataset_test)
    percentError_test = percentError(y_test.argmax(axis=1), dataset_test['target'].argmax(axis=1))
    error_test.append(percentError_test)

    print("Epoca: {:4d} | Treinamento - Erro / Acuracia: {:7.3f} / {:7.3f} | Teste - Erro / Acuracia: {:7.3f} / {:7.3f}".format(trainer.totalepochs, percentError_train, 100.0-percentError_train,percentError_test, 100.0-percentError_test))

fig, ax = plt.subplots()
line_train, = ax.plot(range(1,EPOCHS+1), error_train, 'bo-', label='Erro de treinamento')
line_test, = ax.plot(range(1,EPOCHS+1), error_test, 'ro-', label='Erro de teste')
legend = ax.legend(loc='upper center', shadow=True)
plt.axes().set_xlim(1, EPOCHS)
plt.xlabel('Epoca')
plt.ylabel('Erro')
plt.show()

A saída nos mostra que:

Epoca:1 | Treinamento - Erro / Acuracia:   6.440 /  93.560 | Teste - Erro / Acuracia:   7.050 /  92.950
Epoca:2 | Treinamento - Erro / Acuracia:   4.802 /  95.198 | Teste - Erro / Acuracia:   6.110 /  93.890
Epoca:3 | Treinamento - Erro / Acuracia:   5.528 /  94.472 | Teste - Erro / Acuracia:   6.070 /  93.930
Epoca:4 | Treinamento - Erro / Acuracia:   3.048 /  96.952 | Teste - Erro / Acuracia:   4.510 /  95.490
Epoca:5 | Treinamento - Erro / Acuracia:   2.610 /  97.390 | Teste - Erro / Acuracia:   4.430 /  95.570
Epoca:6 | Treinamento - Erro / Acuracia:   3.315 /  96.685 | Teste - Erro / Acuracia:   5.260 /  94.740
Epoca:7 | Treinamento - Erro / Acuracia:   2.140 /  97.860 | Teste - Erro / Acuracia:   3.940 /  96.060
Epoca:8 | Treinamento - Erro / Acuracia:   1.948 /  98.052 | Teste - Erro / Acuracia:   4.160 /  95.840
Epoca:9 | Treinamento - Erro / Acuracia:   1.830 /  98.170 | Teste - Erro / Acuracia:   4.020 /  95.980
Epoca:   10 | Treinamento - Erro / Acuracia:   1.353 /  98.647 | Teste - Erro / Acuracia:   3.410 /  96.590

O gráfico do erro em função da época é dado por:

A imagem será apresentada aqui.

Apesar do erro ainda não ter convergido completamente, o treinamento termina com um percentual de acerto de \(96.59\%\) nos dados de teste.

+1 voto
respondida Jul 7, 2016 por Saulo (426 pontos)  
editado Jul 7, 2016 por Saulo

Solução 2: Toolbox Tensorflow

A biblioteca tensorflow já foi objeto de estudo em respostas anteriores no Prorum.

O site do tensorflow já fornece alguns tutorais - muito bem escritos e explicativos - que resolvem o problema MNIST. O leitor menos experiente pode começar com MNIST For ML Beginners. Existem exemplos que utilizam redes de convolução, que são uma técnica de regularização chamada de compartilhamento de parâmetros (''parameter sharing''), como Deep MNIST for Experts e TensorFlow deep NN.

Nesta solução, vamos nos ater a apenas uma rede neural feed-foward com regularização \(L^2\). Mostraremos uma solução similar ao tutorial TensorFlow Mechanics 101, mas com algumas alterações para deixar este post um pouco mais interessante e educativo para o leitor. Por exemplo, fizemos a função de batch dos dados (para fazer o treinamento estocástico), ao invés de usar a já pronta implementada na classe para a base de dados MNIST do tensorflow.

O tensorflow já fornece rotinas específicas para usar a base de dados MNINT:

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

Porém, usaremos os mesmos dados já apresentados, pois desta forma teremos um código mais genérico.

Vamos usar uma rede com duas camadas escondidas, com 128 e 32 neurônios em cada, respectivamente. Os parâmetros estão definidos como constantes logo no início do código-fonte. A regularização L2 do tensorflow utiliza dois métodos principais: (1) l2regularizer, que é uma função que recebe como parâmetro os pesos e calcula norma; (2) applyregularization, que efetivamente faz o cálculo usando esta função sobre os pesos da rede. Os pesos podem ser passados como parâmetros ou podem ser obtidos diretamente da rede, que reconhece os pesos por meio da tag tf.GraphKeys.WEIGHTS. Por esta razão que associou-se os pesos da rede a esta tag no momento da criação do modelo (método "model"). No modelo em questão, temos três pesos: "hidden1/weights:0", "hidden2/weights:0" e "softmax_linear/weights:0". Podemos pegar esses pesos de qualquer uma das formas abaixo:

weights_list = [v for v in tf.all_variables() if "/weights:0" in v.name]

ou

weights_list = tf.get_collection(tf.GraphKeys.WEIGHTS)

e calcular a regularização L2 manualmente por meio da norma. Ou simplesmente usar a função "apply_regularization" sem passar nenhum parâmetro (ou passando todos os pesos).

O código-fonte é mostrado a seguir:

import time

from six.moves import xrange    # pylint: disable=redefined-builtin

import math

import tensorflow as tf

INPUT_SIZE = 28*28
OUTPUT_SIZE = 10

# Inicializacao de variaveis do Tensorflow
flags = tf.app.flags
FLAGS = flags.FLAGS
flags.DEFINE_float('learning_rate', 0.1, 'Taxa de aprendizado inicial.')
flags.DEFINE_integer('max_steps', 10000, 'Numero de passos no treinamento.')
flags.DEFINE_integer('hidden1', 128, 'Numero de neuronios na camada escondida 1.')
flags.DEFINE_integer('hidden2', 32, 'Numero de neuronios na camada escondida 2.')
flags.DEFINE_float('lambda_regularization', 1e-6, 'Parametro de regularizacao.')
flags.DEFINE_integer('batch_size', 100, 'Tamanho do batch.')
train_dir = '/home/saulo/Documentos/Saulo/UnB/Disciplinas/Metodos_Computacionais_em_Economia/aulas/Exercicios_Python/Aula24_Exercicio06/data'
flags.DEFINE_string('train_dir', train_dir, 'Diretorio para colocar os dados de treinamento.')
flags.DEFINE_boolean('fake_data', False, 'Se verdadeiro, usa dados falsos para teste de unidade.')

def create_initializers(batch_size):
    images_placeholder = tf.placeholder(tf.float32, shape=(batch_size, INPUT_SIZE))

    labels_placeholder = tf.placeholder(tf.float32, shape=(batch_size, OUTPUT_SIZE))

    return images_placeholder, labels_placeholder

def model(x, hidden1_units, hidden2_units):

    with tf.variable_scope('hidden1'):
        weights = tf.Variable(tf.truncated_normal([INPUT_SIZE, hidden1_units], stddev=1.0 / math.sqrt(float(INPUT_SIZE))), name='weights')
        biases = tf.Variable(tf.zeros([hidden1_units]), name='biases')
        hidden1 = tf.nn.relu(tf.matmul(x, weights) + biases)

        tf.add_to_collection(tf.GraphKeys.WEIGHTS, weights)

    with tf.variable_scope('hidden2'):
        weights = tf.Variable(tf.truncated_normal([hidden1_units, hidden2_units], stddev=1.0 / math.sqrt(float(hidden1_units))), name='weights')
        biases = tf.Variable(tf.zeros([hidden2_units]), name='biases')
        hidden2 = tf.nn.relu(tf.matmul(hidden1, weights) + biases)

        tf.add_to_collection(tf.GraphKeys.WEIGHTS, weights)

    with tf.variable_scope('softmax_linear'):
        weights = tf.Variable(tf.truncated_normal([hidden2_units, OUTPUT_SIZE], stddev=1.0 / math.sqrt(float(hidden2_units))), name='weights')
        biases = tf.Variable(tf.zeros([OUTPUT_SIZE]), name='biases')
        logits = tf.matmul(hidden2, weights) + biases

        tf.add_to_collection(tf.GraphKeys.WEIGHTS, weights)

    return logits

def loss(y_model, y_real, lambda_regularization):
    forma = 2

    if (forma == 1):
        cross_entropy = tf.nn.softmax_cross_entropy_with_logits(y_model, y_real, name='cross_entropy')
        loss = tf.reduce_mean(cross_entropy, name='xentropy_mean')

        l2penalty = tf.Variable(initial_value=0.0, dtype=tf.float32)
        weights_list = [v for v in tf.all_variables() if "/weights:0" in v.name]
        for weight in weights_list:
            l2penalty = tf.add(l2penalty, tf.nn.l2_loss(weight))
        l2penalty = tf.mul(lambda_regularization, l2penalty)

        return loss + l2penalty

    else:
        cross_entropy = tf.nn.softmax_cross_entropy_with_logits(y_model, y_real, name='cross_entropy')
        loss = tf.reduce_mean(cross_entropy, name='xentropy_mean')

        lambda_tensor = tf.convert_to_tensor(lambda_regularization, dtype=tf.float32)
        regularizer_op = tf.contrib.layers.l2_regularizer(lambda_tensor)

        #weights_list = [v for v in tf.all_variables() if "/weights:0" in v.name]
        #weights_list = tf.get_collection(tf.GraphKeys.WEIGHTS)
        #l2penalty = tf.contrib.layers.apply_regularization(regularizer_op, weights_list)

        l2penalty = tf.contrib.layers.apply_regularization(regularizer_op)

        return loss + l2penalty

def training(loss_op, learning_rate):
    tf.scalar_summary('loss', loss_op)

    optimizer = tf.train.GradientDescentOptimizer(learning_rate)

    global_step = tf.Variable(0, name='global_step', trainable=False)

    train_op = optimizer.minimize(loss_op, global_step=global_step)

    return train_op

def get_accuracy(y_model, y_real, summary_name):
    with tf.name_scope('accuracy'):
    with tf.name_scope('correct_prediction'):
        correct_prediction = tf.equal(tf.argmax(y_model, 1), tf.argmax(y_real, 1))
    with tf.name_scope('accuracy'):
        accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    tf.scalar_summary('accuracy_'+summary_name, accuracy)
    return accuracy

def get_correct_prediction_count(y_model, y_real, summary_name):
    with tf.name_scope('correct_prediction_count'):
    with tf.name_scope('correct_prediction'):
        correct_prediction = tf.equal(tf.argmax(y_model, 1), tf.argmax(y_real, 1))
    with tf.name_scope('correct_prediction_count'):
        correct_prediction_count = tf.reduce_sum(tf.cast(correct_prediction, tf.float32))
    return correct_prediction_count

epochs_completed = 0
index_in_epoch = 0
def next_batch(batch_size):
    global images_train
    global labels_train
    global index_in_epoch
    global epochs_completed

    start = index_in_epoch
    index_in_epoch += batch_size

    if index_in_epoch > n_train:
    epochs_completed += 1

    perm = np.arange(n_train)
    np.random.shuffle(perm)
    images_train = images_train[perm]
    labels_train = labels_train[perm]

    start = 0
    index_in_epoch = batch_size
    assert batch_size <= n_train
    end = index_in_epoch
    return images_train[start:end], labels_train[start:end]

def print_all_data_precision(sess, correct_prediction_count, images_placeholder, labels_placeholder, example, label, summary_name, session):
    true_count = 0  # Counts the number of correct predictions.
    num_examples = example.shape[0]
    steps_per_epoch = num_examples // FLAGS.batch_size
    num_examples = steps_per_epoch * FLAGS.batch_size
    for step in xrange(steps_per_epoch):
    images_batch, labels_batch = next_batch(FLAGS.batch_size)
    feed_dict={images_placeholder: images_batch, labels_placeholder: labels_batch}
    true_count += sess.run(correct_prediction_count, feed_dict=feed_dict)
    precision = true_count / num_examples
    print('Qtd exemplos: %d Qtd corretos: %d    Precisao: %.4f' %  (num_examples, true_count, precision))
    return precision

with tf.Session() as sess:
    images_placeholder, labels_placeholder = create_initializers(FLAGS.batch_size)

    y_model = model(images_placeholder, FLAGS.hidden1, FLAGS.hidden2)

    loss_op = loss(y_model, labels_placeholder, FLAGS.lambda_regularization)

    train_op = training(loss_op, FLAGS.learning_rate)

    accuracy = get_accuracy(y_model, labels_placeholder, 'train')
    correct_prediction_count_train = get_correct_prediction_count(y_model, labels_placeholder, 'train')
    correct_prediction_count_test = get_correct_prediction_count(y_model, labels_placeholder, 'test')

    summary_op = tf.merge_all_summaries()
    summary_writer = tf.train.SummaryWriter(FLAGS.train_dir, sess.graph)

    init = tf.initialize_all_variables()
    sess.run(init)

    for step in xrange(FLAGS.max_steps):
    start_time = time.time()

    images_batch, labels_batch = next_batch(FLAGS.batch_size)
    feed_dict={images_placeholder: images_batch, labels_placeholder: labels_batch}

    _, loss_value = sess.run([train_op, loss_op], feed_dict=feed_dict)

    duration = time.time() - start_time

    if step % 100 == 0:
        train_accuracy = accuracy.eval(feed_dict=feed_dict)
        print('Epoca %d: custo = %.6f (%.4f sec), acuracia treinamento batch %.6f' % (step, loss_value, duration, train_accuracy))

        if step % 500 == 0 or step == FLAGS.max_steps:
            print('Treinamento:')
            precision_train = print_all_data_precision(sess, correct_prediction_count_train, images_placeholder, labels_placeholder, images_train, labels_train, 'train_precision', sess)

            print('Teste:')
            precision_test = print_all_data_precision(sess, correct_prediction_count_test, images_placeholder, labels_placeholder, images_test, labels_test, 'test_precision', sess)

        summary_str = sess.run(summary_op, feed_dict=feed_dict)
        summary_writer.add_summary(summary_str, step)
        summary_writer.flush()

    summary_writer.close()

    print('Lambda: %.12f, Precisao treinamento: %.8f Precisao teste: %.8f' %  (FLAGS.lambda_regularization, 100.0*precision_train, 100.0*precision_test))
    print('Lambda: %.12f, Erro treinamento: %.8f Erro teste: %.8f' %  (FLAGS.lambda_regularization, 100.0*(1.0-precision_train), 100.0*(1.0-precision_test)))

Que resulta em uma acurácia de \(98.82\%\) ou um erro de \(1.18\%\). Este é um resultado razoável, quando comparado com alguns benchmakrs.

Para ver o gráfico gerado da variável de sumário "loss", basta digitar o seguinte no terminal:

tensorboard --logdir=<DIRETORIO_DADOS_SUMARIO> --debug

onde deve ser substituído pelo diretório onde se gravou os dados do sumário. Este programa vai inicializar o TensorBoard, cuja localização encontra-se nas primeiras linhas da saída deste programa. Tipicamente em http://0.0.0.0:6006. Basta abrir um navegador, digitar o link e visualizar as informações gravadas. O gráfico de "loss" é mostrado abaixo:

A imagem será apresentada aqui.

...