Hướng dẫn quantization python code - mã python lượng tử hóa

Bài 51 - Quantization trong deep learning

23 Nov 2020 - phamdinhkhanh

Show

1. Tại sao cần quantization

Khi tôi viết bài này thì quantization đã khá phổ biến trong Deep Learning. Đây là một khái niệm không còn mới, nhưng lại rất quan trọng. Vậy nó quan trọng như thế nào và vì sao chúng ta lại cần quantization ?

Các mô hình deep learning ngày càng đạt độ chính xác cao hơn qua thời gian. Không khó để bạn tìm thấy những mô hình đạt độ chính xác SOTA trên bộ dữ liệu ImageNet. Nếu không tin bạn có thể theo dõi độ chính xác qua thời gian trên bảng leader board của Imagenet classification.

Nhưng hầu hết những mô hình có độ chính xác cao lại không có khả năng deploy trên các thiết bị IoT và mobile có phần cứng rất yếu vì nó quá lớn để triển khai.

Quantization là một kỹ thuật giúp bạn giảm nhẹ kích thước các mô hình deep learning nhiều lần, đồng thời giảm độ trễ (latency) và tăng tốc độ inference. Cùng tìm hiểu về kỹ thuật tuyệt vời này qua bài viết hôm nay.giảm nhẹ kích thước các mô hình deep learning nhiều lần, đồng thời giảm độ trễ (latency)tăng tốc độ inference. Cùng tìm hiểu về kỹ thuật tuyệt vời này qua bài viết hôm nay.

1.1. Khái niệm về quantization

Có nhiều định nghĩa khác nhau về quantization. Xin được trích dẫn khái niệm từ wikipedia:

` Quantization, in mathematics and digital signal processing, is the process of mapping input values from a large set (often a continuous set) to output values in a (countable) smaller set, often with a finite number of elements. Rounding and truncation are typical examples of quantization processes. Quantization is involved to some degree in nearly all digital signal processing, as the process of representing a signal in digital form ordinarily involves rounding. Quantization also forms the core of essentially all lossy compression algorithms. `

Như vậy quantization trong toán học và xử lý tín hiệu số là quá trình map các giá trị đầu vào từ một tập số lớn (thường là liên tục) sang các giá trị output là một tập nhỏ hơn có thể đếm được, hữu hạn các phần tử.

Ví dụ về quantization

Bạn có thể hình dung dễ dàng khái niệm trên thông qua ví dụ sau đây:

Giả sử một hàm $f(x) = sin(x)$ là một hàm liên tục trong khoảng $[-\pi, \pi]$. Ta có thể tìm được một tập hợp $S = {\frac{i \pi}{100}} \forall, i \in [-100, 100]$ là một tập hữu hạn rời rạc các điểm cách đều nhau một khoảng là $\frac{\pi}{100}$ sao cho tập các điểm $(x, y)$ có miền xác định trên tập $S$ có thể biểu diễn một cách gần đúng mọi điểm $(x, y)$ trên miền liên tục $[-\pi, \pi]$.

Hướng dẫn quantization python code - mã python lượng tử hóa

Hình 1: Biểu diễn của hàm $sin(x)$ và giá trị quantization của nó trên khoảng $[-\pi, \pi].$ Biểu diễn của hàm $sin(x)$ và giá trị quantization của nó trên khoảng $[-\pi, \pi].$

Trong quá trình inference model thì các xử lý được tính toán chủ yếu trên kiểu dữ liệu float32. Đơn vị khoảng (interval unit) của float32 rất nhỏ và dường như là liên tục. Do đó nó có thể biểu diễn mọi giá trị. Trong khi int8 và float16 là những tập hợp có kích thước nhỏ hơn và có đơn vị khoảng lớn hơn rất nhiều so với float32. Việc chuyển đổi định dạng từ float32 sang float16 hoặc int8 có thể coi là quantization theo định nghĩa nêu trên.

float32 sẽ có kích thước nghi nhớ lớn hơn so với các kiểu lưu trữ khác như int8, float16. Để lưu trữ float32 thì chúng ta cần 32 bits. Các bits này nhận một trong hai gía trị 0, 1 để máy có thể đọc được. Quantization hiểu đơn giản hơn là kỹ thuật chuyển đổi định dạng của dữ liệu từ những kiểu dữ liệu có độ chính xác cao sang những kiểu dữ liệu có độ chính xác thấp hơn, qua đó làm giảm memory khi lưu trữ. Do đó làm tăng tốc độ inference là giảm latency khi load model. Tất nhiên là khi bạn làm giảm độ chính xác xuống thì thường accuracy của mô hình sẽ giảm một chút (không quá nhiều) so với mô hình gốc.

Bạn đọc đã hiểu về quantization rồi chứ ? Đơn giản phải không nào ?

1.2. Quantization giảm kích thước bao nhiêu lần ?

1.2.1. Biểu diễn nhị phân

Trên máy tính, mọi giá trị đều được biểủ diễn dưới dạng nhị phân gồm các bit ${0, 1}$.

Cách convert một số nguyên từ hệ thập phân sang hệ nhị phân

Chúng ta có thể biểu diễn dễ dàng một số nguyên dưới dạng nhị phân bằng cách khai triển thành tổng các lũy thừa của 2.

\[x = \sum_{i=1}^{n} d_i(0, 1) \times 2^{i}\]

Trong đó $d_i(0, 1) \in \{ 0, 1 \}$. Biểu diễn nhị phân của $x$ sẽ là chuỗi $d_n d_{n-1} \dots d_0$ mà mỗi vị trí $d_i$ nhận 2 giá trị $\{ 0, 1 \}$. Ở cấp 2 chắc các bạn đã từng làm bài toán biến đổi số thập phân sang hệ nhị phân. Bạn còn nhớ chúng ta sẽ thực hiện liên tiếp các phép chia liên hoàn cho 2 cho đến khi không chia được nữa chứ ? Quá trình này lặp lại cho đến khi kết quả thu được sau cùng là 0. Khi đó phần dư ở các bước chia sẽ được lưu lại và cuối cùng nghịch đảo thứ tự của chuỗi phần dư ta thu được biểu diễn nhị phân của số thập phân. Cùng xem hình bên dưới.

Hướng dẫn quantization python code - mã python lượng tử hóa

Hình 2: Phương pháp tìm biểu diễn nhị phân của 19 thông qua phép chia liên hoàn cho 2. Kết quả thu được là

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate

x = np.linspace(-1, 1, num=100)
xnew = np.linspace(-1, 1, num=1000, endpoint=True)
y = np.sin(x*np.pi)
f = interpolate.interp1d(x, y, kind='previous')
intpol = f(xnew)
plt.figure(figsize=(16, 10))
plt.plot(x, y, marker='o', label = 'continous line')
plt.plot(xnew, intpol, '--', label = 'quantization line')
plt.legend()
plt.show()
1
2
6. Phương pháp tìm biểu diễn nhị phân của 19 thông qua phép chia liên hoàn cho 2. Kết quả thu được là
1
2
6.

Chứng minh công thức này không khó, mình xin dành cho bạn đọc như một bài tập bổ sung.

Dựa trên ý tưởng trên, chúng ta có thể code hàm biến đổi số nguyên dương sang nhị phân một cách khá dễ dàng:

Như vậy bạn đã nắm được ý tưởng biến đổi một số nguyên sang hệ nhị phân rồi chứ ? Tiếp theo chúng ta cùng phân tích biểu diễn của một số nguyên đối với định dạng float 32.

Giả sử số đó là

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import numpy as np

def _binary_integer(integer):
  exps = []
  odds = []
  # Hàm lấy phần nguyên và phần dư khi chia cho 2
  def _whole_odd(integer):
    # phần nguyên là whole, phần dư là odd.
    whole = integer // 2;
    odd = integer % 2;
    return whole, odd
  
  (whole, odd) = _whole_odd(integer)
  odds.append(odd)
  while (whole > 0): # Khi phần nguyên vẫn lớn hơn 0 thì còn tiếp tục chia.
    (whole, odd) = _whole_odd(whole)
    # Lưu lại phần dư sau mỗi lượt chia
    odds.append(odd)
  # Revert chuỗi số dư để thu được list nhị phân
  odds = np.array(odds)[::-1]
  return odds
    
_binary_integer(19)
1
2
7. Khi biểu diễn dưới dạng float 32 thì chúng ta sẽ đưa thêm phần thập phân vào sau nó.

1
2
print("Integer Part: ", "".join([str(i) for i in _binary_integer(1993)]))
print("Total Bits: ", len(_binary_integer(1993)))

Integer Part:  11111001001
Total Bits:  11

Phần nguyên chiếm 11 bits, như vậy 21 bits còn lại sẽ là các chữ số 0 ở phần dư và dấu phảy. Tuy nhiên khi chuyển sang định dạng float 16 thì ta chỉ cần 16 bits để lưu cùng số nguyên ở trên, trong đó 11 bits cho phần nguyên và 5 bits còn lại cho phần dư và dấu phảy. Số bits sử dụng sẽ giảm một nửa giúp cho mô hình giảm một nửa bộ nhớ lưu trữ.

1.3. Các dạng quantization

Hướng dẫn quantization python code - mã python lượng tử hóa

Bên dưới là bảng mức độ giảm kích thước (về bộ nhớ, số lượng tham số mô hình không đổi) khi thực hiện quantization theo các kiểu dữ liệu khác nhau.

Source: Post training quantization - tensorflow

  • Trong bảng trên là 3 kiểu quantization cơ bản với các tính chất khác nhau.: Đây là kiểu quantization mặc định của tflite. Ý tưởng chính là dựa trên khoảng biến thiên của dữ liệu trên một batch để ước lượng các giá trị quantization. Kiểu quantization này giúp giảm 4 lần kích thước bộ nhớ, tăng tốc độ 2-3 lần trên CPU.

  • Dynamic range quantization: Đây là kiểu quantization mặc định của tflite. Ý tưởng chính là dựa trên khoảng biến thiên của dữ liệu trên một batch để ước lượng các giá trị quantization. Kiểu quantization này giúp giảm 4 lần kích thước bộ nhớ, tăng tốc độ 2-3 lần trên CPU.: Toàn bộ các hệ số sẽ được chuyển về kiểu số nguyên. Kích thước bộ nhớ giảm 4 lần, tăng 3 lần tốc độ trên CPU, TPU, Microcontrollers.

  • Full Integer quantization: Toàn bộ các hệ số sẽ được chuyển về kiểu số nguyên. Kích thước bộ nhớ giảm 4 lần, tăng 3 lần tốc độ trên CPU, TPU, Microcontrollers.: Tất nhiên là sẽ được chuyển về kiểu float và kích thước giảm 2 lần (từ 32 bits về 16 bits). Giúp tăng tốc tính toán trên GPU.

Float16 quantization: Tất nhiên là sẽ được chuyển về kiểu float và kích thước giảm 2 lần (từ 32 bits về 16 bits). Giúp tăng tốc tính toán trên GPU.

Hiện nay hầu hết các framework deep learning đều hỗ trợ quantization. Trong khuôn khổ của blog này mình chỉ giới thiệu quantization trên framework tensorflow và pytorch.

2. Quantization trên tensorflow

Về Quantization trên tensorflow thì đã có hướng dẫn khá chi tiết cho 3 kiểu ở trên. Các bạn có thể xem tại Post-training quantization.

  • Mình xin tổng hợp lại các ý chính bao gồm:
  • Cách convert model trên tflite.
  • Thực hiện quantization.

Kiểm tra độ chính xác của mô hình sau khi quantization.

Trên tensorflow chúng ta thường quantization mô hình trên tensorflow lite trước khi deploy trên các thiết bị di động. Nếu bạn chưa biết về tensorflow lite thì đây là một định dạng thuộc kiểu FlatBuffer, một cross platform hỗ trợ nhiều ngôn ngữ khác nhau như

1
2
print("Integer Part: ", "".join([str(i) for i in _binary_integer(1993)]))
print("Total Bits: ", len(_binary_integer(1993)))
1
2
8. FlatBuffer cho phép serialization data nhanh hơn so với các kiểu dữ liệu khác như Protocol Buffer vì nó bỏ qua quá trình data parsing. Do đó quá trình load model sẽ nhanh hơn đáng kể.

Tiếp theo chúng ta sẽ thực hành Quantization model trên định dạng 1 2 9.

2.1. Convert một model tflite

Tensorflow cung cấp một module là

print("Integer Part: ", "".join([str(i) for i in _binary_integer(1993)]))
print("Total Bits: ", len(_binary_integer(1993)))
0 để convert dữ liệu từ các định dạng như
print("Integer Part: ", "".join([str(i) for i in _binary_integer(1993)]))
print("Total Bits: ", len(_binary_integer(1993)))
1 sang
print("Integer Part: ", "".join([str(i) for i in _binary_integer(1993)]))
print("Total Bits: ", len(_binary_integer(1993)))
2.

Để đơn giản thì mình sẽ không huấn luyện model từ đầu mà load một pretrain model từ tensorflow hub. Tensorflow hub là một nơi khá tuyệt vời chia sẻ opensource chia sẻ các mô hình pretrain trên tensorflow. Từ hình ảnh, văn bản cho tới tiếng nói. Bạn có thể tìm được rất nhiều các pretrain model tại đây. Đồng thời nếu muốn chia sẻ weight từ các mô hình của mình, bạn cũng có thể trở thành một Tensorhub publisher.

Để load một pretrain layer trên tensorflow hub thực hiện như sau:

import tensorflow as tf
import tensorflow_hub as hub

mobilenet_v2 = tf.keras.Sequential([
  tf.keras.layers.InputLayer(input_shape=(28, 28, 1)),
  hub.KerasLayer("https://tfhub.dev/tensorflow/tfgan/eval/mnist/logits/1"),
  tf.keras.layers.Dense(10, activation='softmax')
])

import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate

x = np.linspace(-1, 1, num=100)
xnew = np.linspace(-1, 1, num=1000, endpoint=True)
y = np.sin(x*np.pi)
f = interpolate.interp1d(x, y, kind='previous')
intpol = f(xnew)
plt.figure(figsize=(16, 10))
plt.plot(x, y, marker='o', label = 'continous line')
plt.plot(xnew, intpol, '--', label = 'quantization line')
plt.legend()
plt.show()
1

Huấn luyện lại model trên tập dữ liệu mnist

import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate

x = np.linspace(-1, 1, num=100)
xnew = np.linspace(-1, 1, num=1000, endpoint=True)
y = np.sin(x*np.pi)
f = interpolate.interp1d(x, y, kind='previous')
intpol = f(xnew)
plt.figure(figsize=(16, 10))
plt.plot(x, y, marker='o', label = 'continous line')
plt.plot(xnew, intpol, '--', label = 'quantization line')
plt.legend()
plt.show()
3

import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate

x = np.linspace(-1, 1, num=100)
xnew = np.linspace(-1, 1, num=1000, endpoint=True)
y = np.sin(x*np.pi)
f = interpolate.interp1d(x, y, kind='previous')
intpol = f(xnew)
plt.figure(figsize=(16, 10))
plt.plot(x, y, marker='o', label = 'continous line')
plt.plot(xnew, intpol, '--', label = 'quantization line')
plt.legend()
plt.show()
5

Model của chúng ta được khởi tạo từ keras nên nó có định dạng

print("Integer Part: ", "".join([str(i) for i in _binary_integer(1993)]))
print("Total Bits: ", len(_binary_integer(1993)))
3. Chúng ta có thể convert model sang
print("Integer Part: ", "".join([str(i) for i in _binary_integer(1993)]))
print("Total Bits: ", len(_binary_integer(1993)))
2.

import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate

x = np.linspace(-1, 1, num=100)
xnew = np.linspace(-1, 1, num=1000, endpoint=True)
y = np.sin(x*np.pi)
f = interpolate.interp1d(x, y, kind='previous')
intpol = f(xnew)
plt.figure(figsize=(16, 10))
plt.plot(x, y, marker='o', label = 'continous line')
plt.plot(xnew, intpol, '--', label = 'quantization line')
plt.legend()
plt.show()
3

import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate

x = np.linspace(-1, 1, num=100)
xnew = np.linspace(-1, 1, num=1000, endpoint=True)
y = np.sin(x*np.pi)
f = interpolate.interp1d(x, y, kind='previous')
intpol = f(xnew)
plt.figure(figsize=(16, 10))
plt.plot(x, y, marker='o', label = 'continous line')
plt.plot(xnew, intpol, '--', label = 'quantization line')
plt.legend()
plt.show()
5

Model của chúng ta được khởi tạo từ keras nên nó có định dạng print("Integer Part: ", "".join([str(i) for i in _binary_integer(1993)])) print("Total Bits: ", len(_binary_integer(1993))) 3. Chúng ta có thể convert model sang print("Integer Part: ", "".join([str(i) for i in _binary_integer(1993)])) print("Total Bits: ", len(_binary_integer(1993))) 2.

import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate

x = np.linspace(-1, 1, num=100)
xnew = np.linspace(-1, 1, num=1000, endpoint=True)
y = np.sin(x*np.pi)
f = interpolate.interp1d(x, y, kind='previous')
intpol = f(xnew)
plt.figure(figsize=(16, 10))
plt.plot(x, y, marker='o', label = 'continous line')
plt.plot(xnew, intpol, '--', label = 'quantization line')
plt.legend()
plt.show()
6

import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate

x = np.linspace(-1, 1, num=100)
xnew = np.linspace(-1, 1, num=1000, endpoint=True)
y = np.sin(x*np.pi)
f = interpolate.interp1d(x, y, kind='previous')
intpol = f(xnew)
plt.figure(figsize=(16, 10))
plt.plot(x, y, marker='o', label = 'continous line')
plt.plot(xnew, intpol, '--', label = 'quantization line')
plt.legend()
plt.show()
7

Như vậy ở định dạng tflite chúng ta có mô hình

print("Integer Part: ", "".join([str(i) for i in _binary_integer(1993)]))
print("Total Bits: ", len(_binary_integer(1993)))
5 có kích thước là 13 Mb.

Để quantization được các layer input, output và layer trung gian thì chúng ta phải thông qua RepresentativeDataset, RepresentativeDataset là một generator function có kích thước đủ lớn, có tác dụng ước lượng khoảng biến thiên cho toàn bộ các tham số của mô hình.

1
2
3
4
5
6
7
8
import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate

x = np.linspace(-1, 1, num=100)
xnew = np.linspace(-1, 1, num=1000, endpoint=True)
y = np.sin(x*np.pi)
f = interpolate.interp1d(x, y, kind='previous')
intpol = f(xnew)
plt.figure(figsize=(16, 10))
plt.plot(x, y, marker='o', label = 'continous line')
plt.plot(xnew, intpol, '--', label = 'quantization line')
plt.legend()
plt.show()
0
import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate

x = np.linspace(-1, 1, num=100)
xnew = np.linspace(-1, 1, num=1000, endpoint=True)
y = np.sin(x*np.pi)
f = interpolate.interp1d(x, y, kind='previous')
intpol = f(xnew)
plt.figure(figsize=(16, 10))
plt.plot(x, y, marker='o', label = 'continous line')
plt.plot(xnew, intpol, '--', label = 'quantization line')
plt.legend()
plt.show()
2
import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate

x = np.linspace(-1, 1, num=100)
xnew = np.linspace(-1, 1, num=1000, endpoint=True)
y = np.sin(x*np.pi)
f = interpolate.interp1d(x, y, kind='previous')
intpol = f(xnew)
plt.figure(figsize=(16, 10))
plt.plot(x, y, marker='o', label = 'continous line')
plt.plot(xnew, intpol, '--', label = 'quantization line')
plt.legend()
plt.show()
4
import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate

x = np.linspace(-1, 1, num=100)
xnew = np.linspace(-1, 1, num=1000, endpoint=True)
y = np.sin(x*np.pi)
f = interpolate.interp1d(x, y, kind='previous')
intpol = f(xnew)
plt.figure(figsize=(16, 10))
plt.plot(x, y, marker='o', label = 'continous line')
plt.plot(xnew, intpol, '--', label = 'quantization line')
plt.legend()
plt.show()
6
import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate

x = np.linspace(-1, 1, num=100)
xnew = np.linspace(-1, 1, num=1000, endpoint=True)
y = np.sin(x*np.pi)
f = interpolate.interp1d(x, y, kind='previous')
intpol = f(xnew)
plt.figure(figsize=(16, 10))
plt.plot(x, y, marker='o', label = 'continous line')
plt.plot(xnew, intpol, '--', label = 'quantization line')
plt.legend()
plt.show()
4
import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate

x = np.linspace(-1, 1, num=100)
xnew = np.linspace(-1, 1, num=1000, endpoint=True)
y = np.sin(x*np.pi)
f = interpolate.interp1d(x, y, kind='previous')
intpol = f(xnew)
plt.figure(figsize=(16, 10))
plt.plot(x, y, marker='o', label = 'continous line')
plt.plot(xnew, intpol, '--', label = 'quantization line')
plt.legend()
plt.show()
9
2.2. Quantization model
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
0
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    1
  • target_spec: Định dạng tính toán của các operation trong mạng deep learning.
  • inference_input_type: Định dạng input.
  • inference_output_type: Định dạng output.
  • Định dạng input, output và target_spec phải chung một kiểu với định dạng dữ liệu quantization.

    Trước khi quantization thì định dạng của input và output là float32, sau quantization thì chúng được chuyển về int8.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    4

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    5
    1
    2
    

    Kích thước mô hình sau khi chuyển đổi

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    7
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    8

    Chúng ta có thể thấy quantization đã làm giảm kích thước file tflite so với ban đầu gấp 4 lần. Bạn đọc cũng có thể tự kiểm tra tốc độ inference của mô hình sau khi quantization.

    2.3. Độ chính xác của mô hình quantization

    Sau khi thực hiện quantization chúng ta luôn phải kiểm tra lại độ chính xác trên một tập validation độc lập. Quá trình này đảm bảo mô hình sau quantization có chất lượng không quá giảm so với mô hình gốc.

    Kiểm tra độ chính xác mô hình trên tập validation

    import numpy as np
    
    def _binary_integer(integer):
      exps = []
      odds = []
      # Hàm lấy phần nguyên và phần dư khi chia cho 2
      def _whole_odd(integer):
        # phần nguyên là whole, phần dư là odd.
        whole = integer // 2;
        odd = integer % 2;
        return whole, odd
      
      (whole, odd) = _whole_odd(integer)
      odds.append(odd)
      while (whole > 0): # Khi phần nguyên vẫn lớn hơn 0 thì còn tiếp tục chia.
        (whole, odd) = _whole_odd(whole)
        # Lưu lại phần dư sau mỗi lượt chia
        odds.append(odd)
      # Revert chuỗi số dư để thu được list nhị phân
      odds = np.array(odds)[::-1]
      return odds
        
    _binary_integer(19)
    
    0

    import numpy as np
    
    def _binary_integer(integer):
      exps = []
      odds = []
      # Hàm lấy phần nguyên và phần dư khi chia cho 2
      def _whole_odd(integer):
        # phần nguyên là whole, phần dư là odd.
        whole = integer // 2;
        odd = integer % 2;
        return whole, odd
      
      (whole, odd) = _whole_odd(integer)
      odds.append(odd)
      while (whole > 0): # Khi phần nguyên vẫn lớn hơn 0 thì còn tiếp tục chia.
        (whole, odd) = _whole_odd(whole)
        # Lưu lại phần dư sau mỗi lượt chia
        odds.append(odd)
      # Revert chuỗi số dư để thu được list nhị phân
      odds = np.array(odds)[::-1]
      return odds
        
    _binary_integer(19)
    
    2

    Ta thấy sau khi quantization thì độ chính xác của mô hình giảm khoảng 0.05%. Đây là một mức giảm không đáng kể so với lợi ích đạt được từ việc giảm kích thước mô hình và tốc độ inference.

    2.3.1. Convert model by commandline

    Ngoài sử dụng

    print("Integer Part: ", "".join([str(i) for i in _binary_integer(1993)]))
    print("Total Bits: ", len(_binary_integer(1993)))
    
    0 để convert model thì chúng ta có thể convert trên commandline. Để tránh các lỗi phát sinh thì kinh nghiệm của mình là các bạn nên convert từ save mode của mô hình (save model là định dạng lưu trữ được cả graph của mô hình và tham số trong các checkpoint, do đó hạn chế được lỗi so với phương pháp chỉ lưu tham số).

    Lưu mô hình dưới dạng savemode

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    9
    import numpy as np
    
    def _binary_integer(integer):
      exps = []
      odds = []
      # Hàm lấy phần nguyên và phần dư khi chia cho 2
      def _whole_odd(integer):
        # phần nguyên là whole, phần dư là odd.
        whole = integer // 2;
        odd = integer % 2;
        return whole, odd
      
      (whole, odd) = _whole_odd(integer)
      odds.append(odd)
      while (whole > 0): # Khi phần nguyên vẫn lớn hơn 0 thì còn tiếp tục chia.
        (whole, odd) = _whole_odd(whole)
        # Lưu lại phần dư sau mỗi lượt chia
        odds.append(odd)
      # Revert chuỗi số dư để thu được list nhị phân
      odds = np.array(odds)[::-1]
      return odds
        
    _binary_integer(19)
    
    1
    import numpy as np
    
    def _binary_integer(integer):
      exps = []
      odds = []
      # Hàm lấy phần nguyên và phần dư khi chia cho 2
      def _whole_odd(integer):
        # phần nguyên là whole, phần dư là odd.
        whole = integer // 2;
        odd = integer % 2;
        return whole, odd
      
      (whole, odd) = _whole_odd(integer)
      odds.append(odd)
      while (whole > 0): # Khi phần nguyên vẫn lớn hơn 0 thì còn tiếp tục chia.
        (whole, odd) = _whole_odd(whole)
        # Lưu lại phần dư sau mỗi lượt chia
        odds.append(odd)
      # Revert chuỗi số dư để thu được list nhị phân
      odds = np.array(odds)[::-1]
      return odds
        
    _binary_integer(19)
    
    3
    1
    2
    

    Kích thước mô hình sau khi chuyển đổi

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    7

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    8
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    4

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    5
    1
    2
    

    Kích thước mô hình sau khi chuyển đổi

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    7
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    8

    Kiểm tra độ chính xác mô hình trên tập validation

    import numpy as np
    
    def _binary_integer(integer):
      exps = []
      odds = []
      # Hàm lấy phần nguyên và phần dư khi chia cho 2
      def _whole_odd(integer):
        # phần nguyên là whole, phần dư là odd.
        whole = integer // 2;
        odd = integer % 2;
        return whole, odd
      
      (whole, odd) = _whole_odd(integer)
      odds.append(odd)
      while (whole > 0): # Khi phần nguyên vẫn lớn hơn 0 thì còn tiếp tục chia.
        (whole, odd) = _whole_odd(whole)
        # Lưu lại phần dư sau mỗi lượt chia
        odds.append(odd)
      # Revert chuỗi số dư để thu được list nhị phân
      odds = np.array(odds)[::-1]
      return odds
        
    _binary_integer(19)
    
    1

    import numpy as np def _binary_integer(integer): exps = [] odds = [] # Hàm lấy phần nguyên và phần dư khi chia cho 2 def _whole_odd(integer): # phần nguyên là whole, phần dư là odd. whole = integer // 2; odd = integer % 2; return whole, odd (whole, odd) = _whole_odd(integer) odds.append(odd) while (whole > 0): # Khi phần nguyên vẫn lớn hơn 0 thì còn tiếp tục chia. (whole, odd) = _whole_odd(whole) # Lưu lại phần dư sau mỗi lượt chia odds.append(odd) # Revert chuỗi số dư để thu được list nhị phân odds = np.array(odds)[::-1] return odds _binary_integer(19) 2

    import numpy as np
    
    def _binary_integer(integer):
      exps = []
      odds = []
      # Hàm lấy phần nguyên và phần dư khi chia cho 2
      def _whole_odd(integer):
        # phần nguyên là whole, phần dư là odd.
        whole = integer // 2;
        odd = integer % 2;
        return whole, odd
      
      (whole, odd) = _whole_odd(integer)
      odds.append(odd)
      while (whole > 0): # Khi phần nguyên vẫn lớn hơn 0 thì còn tiếp tục chia.
        (whole, odd) = _whole_odd(whole)
        # Lưu lại phần dư sau mỗi lượt chia
        odds.append(odd)
      # Revert chuỗi số dư để thu được list nhị phân
      odds = np.array(odds)[::-1]
      return odds
        
    _binary_integer(19)
    
    3

    Ta thấy sau khi quantization thì độ chính xác của mô hình giảm khoảng 0.05%. Đây là một mức giảm không đáng kể so với lợi ích đạt được từ việc giảm kích thước mô hình và tốc độ inference.

    2.3.1. Convert model by commandline

    Ngoài sử dụng print("Integer Part: ", "".join([str(i) for i in _binary_integer(1993)])) print("Total Bits: ", len(_binary_integer(1993))) 0 để convert model thì chúng ta có thể convert trên commandline. Để tránh các lỗi phát sinh thì kinh nghiệm của mình là các bạn nên convert từ save mode của mô hình (save model là định dạng lưu trữ được cả graph của mô hình và tham số trong các checkpoint, do đó hạn chế được lỗi so với phương pháp chỉ lưu tham số).

    1. Lưu mô hình dưới dạng savemode
    2. import numpy as np
      
      def _binary_integer(integer):
        exps = []
        odds = []
        # Hàm lấy phần nguyên và phần dư khi chia cho 2
        def _whole_odd(integer):
          # phần nguyên là whole, phần dư là odd.
          whole = integer // 2;
          odd = integer % 2;
          return whole, odd
        
        (whole, odd) = _whole_odd(integer)
        odds.append(odd)
        while (whole > 0): # Khi phần nguyên vẫn lớn hơn 0 thì còn tiếp tục chia.
          (whole, odd) = _whole_odd(whole)
          # Lưu lại phần dư sau mỗi lượt chia
          odds.append(odd)
        # Revert chuỗi số dư để thu được list nhị phân
        odds = np.array(odds)[::-1]
        return odds
          
      _binary_integer(19)
      
      5
    3. import numpy as np
      
      def _binary_integer(integer):
        exps = []
        odds = []
        # Hàm lấy phần nguyên và phần dư khi chia cho 2
        def _whole_odd(integer):
          # phần nguyên là whole, phần dư là odd.
          whole = integer // 2;
          odd = integer % 2;
          return whole, odd
        
        (whole, odd) = _whole_odd(integer)
        odds.append(odd)
        while (whole > 0): # Khi phần nguyên vẫn lớn hơn 0 thì còn tiếp tục chia.
          (whole, odd) = _whole_odd(whole)
          # Lưu lại phần dư sau mỗi lượt chia
          odds.append(odd)
        # Revert chuỗi số dư để thu được list nhị phân
        odds = np.array(odds)[::-1]
        return odds
          
      _binary_integer(19)
      
      6
    4. import numpy as np
      
      def _binary_integer(integer):
        exps = []
        odds = []
        # Hàm lấy phần nguyên và phần dư khi chia cho 2
        def _whole_odd(integer):
          # phần nguyên là whole, phần dư là odd.
          whole = integer // 2;
          odd = integer % 2;
          return whole, odd
        
        (whole, odd) = _whole_odd(integer)
        odds.append(odd)
        while (whole > 0): # Khi phần nguyên vẫn lớn hơn 0 thì còn tiếp tục chia.
          (whole, odd) = _whole_odd(whole)
          # Lưu lại phần dư sau mỗi lượt chia
          odds.append(odd)
        # Revert chuỗi số dư để thu được list nhị phân
        odds = np.array(odds)[::-1]
        return odds
          
      _binary_integer(19)
      
      7
    5. import numpy as np
      
      def _binary_integer(integer):
        exps = []
        odds = []
        # Hàm lấy phần nguyên và phần dư khi chia cho 2
        def _whole_odd(integer):
          # phần nguyên là whole, phần dư là odd.
          whole = integer // 2;
          odd = integer % 2;
          return whole, odd
        
        (whole, odd) = _whole_odd(integer)
        odds.append(odd)
        while (whole > 0): # Khi phần nguyên vẫn lớn hơn 0 thì còn tiếp tục chia.
          (whole, odd) = _whole_odd(whole)
          # Lưu lại phần dư sau mỗi lượt chia
          odds.append(odd)
        # Revert chuỗi số dư để thu được list nhị phân
        odds = np.array(odds)[::-1]
        return odds
          
      _binary_integer(19)
      
      9
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    9

    Chúng ta có thể thấy quantization đã làm giảm kích thước file tflite so với ban đầu gấp 4 lần. Bạn đọc cũng có thể tự kiểm tra tốc độ inference của mô hình sau khi quantization.

    2.3. Độ chính xác của mô hình quantization

    Sau khi thực hiện quantization chúng ta luôn phải kiểm tra lại độ chính xác trên một tập validation độc lập. Quá trình này đảm bảo mô hình sau quantization có chất lượng không quá giảm so với mô hình gốc.
    import numpy as np
    
    def _binary_integer(integer):
      exps = []
      odds = []
      # Hàm lấy phần nguyên và phần dư khi chia cho 2
      def _whole_odd(integer):
        # phần nguyên là whole, phần dư là odd.
        whole = integer // 2;
        odd = integer % 2;
        return whole, odd
      
      (whole, odd) = _whole_odd(integer)
      odds.append(odd)
      while (whole > 0): # Khi phần nguyên vẫn lớn hơn 0 thì còn tiếp tục chia.
        (whole, odd) = _whole_odd(whole)
        # Lưu lại phần dư sau mỗi lượt chia
        odds.append(odd)
      # Revert chuỗi số dư để thu được list nhị phân
      odds = np.array(odds)[::-1]
      return odds
        
    _binary_integer(19)
    
    0
    1
    2
    
    0
  • 1
    2
    
    1
  • 2.3.2. Quantization trên pytorch
  • Trên pytorch cũng hỗ trợ ba định dạng quantization tương tự như tensorflow. Về hướng dẫn quantization cho các model trên pytorch thì các bạn có thể tham khảo tại hướng dẫn pytorch - quantization, khá đầy đủ và chi tiết.