Lightfm: handling user and item cold-start
Asked Answered
I

2

15

I remember one of the strong points of lightfm is that the model does not suffer from cold start problem, both user and item cold start: lightfm original paper

However, I still don't understand how to use lightfm to address the cold start problem. I trained my model on user-item interaction data. as I understand, I can only make prediction on profile_ids that exist on my data set.

def predict(self, user_ids, item_ids, item_features=None,
            user_features=None, num_threads=1):
    """
    Compute the recommendation score for user-item pairs.

    Arguments
    ---------

    user_ids: integer or np.int32 array of shape [n_pairs,]
         single user id or an array containing the user ids for the
         user-item pairs for which a prediction is to be computed
    item_ids: np.int32 array of shape [n_pairs,]
         an array containing the item ids for the user-item pairs for which
         a prediction is to be computed.
    user_features: np.float32 csr_matrix of shape [n_users, n_user_features], optional
         Each row contains that user's weights over features.
    item_features: np.float32 csr_matrix of shape [n_items, n_item_features], optional
         Each row contains that item's weights over features.
    num_threads: int, optional
         Number of parallel computation threads to use. Should
         not be higher than the number of physical cores.

    Returns
    -------

    np.float32 array of shape [n_pairs,]
        Numpy array containing the recommendation scores for pairs defined
        by the inputs.
    """

    self._check_initialized()

    if not isinstance(user_ids, np.ndarray):
        user_ids = np.repeat(np.int32(user_ids), len(item_ids))

    assert len(user_ids) == len(item_ids)

    if user_ids.dtype != np.int32:
        user_ids = user_ids.astype(np.int32)
    if item_ids.dtype != np.int32:
        item_ids = item_ids.astype(np.int32)

    n_users = user_ids.max() + 1
    n_items = item_ids.max() + 1

    (user_features,
     item_features) = self._construct_feature_matrices(n_users,
                                                       n_items,
                                                       user_features,
                                                       item_features)

    lightfm_data = self._get_lightfm_data()

    predictions = np.empty(len(user_ids), dtype=np.float64)

    predict_lightfm(CSRMatrix(item_features),
                    CSRMatrix(user_features),
                    user_ids,
                    item_ids,
                    predictions,
                    lightfm_data,
                    num_threads)

    return predictions

any suggestions or pointers to help my understanding would really be appreciated. thank you

Ireland answered 25/10, 2017 at 4:40 Comment(1)
Probably you can try a random intialization and based on more nd more new real data, the model should predict it right. If the initial features are all replaced start involving the user / id to the (online) training.Tug
C
12

LightFM, like any other recommender algorithm, cannot make predictions about entirely new users if it is not given additional information about those users. The trick when trying to make recommendations for new users is to describe them in terms of the features that the algorithm has seen during training.

This is probably best explained using an example. Suppose you have users with IDs between 0 and 10 in your training set, and you want to make predictions for a new user, ID 11. If all you had was the ID of the new user, the algorithm would not be able to make predictions: after all, it knows nothing about what the preferences of user 11 are. Suppose however, that you have some features to describe the users: maybe during the sign-up process every user chooses a number of interests they have (horror movies or romantic comedies, for example). If these features are present during training, the algorithm can learn what preferences are, on average, associated with these characteristics, and will be able to produce recommendations for any new users who can be described using the same characteristics. In this example, you would be able to make predictions for user 11 if you could supply the preferences they chose during the sign-up process.

In the LightFM implementation, all of these features will be encoded in the feature matrices, probably in the form of one-hot encoding. When making recommendations for user 11, you would construct a new feature matrix for that user: as long as that feature matrix contains only the features present during training, you will be able to make predictions.

Note that it is normally useful to have a feature that corresponds only to a single user --- so a 'Is user 0' feature, a 'Is user 1' feature and so on. In the case of new users, such a feature is useless, as there is no information in training that the model could use to learn about that feature.

Catercorner answered 28/10, 2017 at 5:49 Comment(6)
hi @Maciej Kula thanks for the explanation, now I understand that the cold start problem for either user or item can be solved by incorporating the features to the model. However, it still unclear to me on how should I do this. So, given a new user, I would construct a new feature matrix for that user, and then 1) should I build a function to find similar user according to this feature matrix? or 2) can I directly use the model that I trained to generate a recommendation to this new user? thanks againIreland
You would build a feature matrix (a row, really) for the new user and use it directly in the model.predict methods. Part of the responsibility of the model is to automatically model that similarity for you.Catercorner
ah got it, but for the ids parameter, what should I put?Ireland
In this case, you can just set all the ID-related columns to zero.Catercorner
@Bohr...can you please share your complete code for the above problem? Also did you use any other approach for solving cold start problem apart from this approach?Verso
Hi @Ireland would you be willing to share your code on how you solved this issue of adding the new user? I am trying to solve that problem myself but I am struggling a bit.. thank you!Gassman
N
8

This worked for me:

if user_index is not None:
    predictions = model.predict([user_index, ], np.array(target_item_indices))
else:
    predictions = model.predict(0, np.array(target_item_indices), user_features=user_features)

Here, user_features is a sparse array, that's been assembled carefully from the feature set that was used when training the model.

E.g. if I get a new user, and the user's features are something like user_feature_list = ["id-2837", "Cape Town", "Woodstock", 7700], then I build the feature array as follows:

from scipy import sparse

user_feature_map = store_model.user_feature_map  # the feature map was persisted during the previous round of offline training
num_features = len(user_feature_list)
normalised_val = 1.0 / num_features
target_indices = []
for feature in user_feature_list:
    try:
        target_indices.append(user_feature_map[feature])
    except KeyError:
        print("new user feature encountered '{}'".format(feature))
        pass
print("target indices: {}".format(target_indices))
user_features = np.zeros(len(user_feature_map.keys()))
for i in target_indices:
    user_features[i] = normalised_val
user_features = sparse.csr_matrix(user_features)

The user_feature_map was generated previously by calling LightFM's mapping() method on the original input dataset, after fitting:

dataset.fit(
    unique_user_ids,
    unique_item_ids,
    item_features=item_feature_list,
    user_features=user_feature_list
)

user_id_map, user_feature_map, item_id_map, item_feature_map = dataset.mapping()
Nebulous answered 13/8, 2018 at 9:41 Comment(5)
in you second code snippet, you have written store_model.user_feature_map, what is store_model object. also, when you get user_id_map, user_feature_map, item_id_map, item_feature_map = dataset.mapping() the suer_feature_map is just giving me same as user_id_map , ie user vs user encoding mapping. lastly, does that mean, for a cold start problem, you have to have user_features, created from dataset.build_user_features method of dataset.Columnist
@Nebulous Is there any special reason you used index 0 ``` model.predict(0, np.array(target_item_indices), user_features=user_features) ``` Also, do you have an example for item cold start?Stria
@Max, the id must correspond to an index (row) in the user_features (CSR) matrix (which has num_features columns). So if you construct the user_features matrix to only contain 1 row it will correctly select that row of features. If the id does not correspond to a row you will receive the exception *** Exception: Number of user feature rows does not equal the number of usersBraden
i'm quite confused on how the example provided could work. My user_feature_map produced with mapping method is a long dictionary with keys made up of userid and tuple of features + user id. for example ('user_id','feat_0:val','feat_1:val2') . How this instructions user_feature_map[feature] could work in this setting?Ld
There is no evidence why user 0, should be used for the prediction. Although it might work, this is not a stable, rocust way to deal with cold user recomendations.Motorboating

© 2022 - 2024 — McMap. All rights reserved.