{
"cells": [
{
"cell_type": "markdown",
"id": "0ac5d986",
"metadata": {
"origin_pos": 0
},
"source": [
"# Mạng kết nối mật độ (DenseNet)\n",
"\n",
"ResNet thay đổi đáng kể quan điểm về cách tham số hóa các chức năng trong các mạng sâu. * DenseNet* (mạng phức tạp dày đặc) ở một mức độ nào đó là phần mở rộng hợp lý của :cite:`Huang.Liu.Van-Der-Maaten.ea.2017` này. Để hiểu làm thế nào để đến nó, chúng ta hãy đi một đường vòng nhỏ đến toán học. \n",
"\n",
"## Từ ResNet đến DenseNet\n",
"\n",
"Nhớ lại bản mở rộng Taylor cho các chức năng. Đối với điểm $x = 0$ nó có thể được viết là \n",
"\n",
"$$f(x) = f(0) + f'(0) x + \\frac{f''(0)}{2!} x^2 + \\frac{f'''(0)}{3!} x^3 + \\ldots.$$\n",
"\n",
"Điểm mấu chốt là nó phân hủy một hàm thành các thuật ngữ thứ tự ngày càng cao hơn. Trong một tĩnh mạch tương tự, ResNet phân hủy các chức năng thành \n",
"\n",
"$$f(\\mathbf{x}) = \\mathbf{x} + g(\\mathbf{x}).$$\n",
"\n",
"Đó là, ResNet phân hủy $f$ thành một thuật ngữ tuyến tính đơn giản và một thuật ngữ phi tuyến phức tạp hơn. Điều gì sẽ xảy ra nếu chúng ta muốn nắm bắt (không nhất thiết phải thêm) thông tin ngoài hai thuật ngữ? Một giải pháp là DenseNet :cite:`Huang.Liu.Van-Der-Maaten.ea.2017`. \n",
"\n",
"\n",
":label:`fig_densenet_block`\n",
"\n",
"Như thể hiện trong :numref:`fig_densenet_block`, sự khác biệt chính giữa ResNet và DenseNet là trong trường hợp đầu ra sau là * concatenated* (được biểu thị bởi $[,]$) thay vì được thêm vào. Do đó, chúng tôi thực hiện ánh xạ từ $\\mathbf{x}$ đến các giá trị của nó sau khi áp dụng một chuỗi hàm ngày càng phức tạp: \n",
"\n",
"$$\\mathbf{x} \\to \\left[\n",
"\\mathbf{x},\n",
"f_1(\\mathbf{x}),\n",
"f_2([\\mathbf{x}, f_1(\\mathbf{x})]), f_3([\\mathbf{x}, f_1(\\mathbf{x}), f_2([\\mathbf{x}, f_1(\\mathbf{x})])]), \\ldots\\right].$$\n",
"\n",
"Cuối cùng, tất cả các chức năng này được kết hợp trong MLP để giảm số lượng tính năng một lần nữa. Về mặt thực hiện, điều này khá đơn giản: thay vì thêm các thuật ngữ, chúng tôi nối chúng. Tên DenseNet phát sinh từ thực tế là đồ thị phụ thuộc giữa các biến trở nên khá dày đặc. Lớp cuối cùng của một chuỗi như vậy được kết nối mật độ với tất cả các lớp trước đó. Các kết nối dày đặc được hiển thị trong :numref:`fig_densenet`. \n",
"\n",
"\n",
":label:`fig_densenet`\n",
"\n",
"Các thành phần chính tạo ra DenseNet là các khối * dày đặc * và lớp chuyển thuyền*. Cái trước xác định cách các đầu vào và đầu ra được nối, trong khi cái sau kiểm soát số lượng kênh để nó không quá lớn. \n",
"\n",
"## [** Khối dày dàng**]\n",
"\n",
"DenseNet sử dụng cấu trúc “chuẩn hóa hàng loạt, kích hoạt và phức tạp” được sửa đổi của ResNet (xem bài tập trong :numref:`sec_resnet`). Đầu tiên, chúng tôi thực hiện cấu trúc khối phức tạp này.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "24efe9c7",
"metadata": {
"execution": {
"iopub.execute_input": "2022-04-26T12:36:08.987285Z",
"iopub.status.busy": "2022-04-26T12:36:08.986724Z",
"iopub.status.idle": "2022-04-26T12:36:11.010666Z",
"shell.execute_reply": "2022-04-26T12:36:11.009789Z"
},
"origin_pos": 2,
"tab": [
"pytorch"
]
},
"outputs": [],
"source": [
"import torch\n",
"from torch import nn\n",
"from d2l import torch as d2l\n",
"\n",
"\n",
"def conv_block(input_channels, num_channels):\n",
" return nn.Sequential(\n",
" nn.BatchNorm2d(input_channels), nn.ReLU(),\n",
" nn.Conv2d(input_channels, num_channels, kernel_size=3, padding=1))"
]
},
{
"cell_type": "markdown",
"id": "d69de923",
"metadata": {
"origin_pos": 4
},
"source": [
"Một khối * dày dàng* bao gồm nhiều khối phức tạp, mỗi khối sử dụng cùng một số kênh đầu ra. Tuy nhiên, trong quá trình lan truyền về phía trước, chúng tôi nối đầu vào và đầu ra của mỗi khối phức tạp trên kích thước kênh.\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "cd2e02dc",
"metadata": {
"execution": {
"iopub.execute_input": "2022-04-26T12:36:11.015408Z",
"iopub.status.busy": "2022-04-26T12:36:11.014988Z",
"iopub.status.idle": "2022-04-26T12:36:11.020717Z",
"shell.execute_reply": "2022-04-26T12:36:11.020075Z"
},
"origin_pos": 6,
"tab": [
"pytorch"
]
},
"outputs": [],
"source": [
"class DenseBlock(nn.Module):\n",
" def __init__(self, num_convs, input_channels, num_channels):\n",
" super(DenseBlock, self).__init__()\n",
" layer = []\n",
" for i in range(num_convs):\n",
" layer.append(conv_block(\n",
" num_channels * i + input_channels, num_channels))\n",
" self.net = nn.Sequential(*layer)\n",
"\n",
" def forward(self, X):\n",
" for blk in self.net:\n",
" Y = blk(X)\n",
" # Concatenate the input and output of each block on the channel\n",
" # dimension\n",
" X = torch.cat((X, Y), dim=1)\n",
" return X"
]
},
{
"cell_type": "markdown",
"id": "506341de",
"metadata": {
"origin_pos": 8
},
"source": [
"Trong ví dụ sau, chúng ta [**define a `DenseBlock` instance**] với 2 khối phức tạp của 10 kênh đầu ra. Khi sử dụng đầu vào với 3 kênh, chúng tôi sẽ nhận được đầu ra với $3+2\\times 10=23$ kênh. Số lượng các kênh khối phức tạp kiểm soát sự tăng trưởng về số lượng kênh đầu ra so với số lượng kênh đầu vào. Đây cũng được gọi là * tốc độ tăng trưởng*.\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "699d081e",
"metadata": {
"execution": {
"iopub.execute_input": "2022-04-26T12:36:11.023669Z",
"iopub.status.busy": "2022-04-26T12:36:11.023291Z",
"iopub.status.idle": "2022-04-26T12:36:11.038682Z",
"shell.execute_reply": "2022-04-26T12:36:11.037927Z"
},
"origin_pos": 10,
"tab": [
"pytorch"
]
},
"outputs": [
{
"data": {
"text/plain": [
"torch.Size([4, 23, 8, 8])"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"blk = DenseBlock(2, 3, 10)\n",
"X = torch.randn(4, 3, 8, 8)\n",
"Y = blk(X)\n",
"Y.shape"
]
},
{
"cell_type": "markdown",
"id": "0b875ccd",
"metadata": {
"origin_pos": 12
},
"source": [
"## [**Layers Transition**]\n",
"\n",
"Vì mỗi khối dày đặc sẽ làm tăng số lượng kênh, việc thêm quá nhiều trong số chúng sẽ dẫn đến một mô hình quá phức tạp. Một lớp chuyển đổi* được sử dụng để kiểm soát độ phức tạp của mô hình. Nó làm giảm số lượng kênh bằng cách sử dụng lớp ghép $1\\times 1$ và giảm một nửa chiều cao và chiều rộng của lớp tổng hợp trung bình với một bước tiến là 2, làm giảm thêm độ phức tạp của mô hình.\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "86cb8d8f",
"metadata": {
"execution": {
"iopub.execute_input": "2022-04-26T12:36:11.041709Z",
"iopub.status.busy": "2022-04-26T12:36:11.041245Z",
"iopub.status.idle": "2022-04-26T12:36:11.045364Z",
"shell.execute_reply": "2022-04-26T12:36:11.044718Z"
},
"origin_pos": 14,
"tab": [
"pytorch"
]
},
"outputs": [],
"source": [
"def transition_block(input_channels, num_channels):\n",
" return nn.Sequential(\n",
" nn.BatchNorm2d(input_channels), nn.ReLU(),\n",
" nn.Conv2d(input_channels, num_channels, kernel_size=1),\n",
" nn.AvgPool2d(kernel_size=2, stride=2))"
]
},
{
"cell_type": "markdown",
"id": "812cc895",
"metadata": {
"origin_pos": 16
},
"source": [
"[**Áp dụng một lớp chuyển tiếp**] với 10 kênh đến đầu ra của khối dày đặc trong ví dụ trước. Điều này làm giảm số lượng kênh đầu ra xuống 10 và giảm một nửa chiều cao và chiều rộng.\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "58606569",
"metadata": {
"execution": {
"iopub.execute_input": "2022-04-26T12:36:11.048397Z",
"iopub.status.busy": "2022-04-26T12:36:11.047961Z",
"iopub.status.idle": "2022-04-26T12:36:11.055525Z",
"shell.execute_reply": "2022-04-26T12:36:11.054876Z"
},
"origin_pos": 18,
"tab": [
"pytorch"
]
},
"outputs": [
{
"data": {
"text/plain": [
"torch.Size([4, 10, 4, 4])"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"blk = transition_block(23, 10)\n",
"blk(Y).shape"
]
},
{
"cell_type": "markdown",
"id": "06e3e5d9",
"metadata": {
"origin_pos": 20
},
"source": [
"## [**Mô hình DenseNet**]\n",
"\n",
"Tiếp theo, chúng ta sẽ xây dựng một mô hình DenseNet. DenseNet lần đầu tiên sử dụng cùng một lớp tích hợp đơn và lớp tổng hợp tối đa như trong ResNet.\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "2ec88e42",
"metadata": {
"execution": {
"iopub.execute_input": "2022-04-26T12:36:11.058792Z",
"iopub.status.busy": "2022-04-26T12:36:11.058150Z",
"iopub.status.idle": "2022-04-26T12:36:11.062922Z",
"shell.execute_reply": "2022-04-26T12:36:11.062216Z"
},
"origin_pos": 22,
"tab": [
"pytorch"
]
},
"outputs": [],
"source": [
"b1 = nn.Sequential(\n",
" nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),\n",
" nn.BatchNorm2d(64), nn.ReLU(),\n",
" nn.MaxPool2d(kernel_size=3, stride=2, padding=1))"
]
},
{
"cell_type": "markdown",
"id": "914c1d3d",
"metadata": {
"origin_pos": 24
},
"source": [
"Sau đó, tương tự như bốn mô-đun được tạo thành từ các khối còn lại mà ResNet sử dụng, DenseNet sử dụng bốn khối dày đặc. Tương tự như ResNet, chúng ta có thể đặt số lớp phức tạp được sử dụng trong mỗi khối dày đặc. Ở đây, chúng tôi đặt nó thành 4, phù hợp với mô hình ResNet-18 trong :numref:`sec_resnet`. Hơn nữa, chúng tôi đặt số lượng kênh (tức là tốc độ tăng trưởng) cho các lớp phức tạp trong khối dày đặc thành 32, vì vậy 128 kênh sẽ được thêm vào mỗi khối dày đặc. \n",
"\n",
"Trong ResNet, chiều cao và chiều rộng được giảm giữa mỗi mô-đun bởi một khối còn lại với một sải chân là 2. Ở đây, chúng ta sử dụng layer transition để giảm một nửa chiều cao và chiều rộng và giảm một nửa số kênh.\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "e5453a27",
"metadata": {
"execution": {
"iopub.execute_input": "2022-04-26T12:36:11.065655Z",
"iopub.status.busy": "2022-04-26T12:36:11.065433Z",
"iopub.status.idle": "2022-04-26T12:36:11.084858Z",
"shell.execute_reply": "2022-04-26T12:36:11.084201Z"
},
"origin_pos": 26,
"tab": [
"pytorch"
]
},
"outputs": [],
"source": [
"# `num_channels`: the current number of channels\n",
"num_channels, growth_rate = 64, 32\n",
"num_convs_in_dense_blocks = [4, 4, 4, 4]\n",
"blks = []\n",
"for i, num_convs in enumerate(num_convs_in_dense_blocks):\n",
" blks.append(DenseBlock(num_convs, num_channels, growth_rate))\n",
" # This is the number of output channels in the previous dense block\n",
" num_channels += num_convs * growth_rate\n",
" # A transition layer that halves the number of channels is added between\n",
" # the dense blocks\n",
" if i != len(num_convs_in_dense_blocks) - 1:\n",
" blks.append(transition_block(num_channels, num_channels // 2))\n",
" num_channels = num_channels // 2"
]
},
{
"cell_type": "markdown",
"id": "c3e82bdf",
"metadata": {
"origin_pos": 28
},
"source": [
"Tương tự như ResNet, một lớp tổng hợp toàn cầu và một lớp kết nối hoàn toàn được kết nối ở cuối để tạo ra đầu ra.\n"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "5bdcaf9d",
"metadata": {
"execution": {
"iopub.execute_input": "2022-04-26T12:36:11.087600Z",
"iopub.status.busy": "2022-04-26T12:36:11.087379Z",
"iopub.status.idle": "2022-04-26T12:36:11.092597Z",
"shell.execute_reply": "2022-04-26T12:36:11.091693Z"
},
"origin_pos": 30,
"tab": [
"pytorch"
]
},
"outputs": [],
"source": [
"net = nn.Sequential(\n",
" b1, *blks,\n",
" nn.BatchNorm2d(num_channels), nn.ReLU(),\n",
" nn.AdaptiveAvgPool2d((1, 1)),\n",
" nn.Flatten(),\n",
" nn.Linear(num_channels, 10))"
]
},
{
"cell_type": "markdown",
"id": "36d55bea",
"metadata": {
"origin_pos": 32
},
"source": [
"## [**Đào tạo**]\n",
"\n",
"Vì chúng ta đang sử dụng mạng sâu hơn ở đây, trong phần này, chúng ta sẽ giảm chiều cao và chiều rộng đầu vào từ 224 xuống 96 để đơn giản hóa tính toán.\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "1b0433e2",
"metadata": {
"execution": {
"iopub.execute_input": "2022-04-26T12:36:11.095323Z",
"iopub.status.busy": "2022-04-26T12:36:11.095103Z",
"iopub.status.idle": "2022-04-26T12:38:36.403746Z",
"shell.execute_reply": "2022-04-26T12:38:36.402997Z"
},
"origin_pos": 33,
"tab": [
"pytorch"
]
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"loss 0.146, train acc 0.946, test acc 0.905\n",
"5558.7 examples/sec on cuda:0\n"
]
},
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"lr, num_epochs, batch_size = 0.1, 10, 256\n",
"train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)\n",
"d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())"
]
},
{
"cell_type": "markdown",
"id": "80a3b1a8",
"metadata": {
"origin_pos": 34
},
"source": [
"## Tóm tắt\n",
"\n",
"* Về kết nối chéo lớp, không giống như ResNet, nơi đầu vào và đầu ra được thêm vào với nhau, DenseNet nối đầu vào và đầu ra trên kích thước kênh.\n",
"* Các thành phần chính sáng tác DenseNet là các khối dày đặc và các lớp chuyển tiếp.\n",
"* Chúng ta cần phải kiểm soát kích thước khi soạn mạng bằng cách thêm các lớp chuyển tiếp thu nhỏ lại số kênh.\n",
"\n",
"## Bài tập\n",
"\n",
"1. Tại sao chúng ta sử dụng tổng hợp trung bình chứ không phải là tổng hợp tối đa trong layer chuyển tiếp?\n",
"1. Một trong những lợi thế được đề cập trong giấy DenseNet là các thông số mô hình của nó nhỏ hơn so với các thông số của ResNet. Tại sao lại là trường hợp này?\n",
"1. Một vấn đề mà DenseNet đã bị chỉ trích là mức tiêu thụ bộ nhớ cao.\n",
" 1. Đây có thực sự là trường hợp? Cố gắng thay đổi hình dạng đầu vào thành $224\\times 224$ để xem mức tiêu thụ bộ nhớ GPU thực tế.\n",
" 1. Bạn có thể nghĩ về một phương tiện thay thế để giảm tiêu thụ bộ nhớ? Làm thế nào bạn sẽ cần phải thay đổi khuôn khổ?\n",
"1. Thực hiện các phiên bản DenseNet khác nhau được trình bày trong Bảng 1 của giấy DenseNet :cite:`Huang.Liu.Van-Der-Maaten.ea.2017`.\n",
"1. Thiết kế mô hình dựa trên MLP bằng cách áp dụng ý tưởng DenseNet. Áp dụng nó cho nhiệm vụ dự đoán giá nhà ở trong :numref:`sec_kaggle_house`.\n"
]
},
{
"cell_type": "markdown",
"id": "4e745d1f",
"metadata": {
"origin_pos": 36,
"tab": [
"pytorch"
]
},
"source": [
"[Discussions](https://discuss.d2l.ai/t/88)\n"
]
}
],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}