Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions csrank/choicefunction/cmpnet_choice.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ def __init__(
n_units=8,
loss_function="binary_crossentropy",
batch_normalization=True,
kernel_regularizer=l2(1e-4),
kernel_regularizer=l2(),
kernel_initializer="lecun_normal",
activation="relu",
optimizer=SGD,
metrics=["binary_accuracy"],
metrics=("binary_accuracy",),
batch_size=256,
random_state=None,
**kwargs,
Expand Down
4 changes: 2 additions & 2 deletions csrank/choicefunction/fate_choice.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ def __init__(
loss_function=binary_crossentropy,
activation="selu",
kernel_initializer="lecun_normal",
kernel_regularizer=l2(0.01),
kernel_regularizer=l2(),
optimizer=SGD,
batch_size=256,
metrics=None,
metrics=(),
random_state=None,
**kwargs,
):
Expand Down
20 changes: 11 additions & 9 deletions csrank/choicefunction/feta_choice.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ def __init__(
num_subsample=5,
loss_function=binary_crossentropy,
batch_normalization=False,
kernel_regularizer=l2(1e-4),
kernel_regularizer=l2(),
kernel_initializer="lecun_normal",
activation="selu",
optimizer=SGD,
metrics=["binary_accuracy"],
metrics=("binary_accuracy",),
batch_size=256,
random_state=None,
**kwargs,
Expand Down Expand Up @@ -119,7 +119,7 @@ def _construct_layers(self, **kwargs):
# Todo: Variable sized input
# X = Input(shape=(None, n_features))
if self.batch_normalization:
if self._use_zeroth_model:
if self.add_zeroth_order_model:
self.hidden_layers_zeroth = [
NormalizedDense(
self.n_units, name="hidden_zeroth_{}".format(x), *kwargs
Expand All @@ -131,7 +131,7 @@ def _construct_layers(self, **kwargs):
for x in range(self.n_hidden)
]
else:
if self._use_zeroth_model:
if self.add_zeroth_order_model:
self.hidden_layers_zeroth = [
Dense(self.n_units, name="hidden_zeroth_{}".format(x), **kwargs)
for x in range(self.n_hidden)
Expand All @@ -144,7 +144,7 @@ def _construct_layers(self, **kwargs):
self.output_node = Dense(
1, activation="linear", kernel_regularizer=self.kernel_regularizer
)
if self._use_zeroth_model:
if self.add_zeroth_order_model:
self.output_node_zeroth = Dense(
1, activation="linear", kernel_regularizer=self.kernel_regularizer
)
Expand All @@ -169,7 +169,7 @@ def construct_model(self):
def create_input_lambda(i):
return Lambda(lambda x: x[:, i])

if self._use_zeroth_model:
if self.add_zeroth_order_model:
self.logger.debug("Create 0th order model")
zeroth_order_outputs = []
inputs = []
Expand All @@ -184,7 +184,7 @@ def create_input_lambda(i):
self.logger.debug("Create 1st order model")
outputs = [list() for _ in range(self.n_objects_fit_)]
for i, j in combinations(range(self.n_objects_fit_), 2):
if self._use_zeroth_model:
if self.add_zeroth_order_model:
x1 = inputs[i]
x2 = inputs[j]
else:
Expand Down Expand Up @@ -214,13 +214,15 @@ def create_input_lambda(i):
]
scores = concatenate(scores)
self.logger.debug("1st order model finished")
if self._use_zeroth_model:
if self.add_zeroth_order_model:
scores = add([scores, zeroth_order_scores])
scores = Activation("sigmoid")(scores)
model = Model(inputs=self.input_layer, outputs=scores)
self.logger.debug("Compiling complete model...")
model.compile(
loss=self.loss_function, optimizer=self.optimizer_, metrics=self.metrics
loss=self.loss_function,
optimizer=self.optimizer_,
metrics=list(self.metrics),
)
return model

Expand Down
4 changes: 2 additions & 2 deletions csrank/choicefunction/ranknet_choice.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ def __init__(
n_units=8,
loss_function="binary_crossentropy",
batch_normalization=True,
kernel_regularizer=l2(1e-4),
kernel_regularizer=l2(),
kernel_initializer="lecun_normal",
activation="relu",
optimizer=SGD,
metrics=["binary_accuracy"],
metrics=("binary_accuracy",),
batch_size=256,
random_state=None,
**kwargs,
Expand Down
8 changes: 5 additions & 3 deletions csrank/core/cmpnet_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ def __init__(
n_units=8,
loss_function="binary_crossentropy",
batch_normalization=True,
kernel_regularizer=l2(1e-4),
kernel_regularizer=l2(),
kernel_initializer="lecun_normal",
activation="relu",
optimizer=SGD,
metrics=["binary_accuracy"],
metrics=("binary_accuracy",),
batch_size=256,
random_state=None,
**kwargs,
Expand Down Expand Up @@ -109,7 +109,9 @@ def construct_model(self):
merged_output = concatenate([N_g, N_l])
model = Model(inputs=[self.x1, self.x2], outputs=merged_output)
model.compile(
loss=self.loss_function, optimizer=self.optimizer_, metrics=self.metrics
loss=self.loss_function,
optimizer=self.optimizer_,
metrics=list(self.metrics),
)
return model

Expand Down
6 changes: 4 additions & 2 deletions csrank/core/fate_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def __init__(
n_hidden_joint_units=32,
activation="selu",
kernel_initializer="lecun_normal",
kernel_regularizer=l2(0.01),
kernel_regularizer=l2(),
optimizer=SGD,
batch_size=256,
random_state=None,
Expand Down Expand Up @@ -475,7 +475,9 @@ def construct_model(self, n_features, n_objects):
model = Model(inputs=input_layer, outputs=scores)

model.compile(
loss=self.loss_function, optimizer=self.optimizer_, metrics=self.metrics
loss=self.loss_function,
optimizer=self.optimizer_,
metrics=list(self.metrics),
)
return model

Expand Down
79 changes: 78 additions & 1 deletion csrank/core/feta_linear.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,21 @@


class FETALinearCore(Learner):
"""Core Learner implementing the First Evaluate then Aggregate approach.

This implements a linear variant of the FETA approach introduced in
[PfGuH18]. The idea is to first evaluate each object in each sub-context of
fixed size with a linear function approximator and then to aggregate these
evaluations.

References
----------

.. [PfGuH18] Pfannschmidt, K., Gupta, P., & Hüllermeier, E. (2018). Deep
architectures for learning context-dependent ranking functions. arXiv
preprint arXiv:1803.05796. https://arxiv.org/pdf/1803.05796.pdf
"""

def __init__(
self,
learning_rate=1e-3,
Expand All @@ -23,6 +38,25 @@ def __init__(
random_state=None,
**kwargs,
):
"""
Parameters
----------
learning_rate : float
The learning rate used by the gradient descent optimizer.
batch_size : int
The size of the mini-batches used to train the Neural Network.
loss_function
The loss function to minimize when training the Neural Network. See
the functions offered in the keras.losses module for more details.
epochs_drop: int
The amount of training epochs after which the learning rate is
decreased by a factor of `drop`.
drop: float
The factor by which to decrease the learning rate every
`epochs_drop` epochs.
random_state: np.RandomState
The random state to use in this object.
"""
self.learning_rate = learning_rate
self.batch_size = batch_size
self.random_state = random_state
Expand Down Expand Up @@ -90,6 +124,18 @@ def _construct_model_(self, n_objects):
)

def step_decay(self, epoch):
"""Update the current learning rate.

Computes the current learning rate based on the initial learning rate,
the current epoch and the decay speed set by the `epochs_drop` and
`drop` hyperparameters.

Parameters
----------

epoch: int
The current epoch.
"""
step = math.floor((1 + epoch) / self.epochs_drop)
self.current_lr = self.learning_rate * math.pow(self.drop, step)
self.optimizer = tf.train.GradientDescentOptimizer(self.current_lr).minimize(
Expand All @@ -99,6 +145,22 @@ def step_decay(self, epoch):
def fit(
self, X, Y, epochs=10, callbacks=None, validation_split=0.1, verbose=0, **kwd
):
"""
Fit the preference learning algorithm on the provided set of queries X
and preferences Y of those objects. The provided queries and
corresponding preferences are of a fixed size (numpy arrays).

Parameters
----------
X : array-like, shape (n_samples, n_objects, n_features)
Feature vectors of the objects
Y : array-like, shape (n_samples, n_objects)
Preferences of the objects in form of rankings or choices
epochs: int
The amount of epochs to train for. The training loop will try to
predict the target variables and adjust its parameters by gradient
descent `epochs` times.
"""
self.random_state_ = check_random_state(self.random_state)
# Global Variables Initializer
n_instances, self.n_objects_fit_, self.n_object_features_fit_ = X.shape
Expand Down Expand Up @@ -146,6 +208,18 @@ def _fit_(self, X, Y, epochs, n_instances, tf_session, verbose):
self.logger.info("Epoch {}: cost {} ".format((epoch + 1), np.mean(c)))

def _predict_scores_fixed(self, X, **kwargs):
"""Predict the scores for a given collection of sets of objects of same size.

Parameters
----------
X : array-like, shape (n_samples, n_objects, n_features)


Returns
-------
Y : array-like, shape (n_samples, n_objects)
Returns the scores of each of the objects for each of the samples.
"""
n_instances, n_objects, n_features = X.shape
assert n_features == self.n_object_features_fit_
outputs = [list() for _ in range(n_objects)]
Expand All @@ -168,7 +242,10 @@ def set_tunable_parameters(
self, learning_rate=1e-3, batch_size=128, epochs_drop=300, drop=0.1, **point
):
"""
Set tunable parameters of the FETA-network to the values provided.
Set tunable hyperparameters of the FETA-network to the values provided.

This can be used for automatic hyperparameter optimization. See
csrank.tuning for more information.

Parameters
----------
Expand Down
28 changes: 15 additions & 13 deletions csrank/core/feta_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ def __init__(
num_subsample=5,
loss_function=hinged_rank_loss,
batch_normalization=False,
kernel_regularizer=l2(1e-4),
kernel_regularizer=l2(),
kernel_initializer="lecun_normal",
activation="selu",
optimizer=SGD,
metrics=None,
metrics=(),
batch_size=256,
random_state=None,
**kwargs,
Expand All @@ -54,7 +54,7 @@ def __init__(
self.batch_size = batch_size
self.hash_file = None
self.optimizer = optimizer
self._use_zeroth_model = add_zeroth_order_model
self.add_zeroth_order_model = add_zeroth_order_model
self.n_hidden = n_hidden
self.n_units = n_units
keys = list(kwargs.keys())
Expand All @@ -80,7 +80,7 @@ def _construct_layers(self, **kwargs):
# X = Input(shape=(None, n_features))
self.logger.info("n_hidden {}, n_units {}".format(self.n_hidden, self.n_units))
if self.batch_normalization:
if self._use_zeroth_model:
if self.add_zeroth_order_model:
self.hidden_layers_zeroth = [
NormalizedDense(
self.n_units, name="hidden_zeroth_{}".format(x), **kwargs
Expand All @@ -92,7 +92,7 @@ def _construct_layers(self, **kwargs):
for x in range(self.n_hidden)
]
else:
if self._use_zeroth_model:
if self.add_zeroth_order_model:
self.hidden_layers_zeroth = [
Dense(self.n_units, name="hidden_zeroth_{}".format(x), **kwargs)
for x in range(self.n_hidden)
Expand All @@ -105,14 +105,14 @@ def _construct_layers(self, **kwargs):
self.output_node = Dense(
1, activation="sigmoid", kernel_regularizer=self.kernel_regularizer
)
if self._use_zeroth_model:
if self.add_zeroth_order_model:
self.output_node_zeroth = Dense(
1, activation="sigmoid", kernel_regularizer=self.kernel_regularizer
)

@property
def zero_order_model(self):
if self._zero_order_model is None and self._use_zeroth_model:
if self._zero_order_model is None and self.add_zeroth_order_model:
self.logger.info("Creating zeroth model")
inp = Input(shape=(self.n_object_features_fit_,))

Expand Down Expand Up @@ -153,7 +153,7 @@ def pairwise_model(self):
def _predict_pair(self, a, b, only_pairwise=False, **kwargs):
# TODO: Is this working correctly?
pairwise = self.pairwise_model.predict([a, b], **kwargs)
if not only_pairwise and self._use_zeroth_model:
if not only_pairwise and self.add_zeroth_order_model:
utility_a = self.zero_order_model.predict([a])
utility_b = self.zero_order_model.predict([b])
return pairwise + (utility_a, utility_b)
Expand All @@ -173,7 +173,7 @@ def _predict_scores_using_pairs(self, X, **kwd):
scores[n] += result.reshape(n_objects, n_objects - 1).mean(axis=1)
del result
del pairs
if self._use_zeroth_model:
if self.add_zeroth_order_model:
scores_zero = self.zero_order_model.predict(X.reshape(-1, n_features))
scores_zero = scores_zero.reshape(n_instances, n_objects)
scores = scores + scores_zero
Expand All @@ -199,7 +199,7 @@ def construct_model(self):
def create_input_lambda(i):
return Lambda(lambda x: x[:, i])

if self._use_zeroth_model:
if self.add_zeroth_order_model:
self.logger.debug("Create 0th order model")
zeroth_order_outputs = []
inputs = []
Expand All @@ -214,7 +214,7 @@ def create_input_lambda(i):
self.logger.debug("Create 1st order model")
outputs = [list() for _ in range(self.n_objects_fit_)]
for i, j in combinations(range(self.n_objects_fit_), 2):
if self._use_zeroth_model:
if self.add_zeroth_order_model:
x1 = inputs[i]
x2 = inputs[j]
else:
Expand Down Expand Up @@ -244,12 +244,14 @@ def create_input_lambda(i):
]
scores = concatenate(scores)
self.logger.debug("1st order model finished")
if self._use_zeroth_model:
if self.add_zeroth_order_model:
scores = add([scores, zeroth_order_scores])
model = Model(inputs=self.input_layer, outputs=scores)
self.logger.debug("Compiling complete model...")
model.compile(
loss=self.loss_function, optimizer=self.optimizer_, metrics=self.metrics
loss=self.loss_function,
optimizer=self.optimizer_,
metrics=list(self.metrics),
)
return model

Expand Down
Loading