sequor-0.1: src/Labeler.hs
{-# LANGUAGE OverloadedStrings #-}
module Labeler
( ModelData(..)
, Config(..)
, train
, predict
)
where
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Data.IntMap as IntMap
import qualified Data.IntSet as IntSet
import Data.List (foldl',tails)
import Data.Maybe (fromMaybe)
import ListZipper
import qualified Perceptron.Sequence as P
import Perceptron.Sequence (Options(..))
import CorpusReader (Token)
import Utils (splitWith,uniq)
import Text.Printf
import Atom
import Control.Monad.RWS
import Features (maybeFeatures,features,outputFeatures,indexFeatures)
import qualified Data.Array as A
import qualified Data.Vector.Unboxed as V
import qualified Data.Binary as Binary
import qualified Text
import Text(Txt)
import Data.Char
import Data.Maybe (catMaybes)
import Config
data ModelData = ModelData { model :: P.Model
, config :: Config
}
instance Binary.Binary ModelData where
get = return ModelData `ap` Binary.get `ap` Binary.get
put (ModelData a b) = Binary.put a >> Binary.put b
-- Main exported functions
predict :: ModelData -> [[ListZipper Token]] -> [[Txt]]
predict m testdat =
fst . flip runAtoms (atomTable . config $ m) $
do flip mapM testdat $ \x ->
do x' <- mapM (maybeFeatures (config m)) $ x
predict' (P.decode (model m)) $ x'
train :: Config
-> Float
-> Int
-> Int
-> [([ListZipper Token],[Txt])]
-> [([ListZipper Token],[Txt])]
-> ModelData
train conf rate limit beam traindat heldout =
let ((m,_predicted),_atoms) =
runAtoms (run conf
(rate,limit,beam)
traindat
heldout)
$ empty
in m
-- Implementation
type F = Int
type Tag = Int
tagDictionary :: IntSet.IntSet
-> Int
-> [([V.Vector Int], [F])]
-> IntMap.IntMap [Tag]
tagDictionary indexFeatureSet wmin trainset =
let tags = concat . map snd $ trainset
ws = catMaybes
. map (V.find (`IntSet.member` indexFeatureSet))
. concat
. map fst
$ trainset
count_ws = IntMap.fromListWith (+) [ (w,1) | w <- ws ]
dict = IntMap.map Set.toList
. IntMap.fromListWith Set.union
$ [ (w,Set.singleton t) | (w,t) <- zip ws tags
, count_ws IntMap.! w >= wmin]
in dict == dict `seq` dict
pruneLabels :: Int -> [(x,[Txt])] -> [(x,[Txt])]
pruneLabels lim xys =
let freq = Map.fromListWith (+)
. map (\y -> (y,1))
. concat
. map snd
$ xys
undet = "UNDETERMINED"
in [ (x,[ if freq Map.! yi < lim then undet else yi | yi <- y ])
| (x,y) <- xys ]
run :: (Functor m, MonadAtoms m) =>
Config
-> (Float, Int,Int)
-> [([ListZipper Token], [Txt])]
-> [([ListZipper Token], [Txt])]
-> m (ModelData, [[Txt]])
run conf (rate, limit,beamp) trainset_in_full testset_in = do
let trainset_in = pruneLabels (minLabelFreq conf) trainset_in_full
ys = uniq . concat . map snd $ trainset_in :: [Txt]
ys' <- mapM toAtom ys
trainset <- mapM (mkfs $ features conf) trainset_in
outm <- mkOutputFeatureAtoms . map snd $ trainset_in
testset <- mapM (mkfs $ maybeFeatures conf) testset_in
tab <- table
let indexFeatureSet = indexFeatures tab
conf' = conf {atomTable = tab }
opts = Options { oYMap = outm
, oIndexSet = indexFeatureSet
, oYDict = tagDictionary indexFeatureSet
(wordMinCount conf') trainset
, oYs = ys'
, oBeam = beamp
, oRate = rate
, oEpochs = limit
}
m = P.train opts testset formatEval trainset
ps <- mapM (predict' (P.decode m . fst)) testset
return $ (ModelData { model = m , config = conf' }
,ps)
predict' :: (MonadAtoms m) =>
(t -> [Int]) -> t -> m [Txt]
predict' dec x = do
let xr = dec x
xr'<- mapM fromAtom xr
return xr'
mkOutputFeatureAtoms :: (MonadAtoms m) => [[Txt]] -> m P.YMap
mkOutputFeatureAtoms yss = do
let unigrams = map return . uniq . concat $ yss
bigrams = uniq $ concat [ filter ((==2) . length)
. map (take 2)
. tails
$ ys | ys <- yss ]
unigramis <- mapM (mapM toAtom) unigrams
bigramis <- mapM (mapM toAtom) bigrams
let ys = map head unigramis
(lo,hi) = (minimum ys,maximum ys)
unigramfs <- mapM (mapM toAtom) . map outputFeatures $ unigrams
bigramfs <- mapM (mapM toAtom) . map outputFeatures $ bigrams
zerofs <- mapM toAtom . outputFeatures $ []
let ymap1 = A.accumArray (V.++) V.empty (lo,hi)
. zip (map head unigramis)
. map V.fromList
$ unigramfs
ymap2 = A.accumArray (V.++) V.empty ((lo,lo),(hi,hi))
. zip (map (\ [y1,y2] -> (y1,y2)) bigramis)
. map V.fromList
$ bigramfs
return $ (V.fromList zerofs, ymap1, ymap2)
mkfs :: (MonadAtoms m) =>
(ListZipper Token -> m (V.Vector F))
-> ([ListZipper Token], [Txt])
-> m ([V.Vector F], [Tag])
mkfs f (x,y) = do
fs <- mapM f x
fs == fs `seq` return ()
y' <- mapM toAtom y
y' == y' `seq` return ()
return $ (fs,y')
formatEval :: P.Eval
formatEval 0 _ _ = printf "%10s %10s %10s" ("Iter"::String)
("Train"::String)
("Heldout"::String)
formatEval i ss heldout = printf "%10d %10.4f %10.4f" i (eval ss) (eval heldout)
eval :: Eq a => [([a],[a])] -> Double
eval ys =
let corr = foldl' (+) 0
. concat
$ [ [ 1 | (y,y') <- ys , (yi,yi') <- zip y y'
, yi == yi' ] ]
in corr / fromIntegral (length . concatMap fst $ ys)