Open chezou opened 3 months ago
Hi! That would be great, we've been discussing this feature some time ago. Added to backlog. But right now priority is low for our team since we are focusing on more complicated models in the nearest future. But we would be absolutely happy to help if someone takes it outside our team. Just contact us in Telegram if you ever want to contribute.
Lesson learned in the first attempt:
fit()
requirement.
fit()
before partial_fit_users()/items()
, however, LightFM's fit_partial()
is equivalent with fit()
without initialization.Let's figure it out. My thoughts are:
fit_partial
when it's not fitted.fit_partial
should always have epochs
parameter. It's important both for epochal and incremental training.From these points I see the following priorities:
fit_partial
method and epochs
parameter. Support for partial_fit
from unfitted state for both models. Tests that epochal training provides exactly the same embeddings as usual training.fit_partial
method for ALS with features. This requires major refactoring of our methods of ALS training.fit_partial
method.rebuild_with_new_data
method from the last PR (but it's very raw for now). We can discuss it in details when we come to this. Features must be carefully checked, we must guarantee exactly the same order of features in the feature matrix. Otherwise we will break embeddings linkage to features from the fitted model.Thanks for your detailed explanation. It makes clear understanding now.
I agree with not allowing adding new features. In general supervised learning tasks, we fix the input feature, even for fine-tuning. If we want to use different features, then we should train the model from scratch. That's a completely different model.
Supporting new users/items is critical for recommender systems. 100%.
One concern that comes to mind is providing fit_partial()
method to ALS without fitting. There can be two options:
fit()
method if not fitted.ALS.fit()
method into RecTools' fit_partial()
method.In either option, we have to be careful that ALS.fit()
can use two solvers while ALS.partial_fit_users()/items()
can use a single solver.
https://github.com/benfred/implicit/blob/b33b809cb585cb8a65ad39d0f97497d37e98acaa/implicit/cpu/als.py#L420-L422
If RecTools enforces not using Conjugate Gradient ALS by passing use_cg=False
, RecTools' fit()
and fit_partial()
methods can be the same output. However, that means a user can't benefit from faster matrix factorization of Implicit. ref: https://github.com/benfred/implicit/commit/4139e0ae19eb227c9d3a1358fefc4dff57f627b5
How do you prioritize the tradeoff between consistent training results and training speed? Of course, we may be able to give users the option of using Conjugate Gradient ALS or not.
If we agree that adding support for features in epochal and incremental training of ALS is important, then we should take it into account already.
ALS.partial_fit_users()/items()
if we select this way of implementation.ALS.partial_fit_users()/items()
we have simpler code for now. But: 1) we can't support features, it's gonna be a mess. 2) we can't support using exactly the same solver that user selected => we can't guarantee that epochal training will give us same results as usual training. My opinion that this is a bad way to choose, it is messy and confusing. But I'm completely open to any other opinions.If we drop ALS.partial_fit_users()/items()
in implementation and thinks about adding support for features in epochal training I see the following steps:
_fit
for any number of epochs, not just ImplicitALSWrapperModel.model.iterations
number. This will enable epochal training with epochs
argument after fit_partial
method is introduced.
1.2 Make clear distinction in the code between factors initialization and factors training. This will enable continuing training after model was fitted.Last point it the most difficult one. We have 2 methods to refactor: fit_als_with_features_separately_inplace
and fit_als_with_features_together_inplace
.
When calling these methods for an already fitted model of course we shouldn't make a deepcopy of an unfitted model (https://github.com/MobileTeleSystems/RecTools/blob/8a3b7165a6928507a4edfe01ecd100280b7a0a00/rectools/models/implicit_als.py#L73) but just pass self.model instead
Let's dive deeper.
fit_als_with_features_together_inplace
For now for epochal training we assume dataset hasn't changed. So for fitted model we shouldn't init user_explicit_factors
, user_latent_factors
and user_factors_paired_to_items
from scratch. We should take learnt factors from model current state.
if self.is_fitted:
user_factors = self.get_user_vectors()
item_factors = self.get_item_vectors()
and then call _fit_combined_factors_on_gpu_inplace
with additional epochs
argument (same for cpu)
_fit_combined_factors_on_gpu_inplace
We can leave the whole code of this method as is. But we need to change ImplicitALSWrapperModel.model.iterations
attribute to the required number before calling it (and then change it back afterwards).
@feldlime please check me carefully :) and write your opinion
@chezou please tell me what you think and you have any questions/doubts? Are you willing to do this part of work?
I would suggest to do this refactoring first. And then introduce fit_partial
method for both ALS and LightFM in one PR to enable epochal training, and then work in incremental training
@chezou if you don't feel comfortable with ALS refactoring PR, we can do it ourselves. And afterwards you can continue with further PRs
@blondered Sorry, I've been super busy with my work. Please go ahead for refactoring PR since I'm not confident enough.
As long as ALS.recalculate_user()/item()
will be eventually re-implemented and user/item adding will be supported in the future, I agree with dropping ALS.partial_fit_users()/items()
.
Ok. It will not be immediate bit we'll try to do it as soon as we can.
@chezou
Hi! I finished refactoring ALS code. As soon as we merge https://github.com/MobileTeleSystems/RecTools/pull/203 it is possible to open PRs for partial_fit
.
Great news is that partial_fit
will work with features for ALS.
Please write to me if you still want to do this :)
@chezou now that we've merged ALS refactoring, we can move on.
I suggest next steps to be:
partial_fit
in ModelBase
+ iALS partial_fit
implementation. We already have tests written for this model so that should be quite easy.partial_fit
implementation for LightFMI can take the first PR (partial_fit
for iALS) if you don't have the time right now. Would be great to add it to the upcoming release.
@blondered Sorry for the delayed response.
For personal reasons, I will be busy in the coming weeks.
I'm happy to take the first PR if it's not urgent. I think I can find some time at the end of this month. If urgent, please go ahead on your end.
@chezou I will write to you if I will take it myself. A bit busy too.
@chezou found some time to complete partial_fit for ALS: https://github.com/MobileTeleSystems/RecTools/pull/210
Feature Description
Implicit's ALS and LightFM provide partial_fit capability. https://benfred.github.io/implicit/api/models/cpu/als.html#implicit.cpu.als.AlternatingLeastSquares.partial_fit_users https://benfred.github.io/implicit/api/models/cpu/als.html#implicit.cpu.als.AlternatingLeastSquares.partial_fit_items https://making.lyst.com/lightfm/docs/lightfm.html#lightfm.LightFM.fit_partial
It would be great if RecTools had a standardized way to use them for incremental model updates.
Why this feature?
If we can use partial_fit, we can avoid full re-training every time. This is beneficial when the original model is trained by a massive dataset.
Additional context
No response