from .BinaryMFPenalty import BinaryMFPenalty, error, rec_error, reg_error
from ..utils import sigmoid, d_sigmoid, power, multiply, ignore_warnings, subtract, get_prediction
from .ContinuousModel import ContinuousModel
from ..utils import binarize, to_dense, power, multiply, ignore_warnings, get_prediction, get_prediction_with_threshold, show_factor_distribution
import numpy as np
from scipy.sparse import spmatrix
from tqdm import tqdm
[docs]
class PNLPF(BinaryMFPenalty):
'''PNLPF, binary matrix factorization's penalty function algorithm with sigmmoid link function.
.. topic:: Reference
Boolean decomposition of binary matrices using a post-nonlinear mixture approach.
Solving the problem with multiplicative update:
min 1/2 ||X - sigmoid(U @ V.T - 1/2)||_F^2 + 1/2 * reg * ||U^2 - U||_F^2 + 1/2 * reg * ||V^2 - V||_F^2
Parameters
----------
reg : float
The regularization weight 'lambda' in the paper.
reg_growth : float
The growing rate of regularization weight.
max_reg : float
The upper bound of regularization weight.
tol : float
The error tolerance 'epsilon' in the paper.
'''
def __init__(self, k, U=None, V=None, W='full', reg=2.0, beta_loss="frobenius", solver="mu", link_lamda=10, reg_growth=3, max_reg=1e10, tol=0.01, min_diff=0.0, max_iter=100, init_method='custom', normalize_method='balance', seed=None):
self.check_params(k=k, U=U, V=V, W=W, reg=reg, beta_loss=beta_loss, solver=solver, link_lamda=link_lamda, reg_growth=reg_growth, max_reg=max_reg, tol=tol, min_diff=min_diff, max_iter=max_iter, init_method=init_method, normalize_method=normalize_method, seed=seed)
@ignore_warnings
def update_U(self):
'''Multiplicative update of U.
'''
self.U = update_U(X=self.X_train, W=self.W, U=self.U, V=self.V, reg=self.reg, link_lamda=self.link_lamda)
@ignore_warnings
def update_V(self):
'''Multiplicative update of V.
'''
self.V = update_V(X=self.X_train, W=self.W, U=self.U, V=self.V, reg=self.reg, link_lamda=self.link_lamda)
[docs]
def get_prediction(self):
return get_prediction_with_sigmoid(U=self.U, V=self.V, link_lamda=self.link_lamda)
[docs]
def get_prediction_with_sigmoid(U, V, link_lamda):
'''Get prediction with sigmoid link function.
'''
S = subtract(U @ V.T, 1/2) * link_lamda
return sigmoid(S)
[docs]
def update_U(X, W, U, V, reg, link_lamda, solver='mu', beta_loss='frobenius'):
S = subtract(U @ V.T, 1/2) * link_lamda
sig = sigmoid(S)
d_sig = d_sigmoid(S)
num = link_lamda * multiply(W, multiply(X, d_sig)) @ V
num += 3 * reg * power(U, 2)
denom = link_lamda * multiply(W, multiply(sig, d_sig)) @ V
denom += 2 * reg * power(U, 3) + reg * U
denom[denom == 0] = np.finfo(np.float64).eps
U_next = multiply(U, num / denom)
U_next[U_next == 0] = np.finfo(float).eps
return U_next
[docs]
def update_V(X, W, U, V, reg, link_lamda, solver='mu', beta_loss='frobenius'):
S = subtract(U @ V.T, 1/2) * link_lamda
sig = sigmoid(S)
d_sig = d_sigmoid(S)
num = link_lamda * multiply(W, multiply(X, d_sig)).T @ U
num += 3 * reg * power(V, 2)
denom = link_lamda * multiply(W, multiply(sig, d_sig)).T @ U
denom += 2 * reg * power(V, 3) + reg * V
denom[denom == 0] = np.finfo(np.float64).eps
V_next = multiply(V, num / denom)
V_next[V_next == 0] = np.finfo(float).eps
return V_next