Esempio n. 1
0
	def _MCMC(self, max_iter, method='BFS', is_effort=False, is_exit=False, hazard_model='cell', hazard_state='X'):
		# initialize for iteration
		if not is_effort and self.effort_prob_matrix[:,:,0].sum() != 0: 
			raise Exception('Effort rates are not set to 1 while disabled the update in effort parameter.')
		
		if not is_exit and self.hazard_matrix.sum() != 0: 
			raise Exception('Hazard rates are not set to 0 while disabled the update in hazard parameter.')			
		
		lMx = int((self.Mx-1)*self.Mx*self.J/2)
		param_chain = {'l': np.zeros((max_iter, lMx*self.num_mixture)), # for each item, the possible transition is (self.X-1 + 1)* (self.X-1)/2
					   'pi':np.zeros((max_iter, (self.Mx-1)*self.num_mixture)),
					   'mixture':np.zeros((max_iter,self.num_mixture-1)),
					   'c': np.zeros((max_iter, (self.Mx*(self.My-1))*self.unique_item_num))
					   } 
			
		if is_exit:
			if hazard_state == 'X':
				Mh = self.Mx
			elif hazard_state =='Y':
				Mh = self.My	
			
			if hazard_model == 'prop':
				self.Lambdas = [self.Lambda for s in range(Mh)]
				self.betas = [self.beta for s in range(Mh)]
				param_chain['h'] = np.zeros((max_iter, Mh*2))
			elif hazard_model == 'cell':
				param_chain['h'] = np.zeros((max_iter, Mh*self.T))
			
			
		if is_effort:
			param_chain['e'] = np.zeros((max_iter, self.Mx*self.J))
		
		# cache the generated states
		X_mat_dict = {}
		for t in range(1,self.T+1):
			X_mat_dict[t] = generate_states(t, self.Mx, self.Mx-1)
		
		tot_error_cnt = 0
		for iter in tqdm(range(max_iter)):
		
			if tot_error_cnt > 10:
				raise Exception('Too many erros in drawing')
		
			#############################
			# Step 1: Data Augmentation #
			#############################
			if method == "BFS":
				# UNIT Test OK
				for key in self.obs_type_info.keys():
					# get the obseration state
					O = self.obs_type_info[key]['O']
					H = self.obs_type_info[key]['H']
					J = self.obs_type_info[key]['J']
					E = self.obs_type_info[key]['E']
					# translate the J to item id
					item_ids = [self.item_param_dict[j] for j in J]
					Ts = len(O)		
					X_mat = X_mat_dict[Ts]
					
					llk_vec={}
					pis={}
					l_mat={}
					z_llk= np.zeros((1,self.num_mixture))
					for z in range(self.num_mixture):
						llk_vec[z], pis[z], l_mat[z] = update_state_parmeters(X_mat, self.Mx,
								O,E,H,
								J, item_ids,
							   self.hazard_matrix, self.observ_prob_matrix, 
							   self.state_init_dist[z], self.state_transit_matrix[:,z,:,:,:], self.effort_prob_matrix,
							   is_effort, 
							   is_exit, 
							   hazard_state)
						z_llk[0,z] = sum(llk_vec[z])
					
					self.obs_type_info[key]['user_mixture'] = (z_llk*self.user_mixture_density/np.dot(z_llk, self.user_mixture_density)).tolist()[0]
					self.obs_type_info[key]['llk_vec'] = llk_vec
					self.obs_type_info[key]['pi'] = pis
					self.obs_type_info[key]['l_mat'] = l_mat
			else:
				raise Exception('Algorithm %s not implemented.' % method)
			
			# sample states backwards 
			X = np.empty((self.T, self.K),dtype=np.int)
			Z = np.empty((self.K, 1),dtype=np.int)
			for i in range(self.K):
				# check the key
				obs_key = self.obs_type_ref[i]
				user_mixture = self.obs_type_info[obs_key]['user_mixture']
				# sample user type
				z = random_choice(user_mixture)
				Z[i] = z
				# sample the state
				pi = self.obs_type_info[obs_key]['pi'][z]
				l_mat = self.obs_type_info[obs_key]['l_mat'][z]
				Ti = self.T_vec[i]
				
				X[Ti-1, i] = random_choice(pi)
				for t in range(Ti-1, 0,-1):
					pt = l_mat[t, X[t,i],:]
					if pt.sum()==0:
						raise Exception('Invalid transition kernel')
					X[t-1,i] = random_choice(pt.tolist())
			
			#############################
			# Step 2: Update Parameter  #
			#############################
			try:
				user_mixture_param = [self.prior_param['mixture'][z]+ np.sum(Z==z) for z in range(self.num_mixture)]
				new_user_mixture = np.random.dirichlet(user_mixture_param)
				
				# upate pi | Type 0 and 1 are low mastery, Type 2 are high mastery
				pi_params  = [[self.prior_param['pi'][z][x]+ np.sum(X[0,np.where(Z==z)[0]]==x) for x in range(self.Mx)] for z in range(self.num_mixture)]
				if self.num_mixture > 1:
					new_state_init_dist = draw_multilevel_pi(pi_params, self.num_mixture, self.Mx)
				else:
					new_state_init_dist = np.zeros((1,self.Mx))
					new_state_init_dist[0] = np.random.dirichlet(pi_params[0])
				
				# update l
				trans_matrix = np.zeros((self.J, self.num_mixture, self.Mx, self.Mx), dtype=np.int)
				for k in range(self.K):
					z = Z[k]
					for t in range(0, self.T_vec[k]):
						o_j = self.J_array[t,k]
						o_is_e = self.E_array[t,k]
						x1 = X[t,k]
						if t>0 and self.E_array[t-1,k]>0:
							l_j = self.J_array[t-1, k] # transition happens at t, item at t-1 takes credit
							x0 = X[t-1,k]
							trans_matrix[l_j,z,x0,x1] += 1
				
				new_state_transit_matrix = np.zeros((self.J,self.num_mixture,2,self.Mx,self.Mx))
				for j in range(self.J):
					# Need to ensure transition here
					state_transit_matrix = np.zeros((self.num_mixture, 2, self.Mx, self.Mx))
					for z in range(self.num_mixture):
						params =  [[self.prior_param['l'][z][m][n]+trans_matrix[j,z,m,n] for n in range(self.Mx)] for m in range(self.Mx)]				
						state_transit_matrix[z] = draw_l(params, self.Mx)					
					new_state_transit_matrix[j] = state_transit_matrix
					
				# update c	
				obs_cnt = np.zeros((self.unique_item_num, self.Mx, self.My)) # state,observ
				for k in range(self.K):
					for t in range(0, self.T_vec[k]):
						o_j = self.J_array[t,k]
						o_is_e = self.E_array[t,k]					
						if o_is_e:
							obs_cnt[self.item_param_dict[o_j], X[t,k], self.O_array[t,k]] += 1 
				
				new_observ_prob_matrix = np.zeros((self.J,self.Mx,self.My))
				for j in range(self.unique_item_num):
					c_params = [[self.prior_param['c'][x][y] + obs_cnt[j,x,y] for y in range(self.My)] for x in range(self.Mx)] 
					c_draws = draw_c(c_params, self.Mx, self.My)
					new_observ_prob_matrix[j] = c_draws			
				
				# update h
				if is_exit:
					if hazard_model == 'prop':
						if hazard_state == 'X':
							self.hazard_matrix, self.Lambdas, self.betas = prop_hazard(self.Mx, self.T_vec, X, self.H_array, self.Lambdas, self.betas)
						elif hazard_state == 'Y':
							self.hazard_matrix, self.Lambdas, self.betas = prop_hazard(self.My, self.T_vec, self.O_array, self.H_array, self.Lambdas, self.betas)
						else:
							raise Exception('Unknown dependent states! %s ' % hazard_state)
					elif hazard_model == 'cell':
						if hazard_state == 'X':
							self.hazard_matrix = cell_hazard(self.Mx, self.T_vec, X, self.H_array, self.prior_param['h'])
						elif hazard_state == 'Y':
							self.hazard_matrix = cell_hazard(self.My, self.T_vec, self.O_array, self.H_array, self.prior_param['h'])
						else:
							raise Exception('Unknown dependent states! %s ' % hazard_state)					
					else:
						raise Exception('Unknown hazard model! %s ' % hazard_model)
						
				
				# update e
				if is_effort:
					effort_cnt = np.zeros((self.J,self.Mx),dtype=np.int)
					effort_state_cnt = np.zeros((self.J,self.Mx),dtype=np.int)
					for k in range(self.K):
						for t in range(0, self.T_vec[k]):
							o_j = self.J_array[t,k]
							o_is_e = self.E_array[t,k]							
								
							effort_cnt[o_j, X[t,k]] += o_is_e
							effort_state_cnt[o_j, X[t,k]] += 1	
					for j in range(self.J):
						self.effort_prob_matrix[j] = [np.random.dirichlet((self.prior_param['e'][0]+effort_state_cnt[j,x]-effort_cnt[j,x], self.prior_param['e'][1]+effort_cnt[j,x])) for x in range(self.Mx)]
			except AttributeError as e:
				tot_error_cnt += 1
				print(e)
			except:
				# without disrupting the chain due to a bad draw in X
				tot_error_cnt += 1
				print(sys.exc_info()[0])
				continue				
			self.user_mixture_density = new_user_mixture
			self.state_init_dist = new_state_init_dist
			self.state_transit_matrix = new_state_transit_matrix
			self.observ_prob_matrix = new_observ_prob_matrix
			
			#############################
			# Step 3: Preserve the Chain#
			#############################
			
			lHat_vec = []
			for z in range(self.num_mixture):
				for j in range(self.J):
					for m in range(self.Mx-1):
						lHat_vec += self.state_transit_matrix[j,z,1,m,(m+1):self.Mx].tolist() 
			param_chain['l'][iter,:] = lHat_vec
			
			pi_vec = []
			for z in range(self.num_mixture):
				pi_vec += self.state_init_dist[z,0:-1].tolist()	
			param_chain['pi'][iter,:] = pi_vec
			param_chain['mixture'][iter,:] = self.user_mixture_density[0:-1]
			param_chain['c'][iter,:] = self.observ_prob_matrix[:,:,1:].reshape(self.unique_item_num*self.Mx*(self.My-1)).tolist()
			
			if is_exit:
				if hazard_model == 'prop':
					h_vec = []
					for i in range(Mh):
						h_vec += [self.Lambdas[i], self.betas[i] ]
					param_chain['h'][iter,:] = h_vec
				elif hazard_model == 'cell':
					param_chain['h'][iter,:] = self.hazard_matrix.reshape(1,Mh*self.T)					
			
			if is_effort:
				param_chain['e'][iter,:] = self.effort_prob_matrix[:,:,1].flatten()
			# update parameter chain here
			self.X = X
		#ipdb.set_trace()
		return param_chain
Esempio n. 2
0
	def _get_initial_param(self, init_param, prior_dist, zero_mass_set, item_param_constraint, is_effort, is_exit, hazard_model, hazard_state):
		# c: probability of correct. Let cij=p(Y=j|X=i). 
		# pi: initial distribution of latent state, [Mx]
		# l: learning rate/pedagogical efficacy, 
		# e: probability of effort, [Mx]*nJ
		# Lambda: hazard rate with at time 0. scalar
		# betas: time trend of proportional hazard. scalar	
			
		# build the item dict
		self.unique_item_num, self.item_param_dict = get_item_dict(item_param_constraint, self.J)

		# build the prior dist
		# generate parameters from the prior
		if not prior_dist:
			pi_prior_list = []
			for z in range(self.num_mixture):
				pi_prior = []
				for x in range(self.Mx):
					if x != self.Mx-1:
						pi_prior.append(1)
					else:
						pi_prior.append(max(1,z+1))
				pi_prior_list.append(pi_prior)
		
			self.prior_param = {'l': [[[int(m<=n) for n in range(self.Mx)] for m in range(self.Mx)] for z in range(self.num_mixture)], # encoding the non-regressive state
								'e': [1, 1],
								'pi':pi_prior_list,
								'c' :[[y+1 for y in range(self.My)] for x in range(self.Mx)],
								'mixture':[1 for z in range(self.num_mixture)]
								}
		else:
			# TODO: check the specification of the prior
			self.prior_param = prior_dist
			
		# get the parameters
		if init_param:
			# for special purpose, allow for pre-determined starting point.
			param = copy.copy(init_param)
			# ALL items share the same prior for now
			self.user_mixture_density = param['mixture']
			self.observ_prob_matrix = param['c']
			self.effort_prob_matrix = param['e']
			self.state_init_dist = param['pi']
			self.state_transit_matrix = param['l'] 
			self.hazard_matrix = param['h'] 
		else:
			if zero_mass_set:
				if 'X' in zero_mass_set:
					for pos in zero_mass_set['X']:
						m,n = pos
						self.prior_param['l'][m][n] = 0
				if 'Y' in zero_mass_set:
					for pos in zero_mass_set['Y']:
						m,n = pos
						self.prior_param['c'][m][n] = 0
			
			#TODO: need to implement draws here
			if self.num_mixture>1:
				self.user_mixture_density = np.random.dirichlet(self.prior_param['mixture'])
				self.state_init_dist = draw_multilevel_pi(self.prior_param['pi'], self.num_mixture, self.Mx)
			else:
				self.user_mixture_density = 1.0
				self.state_init_dist = np.zeros((1,self.Mx))
				self.state_init_dist[0] = np.random.dirichlet(self.prior_param['pi'][0]) # wrap a list to allow for 1 mixture 
				
			self.state_transit_matrix = np.zeros((self.J,self.num_mixture,2,self.Mx,self.Mx))
			for j in range(self.J):
				for z in range(self.num_mixture):
					self.state_transit_matrix[j,z,:,:] = draw_l(self.prior_param['l'][0],self.Mx)
			
			self.observ_prob_matrix = np.array([draw_c(self.prior_param['c'], self.Mx, self.My) for j in range(self.unique_item_num)])
			
			if is_effort:
				self.effort_prob_matrix = np.array([[np.random.dirichlet(self.prior_param['e']) for x in range(self.Mx)] for j in range(self.J)])
			else:
				self.effort_prob_matrix = np.zeros((self.J, self.Mx, 2))
				self.effort_prob_matrix[:,:,1] = 1.0
			
			if is_exit:
				if hazard_model == 'cell':
					if hazard_state == 'X':
						Mh = self.Mx
					elif hazard_state == 'Y':
						Mh = self.My
					self.prior_param['h'] = np.ones((Mh,self.T,2))
					self.hazard_matrix = np.zeros((Mh,self.T))	
					for m in range(Mh):
						for t in range(self.T):
							self.hazard_matrix[m,t] = np.random.beta(self.prior_param['h'][m,t,0], self.prior_param['h'][m,t,1])
				elif hazard_model == 'prop':
					self.Lambda = 0.1
					self.beta = 0.01
					self.hazard_matrix = np.array([[self.Lambda*np.exp(self.beta*t) for t in range(self.T)] for x in range(self.Mx)])
			else:
				self.hazard_matrix = np.array([[0.0 for t in range(self.T)] for x in range(self.Mx)])