@SuppressWarnings("static-access")
	@Override
	public void run(String[] args) {
		Options options = new Options();
		options.addOption(OptionBuilder
				.withLongOpt("train")
				.isRequired()
				.withArgName("filename")
				.hasArg()
				.withDescription(
						"Filename prefix with training dataset. It must "
								+ "exist three files with this prefix with "
								+ "the following sufixes: .grandparent, "
								+ ".leftsiblings and .rightsiblings.").create());
		options.addOption(OptionBuilder.withLongOpt("templates").isRequired()
				.withArgName("filename").hasArg()
				.withDescription("Filename prefix with templates.").create());
		options.addOption(OptionBuilder
				.withLongOpt("numepochs")
				.withArgName("integer")
				.hasArg()
				.withDescription(
						"Number of epochs: how many iterations over the"
								+ " training set.").create());
		options.addOption(OptionBuilder.withLongOpt("testconll")
				.withArgName("filename").hasArg()
				.withDescription("CoNLL-format test dataset.").create());
		options.addOption(OptionBuilder
				.withLongOpt("outputconll")
				.withArgName("filename")
				.hasArg()
				.withDescription(
						"Name of the CoNLL-format file to save the output.")
				.create());
		options.addOption(OptionBuilder.withLongOpt("test")
				.withArgName("filename").hasArg()
				.withDescription("Filename prefix with test dataset.").create());
		options.addOption(OptionBuilder.withLongOpt("script")
				.withArgName("path").hasArg()
				.withDescription("CoNLL evaluation script (eval.pl).").create());
		options.addOption(OptionBuilder
				.withLongOpt("perepoch")
				.withDescription(
						"The evaluation on the test corpus will "
								+ "be performed after each training epoch.")
				.create());
		options.addOption(OptionBuilder
				.withLongOpt("maxsteps")
				.withArgName("integer")
				.hasArg()
				.withDescription(
						"Maximum number of steps in the subgradient method.")
				.create());
		options.addOption(OptionBuilder
				.withLongOpt("beta")
				.withArgName("real number")
				.hasArg()
				.withDescription(
						"Fraction of the edge factor weights that "
								+ "are passed to the maximum branching "
								+ "algorithm, instead of the passing to "
								+ "the grandparent/siblings algorithm.")
				.create());
		options.addOption(OptionBuilder.withLongOpt("seed")
				.withArgName("integer").hasArg()
				.withDescription("Random number generator seed.").create());
		options.addOption(OptionBuilder
				.withLongOpt("lossweight")
				.withArgName("double")
				.hasArg()
				.withDescription(
						"Weight of the loss term in the inference objective function.")
				.create());
		options.addOption(OptionBuilder
				.withLongOpt("noavg")
				.withDescription(
						"Turn off the weight vector averaging, i.e.,"
								+ " the algorithm returns only the final weight "
								+ "vector instead of the average of each step "
								+ "vectors.").create());

		// Parse the command-line arguments.
		CommandLine cmdLine = null;
		PosixParser parser = new PosixParser();
		try {
			cmdLine = parser.parse(options, args);
		} catch (ParseException e) {
			System.err.println(e.getMessage());
			CommandLineOptionsUtil.usage(getClass().getSimpleName(), options);
		}

		// Print the list of options along the values provided by the user.
		CommandLineOptionsUtil.printOptionValues(cmdLine, options);

		// Training options.
		String trainPrefix = cmdLine.getOptionValue("train");
		String trainEdgeDatasetFileName = trainPrefix + ".edges";
		String trainGPDatasetFileName = trainPrefix + ".grandparent";
		String trainLSDatasetFileName = trainPrefix + ".siblings.left";
		String trainRSDatasetFileName = trainPrefix + ".siblings.right";
		String templatesPrefix = cmdLine.getOptionValue("templates");
		String templatesEdgeFileName = templatesPrefix + ".edges";
		String templatesGPFileName = templatesPrefix + ".grandparent";
		String templatesLSFileName = templatesPrefix + ".siblings.left";
		String templatesRSFileName = templatesPrefix + ".siblings.right";
		int numEpochs = Integer.parseInt(cmdLine.getOptionValue("numepochs",
				"10"));
		int maxSubgradientSteps = Integer.valueOf(cmdLine.getOptionValue(
				"maxsteps", "500"));
		double beta = Double.valueOf(cmdLine.getOptionValue("beta", "0.001"));
		// double lossWeight =
		// Double.parseDouble(cmdLine.getOptionValue("lossweight", "0d"));
		boolean averaged = !cmdLine.hasOption("noavg");
		String seedStr = cmdLine.getOptionValue("seed");

		// Test options.
		String testConllFileName = cmdLine.getOptionValue("testconll");
		String outputConllFilename = cmdLine.getOptionValue("outputconll");
		String testPrefix = cmdLine.getOptionValue("test");
		String testEdgeDatasetFilename = testPrefix + ".edges";
		String testGPDatasetFilename = testPrefix + ".grandparent";
		String testLSDatasetFilename = testPrefix + ".siblings.left";
		String testRSDatasetFilename = testPrefix + ".siblings.right";
		String script = cmdLine.getOptionValue("script");
		boolean evalPerEpoch = cmdLine.hasOption("perepoch");

		/*
		 * Options --testconll, --outputconll and --test must always be provided
		 * together.
		 */
		if ((testPrefix == null) != (testConllFileName == null)
				|| (outputConllFilename == null) != (testConllFileName == null)) {
			LOG.error("the options --testconll, --outputconll and --test "
					+ "must always be provided together (all or none)");
			System.exit(1);
		}

		DPGSDataset trainDataset = null;
		FeatureEncoding<String> featureEncoding = null;
		try {
			/*
			 * Create an empty and flexible feature encoding that will encode
			 * unambiguously all feature values. If the training dataset is big,
			 * this may not fit in memory and one should consider using a fixed
			 * encoding dictionary (based on test data or frequency on training
			 * data, for instance) or a hash-based encoding.
			 */
			featureEncoding = new StringMapEncoding();

			trainDataset = new DPGSDataset(new String[] { "bet-postag",
					"add-head-feats", "add-mod-feats" }, new String[] {
					"bet-hm-postag", "bet-hg-postag", "add-head-feats",
					"add-mod-feats", "add-gp-feats" }, new String[] {
					"bet-hm-postag", "bet-ms-postag", "add-head-feats",
					"add-mod-feats", "add-sib-feats" }, "|", featureEncoding);

			LOG.info(String.format("Loading training edge dataset (%s)...",
					trainEdgeDatasetFileName));
			trainDataset.loadEdgeFactors(trainEdgeDatasetFileName);

			LOG.info(String.format(
					"Loading training left siblings dataset (%s)...",
					trainLSDatasetFileName));
			trainDataset.loadSiblingsFactors(trainLSDatasetFileName);

			LOG.info(String.format(
					"Loading training right siblings dataset (%s)...",
					trainRSDatasetFileName));
			trainDataset.loadSiblingsFactors(trainRSDatasetFileName);

			/*
			 * Grandparent factors shall be the last ones to avoid problems with
			 * short sentences (1 ordinary token), since grandparent factors do
			 * not exist for such short sentences.
			 */
			LOG.info(String.format(
					"Loading training grandparent dataset (%s)...",
					trainGPDatasetFileName));
			trainDataset.loadGrandparentFactors(trainGPDatasetFileName);

			// Set modifier variables in all output structures.
			trainDataset.setModifierVariables();

			// Template-based model.
			LOG.info("Allocating initial model...");

			// Model.
			DPGSModel model = new DPGSModel(0);

			// Templates.
			model.loadEdgeTemplates(templatesEdgeFileName, trainDataset);
			model.loadGrandparentTemplates(templatesGPFileName, trainDataset);
			model.loadLeftSiblingsTemplates(templatesLSFileName, trainDataset);
			model.loadRightSiblingsTemplates(templatesRSFileName, trainDataset);

			// Generate derived features from templates.
			model.generateFeatures(trainDataset);

			// Inference algorithm for training.
			DPGSInference inference = new DPGSInference(
					trainDataset.getMaxNumberOfTokens());

			// Learning algorithm.
			Perceptron alg = new Perceptron(inference, model, numEpochs, 1d,
					true, averaged, LearnRateUpdateStrategy.NONE);

			if (seedStr != null)
				// User provided seed to random number generator.
				alg.setSeed(Long.parseLong(seedStr));

			if (testConllFileName != null && evalPerEpoch) {
				LOG.info("Loading test factors...");
				DPGSDataset testset = new DPGSDataset(trainDataset);
				testset.loadEdgeFactors(testEdgeDatasetFilename);
				testset.loadSiblingsFactors(testLSDatasetFilename);
				testset.loadSiblingsFactors(testRSDatasetFilename);
				testset.loadGrandparentFactors(testGPDatasetFilename);
				testset.setModifierVariables();
				model.generateFeatures(testset);

				LOG.info("Evaluating...");

				// Use dual inference algorithm for testing.
				DPGSDualInference inferenceDual = new DPGSDualInference(
						testset.getMaxNumberOfTokens());
				inferenceDual
						.setMaxNumberOfSubgradientSteps(maxSubgradientSteps);
				inferenceDual.setBeta(beta);

				// // TODO test
				// DPGSInference inferenceDual = new DPGSInference(
				// testset.getMaxNumberOfTokens());
				// inferenceDual.setCopyPredictionToParse(true);

				EvaluateModelListener eval = new EvaluateModelListener(script,
						testConllFileName, outputConllFilename, testset,
						averaged, inferenceDual);
				eval.setQuiet(true);
				alg.setListener(eval);
			}

			LOG.info("Training model...");
			// Train model.
			alg.train(trainDataset.getInputs(), trainDataset.getOutputs());

			LOG.info(String.format("# updated parameters: %d",
					model.getNumberOfUpdatedParameters()));

			if (testConllFileName != null && !evalPerEpoch) {
				LOG.info("Loading test factors...");
				DPGSDataset testset = new DPGSDataset(trainDataset);
				testset.loadEdgeFactors(testEdgeDatasetFilename);
				testset.loadSiblingsFactors(testLSDatasetFilename);
				testset.loadSiblingsFactors(testRSDatasetFilename);
				testset.loadGrandparentFactors(testGPDatasetFilename);
				testset.setModifierVariables();
				model.generateFeatures(testset);

				LOG.info("Evaluating...");

				// Use dual inference algorithm for testing.
				DPGSDualInference inferenceDual = new DPGSDualInference(
						testset.getMaxNumberOfTokens());
				inferenceDual
						.setMaxNumberOfSubgradientSteps(maxSubgradientSteps);
				inferenceDual.setBeta(beta);

				// // TODO test
				// DPGSInference inferenceDual = new DPGSInference(
				// testset.getMaxNumberOfTokens());
				// inferenceDual.setCopyPredictionToParse(true);

				EvaluateModelListener eval = new EvaluateModelListener(script,
						testConllFileName, outputConllFilename, testset, false,
						inferenceDual);
				eval.setQuiet(true);
				eval.afterEpoch(inferenceDual, model, -1, -1d, -1);
			}

			LOG.info("Training done!");

		} catch (Exception e) {
			LOG.error("Error during training", e);
			System.exit(1);
		}
	}