{ "cells": [ { "cell_type": "markdown", "id": "f2160c1d-37bb-482b-a270-daa13c612f7c", "metadata": {}, "source": [ "# 卷积神经网络\n", "\n", "目录\n", "\n", "- 图像卷积\n", "- 填充和步幅\n", "- 多输入多输出通道\n", "- 池化层\n", "- 卷积神经网络(LeNet)" ] }, { "cell_type": "markdown", "id": "6846ad05-7209-4c31-9378-b9b532a3667e", "metadata": { "jp-MarkdownHeadingCollapsed": true, "slideshow": { "slide_type": "-" }, "tags": [] }, "source": [ "## 图像卷积\n", "\n", "互相关运算" ] }, { "cell_type": "code", "execution_count": 2, "id": "ac9478e0-6433-4cf7-917f-600f08edc110", "metadata": { "origin_pos": 3, "tab": [ "pytorch" ] }, "outputs": [], "source": [ "import torch\n", "from torch import nn\n", "from d2l import torch as d2l\n", "\n", "def corr2d(X, K): \n", " \"\"\"计算二维互相关运算\"\"\"\n", " h, w = K.shape\n", " Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))\n", " for i in range(Y.shape[0]):\n", " for j in range(Y.shape[1]):\n", " Y[i, j] = (X[i:i + h, j:j + w] * K).sum()\n", " return Y" ] }, { "cell_type": "markdown", "id": "314a155d-9475-4a87-950d-c14bbcad6d44", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "验证上述二维互相关运算的输出" ] }, { "cell_type": "code", "execution_count": 3, "id": "3f8076da-f6b1-4b35-ba49-c31cb1314304", "metadata": { "origin_pos": 6, "tab": [ "pytorch" ] }, "outputs": [ { "data": { "text/plain": [ "tensor([[19., 25.],\n", " [37., 43.]])" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])\n", "K = torch.tensor([[0.0, 1.0], [2.0, 3.0]])\n", "corr2d(X, K)" ] }, { "cell_type": "markdown", "id": "aab13ec8-679c-41b7-a686-98527d50a6f7", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "实现二维卷积层" ] }, { "cell_type": "code", "execution_count": 4, "id": "8c2795ba-6b7e-4d65-a718-7834d515eebf", "metadata": { "origin_pos": 9, "tab": [ "pytorch" ] }, "outputs": [], "source": [ "class Conv2D(nn.Module):\n", " def __init__(self, kernel_size):\n", " super().__init__()\n", " self.weight = nn.Parameter(torch.rand(kernel_size))\n", " self.bias = nn.Parameter(torch.zeros(1))\n", "\n", " def forward(self, x):\n", " return corr2d(x, self.weight) + self.bias" ] }, { "cell_type": "markdown", "id": "9a4b28c4-b74d-47f0-a549-2b2d7d350e55", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "卷积层的一个简单应用:\n", "检测图像中不同颜色的边缘" ] }, { "cell_type": "code", "execution_count": 5, "id": "2763c05e-3af6-48ab-8660-e7ffff81bbf5", "metadata": { "origin_pos": 12, "tab": [ "pytorch" ] }, "outputs": [ { "data": { "text/plain": [ "tensor([[1., 1., 0., 0., 0., 0., 1., 1.],\n", " [1., 1., 0., 0., 0., 0., 1., 1.],\n", " [1., 1., 0., 0., 0., 0., 1., 1.],\n", " [1., 1., 0., 0., 0., 0., 1., 1.],\n", " [1., 1., 0., 0., 0., 0., 1., 1.],\n", " [1., 1., 0., 0., 0., 0., 1., 1.]])" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X = torch.ones((6, 8))\n", "X[:, 2:6] = 0\n", "X" ] }, { "cell_type": "code", "execution_count": 6, "id": "c3d67537-eb49-4136-b5bd-51cc13eaafb2", "metadata": { "origin_pos": 15, "tab": [ "pytorch" ] }, "outputs": [], "source": [ "K = torch.tensor([[1.0, -1.0]])" ] }, { "cell_type": "markdown", "id": "965ca956-b67c-451f-9dd4-8ab725165f1a", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "输出`Y`中的1代表从白色到黑色的边缘,-1代表从黑色到白色的边缘" ] }, { "cell_type": "code", "execution_count": 7, "id": "5cf508d4-c35a-428d-9bb1-96fc32ae5864", "metadata": { "origin_pos": 17, "tab": [ "pytorch" ] }, "outputs": [ { "data": { "text/plain": [ "tensor([[ 0., 1., 0., 0., 0., -1., 0.],\n", " [ 0., 1., 0., 0., 0., -1., 0.],\n", " [ 0., 1., 0., 0., 0., -1., 0.],\n", " [ 0., 1., 0., 0., 0., -1., 0.],\n", " [ 0., 1., 0., 0., 0., -1., 0.],\n", " [ 0., 1., 0., 0., 0., -1., 0.]])" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Y = corr2d(X, K)\n", "Y" ] }, { "cell_type": "markdown", "id": "7bc03775-24f6-4d2a-b2aa-e870717de626", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "卷积核`K`只可以检测垂直边缘" ] }, { "cell_type": "code", "execution_count": 8, "id": "39d014be-40db-4d15-8caf-591a2710845c", "metadata": { "origin_pos": 19, "tab": [ "pytorch" ] }, "outputs": [ { "data": { "text/plain": [ "tensor([[0., 0., 0., 0., 0.],\n", " [0., 0., 0., 0., 0.],\n", " [0., 0., 0., 0., 0.],\n", " [0., 0., 0., 0., 0.],\n", " [0., 0., 0., 0., 0.],\n", " [0., 0., 0., 0., 0.],\n", " [0., 0., 0., 0., 0.],\n", " [0., 0., 0., 0., 0.]])" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "corr2d(X.t(), K)" ] }, { "cell_type": "markdown", "id": "87ad3bb5-f1b5-4788-9db8-2c1c27cffbfa", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "学习由`X`生成`Y`的卷积核" ] }, { "cell_type": "code", "execution_count": null, "id": "dd75f3b8-6a8e-4956-8f9c-f39ac8ceb791", "metadata": { "origin_pos": 22, "tab": [ "pytorch" ] }, "outputs": [], "source": [ "# 构造一个二维卷积层,它具有1个输出通道和形状为(1,2)的卷积核\n", "conv2d = nn.Conv2d(1,1, kernel_size=(1, 2), bias=False)\n", "\n", "# 这个二维卷积层使用四维输入和输出格式(批量大小、通道、高度、宽度),\n", "# 其中批量大小和通道数都为1\n", "X = X.reshape((1, 1, 6, 8))\n", "Y = Y.reshape((1, 1, 6, 7))\n", "lr = 3e-2" ] }, { "cell_type": "code", "execution_count": 9, "id": "0d28ad4a-bf04-4352-b0c4-c5d5ec11646a", "metadata": { "origin_pos": 22, "slideshow": { "slide_type": "slide" }, "tab": [ "pytorch" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "epoch 2, loss 8.836\n", "epoch 4, loss 1.692\n", "epoch 6, loss 0.370\n", "epoch 8, loss 0.097\n", "epoch 10, loss 0.031\n" ] } ], "source": [ "for i in range(10):\n", " Y_hat = conv2d(X)\n", " l = (Y_hat - Y) ** 2\n", " conv2d.zero_grad()\n", " l.sum().backward()\n", " # 迭代更新卷积核\n", " conv2d.weight.data[:] -= lr * conv2d.weight.grad\n", " if (i + 1) % 2 == 0:\n", " print(f'epoch {i+1}, loss {l.sum():.3f}')" ] }, { "cell_type": "markdown", "id": "eaeb6855-12e3-40b0-acc8-71cadbf9f55e", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "所学的卷积核的权重张量" ] }, { "cell_type": "code", "execution_count": 10, "id": "b473f6f7-6fca-4aa6-8c77-87c57b7f9b5b", "metadata": { "origin_pos": 26, "tab": [ "pytorch" ] }, "outputs": [ { "data": { "text/plain": [ "tensor([[ 1.0015, -0.9692]])" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "conv2d.weight.data.reshape((1, 2))" ] }, { "cell_type": "markdown", "id": "5879af59-0af7-4128-a750-84d5ce60f25d", "metadata": { "jp-MarkdownHeadingCollapsed": true, "slideshow": { "slide_type": "-" }, "tags": [] }, "source": [ "## 填充和步幅\n", "\n", "在所有侧边填充1个像素" ] }, { "cell_type": "code", "execution_count": null, "id": "979a995a-96f9-4d41-8442-2d32af9074bf", "metadata": { "origin_pos": 2, "tab": [ "pytorch" ] }, "outputs": [], "source": [ "import torch\n", "from torch import nn\n", "\n", "\n", "def comp_conv2d(conv2d, X):\n", " \"\"\"初始化卷积层权重,并对输入和输出提高和缩减相应的维数\"\"\"\n", " X = X.reshape((1, 1) + X.shape) # 批量大小和通道数都是1\n", " Y = conv2d(X)\n", " return Y.reshape(Y.shape[2:]) # 省略前两个维度:批量大小和通道" ] }, { "cell_type": "code", "execution_count": 1, "id": "f9f0ed64-7d08-4788-bf0b-932b9ec4803b", "metadata": { "origin_pos": 2, "slideshow": { "slide_type": "slide" }, "tab": [ "pytorch" ] }, "outputs": [ { "data": { "text/plain": [ "torch.Size([8, 8])" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 注意,每边都填充了1行或1列,因此总共添加了2行或2列\n", "conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1)\n", "X = torch.rand(size=(8, 8))\n", "comp_conv2d(conv2d, X).shape" ] }, { "cell_type": "markdown", "id": "cf74250e-eb1b-47df-9141-f931fafc3af0", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "填充不同的高度和宽度" ] }, { "cell_type": "code", "execution_count": 2, "id": "545bfb03-c86e-4f29-9f2f-940c7ec49aaf", "metadata": { "origin_pos": 6, "tab": [ "pytorch" ] }, "outputs": [ { "data": { "text/plain": [ "torch.Size([8, 8])" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "conv2d = nn.Conv2d(1, 1, kernel_size=(5, 3), padding=(2, 1))\n", "comp_conv2d(conv2d, X).shape" ] }, { "cell_type": "markdown", "id": "6a8ce394-d251-438d-a6ad-0ae688a17a91", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "将高度和宽度的步幅设置为2" ] }, { "cell_type": "code", "execution_count": 3, "id": "15d25626-14d6-49c2-a7d2-f5b32d4ae621", "metadata": { "origin_pos": 10, "tab": [ "pytorch" ] }, "outputs": [ { "data": { "text/plain": [ "torch.Size([4, 4])" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2)\n", "comp_conv2d(conv2d, X).shape" ] }, { "cell_type": "markdown", "id": "64f13f70-7ba6-4c68-adc4-951c77fb7b6c", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "一个稍微复杂的例子" ] }, { "cell_type": "code", "execution_count": 4, "id": "7aae79d5-a75d-4769-bac5-52c0dd6c2cd4", "metadata": { "origin_pos": 14, "tab": [ "pytorch" ] }, "outputs": [ { "data": { "text/plain": [ "torch.Size([2, 2])" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "conv2d = nn.Conv2d(1, 1, kernel_size=(3, 5), padding=(0, 1), stride=(3, 4))\n", "comp_conv2d(conv2d, X).shape" ] }, { "cell_type": "markdown", "id": "b0ade0d5-8c2e-42d3-874d-bb9880c89dab", "metadata": { "jp-MarkdownHeadingCollapsed": true, "slideshow": { "slide_type": "-" }, "tags": [] }, "source": [ "## 多输入多输出通道\n", "\n", "实现一下多输入通道互相关运算" ] }, { "cell_type": "code", "execution_count": 2, "id": "9be9ab73-d95c-4a89-acfe-ac912e33e831", "metadata": { "origin_pos": 3, "tab": [ "pytorch" ] }, "outputs": [], "source": [ "import torch\n", "from d2l import torch as d2l\n", "\n", "def corr2d_multi_in(X, K):\n", " # 先遍历“X”和“K”的第0个维度(通道维度,忽略批量),再把它们加在一起\n", " return sum(d2l.corr2d(x, k) for x, k in zip(X, K))" ] }, { "cell_type": "markdown", "id": "5cb1a6f4-9155-4c6e-b452-6f0575ac6bc3", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "验证互相关运算的输出" ] }, { "cell_type": "code", "execution_count": 3, "id": "ffc69b60-744c-4a12-8f3c-ffb93993e6b8", "metadata": { "origin_pos": 6, "tab": [ "pytorch" ] }, "outputs": [ { "data": { "text/plain": [ "tensor([[ 56., 72.],\n", " [104., 120.]])" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X = torch.tensor([[[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]],\n", " [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]])\n", "K = torch.tensor([[[0.0, 1.0], [2.0, 3.0]], [[1.0, 2.0], [3.0, 4.0]]])\n", "\n", "corr2d_multi_in(X, K)" ] }, { "cell_type": "markdown", "id": "2b26766f-0d38-4dec-b444-49564c7b1706", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "计算多个通道的输出的互相关函数" ] }, { "cell_type": "code", "execution_count": 5, "id": "f973b848-4341-43b5-bdeb-dde75fc953e9", "metadata": { "origin_pos": 10, "tab": [ "pytorch" ] }, "outputs": [ { "data": { "text/plain": [ "torch.Size([3, 2, 2, 2])" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def corr2d_multi_in_out(X, K):\n", " # 迭代“K”的第0个维度,每次都对输入“X”执行互相关运算。\n", " # 最后将所有结果都叠加在一起\n", " return torch.stack([corr2d_multi_in(X, k) for k in K], 0)\n", "\n", "K = torch.stack((K, K + 1, K + 2), 0)\n", "K.shape" ] }, { "cell_type": "code", "execution_count": 6, "id": "8a5ef08c-2688-4551-9fe4-4fe83476b568", "metadata": { "origin_pos": 12, "slideshow": { "slide_type": "slide" }, "tab": [ "pytorch" ] }, "outputs": [ { "data": { "text/plain": [ "tensor([[[ 56., 72.],\n", " [104., 120.]],\n", "\n", " [[ 76., 100.],\n", " [148., 172.]],\n", "\n", " [[ 96., 128.],\n", " [192., 224.]]])" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "corr2d_multi_in_out(X, K)" ] }, { "cell_type": "markdown", "id": "8b5646a7-4eaf-4c73-ae23-a6b0ae4b3667", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "1x1卷积" ] }, { "cell_type": "code", "execution_count": null, "id": "fd63dcb7-8f02-46e9-8696-8cd24313460d", "metadata": { "origin_pos": 18, "tab": [ "pytorch" ] }, "outputs": [], "source": [ "def corr2d_multi_in_out_1x1(X, K):\n", " c_i, h, w = X.shape\n", " c_o = K.shape[0]\n", " X = X.reshape((c_i, h * w))\n", " K = K.reshape((c_o, c_i))\n", " # 全连接层中的矩阵乘法\n", " Y = torch.matmul(K, X)\n", " return Y.reshape((c_o, h, w))" ] }, { "cell_type": "code", "execution_count": 9, "id": "18e97ece-4c09-4273-9e7b-a0934046c97c", "metadata": { "origin_pos": 18, "slideshow": { "slide_type": "slide" }, "tab": [ "pytorch" ] }, "outputs": [], "source": [ "X = torch.normal(0, 1, (3, 3, 3))\n", "K = torch.normal(0, 1, (2, 3, 1, 1))\n", "\n", "Y1 = corr2d_multi_in_out_1x1(X, K)\n", "Y2 = corr2d_multi_in_out(X, K)\n", "assert float(torch.abs(Y1 - Y2).sum()) < 1e-6" ] }, { "cell_type": "markdown", "id": "64c48e4b-8c94-4de9-bd8d-e03512695e7e", "metadata": { "jp-MarkdownHeadingCollapsed": true, "slideshow": { "slide_type": "-" }, "tags": [] }, "source": [ "## 池化层\n", "\n", "池化也有译成汇聚。实现池化层的前向传播" ] }, { "cell_type": "code", "execution_count": 2, "id": "7ce8507e-5ba3-427a-9066-d1031580472d", "metadata": { "origin_pos": 3, "tab": [ "pytorch" ] }, "outputs": [], "source": [ "import torch\n", "from torch import nn\n", "from d2l import torch as d2l\n", "\n", "def pool2d(X, pool_size, mode='max'):\n", " p_h, p_w = pool_size\n", " Y = torch.zeros((X.shape[0] - p_h + 1, X.shape[1] - p_w + 1))\n", " for i in range(Y.shape[0]):\n", " for j in range(Y.shape[1]):\n", " if mode == 'max':\n", " Y[i, j] = X[i: i + p_h, j: j + p_w].max()\n", " elif mode == 'avg':\n", " Y[i, j] = X[i: i + p_h, j: j + p_w].mean()\n", " return Y" ] }, { "cell_type": "markdown", "id": "fd4f9890-9159-4d0f-92bf-629c91f522ac", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "验证二维最大汇聚层的输出" ] }, { "cell_type": "code", "execution_count": 3, "id": "4d6aca2f-7a2b-4e4e-a33f-51cfeabe3149", "metadata": { "origin_pos": 6, "tab": [ "pytorch" ] }, "outputs": [ { "data": { "text/plain": [ "tensor([[4., 5.],\n", " [7., 8.]])" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])\n", "pool2d(X, (2, 2))" ] }, { "cell_type": "markdown", "id": "da22e032-1759-4359-836e-36ecfd643583", "metadata": { "slideshow": { "slide_type": "-" } }, "source": [ "验证平均汇聚层" ] }, { "cell_type": "code", "execution_count": 4, "id": "2939723a-f3ea-4f70-b291-cf5dbfd8fe30", "metadata": { "origin_pos": 8, "tab": [ "pytorch" ] }, "outputs": [ { "data": { "text/plain": [ "tensor([[2., 3.],\n", " [5., 6.]])" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pool2d(X, (2, 2), 'avg')" ] }, { "cell_type": "markdown", "id": "c0cb77fd-3e7b-4d1d-a616-63c6100d2feb", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "填充和步幅" ] }, { "cell_type": "code", "execution_count": 5, "id": "a574a0ed-4184-4ad5-8f16-b802bea2bb99", "metadata": { "origin_pos": 11, "tab": [ "pytorch" ] }, "outputs": [ { "data": { "text/plain": [ "tensor([[[[ 0., 1., 2., 3.],\n", " [ 4., 5., 6., 7.],\n", " [ 8., 9., 10., 11.],\n", " [12., 13., 14., 15.]]]])" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X = torch.arange(16, dtype=torch.float32).reshape((1, 1, 4, 4))\n", "X" ] }, { "cell_type": "markdown", "id": "0209afc0-8eb4-4c58-a5c6-efdc1988e0fd", "metadata": { "slideshow": { "slide_type": "-" } }, "source": [ "深度学习框架中的步幅与汇聚窗口的大小相同" ] }, { "cell_type": "code", "execution_count": 6, "id": "b66fd222-9233-4dc9-bbce-87925a86498b", "metadata": { "origin_pos": 15, "tab": [ "pytorch" ] }, "outputs": [ { "data": { "text/plain": [ "tensor([[[[10.]]]])" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pool2d = nn.MaxPool2d(3)\n", "pool2d(X)" ] }, { "cell_type": "markdown", "id": "0c169cd1-b0d6-4760-88e2-0ad530c51cfc", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "填充和步幅可以手动设定" ] }, { "cell_type": "code", "execution_count": 7, "id": "4fdbb845-7f4b-4649-96c0-3c0300b6021a", "metadata": { "origin_pos": 19, "tab": [ "pytorch" ] }, "outputs": [ { "data": { "text/plain": [ "tensor([[[[ 5., 7.],\n", " [13., 15.]]]])" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pool2d = nn.MaxPool2d(3, padding=1, stride=2)\n", "pool2d(X)" ] }, { "cell_type": "markdown", "id": "7b83b34f-526b-48b1-b5ff-963c84b57471", "metadata": { "slideshow": { "slide_type": "-" } }, "source": [ "设定一个任意大小的矩形汇聚窗口,并分别设定填充和步幅的高度和宽度" ] }, { "cell_type": "code", "execution_count": 8, "id": "fc4d58c9-df77-4765-8344-948d66e9d55f", "metadata": { "origin_pos": 25, "tab": [ "pytorch" ] }, "outputs": [ { "data": { "text/plain": [ "tensor([[[[ 5., 7.],\n", " [13., 15.]]]])" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pool2d = nn.MaxPool2d((2, 3), stride=(2, 3), padding=(0, 1))\n", "pool2d(X)" ] }, { "cell_type": "markdown", "id": "6a771a4c-bda2-48e6-bd31-d8d4eec503ad", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "汇聚层在每个输入通道上单独运算" ] }, { "cell_type": "code", "execution_count": 9, "id": "8fc17adc-68a2-4395-8ca4-db4c853eae72", "metadata": { "origin_pos": 29, "tab": [ "pytorch" ] }, "outputs": [ { "data": { "text/plain": [ "tensor([[[[ 0., 1., 2., 3.],\n", " [ 4., 5., 6., 7.],\n", " [ 8., 9., 10., 11.],\n", " [12., 13., 14., 15.]],\n", "\n", " [[ 1., 2., 3., 4.],\n", " [ 5., 6., 7., 8.],\n", " [ 9., 10., 11., 12.],\n", " [13., 14., 15., 16.]]]])" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X = torch.cat((X, X + 1), 1)\n", "X" ] }, { "cell_type": "code", "execution_count": 10, "id": "4622f1b6-0352-446f-8a67-c336f42249d2", "metadata": { "origin_pos": 33, "tab": [ "pytorch" ] }, "outputs": [ { "data": { "text/plain": [ "tensor([[[[ 5., 7.],\n", " [13., 15.]],\n", "\n", " [[ 6., 8.],\n", " [14., 16.]]]])" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pool2d = nn.MaxPool2d(3, padding=1, stride=2)\n", "pool2d(X)" ] }, { "cell_type": "markdown", "id": "a71866f9-e712-467a-9e0d-3832db22e4ad", "metadata": { "slideshow": { "slide_type": "-" }, "tags": [] }, "source": [ "## 卷积神经网络(LeNet)\n", "\n", "LeNet(LeNet-5)由两个部分组成:\n", "卷积编码器和全连接层密集块" ] }, { "cell_type": "code", "execution_count": 1, "id": "10bf89f8-9718-4d30-bceb-f64ccd57d7e8", "metadata": { "origin_pos": 2, "tab": [ "pytorch" ] }, "outputs": [], "source": [ "import torch\n", "from torch import nn\n", "from d2l import torch as d2l\n", "\n", "net = nn.Sequential(\n", " nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid(),\n", " nn.AvgPool2d(kernel_size=2, stride=2),\n", " nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(),\n", " nn.AvgPool2d(kernel_size=2, stride=2),\n", " nn.Flatten(),\n", " nn.Linear(16 * 5 * 5, 120), nn.Sigmoid(),\n", " nn.Linear(120, 84), nn.Sigmoid(),\n", " nn.Linear(84, 10))" ] }, { "cell_type": "markdown", "id": "b108595b-2b06-4670-b55e-4096d2c97e7d", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "检查模型" ] }, { "cell_type": "code", "execution_count": 2, "id": "fca54cef-3f57-4901-b1e7-4039c9050a53", "metadata": { "origin_pos": 6, "tab": [ "pytorch" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Conv2d output shape: \t torch.Size([1, 6, 28, 28])\n", "Sigmoid output shape: \t torch.Size([1, 6, 28, 28])\n", "AvgPool2d output shape: \t torch.Size([1, 6, 14, 14])\n", "Conv2d output shape: \t torch.Size([1, 16, 10, 10])\n", "Sigmoid output shape: \t torch.Size([1, 16, 10, 10])\n", "AvgPool2d output shape: \t torch.Size([1, 16, 5, 5])\n", "Flatten output shape: \t torch.Size([1, 400])\n", "Linear output shape: \t torch.Size([1, 120])\n", "Sigmoid output shape: \t torch.Size([1, 120])\n", "Linear output shape: \t torch.Size([1, 84])\n", "Sigmoid output shape: \t torch.Size([1, 84])\n", "Linear output shape: \t torch.Size([1, 10])\n" ] } ], "source": [ "X = torch.rand(size=(1, 1, 28, 28), dtype=torch.float32)\n", "for layer in net:\n", " X = layer(X)\n", " print(layer.__class__.__name__,'output shape: \\t',X.shape)" ] }, { "cell_type": "markdown", "id": "97b8c6d4-e9ff-4a47-94f9-9f12348683c3", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "LeNet在Fashion-MNIST数据集上的表现" ] }, { "cell_type": "code", "execution_count": 3, "id": "f0f07725-b2d3-4461-92f3-e8465153bd96", "metadata": { "origin_pos": 9, "tab": [ "pytorch" ] }, "outputs": [], "source": [ "batch_size = 256\n", "train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)" ] }, { "cell_type": "markdown", "id": "4b457e3e-b2b0-4bab-9821-7ed7ce2e3f7f", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "对\n", "`evaluate_accuracy`函数进行轻微的修改" ] }, { "cell_type": "code", "execution_count": 4, "id": "43a68342-901c-4d8e-8066-c3f6b0f32a2a", "metadata": { "origin_pos": 13, "tab": [ "pytorch" ] }, "outputs": [], "source": [ "def evaluate_accuracy_gpu(net, data_iter, device=None): \n", " \"\"\"使用GPU计算模型在数据集上的精度\"\"\"\n", " if isinstance(net, nn.Module):\n", " net.eval() # 设置为评估模式\n", " if not device:\n", " device = next(iter(net.parameters())).device\n", " metric = d2l.Accumulator(2) # 正确预测的数量,总预测的数量\n", " with torch.no_grad():\n", " for X, y in data_iter:\n", " if isinstance(X, list): # BERT微调所需(之后介绍)\n", " X = [x.to(device) for x in X]\n", " else:\n", " X = X.to(device)\n", " y = y.to(device)\n", " metric.add(d2l.accuracy(net(X), y), y.numel())\n", " return metric[0] / metric[1]" ] }, { "cell_type": "markdown", "id": "0fb6644e-bfe3-47da-acdc-e223e632801d", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "为了使用GPU,我们还需要一点小改动" ] }, { "cell_type": "code", "execution_count": 5, "id": "c7b9b01a-3c00-4e6d-b991-06c081e442bf", "metadata": { "origin_pos": 16, "tab": [ "pytorch" ] }, "outputs": [], "source": [ "def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):\n", " \"\"\"用GPU训练模型(在第六章定义)\"\"\"\n", " def init_weights(m):\n", " if type(m) == nn.Linear or type(m) == nn.Conv2d:\n", " nn.init.xavier_uniform_(m.weight)\n", " net.apply(init_weights)\n", " print('training on', device)\n", " net.to(device)\n", " optimizer = torch.optim.SGD(net.parameters(), lr=lr)\n", " loss = nn.CrossEntropyLoss()\n", " animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],\n", " legend=['train loss', 'train acc', 'test acc'])\n", " timer, num_batches = d2l.Timer(), len(train_iter)\n", " for epoch in range(num_epochs):\n", " metric = d2l.Accumulator(3) # 训练损失之和,训练准确率之和,样本数\n", " net.train()\n", " for i, (X, y) in enumerate(train_iter):\n", " timer.start()\n", " optimizer.zero_grad()\n", " X, y = X.to(device), y.to(device)\n", " y_hat = net(X)\n", " l = loss(y_hat, y)\n", " l.backward()\n", " optimizer.step()\n", " with torch.no_grad():\n", " metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0])\n", " timer.stop()\n", " train_l = metric[0] / metric[2]\n", " train_acc = metric[1] / metric[2]\n", " if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:\n", " animator.add(epoch + (i + 1) / num_batches,\n", " (train_l, train_acc, None))\n", " test_acc = evaluate_accuracy_gpu(net, test_iter)\n", " animator.add(epoch + 1, (None, None, test_acc))\n", " print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, '\n", " f'test acc {test_acc:.3f}')\n", " print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec '\n", " f'on {str(device)}')" ] }, { "cell_type": "markdown", "id": "a6c9ffd8-fc3a-4480-bb2a-8ec164f7fe37", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "训练和评估LeNet-5模型" ] }, { "cell_type": "code", "execution_count": 6, "id": "103901ca-60e4-4694-96fd-02f56736c134", "metadata": { "origin_pos": 19, "tab": [ "pytorch" ], "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "loss 0.472, train acc 0.823, test acc 0.786\n", "3560.3 examples/sec on cpu\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " 2023-02-28T15:39:17.744575\n", " image/svg+xml\n", " \n", " \n", " Matplotlib v3.5.1, https://matplotlib.org/\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "lr, num_epochs = 0.9, 10\n", "train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.8.16" } }, "nbformat": 4, "nbformat_minor": 5 }