ankane / disco

Recommendations for Ruby and Rails using collaborative filtering
MIT License
565 stars 11 forks source link

Scope of recommendation #12

Closed sthyregod closed 3 years ago

sthyregod commented 3 years ago

I've got a use case where I would like to put a scope on the recommendation. My setup involves two separate domains that are interconnected in the backend. Here it is important to filter out any items that are not set as visible on the current domain of the request.

So:

{ item: 1, sites: [example.com, example.eu] },
{ item: 2, sites: [example.eu] },
{ item: 3, sites: [example.com] },
{ item: 4, sites: [] },

In the case that the visitor accesses example.com I want only item 1 and 3 to be considered in the recommendation and if they visit example.eu, only item 1 and 2 should be considered.

I'm currently doing a bit of a workaround in regards to filtering them after running the recommendation, but it's not ideal as I can't guarantee a good result as some of the outputs will be filtered. Are there another way to do this sorting beforehand that I've overlooked?

ankane commented 3 years ago

Hey @sthyregod, post-filtering (like you're doing now) should work, or you could create a separate recommender for each site and save recommendations separately.

class User < ApplicationRecord
  has_recommended :items_com
  has_recommended :items_eu
end
sthyregod commented 3 years ago

I haven't diven deep down into the inner workings of it, but wouldn't post-filtering result in some of the recommended items being omitted, e.g. example.com is maybe much more popular than example.eu and as such if I find recommendations with a count of 10 and 8 of them belong to .com but I'm looking for .eu related items, meaning I'd only get 2 results after filtering? This would most likely not be a problem in most cases I can think of but I'm not fond of letting it up to fate whether it covers all bases :p

I totally missed that the separate recommenders could work for multiple domains too. It could work for now but a new recommender would need to be created in case a new site is created (my use case does look like it could expand in the future, possibly making the approach a bit cumbersome).

I realised that I simply could add the site to the Ahoy events though and filter the input data there before they even hit the recommender. Sometimes I just gotta sleep on my problems for one more night before raising an issue... In case others sit with the same problem I am looking at something like this as a solution:

def show
   ...
   ahoy.track 'View item', item_id: @item.id, site: @site
end

def recommendations(site_domain)
  views = Ahoy::Event.where('name = ? AND properties @> ?', 'View product', { 'site': site_domain }.to_json)
  recommender = Disco::Recommender.new
  recommender.fit(data)
  ...
end

The solution might still suffer from when items change site but I don't think that is an issue I'll encounter. I'll keep the post-filtering as backup.

Anyhow, is there a reason the gem is not integrated with the models themselves? I could imagine it might make some things easier like the problem of filtering out a site or an item category. I thought it might be a performance problem but I found it was a bit weird since the use case is most likely always models in, models out. The multiple-find (Item.find [1, 2, 3, 4]) shortcut of Rails does make that easier though.

ankane commented 3 years ago

For post-filtering, you'd likely want to have a metric on how often there were too few recommendations and increase the number of stored recommendations if needed.

For separate recommenders, an approach like the code above should work, but you'll likely want to store recommendations, the recommender, or factors to serve recommendations.

Anyhow, is there a reason the gem is not integrated with the models themselves?

There is some model integration, but feel free to create a new issue if you have a specific proposal for new functionality.

sthyregod commented 3 years ago

For separate recommenders, an approach like the code above should work, but you'll likely want to store recommendations, the recommender, or factors to serve recommendations.

My project isn't that big at the moment so I opted for just using an on-the-fly approach as a sort of cost/time vs. benefit. Although it seems like there's some objects that aren't released from memory around that code so I might have to do it properly.

There is some model integration, but feel free to create a new issue if you have a specific proposal for new functionality.

I think I see what you mean now, I assume models will be returned when fetching stored recommendations. If I find time I'd surely look into stuff I can help with, unfortunately I'm a bit swarmed by ComSci study and this job currently so it'll have to wait.

ankane commented 3 years ago

Just pushed a new version of the libmf gem (0.2.4) that fixes the memory leak.