Beispiel #1
0
def _psi_solve(y, eps=0.00001):
    """ Solve psi(c)-log(c)=y by dichotomy
    """
    if y > 0:
        print "y", y
        raise ValueError("y>0, the problem cannot be solved")
    u = 1.0
    if y > sp.psi(u) - sp.log(u):
        while sp.psi(u) - sp.log(u) < y:
            u *= 2
        u /= 2
    else:
        while sp.psi(u) - sp.log(u) > y:
            u /= 2
    return _dichopsi_log(u, 2 * u, y, eps)
Beispiel #2
0
def _psi_solve(y, eps=0.00001):
    """
    This function solves
    solve psi(c)-log(c)=y
    by dichotomy
    """
    if y>0:
        print "y", y
        raise ValueError, "y>0, the problem cannot be solved"
    u = 1.
    if y>sp.psi(u)-sp.log(u):
        while sp.psi(u)-sp.log(u)<y:
            u = 2*u
        u = u/2
    else:
        while sp.psi(u)-sp.log(u)>y:
            u = u/2
    return _dichopsi_log(u,2*u,y,eps)
Beispiel #3
0
def _dichopsi_log(u, v, y, eps=0.00001):
    """ Implements the dichotomic part of the solution of psi(c)-log(c)=y
    """
    if u > v:
        u, v = v, u
    t = (u + v) / 2
    if np.absolute(u - v) < eps:
        return t
    else:
        if sp.psi(t) - sp.log(t) > y:
            return _dichopsi_log(u, t, y, eps)
        else:
            return _dichopsi_log(t, v, y, eps)
Beispiel #4
0
def learn(A,K,net0={},opts={}):
	"""
	runs variational bayes for inference of network modules
	(i.e. community detection) under a constrained stochastic block
	model.
	
	net0 and opts inputs are optional. if provided, length(net0.a0)
	must equal K.
	
	inputs:
	  A: N-by-N undirected (symmetric), binary adjacency matrix w/o
		 self-edges (note: fastest for sparse and logical A)
	  K: (maximum) number of modules
	  net0: initialization/hyperparameter structure for network
		net0['Q0']: N-by-K initial mean-field matrix (rows sum to 1)
		net0['ap0']: alpha_{+0}, hyperparameter for prior on \theta_+
		net0['bp0']: beta_{+0}, hyperparameter for prior on \theta_+
		net0['am0']: alpha_{-0}, hyperparameter for prior on \theta_-
		net0['bm0']: beta_{-0}, hyperparameter for prior on \theta_-
		net0['a0']: alpha_{\mu0}, 1-by-K vector of hyperparameters for
				 prior on \pi
	  opts: options
		opts['TOL_DF']: tolerance on change in F (outer loop)
		opts['MAX_FITER']: maximum number of F iterations (outer loop)
		opts['VERBOSE']: verbosity (0=quiet (default),1=print, 2=figures)
	
	outputs:
	  net: posterior structure for network
		net['F']: converged free energy (same as net.F_iter(end))
		net['F_iter']: free energy over iterations (learning curve)
		net['Q']: N-by-K mean-field matrix (rows sum to 1)
		net['K']: K, passed for compatibility with vbmod_restart
		net['ap']: alpha_+, hyperparameter for posterior on \theta_+
		net['bp']: beta_+, hyperparameter for posterior on \theta_+
		net['am']: alpha_-, hyperparameter for posterior on \theta_-
		net['bm']: beta_-, hyperparameter for posterior on \theta_-
		net['a']: alpha_{\mu}, 1-by-K vector of hyperparameters for
				 posterior on \pi
	"""
	# default options
	TOL_DF=1e-2
	MAX_FITER=30
	VERBOSE=0
	SAVE_ITER=0

	#print "get options from opts struct"
	if (type(opts) == type({})) and (len(opts) > 0):
		if 'TOL_DF' in opts: TOL_DF=opts['TOL_DF']
		if 'MAX_FITER' in opts: MAX_FITER=opts['MAX_FITER']
		if 'VERBOSE' in opts: VERBOSE=opts['VERBOSE']
		if 'SAVE_ITER' in opts: SAVE_ITER=opts['SAVE_ITER']		   

	N=A.shape[0]		# number of nodes
	M=0.5*A.sum(0).sum(1)  # total number of non-self edges
	M=M[0,0]
	C=comb(N,2)	   # total number of possible edges between N nodes
	
	uk=mat(ones([K,1]))
	un=mat(ones([N,1]))
	
	#print "default prior hyperparameters"
	ap0=2;
	bp0=1;
	am0=1;
	bm0=2;
	a0=ones([1,K]);

	#print "get initial Q0 matrix and prior hyperparameters from net0 struct"
	if (type(net0) == type({})) and (len(net0) > 0):
		if 'Q0' in net0: Q=net0['Q0']
		if 'ap0' in net0: ap0=net0['ap0']
		if 'bp0' in net0: bp0=net0['bp0']
		if 'am0' in net0: am0=net0['am0']
		if 'bm0' in net0: bm0=net0['bm0']
		if 'a0' in net0: a0=net0['a0']
	
	#print "initialize Q if not provided"
	try: Q
	except NameError: Q=init(N,K)
	
	Qmat=mat(Q)
	#print "size of Q=", Q.shape
	#Q=init(N,K)

	# ensure a0 is a 1-by-K vector
	assert(a0.shape == (1,K))
	
	#print "intialize variational distribution hyperparameters to be equal to prior hyperparameters"
	ap=ap0
	bp=bp0
	am=am0
	bm=bm0
	a=a0
	n=Q.sum(0)

	# get indices of non-zero row/columns
	# to be passed to vbmod_estep_inline
	# jntj: must be better way
	(rows,cols)=A.nonzero()

	# vector to store free energy over iterations
	F=[]
	for i in range(MAX_FITER):
		####################
		#VBE-step, to update mean-field Q matrix over module assignments
		####################
		
		# compute local and global coupling constants, JL and JG and
		# chemical potentials -lnpi
		psiap=digamma(ap)
		psibp=digamma(bp)
		psiam=digamma(am)
		psibm=digamma(bm)
		psip=digamma(ap+bp)
		psim=digamma(am+bm)
		JL=psiap-psibp-psiam+psibm
		JG=psibm-psim-psibp+psip

		lnpi=digamma(a)-digamma(sum(a))

		estep_inline(rows,cols,Q,float(JL),float(JG),array(lnpi),array(n))
		
		"""
		# local update (technically correct, but slow)
		for l in range(N):
			# exclude Q[l,:] from contributing to its own update
			Q[l,:]=zeros([1,K])
			# jntj: doesn't take advantage of sparsity
			Al=mat(A.getrow(l).toarray())
			AQl=multiply((Al.T*uk.T),Q).sum(0)
			nl=Q.sum(0)
			lnQl=JL*AQl-JG*nl+lnpi
			lnQl=lnQl-lnQl.max()
			Q[l,:]=exp(lnQl)
			Q[l,:]=Q[l,:]/Q[l,:].sum()
		"""
	
		####################
		#VBM-step, update distribution over parameters
		####################
		
		# compute expected occupation numbers <n*>s
		n=Qmat.sum(0)
	
		# compute expected edge counts <n**>s
		#QTAQ=mat((Q.T*A*Q).toarray())
		#npp=0.5*trace(QTAQ)
		
		npp=0.5*(Qmat.T*A*Qmat).diagonal().sum()
		npm=0.5*trace(Qmat.T*(un*n-Qmat))-npp
		nmp=M-npp
		nmm=C-M-npm	 
	
		# compute hyperparameters for beta and dirichlet distributions over
		# theta_+, theta_-, and pi_mu
		ap=npp+ap0
		bp=npm+bp0
		am=nmp+am0
		bm=nmm+bm0
		a=n+a0
		#print ap, bp, am, bm, a

		# evaluate variational free energy, an approximation to the
		# negative log-evidence	
		F.append(betaln(ap,bp)-betaln(ap0,bp0)+betaln(am,bm)-betaln(am0,bm0)+sum(gammaln(a))-gammaln(sum(a))-(sum(gammaln(a0))-gammaln(sum(a0)))-sum(multiply(Qmat,log(Qmat))))
		F[i]=-F[i]
		
		print "iteration", i+1 , ": F =", F[i]

		# F should always decrease
		if (i>1) and F[i] > F[i-1]:
			print "\twarning: F increased from", F[i-1] ,"to", F[i]

		if (i>1) and (abs(F[i]-F[i-1]) < TOL_DF):
			break
	  
	return dict(F=F[-1],F_iter=F,Q=Q,K=K,ap=ap,bp=bp,am=am,bm=bm,a=a)