semiring (empty) → 0.1
raw patch · 12 files changed
+503/−0 lines, 12 filesdep +Booleandep +HUnitdep +QuickChecksetup-changed
Dependencies added: Boolean, HUnit, QuickCheck, base, containers, monoids, test-framework, test-framework-hunit, test-framework-quickcheck2
Files
- LICENSE +30/−0
- NLP/Semiring.hs +63/−0
- NLP/Semiring/Boolean.hs +17/−0
- NLP/Semiring/Counting.hs +21/−0
- NLP/Semiring/Derivation.hs +59/−0
- NLP/Semiring/Prob.hs +19/−0
- NLP/Semiring/Viterbi.hs +12/−0
- NLP/Semiring/ViterbiNBest.hs +50/−0
- NLP/Semiring/ViterbiNBestDerivation.hs +21/−0
- Setup.lhs +3/−0
- semiring.cabal +54/−0
- tests/Tests.hs +154/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2008, University of Brighton+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.+ * Neither the name of the University of Brighton nor the names of+ its contributors may be used to endorse or promote products+ derived from this software without specific prior written+ permission.++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/Semiring.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module NLP.Semiring(+ -- * Semiring+ -- + -- $SemiringDesc++ Multiplicative(..), + Monoid(..), + Semiring, + WeightedSemiring, + Weighted(..)) where+ +import Data.Monoid+import Data.Monoid.Multiplicative +import Data.Monoid.Additive +import Data.Function (on)++++-- $SemiringDesc+-- Semirings (rings without additive inverses, <http://en.wikipedia.org/wiki/Semiring>) are +-- a commonly used structure for performing computations over finite state machines, +-- parsers, and other dynamic programmy-systems. This library extends the basic structures +-- defined for Monoids to Semirings and includes implementations of the major semirings +-- for parsing. +--+-- This work is based largely on "Semiring Parsing" by Joshua Goodman. (<http://www.ldc.upenn.edu/acl/J/J99/J99-4004.pdf>) +-- which describes many of the interesting parsing semirings.++++-- | A 'Semiring' is made up of an additive Monoid and a Multiplicative.+-- It must also satisfy several other algebraic properties checked by quickcheck. +class (Multiplicative a, Monoid a) => Semiring a+++-- | A 'WeightedSemiring' also includes a sensical ordering over choices. +-- i.e. out of two choices which is better. This is used for Viterbi selection. +class (Semiring a, Ord a) => WeightedSemiring a++ +instance (Multiplicative a, Multiplicative b) => Multiplicative (a,b) where + one = (one, one)+ times (a, b) (a', b') = (a `times` a', b `times` b')+++-- | Dual semirings can be useful. For instance combining the +-- Prob semiring and the MultiDerivation ring gives the total likelihood of +-- a derivation along with the paths to get there. +instance (Semiring a, Semiring b) => Semiring (a,b) +++-- | The 'Weighted' type is the main type of WeightedSemiring.+-- It combines scoring semiring with a history semiring.+-- +-- The best example of this is the ViterbiDerivation semiring.+newtype Weighted semi1 semi2 = Weighted (semi1, semi2)+ deriving (Eq, Show, Monoid, Multiplicative, Semiring)++instance (Ord semi1, Eq semi2) => Ord (Weighted semi1 semi2) where + compare (Weighted s1) (Weighted s2) = (compare `on` fst) s1 s2 ++instance (WeightedSemiring a, Eq b, Semiring b) => WeightedSemiring (Weighted a b)
+ NLP/Semiring/Boolean.hs view
@@ -0,0 +1,17 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module NLP.Semiring.Boolean where+import NLP.Semiring+import qualified Data.Boolean as B+import Data.Boolean ((&&*),(||*)) +newtype Boolean = Boolean Bool+ deriving (Eq, Show, B.Boolean) ++instance Multiplicative Boolean where+ one = B.true+ times = (&&*)++instance Monoid Boolean where + mempty = B.false+ mappend = (||*)++instance Semiring Boolean
+ NLP/Semiring/Counting.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module NLP.Semiring.Counting where+import NLP.Semiring++-- | The 'Counting' semiring keeps track of the number of paths +-- or derivations led to a given output.+newtype Counting = Counting Integer+ deriving (Eq, Show, Num, Ord) ++instance Multiplicative Counting where+ one = 1+ times = (*) ++instance Monoid Counting where + mempty = 0+ mappend = (+)+++instance Semiring Counting +instance WeightedSemiring Counting +
+ NLP/Semiring/Derivation.hs view
@@ -0,0 +1,59 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module NLP.Semiring.Derivation (Derivation(..), MultiDerivation(..)) where+import NLP.Semiring+import NLP.Semiring.Helpers+import qualified Data.Set as S +import Data.Monoid+import Data.Maybe (isNothing)+++-- | The 'Derivation' semiring keeps track of a single path or derivation +-- that led to the known output. If there are more than one path it discards +-- in favor the lesser path (based on ord). The main purpose of this semiring +-- is to track derivations for ViterbiNBestDerivation. If you want to keep all paths, +-- use 'MultiDerivation'.+--+-- Derivation takes a Monoid as an argument that describes how to build up paths or +-- more complicated structures. +newtype Derivation m = Derivation (Maybe m)+ deriving (Eq, Show, Ord) ++instance (Monoid m) => Multiplicative (Derivation m) where+ one = Derivation $ Just mempty+ times (Derivation d1) (Derivation d2) = Derivation $ do + d1' <- d1+ d2' <- d2+ return $ mappend d1' d2'++instance (Ord m) => Monoid (Derivation m) where + mempty = Derivation Nothing+ mappend (Derivation s1) (Derivation s2) = + Derivation $ case (s1,s2) of + (Nothing, s2) -> s2+ (s1, Nothing) -> s1+ (s1, s2) -> max s1 s2 ++instance (Monoid m, Eq m, Ord m) => Semiring (Derivation m)+++-- | The 'MultiDerivation' semiring keeps track of a all paths or derivations +-- that led to the known output. This can be useful for debugging output.+-- +-- Keeping all these paths around can be expensive. 'MultiDerivation' leaves open +-- the implementation of the internal path monoid for more compact representations. +newtype MultiDerivation m = MultiDerivation (S.Set m)+ deriving (Eq, Show, Ord) ++instance (Monoid m, Ord m) => Multiplicative (MultiDerivation m) where+ one = MultiDerivation $ S.fromList [mempty]+ times (MultiDerivation d1) (MultiDerivation d2) = MultiDerivation $ + S.fromList $ + map (uncurry mappend) $ + cartesian (S.toList d1) (S.toList d2) ++instance (Ord m) => Monoid (MultiDerivation m) where + mempty = MultiDerivation S.empty+ mappend (MultiDerivation s1) (MultiDerivation s2) = MultiDerivation $ S.union s1 s2++instance (Ord m, Monoid m, Eq m) => Semiring (MultiDerivation m)+
+ NLP/Semiring/Prob.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module NLP.Semiring.Prob where +import NLP.Semiring++-- | The 'Prob' semiring keeps track of the likelihood of the known output +-- by keeping track of the probability of all paths. +newtype Prob = Prob Double+ deriving (Eq, Show, Num, Fractional, Ord) ++instance Multiplicative Prob where+ one = 1.0+ times = (*) ++instance Monoid Prob where + mempty = 0.0+ mappend = (+)++instance Semiring Prob +instance WeightedSemiring Prob
+ NLP/Semiring/Viterbi.hs view
@@ -0,0 +1,12 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module NLP.Semiring.Viterbi where+import NLP.Semiring+import NLP.Semiring.ViterbiNBest++data One = One +instance N One where + mkN = One+ n _ = 1++type Viterbi semi = ViterbiNBest One semi+
+ NLP/Semiring/ViterbiNBest.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE ExistentialQuantification, ScopedTypeVariables #-}+module NLP.Semiring.ViterbiNBest where+import NLP.Semiring+import NLP.Semiring.Helpers+import Data.List +++class N a where + mkN :: a+ n :: a -> Int+ +-- | The 'ViterbiNBest' semiring keeps track of the n best scoring path to a known+-- output. This score is determined by a user defined 'WeightedSemiring'. +-- +-- The value of n (the number of of values to rank) is included in the type to prevent +-- combining mismatching values. To create a new n, make a new unary type and an instance+-- of N.+-- +-- @+-- data Ten = Ten +-- instance N Ten where +-- mkN = Ten+-- n _ = 10+-- @+-- +data ViterbiNBest n semi = ViterbiNBest [semi] + deriving (Eq, Show)++instance (N n, Ord semi, WeightedSemiring semi) => Multiplicative (ViterbiNBest n semi) where+ one = ViterbiNBest [one]+ times (ViterbiNBest a) (ViterbiNBest b) = + ViterbiNBest $+ take (n (mkN::n)) $+ reverse $ sort $+ map (uncurry times) $ cartesian a b ++instance (N n, WeightedSemiring semi, Ord semi) => Monoid (ViterbiNBest n semi) where + mempty = ViterbiNBest []+ mappend (ViterbiNBest a) (ViterbiNBest b) = + ViterbiNBest $ take (n (mkN::n)) $ reverse $ sort (a ++ b)++instance (N n, WeightedSemiring semi, Ord semi) => Semiring (ViterbiNBest n semi)+++data Ten = Ten +instance N Ten where + mkN = Ten+ n _ = 10++type Viterbi10Best semi = ViterbiNBest Ten semi
+ NLP/Semiring/ViterbiNBestDerivation.hs view
@@ -0,0 +1,21 @@+module NLP.Semiring.ViterbiNBestDerivation where+import NLP.Semiring+import Data.List +import NLP.Semiring.Viterbi+import NLP.Semiring.ViterbiNBest+import NLP.Semiring.Prob+import NLP.Semiring.Derivation++-- | The 'ViterbiNBestDerivation' is an example of a more complicated semiring+-- built up from smaller components. It keeps track of the top N scoring paths +-- along with their derivations.+-- +-- > type ViterbiNBestDerivation n m = ViterbiNBest n (Weighted Prob (Derivation m))+type ViterbiNBestDerivation n m = ViterbiNBest n (Weighted Prob (Derivation m))+++-- | The 'ViterbiDerivation' is a simpler semiring. It just keeps track of the best +-- scoring path and it's derivation.+-- +-- > type ViterbiDerivation m = Viterbi (Weighted Prob (Derivation m))+type ViterbiDerivation m = Viterbi (Weighted Prob (Derivation m))
+ Setup.lhs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
+ semiring.cabal view
@@ -0,0 +1,54 @@+name: semiring+version: 0.1+synopsis: Semirings, ring-like structures used for dynamic programming applications +description: This provides a type class for semirings and + implementations of the common semirings used in natural language + processing.+category: Natural Language Processing+license: BSD3+license-file: LICENSE+author: Sasha Rush+maintainer: <srush at mit dot edu>+build-Type: Simple+cabal-version: >= 1.2++flag testing+ description: Testing mode, only build minimal components+ default: False++library+ exposed-modules: NLP.Semiring+ NLP.Semiring.Boolean+ NLP.Semiring.Prob+ NLP.Semiring.Viterbi+ NLP.Semiring.ViterbiNBest+ NLP.Semiring.Counting+ NLP.Semiring.Derivation+ NLP.Semiring.ViterbiNBestDerivation++ if flag(testing)+ buildable: False++ build-Depends: base >= 3 && < 4,+ containers >= 0.1 && < 0.3,+ monoids >= 0.2.0.2 && < 0.3,+ Boolean++executable hstestsemi+ main-is: Tests.hs+ hs-source-dirs: . tests/++ build-Depends: base >= 3 && < 4,+ containers >= 0.1 && < 0.3,+ monoids >= 0.2.0.2 && < 0.3,+ Boolean,+ QuickCheck >= 2,+ HUnit,+ test-framework,+ test-framework-hunit,+ test-framework-quickcheck2+ + + if !flag(testing)+ buildable: False+
+ tests/Tests.hs view
@@ -0,0 +1,154 @@+{-# LANGUAGE ScopedTypeVariables #-}+module Main where ++import Test.Framework (defaultMain, testGroup)+import Test.Framework.Providers.HUnit+import Test.Framework.Providers.QuickCheck2 (testProperty)++import Test.QuickCheck+import Test.HUnit++import NLP.Semiring+import NLP.Semiring.Boolean+import NLP.Semiring.Prob+import NLP.Semiring.Viterbi+import NLP.Semiring.ViterbiNBest+import NLP.Semiring.Counting+import NLP.Semiring.Derivation+import NLP.Semiring.ViterbiNBestDerivation++import qualified Data.Set as S+import Data.List+import Control.Monad (liftM)++main = defaultMain tests++tests = [+ testGroup "Semiring Props" [+ testProperty "semiProb bool" prop_boolRing,+ testProperty "semiProb prob" prop_probRing,+ testProperty "semiProb viterbi" prop_viterbiRing,+ testProperty "semiProb counting" prop_counting, + testProperty "semiProb viterbi n-best" prop_viterbiNBest,+ testProperty "semiProb derivation" prop_derivation,+ testProperty "semiProb multi-derivation" prop_multiDerivation,+ testProperty "semiProb nbest derivation" prop_nbestMultiDerivation+ ]+ ]++instance Arbitrary Prob where + arbitrary = Prob `liftM` choose (0.0, 1.0)++instance (N n, Ord a, Arbitrary a) => Arbitrary (ViterbiNBest n a) where + arbitrary = do+ v <- arbitrary+ return $ ViterbiNBest $ reverse $ sort $ take (n $ (mkN::n)) $ v+ +instance Arbitrary Boolean where + arbitrary = Boolean `liftM` choose (True, False)++instance Arbitrary Counting where + arbitrary = Counting `liftM` abs `liftM` arbitrary++instance (Arbitrary a) => Arbitrary (Derivation a) where + arbitrary = Derivation `liftM` arbitrary ++instance (Arbitrary a, Ord a) => Arbitrary (MultiDerivation a) where + arbitrary = (MultiDerivation . S.fromList . take 10) `liftM` arbitrary + +instance (Arbitrary a, Arbitrary b) => Arbitrary (Weighted a b) where + arbitrary = Weighted `liftM` arbitrary +++type Eql s = (s -> s -> Bool) +++-- (a * b) * c = a * (b * c)+associativeTimes :: (Semiring s) => (s,s,s) -> Eql s -> Bool+associativeTimes (s1, s2, s3) eq = + ((s1 `times` s2) `times` s3) `eq`+ (s1 `times` (s2 `times` s3))++-- (a + b) + c = a + (b + c)+associativePlus :: (Semiring s) => (s,s,s) -> Eql s -> Bool+associativePlus (s1, s2, s3) eq = + ((s1 `mappend` s2) `mappend` s3) `eq`+ (s1 `mappend` (s2 `mappend` s3))+++-- a + b = b + a+commutativePlus :: (Semiring s) => (s,s,s) -> Eql s -> Bool+commutativePlus (a, b, _) eq = + (a `mappend` b) `eq`+ (b `mappend` a)+++-- a * (b + c) = (a * b) + (a * c)+distribution :: (Semiring s) => (s,s,s) -> Eql s -> Bool+distribution (s1, s2, s3) eq = + (s1 `times` (s2 `mappend` s3)) `eq`+ ((s1 `times` s2) `mappend` (s1 `times` s3))+++-- a + 0 = 0 + a = a+zeroAdd :: (Semiring s) => (s,s,s) -> Eql s -> Bool+zeroAdd (a, _, _) eq = + (mempty `mappend` a) `eq` a &&+ (a `mappend` mempty) `eq` a+++-- a * 0 = 0+zeroMult :: (Semiring s) => (s,s,s) -> Eql s -> Bool+zeroMult (a, _, _) eq = + (mempty `times` a) `eq` mempty && + (a `times` mempty) `eq` mempty + +++oneMult :: (Semiring s) => (s,s,s) -> Eql s -> Bool+oneMult (a, _, _) eq = + (one `times` a) `eq` a &&+ (a `times` one) `eq` a++semiRingProps :: (Semiring s) => (s,s,s) -> Eql s -> Bool+semiRingProps s eq = and [distribution s eq, + associativePlus s eq, + zeroAdd s eq, + zeroMult s eq, + oneMult s eq, + commutativePlus s eq, + associativeTimes s eq]++doubEq a b = abs (a - b) < 0.000001 ++prop_probRing s1 s2 s3 = semiRingProps (s1, s2, s3) doubEq+ where types = ((s1,s2,s3 ):: (Prob, Prob, Prob))++prop_boolRing s1 s2 s3 = semiRingProps (s1, s2, s3) (==)+ where types = ((s1,s2,s3 ):: (Boolean, Boolean, Boolean))++prop_viterbiRing s1 s2 s3 = semiRingProps (s1, s2, s3) + (\(ViterbiNBest a) (ViterbiNBest b) -> and $ zipWith doubEq a b)+ where types = ((s1,s2,s3 ):: (Viterbi Prob, Viterbi Prob, Viterbi Prob))++prop_counting s1 s2 s3 = semiRingProps (s1, s2, s3) (==)+ where types = ((s1,s2,s3 ):: (Counting, Counting, Counting))+++prop_viterbiNBest s1 s2 s3 = semiRingProps (s1, s2, s3) (==)+ where types = ((s1,s2,s3 ):: (Viterbi10Best Counting, Viterbi10Best Counting, Viterbi10Best Counting))+++prop_derivation s1 s2 s3 = semiRingProps (s1, s2, s3) (==)+ where types = ((s1,s2,s3):: (Derivation String, Derivation String, Derivation String))++prop_multiDerivation s1 s2 s3 = semiRingProps (s1, s2, s3) (==)+ where types = ((s1,s2,s3):: (MultiDerivation String, MultiDerivation String, MultiDerivation String))++prop_nbestMultiDerivation s1 s2 s3 = + semiRingProps (s1, s2, s3) + (\(ViterbiNBest a) (ViterbiNBest b) ->+ and $ zipWith + (\(Weighted (a,b)) (Weighted (a',b')) -> + doubEq a a' && b == b') a b)+ where types = ((s1,s2,s3):: (ViterbiNBestDerivation Ten String, ViterbiNBestDerivation Ten String, ViterbiNBestDerivation Ten String))