packages feed

morfette-0.4.2: src/GramLab/Morfette/Models2.hs

module GramLab.Morfette.Models2 ( train
                               , trainFun
                               , predict 
                               , predictPipeline
                               , toModelFun
                               , mkPreprune
                               , sentToExamples
                               , FeatureSpec (..)
                               , Row(..)
                               )
where
import GramLab.Morfette.LZipper
import qualified Data.Map as Map
import Data.Map ((!))
import Data.List (sortBy,foldl',transpose)
import Data.Ord (comparing)
import Data.Dynamic
import GramLab.FeatureSet
import qualified GramLab.Perceptron.Model as M
import Data.Traversable (forM)
import qualified Data.IntMap as IntMap
import Data.Maybe (fromMaybe)
import Debug.Trace
import Data.Binary
import Control.Monad (liftM, liftM2)
import GramLab.Utils (uniq)
import qualified Data.Vector.Unboxed as U
import GramLab.Morfette.BinaryInstances

data Row t a = Row { input :: !t, output :: ![a] } 
               deriving (Eq,Ord,Show,Read)


data FeatureSpec t a = 
    FS { label    :: Row t a -> a
       , features :: LZipper (Row t a) (Row t a) (Row t a) -> [Feature String Double] 
       , preprune :: ProbDist a -> ProbDist a
       , check    :: LZipper (Row t a) (Row t a) (Row t a) -> a -> Bool
       , pruneUniqLabels :: Bool
       , trainSettings :: M.TrainSettings }
    
    
instance (Binary t, Binary a) => Binary (Row t a) where
  put t = put (input t) >> put (output t)
  get = liftM2 Row get get
    

type ProbDist a = [(a,Double)]

type Model t a = LZipper (Row t a)  (Row t a) (Row t a) -> ProbDist a

beamSearch :: 
              Int   
              -- beam size
           -> [Model t a] 
              -- models for each output column
           -> ProbDist (LZipper (Row t a) (Row t a) (Row t a)) 
              -- prob dist over sequence of "tokens in context" (as lzippers)
           -> ProbDist [Row t a] 
              -- prob dist over sequences of "tokens"
beamSearch n ms pzs =
  let apply pzs model = 
        prune n 
          [ (modify (\t -> t { output = output t ++ [label] }) z  -- add label to output
          , p0 * p                                                -- multiply probability in
          ) 
          | (z, p0) <- pzs                                        -- for each sequence (lzipper)
          , (label, p) <- model z                                 -- get label and prob by applying
                                                                  -- model. 
          ]
  in if any (atEnd . fst) pzs  
        -- of any lzipper at end then return tokens
     then flip map pzs $ \(z,p) -> (reverse (left z), p)
     else 
       -- otherwise apply both classifiers in turn in the lzipper seq,
       -- prune, adjust probs
       beamSearch n ms . map (\(z,p) -> (slide z,p)) $! (foldl' apply pzs ms)

-- pruning and prepruning

prune :: Int -> ProbDist a -> ProbDist a
prune n = take n . sortBy (flip (comparing snd))
collectUntil cond f z []     = []
collectUntil cond f z (x:xs) = let z' = (f $! x) $! z 
                               in  if   cond x z' then []
                                   else x: collectUntil cond f z' xs
mkPreprune th = collectUntil (\x z -> th > snd x / z) ((+) . snd) 0

trainFun ::  (Ord a, Show a) => [FeatureSpec t a] -> [[Row t a]]  -> [Model t a]
trainFun fspecs sents = 
  let ms = train fspecs sents
  in (zipWith toModelFun fspecs ms) 
 
train :: (Ord a,Show a) => 
         [FeatureSpec t a] 
      -> [[Row t a]]  
      -> [M.Model a Int String Double]
train fspecs sents = 
  flip map fspecs
           $ \fs ->  let yxs_all = concatMap (sentToExamples fs) $ sents
                         zs_all  = concat [ take (length s) 
                                        . iterate slide 
                                        . fromList
                                        $ s 
                                        | s <- sents ]
                         ys  = (if pruneUniqLabels fs then Map.filter (>1) else id)
                               . Map.fromListWith (+)
                               . map (\ (y, _) -> (y, 1))
                               $ yxs_all
                         (yxs, zs) = unzip
                                     . filter (flip Map.member ys . fst . fst)
                                     $ zip yxs_all zs_all  
                         yss = [ [ y | y <- Map.keys ys , check fs z y ] 
                                 | z <- zs ]
                     in M.train (trainSettings fs) yss yxs

toModelFun :: (Ord a, Show a) => 
              FeatureSpec t a 
           -> (M.Model a Int String Double) 
           -> Model t a
toModelFun fs m = 
    let ys = Map.keys . M.classMap . M.modelData $ m
    in
    \ z -> case filter (check fs z) ys of
             [] -> error "GramLab.Morfette.Models.toModelFun: unexpected []"
             y:ys' -> 
                 preprune fs 
                  . M.distribution m (y:ys')
                  . features fs 
                  $ z 
           
predict :: Int -> Int -> [Model t a] -> [[Row t a]] -> [[[Row t a]]]
predict k beamSize models sents = map predictK sents
    where predictK s = transpose 
                         . map fst 
                         . take k
                         . beamSearch beamSize models 
                         $ [(fromList s,1)]

predictPipeline :: Int -> [Model t a] -> [[Row t a]] -> [[Row t a]]
predictPipeline beamSize models sents = map predictK sents
    where predictK s = foldl' (\s1 m -> fst . head . beamSearch beamSize [m] 
                                         $ [(fromList s1,1)]) s models
                               

sentToExamples ::  FeatureSpec t a 
               -> [Row t a] 
               -> [(a,[Feature String Double])]
sentToExamples fs xs = slideThru f (fromList xs)
    where f z = 
              (  label fs 
               . fromMaybe
                     (error "GramLab.Morfette.Models.sentToExample:fromMaybe")
               . focus 
               $  z
              , features fs z)
    
slideThru f z  | atEnd z   = []
slideThru f z              = f z:slideThru f (slide z)