diff --git a/server/djrandom/frontend/api_views.py b/server/djrandom/frontend/api_views.py index e6b3e906eca2a6ee41ccd67abb9cf4b29f7cb39d..fb9ef873695d3b7d804738a65877d3a7a5eba4fa 100644 --- a/server/djrandom/frontend/api_views.py +++ b/server/djrandom/frontend/api_views.py @@ -203,6 +203,15 @@ def markov_json(): return jsonify(results=sequence) +@app.route('/json/markov_vector', methods=['GET']) +@require_auth +def markov_vector_json(): + hashes = request.args.get('h', '').split(',') + last_songs = hashes[-1:] + sequence = svcs['markov'].get_vector(last_songs) + return jsonify(results=sequence) + + @app.route('/json/random', methods=['GET']) @require_auth def random_json(): diff --git a/server/djrandom/frontend/static/js/djr/backend.js b/server/djrandom/frontend/static/js/djr/backend.js index 2364a3308c4e28502fef66f83995dbe3c5da5b12..0125cfdec1874fa9aa56cae61aaa317c4bf0d3b3 100644 --- a/server/djrandom/frontend/static/js/djr/backend.js +++ b/server/djrandom/frontend/static/js/djr/backend.js @@ -329,6 +329,22 @@ djr.Backend.prototype.markovPlaylist = function(num, uuids, callback ,ctx) { }); }; +/** + * Return the Markovian probability vector for a playlist. + */ +djr.Backend.prototype.markovVector = function(uuids, callback ,ctx) { + wrap_with_loader( + {url: '/json/markov_vector', + data: {'h': uuids.join(',')}, + dataType: 'json', + type: 'GET', + context: ctx || this, + success: function(data, status, jqxhr) { + callback(data.results); + } + }); +}; + /** * Return N completely random songs. * diff --git a/server/djrandom/model/markov.py b/server/djrandom/model/markov.py index 25d83d576b18d3c0bd8617b855699d44c96e85a9..7c536839e9ed7c81870e730cd6247174a9d81d77 100644 --- a/server/djrandom/model/markov.py +++ b/server/djrandom/model/markov.py @@ -1,3 +1,4 @@ +import math import os import optparse import logging @@ -59,12 +60,14 @@ class MarkovModel(object): norm_vec = [] tot = cur = 0 - for target, count in target_map.iteritems(): + freqs = [(target, math.log1p(count)) + for target, count in target_map.iteritems()] + for target, count in freqs: if target != last_song: tot += count - for target, count in target_map.iteritems(): + for target, count in freqs: if target != last_song: - cur += float(count) / tot + cur += count / tot norm_vec.append((cur, target)) norm_map[key] = norm_vec @@ -77,7 +80,7 @@ class MarkovModel(object): for off, value in self._map[prev_n]: if off > r: result = self._i2hash[value] - log.debug('suggest: in=%s, scores=%s, result=%s', + log.info('suggest: in=%s, scores=%s, result=%s', prev_n, self._map[prev_n], result) return result # Can't find anything, get a random song instead. @@ -95,6 +98,19 @@ class MarkovModel(object): out.append(song) return out + def get_vector(self, prev): + prev_n = tuple(self._to_i(x) for x in prev) + vector = self._map.get(prev_n) + if not vector: + log.warning('get_vector() not found: %s', prev_n) + return None + out = [] + score = 0 + for off, val in vector: + out.append({'score': off - score, 'id': self._i2hash[val]}) + score = off + return out + def main(): parser = optparse.OptionParser()