estimators-0.1: NLP/Probability/ConditionalDistribution.hs
{-# LANGUAGE GeneralizedNewtypeDeriving, TypeFamilies, Rank2Types, FlexibleContexts #-}
module NLP.Probability.ConditionalDistribution (
-- * Conditional Distributions
--
-- $CondDistDesc
CondObserved(),
CondDistribution,
condObservation,
Context(..),
estimateGeneralLinear,
Weighting,
wittenBell,
simpleLinear
) where
import qualified Data.ListTrie.Base.Map as M
import Data.List (inits)
import Data.Monoid
import qualified NLP.Probability.SmoothTrie as ST
import NLP.Probability.Distribution
import NLP.Probability.Observation
import Data.Binary
-- $CondDistDesc
-- Say we want to estimate a conditional distribution based on a very large set of observed data.
-- Naively, we could just collect all the data and estimate a large table, but
-- our table would have little or no counts for a feasible future observations.
--
-- In practice, we use smoothing to supplement rare contexts with data from similar, more often seen contexts. For instance,
-- using bigram probabilities when the given trigrams observations are too sparse.
-- Most of these smoothing techniques are special cases of general linear interpolation, which chooses the weight of
-- each level of smoothing based on the sparsity of the current context.
--
-- In this module, we give an implementation of this process that separates out count collection
-- from the smoothing model, using a Trie. The user specifies a Context instance that relates the full conditional context
-- to a sequences of SubContexts that characterize the levels of smoothing and the transitions in the Trie. We also give a small set of smoothing techniques
-- to combine these levels.
--
-- This work is based on Chapter 6 of ''Foundations of Statistical Natural Language Processing''
-- by Chris Manning and Hinrich Schutze.
--
-- | The set of observations of event conditioned on context. event must be an instance of Event and context of Context
type CondObserved event context = (ST.SmoothTrie (SubMap context) (Sub context) (Counts event))
-- | Events are conditioned on Contexts. When Contexts are sparse, we need a way to decompose into simpler SubContexts.
-- This class allows us to separate this decomposition from the collection of larger contexts.
class (M.Map (SubMap a) (Sub a)) => Context a where
-- | The type of sub contexts
type Sub a
-- | A map over subcontexts (for efficiency)
type SubMap a :: * -> * -> *
-- | A function to enumerate subcontexts of a context
decompose :: a -> [Sub a]
-- | A CondObserved set for a single event and context.
condObservation :: (Context context, Event event) =>
event -> context -> CondObserved event context
condObservation event context =
ST.addColumn decomp observed mempty
where observed = observation event
decomp = decompose context
type CondDistribution event context = context -> Distribution event
type Weighting = forall a. [Maybe (Observed a)] -> [Double]
-- | General Linear Interpolation. Produces a Conditional Distribution from observations.
-- It requires a GeneralLambda function which tells it how to weight each level of smoothing.
-- The GeneralLambda function can observe the counts of each level of context.
--
-- Note: We include a final level of backoff where everything is given an epsilon likelihood. To
-- ignore this, just give it lambda = 0.
estimateGeneralLinear :: (Event event, Context context) =>
Weighting ->
CondObserved event context ->
CondDistribution event context
estimateGeneralLinear genLambda cstat = conFun
where
conFun context = (\event -> sum $ zipWith (*) lambdas $ map (probE event) stats)
where stats = reverse $
Nothing : (map (\k -> Just $ ST.lookupWithDefault (finish mempty) k cstat') $
tail $ inits $ decompose context)
probE event (Just dist) = if isNaN p then 0.0 else p
where p = mle dist event
probE event Nothing = 1e-19
lambdas = genLambda stats
cstat' = fmap finish cstat
-- | Weight each level by a fixed predefined amount.
simpleLinear :: [Double] -> Weighting
simpleLinear lambdas = const lambdas
lambdaWBC :: Int -> Observed b -> Double
lambdaWBC n eobs = total' / (((fromIntegral n) * distinct) + total')
where total' = total eobs
distinct = unique eobs
-- | Weight each level by the likelihood that a new event will be seen at that level.
-- t / ((n * d) + t) where t is the total count, d is the number of distinct observations,
-- and n is a user defined constant.
wittenBell :: Int -> Weighting
wittenBell n ls = wittenBell' ls 1.0
where
wittenBell' [Nothing] mult = [mult]
wittenBell' (Just cur:ls) mult =
if total cur > 0 then (l*mult : wittenBell' ls ((1-l)*mult))
else (0.0: wittenBell' ls mult)
where l = lambdaWBC n cur