Weighted Slope One in Haskell: collaborative filtering in 29 lines of code by Bryan O'Sullivan 曾经写过一个 SlopeOne 算法的Python版本,后又将其移植到 Haskell版本,但是他的blog上的代码好像有点问题,无法编译通过,特将debug后的版本放出:
module SlopeTwo (Rating, SlopeOne, empty, predict, update) where
import Data.List (foldl')
import qualified Data.Map as M
newtype SlopeOne a = SlopeOne (M.Map a (M.Map a (Int, Double)))
deriving (Eq, Show)
type Rating a = M.Map a Double
empty = SlopeOne M.empty
addT (a,b) (c,d) = let (l,r) = (a+c, b+d) in l `seq` r `seq` (l, r)
update :: Ord a => SlopeOne a -> [Rating a] -> SlopeOne a
update (SlopeOne s) = SlopeOne . M.map (M.map norm) . foldl' update' s
where norm (a,b) = (a, b / fromIntegral a)
update' s rm = foldl' (\m (k, v) -> insert m k v) s prod
where prod = [((a, b), (1, m - n)) | (a, m) <- rs, (b, n) <- rs]
rs = M.toList rm
insert m (a, b) v = M.alter foo a m
where foo = Just . maybe (M.singleton b v) (M.insertWith' addT b v)
predict :: Ord a => SlopeOne a -> Rating a -> Rating a
predict (SlopeOne s) rm =
let
freqm = foldl' insert M.empty
[(a,b,r) | a <- M.keys s, (b, r) <- M.toList rm]
insert n (a,b,r) = let (f, d) = find s (a, b) in
M.insertWith' addT a (f, fromIntegral f * (r + d)) n
norm = M.map (\(a, b) -> b / fromIntegral a) freqm
find m (a,b) = M.findWithDefault (0,0) b (M.findWithDefault M.empty a m)
in M.filterWithKey (\k v -> v > 0 && M.notMember k rm) norm
userData :: [Rating String]
userData = map M.fromList [
[("squid", 1.0), ("cuttlefish", 0.5), ("octopus", 0.2)],
[("squid", 1.0), ("octopus", 0.5), ("nautilus", 0.2)],
[("squid", 0.2), ("octopus", 1.0), ("cuttlefish", 0.4), ("nautilus", 0.4)],
[("cuttlefish", 0.9), ("octopus", 0.4), ("nautilus", 0.5)]
]
prediction = predict (update empty userData) (M.fromList [("squid", 0.4)])
Recent comments
18 hours 9 min ago
1 day 10 hours ago
4 days 5 hours ago
4 days 13 hours ago
4 days 18 hours ago
6 days 11 hours ago
6 days 13 hours ago
6 days 14 hours ago
1 week 12 hours ago
1 week 17 hours ago