{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 1.4 Keras MNIST Playground"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1.4.1 Keras 基础介绍"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<img src=\"http://imgbed.momodel.cn//20200112152635.png\" width=300>\n",
"\n",
"Keras 是一个用 Python 编写的高级神经网络 API,它能够以 TensorFlow,CNTK 或者 Theano 作为后端运行。\n",
"\n",
"Keras 具有如下优点:\n",
"- 由于用户友好,高度模块化,可扩展性,可以简单而快速的进行原型设计。\n",
"- 同时支持卷积神经网络和循环神经网络,以及两者的组合。\n",
"- 在 CPU 和 GPU 上无缝运行。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Keras 的核心数据结构是 model,一种组织网络层的方式。最简单的模型是 Sequential 顺序模型,它把多个网络层线性堆叠起来。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Sequential 模型如下所示:"
]
},
{
"cell_type": "raw",
"metadata": {},
"source": [
"from keras.models import Sequential\n",
"\n",
"model = Sequential()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"可以简单地使用 .add() 来堆叠模型:"
]
},
{
"cell_type": "raw",
"metadata": {},
"source": [
"from keras.layers import Dense\n",
"\n",
"model.add(Dense(units=64, activation='relu', input_dim=100))\n",
"model.add(Dense(units=10, activation='softmax'))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"在完成了模型的构建后, 可以使用 .compile() 来配置学习过程:"
]
},
{
"cell_type": "raw",
"metadata": {},
"source": [
"model.compile(loss='categorical_crossentropy',\n",
" optimizer='sgd',\n",
" metrics=['accuracy'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"然后,就可以批量地在训练数据上进行迭代了。"
]
},
{
"cell_type": "raw",
"metadata": {},
"source": [
"# x_train 和 y_train 是 Numpy 数组 -- 就像在 Scikit-Learn API 中一样。\n",
"model.fit(x_train, y_train, epochs=5, batch_size=32)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"只需一行代码就能评估模型性能:"
]
},
{
"cell_type": "raw",
"metadata": {},
"source": [
"loss_and_metrics = model.evaluate(x_test, y_test, batch_size=128)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"或者对新的数据生成预测:"
]
},
{
"cell_type": "raw",
"metadata": {},
"source": [
"classes = model.predict(x_test, batch_size=128)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1.4.2 MNIST Playground\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"欢迎来到 MINST Playgound,在这里,你将了解到数据增强技术,并可以通过拖拽一个控制面板来创建深度学习模型,识别手写数字。无需编写任何代码,快来试试吧。\n",
"\n",
"*你需要按顺序运行下方每个 cell,直到看到控制面板,然后即可点击或拖拽来创建模型并训练。*"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Using TensorFlow backend.\n"
]
}
],
"source": [
"# 导入必要的包\n",
"\n",
"!mkdir -p ~/.keras/datasets\n",
"!cp ./mnist.npz ~/.keras/datasets/mnist.npz\n",
"\n",
"\n",
"%matplotlib inline\n",
"import time\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"import imgaug\n",
"import imgaug.augmenters as iaa\n",
"from ipywidgets import interact, interactive, fixed, interact_manual\n",
"from ipywidgets import IntSlider, FloatSlider, Dropdown, Checkbox, Button, Output, \\\n",
" SelectMultiple,Image, HBox, VBox,SelectionSlider, HTML\n",
"from IPython import display\n",
"import tensorflow as tf\n",
"from tensorflow.keras.datasets import mnist\n",
"from tensorflow import keras\n",
"from tensorflow.python.util import deprecation\n",
"from keras.layers import Input, Dense,Flatten\n",
"from keras.models import Model\n",
"from keras.callbacks import Callback\n",
"from keras.utils.np_utils import to_categorical\n",
"from keras.utils import model_to_dot\n",
"deprecation._PRINT_DEPRECATION_WARNINGS = False"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 处理数据\n",
"首先我们定义一个 MNIST 类,它将负责管理 MNIST 数据集"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"class MNIST:\n",
" \"\"\" 用来管理 MNIST 数据集的类\n",
" \n",
" Attributes:\n",
" x_train: MNIST 训练集的特征数据\n",
" y_train: MNIST 训练集的标签数据\n",
" x_test: MNIST 测试集的特征数据\n",
" y_test: MNIST 测试集的标签数据\n",
" train_size: MNIST 训练集的样本数\n",
" test_size: MNIST 测试集的样本数\n",
" augmented: 是否对训练集数据进行数据增强\n",
" seq: 进行数据增强的 pipline\n",
" \"\"\"\n",
" def __init__(self):\n",
" \"\"\"初始化 MNIST 类\n",
" \"\"\"\n",
" (self.x_train, self.y_train), (\n",
" self.x_test, self.y_test) = mnist.load_data()\n",
" self.x_train = self.x_train.reshape(self.x_train.shape[0], 28, 28, 1)\n",
" self.x_test = self.x_test.reshape(self.x_test.shape[0], 28, 28, 1)\n",
" self.x_train = self.x_train.astype('float32')\n",
" self.x_test = self.x_test.astype('float32')\n",
" self.train_size = self.x_train.shape[0]\n",
" self.test_size = self.x_test.shape[0]\n",
" self.y_train = to_categorical(self.y_train, 10)\n",
" self.y_test = to_categorical(self.y_test, 10)\n",
" self.noise = 0\n",
" self.rotation = 0\n",
" self.aug_possibility = 0 \n",
" self.seq = None\n",
" \n",
" def update_noise(self, noise):\n",
" \"\"\"数据增强中是否增加 noise\n",
" :param noise: 噪声比例\n",
" :return:\n",
" \"\"\"\n",
" self.noise = noise\n",
" self.update_aug_seq()\n",
" \n",
" def update_rotation(self, rotation):\n",
" \"\"\"数据增强中图片旋转的最大角度\n",
" :param rotation: 图片旋转的最大角度值\n",
" :return:\n",
" \"\"\"\n",
" self.rotation = rotation\n",
" self.update_aug_seq()\n",
" \n",
" def update_aug_possibility(self, possibility):\n",
" \"\"\"数据增强中图片旋转的最大角度\n",
" :param rotation: 图片旋转的最大角度值\n",
" :return:\n",
" \"\"\"\n",
" self.aug_possibility = possibility\n",
" self.update_aug_seq()\n",
" \n",
" def update_aug_seq(self):\n",
" \"\"\"更新数据增强的 pipline\n",
" :return:None\n",
" \"\"\"\n",
" if self.aug_possibility:\n",
" aug_seq = []\n",
" sometimes = lambda aug: iaa.Sometimes(self.aug_possibility, aug)\n",
" if self.noise:\n",
" aug_seq.append(sometimes(iaa.Salt(self.noise))) \n",
" if self.rotation:\n",
" aug_seq.append(sometimes(iaa.Affine(\n",
" rotate=(-self.rotation, self.rotation), \n",
" )))\n",
" self.seq = iaa.Sequential(aug_seq)\n",
" else:\n",
" self.seq = None\n",
"\n",
" def get_batch(self, batch_size, get_sample=False):\n",
" \"\"\"获取一个 batch 的训练数据(原始数据或者经过数据增强后的数据)\n",
" :param batch_size: 一个 batch 所包含的图片数目\n",
" :param get_sample: 是否返回固定样本来做展示\n",
" :return:\n",
" \"\"\"\n",
" while True:\n",
" if get_sample:\n",
" randidx = [i for i in range(batch_size)]\n",
" else:\n",
" randidx = np.random.randint(self.train_size, size=batch_size)\n",
" epoch_x = self.x_train[randidx]\n",
" epoch_y = self.y_train[randidx]\n",
" if self.aug_possibility and (self.noise or self.rotation):\n",
" epoch_x = self.seq(images=epoch_x)\n",
" epoch_x /= 255.0\n",
" yield epoch_x, epoch_y\n",
"\n",
" def get_batch_test(self, batch_size):\n",
" \"\"\"获取一个 batch 的测试数据\n",
" :param batch_size: 一个 batch 所包含的图片数目\n",
" :return:\n",
" \"\"\"\n",
" while True:\n",
" randidx = np.random.randint(self.test_size, size=batch_size)\n",
" epoch_x = self.x_test[randidx]\n",
" epoch_y = self.y_test[randidx]\n",
" epoch_x /= 255.0\n",
" yield epoch_x, epoch_y\n",
"\n",
" def plot_images(self, show=True):\n",
" \"\"\"绘制几个样本图片\n",
" :param show: 是否显示绘图\n",
" :return:\n",
" \"\"\"\n",
" sample_num = 9\n",
" imgs, _ = next(self.get_batch(sample_num,get_sample=True))\n",
" img_figure = plt.figure(1)\n",
" img_figure.set_figwidth(5)\n",
" img_figure.set_figheight(5)\n",
" for index in range(0, sample_num):\n",
" ax = plt.subplot(3, 3, index + 1)\n",
" ax.imshow(imgs[index].reshape(28, 28), cmap='gray')\n",
" plt.margins(0, 0)\n",
" img_figure.savefig('data_sample.png')\n",
" if not show:\n",
" plt.close(fig=img_figure)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 数据增强技术"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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FyDRYi3cwDo4KJ8JhRoA4TfsZak/NxAEQQWACgAQEaeXybDX41x44YWa7aofn3/+OQDglltuia9h2XGmFnYy4LrrrrPvD6Dh7Th20c9MZ2vW9ddf3+jP7QoUn3zySdONLT5n6mBdfPHFmsePHw8AmD+//oTynXfe0bx+/fqcXnvPPfcssHXRy+fMbSGAcGxQDmBx8ZpDOWIt3MA6OCifzm06gG4isgrABmwrLJUGa+EG1sFBWQ9LgyA4MPXfOgDDm3i40+zCfDNmzMj8wJT77rsPALB4sRv/ILtSixtvvFGzHYpu3bpV87x58wA0/JJ6y5YtaV/PrrpiJw723ntvzeFVFvYrglmzZuXc9mJwpQ6Z2JVTJk2aVNTXtotYuop3KBCRl9i5EZGXmuXtVyeffLLmHj16pH3MwoX1X5vYVSyau912203zZZddptnuHREORQHgtNNOa/T17G0806dP13zMMcekffzvf/97AMBdd92VZYspG3bGeZdddmny8UccccQOx15++WXNr7zySnEaVgCeuRGRl9i5EZGXms2w1A6P7rjjjrSPsStR2FuxNm7cmO7hzVKrVq00Z1qQ0A5xvvWtbwEALrjgAj02cuRIzXa7OLujuR3m2hzukbB58+ac296chSuxHHrooXrsF7/4heahQ4emfd5OO9Wf/6TbztLOyNoaf/311/k3tkh45kZEXmLnRkRe8npYmuvFuu+9955m7oWQnr1A197Hafcu+Ne//qXZDinTscMae5+p3b28pqb+Vs05c+bk2OLmJdzbAACOOuoozeHff/v/1V5MbetgZzrtlQV2kdGQXZj09NNP12yvMLB/Z+LEMzci8hI7NyLyktfDUns/Y7qZnu1lmkWleuHyT0DDGejKykrNnTrVL2cWrsdv7/984oknNG/YsEHz008/rdkOn+xx2pGdwbbDyD/84Q87PPamm27SvGjRIs0vvfSSZls/+xg7sx2yX0fcfvvtmj/44APNM2fO1FxXV5fhUxQfz9yIyEvs3IjIS14OS4888kgAmbd/s+xw6e23346sTT569dVXNdvhSS769eunuX///prt1wh2Fpu2sbOidqg5ceLEtI+fO3cugPrlu4CGXzHY+j3//POa7T2kdtYzvLfXDlVPPfVUzfY+4T//+c+aw53vAeCzzz7boZ0rVqxI2/588MyNiLzk5ZlbuE58x44d0/582bJlmseNGxdHkyiDtm3barZna/b6OE4obLPzzjtrtntWXHPNNZrtbWnXXnut5vD/oT1bCzfMBoD7779fs70+bs2aNZovvfRSzeHCrR06dNBjffr00Tx69GjN9nY7u/OZFe6ytd9++6X9eT545kZEXmLnRkRe8nJY2rlzZwCZr22z28x98cUXsbSJ0rMLW1Ljwu35gIZD0S+//FLzJZdcotlu49e7d28ADVfuOOWUUzTbrwd++ctfan788cc1p9ug2d4y96c//SltPvfcczV///vf3+E1AODqq69Oe7wQPHMjIi812bnJNlNFZJmIzBaR9iJSKSIrRWSahNsRUaRYB3ewFsmQzbD0BAAtgiDoLSJLAFwIoDoIguEiUglgMID5jb1AHOzps11gLx271nuCJKIOuTrppJNK3YR8lKQWditFy86i2uvc7HZ+dq+KdOxj7W1UxVh08re//W3aHLVshqXrAITrl2wFMAlAOJ+7CMDA7Z8gIuNFpEpEqorRSAKQRx0A1iIi/J1IgCbP3IIgWAMAIjIKQCsAywGE627XAjg4zXMqAFSkntf4gl6UlXzqkHoea1Fk/J1IhqxmS0VkJIAJAEYAmAKgLPWjMgA1mZ4XtfA2KwAYNGiQ5nCW1N4u8sADD2hO6kKUrtahEPvvv3+pm5CXUtTi448/1mxvl2rdurXmnj17pn1ueEvV0qVL9ZhdreP999/X7ML+B8WQzYRCFwATAQwLgmATgIUAwps2ywEsjq55FGId3MFaJEM237mNBdAVwDwReRFASwDdRGQVgA3YVliKHuvgDtYiAaSpNe4LfoMIv18YMGCAZnvPWjhbatfyb2q2yGHLgyDo1fTDmubidz12VYl//OMfmu0F2F26dNFs920ogaLUIt867LrrrprtQqFHH3205vXr12t+7LHHNIcrcJRqP4Miy6oOvIiXiLzEzo2IvOTlvaWUHK+//rpmu7yOnUU94IADNJd4WFpSmzZt0jxt2rS0merxzI2IvMTOjYi8lOhh6erVqzXb+0X79u1biuZQgW677TbNjz76qOZbb71V8xVXXAEAePPNN+NrGCUSz9yIyEuJvs6tmfD6OjfLrsf/zDPPaLa31oUbDdtFF+2+AREr6XVupHidGxE1X+zciMhLHJa6r9kMSy07RLUTCuH2cj169NBjMU4ucFjqBg5Liaj5YudGRF7isNR9zXJY6igOS93AYSkRNV/s3IjIS3HcflUDYC2A3ZHQdf5zEMVn3KeIr1UDYDNYh3wVqxb8nShMVnWI/Ds3fSORqmJ9d+SqJHzGJLSxUEn5jElpZyFK+Rk5LCUiL7FzIyIvxdm5VcT4XqWShM+YhDYWKimfMSntLETJPmNs37kREcWJw1Ii8hI7NyLyUqSdm4i0EZFKEVkpItNERKJ8vzjJNlNFZJmIzBaR9i5/VtbCDaxDfKI+cxsDoDoIgp4AOgIYHPH7xekEAC2CIOgNoAOAC+H2Z2Ut3MA6xCTqzq0cwIJUXgRgYMTvF6d1ACan8lYAk+D2Z2Ut3MA6xCTq2686A9iYyrUADo74/WITBMEaABCRUQBaAVgOtz8ra+EG1iEmUZ+51QAoS+UyeHYfnYiMBDABwAgA6+H2Z2Ut3MA6xCTqzm0hgCGpXA5gccTvFxsR6QJgIoBhQRBsgvuf1fX25S1htXC5bQVxrQ45d245zvZMB9BNRFYB2IBtH9YXYwF0BTBPRF4E0BIxf1bWQpW0FqyDKvnvhJXzHQoi8gMAvYIg+D8RqQRwbxAE8yNpHTWKtXAD6+CmfCYUygHMSOVwBiRjIbmkcsFqgiDYI8PPWIt4ZaoF6xCvxn4nVD7fuW0/29Np+weIyHgRqRKRqjxenxpa28jPWIt4ZaoF6xCvxn4nVD5nbk3O9gRBUIHUagD8VypSrIUbWAcH5XPm5u1sTwKxFm5gHRyUT+fm82xP0rAWbmAdHJTzsDQIgjoAwyNoC+WItXAD6+AmLnlERF5i50ZEXmLnRkReYudGRF5i50ZEXop6PTdnTJ48WfOVV16p+fXXX9c8fHj9hNfatVldBE1EjuKZGxF5iZ0bEXnJ62Hpvvvuq3nMmDGav/nmG82HHHKI5u7du2vmsLS4DjroIM0tW7bU3K9fP80PPvigZlujXMyaNUvzOeeco3nr1q15vZ7PbB369Omj+bbbbtN8wgknxNqmYuKZGxF5iZ0bEXnJ62HpJ598onnp0qWaR44cWYrmNBuHHXaY5nHjxgEAzjzzTD220071/6butddemu1QNNcVokO2tlOmTNF81VVXaa6trc3rtX1TVlamefHi+oVMPv74Y81dunRJezwJeOZGRF5i50ZEXvJ6WLp582bNnP2Mz+2336556NChJWvH+eefr/k3v/mN5pdeeqkUzUkMOxTlsJSIyDHs3IjIS14PS3fbbTfNPXv2LGFLmpcFCxZoTjcsXb9+vWY7XLSzqJku4g0vNu3fv3/B7aT0Gt9TOjl45kZEXmLnRkRe8npY2q5dO8177713k48/9thjNa9evVozZ1pz89BDD2meOXPmDj//73//qznXGbgOHToAaLhUlb0Q2LLvXVXFvZCzZS+gbtOmTQlbUpisztxEpKWIzEnlNiJSKSIrRWSa+DJATwjWwg2sg/ua7NxEpC2A5QAGpw6NAVAdBEFPAB3NcYoYa+EG1iEZmhyWBkGwBUAPEXkndagcwIxUXgRgIID50TSvMB999JHmJ554QvOkSZPSPt4e//zzzzXff//9xW5aXpJSi//973+aP/zww6K+9kknnQQA6NixY5OPra6u1lxXV1e0NiSlDsXQq1cvzcuWLSthS3KXz4RCZwAbU7kWQKftHyAi40WkSkT4RUe0WAs3sA4OymdCoQZAuJxAWerPDQRBUAGgAgBEJL/lHYrs5ptv1pzpzC2BElmLXNlFJy+++GIAQNu2bZt83o033hhZm7aTyDrYM+yNGzdqtquFHHDAAbG2qZjyOXNbCGBIKpcDWNzIYylarIUbWAcH5dO5TQfQTURWAdiAbYWl0mAt3MA6OCjrYWkQBAem/lsHYHgTD3daNrf5uMynWlijR4/WfO2112o+8MADNdt1/9NZsWKFZns9XRSSXgc7afaXv/xFs93iMsl4hwIReYmdGxF5yevbrzIpxlr9lJndUvG8884DAAwaNKjJ5/Xt21dzU3Wx+yDYIezzzz+vecuWLU2+J/mLZ25E5CV2bkTkpWY5LKXiO/zwwzXPnj1bczarseTDzu5VVFRE8h4EdO7cudRNyBvP3IjIS+zciMhLHJZS0dnlzHJZ2iyXi6vthaannHKK5rlz52b9ftS0kSNHlroJeeOZGxF5iZ0bEXmpWQ5Lsxn+9OvXT7Mri1W6zO5pMGDAAM1jxowBAMybN0+PffXVVzm99kUXXaT5iiuuyLOF1JjFi+sXMuG9pUREDmPnRkRekqjvrXRl1VHr66+/1pzN5+/RowcA4M0334ysTY1YHgRBr6Yf1jQXa5ENuzLsp59+usPPR4wYoTni2dKi1MLFOpxxxhman332Wc32/txDDz1Uc4m3u8yqDjxzIyIvNcsJhSlTpmi+5JJLmnz8+PHjAQBXXXVVZG2izMIdryg6dj8Fy16n2Lp167iaUxQ8cyMiL7FzIyIvNcth6erVq0vdhMSyexgMGTJE86JFizQXY5HICy64QPPkyZMLfj1q3KxZszTb34/u3btrtl/LXHbZZfE0rAA8cyMiLzXZuck2U0VkmYjMFpH2IlIpIitFZJrkcmc05Y11cAdrkQzZDEtPANAiCILeIrIEwIUAqoMgGC4ilQAGA5gfYRuL7r777tNsb+fJtLv2hAkTdnjeu+++G1HrMipZHezeBtdff73mwYMHa95vv/00f/jhh1m/dqdOnTQPHTpU8z333KO5Xbv7qSSQAAAD4UlEQVR2OzzPDn1zvZ2rCLz7nbDmz69verdu3TT/6Ec/KkVz8pbNsHQdgPBLj60AJgFYkPrzIgADt3+CiIwXkSoRqSpGIwlAHnUAWIuI8HciAZo8cwuCYA0AiMgoAK0ALAewMfXjWgAHp3lOBYCK1POcuxo7ifKpQ+p5rEWR8XciGbKaLRWRkQAmABgBYAqA8H6YMgA10TQtHm+88Ybm/fffP+1jXNmVvlR1sKui2L0SrJ/85CeaN23alPVr26Ht0UcfrTnTbXFLliwBADz00EN6zK5oEReffycsW4etW7eWsCW5y2ZCoQuAiQCGBUGwCcBCAOE1AOUA4v+b1QyxDu5gLZIhm+/cxgLoCmCeiLwIoCWAbiKyCsAGbCssRY91cAdrkQDZfOd2J4A7tzv8cDTNiZ/dFs6uLuEa1+tw6aWXFvX11q9fr3nOnDmaw5nrEsyQKtdrUUwdOnTQfOqpp2p+7rnnStGcnPAiXiLyEjs3IvJSs7y31LILUL711luaDznkkFI0x0njxo3TbC96Hjt2bF6vZy+A/vLLLzVn2kXe7s9A0TvrrLM019XVaba/H0nAMzci8hI7NyLyUrMfltq14I844ogStsRdK1as0GyXuvnrX/+q+ZZbbtHcsWNHzTNnzgQALFiwQI/Z5XU+/vjj4jaWCrZ06VLN9uuZYixlFSeeuRGRl5rl7lcJ0+x3v3KIt7tfJQx3vyKi5oudGxF5iZ0bEXmJnRsReYmdGxF5iZ0bEXmJnRsReYmdGxF5KY7br2oArAWwOzxaWz6DKD7jPkV8rRoAm8E65KtYteDvRGGyqkPkdyjoG4lUFetKe1cl4TMmoY2FSspnTEo7C1HKz8hhKRF5iZ0bEXkpzs6toumHJF4SPmMS2liopHzGpLSzECX7jLF950ZEFCcOS4nIS5F2biLSRkQqRWSliEwTEYny/eIk20wVkWUiMltE2rv8WVkLN7AO8Yn6zG0MgOogCHoC6AhgcMTvF6cTALQIgqA3gA4ALoTbn5W1cAPrEJOoO7dyAOHi+YsADIz4/eK0DsDkVN4KYBLc/qyshRtYh5hEfYdCZwAbU7kWwMERv19sgiBYAwAiMgpAKwDL4fZnZS3cwDrEJOoztxoAZalcBs9uNRGRkQAmABgBYD3c/qyshRtYh5hE3bktBDAklcsBLI74/WIjIl0ATAQwLAiCTXD/s7revrwlrBYut60grtUh6s5tOoBuIrIKwAZs+7C+GAugK4B5IvIigJZw+7OyFm5gHWLCi3iJyEu8iJeIvMTOjYi8xM6NiLzEzo2IvMTOjYi8xM6NiLzEzo2IvPT/AXA4a0ryXH9+AAAAAElFTkSuQmCC\n",
"text/plain": [
"<Figure size 360x360 with 9 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"# 查看原始图片\n",
"mnist_data = MNIST()\n",
"mnist_data.plot_images()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"数据增强技术就是利用有限的数据产生更多的等价数据。我们可以对原始的图片进行平移、旋转、缩放、加噪声等技术处理,来产生新的图片。\n",
"比如我们对一张小狗的图片进行旋转,加噪声,缩放等处理后,可以得到 6 张新的图片。\n",
"\n",
"使用数据增强技术可以拓展我们的数据集,提高模型的识别性能和泛化能力。\n",
"\n",
"<img src='https://miro.medium.com/max/3262/1*7udw5GZHwVo6u-V0lzglvQ.png' width=800 />\n",
"\n",
"下面我们对 mnist 数据集进行加噪声和旋转的处理。"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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Y7S+Kz02ldO7KmlbeC0qvP7AVakSW9T//vvvotu2bSt67dq1no8JOzy1y05I48+gVatWu/07APz+++9+ttDfRHYp7zchpHJzixvtMcaMAjAqhddWdqe8oQBJ24LnHljsaEWmdu3aonnKvJ0nAQCDBw8WzaMLw864ceNEX3nllQDcowl53ONll10mevv27X620N9EdkloUEYqN7e40R7HcQpQ2g1A/0oFSlK2qFKlitoiGPQ3EUJSubnZCMhs7FqO35nRM1KSISlbNG3aFBdddNFux2+88UbRdhX35ptvyrGePXuK7t+/v+idO3d6vg8P17YDlW+44YZy3688eCV1++23AwB27NgR93l+2NUa4F6xWXr06CG6atWqibyk/iZCSCqpIFGO9uQaaotwoHYIIUmv3BzH2QFgYADnoiSJ2iIcqB3CSVaHMivhIZ5rWLduXc/nrVq1SvQRRxwheuvWraLvuece0ZdffjkA4Nprr83IuXrx8MMPix4zZozo7du3i+bB2WeeeeZur/HCCy+IZtebgylKbqH93BRFiSR6c1MUJZJkYyizhr3ToyhTcx+rVq3q1KpVCwDw448/lvvYevXqiWaX049q1aqJ/uWXX0RXr14dgDuyes0114i20U8A+OSTT0Tn5eWJ5qTaLVu2oCx+753MY+rUqSO6nJy9jNhCfxNpk5AddOWmKEokqTQBhcmTJ4v+y1/+Inr16tWiBw6MBbzWrUsoCTqnaN26NSZNmgQAGDUqlizvtYpbuXKl6G7duonmTXqbwwYAc+bMEX3qqaeKnjt3LgB3NYM9BwB46KGHRLdv3170k08+Kfqjjz4S/f777wMAhgwZUu75l8VvRbdkyRIAwP777y/HDjroINGJrFqVcKIrN0VRIone3BRFiSSRDijsu+++oouKikTXr19fNF//gAEDRM+bNy/Yk0ucjAUUkrGFDTwA7hKlu+++W/T48eM9H8OdR2z5UuvWreUYF+9zaVevXr1En3baaaI5GGDt9f3333ueK9tt5MiRovnxXAp21113AXAHPLyCFqXkbEDBdnk58MAD5RiXtU2YMEH0N998IzqkQ9s1oKAoSuVFb26KokSSSEdLN27cKPr1118XzZE7xZs774w1trj44otFX3HFFaL9OnN07NhR9DnnnAMAOPnkk+UYdw1p1qyZaL/u3Owa2cfw1gI/jyPe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\n",
"text/plain": [
"<Figure size 360x360 with 9 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"mnist_data.update_noise(0.2)\n",
"mnist_data.update_rotation(15)\n",
"mnist_data.update_aug_possibility(0.5)\n",
"mnist_data.plot_images()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 定义模型\n",
"下面我们先定义一个 DrawCallback 类,用来在每个训练 epoch 结束后,打印出训练曲线。"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"class DrawCallback(Callback):\n",
" \"\"\" 进行模型训练的 callback 类,用来绘制训练图\n",
"\n",
" Attributes:\n",
" init_loss: 初始的 loss 值\n",
" epoch_data: 已经完成的 epoch 数的列表\n",
" loss_data: 每个 epoch 的训练数据的 loss 值的列表\n",
" val_loss_data: 每个 epoch 的测试数据的 loss 值的列表\n",
" accuracy_data: 每个 epoch 的训练数据的 acc 值的列表\n",
" val_accuracy_data: 每个 epoch 的测试数据的 acc 值的列表\n",
" best_val_acc: 测试数据的最佳的 acc\n",
"\n",
" \"\"\"\n",
" def __init__(self):\n",
" \"\"\"初始化 DrawCallback 类\n",
" \"\"\"\n",
" super().__init__()\n",
" self.init_loss = None\n",
" self.epoch_data = []\n",
" self.loss_data = []\n",
" self.val_loss_data = []\n",
" self.accuracy_data = []\n",
" self.val_accuracy_data = []\n",
" self.best_val_acc = 0\n",
"\n",
" def runtime_plot(self, epoch=None):\n",
" \"\"\"绘制训练曲线\n",
" :param epoch: 当前进行到的 epoch 数\n",
" :return: None\n",
" \"\"\"\n",
" # 总的 epoch 数\n",
" epochs = self.params.get(\"epochs\")\n",
" # 定义图片尺寸\n",
" img_figure = plt.figure(1)\n",
" img_figure.set_figwidth(16)\n",
" img_figure.set_figheight(5)\n",
" # 绘制 loss 曲线\n",
" ax1 = plt.subplot(1, 2, 1)\n",
" ax1.set_ylim(0, int(self.init_loss * 3))\n",
" ax1.set_xlim(1, epochs)\n",
" ax1.plot(self.epoch_data, self.loss_data, 'b', label='loss_train')\n",
" ax1.plot(self.epoch_data, self.val_loss_data, 'r', label='loss_val')\n",
" ax1.set_xlabel('Epoch {}/{}'.format(epoch, epochs))\n",
" ax1.set_ylabel('Loss')\n",
" ax1.legend()\n",
" # 绘制 acc 曲线\n",
" ax2 = plt.subplot(1, 2, 2)\n",
" ax2.set_ylim(0, 1)\n",
" ax2.set_xlim(1, epochs)\n",
" ax2.plot(self.epoch_data, self.accuracy_data, 'b', label='acc_train')\n",
" ax2.plot(self.epoch_data, self.val_accuracy_data, 'r', label='acc_val')\n",
" ax2.set_xlabel('Epoch {}/{}'.format(epoch, epochs))\n",
" ax2.set_ylabel('ACC')\n",
" ax2.legend()\n",
" # 清除历史图片\n",
" display.clear_output(wait=True)\n",
" # 展示新图片\n",
" plt.show()\n",
"\n",
" def on_epoch_end(self, epoch, logs=None):\n",
" \"\"\"在每个 epoch 结束后,存储 loss 值和 acc 值,并更新训练曲线图\n",
" :param epoch: 当前进行到的 epoch 数\n",
" :param logs: 当前 epoch 返回的 log\n",
" :return: None\n",
" \"\"\"\n",
" # 从 logs 中获取 loss 和 acc 的值并存到对应的 list 里\n",
" epoch = epoch + 1\n",
" logs = logs or {}\n",
" loss = logs.get(\"loss\")\n",
" val_loss = logs.get(\"val_loss\")\n",
" accuracy = logs.get(\"accuracy\")\n",
" val_accuracy = logs.get(\"val_accuracy\")\n",
" if val_accuracy > self.best_val_acc:\n",
" self.best_val_acc = val_accuracy\n",
" if self.init_loss is None:\n",
" self.init_loss = loss\n",
" self.epoch_data.append(epoch)\n",
" self.loss_data.append(loss)\n",
" self.val_loss_data.append(val_loss)\n",
" self.accuracy_data.append(accuracy)\n",
" self.val_accuracy_data.append(val_accuracy)\n",
" # 绘制训练图\n",
" self.runtime_plot(epoch)\n",
" # 如果训练完成,打印最终的结果\n",
" if epoch == self.params.get(\"epochs\"):\n",
" print('训练完成')\n",
" print('训练集准确率: {:.2%}'.format(accuracy))\n",
" print('测试集准确率: {:.2%}'.format(val_accuracy))\n",
" print('最高测试集准确率:{:.2%}'.format(self.best_val_acc))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"然后我们定义一个 NN 类,我们将基于此类来构建和管理模型。"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"class NN(object):\n",
" def __init__(self):\n",
" \"\"\"初始化 NN 类,构建一个多层全连接神经网络\n",
" Attributes:\n",
" layers: 隐藏层的数目\n",
" neurons: 每一层隐藏层的神经元的个数\n",
" epochs: 模型训练的轮数\n",
" model: 构建后的模型\n",
" \"\"\"\n",
" self.layers = 1\n",
" self.neurons = [8]\n",
" self.epochs = 5\n",
" self.model = None\n",
"\n",
" def build_model(self):\n",
" \"\"\"构建并编译模型\n",
" :return: None\n",
" \"\"\"\n",
" # 定义输入层和 flatten 层\n",
" inputs = Input(shape=(28, 28, 1,))\n",
" output = Flatten()(inputs)\n",
" # 定义隐藏层\n",
" for neuron in self.neurons:\n",
" output = Dense(neuron, activation='relu')(output)\n",
" # 定义输出层\n",
" predictions = Dense(10, activation='softmax')(output)\n",
" # 构建模型\n",
" self.model = Model(inputs=inputs, outputs=predictions)\n",
" # 编译模型\n",
" self.model.compile(loss='categorical_crossentropy',\n",
" optimizer='rmsprop',\n",
" metrics=['accuracy'])\n",
"\n",
" def start_train(self, data, validation_data):\n",
" \"\"\"开始训练模型\n",
" :param data: 训练数据\n",
" :param validation_data: 测试数据\n",
" :return: None\n",
" \"\"\"\n",
" plot_callback = DrawCallback()\n",
" self.model.fit_generator(data,\n",
" validation_data=validation_data,\n",
" validation_steps=300,\n",
" steps_per_epoch=500, \n",
" epochs=self.epochs, \n",
" verbose=1,\n",
" callbacks=[plot_callback])\n",
"\n",
" def plot_model(self):\n",
" \"\"\"保存模型结构图\n",
" :return: None\n",
" \"\"\"\n",
" keras.utils.plot_model(\n",
" self.model,\n",
" to_file='model.png',\n",
" show_shapes=True,\n",
" show_layer_names=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 操作面板\n",
"下面的代码将创建出一个可交互的操作面板,你可以通过此面板来进行数据增强,模型定义和训练的操作,并查看训练曲线图。"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "559c47cc65364e4f97874c93b7e65cc5",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"HTML(value='<h1>准备数据</h1>', placeholder='')"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "1000c4767996473db1877811019400be",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"HBox(children=(VBox(children=(FloatSlider(value=0.0, continuous_update=False, description='每个 batch 内增强图片的比例:'…"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "dae8559b95e8486dabf723c5878bb327",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"HTML(value='<h1>构建模型</h1>', placeholder='')"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "8ae28cfd09914c0a9282f745f1ef5f26",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"HBox(children=(VBox(children=(IntSlider(value=1, description='隐藏层数目:', layout=Layout(width='400px'), max=3, st…"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "3005c0649f8c4eafaf8e913ceb471ab0",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"HTML(value='<h1>训练模型</h1>', placeholder='')"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "99a3202d416d4da4a7aa7e0c697d3c64",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"SelectionSlider(continuous_update=False, description='训练轮数:', layout=Layout(width='400px'), options=(5, 10, 20…"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "52cc57915d66417e85e7e5a818a4e673",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Button(button_style='info', description='开始训练', style=ButtonStyle(), tooltip='点击开始训练')"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "58a2632a35bd4b5dbb20c5884ddcfab2",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Output()"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"mlp = NN()\n",
"mlp.build_model()\n",
"mnist_data = MNIST()\n",
"## 定义操作界面\n",
"# 隐藏层数目\n",
"hidden_layers_widget = IntSlider(min=0, \n",
" max=3,\n",
" value=1,\n",
" description='隐藏层数目:',\n",
" layout={'width': '400px'},\n",
" style={'description_width': 'initial'})\n",
"# 每层隐藏层包含的神经元数目\n",
"neuron_widgets = VBox([SelectionSlider(options=[8,16, 32, 64,128,256,512],\n",
" value=8,\n",
" description='隐藏层1的神经元数目:',\n",
" continuous_update=False,\n",
" orientation='horizontal',\n",
" readout=True,\n",
" layout={'width': '400px'},\n",
" style={'description_width': 'initial'})])\n",
"\n",
"# 开始训练的按钮\n",
"start_train_widget = Button(\n",
" description='开始训练',\n",
" button_style='info',\n",
" tooltip='点击开始训练',\n",
")\n",
"\n",
"output_widget = Output()\n",
"\n",
"data_head_widget = HTML(\n",
" value=\"<h1>准备数据</h1>\",\n",
" placeholder='',\n",
")\n",
"\n",
"data_aug_possibility_widget = FloatSlider(\n",
" value=0,\n",
" min=0,\n",
" max=1.0,\n",
" step=0.1,\n",
" description='每个 batch 内增强图片的比例:',\n",
" disabled=False,\n",
" continuous_update=False,\n",
" orientation='horizontal',\n",
" readout=True,\n",
" readout_format='.1f',\n",
" layout={'width': '400px'},\n",
" style={'description_width': 'initial'}\n",
")\n",
"\n",
"\n",
"data_noise_widget = FloatSlider(\n",
" value=0,\n",
" min=0,\n",
" max=0.2,\n",
" step=0.05,\n",
" description='为图片增加白噪声点:',\n",
" disabled=False,\n",
" continuous_update=False,\n",
" orientation='horizontal',\n",
" readout=True,\n",
" readout_format='.2f',\n",
" layout={'width': '400px'},\n",
" style={'description_width': 'initial'}\n",
")\n",
"\n",
"data_rotate_widget = SelectionSlider(options=[0, 10, 20, 30],\n",
" value=0,\n",
" description='图片左右旋转的最大角度:',\n",
" continuous_update=False,\n",
" orientation='horizontal',\n",
" readout=True,\n",
" layout={'width': '400px'},\n",
" style={'description_width': 'initial'})\n",
"\n",
"def plot_sample_data():\n",
" mnist_data.plot_images(show=False)\n",
" file = open(\"data_sample.png\", \"rb\")\n",
" image = file.read()\n",
" show_data_sample_widget.value = image\n",
"\n",
"def update_data_noise(*args):\n",
" mnist_data.update_noise(data_noise_widget.value)\n",
" plot_sample_data()\n",
"\n",
"def update_data_rotation(*args):\n",
" mnist_data.update_rotation(data_rotate_widget.value)\n",
" plot_sample_data() \n",
" \n",
"def update_data_aug_possibility(*args):\n",
" mnist_data.update_aug_possibility(data_aug_possibility_widget.value)\n",
" plot_sample_data() \n",
"\n",
"data_noise_widget.observe(update_data_noise, 'value') \n",
"data_rotate_widget.observe(update_data_rotation, 'value') \n",
"data_aug_possibility_widget.observe(update_data_aug_possibility, 'value') \n",
"\n",
"show_data_sample_widget = Image(\n",
" value=b'',\n",
" format='png',\n",
" width=200,\n",
")\n",
"\n",
"plot_sample_data()\n",
"\n",
"model_head_widget = HTML(\n",
" value=\"<h1>构建模型</h1>\",\n",
" placeholder='',\n",
")\n",
"\n",
"show_model_widget = Image(\n",
" value=b'',\n",
" format='jpg',\n",
" width=250,\n",
")\n",
"\n",
"def build_plot_model(*args):\n",
" keras.backend.clear_session()\n",
" mlp.build_model()\n",
" mlp.plot_model()\n",
" file = open(\"model.png\", \"rb\")\n",
" image = file.read()\n",
" show_model_widget.value = image\n",
"\n",
"build_plot_model()\n",
"\n",
"\n",
"# 更改隐藏层神经元数量时,更新模型实例的参数\n",
"def update_neuron_number(*args):\n",
" mlp.neurons = [c.value for c in neuron_widgets.children]\n",
" \n",
"for e in neuron_widgets.children:\n",
" e.observe(update_neuron_number, 'value')\n",
" e.observe(build_plot_model, 'value')\n",
" \n",
"\n",
"# 更改隐藏层数目时,动态增加或减小控制每一层神经元数量的按钮,同时更新模型实例的参数\n",
"def update_layers(*args):\n",
" if hidden_layers_widget.value > mlp.layers:\n",
" neuron_widgets.children += (SelectionSlider(options=[8,16, 32, 64,128,256,512],\n",
" value=8,\n",
" description='隐藏层{}的神经元数目:'.format(hidden_layers_widget.value),\n",
" continuous_update=False,\n",
" orientation='horizontal',\n",
" readout=True,\n",
" layout={'width': '400px'},\n",
" style={'description_width': 'initial'}),)\n",
" else:\n",
" neuron_widgets.children = neuron_widgets.children[:-1]\n",
" mlp.layers = hidden_layers_widget.value\n",
" mlp.neurons = [c.value for c in neuron_widgets.children]\n",
" for e in neuron_widgets.children:\n",
" e.observe(update_neuron_number, 'value')\n",
" e.observe(build_plot_model, 'value')\n",
" build_plot_model()\n",
"\n",
"hidden_layers_widget.observe(update_layers, 'value') \n",
"\n",
"\n",
"data_H_widget = HBox([VBox([data_aug_possibility_widget, \n",
" data_noise_widget, \n",
" data_rotate_widget]), \n",
" show_data_sample_widget])\n",
"\n",
"model_H_widget = HBox([VBox([hidden_layers_widget ,\n",
" neuron_widgets]), \n",
" show_model_widget])\n",
"\n",
"train_head_widget = HTML(\n",
" value=\"<h1>训练模型</h1>\",\n",
" placeholder='',\n",
")\n",
"\n",
"epochs_widget = SelectionSlider(options=[5, 10, 20, 50, 100],\n",
" value=5,\n",
" description='训练轮数:',\n",
" continuous_update=False,\n",
" orientation='horizontal',\n",
" readout=True,\n",
" layout={'width': '400px'},\n",
" style={'description_width': 'initial'})\n",
"\n",
"def update_epochs(*args):\n",
" mlp.epochs = epochs_widget.value\n",
"\n",
"epochs_widget.observe(update_epochs, 'value') \n",
"\n",
"\n",
" \n",
"def on_button_clicked(b):\n",
" with output_widget:\n",
" display.clear_output(wait=True)\n",
" mlp.start_train(mnist_data.get_batch(32),\n",
" validation_data=mnist_data.get_batch_test(32))\n",
" \n",
"start_train_widget.on_click(on_button_clicked) \n",
"\n",
"display.display(data_head_widget, \n",
" data_H_widget, \n",
" model_head_widget, \n",
" model_H_widget , \n",
" train_head_widget, \n",
" epochs_widget, \n",
" start_train_widget, \n",
" output_widget)\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.5.2"
},
"pycharm": {
"stem_cell": {
"cell_type": "raw",
"metadata": {
"collapsed": false
},
"source": []
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}