def train_model_final(
  hParams,
  dataset,
  noteInitState: pd.DataFrame,
  raterInitState: pd.DataFrame,
  globalInterceptInit: c.ReputationGlobalIntercept,
  device=torch.device("cpu"),
):
  """
  Args:
    hParams (ReputationModelHyperparameters)
    dataset (ReputationDataset)
    noteInitState (Optional[pd.DataFrame]): expects internal column names e.g. internalNoteIntercept
    raterInitState (Optional[pd.DataFrame]): expects internal column names e.g. internalRaterIntercept
  """
  hParams.defaultReputation = 0.0  # 0 reputation for raters missing from init.

  # setup_model initializes uses the internal intercepts, but we want to initialize with round 2 intercepts,
  #  and save the final rater intercepts for later initialization.
  noteInitState[c.internalNoteInterceptKey] = noteInitState[c.internalNoteInterceptRound2Key]

  savedFinalRoundPrescoringRaterIntercept = raterInitState[c.internalRaterInterceptKey].copy()
  raterInitState[c.internalRaterInterceptKey] = raterInitState[c.internalRaterInterceptRound2Key]

  model, loss_fn = _setup_model(
    dataset, hParams, noteInitState, raterInitState, globalInterceptInit.secondRound
  )

  logger.info(
    "Final scoring, initial round fitting reputation MF (equivalent to Round 2 in Prescoring - learn note factor)"
  )

  model.noteBias.requires_grad_(False)
  model.noteEmbedding.requires_grad_(True)
  model.raterEmbedding.requires_grad_(False)
  model.raterReputation.requires_grad_(False)
  model.raterBias.requires_grad_(False)
  model.globalBias.requires_grad_(False)

  loss_fn_2 = WeightedLoss(
    loss_fn,
    dataset.noteTensor,
    dataset.raterTensor,
    dataset.targetTensor,
    posWeight=hParams.posWeight * hParams.posWeightSecondRoundMultiplier,
    noteNormExp=hParams.noteNormExpSecondRound,
    raterNormExp=hParams.raterNormExpSecondRound,
    device=device,
  )
  _train_one_round(model, loss_fn_2, dataset, hParams)

  logger.info("Final scoring, final round fitting reputation MF: learn just note intercept")

  # Now set the global intercept to the value from the final round
  model.globalBias.data = torch.tensor(globalInterceptInit.finalRound, **model.format)

  # Set rater intercepts back to final round. We will learn note intercepts, so no need to set them back.
  raterInitState[c.internalRaterInterceptKey] = savedFinalRoundPrescoringRaterIntercept
  model.init_rater_intercept(raterInitState, dataset, device)

  model.l2Lambda = hParams.l2Lambda * hParams.l2LambdaThirdRoundMultiplier
  model.l2NoteBiasMultiplier = hParams.l2NoteBiasMultiplier * hParams.l2NoteBiasThirdRoundMultiplier

  model.noteBias.requires_grad_(True)
  model.noteEmbedding.requires_grad_(False)
  model.raterEmbedding.requires_grad_(False)
  model.raterBias.requires_grad_(False)
  model.raterReputation.requires_grad_(False)
  model.globalBias.requires_grad_(False)

  raterReputation = model.raterReputation.weight.detach().clone().clip(min=0)
  loss_fn_final = WeightedLoss(
    loss_fn,
    dataset.noteTensor,
    dataset.raterTensor,
    dataset.targetTensor,
    posWeight=hParams.posWeight * hParams.posWeightThirdRoundMultiplier,
    raterReputation=raterReputation,
    reputationExp=hParams.reputationExp,
    alpha=hParams.alpha,
    noteNormExp=hParams.noteNormExpThirdRound,
    raterNormExp=hParams.raterNormExpThirdRound,
    device=device,
  )

  loss_final = _train_one_round(model, loss_fn_final, dataset, hParams)
  return model, loss_final

  Args:
    hParams (ReputationModelHyperparameters)
    dataset (ReputationDataset)
    noteInitState (Optional[pd.DataFrame]): expects internal column names e.g. internalNoteIntercept
    raterInitState (Optional[pd.DataFrame]): expects internal column names e.g. internalRaterIntercept
  
sourcecode/scoring/reputation_matrix_factorization/reputation_matrix_factorization.py