code: def fit_model(self, ctx, encryptor, train_data, validate_data=None):
      coef_count = train_data.shape[1]
      self.header = train_data.schema.columns.to_list()
      # logger.debug(f"init param: {self.init_param}")
      if self.init_param.get("fit_intercept"):
          logger.debug(f"add intercept to train data")
          train_data["intercept"] = 1.0
      w = self.w
      if self.w is None:
          w = initialize_param(coef_count, **self.init_param)
          self.optimizer.init_optimizer(
              model_parameter_length=w.size()[0]
          )
          self.lr_scheduler.init_scheduler(
              optimizer=self.optimizer.optimizer
          )
      batch_loader = dataframe.DataLoader(
          train_data,
          ctx=ctx,
          batch_size=self.batch_size,
          mode="hetero",
          role="guest",
          sync_arbiter=True,
      )
      # if self.end_epoch >= 0:
      #    self.start_epoch = self.end_epoch + 1
      is_centralized = len(ctx.hosts) > 1
  
      for i, iter_ctx in ctx.on_iterations.ctxs_range(self.epochs):
          self.optimizer.set_iters(i)
          logger.info(f"self.optimizer set epoch {i}")
          for batch_ctx, batch_data in iter_ctx.on_batches.ctxs_zip(
              batch_loader
          ):
              X = batch_data.x
              Y = batch_data.label
              weight = batch_data.weight
              if is_centralized:
                  g = self.centralized_compute_gradient(
                      batch_ctx, w, X, Y, weight
                  )
              else:
                  g = self.asynchronous_compute_gradient(
                      batch_ctx, encryptor, w, X, Y, weight
                  )
              g = self.optimizer.add_regular_to_grad(
                  g, w, self.init_param.get("fit_intercept")
              )
              batch_ctx.arbiter.put("g_enc", g)
              g = batch_ctx.arbiter.get("g")
  
              w = self.optimizer.update_weights(
                  w,
                  g,
                  self.init_param.get("fit_intercept"),
                  self.lr_scheduler.lr,
              )
              # logger.info(f"w={w}")
          self.is_converged = iter_ctx.arbiter("converge_flag").get()
          if self.is_converged:
              self.end_epoch = i
              break
          if i < self.epochs - 1:
              self.lr_scheduler.step()
      if not self.is_converged:
          self.end_epoch = self.epochs
      self.w = w
      logger.debug(f"Finish training at {self.end_epoch}th epoch.")
  
imports: import logging
  import torch
  from fate.arch import Context, dataframe
  from fate.ml.abc.module import HeteroModule
  from fate.ml.utils import predict_tools
  from fate.ml.utils._model_param import deserialize_param, initialize_param, serialize_param
  from fate.ml.utils._optimizer import LRScheduler, Optimizer
label_desc: Use image node_id 4 for calling the CoordinatedLinREstimatorGuest obj's underlying member method code with example usage: obj.fit_model(ctx, encryptor, train_data, validate_data) without return types