├── 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 |
--------------------------------------------------------------------------------