* @param isSparse <code>true</code> if the training set is treated as sparse.
	 * @param groups the groups of variables.
	 * @param maxNumIters the maximum number of iterations.
	 * @param lambda the lambda.
	 * @return a group-lasso penalized classifier.
	 */
	public GLM buildClassifier(Instances trainSet, boolean isSparse, List<int[]> groups, int maxNumIters, double lambda) {
		Attribute classAttribute = trainSet.getTargetAttribute();
		if (classAttribute.getType() != Attribute.Type.NOMINAL) {
			throw new IllegalArgumentException("Class attribute must be nominal.");
		}
		NominalAttribute clazz = (NominalAttribute) classAttribute;
		int numClasses = clazz.getCardinality();
		
		if (isSparse) {
			SparseDataset sd = getSparseDataset(trainSet, true);
			SparseDesignMatrix sm = createDesignMatrix(sd, groups);
			int[] attrs = sd.attrs;
			int[][] group = sm.group;
			int[][][] indices = sm.indices;
			double[][][] values = sm.values;
			double[] y = new double[sd.y.length];
			double[] cList = sd.cList;
			
			if (numClasses == 2) {
				for (int i = 0; i < y.length; i++) {
					int label = (int) sd.y[i];
					y[i] = label == 0 ? 1 : 0;
				}
				
				GLM glm = buildBinaryClassifier(group, indices, values, y, maxNumIters, lambda);
				
				double[] w = glm.coefficients(0);
				for (int j = 0; j < cList.length; j++) {
					int attIndex = sd.attrs[j];
					w[attIndex] *= cList[j];
				}

				return glm;
			} else {
				int p = attrs.length == 0 ? 0 : (StatUtils.max(attrs) + 1);
				GLM glm = new GLM(numClasses, p);

				for (int k = 0; k < numClasses; k++) {
					// One-vs-the-rest
					for (int i = 0; i < y.length; i++) {
						int label = (int) sd.y[i];
						y[i] = label == k ? 1 : 0;
					}

					GLM binaryClassifier = buildBinaryClassifier(group, indices, values, y, maxNumIters, lambda);

					double[] w = binaryClassifier.w[0];
					for (int j = 0; j < cList.length; j++) {
						int attIndex = attrs[j];
						glm.w[k][attIndex] = w[attIndex] * cList[j];
					}
					glm.intercept[k] = binaryClassifier.intercept[0];
				}

				return glm;
			}
		} else {
			DenseDataset dd = getDenseDataset(trainSet, true);
			DenseDesignMatrix dm = createDesignMatrix(dd, groups);
			int[] attrs = dd.attrs;
			int[][] group = dm.groups;
			double[][][] x = dm.x;
			double[] y = new double[dd.y.length];
			double[] cList = dd.cList;
			
			if (numClasses == 2) {
				for (int i = 0; i < y.length; i++) {
					int label = (int) dd.y[i];
					y[i] = label == 0 ? 1 : 0;
				}
				
				GLM glm = buildBinaryClassifier(group, x, y, maxNumIters, lambda);

				double[] w = glm.coefficients(0);
				for (int j = 0; j < cList.length; j++) {
					int attIndex = dd.attrs[j];
					w[attIndex] *= cList[j];
				}

				return glm;
			} else {
				int p = attrs.length == 0 ? 0 : (StatUtils.max(attrs) + 1);
				GLM glm = new GLM(numClasses, p);

				for (int k = 0; k < numClasses; k++) {
					// One-vs-the-rest
					for (int i = 0; i < y.length; i++) {
						int label = (int) dd.y[i];
						y[i] = label == k ? 1 : 0;
					}

					GLM binaryClassifier = buildBinaryClassifier(group, x, y, maxNumIters, lambda);

					double[] w = binaryClassifier.w[0];
					for (int j = 0; j < cList.length; j++) {
						int attIndex = attrs[j];
						glm.w[k][attIndex] = w[attIndex] * cList[j];
					}
					glm.intercept[k] = binaryClassifier.intercept[0];
				}

				return glm;
			}
		}
	}