git.iko.soy/distributed-process
on master help sign in
Finish k-means example
commit6ad0728a7f1a71216de47a941a630964fad060c8 authorEdsko de Vries <edsko@edsko.net> dateWed 26 Sep 2012, 13:12 (14 years ago) parent9403fd0
4 files changed+148−62
distributed-process-demos/src/MapReduce/CountWords.hs+6−3 distributed-process-demos/src/MapReduce/KMeans.hs+90−5 distributed-process-demos/src/MapReduce/MapReduce.hs+41−26 distributed-process-demos/src/MapReduce/SimpleLocalnet.hs+11−28
distributed-process-demos/src/MapReduce/CountWords.hs+6−3
@@ -33,6 +33,9 @@ dictOut = SerializableDict
3333remotable ['dictIn, 'dictOut, 'countWords]
3434
3535distrCountWords :: [NodeId] -> Map FilePath Document -> Process (Map Word Frequency)
36−distrCountWords = distrMapReduce $(mkStatic 'dictIn)
37− $(mkStatic 'dictOut)
38− (staticClosure $(mkStatic 'countWords))
36+distrCountWords mappers input =
37+ distrMapReduce $(mkStatic 'dictIn)
38+ $(mkStatic 'dictOut)
39+ (staticClosure $(mkStatic 'countWords))
40+ mappers
41+ (\iteration -> iteration input)
distributed-process-demos/src/MapReduce/KMeans.hs+90−5
@@ -1,14 +1,21 @@
1+{-# LANGUAGE TupleSections #-}
12module KMeans
23 ( Point
34 , Cluster
45 , localKMeans
6+ , distrKMeans
7+ , createGnuPlot
8+ , __remoteTable
59 ) where
610
11+import System.IO
712import Data.List (minimumBy)
813import Data.Function (on)
914import Data.Array (Array, (!), bounds)
10−import qualified Data.Map as Map (fromList)
11−import MapReduce
15+import qualified Data.Map as Map (fromList, elems, toList, size)
16+import Control.Distributed.Process
17+import Control.Distributed.Process.Closure
18+import MapReduce hiding (__remoteTable)
1219
1320type Point = (Double, Double)
1421type Cluster = (Double, Double)
@@ -36,6 +43,84 @@ kmeans points = MapReduce {
3643 , mrReduce = \_ ps -> (ps, center ps)
3744 }
3845
39−localKMeans :: Array Int Point -> [Cluster] -> Map Cluster ([Point], Point)
40−localKMeans points cs =
41− localMapReduce (kmeans points) (Map.fromList [(bounds points, cs)])
46+localKMeans :: Array Int Point
47+ -> [Cluster]
48+ -> Int
49+ -> Map Cluster ([Point], Point)
50+localKMeans points cs iterations = go (iterations - 1)
51+ where
52+ mr :: [Cluster] -> Map Cluster ([Point], Point)
53+ mr = localMapReduce (kmeans points) . trivialSegmentation
54+
55+ go :: Int -> Map Cluster ([Point], Point)
56+ go 0 = mr cs
57+ go n = mr . map snd . Map.elems . go $ n - 1
58+
59+ trivialSegmentation :: [Cluster] -> Map (Int, Int) [Cluster]
60+ trivialSegmentation cs' = Map.fromList [(bounds points, cs')]
61+
62+dictIn :: SerializableDict (ProcessId, (Int, Int), [Cluster])
63+dictIn = SerializableDict
64+
65+dictOut :: SerializableDict [(Cluster, Point)]
66+dictOut = SerializableDict
67+
68+remotable ['kmeans, 'dictIn, 'dictOut]
69+
70+distrKMeans :: Array Int Point
71+ -> [Cluster]
72+ -> [NodeId]
73+ -> Int
74+ -> Process (Map Cluster ([Point], Point))
75+distrKMeans points cs mappers iterations =
76+ distrMapReduce $(mkStatic 'dictIn)
77+ $(mkStatic 'dictOut)
78+ ($(mkClosure 'kmeans) points)
79+ mappers
80+ (go (iterations - 1))
81+ where
82+ go :: Int
83+ -> (Map (Int, Int) [Cluster] -> Process (Map Cluster ([Point], Point)))
84+ -> Process (Map Cluster ([Point], Point))
85+ go 0 iteration = do
86+ iteration (Map.fromList $ map (, cs) segments)
87+ go n iteration = do
88+ clusters <- go (n - 1) iteration
89+ let centers = map snd $ Map.elems clusters
90+ iteration (Map.fromList $ map (, centers) segments)
91+
92+ segments :: [(Int, Int)]
93+ segments = let (lo, _) = bounds points in dividePoints numPoints lo
94+
95+ dividePoints :: Int -> Int -> [(Int, Int)]
96+ dividePoints pointsLeft offset
97+ | pointsLeft <= pointsPerMapper = [(offset, offset + pointsLeft - 1)]
98+ | otherwise = let offset' = offset + pointsPerMapper in
99+ (offset, offset' - 1)
100+ : dividePoints (pointsLeft - pointsPerMapper) offset'
101+
102+ pointsPerMapper :: Int
103+ pointsPerMapper =
104+ ceiling (toRational numPoints / toRational (length mappers))
105+
106+ numPoints :: Int
107+ numPoints = let (lo, hi) = bounds points in hi - lo + 1
108+
109+-- | Create a gnuplot data file for the output of the k-means algorithm
110+--
111+-- To plot the data, use
112+--
113+-- > plot "<<filename>>" u 1:2:3 with points palette
114+createGnuPlot :: Map KMeans.Cluster ([KMeans.Point], KMeans.Point) -> Handle -> IO ()
115+createGnuPlot clusters h =
116+ mapM_ printPoint . flatten . zip colors . Map.toList $ clusters
117+ where
118+ printPoint (x, y, color) =
119+ hPutStrLn h $ show x ++ " " ++ show y ++ " " ++ show color
120+
121+ flatten :: [(Float, (KMeans.Cluster, ([KMeans.Point], KMeans.Point)))]
122+ -> [(Double, Double, Float)]
123+ flatten = concat . map (\(color, (_, (points, _))) -> map (\(x, y) -> (x, y, color)) points)
124+
125+ colors :: [Float]
126+ colors = [0, 1 / fromIntegral (Map.size clusters) .. 1]
distributed-process-demos/src/MapReduce/MapReduce.hs+41−26
@@ -17,6 +17,7 @@ import qualified Data.Map as Map (mapWithKey, fromListWith, toList, size)
1717import Control.Arrow (second)
1818import Control.Monad (forM_, forever, replicateM)
1919import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
20+import qualified Control.Concurrent.Chan as Chan
2021import Control.Distributed.Process
2122import Control.Distributed.Process.Serializable (Serializable)
2223import Control.Distributed.Process.Closure
@@ -113,43 +114,57 @@ mapperProcessClosure dictIn dictOut mr pid =
113114 `staticCompose`
114115 staticDecode sdictProcessId
115116
116−distrMapReduce :: forall k1 k2 v1 v2 v3.
117+distrMapReduce :: forall k1 k2 v1 v2 v3 a.
117118 (Serializable k1, Serializable v1, Serializable k2, Serializable v2, Serializable v3, Ord k2)
118119 => Static (SerializableDict (ProcessId, k1, v1))
119120 -> Static (SerializableDict [(k2, v2)])
120121 -> Closure (MapReduce k1 v1 k2 v2 v3)
121122 -> [NodeId]
122− -> Map k1 v1
123− -> Process (Map k2 v3)
124−distrMapReduce dictIn dictOut mr mappers input = do
123+ -> ((Map k1 v1 -> Process (Map k2 v3)) -> Process a)
124+ -> Process a
125+distrMapReduce dictIn dictOut mr mappers p = do
126+ mr' <- unClosure mr
125127 us <- getSelfPid
128+ queue <- liftIO $ Chan.newChan
126129 slavesTerminated <- liftIO $ newEmptyMVar
127−
130+
128131 workQueue <- spawnLocal $ do
129− -- As long as there is work, return the next Fib to compute
130− forM_ (Map.toList input) $ \(key, val) -> do
131− them <- expect
132− send them (us, key, val)
133−
134− -- After that, just report that the work is done
135− replicateM (length mappers) $ do
136− pid <- expect
137− send pid ()
138−
139− liftIO $ putMVar slavesTerminated ()
132+ let go :: Process ()
133+ go = do
134+ mWork <- liftIO $ Chan.readChan queue
135+ case mWork of
136+ Just (key, val) -> do
137+ -- As long there is work, make it available to the mappers
138+ them <- expect
139+ send them (us, key, val)
140+ go
141+ Nothing -> do
142+ -- Tell the mappers to terminate
143+ replicateM (length mappers) $ do
144+ them <- expect
145+ send them ()
146+ liftIO $ putMVar slavesTerminated ()
147+ go
140148
141149 -- Start the mappers
142− liftIO $ print mappers
143150 forM_ mappers $ flip spawn (mapperProcessClosure dictIn dictOut mr workQueue)
144151
145− liftIO $ threadDelay 1000000
152+ let iteration :: Map k1 v1 -> Process (Map k2 v3)
153+ iteration input = do
154+ -- Make work available to the mappers
155+ liftIO $ mapM_ (Chan.writeChan queue . Just) (Map.toList input)
146156
147− -- Wait for the result from the mappers
148− partials <- replicateM (Map.size input) expect
149−
150− -- Wait for the slaves to terminate
151− liftIO $ takeMVar slavesTerminated
157+ -- Wait for the partial results
158+ partials <- replicateM (Map.size input) expect
152159
153− -- We reduce on this node
154− mr' <- unClosure mr
155− return (reducePerKey mr' . groupByKey . concat $ partials)
160+ -- We reduce on this node
161+ return (reducePerKey mr' . groupByKey . concat $ partials)
162+
163+ result <- p iteration
164+
165+ -- Terminate the wrappers
166+ liftIO $ do
167+ Chan.writeChan queue Nothing
168+ takeMVar slavesTerminated
169+
170+ return result
distributed-process-demos/src/MapReduce/SimpleLocalnet.hs+11−28
@@ -8,7 +8,7 @@ import Control.Distributed.Process.Node (initRemoteTable)
88import Control.Distributed.Process.Backend.SimpleLocalnet
99import Data.Map (Map)
1010import Data.Array (Array, listArray)
11−import qualified Data.Map as Map (fromList, toList, size, elems)
11+import qualified Data.Map as Map (fromList)
1212
1313import qualified CountWords
1414import qualified MapReduce
@@ -17,6 +17,7 @@ import qualified KMeans
1717rtable :: RemoteTable
1818rtable = MapReduce.__remoteTable
1919 . CountWords.__remoteTable
20+ . KMeans.__remoteTable
2021 $ initRemoteTable
2122
2223main :: IO ()
@@ -40,15 +41,16 @@ main = do
4041 -- Local k-means
4142 "local" : "kmeans" : [] -> do
4243 points <- replicateM 1000 randomPoint
43− let kmeans = KMeans.localKMeans (arrayFromList points)
44+ withFile "plot.data" WriteMode $ KMeans.createGnuPlot $
45+ KMeans.localKMeans (arrayFromList points) (take 5 points) 5
4446
45− let it 0 = kmeans (take 5 points)
46− it n = let clusters = it (n - 1) in
47− kmeans (map snd $ Map.elems clusters)
48−
49− forM_ ([0 .. 4] :: [Int]) $ \n ->
50− withFile ("plot" ++ show n) WriteMode $
51− createGnuPlot (it n)
47+ -- Distributed k-means
48+ "master" : "kmeans" : host : port : [] -> do
49+ points <- replicateM 1000 randomPoint
50+ backend <- initializeBackend host port rtable
51+ startMaster backend $ \slaves -> do
52+ result <- KMeans.distrKMeans (arrayFromList points) (take 5 points) slaves 5
53+ liftIO $ withFile "plot.data" WriteMode $ KMeans.createGnuPlot result
5254
5355 -- Generic slave for distributed examples
5456 "slave" : host : port : [] -> do
@@ -69,22 +71,3 @@ randomPoint = (,) <$> randomIO <*> randomIO
6971
7072arrayFromList :: [e] -> Array Int e
7173arrayFromList xs = listArray (0, length xs - 1) xs
72−
73−-- | Create a gnuplot data file for the output of the k-means algorithm
74−--
75−-- To plot the data, use
76−--
77−-- > plot "<<filename>>" u 1:2:3 with points palette
78−createGnuPlot :: Map KMeans.Cluster ([KMeans.Point], KMeans.Point) -> Handle -> IO ()
79−createGnuPlot clusters h =
80− mapM_ printPoint . flatten . zip colors . Map.toList $ clusters
81− where
82− printPoint (x, y, color) =
83− hPutStrLn h $ show x ++ " " ++ show y ++ " " ++ show color
84−
85− flatten :: [(Float, (KMeans.Cluster, ([KMeans.Point], KMeans.Point)))]
86− -> [(Double, Double, Float)]
87− flatten = concat . map (\(color, (_, (points, _))) -> map (\(x, y) -> (x, y, color)) points)
88−
89− colors :: [Float]
90− colors = [0, 1 / fromIntegral (Map.size clusters) .. 1]