Showing posts with label tagging. Show all posts
Showing posts with label tagging. Show all posts

Wednesday, 17 September 2008

MIREX 2008

This year's MIREX evaluation task has been one of my personal ISMIR 2008 highlights. Stephen Downie and his team computed more numbers than I could possible keep track of for lots of different algorithms in 12 different MIR tasks. That's a lot more than in any of the previous years, and that's a lot of interesting data to dig into.

I've been particularly interested in the auto-tagging task. It's the first time MIREX ran this form of task, and there have been only few research papers in the MIR community on the subject. As far as I understood there is no agreement yet as to how to exactly evaluate the algorithms, which is also reflected on the result page. Kris West has added information on the statistical significant of the results which show that none of the submissions was consistently and significantly better than others. Nevertheless, there's a lot to learn from the evaluation and I hope we'll see many more participants next year.

Paul has a good summary of the discussion of this year's MIREX panel.

Thursday, 11 September 2008

ISMIR 2008 Tutorial: Social Tags and Music Information Retrieval

When Paul originally asked me if I'd be interested in helping him put together a tutorial proposal for ISMIR I was a bit reluctant. I did an ISMIR tutorial a long time ago, and I didn't forget how much work it was (although it was only a one hour mini tutorial). In fact, only last year Paul and Oscar told me how much work it was to put together their recommendation tutorial (which I liked a lot).

Anyway, somehow I couldn't say no and looking back I don't regret it at all. I'm totally fascinated by social tags. There's no tutorial topic I'd rather talk about. Working together with Paul has been a great pleasure, and I've learned a lot. However, I'm very much looking forward to have a free weekend or even just a free evening again. (Free as in not-MIR-related.)

Paul and I have put together about 200 slides for our tutorial. I think we still need to figure out our time budget. Hopefully we'll have plenty of time for interesting discussions.

Btw, as part of the tutorial we started compiling a list of relevant papers. That list started to grow. Then we thought it would be a good idea to group papers into topics (e.g. autotagging). Then we realized that several papers were in several categories... which is when we moved everything to delicious. In particular, we started using the tag "SocialMusicResearch" to mark interesting things we found in the Internet. Here's a list of some of the items we have tagged. We hope that others will start using that tag as well.

Btw, if you are going to ISMIR, please say hi! If you don't know what I look like: try to spot the guy wearing a Last.fm t-shirt.

Monday, 25 August 2008

Librarians and Tags

Yves pointed me to this really nice presentation by a librarian who seems to have a really good understanding of tagging. The only part missing in that presentation is music.

Getting Last.fm Tags for MP3s with Python

Paul (who is already distributing a large chunk of Last.fm tags) and I are planing to include a few slides in our ISMIR tutorial on how to obtain tag data.

Below is some Python code that basically takes an MP3 file as input and outputs a list of Last.fm tags (for both artist and track). The MP3s don't need correct ID3 tags, but they need to be full length (clips won't work).

The Python code uses Norman's command line finger printing client to find the correct artist and track name. The path to the executable needs to be set in the code. Norman supports Win32, OSX Intel, Linux - 32.

The output is written to a file. For each MP3 file passed as argument there are up to two rows in the output file: one for the artist tags, and one for the track tags. Each row has the format: "<mp3filename> <encoded artist or artist/track name> <tag> <score> [<tag> <score> ...]". Tabs are used as delimiters.

The data from the Last.fm API is available under the Creative Commons Attribution-Non-Commercial-Share Alike License.

Btw, special thanks to Eric Casteleijn for various Python recommendations (lxml etc). (Which reminds me that I still need to fix the other Python code I posted.) As usual any feedback is much appreciated.

import subprocess, sys, re, time, urllib
from lxml import etree

FP_CLIENT_PATH = '"C:\\fpclient\\lastfmfpclient.exe"'
MAX_RETRIES_URL_OPEN = 5

def getArtistTrack(mp3FileName): # ret: (artist, track)
command = FP_CLIENT_PATH + ' ' + mp3FileName
pipe = subprocess.Popen(command, \
stdout=subprocess.PIPE).stdout
for line in pipe:
mo = re.search('<url>.*/([^/]+)/_/(.+)<',line)
if mo:
return urllib.quote(mo.group(1)), \
urllib.quote(mo.group(2))
print "ERROR: failed to get artist/track for: " + \
mp3FileName

def crawlTags(url): # ret: [(tag, count), ...]
for i in xrange(MAX_RETRIES_URL_OPEN):
tagCounts = []
time.sleep(1) # be nice!
try:
root = etree.parse(
urllib.urlopen(url)).getroot()
except IOError:
print "(%d/%d) Failed trying to get: %s." % \
(i, MAX_RETRIES_URL_OPEN, url)
else:
for tag in root.iter('tag'):
tagCounts.append(
(tag.find('name').text, \
tag.find('count').text))
return tagCounts

def tags(prefix, items, outStream): # crawl and write
for mp3FileName, item in items:
url = prefix + item + '/toptags.xml'
print url
tagCounts = crawlTags(url)
outStream.write('%s\t%s\t%s\n' %
(mp3FileName, item, '\t'.join(
tag + '\t' + str(count)
for tag, count in tagCounts)))

def main():
if len(sys.argv)<3:
print 'USAGE: python getTags.py ' + \
'<outFile> <f1.mp3> [<f2.mp3> ...]'
sys.exit(2)
outFile = sys.argv[1]
mp3FileNames = sys.argv[2:]
artists = set()
artistTracks = set()
for mp3FileName in mp3FileNames:
print 'Fingerprinting: ' + mp3FileName
artist,track = getArtistTrack(mp3FileName)
artists.add((mp3FileName, artist))
artistTracks.add((mp3FileName,
artist + '/' + track))

print 'start crawling tags'
o = open(outFile,'w');
tags('http://ws.audioscrobbler.com/1.0/artist/', \
artists, o)
tags('http://ws.audioscrobbler.com/1.0/track/', \
artistTracks, o)
o.close()

if __name__ == "__main__":
main()

Sunday, 24 August 2008

Tagging Critics

I was doing some research for the ISMIR tag tutorial when I stumbled upon (via this interesting paper Playing Tag: An Analysis of Vocabulary Patterns and Relationships Within a Popular Music Folksonomy by Abbey E. Thompson):

The following expert from this paper:

[...] "tags are often ambiguous, overly personalised and inexact" [...] "The result is an uncontrolled and chaotic set of tagging terms that do not support searching as effectively as more controlled vocabularies do." [...]

This was published in the D-Lib magazine in early 2006. I wouldn't be surprised if by now the authors realized they were wrong.

But why would anyone ever want to control the vocabulary people use when describing something so extremely multifaceted and something that evolves so fast like the content on the web (delicious), or snapshots of life (flickr), or music (Last.fm)? I guess I'd need to think more like an old-skool librarian to understand that.

Saturday, 23 August 2008

Tagging Games (Callabio on Facebook)

Did Microsoft researchers clone the scoring system MajorMiner uses for a tagging game on Facebook? Well, maybe it's not identical, but it seems kind of similar. And I guess it could be lots of fun.

Btw, if scoring points is what drives people to play that game, what would stop them from entering a whole dictionary?

Sunday, 29 June 2008

Last.fm's API, Python, and tagging behaviour (Part 2)

Update: (2008/08/25) I fixed the same kind of bug I had in the previous post on this topic. While fixing it I decided to rerun it with a sample of 5000 instead of 2000 users. The code is fixed and the data is updated.

Last night I was a bit tired and quickly concluded "the numbers show...". But do they really?

To find out I use Python to do some simple statistical analysis to add some weight to the claim that older Last.fm users have a larger vocabulary and tag items more frequently.

First, I compute 95% confidence intervals for the percentage of non-taggers in each age group. Seeing the large margins (in the table below) helps explain why the age group 25-30 has a higher percentage than the age group 19-22. However, it doesn’t help explain why the age group 22-25 has a lower percentage. (I’d blame that on the relatively small and skewed sample, and I’d argue that they are still reasonably similar, both in average age and deviations of the percentages). (With the larger sample size this is not the case any longer.)

Computing the confidence interval is very easy. A user is either a tagger or not. The probability within an age group can thus be modeled with a Bernoulli distribution. The 95% confidence intervals for a Bernoulli distribution can be computed with:
z = 1.96 # norminv(0.975) for a 95% confidence interval
def binom_confidence(p,n):
if n*p*(1-p) >= 5:
return z*(p*(1-p)/n)**0.5

Btw, I couldn’t find the equivalent to the Matlab norminv function in Python. Any pointers would be appreciated!

To test the hypothesis that the vocabulary size of a tagger depends on her or his age I test the following: Given my observations, are all age groups likely to have the same vocabulary size, i.e, are the differences I observed just random noise? Since the distributions within each age group are far from Gaussian I can’t use a standard ANOVA. Instead I use the non-parametric version of a one-way ANOVA which is the Kruskal-Wallis test. In particular, I use the test to compute a p-value. The p-value is the probability that I would have made the same observation if the hypothesis that there is no difference between age groups would be true. (Thus smaller p-values are better. Usually one would expect at least a value below 0.05 before accepting an alternative hypothesis.) In this case the resulting p-value is nice and low indicating that it's extremely unlikely that older users don't have larger vocabularies.

Here are the results, and below is the Python code.

age || % non taggers || tagger's median
vocabulary size
14-19 || 41.3-48.1 || 6
19-22 || 37.5-43.6 || 7
22-25 || 40.6-47.3 || 9
25-30 || 34.8-41.4 || 8
30-60 || 28.4-36.0 || 13
Kruskal-Wallis p-value: 1.04e-008


from scipy.stats.stats import kruskal
from numpy import asarray

def print_stats(age_tags):
age_groups = (14,19,22,25,30,60)
ll = []
print "age || % non taggers || " + \
"tagger's median \n" + \
" vocabulary size"
for i in xrange(0,len(age_groups)-1):
nonzeros = [];
zero_count = 0
for j in xrange(age_groups[i],age_groups[i+1]):
for item in age_tags[j]:
if item!=0:
nonzeros.append(item)
else:
zero_count += 1
conf = binom_confidence(
zero_count/float(zero_count+len(nonzeros)),
zero_count+len(nonzeros))
ll.append(nonzeros);
print \
"%d-%d || %8.1f-%.1f || %2d" % \
(age_groups[i],age_groups[i+1],
(zero_count/float(max((1,len(nonzeros)+\
zero_count)))-conf)*100,
(zero_count/float(max((1,len(nonzeros)+\
zero_count)))+conf)*100,
median(nonzeros))
p = kruskal(*(asarray(ll[i]) for i in
xrange(len(age_groups)-1)))[1]
print "Kruskal-Wallis p-value: %.2e" % p

Btw, that part where I use eval to convert my lists into function arguments could hardly be any uglier. I’m sure there must be a better way of doing that? (Thanks Klaas!)

Last.fm's API, Python, and tagging behaviour

Update: (2008/08/25) I fixed the bug pointed out by thisfred. And I noticed that what I thought was the percentage of non-taggers was actually the ratio of non-taggers vs taggers... I changed that now.

My colleagues completely redesigned the Last.fm API. Inspired by their efforts and all the amazing things the Last.fm community has already built with the old API I decided that I wanted to try doing something with the API as well. The first thing that came to my mind was to use the public API to show that younger people have a smaller tagging vocabulary than older people. I couldn't figure out how to get a user's age from the new API so I used the old one. Anyway, here are the results and I also included the Python script I used. (Btw, any feedback on my Python coding is very welcome, I'm still very much a Python newbie.)

I crawled about 2000 users starting with RJ as seed. The first column is the age group, the second column is the ratio of users who haven't used any tags vs number of users who have used tags, the last number is the median number of unique tags which users who have applied tags have used.

14-19: zeros: 0.44 (120/155), median tags: 6
19-22: zeros: 0.42 (153/215), median tags: 6
22-25: zeros: 0.38 (114/184), median tags: 9
25-30: zeros: 0.43 (141/188), median tags: 10
30-60: zeros: 0.31 (79/179), median tags: 11

The numbers show that older users tag more and apply more unique tags.

from xml.dom import minidom
from urllib import quote, urlopen
from time import sleep
from numpy import median
from collections import defaultdict

seed = 'RJ' # start with Last.fm's CTO
MAX_RETRIES_URL_OPEN = 5

def get_xml(url):
for i in xrange(MAX_RETRIES_URL_OPEN):
try:
sleep(1) # be nice!
return minidom.parse(urlopen(url))
except IOError:
print "(%d/%d) Failed trying to get: %s." % \
(i, MAX_RETRIES_URL_OPEN, url)

def get_friends(user, friends, ignore_friends):
url = u'http://ws.audioscrobbler.com/1.0/user/' \
+ quote(user) + u'/friends.xml'
xmldoc = get_xml(url)
xmlusers = xmldoc.getElementsByTagName("user")
for user in xmlusers:
u = user.getAttribute("username")
if u not in ignore_friends:
friends.add(u)
print "%d/%d" % (len(friends), len(ignore_friends))
return friends

def get_age(user):
''' returns zero if user has not set his or her age '''
url = u'http://ws.audioscrobbler.com/1.0/user/' \
+ quote(user) + u'/profile.xml'
xmlage = get_xml(url).getElementsByTagName("age")
if len(xmlage)==0: return 0
return int(xmlage[0].firstChild.nodeValue)

def get_tags(user):
url = u'http://ws.audioscrobbler.com/1.0/user/' \
+ quote(user) + u'/tags.xml'
return len(get_xml(url).getElementsByTagName("tag"))

def print_stats(age_tags):
age_groups = (14,19,22,25,30,60)
for i in xrange(0,len(age_groups)-1):
nonzeros = [];
zero_count = 0
for j in xrange(age_groups[i],age_groups[i+1]):
for item in age_tags[j]:
if item!=0:
nonzeros.append(item)
else:
zero_count += 1
print \
"%d-%d: zeros: %.2f (%d/%d), median tags: %d" % \
(age_groups[i],age_groups[i+1],
zero_count/max((1,float(len(nonzeros)+ \
zero_count))),
zero_count, len(nonzeros), median(nonzeros))


users_notvisited = set([seed])
users_visited = set()

while len(users_notvisited)>0 and \
len(users_notvisited) + len(users_visited)<2000:
user = users_notvisited.pop()
if user not in users_visited:
users_notvisited = \
get_friends(user, users_notvisited, \
users_visited)
users_visited.add(user)

users = users_notvisited.union(users_visited)

age_tags = defaultdict(list)
i = 0
for user in users:
i += 1
print "%d/%d" % (i, len(users))
age_tags[get_age(user)].append(get_tags(user))
if i % 5 == 0:
print_stats(age_tags)

Friday, 13 June 2008

Myths about Last.fm tags

Today I was pointed to the following: "Last.fm has thousands of tags, unfortunately they are all pretty bad." (A statement made in this video of a very interesting talk about autotagging and applications that can be built using tags, around minute 51.)

I think this needs some clarification: Last.fm has a lot more than just a few thousand tags. The 1,000,000th unique tag applied by a Last.fm user was earthbeat about half a year ago.

Related links: fun stats on Last.fm tags, Last.fm's multi-tag search.

Tuesday, 20 May 2008

Islands of Music



It all started about 7 years ago when I was an intern working in Aberdeen (Scotland). Aberdeen is a rather quiet town. It's beach was a wonderful place to sit and think about my MSc thesis which I was working on at that time.

At first I was very enthusiastic about the idea of using a metaphor of islands to visualize a music collection. However, a few years and many not so perfect island of music maps later my enthusiasm had faded away and I had more or less given up trying to map the entire music world onto a map.

Recently at Last.fm we launched our playground and one response we heard a few times was that it wasn't very colorful. (All three initial demonstrations were basically simple lists of artists or tracks.) At the same time I had a dataset of users described in terms of tags loaded in Matlab. Previous results using simple clustering algorithms had shown that it was very easy to extract meaningful clusters.

Now there's an islands of music demonstration on playground. There's also a journal entry talking about how it was computed.

Wednesday, 14 May 2008

Last.fm's Playground

At Last.fm we launched a public version of our playground. One of the motivations has been to be able to gather more user feedback on some of the ideas we have. In particular, to find out if ideas we have are sticky or if they are just nice ideas. Eventually, all sticky ideas will (hopefully) find their way into the main site.

I'm very excited that we now have this platform. So far we've been running most of our experiments and evaluations in the background but now we can directly interact with users and expose possibilities we have even if there is no straight forward way to integrate them into the main site.

Special thanks to Klaas Bosteels who's done some amazing work during his 3 month internship at Last.fm building up playground. Btw, if you know someone who might be a good fit for an internship at Last.fm, let me know! (Java/Hadoop, PHP, Python, and being able to work efficiently on Linux systems is more or less a requirement.)

Btw, from a MIR research perspective I believe the multi tag search is the most interesting. Primarily because it indicates how much potential tags have.

Friday, 25 April 2008

Tag of the Day: Death Country

Today I learned from Mischa about death country. Apparently it's related to gothic americana... which btw sounds nothing like gothic.

Friday, 7 March 2008

Thoughts on Autotagging

I've recently spent a lot of time looking at tags. I'm fascinated by how creative listeners are when it comes to inventing new tags for the music they care about. And I'm fascinated by how small communities adapt these newly invented terms as quickly as they tune into a new radio station. This constantly changing and seemingly endless vocabulary of words to describe music makes me wonder if research on automatically classifying or tagging music will ever be able to catch up.

If you think research is catching up, check out the 22,703 shades of metal, or the 11,929 shades of pop, or the 29,940 shades of rock at Last.fm.



See also Paul's experiments on creating a taxonomy of metal and gothic tags.

See also Mike's recent results on autotagging of music (which includes some impressive numbers).

Wednesday, 28 March 2007

The Major Miner Game

I’m sure you heard of the major minor music game. It was posted on the music-ir list and Paul has blogged about it. You probably also know that Paul recently lost his lead (he had the highest overall score for at least a week I think), and that many other researchers (including Martin and myself) had made it into the top 10 at some point.

The clever game was also mentioned on the Listening Post. It would be nice to see it climb high in Digg’s playable web games section.

Maybe such games could be the solution to automatic segmentation, music classification, maybe also music recommendation, and replace various other content-based techniques we are still working on?

Instead of teaching computers how to understand music we just need to find clever ways to motivate those 8.5 millions playing World of Warcraft to play similar games. I think that might even solve the various scalability issues Jeremy and Paul mentioned recently.