Skip to content

Commit

Permalink
GH-16312 constrainted glm issues [nocheck] (#16317) (#16383)
Browse files Browse the repository at this point in the history
* Continue to double check algo.

* fix bug in gradient update.

* implemented various version of IRLSM

* Found GLM original with gradient magnitude change best

* GH-16312: fix wrong error raised by duplicated/conflicted constraints.

* force beta constraint to be satisfied at the end if it is not.

* GH-16312: add assert check to test per Veronika suggestion.

* GH-16312: fix tests after fixing constrained GLM bugs.

* GH-16312: fixed NPE error in checkCoeffsBounds

* GH-16312: fix test failure.

* remove conflicting constraint tests as we currently do not have the capability to do so right now.
* change dataset path from AWS to local
  • Loading branch information
wendycwong authored Sep 11, 2024
1 parent 62c1c25 commit cd390df
Show file tree
Hide file tree
Showing 18 changed files with 1,015 additions and 154 deletions.
5 changes: 2 additions & 3 deletions h2o-algos/src/main/java/hex/glm/ComputationState.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ public final class ComputationState {
int _totalBetaLength; // actual coefficient length without taking into account active columns only
int _betaLengthPerClass;
public boolean _noReg;
public boolean _hasConstraints;
public ConstrainedGLMUtils.ConstraintGLMStates _csGLMState;

public ComputationState(Job job, GLMParameters parms, DataInfo dinfo, BetaConstraint bc, GLM.BetaInfo bi){
Expand Down Expand Up @@ -1414,7 +1413,7 @@ protected GramXY computeNewGram(DataInfo activeData, double [] beta, GLMParamete
double obj_reg = _parms._obj_reg;
if(_glmw == null) _glmw = new GLMModel.GLMWeightsFun(_parms);
GLMTask.GLMIterationTask gt = new GLMTask.GLMIterationTask(_job._key, activeData, _glmw, beta,
_activeClass, _hasConstraints).doAll(activeData._adaptedFrame);
_activeClass).doAll(activeData._adaptedFrame);
gt._gram.mul(obj_reg);
if (_parms._glmType.equals(GLMParameters.GLMType.gam)) { // add contribution from GAM smoothness factor
Integer[] activeCols=null;
Expand Down Expand Up @@ -1463,7 +1462,7 @@ protected GramGrad computeGram(double [] beta, GLMGradientInfo gradientInfo){
double obj_reg = _parms._obj_reg;
if(_glmw == null) _glmw = new GLMModel.GLMWeightsFun(_parms);
GLMTask.GLMIterationTask gt = new GLMTask.GLMIterationTask(_job._key, activeData, _glmw, beta,
_activeClass, _hasConstraints).doAll(activeData._adaptedFrame);
_activeClass).doAll(activeData._adaptedFrame);
double[][] fullGram = gt._gram.getXX(); // only extract gram matrix
mult(fullGram, obj_reg);
if (_gramEqual != null)
Expand Down
75 changes: 45 additions & 30 deletions h2o-algos/src/main/java/hex/glm/ConstrainedGLMUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public static class ConstraintGLMStates {
double _ckCS;
double _ckCSHalf; // = ck/2
double _epsilonkCS;
double _epsilonkCSSquare;
public double _epsilonkCSSquare;
double _etakCS;
double _etakCSSquare;
double _epsilon0;
Expand Down Expand Up @@ -137,30 +137,35 @@ public static int[] extractBetaConstraints(ComputationState state, String[] coef
List<LinearConstraints> equalityC = new ArrayList<>();
List<LinearConstraints> lessThanEqualToC = new ArrayList<>();
List<Integer> betaIndexOnOff = new ArrayList<>();
if (betaC._betaLB != null) {
int numCons = betaC._betaLB.length-1;
for (int index=0; index<numCons; index++) {
if (!Double.isInfinite(betaC._betaUB[index]) && (betaC._betaLB[index] == betaC._betaUB[index])) { // equality constraint
addBCEqualityConstraint(equalityC, betaC, coefNames, index);
betaIndexOnOff.add(1);
} else if (!Double.isInfinite(betaC._betaUB[index]) && !Double.isInfinite(betaC._betaLB[index]) &&
(betaC._betaLB[index] < betaC._betaUB[index])) { // low < beta < high, generate two lessThanEqualTo constraints
addBCGreaterThanConstraint(lessThanEqualToC, betaC, coefNames, index);
addBCLessThanConstraint(lessThanEqualToC, betaC, coefNames, index);
betaIndexOnOff.add(1);
betaIndexOnOff.add(0);
} else if (Double.isInfinite(betaC._betaUB[index]) && !Double.isInfinite(betaC._betaLB[index])) { // low < beta < infinity
addBCGreaterThanConstraint(lessThanEqualToC, betaC, coefNames, index);
betaIndexOnOff.add(1);
} else if (!Double.isInfinite(betaC._betaUB[index]) && Double.isInfinite(betaC._betaLB[index])) { // -infinity < beta < high
addBCLessThanConstraint(lessThanEqualToC, betaC, coefNames, index);
betaIndexOnOff.add(1);
}
boolean bothEndsPresent = (betaC._betaUB != null) && (betaC._betaLB != null);
boolean lowerEndPresentOnly = (betaC._betaUB == null) && (betaC._betaLB != null);
boolean upperEndPresentOnly = (betaC._betaUB != null) && (betaC._betaLB == null);

int numCons = betaC._betaLB != null ? betaC._betaLB.length - 1 : betaC._betaUB.length - 1;
for (int index = 0; index < numCons; index++) {
if (bothEndsPresent && !Double.isInfinite(betaC._betaUB[index]) && (betaC._betaLB[index] == betaC._betaUB[index])) { // equality constraint
addBCEqualityConstraint(equalityC, betaC, coefNames, index);
betaIndexOnOff.add(1);
} else if (bothEndsPresent && !Double.isInfinite(betaC._betaUB[index]) && !Double.isInfinite(betaC._betaLB[index]) &&
(betaC._betaLB[index] < betaC._betaUB[index])) { // low < beta < high, generate two lessThanEqualTo constraints
addBCGreaterThanConstraint(lessThanEqualToC, betaC, coefNames, index);
addBCLessThanConstraint(lessThanEqualToC, betaC, coefNames, index);
betaIndexOnOff.add(1);
betaIndexOnOff.add(0);
} else if ((lowerEndPresentOnly || (betaC._betaUB != null && Double.isInfinite(betaC._betaUB[index]))) &&
betaC._betaLB != null && !Double.isInfinite(betaC._betaLB[index])) { // low < beta < infinity
addBCGreaterThanConstraint(lessThanEqualToC, betaC, coefNames, index);
betaIndexOnOff.add(1);
} else if ((upperEndPresentOnly || (betaC._betaLB != null && Double.isInfinite(betaC._betaLB[index]))) &&
betaC._betaUB != null && !Double.isInfinite(betaC._betaUB[index])) { // -infinity < beta < high
addBCLessThanConstraint(lessThanEqualToC, betaC, coefNames, index);
betaIndexOnOff.add(1);
}
}

state.setLinearConstraints(equalityC.toArray(new LinearConstraints[0]),
lessThanEqualToC.toArray(new LinearConstraints[0]), true);
return betaIndexOnOff.size()==0 ? null : betaIndexOnOff.stream().mapToInt(x->x).toArray();
return betaIndexOnOff.size() == 0 ? null : betaIndexOnOff.stream().mapToInt(x -> x).toArray();
}

/***
Expand Down Expand Up @@ -506,11 +511,10 @@ public static boolean extractCoeffNames(List<String> coeffList, LinearConstraint

public static List<String> foundRedundantConstraints(ComputationState state, final double[][] initConstraintMatrix) {
Matrix constMatrix = new Matrix(initConstraintMatrix);
Matrix constMatrixLessConstant = constMatrix.getMatrix(0, constMatrix.getRowDimension() -1, 1, constMatrix.getColumnDimension()-1);
Matrix constMatrixTConstMatrix = constMatrixLessConstant.times(constMatrixLessConstant.transpose());
int rank = constMatrixLessConstant.rank();
Matrix matrixSquare = constMatrix.times(constMatrix.transpose());
int rank = matrixSquare.rank();
if (rank < constMatrix.getRowDimension()) { // redundant constraints are specified
double[][] rMatVal = constMatrixTConstMatrix.qr().getR().getArray();
double[][] rMatVal = matrixSquare.qr().getR().getArray();
List<Double> diag = IntStream.range(0, rMatVal.length).mapToDouble(x->Math.abs(rMatVal[x][x])).boxed().collect(Collectors.toList());
int[] sortedIndices = IntStream.range(0, diag.size()).boxed().sorted((i, j) -> diag.get(i).compareTo(diag.get(j))).mapToInt(ele->ele).toArray();
List<Integer> duplicatedEleIndice = IntStream.range(0, diag.size()-rank).map(x -> sortedIndices[x]).boxed().collect(Collectors.toList());
Expand Down Expand Up @@ -645,6 +649,16 @@ public static void genInitialLambda(Random randObj, LinearConstraints[] constrai
}
}

public static void adjustLambda(LinearConstraints[] constraints, double[] lambda) {
int numC = constraints.length;
LinearConstraints oneC;
for (int index=0; index<numC; index++) {
oneC = constraints[index];
if (!oneC._active)
lambda[index]=0.0;
}
}


public static double[][] sumGramConstribution(ConstraintsGram[] gramConstraints, int numCoefs) {
if (gramConstraints == null)
Expand Down Expand Up @@ -714,16 +728,16 @@ public static void updateConstraintParameters(ComputationState state, double[] l
LinearConstraints[] equalConst, LinearConstraints[] lessThanConst,
GLMModel.GLMParameters parms) {
// calculate ||h(beta)|| square, ||gradient|| square
double hBetaMag = state._csGLMState._constraintMagSquare;
if (hBetaMag <= state._csGLMState._etakCSSquare) { // implement line 26 to line 29 of Algorithm 19.1
double hBetaMagSquare = state._csGLMState._constraintMagSquare;
if (hBetaMagSquare <= state._csGLMState._etakCSSquare) { // implement line 26 to line 29 of Algorithm 19.1
if (equalConst != null)
updateLambda(lambdaEqual, state._csGLMState._ckCS, equalConst);
if (lessThanConst != null)
updateLambda(lambdaLessThan, state._csGLMState._ckCS, lessThanConst);
state._csGLMState._epsilonkCS = state._csGLMState._epsilonkCS/state._csGLMState._ckCS;
state ._csGLMState._etakCS = state._csGLMState._etakCS/Math.pow(state._csGLMState._ckCS, parms._constraint_beta);
} else { // implement line 31 to 34 of Algorithm 19.1
state._csGLMState._ckCS = state._csGLMState._ckCS*parms._constraint_tau;
state._csGLMState._ckCS = state._csGLMState._ckCS*parms._constraint_tau; // tau belongs to [4,10]
state._csGLMState._ckCSHalf = state._csGLMState._ckCS*0.5;
state._csGLMState._epsilonkCS = state._csGLMState._epsilon0/state._csGLMState._ckCS;
state._csGLMState._etakCS = parms._constraint_eta0/Math.pow(state._csGLMState._ckCS, parms._constraint_alpha);
Expand Down Expand Up @@ -813,13 +827,14 @@ public static void updateConstraintValues(double[] betaCnd, List<String> coefNam
if (equalityConstraints != null) // equality constraints
Arrays.stream(equalityConstraints).forEach(constraint -> {
evalOneConstraint(constraint, betaCnd, coefNames);
constraint._active = (Math.abs(constraint._constraintsVal) > EPS2);
// constraint._active = (Math.abs(constraint._constraintsVal) > EPS2);
constraint._active = true;
});

if (lessThanEqualToConstraints != null) // less than or equal to constraints
Arrays.stream(lessThanEqualToConstraints).forEach(constraint -> {
evalOneConstraint(constraint, betaCnd, coefNames);
constraint._active = constraint._constraintsVal > 0;
constraint._active = constraint._constraintsVal >= 0;
});
}

Expand Down
Loading

0 comments on commit cd390df

Please sign in to comment.