Một trong những yếu tố dẫn đến thành công của học sâu là sự đa dạng của các tầng. Những tầng này có thể được sắp xếp theo nhiều cách sáng tạo để thiết kế nên những kiến trúc phù hợp với nhiều tác vụ khác nhau. Ví dụ, các nhà nghiên cứu đã phát minh ra các tầng chuyên dụng để xử lý ảnh, chữ viết, lặp trên dữ liệu tuần tự, thực thi quy hoạch động, v.v... Dù sớm hay muộn, bạn cũng sẽ gặp (hoặc sáng tạo) một tầng không có trong Gluon. Đối với những trường hợp như vậy, bạn cần xây dựng một tầng tuỳ chỉnh. Phần này sẽ hướng dẫn bạn cách thực hiện điều đó.
Để bắt đầu, ta tạo một tầng tùy chỉnh (một Khối) không chứa bất kỳ tham số nào.
Bước này khá quen thuộc nếu bạn còn nhớ phần giới thiệu về Block
của Gluon tại :numref:sec_model_construction
.
Lớp CenteredLayer
chỉ đơn thuần trừ đi giá trị trung bình từ đầu vào của nó.
Để xây dựng nó, chúng ta chỉ cần kế thừa từ lớp Block
và lập trình phương thức forward
.
from mxnet import gluon, np, npx
from mxnet.gluon import nn
npx.set_np()
class CenteredLayer(nn.Block):
def __init__(self, **kwargs):
super(CenteredLayer, self).__init__(**kwargs)
def forward(self, x):
return x - x.mean()
Hãy cùng xác thực rằng tầng này hoạt động như ta mong muốn bằng cách truyền dữ liệu vào nó.
layer = CenteredLayer()
layer(np.array([1, 2, 3, 4, 5]))
Chúng ta cũng có thể kết hợp tầng này như là một thành phần để xây dựng các mô hình phức tạp hơn.
net = nn.Sequential()
net.add(nn.Dense(128), CenteredLayer())
net.initialize()
Để kiểm tra thêm, chúng ta có thể truyền dữ liệu ngẫu nhiên qua mạng và chứng thực xem giá trị trung bình đã về 0 hay chưa. Chú ý rằng vì đang làm việc với các số thực dấu phẩy động, chúng ta sẽ thấy một giá trị khác không rất nhỏ.
y = net(np.random.uniform(size=(4, 8)))
y.mean()
Giờ đây ta đã biết cách định nghĩa các tầng đơn giản, hãy chuyển sang việc định nghĩa các tầng chứa tham số có thể điều chỉnh được trong quá trình huấn luyện.
Để tự động hóa các công việc lặp lại, lớp Parameter
và từ điển ParameterDict
cung cấp một số tính năng quản trị cơ bản.
Cụ thể, chúng sẽ quản lý việc truy cập, khởi tạo, chia sẻ, lưu và nạp các tham số mô hình.
Bằng cách này, cùng với nhiều lợi ích khác, ta không cần phải viết lại các thủ tục tuần tự hóa (serialization) cho mỗi tầng tùy chỉnh mới.
Lớp Block
chứa biến params
với kiểu dữ liệu ParameterDict
.
Từ điển này ánh xạ các xâu kí tự biểu thị tên tham số đến các tham số mô hình (thuộc kiểu Parameter
).
ParameterDict
cũng cung cấp hàm get
giúp việc tạo tham số mới với tên và chiều cụ thể trở nên dễ dàng.
params = gluon.ParameterDict()
params.get('param2', shape=(2, 3))
params
Giờ đây chúng ta đã có tất cả các thành phần cơ bản cần thiết để tự tạo một phiên bản tùy chỉnh của tầng Dense
trong Gluon.
Chú ý rằng tầng này yêu cầu hai tham số: một cho trọng số và một cho hệ số điều chỉnh.
Trong cách lập trình này, ta sử dụng hàm kích hoạt mặc định là hàm ReLU.
Trong hàm __init__
, in_units
và units
biểu thị lần lượt số lượng đầu vào và đầu ra.
class MyDense(nn.Block):
# units: the number of outputs in this layer; in_units: the number of
# inputs in this layer
def __init__(self, units, in_units, **kwargs):
super(MyDense, self).__init__(**kwargs)
self.weight = self.params.get('weight', shape=(in_units, units))
self.bias = self.params.get('bias', shape=(units,))
def forward(self, x):
linear = np.dot(x, self.weight.data()) + self.bias.data()
return npx.relu(linear)
Việc đặt tên cho các tham số cho phép ta truy cập chúng theo tên thông qua tra cứu từ điển sau này.
Nhìn chung, bạn sẽ muốn đặt cho các biến những tên đơn giản biểu thị rõ mục đích của chúng.
Tiếp theo, ta sẽ khởi tạo lớp MyDense
và truy cập các tham số mô hình.
Lưu ý rằng tên của Khối được tự động thêm vào trước tên các tham số.
dense = MyDense(units=3, in_units=5)
dense.params
Ta có thể trực tiếp thực thi các phép tính truyền xuôi có sử dụng các tầng tùy chỉnh.
dense.initialize()
dense(np.random.uniform(size=(2, 5)))
Các tầng tùy chỉnh cũng có thể được dùng để xây dựng mô hình. Chúng có thể được sử dụng như các tầng kết nối dày đặc được lập trình sẵn. Ngoại lệ duy nhất là việc suy luận kích thước sẽ không được thực hiện tự động. Để biết thêm chi tiết về cách thực hiện việc này, vui lòng tham khảo tài liệu MXNet.
net = nn.Sequential()
net.add(MyDense(8, in_units=64),
MyDense(1, in_units=8))
net.initialize()
net(np.random.uniform(size=(2, 64)))
- Ta có thể thiết kế các tầng tùy chỉnh thông qua lớp
Block
. Điều này cho phép ta định nghĩa một cách linh hoạt các tầng có cách hoạt động khác với các tầng có sẵn trong thư viện. - Một khi đã được định nghĩa, các tầng tùy chỉnh có thể được gọi trong những bối cảnh và kiến trúc tùy ý.
- Các khối có thể có các tham số cục bộ, được lưu trữ dưới dạng đối tượng
ParameterDict
trong mỗi thuộc tínhparams
của Block.
- Thiết kế một tầng có khả năng học một phép biến đổi affine của dữ liệu.
- Thiết kế một tầng nhận đầu vào và tính toán phép giảm tensor, tức trả về
$y_k = \sum_{i, j} W_{ijk} x_i x_j$ . - Thiết kế một tầng trả về nửa đầu của các hệ số Fourier của dữ liệu. Gợi ý: hãy tra cứu hàm
fft
trong MXNet.
Bản dịch trong trang này được thực hiện bởi:
- Đoàn Võ Duy Thanh
- Nguyễn Lê Quang Nhật
- Nguyễn Văn Cường
- Phạm Hồng Vinh
- Lê Khắc Hồng Phúc
- Phạm Minh Đức
- Nguyễn Duy Du