tsne (empty) → 1.0.0.1
raw patch · 14 files changed
+793/−0 lines, 14 filesdep +basedep +data-defaultdep +deepseqsetup-changed
Dependencies added: base, data-default, deepseq, hspec, normaldistribution, pipes, tsne
Files
- LICENSE +165/−0
- Setup.hs +2/−0
- example/Main.hs +45/−0
- src/Data/Algorithm/TSNE.hs +26/−0
- src/Data/Algorithm/TSNE/Checks.hs +19/−0
- src/Data/Algorithm/TSNE/Internals.hs +177/−0
- src/Data/Algorithm/TSNE/Types.hs +40/−0
- src/Data/Algorithm/TSNE/Utils.hs +58/−0
- test/Data/Algorithm/TSNE/ChecksSpec.hs +55/−0
- test/Data/Algorithm/TSNE/InternalsSpec.hs +97/−0
- test/Data/Algorithm/TSNE/UtilsSpec.hs +33/−0
- test/Data/Algorithm/TSNESpec.hs +17/−0
- test/Spec.hs +1/−0
- tsne.cabal +58/−0
+ LICENSE view
@@ -0,0 +1,165 @@+ GNU LESSER GENERAL PUBLIC LICENSE+ Version 3, 29 June 2007++ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>+ Everyone is permitted to copy and distribute verbatim copies+ of this license document, but changing it is not allowed.+++ This version of the GNU Lesser General Public License incorporates+the terms and conditions of version 3 of the GNU General Public+License, supplemented by the additional permissions listed below.++ 0. Additional Definitions.++ As used herein, "this License" refers to version 3 of the GNU Lesser+General Public License, and the "GNU GPL" refers to version 3 of the GNU+General Public License.++ "The Library" refers to a covered work governed by this License,+other than an Application or a Combined Work as defined below.++ An "Application" is any work that makes use of an interface provided+by the Library, but which is not otherwise based on the Library.+Defining a subclass of a class defined by the Library is deemed a mode+of using an interface provided by the Library.++ A "Combined Work" is a work produced by combining or linking an+Application with the Library. The particular version of the Library+with which the Combined Work was made is also called the "Linked+Version".++ The "Minimal Corresponding Source" for a Combined Work means the+Corresponding Source for the Combined Work, excluding any source code+for portions of the Combined Work that, considered in isolation, are+based on the Application, and not on the Linked Version.++ The "Corresponding Application Code" for a Combined Work means the+object code and/or source code for the Application, including any data+and utility programs needed for reproducing the Combined Work from the+Application, but excluding the System Libraries of the Combined Work.++ 1. Exception to Section 3 of the GNU GPL.++ You may convey a covered work under sections 3 and 4 of this License+without being bound by section 3 of the GNU GPL.++ 2. Conveying Modified Versions.++ If you modify a copy of the Library, and, in your modifications, a+facility refers to a function or data to be supplied by an Application+that uses the facility (other than as an argument passed when the+facility is invoked), then you may convey a copy of the modified+version:++ a) under this License, provided that you make a good faith effort to+ ensure that, in the event an Application does not supply the+ function or data, the facility still operates, and performs+ whatever part of its purpose remains meaningful, or++ b) under the GNU GPL, with none of the additional permissions of+ this License applicable to that copy.++ 3. Object Code Incorporating Material from Library Header Files.++ The object code form of an Application may incorporate material from+a header file that is part of the Library. You may convey such object+code under terms of your choice, provided that, if the incorporated+material is not limited to numerical parameters, data structure+layouts and accessors, or small macros, inline functions and templates+(ten or fewer lines in length), you do both of the following:++ a) Give prominent notice with each copy of the object code that the+ Library is used in it and that the Library and its use are+ covered by this License.++ b) Accompany the object code with a copy of the GNU GPL and this license+ document.++ 4. Combined Works.++ You may convey a Combined Work under terms of your choice that,+taken together, effectively do not restrict modification of the+portions of the Library contained in the Combined Work and reverse+engineering for debugging such modifications, if you also do each of+the following:++ a) Give prominent notice with each copy of the Combined Work that+ the Library is used in it and that the Library and its use are+ covered by this License.++ b) Accompany the Combined Work with a copy of the GNU GPL and this license+ document.++ c) For a Combined Work that displays copyright notices during+ execution, include the copyright notice for the Library among+ these notices, as well as a reference directing the user to the+ copies of the GNU GPL and this license document.++ d) Do one of the following:++ 0) Convey the Minimal Corresponding Source under the terms of this+ License, and the Corresponding Application Code in a form+ suitable for, and under terms that permit, the user to+ recombine or relink the Application with a modified version of+ the Linked Version to produce a modified Combined Work, in the+ manner specified by section 6 of the GNU GPL for conveying+ Corresponding Source.++ 1) Use a suitable shared library mechanism for linking with the+ Library. A suitable mechanism is one that (a) uses at run time+ a copy of the Library already present on the user's computer+ system, and (b) will operate properly with a modified version+ of the Library that is interface-compatible with the Linked+ Version.++ e) Provide Installation Information, but only if you would otherwise+ be required to provide such information under section 6 of the+ GNU GPL, and only to the extent that such information is+ necessary to install and execute a modified version of the+ Combined Work produced by recombining or relinking the+ Application with a modified version of the Linked Version. (If+ you use option 4d0, the Installation Information must accompany+ the Minimal Corresponding Source and Corresponding Application+ Code. If you use option 4d1, you must provide the Installation+ Information in the manner specified by section 6 of the GNU GPL+ for conveying Corresponding Source.)++ 5. Combined Libraries.++ You may place library facilities that are a work based on the+Library side by side in a single library together with other library+facilities that are not Applications and are not covered by this+License, and convey such a combined library under terms of your+choice, if you do both of the following:++ a) Accompany the combined library with a copy of the same work based+ on the Library, uncombined with any other library facilities,+ conveyed under the terms of this License.++ b) Give prominent notice with the combined library that part of it+ is a work based on the Library, and explaining where to find the+ accompanying uncombined form of the same work.++ 6. Revised Versions of the GNU Lesser General Public License.++ The Free Software Foundation may publish revised and/or new versions+of the GNU Lesser General Public License from time to time. Such new+versions will be similar in spirit to the present version, but may+differ in detail to address new problems or concerns.++ Each version is given a distinguishing version number. If the+Library as you received it specifies that a certain numbered version+of the GNU Lesser General Public License "or any later version"+applies to it, you have the option of following the terms and+conditions either of that published version or of any later version+published by the Free Software Foundation. If the Library as you+received it does not specify a version number of the GNU Lesser+General Public License, you may choose any version of the GNU Lesser+General Public License ever published by the Free Software Foundation.++ If the Library as you received it specifies that a proxy can decide+whether future versions of the GNU Lesser General Public License shall+apply, that proxy's public statement of acceptance of any version is+permanent authorization for you to choose that version for the+Library.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ example/Main.hs view
@@ -0,0 +1,45 @@+module Main where++import System.Environment(getArgs)+import System.Exit+import Control.Monad(when)+import Data.Default(def)+import Pipes+import Data.Algorithm.TSNE+++main :: IO ()+main = do+ putStrLn "Haskell tSNE example"++ args <- getArgs+ let argc = length args+ when (argc < 1 || argc > 2) $ do+ putStrLn "Usage haskell_tsne_example {input file name} [{num input values}]"+ exitFailure++ let inputFileName = head args+ inputData <- readDataFile inputFileName++ let n = length inputData+ putStrLn $ "total input: " ++ show n++ let n' = if (argc < 2) then n else (read (args !! 1)) ++ putStrLn $ "using: " ++ show n'++ forTsne3D outputResult def $ take n' inputData+ + --runEffect $ for (tsne3D def $ take n' inputData) $ \r -> do+ -- lift $ outputResult r++readDataFile :: FilePath -> IO [[Double]]+readDataFile f = do+ d <- readFile f+ return $ map read (lines d)++outputResult :: TSNEOutput3D -> IO ()+outputResult s = do+ putStrLn $ "iteration: " ++ (show.tsneIteration) s+ putStrLn $ "cost: " ++ (show.tsneCost) s+
+ src/Data/Algorithm/TSNE.hs view
@@ -0,0 +1,26 @@+module Data.Algorithm.TSNE ( + tsne3D,+ forTsne3D,+ TSNEOptions(..),+ TSNEOutput3D(..)+ ) where++import Pipes++import Data.Algorithm.TSNE.Types+import Data.Algorithm.TSNE.Internals+import Data.Algorithm.TSNE.Utils++-- | Generates an infinite stream of 3D tSNE iterations.+tsne3D :: TSNEOptions -> TSNEInput -> Producer TSNEOutput3D IO ()+tsne3D opts input = do+ st <- liftIO $ initState $ length input+ runTSNE opts input ps st+ where ps = neighbourProbabilities opts input++-- | Executes an IO action for each iteration of the 3D tSNE algorithm.+forTsne3D :: (TSNEOutput3D -> IO ()) -> TSNEOptions -> TSNEInput -> IO ()+forTsne3D action opts input = do+ runEffect $ for (tsne3D opts input) $ \o -> do+ lift $ action o +
+ src/Data/Algorithm/TSNE/Checks.hs view
@@ -0,0 +1,19 @@+module Data.Algorithm.TSNE.Checks where++import Data.Algorithm.TSNE.Types++isSquare :: Int -> [[a]] -> Bool+isSquare 0 [] = True+isSquare _ [] = False+isSquare n xss = length xss == n && all (\xs -> length xs == n) xss++has2DShape :: (Int, Int) -> [[a]] -> Bool+has2DShape (w,h) xss = length xss == h && all (\xs -> length xs == w) xss++shape2D :: [[a]] -> (Int, Int)+shape2D [] = (undefined, 0)+shape2D xss = (length (head xss), length xss)++isRectangular :: [[a]] -> Bool+isRectangular xss = has2DShape (shape2D xss) xss+
+ src/Data/Algorithm/TSNE/Internals.hs view
@@ -0,0 +1,177 @@+module Data.Algorithm.TSNE.Internals where++import Control.Applicative+import Control.DeepSeq+import Control.Exception (assert)+import Data.Default (def)+import Data.List(zipWith4)+import Data.Random.Normal (normalsIO')+import Pipes+--import Debug.Trace++import Data.Algorithm.TSNE.Types+import Data.Algorithm.TSNE.Utils+import Data.Algorithm.TSNE.Checks+++inputSize :: TSNEInput -> Int+inputSize = length++inputValueSize :: TSNEInput -> Int+inputValueSize i = w + where (w,h) = shape2D i ++inputIsValid :: TSNEInput -> Either String ()+inputIsValid [] = Left "empty input data"+inputIsValid xss+ | not (isRectangular xss) = Left "input data values are not all the same length"+ | otherwise = Right () ++isValidStateForInput :: TSNEInput -> TSNEState -> Either String ()+isValidStateForInput i st+ | not (has2DShape (n,3) s) = Left $ "solution is wrong shape: " ++ show (shape2D s) + | otherwise = Right ()+ where+ n = inputSize i+ s = stSolution st ++initState :: Int -> IO TSNEState+initState n = do+ s <- initSolution3D n+ return $ TSNEState 0 s (rr 1) (rr 0)+ where+ rr = repeat.repeat++initSolution3D :: Int -> IO [[Double]]+initSolution3D n = do+ let ns = normalsIO' (0, 1e-4)+ xs <- ns+ ys <- ns+ zs <- ns+ return $ take n <$> [xs,ys,zs]++runTSNE :: TSNEOptions -> TSNEInput -> [[Probability]] -> TSNEState -> Producer TSNEOutput3D IO ()+runTSNE opts vs ps st = do+ let st' = force $ stepTSNE opts vs ps st+ yield $ output3D ps st'+ runTSNE opts vs ps st'++stepTSNE :: TSNEOptions -> TSNEInput -> [[Probability]] -> TSNEState -> TSNEState+stepTSNE opts vs ps st = TSNEState i' s'' g' d'+ where+ i = stIteration st+ s = stSolution st+ g = stGains st+ d = stDeltas st+ gr = gradients ps st+ i' = i + 1+ s' = recenter $ z (+) s d'+ g' = z3 newGain g d gr+ d' = z3 (newDelta (tsneLearningRate opts) i) g' d gr+ z = zipWith.zipWith+ z3 = zipWith3.zipWith3+ s'' = assert (length s' == length vs) s'++newGain :: Gain -> Delta -> Gradient -> Gain+newGain g d gr = max 0.01 g'+ where+ g' = if signum d == signum gr + then g * 0.8+ else g + 0.2 ++newDelta :: Double -> Int -> Gain -> Delta -> Gradient -> Delta+newDelta e i g' d gr = (m * d) - (e * g' * gr)+ where+ m = if i < 250 then 0.5 else 0.8++gradients :: [[Probability]] -> TSNEState -> [[Gradient]]+gradients pss st = gradient <$> ss+ where+ gradient :: [Double] -> [Gradient]+ gradient s = zipWith4 (f s) s pss qss qss'+ ss = stSolution st+ i = stIteration st+ qss = qdist ss+ qss' = qdist' ss + f :: [Double] -> Double -> [Double] -> [Double] -> [Double] -> Gradient+ f s x ps qs qs' = sum $ zipWith4 g s ps qs qs'+ where+ g y p q q' = m * (x - y)+ where+ m = 4 * (k * p - q') * q+ k = if i < 100 then 4 else 1++solution3D :: [[Double]] -> [Position3D]+solution3D (xs:ys:zs:_) = zip3 xs ys zs++output3D :: [[Double]] -> TSNEState -> TSNEOutput3D+output3D pss st = TSNEOutput3D i s c+ where+ i = stIteration st+ s = (solution3D . stSolution) st+ c = cost pss st++cost :: [[Double]] -> TSNEState -> Double+cost pss st = sumsum $ (zipWith.zipWith) c pss (qdist' (stSolution st))+ where+ c p q = -p * log q ++targetEntropy :: TSNEOptions -> Entropy+targetEntropy = log.realToFrac.tsnePerplexity++data Beta = Beta {+ betaValue :: Double,+ betaMin :: Double,+ betaMax :: Double+}++neighbourProbabilities :: TSNEOptions -> TSNEInput -> [[Probability]]+neighbourProbabilities opts vs = symmetrize $ rawNeighbourProbabilities opts vs++rawNeighbourProbabilities :: TSNEOptions -> TSNEInput -> [[Probability]]+rawNeighbourProbabilities opts vs = map np vs+ where + np a = aps (beta a) vs a+ beta a = betaValue $ binarySearchBeta opts vs a++ aps :: Double -> TSNEInput -> TSNEInputValue -> [Probability]+ aps beta bs a = map pj' bs+ where+ psum = sum $ map pj bs+ pj b + | a == b = 0+ | otherwise = exp $ -(distanceSquared a b) * beta + pj' b = pj b / psum++binarySearchBeta :: TSNEOptions -> TSNEInput -> TSNEInputValue -> Beta+binarySearchBeta opts vs = binarySearchBeta' opts vs 1e-4 0 (Beta 1 (-infinity) infinity)++binarySearchBeta' :: TSNEOptions -> TSNEInput -> Double -> Int -> Beta -> TSNEInputValue -> Beta+binarySearchBeta' opts bs tol i beta a+ | i == 50 = beta+ | abs (e - t) < tol = beta+ | e > t = r $ incPrecision beta+ | otherwise = r $ decPrecision beta + where+ t = targetEntropy opts+ e = entropyForInputValue (betaValue beta) bs a+ incPrecision (Beta b _ bmax) + | bmax == infinity = Beta (b * 2) b bmax+ | otherwise = Beta ((b + bmax) / 2) b bmax+ decPrecision (Beta b bmin _) + | bmin == -infinity = Beta (b / 2) bmin b+ | otherwise = Beta ((b + bmin) / 2) bmin b+ r beta' = binarySearchBeta' opts bs tol (i+1) beta' a ++entropyForInputValue :: Double -> TSNEInput -> TSNEInputValue -> Entropy+entropyForInputValue beta bs a = sum $ map h bs+ where+ h b = if x > 1e-7 then -x * log x else 0+ where x = pj' b+ psum = sum $ map pj bs+ pj b + | a == b = 0+ | otherwise = exp $ -(distanceSquared a b) * beta + pj' b = pj b / psum++
+ src/Data/Algorithm/TSNE/Types.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-}++module Data.Algorithm.TSNE.Types where++import GHC.Generics (Generic)+import Control.DeepSeq+import Data.Default+++data TSNEOptions = TSNEOptions {+ tsnePerplexity :: Int,+ tsneLearningRate :: Double+}++type TSNEInputValue = [Double]+type TSNEInput = [TSNEInputValue]++type Position3D = (Double,Double,Double)++data TSNEOutput3D = TSNEOutput3D {+ tsneIteration :: Int,+ tsneSolution3D :: [Position3D],+ tsneCost :: Double+} deriving (Show, Eq)++instance Default TSNEOptions where+ def = TSNEOptions 30 10++type Probability = Double+type Gain = Double+type Delta = Double+type Gradient = Double+type Entropy = Double++data TSNEState = TSNEState {+ stIteration :: Int,+ stSolution :: [[Double]],+ stGains :: [[Gain]],+ stDeltas :: [[Delta]]+} deriving (Show, Generic, NFData)
+ src/Data/Algorithm/TSNE/Utils.hs view
@@ -0,0 +1,58 @@+module Data.Algorithm.TSNE.Utils where++import Data.List(foldr, transpose)++infinity :: Double+infinity = read "Infinity"+ +distanceSquared :: [Double] -> [Double] -> Double+distanceSquared as bs = foldr d 0 (zip as bs)+ where d (a,b) t = t + (a-b) * (a-b)++symmetrize :: [[Double]] -> [[Double]]+symmetrize m = (zipWith.zipWith) f m (transpose m)+ where + f :: Double -> Double -> Double+ f x y = max a 1e-100+ where a = (x + y) / (2 * (realToFrac.length) m) ++recenter :: [[Double]] -> [[Double]]+recenter ss = map r ss+ where + r s = subtract (mean s) <$> s+ mean s = sum s / (realToFrac.length) s+ +qdist :: [[Double]] -> [[Double]]+qdist ss = symmetricalMatrixFromTopRight $ qd (transpose ss)+ where+ qd [] = []+ qd ps = [qr ps] ++ qd (tail ps)+ qr :: [[Double]] -> [Double]+ qr ps = [0::Double] ++ (q (head ps) <$> tail ps)+ q as bs = 1 / (1 + s)+ where+ s = sum $ zipWith f as bs+ f a b = (a-b) * (a-b) ++qdist' :: [[Double]] -> [[Double]]+qdist' ss = (map.map) f qd+ where+ qd = qdist ss+ f :: Double -> Double + f q = max (q / sumsum qd) 1e-100+ +sumsum :: [[Double]] -> Double+sumsum m = sum $ sum <$> m ++reprep :: a -> [[a]]+reprep = repeat.repeat++symmetricalMatrixFromTopRight :: [[a]] -> [[a]]+symmetricalMatrixFromTopRight tr = zipWith (++) bl tr+ where+ bl = zipWith take [0..] (transpose m)+ m = zipWith (++) ebl tr+ ebl = zipWith take [0..] (reprep undefined)+++
+ test/Data/Algorithm/TSNE/ChecksSpec.hs view
@@ -0,0 +1,55 @@+module Data.Algorithm.TSNE.ChecksSpec (main, spec) where++import Test.Hspec+import Data.Algorithm.TSNE.Checks++-- `main` is here so that this module can be run from GHCi on its own. It is+-- not needed for automatic spec discovery.+main :: IO ()+main = hspec spec++u = undefined++spec :: Spec+spec = do+ describe "isSquare" $ do+ it "empty is 0x0 square" $ do+ isSquare 0 [] `shouldBe` True+ it "empty is not 1x1 square" $ do+ isSquare 1 [] `shouldBe` False+ it "single is 1x1 square" $ do+ isSquare 1 [[u]] `shouldBe` True+ it "single is not 0x0 square" $ do+ isSquare 0 [[u]] `shouldBe` False+ it "2x2 square" $ do+ isSquare 2 [[u,u],[u,u]] `shouldBe` True+ it "2x2 is not 0x0" $ do+ isSquare 0 [[u,u],[u,u]] `shouldBe` False+ it "2x2 is not 0x0" $ do+ isSquare 1 [[u,u],[u,u]] `shouldBe` False+ it "2x2 is not 3x3" $ do+ isSquare 3 [[u,u],[u,u]] `shouldBe` False+ it "2x1 is not 1x1" $ do+ isSquare 1 [[u,u]] `shouldBe` False+ it "2x1 is not 2x2" $ do+ isSquare 2 [[u,u]] `shouldBe` False+ it "1x2 is not 1x1" $ do+ isSquare 1 [[u,u]] `shouldBe` False+ it "1x2 is not 2x2" $ do+ isSquare 2 [[u,u]] `shouldBe` False++ describe "has2DShape" $ do+ it "empty is 0x0" $ do+ has2DShape (0,0) [] `shouldBe` True+ --it "empty is not 1x0" $ do+ -- has2DShape (1,0) [] `shouldBe` False+ it "empty is not 0x1" $ do+ has2DShape (0,1) [] `shouldBe` False+ it "1x2 is 1x2" $ do+ has2DShape (1,2) [[u],[u]] `shouldBe` True+ it "2x1 is 2x1" $ do+ has2DShape (2,1) [[u,u]] `shouldBe` True+ it "1x2 is not 2x1" $ do+ has2DShape (2,1) [[u],[u]] `shouldBe` False+ it "2x1 is not 1x2" $ do+ has2DShape (1,2) [[u,u]] `shouldBe` False
+ test/Data/Algorithm/TSNE/InternalsSpec.hs view
@@ -0,0 +1,97 @@+module Data.Algorithm.TSNE.InternalsSpec (main, spec) where++import Data.Default (def)++import Test.Hspec+import Data.Algorithm.TSNE.Internals+import Data.Algorithm.TSNE.Checks+import Data.Algorithm.TSNE.Types+import Data.Algorithm.TSNE.Utils++-- `main` is here so that this module can be run from GHCi on its own. It is+-- not needed for automatic spec discovery.+main :: IO ()+main = hspec spec++u = undefined++spec :: Spec+spec = do+ let n = inputSize testInput+ w = inputValueSize testInput++ describe "testInput" $ do+ it "is right shape" $ do+ testInput `shouldSatisfy` has2DShape (64, 20)+ it "is valid" $ do+ inputIsValid testInput `shouldBe` Right ()+ it "has right size" $ do+ inputSize testInput `shouldBe` 20+ it "has right value size" $ do+ inputValueSize testInput `shouldBe` 64++ describe "initSolution3D" $ do+ it "is right shape" $+ --initSolution3D 99 >>= (`shouldSatisfy` (\s -> length s == 3 && all (\xs -> length xs == 99) s))+ initSolution3D n >>= (`shouldSatisfy` has2DShape (n,3))++ describe "initState" $ do+ it "is valid state" $+ initState n >>= (`shouldSatisfy` isRight . (isValidStateForInput testInput))++ describe "neighbourProbabilities" $ do+ it "is right shape" $ do+ testNeighbourProbs `shouldSatisfy` has2DShape (n,n)++ describe "qdist" $ do+ it "is right shape" $ do+ s <- initSolution3D n+ qdist s `shouldSatisfy` has2DShape (n,n) ++ describe "qdist'" $ do+ it "is right shape" $ do+ s <- initSolution3D n+ qdist' s `shouldSatisfy` has2DShape (n,n) ++ describe "gradients" $ do+ it "is right shape" $ do+ s <- initState n+ gradients testNeighbourProbs s `shouldSatisfy` has2DShape (n,3)++ describe "stepTSNE" $ do+ it "works" $ do + s <- initState n+ stepTSNE def testInput testNeighbourProbs s `shouldSatisfy` isRight . (isValidStateForInput testInput)+++isRight :: Either a b -> Bool+isRight (Left _) = False+isRight (Right _) = True++-- first 20 digits from the Python sklearn digits dataset+testInput :: TSNEInput+testInput = [+ [0.0,0.0,5.0,13.0,9.0,1.0,0.0,0.0,0.0,0.0,13.0,15.0,10.0,15.0,5.0,0.0,0.0,3.0,15.0,2.0,0.0,11.0,8.0,0.0,0.0,4.0,12.0,0.0,0.0,8.0,8.0,0.0,0.0,5.0,8.0,0.0,0.0,9.0,8.0,0.0,0.0,4.0,11.0,0.0,1.0,12.0,7.0,0.0,0.0,2.0,14.0,5.0,10.0,12.0,0.0,0.0,0.0,0.0,6.0,13.0,10.0,0.0,0.0,0.0],+ [0.0,0.0,0.0,12.0,13.0,5.0,0.0,0.0,0.0,0.0,0.0,11.0,16.0,9.0,0.0,0.0,0.0,0.0,3.0,15.0,16.0,6.0,0.0,0.0,0.0,7.0,15.0,16.0,16.0,2.0,0.0,0.0,0.0,0.0,1.0,16.0,16.0,3.0,0.0,0.0,0.0,0.0,1.0,16.0,16.0,6.0,0.0,0.0,0.0,0.0,1.0,16.0,16.0,6.0,0.0,0.0,0.0,0.0,0.0,11.0,16.0,10.0,0.0,0.0],+ [0.0,0.0,0.0,4.0,15.0,12.0,0.0,0.0,0.0,0.0,3.0,16.0,15.0,14.0,0.0,0.0,0.0,0.0,8.0,13.0,8.0,16.0,0.0,0.0,0.0,0.0,1.0,6.0,15.0,11.0,0.0,0.0,0.0,1.0,8.0,13.0,15.0,1.0,0.0,0.0,0.0,9.0,16.0,16.0,5.0,0.0,0.0,0.0,0.0,3.0,13.0,16.0,16.0,11.0,5.0,0.0,0.0,0.0,0.0,3.0,11.0,16.0,9.0,0.0],+ [0.0,0.0,7.0,15.0,13.0,1.0,0.0,0.0,0.0,8.0,13.0,6.0,15.0,4.0,0.0,0.0,0.0,2.0,1.0,13.0,13.0,0.0,0.0,0.0,0.0,0.0,2.0,15.0,11.0,1.0,0.0,0.0,0.0,0.0,0.0,1.0,12.0,12.0,1.0,0.0,0.0,0.0,0.0,0.0,1.0,10.0,8.0,0.0,0.0,0.0,8.0,4.0,5.0,14.0,9.0,0.0,0.0,0.0,7.0,13.0,13.0,9.0,0.0,0.0],+ [0.0,0.0,0.0,1.0,11.0,0.0,0.0,0.0,0.0,0.0,0.0,7.0,8.0,0.0,0.0,0.0,0.0,0.0,1.0,13.0,6.0,2.0,2.0,0.0,0.0,0.0,7.0,15.0,0.0,9.0,8.0,0.0,0.0,5.0,16.0,10.0,0.0,16.0,6.0,0.0,0.0,4.0,15.0,16.0,13.0,16.0,1.0,0.0,0.0,0.0,0.0,3.0,15.0,10.0,0.0,0.0,0.0,0.0,0.0,2.0,16.0,4.0,0.0,0.0],+ [0.0,0.0,12.0,10.0,0.0,0.0,0.0,0.0,0.0,0.0,14.0,16.0,16.0,14.0,0.0,0.0,0.0,0.0,13.0,16.0,15.0,10.0,1.0,0.0,0.0,0.0,11.0,16.0,16.0,7.0,0.0,0.0,0.0,0.0,0.0,4.0,7.0,16.0,7.0,0.0,0.0,0.0,0.0,0.0,4.0,16.0,9.0,0.0,0.0,0.0,5.0,4.0,12.0,16.0,4.0,0.0,0.0,0.0,9.0,16.0,16.0,10.0,0.0,0.0],+ [0.0,0.0,0.0,12.0,13.0,0.0,0.0,0.0,0.0,0.0,5.0,16.0,8.0,0.0,0.0,0.0,0.0,0.0,13.0,16.0,3.0,0.0,0.0,0.0,0.0,0.0,14.0,13.0,0.0,0.0,0.0,0.0,0.0,0.0,15.0,12.0,7.0,2.0,0.0,0.0,0.0,0.0,13.0,16.0,13.0,16.0,3.0,0.0,0.0,0.0,7.0,16.0,11.0,15.0,8.0,0.0,0.0,0.0,1.0,9.0,15.0,11.0,3.0,0.0],+ [0.0,0.0,7.0,8.0,13.0,16.0,15.0,1.0,0.0,0.0,7.0,7.0,4.0,11.0,12.0,0.0,0.0,0.0,0.0,0.0,8.0,13.0,1.0,0.0,0.0,4.0,8.0,8.0,15.0,15.0,6.0,0.0,0.0,2.0,11.0,15.0,15.0,4.0,0.0,0.0,0.0,0.0,0.0,16.0,5.0,0.0,0.0,0.0,0.0,0.0,9.0,15.0,1.0,0.0,0.0,0.0,0.0,0.0,13.0,5.0,0.0,0.0,0.0,0.0],+ [0.0,0.0,9.0,14.0,8.0,1.0,0.0,0.0,0.0,0.0,12.0,14.0,14.0,12.0,0.0,0.0,0.0,0.0,9.0,10.0,0.0,15.0,4.0,0.0,0.0,0.0,3.0,16.0,12.0,14.0,2.0,0.0,0.0,0.0,4.0,16.0,16.0,2.0,0.0,0.0,0.0,3.0,16.0,8.0,10.0,13.0,2.0,0.0,0.0,1.0,15.0,1.0,3.0,16.0,8.0,0.0,0.0,0.0,11.0,16.0,15.0,11.0,1.0,0.0],+ [0.0,0.0,11.0,12.0,0.0,0.0,0.0,0.0,0.0,2.0,16.0,16.0,16.0,13.0,0.0,0.0,0.0,3.0,16.0,12.0,10.0,14.0,0.0,0.0,0.0,1.0,16.0,1.0,12.0,15.0,0.0,0.0,0.0,0.0,13.0,16.0,9.0,15.0,2.0,0.0,0.0,0.0,0.0,3.0,0.0,9.0,11.0,0.0,0.0,0.0,0.0,0.0,9.0,15.0,4.0,0.0,0.0,0.0,9.0,12.0,13.0,3.0,0.0,0.0],+ [0.0,0.0,1.0,9.0,15.0,11.0,0.0,0.0,0.0,0.0,11.0,16.0,8.0,14.0,6.0,0.0,0.0,2.0,16.0,10.0,0.0,9.0,9.0,0.0,0.0,1.0,16.0,4.0,0.0,8.0,8.0,0.0,0.0,4.0,16.0,4.0,0.0,8.0,8.0,0.0,0.0,1.0,16.0,5.0,1.0,11.0,3.0,0.0,0.0,0.0,12.0,12.0,10.0,10.0,0.0,0.0,0.0,0.0,1.0,10.0,13.0,3.0,0.0,0.0],+ [0.0,0.0,0.0,0.0,14.0,13.0,1.0,0.0,0.0,0.0,0.0,5.0,16.0,16.0,2.0,0.0,0.0,0.0,0.0,14.0,16.0,12.0,0.0,0.0,0.0,1.0,10.0,16.0,16.0,12.0,0.0,0.0,0.0,3.0,12.0,14.0,16.0,9.0,0.0,0.0,0.0,0.0,0.0,5.0,16.0,15.0,0.0,0.0,0.0,0.0,0.0,4.0,16.0,14.0,0.0,0.0,0.0,0.0,0.0,1.0,13.0,16.0,1.0,0.0],+ [0.0,0.0,5.0,12.0,1.0,0.0,0.0,0.0,0.0,0.0,15.0,14.0,7.0,0.0,0.0,0.0,0.0,0.0,13.0,1.0,12.0,0.0,0.0,0.0,0.0,2.0,10.0,0.0,14.0,0.0,0.0,0.0,0.0,0.0,2.0,0.0,16.0,1.0,0.0,0.0,0.0,0.0,0.0,6.0,15.0,0.0,0.0,0.0,0.0,0.0,9.0,16.0,15.0,9.0,8.0,2.0,0.0,0.0,3.0,11.0,8.0,13.0,12.0,4.0],+ [0.0,2.0,9.0,15.0,14.0,9.0,3.0,0.0,0.0,4.0,13.0,8.0,9.0,16.0,8.0,0.0,0.0,0.0,0.0,6.0,14.0,15.0,3.0,0.0,0.0,0.0,0.0,11.0,14.0,2.0,0.0,0.0,0.0,0.0,0.0,2.0,15.0,11.0,0.0,0.0,0.0,0.0,0.0,0.0,2.0,15.0,4.0,0.0,0.0,1.0,5.0,6.0,13.0,16.0,6.0,0.0,0.0,2.0,12.0,12.0,13.0,11.0,0.0,0.0],+ [0.0,0.0,0.0,8.0,15.0,1.0,0.0,0.0,0.0,0.0,1.0,14.0,13.0,1.0,1.0,0.0,0.0,0.0,10.0,15.0,3.0,15.0,11.0,0.0,0.0,7.0,16.0,7.0,1.0,16.0,8.0,0.0,0.0,9.0,16.0,13.0,14.0,16.0,5.0,0.0,0.0,1.0,10.0,15.0,16.0,14.0,0.0,0.0,0.0,0.0,0.0,1.0,16.0,10.0,0.0,0.0,0.0,0.0,0.0,10.0,15.0,4.0,0.0,0.0],+ [0.0,5.0,12.0,13.0,16.0,16.0,2.0,0.0,0.0,11.0,16.0,15.0,8.0,4.0,0.0,0.0,0.0,8.0,14.0,11.0,1.0,0.0,0.0,0.0,0.0,8.0,16.0,16.0,14.0,0.0,0.0,0.0,0.0,1.0,6.0,6.0,16.0,0.0,0.0,0.0,0.0,0.0,0.0,5.0,16.0,3.0,0.0,0.0,0.0,1.0,5.0,15.0,13.0,0.0,0.0,0.0,0.0,4.0,15.0,16.0,2.0,0.0,0.0,0.0],+ [0.0,0.0,0.0,8.0,15.0,1.0,0.0,0.0,0.0,0.0,0.0,12.0,14.0,0.0,0.0,0.0,0.0,0.0,3.0,16.0,7.0,0.0,0.0,0.0,0.0,0.0,6.0,16.0,2.0,0.0,0.0,0.0,0.0,0.0,7.0,16.0,16.0,13.0,5.0,0.0,0.0,0.0,15.0,16.0,9.0,9.0,14.0,0.0,0.0,0.0,3.0,14.0,9.0,2.0,16.0,2.0,0.0,0.0,0.0,7.0,15.0,16.0,11.0,0.0],+ [0.0,0.0,1.0,8.0,15.0,10.0,0.0,0.0,0.0,3.0,13.0,15.0,14.0,14.0,0.0,0.0,0.0,5.0,10.0,0.0,10.0,12.0,0.0,0.0,0.0,0.0,3.0,5.0,15.0,10.0,2.0,0.0,0.0,0.0,16.0,16.0,16.0,16.0,12.0,0.0,0.0,1.0,8.0,12.0,14.0,8.0,3.0,0.0,0.0,0.0,0.0,10.0,13.0,0.0,0.0,0.0,0.0,0.0,0.0,11.0,9.0,0.0,0.0,0.0],+ [0.0,0.0,10.0,7.0,13.0,9.0,0.0,0.0,0.0,0.0,9.0,10.0,12.0,15.0,2.0,0.0,0.0,0.0,4.0,11.0,10.0,11.0,0.0,0.0,0.0,0.0,1.0,16.0,10.0,1.0,0.0,0.0,0.0,0.0,12.0,13.0,4.0,0.0,0.0,0.0,0.0,0.0,12.0,1.0,12.0,0.0,0.0,0.0,0.0,1.0,10.0,2.0,14.0,0.0,0.0,0.0,0.0,0.0,11.0,14.0,5.0,0.0,0.0,0.0],+ [0.0,0.0,6.0,14.0,4.0,0.0,0.0,0.0,0.0,0.0,11.0,16.0,10.0,0.0,0.0,0.0,0.0,0.0,8.0,14.0,16.0,2.0,0.0,0.0,0.0,0.0,1.0,12.0,12.0,11.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,11.0,3.0,0.0,0.0,0.0,0.0,0.0,0.0,5.0,11.0,0.0,0.0,0.0,1.0,4.0,4.0,7.0,16.0,2.0,0.0,0.0,7.0,16.0,16.0,13.0,11.0,1.0]+ ]++testNeighbourProbs :: [[Probability]]+testNeighbourProbs = neighbourProbabilities def testInput
+ test/Data/Algorithm/TSNE/UtilsSpec.hs view
@@ -0,0 +1,33 @@+module Data.Algorithm.TSNE.UtilsSpec (main, spec) where++import Test.Hspec+import Data.Algorithm.TSNE.Utils++-- `main` is here so that this module can be run from GHCi on its own. It is+-- not needed for automatic spec discovery.+main :: IO ()+main = hspec spec++u = undefined++spec :: Spec+spec = do+ describe "infinity" $ do+ it "greater than 1e99" $ do+ infinity `shouldSatisfy` (> 1e99)++ describe "symmetricalMatrixFromTopRight" $ do+ it "passed nothing returns nothing" $ do+ symmetricalMatrixFromTopRight [] `shouldBe` ([] :: [[Double]])+ it "passed one value returns one value" $ do+ symmetricalMatrixFromTopRight [[42]] `shouldBe` ([[42]] :: [[Double]])+ it "passed 2x2 top right" $ do+ symmetricalMatrixFromTopRight [["a1", "a2"], ["b2"]] `shouldBe` [["a1", "a2"], ["a2", "b2"]] + it "passed 3x3 top right" $ do+ symmetricalMatrixFromTopRight [["a1", "a2", "a3"], ["b2", "b3"], ["c3"]] `shouldBe` [["a1", "a2", "a3"], ["a2", "b2", "b3"], ["a3", "b3", "c3"]] ++ describe "qdist" $ do+ it "passed nothing return nothing" $ do+ pending+ --qdist [] `shouldBe` ([] :: [[Double]])+
+ test/Data/Algorithm/TSNESpec.hs view
@@ -0,0 +1,17 @@+module Data.Algorithm.TSNESpec (main, spec) where++import Test.Hspec+import Data.Default (def)+import Data.Algorithm.TSNE++-- `main` is here so that this module can be run from GHCi on its own. It is+-- not needed for automatic spec discovery.+main :: IO ()+main = hspec spec++spec :: Spec+spec = do+ describe "tsne" $ do+ it "passed nothing returns nothing" $ do+ pending+ --tsne def [[]] `shouldReturn` []
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ tsne.cabal view
@@ -0,0 +1,58 @@+name: tsne+version: 1.0.0.1+synopsis: t-SNE+description: Pure Haskell implementation of the t-SNE dimension reduction algorithm.+homepage: https://bitbucket.org/robagar/haskell-tsne+license: LGPL+license-file: LICENSE+author: Rob Agar+maintainer: robagar@fastmail.net+copyright: 2017 Rob Agar+category: Algorithms+build-type: Simple+cabal-version: >=1.10++library+ hs-source-dirs: src+ exposed-modules: Data.Algorithm.TSNE,+ Data.Algorithm.TSNE.Types, + Data.Algorithm.TSNE.Internals, + Data.Algorithm.TSNE.Utils,+ Data.Algorithm.TSNE.Checks + build-depends: base >= 4.7 && < 5,+ data-default,+ deepseq,+ normaldistribution,+ pipes+ default-language: Haskell2010++test-suite tSNE-test+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Spec.hs+ other-modules: Data.Algorithm.TSNESpec,+ Data.Algorithm.TSNE.InternalsSpec,+ Data.Algorithm.TSNE.UtilsSpec,+ Data.Algorithm.TSNE.ChecksSpec+ build-depends: base,+ hspec,+ data-default,+ tsne+ ghc-options: -threaded + -rtsopts + -with-rtsopts=-N+ default-language: Haskell2010++executable haskell_tsne_example+ hs-source-dirs: example+ main-is: Main.hs+ build-depends: base,+ data-default,+ pipes,+ tsne+ ghc-options: -threaded+ default-language: Haskell2010++source-repository head+ type: mercurial+ location: https://bitbucket.org/robagar/haskell-tsne