├── LICENSE ├── README.md ├── main.py ├── outputs ├── MSE_loss.npy ├── repre.npy ├── results.png └── thre.npy └── utils.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Fuengfusin Ninnart 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lloyd-Max Quantizer: 2 | This is a Python implementation of Lloyd-Max Quantizer. Currently, this repository only supports for quantization of the Gaussian distribution signal. 3 | 4 | ## Requirements: 5 | 1. numpy 6 | 2. matplotlib 7 | 3. scipy 8 | 9 | 10 | ## Usage: 11 | To quantization with 8-bit for 1,000,000 iterations, please use a command as follows: 12 | ~~~shell 13 | python3 main.py -b 8 -i 1000000 14 | ~~~ 15 | 16 | ## Result of quantization with 8-bit: 17 |

18 | 19 | 20 | From the figure, the upper-figure shows an input signal or a Gaussian noise with zero mean and a unit variance. The middle-figure shows optimized quantization locations given the input signal. The bottom-figure shows a result signal or an quantized Gaussian noise.
21 | 22 | From this implementation, mean square error of 8-bit quantization of Gaussian noise is 3.595888887954022e-05. 23 | 24 | ## License: 25 | MIT license. 26 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """main.py for Lloyd Max Quantizer. 4 | To run with 8 bits with 1,000,000 iterations using: python3 main.py -b 8 -i 1000000 5 | In case 8 bits, after 1,000,000 iterations, the minimum MSE loss is around 3.6435e-05. 6 | @author: Ninnart Fuengfusin 7 | """ 8 | import argparse, os 9 | #Guard protection for error: No module named 'tkinter' 10 | try: import matplotlib.pyplot as plt 11 | except ModuleNotFoundError: 12 | import matplotlib 13 | matplotlib.use('agg') 14 | import matplotlib.pyplot as plt 15 | import numpy as np 16 | from utils import normal_dist, expected_normal_dist, MSE_loss, LloydMaxQuantizer 17 | 18 | parser = argparse.ArgumentParser(description='lloyd-max iteration quantizer') 19 | parser.add_argument('--bit', '-b', type=int, default=8, help='number of bit for quantization') 20 | parser.add_argument('--iteration', '-i', type=int, default=1_000_000, help='number of iteration') 21 | parser.add_argument('--range', '-r', type=int, default=10, help='range of the initial distribution') 22 | parser.add_argument('--resolution', '-re', type=int, default=100, help='resolution of the initial distribution') 23 | parser.add_argument('--save_location', '-s', type=str, default='outputs', help='save location of representations and ') 24 | args = parser.parse_args() 25 | 26 | if __name__ == '__main__': 27 | #Generate the 1000 simple of input signal as the gaussain noise in range [0,1]. 28 | x = np.random.normal(0, 1, 1000) 29 | repre = LloydMaxQuantizer.start_repre(x, args.bit) 30 | min_loss = 1.0 31 | 32 | for i in range(args.iteration): 33 | thre = LloydMaxQuantizer.threshold(repre) 34 | #In case wanting to use with another mean or variance, need to change mean and variance in untils.py file 35 | repre = LloydMaxQuantizer.represent(thre, expected_normal_dist, normal_dist) 36 | x_hat_q = LloydMaxQuantizer.quant(x, thre, repre) 37 | loss = MSE_loss(x, x_hat_q) 38 | 39 | # Print every 10 loops 40 | if(i%10 == 0 and i != 0): 41 | print('iteration: ' + str(i)) 42 | print('thre: ' + str(thre)) 43 | print('repre: ' + str(repre)) 44 | print('loss: ' + str(loss)) 45 | print('++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++') 46 | 47 | # Keep the threhold and representation that has the lowest MSE loss. 48 | if(min_loss > loss): 49 | min_loss = loss 50 | min_thre = thre 51 | min_repre = repre 52 | 53 | print('min loss' + str(min_loss)) 54 | print('min thre' + str(min_thre)) 55 | print('min repre' + str(min_repre)) 56 | 57 | # Save the best thresholds and representations in the numpy format for further using. 58 | try: os.mkdir(args.save_location) 59 | except FileExistsError: 60 | pass 61 | np.save(args.save_location + '/' + 'MSE_loss', min_loss) 62 | np.save(args.save_location + '/' + 'thre', min_thre) 63 | np.save(args.save_location + '/' + 'repre', min_repre) 64 | 65 | #x_hat_q with the lowest amount of loss. 66 | best_x_hat_q = LloydMaxQuantizer.quant(x, min_thre, min_repre) 67 | fig = plt.figure() 68 | ax = fig.add_subplot(3,1,1) 69 | ax.plot(range(np.size(x)), x, 'b') 70 | ax = fig.add_subplot(3,1,2) 71 | ax.plot(range(np.size(best_x_hat_q)), best_x_hat_q, 'rx') 72 | ax = fig.add_subplot(3,1,3) 73 | ax.plot(range(np.size(best_x_hat_q)), best_x_hat_q, 'y') 74 | plt.show() 75 | fig.savefig(args.save_location + '/' + 'results.png', dpi=fig.dpi) 76 | 77 | -------------------------------------------------------------------------------- /outputs/MSE_loss.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninfueng/lloyd-max-quantizer/081bf2aba230b88811be2e074d86929986fd87e6/outputs/MSE_loss.npy -------------------------------------------------------------------------------- /outputs/repre.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninfueng/lloyd-max-quantizer/081bf2aba230b88811be2e074d86929986fd87e6/outputs/repre.npy -------------------------------------------------------------------------------- /outputs/results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninfueng/lloyd-max-quantizer/081bf2aba230b88811be2e074d86929986fd87e6/outputs/results.png -------------------------------------------------------------------------------- /outputs/thre.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninfueng/lloyd-max-quantizer/081bf2aba230b88811be2e074d86929986fd87e6/outputs/thre.npy -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """Collection of utility functions for Lloyd Max Quantizer. 4 | @author: Ninnart Fuengfusin 5 | """ 6 | import numpy as np 7 | import scipy.integrate as integrate 8 | 9 | 10 | def normal_dist(x, mean=0.0, vari=1.0): 11 | """A normal distribution function created to use with scipy.integral.quad 12 | """ 13 | return (1.0/(np.sqrt(2.0*np.pi*vari)))*np.exp((-np.power((x-mean),2.0))/(2.0*vari)) 14 | 15 | 16 | def expected_normal_dist(x, mean=0.0, vari=1.0): 17 | """A expected value of normal distribution function which created to use with scipy.integral.quad 18 | """ 19 | return (x/(np.sqrt(2.0*np.pi*vari)))*np.exp((-np.power((x-mean),2.0))/(2.0*vari)) 20 | 21 | 22 | def laplace_dist(x, mean=0.0, vari=1.0): 23 | """ A laplace distribution function to use with scipy.integral.quad 24 | """ 25 | #In laplace distribution beta is used instead of variance so, the converting is necessary. 26 | scale = np.sqrt(vari/2.0) 27 | return (1.0/(2.0*scale))*np.exp(-(np.abs(x-mean))/(scale)) 28 | 29 | def expected_laplace_dist(x, mean=0.0, vari=1.0): 30 | """A expected value of laplace distribution function which created to use with scipy.integral.quad 31 | """ 32 | scale = np.sqrt(vari/2.0) 33 | return x*(1.0/(2.0*scale))*np.exp(-(np.abs(x-mean))/(scale)) 34 | 35 | #def variance(x, mean=0.0, std=1.0): 36 | # """ 37 | # create normal distribution 38 | # """ 39 | # return (1.0/(std*np.sqrt(2.0*np.pi)))*np.power(x-mean,2)*np.exp((-np.power((x-mean),2.0)/(2.0*np.power(std,2.0)))) 40 | 41 | def MSE_loss(x, x_hat_q): 42 | """Find the mean square loss between x (orginal signal) and x_hat (quantized signal) 43 | Args: 44 | x: the signal without quantization 45 | x_hat_q: the signal of x after quantization 46 | Return: 47 | MSE: mean square loss between x and x_hat_q 48 | """ 49 | #protech in case of input as tuple and list for using with numpy operation 50 | x = np.array(x) 51 | x_hat_q = np.array(x_hat_q) 52 | assert np.size(x) == np.size(x_hat_q) 53 | MSE = np.sum(np.power(x-x_hat_q,2))/np.size(x) 54 | return MSE 55 | 56 | 57 | class LloydMaxQuantizer(object): 58 | """A class for iterative Lloyd Max quantizer. 59 | This quantizer is created to minimize amount SNR between the orginal signal 60 | and quantized signal. 61 | """ 62 | @staticmethod 63 | def start_repre(x, bit): 64 | """ 65 | Generate representations of each threshold using 66 | Args: 67 | x: input signal for 68 | bit: amount of bit 69 | Return: 70 | threshold: 71 | """ 72 | assert isinstance(bit, int) 73 | x = np.array(x) 74 | num_repre = np.power(2,bit) 75 | step = (np.max(x)-np.min(x))/num_repre 76 | 77 | middle_point = np.mean(x) 78 | repre = np.array([]) 79 | for i in range(int(num_repre/2)): 80 | repre = np.append(repre, middle_point+(i+1)*step) 81 | repre = np.insert(repre, 0, middle_point-(i+1)*step) 82 | return repre 83 | 84 | @staticmethod 85 | def threshold(repre): 86 | """ 87 | """ 88 | t_q = np.zeros(np.size(repre)-1) 89 | for i in range(len(repre)-1): 90 | t_q[i] = 0.5*(repre[i]+repre[i+1]) 91 | return t_q 92 | 93 | @staticmethod 94 | def represent(thre, expected_dist, dist): 95 | """ 96 | """ 97 | thre = np.array(thre) 98 | x_hat_q = np.zeros(np.size(thre)+1) 99 | #prepare for all possible integration range 100 | thre = np.append(thre, np.inf) 101 | thre = np.insert(thre, 0, -np.inf) 102 | 103 | for i in range(len(thre)-1): 104 | x_hat_q[i] = integrate.quad(expected_dist, thre[i], thre[i+1])[0]/(integrate.quad(dist,thre[i],thre[i+1])[0]) 105 | return x_hat_q 106 | 107 | @staticmethod 108 | def quant(x, thre, repre): 109 | """Quantization operation. 110 | """ 111 | thre = np.append(thre, np.inf) 112 | thre = np.insert(thre, 0, -np.inf) 113 | x_hat_q = np.zeros(np.shape(x)) 114 | for i in range(len(thre)-1): 115 | if i == 0: 116 | x_hat_q = np.where(np.logical_and(x > thre[i], x <= thre[i+1]), 117 | np.full(np.size(x_hat_q), repre[i]), x_hat_q) 118 | elif i == range(len(thre))[-1]-1: 119 | x_hat_q = np.where(np.logical_and(x > thre[i], x <= thre[i+1]), 120 | np.full(np.size(x_hat_q), repre[i]), x_hat_q) 121 | else: 122 | x_hat_q = np.where(np.logical_and(x > thre[i], x < thre[i+1]), 123 | np.full(np.size(x_hat_q), repre[i]), x_hat_q) 124 | return x_hat_q 125 | 126 | if __name__ == '__main__': 127 | print('Please compile with main.py, this file is a collection of functions only.') 128 | --------------------------------------------------------------------------------