* @param x the inputs.
	 * @param y the targets.
	 * @param maxNumIters the maximum number of iterations.
	 * @param lambda the lambda.
	 * @return a group-lasso penalized classifier.
	 */
	public GLM buildBinaryClassifier(int[][] attrs, double[][][] x, double[] y, int maxNumIters, double lambda) {
		int p = 0;
		if (attrs.length > 0) {
			for (int[] attr : attrs) {
				p = Math.max(p, StatUtils.max(attr));
			}
			p += 1;
		}
		
		double[][] w = new double[attrs.length][];
		double[] tl1 = new double[attrs.length];
		int m = 0;
		for (int j = 0; j < attrs.length; j++) {
			w[j] = new double[attrs[j].length];
			tl1[j] = lambda * Math.sqrt(w[j].length);
			if (w[j].length > m) {
				m = w[j].length;
			}
		}

		double[] pTrain = new double[y.length];
		double[] rTrain = new double[y.length];
		OptimUtils.computePseudoResidual(pTrain, y, rTrain);

		double[] stepSize = new double[attrs.length];
		for (int j = 0; j < stepSize.length; j++) {
			double max = 0;
			double[][] block = x[j];
			for (double[] t : block) {
				double l = StatUtils.sumSq(t) / 4;
				if (l > max) {
					max = l;
				}
			}
			stepSize[j] = 1.0 / max;
		}
		double[] g = new double[m];
		double[] gradient = new double[m];

		boolean[] activeSet = new boolean[attrs.length];

		double intercept = 0;
		
		// Block coordinate gradient descent
		int iter = 0;
		while (iter < maxNumIters) {

			if (fitIntercept) {
				intercept += OptimUtils.fitIntercept(pTrain, rTrain, y);
			}

			boolean activeSetChanged = doOnePassBinomial(x, y, tl1, true, activeSet, w, stepSize, 
					g, gradient, pTrain, rTrain);

			iter++;

			if (!activeSetChanged || iter > maxNumIters) {
				break;
			}

			for (; iter < maxNumIters; iter++) {

				double prevLoss = GLMOptimUtils.computeGroupLassoLoss(pTrain, y, w, tl1);
				
				if (fitIntercept) {
					intercept += OptimUtils.fitIntercept(pTrain, rTrain, y);
				}

				doOnePassBinomial(x, y, tl1, false, activeSet, w, stepSize, g, gradient, pTrain, rTrain);

				double currLoss = GLMOptimUtils.computeGroupLassoLoss(pTrain, y, w, tl1);
				
				if (OptimUtils.isConverged(prevLoss, currLoss, epsilon)) {
					break;
				}

				if (verbose) {
					System.out.println("Iteration " + iter + ": " + currLoss);
				}
			}
		}

		if (refit) {
			boolean[] selected = new boolean[attrs.length];
			for (int i = 0; i < selected.length; i++) {
				selected[i] = !ArrayUtils.isConstant(w[i], 0, w[i].length, 0);
			}
			return refitGaussianRegressor(p, attrs, selected, x, y, w, maxNumIters);
		} else {
			return getGLM(p, attrs, w, intercept, LinkFunction.LOGIT);
		}
	}