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()