concraft-pl 0.7.4 → 2.0.1
raw patch · 13 files changed
+2040/−201 lines, 13 filesdep +concraft-pldep +crf-chain1-constraineddep +crf-chain2-tiersdep ~aesondep ~binarydep ~bytestringsetup-changed
Dependencies added: concraft-pl, crf-chain1-constrained, crf-chain2-tiers, dhall, filepath, http-types, lens, pedestrian-dag, scotty, vector, wreq
Dependency ranges changed: aeson, binary, bytestring, concraft, containers, process, sgd, tagset-positional, text, transformers
Files
- Setup.hs +0/−2
- changelog +11/−0
- concraft-pl.cabal +46/−12
- src/NLP/Concraft/Polish.hs +3/−2
- src/NLP/Concraft/Polish/DAG/Config.hs +26/−0
- src/NLP/Concraft/Polish/DAG/Config/Disamb.hs +62/−0
- src/NLP/Concraft/Polish/DAG/Format/Base.hs +304/−0
- src/NLP/Concraft/Polish/DAG/Morphosyntax.hs +363/−0
- src/NLP/Concraft/Polish/DAG/Server.hs +128/−0
- src/NLP/Concraft/Polish/DAGSeg.hs +612/−0
- src/NLP/Concraft/Polish/Maca.hs +1/−0
- src/NLP/Concraft/Polish/Morphosyntax.hs +6/−5
- tools/concraft-pl.hs +478/−180
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
changelog view
@@ -1,5 +1,16 @@ -*-change-log-*- +2.0.1 Jul 2018+ * External configuration of disambiguation tiers++2.0.0 Jul 2018+ * Support Morfeusz DAGs+ * Use the Morfeusz format+ * Drop dependency on Maca++0.7.5 Aug 2016+ * Start using `stack` to avoid dependency problems+ 0.7.4 Nov 2014 * Ignore non-printable characters for compatibility with Maca
concraft-pl.cabal view
@@ -1,12 +1,12 @@ name: concraft-pl-version: 0.7.4+version: 2.0.1 synopsis: Morphological tagger for Polish description: A morphological tagger for Polish based on the concraft library. license: BSD3 license-file: LICENSE-cabal-version: >= 1.6-copyright: Copyright (c) 2013 Jakub Waszczuk+cabal-version: >= 1.10+copyright: Copyright (c) 2012-2018 Jakub Waszczuk, IPI PAN author: Jakub Waszczuk maintainer: waszczuk.kuba@gmail.com stability: experimental@@ -19,24 +19,34 @@ data-files: config/nkjp-tagset.cfg library+ default-language:+ Haskell2010 hs-source-dirs: src- build-depends: base >= 4 && < 5- , concraft >= 0.9 && < 0.10+ , concraft >= 0.11 && < 0.12+ , pedestrian-dag >= 0.2 && < 0.3+ , crf-chain1-constrained >= 0.4 && < 0.5+ , crf-chain2-tiers >= 0.3 && < 0.4 , tagset-positional >= 0.3 && < 0.4- , sgd >= 0.3.3 && < 0.4+ , sgd >= 0.4 && < 0.5 , containers >= 0.4 && < 0.6 , bytestring >= 0.9 && < 0.11 , text >= 0.11 && < 1.3- , aeson >= 0.6 && < 0.9- , binary >= 0.5 && < 0.8- , process >= 1.1 && < 1.3+ , aeson >= 0.6 && < 1.3+ , binary >= 0.5 && < 0.9+ , process >= 1.1 && < 1.7 , mtl >= 2.0 && < 2.3- , transformers >= 0.2 && < 0.5+ , transformers >= 0.2 && < 0.6 , network >= 2.3 && < 2.7 , lazy-io >= 0.1 && < 0.2 , split >= 0.2 && < 0.3+ , scotty >= 0.11 && < 0.12+ , http-types >= 0.9 && < 0.13+ , wreq >= 0.5 && < 0.6+ , lens >= 4.15 && < 4.17+ , dhall >= 1.11 && < 1.12+ , vector >= 0.11 && < 0.13 exposed-modules: NLP.Concraft.Polish@@ -45,6 +55,16 @@ , NLP.Concraft.Polish.Request , NLP.Concraft.Polish.Server + -- , NLP.Concraft.Polish.DAG+ -- , NLP.Concraft.Polish.DAG2+ , NLP.Concraft.Polish.DAGSeg+ , NLP.Concraft.Polish.DAG.Morphosyntax+ , NLP.Concraft.Polish.DAG.Format.Base+ , NLP.Concraft.Polish.DAG.Server++ , NLP.Concraft.Polish.DAG.Config+ , NLP.Concraft.Polish.DAG.Config.Disamb+ other-modules: NLP.Concraft.Polish.Format.Plain @@ -55,8 +75,22 @@ location: https://github.com/kawu/concraft-pl.git executable concraft-pl+ default-language:+ Haskell2010 build-depends:- cmdargs >= 0.10 && < 0.11- hs-source-dirs: src, tools+ concraft-pl+ , concraft+ , tagset-positional+ , text+ , bytestring+ , sgd+ , pedestrian-dag+ , crf-chain1-constrained+ , containers+ , base >= 4 && < 5+ , cmdargs >= 0.10 && < 0.11+ , dhall >= 1.11 && < 1.12+ , filepath >= 1.3 && < 1.5+ hs-source-dirs: tools main-is: concraft-pl.hs ghc-options: -Wall -O2 -threaded -rtsopts
src/NLP/Concraft/Polish.hs view
@@ -29,6 +29,7 @@ ) where +import Prelude hiding (Word) import Control.Applicative ((<$>)) import qualified Data.Text.Lazy as L import qualified Data.Set as S@@ -79,8 +80,8 @@ tiersDefault = [tier1, tier2] where- tier1 = D.Tier True $ S.fromList ["cas", "per"]- tier2 = D.Tier False $ S.fromList+ tier1 = D.Tier True False $ S.fromList ["cas", "per"]+ tier2 = D.Tier False False $ S.fromList [ "nmb", "gnd", "deg", "asp" , "ngt", "acm" , "acn", "ppr", "agg", "vlc", "dot" ]
+ src/NLP/Concraft/Polish/DAG/Config.hs view
@@ -0,0 +1,26 @@+{-# LANGUAGE DeriveGeneric #-}+++module NLP.Concraft.Polish.DAG.Config+ ( Config(..)+ ) where+++import Dhall+import qualified Data.Aeson as JSON++import qualified NLP.Concraft.Polish.DAG.Config.Disamb as D+++-- | Tagging configuration (everything apart tagset).+data Config = Config+ { disambCfg :: D.DisambCfg+ -- , guessCfg :: G.GuessCfg+ } deriving (Generic, Show)+++instance Interpret Config++instance JSON.FromJSON Config+instance JSON.ToJSON Config where+ toEncoding = JSON.genericToEncoding JSON.defaultOptions
+ src/NLP/Concraft/Polish/DAG/Config/Disamb.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE DeriveGeneric #-}+++module NLP.Concraft.Polish.DAG.Config.Disamb+ ( DisambCfg (..)+ , TierCfg (..)+ , Set(..)+ ) where+++import qualified Data.Set as S+import qualified Data.Vector as V+import qualified Data.Text as T++import Dhall+import qualified Data.Aeson as JSON+++-- | Disamb module configuration.+data DisambCfg = DisambCfg+ { tiersCfg :: [TierCfg]+ } deriving (Generic, Show, Eq, Ord)+++-- | Disamb tiers configuration.+data TierCfg = TierCfg+ { withPos :: Bool+ , withEos :: Bool+ , withAtts :: Set T.Text+ } deriving (Generic, Show, Eq, Ord)+++instance Interpret DisambCfg++instance JSON.FromJSON DisambCfg+instance JSON.ToJSON DisambCfg where+ toEncoding = JSON.genericToEncoding JSON.defaultOptions+++instance Interpret TierCfg++instance JSON.FromJSON TierCfg+instance JSON.ToJSON TierCfg where+ toEncoding = JSON.genericToEncoding JSON.defaultOptions+++------------------------------+-- Set+------------------------------+++newtype Set a = Set {unSet :: S.Set a}+ deriving (Generic, Show, Eq, Ord)++instance (Ord a, Interpret a) => Interpret (Set a) where+ autoWith = fmap+ (fmap $ Set . S.fromList . V.toList)+ autoWith++instance (Ord a, JSON.FromJSON a) => JSON.FromJSON (Set a)+instance JSON.ToJSON a => JSON.ToJSON (Set a) where+ toEncoding = JSON.genericToEncoding JSON.defaultOptions
+ src/NLP/Concraft/Polish/DAG/Format/Base.hs view
@@ -0,0 +1,304 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE DeriveDataTypeable #-}+++module NLP.Concraft.Polish.DAG.Format.Base+(+-- * Printing+ ShowCfg (..)+, ProbType (..)+, showSent+, showData++-- * Parsing+, parseData+, parseSent+) where+++import Prelude hiding (Word)+import Data.Monoid (mconcat, mappend)+import qualified Data.Map as M+import Data.List (intersperse, groupBy)+import Data.Maybe (listToMaybe)+import Data.String (IsString)+import Data.Data (Data)+import Data.Typeable (Typeable)+import qualified Data.Text as T+import qualified Data.Text.Lazy as L+import qualified Data.Text.Lazy.Builder as L+import Text.Printf (printf)+import Text.Read (readMaybe)++import qualified Data.DAG as DAG+-- import qualified Data.CRF.Chain1.Constrained.DAG.Dataset.Internal as DAG++import qualified NLP.Concraft.DAG.Morphosyntax as X+-- import qualified NLP.Concraft.Polish.DAG2 as C+-- import NLP.Concraft.Polish.DAG2 (AnnoSent(..))+import qualified NLP.Concraft.Polish.DAGSeg as C+import NLP.Concraft.Polish.DAGSeg (AnnoSent(..))+import qualified NLP.Concraft.Polish.Morphosyntax as I++import NLP.Concraft.Polish.DAG.Morphosyntax hiding (tag, Tag)+import qualified NLP.Concraft.Polish.DAG.Morphosyntax as PolX+++-----------------------------+-- Base+-----------------------------+++type Tag = PolX.Interp PolX.Tag+++-----------------------------+-- Showing+-----------------------------+++-- | Printing configuration.+data ShowCfg = ShowCfg+-- { suppressProbs :: Bool+-- -- ^ Do not show any probabilities+ { probType :: ProbType+ -- ^ Which type of probabilities to show (unless suppressed)+ , numericDisamb :: Bool+ -- ^ Print disamb markers as numerical values instead of probability values+ }+++-- | Type of probabilities.+data ProbType+ = Marginals+ -- ^ Marginals of the disambiguation model+ | MaxProbs+ -- ^ Max probabilities of the disambiguation model+ | GuessedMarginals+ -- ^ Marginals of the guessing model+ deriving (Show, Eq, Ord, Enum, Typeable, Data)+-- Above, deriving Typeable and Data so that it can be easily parsed+-- for the command-line tool.+++-- mkProbType :: ProbType -> Disamb.ProbType+-- mkProbType Marginals = Disamb.Marginals+-- mkProbType MaxProbs = Disamb.MaxProbs+++-- | Show entire data.+showData :: ShowCfg -> [[AnnoSent]] -> L.Text+showData cfg+ = flip L.append "\n"+ . L.toLazyText+ . mconcat+ . intersperse "\n"+ . map (buildSents cfg)++-- | Show the given sentence.+showSent :: ShowCfg -> [AnnoSent] -> L.Text+showSent cfg = L.toLazyText . buildSents cfg++buildSents :: ShowCfg -> [AnnoSent] -> L.Builder+buildSents cfg =+ finalize . map (buildSent cfg)+ where+ -- finalize = (`mappend` "\n") . mconcat . intersperse "\n"+ finalize = mconcat++buildSent :: ShowCfg -> AnnoSent -> L.Builder+buildSent showCfg AnnoSent{..} = finalize $ do+ let dag = guessSent+ edgeID <- DAG.dagEdges dag+ let tailNode = DAG.begsWith edgeID dag+ headNode = DAG.endsWith edgeID dag+ X.Seg{..} = DAG.edgeLabel edgeID dag+ interp <- map Just (M.toList (X.unWMap tags)) +++ if known word then [] else [Nothing]+ return $ case interp of+ Just (interp@Interp{..}, weight) -> buildInterp+ showCfg tailNode headNode word interp+ (case probType showCfg of+ Marginals ->+ tagWeightIn edgeID interp marginals+ MaxProbs ->+ tagWeightIn edgeID interp maxProbs+ GuessedMarginals ->+ weight)+ (tagLabelIn False edgeID interp disambs)+ -- below, the case when the word is unknown+ Nothing ->+ let interp = Interp+ { base = "none"+ , tag = ign+ , commonness = Nothing+ , qualifier = Nothing+ , metaInfo = Nothing+ , eos = False }+ in buildInterp showCfg tailNode headNode word interp 0 False+ where+ finalize = (`mappend` "\n") . mconcat . intersperse "\n"+ tagWeightIn = tagLabelIn 0+ tagLabelIn def i x anno+ = maybe def (tagLabel def x) (DAG.maybeEdgeLabel i anno)+ tagLabel def x = maybe def id . M.lookup x+++buildInterp+ :: ShowCfg+ -> DAG.NodeID -- ^ Tail node+ -> DAG.NodeID -- ^ Head node+ -> Word -- ^ Word+ -> Interp PolX.Tag+ -> Double+ -> Bool+ -> L.Builder+buildInterp ShowCfg{..} tailNode headNode word Interp{..} weight disamb =+ mconcat $ intersperse "\t" $+ [ buildNode tailNode+ , buildNode headNode+ , L.fromText $ orth word+ , L.fromText $ if known word then base else orth word+ , L.fromText tag+ , buildMayText commonness+ , buildMayText qualifier+ , if numericDisamb+ then buildDisamb disamb+ else buildWeight weight+ , buildMayText metaInfo+ , if eos then "eos" else ""+ ] +++ if numericDisamb then [] else [buildDisamb disamb]+ where+ buildNode (DAG.NodeID i) = L.fromString (show i)+ buildWeight = L.fromString . printf "%.4f"+ buildDisamb True = if numericDisamb then "1.0000" else "disamb"+ buildDisamb False = if numericDisamb then "0.0000" else ""+ -- buildDmb = between "\t" "\n" . L.fromString . printf "%.3f"+ -- between x y z = x <> z <> y+ buildMayText Nothing = ""+ buildMayText (Just x) = L.fromText x+++-----------------------------+-- Parsing+-----------------------------+++-- | Parse the text in the DAG format.+parseData :: L.Text -> [Sent Tag]+parseData =+ map parseSent . filter realSent . L.splitOn "\n\n"+ where+ realSent = not . L.null+++-- | Parse sentence in the DAG format.+parseSent :: L.Text -> Sent Tag+parseSent = fromRows . parseRows+++data Row = Row+ { tailNode :: Int+ , headNode :: Int+ , orthForm :: T.Text+ , baseForm :: T.Text+ , theTag :: PolX.Tag+ , commonness :: Maybe T.Text+ , qualifier :: Maybe T.Text+ , tagProb :: Double+ , metaInfo :: Maybe T.Text+ , eos :: Bool+ }+++fromRows :: [Row] -> Sent Tag+fromRows =+ -- DAG.fromList' I.None . zip (repeat I.None) . getEdges+ DAG.mapN (const I.None) . DAG.fromEdgesUnsafe . getEdges+ where+ getEdges = map mkEdge . groupBy theSameEdge+ theSameEdge r1 r2+ = tailNode r1 == tailNode r2+ && headNode r1 == headNode r2+ mkEdge [] = error "Format.Base.fromRows: empty list"+ mkEdge rows@(row0:_) = DAG.Edge+ { DAG.tailNode = DAG.NodeID $ tailNode row0+ , DAG.headNode = DAG.NodeID $ headNode row0+ , DAG.edLabel = edge }+ where+ edge = X.Seg+ { word = newWord+ , tags = newTags }+ newWord = Word+ { orth = orthForm row0+ , known = not $ ign `elem` map theTag rows }+ newTags = X.mkWMap+ [ (interp, tagProb)+ | Row{..} <- rows+ , not $ theTag == ign+ , let interp = Interp+ { base = baseForm+ , tag = theTag+ , commonness = commonness+ , qualifier = qualifier+ , metaInfo = metaInfo+ , eos = eos }+ ]+++parseRows :: L.Text -> [Row]+parseRows = map parseRow . L.splitOn "\n"+++parseRow :: L.Text -> Row+parseRow =+ doit . L.splitOn "\t"+ where+ doit (tlNode : hdNode : otForm : bsForm : tag :+ comm : qual : prob : meta : eos : _) = Row+ { tailNode = readTyp "tail node" $ L.unpack tlNode+ , headNode = readTyp "head node" $ L.unpack hdNode+ , orthForm = L.toStrict otForm+ , baseForm = L.toStrict bsForm+ , theTag = L.toStrict tag+ , commonness = nullIfEmpty comm+ , qualifier = nullIfEmpty qual+ , tagProb = readTyp "probability value" $ L.unpack prob+ , metaInfo = nullIfEmpty meta+ , eos = case eos of+ "eos" -> True+ _ -> False+ }+ doit _ = error "parseRow: unexpected number of row cells"+ nullIfEmpty x = case x of+ "" -> Nothing+ _ -> Just (L.toStrict x)+++-----------+-- Utils+-----------+++-- -- | An infix synonym for 'mappend'.+-- (<>) :: Monoid m => m -> m -> m+-- (<>) = mappend+-- {-# INLINE (<>) #-}+++readTyp :: (Read a) => String -> String -> a+readTyp typ x =+ case readMaybe x of+ Just x -> x+ Nothing -> error $+ "unable to parse \"" ++ x ++ "\" to a " ++ typ+-- "Unable to parse <" ++ typ ++ ">" +++-- " (string=" ++ x ++ ")"+++-- | Tag which indicates unknown words.+ign :: IsString a => a+ign = "ign"+{-# INLINE ign #-}
+ src/NLP/Concraft/Polish/DAG/Morphosyntax.hs view
@@ -0,0 +1,363 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE LambdaCase #-}+++-- | DAG-aware morphosyntax data layer in Polish.+++module NLP.Concraft.Polish.DAG.Morphosyntax+(+-- * Tag+ Tag++-- * Edge+-- , Edge (..)+, Word (..)+, Interp (..)+, voidInterp+, Space (..)+-- , select+-- , select'+, selectWMap+, selectAnno++-- * Sentence+, Sent+, SentO (..)+, restore+, restore'+, withOrig++-- * Conversion+, packSent+, packSentO+-- , packSeg++-- -- ** From simple sentence+-- , fromList+) where+++import Prelude hiding (Word)+import Control.Applicative ((<$>), (<*>))+-- import Control.Arrow (first)+import Control.Monad (guard)+import Data.Binary (Binary, put, get, putWord8, getWord8)+import Data.Aeson+import qualified Data.Aeson as Aeson+import qualified Data.Set as S+import qualified Data.Map.Strict as M+import Data.Maybe (catMaybes)+import qualified Data.Text as T+import qualified Data.Text.Lazy as L+import qualified Data.Tagset.Positional as P++import Data.DAG (DAG)+import qualified Data.DAG as DAG+-- import Data.CRF.Chain1.Constrained.DAG.Dataset.Internal (DAG)+-- import qualified Data.CRF.Chain1.Constrained.DAG.Dataset.Internal as DAG++-- import qualified NLP.Concraft.DAG2 as C+import qualified NLP.Concraft.DAG.Morphosyntax as X+import qualified NLP.Concraft.DAG.Segmentation as Seg+import qualified NLP.Concraft.Polish.Morphosyntax as R+import NLP.Concraft.Polish.Morphosyntax (Space(..))+++--------------------------------+-- Basics+--------------------------------+++-- | A textual representation of a morphosyntactic tag.+type Tag = T.Text+++--------------------------------+-- Interp+--------------------------------+++-- | A morphosyntactic interpretation.+data Interp t = Interp+ { base :: T.Text+ -- ^ The base form (lemma)+ , tag :: t+ -- ^ The (morphosyntactic) tag+ , commonness :: Maybe T.Text+ , qualifier :: Maybe T.Text+ , metaInfo :: Maybe T.Text+ , eos :: Bool+ -- ^ The remaining four are ignored for the moment, but we plan to rely on+ -- them later on.+ } deriving (Show, Eq, Ord)+++-- | An almost empty interpretation, with only the `tag` actually specified.+voidInterp :: t -> Interp t+voidInterp x = Interp+ { base = "none"+ , tag = x+ , commonness = Nothing+ , qualifier = Nothing+ , metaInfo = Nothing+ , eos = False+ }+++instance (Ord t, Binary t) => Binary (Interp t) where+ put Interp{..} = do+ put base+ put tag+ put commonness+ put qualifier+ put metaInfo+ put eos+ get = Interp <$> get <*> get <*> get <*> get <*> get <*> get+++--------------------------------+-- Edge+--------------------------------+++-- -- | An edge consists of a word and a set of morphosyntactic interpretations.+-- data Edge t = Edge+-- { word :: Word+-- -- | Interpretations of the word, each interpretation annotated+-- -- with a /disamb/ Boolean value (if 'True', the interpretation+-- -- is correct within the context).+-- , interps :: X.WMap (Interp t) }+-- deriving (Show, Eq, Ord)+--+-- instance (Ord t, Binary t) => Binary (Edge t) where+-- put Edge{..} = put word >> put interps+-- get = Edge <$> get <*> get+--+-- instance X.Word (Edge t) where+-- orth = X.orth . word+-- oov = X.oov . word+++--------------------------------+-- Word+--------------------------------+++-- | A word.+data Word = Word+ { orth :: T.Text+ -- , space :: Space+ , known :: Bool }+ deriving (Show, Eq, Ord)++instance X.Word Word where+ orth = orth+ oov = not.known++instance Binary Word where+ -- put Word{..} = put orth >> put space >> put known+ put Word{..} = put orth >> put known+ -- get = Word <$> get <*> get <*> get+ get = Word <$> get <*> get++instance ToJSON Word where+ toJSON Word{..} = object+ [ "orth" .= orth+ , "known" .= known ]++instance FromJSON Word where+ parseJSON (Object v) = Word+ <$> v .: "orth"+ <*> v .: "known"+ parseJSON _ = error "parseJSON [Word]"+++---------------------------------------------------------------------------------+-- Selection+--+-- (Honestly, I don't remember what is this one about...)+--+-- Update: maybe related to the fact that base forms have to be handled somehow?+---------------------------------------------------------------------------------+++-- -- | Select one chosen interpretation.+-- select :: Ord a => a -> Edge a -> Edge a+-- select = select' []+--+--+-- -- | Select multiple interpretations and one chosen interpretation.+-- select' :: Ord a => [a] -> a -> Edge a -> Edge a+-- select' ys x = selectWMap . X.mkWMap $ (x, 1) : map (,0) ys+++-- | Select interpretations.+selectAnno :: Ord a => M.Map (Interp a) Double -> X.Seg Word (Interp a) -> X.Seg Word (Interp a)+selectAnno = selectWMap . X.fromMap+++-- | Select interpretations.+selectWMap :: Ord a => X.WMap (Interp a) -> X.Seg Word (Interp a) -> X.Seg Word (Interp a)+selectWMap wMap seg = seg {X.tags = wMap}+-- seg { X.tags = newTags }+-- where+-- wSet = S.fromList . map tag . M.keys . X.unWMap . X.tags $ seg+-- newTags = X.mkWMap $+-- -- [ case M.lookup (tag interp) (X.unWMap wMap) of+-- [ case M.lookup interp (X.unWMap wMap) of+-- Just x -> (interp, x)+-- Nothing -> (interp, 0)+-- | interp <- (M.keys . X.unWMap) (X.tags seg) ]+-- ++ catMaybes+-- [ if interp `S.member` wSet+-- then Nothing+-- else Just (interp, x)+-- | (interp, x) <- M.toList (X.unWMap wMap)+-- -- | let lemma = orth $ X.word seg -- Default base form+-- -- , (tag, x) <- M.toList (X.unWMap wMap)+-- -- , let interp = Interp+-- -- { base = lemma+-- -- , tag = tag+-- -- , commonness = Nothing+-- -- , qualifier = Nothing+-- -- , metaInfo = Nothing+-- -- , eos = False }+-- ]+++--------------------------------+-- Sentence+--------------------------------+++-- | A sentence.+-- type Sent t = DAG Space (Edge t)+type Sent t = DAG Space (X.Seg Word t)+++-- | A sentence with its original textual representation.+data SentO t = SentO+ { sent :: Sent t+ , orig :: L.Text }+++-- | Restore textual representation of a sentence. The function is not very+-- accurate, it could be improved if we enriched the representation of spaces.+restore :: Sent t -> L.Text+restore =+ let edgeStr = orth . X.word+ spaceStr None = ""+ spaceStr Space = " "+ spaceStr NewLine = "\n"+ in L.fromChunks . map (either spaceStr edgeStr) . pickPath+++-- | Use `restore` to translate `Sent` to a `SentO`.+withOrig :: Sent t -> SentO t+withOrig s = SentO+ { sent = s+ , orig = restore s }+++-- | A version of `restore` which places a space on each node.+restore' :: Sent t -> L.Text+restore' =+ let edgeStr = orth . X.word+ spaceStr = const " "+ in L.fromChunks . map (either spaceStr edgeStr) . tail . pickPath+++--------------------------------+-- Utils+--------------------------------+++-- | Pick any path from the given DAG. The result is a list+-- of interleaved node and edge labels.+pickPath :: (X.Word b) => DAG a b -> [Either a b]+pickPath dag =+ fstNodeVal path : concatMap getVals path+ where+ -- Take the value on the first node on the path+ fstNodeVal = \case+ edgeID : _ ->+ Left $ DAG.nodeLabel (DAG.begsWith edgeID dag) dag+ [] -> error "Morphosyntax.pickPath: empty path"+ -- Select a shortest path in the DAG; thanks to edges being topologically+ -- sorted, this should give us the list of edge IDs in an appropriate order.+ path = S.toList $ Seg.findPath Seg.Min dag+ -- Get the labels of the nodes and labels+ getVals edgeID =+ let+ nodeID = DAG.endsWith edgeID dag+ in+ [ Right $ DAG.edgeLabel edgeID dag+ , Left $ DAG.nodeLabel nodeID dag ]+++---------------------------+-- Conversion+---------------------------+++-- -- | Convert a segment to a segment from the core library.+-- packSeg :: Ord a => X.Seg Word a -> X.Seg Word a+-- packSeg = id+-- -- packSeg Edge{..}+-- -- = X.Seg word+-- -- $ X.mkWMap+-- -- $ map (first tag)+-- -- $ M.toList+-- -- $ X.unWMap interps+++-- | Convert a sentence to a sentence from the core library.+packSent :: Ord a => Sent a -> X.Sent Word a+packSent+ = DAG.mapN (const ())+ -- . fmap packSeg+++-- | Convert a sentence to a sentence from the core library.+-- packSentO :: P.Tagset -> SentO Tag -> X.SentO Word P.Tag+packSentO :: Ord a => SentO a -> X.SentO Word a+packSentO s = X.SentO+ { segs = packSent (sent s)+ , orig = orig s }+++---------------------------+-- From simple sentence+---------------------------+++-- fromWord :: R.Word -> (Space, Word)+-- fromWord R.Word{..} = (space, Word {orth=orth, known=known})+--+--+-- fromSeg :: (Ord t) => R.Seg t -> (Space, Edge t)+-- fromSeg R.Seg{..} =+-- (space, edge)+-- where+-- edge = Edge {word = newWord, interps = updateInterps interps}+-- (space, newWord) = fromWord word+-- -- updateInterps = X.mkWMap . map (first fromInterp) . X.unWMap+-- updateInterps = X.mapWMap fromInterp+--+--+-- fromInterp :: R.Interp t -> Interp t+-- fromInterp R.Interp{..} = Interp+-- { base = base+-- , tag = tag+-- , commonness = Nothing+-- , qualifier = Nothing+-- , metaInfo = Nothing+-- , eos = False }+--+--+-- -- | Create a DAG-based sentence from a list-based sentence.+-- fromList :: Ord t => R.Sent t -> Sent t+-- fromList = DAG.fromList' None . map fromSeg
+ src/NLP/Concraft/Polish/DAG/Server.hs view
@@ -0,0 +1,128 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE DuplicateRecordFields #-}+++-- | Concraft (DAG) web server.+++module NLP.Concraft.Polish.DAG.Server+ (+ -- * Types+ ServerCfg(..)+ , ClientCfg(..)+ , Request(..)+ , Answer(..)++ -- * Server+ , runServer++ -- * Client+ , sendRequest+ ) where+++import Control.Monad.IO.Class (liftIO)++import GHC.Generics++import qualified Data.Text as T+import qualified Data.Text.Lazy as L+import Network.HTTP.Types (badRequest400)+import qualified Web.Scotty as W+import qualified Data.Aeson as A+import Data.Aeson ((.=))+import qualified Network.Wreq as Wreq+import Control.Lens ((^?))++import qualified NLP.Concraft.Polish.DAGSeg as Pol+import qualified NLP.Concraft.Polish.DAG.Format.Base as DB+++---------------------------------------+-- Types+---------------------------------------+++type Concraft = Pol.Concraft Pol.Tag+++data Request = Request+ { dag :: T.Text+ } deriving (Generic)++instance A.FromJSON Request+instance A.ToJSON Request+++data Answer = Answer+ { dag :: T.Text+ } deriving (Generic)++instance A.ToJSON Answer+instance A.FromJSON Answer+++data ServerCfg = ServerCfg+ { concraft :: Concraft+ , annoCfg :: Pol.AnnoConf+ , showCfg :: DB.ShowCfg+ }+++---------------------------------------+-- Server+---------------------------------------+++serverApp :: ServerCfg -> W.ScottyM ()+serverApp env = do+ W.post "/parse" $ parse env+ W.post "/parse" parseFailure+++parse :: ServerCfg -> W.ActionM ()+parse ServerCfg{..} = flip W.rescue (const W.next) $ do+ Request{..} <- W.jsonData+ let inp = DB.parseData (L.fromStrict dag)+ out = Pol.annoAll annoCfg concraft <$> inp+ dagStr = DB.showData showCfg out+ ans = Answer { dag = L.toStrict dagStr }+ -- W.json $ A.object [ "dag" .= dagStr ]+ W.json $ A.toJSON ans+++parseFailure :: W.ActionM ()+parseFailure = do+ W.json $ A.object+ [ "error" .= ("Invalid request" :: T.Text) ]+ W.status badRequest400+++runServer+ :: ServerCfg+ -> Int -- ^ Port+ -> IO ()+runServer env port = W.scotty port (serverApp env)+++---------------------------------------+-- Client+---------------------------------------+++data ClientCfg = ClientCfg+ { serverAddr :: String+ }+++sendRequest+ :: ClientCfg+ -> Request+ -> IO (Maybe Answer)+sendRequest ClientCfg{..} req = do+ let json = A.toJSON req+ r <- Wreq.post serverAddr json+ let result = A.decode =<< r ^? Wreq.responseBody+ return result
+ src/NLP/Concraft/Polish/DAGSeg.hs view
@@ -0,0 +1,612 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE DeriveDataTypeable #-}+++-- | DAG-based model for morphosyntactic tagging.+++module NLP.Concraft.Polish.DAGSeg+(++-- * Types+ Tag (..)+-- ** Simplification+, simplify4gsr+, simplify4dmb++-- ** Model+, C.Concraft+, C.saveModel+, C.loadModel++-- * Tagging+, guess+-- , disamb+-- , disamb'+-- , tag+-- , tag'+-- ** High level+, AnnoSent (..)+, AnnoConf (..)+, annoAll++-- * Training+, TrainConf (..)+-- , DisambTiersCfg (..)+, train++-- * Pruning+-- , C.prune+) where+++import Prelude hiding (Word)+import Control.Applicative ((<$>))+import Control.Arrow (first)+import Control.Monad (guard)+import Data.Maybe (listToMaybe)+import qualified Data.Text.Lazy as L+import qualified Data.Set as S+import qualified Data.Map.Strict as M+import Data.Data (Data)+import Data.Typeable (Typeable)++import qualified Data.Tagset.Positional as P+import qualified Numeric.SGD.Momentum as SGD++import qualified Data.DAG as DAG++import qualified NLP.Concraft.DAG.Morphosyntax as X+import qualified NLP.Concraft.DAG.Morphosyntax.Ambiguous as XA+import qualified NLP.Concraft.DAG.Schema as S+import NLP.Concraft.DAG.Schema (SchemaConf(..), entry, entryWith)+import qualified NLP.Concraft.DAG.Guess as G+import qualified NLP.Concraft.DAG.DisambSeg as D+import qualified NLP.Concraft.DAG.Segmentation as Seg+import qualified NLP.Concraft.DAGSeg as C++import NLP.Concraft.Polish.DAG.Morphosyntax hiding (tag, Tag)+import qualified NLP.Concraft.Polish.DAG.Morphosyntax as PolX+import qualified NLP.Concraft.Polish.DAG.Config as Cfg+import qualified NLP.Concraft.Polish.DAG.Config.Disamb as Cfg++-- import Debug.Trace (trace)+++-------------------------------------------------+-- Default configuration+-------------------------------------------------+++-- | Default configuration for the guessing observation schema.+guessSchemaDefault :: SchemaConf+guessSchemaDefault = S.nullConf+ { lowPrefixesC = entryWith [1, 2] [0]+ , lowSuffixesC = entryWith [1, 2] [0]+ , knownC = entry [0]+ , begPackedC = entry [0]+ }+++-- | Default configuration for the segmentation observation schema.+segmentSchemaDefault :: SchemaConf+segmentSchemaDefault = S.nullConf+ { lowPrefixesC = entryWith [1, 2] [-1, 0, 1]+ , begPackedC = entry [-1, 0, 1] }+ -- NOTE: The current configuration works quite well for segmentation.+ -- Adding orthographic forms was not a good idea, at least not on a small+ -- training dataset.+++-- -- | Default configuration for the guessing observation schema.+-- disambSchemaDefault :: SchemaConf+-- disambSchemaDefault = S.nullConf+-- { lowPrefixesC = entryWith [1, 2] [-1, 0, 1]+-- , begPackedC = entry [-1, 0, 1] }+++-- -- | Default configuration for the guessing observation schema.+-- disambSchemaDefault :: SchemaConf+-- disambSchemaDefault = S.nullConf+-- { orthC = entry [-2, -1, 0, 1, 2]+-- -- { lowOrthC = entry [-2, -1, 0, 1, 2]+-- , lowPrefixesC = entryWith [1, 2, 3] [-2, -1, 0, 1, 2]+-- , lowSuffixesC = entryWith [1, 2, 3] [-2, -1, 0, 1, 2]+-- , begPackedC = entry [-2, -1, 0, 1, 2] }+++-- | Default configuration for the disambiguation observation schema.+disambSchemaDefault :: SchemaConf+disambSchemaDefault = S.nullConf+ { lowOrthC = entry [-2, -1, 0, 1]+ , lowPrefixesC = oov $ entryWith [1, 2, 3] [0]+ , lowSuffixesC = oov $ entryWith [1, 2, 3] [0]+ , begPackedC = oov $ entry [0] }+ where+ oov (Just body) = Just $ body { S.oovOnly = True }+ oov Nothing = Nothing+++-- | Default tiered tagging configuration for the segmentation model.+tiersSegment :: [D.Tier]+tiersSegment =+ [tier]+ where+ tier = D.Tier+ { D.withPos = True+ , D.withEos = True+ , D.withAtts = S.fromList []+ }+++-------------------------------------------------+-- Tagging Tiers+-------------------------------------------------+++-- -- | Configuration of disambiguation tiers.+-- data DisambTiersCfg+-- = TiersDefault+-- | TiersGndCasSeparately+-- deriving (Data, Typeable, Show, Eq, Ord)+--+--+-- -- | Tiered tagging configuration for the disambiguation model.+-- tiersDisamb :: DisambTiersCfg -> [D.Tier]+-- tiersDisamb cfg = case cfg of+-- TiersDefault -> tiersDisambDefault+-- TiersGndCasSeparately -> tiersDisambGndCasSeparately+--+--+-- -- | Default tiered tagging configuration for the disambiguation model.+-- tiersDisambDefault :: [D.Tier]+-- tiersDisambDefault =+-- [tier1, tier2]+-- where+-- tier1 = D.Tier True False $ S.fromList+-- ["cas", "per"]+-- tier2 = D.Tier False False $ S.fromList+-- [ "nmb", "gnd", "deg", "asp" , "ngt", "acm"+-- , "acn", "ppr", "agg", "vlc", "dot"+-- , "sbg", "col"+-- ]+--+-- -- | Separate tier with gender and case values.+-- tiersDisambGndCasSeparately :: [D.Tier]+-- tiersDisambGndCasSeparately =+-- [tier1, tier2, tier3]+-- where+-- tier1 = D.Tier True False $ S.fromList+-- [ "per" ]+-- tier2 = D.Tier False False $ S.fromList+-- [ "nmb", "deg", "asp" , "ngt", "acm"+-- , "acn", "ppr", "agg", "vlc", "dot"+-- , "sbg", "col"+-- ]+-- tier3 = D.Tier False False $ S.fromList+-- [ "cas", "gnd"+-- ]+++tiersDisamb :: Cfg.Config -> [D.Tier]+tiersDisamb Cfg.Config{..} = do+ Cfg.TierCfg{..} <- Cfg.tiersCfg disambCfg+ return $ D.Tier+ { D.withPos = withPos+ , D.withEos = withEos+ , D.withAtts = Cfg.unSet withAtts+ }+++-------------------------------------------------+-- Tagging+-------------------------------------------------+++-- type Tag = PolX.Tag+type Tag = PolX.Interp PolX.Tag+++-- | Tag the sentence with guessing marginal probabilities.+guess :: C.Concraft Tag -> Sent Tag -> Sent Tag+guess = tagWith (C.guessMarginals . C.guesser)+++-- | Tag the sentence with disambiguation marginal probabilities.+disamb :: C.Concraft Tag -> Sent Tag -> Sent Tag+disamb = tagWith (C.disambMarginals . C.disamb)+++-- | Tag the sentence with disambiguation probabilities.+disamb' :: C.Concraft Tag -> D.ProbType -> Sent Tag -> Sent Tag+disamb' crf typ = tagWith (C.disambProbs typ . C.disamb) crf+++-- | Perform guessing -> trimming -> disambiguation.+tag+ :: Int+ -- ^ Trimming parameter+ -> C.Concraft Tag+ -> Sent Tag+ -> Sent Tag+tag = tagWith . C.tag+++-- -- | Perform guessing -> trimming -> disambiguation.+-- tag'+-- :: Int+-- -- ^ Trimming parameter+-- -> D.ProbType+-- -> C.Concraft+-- -> Sent Tag+-- -> Sent Tag+-- tag' k probTyp = tagWith (C.tag' k probTyp)+++-- | Tag with the help of a lower-level annotation function.+tagWith+ -- :: (C.Concraft Tag -> X.Sent Word P.Tag -> C.Anno P.Tag Double)+ :: (C.Concraft Tag -> X.Sent Word Tag -> C.Anno Tag Double)+ -> C.Concraft Tag -> Sent Tag -> Sent Tag+tagWith annoFun concraft sent+ = fmap select+ $ DAG.zipE sent annoSent+ where+ select (edge, anno) = selectAnno anno edge+ annoSent = annoWith annoFun concraft sent+++-- | Annotate with the help of a lower-level annotation function.+annoWith+ :: (C.Concraft Tag -> X.Sent Word Tag -> C.Anno Tag a)+ -> C.Concraft Tag -> Sent Tag -> C.Anno Tag a+annoWith anno concraft =+ anno concraft . packSent+++-- | Tranform each tag into two tags, one with EOS(end-of-sentence)=True, one+-- with EOS=False. The idea behind this transformation is that, at some point,+-- we may want to tag with an EOS-aware model, with no EOS-related information+-- coming from the previous tagging stages.+--+-- NOTE: this procedure does not apply to OOV words. The motivation: it seems+-- highly unlikely that any OOV word can mark a sentence end.+--+-- NOTE: this procedure does not apply to segmentation-ambiguous words either.+-- Allowing the tagger to cut on such words might cause losing DAG edges.+addEosMarkers+ :: DAG.DAG a Bool+ -> DAG.EdgeID+ -> X.Seg Word Tag+ -> X.Seg Word Tag+addEosMarkers ambiDag edgeID seg+ | X.oov seg = seg+ | DAG.edgeLabel edgeID ambiDag = seg+ | otherwise = seg {X.tags = newTags (X.tags seg)}+ where+ newTags tagMap = X.mkWMap $ concat+ [ multiply interp p+ | (interp, p) <- M.toList (X.unWMap tagMap) ]+ multiply interp p+ | eos interp == True =+ [ (interp {eos=True}, p)+ , (interp {eos=False}, 0) ]+ | otherwise =+ [ (interp {eos=True}, 0)+ , (interp {eos=False}, p) ]+++-- | Decide if the word should be marked as eos or not.+resolveEOS+ :: Double+ -- ^ 0.5 means that the probability of the tag being EOS is twice as high+ -- as that of not being EOS. 1.0 means that EOS will be marked only if its+ -- 100% probable.+ -> X.Seg Word Tag+ -> X.Seg Word Tag+resolveEOS minProp seg+ | isEos = seg {X.tags = markEos}+ | otherwise = seg {X.tags = markNoEos}+ where+ tagMap = X.tags seg+ -- Determine the weights of the most probable EOS-marked and non-marked tags+ withEosW = maxWeightWith ((==True) . PolX.eos) tagMap+ withNoEosW = maxWeightWith ((==False) . PolX.eos) tagMap+ -- Should the segment be marked as EOS?+ isEos = case (withEosW, withNoEosW) of+ (Just eosW, Just noEosW) ->+ eosW / (eosW + noEosW) >= minProp+ (Just _, Nothing) -> True+ _ -> False+ -- Mark the segment as EOS or not+ markEos = X.mkWMap+ [ (interp {PolX.eos=True}, p)+ | (interp, p) <- M.toList (X.unWMap tagMap) ]+ markNoEos = X.mkWMap+ [ (interp {PolX.eos=False}, p)+ | (interp, p) <- M.toList (X.unWMap tagMap) ]+++-- | Determine the weight of the most probable interpretation which satisfies+-- the given predicate.+maxWeightWith :: (Tag -> Bool) -> X.WMap Tag -> Maybe Double+maxWeightWith pred tagMap = mayMaximum+ [ p+ | (interp, p) <- M.toList (X.unWMap tagMap)+ , pred interp ]+ where+ mayMaximum [] = Nothing+ mayMaximum xs = Just $ maximum xs+++-- | Try to segment the sentence based on the EOS markers.+-- segment :: X.Sent Word Tag -> [X.Sent Word Tag]+segment :: DAG.DAG a (X.Seg Word Tag) -> [DAG.DAG a (X.Seg Word Tag)]+segment sent =++ -- (\xs -> trace ("splits: " ++ show (length xs)) xs) $+ -- go (trace ("splitPoints: " ++ show splitPoints) splitPoints) sent+ go splitPoints sent++ where++ splitPoints = (S.toList . S.fromList)+ [ DAG.endsWith edgeID sent+ | edgeID <- DAG.dagEdges sent+ , interp <- M.keys . X.unWMap . X.tags . DAG.edgeLabel edgeID $ sent+ , PolX.eos interp ]++ go (splitPoint:rest) dag =+ case split splitPoint dag of+ Just (left, right) -> left : go rest right+ Nothing -> go rest dag+ go [] dag = [dag]++ split point dag = do+ (x, y) <- DAG.splitTmp point dag+ let empty = null . DAG.dagEdges+ guard . not . empty $ x+ guard . not . empty $ y+ return (x, y)+++-------------------------------------------------+-- High-level Tagging+-------------------------------------------------+++-- -- | Configuration related to frequency-based path picking.+-- data PickFreqConf = PickFreqConf+-- { pickFreqMap :: Maybe (M.Map T.Text (Int, Int))+-- -- ^ A map which assigns (chosen, not chosen) counts to the invidiaul+-- -- orthographic forms.+-- , smoothingParam :: Double+-- -- ^ A naive smoothing related parameter, which should be adddd to each+-- -- count in `pickFreqMap`.+-- }+++-- | Annotation config.+data AnnoConf = AnnoConf+ { trimParam :: Int+ -- ^ How many morphosyntactic tags should be kept for OOV words+ , pickPath :: Maybe Seg.PathTyp+ -- ^ Which path picking method should be used. The function takes the+ }+++-- | Annotated sentence.+data AnnoSent = AnnoSent+ { guessSent :: Sent Tag+ -- ^ The sentence after guessing and segmentation+ -- (TODO: and annotated with marginal probabilities?)+ , disambs :: C.Anno Tag Bool+ -- ^ Disambiguation markers+ , marginals :: C.Anno Tag Double+ -- ^ Marginal probabilities according to the disambiguation model+ , maxProbs :: C.Anno Tag Double+ -- ^ Maximal probabilities according to the disambiguation model+ }+++-- | Annotate all possibly interesting information.+annoAll+-- :: Int+-- -- ^ Trimming parameter+ :: AnnoConf+ -> C.Concraft Tag+ -> Sent Tag+ -> [AnnoSent]+-- annoAll k concraft sent0 =+annoAll AnnoConf{..} concraft sent00 =++ map annoOne _guessSent1++ where++ -- See whether the shortest path should be computed first+ sent0 =+ case pickPath of+ Just typ -> Seg.pickPath typ sent00+ Nothing -> sent00++ -- We add EOS markers only after guessing, because the possible tags are not+ -- yet determined for the OOV words.+ ambiDag = XA.identifyAmbiguousSegments sent0+ _guessSent0 = DAG.mapE (addEosMarkers ambiDag) $+ tagWith (C.guess trimParam . C.guesser) concraft sent0+ -- Resolve EOS tags based on the segmentation model+ _guessSent1 = segment . fmap (resolveEOS 0.5) $+ tagWith (C.disambProbs D.MaxProbs . C.segmenter) concraft _guessSent0++ annoOne _guessSent = AnnoSent+ { guessSent = _guessSent+ , disambs = _disambs+ , marginals = _marginals+ , maxProbs = _maxProbs+ }+ where+ _marginals =+ annoWith (C.disambProbs D.Marginals . C.disamb) concraft _guessSent+ _maxProbs =+ annoWith (C.disambProbs D.MaxProbs . C.disamb) concraft _guessSent+ _disambs =+ C.disambPath (optimal _maxProbs) _maxProbs+ where optimal = maybe [] id . listToMaybe . C.findOptimalPaths+++-------------------------------------------------+-- Training+-------------------------------------------------+++-- | Training configuration.+data TrainConf = TrainConf {+ -- | Tagset.+ tagset :: P.Tagset+ -- | SGD parameters.+ , sgdArgs :: SGD.SgdArgs+ -- | Store SGD dataset on disk.+ , onDisk :: Bool+ -- | Numer of guessed tags for each word.+ , guessNum :: Int+ -- | `G.r0T` parameter.+ , r0 :: G.R0T+ -- | `G.zeroProbLabel` parameter+ , zeroProbLabel :: Tag+ -- | Extract only visible features for the guesser+ , guessOnlyVisible :: Bool+ -- | Global configuration+ , globalConfig :: Cfg.Config+ -- -- | Disambiguation tiers configuration+ -- , disambTiersCfg :: DisambTiersCfg+ }+++-- | Train concraft model.+-- TODO: It should be possible to supply the two training procedures with+-- different SGD arguments.+train+ :: TrainConf+ -> IO [Sent Tag] -- ^ Training data+ -> IO [Sent Tag] -- ^ Evaluation data+ -> IO (C.Concraft Tag)+train TrainConf{..} train0 eval0 = do+ let trainR'IO = map packSent <$> train0+ evalR'IO = map packSent <$> eval0++ putStr $ concat+ [ "Batch size for the "+ , "guessing and sentence segmentation models = "+ ]+ -- average number of sentences per paragraph+ averageParSize <- average . map (fromIntegral . length . segment) <$> trainR'IO+ let parBatchSize = ceiling $ fromIntegral (SGD.batchSize sgdArgs) / averageParSize+ print parBatchSize++ putStrLn "\n===== Train guessing model ====="+ guesser <- G.train (guessConf parBatchSize) trainR'IO evalR'IO+ let guess = C.guessSent guessNum guesser+ prepSent dag =+ let ambiDag = XA.identifyAmbiguousSegments dag+ in DAG.mapE (addEosMarkers ambiDag) (guess dag)+ trainG'IO = map prepSent <$> trainR'IO+ evalG'IO = map prepSent <$> evalR'IO++ putStrLn "\n===== Train sentence segmentation model ====="+ segmenter <- D.train (segmentConf parBatchSize) trainG'IO evalG'IO+ let prepSent = segment . fmap (resolveEOS 0.5)+ trainS'IO = concatMap prepSent <$> trainG'IO+ evalS'IO = concatMap prepSent <$> evalG'IO++ putStrLn "\n===== Train disambiguation model ====="+ disamb <- D.train disambConf trainS'IO evalS'IO++ return $ C.Concraft tagset guessNum guesser segmenter disamb++ where++ guessConf batchSize = G.TrainConf+ guessSchemaDefault+ (sgdArgs {SGD.batchSize = batchSize})+ onDisk r0 zeroProbLabel+ (simplify4gsr tagset) strip4gsr+ guessOnlyVisible+ strip4gsr interp = PolX.voidInterp (PolX.tag interp)++ segmentConf batchSize = D.TrainConf+ tiersSegment segmentSchemaDefault+ (sgdArgs {SGD.batchSize = batchSize})+ onDisk+ (simplify4dmb tagset)++ disambConf = D.TrainConf+ -- (tiersDisamb disambTiersCfg)+ (tiersDisamb globalConfig)+ disambSchemaDefault sgdArgs onDisk+ (simplify4dmb tagset)+++-- | Simplify the tag for the sake of the disambiguation model.+simplify4dmb :: P.Tagset -> PolX.Interp PolX.Tag -> D.Tag+simplify4dmb tagset PolX.Interp{..} = D.Tag+ { D.posiTag = P.parseTag tagset tag+ , D.hasEos = eos }+++-- | Simplify the tag for the sake of the guessing model.+-- TODO: it is also used in the evaluation script, which assumes that+-- `simplify4gsr` simplifies to a positional tag. The name of the function+-- should reflect this, perhaps, or there should be two separate functions: one+-- dedicated to guesser, one dedicated to evaluation (and other more generic+-- things).+simplify4gsr :: P.Tagset -> PolX.Interp PolX.Tag -> P.Tag+simplify4gsr tagset PolX.Interp{..} = P.parseTag tagset tag+++-- -- | Train the `Concraft` model.+-- -- No reanalysis of the input data will be performed.+-- --+-- -- The `FromJSON` and `ToJSON` instances are used to store processed+-- -- input data in temporary files on a disk.+-- train+-- :: (X.Word w, Ord t)+-- => P.Tagset -- ^ A morphosyntactic tagset to which `P.Tag`s+-- -- of the training and evaluation input data+-- -- must correspond.+-- -> Int -- ^ How many tags is the guessing model supposed+-- -- to produce for a given OOV word? It will be+-- -- used (see `G.guessSent`) on both training and+-- -- evaluation input data prior to the training+-- -- of the disambiguation model.+-- -> G.TrainConf t P.Tag -- ^ Training configuration for the guessing model.+-- -> D.TrainConf t -- ^ Training configuration for the+-- -- disambiguation model.+-- -> IO [Sent w t] -- ^ Training dataset. This IO action will be+-- -- executed a couple of times, so consider using+-- -- lazy IO if your dataset is big.+-- -> IO [Sent w t] -- ^ Evaluation dataset IO action. Consider using+-- -- lazy IO if your dataset is big.+-- -> IO (Concraft t)+-- train tagset guessNum guessConf disambConf trainR'IO evalR'IO = do+-- Temp.withTempDirectory "." ".guessed" $ \tmpDir -> do+-- let temp = withTemp tagset tmpDir+--+-- putStrLn "\n===== Train guessing model ====="+-- guesser <- G.train guessConf trainR'IO evalR'IO+-- let guess = guessSent guessNum guesser+-- trainG <- map guess <$> trainR'IO+-- evalG <- map guess <$> evalR'IO+--+-- temp "train" trainG $ \trainG'IO -> do+-- temp "eval" evalG $ \evalG'IO -> do+--+-- putStrLn "\n===== Train disambiguation model ====="+-- disamb <- D.train disambConf trainG'IO evalG'IO+-- return $ Concraft tagset guessNum guesser disamb+++-- | Compute an average of the list.+average :: [Double] -> Double+average xs = sum xs / fromIntegral (length xs)
src/NLP/Concraft/Polish/Maca.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE FlexibleContexts #-} -- | The module provides interface for the Maca analysis tool.
src/NLP/Concraft/Polish/Morphosyntax.hs view
@@ -7,11 +7,11 @@ module NLP.Concraft.Polish.Morphosyntax-( +( -- * Tag Tag --- * Segment +-- * Segment , Seg (..) , Word (..) , Interp (..)@@ -33,6 +33,7 @@ ) where +import Prelude hiding (Word) import Control.Applicative ((<$>), (<*>)) import Control.Arrow (first) import Data.Maybe (catMaybes)@@ -58,7 +59,7 @@ -- | A segment consists of a word and a set of morphosyntactic interpretations.-data Seg t = Seg +data Seg t = Seg { word :: Word -- | Interpretations of the token, each interpretation annotated -- with a /disamb/ Boolean value (if 'True', the interpretation@@ -70,8 +71,8 @@ instance (Ord t, Binary t) => Binary (Seg t) where put Seg{..} = put word >> put interps get = Seg <$> get <*> get- + -- | A word. data Word = Word { orth :: T.Text@@ -103,7 +104,7 @@ instance Binary Word where put Word{..} = put orth >> put space >> put known get = Word <$> get <*> get <*> get- + -- | A morphosyntactic interpretation. data Interp t = Interp
tools/concraft-pl.hs view
@@ -1,60 +1,73 @@+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE DoAndIfThenElse #-}+{-# LANGUAGE LambdaCase #-} import Control.Applicative ((<$>))-import Control.Monad (unless)+import Control.Monad (unless, forM_)+import System.FilePath (isAbsolute, (</>)) import System.Console.CmdArgs-import System.IO (hFlush, stdout)-import qualified Network as N-import qualified Numeric.SGD as SGD+-- import System.IO (hFlush, stdout)+import qualified Numeric.SGD.Momentum as SGD+import Data.String (fromString)+import qualified Data.Text as T+import qualified Data.Text.IO as T import qualified Data.Text.Lazy as L-import qualified Data.Text.Lazy.IO as L+-- import qualified Data.Text.Lazy.IO as L+import qualified Data.Text.Lazy.Encoding as L+import qualified Data.ByteString.Lazy as BL import Data.Tagset.Positional (parseTagset)-import GHC.Conc (numCapabilities)+import qualified Data.Tagset.Positional as P+import qualified Data.Map.Strict as M+import qualified Data.Set as S -import qualified NLP.Concraft.Morphosyntax.Accuracy as Acc-import qualified NLP.Concraft.Guess as Guess+import qualified Dhall as Dhall -import qualified NLP.Concraft.Polish.Maca as Maca-import qualified NLP.Concraft.Polish as C-import qualified NLP.Concraft.Polish.Request as R-import qualified NLP.Concraft.Polish.Server as S-import qualified NLP.Concraft.Polish.Morphosyntax as X-import qualified NLP.Concraft.Polish.Format.Plain as P+import qualified Data.DAG as DAG -import Paths_concraft_pl (version, getDataFileName)-import Data.Version (showVersion)+import qualified Data.CRF.Chain1.Constrained.DAG.Dataset.Codec as CRF.Codec+import qualified Data.CRF.Chain1.Constrained.DAG.Train as CRF.Train +import qualified NLP.Concraft.DAG.Guess as Guess+import qualified NLP.Concraft.DAG.Schema as Schema+-- import qualified NLP.Concraft.DAG.Disamb as Disamb --- | Default port number.-portDefault :: Int-portDefault = 10089 +-- import qualified NLP.Concraft.DAG2 as C+-- import qualified NLP.Concraft.Polish.DAG2 as P+import qualified NLP.Concraft.DAGSeg as C+import qualified NLP.Concraft.DAG.Morphosyntax as X+import qualified NLP.Concraft.DAG.Morphosyntax.Accuracy as Acc+import qualified NLP.Concraft.DAG.Segmentation as Seg+import qualified NLP.Concraft.Polish.DAG.Morphosyntax as PX+import qualified NLP.Concraft.Polish.DAGSeg as Pol+import qualified NLP.Concraft.Polish.DAG.Format.Base as DB+import qualified NLP.Concraft.Polish.DAG.Server as Server +-- import qualified NLP.Concraft.Polish.Request as R++import Paths_concraft_pl (version, getDataFileName)+import Data.Version (showVersion)++ --------------------------------------- -- Command line options --------------------------------------- --- | Data formats.-data Format = Plain deriving (Data, Typeable, Show)----- | A description of the Concraft-pl tool+-- | A description of the Concraft-pl tool. concraftDesc :: String concraftDesc = "Concraft-pl " ++ showVersion version data Concraft = Train- { trainPath :: FilePath+ { trainPath :: FilePath , evalPath :: Maybe FilePath- , format :: Format , tagsetPath :: Maybe FilePath- , noAna :: Bool- -- , discardHidden :: Bool , iterNum :: Double , batchSize :: Int , regVar :: Double@@ -63,33 +76,66 @@ , disk :: Bool , outModel :: FilePath , guessNum :: Int- , r0 :: Guess.R0T }+ , r0 :: Guess.R0T+ , zeroProbLabel :: String+ , visibleOnly :: Bool+ -- , disambTiers :: Pol.DisambTiersCfg+ , config :: FilePath+ } | Tag { inModel :: FilePath- , noAna :: Bool- , format :: Format- , marginals :: Bool }- -- , guessNum :: Int }+ -- , marginals :: Bool+ , inFile :: Maybe FilePath+ , outFile :: Maybe FilePath+ , probType :: DB.ProbType+ -- , suppressProbs :: Bool+ , mayGuessNum :: Maybe Int+ , freqPath :: Maybe FilePath+ , freqSmoothing :: Double+ , shortestPath :: Bool+ , longestPath :: Bool+ , numericDisamb :: Bool+ } | Server { inModel :: FilePath- , port :: Int }+ , probType :: DB.ProbType+ , mayGuessNum :: Maybe Int+ , numericDisamb :: Bool+ , port :: Int+ } | Client- { noAna :: Bool- , format :: Format- , marginals :: Bool- , host :: String- , port :: Int }- | Compare- { tagsetPath :: Maybe FilePath- , refPath :: FilePath- , otherPath :: FilePath- , format :: Format }- | Prune- { inModel :: FilePath- , outModel :: FilePath- , threshold :: Double }--- | ReAna--- { format :: Format }+ { serverAddr :: String+ , inFile :: Maybe FilePath+ , outFile :: Maybe FilePath+ , batchSize :: Int+ }+ | Eval+ { justTagsetPath :: FilePath+ , goldPath :: FilePath+ , taggPath :: FilePath+ , onlyOov :: Bool+ , onlyAmb :: Bool+ , onlyEos :: Bool+ , expandTags :: Bool+ , ignoreTags :: Bool+ , heedEos :: Bool+ , weak :: Bool+ , discardProb0 :: Bool+ , verbose :: Bool+ }+ | Check+ { justTagsetPath :: FilePath+ , dagPath :: FilePath+ }+ | Freqs+ { dagPath :: FilePath+ -- , justTagsetPath :: FilePath+ -- , outPath :: FilePath+ }+ | Ambi+ { dagPath :: FilePath+ , onlyChosen :: Bool+ } deriving (Data, Typeable, Show) @@ -98,68 +144,113 @@ { trainPath = def &= argPos 1 &= typ "TRAIN-FILE" , evalPath = def &= typFile &= help "Evaluation file" , tagsetPath = def &= typFile &= help "Tagset definition file"- , format = enum [Plain &= help "Plain format"]- , noAna = False &= help "Do not perform reanalysis"+ , config = def &= typFile &= help "Global configuration file" -- , discardHidden = False &= help "Discard hidden features" , iterNum = 20 &= help "Number of SGD iterations"- , batchSize = 50 &= help "Batch size"- , regVar = 10.0 &= help "Regularization variance"- , gain0 = 1.0 &= help "Initial gain parameter"- , tau = 5.0 &= help "Initial tau parameter"+ , batchSize = 50 &= help+ "Batch size (the number of dataset elements taken in a single SGD update)"+ , regVar = 10.0 &=+ help "Regularization variance (the higher variance, the higher penalty for large params)"+ , gain0 = 0.25 &= help+ "Initial gain parameter (gain is used to scale the gradient before parameter update)"+ -- The value `0.25` makes sense given that SGD momentum is used+ , tau = 5.0 &= help+ "Initial tau parameter (after how many passes over the full dataset the gain is halved)" , disk = False &= help "Store SGD dataset on disk" , outModel = def &= typFile &= help "Output Model file" , guessNum = 10 &= help "Number of guessed tags for each unknown word"- , r0 = Guess.OovChosen &= help "R0 construction method" }+ , r0 = Guess.OovChosen &= help "R0 construction method"+ , zeroProbLabel = "xxx" &= help "Zero probability label"+ , visibleOnly = False &= help "Extract only visible features for the guesser"+ -- , disambTiers = Pol.TiersDefault &= help "Dismabiguation tiers configuration"+ } tagMode :: Concraft tagMode = Tag { inModel = def &= argPos 0 &= typ "MODEL-FILE"- , noAna = False &= help "Do not analyse input text"- , format = enum [Plain &= help "Plain format"]- , marginals = False &= help "Tag with marginal probabilities" }- -- , guessNum = 10 &= help "Number of guessed tags for each unknown word" }+ -- , noAna = False &= help "Do not analyse input text"+ -- , marginals = False &= help "Tag with marginal probabilities" }+ , inFile = def &= typFile &= help "Input file (stdin by default)"+ , outFile = def &= typFile &= help "Output file (stdout by default)"+ , probType = DB.Marginals &= help "Type of probabilities"+ -- , suppressProbs = False &= help "Do not show probabilities"+ , freqPath = def &= typFile &= help "File with chosen/not-chosen counts"+ , freqSmoothing = 1.0 &= help+ "Smoothing parameter for frequency-based path selection"+ , shortestPath = False &= help+ "Select shortest paths prior to parsing (can serve as a segmentation baseline)"+ , longestPath = False &= help+ "Select longest paths prior to parsing (mutually exclusive with shortestPath)"+ , mayGuessNum = def &= help "Number of guessed tags for each unknown word"+ , numericDisamb = False &= help+ "Print disamb markers as numerical values in the probability column"+ } serverMode :: Concraft serverMode = Server- { inModel = def &= argPos 0 &= typ "MODEL-FILE"- , port = portDefault &= help "Port number" }+ { inModel = def &= argPos 0 &= typ "MODEL-FILE"+ , probType = DB.Marginals &= help "Type of probabilities"+ , mayGuessNum = def &= help "Number of guessed tags for each unknown word"+ , numericDisamb = False &= help+ "Print disamb markers as numerical values in the probability column"+ , port = 3000 &= help "Server port"+ } clientMode :: Concraft clientMode = Client- { noAna = False &= help "Do not perform reanalysis"- , port = portDefault &= help "Port number"- , host = "localhost" &= help "Server host name"- , format = enum [Plain &= help "Plain output format"]- , marginals = False &= help "Tag with marginal probabilities" }+ { serverAddr = "http://localhost:3000/parse" &= help "Server address"+ , inFile = def &= typFile &= help "Input file (stdin by default)"+ , outFile = def &= typFile &= help "Output file (stdout by default)"+ , batchSize = 10 &= help "Sent graphs in batches of the given size"+ } -compareMode :: Concraft-compareMode = Compare- { refPath = def &= argPos 1 &= typ "REFERENCE-FILE"- , otherPath = def &= argPos 2 &= typ "OTHER-FILE"- , tagsetPath = def &= typFile &= help "Tagset definition file"- , format = enum [Plain &= help "Plain format"] }+evalMode :: Concraft+evalMode = Eval+ { justTagsetPath = def &= typ "TAGSET-FILE" &= argPos 0+ , goldPath = def &= typ "GOLD-FILE" &= argPos 1+ , taggPath = def &= typ "TAGGED-FILE" &= argPos 2+ , onlyOov = False &= help "Only OOV edges"+ , onlyAmb = False &= help "Only segmentation-ambiguous edges"+ , onlyEos = False &= help "Only EOS edges"+ , expandTags = False &= help "Expand tags"+ , ignoreTags = False &= help "Ignore tags (compute segmentation-level accurracy)"+ , heedEos = False &= help "Pay attention to EOS markers (ignored by default)"+ , weak = False &= help "Compute weak accuracy rather than strong"+ , discardProb0 = False &= help "Discard sentences with near 0 probability"+ , verbose = False &= help "Print information about compared elements"+ } -pruneMode :: Concraft-pruneMode = Prune- { inModel = def &= argPos 0 &= typ "INPUT-MODEL"- , outModel = def &= argPos 1 &= typ "OUTPUT-MODEL"- , threshold = 0.05 &=- help "Remove disambiguation features below the threshold" }+checkMode :: Concraft+checkMode = Check+ { justTagsetPath = def &= typ "TAGSET-FILE" &= argPos 0+ , dagPath= def &= typ "DAG-FILE" &= argPos 1+ } --- reAnaMode :: Concraft--- reAnaMode = ReAna--- { format = enum [Plain &= help "Plain format"] }+freqsMode :: Concraft+freqsMode = Freqs+ { dagPath = def &= typ "DAG-FILE" &= argPos 1+ -- , justTagsetPath = def &= typ "TAGSET-FILE" &= argPos 0+ -- , outPath = def &= typ "FREQ-FILE" &= help "Output file to store counts"+ } +ambiMode :: Concraft+ambiMode = Ambi+ { dagPath = def &= typ "DAG-FILE" &= argPos 1+ , onlyChosen = False &= help "Take only the chose tokens into account"+ }++ argModes :: Mode (CmdArgs Concraft) argModes = cmdArgsMode $ modes- [trainMode, tagMode, serverMode, clientMode, compareMode, pruneMode]+ [ trainMode, tagMode, serverMode, clientMode+ , evalMode, checkMode, freqsMode, ambiMode ] &= summary concraftDesc &= program "concraft-pl" @@ -181,155 +272,362 @@ Nothing -> getDataFileName "config/nkjp-tagset.cfg" Just x -> return x tagset <- parseTagset tagsetPath' <$> readFile tagsetPath'- let train0 = parseFileO format trainPath- let eval0 = parseFileO' format evalPath- concraft <- C.train (trainConf tagset) train0 eval0++ -- Dhall configuration+ let configPath =+ if isAbsolute config+ then config+ else "./" </> config+ dhall <- Dhall.detailed+ (Dhall.input Dhall.auto $ fromString configPath)++ -- let zeroProbLab = P.parseTag taset zeroProbLabel+ let zeroProbLab = PX.Interp+ { PX.base = "none"+ , PX.tag = T.pack zeroProbLabel+ , PX.commonness = Nothing+ , PX.qualifier = Nothing+ , PX.metaInfo = Nothing+ , PX.eos = False }+ train0 = DB.parseData <$> readFileUtf8 trainPath+ eval0 = case evalPath of+ Nothing -> return []+ Just ph -> DB.parseData <$> readFileUtf8 ph+ -- putStrLn $ "\nRegularization variance: " ++ show regVar+ concraft <- Pol.train (trainConf dhall tagset zeroProbLab) train0 eval0 unless (null outModel) $ do putStrLn $ "\nSaving model in " ++ outModel ++ "..."- C.saveModel outModel concraft+ Pol.saveModel outModel concraft where sgdArgs = SGD.SgdArgs { SGD.batchSize = batchSize , SGD.regVar = regVar , SGD.iterNum = iterNum , SGD.gain0 = gain0- , SGD.tau = tau }- trainConf tagset = C.TrainConf- { tagset = tagset + , SGD.tau = tau+ }+ trainConf dhall tagset zeroLab = Pol.TrainConf+ { tagset = tagset , sgdArgs = sgdArgs- , reana = not noAna+ -- , reana = not noAna , onDisk = disk , guessNum = guessNum- , r0 = r0 }+ , r0 = r0+ , zeroProbLabel = zeroLab+ , guessOnlyVisible = visibleOnly+ -- , disambTiersCfg = disambTiers+ , globalConfig = dhall+ } exec Tag{..} = do- cft <- C.loadModel inModel- pool <- Maca.newMacaPool numCapabilities- inp <- L.getContents- out <- R.long (R.short pool cft) $ rq $ if noAna- then R.Doc $ parseText format inp- else R.Long inp- L.putStr $ showData showCfg out- where- rq x = R.Request- { R.rqBody = x- , R.rqConf = rqConf }- rqConf = R.Config- { R.tagProbs = marginals }- showCfg = ShowCfg- { formatCfg = format- , showWsCfg = marginals }+ -- crf <- Pol.loadModel P.parseTag inModel+ crf <- Pol.loadModel Pol.simplify4gsr Pol.simplify4dmb inModel+ -- inp <- DB.parseData <$> L.getContents+ inp <- DB.parseData <$> case inFile of+ Nothing -> getContentsUtf8+ Just path -> readFileUtf8 path+ pathSelection <-+ case (shortestPath, longestPath, freqPath) of+ (True, _, _) -> return $ Just Seg.Min+ (_, True, _) -> return $ Just Seg.Max+ (_, _, Just freqPath) -> do+ freqMap <- loadFreqMap freqPath+ let conf = Seg.FreqConf+ { Seg.pickFreqMap = freqMap+ , Seg.smoothingParam = freqSmoothing+ }+ return . Just $ Seg.Freq conf+ _ -> return Nothing+ let guessNum = case mayGuessNum of+ Nothing -> C.guessNum crf+ Just k -> k+ cfg = Pol.AnnoConf+ { trimParam = guessNum+ , pickPath = pathSelection+ }+ out = Pol.annoAll cfg crf <$> inp+ showCfg = DB.ShowCfg+ -- { suppressProbs = suppressProbs+ { probType = probType+ , numericDisamb = numericDisamb }+ case outFile of+ Nothing -> putStrUtf8 $ DB.showData showCfg out+ Just path -> writeFileUtf8 path $ DB.showData showCfg out exec Server{..} = do- putStr "Loading model..." >> hFlush stdout- concraft <- C.loadModel inModel- putStrLn " done"- pool <- Maca.newMacaPool numCapabilities- let portNum = N.PortNumber $ fromIntegral port- putStrLn $ "Listening on port " ++ show port- S.runConcraftServer pool concraft portNum+ crf <- Pol.loadModel Pol.simplify4gsr Pol.simplify4dmb inModel+ let guessNum = case mayGuessNum of+ Nothing -> C.guessNum crf+ Just k -> k+ cfg = Pol.AnnoConf+ { trimParam = guessNum+ , pickPath = Nothing+ }+ showCfg = DB.ShowCfg+ { probType = probType+ , numericDisamb = numericDisamb+ }+ serverCfg = Server.ServerCfg+ { concraft = crf+ , annoCfg = cfg+ , showCfg = showCfg+ }+ Server.runServer serverCfg port exec Client{..} = do- let portNum = N.PortNumber $ fromIntegral port- inp <- L.getContents- out <- R.long (S.submit host portNum) $ rq $ if noAna- then R.Doc $ parseText format inp- else R.Long inp- L.putStr $ showData showCfg out- where- rq x = R.Request- { R.rqBody = x- , R.rqConf = rqConf }- rqConf = R.Config- { R.tagProbs = marginals }- showCfg = ShowCfg- { formatCfg = format- , showWsCfg = marginals }+ -- clear the file, if specified (stdout otherwise)+ case outFile of+ Nothing -> return ()+ Just path -> writeFileUtf8 path ""+ inpAll <- case inFile of+ Nothing -> getContentsUtf8+ Just path -> readFileUtf8 path+ let inputs+ = map (L.intercalate "\n\n")+ . group batchSize+ $ filter+ (not . L.null)+ (L.splitOn "\n\n" inpAll)+ forM_ (map L.toStrict inputs) $ \inp -> do+ let req = Server.Request {dag = inp}+ cfg = Server.ClientCfg {serverAddr=serverAddr}+ Server.sendRequest cfg req >>= \case+ Nothing -> putStrLn "<< NO RESPONSE >>"+ Just Server.Answer{..} -> case outFile of+ Nothing -> putStrUtf8 (L.fromStrict dag)+ Just path -> appendFileUtf8 path (L.fromStrict dag) -exec Compare{..} = do- tagsetPath' <- case tagsetPath of- Nothing -> getDataFileName "config/nkjp-tagset.cfg" - Just x -> return x- tagset <- parseTagset tagsetPath' <$> readFile tagsetPath'- let convert = map (X.packSeg tagset) . concat- xs <- convert <$> parseFile format refPath- ys <- convert <$> parseFile format otherPath- let s = Acc.weakLB tagset xs ys- putStrLn $ "Number of segments in reference file: " ++ show (Acc.gold s)- putStrLn $ "Number of correct tags: " ++ show (Acc.good s)- putStrLn $ "Weak accuracy lower bound: " ++ show (Acc.accuracy s)+exec Eval{..} = do+ tagset <- parseTagset justTagsetPath <$> readFile justTagsetPath+ let simplify = fmap $ \seg ->+ let simplify4eval interp =+ ( P.parseTag tagset $ PX.tag interp+ , PX.eos interp && heedEos )+ newTags = X.mapWMap simplify4eval (X.tags seg)+ in seg {X.tags = newTags}+ process = PX.packSent . simplify+ fromFile = fmap (map process . DB.parseData) . readFileUtf8 + putStrLn $ concat+ [ "Note that in this evaluation lemmas "+ , "are *not* taken into account."+ ] -exec Prune{..} = do- cft <- C.loadModel inModel- C.saveModel outModel $ C.prune threshold cft+ let cfg = Acc.AccCfg+ { Acc.onlyOov = onlyOov+ , Acc.onlyAmb = onlyAmb+ , Acc.onlyMarkedWith =+ if onlyEos+ then S.singleton True+ else S.empty+ , Acc.accTagset = tagset+ , Acc.expandTag = expandTags+ , Acc.ignoreTag = ignoreTags+ , Acc.weakAcc = weak+ , Acc.discardProb0 = discardProb0+ , Acc.verbose = verbose+ }+ stats <- Acc.collect cfg+ <$> fromFile goldPath+ <*> fromFile taggPath+ putStr "Precision: " >> print (Acc.precision stats)+ putStr "Recall: " >> print (Acc.recall stats)+ putStr "Accuracy: " >> print (Acc.accuracy stats) --- exec ReAna{..} = do--- inp <- parseText format <$> L.getContents--- out <- showData format <$> +exec Check{..} = do+ tagset <- parseTagset justTagsetPath <$> readFile justTagsetPath+ dags <- DB.parseData <$> readFileUtf8 dagPath+ forM_ dags $ \dag -> do+ if (not $ DAG.isOK dag) then do+ putStrLn "Incorrectly structured graph:"+ showDAG dag+ else if (not $ DAG.isDAG dag) then do+ putStrLn "Graph with cycles:"+ showDAG dag+ else case verifyProb tagset dag of+ Nothing -> return ()+ Just p -> do+ putStr "Probability equal to "+ putStr (show p)+ putStrLn ":"+ showDAG dag+ where+ showDAG dag =+ forM_ (DAG.dagEdges dag) $ \edgeID -> do+ let from = DAG.begsWith edgeID dag+ to = DAG.endsWith edgeID dag+ val = DAG.edgeLabel edgeID dag+ putStr (show $ DAG.unNodeID from)+ putStr ", "+ putStr (show $ DAG.unNodeID to)+ putStr " => "+ T.putStrLn (PX.orth $ X.word val)+ verifyProb tagset dag =+ let schema = Schema.fromConf Schema.nullConf+ rawData = Guess.schemed (Pol.simplify4gsr tagset) schema [PX.packSent dag]+ [encDag] = CRF.Codec.encodeDataL (CRF.Codec.mkCodec rawData) rawData+ p = CRF.Train.dagProb encDag+ eps = 1e-9+ in if p >= 1 - eps && p <= 1 + eps+ then Nothing+ else Just p ------------------------------------------- Reading files----------------------------------------+exec Freqs{..} = do+ -- tagset <- parseTagset justTagsetPath <$> readFile justTagsetPath+ dags <- DB.parseData <$> readFileUtf8 dagPath+ let freqMap = Seg.computeFreqs $ map PX.packSent dags+ printFreqMap freqMap -parseFileO' :: Format -> Maybe FilePath -> IO [X.SentO X.Tag]-parseFileO' format path = case path of- Nothing -> return []- Just pt -> parseFileO format pt+exec Ambi{..} = do+ dags <- DB.parseData <$> readFileUtf8 dagPath+ let stats = Seg.computeAmbiStats cfg $ map PX.packSent dags+ print stats+ where+ cfg = Seg.AmbiCfg+ { Seg.onlyChosen = onlyChosen+ } -parseFileO :: Format -> FilePath -> IO [X.SentO X.Tag]-parseFileO format path = parseParaO format <$> L.readFile path+---------------------------------------+-- Frequency map+--------------------------------------- -parseFile :: Format -> FilePath -> IO [X.Sent X.Tag]-parseFile format path = parsePara format <$> L.readFile path+printFreqMap+ :: M.Map T.Text (Int, Int)+ -> IO ()+printFreqMap freqMap =+ forM_ (M.toList freqMap) $ \(orth, (chosen, notChosen)) -> do+ T.putStr $ T.replace "\t" " " orth+ T.putStr "\t"+ putStr $ show chosen+ T.putStr "\t"+ putStrLn $ show notChosen +loadFreqMap+ :: FilePath+ -> IO (M.Map T.Text (Int, Int))+loadFreqMap filePath = do+ M.fromList . map readPair . T.lines <$> T.readFile filePath+ where+ readPair line =+ case T.splitOn "\t" line of+ [orth, chosen, notChosen] ->+ (orth, (readInt chosen, readInt notChosen))+ _ -> error $ "loadFreqMap: line incorrectly formatted: " ++ T.unpack line+ readInt = read . T.unpack++ ------------------------------------------ Parsing text+-- UTF8 --------------------------------------- --- parseTextO :: Format -> L.Text -> [[X.SentO X.Tag]]--- parseTextO format = map (map X.withOrig) . parseText format+readFileUtf8 :: FilePath -> IO L.Text+readFileUtf8 path = L.decodeUtf8 <$> BL.readFile path -parseParaO :: Format -> L.Text -> [X.SentO X.Tag]-parseParaO format = map X.withOrig . parsePara format+writeFileUtf8 :: FilePath -> L.Text -> IO ()+writeFileUtf8 path = BL.writeFile path . L.encodeUtf8 ------------------------------------------- Parsing (format dependent)----------------------------------------+appendFileUtf8 :: FilePath -> L.Text -> IO ()+appendFileUtf8 path = BL.appendFile path . L.encodeUtf8 -parseText :: Format -> L.Text -> [[X.Sent X.Tag]]-parseText Plain = P.parsePlain+getContentsUtf8 :: IO L.Text+getContentsUtf8 = L.decodeUtf8 <$> BL.getContents -parsePara :: Format -> L.Text -> [X.Sent X.Tag]-parsePara Plain = P.parsePara+putStrUtf8 :: L.Text -> IO ()+putStrUtf8 = BL.putStr . L.encodeUtf8 ------------------------------------------ Showing (format dependent)+-- Utils --------------------------------------- -data ShowCfg = ShowCfg {- -- | The format used.- formatCfg :: Format- -- | Show weights?- , showWsCfg :: Bool }+-- | Group the input list into the groups of X.+group :: Int -> [a] -> [[a]]+group n =+ doit n []+ where+ doit k acc (x:xs)+ | k > 0 =+ doit (k-1) (x:acc) xs+ | otherwise =+ reverse acc : doit n [] (x:xs)+ doit _ acc []+ | null acc = []+ | otherwise = [reverse acc] -showData :: ShowCfg -> [[X.Sent X.Tag]] -> L.Text-showData ShowCfg{..} = P.showPlain (P.ShowCfg {P.showWsCfg = showWsCfg})+-- ---------------------------------------+-- -- Reading files+-- ---------------------------------------+--+-- -- TODO: make everything work on DAG input/output.+--+-- parseFileO' :: Format -> Maybe FilePath -> IO [X.SentO X.Tag]+-- parseFileO' format path = case path of+-- Nothing -> return []+-- Just pt -> parseFileO format pt+--+--+-- parseFileO :: Format -> FilePath -> IO [X.SentO X.Tag]+-- parseFileO format path = parseParaO format <$> L.readFile path+--+--+-- parseFile :: Format -> FilePath -> IO [X.Sent X.Tag]+-- parseFile format path = parsePara format <$> L.readFile path+--+--+-- ---------------------------------------+-- -- Parsing text+-- ---------------------------------------+--+--+-- -- parseTextO :: Format -> L.Text -> [[X.SentO X.Tag]]+-- -- parseTextO format = map (map X.withOrig) . parseText format+--+--+-- parseParaO :: Format -> L.Text -> [X.SentO X.Tag]+-- parseParaO format = map X.withOrig . parsePara format+--+--+-- ---------------------------------------+-- -- Parsing (format dependent)+-- ---------------------------------------+--+--+-- parseText :: Format -> L.Text -> [[X.Sent X.Tag]]+-- parseText Plain = P.parsePlain+--+--+-- parsePara :: Format -> L.Text -> [X.Sent X.Tag]+-- parsePara Plain = P.parsePara+--+--+-- ---------------------------------------+-- -- Showing (format dependent)+-- ---------------------------------------+--+--+-- data ShowCfg = ShowCfg {+-- -- | The format used.+-- formatCfg :: Format+-- -- | Show weights?+-- , showWsCfg :: Bool }+--+--+-- showData :: ShowCfg -> [[X.Sent X.Tag]] -> L.Text+-- showData ShowCfg{..} = P.showPlain (P.ShowCfg {P.showWsCfg = showWsCfg})