{"cells":[{"metadata":{"trusted":true},"cell_type":"code","source":"from PIL import Image\npil_im = Image.open('../input/logocanal/LOGO PNG.png')\npil_im","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"markdown","source":"Tradução do trabalho https://www.kaggle.com/ilyamich/kannada-mnist-choosing-the-right-optimizer\n\nNem todos os otimizadores nascem iguais. Eles  são implementados com diferentes equações e complexidades, mas têm uma coisa em comum: estão todos lá para ajudá-lo a treinar sua rede. Não importa se você está trabalhando em tarefas de previsão, classificação ou mesmo segmentação, eles sempre tentarão fazer o melhor!\n\nCada otimizador tem seus próprios pontos fortes e fracos e todo desenvolvedor deve estar familiarizado com eles. Todo o objetivo dos otimizadores é o mesmo, reduzir a perda tanto quanto possível à sua própria maneira, manipulando os parâmetros do modelo.\n\n\n\n### Introdução aos Otimizadores\n\nTodos os otimizadores têm a mesma linha de base, onde os gradientes da função de custo são calculados por toda a cadeia do modelo e, em seguida, subtraem esses gradientes dos parâmetros do modelo. Nós subtraímos porque estamos tentando encontrar o ponto mais baixo no plano da função de custo e os gradientes \"apontam\" para cima na inclinação. O que define os otimizadores é como eles regularizam o processo de atualização dos parâmetros, como veremos mais tarde.\n\n<img src=\"https://miro.medium.com/max/875/1*47skUygd3tWf3yB9A10QHg.gif\">\n\n\n### SGD (Stochastic Gradient Descent)\n\nO primeiro otimizador a ser estudado é o  GD (Gradient Descent). Na verdade, existem três tipos de GDs e Keras implementa todos eles em uma função. Vamos dar uma olhada neles.\n\n\n#### BGD (Batch Gradient Descent))\n\n\nO primeiro tipo de GD é denominado BGD e é o otimizador mais simples de entender. Mas não se deixe enganar, pois em grandes conjuntos de dados ele é o mais complexo computacionalmente. Isso ocorre porque no BGD todo o detaset precisa ser alimentado na rede para apenas uma \"etapa\". Além disso, em conjuntos de dados muito grandes, pode não haver RAM suficiente para armazenar todo o conjunto de dados. Por outro lado, o BGD teoricamente sempre terá como objetivo o ponto mais baixo no plano da função de perda.\n\nA equação para BGD pega o gradiente da função de custo e o subtrai do parâmetro. Normalmente, um hiperparâmetro de regularização é adicionado, chamado de \"taxa de aprendizado\", para regular a convergência. A \"taxa de aprendizagem\" está geralmente na faixa de (1e-3, 1e-2). Abaixo está a equação para BGD\n\n\\begin{align}\n\\theta = \\theta - \\eta \\cdot \\nabla_\\theta J( \\theta)\n\\end{align}\n\n* θ: peso\n* η: taxa de aprendizado\n* ∇<span class=\"mjx-sub\" style=\"font-size: 70.7%; vertical-align: -0.23em; padding-right: 0.071em;\">θ</span>J(θ): derivada da função de custo \n\n\n#### SGD (Stochastic Gradient Descent)\n\n\nO segundo tipo é SGD, que é exatamente o oposto do BGD, o modelo utiliza apenas uma amostra do conjunto de dados por vez. Esta técnica é muito mais rápida que o BGD e mais prática, pois você não precisa armazenar o conjunto de dados inteiro na RAM. Em vez disso, os dados relevantes podem ser carregados conforme a necessidade. Essas vantagens têm um preço, pois o SGD sofre de alta variação e os \"passos\" nem sempre serão em direção à convergência. Há uma maneira de contornar isso, reduzindo cuidadosamente a taxa de aprendizado em cada época. Isso pode melhorar o desempenho do SGD tanto quanto ser igual ao do BGD. Praticamente SGD é preferível a BGD para aplicações onde o conjunto de dados não é pequeno.\n\n\n\n\\begin{align}\n\\theta = \\theta - \\eta \\cdot \\nabla_\\theta J( \\theta; x^{(i)}; y^{(i)})\n\\end{align}\n\n\n* θ: pesos\n* η: taxa de aprendizado\n* ∇<span class=\"mjx-sub\" style=\"font-size: 70.7%; vertical-align: -0.23em; padding-right: 0.071em;\">θ</span>J(θ; X<span class=\"mjx-sup\" style=\"font-size: 70.7%; vertical-align: 0.513em; padding-left: 0px; padding-right: 0.071em;\">(i)</span>; Y<span class=\"mjx-sup\" style=\"font-size: 70.7%; vertical-align: 0.513em; padding-left: 0px; padding-right: 0.071em;\">(i)</span>): derivada da função de custo de um exemplo\n* X<span class=\"mjx-sup\" style=\"font-size: 70.7%; vertical-align: 0.513em; padding-left: 0px; padding-right: 0.071em;\">(i)</span>: features do exemplo i\n* Y<span class=\"mjx-sup\" style=\"font-size: 70.7%; vertical-align: 0.513em; padding-left: 0px; padding-right: 0.071em;\">(i)</span>: ground truth do exemplo i\n\n\n#### MBGD (Mini-Batch Gradient Descent))\n\n\nO terceiro tipo é MBGD e é um meio-termo entre SGD e BGD, onde o modelo aprende com um lote  de exemplos a cada \"etapa\". Sendo um meio-termo, sua variância é menor do que SGD, o que o torna mais estável.\n\nAbaixo está a equação de MBGD e é a mesma que SGD, exceto que a derivada depende de um lote de exemplos.\n\n\\begin{align}\n\\theta = \\theta - \\eta \\cdot \\nabla_\\theta J( \\theta; x^{(i:i+n)}; y^{(i:i+n)})\n\\end{align}\n\n* θ: pesos\n* η: learning rate\n* ∇<span class=\"mjx-sub\" style=\"font-size: 70.7%; vertical-align: -0.23em; padding-right: 0.071em;\">θ</span>J(θ; X<span class=\"mjx-sup\" style=\"font-size: 70.7%; vertical-align: 0.513em; padding-left: 0px; padding-right: 0.071em;\">(i)</span>; Y<span class=\"mjx-sup\" style=\"font-size: 70.7%; vertical-align: 0.513em; padding-left: 0px; padding-right: 0.071em;\">(i)</span>): derivada da função de custo\n* X<span class=\"mjx-sup\" style=\"font-size: 70.7%; vertical-align: 0.513em; padding-left: 0px; padding-right: 0.071em;\">(i:i+n)</span>: features dos exemplos i a n\n* Y<span class=\"mjx-sup\" style=\"font-size: 70.7%; vertical-align: 0.513em; padding-left: 0px; padding-right: 0.071em;\">(i:i+n)</span>: ground truth dos exemplos i a n\n\n\n#### Conclusão\n\nNo Keras, você pode controlar o tamanho do lote, o que lhe dá a opção de transformar o otimizador em BGD, definindo o tamanho do lote para o comprimento dos dados. Ou torne-o SGD definindo o tamanho do lote para 1. Ou você pode definir o tamanho do lote para qualquer outro número entre 1 e o tamanho do conjunto de dados para obter MBGD. \n\n\n### Adagrad (Adaptive Gradient )\n\n\nO Adagrad difere do SGD pelo cálculo da taxa de aprendizagem diferente para cada parâmetro que muda a cada etapa. Definindo g como a derivada parcial em relação a θ:\n\n\\begin{align}\ng_{t, i} = \\nabla_\\theta J( \\theta_{t, i} )\n\\end{align}\n\nA equação do Adagrad tem dois novos parâmetros:\n\n\\begin{align}\n\\theta_{t+1, i} = \\theta_{t, i} - \\dfrac{\\eta}{\\sqrt{G_{t, ii} + \\epsilon}} \\cdot g_{t, i}\n\\end{align}\n\n* G<span class=\"mjx-sub\" style=\"font-size: 70.7%; vertical-align: -0.212em; padding-right: 0.071em;\">t</span>: soma do quadrado dos gradientes previos\n* ϵ: pequeno número para evitar divisão por zero (usualmente 1e-8)\n\nEmbora o Adagrad reduza automaticamente a taxa de aprendizado de maneira diferente para cada parâmetro, ele tem uma grande desvantagem. Ao somar quadrados de gradientes (que sempre são números positivos), eventualmente produzirá um grande número que fará com que o gradiente \"desapareça\" (fique próximo de zero).\n\n\n\n\n### Adadelta (ADAPTIVE LEARNING RATE METHOD)\n\nAdadelta tenta resolver as desvantagens do Adagrad e é uma extensão direta. Em vez de somar todos os gradientes anteriores, o Adadelta restringe o número de gradientes anteriores dos quais depende, calcula-se uma média de execução. \n\n\\begin{align}\nE[g^2]_t = \\gamma E[g^2]_{t-1} + (1 - \\gamma) g^2_t\n\\end{align}\n\n\\begin{align}\nRMS[g]_{t} = \\sqrt{E[g^2]_t + \\epsilon}\n\\end{align}\n\n\\begin{align}\nRMS[\\Delta \\theta]_{t} = \\sqrt{E[\\Delta \\theta^2]_t + \\epsilon}\n\\end{align}\n\n\\begin{align} \n\\begin{split}\n\\Delta \\theta_t &= - \\dfrac{RMS[\\Delta \\theta]_{t-1}}{RMS[g]_{t}} g_{t} \\\\ \n\\theta_{t+1} &= \\theta_t + \\Delta \\theta_t \n\\end{split} \n\\end{align}\n\n* E[g<span class=\"mjx-sup\" style=\"font-size: 70.7%; vertical-align: 0.513em; padding-left: 0px; padding-right: 0.071em;\">2</span>]<span class=\"mjx-sub\" style=\"font-size: 70.7%; vertical-align: -0.212em; padding-right: 0.071em;\">t</span>: média de execução\n* γ: decaimento constante. geralmente em torno de  0.9\n\n\n\n\n\n### RMSprop\n\nO RMSprop é na verdade um algoritmo não publicado. Foi proposto no curso Coursera. O algoritmo é uma extensão do Adagrad e muito semelhante ao Adadelta. RMSpror muda apenas o denominador para a mesma equação de Adadelta e define γ = 0,9:\n\n\\begin{align} \n\\begin{split} \nE[g^2]_t &= 0.9 E[g^2]_{t-1} + 0.1 g^2_t \\\\ \n\\theta_{t+1} &= \\theta_{t} - \\dfrac{\\eta}{\\sqrt{E[g^2]_t + \\epsilon}} g_{t} \n\\end{split} \n\\end{align}\n\n\n### Adam (Adaptive Moment Estimation)\n\nO otimizador Adam é uma extensão de dois otimizadores, RMSpror e Momentum. O Adam usa o Momentum para corrigir o primeiro momento e o RMSprop para corrigir o segundo momento.\n\n\\begin{align} \n\\begin{split} \nm_t &= \\beta_1 m_{t-1} + (1 - \\beta_1) g_t \\\\ \nv_t &= \\beta_2 v_{t-1} + (1 - \\beta_2) g_t^2 \n\\end{split} \n\\end{align}\n\n* m<span class=\"mjx-sub\" style=\"font-size: 70.7%; vertical-align: -0.212em; padding-right: 0.071em;\">t</span>: exponentially decaying average (first momentum)\n* v<span class=\"mjx-sub\" style=\"font-size: 70.7%; vertical-align: -0.212em; padding-right: 0.071em;\">t</span>: exponentially decaying average of past squared gradients (second momentum)\n\nNo estágio inicial m<span class=\"mjx-sub\" style=\"font-size: 70.7%; vertical-align: -0.212em; padding-right: 0.071em;\">t</span> and v<span class=\"mjx-sub\" style=\"font-size: 70.7%; vertical-align: -0.212em; padding-right: 0.071em;\">t</span> deve ser inicializado com vetores zero. Essa inicialização cria um problema de polarização do gradiente para zero. Para superar esse problema, o autor adiciona equações de correção de viés::\n\n\\begin{align} \n\\begin{split} \n\\hat{m}_t &= \\dfrac{m_t}{1 - \\beta^t_1} \\\\ \n\\hat{v}_t &= \\dfrac{v_t}{1 - \\beta^t_2} \n\\end{split} \n\\end{align}\n\n\nFinalmente, para atualizar os parâmetros,o otimizador usa a seguinte equação:\n\n\\begin{align} \n\\theta_{t+1} = \\theta_{t} - \\dfrac{\\eta}{\\sqrt{\\hat{v}_t} + \\epsilon} \\hat{m}_t\n\\end{align}\n\nThe author suggests to use the following hyper parameter values:\n* β<span class=\"mjx-sub\" style=\"font-size: 70.7%; vertical-align: -0.212em; padding-right: 0.071em;\">1</span> = 0.9\n* β<span class=\"mjx-sub\" style=\"font-size: 70.7%; vertical-align: -0.212em; padding-right: 0.071em;\">2</span> = 0.999\n* γ = 1e-8\n\n\nAdam é um dos otimizadores mais usados ​​em ML, pois é estável e geralmente produz os melhores resultados.\n\n\n","execution_count":null},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","collapsed":true,"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":false},"cell_type":"markdown","source":"## Importando Bibliotecas","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"import time\nimport pandas as pd\nimport numpy as np\n\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nplt.style.use('ggplot')\n%matplotlib inline\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import confusion_matrix\n\nfrom tensorflow.keras.utils import to_categorical\nfrom tensorflow.keras.models import Sequential, Model\nfrom tensorflow.keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPool2D, Input, BatchNormalization\nfrom tensorflow.keras.callbacks import ReduceLROnPlateau, ModelCheckpoint\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.models import model_from_json","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# random seed\nseed = 33\nnp.random.RandomState(seed)\n\n# validation to training split ration\nvalid_size = 0.1\n\n# use data augmentation i nthe first part of training\nto_augment = False","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Carregando e preparando os dados\n","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"data_path = '../input/Kannada-MNIST/'\n\ntrain_path = data_path + 'train.csv'\ntest_path = data_path + 'test.csv'\ndig_path = data_path + 'Dig-MNIST.csv'\nsample_path = data_path + 'sample_submission.csv'\n\nsave_path = ''\nload_path = '../input/kennada-mnist-pretrained-model/'","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Carregando os Datasets","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df = pd.read_csv(train_path)\ntest_df = pd.read_csv(test_path)\ndig_df = pd.read_csv(dig_path)\nsample_df = pd.read_csv(sample_path)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# convert dataframes to numpy matricies\nX = train_df.drop('label', axis=1).to_numpy()\ny = train_df['label'].to_numpy()\nX_dig = dig_df.drop('label', axis=1).to_numpy()\ny_dig = dig_df['label'].to_numpy()\nX_test = test_df.drop('id', axis=1).to_numpy()\n\n# reshape X's for keras and encode y using one-hot-vector-encoding\nX = X.reshape(-1, 28, 28, 1)\ny = to_categorical(y)\nX_dig = X_dig.reshape(-1, 28, 28, 1)\nX_test = X_test.reshape(-1, 28, 28, 1)\n\n# normalize the data to range(0, 1)\nX = X / 255\nX_dig = X_dig / 255\nX_test = X_test / 255\n\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Separando em treino e teste","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# split to train and validation sets\nX_train, X_valid, y_train, y_valid = train_test_split(X, y, test_size=valid_size, random_state=seed) \n\nprint('X_train shape = {}'.format(X_train.shape))\nprint('Y_train shape = {}'.format(y_train.shape))\nprint('X_valid shape = {}'.format(X_valid.shape))\nprint('Y_valid shape = {}'.format(y_valid.shape))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"##  Criando modelo\n\n","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# model builder\ndef build_model(optimizer):\n    model = Sequential()\n    \n    model.add(Conv2D(filters=32, kernel_size=(5,5), padding='Same', activation='relu', input_shape=(28,28,1)))\n    model.add(Conv2D(filters=32, kernel_size=(5,5), padding='Same', activation='relu'))\n    model.add(MaxPool2D(pool_size=(2,2)))\n    model.add(Dropout(0.25))\n\n    model.add(Conv2D(filters=64, kernel_size=(3,3), padding='Same', activation='relu'))\n    model.add(Conv2D(filters=64, kernel_size=(3,3), padding='Same', activation='relu'))\n    model.add(MaxPool2D(pool_size=(2,2), strides=(2,2)))\n    model.add(Dropout(0.25))\n\n    model.add(Flatten())\n    model.add(Dense(256, activation='relu'))\n    model.add(Dropout(0.5))\n    model.add(Dense(10, activation='softmax'))\n    \n    model.compile(optimizer=optimizer, loss='categorical_crossentropy', metrics=['accuracy'])\n    \n    return model","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# save model\ndef save_trained_model(model, save_path, optimizer):\n    # serialize model to JSON\n    model_json = model.to_json()\n    with open('{}Kennada MNIST with {}.json'.format(save_path, optimizer), \"w\") as json_file:\n        json_file.write(model_json)\n\n    # serialize weights to HDF5\n    model.save_weights('{}Kennada MNIST with {}.h5'.format(save_path, optimizer))\n\n    \n# load pretrained model\ndef load_trained_model(optimizers, optimizer, load_path):\n    # load json and create model\n    json_file = open('{}Kennada MNIST with {}.json'.format(load_path, optimizers[optimizer]), 'r')\n    loaded_model_json = json_file.read()\n    json_file.close()\n    model = model_from_json(loaded_model_json)\n\n    # load weights into new model\n    model.load_weights('{}Kennada MNIST with {}.h5'.format(load_path, optimizers[optimizer]))\n    \n    # compile the model\n    model.compile(optimizer=optimizer, loss='categorical_crossentropy', metrics=['accuracy'])\n    \n    return model","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def load_history(load_path, optimizer):\n    history = pd.read_csv('{}Kennada MNIST with {}.csv'.format(load_path, optimizer))\n    \n    return history.to_dict('list')\n\ndef save_history(history, save_path, optimizer):\n    hist_df = pd.DataFrame(history)\n    hist_df.to_csv('{}Kennada MNIST with {}.csv'.format(save_path, optimizer), index=False)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Treinando a Rede Neural","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# integer or None. Number of samples per gradient update. If unspecified, batch_size will default to 32\nbatch_size = 1024\n# integer. 0, 1, or 2. Verbosity mode. 0 = silent, 1 = progress bar, 2 = one line per epoch\nverbose = 0\n# integer. Number of epochs to train the model. An epoch is an iteration over the entire x and y data provided\nepochs = 30","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Definindo otimizadores a serem comparados","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# every optimizer has a name\noptimizers = {\n    'sgd':        'SGD',\n    'rmsprop':    'RMSprop',\n    'adagrad':    'Adagrad',\n    'adadelta':   'Adadelta',\n    'adam':       'Adam',\n    'adamax':     'Adamax',\n    'nadam':      'Nadam',\n}\n\n# and default learning rate\nlearning_rates = {\n    'sgd':        1e-2,\n    'rmsprop':    1e-3,\n    'adagrad':    1e-2,\n    'adadelta':   1.0,\n    'adam':       1e-3,\n    'adamax':     2e-3,\n    'nadam':      2e-3,\n}","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# create learning rate decay callback borrowed from here: https://www.kaggle.com/cdeotte/25-million-images-0-99757-mnist\nlearning_rate_reduction = ReduceLROnPlateau(monitor='val_accuracy', \n                                            patience=3, \n                                            verbose=0, \n                                            factor=0.5, \n                                            min_lr=0.00001)\n\n# artificially increase training set\ntrain_datagen = ImageDataGenerator(rescale=1.0,\n                                   rotation_range=10,\n                                   width_shift_range=0.25,\n                                   height_shift_range=0.25,\n                                   shear_range=0.1,\n                                   zoom_range=0.25,\n                                   horizontal_flip=False)\n\n# artificially increase validation set\nvalid_datagen = ImageDataGenerator(rescale=1.0)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Comparando otimizadores","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# prepare empty dictionaries\nhistory = {}\nmodel = {}\n\nfor n, optimizer in enumerate(optimizers):\n    # build model for every optimizer\n    model[optimizer] = build_model(optimizer)\n\n    # measure training time\n    start = time.time()\n\n    # train model\n    if to_augment:\n        h = model[optimizer].fit_generator(train_datagen.flow(X_train, y_train, batch_size=batch_size),\n                                           steps_per_epoch=100,\n                                           epochs=epochs,\n                                           validation_data=valid_datagen.flow(X_valid, y_valid),\n                                           callbacks=[learning_rate_reduction],\n                                           verbose=verbose)\n    else:\n        h = model[optimizer].fit(X_train,\n                                 y_train,\n                                 batch_size=batch_size,\n                                 epochs=epochs,\n                                 validation_data=(X_valid,y_valid),\n                                 callbacks=[learning_rate_reduction],\n                                 verbose=verbose)\n\n    history[optimizer] = h.history\n\n    # print results\n    print(\"{0} Optimizer: \".format(optimizers[optimizer]))\n    print(\"Epochs={0:d}, Train accuracy={1:.5f}, Validation accuracy={2:.5f}, Training time={3:.2f} minutes\"\n              .format(epochs, \n                      max(history[optimizer]['accuracy']), \n                      max(history[optimizer]['val_accuracy']), \n                      (time.time()-start)/60))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# apply smoothing filter\ndef smoothing_filter(data, filter_n=3):\n    # filter_n should be odd number\n    # extend the end for better accuracy at the end\n    data = np.concatenate((data, [data[-1]]*filter_n))\n    \n    # apply filter\n    data = np.convolve(data, [1/filter_n]*filter_n)\n    \n    # remove filter delay and padding\n    return data[int(np.ceil(filter_n/2)) : -filter_n]\n\n\n# plot training accuracy helper function\ndef plot_training_accuracy(history, names, epochs, to_smooth=False, filter_n=3, styles=[':','-.','--','-',':','-.','--','-',':','-.','--','-']):\n    # filter_n should be odd number\n    plt.figure(figsize=(15, 5))\n    \n    for n, h in enumerate(history.values()):\n        # get validation accuracy history\n        val_acc = h['val_accuracy']\n        \n        # smooth on request\n        if to_smooth:\n            val_acc = smoothing_filter(val_acc, filter_n)\n        \n        # plot history\n        plt.plot(val_acc, linestyle=styles[n])\n    \n    plt.title('Model validation accuracy')\n    plt.ylabel('accuracy')\n    plt.xlabel('epoch')\n    plt.legend(names, loc='upper left')\n    axes = plt.gca()\n    axes.set_ylim([0.99, 0.997])\n    axes.set_xlim([0, epochs-1])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# plot learning hystory for all optimizers\nplot_training_accuracy(history, optimizers.values(), epochs)","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}