Skip to content

Commit

Permalink
Added genK and kprl
Browse files Browse the repository at this point in the history
  • Loading branch information
makgyver committed Mar 5, 2019
1 parent eec882c commit 680e1bb
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 92 deletions.
2 changes: 1 addition & 1 deletion PRL/evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def confusion_matrix(prl, gen_pref_test):
for i in range(N):
x = X[i,:]
sco = [0.0 for c in range(prl.dim)]
for j, (p, f) in enumerate(prl.feat_list):
for j, (p, f) in enumerate(prl.col_list):
if prl.Q[j] > 0.0:
for c in range(prl.dim):
if p[0][1] == c:
Expand Down
56 changes: 56 additions & 0 deletions PRL/genK.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import numpy as np
import random

def apply2prefs(k_fun, p1, p2):
(x1p, y1p), (x1n, y1n) = p1
(x2p, y2p), (x2n, y2n) = p2
res = 0.
if y1p == y2p:
res += k_fun(x1p, x2p)
if y1n == y2n:
res += k_fun(x1n, x2n)
elif y1p == y2n:
res -= k_fun(x1p, x2n)
if y1n == y2p:
res -= k_fun(x1n, x2p)
return res


class GenK:
def __init__(self):
pass

def get_random_kernel(self):
pass

def get_kernel_function(self):
pass


class GenKList(GenK):
def __init__(self, k_list):
self.kernel_list = k_list

def get_random_kernel(self):
return random.randint(0, len(self.kernel_list)-1)

def get_kernel_function(self, d):
return self.kernel_list[d]

def __repr__(self):
return "GenKList"


class GenHPK(GenK):
def __init__(self, min_deg=2, max_deg=2):
self.min_deg = min_deg
self.max_deg = max(max_deg, min_deg)

def get_random_kernel(self):
return random.randint(self.min_deg, self.max_deg)

def __repr__(self):
return "GenHPK(dmin=%d, dmax=%d)" %(self.min_deg, self.max_deg)

def get_kernel_function(self, d):
return lambda p1, p2: apply2prefs(lambda x,z: np.dot(x,z)**d, p1, p2)
216 changes: 165 additions & 51 deletions PRL/prl.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ def __init__(self, gen_pref, gen_feat, dim, n_cols, solver):

self.pref_list = self.gen_pref.get_all_prefs()
self.n_rows = len(self.pref_list)
self.feat_list = []
self.feat_set = set()
self.col_list = []
self.col_set = set()

self.M = np.zeros((self.n_rows, self.n_cols))
self.Q = None
Expand All @@ -65,7 +65,7 @@ def get_random_pair(self):
return (self.gen_pref.get_random_pref(), self.gen_feat.get_random_feat())


def col_pref_repr(self, q, f):
def pref_repr(self, q, f):
"""Computes the representation of the preference q w.r.t. the feature f.
:param q: the new preference
Expand All @@ -81,22 +81,26 @@ def col_pref_repr(self, q, f):
r[y_n] = -self.gen_feat.get_feat_value(f, x_n)
return r

def compute_column(self, rq, f):
"""Computes the column representation of the preference q w.r.t. the feature f.
def row_prefs_repr(self, f):
"""Computes the representation of all the preferences w.r.t. the selected feature f.
:param f: a feature
:param rq: the new preference representation
:param f: the feature identifier
:type rq: numpy.ndarray
:type f: int, tuple
:returns: the representation of all the preferences w.r.t. f
:returns: the representation of the column
:rtype: numpy.ndarray
"""
R = np.zeros((self.n_rows, self.dim))
for i, q in enumerate(self.pref_list):
(x_p, y_p), (x_n, y_n) = self.gen_pref.get_pref_value(q)
for i, p in enumerate(self.pref_list):
(x_p, y_p), (x_n, y_n) = self.gen_pref.get_pref_value(p)
R[i, y_p] = +self.gen_feat.get_feat_value(f, x_p)
R[i, y_n] = -self.gen_feat.get_feat_value(f, x_n)
return R

return np.dot(R, rq)

def compute_entry(self, p, q, f):
return np.dot(self.pref_repr(p, f), self.pref_repr(q, f))

def _get_new_col(self):
"""Internal method that randomly pick a new column in such a way that its representation is not null and it is not already in the game matrix.
Expand All @@ -105,10 +109,10 @@ def _get_new_col(self):
:rtype: tuple
"""
(p, f) = self.get_random_pair()
rp = self.col_pref_repr(p, f)
while (p, f) in self.feat_set or not rp.any():
rp = self.pref_repr(p, f)
while (p, f) in self.col_set or not rp.any():
(p, f) = self.get_random_pair()
rp = self.col_pref_repr(p, f)
rp = self.pref_repr(p, f)
return (p, f), rp


Expand All @@ -126,11 +130,9 @@ def fit(self, iterations=1000, verbose=False):

for j in range(self.n_cols):
(p, f), rp = self._get_new_col()
self.feat_list.append((p, f))
self.feat_set.add((p, f))
R = self.row_prefs_repr(f)
x = np.dot(R, rp)
self.M[:,j] = x
self.col_list.append((p, f))
self.col_set.add((p, f))
self.M[:,j] = self.compute_column(rp, f)

#iterative updates
for t in range(iterations):
Expand All @@ -141,15 +143,14 @@ def fit(self, iterations=1000, verbose=False):
for j in range(self.n_cols):
if Q[j] <= 0:
(p, f), rp = self._get_new_col()
self.feat_set.remove(self.feat_list[j])
self.feat_list[j] = (p, f)
self.feat_set.add((p, f))
R = self.row_prefs_repr(f)
x = np.dot(R, rp)
self.M[:,j] = x
self.col_set.remove(self.col_list[j])
self.col_list[j] = (p, f)
self.col_set.add((p, f))
self.M[:,j] = self.compute_column(rp, f)

if verbose:
logging.info("# of kept columns: %d" %(np.sum(Q>0)))
logging.info("# of unique fetures: %d\n" %len(set([f for i, (p, f) in enumerate(self.feat_list) if Q[i]>0.])))
logging.info("## of kept columns: %d" %(np.sum(Q>0)))
logging.info("# of unique features: %d\n" %len(set([f for i, (p, f) in enumerate(self.col_list) if Q[i]>0.])))
self.Q = Q


Expand All @@ -161,7 +162,7 @@ def get_best_features(self, k=10):
:returns: the k best features (along with their weights) sorted by their weights
:rtype: list of tuples
"""
list_w_feat = [(self.Q[i], pf) for i, pf in enumerate(self.feat_list) if self.Q[i] > 0]
list_w_feat = [(self.Q[i], pf) for i, pf in enumerate(self.col_list) if self.Q[i] > 0]
list_w_feat.sort(reverse=True)

return [(pf, q) for (q, pf) in list_w_feat[:k]]
Expand All @@ -171,7 +172,7 @@ def get_best_features(self, k=10):


class PRL_ext(PRL):

def fit(self, iterations=1000, verbose=False):

if verbose:
Expand All @@ -180,11 +181,9 @@ def fit(self, iterations=1000, verbose=False):

for j in range(self.n_cols):
(p, f), rp = self._get_new_col()
self.feat_list.append((p, f))
self.feat_set.add((p, f))
R = self.row_prefs_repr(f)
x = np.dot(R, rp)
self.M[:,j] = x
self.col_list.append((p, f))
self.col_set.add((p, f))
self.M[:,j] = self.compute_column(rp, f)

#iterative updates
for t in range(iterations):
Expand All @@ -195,29 +194,144 @@ def fit(self, iterations=1000, verbose=False):
for j in range(self.M.shape[1]):
if Q[j] <= 0:
(p, f), rp = self._get_new_col()
self.feat_set.remove(self.feat_list[j])
self.feat_list[j] = (p, f)
self.feat_set.add((p, f))
R = self.row_prefs_repr(f)
x = np.dot(R, rp)
self.M[:,j] = x

self.col_set.remove(self.col_list[j])
self.col_list[j] = (p, f)
self.col_set.add((p, f))
self.M[:,j] = self.compute_column(rp, f)

n_zeros = np.sum(Q <= 0)
if n_zeros < self.n_cols:
M_r = np.zeros((self.n_rows, self.n_cols-n_zeros))
for j in range(self.n_cols-n_zeros):
(p, f), rp = self._get_new_col()
self.feat_list.append((p, f))
self.feat_set.add((p, f))
R = self.row_prefs_repr(f)
x = np.dot(R, rp)
M_r[:,j] = x

self.col_list.append((p, f))
self.col_set.add((p, f))
self.M[:,j] = self.compute_column(rp, f)

self.M = np.concatenate((self.M, M_r), axis=1)

if verbose:
logging.info("# of kept columns: %d" %(np.sum(Q>0)))
logging.info("# of unique fetures: %d" %len(set([f for i, (p, f) in enumerate(self.feat_list[:len(Q)]) if Q[i]>0.])))
logging.info("# of unique features: %d" %len(set([f for i, (p, f) in enumerate(self.col_list[:len(Q)]) if Q[i]>0.])))
logging.info("# of columns: %d\n" %self.M.shape[1])

self.Q = Q

self.Q = Q


class KPRL:
"""This class implements the Kernelized Preference and Rule Learning (KPRL) algorithm."""

def __init__(self, gen_pref, gen_kernel, dim, n_cols, solver):
"""Initializes all the useful structures.
:param gen_pref: the preference generator. See <:genP.GenMacroP> and <:genP.GenMicroP>
:param gen_kernel: the kernel generator
:param dim: number of possible labels
:param n_cols: number of columns of the matrix sub-game
:param solver: game solver. See for example <:solvers.FictitiousPlay>
:type gen_pref: object of class which inherits from <:genP.GenP>, e.g., GenMacroP
:type gen_kernel: object of class which inherits from <:genK.GenK>, e.g., GenHPK
:type dim: int
:type n_cols: int
:type solver: object of class which inherits from <:solvers.Solver>
"""
self.gen_pref = gen_pref
self.gen_kernel = gen_kernel
self.n_cols = n_cols
self.dim = dim
self.solver = solver

self.pref_list = self.gen_pref.get_all_prefs()
self.n_rows = len(self.pref_list)
self.col_list = []
self.col_set = set()

self.M = np.zeros((self.n_rows, self.n_cols))
self.Q = None

def __repr__(self):
"""Returns a string representation of the KPRL object.
:returns: return a string representation of the KPRL object
:rtype: string
"""
return "KPRL(gen_pref=%s, gen_kernel=%s, n_rows=%d, n_cols=%d, solver=%s)"\
%(self.gen_pref, self.gen_kernel, self.n_rows, self.n_cols, self.solver)

def get_random_pair(self):
"""Returns a new random columns composed by a preference-kernel pair.
:returns: a new random columns composed by a preference-kernel pair
:rtype: tuple(preference, kernel)
"""
return (self.gen_pref.get_random_pref(), self.gen_kernel.get_random_kernel())

def compute_column(self, q, k):
"""Computes the representation of the preference q w.r.t. the kernel k.
:param q: the new preference
:param k: the kernel function identifier
:type q: tuple
:type k: int, tuple
:returns: the representation of the column
:rtype: numpy.ndarray
"""
p_col = self.gen_pref.get_pref_value(q)
k_fun = self.gen_kernel.get_kernel_function(k)
R = np.zeros((self.n_rows, 1))
for i, r in enumerate(self.pref_list):
p_row = self.gen_pref.get_pref_value(r)
R[i, 0] = k_fun(p_col, p_row)

return R

def compute_entry(self, p, q, k):
return self.gen_kernel.get_kernel_function(k)(p, q)

def _get_new_col(self):
"""Internal method that randomly pick a new column in such a way that its representation is not null and it is not already in the game matrix.
:returns: a not null representation of a random picked preference-kernel pair
:rtype: tuple
"""
(p, k) = self.get_random_pair()
while (p, k) in self.col_set:
(p, k) = self.get_random_pair()
return (p, k)


def fit(self, iterations=1000, verbose=False):
"""Trains the KPRL method.
:param iterations: the number of iterations of KPRL
:param verbose: whether the output is verbose or not
:type iterations: int
:type verbose: bool
"""
if verbose:
logging.info("Starting training of %s" %self)
logging.info("Matrix game initialization...")

for j in range(self.n_cols):
(p, k) = self._get_new_col()
self.col_list.append((p, k))
self.col_set.add((p, k))
self.M[:,j] = self.compute_column(p, k)

#iterative updates
for t in range(iterations):
if verbose: logging.info("KPRL iteration %d/%d" %(t+1, iterations))
(P, Q, V) = self.solver.solve(self.M, self.n_rows, self.n_cols)
if verbose: logging.info("Value of the game (current margin): %.6f" %V)
if (t+1 < iterations):
for j in range(self.n_cols):
if Q[j] <= 0:
(p, k) = self._get_new_col()
self.col_set.remove(self.col_list[j])
self.col_list[j] = (p, k)
self.col_set.add((p, k))
self.M[:,j] = self.compute_column(p, k)
if verbose:
logging.info("# of kept columns: %d" %(np.sum(Q>0)))

self.Q = Q

0 comments on commit 680e1bb

Please sign in to comment.