docs: @Override
      public ISeqClassifier train(List<SequenceTuple> trainingData) {
          if (trainingData == null || trainingData.size() == 0) {
              LOG.warn("Training data is empty.");
              return this;
          }
  
          if (modelPath == null) {
              try {
                  modelPath = Files.createTempDirectory("crfsuite").toAbsolutePath().toString();
              } catch (IOException e) {
                  LOG.error("Create temp directory failed.", e);
                  e.printStackTrace();
              }
          }
  
          Pair<List<ItemSequence>, List<StringList>> crfCompatibleTrainingData = loadTrainingData(trainingData);
          Trainer trainer = new Trainer();
  
          String algorithm = (String) props.getOrDefault("algorithm", DEFAULT_ALGORITHM);
          props.remove("algorithm");
          String graphicalModelType = (String) props.getOrDefault("graphicalModelType", DEFAULT_GRAPHICAL_MODEL_TYPE);
          props.remove("graphicalModelType");
  
          trainer.select(algorithm, graphicalModelType);
  
          // Set parameters
          props.entrySet().forEach(pair -> trainer.set((String) pair.getKey(), (String) pair.getValue()));
  
          // Add training data into the trainer
          for (int i = 0; i < trainingData.size(); i++) {
              // Use group id = 0 but the API doesn't say what it is used for :(
              trainer.append(crfCompatibleTrainingData.getLeft().get(i), crfCompatibleTrainingData.getRight().get(i), 0);
          }
  
          // Start training without hold-outs. trainer.message()
          // will be called to report the training process
          trainer.train(modelPath, -1);
          return this;
      }