packages feed

concraft (empty) → 0.1.0

raw patch · 7 files changed

+534/−0 lines, 7 filesdep +basedep +binarydep +cmdargssetup-changed

Dependencies added: base, binary, cmdargs, containers, crf-chain1-constrained, monad-ox, sgd, text, text-binary, vector

Files

+ LICENSE view
@@ -0,0 +1,26 @@+Copyright (c) 2011 Jakub Waszczuk, 2012 IPI PAN+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ NLP/Concraft/Guess.hs view
@@ -0,0 +1,131 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module NLP.Concraft.Guess+( Ox+, Schema+, Ob+, schema+, schematize+, Guesser (..)+, guess+, tagFile+, learn+) where++import Control.Applicative (pure, (<$>), (<*>))+import qualified Data.Set as S+import qualified Data.Map as M+import qualified Data.Text as T+import qualified Data.Text.Lazy as L+import qualified Data.Vector as V++import Data.Binary (Binary, get, put)+import Data.Text.Binary ()++import qualified Control.Monad.Ox as Ox+import qualified Control.Monad.Ox.Text as Ox+import qualified Data.CRF.Chain1.Constrained as CRF+import qualified Numeric.SGD as SGD++import NLP.Concraft.Morphosyntax+import qualified NLP.Concraft.Plain as P++-- | The Ox monad specialized to word token type and text observations.+-- TODO: Move to monad-ox package from here and from the nerf library.+type Ox t a = Ox.Ox (Word t) T.Text a++-- | A schema is a block of the Ox computation performed within the+-- context of the sentence and the absolute sentence position.+type Schema t a = V.Vector (Word t) -> Int -> Ox t a++-- | An observation consist of an index (of list type) and an actual+-- observation value.+type Ob = ([Int], T.Text)++schema :: Schema t ()+schema sent = \k -> do+    mapM_ (Ox.save . lowPref k) [1, 2]+    mapM_ (Ox.save . lowSuff k) [1, 2]+    Ox.save (knownAt k)+    Ox.save (isBeg k <> pure "-" <> shapeP k)+  where+    at          = Ox.atWith sent+    lowOrth i   = T.toLower <$> orth `at` i+    lowPref i j = Ox.prefix j =<< lowOrth i+    lowSuff i j = Ox.suffix j =<< lowOrth i+    shape i     = Ox.shape <$> orth `at` i+    shapeP i    = Ox.pack <$> shape i+    knownAt i   = boolF <$> known `at` i+    isBeg i     = (Just . boolF) (i == 0)+    boolF True  = "T"+    boolF False = "F"+    x <> y      = T.append <$> x <*> y++-- | Schematize the input sentence with according to 'schema' rules.+schematize :: Ord t => Sent t -> CRF.Sent Ob t+schematize sent =+    [ CRF.Word (obs i) (lbs i)+    | i <- [0 .. n - 1] ]+  where+    v = V.fromList sent+    n = V.length v+    obs = S.fromList . Ox.execOx . schema v+    lbs = tags . (v V.!)++-- | A guesser represented by the conditional random field.+data Guesser t = Guesser+    { crf   :: CRF.CRF Ob t -- ^ The CRF model+    , ign   :: t            -- ^ The tag indicating unkown words+    }++instance (Ord t, Binary t) => Binary (Guesser t) where+    put Guesser{..} = put crf >> put ign+    get = Guesser <$> get <*> get++-- | Determine the 'k' most probable labels for each unknown word+-- in the sentence.+guess :: Ord t => Int -> Guesser t -> Sent t -> [[t]]+guess k gsr sent = CRF.tagK k (crf gsr) (schematize sent)+{-# INLINE guess #-}++-- | Tag the file.+tagFile+    :: Int              -- ^ Guesser argument+    -> Guesser T.Text   -- ^ Guesser itself+    -> FilePath         -- ^ File to tag (plain format)+    -> IO L.Text+tagFile k gsr path =+    P.showPlain (ign gsr) . map onSent <$> P.readPlain (ign gsr) path+  where+    onSent sent =+        let (xs, _) = unzip (map P.fromTok sent)+            yss = guess k gsr xs+        in  [ if P.known tok+                then tok+                else P.addNones False tok ys+            | (tok, ys) <- zip sent yss ]++-- | TODO: Abstract over the format type.+learn+    :: SGD.SgdArgs      -- ^ SGD parameters +    -> T.Text        	-- ^ The tag indicating unknown words+    -> FilePath         -- ^ Train file (plain format)+    -> Maybe FilePath   -- ^ Maybe eval file+    -> IO (Guesser T.Text)+learn sgdArgs _ign trainPath evalPath'Maybe = do+    _crf <- CRF.train sgdArgs+        (schemed _ign trainPath)+        (schemed _ign <$> evalPath'Maybe)+        (const CRF.presentFeats)+    return $ Guesser _crf _ign++-- | Schematized data from the plain file.+schemed :: T.Text -> FilePath -> IO [CRF.SentL Ob T.Text]+schemed _ign =+    fmap (map onSent) . P.readPlain _ign+  where+    onSent sent =+        let (xs, ys) = unzip (map P.fromTok sent)+            mkDist = CRF.mkDist . M.toList . M.map unPositive+        in  zip (schematize xs) (map mkDist ys)
+ NLP/Concraft/Morphosyntax.hs view
@@ -0,0 +1,57 @@+module NLP.Concraft.Morphosyntax+( Word (..)+, Sent+, Choice+, Positive (unPositive)+, (<+>)+, mkPositive+, best+, known+) where++import Data.Ord (comparing)+import Data.List (maximumBy)+import qualified Data.Set as S+import qualified Data.Map as M+import qualified Data.Text as T++-- | A word parametrized over the tag type.+data Word t = Word {+    -- | Orthographic form.+      orth  :: T.Text+    -- | Set of word interpretations.+    , tags  :: S.Set t }+    deriving (Show, Read, Eq, Ord)++-- | A sentence of 'Word's.+type Sent t = [Word t]++-- | Interpretations chosen in the given context with+-- corresponding positive weights.+type Choice t = M.Map t (Positive Double)++-- | Positive number.+newtype Positive a = Positive { unPositive :: a }+    deriving (Show, Eq, Ord)++(<+>) :: Num a => Positive a -> Positive a -> Positive a+Positive x <+> Positive y = Positive (x + y)+{-# INLINE (<+>) #-}++mkPositive :: (Num a, Ord a) => a -> Positive a+mkPositive x+    | x > 0     = Positive x+    | otherwise = error "mkPositive: not a positive number"+{-# INLINE mkPositive #-}++-- | Retrieve the most probable interpretation.+best :: Choice t -> t+best c+    | M.null c  = error "best: null choice" +    | otherwise = fst . maximumBy (comparing snd) $ M.toList c++-- | A word is considered to be known when the set of possible+-- interpretations is not empty.+known :: Word t -> Bool+known = not . S.null . tags+{-# INLINE known #-}
+ NLP/Concraft/Plain.hs view
@@ -0,0 +1,196 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++-- | Simple format for morphosyntax representation which+-- assumes that all tags have a textual representation+-- with no spaces inside and that one of the tags indicates+-- unknown words.++module NLP.Concraft.Plain+(+-- * Types+  Space (..)+, Token (..)+, Interp (..)++-- * Interface+, fromTok+, choose+, addInterps+, addNones++-- * Parsing+, readPlain+, parsePlain+, parseSent++-- * Showing+, writePlain+, showPlain+, showSent+, showWord+) where++import Data.Monoid (Monoid, mappend, mconcat)+import Data.Maybe (catMaybes)+import Data.List (groupBy)+import qualified Data.Set as S+import qualified Data.Map as M+import qualified Data.Text as T+import qualified Data.Text.Lazy as L+import qualified Data.Text.Lazy.IO as L+import qualified Data.Text.Lazy.Builder as L++import qualified NLP.Concraft.Morphosyntax as Mx++-- | No space, space or newline.+data Space+    = None+    | Space+    | NewLine+    deriving (Show, Eq, Ord)++-- | A token.+data Token = Token+    { orth      :: T.Text+    , space     :: Space+    , known     :: Bool+    -- | Interpretations with disambiguation info.+    , interps   :: M.Map Interp Bool }+    deriving (Show, Eq, Ord)+    +data Interp = Interp+    { base      :: T.Text+    , tag       :: T.Text }+    deriving (Show, Eq, Ord)++-- | Extract information relevant for tagging.+fromTok :: Token -> (Mx.Word T.Text, Mx.Choice T.Text)+fromTok tok =+    (word, choice)+  where+    word = Mx.Word+        { Mx.orth   = orth tok+        , Mx.tags   = if known tok+            then S.fromList . map tag . M.keys $ interps tok+            else S.empty }+    choice = M.fromListWith (Mx.<+>)+        [ (tag x, Mx.mkPositive 1)+        | (x, True) <- M.toList (interps tok) ]++-- | Mark all interpretations with tag component beeing a member of+-- the given choice set with disamb annotations.+choose :: Token -> S.Set T.Text -> Token+choose tok choice =+    tok { interps = (M.fromList . map mark . M.keys) (interps tok) }+  where+    mark ip +        | tag ip `S.member` choice  = (ip, True) +        | otherwise                 = (ip, False)++-- | Add new interpretations with given disamb annotation.+addInterps :: Bool -> Token -> [Interp] -> Token+addInterps dmb tok xs =+    let newIps = M.fromList [(x, dmb) | x <- xs]+    in  tok { interps = M.unionWith max newIps (interps tok) }++-- | Add new interpretations with "None" base and given disamb annotation.+addNones :: Bool -> Token -> [T.Text] -> Token+addNones dmb tok = addInterps dmb tok . map (Interp "None")++readPlain :: T.Text -> FilePath -> IO [[Token]]+readPlain ign = fmap (parsePlain ign) . L.readFile++parsePlain :: T.Text -> L.Text -> [[Token]]+parsePlain ign = map (parseSent ign) . init . L.splitOn "\n\n"++parseSent :: T.Text -> L.Text -> [Token]+parseSent ign+    = map (parseWord ignL)+    . groupBy (\_ x -> cond x)+    . L.lines+  where+    cond = ("\t" `L.isPrefixOf`)+    ignL = L.fromStrict ign++parseWord :: L.Text -> [L.Text] -> Token+parseWord ign xs =+    (Token _orth _space _known _interps)+  where+    (_orth, _space) = parseHeader (head xs)+    ys          = map (parseInterp ign) (tail xs)+    _known      = not (Nothing `elem` ys)+    _interps    = M.fromList (catMaybes ys)++parseInterp :: L.Text -> L.Text -> Maybe (Interp, Bool)+parseInterp ign =+    doIt . tail . L.splitOn "\t"+  where+    doIt [form, tag]+        | tag == ign    = Nothing+        | otherwise     = Just $+            (mkInterp form tag, False)+    doIt [form, tag, "disamb"] = Just $+        (mkInterp form tag, True)+    doIt xs = error $ "parseInterp: " ++ show xs+    mkInterp form tag = Interp (L.toStrict form) (L.toStrict tag)++parseHeader :: L.Text -> (T.Text, Space)+parseHeader xs =+    let [_orth, space] = L.splitOn "\t" xs+    in  (L.toStrict _orth, parseSpace space)++parseSpace :: L.Text -> Space+parseSpace "none"    = None+parseSpace "space"   = Space+parseSpace "newline" = NewLine+parseSpace "newlines" = NewLine -- TODO: Remove this temporary fix+parseSpace xs        = error ("parseSpace: " ++ L.unpack xs)++-- | Printing.++-- | An infix synonym for 'mappend'.+(<>) :: Monoid m => m -> m -> m+(<>) = mappend+{-# INLINE (<>) #-}++writePlain :: T.Text -> FilePath -> [[Token]] -> IO ()+writePlain ign path = L.writeFile path . showPlain ign ++showPlain :: T.Text -> [[Token]] -> L.Text+showPlain ign =+    L.toLazyText . mconcat  . map (\xs -> buildSent ign xs <> "\n")++showSent :: T.Text -> [Token] -> L.Text+showSent ign = L.toLazyText . buildSent ign++showWord :: T.Text -> Token -> L.Text+showWord ign = L.toLazyText . buildWord ign++buildSent :: T.Text -> [Token] -> L.Builder+buildSent ign = mconcat . map (buildWord ign)++buildWord :: T.Text -> Token -> L.Builder+buildWord ign tok+    =  L.fromText (orth tok) <> "\t"+    <> buildSpace (space tok) <> "\n"+    <> buildKnown ign (known tok)+    <> buildInterps (M.toList $ interps tok)++buildInterps :: [(Interp, Bool)] -> L.Builder+buildInterps interps = mconcat+    [ "\t" <> L.fromText _base <>+      "\t" <> L.fromText _tag <>+      if dmb+        then "\tdisamb\n"+        else "\n"+    | (Interp _base _tag, dmb) <- interps ]++buildSpace :: Space -> L.Builder+buildSpace None     = "none"+buildSpace Space    = "space"+buildSpace NewLine  = "newline"++buildKnown :: T.Text -> Bool -> L.Builder+buildKnown _ True       = ""+buildKnown ign False    = "\tNone\t" <> L.fromText ign <> "\n"
+ Setup.lhs view
@@ -0,0 +1,4 @@+#! /usr/bin/env runhaskell++> import Distribution.Simple+> main = defaultMain
+ concraft.cabal view
@@ -0,0 +1,46 @@+name:               concraft+version:            0.1.0+synopsis:           Morphosyntactic tagging tool based on constrained CRFs+description:+    A morphosyntactic tagging tool based on constrained conditional+    random fields.+license:            BSD3+license-file:       LICENSE+cabal-version:      >= 1.6+copyright:          Copyright (c) 2011 Jakub Waszczuk, 2012 IPI PAN+author:             Jakub Waszczuk+maintainer:         waszczuk.kuba@gmail.com+stability:          experimental+category:           Natural Language Processing+homepage:           https://github.com/kawu/concraft+build-type:         Simple++library+    build-depends:+        base >= 4 && < 5+      , containers+      , binary+      , text+      , text-binary >= 0.1 && < 0.2+      , vector+      , crf-chain1-constrained >= 0.1.1 && < 0.2+      , monad-ox >= 0.2 && < 0.3+      , sgd >= 0.2.2 && < 0.3++    exposed-modules:+        NLP.Concraft.Morphosyntax+      , NLP.Concraft.Guess+      , NLP.Concraft.Plain++    ghc-options: -Wall -O2++source-repository head+    type: git+    location: git://github.com/kawu/concraft.git++executable concraft-guess+    build-depends:+        cmdargs+    hs-source-dirs: ., tools+    main-is: concraft-guess.hs    +    ghc-options: -Wall -O2 -threaded
+ tools/concraft-guess.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE RecordWildCards #-}++import Control.Monad (when)+import System.Console.CmdArgs+import Data.Binary (encodeFile, decodeFile)+import Data.Text.Binary ()+import qualified Numeric.SGD as SGD+import qualified Data.Text as T+import qualified Data.Text.Lazy.IO as L++import NLP.Concraft.Guess (learn, tagFile)++data Args+  = LearnMode+    { learnPath	    :: FilePath+    , evalPath      :: Maybe FilePath+    , ignTag        :: String+    , iterNum       :: Double+    , batchSize     :: Int+    , regVar        :: Double+    , gain0         :: Double+    , tau           :: Double+    , outGuesser    :: FilePath }+  | TagMode+    { dataPath      :: FilePath+    , inGuesser     :: FilePath+    , guessNum      :: Int }+  deriving (Data, Typeable, Show)++learnMode :: Args+learnMode = LearnMode+    { ignTag = def &= argPos 0 &= typ "IGN-TAG"+    , learnPath = def &= argPos 1 &= typ "TRAIN-FILE"+    , evalPath = def &= typFile &= help "Evaluation file"+    , iterNum = 10 &= help "Number of SGD iterations"+    , batchSize = 30 &= help "Batch size"+    , regVar = 10.0 &= help "Regularization variance"+    , gain0 = 1.0 &= help "Initial gain parameter"+    , tau = 5.0 &= help "Initial tau parameter"+    , outGuesser = def &= typFile &= help "Output Guesser file" }++tagMode :: Args+tagMode = TagMode+    { inGuesser = def &= argPos 0 &= typ "GUESSER-FILE"+    , dataPath = def &= argPos 1 &= typ "INPUT"+    , guessNum = 10 &= help "Number of guessed tags for each unknown word" }++argModes :: Mode (CmdArgs Args)+argModes = cmdArgsMode $ modes [learnMode, tagMode]++main :: IO ()+main = exec =<< cmdArgsRun argModes++exec :: Args -> IO ()++exec LearnMode{..} = do+    gsr <- learn sgdArgs (T.pack ignTag) learnPath evalPath+    when (not . null $ outGuesser) $ do+        putStrLn $ "\nSaving model in " ++ outGuesser ++ "..."+        encodeFile outGuesser gsr+  where+    sgdArgs = SGD.SgdArgs+        { SGD.batchSize = batchSize+        , SGD.regVar = regVar+        , SGD.iterNum = iterNum+        , SGD.gain0 = gain0+        , SGD.tau = tau }++exec TagMode{..} = do+    gsr <- decodeFile inGuesser+    L.putStr =<< tagFile guessNum gsr dataPath