tek4

Triển Khai Phương Thức Forward Cho Mạng Neural Tích Chập Trong PyTorch

by - September. 21, 2021
Kiến thức
Học
<p style="text-align: justify;"><em>Ch&agrave;o mừng c&aacute;c bạn đến với b&agrave;i viết thứ 21 trong loạt b&agrave;i về&nbsp;<strong><a href="../../../lap-trinh-neural-network-voi-pytorch-deep-learning-voi-pytorch/" target="_blank" rel="noopener">Lập Tr&igrave;nh Neural Network Với Pytorch</a>.&nbsp;</strong>Trong phần n&agrave;y, ch&uacute;ng ta sẽ tr&igrave;nh b&agrave;y về c&aacute;ch triển khai phương thức forward cho một mạng neural t&iacute;ch chập (CNN) trong PyTorch. Bắt đầu th&ocirc;i!</em></p> <h3 style="text-align: justify;">Tiến độ của dự &aacute;n</h3> <p style="text-align: justify;">Cho đến nay trong loạt b&agrave;i n&agrave;y, ch&uacute;ng ta đ&atilde; thực hiện xong giai đoạn 1 l&agrave; chuẩn bị dữ liệu của m&igrave;nh v&agrave; b&acirc;y giờ ch&uacute;ng ta&nbsp;hiện đang trong qu&aacute; tr&igrave;nh x&acirc;y dựng m&ocirc; h&igrave;nh.</p> <p style="text-align: justify;">Ch&uacute;ng ta đ&atilde; tạo mạng của m&igrave;nh bằng c&aacute;ch mở rộng lớp cơ sở <em>nn.Module</em> PyTorch v&agrave; sau đ&oacute; trong h&agrave;m tạo lớp, ch&uacute;ng ta định nghĩa c&aacute;c layers của mạng l&agrave; thuộc t&iacute;nh lớp.&nbsp;B&acirc;y giờ, ch&uacute;ng ta cần triển khai phương thức <em>forward()</em> của mạng để sẵn s&agrave;ng đến giai đoạn đ&agrave;o tạo m&ocirc; h&igrave;nh.</p> <ol style="text-align: justify;"> <li><span class="font-weight-bold">Chuẩn bị dữ liệu</span></li> <li><strong>X&acirc;y dựng m&ocirc; h&igrave;nh</strong></li> <li>Đ&agrave;o tạo m&ocirc; h&igrave;nh</li> <li>Ph&acirc;n t&iacute;ch kết quả của m&ocirc; h&igrave;nh</li> </ol> <h3 class="section-heading" style="text-align: justify;">Đ&aacute;nh gi&aacute; mạng</h3> <p style="text-align: justify;">Hiện tại, ch&uacute;ng ta biết rằng phương thức <em>forward()</em>&nbsp;chấp nhận một tensor l&agrave;m đầu v&agrave;o v&agrave; sau đ&oacute; trả về tensor l&agrave;m đầu ra.</p> <p style="text-align: justify;">Điều n&agrave;y c&oacute; nghĩa l&agrave; việc triển khai phương thức forward sẽ sử dụng tất cả c&aacute;c layers m&agrave; ch&uacute;ng ta đ&atilde; x&aacute;c định b&ecirc;n trong phương thức khởi tạo.&nbsp;Bằng c&aacute;ch n&agrave;y, phương thức forward x&aacute;c định r&otilde; r&agrave;ng sự chuyển đổi của mạng.</p> <p style="text-align: justify;">Phương thức forward &aacute;nh xạ một tensor đầu v&agrave;o với tensor đầu ra dự đo&aacute;n.&nbsp;H&atilde;y xem l&agrave;m thế n&agrave;o điều n&agrave;y được thực hiện.</p> <p style="text-align: justify;">Nhớ lại rằng trong phương thức khởi tạo của mạng, ch&uacute;ng ta c&oacute; thể thấy rằng ch&uacute;ng ta c&oacute; năm layers được x&aacute;c định.</p> <pre class="language-python"><code>class Network(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5) self.conv2 = nn.Conv2d(in_channels=6, out_channels=12, kernel_size=5) self.fc1 = nn.Linear(in_features=12 * 4 * 4, out_features=120) self.fc2 = nn.Linear(in_features=120, out_features=60) self.out = nn.Linear(in_features=60, out_features=10) def forward(self, t): # implement the forward pass return t</code></pre> <p style="text-align: justify;">Ch&uacute;ng ta c&oacute; hai layers t&iacute;ch chập v&agrave; ba layers tuyến t&iacute;nh.&nbsp;Nếu ch&uacute;ng ta đếm cả layer đầu v&agrave;o, điều n&agrave;y cho ch&uacute;ng ta một mạng c&oacute; tổng cộng s&aacute;u layers.</p> <h3 class="section-heading" style="text-align: justify;">Triển Khai phương thức <em>Forward()</em></h3> <p style="text-align: justify;">Ch&uacute;ng ta sẽ bắt đầu mọi thứ từ layer đầu v&agrave;o.</p> <h4 class="sub-section-heading" style="text-align: justify;">Input Layer #1</h4> <p style="text-align: justify;">Layer đầu v&agrave;o của bất kỳ mạng neural n&agrave;o được x&aacute;c định bởi dữ liệu đầu v&agrave;o.&nbsp;V&iacute; dụ, nếu tensor đầu v&agrave;o của ch&uacute;ng ta chứa ba phần tử th&igrave; mạng của ch&uacute;ng ta sẽ c&oacute; ba n&uacute;t được chứa trong layer đầu v&agrave;o của n&oacute;.</p> <p style="text-align: justify;">V&igrave; l&yacute; do n&agrave;y, ch&uacute;ng ta c&oacute; thể coi lớp đầu v&agrave;o l&agrave;&nbsp;identity transformation.&nbsp;Về mặt to&aacute;n học, đ&acirc;y l&agrave; h&agrave;m:</p> <p style="text-align: justify;">f(x)=x</p> <p style="text-align: justify;">Ch&uacute;ng ta cho bất kỳ x n&agrave;o l&agrave;m đầu v&agrave;o v&agrave; ch&uacute;ng ta nhận lại c&ugrave;ng một x ở đầu ra.&nbsp;Logic n&agrave;y giống nhau bất kể ch&uacute;ng ta đang l&agrave;m việc với một tensor c&oacute; ba phần tử hay tensor đại diện cho một h&igrave;nh ảnh c&oacute; ba k&ecirc;nh.&nbsp;Dữ liệu v&agrave;o l&agrave; dữ liệu ra!</p> <p style="text-align: justify;">Đ&acirc;y l&agrave; l&yacute; do ch&uacute;ng ta thường kh&ocirc;ng thấy lớp đầu v&agrave;o khi ch&uacute;ng ta đang l&agrave;m việc với c&aacute;c API mạng neural.&nbsp;Lớp đầu v&agrave;o tồn tại ngầm định.</p> <p style="text-align: justify;">Để ho&agrave;n th&agrave;nh, ch&uacute;ng ta sẽ hiển thị hoạt động nhận dạng trong phương thức forward của ch&uacute;ng ta.</p> <h4 class="sub-section-heading" style="text-align: justify;">Hidden convolutional layers: Layers #2 v&agrave; #3</h4> <p style="text-align: justify;">Cả hai lớp t&iacute;ch chập ẩn sẽ rất giống nhau về c&aacute;ch thực hiện ph&eacute;p biến đổi.</p> <p><img style="width: 346px; display: block; margin-left: auto; margin-right: auto;" src="http://tek4vn.2soft.top/public_files/convolution-animation-1-gif-1" alt="convolution-animation-1" height="393" /></p> <p style="text-align: justify;">Để thực hiện ph&eacute;p to&aacute;n t&iacute;ch chập, ch&uacute;ng ta chuyển tensor tới phương thức forward của lớp t&iacute;ch chập đầu ti&ecirc;n <em>self.conv1.&nbsp;</em>Ch&uacute;ng ta đ&atilde; biết tất cả c&aacute;c m&ocirc;-đun mạng neural PyTorch c&oacute; c&aacute;c phương thức <em>forward()</em> v&agrave; khi ch&uacute;ng ta gọi phương thức <em>forward()</em> của một <em>nn.Module,</em> c&oacute; một c&aacute;ch đặc biệt m&agrave; ch&uacute;ng ta thực hiện.</p> <p style="text-align: justify;">Khi muốn gọi phương thức <em>forward()</em> của một &nbsp;instance <em>nn.Module,</em> ch&uacute;ng ta gọi &nbsp;instance thực thay v&igrave; gọi trực tiếp phương thức <em>forward().&nbsp;</em>Thay v&igrave; l&agrave;m điều n&agrave;y&nbsp;<em>self.conv1.osystem(tensor),</em> ch&uacute;ng ta l&agrave;m điều n&agrave;y&nbsp; &nbsp;<em>self.conv1(tensor).&nbsp;</em></p> <p style="text-align: justify;">H&atilde;y tiếp tục v&agrave; th&ecirc;m tất cả c&aacute;c lệnh gọi cần thiết để triển khai cả hai convolutional layers của ch&uacute;ng ta.</p> <pre class="language-python"><code># (2) hidden conv layer t = self.conv1(t) t = F.relu(t) t = F.max_pool2d(t, kernel_size=2, stride=2) # (3) hidden conv layer t = self.conv2(t) t = F.relu(t) t = F.max_pool2d(t, kernel_size=2, stride=2)</code></pre> <p style="text-align: justify;">Như ch&uacute;ng ta c&oacute; thể thấy ở đ&acirc;y, tensor đầu v&agrave;o của ch&uacute;ng ta được biến đổi khi ch&uacute;ng ta di chuyển qua c&aacute;c layers t&iacute;ch chập.&nbsp;Layers t&iacute;ch chập đầu ti&ecirc;n c&oacute; một ph&eacute;p to&aacute;n t&iacute;ch chập, tiếp theo l&agrave; một <a href="https://en.wikipedia.org/wiki/Rectifier_(neural_networks)" target="_blank" rel="noopener">ph&eacute;p to&aacute;n k&iacute;ch hoạt relu</a> m&agrave; đầu ra của n&oacute; sau đ&oacute; được chuyển cho một ph&eacute;p to&aacute;n gộp tối đa với <em>kernel_size = 2</em> v&agrave; <em>stride = 2</em>.</p> <p style="text-align: justify;">Đầu ra tensor <em>t</em> của layer chập đầu ti&ecirc;n sau đ&oacute; được chuyển đến layer chập tiếp theo, layer n&agrave;y giống hệt nhau ngoại trừ thực tế l&agrave; ch&uacute;ng ta gọi <em>self.conv2()</em> thay v&igrave; <em>self.conv1().&nbsp;</em></p> <p style="text-align: justify;">Mỗi lớp n&agrave;y bao gồm một tập hợp c&aacute;c trọng số (dữ liệu) v&agrave; một&nbsp;collection operations (code).&nbsp;C&aacute;c trọng số được đ&oacute;ng g&oacute;i b&ecirc;n trong thể hiện của lớp <em>nn.Conv2d().&nbsp;</em>C&aacute;c lệnh gọi <em>relu()</em> v&agrave; <em>max_pool2d()</em> chỉ l&agrave; c&aacute;c ph&eacute;p to&aacute;n thuần t&uacute;y.&nbsp;Kh&ocirc;ng lệnh n&agrave;o trong số n&agrave;y c&oacute; trọng số v&agrave; đ&acirc;y l&agrave; l&yacute; do tại sao ch&uacute;ng ta gọi ch&uacute;ng trực tiếp từ API <em>nn.functional.&nbsp;</em></p> <p style="text-align: justify;">Đ&ocirc;i khi ch&uacute;ng ta c&oacute; thể thấy c&aacute;c hoạt động gộp (&nbsp;pooling operations) được gọi l&agrave; c&aacute;c pooling layers.&nbsp;Đ&ocirc;i khi ch&uacute;ng ta thậm ch&iacute; c&oacute; thể nghe thấy c&aacute;c hoạt động k&iacute;ch hoạt được gọi l&agrave; c&aacute;c <em>activation layers</em>.</p> <p style="text-align: justify;">Tuy nhi&ecirc;n, điều l&agrave;m cho một layer kh&aacute;c biệt với một operation l&agrave; c&aacute;c layers c&oacute; trọng số.&nbsp;V&igrave; c&aacute;c hoạt động gộp v&agrave; c&aacute;c h&agrave;m k&iacute;ch hoạt kh&ocirc;ng c&oacute; trọng số n&ecirc;n ch&uacute;ng ta sẽ gọi ch&uacute;ng l&agrave; c&aacute;c operations v&agrave; xem ch&uacute;ng như được th&ecirc;m v&agrave;o tập hợp c&aacute;c operations của layer.</p> <p style="text-align: justify;">V&iacute; dụ: ch&uacute;ng ta sẽ n&oacute;i rằng layer thứ hai trong mạng của ch&uacute;ng ta l&agrave; một lớp t&iacute;ch chập chứa một tập hợp c&aacute;c trọng số v&agrave; định dạng trước ba ph&eacute;p to&aacute;n, một ph&eacute;p to&aacute;n t&iacute;ch chập, ph&eacute;p to&aacute;n k&iacute;ch hoạt relu v&agrave; ph&eacute;p to&aacute;n gộp tối đa (&nbsp;max pooling operation).</p> <p style="text-align: justify;">Lưu &yacute; rằng c&aacute;c quy tắc v&agrave; thuật ngữ ở đ&acirc;y kh&ocirc;ng nghi&ecirc;m ngặt.&nbsp;Đ&acirc;y chỉ l&agrave; một c&aacute;ch để m&ocirc; tả một mạng m&agrave; th&ocirc;i.&nbsp;C&oacute; nhiều c&aacute;ch kh&aacute;c để thể hiện những &yacute; tưởng n&agrave;y.&nbsp;Điều ch&iacute;nh ch&uacute;ng ta cần biết l&agrave; hoạt động n&agrave;o được x&aacute;c định bằng c&aacute;ch sử dụng trọng số v&agrave; hoạt động n&agrave;o kh&ocirc;ng sử dụng bất kỳ trọng số n&agrave;o.</p> <p style="text-align: justify;">Về mặt lịch sử, c&aacute;c hoạt động được x&aacute;c định bằng c&aacute;ch sử dụng trọng số l&agrave; những g&igrave; m&agrave; ch&uacute;ng ta gọi l&agrave; layer.&nbsp;Sau đ&oacute;, c&aacute;c hoạt động kh&aacute;c đ&atilde; được th&ecirc;m v&agrave;o như chức năng k&iacute;ch hoạt, hoạt động gộp v&agrave; điều n&agrave;y đ&atilde; g&acirc;y ra một số nhầm lẫn trong thuật ngữ.</p> <p style="text-align: justify;">Về mặt to&aacute;n học, to&agrave;n bộ mạng chỉ l&agrave; một th&agrave;nh phần của c&aacute;c h&agrave;m, v&agrave; một th&agrave;nh phần của c&aacute;c h&agrave;m l&agrave; một h&agrave;m.&nbsp;V&igrave; vậy, một mạng chỉ l&agrave; một h&agrave;m.&nbsp;Tất cả c&aacute;c thuật ngữ như layer, h&agrave;m k&iacute;ch hoạt (activation functions) v&agrave; trọng số (weights), chỉ được sử dụng để gi&uacute;p m&ocirc; tả c&aacute;c phần kh&aacute;c nhau.</p> <h4 class="sub-section-heading" style="text-align: justify;">Hidden linear layers: Layers #4 v&agrave; #5</h4> <p style="text-align: justify;">Trước khi chuyển đầu v&agrave;o cho lớp&nbsp;hidden linear layer đầu ti&ecirc;n,&nbsp;ch&uacute;ng ta phải reshape() lại hoặc flatten tensor của ch&uacute;ng ta. Đ&acirc;y sẽ l&agrave; việc cần l&agrave;m&nbsp;bất cứ khi n&agrave;o ch&uacute;ng ta chuyển đầu ra từ một lớp t&iacute;ch chập l&agrave;m đầu v&agrave;o cho một lớp tuyến t&iacute;nh.</p> <p style="text-align: justify;">V&igrave; layer thứ tư l&agrave; layer tuyến t&iacute;nh đầu ti&ecirc;n n&ecirc;n hoạt động reshape như một phần của lớp thứ tư.</p> <pre class="language-python"><code># (4) hidden linear layer t = t.reshape(-1, 12 * 4 * 4) t = self.fc1(t) t = F.relu(t) # (5) hidden linear layer t = self.fc2(t) t = F.relu(t)</code></pre> <p style="text-align: justify;">Trong b&agrave;i viết&nbsp;<a href="../../../cnn-weight-lap-trinh-neural-network-voi-pytorch-bai-18/" target="_blank" rel="noopener">CNN Weight</a>&nbsp;ch&uacute;ng ta đ&atilde; thấy rằng số 12&nbsp;trong reshape được x&aacute;c định bởi số lượng k&ecirc;nh đầu ra đến từ layer chập trước đ&oacute;.</p> <p style="text-align: justify;">Tuy nhi&ecirc;n, 4 * 4 vẫn c&ograve;n l&agrave; một c&acirc;u hỏi bỏ ngỏ.&nbsp;H&atilde;y c&ugrave;ng tiết lộ c&acirc;u trả lời ngay b&acirc;y giờ.&nbsp;4 * 4 thực tế l&agrave; chiều cao v&agrave; chiều rộng của mỗi k&ecirc;nh trong số 12 k&ecirc;nh đầu ra.</p> <p style="text-align: justify;">Ch&uacute;ng ta bắt đầu với tensor đầu v&agrave;o 1 x 28 x 28.&nbsp;Điều n&agrave;y tạo ra một <a href="https://en.wikipedia.org/wiki/Channel_(digital_image)" target="_blank" rel="noopener">k&ecirc;nh m&agrave;u</a> duy nhất, h&igrave;nh ảnh 28 x 28 v&agrave;o thời điểm tensor của ch&uacute;ng ta đến layer tuyến t&iacute;nh đầu ti&ecirc;n, k&iacute;ch thước đ&atilde; thay đổi.</p> <p style="text-align: justify;">K&iacute;ch thước chiều cao v&agrave; chiều rộng đ&atilde; được giảm từ 28 x 28 xuống 4 x 4 bằng c&aacute;c ph&eacute;p to&aacute;n t&iacute;ch chập v&agrave; gộp.</p> <p style="text-align: justify;">Ph&eacute;p to&aacute;n&nbsp;convolution v&agrave; pooling l&agrave; c&aacute;c ph&eacute;p to&aacute;n l&agrave;m giảm k&iacute;ch thước chiều cao v&agrave; chiều rộng.&nbsp;Ch&uacute;ng ta sẽ xem điều n&agrave;y hoạt động như thế n&agrave;o trong b&agrave;i đăng tiếp theo.&nbsp;B&acirc;y giờ, h&atilde;y ho&agrave;n th&agrave;nh việc triển khai phương thức <em>forward()</em> n&agrave;y của ch&uacute;ng ta.</p> <p style="text-align: justify;">Sau khi tensor được reshape lại, ch&uacute;ng ta chuyển tensor flatten đến layer tuyến t&iacute;nh v&agrave; chuyển kết quả n&agrave;y cho h&agrave;m k&iacute;ch hoạt <em>relu().&nbsp;</em></p> <h4 class="sub-section-heading" style="text-align: justify;">Output layer #6</h4> <p style="text-align: justify;">Layer thứ 6 v&agrave; cũng l&agrave; layer cuối c&ugrave;ng của mạng&nbsp;l&agrave; layer tuyến t&iacute;nh m&agrave; ch&uacute;ng ta gọi l&agrave; đầu ra.&nbsp;Khi ch&uacute;ng ta chuyển tensor của ch&uacute;ng ta đến layer đầu ra, kết quả sẽ l&agrave; tensor dự đo&aacute;n.&nbsp;V&igrave; dữ liệu của ch&uacute;ng ta c&oacute; mười lớp dự đo&aacute;n n&ecirc;n ch&uacute;ng ta biết tensor đầu ra sẽ c&oacute; mười phần tử.</p> <pre class="language-python"><code># (6) output layer t = self.out(t) #t = F.softmax(t, dim=1)</code></pre> <p style="text-align: justify;">C&aacute;c gi&aacute; trị b&ecirc;n trong mỗi th&agrave;nh phần trong số mười th&agrave;nh phần sẽ tương ứng với gi&aacute; trị dự đo&aacute;n cho mỗi lớp dự đo&aacute;n của ch&uacute;ng ta.</p> <p style="text-align: justify;">B&ecirc;n trong mạng, ch&uacute;ng ta thường sử dụng <em>relu()</em> l&agrave;m h&agrave;m k&iacute;ch hoạt phi tuyến t&iacute;nh, nhưng đối với lớp đầu ra, bất cứ khi n&agrave;o ch&uacute;ng ta c&oacute; một danh mục duy nhất m&agrave; ch&uacute;ng ta đang cố gắng dự đo&aacute;n th&igrave; ch&uacute;ng ta sẽ sử dụng <em>softmax().&nbsp;</em>H&agrave;m softmax trả về x&aacute;c suất dương (positive&nbsp;) cho mỗi lớp dự đo&aacute;n v&agrave; tổng x&aacute;c suất bằng 1.</p> <p style="text-align: justify;">Tuy nhi&ecirc;n, trong trường hợp n&agrave;y, ch&uacute;ng ta sẽ kh&ocirc;ng sử dụng <em>softmax()</em> v&igrave; h&agrave;m mất m&aacute;t m&agrave; ch&uacute;ng ta sử dụng l&agrave; <em>F.cross_entropy(),</em> thực hiện ngầm định hoạt động <em>softmax()</em> tr&ecirc;n đầu v&agrave;o của n&oacute;, v&igrave; vậy ch&uacute;ng ta sẽ chỉ trả về kết quả l&agrave; ph&eacute;p biến đổi tuyến t&iacute;nh cuối c&ugrave;ng.</p> <h3 class="section-heading" style="text-align: justify;">Kết Luận</h3> <p style="text-align: justify;">Đ&acirc;y l&agrave; c&aacute;ch ch&uacute;ng ta triển khai phương thức forward mạng neural trong PyTorch:</p> <pre class="language-python"><code>def forward(self, t): # (1) input layer t = t # (2) hidden conv layer t = self.conv1(t) t = F.relu(t) t = F.max_pool2d(t, kernel_size=2, stride=2) # (3) hidden conv layer t = self.conv2(t) t = F.relu(t) t = F.max_pool2d(t, kernel_size=2, stride=2) # (4) hidden linear layer t = t.reshape(-1, 12 * 4 * 4) t = self.fc1(t) t = F.relu(t) # (5) hidden linear layer t = self.fc2(t) t = F.relu(t) # (6) output layer t = self.out(t) #t = F.softmax(t, dim=1) return t</code></pre> <p style="text-align: justify;">Hẹn gặp lại bạn trong phần tiếp theo!</p> <p style="text-align: justify;">&nbsp;</p> <hr /> <p style="text-align: center;"><em><strong>Fanpage Facebook:</strong>&nbsp;<a href="https://www.facebook.com/tek4.vn/">TEK4.VN</a></em>&nbsp;</p> <p style="text-align: center;"><em><strong>Tham gia cộng đồng để chia sẻ, trao đổi v&agrave; thảo luận:</strong>&nbsp;<a href="https://www.facebook.com/groups/tek4.vn/">TEK4.VN - Học Lập Tr&igrave;nh Miễn Ph&iacute;</a></em></p>