mercoledì 29 dicembre 2010

Auto-learning playlist generator

Auto-rating algorithms for music players are quite primitive: if you listen to the song, the song gains a +1 rating, if you skip it, the rating stands still.

That's a quite basic method to give meaning to your choice to skip that particular song.

But... Maybe the song wasn't fitting the playlist mood or your mood in that moment.

What about those lovely 14-minutes-long songs? Skipping after a few seconds or after 10 minutes (maybe during that boring drums solo) should have different meanings.

What about these times when rand() doesn't love you and you skip 20 songs in a row? The player should be clever enough to present you with one of your favourites after a few clicks, so you can stop pressing Next and get back to listening.

How to deal with afk users? It happens: people get away from their computer and doesn't bother stopping their music player. Or maybe they are listening, but too lazy to get to computer to switch song. A "full play" should not reward much and a fast skip (skipping during the first 10-15 seconds) should penalize the song more than a full play.

Base play

Let's see one example: all songs starts with an internal rating of 10.

(A..Z) = 10
Base penalty

Our user starts playing from song A, but he don't like it now (why? back to that in a few moments). So he press Next after a few seconds, song A gets penalized.

A -= 0.5

Meta-data penalty

What about Artist, Album, Genre and Publishing Year? All those should be negatively affected, maybe toning down the rating a bit:

A.artist -= 0.2
A.album -= 0.2
A.genre -= 0.2
A.year -= 0.2

Timestamping rating associations

That's quite unfair: maybe song A doesn't fit with our current mood. Maybe we like that song (or artist, album, genre, year) in a different moment, so we should create an association between the timestamp and the bad rating, so we'll better write it as:

hour-of-the-day[A] -= 0.5
day-of-the-year[A] -= 0.5

It's the same for artist, album, etc:

hour-of-the-day[A.artist] -= 0.2
day-of-the-year[A.artist] -= 0.2
# [...]
hour-of-the-day[A.year] -= 0.2
day-of-the-year[A.year] -= 0.2

In the mood

Using these choices we could try to "learn" about the user mood with time. Every time the player starts it creates a new "mood profile" and eventually switches to an already existant mood profile if the user acts according to it.

So we can rewrite that as:

mood-profile01[ hour-of-the-day[A] ] -= 0.5
mood-profile01[ day-of-the-year[A] ] -= 0.5
# [...]
mood-profile01[ hour-of-the-day[A.year] ] -= 0.2
mood-profile01[ day-of-the-year[A.year] ] -= 0.2

Maybe we should consider merging similar profiles after a lot of learning.

Dealing with ossessive-compulsive skipping

Back to listening. Our user Next action has brought up another song chosen by his fellow rand() function, the song G. But it seems he doesn't like that also at the moment, so he press Next again. We should penalize song G, too, but maybe the user is only a bit lazy and we tone down the penalty, since it's the second fast-skip in a row:

mood-profile01[ hour-of-the-day[G] ] -= 0.4
mood-profile01[ day-of-the-year[G] ] -= 0.4
# [...]
mood-profile01[ hour-of-the-day[G.year] ] -= 0.1
mood-profile01[ day-of-the-year[G.year] ] -= 0.1

Our rand() function brings the user to listening to song M. He dislikes that too, so we tone down the penalty again:

mood-profile01[ hour-of-the-day[G] ] -= 0.25
mood-profile01[ day-of-the-year[G] ] -= 0.25
# [...]
mood-profile01[ hour-of-the-day[G.year] ] -= 0.03
mood-profile01[ day-of-the-year[G.year] ] -= 0.03

And so on.

Context matters

Now, what about the context? Our fellow user listen flawlessly to the 4 next songs: song V, F, I, P, each of which gains a little award:

mood-profile01[ hour-of-the-day[V] ] += 0.1
mood-profile01[ day-of-the-year[V] ] += 0.1
# [...]
mood-profile01[ hour-of-the-day[V.year] ] -= 0.01
mood-profile01[ day-of-the-year[V.year] ] -= 0.01

Then the user stumbles on song X. Maybe that song doesn't fit the current playlist (with time that should never occur), so we penalize it:

mood-profile01[ hour-of-the-day[V] ] -= 0.5
mood-profile01[ day-of-the-year[V] ] -= 0.5
# [...]
mood-profile01[ hour-of-the-day[V.year] ] -= 0.2
mood-profile01[ day-of-the-year[V.year] ] -= 0.2

And then we penalize the playlist association (maybe we can use less songs, like the last 3 or so):

mood-profile01[ playlist(V, F, I, P, X) ] -= 0.5
mood-profile01[ playlist(V, F, I, P, X) ] -= 0.5

Dealing with urgency

If you're skipping a song in the first few (10-15) seconds of playing, we're giving a 0.5 points penalty to that song (in that particular moment). But what about a skip that occurs when you've listened up to 95% of the song?

Penalty should scale so it's 0.5 for a skip during the first seconds and then tone down to 0.4 for the first quarter of the song, 0.25 for the first half and 0.1 if the skip occurs past half song.

Getting tags into consideration

At the moment I'm not aware of players supporting tags for songs, but that would be a great feature (maybe checking the associations on last.fm or any other similar site would be a plus). Of course with such a feature in place, profiling would be even easier. We could take into account also the tag list, like:

mood-profile01[ hour-of-the-day[V.tags] ] -= 0.2
mood-profile01[ day-of-the-year[V.tags] ] -= 0.2

Nessun commento:

Posta un commento