この記事について
量子化処理において、量子化前の値が持つ値域を効率よくカバーするように量子化ステップを設定すると、量子化誤差に対するSN比が約$6×N(bit)[dB]$になることを確認します。
音響や信号処理などでよく知られているように、あるリニア信号を量子化した際に、量子化ビットが1ビット増えるごとにSNが$6[dB]$改善します。
FP32で学習された機械学習モデルをエッジデバイスに搭載する場合などに用いられる量子化において、効率よく量子化できているかの目安にもなると考えられます。
検証
理想的なケース
適当な値域を設定してFP32の値を複数生成し、それらをINT8(8bit量子化)した場合のSN比の計算します。この場合、指定した値域内でFP32は均等に生成されます。量子化の最小単位となる量子化ステップもこの値域をもとに算出します。このような理想的な場合のSN比の期待値は$6\times8(bit)=48[dB]$です。
# include <stdio.h>
# include <stdlib.h>
# include <math.h>
// 0.0~1.0の乱数を生成
float rand_norm(){
float r;
r = rand();
r /= (float)RAND_MAX;
return r;
}
int main(void){
const int EXP_NUM = 1000; // 乱数のサンプリング回数
const float MAX = 1000; // 値域の最大値
const float WAYS = 256; // 量子化のパターン数(Nビット量子化の場合 → 2^N)
const float Q = MAX/WAYS; // 量子化ステップ
double sn = 0; // 平均SN比
int i;
for(i=0;i<EXP_NUM;i++)
{
float sample;
float quanted_sample;
double err,mse;
// 0~MAXの値域でFP32の値を生成し
// ステップQで量子化した値とのSNを計算する
//-------------------------------------------
sample = rand_norm()*MAX; // 量子化前の値をサンプリング
quanted_sample = floor(sample/Q)*Q; // 量子化後の値
err = sample - quanted_sample;
mse = sqrt(err*err) + 10e-6; // MSEを計算
sn += 20*log10(sample/mse); // SN比を計算
}
sn /= EXP_NUM; // 平均化
printf("SN=%f db\n", sn);
return 0;
}
実験結果は以下のようになり、期待値通りとなります。
SN=48.630352 db
量子化ステップが最適でない場合
上記は、FP32の値がとる値域がわかっている想定で検証しました。量子化前のFP32の値が0~MAXまでの値域を持つと想定して量子化ステップを設定しておきながら、実際はそれよりも量子化前のFP32の値が散らばる値域が狭い場合はSN比が期待値より劣化します。
検証プログラムで用いた0.0~1.0の乱数を生成するプログラムを下記のように変更して、1bit分、値域を狭くしてみます。
float rand_norm(){
float r;
r = rand() >> 1; // 生成される乱数を1bitシフトする(値域を狭くする)
r /= (float)RAND_MAX;
return r;
}
量子化ステップは0~MAXの値域を想定して設定したままにしています。そのため、検証結果は1bit分である$6[dB]$だけ先の結果より劣化するようになります。
SN=42.780143 db
このように期待されるSN比よりも劣化している場合は、無駄に広いダイナミックレンジをカバーしようとして量子化ステップを必要以上に大きくとりすぎて解像度が低下してしまっている可能性があることを示しています。
FP32の範囲は大きな値域をカバーしますが、一般に、量子化対象の値がこの値域全域に一様に分布するようなことは稀です。そのため、FP32を量子化する場合は、以下の事を検討する必要があります。
- 量子化対象の数値群がどのような分布をしているかを確認する
- 分布範囲の多数をカバーするように値域を設定する
- 設定した値域から量子化ステップを算出する