hmatrix-nipals 0.1 → 0.2
raw patch · 3 files changed
+98/−27 lines, 3 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Numeric.LinearAlgebra.NIPALS: firstPCFromScoresM :: Monad m => m [Vector Double] -> Vector Double -> m (Vector Double, Vector Double)
+ Numeric.LinearAlgebra.NIPALS: residual :: [Vector Double] -> Vector Double -> Vector Double -> [Vector Double]
Files
- hmatrix-nipals.cabal +16/−10
- src/Numeric/LinearAlgebra/NIPALS.hs +68/−17
- test/tests.hs +14/−0
hmatrix-nipals.cabal view
@@ -1,5 +1,5 @@ Name: hmatrix-nipals-Version: 0.1+Version: 0.2 Synopsis: NIPALS method for Principal Components Analysis on large data-sets. @@ -26,16 +26,17 @@ . NIPALS is not generally recommended because sample matrices where the largest eigenvalues are close in magnitude will cause NIPALS to- converge very slowly. In general, Lanczos methods- <http://en.wikipedia.org/wiki/Lanczos_algorithm> or some other- truncated singular value decomposition algorithm are preferred to- NIPALS because of this convergence issue, but these methods often- require the sample matrix to fit in memory, or store large- conditioning matrices, which isn't always feasible. However, if you- know of free and memory-efficient implementations of these more- sophisticated algorithms, please contact the author with a pointer.+ converge very slowly. For sparse matrices, use Lanczos methods+ <http://en.wikipedia.org/wiki/Lanczos_algorithm>, and for dense+ matrices, random-projection methods+ <http://amath.colorado.edu/faculty/martinss/Pubs/2009_HMT_random_review.pdf>+ can be used. However, these methods are harder to implement in a+ single pass. If you know of a good, single-pass, and+ memory-efficient implementation of either of these methods, please+ contact the author. Homepage: http://github.com/alanfalloon/hmatrix-nipals+Bug-reports: https://github.com/alanfalloon/hmatrix-nipals/issues License: LGPL-2.1 License-file: LICENSE Author: Alan Falloon@@ -44,7 +45,12 @@ Stability: Experimental Category: Math Build-type: Simple-Cabal-version: >=1.4+Cabal-version: >=1.6++Source-repository head+ Type: git+ Location: git://github.com/alanfalloon/hmatrix-nipals.git+ Branch: master Flag test Description: Build unit-tests
src/Numeric/LinearAlgebra/NIPALS.hs view
@@ -4,8 +4,12 @@ ( -- * Simplified Interface firstPC , firstPCFromScores+ -- * Monadic interface+ , firstPCFromScoresM+ , residual ) where +import Data.List (foldl1') import Numeric.LinearAlgebra -- | Calculate the first principal component of a set of samples.@@ -48,7 +52,7 @@ steps = iterate refine (t0,undefined) convergence = let scores = map fst steps dscores = zipWith diffScores scores $ tail scores- in smooth dscores+ in dscores (t,p) = let steps' = zip convergence $ tail steps steps'' = dropWhile (\(c,_) -> c > threshold) steps' in snd $ head steps''@@ -59,29 +63,76 @@ p' = toUnit (trans m <> t) t' = m <> p' - diffScores ta tb = let xa = ta<.>ta- xb = tb<.>tb- x = abs $ xa - xb- in (x / xb) :: Double+ threshold = 16*eps - threshold = sqrt (fromIntegral (cols m + rows m)) * eps+diffScores :: Vector Double -> Vector Double -> Double+diffScores ta tb = let xa = ta<.>ta+ xb = tb<.>tb+ x = abs $ xa - xb+ in (x / (eps+xb)) :: Double toUnit :: Vector Double -> Vector Double toUnit v = if mag <= 0.0 then dim v |> (1 : repeat 0) else scale (1/mag) v where mag = norm2 v -smooth :: [Double] -> [Double]-smooth (x0:xs@(x1:_)) = (2*x0+x1)/3 : smooth xs-smooth _ = []- -- | Calculate the first principal component -- calculating the -- samples fresh on every pass. ----- This function calculates the exact same results as 'firstPC', but--- instead of an input 'Matrix', it takes a monad action that yields--- the list of samples, and it guarantees that the list returned by--- the action will be consumed in a single pass. However the action--- may be demanded many times.-firstPCM :: (Product t, Monad m) => m [Vector t] -> m (Vector t, Vector t, [Vector t])-firstPCM = undefined+-- This function calculates the exact same results as+-- 'firstPCFromScores' (minus the residual), but instead of an input+-- 'Matrix', it takes a monad action that yields the list of samples,+-- and it guarantees that the list returned by the action will be+-- consumed in a single pass. However the action may be demanded many+-- times.+--+-- The residual can't be calculated lazily, like it is in+-- 'firstPCFromScores', because the samples would need to be+-- demanded. Instead, to calculate the residual use 'residual'.+--+-- There is no corresponding @firstPCM@ that guesses the initial score+-- vector for you because if you need to use this function instead of+-- 'firstPC', then you really should come up with a reasonable+-- starting point or it will take forever.+firstPCFromScoresM :: Monad m =>+ m [Vector Double]+ -> Vector Double+ -> m (Vector Double, Vector Double)+firstPCFromScoresM samplesM t0 = do+ (t,p) <- refine t0+ iter t0 t p+ where+ refine t = do+ p <- samplesM >>= (\s -> return $ transMult1Pass s t)+ let p' = toUnit p+ t' <- samplesM >>= (\s -> return $ mult1Pass s p')+ return (t',p')+ threshold = 16 * eps+ iter t0 t1 p1 | diff < threshold = return $ (p1,t1)+ | otherwise = do+ (t2,p2) <- refine t1+ iter t1 t2 p2+ where+ diff = diffScores t0 t1++-- Single pass versions of (trans m <> v) and (m <> v) respectively,+-- where m is a list of rows+transMult1Pass, mult1Pass :: [Vector Double] -> Vector Double -> Vector Double+mult1Pass rows vec = fromList $ map (dot vec) rows+transMult1Pass cols vec = foldl1' add $ zipWith scale (toList vec) cols++-- | Calculate the residuals of a series of samples given a component+-- and score vector.+--+-- > (p,t) <- firstPCFromScoresM samplesM (randomVector 0 Gaussian numSamples)+-- > samples <- samplesM+-- > let r = residual samples p t+residual :: [Vector Double] -- ^ The samples+ -> Vector Double -- ^ The component (also called the loading)+ -> Vector Double -- ^ The scores+ -> [Vector Double] -- ^ The residuals for each sample+residual s p t = r+ where+ ts = toList t+ comp = map (`scale` p) ts+ r = zipWith sub s comp
test/tests.hs view
@@ -1,4 +1,5 @@ import Data.List+import Data.Maybe (fromJust) import Numeric.LinearAlgebra import Numeric.LinearAlgebra.NIPALS import Foreign.Storable@@ -15,6 +16,7 @@ ] , testGroup "Correctness" [ testProperty "resultCanRecoverInput" prop_recoverInput+ , testProperty "monadSameAnswer" prop_monadSameAnswer ] ] @@ -43,6 +45,18 @@ tRelErr = relativeDifference t t' pRelErr = relativeDifference p p' th = sqrt $ fromIntegral (cols m * rows m) * eps++prop_monadSameAnswer m = tRelErr <= th .&&. pRelErr <= th .&&. rRelErr <= th+ where+ guess = head $ toColumns m+ samplesM = return $ toRows m+ (p,t,r) = firstPCFromScores m guess+ (p',t') = fromJust $ firstPCFromScoresM samplesM guess+ r' = fromRows $ residual (toRows m) p' t'+ tRelErr = relativeDifference t t'+ pRelErr = relativeDifference p p'+ rRelErr = if rank m > 2 then relativeDifference r r' else 0+ th = 1e-7 relativeDifference :: (Normed c t, Container c t) => c t -> c t -> Double relativeDifference x y = realToFrac (norm (x `sub` y) / (peps + norm x + norm y))