packages feed

Learning 0.0.0 → 0.0.1

raw patch · 3 files changed

+182/−57 lines, 3 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Learning: learn :: Storable a => Vector a -> Matrix Double -> Matrix Double -> Either String (Classifier a)
- Learning: type Classifier a = (Matrix Double -> a)
+ Learning: Classifier :: (Matrix Double -> a) -> Classifier a
+ Learning: Regressor :: (Matrix Double -> Matrix Double) -> Regressor
+ Learning: [classify] :: Classifier a -> Matrix Double -> a
+ Learning: [predict] :: Regressor -> Matrix Double -> Matrix Double
+ Learning: [toList] :: Dataset a b -> [(a, b)]
+ Learning: accuracy :: (Eq a, Fractional acc) => [a] -> [a] -> acc
+ Learning: fromList :: [(a, b)] -> Dataset a b
+ Learning: learnClassifier :: (Storable a, Eq a) => Vector a -> Matrix Double -> Matrix Double -> Either String (Classifier a)
+ Learning: learnRegressor :: Matrix Double -> Matrix Double -> Either String Regressor
+ Learning: newtype Classifier a
+ Learning: newtype Regressor
+ Learning: nrmse :: (Storable a, Floating a) => Vector a -> Vector a -> a
+ Learning: pca' :: [Vector Double] -> (Matrix Double, Vector Double)
+ Learning: type Readout = Matrix Double
+ Learning: type Teacher = Matrix Double
- Learning: Dataset :: [a] -> [b] -> Dataset a b
+ Learning: Dataset :: [a] -> [b] -> [(a, b)] -> Dataset a b
- Learning: learn' :: Matrix Double -> Matrix Double -> Maybe (Matrix Double)
+ Learning: learn' :: Matrix Double -> Matrix Double -> Maybe Readout
- Learning: scores :: Matrix Double -> Matrix Double -> Vector Double
+ Learning: scores :: Readout -> Matrix Double -> Vector Double
- Learning: teacher :: Int -> Int -> Int -> Matrix Double
+ Learning: teacher :: Int -> Int -> Int -> Teacher
- Learning: winnerTakesAll :: Storable a => Matrix Double -> Vector a -> Classifier a
+ Learning: winnerTakesAll :: (Storable a, Eq a) => Readout -> Vector a -> Classifier a

Files

ChangeLog.md view
@@ -1,3 +1,6 @@ # Changelog for Learning -## Unreleased changes+## 0.0.1 *February 9th 2018*+  * Define core data structures+  * Provide linear classifiers and regressors for the supervised learning+  * Provide principal components analysis (PCA) and evaluation tools
Learning.cabal view
@@ -2,10 +2,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: cc0645cca2baee4a686a5bc4eebc313fda168d1efe6b0301bccb1ec7e7543c25+-- hash: 41211cf12c83c4bc7d6b1152f8d6adc0e35b8d8a8aee00c41bf06728458510ac  name:           Learning-version:        0.0.0+version:        0.0.1 synopsis:       Most frequently used machine learning tools description:    Please see the README on Github at <https://github.com/masterdezign/Learning#readme> category:       ML
src/Learning.hs view
@@ -1,6 +1,6 @@ -- | -- = Machine learning utilities--- +-- -- A micro library containing the most common machine learning tools. -- Check also the mltool package https://hackage.haskell.org/package/mltool. @@ -8,107 +8,182 @@ module Learning (   -- * Datasets   Dataset (..)+  , Learning.fromList -  -- * Principal component analysis+  -- * Principal components analysis   , PCA (..)   , pca+  , pca'    -- * Supervised learning-  , Classifier-  , learn-  , learn'+  , Teacher   , teacher+  , Classifier (..)+  , Regressor (..)+  , Readout+  , learnClassifier+  , learnRegressor+  , learn'   , scores   , winnerTakesAll    -- * Evaluation-  , errors   , errorRate+  , errors+  , accuracy+  , nrmse   ) where  import           Numeric.LinearAlgebra import qualified Data.Vector.Storable as V --- Supervised dataset-data Dataset a b = Dataset { _samples :: [a], _labels :: [b] }+-- | A dataset representation for supervised learning+data Dataset a b = Dataset+  { _samples :: [a]+  , _labels :: [b]+  , toList :: [(a, b)]+  } --- | Computes "covariance matrix", alternative to (snd. meanCov).+-- | Create a `Dataset` from list of samples (first) and labels (second)+fromList :: [(a, b)] -> Dataset a b+fromList xs = let (samples', labels') = unzip xs+              in Dataset+                 { Learning.toList = xs+                 , _samples = samples'+                 , _labels = labels'+                 }++-- The snippet below computes "covariance matrix", alternative to (snd. meanCov). -- Source: https://hackage.haskell.org/package/mltool-0.1.0.2/docs/src/MachineLearning.PCA.html--- covarianceMatrix :: Matrix Double -> Matrix Double--- covarianceMatrix x = ((tr x) <> x) / (fromIntegral $ rows x)+--+--     > covarianceMatrix :: Matrix Double -> Matrix Double+--     > covarianceMatrix x = ((tr x) <> x) / (fromIntegral $ rows x) --- | Produces a compression matrix u'-pca' :: Int -> [Vector Double] -> Matrix Double-pca' maxDim xs = tr u ? [0..maxDim - 1]+-- | Compute the covariance matrix @sigma@+-- and return its eigenvectors @u'@ and eigenvalues @s@+pca' :: [Vector Double]  -- ^ Data samples+     -> (Matrix Double, Vector Double)+pca' xs = (u', s)   where     xs' = fromBlocks $ map ((: []). tr. reshape 1) xs-    -- Covariance matrix Sigma+    -- Covariance matrix     sigma = snd $ meanCov xs'-    -- Eigenvectors matrix U-    (u, _, _) = svd $ unSym sigma+    -- Eigenvectors matrix u' and eigenvalues vector s+    (u', s, _) = svd $ unSym sigma +-- | Principal components analysis tools data PCA = PCA-  { _u :: Matrix Double  -- Compression matrix U+  { _u :: Matrix Double+  -- ^ Compression matrix U   , _compress :: Vector Double -> Matrix Double+  -- ^ Compression function   , _decompress :: Matrix Double -> Vector Double+  -- ^ Inverse to compression function   } --- | Principal component analysis (PCA)-pca :: Int-    -> [Vector Double]+-- | Principal components analysis resulting in `PCA` tools+pca :: Int  -- ^ Number of principal components to preserve+    -> [Vector Double]  -- ^ Analyzed data samples     -> PCA-pca maxDim xs = let u' = pca' maxDim xs-                    u = tr u'+pca maxDim xs = let (u', _) = pca' xs+                    u = takeColumns maxDim u'                 in PCA                    { _u = u-                   , _compress = (u' <>). reshape 1+                   , _compress = (tr u <>). reshape 1                    , _decompress = flatten. (u <>)                    } -type Classifier a = (Matrix Double -> a)+-- | Classifier function that maps some network state with measurements as matrix columns+-- and features as rows, into a categorical output.+newtype Classifier a = Classifier { classify :: Matrix Double -> a } --- | Perform supervised learning to create a linear classifier.--- The ridge regression is run with regularization parameter mu=1e-4.-learn-  :: V.Storable a =>+-- | Regressor function that maps some feature matrix+-- into a continuous multidimensional output. The feature matrix is expected+-- to have columns corresponding to measurements (data points) and rows, features.+newtype Regressor = Regressor { predict :: Matrix Double -> Matrix Double }++-- | Linear readout (matrix)+type Readout = Matrix Double++-- | Teacher matrix+--+--     > 0 0 0 0 0+--     > 0 0 0 0 0+--     > 1 1 1 1 1 <- Desired class index is 2+--     > 0 0 0 0 0 <- Number of classes is 4+--     >         ^+--     >   5 repetitions+type Teacher = Matrix Double++-- | Perform supervised learning (ridge regression) and create+-- a linear `Classifier` function.+-- The regression is run with regularization parameter μ = 1e-4.+learnClassifier+  :: (V.Storable a, Eq a) =>      Vector a+     -- ^ All possible outcomes (classes) list      -> Matrix Double+     -- ^ Network state (nonlinear response) where each matrix column corresponds to a measurement (data point)+     -- and each row corresponds to a feature      -> Matrix Double+     -- ^ Horizontally concatenated `Teacher` matrices where each row corresponds to a desired class      -> Either String (Classifier a)-learn klasses xs teacher' =+learnClassifier klasses xs teacher' =   case learn' xs teacher' of-    Just readout -> Right (classify readout klasses)+    Just readout -> Right (classify' readout klasses)     Nothing -> Left "Couldn't learn: check `xs` matrix properties"-{-# SPECIALIZE learn+{-# SPECIALIZE learnClassifier   :: Vector Int      -> Matrix Double      -> Matrix Double      -> Either String (Classifier Int) #-} --- | Create a linear readout using the ridge regression-learn'+-- | Perform supervised learning (ridge regression) and create+-- a linear `Regressor` function.+learnRegressor   :: Matrix Double-     -> Matrix Double-     -> Maybe (Matrix Double)+  -- ^ Feature matrix with data points (measurements) as colums and features as rows+  -> Matrix Double+  -- ^ Desired outputs matrix corresponding to data point columns.+  -- In case of scalar (one-dimensional) prediction output, it should be a single row matrix.+  -> Either String Regressor+learnRegressor xs target =+  case learn' xs target of+    Just readout -> let rgr = Regressor (readout <>)+                    in Right rgr+    Nothing -> Left "Couldn't learn: check `xs` matrix properties"++-- | Create a linear `Readout` using the ridge regression.+-- Similar to `learnRegressor`, but instead of a `Regressor` function+-- a (already transposed) `Readout` matrix may be returned.+learn'+  :: Matrix Double  -- ^ Network state (nonlinear response)+  -> Matrix Double  -- ^ Horizontally concatenated `Teacher` matrices+  -> Maybe Readout learn' a b = case ridgeRegression 1e-4 a b of     (Just x) -> Just (tr x)     _ -> Nothing --- | Teacher matrix-teacher :: Int -> Int -> Int -> Matrix Double+-- | Create a binary `Teacher` matrix with ones row corresponding to+-- the desired class index+teacher+  :: Int  -- ^ Number of classes (labels)+  -> Int  -- ^ Desired class index (starting from zero)+  -> Int  -- ^ Number of repeated columns in teacher matrix+  -> Teacher teacher nLabels correctIndex repeatNo = fromBlocks. map f $ [0..nLabels-1]   where ones = konst 1.0 (1, repeatNo)         zeros = konst 0.0 (1, repeatNo)         f i | i == correctIndex = [ones]             | otherwise = [zeros] --- | Performs the supervised training that results in a linear readout.+-- | Performs a supervised training that results in a linear readout. -- See https://en.wikipedia.org/wiki/Tikhonov_regularization-ridgeRegression :: +ridgeRegression ::   Double  -- ^ Regularization constant   -> Matrix Double-  -> Matrix Double -  -> Maybe (Matrix Double)+  -> Matrix Double+  -> Maybe Readout ridgeRegression μ tA tB = linearSolve oA oB   where     oA = (tA <> tr tA) + (scalar μ * ident (rows tA))@@ -118,36 +193,83 @@  -- | Winner-takes-all classification method winnerTakesAll-  :: V.Storable a-  => Matrix Double  -- ^ Transposed readout matrix+  :: (V.Storable a, Eq a)+  => Readout  -- ^ `Readout` matrix   -> Vector a  -- ^ Vector of possible classes   -> Classifier a  -- ^ `Classifier`-winnerTakesAll readout klasses response = klasses V.! klass-  where klass = maxIndex $ scores readout response+winnerTakesAll readout klasses = Classifier clf+  where clf x = let klass = maxIndex $ scores readout x+                in klasses V.! klass  -- | Evaluate the network state (nonlinear response) according--- to some readout matrix trW.-scores :: Matrix Double -> Matrix Double -> Vector Double+-- to some `Readout` matrix. Used by classification strategies+-- such as `winnerTakesAll`.+scores+  :: Readout  -- ^ `Readout` matrix+  -> Matrix Double  -- ^ Network state+  -> Vector Double scores trW response = evalScores   where w = trW <> response         -- Sum the elements in each row         evalScores = w #> vector (replicate (cols w) 1.0) -classify-  :: V.Storable a+classify'+  :: (V.Storable a, Eq a)      => Matrix Double -> Vector a -> Classifier a-classify = winnerTakesAll-{-# SPECIALIZE classify+classify' = winnerTakesAll+{-# SPECIALIZE classify'   :: Matrix Double -> Vector Int -> Classifier Int   #-} --- | Calculates the error rate in %+-- | Error rate in %, an error measure for classification tasks+--+-- >>> errorRate [1,2,3,4] [1,2,3,7]+-- 25.0 errorRate :: (Eq a, Fractional err) => [a] -> [a] -> err errorRate tgtLbls cLbls = 100 * fromIntegral errNo / fromIntegral (length tgtLbls)   where errNo = length $ errors $ zip tgtLbls cLbls {-# SPECIALIZE errorRate :: [Int] → [Int] → Double #-} --- | Returns the misclassified cases+-- | Accuracy of classification, @100% - errorRate@+--+-- >>> accuracy [1,2,3,4] [1,2,3,7]+-- 75.0+accuracy :: (Eq a, Fractional acc) => [a] -> [a] -> acc+accuracy tgt clf = let erate = errorRate tgt clf+                   in 100 - erate+{-# SPECIALIZE accuracy :: [Int] → [Int] → Double #-}++-- | Pairs of misclassified and correct values+--+-- >>> errors $ zip ['x','y','z'] ['x','b','a']+-- [('y','b'),('z','a')] errors :: Eq a => [(a, a)] -> [(a, a)] errors = filter (uncurry (/=)) {-# SPECIALIZE errors :: [(Int, Int)] -> [(Int, Int)] #-}++mean :: (V.Storable a, Fractional a) => Vector a -> a+mean xs = V.sum xs / fromIntegral (V.length xs)+{-# SPECIALISE mean :: Vector Double -> Double #-}++cov :: (V.Storable a, Fractional a) => Vector a -> Vector a -> a+cov xs ys = V.sum (V.zipWith (*) xs' ys') / fromIntegral (V.length xs')+  where+    xs' = V.map (`subtract` (mean xs)) xs+    ys' = V.map (`subtract` (mean ys)) ys+{-# SPECIALISE cov :: Vector Double -> Vector Double -> Double #-}++var :: (V.Storable a, Fractional a) => Vector a -> a+var x = cov x x+{-# SPECIALISE var :: Vector Double -> Double #-}++-- | Normalized root mean square error (NRMSE),+-- one of the most common error measures for regression tasks+nrmse :: (V.Storable a, Floating a)+      => Vector a  -- ^ Target signal+      -> Vector a  -- ^ Predicted signal+      -> a  -- ^ NRMSE+nrmse target estimated = sqrt (meanerr / targetVariance)+  where+    meanerr = mean. V.map (^2) $ V.zipWith (-) estimated target+    targetVariance = var target+{-# SPECIALIZE nrmse :: Vector Double -> Vector Double -> Double #-}