├── .gitignore ├── Fitter.py ├── LICENSE.md ├── Model.py ├── NbTrolsModel.py ├── NoNbTrolsModel.py ├── Plotter.py ├── README.md ├── TimeStep.py ├── quantum_edward └── __init__.py ├── setup.py └── utilities.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | .idea 4 | __pycache__ 5 | .ipynb_checkpoints 6 | .octave-workspace -------------------------------------------------------------------------------- /Fitter.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import numpy.random as npr 3 | import scipy.stats as ss 4 | import utilities as ut 5 | from TimeStep import * 6 | from Plotter import * 7 | 8 | 9 | class Fitter: 10 | """ 11 | Read docstrings for Model class first. 12 | 13 | The goal of this class is to implement the BBVI(see ref below) for a 14 | Model object 'model' to estimate those values for the hidden variables 15 | list1_angs which best fit the training data y_nsam_nb, x_nsam_na. 16 | 17 | In BBVI, one maximizes ELBO with respect to a parameter lambda. In this 18 | case, lambda = list1_conc0, list1_conc1 and z = list1_z = 19 | list1_angs/dpi. The angles in list1_angs are in the interval [0, dpi] so 20 | the entries list1_z are in the interval [0, 1]. 21 | 22 | References 23 | ---------- 24 | R. Ranganath, S. Gerrish, D. M. Blei, "Black Box Variational 25 | Inference", https://arxiv.org/abs/1401.0118 26 | 27 | """ 28 | 29 | def __init__(self, model, y_nsam_nb, x_nsam_na, nsamgrad, 30 | nt, eta, t_step_meth): 31 | """ 32 | Constructor 33 | 34 | Parameters 35 | ---------- 36 | model : Model 37 | y_nsam_nb : np.array 38 | An array of zeros and ones with shape=(nsam, nb) containing nsam 39 | samples of y output. 40 | x_nsam_na : np.array 41 | An array of zeros and ones with shape=(nsam, na) containing nsam 42 | samples of x input. 43 | nsamgrad : int 44 | Number of samples used during averaging of the gradient of ELBO 45 | nt : int 46 | Number of time steps (aka iterations). Value of ELBO changes ( 47 | increases or stays the same) with each iteration. 48 | eta : float 49 | positive scaling parameter (proportionality factor) for delta 50 | lambda. Passed to TimeStep class 51 | t_step_meth : str 52 | str labelling the method used to calculate delta lambda. This 53 | str is passed to TimeStep class. 54 | 55 | Returns 56 | ------- 57 | None 58 | 59 | """ 60 | self.mod = model 61 | self.y_nsam_nb = y_nsam_nb 62 | self.x_nsam_na = x_nsam_na 63 | self.nsamgrad = nsamgrad 64 | self.nt = nt 65 | self.eta = eta 66 | self.t_step_meth = t_step_meth 67 | 68 | assert self.mod.na == x_nsam_na.shape[1] 69 | assert self.mod.nb == y_nsam_nb.shape[1] 70 | assert self.y_nsam_nb.shape[0] == self.x_nsam_na.shape[0] 71 | 72 | # the following will be filled by do_fit() 73 | self.fin_t = None 74 | self.fin_list1_conc0 = None 75 | self.fin_list1_conc1 = None 76 | 77 | len1 = self.mod.len1 78 | self.conc_nt_2_len1 = np.zeros((nt, 2, len1), dtype=float) 79 | self.delta_conc_nt_2_len1 = np.zeros((nt, 2, len1), dtype=float) 80 | self.elbo_nt_len1 = np.zeros((nt, len1), dtype=float) 81 | 82 | def get_delbo_and_grad_delbo(self, list1_z, list1_conc0, list1_conc1): 83 | """ 84 | delbo = density of elbo. grad = gradient. This is a private 85 | auxiliary function used by do_fit(). Inside the method do_fit(), 86 | we calculate elbo from delbo by taking expected value of delbo over 87 | z~ q(z | lambda) 88 | 89 | 90 | Parameters 91 | ---------- 92 | list1_z : list[np.array] 93 | list1_conc0 : list[np.array] 94 | list1_conc1 : list[np.array] 95 | 96 | Returns 97 | ------- 98 | tuple[list[np.array], list[np.array], list[np.array]] 99 | 100 | """ 101 | nsam = self.y_nsam_nb.shape[0] 102 | len1 = self.mod.len1 103 | 104 | # grad0,1 log q(z| lambda=conc0, conc1) 105 | xx = [ut.grad_log_beta_prob(list1_z[k], 106 | list1_conc0[k], 107 | list1_conc1[k]) 108 | for k in range(len1)] 109 | # zip doesn't work 110 | # list1_g0, list1_g1 = zip(xx) 111 | 112 | def my_zip(a): 113 | return [[a[j][k] for j in range(len(a))] 114 | for k in range(len(a[0]))] 115 | 116 | # print('---------xx') 117 | # for j in range(2): 118 | # print(j, xx[j]) 119 | # print('---------zip(zz)') 120 | # for j in range(2): 121 | # tempo = list(zip(xx)) 122 | # print(j, tempo[j]) 123 | # print('---------my_zip(zz)') 124 | # for j in range(2): 125 | # tempo = my_zip(xx) 126 | # print(j, tempo[j]) 127 | 128 | list1_g0, list1_g1 = my_zip(xx) 129 | 130 | # sum_sam (log p(y| x, z = angs/dpi)) 131 | x_nsam = ut.bin_vec_to_dec(self.x_nsam_na, nsam=nsam) 132 | y_nsam = ut.bin_vec_to_dec(self.y_nsam_nb, nsam=nsam) 133 | list1_angs = [list1_z[k]*ut.dpi for k in range(len1)] 134 | # log_py is a constant with shape 1 135 | log_py = np.sum(np.log(1e-8 + np.array( 136 | [self.mod.prob_y_given_x_and_angs_prior(y_nsam[sam], 137 | x_nsam[sam], list1_angs) for sam in range(nsam)] 138 | ))) 139 | 140 | # log_px is a constant with shape 1 141 | log_px = np.sum(np.log(1e-8 + np.array( 142 | [self.mod.prob_x(x_nsam[sam], list1_angs) for sam in range(nsam)] 143 | ))) 144 | 145 | # log p(z) 146 | list1_log_pz = [ut.log_beta_prob(list1_z[k], 147 | self.mod.list1_conc0_prior[k], 148 | self.mod.list1_conc1_prior[k]) 149 | for k in range(len1)] 150 | 151 | # log q(z| lambda) 152 | list1_log_qz = [ut.log_beta_prob(list1_z[k], 153 | list1_conc0[k], 154 | list1_conc1[k]) 155 | for k in range(len1)] 156 | 157 | # log p(y, x, z) - log q(z | lambda) 158 | list1_delbo = [log_py + log_px + list1_log_pz[k] - list1_log_qz[k] 159 | for k in range(len1)] 160 | # print("//", len1, "log_py=", log_py, list1_delbo) 161 | 162 | list1_grad0_delbo = [np.multiply(list1_g0[k], list1_delbo[k]) 163 | for k in range(len1)] 164 | 165 | list1_grad1_delbo = [np.multiply(list1_g1[k], list1_delbo[k]) 166 | for k in range(len1)] 167 | 168 | return list1_delbo, list1_grad0_delbo, list1_grad1_delbo 169 | 170 | def do_fit(self): 171 | """ 172 | This function attempts to maximize ELBO over lambda. Does at most nt 173 | iterations (i.e., lambda changes, time steps). But may reach a 174 | convergence condition before doing nt iterations. Final iteration 175 | time is stored in self.fin_t. 176 | 177 | This function stores final values for time and lambda (lambda = 178 | concentrations 0, 1) 179 | 180 | self.fin_t 181 | self.fin_list1_conc0 182 | self.fin_list1_conc1 183 | 184 | It also stores traces (time series) for lambda (lambda = 185 | concentrations 0, 1), delta lambda between consecutive steps, 186 | and the ELBO value: 187 | 188 | self.conc_nt_2_len1 189 | self.delta_conc_nt_2_len1 190 | self.elbo_nt_len1 191 | 192 | Returns 193 | ------- 194 | None 195 | 196 | """ 197 | len1 = self.mod.len1 198 | 199 | # starting values 200 | shapes = self.mod.shapes1 201 | list1_conc0 = ut.new_uniform_array_list(1., shapes) 202 | list1_conc1 = ut.new_uniform_array_list(1., shapes) 203 | step = TimeStep(self.t_step_meth, self.eta, self.mod.len1) 204 | 205 | for t in range(self.nt): 206 | 207 | list1_elbo = ut.new_uniform_array_list(0., shapes) 208 | list1_grad0_elbo = ut.new_uniform_array_list(0., shapes) 209 | list1_grad1_elbo = ut.new_uniform_array_list(0., shapes) 210 | 211 | for s in range(self.nsamgrad): 212 | list1_z = [ss.beta.rvs(list1_conc0[k], list1_conc1[k]) 213 | for k in range(len1)] 214 | x0, x1, x2 =\ 215 | self.get_delbo_and_grad_delbo(list1_z, 216 | list1_conc0, 217 | list1_conc1) 218 | for k in range(len1): 219 | list1_elbo[k] += x0[k]/self.nsamgrad 220 | list1_grad0_elbo[k] += x1[k]/self.nsamgrad 221 | list1_grad1_elbo[k] += x2[k]/self.nsamgrad 222 | 223 | g0 = list1_grad0_elbo 224 | g1 = list1_grad1_elbo 225 | for k in range(len1): 226 | delta_conc = step.get_delta_conc(g0[k], g1[k], t, k) 227 | 228 | old_conc0 = np.copy(list1_conc0[k]) 229 | list1_conc0[k] += delta_conc[0] 230 | list1_conc0[k] = np.clip(list1_conc0[k], 1e-5, 15) 231 | true_delta_conc0 = list1_conc0[k] - old_conc0 232 | 233 | old_conc1 = np.copy(list1_conc1[k]) 234 | list1_conc1[k] += delta_conc[1] 235 | list1_conc1[k] = np.clip(list1_conc1[k], 1e-5, 15) 236 | true_delta_conc1 = list1_conc1[k] - old_conc1 237 | 238 | self.conc_nt_2_len1[t, 0, k] = np.sum(list1_conc0[k]) 239 | self.conc_nt_2_len1[t, 1, k] = np.sum(list1_conc1[k]) 240 | self.delta_conc_nt_2_len1[t, 0, k] = np.sum(true_delta_conc0) 241 | self.delta_conc_nt_2_len1[t, 1, k] = np.sum(true_delta_conc1) 242 | 243 | self.elbo_nt_len1[t, :] = \ 244 | ut.av_each_elem_in_array_list(list1_elbo) 245 | if np.all(self.delta_conc_nt_2_len1[t, :, :] < 0.001): 246 | break 247 | 248 | self.fin_t = t 249 | self.fin_list1_conc0 = list1_conc0 250 | self.fin_list1_conc1 = list1_conc1 251 | 252 | def print_fit_values_at_fin_t(self): 253 | """ 254 | Prints to screen summary of values at final time fin_t of do_fit() 255 | run. 256 | 257 | Recall z = ang/dpi with ang in interval [0, dpi] so z in interval [ 258 | 0, 1].This function calculates for each z, its estimate, the std of 259 | that estimate, and the fractional error (z_estimate - 260 | z_prior)/z_prior. z_prior = angs_prior/dpi. 261 | 262 | angs_prior are the prior angles assumed for the model. If we use 263 | training data generated by Model:get_toy_data(), angs_prior are true 264 | values, the ones used to generate the synthetic data. 265 | 266 | Returns 267 | ------- 268 | None 269 | 270 | """ 271 | len1 = self.mod.len1 272 | list1_conc0 = self.fin_list1_conc0 273 | list1_conc1 = self.fin_list1_conc1 274 | 275 | list1_zpred = [ss.beta.mean(list1_conc0[k], list1_conc1[k]) 276 | for k in range(len1)] 277 | list1_std_zpred = [ss.beta.std(list1_conc0[k], list1_conc1[k]) 278 | for k in range(len1)] 279 | 280 | print('fin_t=', self.fin_t, "\n") 281 | for k in range(len1): 282 | print("list1_z[" + str(k) + "]:") 283 | print("estimate:\n" + str(list1_zpred[k])) 284 | print("st.dev.:\n" + str(list1_std_zpred[k])) 285 | zprior = self.mod.list1_angs_prior[k]/ut.dpi 286 | print("frac. error = (est-prior)/prior:\n" + 287 | str((list1_zpred[k] - zprior)/zprior) + "\n") 288 | 289 | def plot_fit_traces(self): 290 | """ 291 | Calls Plotter to plot traces (time series) collected during do_fit() 292 | run. Plots time series of lambda, delta lambda and ELBO. 293 | 294 | Returns 295 | ------- 296 | None 297 | 298 | """ 299 | Plotter.plot_conc_traces(self.fin_t, 300 | self.conc_nt_2_len1, 301 | self.delta_conc_nt_2_len1) 302 | Plotter.plot_elbo_traces(self.fin_t, 303 | self.elbo_nt_len1) 304 | 305 | 306 | if __name__ == "__main__": 307 | from NbTrolsModel import * 308 | from NoNbTrolsModel import * 309 | 310 | def main(): 311 | 312 | # Ridiculously small numbers, 313 | # just to make sure it runs without crashing 314 | npr.seed(1234) 315 | na = 2 # number of alpha qubits 316 | nb = 2 # number of beta qubits 317 | mod = NbTrolsModel(nb, na) 318 | # mod = NoNbTrolsModel(nb, na) 319 | 320 | nsam = 20 # number of samples 321 | y_nsam_nb, x_nsam_na = mod.gen_toy_data(nsam) 322 | 323 | nsamgrad = 10 # number of samples for grad estimate 324 | nt = 20 # number of interations 325 | 326 | # t_step_type, eta = naive', .0003 # very sensitive to eta 327 | # t_step_type, eta = 'naive_t', .0003 # very sensitive to eta 328 | # t_step_type, eta = 'mag1_grad', .2 329 | t_step_meth, eta = 'ada_grad', .1 330 | 331 | ff = Fitter(mod, y_nsam_nb, x_nsam_na, 332 | nsamgrad, nt, eta, t_step_meth) 333 | ff.do_fit() 334 | ff.print_fit_values_at_fin_t() 335 | ff.plot_fit_traces() 336 | 337 | main() 338 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Quantum Edward contributors 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /Model.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import numpy.random as npr 3 | import scipy.stats as ss 4 | import utilities as ut 5 | 6 | 7 | class Model: 8 | """ 9 | The models (objects of this class) represent quantum circuits that 10 | operate on na + nb qubits. 11 | 12 | We will call a list1 any list of numpy arrays of shapes given by the 13 | shapes1, with length given by len1 = len(shapes1). len1 will be referred 14 | to as the number of layers. 15 | 16 | The first version of Quantum Edward considers two models called NbTrols 17 | and NoNbTrols, but QEdward is more general than those. QEdward was 18 | written so that it can also analyze other akin models. Akin models 19 | should have len1 'layers'. 20 | 21 | len1=na+nb for both models NbTrols and NoNbTrols, but they have 22 | different shapes1. In fact, for the NoNbTrols model 23 | 24 | self.shapes1 = [(1 << k,) for k in range(na)] + [(powna,)]*nb 25 | 26 | where powna = 2^na = 1 << na, whereas for the NbTrols model, 27 | 28 | self.shapes1 = [(1 << k,) for k in range(na+nb)] 29 | 30 | The Wikipedia article cited below refers to the Beta distribution 31 | concentrations conc0 and conc1 as alpha and beta, respectively. 32 | The Bayesian net for the model looks like this 33 | 34 | list1_angs[0:na] 35 | / 36 | x list1_angs[na:] 37 | | / 38 | y 39 | 40 | with the arrows pointing downward. 41 | 42 | y is an int in range(pownb). x is an int in range(powna). list1_angs 43 | is a list1 of numpy arrays containing qc qubit rotation angles. 44 | 45 | y and x are observed variables. nsam measurements of (y, x) are 46 | given by the training data. list1_angs are hidden variables. We want 47 | to estimate those values for list1_angs which best fit the training 48 | data. 49 | 50 | 51 | References 52 | ---------- 53 | https://en.wikipedia.org/wiki/Beta_distribution 54 | 55 | """ 56 | 57 | def __init__(self, nb, na, list1_conc0_prior=None, 58 | list1_conc1_prior=None): 59 | """ 60 | Constructor 61 | 62 | Parameters 63 | ---------- 64 | nb : int 65 | Total number of qubits is na + nb. Input goes into first na qubits 66 | and only last nb measured 67 | na : int 68 | list1_conc0_prior : list[np.array] 69 | a list1 of conc0 for a beta distribution. Used as priors 70 | list1_conc1_prior : list[np.array] 71 | a list1 of conc1 for a beta distribution. Used as priors 72 | 73 | Returns 74 | ------- 75 | None 76 | 77 | """ 78 | self.na = na 79 | self.nb = nb 80 | 81 | self.shapes1 = self.get_shapes1() 82 | self.len1 = len(self.shapes1) 83 | # print("..", self.len1) 84 | 85 | self.list1_conc0_prior = list1_conc0_prior 86 | self.list1_conc1_prior = list1_conc1_prior 87 | if list1_conc0_prior is None or list1_conc1_prior is None: 88 | self.list1_conc0_prior = ut.new_uniformly_random_array_list( 89 | low=0., high=5., shapes=self.shapes1) 90 | self.list1_conc1_prior = ut.new_uniformly_random_array_list( 91 | low=0., high=5., shapes=self.shapes1) 92 | 93 | # with beta function, if two concentrations are equal, 94 | # mean_x beta(x, conc0=1, conc1=1) = .5 95 | # ang = prob*dpi 96 | self.list1_angs_prior = [ss.beta.mean(self.list1_conc0_prior[k], 97 | self.list1_conc1_prior[k])*ut.dpi 98 | for k in range(self.len1)] 99 | # print(';;', self.list1_angs_prior) 100 | 101 | def get_shapes1(self): 102 | """ 103 | Abstract method. Must be overridden by descendant class. In 104 | descendant class, this function should return a list of the shapes 105 | of the elements of a list1. 106 | 107 | Returns 108 | ------- 109 | list[tuple] 110 | 111 | """ 112 | assert False, "this function must be overridden" 113 | 114 | def prob_x(self, x, 115 | list1_angs, 116 | verbose=False): 117 | """ 118 | Abstract method. Must be overridden by descendant class. Returns 119 | probability of input x, P(x). 120 | 121 | Parameters 122 | ---------- 123 | x : int 124 | x is an int in range(powna). 125 | list1_angs : list[np.array] 126 | verbose : bool 127 | 128 | Returns 129 | ------- 130 | float 131 | 132 | """ 133 | assert False, "this function must be overridden" 134 | 135 | def prob_y_given_x_and_angs_prior(self, y, x, 136 | list1_angs, 137 | verbose=False): 138 | """ 139 | Abstract method. Must be overridden by descendant class. In 140 | descendant class, should return the probability of y given x and 141 | list1_angs, P(y | x, list1_angs). 142 | 143 | 144 | Parameters 145 | ---------- 146 | y : int 147 | y is an int in range(pownb). 148 | x : int 149 | x is an int in range(powna). 150 | list1_angs : list[np.array] 151 | verbose : bool 152 | 153 | Returns 154 | ------- 155 | float 156 | 157 | """ 158 | assert False, "this function must be overridden" 159 | 160 | def sum_over_x_of_prob_x(self, list1_angs): 161 | """ 162 | This function should return 1. It can be used to check that the 163 | function prob_x() yields a set of probabilities which when summed 164 | over x gives 1. 165 | 166 | Parameters 167 | ---------- 168 | list1_angs : list[np.array] 169 | 170 | Returns 171 | ------- 172 | float 173 | 174 | """ 175 | powna = 1 << self.na 176 | tot_prob = 0. 177 | for x in range(powna): 178 | tot_prob == self.prob_x(x, list1_angs) 179 | return tot_prob 180 | 181 | def sum_over_y_of_prob_y(self, x, list1_angs): 182 | """ 183 | This function should return 1. It can be used to check that the 184 | function prob_y_given_x_and_angs_prior() yields a set of 185 | probabilities which when summed over y gives 1. 186 | 187 | Parameters 188 | ---------- 189 | x : int 190 | list1_angs : list[np.array] 191 | 192 | Returns 193 | ------- 194 | float 195 | 196 | """ 197 | pownb = 1 << self.nb 198 | tot_prob = 0. 199 | for y in range(pownb): 200 | tot_prob == self.prob_y_given_x_and_angs_prior(y, x, 201 | list1_angs) 202 | return tot_prob 203 | 204 | def gen_toy_data(self, nsam, verbose=False): 205 | """ 206 | This function generates nsam samples of (y, x). It returns these 207 | samples as arrays with 0 and 1 entries y_nsam_nb and x_nsam_nb. 208 | y_nsam_nb.shape = (nsam, nb), x_nsam_na.shape = (nsam, na). 209 | 210 | For example, x_nsam_na[0, :] could look like [1, 0, 1, 1], meaning 211 | that with na=4 and nb=3, the qubit values are 212 | 213 | q0=1 214 | q1=0 215 | q2=1 216 | q3=1 217 | q4=0 218 | q5=0 219 | q6=0 220 | 221 | Parameters 222 | ---------- 223 | nsam : int 224 | verbose : bool 225 | 226 | Returns 227 | ------- 228 | y_nsam_nb, x_nsam_na : tuple(np.array, np.array) 229 | 230 | """ 231 | 232 | powna = 1 << self.na 233 | # x_nsam entries are integers in range(powna) 234 | # x_nsam_na entries are integers 0 or 1 235 | # multinomial with just one draw will give vec with 236 | # all entries 0 except one entry equal to 1 237 | pvals = [self.prob_x(x, self.list1_angs_prior, 238 | verbose) for x in range(powna)] 239 | if verbose: 240 | print('tot_prob=', np.sum(pvals), '\n') 241 | x_nsam = np.array( 242 | [list(npr.multinomial(n=1, pvals=pvals)).index(1) 243 | for sam in range(nsam)]) 244 | x_nsam_na = ut.dec_to_bin_vec(x_nsam, self.na, nsam=nsam) 245 | 246 | # p_nsam_pownb entries will be probabilities 247 | pownb = 1 << self.nb 248 | p_nsam_pownb = np.zeros((nsam, pownb)) 249 | for sam in range(nsam): 250 | p_nsam_pownb[sam, :] = np.array([ 251 | self.prob_y_given_x_and_angs_prior(y, x_nsam[sam], 252 | self.list1_angs_prior, 253 | verbose) 254 | for y in range(pownb)]) 255 | if verbose: 256 | print("sample=", sam, 257 | " total_prob=", np.sum(p_nsam_pownb[sam, :]), 258 | '\n') 259 | 260 | # multinomial with just one draw will give vec with 261 | # all entries 0 except one entry equal to 1 262 | y_nsam = np.array( 263 | [list(npr.multinomial(n=1, pvals=p_nsam_pownb[sam, :])).index(1) 264 | for sam in range(nsam)]) 265 | 266 | y_nsam_nb = ut.dec_to_bin_vec(y_nsam, self.nb, nsam=nsam) 267 | 268 | return y_nsam_nb, x_nsam_na 269 | 270 | 271 | if __name__ == "__main__": 272 | def main(): 273 | print(5) 274 | main() 275 | -------------------------------------------------------------------------------- /NbTrolsModel.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import numpy.random as npr 3 | import scipy.stats as ss 4 | import utilities as ut 5 | import math 6 | from Model import * 7 | 8 | 9 | class NbTrolsModel(Model): 10 | """ 11 | In this class and its twin class NoNbTrolsModel, we consider Quantum 12 | circuits of the following kind. Below we represent them in Qubiter ASCII 13 | picture notation in ZL convention, for nb=3 and na=4 14 | 15 | [--nb---] [----na-----] 16 | 17 | NbTrols (nb Controls) model: 18 | |0> |0> |0> |0> |0> |0> |0> 19 | NOTA P(x) next 20 | |---|---|---|---|---|---Ry 21 | |---|---|---|---|---Ry--% 22 | |---|---|---|---Ry--%---% 23 | |---|---|---Ry--%---%---% 24 | NOTA P(y|x) next 25 | |---|---Ry--%---%---%---% 26 | |---Ry--%---%---%---%---% 27 | Ry--%---%---%---%---%---% 28 | M M M 29 | 30 | NoNbTrols (no nb Controls) model: 31 | |0> |0> |0> |0> |0> |0> |0> 32 | NOTA P(x) next 33 | |---|---|---|---|---|---Ry 34 | |---|---|---|---|---Ry--% 35 | |---|---|---|---Ry--%---% 36 | |---|---|---Ry--%---%---% 37 | NOTA P(y|x) next 38 | |---|---Ry--%---%---%---% 39 | |---Ry--|---%---%---%---% 40 | Ry--|---|---%---%---%---% 41 | M M M 42 | 43 | A gate |---|---Ry--%---%---%---% is called an MP_Y Multiplexor, 44 | or plexor for short. In Ref.1 (Qubiter repo at github), see Rosetta Stone 45 | pdf and Quantum CSD Compiler folder for more info about multiplexors. 46 | 47 | In NbTrols and NoNbtrols models, each layer of a list1 corresponds to a 48 | single plexor. We list plexors (layers) in a list1 in order of 49 | increasing distance between the Ry target qubit and the 0th qubit. 50 | 51 | Note that the expansion of a multiplexor into elementary gates (cnots 52 | and single qubit rotations) contains a huge number of gates (exp in the 53 | number of controls). However, such expansions can be shortened by 54 | approximating the multiplexors, using, for instance, the technique of 55 | Ref.2. 56 | 57 | Ref.3 explains the motivation for choosing this model. This model is in 58 | fact guaranteed to fully parametrize P(x) and P(y|x). 59 | 60 | One can train these circuits in two steps. Consider the NbTrolsModel 61 | circuit as an example. The data consists of many rows with one (y, 62 | x) pair per row. 63 | 64 | 1. fit P(x) as follows: use the first na=4 gates of the circuit, let the 65 | x part of each row of the data be the output at qubits in range(na). 66 | 67 | 2. fit P(y | x) as follows: use gates from na=4 to the last one at na + 68 | nb = 7, use pair (y, x) of each row of the data, let x be the input at 69 | qubits in range(na) and y the output at qubits in range(na, na+nb). ( 70 | ELBO would be different for steps 1, 2) 71 | 72 | An alternative training method is to fit P(y, x) all at once. Use all 73 | na+nb=7 gates, use pair (y, x) of each row of the data, let (y, 74 | x) be the output at qubits in range(na+nb). 75 | 76 | The circuits given above are for finding a fit of both P(x) and P(y|x). 77 | However, if one wants to use a physical hardware device as a classifier, 78 | then one should omit the beginning part of the circuits (the parts that 79 | represent P(x)), and feed the input x into the first na qubits. In other 80 | words, for classifying, use the following circuits instead of the ones 81 | above: 82 | 83 | [--nb---] [----na-----] 84 | 85 | NbTrols (nb Controls) model: 86 | |0> |0> |0> 87 | |---|---Ry--%---%---%---% 88 | |---Ry--%---%---%---%---% 89 | Ry--%---%---%---%---%---% 90 | M M M 91 | 92 | NoNbTrols (no nb Controls) model: 93 | |0> |0> |0> 94 | |---|---Ry--%---%---%---% 95 | |---Ry--|---%---%---%---% 96 | Ry--|---|---%---%---%---% 97 | M M M 98 | 99 | 100 | References 101 | ---------- 102 | 1. https://github.com/artiste-qb-net/qubiter 103 | 104 | 2. Oracular Approximation of Quantum Multiplexors and Diagonal Unitary 105 | Matrices, by Robert R. Tucci, https://arxiv.org/abs/0901.3851 106 | 107 | 3. Code Generator for Quantum Simulated Annealing, by Robert R. Tucci, 108 | https://arxiv.org/abs/0908.1633 , Appendix B 109 | 110 | 4. "Quantum Edward Algebra.pdf", pdf included in this repo 111 | 112 | """ 113 | 114 | def __init__(self, nb, na): 115 | """ 116 | Constructor 117 | 118 | Parameters 119 | ---------- 120 | nb : int 121 | na : int 122 | 123 | Returns 124 | ------- 125 | None 126 | 127 | """ 128 | Model.__init__(self, nb, na) 129 | 130 | def get_shapes1(self): 131 | """ 132 | Returns a list of the shapes of the elements of a list1. 133 | 134 | Returns 135 | ------- 136 | list[tuple] 137 | 138 | """ 139 | na = self.na 140 | nb = self.nb 141 | return [(1 << k,) for k in range(na+nb)] 142 | 143 | @staticmethod 144 | def static_prob_x(x, na, list1_angs, verbose=False): 145 | """ 146 | Returns probability of input x, P(x). 147 | 148 | Parameters 149 | ---------- 150 | x : int 151 | x is an int in range(powna). 152 | na : int 153 | list1_angs : list[np.array] 154 | verbose : bool 155 | 156 | Returns 157 | ------- 158 | float 159 | 160 | """ 161 | prob = 1. 162 | 163 | # x = input in decimal 164 | xbin = ut.dec_to_bin_vec(x, na) 165 | num_trols = 0 166 | # print(".,.", list1_angs) 167 | for angs in list1_angs[0:na]: 168 | xlast_bit = xbin[num_trols] 169 | if num_trols == 0: 170 | xrest = 0 171 | angs1 = float(angs) 172 | else: 173 | xrest = ut.bin_vec_to_dec(xbin[: num_trols]) 174 | angs1 = angs[xrest] 175 | factor = ut.ang_to_cs2_prob(angs1, xlast_bit) 176 | if verbose: 177 | print('num_trols=', num_trols, 178 | "xrest_bin, xlast_bit=", 179 | xbin[: num_trols], 180 | xlast_bit) 181 | prob *= factor 182 | 183 | num_trols += 1 184 | if verbose: 185 | print("\txbin, prob:", xbin, prob) 186 | 187 | return prob 188 | 189 | def prob_x(self, x, 190 | list1_angs, 191 | verbose=False): 192 | """ 193 | Returns probability of input x, P(x). 194 | 195 | Parameters 196 | ---------- 197 | x : int 198 | x is an int in range(powna). 199 | list1_angs : list[np.array] 200 | verbose : bool 201 | 202 | Returns 203 | ------- 204 | float 205 | 206 | """ 207 | return NbTrolsModel.static_prob_x(x, self.na, list1_angs, verbose) 208 | 209 | def prob_y_given_x_and_angs_prior(self, y, x, 210 | list1_angs, 211 | verbose=False): 212 | """ 213 | Returns the probability of y given x and list1_angs, P(y | x, 214 | list1_angs). 215 | 216 | Parameters 217 | ---------- 218 | y : int 219 | int in range(pownb) 220 | x : int 221 | int in range(powna) 222 | list1_angs : list[np.array] 223 | verbose : str 224 | 225 | Returns 226 | ------- 227 | float 228 | 229 | """ 230 | na = self.na 231 | nb = self.nb 232 | prob = 1. 233 | 234 | # y = output in decimal 235 | ybin = ut.dec_to_bin_vec(y, nb) 236 | # x = input in decimal 237 | xbin = ut.dec_to_bin_vec(x, na) 238 | num_trols = na 239 | for angs in list1_angs[na:]: 240 | ylast_bit = ybin[num_trols-na] 241 | if num_trols == na: 242 | x_yrest_bin = x 243 | x_yrest = x 244 | else: 245 | x_yrest_bin = np.concatenate([xbin, 246 | ybin[: num_trols-na]]) 247 | x_yrest = ut.bin_vec_to_dec(x_yrest_bin) 248 | factor = ut.ang_to_cs2_prob(angs[x_yrest], ylast_bit) 249 | if verbose: 250 | print('num_trols=', num_trols, 251 | "x_yrest_bin, ylast_bit=", 252 | x_yrest_bin, 253 | ylast_bit) 254 | prob *= factor 255 | 256 | num_trols += 1 257 | 258 | if verbose: 259 | print('\txbin, ybin, prob:', xbin, ybin, prob) 260 | 261 | return prob 262 | 263 | 264 | if __name__ == "__main__": 265 | def main(): 266 | nsam = 10 267 | na = 2 268 | nb = 3 269 | mod = NbTrolsModel(nb, na) 270 | y_nsam_nb, x_nsam_na = mod.gen_toy_data(nsam, verbose=True) 271 | print("x_nsam_na:") 272 | print(x_nsam_na) 273 | print("\ny_nsam_nb:") 274 | print(y_nsam_nb) 275 | main() 276 | -------------------------------------------------------------------------------- /NoNbTrolsModel.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import numpy.random as npr 3 | import scipy.stats as ss 4 | import utilities as ut 5 | import math 6 | from Model import * 7 | from NbTrolsModel import * 8 | 9 | 10 | class NoNbTrolsModel(Model): 11 | """ 12 | See docstring describing class NbTrolsModel 13 | 14 | """ 15 | 16 | def __init__(self, nb, na): 17 | """ 18 | Constructor 19 | 20 | Parameters 21 | ---------- 22 | nb : int 23 | na : int 24 | 25 | Returns 26 | ------- 27 | None 28 | 29 | """ 30 | Model.__init__(self, nb, na) 31 | 32 | def get_shapes1(self): 33 | """ 34 | Returns a list of the shapes of the elements of a list1. 35 | 36 | Returns 37 | ------- 38 | list[tuple] 39 | 40 | """ 41 | na = self.na 42 | nb = self.nb 43 | powna = 1 << na 44 | return [(1 << k,) for k in range(na)] + [(powna,)]*nb 45 | 46 | def prob_x(self, x, 47 | list1_angs, 48 | verbose=False): 49 | """ 50 | Returns probability of input x, P(x). 51 | 52 | Parameters 53 | ---------- 54 | x : int 55 | x is an int in range(powna). 56 | list1_angs : list[np.array] 57 | verbose : bool 58 | 59 | Returns 60 | ------- 61 | float 62 | 63 | """ 64 | return NbTrolsModel.static_prob_x(x, self.na, list1_angs, verbose) 65 | 66 | def prob_y_given_x_and_angs_prior(self, y, x, 67 | list1_angs, 68 | verbose=False): 69 | """ 70 | Returns the probability of y given x and list1_angs, P(y | x, 71 | list1_angs). 72 | 73 | Parameters 74 | ---------- 75 | y : int 76 | int in range(pownb) 77 | x : int 78 | int in range(powna) 79 | list1_angs : list[np.array] 80 | verbose : bool 81 | 82 | Returns 83 | ------- 84 | float 85 | 86 | """ 87 | na = self.na 88 | prob = 1. 89 | 90 | # y = output in decimal 91 | ybin = ut.dec_to_bin_vec(y, self.nb) 92 | num_trols = na 93 | for angs in list1_angs[na:]: 94 | ylast_bit = ybin[num_trols-na] 95 | factor = ut.ang_to_cs2_prob(angs[x], ylast_bit) 96 | if verbose: 97 | print('num_trols=', num_trols, 98 | "ybin[:num_trols-na], y_last_bit", 99 | ybin[:num_trols-na], 100 | ylast_bit) 101 | prob *= factor 102 | 103 | num_trols += 1 104 | 105 | if verbose: 106 | print('\tybin, prob:', ybin, prob) 107 | 108 | return prob 109 | 110 | 111 | if __name__ == "__main__": 112 | def main(): 113 | nsam = 10 114 | na = 2 115 | nb = 3 116 | mod = NoNbTrolsModel(nb, na) 117 | y_nsam_nb, x_nsam_na = mod.gen_toy_data(nsam, verbose=True) 118 | print("x_nsam_na:") 119 | print(x_nsam_na) 120 | print("\ny_nsam_nb:") 121 | print(y_nsam_nb) 122 | main() 123 | -------------------------------------------------------------------------------- /Plotter.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | 4 | 5 | class Plotter: 6 | """ 7 | matplotlib plotting routines. 8 | 9 | Many inputs to the functions of this class are arrays of shape (nt, 2, 10 | len1). In that shape, 11 | 12 | * nt refers to time (aka iteration). fin_t <= nt-1 and only the first 13 | fin_t entries of the array contain valid data. 14 | 15 | * 2 refers to whether for conc0 or conc1. 16 | 17 | * len1 refers to the number of layers. For instance, for NbTrols and 18 | NoNbTrols models, len1=na+nb. 19 | 20 | For plotting purposes, to reduce number of angles being displayed, 21 | each layer of list1_conc0 and list1_conc1 is averaged so that it becomes 22 | a scalar. 23 | 24 | """ 25 | 26 | @staticmethod 27 | def plot_conc_traces(fin_t, conc_nt_2_len1, delta_conc_nt_2_len1): 28 | """ 29 | The input parameters of this function are created and filled when 30 | one runs Fitter:do_fit(). 31 | 32 | This function plots time series (aka traces), for t = int in range( 33 | fin_t), of concentrations conc0 and conc1 and their deltas 34 | delta_conc0, delta_conc1, for each layer. 35 | 36 | Parameters 37 | ---------- 38 | fin_t : int 39 | final time. Must be <= nt-1 40 | conc_nt_2_len1 : np.array 41 | shape=(nt, 2, len1). This array is stored within Fitter and 42 | filled by a call to Fitter:do_fit(). It contains the time-series 43 | of conc0, conc1 for each layer. 44 | delta_conc_nt_2_len1 : np.array 45 | shape=(nt, 2, len1). This array is stored within Fitter and 46 | filled by a call to Fitter:do_fit(). It contains the time-series 47 | of the CHANGES in conc0 and conc1 for each layer. (here CHANGES 48 | refers to changes between consecutive time steps). 49 | 50 | Returns 51 | ------- 52 | None 53 | 54 | """ 55 | y_shape = conc_nt_2_len1.shape 56 | assert fin_t <= y_shape[0]-1 57 | len1 = y_shape[2] 58 | 59 | fig, ax = plt.subplots(nrows=2, ncols=2, sharex=True) 60 | for k in range(len1): 61 | for y in range(2): 62 | ax[0, y].plot(range(fin_t), conc_nt_2_len1[0:fin_t, y, k], 63 | label='layer ' + str(k)) 64 | ax[0, y].legend(loc='best', fontsize='xx-small') 65 | ax[0, y].set_ylabel("conc" + str(y)) 66 | 67 | ax[1, y].plot(range(fin_t), 68 | delta_conc_nt_2_len1[0:fin_t, y, k], 69 | label='layer ' + str(k)) 70 | ax[1, y].legend(loc='best', fontsize='xx-small') 71 | ax[1, y].set_ylabel("delta_conc" + str(y)) 72 | 73 | ax[0, 0].get_shared_y_axes().join(ax[0, 0], ax[0, 1]) 74 | ax[1, 0].get_shared_y_axes().join(ax[1, 0], ax[1, 1]) 75 | plt.tight_layout() 76 | plt.show() 77 | 78 | @staticmethod 79 | def plot_elbo_traces(fin_t, elbo_nt_len1): 80 | """ 81 | The input parameters of this function are created and filled when 82 | one runs Fitter:do_fit(). 83 | 84 | This function plots time series (aka traces), for t = int in range( 85 | fin_t), of ELBO for each layer. Since we are attempting to maximize 86 | ELBO, ideally this curve should be increasing or flat. 87 | 88 | Parameters 89 | ---------- 90 | fin_t : int 91 | final time. Must be <= nt-1 92 | elbo_nt_len1 : np.array 93 | shape=(nt, len1) This array is stored within Fitter and 94 | filled by a call to Fitter:do_fit(). It contains the time-series 95 | of ELBO for each layer. 96 | 97 | Returns 98 | ------- 99 | None 100 | 101 | """ 102 | y_shape = elbo_nt_len1.shape 103 | assert fin_t <= y_shape[0] 104 | len1 = y_shape[1] 105 | 106 | for k in range(len1): 107 | plt.plot(range(fin_t), elbo_nt_len1[0:fin_t, k], 108 | label='layer ' + str(k)) 109 | plt.xlabel("t") 110 | plt.ylabel("ELBO") 111 | plt.legend(loc=0, fontsize='xx-small') 112 | plt.show() 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quantum Edward 2 | 3 | ## Installation 4 | 5 | You can install Quantum Edward from the Python package manager `pip` using: 6 | ``` 7 | pip install quantum_edward --user 8 | ``` 9 | If you are useing JupyterNotebook, use: 10 | ``` 11 | !pip install quantum_edward --user 12 | ``` 13 | and restart the kernel. 14 | 15 | Quantum Edward at this point is just a small library of Python tools for 16 | doing classical supervised learning on Quantum Neural Networks (QNNs). 17 | 18 | An analytical model of the QNN is entered as input into QEdward and the training 19 | is done on a classical computer, using training data already available (e.g., 20 | MNIST), and using the famous BBVI (Black Box Variational Inference) method 21 | described in Reference 1 below. 22 | 23 | The input analytical model of the QNN is given as a sequence of gate 24 | operations for a gate model quantum computer. The hidden variables are 25 | angles by which the qubits are rotated. The observed variables are the input 26 | and output of the quantum circuit. Since it is already expressed in the qc's 27 | native language, once the QNN has been trained using QEdward, it can be 28 | run immediately on a physical gate model qc such as the ones that IBM and 29 | Google have already built. By running the QNN on a qc and doing 30 | classification with it, we can compare the performance in classification 31 | tasks of QNNs and classical artificial neural nets (ANNs). 32 | 33 | Other workers have proposed training a QNN on an actual physical qc. But 34 | current qc's are still fairly quantum noisy. Training an analytical QNN on a 35 | classical computer might yield better results than training it on a qc 36 | because in the first strategy, the qc's quantum noise does not degrade the 37 | training. 38 | 39 | The BBVI method is a mainstay of the "Edward" software library. Edward uses 40 | Google's TensorFlow lib to implement various inference methods (Monte Carlo 41 | and Variational ones) for Classical Bayesian Networks and for Hierarchical 42 | Models. H.M.s (pioneered by Andrew Gelman) are a subset of C.B. nets 43 | (pioneered by Judea Pearl). Edward is now officially a part of TensorFlow, 44 | and the original author of Edward, Dustin Tran, now works for Google. Before 45 | Edward came along, TensorFlow could only do networks with deterministic 46 | nodes. With the addition of Edward, TensorFlow now can do nets with both 47 | deterministic and non-deterministic (probabilistic) nodes. 48 | 49 | This first baby-step lib does not do distributed computing. The hope is that 50 | it can be used as a kindergarten to learn about these techniques, and that 51 | then the lessons learned can be used to write a library that does the same 52 | thing, classical supervised learning on QNNs, but in a distributed fashion 53 | using Edward/TensorFlow on the cloud. 54 | 55 | The first version of Quantum Edward analyzes two QNN models called NbTrols 56 | and NoNbTrols. These two models were chosen because they are interesting to 57 | the author, but the author attempted to make the library general enough so 58 | that it can accommodate other akin models in the future. The allowable 59 | models are referred to as QNNs because they consist of 'layers', 60 | as do classical ANNs (Artificial Neural Nets). TensorFlow can analyze 61 | layered models (e.g., ANN) or more general DAG (directed acyclic graph) 62 | models (e.g., Bayesian networks). 63 | 64 | This software is distributed under the MIT License. 65 | 66 | References 67 | ---------- 68 | 69 | 1. R. Ranganath, S. Gerrish, D. M. Blei, "Black Box Variational 70 | Inference", https://arxiv.org/abs/1401.0118 71 | 72 | 2. https://en.wikipedia.org/wiki/Stochastic_approximation 73 | discusses Robbins-Monro conditions 74 | 75 | 3. https://github.com/keyonvafa/logistic-reg-bbvi-blog/blob/master/log_reg_bbvi.py 76 | 77 | 4. http://edwardlib.org/ 78 | 79 | 5. https://discourse.edwardlib.org/ 80 | 81 | 82 | -------------------------------------------------------------------------------- /TimeStep.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class TimeStep: 5 | """ 6 | An iteration or time step 't' is each time the parameters lambda = 7 | list1_conc0, list1_conc1 and the function of lambda being MAXIMIZED ( 8 | ELBO) are changed. This class calculates the change in lambda, 9 | delta lambda, for a single iteration at the current time t=cur_t. There 10 | are various possible methods for calculating delta lambda. A nice 11 | description of the various methods can be found in the Wikipedia article 12 | (cited below) for "Stochastic Gradient Descent" (in our case, it's an 13 | ascent, not a descent) 14 | 15 | In conventional Artificial Neural Net algorithms, one is minimizing 16 | Cost, so change in Cost must be negative. Here, we are trying to 17 | maximize ELBO so the change in ELBO must be positive. In both cases, 18 | eta > 0 (eta is a scalar factor multiplying delta lambda). But replace 19 | eta by - eta to go from time step of Cost to that of ELBO or vice versa. 20 | 21 | References 22 | ---------- 23 | https://en.wikipedia.org/wiki/Stochastic_gradient_descent 24 | 25 | """ 26 | 27 | def __init__(self, method, eta, len1): 28 | """ 29 | Constructor 30 | 31 | Parameters 32 | ---------- 33 | method : str 34 | A string that identifies the method of calculating delta lambda. 35 | For example, method = 'adam' 36 | eta : float 37 | positive scalar, delta lambda is proportional to it. 38 | len1 : int 39 | length of a list1 40 | 41 | Returns 42 | ------- 43 | None 44 | 45 | """ 46 | self.method = method 47 | self.eta = eta 48 | 49 | # These are used by successive calls to get_delta_conc() 50 | self.list1_cum_grad = [None]*len1 51 | self.list1_cum_sq_grad = [None]*len1 52 | 53 | def get_delta_conc(self, grad0, grad1, cur_t, k): 54 | """ 55 | Change in lambda = concentrations conc0 and conc1. grad0 and grad1 56 | are the gradients of ELBO at time t=cur_t with respect to conc0 and 57 | conc1, respectively. ELBO is being maximized. 'k' is the layer being 58 | considered. 59 | 60 | Parameters 61 | ---------- 62 | grad0 : np.array 63 | grad1 : np.array 64 | cur_t : int 65 | k : int 66 | 67 | Returns 68 | ------- 69 | np.array 70 | 71 | """ 72 | method = self.method 73 | grad = np.stack([grad0, grad1]) 74 | 75 | if method == 'naive': 76 | return self.eta*grad 77 | elif method == 'naive_t': 78 | return (self.eta/(cur_t+1))*grad 79 | elif method == 'mag1_grad': 80 | # mag_grad = magnitude of gradient 81 | mag_grad = np.sqrt(np.square(grad0) + 82 | np.square(grad0)) 83 | return np.divide(self.eta*grad, mag_grad) 84 | elif method == 'ada_grad': 85 | assert cur_t is not None 86 | if cur_t == 0: 87 | self.list1_cum_sq_grad[k] = np.square(grad) 88 | # print("**************", self.list1_cum_sq_grad[k]) 89 | else: 90 | # print('..', cur_t, self.list1_cum_sq_grad[k]) 91 | self.list1_cum_sq_grad[k] += np.square(grad) 92 | return np.divide(self.eta * grad, 93 | np.sqrt(self.list1_cum_sq_grad[k]) + 1e-6) 94 | elif method == 'adam': 95 | assert cur_t is not None 96 | if cur_t == 0: 97 | self.list1_cum_grad[k] = grad 98 | self.list1_cum_sq_grad[k] = np.square(grad) 99 | else: 100 | self.list1_cum_grad[k] += grad 101 | self.list1_cum_sq_grad[k] += np.square(grad) 102 | return np.divide(np.multiply(self.eta * grad, 103 | self.list1_cum_grad[k]), 104 | np.sqrt(self.list1_cum_sq_grad[k]) + 1e-6) 105 | else: 106 | assert False, "unsupported time step method" 107 | -------------------------------------------------------------------------------- /quantum_edward/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artiste-qb-net/Quantum_Edward/89d3a7d40177065eaa34fabd4b4c255b8ef51881/quantum_edward/__init__.py -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from os import path 3 | from io import open 4 | 5 | with open("README.md", "r") as fh: 6 | long_description = fh.read() 7 | 8 | setup( 9 | name="quantum_edward", 10 | version="0.0.0", 11 | author="Robert Tucci", 12 | keywords = ('Quantum Neural Networks'), 13 | author_email="Robert.Tucci@artiste-qb.net", 14 | description="Python tools for supervised learning by Quantum Neural Networks.", 15 | long_description=long_description, 16 | long_description_content_type="text/markdown", 17 | url="https://github.com/artiste-qb-net/Quantum_Edward", 18 | packages=find_packages(exclude=['contrib', 'docs', 'tests']), 19 | install_requires=[ 20 | 'numpy', 21 | 'matplotlib', 22 | 'scipy' 23 | ], 24 | classifiers=[ 25 | "Programming Language :: Python :: 3", 26 | "License :: OSI Approved :: MIT License", 27 | "Operating System :: OS Independent", 28 | ], 29 | ) -------------------------------------------------------------------------------- /utilities.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import numpy.random as npr 3 | import scipy.special as sp 4 | import scipy.stats as ss 5 | 6 | dpi = 2*np.pi 7 | 8 | 9 | def bin_vec_to_dec(bin_vec, nsam=1): 10 | """ 11 | This function takes a 1 dim array of zeros and ones and returns the 12 | decimal representation of that array. If nsam > 1, the function operates 13 | on each row, assuming bin_vec is 2 dim matrix with bin_vec.shape[0] = nsam 14 | 15 | Parameters 16 | ---------- 17 | bin_vec : np.array 18 | entries of array are either 0 or 1 19 | nsam : int 20 | number of samples 21 | 22 | Returns 23 | ------- 24 | int | np.array 25 | 26 | """ 27 | 28 | def fun(bv): 29 | size = len(bv) 30 | x = np.sum(np.array( 31 | [bv[k]*(1 << (size-1-k)) for k in range(size)] 32 | )) 33 | return x 34 | 35 | if nsam == 1: 36 | return fun(bin_vec) 37 | elif nsam > 1: 38 | return np.array([fun(bin_vec[sam, :]) for sam in range(nsam)]) 39 | else: 40 | assert False, "illegal value for number of samples" 41 | 42 | 43 | def dec_to_bin_vec(dec, size, nsam=1): 44 | """ 45 | This function takes in an int and it returns a 1 dim array with entries 46 | 0 or 1 and length equal to 'size'. It checks to make sure 'size' is 47 | large enough to fit the input. If nsam > 1, the function operates on 48 | each entry, assuming dec is 1 dim matrix with dec.shape[0] = nsam 49 | 50 | Parameters 51 | ---------- 52 | dec : int | np.array 53 | size : int 54 | nsam : int 55 | number of samples 56 | 57 | Returns 58 | ------- 59 | np.array 60 | entries of array are either 0 or 1 61 | 62 | """ 63 | def fun(scalar): 64 | assert scalar < (1 << size), "size " + str(size) + " is too small"\ 65 | " to fit bin rep of " + str(scalar) 66 | return np.array([(scalar >> k) & 1 for k in range(size)]) 67 | if nsam == 1: 68 | return fun(dec) 69 | elif nsam > 1: 70 | return np.stack([fun(dec[sam]) for sam in range(nsam)]) 71 | else: 72 | assert False, "illegal value for number of samples" 73 | 74 | 75 | def ang_to_cs2_prob(ang, use_sin=1): 76 | """ 77 | This function takes a float 'ang' and returns its cosine or sine 78 | squared, depending on flag 'use_sin'. The function also works if ang is 79 | a numpy array, in which case it acts elementwise and returns an array of 80 | the same shape as 'ang'. 81 | 82 | Parameters 83 | ---------- 84 | ang : float | np.array 85 | use_sin : int 86 | Either 0 or 1 87 | 88 | Returns 89 | ------- 90 | float | np.array 91 | 92 | """ 93 | 94 | if use_sin == 1: 95 | fun = np.sin 96 | elif use_sin == 0: 97 | fun = np.cos 98 | else: 99 | assert False 100 | return np.square(fun(ang)) 101 | 102 | 103 | def log_beta_prob(x, conc0, conc1): 104 | """ 105 | This function takes in a probability 'x' and returns the log probability 106 | of 'x', according to the Beta distribution with concentrations 'conc0' 107 | and 'conc1'. The Wikipedia article cited below refers to conc0 as alpha 108 | and to conc1 as beta. 109 | 110 | x, conc0, conc1, g0 and g1 are all floats, or they are all numpy arrays 111 | of the same shape. This method works elementwise for arrays. 112 | 113 | References 114 | ---------- 115 | https://en.wikipedia.org/wiki/Beta_distribution 116 | 117 | Parameters 118 | ---------- 119 | x : float | np.array 120 | conc0 : float | np.array 121 | Concentration 0, must be >= 0 122 | conc1 : float | np.array 123 | Concentration 1, must be >= 0 124 | 125 | Returns 126 | ------- 127 | float | np.array 128 | 129 | """ 130 | x = np.clip(x, .00001, .99999) 131 | return np.log(ss.beta.pdf(x, conc0, conc1)) 132 | 133 | 134 | def grad_log_beta_prob(x, conc0, conc1): 135 | """ 136 | This function takes in a probability 'x' and returns the GRADIENT of the 137 | log probability of 'x', according to the Beta distribution with 138 | concentrations 'conc0' and 'conc1'. The Wikipedia article cited below 139 | refers to conc0 as alpha and to conc1 as beta. 140 | 141 | x, conc0, conc1, g0, g1 are all floats, or they are all numpy arrays of 142 | the same shape. This method works elementwise for arrays. 143 | 144 | References 145 | ---------- 146 | https://en.wikipedia.org/wiki/Beta_distribution 147 | 148 | 149 | Parameters 150 | ---------- 151 | x : float | np.array 152 | x in interval [0, 1] 153 | conc0 : float | np.array 154 | Concentration 0, must be >= 0 155 | conc1 : float | np.array 156 | Concentration 1, must be >= 0 157 | 158 | Returns 159 | ------- 160 | [g0, g1]: list[float | np.array, float | np.array] 161 | g0 and g1 are tha gradients with respect to the concentrations conc0 162 | and conc1. 163 | 164 | """ 165 | def fun(z): 166 | a = np.clip(np.real(z), 1e-5, 15) 167 | b = np.clip(np.imag(z), 1e-5, 15) 168 | return sp.digamma(a+b) - sp.digamma(a) 169 | vfun = np.vectorize(fun) 170 | x = np.clip(x, .00001, .99999) 171 | g0 = np.log(x) + vfun(conc0 + 1j*conc1) 172 | g1 = np.log(1-x) + vfun(conc1 + 1j*conc0) 173 | return [g0, g1] 174 | 175 | 176 | def log_bern_prob(x, prob1): 177 | """ 178 | This function takes in x= 0 or 1 and returns the log probability of 'x', 179 | according to the Bernoulli distribution. So it returns P(x=0)= 1-prob1, 180 | P(x=1)= prob1 181 | 182 | x, prob1 and output are all scalars, or they are all numpy arrays of the 183 | same shape. This method works elementwise for arrays. 184 | 185 | References 186 | ---------- 187 | 188 | https://en.wikipedia.org/wiki/Bernoulli_distribution 189 | 190 | Parameters 191 | ---------- 192 | x : int | np.array 193 | either 0 or 1 194 | prob1 : float | np.array 195 | probability in closed interval [0, 1] 196 | 197 | Returns 198 | ------- 199 | float | np.array 200 | 201 | """ 202 | prob1 = np.clip(prob1, .00001, .99999) 203 | return np.log(ss.bernoulli.pmf(x, prob1)) 204 | 205 | 206 | def av_each_elem_in_array_list(list1): 207 | """ 208 | 'list1' is a list of numpy arrays of possibly different shapes. This 209 | function takes 'list1' and returns a new list which replaces each entry 210 | of 'list1' by its average (a scalar). 211 | 212 | Parameters 213 | ---------- 214 | list1 : list[np.array] 215 | 216 | Returns 217 | ------- 218 | list[float] 219 | 220 | """ 221 | return np.array([np.mean(x) for x in list1]) 222 | 223 | 224 | def get_shapes_of_array_list(list1): 225 | """ 226 | 'list1' is a list of numpy arrays of possibly different shapes. This 227 | function takes 'list1' and returns a new list which replaces each entry 228 | of 'list1' by its shape. 229 | 230 | 231 | Parameters 232 | ---------- 233 | list1 : list[np.array] 234 | 235 | Returns 236 | ------- 237 | list[tuples] 238 | 239 | """ 240 | return [arr.shape for arr in list1] 241 | 242 | 243 | def new_uniform_array_list(val, shapes): 244 | """ 245 | This function returns a list whose kth entry is an array with shape= 246 | shapes[k] and with all entries equal to 'val'. 247 | 248 | Parameters 249 | ---------- 250 | val : float 251 | shapes : list[tuples] 252 | 253 | Returns 254 | ------- 255 | list[np.array] 256 | 257 | """ 258 | return [np.ones(shapes[k])*val for k in range(len(shapes))] 259 | 260 | 261 | def new_uniformly_random_array_list(low, high, shapes): 262 | """ 263 | This function returns a list whose kth entry is an array with shape= 264 | shapes[k] and with entries selected uniformly at random from 265 | the interval [low, high]. 266 | 267 | Parameters 268 | ---------- 269 | low : float 270 | high : float 271 | shapes : list[tuples] 272 | 273 | Returns 274 | ------- 275 | list[np.array] 276 | 277 | """ 278 | return [npr.uniform(low, high, size=shapes[k]) 279 | for k in range(len(shapes))] 280 | 281 | if __name__ == "__main__": 282 | def main(): 283 | vec = dec_to_bin_vec(10, 5) 284 | dec = bin_vec_to_dec(vec) 285 | print("10, vec, dec\n", 10, vec, dec) 286 | dec_to_bin_vec(10, 4) 287 | # uncomment this to see that assert on size works 288 | # dec_to_bin_vec(10, 3) 289 | main() 290 | --------------------------------------------------------------------------------