.. _sec_lenet: Mạng thần kinh phức tạp (LeNet) =============================== Bây giờ chúng tôi có tất cả các thành phần cần thiết để lắp ráp một CNN đầy đủ chức năng. Trong cuộc gặp gỡ trước đó của chúng tôi với dữ liệu hình ảnh, chúng tôi đã áp dụng mô hình hồi quy softmax (:numref:`sec_softmax_scratch`) và mô hình MLP (:numref:`sec_mlp_scratch`) cho hình ảnh quần áo trong bộ dữ liệu Fashion-MNIST. Để làm cho dữ liệu như vậy tuân theo hồi quy softmax và MLP s, trước tiên chúng ta làm phẳng mỗi hình ảnh từ một ma trận :math:`28\times28` thành một vector :math:`784` chiều dài cố định, và sau đó xử lý chúng với các lớp được kết nối hoàn toàn. Bây giờ chúng ta có một tay cầm trên các lớp phức tạp, chúng ta có thể giữ lại cấu trúc không gian trong hình ảnh của chúng ta. Là một lợi ích bổ sung của việc thay thế các lớp được kết nối hoàn toàn bằng các lớp kết nối, chúng ta sẽ tận hưởng các mô hình phân tích hơn đòi hỏi ít tham số hơn nhiều. Trong phần này, chúng tôi sẽ giới thiệu *LeNet*, trong số các CNN được xuất bản đầu tiên để thu hút sự chú ý rộng rãi về hiệu suất của nó trên các tác vụ thị giác máy tính. Mô hình được giới thiệu bởi (và đặt tên cho) Yann LeCun, sau đó là một nhà nghiên cứu tại AT&T Bell Labs, với mục đích nhận dạng chữ số viết tay trong hình ảnh :cite:`LeCun.Bottou.Bengio.ea.1998`. Công trình này đại diện cho đỉnh cao của một thập kỷ nghiên cứu phát triển công nghệ. Năm 1989, LeCun công bố nghiên cứu đầu tiên để đào tạo thành công CNN thông qua truyền ngược. Vào thời điểm đó LeNet đạt được kết quả xuất sắc phù hợp với hiệu suất của các máy vector hỗ trợ, sau đó là một cách tiếp cận thống trị trong việc học có giám sát. LeNet cuối cùng đã được điều chỉnh để nhận ra các chữ số để xử lý tiền gửi trong máy ATM. Cho đến ngày nay, một số máy ATM vẫn chạy mã mà Yann và đồng nghiệp Leon Bottou đã viết vào những năm 1990! LeNet ----- Ở cấp độ cao, LeNet (LeNet-5) bao gồm hai phần: (i) một bộ mã hóa phức tạp bao gồm hai lớp phức tạp; và (ii) một khối dày đặc bao gồm ba lớp kết nối đầy đủ; Kiến trúc được tóm tắt trong :numref:`img_lenet`. .. _img_lenet: .. figure:: ../img/lenet.svg Data flow in LeNet. The input is a handwritten digit, the output a probability over 10 possible outcomes. Các đơn vị cơ bản trong mỗi khối phức tạp là một lớp phức tạp, một chức năng kích hoạt sigmoid, và một hoạt động tổng hợp trung bình tiếp theo. Lưu ý rằng trong khi Relus và max-pooling hoạt động tốt hơn, những khám phá này vẫn chưa được thực hiện trong những năm 1990. Mỗi lớp phức hợp sử dụng một hạt nhân :math:`5\times 5` và một hàm kích hoạt sigmoid. Các lớp này ánh xạ các đầu vào sắp xếp không gian cho một số bản đồ tính năng hai chiều, thường làm tăng số lượng kênh. Lớp phức tạp đầu tiên có 6 kênh đầu ra, trong khi thứ hai có 16 kênh. Mỗi hoạt động tổng hợp :math:`2\times2` (sải chân 2) làm giảm kích thước bằng hệ số :math:`4` thông qua lấy mẫu xuống không gian. Khối phức tạp phát ra một đầu ra với hình dạng được đưa ra bởi (kích thước lô, số kênh, chiều cao, chiều rộng). Để truyền đầu ra từ khối phức tạp đến khối dày đặc, chúng ta phải làm phẳng từng ví dụ trong minibatch. Nói cách khác, chúng ta lấy đầu vào bốn chiều này và biến nó thành đầu vào hai chiều được mong đợi bởi các lớp được kết nối hoàn toàn: như một lời nhắc nhở, biểu diễn hai chiều mà chúng ta mong muốn sử dụng kích thước đầu tiên để lập chỉ mục các ví dụ trong minibatch và thứ hai để đưa ra biểu diễn vector phẳng of each mỗi example thí dụ. Khối dày đặc của LeNet có ba lớp kết nối hoàn toàn, tương ứng với 120, 84 và 10 đầu ra. Bởi vì chúng ta vẫn đang thực hiện phân loại, lớp đầu ra 10 chiều tương ứng với số lượng lớp đầu ra có thể. Trong khi đi đến mức bạn thực sự hiểu những gì đang xảy ra bên trong LeNet có thể đã thực hiện một chút công việc, hy vọng đoạn mã sau đây sẽ thuyết phục bạn rằng việc thực hiện các mô hình như vậy với các khuôn khổ học sâu hiện đại rất đơn giản. Chúng ta chỉ cần khởi tạo một khối ``Sequential`` và chuỗi với nhau các lớp thích hợp. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python from mxnet import autograd, gluon, init, np, npx from mxnet.gluon import nn from d2l import mxnet as d2l npx.set_np() net = nn.Sequential() net.add(nn.Conv2D(channels=6, kernel_size=5, padding=2, activation='sigmoid'), nn.AvgPool2D(pool_size=2, strides=2), nn.Conv2D(channels=16, kernel_size=5, activation='sigmoid'), nn.AvgPool2D(pool_size=2, strides=2), # `Dense` will transform an input of the shape (batch size, number of # channels, height, width) into an input of the shape (batch size, # number of channels * height * width) automatically by default nn.Dense(120, activation='sigmoid'), nn.Dense(84, activation='sigmoid'), nn.Dense(10)) .. raw:: html
.. raw:: html
.. code:: python import torch from torch import nn from d2l import torch as d2l net = nn.Sequential( nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid(), nn.AvgPool2d(kernel_size=2, stride=2), nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(), nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(), nn.Linear(16 * 5 * 5, 120), nn.Sigmoid(), nn.Linear(120, 84), nn.Sigmoid(), nn.Linear(84, 10)) .. raw:: html
.. raw:: html
.. code:: python import tensorflow as tf from d2l import tensorflow as d2l def net(): return tf.keras.models.Sequential([ tf.keras.layers.Conv2D(filters=6, kernel_size=5, activation='sigmoid', padding='same'), tf.keras.layers.AvgPool2D(pool_size=2, strides=2), tf.keras.layers.Conv2D(filters=16, kernel_size=5, activation='sigmoid'), tf.keras.layers.AvgPool2D(pool_size=2, strides=2), tf.keras.layers.Flatten(), tf.keras.layers.Dense(120, activation='sigmoid'), tf.keras.layers.Dense(84, activation='sigmoid'), tf.keras.layers.Dense(10)]) .. raw:: html
.. raw:: html
Chúng tôi đã có một sự tự do nhỏ với mô hình ban đầu, loại bỏ kích hoạt Gaussian trong lớp cuối cùng. Ngoài ra, mạng này phù hợp với kiến trúc LeNet-5 ban đầu. Bằng cách truyền một hình ảnh :math:`28 \times 28` một kênh (đen và trắng) qua mạng và in hình dạng đầu ra ở mỗi lớp, chúng ta có thể kiểm tra mô hình để đảm bảo rằng các hoạt động của nó phù hợp với những gì chúng ta mong đợi từ :numref:`img_lenet_vert`. .. _img_lenet_vert: .. figure:: ../img/lenet-vert.svg Compressed notation for LeNet-5. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python X = np.random.uniform(size=(1, 1, 28, 28)) net.initialize() for layer in net: X = layer(X) print(layer.name, 'output shape:\t', X.shape) .. parsed-literal:: :class: output conv0 output shape: (1, 6, 28, 28) pool0 output shape: (1, 6, 14, 14) conv1 output shape: (1, 16, 10, 10) pool1 output shape: (1, 16, 5, 5) dense0 output shape: (1, 120) dense1 output shape: (1, 84) dense2 output shape: (1, 10) .. raw:: html
.. raw:: html
.. code:: python X = torch.rand(size=(1, 1, 28, 28), dtype=torch.float32) for layer in net: X = layer(X) print(layer.__class__.__name__,'output shape: \t',X.shape) .. parsed-literal:: :class: output Conv2d output shape: torch.Size([1, 6, 28, 28]) Sigmoid output shape: torch.Size([1, 6, 28, 28]) AvgPool2d output shape: torch.Size([1, 6, 14, 14]) Conv2d output shape: torch.Size([1, 16, 10, 10]) Sigmoid output shape: torch.Size([1, 16, 10, 10]) AvgPool2d output shape: torch.Size([1, 16, 5, 5]) Flatten output shape: torch.Size([1, 400]) Linear output shape: torch.Size([1, 120]) Sigmoid output shape: torch.Size([1, 120]) Linear output shape: torch.Size([1, 84]) Sigmoid output shape: torch.Size([1, 84]) Linear output shape: torch.Size([1, 10]) .. raw:: html
.. raw:: html
.. code:: python X = tf.random.uniform((1, 28, 28, 1)) for layer in net().layers: X = layer(X) print(layer.__class__.__name__, 'output shape: \t', X.shape) .. parsed-literal:: :class: output Conv2D output shape: (1, 28, 28, 6) AveragePooling2D output shape: (1, 14, 14, 6) Conv2D output shape: (1, 10, 10, 16) AveragePooling2D output shape: (1, 5, 5, 16) Flatten output shape: (1, 400) Dense output shape: (1, 120) Dense output shape: (1, 84) Dense output shape: (1, 10) .. raw:: html
.. raw:: html
Lưu ý rằng chiều cao và chiều rộng của biểu diễn tại mỗi lớp trong suốt khối phức tạp bị giảm (so với lớp trước đó). Lớp kết hợp đầu tiên sử dụng 2 pixel đệm để bù đắp cho việc giảm chiều cao và chiều rộng mà nếu không sẽ là kết quả của việc sử dụng một hạt nhân :math:`5 \times 5`. Ngược lại, lớp phức tạp thứ hai bỏ đệm, và do đó chiều cao và chiều rộng đều giảm 4 pixel. Khi chúng ta đi lên ngăn xếp các lớp, số lượng kênh tăng lớp trên lớp từ 1 trong đầu vào lên 6 sau lớp phức tạp đầu tiên và 16 sau lớp phức tạp thứ hai. Tuy nhiên, mỗi lớp tổng hợp giảm một nửa chiều cao và chiều rộng. Cuối cùng, mỗi lớp được kết nối hoàn toàn làm giảm chiều, cuối cùng phát ra một đầu ra có kích thước phù hợp với số lượng lớp. Đào tạo ------- Bây giờ chúng tôi đã triển khai mô hình, chúng ta hãy chạy một thử nghiệm để xem LeNet giá vé trên Fashion-MNIST như thế nào. .. code:: python batch_size = 256 train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size) Trong khi CNN có ít tham số hơn, chúng vẫn có thể tốn kém hơn để tính toán so với MLP sâu tương tự vì mỗi tham số tham gia vào nhiều phép nhân hơn. Nếu bạn có quyền truy cập vào GPU, đây có thể là thời điểm tốt để đưa nó vào hành động để tăng tốc độ đào tạo. .. raw:: html
mxnetpytorch
.. raw:: html
Để đánh giá, chúng ta cần thực hiện một sửa đổi nhỏ đối với hàm ``evaluate_accuracy`` mà chúng tôi đã mô tả trong :numref:`sec_softmax_scratch`. Vì tập dữ liệu đầy đủ nằm trong bộ nhớ chính, chúng ta cần sao chép nó vào bộ nhớ GPU trước khi mô hình sử dụng GPU để tính toán với bộ dữ liệu. .. code:: python def evaluate_accuracy_gpu(net, data_iter, device=None): #@save """Compute the accuracy for a model on a dataset using a GPU.""" if not device: # Query the first device where the first parameter is on device = list(net.collect_params().values())[0].list_ctx()[0] # No. of correct predictions, no. of predictions metric = d2l.Accumulator(2) for X, y in data_iter: X, y = X.as_in_ctx(device), y.as_in_ctx(device) metric.add(d2l.accuracy(net(X), y), d2l.size(y)) return metric[0] / metric[1] .. raw:: html
.. raw:: html
Để đánh giá, chúng ta cần thực hiện một sửa đổi nhỏ đối với hàm ``evaluate_accuracy`` mà chúng tôi đã mô tả trong :numref:`sec_softmax_scratch`. Vì tập dữ liệu đầy đủ nằm trong bộ nhớ chính, chúng ta cần sao chép nó vào bộ nhớ GPU trước khi mô hình sử dụng GPU để tính toán với bộ dữ liệu. .. code:: python def evaluate_accuracy_gpu(net, data_iter, device=None): #@save """Compute the accuracy for a model on a dataset using a GPU.""" if isinstance(net, nn.Module): net.eval() # Set the model to evaluation mode if not device: device = next(iter(net.parameters())).device # No. of correct predictions, no. of predictions metric = d2l.Accumulator(2) with torch.no_grad(): for X, y in data_iter: if isinstance(X, list): # Required for BERT Fine-tuning (to be covered later) X = [x.to(device) for x in X] else: X = X.to(device) y = y.to(device) metric.add(d2l.accuracy(net(X), y), y.numel()) return metric[0] / metric[1] .. raw:: html
.. raw:: html
Chúng tôi cũng cần cập nhật chức năng đào tạo của chúng tôi để đối phó với GPU. Không giống như ``train_epoch_ch3`` được xác định trong :numref:`sec_softmax_scratch`, bây giờ chúng ta cần di chuyển từng minibatch dữ liệu đến thiết bị được chỉ định của chúng tôi (hy vọng là GPU) trước khi thực hiện tuyên truyền chuyển tiếp và lùi. Chức năng huấn luyện ``train_ch6`` cũng tương tự như ``train_ch3`` được định nghĩa trong :numref:`sec_softmax_scratch`. Vì chúng tôi sẽ triển khai các mạng với nhiều lớp trong tương lai, chúng tôi sẽ chủ yếu dựa vào các API cấp cao. Hàm đào tạo sau đây giả định một mô hình được tạo từ API cấp cao làm đầu vào và được tối ưu hóa cho phù hợp. Chúng tôi khởi tạo các tham số mô hình trên thiết bị được chỉ định bởi đối số ``device``, sử dụng khởi tạo Xavier như được giới thiệu trong :numref:`subsec_xavier`. Cũng giống như với MLP, chức năng mất mát của chúng tôi là cross-entropy, và chúng tôi giảm thiểu nó thông qua minibatch stochastic gradient descent. Vì mỗi kỷ nguyên mất hàng chục giây để chạy, chúng tôi hình dung sự mất mát đào tạo thường xuyên hơn. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python #@save def train_ch6(net, train_iter, test_iter, num_epochs, lr, device): """Train a model with a GPU (defined in Chapter 6).""" net.initialize(force_reinit=True, ctx=device, init=init.Xavier()) loss = gluon.loss.SoftmaxCrossEntropyLoss() trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': lr}) animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs], legend=['train loss', 'train acc', 'test acc']) timer, num_batches = d2l.Timer(), len(train_iter) for epoch in range(num_epochs): # Sum of training loss, sum of training accuracy, no. of examples metric = d2l.Accumulator(3) for i, (X, y) in enumerate(train_iter): timer.start() # Here is the major difference from `d2l.train_epoch_ch3` X, y = X.as_in_ctx(device), y.as_in_ctx(device) with autograd.record(): y_hat = net(X) l = loss(y_hat, y) l.backward() trainer.step(X.shape[0]) metric.add(l.sum(), d2l.accuracy(y_hat, y), X.shape[0]) timer.stop() train_l = metric[0] / metric[2] train_acc = metric[1] / metric[2] if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1: animator.add(epoch + (i + 1) / num_batches, (train_l, train_acc, None)) test_acc = evaluate_accuracy_gpu(net, test_iter) animator.add(epoch + 1, (None, None, test_acc)) print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, ' f'test acc {test_acc:.3f}') print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec ' f'on {str(device)}') .. raw:: html
.. raw:: html
.. code:: python #@save def train_ch6(net, train_iter, test_iter, num_epochs, lr, device): """Train a model with a GPU (defined in Chapter 6).""" def init_weights(m): if type(m) == nn.Linear or type(m) == nn.Conv2d: nn.init.xavier_uniform_(m.weight) net.apply(init_weights) print('training on', device) net.to(device) optimizer = torch.optim.SGD(net.parameters(), lr=lr) loss = nn.CrossEntropyLoss() animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs], legend=['train loss', 'train acc', 'test acc']) timer, num_batches = d2l.Timer(), len(train_iter) for epoch in range(num_epochs): # Sum of training loss, sum of training accuracy, no. of examples metric = d2l.Accumulator(3) net.train() for i, (X, y) in enumerate(train_iter): timer.start() optimizer.zero_grad() X, y = X.to(device), y.to(device) y_hat = net(X) l = loss(y_hat, y) l.backward() optimizer.step() with torch.no_grad(): metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0]) timer.stop() train_l = metric[0] / metric[2] train_acc = metric[1] / metric[2] if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1: animator.add(epoch + (i + 1) / num_batches, (train_l, train_acc, None)) test_acc = evaluate_accuracy_gpu(net, test_iter) animator.add(epoch + 1, (None, None, test_acc)) print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, ' f'test acc {test_acc:.3f}') print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec ' f'on {str(device)}') .. raw:: html
.. raw:: html
.. code:: python class TrainCallback(tf.keras.callbacks.Callback): #@save """A callback to visiualize the training progress.""" def __init__(self, net, train_iter, test_iter, num_epochs, device_name): self.timer = d2l.Timer() self.animator = d2l.Animator( xlabel='epoch', xlim=[1, num_epochs], legend=[ 'train loss', 'train acc', 'test acc']) self.net = net self.train_iter = train_iter self.test_iter = test_iter self.num_epochs = num_epochs self.device_name = device_name def on_epoch_begin(self, epoch, logs=None): self.timer.start() def on_epoch_end(self, epoch, logs): self.timer.stop() test_acc = self.net.evaluate( self.test_iter, verbose=0, return_dict=True)['accuracy'] metrics = (logs['loss'], logs['accuracy'], test_acc) self.animator.add(epoch + 1, metrics) if epoch == self.num_epochs - 1: batch_size = next(iter(self.train_iter))[0].shape[0] num_examples = batch_size * tf.data.experimental.cardinality( self.train_iter).numpy() print(f'loss {metrics[0]:.3f}, train acc {metrics[1]:.3f}, ' f'test acc {metrics[2]:.3f}') print(f'{num_examples / self.timer.avg():.1f} examples/sec on ' f'{str(self.device_name)}') #@save def train_ch6(net_fn, train_iter, test_iter, num_epochs, lr, device): """Train a model with a GPU (defined in Chapter 6).""" device_name = device._device_name strategy = tf.distribute.OneDeviceStrategy(device_name) with strategy.scope(): optimizer = tf.keras.optimizers.SGD(learning_rate=lr) loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True) net = net_fn() net.compile(optimizer=optimizer, loss=loss, metrics=['accuracy']) callback = TrainCallback(net, train_iter, test_iter, num_epochs, device_name) net.fit(train_iter, epochs=num_epochs, verbose=0, callbacks=[callback]) return net .. raw:: html
.. raw:: html
Bây giờ chúng ta hãy đào tạo và đánh giá mô hình LeNet-5. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python lr, num_epochs = 0.9, 10 train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu()) .. parsed-literal:: :class: output loss 0.473, train acc 0.822, test acc 0.778 42138.4 examples/sec on gpu(0) .. figure:: output_lenet_4a2e9e_52_1.svg .. raw:: html
.. raw:: html
.. code:: python lr, num_epochs = 0.9, 10 train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu()) .. parsed-literal:: :class: output loss 0.460, train acc 0.827, test acc 0.823 82186.3 examples/sec on cuda:0 .. figure:: output_lenet_4a2e9e_55_1.svg .. raw:: html
.. raw:: html
.. code:: python lr, num_epochs = 0.9, 10 train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu()) .. parsed-literal:: :class: output loss 0.462, train acc 0.827, test acc 0.806 51978.1 examples/sec on /GPU:0 .. parsed-literal:: :class: output .. figure:: output_lenet_4a2e9e_58_2.svg .. raw:: html
.. raw:: html
Tóm tắt ------- - CNN là một mạng sử dụng các lớp phức tạp. - Trong một CNN, chúng tôi xen kẽ các phức tạp, phi tuyến tính, và (thường) các hoạt động tập hợp. - Trong một CNN, các lớp phức hợp thường được sắp xếp sao cho chúng giảm dần độ phân giải không gian của các biểu diễn, đồng thời tăng số lượng kênh. - Trong CNN truyền thống, các biểu diễn được mã hóa bởi các khối phức tạp được xử lý bởi một hoặc nhiều lớp kết nối đầy đủ trước khi phát ra đầu ra. - LeNet được cho là triển khai thành công đầu tiên của một mạng như vậy. Bài tập ------- 1. Thay thế các pooling trung bình với tổng hợp tối đa. Điều gì xảy ra? 2. Cố gắng xây dựng một mạng phức tạp hơn dựa trên LeNet để cải thiện độ chính xác của nó. 1. Điều chỉnh kích thước cửa sổ covolution. 2. Điều chỉnh số lượng kênh đầu ra. 3. Điều chỉnh chức năng kích hoạt (ví dụ: ReLU). 4. Điều chỉnh số lượng lớp covolution. 5. Điều chỉnh số lượng lớp được kết nối hoàn toàn. 6. Điều chỉnh tỷ lệ học tập và các chi tiết đào tạo khác (ví dụ: khởi tạo và số kỷ nguyên.) 3. Hãy thử mạng cải tiến trên tập dữ liệu MNIST ban đầu. 4. Hiển thị các kích hoạt của lớp đầu tiên và thứ hai của LeNet cho các đầu vào khác nhau (ví dụ, áo len và áo khoác). .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html