diff --git a/GDG_codelab_MNIST.ipynb b/GDG_codelab_MNIST.ipynb new file mode 100644 index 00000000..0a85f05d --- /dev/null +++ b/GDG_codelab_MNIST.ipynb @@ -0,0 +1,1856 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "name": "GDG_codelab_MNIST.ipynb", + "version": "0.3.2", + "provenance": [], + "collapsed_sections": [ + "S2ts4pD4o_LX" + ], + "toc_visible": true, + "include_colab_link": true + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "[View in Colaboratory](https://colab.research.google.com/github/webmalex/BuildingMachineLearningSystemsWithPython/blob/master/GDG_codelab_MNIST.ipynb)" + ] + }, + { + "metadata": { + "id": "N-w5Cf7Bo_IA", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "# 0 Введение" + ] + }, + { + "metadata": { + "id": "x7ieDStYo_IG", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Скрипя своим тщедушным телом,
\n", + "Чтобы добыть на ужин снедь,
\n", + "В предместьях рыбного отдела
\n", + "Мужик закинул нейросеть." + ] + }, + { + "metadata": { + "id": "gvTqF9_Fo_IJ", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "По мотивам [TensorFlow and depp learning without PhD](https://codelabs.developers.google.com/codelabs/cloud-tensorflow-mnist/#0)\n", + "\n", + "\"TensorFlow\n", + "\n", + "Дополнительно:
\n", + "[Get Started with TensorFlow](https://www.tensorflow.org/tutorials/) - доки по TF
\n", + "[TensorBoard Tutorial](https://www.datacamp.com/community/tutorials/tensorboard-tutorial) - годная статья" + ] + }, + { + "metadata": { + "id": "txGnghLpOqCN", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Ресурсы:\n", + "1. Sebastian Raschka, Vahid Mirjalili Python Machine Learning.\n", + "2. https://pprc.qmul.ac.uk/~bevan/statistics/TensorFlow.html - INTRODUCTION TO SOME FEATURES OF\n", + "TENSOR FLOW TO GET YOU STARTED\n" + ] + }, + { + "metadata": { + "id": "0Xx2jBbQo_IL", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "В этой работе мы научимся строить и тренировать нейронную сеть, которая распознает рукописные цифры. По мере улучшения точности предсказаний сети заодно познакомимся с инструментами для эффективного обучения моделей." + ] + }, + { + "metadata": { + "id": "zE3nQlM0o_IO", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Будем работать с набором данных [MNIST](http://yann.lecun.com/exdb/mnist/) - 60000 размеченных образцов рукописных цифр. Все цифры нормализованы по размеру (28х28)" + ] + }, + { + "metadata": { + "id": "e25dIhHbo_IR", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Что узнаем:\n", + " * Что такое нейронная сеть и как ее обучать\n", + " * Как создать базовую 1-слойную нейронную сеть с использованием TensorFlow\n", + " * Как добавить дополнительные слои\n", + " * Советы по обучению и трюки: переобучение, регуляризация, затухание скорости обучения и т.д.\n" + ] + }, + { + "metadata": { + "id": "E0NNltSlo_IT", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "## 0.1 Проброс TensorBoard\n", + "Некая магия, которая заставляет торчать наружу." + ] + }, + { + "metadata": { + "id": "CEaJqn5No_IU", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "### 0.1.1 Первый вариант\n", + "Если не сработает, идём в следующий" + ] + }, + { + "metadata": { + "id": "R2PeR8jOo_IV", + "colab_type": "code", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 65 + }, + "outputId": "5fbb279c-9571-4065-9605-ee28b9c4fb30" + }, + "cell_type": "code", + "source": [ + "LOG_DIR = 'summaries'\n", + "get_ipython().system_raw('tensorboard --logdir={} --host 0.0.0.0 --port 6006 &'.format(LOG_DIR))\n", + "\n", + "!wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip > /dev/null 2>&1\n", + "!unzip ngrok-stable-linux-amd64.zip > /dev/null 2>&1\n", + "\n", + "get_ipython().system_raw('./ngrok http 6006 &')\n", + "\n", + "!curl -s http://localhost:4040/api/tunnels | python3 -c \\\n", + " \"import sys, json; print(json.load(sys.stdin)['tunnels'][0]['public_url'])\"" + ], + "execution_count": 1, + "outputs": [ + { + "output_type": "stream", + "text": [ + "http://4731324f.ngrok.io/n" + ], + "name": "stdout" + } + ] + }, + { + "metadata": { + "id": "8RFtFF_LDosJ", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "### 0.1.2 Второй вариант\n", + "Если не сработает - уже ничего не поможет" + ] + }, + { + "metadata": { + "id": "Gn4-j2tko_Ig", + "colab_type": "code", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 71 + }, + "outputId": "4eeac377-6ab1-4af9-ef7f-20a276d90bc2" + }, + "cell_type": "code", + "source": [ + "#LOG_DIR = 'summaries'\n", + "#get_ipython().system_raw('tensorboard --logdir={} --host 0.0.0.0 --port 6006 &'.format(LOG_DIR))\n", + "\n", + "# Install\n", + "!npm install -g localtunnel\n", + "\n", + "# Tunnel port 6006 (TensorBoard assumed running)\n", + "get_ipython().system_raw('lt --port 6006 >> url.txt 2>&1 &')" + ], + "execution_count": 21, + "outputs": [ + { + "output_type": "stream", + "text": [ + "\u001b[K\u001b[?25h/tools/node/bin/lt -> /tools/node/lib/node_modules/localtunnel/bin/client\n", + "\u001b[K\u001b[?25h+ localtunnel@1.9.1\n", + "added 54 packages from 31 contributors in 3.3s\n" + ], + "name": "stdout" + } + ] + }, + { + "metadata": { + "id": "4xU99g_NwF_X", + "colab_type": "code", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 35 + }, + "outputId": "693218ef-4fd2-4fbb-90dc-658b5f1860be" + }, + "cell_type": "code", + "source": [ + "\n", + "# Get url\n", + "!cat url.txt" + ], + "execution_count": 22, + "outputs": [ + { + "output_type": "stream", + "text": [ + "your url is: https://empty-squid-5.localtunnel.me\n" + ], + "name": "stdout" + } + ] + }, + { + "metadata": { + "id": "j3ZDe2ieo_Io", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "## 0.2 Загрузка данных" + ] + }, + { + "metadata": { + "id": "_bbgdI9Wo_Ip", + "colab_type": "code", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 150 + }, + "outputId": "9e65444d-f4c5-4940-8e31-718eaf072df2" + }, + "cell_type": "code", + "source": [ + "#здесь классик для загрузки MNIST для автономной работы\n", + "!git clone https://github.com/ixomaxip/codelab.git" + ], + "execution_count": 2, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Cloning into 'codelab'...\n", + "remote: Enumerating objects: 6, done.\u001b[K\n", + "remote: Counting objects: 16% (1/6) \u001b[K\rremote: Counting objects: 33% (2/6) \u001b[K\rremote: Counting objects: 50% (3/6) \u001b[K\rremote: Counting objects: 66% (4/6) \u001b[K\rremote: Counting objects: 83% (5/6) \u001b[K\rremote: Counting objects: 100% (6/6) \u001b[K\rremote: Counting objects: 100% (6/6), done.\u001b[K\n", + "remote: Compressing objects: 25% (1/4) \u001b[K\rremote: Compressing objects: 50% (2/4) \u001b[K\rremote: Compressing objects: 75% (3/4) \u001b[K\rremote: Compressing objects: 100% (4/4) \u001b[K\rremote: Compressing objects: 100% (4/4), done.\u001b[K\n", + "remote: Total 6 (delta 0), reused 6 (delta 0), pack-reused 0\u001b[K\n", + "Unpacking objects: 16% (1/6) \rUnpacking objects: 33% (2/6) \rUnpacking objects: 50% (3/6) \rUnpacking objects: 66% (4/6) \rUnpacking objects: 83% (5/6) \rUnpacking objects: 100% (6/6) \rUnpacking objects: 100% (6/6), done.\n" + ], + "name": "stdout" + } + ] + }, + { + "metadata": { + "id": "XtBZGGNSo_It", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "import tensorflow as tf\n", + "import os\n", + "import numpy as np\n", + "import math\n", + "import matplotlib.pyplot as plt\n", + "from codelab.mnist_loader import read_data_sets" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "o-IIt_iko_Iy", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Загрузим данные и посмотрим, что получили:" + ] + }, + { + "metadata": { + "id": "WQlOVzAIo_I1", + "colab_type": "code", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 85 + }, + "outputId": "36bfe685-4497-4807-aa08-c98fe7075509" + }, + "cell_type": "code", + "source": [ + "mnist = read_data_sets(\"MNIST_data\", one_hot=True, reshape=True)" + ], + "execution_count": 4, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Successfully downloaded and unzipped train-images-idx3-ubyte.gz 9912422 bytes.\n", + "Successfully downloaded and unzipped train-labels-idx1-ubyte.gz 28881 bytes.\n", + "Successfully downloaded and unzipped t10k-images-idx3-ubyte.gz 1648877 bytes.\n", + "Successfully downloaded and unzipped t10k-labels-idx1-ubyte.gz 4542 bytes.\n" + ], + "name": "stdout" + } + ] + }, + { + "metadata": { + "id": "98BB-5uLo_I6", + "colab_type": "code", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 51 + }, + "outputId": "4d2af27a-7d84-4e93-a727-c1d27db1cb42" + }, + "cell_type": "code", + "source": [ + "print('Train images shape:\\t', mnist.train.images.shape)\n", + "print('Test images shape:\\t', mnist.test.images.shape)" + ], + "execution_count": 5, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Train images shape:\t (60000, 784)\n", + "Test images shape:\t (10000, 784)\n" + ], + "name": "stdout" + } + ] + }, + { + "metadata": { + "id": "Gw-cLY7Ho_I_", + "colab_type": "code", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 365 + }, + "outputId": "ab0be612-29fe-4e87-fd38-c325da35ea24" + }, + "cell_type": "code", + "source": [ + "flt = np.argmax(mnist.train.labels, axis=1) == 8\n", + "\n", + "X_train = mnist.train.images[flt][:25]\n", + "lab = mnist.train.labels[flt][:25]\n", + "\n", + "fig, ax = plt.subplots(nrows=5, ncols=5, sharex=True, sharey=True, figsize=(10, 5))\n", + "\n", + "ax = ax.flatten()\n", + "\n", + "for i in range(25):\n", + " img = X_train[i].reshape(28,28)\n", + " ax[i].imshow(img, cmap='Greys', interpolation='nearest')\n", + " ax[i].set_title('({}) label: {}'.format(i+1, lab[i].argmax()))\n", + "ax[0].set_xticks([])\n", + "ax[0].set_yticks([])\n", + "plt.tight_layout()\n", + "plt.show() " + ], + "execution_count": 7, + "outputs": [ + { + "output_type": "display_data", + "data": { + "image/png": "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\ndAGoXbs2AEeOHLFr4vPCfkwL0v7BP4ub33//HW9vbwAuX76slvfo0UMN0sypB3AUlOMqMWOfU6dO\nAZZBmfq5pnbt2nz//fcAVKxYMdNzUHYK0nEt7HHzT2NGnzPfbDarWUciIiLUzCS9e/dWD3ABcHJy\nsvrbVoXlmBquQQyW4ABL3vDy5cvVcj3nqn///jlfuVQK0nGVE47tLly4AFgmNE/9pEPdjBkzaNOm\nDYDd02gV9mNakPYPcjZuHqWCclwlZvKPgnRcC3vcSMzkvKyOqaRMCCGEEEIIQzPkiDH9dkGxYsVk\nLliRY/RbUmvWrFGP2hVCCCFE/ic9xEIIIYQQwtCkQSyEEEIIIQwty0F1QgghhBBCFHbSQyyEEEII\nIQxNGsRCCCGEEMLQpEEshBBCCCEMTRrEQgghhBDC0KRBLIQQQgghDE0axEIIIYQQwtCkQSyEEEII\nIQxNGsRCCCGEEMLQpEEshBBCCCEMTRrEQgghhBDC0KRBLIQQQgghDE0axEIIIYQQwtCkQSyEEEII\nIQytQDaI165dy+TJkwGIjIxk8ODBdOzYkR49enDs2DHMZjOvvvoqhw8fznQbHh4eREREZPk9U6ZM\nYenSpXbVLSgoiNdffz3bcqdOnaJ///54e3vTp08fDh48aNf3CPvoMRMcHIy3t7fVHw8PD+Lj4+nR\nowcXL17M8PPh4eHUrVs32+8ZMGAAW7dutatuixcvxt/fP9tyBw4coFevXnh7e+Pj48Pp06ft+h5h\nv9Tnmk2bNtGtWze6du3K4MGDuXLlCgkJCRI3wkrqmNmyZQsvvPAC7du3Z+LEidy/f19iRgDWcZKc\nnMycOXMybJesXLmSrl274uXlhb+/P/fv3ycyMhJvb2+ioqIy3HZISAhdunTJtg4dO3YkNDTUrnrb\n2i7K6HyZ72kFTFhYmNauXTstPj5e0zRNe/3117UvvvhC0zRNO3LkiDZmzBhN0zTtypUrWvv27bXE\nxMQMt+Pu7q7duHEjy++aPHmytmTJErvqt2nTJm3QoEFZljGbzVqbNm20AwcOaJqmab/99pvWuHFj\nLS4uzq7vErZJGzOp7dy5U/Pz89M0zRI/vXv31sxmc4bb8PT0zPa7fH19tS1btthVv8DAQG3atGlZ\nlomNjdUaNWqk/frrr5qmadrBgwe1tm3b2vU9wj6p4+bSpUta06ZNtYiICE3TNG3t2rWaj4+PpmkS\n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\n5ri4OPOVK1fMderUMZ85c8ZsNpvNy5cvN3fu3NlsNpvNN2/eNHt5eZmnTJlibt68ucV7rFmzxty/\nf/8033/fvn2p9k9L06ZNzQcOHMhQ20eOHGmeNWtWuvucPXvWXLduXfOVK1fMZrPZvHLlSrOfn1+G\nPkdYsjZmtmzZYm7Tpo05Li7OnJSUZB48eLD5P//5j9lslpgxGmtjZuPGjebOnTub4+PjzUlJSeYP\nP/zQPG3aNLPZLDFjRNbGje733383N2/e3Ny0aVO1LTg42Dx58uQ03z80NNTcrVu3J7ajQoUK5suX\nL2eo7V27djV///336e7z888/m1u0aGG+ffu22Ww2mz///HPzsGHDMvQ5wpK1MRMaGmoeOXJkmu8h\n55rMZxMjxAsXLqRjx444OzuTK1cugoKCKFeuHAC1a9fm7NmzAJhMJmbNmqWuvFNq164dx44d4+TJ\nk+l+Vnx8PIMHD6ZVq1Z4enoybdo0i+f37duHr68vjRs35osvvlDbIyIiaNu2Lc2aNaN79+7ExMSk\neu+goCBWrVqVavu5c+coVaoUHh4eALz++uucOXPmCUdFpMfamClbtiyBgYE4Ozvj4OBAzZo11bGX\nmDEWa2OmXLlyjB8/nnz58uHg4EDdunXV6IfEjPFYGzcAycnJjB8/nsGDB1u8hz5Se+PGjXQ/6/r1\n6/To0QMvLy88PT1ZsmSJxfObNm2ibdu2NGnShBUrVqjtq1evVq8ZOnQo9+/fT/XeAQEBbN++PdX2\n06dPU7VqVQoWLAhI3GSGjMTM48i5JvPZRIc4PDxcDf+7u7vz5ptvqud2795N9erVAXBxcaFMmTJp\nvkfu3Llp0qQJYWFh6X7WqlWruHv3LmFhYaxbt461a9daTCkcP36c0NBQ1q5dy6pVqzh58iQXL14k\nICCAoKAgtm3bRr169Rg/fnyq9x42bBhdunRJtb169epcuHCB06dPYzabCQ8Pp0GDBk88LuLxrI2Z\n8uXLU7Vq1TSfk5gxFmtjplKlSlSqVAmAuLg4wsLC1EW4xIzxWBs3AN988w0VKlSw2AZQqFAhqlWr\nlmaHNKU5c+ZQokQJwsLCCAkJISgoiMuXL6vn//77bzZu3MiiRYuYNm0aMTExREVFERwcTEhICNu3\nb8fZ2Zng4OBU7z19+vQ0B5Pq1q3LoUOHuHLlCg8ePGDr1q0SN88oIzHz+++/4+/vT6tWrRg9ejRx\n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\nCGFo/w+17dafh4nZLQAAAABJRU5ErkJggg==\n", + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [] + } + } + ] + }, + { + "metadata": { + "id": "hAGuqNlLo_JJ", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "## 0.3 Общие принципы TensorFlow:\n", + "1. Создаём пустой граф\n", + "2. Добавляеем узлы (тензоры и операции над ними) в граф\n", + "3. Выолняем граф:\n", + " 1. Запускаем сессию\n", + " 2. Инициализируем переменные в графе\n", + " 3. Запускаем вычислительный граф в этой сессии" + ] + }, + { + "metadata": { + "id": "86gzmYj2o_JK", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "# 1. Однослойная сеть" + ] + }, + { + "metadata": { + "id": "ZyEM80cdo_JM", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "\"\n"," + ] + }, + { + "metadata": { + "id": "50ID5ZFXo_JR", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + " Сначала определим переменные и метапеременные.\n", + " - Переменные - все те параметры, котрые мы планируем обучать в процессе выполнения алгоритма - веса и смещения.\n", + " - Метапеременные - параметры, которые будут заполнены конкретными данными во время обучения - входные данные.\n" + ] + }, + { + "metadata": { + "id": "vG8jFjLNo_JT", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "image_size = 28\n", + "n_classes = 10\n", + "\n", + "tf.reset_default_graph()\n", + "tf.set_random_seed(0)\n", + "\n", + "#мета для входных картинок\n", + "X = tf.placeholder(tf.float32, [None, image_size*image_size], name='train_inputs')\n", + "# мета для правильных ответов\n", + "Y_ = tf.placeholder(tf.float32, [None, 10], name='train_labels')" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "lVOZaPJ6o_JW", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Форма тензоров для метапеременных определяется размером картинок 28х28 для X и количеством классов (цифры от 0 до 9) для Y. None - эта размерность указывает на количество образцов в пакете данных." + ] + }, + { + "metadata": { + "id": "MEy1Njyno_JZ", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "## 1.1 Модель" + ] + }, + { + "metadata": { + "id": "jkQN5kbbo_Je", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "\"drawing\"" + ] + }, + { + "metadata": { + "id": "MwoiDFLvo_Jf", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "#картинка с нейроном\n", + "with tf.name_scope('out'):\n", + " with tf.name_scope('weights'):\n", + " weights = tf.Variable(tf.zeros([image_size*image_size, n_classes]))\n", + " with tf.name_scope('biases'):\n", + " biases = tf.Variable(tf.zeros([n_classes]))\n", + " with tf.name_scope('xw_plus_b'):\n", + " Y = tf.nn.softmax(tf.matmul(X, weights) + biases)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "8ri3QVszo_Jl", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Сначала задаём веса и смещения. Далее, суммируем с весами 28*28 входных нейронов, добавляем смещения и складываем в переменную - это предсказания сети:\n", + "\n", + "\"softmax\"\n", + "\n", + "\"softmax\"\n" + ] + }, + { + "metadata": { + "id": "JGDhtKpjo_Jn", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "## 1.2 Функция потерь" + ] + }, + { + "metadata": { + "id": "tHiGGLSJo_Jn", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "\"drawing\"" + ] + }, + { + "metadata": { + "id": "o5a2j9Ovo_Jp", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "with tf.name_scope('cross_entropy'):\n", + " with tf.name_scope('total'):\n", + " cross_entropy = - tf.reduce_sum(Y_ * tf.log(Y))" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "1W8tZ9ovo_Jr", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Теперь, когда мы знаем предсказания, нужно понять - насколько они хороши? Надо как-то измерить расстояние между правильными и предсказанными ответами. В качестве расстояния возьмём кросс-энтропию.\n", + "\n" + ] + }, + { + "metadata": { + "id": "ZROzSPwYo_Jw", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "## 1.3 Оптимизатор" + ] + }, + { + "metadata": { + "id": "_Mh8AEGVo_Jx", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "\"drawing\"" + ] + }, + { + "metadata": { + "id": "ibP4j3k3o_J1", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "learning_rate = 0.003\n", + "with tf.name_scope('train'):\n", + " optimizer = tf.train.GradientDescentOptimizer(learning_rate)\n", + " train_step = optimizer.minimize(cross_entropy)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "s53-MxK5o_J7", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Полученную метрику и будем минимизировать. Для этого используем стандартный градиентный спуск. Темп обучения задаёт шаг вдоль вектора градиента. Чтобы не перескочить минимум, берём маленький шаг." + ] + }, + { + "metadata": { + "id": "xNQmbW7oo_J9", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "## 1.4 Отчёты для TensorBoard" + ] + }, + { + "metadata": { + "id": "jekNNULHo_J-", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Чтобы получить графики для процесса обучения и результаты проверки на тестовых данных, создаём различные виды отчётов - summary. Те показатели, которые мы укажем сериализуюстся в процессе выполнения сессии и TensorBoard сможет их считать." + ] + }, + { + "metadata": { + "id": "lyhnJsUbo_KC", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "tf.summary.scalar('cross_entropy', cross_entropy)\n", + "\n", + "with tf.name_scope('accuracy'):\n", + " with tf.name_scope('correct_prediction'):\n", + " correct_prediction = tf.equal(tf.argmax(Y, axis=1), tf.argmax(Y_, axis=1))\n", + " with tf.name_scope('accuracy'):\n", + " accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))\n", + "tf.summary.scalar('accuracy', accuracy)\n", + "\n", + "merged = tf.summary.merge_all()" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "juSpTY0Ho_KL", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "## 1.5 Запускаем сеть" + ] + }, + { + "metadata": { + "id": "Ffq8YFDKo_KN", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "def get_summary_path(sub_folder):\n", + " import os\n", + " summ_folder = LOG_DIR#'summaries'\n", + " \n", + " summary_path = os.path.join(summ_folder, sub_folder)\n", + " if not os.path.exists(summ_folder):\n", + " os.mkdir(summ_folder)\n", + " if not os.path.exists(summary_path):\n", + " os.mkdir(summary_path)\n", + " \n", + " return summary_path" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "ET8f1YsBo_KR", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Всё, что мы до этого момента делали - лишь конструктор графа. Ничего на расчёт не запускалось. Время проверить, всё ли правильно было сделано.\n", + "\n", + "Первым делом надо объявить сессию!\n", + "\n", + "Задаём объекты для сериализации - отдельно для обучения, отдельно для теста.\n", + "\n", + "В цикле с помощью train_step, который минимизирует кросс-энтропию, обновляются веса и смещения и каждые 10 шагов замеряем точность на тестовых данных. Только замеряем!" + ] + }, + { + "metadata": { + "id": "UPU_V2ZLo_Kc", + "colab_type": "code", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 395 + }, + "outputId": "48637e47-2283-4fee-e7ef-a5eb9c6fed6d" + }, + "cell_type": "code", + "source": [ + "batch_size = 100\n", + "max_step = 2001\n", + "\n", + "session = tf.InteractiveSession()\n", + "\n", + "summary_path = get_summary_path('nn_1l')\n", + "\n", + "train_writer = tf.summary.FileWriter(summary_path + '/train', session.graph)\n", + "test_writer = tf.summary.FileWriter(summary_path + '/test')\n", + "\n", + "tf.global_variables_initializer().run()\n", + "\n", + "for i in range(max_step):\n", + " if i % 10 == 0:\n", + " xs, ys = mnist.test.images, mnist.test.labels\n", + " summary, acc = session.run([merged, accuracy], feed_dict={X: xs, Y_: ys})\n", + " test_writer.add_summary(summary, i)\n", + " if i % 100 == 0:\n", + " print('Accuracy at step %s: %s' % (i, acc))\n", + " else:\n", + " xs, ys = mnist.train.next_batch(batch_size)\n", + " summary, _ = session.run([merged, train_step], feed_dict={X: xs, Y_: ys})\n", + " train_writer.add_summary(summary, i)\n", + "\n", + "session.close()" + ], + "execution_count": 14, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Accuracy at step 0: 0.098\n", + "Accuracy at step 100: 0.8863\n", + "Accuracy at step 200: 0.9013\n", + "Accuracy at step 300: 0.9054\n", + "Accuracy at step 400: 0.9071\n", + "Accuracy at step 500: 0.9074\n", + "Accuracy at step 600: 0.9109\n", + "Accuracy at step 700: 0.9118\n", + "Accuracy at step 800: 0.9153\n", + "Accuracy at step 900: 0.9147\n", + "Accuracy at step 1000: 0.9147\n", + "Accuracy at step 1100: 0.9143\n", + "Accuracy at step 1200: 0.917\n", + "Accuracy at step 1300: 0.9177\n", + "Accuracy at step 1400: 0.9159\n", + "Accuracy at step 1500: 0.9194\n", + "Accuracy at step 1600: 0.9211\n", + "Accuracy at step 1700: 0.9203\n", + "Accuracy at step 1800: 0.9206\n", + "Accuracy at step 1900: 0.9215\n", + "Accuracy at step 2000: 0.9212\n" + ], + "name": "stdout" + } + ] + }, + { + "metadata": { + "id": "z4KzpHqVo_Kh", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "# 2. Добавим слои\n", + "Чтобы улучшить распознавательную способность сети, нам понадобится добавить скрытые слои.\n", + "\n", + "Дополнительно, некоторые вещи спихнём в функции, что заметно облегчит жизнь при модификации сети." + ] + }, + { + "metadata": { + "id": "5NvU6Aaoo_Ki", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "В качестве функций активации будем использовать классическую сигмоиду:\n", + "\n", + "\"drawing\"" + ] + }, + { + "metadata": { + "id": "S81dJik7o_Kj", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "image_size = 28\n", + "n_classes = 10\n", + "\n", + "tf.reset_default_graph() #сбросим граф\n", + "\n", + "#мета для входных картинок\n", + "X = tf.placeholder(tf.float32, [None, image_size*image_size], name='train_inputs')\n", + "# мета для правильных ответов\n", + "Y_ = tf.placeholder(tf.float32, [None, 10], name='train_labels')" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "LeF2UdIho_Kt", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "def variable_summaries(var):\n", + " with tf.name_scope('summaries'):\n", + " mean = tf.reduce_mean(var)\n", + " tf.summary.scalar('mean', mean)\n", + " with tf.name_scope('stddev'):\n", + " stddev = tf.sqrt(tf.reduce_mean(tf.square(var - mean)))\n", + " tf.summary.scalar('stddev', stddev)\n", + " tf.summary.scalar('max', tf.reduce_max(var))\n", + " tf.summary.scalar('min', tf.reduce_min(var))\n", + " tf.summary.histogram('histogram', var)\n", + "\n", + "def nn_layer(input_tensor, input_dim, output_dim, layer_name, activation):\n", + " with tf.name_scope(layer_name):\n", + " with tf.name_scope('weights'):\n", + " initial = tf.truncated_normal([input_dim, output_dim], stddev=0.1)\n", + " weights = tf.Variable(initial)\n", + " variable_summaries(weights)\n", + " with tf.name_scope('biases'):\n", + " initial = tf.truncated_normal([output_dim], stddev=0.1)\n", + " biases = tf.Variable(initial)\n", + " variable_summaries(biases)\n", + " with tf.name_scope('xw_plus_b'):\n", + " preactivate = tf.nn.xw_plus_b(input_tensor, weights, biases)\n", + " activations = activation(preactivate, name='activation')\n", + " \n", + " return activations " + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "nojI3FX6o_Kv", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "hidden1 = nn_layer(X, image_size*image_size, 200, 'hidden1', tf.nn.sigmoid)\n", + "hidden2 = nn_layer(hidden1, 200, 100, 'hidden2', tf.nn.sigmoid)\n", + "hidden3 = nn_layer(hidden2, 100, 60, 'hidden3', tf.nn.sigmoid)\n", + "hidden4 = nn_layer(hidden3, 60, 30, 'hidden4', tf.nn.sigmoid)\n", + "\n", + "Y = nn_layer(hidden4, 30, n_classes, 'out', tf.nn.softmax)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "XUNObazAo_K9", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "В качестве функций активации будем использовать классическую сигмоиду:\n", + "\n", + "\"drawing\"" + ] + }, + { + "metadata": { + "id": "XmCGJyIto_K-", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "#БЕЗ ИЗМЕНЕНИЙ\n", + "# функция потерь\n", + "with tf.name_scope('cross_entropy'):\n", + " with tf.name_scope('total'):\n", + " cross_entropy = - tf.reduce_sum(Y_ * tf.log(Y))" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "yIzsbYQ7o_LD", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "#БЕЗ ИЗМЕНЕНИЙ\n", + "# Optimizer\n", + "learning_rate = 0.003\n", + "with tf.name_scope('train'):\n", + " optimizer = tf.train.GradientDescentOptimizer(learning_rate)\n", + " train_step = optimizer.minimize(cross_entropy)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "Q8z2vCgio_LG", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "#БЕЗ ИЗМЕНЕНИЙ\n", + "tf.summary.scalar('cross_entropy', cross_entropy)\n", + "\n", + "with tf.name_scope('accuracy'):\n", + " with tf.name_scope('correct_prediction'):\n", + " correct_prediction = tf.equal(tf.argmax(Y, axis=1), tf.argmax(Y_, axis=1))\n", + " with tf.name_scope('accuracy'):\n", + " accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))\n", + "tf.summary.scalar('accuracy', accuracy)\n", + "\n", + "# Merge all summaries together\n", + "merged = tf.summary.merge_all()" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "uKjX7swGo_LM", + "colab_type": "code", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 451 + }, + "outputId": "61902a1c-0afc-4f60-b69b-9ce166f5f125" + }, + "cell_type": "code", + "source": [ + "max_step=2001\n", + "batch_size=100\n", + "\n", + "session = tf.InteractiveSession()\n", + "\n", + "summary_path = get_summary_path('nn_4hid')\n", + "\n", + "train_writer = tf.summary.FileWriter(summary_path + '/train', session.graph)\n", + "test_writer = tf.summary.FileWriter(summary_path + '/test')\n", + "\n", + "tf.global_variables_initializer().run()\n", + "\n", + "for i in range(max_step):\n", + " if i % 10 == 0:\n", + " xs, ys = mnist.test.images, mnist.test.labels\n", + " summary, acc = session.run([merged, accuracy], feed_dict={X: xs, Y_: ys})\n", + " test_writer.add_summary(summary, i)\n", + " if i % 100 == 0:\n", + " print('Accuracy at step %s: %s' % (i, acc))\n", + " else:\n", + " xs, ys = mnist.train.next_batch(batch_size)\n", + " summary, _ = session.run([merged, train_step], feed_dict={X: xs, Y_: ys})\n", + " train_writer.add_summary(summary, i)\n", + "\n", + "session.close()" + ], + "execution_count": 15, + "outputs": [ + { + "output_type": "stream", + "text": [ + "/usr/local/lib/python3.6/dist-packages/tensorflow/python/client/session.py:1662: UserWarning: An interactive session is already active. This can cause out-of-memory errors in some cases. You must explicitly call `InteractiveSession.close()` to release resources held by the other session(s).\n", + " warnings.warn('An interactive session is already active. This can '\n" + ], + "name": "stderr" + }, + { + "output_type": "stream", + "text": [ + "Accuracy at step 0: 0.098\n", + "Accuracy at step 100: 0.1032\n", + "Accuracy at step 200: 0.1032\n", + "Accuracy at step 300: 0.1135\n", + "Accuracy at step 400: 0.1937\n", + "Accuracy at step 500: 0.1135\n", + "Accuracy at step 600: 0.1135\n", + "Accuracy at step 700: 0.1135\n", + "Accuracy at step 800: 0.098\n", + "Accuracy at step 900: 0.1439\n", + "Accuracy at step 1000: 0.1135\n", + "Accuracy at step 1100: 0.1135\n", + "Accuracy at step 1200: 0.0958\n", + "Accuracy at step 1300: 0.1135\n", + "Accuracy at step 1400: 0.0974\n", + "Accuracy at step 1500: 0.1032\n", + "Accuracy at step 1600: 0.1028\n", + "Accuracy at step 1700: 0.0958\n", + "Accuracy at step 1800: 0.1966\n", + "Accuracy at step 1900: 0.2674\n", + "Accuracy at step 2000: 0.2894\n" + ], + "name": "stdout" + } + ] + }, + { + "metadata": { + "id": "S2ts4pD4o_LX", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "### упс! точность стала только хуже" + ] + }, + { + "metadata": { + "id": "82-jhqyBo_LX", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "# 3. Особенности глубоких сетей" + ] + }, + { + "metadata": { + "id": "qw7dGNgKo_LX", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "### 3.1 Поменяем сигмоиду на RuLU\n", + "\n", + "Сигмоида, конечно, хорошая, но даёт плохие результаты в глубоком обучении. Это связано с тем, что она всю числовую ось сжимает в отрезок от 0 до 1ю Следовательно, при достаточно большом абсолютном значении входа, она будет выдавать всегда что-то близкое к 0 или к 1, и часть нейронов просто обнулится.\n", + "\n", + "В современных сетях часто используют ReLU:
\n", + "\"drawing\"" + ] + }, + { + "metadata": { + "id": "enivQC_io_LZ", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "image_size = 28\n", + "n_classes = 10\n", + "\n", + "tf.reset_default_graph() #сбросим граф\n", + "\n", + "#мета для входных картинок\n", + "X = tf.placeholder(tf.float32, [None, image_size*image_size], name='train_inputs')\n", + "# мета для правильных ответов\n", + "Y_ = tf.placeholder(tf.float32, [None, 10], name='train_labels')" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "5TSVxIYQo_Lf", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "hidden1 = nn_layer(X, image_size*image_size, 200, 'hidden1', tf.nn.relu)\n", + "hidden2 = nn_layer(hidden1, 200, 100, 'hidden2', tf.nn.relu)\n", + "hidden3 = nn_layer(hidden2, 100, 60, 'hidden3', tf.nn.relu)\n", + "hidden4 = nn_layer(hidden3, 60, 30, 'hidden4', tf.nn.relu)\n", + "\n", + "Y = nn_layer(hidden4, 30, n_classes, 'out', tf.nn.softmax)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "H1xHcctDo_Lk", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "МЕНЯЕМ ТУТ" + ] + }, + { + "metadata": { + "id": "ltCxYZomo_Ll", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "#БЕЗ ИЗМЕНЕНИЙ\n", + "# функция потерь\n", + "with tf.name_scope('cross_entropy'):\n", + " with tf.name_scope('total'):\n", + " cross_entropy = - tf.reduce_sum(Y_ * tf.log(Y))\n", + " \n", + "#БЕЗ ИЗМЕНЕНИЙ\n", + "# Optimizer\n", + "learning_rate = 0.003\n", + "with tf.name_scope('train'):\n", + " optimizer = tf.train.GradientDescentOptimizer(learning_rate)\n", + " train_step = optimizer.minimize(cross_entropy)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "LUCwxpOno_Lx", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "#БЕЗ ИЗМЕНЕНИЙ\n", + "tf.summary.scalar('cross_entropy', cross_entropy)\n", + "\n", + "with tf.name_scope('accuracy'):\n", + " with tf.name_scope('correct_prediction'):\n", + " correct_prediction = tf.equal(tf.argmax(Y, axis=1), tf.argmax(Y_, axis=1))\n", + " with tf.name_scope('accuracy'):\n", + " accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))\n", + "tf.summary.scalar('accuracy', accuracy)\n", + "\n", + "# Merge all summaries together\n", + "merged = tf.summary.merge_all()" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "u9PCFQJro_L3", + "colab_type": "code", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 451 + }, + "outputId": "93b26907-c619-4e85-f7f7-59c0bb383161" + }, + "cell_type": "code", + "source": [ + "max_step=2001\n", + "batch_size=100\n", + "\n", + "session = tf.InteractiveSession()\n", + "\n", + "summary_path = get_summary_path('nn_4hid_ReLU')\n", + "\n", + "train_writer = tf.summary.FileWriter(summary_path + '/train', session.graph)\n", + "test_writer = tf.summary.FileWriter(summary_path + '/test')\n", + "\n", + "tf.global_variables_initializer().run()\n", + "\n", + "for i in range(max_step):\n", + " if i % 10 == 0:\n", + " xs, ys = mnist.test.images, mnist.test.labels\n", + " summary, acc = session.run([merged, accuracy], feed_dict={X: xs, Y_: ys})\n", + " test_writer.add_summary(summary, i)\n", + " if i % 100 == 0:\n", + " print('Accuracy at step %s: %s' % (i, acc))\n", + " else:\n", + " xs, ys = mnist.train.next_batch(batch_size)\n", + " summary, _ = session.run([merged, train_step], feed_dict={X: xs, Y_: ys})\n", + " train_writer.add_summary(summary, i)\n", + "\n", + "session.close()" + ], + "execution_count": 20, + "outputs": [ + { + "output_type": "stream", + "text": [ + "/usr/local/lib/python3.6/dist-packages/tensorflow/python/client/session.py:1662: UserWarning: An interactive session is already active. This can cause out-of-memory errors in some cases. You must explicitly call `InteractiveSession.close()` to release resources held by the other session(s).\n", + " warnings.warn('An interactive session is already active. This can '\n" + ], + "name": "stderr" + }, + { + "output_type": "stream", + "text": [ + "Accuracy at step 0: 0.0987\n", + "Accuracy at step 100: 0.8521\n", + "Accuracy at step 200: 0.9075\n", + "Accuracy at step 300: 0.9312\n", + "Accuracy at step 400: 0.9408\n", + "Accuracy at step 500: 0.9192\n", + "Accuracy at step 600: 0.943\n", + "Accuracy at step 700: 0.952\n", + "Accuracy at step 800: 0.9442\n", + "Accuracy at step 900: 0.9625\n", + "Accuracy at step 1000: 0.951\n", + "Accuracy at step 1100: 0.953\n", + "Accuracy at step 1200: 0.9655\n", + "Accuracy at step 1300: 0.9686\n", + "Accuracy at step 1400: 0.9575\n", + "Accuracy at step 1500: 0.966\n", + "Accuracy at step 1600: 0.9709\n", + "Accuracy at step 1700: 0.9557\n", + "Accuracy at step 1800: 0.9605\n", + "Accuracy at step 1900: 0.9667\n", + "Accuracy at step 2000: 0.9664\n" + ], + "name": "stdout" + } + ] + }, + { + "metadata": { + "id": "3iGaY3aCo_L6", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "### 3.2 Сменим оптимизатор\n", + "\n", + "В многомерных пространствах, как наше, частое явление - седловые точки - они не я вляются минимумами, но в них градиент равен 0. Поэтому градиентный спуск может в них засесть и дальше не двигаться. Поэтому заменим его на другой метод." + ] + }, + { + "metadata": { + "id": "oLEnrupxo_L7", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "image_size = 28\n", + "n_classes = 10\n", + "\n", + "tf.reset_default_graph() #сбросим граф\n", + "\n", + "#мета для входных картинок\n", + "X = tf.placeholder(tf.float32, [None, image_size*image_size], name='train_inputs')\n", + "# мета для правильных ответов\n", + "Y_ = tf.placeholder(tf.float32, [None, 10], name='train_labels')" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "tXbfm3iWo_L-", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "hidden1 = nn_layer(X, image_size*image_size, 200, 'hidden1', tf.nn.relu)\n", + "hidden2 = nn_layer(hidden1, 200, 100, 'hidden2', tf.nn.relu)\n", + "hidden3 = nn_layer(hidden2, 100, 60, 'hidden3', tf.nn.relu)\n", + "hidden4 = nn_layer(hidden3, 60, 30, 'hidden4', tf.nn.relu)\n", + "\n", + "Y = nn_layer(hidden4, 30, n_classes, 'out', tf.nn.softmax)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "b9GpYeuzo_ME", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "#БЕЗ ИЗМЕНЕНИЙ\n", + "# функция потерь\n", + "with tf.name_scope('cross_entropy'):\n", + " with tf.name_scope('total'):\n", + " cross_entropy = - tf.reduce_sum(Y_ * tf.log(Y))\n", + " #cross_entropy = tf.losses.softmax_cross_entropy(onehot_labels=Y_, logits=Y)\n", + " \n", + "# Optimizer\n", + "learning_rate = 0.003\n", + "with tf.name_scope('train'):\n", + " optimizer = tf.train.AdamOptimizer(learning_rate) #<-------------------\n", + " train_step = optimizer.minimize(cross_entropy)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "NVzETbtko_MJ", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "#БЕЗ ИЗМЕНЕНИЙ\n", + "tf.summary.scalar('cross_entropy', cross_entropy)\n", + "\n", + "with tf.name_scope('accuracy'):\n", + " with tf.name_scope('correct_prediction'):\n", + " correct_prediction = tf.equal(tf.argmax(Y, axis=1), tf.argmax(Y_, axis=1))\n", + " with tf.name_scope('accuracy'):\n", + " accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))\n", + "tf.summary.scalar('accuracy', accuracy)\n", + "\n", + "# Merge all summaries together\n", + "merged = tf.summary.merge_all()" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "kYBnsRUYo_ML", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "max_step=2001\n", + "batch_size=100\n", + "\n", + "session = tf.InteractiveSession()\n", + "\n", + "summary_path = get_summary_path('nn_4hid_ReLU_Adam')\n", + "\n", + "train_writer = tf.summary.FileWriter(summary_path + '/train', session.graph)\n", + "test_writer = tf.summary.FileWriter(summary_path + '/test')\n", + "\n", + "tf.global_variables_initializer().run()\n", + "\n", + "for i in range(max_step):\n", + " if i % 10 == 0:\n", + " xs, ys = mnist.test.images, mnist.test.labels\n", + " summary, acc = session.run([merged, accuracy], feed_dict={X: xs, Y_: ys})\n", + " test_writer.add_summary(summary, i)\n", + " if i % 100 == 0:\n", + " print('Accuracy at step %s: %s' % (i, acc))\n", + " else:\n", + " xs, ys = mnist.train.next_batch(batch_size)\n", + " summary, _ = session.run([merged, train_step], feed_dict={X: xs, Y_: ys})\n", + " train_writer.add_summary(summary, i)\n", + "\n", + "session.close()" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "KMWr2qoWo_MQ", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "# 4. Затухание темпа обучения\n", + "\n", + "Мы уже получаем довольно хорошую точность - 96%!\n", + "\n", + "Кривые зашумлены, скорее всего это означает что мы идём слишком большим шагом. Но мы не можем просто так его уменьшить, в этом случае обучение будет слишком долгим. Самое лучшее что можно сделать - начать с большого шага и постепенно его уменьшать." + ] + }, + { + "metadata": { + "id": "H2kLRXMro_MR", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "image_size = 28\n", + "n_classes = 10\n", + "\n", + "tf.reset_default_graph() #сбросим граф\n", + "\n", + "#мета для входных картинок\n", + "X = tf.placeholder(tf.float32, [None, image_size*image_size], name='train_inputs')\n", + "# мета для правильных ответов\n", + "Y_ = tf.placeholder(tf.float32, [None, 10], name='train_labels')\n", + "\n", + "hidden1 = nn_layer(X, image_size*image_size, 200, 'hidden1', tf.nn.relu)\n", + "hidden2 = nn_layer(hidden1, 200, 100, 'hidden2', tf.nn.relu)\n", + "hidden3 = nn_layer(hidden2, 100, 60, 'hidden3', tf.nn.relu)\n", + "hidden4 = nn_layer(hidden3, 60, 30, 'hidden4', tf.nn.relu)\n", + "\n", + "Y = nn_layer(hidden4, 30, n_classes, 'out', tf.identity)\n", + "\n", + "#БЕЗ ИЗМЕНЕНИЙ\n", + "# функция потерь\n", + "with tf.name_scope('cross_entropy'):\n", + " with tf.name_scope('total'):\n", + " #ТУТ НАДО ЗАМЕНИТЬ НА БИБЛИОТЕЧНУЮ. т.к. она корректно обрабатывает NaN.\n", + " #При этом, нужно будет на последнем слое softmax убрать\n", + " #cross_entropy = - tf.reduce_sum(Y_ * tf.log(Y)) \n", + " cross_entropy = tf.losses.softmax_cross_entropy(onehot_labels=Y_, logits=Y)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "a7VviSxEo_Mb", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "step = tf.placeholder(tf.int32)\n", + "learning_rate = 0.0001 + tf.train.exponential_decay(0.003, step, 2000, 1/math.e)\n", + "with tf.name_scope('train'):\n", + " optimizer = tf.train.AdamOptimizer(learning_rate)\n", + " train_step = optimizer.minimize(cross_entropy)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "8nBipySDo_Mf", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Чтобы уемньшать шаг в зависимости от итерации, нужно задать placeholder для передачи номера итерации во время вычисления графа. На основе этого шага и сформируем экспоненциально затухающий темп." + ] + }, + { + "metadata": { + "id": "2XNUWIUno_Mf", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "#БЕЗ ИЗМЕНЕНИЙ\n", + "tf.summary.scalar('cross_entropy', cross_entropy)\n", + "\n", + "with tf.name_scope('accuracy'):\n", + " with tf.name_scope('correct_prediction'):\n", + " correct_prediction = tf.equal(tf.argmax(Y, axis=1), tf.argmax(Y_, axis=1))\n", + " with tf.name_scope('accuracy'):\n", + " accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))\n", + "tf.summary.scalar('accuracy', accuracy)\n", + "\n", + "# Merge all summaries together\n", + "merged = tf.summary.merge_all()" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "lnxRXx0Go_Mh", + "colab_type": "code", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1817 + }, + "outputId": "f150ba32-f5bf-4d1b-9c09-6a5a5cbb59ca" + }, + "cell_type": "code", + "source": [ + "max_step=10000\n", + "batch_size=100\n", + "\n", + "session = tf.InteractiveSession()\n", + "\n", + "summary_path = get_summary_path('nn_4hid_ReLU_Adam_lrdecay')\n", + "\n", + "train_writer = tf.summary.FileWriter(summary_path + '/train', session.graph)\n", + "test_writer = tf.summary.FileWriter(summary_path + '/test')\n", + "\n", + "tf.global_variables_initializer().run()\n", + "\n", + "for i in range(max_step):\n", + " if i % 10 == 0:\n", + " xs, ys = mnist.test.images, mnist.test.labels\n", + " summary, acc = session.run([merged, accuracy], feed_dict={X: xs, Y_: ys})\n", + " test_writer.add_summary(summary, i)\n", + " if i % 100 == 0:\n", + " print('Accuracy at step %s: %s' % (i, acc))\n", + " else:\n", + " xs, ys = mnist.train.next_batch(batch_size)\n", + " summary, _ = session.run([merged, train_step], feed_dict={X: xs, Y_: ys, step: i}) # <-----------step: i---------------\n", + " train_writer.add_summary(summary, i)\n", + "\n", + "session.close()" + ], + "execution_count": 20, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Accuracy at step 0: 0.0912\n", + "Accuracy at step 100: 0.888\n", + "Accuracy at step 200: 0.9331\n", + "Accuracy at step 300: 0.95\n", + "Accuracy at step 400: 0.9535\n", + "Accuracy at step 500: 0.9545\n", + "Accuracy at step 600: 0.961\n", + "Accuracy at step 700: 0.9549\n", + "Accuracy at step 800: 0.9693\n", + "Accuracy at step 900: 0.9672\n", + "Accuracy at step 1000: 0.9687\n", + "Accuracy at step 1100: 0.9674\n", + "Accuracy at step 1200: 0.9678\n", + "Accuracy at step 1300: 0.9682\n", + "Accuracy at step 1400: 0.973\n", + "Accuracy at step 1500: 0.9739\n", + "Accuracy at step 1600: 0.9752\n", + "Accuracy at step 1700: 0.9745\n", + "Accuracy at step 1800: 0.9732\n", + "Accuracy at step 1900: 0.9738\n", + "Accuracy at step 2000: 0.976\n", + "Accuracy at step 2100: 0.9738\n", + "Accuracy at step 2200: 0.9769\n", + "Accuracy at step 2300: 0.975\n", + "Accuracy at step 2400: 0.9766\n", + "Accuracy at step 2500: 0.9745\n", + "Accuracy at step 2600: 0.9758\n", + "Accuracy at step 2700: 0.9774\n", + "Accuracy at step 2800: 0.9777\n", + "Accuracy at step 2900: 0.9764\n", + "Accuracy at step 3000: 0.9753\n", + "Accuracy at step 3100: 0.9754\n", + "Accuracy at step 3200: 0.9748\n", + "Accuracy at step 3300: 0.9751\n", + "Accuracy at step 3400: 0.9798\n", + "Accuracy at step 3500: 0.978\n", + "Accuracy at step 3600: 0.9782\n", + "Accuracy at step 3700: 0.9782\n", + "Accuracy at step 3800: 0.9775\n", + "Accuracy at step 3900: 0.9764\n", + "Accuracy at step 4000: 0.9786\n", + "Accuracy at step 4100: 0.9783\n", + "Accuracy at step 4200: 0.9781\n", + "Accuracy at step 4300: 0.9778\n", + "Accuracy at step 4400: 0.9797\n", + "Accuracy at step 4500: 0.9799\n", + "Accuracy at step 4600: 0.9783\n", + "Accuracy at step 4700: 0.98\n", + "Accuracy at step 4800: 0.9781\n", + "Accuracy at step 4900: 0.9771\n", + "Accuracy at step 5000: 0.9796\n", + "Accuracy at step 5100: 0.9787\n", + "Accuracy at step 5200: 0.9805\n", + "Accuracy at step 5300: 0.9794\n", + "Accuracy at step 5400: 0.9801\n", + "Accuracy at step 5500: 0.9809\n", + "Accuracy at step 5600: 0.9796\n", + "Accuracy at step 5700: 0.9804\n", + "Accuracy at step 5800: 0.9805\n", + "Accuracy at step 5900: 0.9795\n", + "Accuracy at step 6000: 0.979\n", + "Accuracy at step 6100: 0.9804\n", + "Accuracy at step 6200: 0.9808\n", + "Accuracy at step 6300: 0.9806\n", + "Accuracy at step 6400: 0.9802\n", + "Accuracy at step 6500: 0.9798\n", + "Accuracy at step 6600: 0.9795\n", + "Accuracy at step 6700: 0.9796\n", + "Accuracy at step 6800: 0.9793\n", + "Accuracy at step 6900: 0.9799\n", + "Accuracy at step 7000: 0.9805\n", + "Accuracy at step 7100: 0.9809\n", + "Accuracy at step 7200: 0.9805\n", + "Accuracy at step 7300: 0.9794\n", + "Accuracy at step 7400: 0.9804\n", + "Accuracy at step 7500: 0.9797\n", + "Accuracy at step 7600: 0.979\n", + "Accuracy at step 7700: 0.9808\n", + "Accuracy at step 7800: 0.9804\n", + "Accuracy at step 7900: 0.9804\n", + "Accuracy at step 8000: 0.9798\n", + "Accuracy at step 8100: 0.9802\n", + "Accuracy at step 8200: 0.9805\n", + "Accuracy at step 8300: 0.9804\n", + "Accuracy at step 8400: 0.9803\n", + "Accuracy at step 8500: 0.9799\n", + "Accuracy at step 8600: 0.98\n", + "Accuracy at step 8700: 0.9798\n", + "Accuracy at step 8800: 0.9801\n", + "Accuracy at step 8900: 0.9803\n", + "Accuracy at step 9000: 0.9806\n", + "Accuracy at step 9100: 0.9803\n", + "Accuracy at step 9200: 0.9805\n", + "Accuracy at step 9300: 0.9801\n", + "Accuracy at step 9400: 0.9804\n", + "Accuracy at step 9500: 0.98\n", + "Accuracy at step 9600: 0.9804\n", + "Accuracy at step 9700: 0.9809\n", + "Accuracy at step 9800: 0.9809\n", + "Accuracy at step 9900: 0.9805\n" + ], + "name": "stdout" + } + ] + }, + { + "metadata": { + "id": "fcDb0wi5o_Mm", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Т.к. ввели новую метапеременную (step), нужно передавать её при выполнении сессии" + ] + }, + { + "metadata": { + "id": "edJPWz3uo_Mn", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "# Прореживание\n", + "\n", + "В какой-то момент на больших итерациях тестовая кросс-энтропия начинает расти - дальше её уже ничто не остановит. Это переобучение. Ведь алгоритм настраивается только на кросс-энтропию обучающих данных. Просто наша сеть смогла запомнить почти все образцы обучающей выборки и потеряла обобщающую способность.\n", + "\n", + "В этом случае мы можем попробовать прореживание, или dropuot - один из видов регуляризации:
\n", + "" + ] + }, + { + "metadata": { + "id": "R0yXXwgho_Mp", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "image_size = 28\n", + "n_classes = 10\n", + "\n", + "tf.reset_default_graph()\n", + "\n", + "#мета для входных картинок\n", + "X = tf.placeholder(tf.float32, [None, image_size*image_size], name='train_inputs')\n", + "# мета для правильных ответов\n", + "Y_ = tf.placeholder(tf.float32, [None, 10], name='train_labels')" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "FqcWSr4no_Mu", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "def variable_summaries(var):\n", + " with tf.name_scope('summaries'):\n", + " mean = tf.reduce_mean(var)\n", + " tf.summary.scalar('mean', mean)\n", + " with tf.name_scope('stddev'):\n", + " stddev = tf.sqrt(tf.reduce_mean(tf.square(var - mean)))\n", + " tf.summary.scalar('stddev', stddev)\n", + " tf.summary.scalar('max', tf.reduce_max(var))\n", + " tf.summary.scalar('min', tf.reduce_min(var))\n", + " tf.summary.histogram('histogram', var)\n", + "\n", + "def nn_layer(input_tensor, input_dim, output_dim, layer_name, activation):\n", + " with tf.name_scope(layer_name):\n", + " with tf.name_scope('weights'):\n", + " initial = tf.truncated_normal([input_dim, output_dim], stddev=0.1)\n", + " weights = tf.Variable(initial)\n", + " variable_summaries(weights)\n", + " with tf.name_scope('biases'):\n", + " initial = tf.truncated_normal([output_dim], stddev=0.1)\n", + " biases = tf.Variable(initial)\n", + " variable_summaries(biases)\n", + " with tf.name_scope('xw_plus_b'):\n", + " preactivate = tf.nn.xw_plus_b(input_tensor, weights, biases)\n", + " activations = activation(preactivate, name='activation')\n", + " \n", + " return activations " + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "4O4I4_kEo_My", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "pkeep = tf.placeholder(tf.float32)\n", + "\n", + "hidden1 = nn_layer(X, image_size*image_size, 200, 'hidden1', tf.nn.relu)\n", + "hidden1_d = tf.nn.dropout(hidden1, pkeep)\n", + "\n", + "hidden2 = nn_layer(hidden1_d, 200, 100, 'hidden2', tf.nn.relu)\n", + "hidden2_d = tf.nn.dropout(hidden2, pkeep)\n", + "\n", + "hidden3 = nn_layer(hidden2_d, 100, 60, 'hidden3', tf.nn.relu)\n", + "hidden3_d = tf.nn.dropout(hidden3, pkeep)\n", + "\n", + "hidden4 = nn_layer(hidden3_d, 60, 30, 'hidden4', tf.nn.relu)\n", + "hidden4_d = tf.nn.dropout(hidden4, pkeep)\n", + "\n", + "Y = nn_layer(hidden4_d, 30, n_classes, 'out', tf.identity)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "Xutg2WuJo_M2", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Дропаут заключается в том, что на каждой итерации обучения некоторые нейроны полностью выбрасываются из сети. Какие именно нейроны это будут, определяется параметром pkeep, который задаёт вероятность того, что нейрон останестя. При работе с тестовыми данными уже нельзя выкидывать нейроны - сеть обучена - и поэтому параметр выставляется в 1." + ] + }, + { + "metadata": { + "id": "owkaFBM7o_M2", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "#БЕЗ ИЗМЕНЕНИЙ\n", + "# функция потерь\n", + "with tf.name_scope('cross_entropy'):\n", + " with tf.name_scope('total'):\n", + " cross_entropy = tf.losses.softmax_cross_entropy(onehot_labels=Y_, logits=Y)\n", + " \n", + "step = step = tf.placeholder(tf.int32)\n", + "learning_rate = 0.0001 + tf.train.exponential_decay(0.003, step, 2000, 1/math.e)\n", + "with tf.name_scope('train'):\n", + " optimizer = tf.train.AdamOptimizer(learning_rate)\n", + " train_step = optimizer.minimize(cross_entropy)\n", + " \n", + "#БЕЗ ИЗМЕНЕНИЙ\n", + "tf.summary.scalar('cross_entropy', cross_entropy)\n", + "\n", + "with tf.name_scope('accuracy'):\n", + " with tf.name_scope('correct_prediction'):\n", + " correct_prediction = tf.equal(tf.argmax(Y, axis=1), tf.argmax(Y_, axis=1))\n", + " with tf.name_scope('accuracy'):\n", + " accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))\n", + "tf.summary.scalar('accuracy', accuracy)\n", + "\n", + "# Merge all summaries together\n", + "merged = tf.summary.merge_all()" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "JqqTPaI2o_M5", + "colab_type": "code", + "colab": {} + }, + "cell_type": "code", + "source": [ + "max_step=10000\n", + "batch_size=100\n", + "\n", + "session = tf.InteractiveSession()\n", + "\n", + "summary_path = get_summary_path('nn_4hid_ReLU_Adam_lrdecay_dp')\n", + "\n", + "train_writer = tf.summary.FileWriter(summary_path + '/train', session.graph)\n", + "test_writer = tf.summary.FileWriter(summary_path + '/test')\n", + "\n", + "tf.global_variables_initializer().run()\n", + "\n", + "for i in range(max_step):\n", + " if i % 10 == 0:\n", + " xs, ys = mnist.test.images, mnist.test.labels\n", + " summary, acc = session.run([merged, accuracy], feed_dict={X: xs, Y_: ys, pkeep: 1.0}) #<----------pkeep: 1.0--------------\n", + " test_writer.add_summary(summary, i)\n", + " if i % 100 == 0:\n", + " print('Accuracy at step %s: %s' % (i, acc))\n", + " else:\n", + " xs, ys = mnist.train.next_batch(batch_size)\n", + " summary, _ = session.run([merged, train_step], feed_dict={X: xs, Y_: ys, step: i, pkeep: 0.75}) #<----------pkeep: 0.75-------------\n", + " train_writer.add_summary(summary, i)\n", + "\n", + "session.close()" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "YeaHar6Fo_M8", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "Опять ввели новую метапеременную - нужно передать её значение, т.к. мы обещали графу, что на момент вычислений там что-нибудь окажется" + ] + } + ] +} \ No newline at end of file