plqcom

Submodules

Attributes

Classes

PLQLoss

PLQLoss is a class represents a continuous convex piecewise quadratic loss function, which adopts three types of

Functions

max_to_plq(quad_coef)

is_continuous(plq_loss)

Check whether a PLQ loss function is continuous

is_convex(plq_loss)

Check whether a PLQ loss function is convex

check_cutoff(plq_loss)

Check whether there exists a cutoff between the knots, if so, add the cutoff to the knot list and update

find_min(plq_loss)

Find the minimum knots and value of the PLQ function, if the minimum value is greater than zero

plq_to_rehloss(plq_loss)

convert the PLQLoss function to a ReHLoss function piece by piece.

affine_transformation(rehloss[, n, c, p, q, form, y])

Since composite ReLU-ReHU function is closure under affine transformation,

Package Contents

plqcom.__version__ = '0.1.0'
class plqcom.PLQLoss(quad_coef=None, form='plq', cutpoints=np.empty(shape=(0,)), points=np.empty(shape=(0, 2)))

Bases: object

PLQLoss is a class represents a continuous convex piecewise quadratic loss function, which adopts three types of input forms: ‘plq’, ‘max’ and ‘points’.

Parameters:
quad_coef{dict-like} of {‘a’: [], ‘b’: [], ‘c’: []}

The quadratic coefficients in pieces of the PLQLoss. The i-th piece Q is: a[i]* x**2 + b[i] * x + c[i]

formstr, optional, default: ‘plq’

Input form of the PLQ function: 'plq', 'max', or 'points'. For 'plq', cutpoints must be given explicitly. For 'max', cutpoints are computed from the pointwise maximum of quadratics. For 'points', the function is piecewise linear through the given points; the first and last pieces extend the adjacent segments.

cutpoints{array-like} of float, optional, default: None

cutpoints of the PLQLoss, except -np.inf and np.inf

if the form is ‘max’ or ‘points’, the cutpoints is not necessary

if the form is ‘plq’, the cutpoints is necessary

points{array-like} of (x,y) pairs [(x1, y1), (x2, y2), … (xn, yn)]

or {dict-like} of {‘x’: [x1, x2, …, xn], ‘y’: [y1, y2, … yn]} or {2d-array-like} of [[x1, x2, …, xn], [y1, y2, … yn]] optional, default: None

Points coordinates of the piecewise linear form of the PLQLoss. The PLQLoss will be constructed by straight lines between each two adjcent points according to their x coordinates. Two points with the same x coordinates will be rejected.

if the form is ‘points’, the points is necessary

if the form is ‘max’ or ‘plq’, the points is not necessary

Examples

>>> import numpy as np
>>> from plqcom import PLQLoss
>>> cutpoints = [0., 1.]
>>> quad_coef = {'a': np.array([0., .5, 0.]), 'b': np.array([-1, 0., 1]), 'c': np.array([0., 0., -.5])}
>>> random_loss = PLQLoss(quad_coef, cutpoints=cutpoints)
>>> x = np.arange(-2,2,.05)
>>> random_loss(x)
cutpoints
min_val
min_knot
__call__(x)

Evaluation of PLQLoss function.

Parameters:
x{array-like} of shape {n_samples}
Training vector, where `n_samples` is the number of samples.
Returns:
y{array-like} of shape {n_samples}

The values of the PLQLoss function on each x y[j] = quad_coef[‘a’][i]*x[j]**2 + quad_coef[‘b’][i]*x[j] + quad_coef[‘c’][i], if cutpoints[i] < x[j] < cutpoints[i+1]

plqcom.max_to_plq(quad_coef)
plqcom.is_continuous(plq_loss)

Check whether a PLQ loss function is continuous

Parameters:
plq_lossPLQLoss

A PLQLoss object

Returns:
bool

Whether the PLQ function is continuous, True for continuous, False for not continuous

Examples

>>> from plqcom import PLQLoss, is_continuous
>>> plq_loss = PLQLoss(cutpoints=np.array([0.]),quad_coef={'a': np.array([0, 0]), 'b': np.array([-1, 1]), 'c': np.array([1, 1])})
>>> is_continuous(plq_loss)
True
plqcom.is_convex(plq_loss)

Check whether a PLQ loss function is convex

Parameters:
plq_lossPLQLoss

A PLQLoss object

Returns:
bool

Whether the PLQ function is convex, True for convex, False for not convex

Examples

>>> from plqcom import PLQLoss, is_convex
>>> plq_loss = PLQLoss(cutpoints=np.array([0.]),quad_coef={'a': np.array([0, 0]), 'b': np.array([-1, 1]), 'c': np.array([1, 1])})
>>> is_convex(plq_loss)
True
plqcom.check_cutoff(plq_loss)

Check whether there exists a cutoff between the knots, if so, add the cutoff to the knot list and update the coefficients

Parameters:
plq_lossPLQLoss

A PLQLoss object

Examples

>>> from plqcom import PLQLoss, check_cutoff
>>> plq_loss = PLQLoss(cutpoints=np.array([0.]),quad_coef={'a': np.array([0, 0]), 'b': np.array([-1, 1]), 'c': np.array([1, 1])})
>>> check_cutoff(plq_loss)
>>> plq_loss.cutpoints
array([-inf,   0.,  inf])
plqcom.find_min(plq_loss)

Find the minimum knots and value of the PLQ function, if the minimum value is greater than zero record the minimum value and knot, remove the minimum value from the PLQ function and update the coefficients

Parameters:
plq_lossPLQLoss

A PLQLoss object

Examples

>>> from plqcom import PLQLoss, find_min
>>> plq_loss = PLQLoss(cutpoints=np.array([0]),quad_coef={'a': np.array([0, 0]), 'b': np.array([-1, 1]), 'c': np.array([1, 1])})
>>> find_min(plq_loss)
>>> plq_loss.min_val
1.0
plqcom.plq_to_rehloss(plq_loss)
convert the PLQLoss function to a ReHLoss function piece by piece.

See Gao, Dai & Qiu (2025), Journal of Data Science, doi:10.6339/24-JDS1162.

Parameters:
plq_lossPLQLoss

A PLQLoss object

Returns:
an object of ReHLoss

Examples

>>> from plqcom import PLQLoss, plq_to_rehloss
>>> plq_loss = PLQLoss(cutpoints=np.array([0]),quad_coef={'a': np.array([0, 0]), 'b': np.array([-1, 1]), 'c': np.array([1, 1])})
>>> reh_loss = plq_to_rehloss(plq_loss)
plqcom.affine_transformation(rehloss: rehline._loss.ReHLoss, n=1, c=1, p=1, q=0, form='custom', y=1)

Since composite ReLU-ReHU function is closure under affine transformation, this function perform affine transformation on the PLQ object.

Parameters:
rehlossReHLoss

A ReHLoss object

c: a number or {array_like} of shape (n_samples,), default=1

Per-sample scale on the prototype loss (mathematical \(C_i\) in \(L_i(z) = C_i L(p_i z + q_i)\)). Use c=1 for uniform weights. This is not the same as C in ReHLine(C=...): for rehline >= 0.1.0, set global ERM strength via ReHLine(C=...) only and keep c=1 here unless you need heterogeneous sample weights. Do not pass c=C when also using ReHLine(C=C) — that applies the penalty twice.

p: a number or {array_like} of shape (n_samples,),default=1

scale parameter on z

q: a number or {array_like} of shape (n_samples,),default=0

shift parameter on z

n: int, default=1

number of samples

form: str, default=’custom’

Affine transformation form: 'custom', 'classification', or 'regression'. For 'custom', c, p, and q can be scalars or arrays. For 'classification', use L_i = c_i L(y_i z_i) (p=y_i, q=0). For 'regression', use L_i = c_i L(y_i - z_i) (p=-1, q=y).

y: {array_like} of shape (n_samples,), default=None, only required when form is ‘classification’ or ‘regression’

the label of the samples

Returns:
ReHLoss

A ReHLoss object after affine transformation

Examples

>>> from plqcom import PLQLoss, affine_transformation, plq_to_rehloss
>>> import numpy as np
>>> from rehline import ReHLine
>>> n, d, C = 1000, 3, 0.5
>>> np.random.seed(1024)
>>> X = np.random.randn(1000, 3)
>>> beta0 = np.random.randn(3)
>>> y = np.sign(X.dot(beta0) + np.random.randn(n))
>>> plqloss = PLQLoss(quad_coef={'a': np.array([0., 0.]), 'b': np.array([0., 1.]), 'c': np.array([0., 0.])}, cutpoints=np.array([0]))
>>> rehloss = plq_to_rehloss(plqloss)
>>> rehloss = affine_transformation(rehloss, n=X.shape[0], c=1, p=-y, q=1)