def run_mf(
    self,
    ratings: pd.DataFrame,
    noteInit: pd.DataFrame = None,
    userInit: pd.DataFrame = None,
    globalInterceptInit: Optional[float] = None,
    specificNoteId: Optional[int] = None,
    validatePercent: Optional[float] = None,
    freezeNoteParameters: bool = False,
    freezeRaterParameters: bool = False,
    freezeGlobalParameters: bool = False,
    ratingPerNoteLossRatio: Optional[float] = None,
    ratingPerUserLossRatio: Optional[float] = None,
    flipFactorsForIdentification: bool = True,
    run_name: str = "",
  ):
    """Train matrix factorization model.

    See https://twitter.github.io/communitynotes/ranking-notes/#matrix-factorization

    Args:
        ratings (pd.DataFrame): pre-filtered ratings to train on
        noteInit (pd.DataFrame, optional)
        userInit (pd.DataFrame, optional)
        globalInterceptInit (float, optional).
        specificNoteId (int, optional) Do approximate analysis to score a particular note

    Returns:
        Tuple[pd.DataFrame, pd.DataFrame, float]:
          noteParams: contains one row per note, including noteId and learned note parameters
          raterParams: contains one row per rating, including raterId and learned rater parameters
          globalIntercept: learned global intercept parameter
    """
    if run_name:
      unique_run_id = f"{run_name}_{int(time.time())}"
      run_name += "/"
    else:
      unique_run_id = f"mf_run_{int(time.time())}"

    # Store hyperparameters in wandb config
    config = {
      "initLearningRate": self._initLearningRate,
      "noInitLearningRate": self._noInitLearningRate,
      "convergence": self._convergence,
      "numFactors": self._numFactors,
      "useGlobalIntercept": self._useGlobalIntercept,
      "useSigmoidCrossEntropy": self._useSigmoidCrossEntropy,
      "posWeight": self._posWeight,
      "userFactorLambda": self._userFactorLambda,
      "noteFactorLambda": self._noteFactorLambda,
      "userInterceptLambda": self._userInterceptLambda,
      "noteInterceptLambda": self._noteInterceptLambda,
      "globalInterceptLambda": self._globalInterceptLambda,
      "diamondLambda": self._diamondLambda,
      "ratingPerNoteLossRatio": ratingPerNoteLossRatio,
      "ratingPerUserLossRatio": ratingPerUserLossRatio,
      "freezeNoteParameters": freezeNoteParameters,
      "freezeRaterParameters": freezeRaterParameters,
      "freezeGlobalParameters": freezeGlobalParameters,
      "validatePercent": validatePercent,
      "flipFactorsForIdentification": flipFactorsForIdentification,
    }
    logger.info(f"Reinitializing wandb run with enabled state: {wandb._enabled}")
    wandb.reinitialize(unique_run_id, config=config)
    self._ratingPerNoteLossRatio = ratingPerNoteLossRatio
    self._ratingPerUserLossRatio = ratingPerUserLossRatio

    self._initialize_note_and_rater_id_maps(ratings)

    self._create_mf_model(noteInit, userInit, globalInterceptInit)
    assert self.mf_model is not None

    logger.info(
      f"Ratings per note in dataset: {len(ratings)/self.mf_model.note_factors.weight.data.shape[0]}"
    )
    logger.info(
      f"Ratings per user in dataset: {len(ratings)/self.mf_model.user_factors.weight.data.shape[0]}"
    )
    if ratingPerNoteLossRatio is not None:
      logger.info(
        f"Correcting loss function to simulate rating per note loss ratio = {ratingPerNoteLossRatio}"
      )
    if ratingPerUserLossRatio is not None:
      logger.info(
        f"Correcting loss function to simulate rating per user loss ratio = {ratingPerUserLossRatio}"
      )

    if freezeRaterParameters:
      self.mf_model._freeze_parameters(set({"user"}))
    if freezeGlobalParameters:
      self.mf_model._freeze_parameters(set({"global"}))
    if freezeNoteParameters:
      self.mf_model._freeze_parameters(set({"note"}))
    if specificNoteId is not None:
      self.mf_model.freeze_rater_and_global_parameters()
    self.prepare_features_and_labels(specificNoteId)

    train_loss, loss, validate_loss = self._fit_model(validatePercent, run_name=run_name)
    if self._normalizedLossHyperparameters is not None:
      _, raterParams = self._get_parameters_from_trained_model(flipFactorsForIdentification)
      assert self.modelData is not None
      self._lossModule = NormalizedLoss(
        self.criterion,
        self.ratingFeaturesAndLabels,
        self.modelData.rating_labels,
        self._normalizedLossHyperparameters,
        self._labelCol,
        raterParams,
        device=self.mf_model.device,
      )
      self._create_mf_model(None, userInit, None)
      train_loss, loss, validate_loss = self._fit_model(validatePercent, run_name=run_name)
      self._lossModule = None

    assert self.mf_model.note_factors.weight.data.cpu().numpy().shape[0] == self.noteIdMap.shape[0]

    globalIntercept = None
    if self._useGlobalIntercept:
      globalIntercept = self.mf_model.global_intercept.item()
      if self._log:
        logger.info(f"Global Intercept: {globalIntercept}")

    fitNoteParams, fitRaterParams = self._get_parameters_from_trained_model(
      flipFactorsForIdentification
    )

    wandb.finish()

    fitRaterParams.drop(Constants.raterIndexKey, axis=1, inplace=True)
    if validatePercent is None:
      return fitNoteParams, fitRaterParams, globalIntercept
    else:
      return (
        fitNoteParams,
        fitRaterParams,
        globalIntercept,
        train_loss,
        loss,
        validate_loss,
      )
Train matrix factorization model.

    See https://twitter.github.io/communitynotes/ranking-notes/#matrix-factorization

    Args:
        ratings (pd.DataFrame): pre-filtered ratings to train on
        noteInit (pd.DataFrame, optional)
        userInit (pd.DataFrame, optional)
        globalInterceptInit (float, optional).
        specificNoteId (int, optional) Do approximate analysis to score a particular note

    Returns:
        Tuple[pd.DataFrame, pd.DataFrame, float]:
          noteParams: contains one row per note, including noteId and learned note parameters
          raterParams: contains one row per rating, including raterId and learned rater parameters
          globalIntercept: learned global intercept parameter
    
sourcecode/scoring/matrix_factorization/matrix_factorization.py