Xin chào các cháu, lại là ông đây. Hôm nay ông sẽ đem đến cho các cháu một chủ đề hết sức thú vị đó chính là Làm thế nào để xây dựng được các mô hình siêu nhỏ li ti mà vẫn đảm bảo độ chính xác cao. Đây chắc hẳn là một câu hỏi vô cùng thú vị với bất kì cháu nào đang thử nghiệm với các thuật toán Deep Learning và đặc biệt là các cháu đã có thích tìm hiểu về các kĩ thuật tăng hiệu năng cho model thì bài viết này chính là dành cho các cháu đó. Hôm nay không viết về các ứng dụng nữa mà sẽ viết sâu hơn về các kĩ thuật là nhiều các cháu ạ. Hi vọng các cháu vẫn sẽ ủng hộ ông nhé. OK chúng ta bắt đầu thôi
Có rất nhiều người chỉ quan tâm đến việc training một mô hình Deep learning sao cho độ chính xác thật là cao các cháu ạ. Đôi khi những mô hình có độ phức tạp khổng lồ mà thực sự không có một quy tắc nào để lựa chọn ra một kiến trúc tốt thật sự cả. Việc tạo dựng một mô hình có độ chính xác đủ tốt nhưng số lượng tính toán thì siêu nhỏ thực sự là một vấn đề đáng được quan tâm phải không các cháu. Có một câu mà ông khá tâm đắc đó là
Mô hình AI chỉ nằm trên jupyter notebook là những mô hình chết (the dead model)
Câu này muốn nói lên rằng dù các cháu có làm mô hình, thí nghiệm tốt đến đâu nhưng chỉ để nó trên jupyter notebook thì cũng chỉ là các mô hình mang tính chất nghiên cứu. Còn nếu muốn áp dụng nó vào trong thực tế thì lại cần rất nhiều yếu tố khác ngoài độ chính xác các cháu ạ. Câu nói này cũng nhấn mạnh hơn đến việc hậu xử lý sau khi traiing mô hình và quá trình deployment lên trên các hệ thống thực. Như vậy đối với các hệ thống thực tế thì chúng ta nên có tư duy làm cho mô hình của mình càng đơn giản, càng nhẹ thì càng tốt. Tất nhiên vẫn phải đảm bảo được độ chính xác nhé. Từ đó khái niệm về compression model ra đời và đây là các lý do chính của nó
- Giúp giảm kích thước lưu trữ: Khi mô hình của chúng ta được deploy trên các siu máy tính thì các cháu cứ thoải con gà mái đi, chả có gì phải lo cả. Rất tiếc rằng đời không phải lúc nào cũng như mơ, không phải lúc nào chúng ta cũng được sờ đến siu máy tính để mà deploy. Đại đa số các dự án đều muốn tiết kiệm chi phí nên việc cân nhắc đưa AI xuống các phần cứng có tốc độ trung bình là điều đáng để nghiên cứu. Chúng ta có thể kể đến các thiết bị có không gian lưu trữ hạn chế như Rapberry Pi, Mobile, Embeded Device… thế nên việc giảm kích thước của mô hình là điều rất quan trọng. Hãy tưởng tượng thay vì lưu trữ 1 mô hình 1GB thì các cháu giảm xuống chỉ còn 10MB thôi thì đã sang một câu chuyện khác. Nếu các cháu ensemble 10 biến thể của mô hình 1GB tức là các cháu sẽ tốn 10GB để lưu trữ bộ nhớ, ngược lại nếu được nén xuống còn 10MB thì các cháu chỉ tốn 100MB bộ nhớ vật lý thôi. Khác bọt quá phải không nào
- Giúp tăng tốc độ xử lý:Điều này là đương nhiên rồi, nếu như cả thế giới này điều là siu máy tính như ông kể trên thì chắc cũng chẳng cần phải quan tâm đến vấn đề này lắm. Tuy nhiên đa phần các mô hình AI kích thước quá lớn sẽ không thể chạy được real time trên các phần cứng có khả năng xử lý thấp. Vậy nên tốt nhất là lựa chọn mô hình đơn giản cho dễ dàng các cháu ạ.
- Độ chính xác thay đổi không đáng kể: Có lẽ các cháu đang thắc mắc là nếu cứ lựa chọn mô hình đơn giản thì không đạt được độ chính xác cần thiết thì sao. Điều này tất nhiên là một thắc mắc đúng. Tuy nhiên nếu việc nén mô hình chỉ được hiểu đơn giản là thay vì training mô hình to thì ta training một mô hình nhỏ hơn thì chưa hoàn toàn đúng. Ý nghĩa của từ nén ở đây đó chính là từ một mô hình lớn hơn, ta giảm kích thước lại mà vẫn giữ được độ chính xác của mô hình lớn ban đầu. Điểm mấu chốt ở đây là độ chính xác không bị thay đổi quá nhiều đó các cháu ạ. Đây chính là kĩ thuật để thiết kế ra một mô hình nhỏ nhưng có võ
Từ việc luyện võ đến training mô hình
Nghe lạ chưa nè, nhỏ nhưng có võ cứ làm ta liên tưởng đến một bộ truyện kiếm hiệp nào đó phải không các cháu. Các cao thủ hồi xưa đa phần đều là anh hùng xuất thiếu niên tuy nhiên có một điểm chung giữa họ đó chính là các thiếu niên anh hùng này thường là do được lĩnh hội từ một sư phụ nổi tiếng nào đó hoặc có kì duyên gặp được bí kíp võ công thượng thừa. Thế nên là không phải tự nhiên mà thành được anh hùng đâu các cháu nhé. Quay lại với việc training mô hình của chúng ta, nếu như mô hình của chúng ta xây dựng ban đầu là siêu nhỏ mà muốn nó có được sức mạnh siêu to khổng lồ thì khó lắm đấy các cháu nhé. Phải try hard rất nhiều mà kết quả chẳng được là bao. Những mô hình này chỉ được gọi là tuổi nhỏ làm việc nhỏ, tùy theo sức của mình thôi các cháu ạ. Vậy làm thế nào để đạt được cảnh giới thượng thừa như trên đây ta. Có một vài cách cơ bản các cháu nắm được như sau:
- Tự tu luyện bí kíp rồi chắt lọc lại thành của riêng mình: Học hỏi từ các bí kíp tức là học hỏi các võ công cao siêu thượng thừa nhưng lại biết cách sáng tạo để tối giản, biến thành võ công của mình. Điều này thể hiện trong việc nén mô hình đó là training một mô hình lớn hơn đạt độ chính xác thật tốt sau đó tinh giản, loại bỏ đi các thành phần không cần thiết trong mô hình đó để đạt được mô hình siêu nhỏ li ti. Cách này có vẻ khá là tự thân vận động, đòi hỏi bạn phải có một nền tảng võ học vững chắc (tương tự như có một kiến trúc mạng tốt, đủ phức tạp để học hết các trường hợp) tuy nhiên kết quả đạt được thường là rất khả quan.
- Hấp thu nội lực từ các bậc tiền bối: Trong võ lâm cũng thi thoảng xuất hiện nhiều thanh niên gặp may, võ học thì cũng chưa có căn cơ gì nhưng lại gặp được cao nhân quý mến truyền cho nội công thâm hậu và tự nhiên trở thành cao thủ. Hẳn tuổi thơ của các cháu vẫn còn nhớ thanh niên bên dưới chứ. Đó là trong giới võ lâm, trong việc training model cũng chẳng khác là mấy các cháu ạ. Có một kĩ thuật người ta gọi là Knowledge distillation tức là cách training một mô hình nhỏ hơn bằng cách học hỏi lại kiến thức đã học được của một mô hình lớn hơn. Kiểu này gần giống như kiểu sư phụ dành hết tâm sức cả đời để truyền dạy ra các đệ tử chân truyền ý các cháu ạ.
Khẩu quyết tâm pháp
Các cháu ạ, các phương pháp luyện võ cũng tương tự như việc training mô hình AI, có rất nhiều nột thăng trầm, rất nhiều biến cố xảy ra để có thể đạt được môt mô hình tốt. Mong là các cháu hiểu được điều đó. Bây giờ cũng đã đến lúc ông chỉ cho các cháu khẩu quyết và tâm pháp của môn võ công nén mô hình này cho các cháu rồi đó. Khuyến khích các cháu nên đọc hết phần này, chớ vội nhảy vào thực hành ngay là dễ bị tẩu hỏa nhập ma lắm đó các cháu à. Bắt đầu nhé các cháu.
Cắt tỉa
Network pruningkhông phải là một khái niệm mới mẻ, nhất là đối với các cháu làm việc nhiều với các mô hình Deep Learning. Thực ra việc dropout mà các cháu thường sử dụng trong quá trình training cũng là một dạng cắt tỉa để tránh hiện tượng overfit. Tuy nhiên việc cắt tỉa ứng dụng trong mô hình nén thì là một khái niệm không hẳn giống với dropout. Nếu như dropout sẽ bỏ ngẫu nhiên một tỉ lệ phần trăm số lượng kết nối giữa các layer trong quá trình training thì khái niệm Network pruning ở đây tức là tìm các connection không cần thiết trong mạng (loại bỏ đi cũng không ảnh hưởng nhiều đến độ chính xác) để cắt tỉa đi. Thuật toán này có thể được thực hiện qua ba bước như sau:
- Training mạng khởi tạo mạng này thường là một mạng lớn với các kết nối đầy đủ giữa từng layer. Sở dĩ chọn một mạng đủ lớn để giúp cho việc học mô hình đạt được độ chính xác cao nhất định
- Loại bỏ các connection dư thừa: Chúng ta sẽ loại bỏ các connection với các trọng số nhỏ hơn một ngưỡng nào đó định trước. Thông thường các trọng số này là các trọng số không quá quan trọng và có thể bỏ đi được. Tất nhiên khi loại bỏ đi các connection này, mạng của các cháu sẽ trở nên thưa thớt hơn vì có rất nhiều các connection được mang giá trị 0 và sẽ làm ảnh hưởng đến độ chính xác lúc đầu. Chính vì thế nên để khôi phục lại được độ chính xác lúc đầu thì các cháu cần phải làm bước thứ 3
- Retrain lại kiến trúc mạng đã được cắt tỉa sau khi cắt tỉa thì độ chính xác sẽ có phần thay đổi, việc của các cháu là training lại các tham số (của mô hình đã được cắt tỉa) sao cho đạt được độ chính xác tương đương với độ chính xác lúc đầu. Và cuối cùng các cháu sẽ thu được một mô hình vừa nhỏ, vừa có võ rồi đấy
Lượng tử hóa và share weight
Sau khi đã nén được mô hình bằng phương pháp cắt tỉa (Trong thí nghiệm dưới của ông nó giảm đến 90% so với mạng lúc đầu). Chúng ta sẽ sử dụng một kĩ thuật gọi là lượng tử hóa để giảm số lượng các bit cần thiết trong quá trình lưu trữ các weight của mạng nơ ron. Nói một cách đơn giản như thế này nhé, các cháu phải nấu cơm cho một đại gia đình gồm rất nhiều thế hệ, nấu một món canh mà bố chồng thì thích cho 0.9 lít nước mắm, mẹ chồng thích cho 0.95 lít nước mắm còn chồng thì thích cho 1 lít nước mắm. Nếu mà nấu như vậy thì chắc sẽ rất mất công nên các cháu chỉ cần chọn một lượng nước mắm vừa đủ cho cả nhà ăn và con số 0.95 lít nước mắm (theo sở thích của mẹ chồng) là cân bằng và khéo léo hơn cả. Đây là quy tắc chọn đánh giá đại diện và cũng là tư tưởng chính của việc lượng tử hóa và share weight. Đây là quá trình chia toàn bộ số lượng weight của một layer thành các cụm nhỏ (clusters) và mỗi cụm sẽ được chia sẻ chung một giá trị weight. Giá trị này thường là giá trị tâm cụm (centroid) thể hiện được đặc trựng nhất của cụm đó. Điều ày cũng giống như việc các cháu chọn mẹ chồng làm trung tâm của gia đình đó các cháu ạ. Để hình dung rõ hơn thì mời các cháu xem hình dưới đây:
Các cháu có thể nhận ra được một vài điều:
- Ma trận Weight đầu tiên (ở trên cùng bên trái) là weight lúc đầu. Từ ma trận này này ta chia là 4 cụm khác nhau thể hiện bằng 4 màu xanh dương, xanh lá, da cam và hồng phấn. Đây được thực hiện bằng cách sử dụng k-means với k bằng 4.
- sau đó ở mỗi cụm sẽ được lưu một giá trị giống nhau là giá trị của tâm cụm, thể hiện ở phía trên cùng bên phải. Như vậy với 4 cụm các cháu chỉ cần lưu 4 giá trị của centroid và tốn 16*2 = 3216∗2=32 bit để lưu vị trí của index
- Một cách tổng quát để tính toán được độ nén của mạng khi sử dụng quantization và share weight thì các cháu cần hình dung như sau. Lúc đầu có nn weight và tốn bb bits để lưu trữ nn weight đó. Vậy lượng bit sử dụng là nbnb. Sau khi lượng tử hóa thành kk cụm thì sẽ tốn kbkb bits để lưu giá trị centroid của mỗi cụm và thêm nlog_2(k)nlog2(k) bits để lưu giá trị của các index. Vậy tỉ số nén sẽ là
Truyền nội công – Knowledge distillation
Knowledge distillation là một tư tưởng mới của compression network, nó không dựa trên cắt tỉa trên chính mạng cũ mà sử dụng một mạng nơ ron lớn hơn đã được trained sẵn để dạy lại cho một mạng nhỏ hơn. Bằng cách dạy lại mạng nhỏ học từng features map sau mỗi lớp tích chập của mạng lớn, đây có thể coi là một soft-label cho mạng nhỏ đó các cháu ạ. Hiểu đơn giản thì thay vì học trực tiếp từ dữ liệu gốc ban đầu thì mạng nhỏ sẽ học lại cách mà mạng lớn đã học, học lại các distribution của wieght và các feature map đã được tổng quát từ mạng lớn. Mạng nhỏ sẽ cố gắng học lại các cách xử lý của mạng lớn ở mọi lớp trong mạng chứ không chỉ là hàm loss tổng đâu nhé. Các cháu có thể hình dung nó trong hình sau nhé. Riêng về phương pháp truyền nội công này thì ông sẽ dành riêng một bài để viết về các kĩ thuật và các cách implement mạng trong đó nha.
Giờ đến phần mà cháu nào cũng mong ngóng đây, chúng ta đi vào code thôi nhé. Dù sao thì học lý thuyết mãi rồi giờ cũng phải lôi ra thực hành chứ nhỉ. Trong phần này ông sẽ không sử dụng một thư viện cắt tỉa nào mà sẽ hướng dẫn các cháu implement từ đầu để hiểu được bản chất của thuật toán này nhé. Không quá phức tạp đâu. Bắt đầu thôi nào
Cắt tỉa
Phương pháp đầu tiên chúng ta sẽ xem xét đến đó chính là cắt tỉa. Để demo cho bài này ông sử dụng kết trúc của Fully Connected đơn giản thôi nhé. Việc implement các mô hình CNN hay LSTM cũng tương tự nếu các cháu hiểu được tư tưởng của nó rồi.
Import các thư viện cần thiết
Trong bài này ông sử dụng Pytorch nên các cháu import một số thư viện cần thiết nha
1 2 3 4 5 6 7 8 9 | importnumpyasnp importtorch fromtorch.nnimportParameter fromtorch.nn.modules.moduleimportModule importtorch.nn.functionalasF importmath fromtorchimportnn |
Xây dựng base model
Model cắt tỉa cần được kế thừa từ base Module của Pytorch các cháu ạ. Như đã nói với các cháu từ phần lý thuyết rồi, bản chất của cắt tỉa là lựa chọn ra một ngưỡng để lọc ra các weight nào nhỏ hơn ngưỡng đó (các weight kém quan trọng). Để cho đơn giản thì ông sẽ sử dụng độ lệch chuẩn để tính toán threshold.
1 2 3 4 5 6 7 8 9 10 11 | classPruningModule(Module): defprune_by_std(self,s=0.25): # Note that module here is the layer # ex) fc1, fc2, fc3 forname,moduleinself.named_modules(): ifnamein['fc1','fc2','fc3']: threshold=np.std(module.weight.data.cpu().numpy())*s print(f'Pruning with threshold : {threshold} for layer {name}') module.prune(threshold) |
các cháu có thể tùy chỉnh tham số s=0.25 để tính toán giá trị của threshold cần cắt tỉa nhé
Xây dựng module cắt tỉa
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | classMaskedLinear(Module): def__init__(self,in_features,out_features,bias=True): super(MaskedLinear,self).__init__() self.in_features=in_features self.out_features=out_features self.weight=Parameter(torch.Tensor(out_features,in_features)) # Initialize the mask with 1 self.mask=Parameter(torch.ones([out_features,in_features]),requires_grad=False) ifbias: self.bias=Parameter(torch.Tensor(out_features)) else: self.register_parameter('bias',None) self.reset_parameters() defreset_parameters(self): stdv=1./math.sqrt(self.weight.size(1)) self.weight.data.uniform_(-stdv,stdv) ifself.biasisnotNone: self.bias.data.uniform_(-stdv,stdv) defforward(self,input): # Caculate weight when forward step returnF.linear(input,self.weight *self.mask,self.bias) def__repr__(self): returnself.__class__.__name__+'(' +'in_features='+str(self.in_features) +', out_features='+str(self.out_features) +', bias='+str(self.biasisnotNone)+')' # Customize of prune function with mask defprune(self,threshold): weight_dev=self.weight.device mask_dev=self.mask.device # Convert Tensors to numpy and calculate tensor=self.weight.data.cpu().numpy() mask=self.mask.data.cpu().numpy() new_mask=np.where(abs(tensor)<threshold,0,mask) # Apply new weight and mask self.weight.data=torch.from_numpy(tensor *new_mask).to(weight_dev) self.mask.data=torch.from_numpy(new_mask).to(mask_dev) |
Trong hàm trên các cháu cần quan tâm đến một số chức năng chính như sau:
- Mặt nạ: các cháu có để ý được layer self.mask được định nghĩa không. Đây là một mặt nạ hay một bộ lọc cho phép chúng ta quyết định những weight nào được tính toán, những weight nào không được tính toán. Ban đầu mặt nạ này được khởi tạo là ma trận toàn số 1. Sau khi cắt tỉa những weight nào không cần nữa thì sẽ thành số 0. Tư tưởng chính là vậy
- Hàm forward hàm này thực hiện chức năng tính toán weight, thay vì nhân thẳng weight với input thông thường thì lúc này weight sẽ được nhân với bộ lọc trước. Điều này giúp loại bỏ đi các weight không cần thiết sau khi đã cắt tỉa
1 2 3 | F.linear(input,self.weight *self.mask,self.bias) |
- Hàm prune hàm này thực hiện chức năng chính là cắt tỉa. Tại mỗi lần cắt tỉa nó sẽ tính toán các trong số nào có weight nhỏ hơn ngưỡng quy định, cập nhật lại mask và weight tại các vị trí đó về giá trị 0. Cũng khá đơn giản thôi phải không các cháu.
Cài đặt mạng Fully Connected
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | classLeNet(PruningModule): def__init__(self,mask=False): super(LeNet,self).__init__() linear=MaskedLinearifmaskelsenn.Linear self.fc1=linear(784,300) self.fc2=linear(300,100) self.fc3=linear(100,10) defforward(self,x): x=x.view(-1,784) x=F.relu(self.fc1(x)) x=F.relu(self.fc2(x
Có thể bạn quan tâm
0
|