* @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 numLambdas the number of lambdas.
	 * @param minLambdaRatio the minimum lambda is minLambdaRatio * max lambda.
	 * @return group-lasso penalized classifiers.
	 */
	public List<GLM> buildClassifiers(Instances trainSet, boolean isSparse, List<int[]> groups, int maxNumIters,
			int numLambdas, double minLambdaRatio) {
		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;
				}
				
				List<GLM> glms = buildBinaryClassifiers(sm.group, sm.indices, sm.values, y, maxNumIters, numLambdas,
						minLambdaRatio);

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

				return glms;
			} else {
				boolean refit = this.refit;
				this.refit = false; // Not supported in multiclass
									// classification

				int p = attrs.length == 0 ? 0 : (StatUtils.max(attrs) + 1);
				List<GLM> glms = new ArrayList<>();
				for (int i = 0; i < numLambdas; i++) {
					GLM glm = new GLM(numClasses, p);
					glms.add(glm);
				}

				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;
					}

					List<GLM> binaryClassifiers = buildBinaryClassifiers(group, indices, values, y, maxNumIters,
							numLambdas, minLambdaRatio);

					for (int l = 0; l < numLambdas; l++) {
						GLM binaryClassifier = binaryClassifiers.get(l);
						GLM glm = glms.get(l);
						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];
					}

				}

				this.refit = refit;

				return glms;
			}
		} 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;
				}

				List<GLM> glms = buildBinaryClassifiers(dm.groups, dm.x, y, maxNumIters, numLambdas, minLambdaRatio);

				for (GLM glm : glms) {
					double[] w = glm.coefficients(0);
					for (int j = 0; j < cList.length; j++) {
						int attIndex = dd.attrs[j];
						w[attIndex] *= cList[j];
					}
				}
				return glms;
			} else {
				boolean refit = this.refit;
				this.refit = false; // Not supported in multiclass
									// classification

				int p = attrs.length == 0 ? 0 : (StatUtils.max(attrs) + 1);
				List<GLM> glms = new ArrayList<>();
				for (int i = 0; i < numLambdas; i++) {
					GLM glm = new GLM(numClasses, p);
					glms.add(glm);
				}

				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;
					}

					List<GLM> binaryClassifiers = buildBinaryClassifiers(group, x, y, maxNumIters, numLambdas,
							minLambdaRatio);

					for (int l = 0; l < numLambdas; l++) {
						GLM binaryClassifier = binaryClassifiers.get(l);
						GLM glm = glms.get(l);
						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];
					}

				}

				this.refit = refit;

				return glms;
			}
		}
	}