///|
/// Interpolated absolute discount: seen estimates include lower-order mass;
/// absent events receive lambda times the suffix estimate through ARPA backoff.
pub fn Counts::train_discount(
self : Counts,
discount~ : Double = 0.75,
unigram_prior~ : Double = 0.5,
) -> Model raise ArpaError {
if !finite_number(discount) || discount <= 0.0 || discount >= 1.0 {
raise Invalid("discount must lie strictly between zero and one")
}
if !finite_number(unigram_prior) || unigram_prior <= 0.0 {
raise Invalid("unigram prior must be positive finite")
}
let (totals, distinct) = self.context_stats()
let vocabulary = self.vocabulary()
let denominator = self.observed.to_double() +
unigram_prior * (vocabulary.length() - 1).to_double()
if !finite_number(denominator) || denominator <= 0.0 {
raise Invalid("training denominator overflow")
}
let records : Array[Gram] = []
let lambda = fn(key : String) {
let total = totals.get(key).unwrap_or(0)
if total == 0 {
1.0
} else {
discount * distinct.get(key).unwrap_or(0).to_double() / total.to_double()
}
}
for entry in self.entries {
if entry.tokens.length() != 1 {
continue
}
let p = if entry.tokens[0] == "" {
0.0
} else {
(entry.count.to_double() + unigram_prior) / denominator
}
let backoff = if self.order == 1 {
None
} else {
Some(log_probability(lambda(gram_key(entry.tokens))))
}
records.push({
tokens: entry.tokens.copy(),
log_probability: log_probability(p),
log_backoff: backoff,
})
}
for n = 2; n <= self.order; n = n + 1 {
let lower = Model::new(self.order, records, limits=self.limits)
for entry in self.entries {
if entry.tokens.length() != n {
continue
}
let history = prefix(entry.tokens)
let key = gram_key(history)
let total = totals.get(key).unwrap_or(0)
if total <= 0 {
raise Invalid("missing context count")
}
let word = entry.tokens[n - 1]
let lower_log = lower.conditional(
suffix(history, n - 2),
word,
oov=Reject,
).log_probability
let lower_p = @math.pow(10.0, lower_log)
let p = (entry.count.to_double() - discount) / total.to_double() +
lambda(key) * lower_p
if !finite_number(p) || p <= 0.0 || p > 1.0 {
raise Invalid("invalid discounted probability")
}
let backoff = if n == self.order {
None
} else {
Some(log_probability(lambda(gram_key(entry.tokens))))
}
records.push({
tokens: entry.tokens.copy(),
log_probability: log_probability(p),
log_backoff: backoff,
})
}
}
Model::new(self.order, records, limits=self.limits)
}