5.5. Đọc/Ghi tệp

Đến nay, ta đã thảo luận về cách xử lý dữ liệu và cách xây dựng, huấn luyện, kiểm tra những mô hình học sâu. Tuy nhiên, có thể đến một lúc nào đó ta sẽ cảm thấy hài lòng với những gì thu được và muốn lưu lại kết quả để sau này sử dụng trong những bối cảnh khác nhau (thậm chí có thể đưa ra dự đoán khi triển khai). Ngoài ra, khi vận hành một quá trình huấn luyện dài hơi, cách tốt nhất là lưu kết quả trung gian một cách định kỳ (điểm kiểm tra) nhằm đảm bảo rằng ta sẽ không mất kết quả tính toán sau nhiều ngày nếu chẳng may ta vấp phải dây nguồn của máy chủ. Vì vậy, đã đến lúc chúng ta học cách đọc và lưu trữ đồng thời các vector trọng số riêng lẻ cùng toàn bộ các mô hình. Mục này sẽ giải quyết cả hai vấn đề trên.

5.5.1. Đọc và Lưu các ndarray

Đối với ndarray riêng lẻ, ta có thể sử dụng trực tiếp các hàm loadsave để đọc và ghi tương ứng. Cả hai hàm đều yêu cầu ta cung cấp tên, và hàm save yêu cầu đầu vào với biến đã được lưu.

from mxnet import np, npx
from mxnet.gluon import nn
npx.set_np()

x = np.arange(4)
npx.save('x-file', x)

Bây giờ, chúng ta có thể đọc lại dữ liệu từ các tệp được lưu vào trong bộ nhớ.

x2 = npx.load('x-file')
x2
[array([0., 1., 2., 3.])]

MXNet đồng thời cho phép ta lưu một danh sách các ndarray và đọc lại chúng vào trong bộ nhớ.

y = np.zeros(4)
npx.save('x-files', [x, y])
x2, y2 = npx.load('x-files')
(x2, y2)
(array([0., 1., 2., 3.]), array([0., 0., 0., 0.]))

Ta còn có thể ghi và đọc một từ điển ánh xạ từ một chuỗi sang một ndarray. Điều này khá là thuận tiện khi chúng ta muốn đọc hoặc ghi tất cả các trọng số của một mô hình.

mydict = {'x': x, 'y': y}
npx.save('mydict', mydict)
mydict2 = npx.load('mydict')
mydict2
{'x': array([0., 1., 2., 3.]), 'y': array([0., 0., 0., 0.])}

5.5.2. Tham số mô hình Gluon

Khả năng lưu từng vector trọng số đơn lẻ (hoặc các ndarray tensor khác) là hữu ích nhưng sẽ mất nhiều thời gian nếu chúng ta muốn lưu (và sau đó nạp lại) toàn bộ mô hình. Dù sao, chúng ta có thể có hàng trăm nhóm tham số rải rác xuyên suốt mô hình. Vì lý do đó mà Gluon cung cấp sẵn tính năng lưu và nạp toàn bộ các mạng. Một chi tiết quan trọng cần lưu ý là chức năng này chỉ lưu các tham số của mô hình, không phải là toàn bộ mô hình. Điều đó có nghĩa là nếu ta có một MLP ba tầng, ta cần chỉ rõ kiến trúc này một cách riêng lẻ. Lý do là vì bản thân các mô hình có thể chứa mã nguồn bất kỳ, chúng không được thêm vào tập tin một cách dễ dàng như các tham số (có một cách thực hiện điều này cho các mô hình đã được biên dịch, chi tiết kĩ thuật đọc thêm trong tài liệu MXNet). Vì vậy, để khôi phục lại một mô hình thì chúng ta cần xây dựng kiến trúc của nó từ mã nguồn rồi nạp các tham số từ ổ cứng vào kiến trúc này. Việc khởi tạo trễ (Section 5.3) lúc này rất có lợi vì ta chỉ cần định nghĩa một mô hình mà không cần gán giá trị cụ thể cho tham số. Như thường lệ, hãy bắt đầu với một MLP quen thuộc.

class MLP(nn.Block):
    def __init__(self, **kwargs):
        super(MLP, self).__init__(**kwargs)
        self.hidden = nn.Dense(256, activation='relu')
        self.output = nn.Dense(10)

    def forward(self, x):
        return self.output(self.hidden(x))

net = MLP()
net.initialize()
x = np.random.uniform(size=(2, 20))
y = net(x)

Tiếp theo, chúng ta lưu các tham số của mô hình vào tệp mlp.params. Những khối Gloun hỗ trợ phương thức từ hàm save_parameter nhằm ghi tất cả các tham số vào ổ cứng được cung cấp với một chuỗi những tên tệp.

net.save_parameters('mlp.params')

Để khôi phục mô hình, chúng ta tạo một đối tượng khác dựa trên mô hình MLP gốc. Thay vì khởi tạo ngẫu nhiên những tham số mô hình, ta đọc các tham số được lưu trực tiếp trong tập tin. Và thật thuận tiện, ta đã có thể nạp các tham số vào khối thông qua phương thức từ hàm load_parameters.

clone = MLP()
clone.load_parameters('mlp.params')

Vì cả hai đối tượng của mô hình có cùng bộ tham số, kết quả tính toán với cùng đầu vào x sẽ như nhau. Hãy kiểm chứng điều này.

yclone = clone(x)
yclone == y
array([[ True,  True,  True,  True,  True,  True,  True,  True,  True,
         True],
       [ True,  True,  True,  True,  True,  True,  True,  True,  True,
         True]])

5.5.3. Tóm tắt

  • Hàm saveload có thể được sử dụng để thực hiện việc xuất nhập tập tin cho các đối tượng ndarray.
  • Hàm load_parameterssave_parameters cho phép ta lưu toàn bộ tập tham số của một mạng trong Gluon.
  • Việc lưu kiến trúc này phải được hoàn thiện trong chương trình thay vì trong các tham số.

5.5.4. Bài tập

  1. Nếu không cần phải triển khai các mô hình huấn luyện sang một thiết bị khác, theo bạn thì lợi ích thực tế của việc lưu các tham số mô hình là gì?
  2. Giả sử chúng ta muốn sử dụng lại chỉ một phần của một mạng nào đó để phối hợp với một mạng của một kiến trúc khác. Trong trường hợp ta muốn sử dụng hai lớp đầu tiên của mạng trước đó vào trong một mạng mới, bạn sẽ làm thể nào để thực hiện được việc này?
  3. Làm thế nào để bạn có thể lưu kiến trúc mạng và các tham số? Có những hạn chế nào khi bạn tận dụng kiến trúc này?

5.5.5. Thảo luận

5.5.6. Những người thực hiện

Bản dịch trong trang này được thực hiện bởi:

  • Đoàn Võ Duy Thanh
  • Nguyễn Duy Du
  • Phạm Minh Đức
  • Lê Khắc Hồng Phúc
  • Nguyễn Văn Cường
  • Phạm Hồng Vinh