docs: def train(self, X_feat, X_seq, y,
                X_feat_valid=None, X_seq_valid=None, y_valid=None,
                n_cores=3):
          """Train the CONCISE model
  
          :py:attr:`X_feat`, :py:attr:`X_seq`, py:attr:`y` are preferrably returned by the :py:func:`concise.prepare_data` function.
  
          Args:
              X_feat: Numpy (float) array of shape :code:`(N, D)`. Feature design matrix storing :code:`N` training samples and :code:`D` features
              X_seq:  Numpy (float) array of shape :code:`(N, 1, N_seq, 4)`. It represents 1-hot encoding of the DNA/RNA sequence.(:code:`N`-seqeuences of length :code:`N_seq`)
              y: Numpy (float) array of shape :code:`(N, 1)`. Response variable.
              X_feat_valid: :py:attr:`X_feat` used for model validation.
              X_seq_valid: :py:attr:`X_seq` used for model validation.
              y: :py:attr:`y` used for model validation.
              n_cores (int): Number of CPU cores used for training. If available, GPU is used for training and this argument is ignored.
          """
  
          if X_feat_valid is None and X_seq_valid is None and y_valid is None:
              X_feat_valid = X_feat
              X_seq_valid = X_seq
              y_valid = y
              print("Using training samples also for validation ")
  
          # insert one dimension - backcompatiblity
          X_seq = np.expand_dims(X_seq, axis=1)
          X_seq_valid = np.expand_dims(X_seq_valid, axis=1)
  
          # TODO: implement the re-training feature
          if self.is_trained() is True:
              print("Model already fitted. Re-training feature not implemented yet")
              return
  
          # input check
          assert X_seq.shape[0] == X_feat.shape[0] == y.shape[0]
          assert y.shape == (X_feat.shape[0], self._num_tasks)
  
          # extract data specific parameters
          self._param["seq_length"] = X_seq.shape[2]
          self._param["n_add_features"] = X_feat.shape[1]
  
          # more input check
          if not self._param["seq_length"] == X_seq_valid.shape[2]:
              raise Exception("sequence lengths don't match")
  
          # setup splines
          if self._param["n_splines"] is not None:
              padd_loss = self._param["motif_length"] - 1  # how much shorter is our sequence, since we don't use padding
              X_spline, S, _ = splines.get_gam_splines(start=0,
                                                       end=self._param["seq_length"] - padd_loss - 1,  # -1 due to zero-indexing
                                                       n_bases=self._param["n_splines"],
                                                       spline_order=3,
                                                       add_intercept=False)
              self._splines = {"X_spline": X_spline,
                               "S": S
                               }
  
          # setup graph and variables
          self._graph = tf.Graph()
          self._var = self._get_var_initialization(self._graph, X_feat_train=X_feat, y_train=y)
          self._other_var = self._build_graph(self._graph, self._var)
          # TODO: save the intialized parameters
          var_res_init = self._get_var_res(self._graph, self._var, self._other_var)
          self.init_weights = self._var_res_to_weights(var_res=var_res_init)
  
          # finally train the model
          # - it saves the accuracy
  
          if self._param["optimizer"] == "adam":
              _train = self._train_adam
          elif self._param["optimizer"] == "lbfgs":
              _train = self._train_lbfgs
          else:
              raise Exception("Optimizer {} not implemented".format(self._param["optimizer"]))
  
          self._var_res = _train(X_feat, X_seq, y,
                                 X_feat_valid, X_seq_valid, y_valid,
                                 graph=self._graph, var=self._var, other_var=self._other_var,
                                 early_stop_patience=self._param["early_stop_patience"],
                                 n_cores=n_cores)
  
          self._model_fitted = True
  
          # TODO: maybe:
          # - add y_train_accuracy
          # - y_train
  
          return True