Skip to content

Instantly share code, notes, and snippets.

@lbourbon
Created February 2, 2018 21:41
Show Gist options
  • Save lbourbon/d111845652fba8e118c46149d6cf5c0c to your computer and use it in GitHub Desktop.
Save lbourbon/d111845652fba8e118c46149d6cf5c0c to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Agora nosso objetivo é automatizar o reconhecimento de caracteres numéricos. <br><br> Para isso, usaremos uma técnica chamada Deep Learning (aprendizado profundo).<br><br>Atualmente, a biblioteca mais popular de Deep Learning é o Tensorflow, desenvolvido pelo Google\n",
"Obs: Entender o funcionamento de uma rede neural é pré-requisito para entender esse algoritmo."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Começamos importando as bibliotecas necessárias:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"C:\\Users\\lbour\\Anaconda3\\lib\\site-packages\\h5py\\__init__.py:34: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.\n",
" from ._conv import register_converters as _register_converters\n"
]
}
],
"source": [
"import numpy as np\n",
"import tensorflow as tf\n",
"import matplotlib.pyplot as plt\n",
"from tensorflow.examples.tutorials.mnist import input_data \n",
"%matplotlib inline"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Usaremos o famoso dataset MNIST que contém imagens digitalizadas de números manuscritos que serão usadas para treinar nosso modelo. <br>Um 'label' está associada a cada imagem do dataset. Usaremos esse label ou etiqueta no formato 'one_hot' que poderia ser interpretada como<br> uma lista contendo a informação sobre qual número aquela \n",
"imagem corresponde. <br>A lista contém 10 elementos, sendo 9 'zeros' e 1 'um'. O índice correspondente ao elemento 1\n",
"é o dígito que está contido na imagem.<br>\n",
"Exemplos:<br><center>\n",
" 0 ==> [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]<br>\n",
" 3 ==> [0, 0, 0, 1, 0, 0, 0, 0, 0, 0]<br>\n",
" 9 ==> [0, 0, 0, 0, 0, 0, 0, 0, 0, 1]<br>"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Extracting .\\train-images-idx3-ubyte.gz\n",
"Extracting .\\train-labels-idx1-ubyte.gz\n",
"Extracting .\\t10k-images-idx3-ubyte.gz\n",
"Extracting .\\t10k-labels-idx1-ubyte.gz\n"
]
}
],
"source": [
"mnist = input_data.read_data_sets(\".\", one_hot = True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Cada amostra de input é uma figura de 28x28 pixels com o desenho manuscrito de um dígito. Pegaremos um exemplo do dataset, para fornecer uma amostra do input"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<matplotlib.text.Text at 0x19aeca8c4e0>"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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\nL3BvzWpnb2dExLg+L6+r4f/czqWBiKhV1kBCr/bWq32Be2tWVb35Zb9Zphx+s0xVHf7+ivef0qu9\n9Wpf4N6aVUlvlb7nN7PqVH3kN7OKOPxmmaok/JIulLRL0puSbqqih0YkDUp6RdJ2SZXOJ17Mgbhf\n0o5RyyZJelrSG8X3MedIrKi32yTtKZ677ZIurqi30yX9i6TXJL0q6YZieaXPXaKvSp63rr/nl3QM\n8J/AbwPvANuAKyLita420oCkQaAWEZVfECJpHnAQeDgifr1Y9ufAexGxoviP86SI+LMe6e024GDV\n07YXs0lNHT2tPHAJ8AdU+Nwl+rqMCp63Ko785wJvRsRbEfEL4MfAggr66HkRsQV477DFC4BVxeNV\n1P/xdF2D3npCROyNiJeKxweAQ9PKV/rcJfqqRBXhPxV4e9TP71DhEzCGAJ6R9KKkxVU3M4YpEbG3\neLwPmFJlM2Monba9mw6bVr5nnrtmprtvN5/w+7zzI2I2cBFwbfHytidF/T1bL43Vjmva9m4ZY1r5\nT1X53DU73X27VRH+PcDpo34+rVjWEyJiT/F9P7CO3pt6/N1DMyQX3/dX3M+nemna9rGmlacHnrte\nmu6+ivBvA86S9DVJXwQuB56ooI/PkXRCcSIGSScA36H3ph5/AlhUPF4EbKiwl8/olWnbG00rT8XP\nXc9Ndx8RXf8CLqZ+xv+/gFuq6KFBX2cCPyu+Xq26N+Bx6i8Df0n93MjVwMnAZuAN4BlgUg/1thp4\nBXiZetCmVtTb+dRf0r8MbC++Lq76uUv0Vcnz5st7zTLlE35mmXL4zTLl8JtlyuE3y5TDb5Yph98s\nUw6/Wab+H3chFIrMeat8AAAAAElFTkSuQmCC\n",
"text/plain": [
"<matplotlib.figure.Figure at 0x19aeaa197f0>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"exemplo = mnist.train.images[1].reshape(28,28)\n",
"plt.imshow(exemplo, cmap='gray_r')\n",
"plt.title('Label: {}'.format(np.argmax(mnist.train.labels[1])))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Começamos a nos concentrar na elaboração do modelo. O primeiro passo é definir os parâmetros:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"taxa_de_aprendizagem = .01 # velocidade com que o nosso algoritmo vai aprender, normalmente são usados valores baixos já que\n",
" # faremos várias iterações no modelo. Valores altos frequentemente levam a piores resultados\n",
"\n",
"epocas = 200 # quantidade de iterações\n",
"lote = 100 # dividimos nosso dataset em 'x' lotes para que nosso computador consiga dar conta do processamento\n",
"exibicao = 10 # exibir informações a cada 'x' épocas\n",
"\n",
"n_input = 784 # tamanho do input: 28 * 28\n",
"n_classes = 10 # número de classes possíveis: de 0 a 9\n",
"\n",
"n_camada_oculta = 256 # quantidade de neurônios da camada oculta"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Iniciamos pesos e viéses do modelo com valores aleatórios entre -1 e 1"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"pesos_camada_oculta = tf.Variable(tf.random_normal([n_input, n_camada_oculta]))\n",
"pesos_output = tf.Variable(tf.random_normal([n_camada_oculta, n_classes]))\n",
"\n",
"vies_camada_oculta = tf.Variable(tf.random_normal([n_camada_oculta]))\n",
"vies_output = tf.Variable(tf.random_normal([n_classes]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Placeholder é uma variável que iremos atribuir dado em algum momento posterior. Isso permite que possamos criar nossa operações e construir nosso grafo sem precisar dos dados a priori"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"x = tf.placeholder(tf.float32, [None, 784])\n",
"y = tf.placeholder(tf.float32, [None, n_classes])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Realizamos as operações necessárias e utilizamos a função de ativação Relu\n",
"\n",
"Relu é uma função que retorna 0 para valores negativos e o próprio valor para números não negativos. Exemplo:\n",
"<code><center>\n",
"def relu(x):\n",
" if x < 0:\n",
" return 0\n",
"else:\n",
" return x"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"camada_oculta = tf.add(tf.matmul(x, pesos_camada_oculta), vies_camada_oculta)\n",
"camada_oculta = tf.nn.relu(camada_oculta)\n",
"\n",
"logits = tf.add(tf.matmul(camada_oculta, pesos_output), vies_output)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Definir a perda (ou custo) e o otimizador (Gradiente Descendente)"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"perda = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=y))\n",
"\n",
"otimizador = tf.train.GradientDescentOptimizer(learning_rate=taxa_de_aprendizagem).minimize(perda)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### INICIAR A SESSÃO E CALCULAR A ACURÁCIA DAS NOSSA PREVIÇÕES\n"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Época: 0001 custo= 5.352\n",
"Época: 0011 custo= 2.775\n",
"Época: 0021 custo= 2.158\n",
"Época: 0031 custo= 1.287\n",
"Época: 0041 custo= 0.218\n",
"Época: 0051 custo= 0.591\n",
"Época: 0061 custo= 0.311\n",
"Época: 0071 custo= 0.142\n",
"Época: 0081 custo= 0.518\n",
"Época: 0091 custo= 0.006\n",
"Época: 0101 custo= 0.009\n",
"Época: 0111 custo= 0.011\n",
"Época: 0121 custo= 0.658\n",
"Época: 0131 custo= 0.006\n",
"Época: 0141 custo= 0.075\n",
"Época: 0151 custo= 0.109\n",
"Época: 0161 custo= 0.011\n",
"Época: 0171 custo= 0.003\n",
"Época: 0181 custo= 0.009\n",
"Época: 0191 custo= 0.023\n",
"\n",
" ACURÁCIA ==> 0.9375\n"
]
}
],
"source": [
"init = tf.global_variables_initializer() # iniciailiza as variáveis globais\n",
"\n",
"\n",
"with tf.Session() as sess:\n",
" sess.run(init)\n",
" \n",
" ### CICLOS DE TREINAMENTO\n",
" for epoca in range(epocas):\n",
" lote_total = int(mnist.train.num_examples/lote)\n",
" \n",
" for i in range(lote_total):\n",
" lote_x, lote_y = mnist.train.next_batch(lote)\n",
" \n",
" # Executar otimização e calcular perda\n",
" sess.run(otimizador, feed_dict={x: lote_x, y:lote_y})\n",
" \n",
" if epoca % exibicao == 0:\n",
" c = sess.run(perda, feed_dict={x: lote_x, y: lote_y})\n",
" print(\"Época:\", '%04d' % (epoca+1), \"custo=\", \\\n",
" \"{:.3f}\".format(c))\n",
" \n",
" ### TESTAR O MODELO\n",
" previsoes_corretas = tf.equal(tf.argmax(logits, 1), tf.argmax(y, 1))\n",
"\n",
" ### CALCULAR ACURÁCIA\n",
" acuracia = tf.reduce_mean(tf.cast(previsoes_corretas, \"float\"))\n",
"\n",
" test_size = 256\n",
" print(\"\\n ACURÁCIA ==> \", acuracia.eval({x: mnist.test.images[:test_size], y: mnist.test.labels[:test_size]}))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<b>FONTES: <br>\n",
" - https://www.tensorflow.org/get_started/mnist/beginners)\n",
" - https://www.oreilly.com/learning/not-another-mnist-tutorial-with-tensorflow\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.1"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment