API Reference¶
This page is generated automatically from the docstrings in the freqprob
package, so it always matches the installed code.
FreqProb: frequency-based probability estimation library.
ScoringMethod ¶
Bases: ABC
Abstract base class for frequency-based probability smoothing methods.
This class provides a unified interface for all probability estimation methods, supporting both regular probabilities and log-probabilities.
The general workflow is to initialize a method with a configuration, fit it
to a frequency distribution, and then score individual elements. Given a
frequency distribution mapping elements to their observed counts, each method
estimates a probability P(w) for every element w. For unobserved
elements, methods reserve probability mass to avoid returning zero.
Subclasses must implement :meth:_compute_probabilities, which populates the
internal probability table and the reserved mass for unobserved elements.
Attributes:
| Name | Type | Description |
|---|---|---|
config |
ScoringMethodConfig
|
Configuration parameters for the method. |
name |
str | None
|
Human-readable name of the method, or |
logprob |
bool | None
|
Whether this instance returns log-probabilities. |
Examples:
>>> from freqprob import MLE
>>> freqdist = {"apple": 3, "banana": 2, "cherry": 1}
>>> scorer = MLE(freqdist, logprob=False)
>>> scorer("apple") # most frequent item
0.5
>>> scorer("unknown") # unobserved item
0.0
Initialize the scoring method.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
ScoringMethodConfig
|
Configuration object containing the method parameters. |
required |
Note
This constructor should typically be called by subclass constructors, not directly by users.
fit ¶
Fit the scoring method to a frequency distribution.
This method trains the scorer on the provided frequency data, computing probability estimates for all observed elements. It resets any prior state so that re-fitting behaves like a fresh fit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqdist
|
FrequencyDistribution
|
Frequency distribution mapping elements to their observed counts. |
required |
Returns:
| Type | Description |
|---|---|
ScoringMethod
|
This scorer, to allow method chaining. |
Examples:
score ¶
Score a single element (scikit-learn-style alias for __call__).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
element
|
Element
|
The element to be scored. |
required |
Returns:
| Type | Description |
|---|---|
Probability | LogProbability
|
The probability (or log-probability) of the element. |
Examples:
predict ¶
Score many elements at once (scikit-learn-style batch alias).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
elements
|
Iterable[Element]
|
The elements to be scored. |
required |
Returns:
| Type | Description |
|---|---|
list[Probability | LogProbability]
|
One probability (or log-probability) per input element, in order. |
Examples:
save ¶
Serialize this fitted scorer to a file.
The scorer can be restored later with :meth:load, avoiding the cost of
re-fitting. Serialization uses :mod:pickle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
Destination file path. |
required |
Examples:
load
classmethod
¶
Load a scorer previously written with :meth:save.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
Path to a file created by :meth: |
required |
Returns:
| Type | Description |
|---|---|
T
|
The restored scorer. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If the file does not contain a compatible scorer. |
Note
Only load files you trust: unpickling executes arbitrary code from the file, so never load a scorer from an untrusted source.
Examples:
ELE ¶
Bases: Lidstone
Expected Likelihood Estimation probability distribution.
The special case of Lidstone smoothing with gamma = 0.5, corresponding
to the Jeffreys prior for multinomial distributions. Each observed element
gets (c_i + 0.5) / (N + 0.5 * B) and any unobserved element gets
0.5 / (N + 0.5 * B) — half a virtual observation per element. This is a
middle ground between MLE and Laplace: use it when add-one smoothing feels
too aggressive but you still want unseen elements handled.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqdist
|
FrequencyDistribution
|
Mapping of elements to observed counts. |
required |
bins
|
int | None
|
Total number of possible elements. If |
None
|
logprob
|
bool
|
Return log-probabilities if |
True
|
Examples:
Add half a count to each element, then normalize:
>>> from freqprob import ELE
>>> freqdist = {"cat": 4, "dog": 2}
>>> ele = ELE(freqdist, logprob=False)
>>> round(ele("cat"), 3) # (4+0.5)/(6+0.5*2) = 4.5/7
0.643
>>> round(ele("dog"), 3) # (2+0.5)/(6+0.5*2) = 2.5/7
0.357
>>> round(ele("bird"), 3) # 0.5/(6+0.5*2) = 0.5/7
0.071
Note
ELE applies less smoothing than Laplace (gamma = 1), so it stays
closer to the observed frequencies while still avoiding zero
probabilities.
Initialize Expected Likelihood Estimation.
Laplace ¶
Bases: Lidstone
Laplace (add-one) smoothing probability distribution.
The special case of Lidstone smoothing with gamma = 1.0, also called
"add-one smoothing." Each observed element gets (c_i + 1) / (N + B) and
any unobserved element gets 1 / (N + B) — i.e. one virtual observation
added to every possible element, corresponding to a uniform Dirichlet prior.
It is the most common additive smoother: reach for it when you want a simple,
reasonable default that never assigns zero probability.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqdist
|
FrequencyDistribution
|
Mapping of elements to observed counts. |
required |
bins
|
int | None
|
Total number of possible elements. If |
None
|
logprob
|
bool
|
Return log-probabilities if |
True
|
Examples:
Add one to every count, then normalize:
>>> from freqprob import Laplace
>>> freqdist = {"red": 3, "blue": 2, "green": 1}
>>> laplace = Laplace(freqdist, logprob=False)
>>> round(laplace("red"), 3) # (3+1)/(6+3) = 4/9
0.444
>>> round(laplace("blue"), 3) # (2+1)/(6+3) = 3/9
0.333
>>> round(laplace("yellow"), 3) # 1/(6+3) = 1/9
0.111
Initialize Laplace smoothing.
Lidstone ¶
Lidstone(freqdist: FrequencyDistribution, gamma: float, bins: int | None = None, logprob: bool = True)
Bases: ScoringMethod
Lidstone additive smoothing probability distribution.
Additive smoothing tackles the zero-probability problem by adding a virtual
count gamma to every possible element before normalizing. For counts
c_i, total N, and B bins, each observed element gets
(c_i + gamma) / (N + B * gamma) and any unobserved element gets
gamma / (N + B * gamma). This is the posterior mean under a symmetric
Dirichlet prior with concentration gamma. Use it when you need a simple,
principled way to keep unseen elements from scoring zero, and tune gamma
to trade off between staying close to the data (small gamma) and a more
uniform distribution (large gamma).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqdist
|
FrequencyDistribution
|
Mapping of elements to observed counts. |
required |
gamma
|
float
|
Additive smoothing parameter ( |
required |
bins
|
int | None
|
Total number of possible elements. If |
None
|
logprob
|
bool
|
Return log-probabilities if |
True
|
Examples:
Add-gamma smoothing gives unseen elements non-zero probability:
>>> from freqprob import Lidstone
>>> freqdist = {"apple": 3, "banana": 1}
>>> lidstone = Lidstone(freqdist, gamma=1.0, logprob=False)
>>> round(lidstone("apple"), 3) # (3+1)/(4+2*1) = 4/6
0.667
>>> round(lidstone("banana"), 3) # (1+1)/(4+2*1) = 2/6
0.333
>>> round(lidstone("cherry"), 3) # 1/(4+2*1) = 1/6
0.167
A larger gamma pulls probabilities toward uniform:
>>> smooth = Lidstone(freqdist, gamma=2.0, logprob=False)
>>> smooth("apple") # (3+2)/(4+2*2) = 5/8
0.625
>>> smooth("cherry") # 2/(4+2*2) = 2/8
0.25
A larger bins reserves more mass for potential unseen elements:
>>> lidstone_big = Lidstone(freqdist, gamma=1.0, bins=1000, logprob=False)
>>> round(lidstone_big("apple"), 4) # (3+1)/(4+1000) = 4/1004
0.004
>>> round(lidstone_big("unseen"), 4) # 1/(4+1000) = 1/1004
0.001
Note
The choice of gamma is a bias-variance tradeoff: small values stay
close to MLE (low bias, high variance) while large values approach a
uniform distribution (higher bias, lower variance).
Initialize Lidstone smoothing.
MLE ¶
Bases: ScoringMethod
Maximum Likelihood Estimation.
Estimates each element's probability as its relative frequency in the
observed data — the most intuitive baseline. For counts c_i with total
N, each observed element gets (1 - p0) * c_i / N and any unobserved
element gets the reserved mass p0 (0.0 by default, i.e. unseen
elements score zero).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqdist
|
FrequencyDistribution
|
Mapping of elements to observed counts. |
required |
unobs_prob
|
Probability
|
Reserved probability mass |
0.0
|
logprob
|
bool
|
Return log-probabilities if |
True
|
Examples:
Probabilities are relative frequencies; unseen elements score zero:
>>> mle = MLE({"apple": 6, "banana": 3, "cherry": 1}, logprob=False)
>>> mle("apple")
0.6
>>> mle("banana")
0.3
>>> mle("unknown")
0.0
Reserving mass for unseen elements rescales the observed ones:
>>> smoothed = MLE({"apple": 6, "banana": 3, "cherry": 1},
... unobs_prob=0.1, logprob=False)
>>> round(smoothed("apple"), 3)
0.54
>>> smoothed("unknown")
0.1
Note
MLE underlies most smoothing methods but assigns zero probability to
unseen events, which is problematic for sparse data. Prefer Laplace,
Lidstone, or a more advanced method when unseen elements are likely.
Initialize MLE distribution.
Random ¶
Random(freqdist: FrequencyDistribution, unobs_prob: Probability = 0.0, logprob: bool = True, seed: int | None = None)
Bases: ScoringMethod
Random probability distribution.
Assigns pseudo-random probabilities, useful for testing and as a randomized
baseline. Each element is given a random count drawn from the observed count
range, and those counts are then normalized like MLE. A fixed seed
makes the result reproducible.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqdist
|
FrequencyDistribution
|
Mapping of elements to observed counts. Only the min/max counts are used, to bound the random counts. |
required |
unobs_prob
|
Probability
|
Reserved probability mass for unobserved elements.
Defaults to |
0.0
|
logprob
|
bool
|
Return log-probabilities if |
True
|
seed
|
int | None
|
Seed for the random number generator, for reproducible results.
Defaults to |
None
|
Examples:
The probabilities are random but form a valid distribution, and a fixed seed is reproducible:
>>> freqdist = {"apple": 5, "banana": 2, "cherry": 8}
>>> a = Random(freqdist, seed=123, logprob=False)
>>> b = Random(freqdist, seed=123, logprob=False)
>>> a("apple") == b("apple")
True
>>> round(sum(a(e) for e in freqdist), 6)
1.0
Note
Primarily for testing, debugging, and providing a randomized baseline for comparison. It is not a smoothing method in any meaningful sense.
Initialize Random distribution.
Uniform ¶
Bases: ScoringMethod
Uniform probability distribution.
The simplest baseline: assign equal probability to every observed element, ignoring the counts entirely. Useful as a non-informative prior or as a reference point when comparing smoothing methods.
For a vocabulary of size V with reserved mass p0, each observed
element gets (1 - p0) / V and any unobserved element gets p0.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqdist
|
FrequencyDistribution
|
Mapping of elements to observed counts. The counts are ignored; only the number of distinct elements matters. |
required |
unobs_prob
|
Probability
|
Reserved probability mass |
0.0
|
logprob
|
bool
|
Return log-probabilities if |
True
|
Examples:
Counts are ignored, so both observed elements score the same:
>>> uniform = Uniform({"apple": 10, "banana": 1}, unobs_prob=0.1, logprob=False)
>>> uniform("apple")
0.45
>>> uniform("banana")
0.45
>>> uniform("cherry") # unobserved
0.1
Note
Because it discards frequency information, Uniform is meant as a
baseline. Use MLE, Lidstone, or Laplace when counts should matter.
Initialize Uniform distribution.
Bayesian ¶
Bases: ScoringMethod
Bayesian smoothing with a Dirichlet prior.
Estimates probabilities as the posterior mean under a symmetric Dirichlet
prior, adding alpha pseudocounts to each possible outcome. Each observed
element gets (c_i + alpha) / (N + V * alpha) and any unobserved element
gets alpha / (N + V * alpha), where N is the total count and V
the support size. This is numerically identical to Lidstone smoothing with
gamma = alpha, but framed so that alpha reads as prior belief: use it
when you want a theoretically principled smoother whose strength you can tune
from near-MLE (small alpha) to strongly uniform (large alpha).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqdist
|
FrequencyDistribution
|
Mapping of elements to observed counts. |
required |
alpha
|
float
|
Dirichlet concentration parameter ( |
1.0
|
logprob
|
bool
|
Return log-probabilities if |
True
|
Examples:
Bayesian smoothing with a uniform prior (alpha = 1):
>>> from freqprob import Bayesian
>>> freqdist = {"apple": 8, "banana": 4, "cherry": 1}
>>> bayes = Bayesian(freqdist, alpha=1.0, logprob=False)
>>> bayes("apple") # (8+1)/(13+3*1) = 9/16
0.5625
>>> bayes("banana") # (4+1)/(13+3*1) = 5/16
0.3125
>>> bayes("unseen") # 1/(13+3*1) = 1/16
0.0625
A larger alpha smooths more strongly toward uniform:
>>> bayes_smooth = Bayesian(freqdist, alpha=2.0, logprob=False)
>>> round(bayes_smooth("apple"), 3) # (8+2)/(13+3*2) = 10/19
0.526
>>> round(bayes_smooth("unseen"), 3) # 2/(13+3*2) = 2/19
0.105
A small alpha stays closer to the observed frequencies:
>>> bayes_minimal = Bayesian(freqdist, alpha=0.1, logprob=False)
>>> round(bayes_minimal("apple"), 3) # (8+0.1)/(13+3*0.1) = 8.1/13.3
0.609
Note
A positive alpha is required; passing alpha <= 0 raises
ValueError. Equivalent to Lidstone with gamma = alpha.
Initialize Bayesian smoothing.
CertaintyDegree ¶
CertaintyDegree(freqdist: FrequencyDistribution, bins: int | None = None, unobs_prob: Probability = 0.0, logprob: bool = True)
Bases: ScoringMethod
Certainty-degree smoothing.
Reserves probability mass for unseen elements based on the degree of certainty that no unobserved samples remain, derived from the number of observations relative to the support size. As more of the possible bins are observed, the reserved mass shrinks; the remaining mass rescales the Maximum-Likelihood estimate of each observed element.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqdist
|
FrequencyDistribution
|
Mapping of elements to observed counts. |
required |
bins
|
int | None
|
Optional total number of possible sample bins for the experiment.
If |
None
|
unobs_prob
|
Probability
|
Reserved probability mass for unobserved states
( |
0.0
|
logprob
|
bool
|
Return log-probabilities if |
True
|
Examples:
Observed elements are rescaled by the computed certainty mass, and the remainder is reserved for unseen elements:
>>> from freqprob import CertaintyDegree
>>> freqdist = {"apple": 8, "banana": 4, "cherry": 2, "date": 1}
>>> cd = CertaintyDegree(freqdist, logprob=False)
>>> round(cd("apple"), 3)
0.515
>>> round(cd("date"), 3)
0.064
>>> round(cd("kiwi"), 3) # unobserved
0.035
Note
This is an experimental distribution under development by Tiago Tresoldi; it should not be used as the sole or main distribution yet.
Initialize Certainty Degree estimation.
SimpleGoodTuring ¶
SimpleGoodTuring(freqdist: FrequencyDistribution, p_value: float = 0.05, default_p0: float | None = None, bins: int | None = None, logprob: bool = True, allow_fail: bool = True)
Bases: ScoringMethod
Simple Good-Turing smoothing.
Reestimates counts using the frequency of frequencies (how many elements were seen once, twice, and so on), fitting a log-linear model to smooth the tail so that mass can be reserved for unseen elements. This is the practical Gale-Sampson formulation of the Good-Turing method originally developed by Turing and Good, and is a strong general-purpose smoother when the data show a spread of low-frequency counts (many singletons, fewer doubletons, etc.).
This implementation follows those by "maxbane" (2011, https://github.com/maxbane/simplegoodturing/blob/master/sgt.py), Geoffrey Sampson's original C code (1995-2008, https://www.grsampson.net/Resources.html), and the NLTK project (Loper, Bird et al., 2001-2018). Because of minor differences intended to guarantee non-zero probabilities under expected underflow, the reliance on scipy, and the handling of probabilities that are not computable when SGT assumptions are not met, most results will not exactly match the Gale-Sampson "gold standard", though the differences are never expected to be significant.
Note
Since v0.4.0, unseen queries return the per-word probability
p0 / (bins - V) rather than the total mass p0, making the method
compatible with perplexity calculations and consistent with the other
smoothers. Use the :attr:total_unseen_mass property to recover p0
(the total mass for all unseen words combined).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqdist
|
FrequencyDistribution
|
Mapping of elements to observed counts. |
required |
p_value
|
float
|
The p-value for the confidence interval of the empirical Turing
estimate, guiding the choice between the Turing estimate |
0.05
|
default_p0
|
float | None
|
Optional probability for unobserved samples ( |
None
|
bins
|
int | None
|
Total support size for computing per-word unseen probabilities. If
|
None
|
logprob
|
bool
|
Return log-probabilities if |
True
|
allow_fail
|
bool
|
If |
True
|
Examples:
SGT needs a spread of low-frequency counts, so use a Zipfian-ish distribution with several singletons. Observed elements are discounted and unseen elements receive a share of the reserved mass:
>>> from freqprob import SimpleGoodTuring
>>> freqdist = {"a": 10, "b": 5, "c": 4, "d": 3, "e": 2,
... "f": 2, "g": 1, "h": 1, "i": 1, "j": 1}
>>> sgt = SimpleGoodTuring(freqdist, logprob=False)
>>> round(float(sgt("a")), 3)
0.307
>>> round(float(sgt("g")), 3) # a singleton
0.025
>>> round(sgt("unknown"), 3) # per-word unseen probability
0.033
>>> round(sgt.total_unseen_mass, 3) # total mass for all unseen words
0.133
Initialize Simple Good-Turing smoothing.
total_unseen_mass
property
¶
Total probability mass reserved for ALL unseen words (p0).
This is the value that Good-Turing actually estimates. The per-word
unseen probability returned by __call__() is this value divided by
the estimated number of unseen types (bins - V_observed).
Returns:
| Type | Description |
|---|---|
float
|
Total probability mass for all unseen words combined. |
Examples:
>>> from freqprob import SimpleGoodTuring
>>> freqdist = {"a": 10, "b": 5, "c": 1, "d": 1, "e": 1}
>>> sgt = SimpleGoodTuring(freqdist, logprob=False)
>>> round(sgt.total_unseen_mass, 3) # total mass for all unseen words
0.167
>>> round(sgt("unknown"), 3) # per-word probability for one unseen word
0.056
WittenBell ¶
Bases: ScoringMethod
Witten-Bell smoothing.
Reserves probability mass for unseen elements based on how many distinct
types were observed, treating each new type as evidence that more unseen
types exist. The reserved mass is T / (N + T), where T is the number
of observed types and N the total number of observations; the remaining
mass is discounted proportionally so all estimates sum to one:
p = T / (Z * (N + T))for an unobserved element, andp = c / (N + T)for an element observedctimes.
Useful as a lightweight discounting method when a simple, parameter-free way to handle unseen elements is needed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqdist
|
FrequencyDistribution
|
Mapping of elements to observed counts. |
required |
bins
|
int | None
|
Optional total number of possible sample bins for the experiment.
If |
None
|
logprob
|
bool
|
Return log-probabilities if |
True
|
Examples:
Observed elements are scored by relative frequency over N + T, and
unseen elements share the reserved mass:
>>> from freqprob import WittenBell
>>> freqdist = {"the": 100, "a": 50, "cat": 3, "dog": 2, "rare": 1}
>>> wb = WittenBell(freqdist, logprob=False)
>>> round(wb("the"), 3)
0.621
>>> round(wb("rare"), 3)
0.006
>>> round(wb("unicorn"), 3) # unobserved
0.031
Initialize Witten-Bell smoothing.
Interpolated ¶
Interpolated(high_order_dist: FrequencyDistribution, low_order_dist: FrequencyDistribution, lambda_weight: float = 0.7, logprob: bool = True)
Bases: ScoringMethod
Linear interpolation smoothing between two frequency distributions.
Blends a higher-order distribution with a lower-order one using a weighted
average, lambda_weight * P_high + (1 - lambda_weight) * P_low. This
keeps the specificity of the high-order model while borrowing robustness
from the smoother low-order model. Two modes are detected automatically
from the keys:
- N-gram interpolation, when the high-order keys are longer tuples than the
low-order keys. The lower-order context is taken as the suffix of each
high-order n-gram (e.g. bigram
(w2, w3)from trigram(w1, w2, w3)). - Same-type interpolation, when both distributions share the same key type (strings or equal-length tuples). Keys are matched directly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
high_order_dist
|
FrequencyDistribution
|
Higher-order frequency distribution mapping elements to
counts (e.g. trigrams). For n-gram mode its tuple keys must be at
least as long as those of |
required |
low_order_dist
|
FrequencyDistribution
|
Lower-order frequency distribution (e.g. bigrams). |
required |
lambda_weight
|
float
|
Interpolation weight |
0.7
|
logprob
|
bool
|
Return log-probabilities if |
True
|
Examples:
N-gram interpolation (trigrams backed off to bigrams). The score of
('the', 'big', 'cat') is 0.7 * (3/5) + 0.3 * (5/10), and an
unseen trigram backs off to 0.3 * (5/10):
>>> trigrams = {("the", "big", "cat"): 3, ("a", "big", "dog"): 2}
>>> bigrams = {("big", "cat"): 5, ("big", "dog"): 3, ("small", "cat"): 2}
>>> interp = Interpolated(trigrams, bigrams, lambda_weight=0.7, logprob=False)
>>> round(interp(("the", "big", "cat")), 3)
0.57
>>> round(interp(("unseen", "big", "cat")), 3)
0.15
Same-type interpolation between two string distributions. The score of
"word1" is 0.6 * (10/15) + 0.4 * (3/10):
>>> model1 = {"word1": 10, "word2": 5}
>>> model2 = {"word1": 3, "word3": 7}
>>> interp = Interpolated(model1, model2, lambda_weight=0.6, logprob=False)
>>> round(interp("word1"), 3)
0.52
>>> round(interp("word2"), 3)
0.2
>>> round(interp("word3"), 3)
0.28
Note
The mode is inferred from the key types, so mixed tuple lengths within a
single distribution raise an error. Unseen high-order n-grams back off
to the low-order model, and all probabilities are floored at 1e-10
for numerical stability. The weight lambda_weight is best tuned on
held-out data.
Initialize Interpolated smoothing.
KneserNey ¶
Bases: ScoringMethod
Kneser-Ney smoothing probability distribution.
One of the most effective smoothing methods for language modeling. It
combines absolute discounting with interpolation and, crucially, weights a
word by the diversity of contexts it appears in (its continuation
probability) rather than its raw frequency. Each observed bigram scores
max(c - d, 0) / c(context) + backoff(context) * P_cont(word), and
unseen bigrams fall back to the average continuation probability.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqdist
|
FrequencyDistribution
|
Frequency distribution mapping bigrams to their observed
counts, in the form |
required |
discount
|
float
|
Absolute discounting parameter |
0.75
|
logprob
|
bool
|
Return log-probabilities if |
True
|
Examples:
Observed bigrams are discounted and interpolated with the continuation model, while unseen bigrams back off to the average continuation probability:
>>> bigram_counts = {
... ("the", "cat"): 5, ("the", "dog"): 3, ("a", "cat"): 2,
... ("a", "dog"): 1, ("big", "cat"): 1, ("small", "dog"): 1,
... }
>>> kn = KneserNey(bigram_counts, discount=0.75, logprob=False)
>>> round(kn(("the", "cat")), 3)
0.625
>>> round(kn(("the", "mouse")), 3)
0.5
>>> round(kn(("new_context", "cat")), 3)
0.5
Note
This implementation assumes bigram input. The discount d is
typically between 0.5 and 0.8; 0.75 is a robust default.
Reliable continuation probabilities require sufficient bigram data.
Initialize Kneser-Ney smoothing.
ModifiedKneserNey ¶
Bases: ScoringMethod
Modified Kneser-Ney smoothing probability distribution.
An enhanced Kneser-Ney variant that applies a different discount depending
on a bigram's count: d1 for singletons, d2 for doubletons, and
d3 for counts of three or more. Those discounts are estimated
automatically from the data's count-of-counts statistics, which usually
makes it more accurate than the fixed-discount version.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqdist
|
FrequencyDistribution
|
Frequency distribution mapping bigrams to their observed
counts, in the form |
required |
logprob
|
bool
|
Return log-probabilities if |
True
|
Examples:
The discount adapts to each bigram's count, and the score interpolates the discounted estimate with the continuation model:
>>> bigram_counts = {
... ("the", "cat"): 5, ("the", "dog"): 3, ("a", "cat"): 2,
... ("a", "dog"): 1, ("big", "cat"): 1, ("small", "dog"): 1,
... }
>>> mkn = ModifiedKneserNey(bigram_counts, logprob=False)
>>> round(mkn(("the", "cat")), 3)
0.625
>>> round(mkn(("a", "cat")), 3)
0.558
Note
Modified Kneser-Ney is a standard, state-of-the-art classical smoothing method for n-gram language models. Because it estimates its discounts from the data, it is more robust than fixed-discount methods but needs enough varied counts to estimate them reliably.
Initialize Modified Kneser-Ney smoothing.
LazyBatchScorer ¶
Batch scorer with lazy evaluation and intelligent caching.
This scorer optimizes batch operations by:
- Using lazy evaluation to avoid unnecessary computations.
- Intelligently ordering computations based on access patterns.
- Providing memory-efficient operations for large datasets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lazy_scorer
|
LazyScoringMethod
|
Lazy scoring method to wrap. |
required |
Examples:
Wrap a lazy MLE scorer and score a batch of elements:
>>> from freqprob import create_lazy_mle
>>> lazy_scorer = create_lazy_mle({"a": 3, "b": 2, "c": 1}, logprob=False)
>>> batch_scorer = LazyBatchScorer(lazy_scorer)
>>> [round(float(x), 4) for x in batch_scorer.score_batch(["a", "c"])]
[0.5, 0.1667]
>>> stats = batch_scorer.get_access_statistics()
>>> stats["total_accesses"]
2
>>> stats["unique_elements"]
2
Initialize lazy batch scorer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lazy_scorer
|
LazyScoringMethod
|
Lazy scoring method to wrap. |
required |
score_batch ¶
Score a batch of elements with lazy evaluation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
elements
|
list[Element]
|
Elements to score. |
required |
Returns:
| Type | Description |
|---|---|
list[float]
|
Scores for the elements, in the same order as the input. |
score_streaming ¶
Score elements from a stream with adaptive lazy evaluation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
element_stream
|
Iterable[Element]
|
Stream of elements to score. |
required |
Yields:
| Type | Description |
|---|---|
float
|
Score for each element in the stream. |
get_access_statistics ¶
Get statistics about element access patterns.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Access statistics including total accesses, unique elements, and |
dict[str, Any]
|
the most frequently accessed element. |
LazyScoringMethod ¶
Bases: ScoringMethod
Scoring method that defers probability computation until requested.
Wraps a lazy computation strategy so that probabilities are calculated only for the elements actually scored, rather than for the whole vocabulary up front. This can significantly improve performance when a large distribution is fitted but only a small subset of elements is ever queried.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lazy_computer
|
LazyProbabilityComputer
|
Strategy object that computes a single element's probability on demand. |
required |
config
|
ScoringMethodConfig
|
Configuration controlling |
required |
name
|
str
|
Human-readable name for the scoring method. |
required |
Note
Instances are normally built through the public factory functions
create_lazy_mle and create_lazy_laplace rather than constructed
directly, since the computer and config types are internal.
Examples:
Probabilities are computed only when an element is first scored:
>>> from freqprob import create_lazy_mle
>>> scorer = create_lazy_mle({"a": 3, "b": 2, "c": 1}, logprob=False)
>>> scorer("a")
0.5
>>> sorted(scorer.get_computed_elements())
['a']
>>> scorer("b")
0.3333333333333333
>>> sorted(scorer.get_computed_elements())
['a', 'b']
>>> scorer("unseen") # unobserved element
0.0
Initialize lazy scoring method.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lazy_computer
|
LazyProbabilityComputer
|
Strategy for lazy computation. |
required |
config
|
ScoringMethodConfig
|
Configuration parameters. |
required |
name
|
str
|
Name of the scoring method. |
required |
precompute_batch ¶
Precompute probabilities for a batch of elements.
This can be useful when you know you'll need several elements and want to compute them all at once for efficiency.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
elements
|
set[Element]
|
Elements to precompute. |
required |
get_computed_elements ¶
Get the set of elements that have been computed so far.
Returns:
| Type | Description |
|---|---|
set[Element]
|
Set of elements whose probabilities have been computed. |
force_full_computation ¶
Force computation of all probabilities in the distribution.
This converts the lazy scorer to a regular scorer by computing all probabilities immediately.
CompressedFrequencyDistribution ¶
CompressedFrequencyDistribution(quantization_levels: int | None = None, use_compression: bool = True, intern_strings: bool = True)
Memory-efficient compressed frequency distribution.
This class uses various compression techniques to reduce memory usage for large vocabularies while maintaining fast access patterns.
Techniques used:
- Integer compression for counts.
- String interning for element storage.
- Sparse representation for zero counts.
- Optional quantization for approximate counts.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
quantization_levels
|
int | None
|
Number of quantization levels for count
compression, or |
None
|
use_compression
|
bool
|
Whether to use data compression when serializing.
Defaults to |
True
|
intern_strings
|
bool
|
Whether to intern string elements for memory
efficiency. Defaults to |
True
|
Examples:
Exact counts are preserved when quantization is disabled:
>>> compressed_dist = CompressedFrequencyDistribution()
>>> compressed_dist.update({"word1": 1000, "word2": 500, "word3": 1})
>>> compressed_dist.get_count("word1")
1000
>>> compressed_dist.get_vocabulary_size()
3
>>> compressed_dist.get_total_count()
1501
>>> sorted(compressed_dist.to_dict().items())
[('word1', 1000), ('word2', 500), ('word3', 1)]
Initialize compressed frequency distribution.
update ¶
Update with a frequency distribution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqdist
|
FrequencyDistribution
|
Frequency distribution to add. |
required |
get_count ¶
Get count for an element.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
element
|
Element
|
Element to query. |
required |
Returns:
| Type | Description |
|---|---|
int
|
Count for the element (dequantized if quantization is used). |
get_memory_usage ¶
Get detailed memory usage information.
Returns:
| Type | Description |
|---|---|
dict[str, int]
|
Memory usage breakdown in bytes, keyed by component, including a |
dict[str, int]
|
|
compress_to_bytes ¶
Compress the entire distribution to bytes.
Returns:
| Type | Description |
|---|---|
bytes
|
Serialized (and optionally gzip-compressed) representation. |
decompress_from_bytes
classmethod
¶
decompress_from_bytes(compressed_data: bytes, use_compression: bool = True) -> CompressedFrequencyDistribution
Decompress distribution from bytes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
compressed_data
|
bytes
|
Compressed data to restore. |
required |
use_compression
|
bool
|
Whether the data was gzip-compressed. Defaults to
|
True
|
Returns:
| Type | Description |
|---|---|
CompressedFrequencyDistribution
|
The reconstructed distribution. |
QuantizedProbabilityTable ¶
Quantized probability table for memory-efficient probability storage.
This class stores probabilities using quantization to reduce memory usage while maintaining reasonable precision for most applications.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_quantization_levels
|
int
|
Number of quantization levels, which
determines precision. Defaults to |
65536
|
log_space
|
bool
|
Whether to quantize in log space for better precision.
Defaults to |
True
|
Examples:
Stored probabilities are approximate; higher level counts are more precise:
>>> prob_table = QuantizedProbabilityTable(num_quantization_levels=1024)
>>> prob_table.set_probabilities(
... {"word1": 0.5, "word2": 0.3, "word3": 0.2}
... )
>>> round(prob_table.get_probability("word1"), 2)
0.49
>>> sorted(prob_table.get_elements())
['word1', 'word2', 'word3']
Initialize quantized probability table.
set_probabilities ¶
Set probabilities for multiple elements.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
probabilities
|
dict[Element, float]
|
Mapping of elements to their probabilities. |
required |
set_probability ¶
Set probability for a single element.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
element
|
Element
|
Element to set probability for. |
required |
probability
|
float
|
Probability value. |
required |
get_probability ¶
Get probability for an element.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
element
|
Element
|
Element to query. |
required |
Returns:
| Type | Description |
|---|---|
float
|
Dequantized probability for the element (or the default). |
set_default_probability ¶
Set default probability for unobserved elements.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
default_prob
|
float
|
Default probability for unobserved elements. |
required |
get_memory_usage ¶
Get memory usage information.
Returns:
| Type | Description |
|---|---|
dict[str, int]
|
Memory usage breakdown in bytes, including a |
get_quantization_error_stats ¶
Analyze quantization error compared to original probabilities.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
original_probs
|
dict[Element, float]
|
Original probabilities to compare against. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, float]
|
Error statistics including mean/max absolute and relative errors. |
SparseFrequencyDistribution ¶
Sparse representation for frequency distributions with many zero counts.
This class is optimized for distributions where most elements have zero counts, using sparse data structures for memory efficiency.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
default_count
|
int
|
Default count for unobserved elements. Defaults to
|
0
|
use_sorted_storage
|
bool
|
Whether to keep elements sorted by frequency for
faster top-k access. Defaults to |
True
|
Examples:
Only non-default counts are stored; missing elements return the default:
>>> sparse_dist = SparseFrequencyDistribution()
>>> sparse_dist.update({"rare_word": 1, "common_word": 1000})
>>> sparse_dist.get_count("rare_word")
1
>>> sparse_dist.get_count("unseen")
0
>>> sparse_dist.get_vocabulary_size()
2
>>> sparse_dist.get_top_k(2)
[('common_word', 1000), ('rare_word', 1)]
Initialize sparse frequency distribution.
update ¶
Update with a frequency distribution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqdist
|
FrequencyDistribution
|
Frequency distribution to add. |
required |
increment ¶
Increment count for a single element.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
element
|
Element
|
Element to increment. |
required |
count
|
int
|
Count increment. Defaults to |
1
|
get_count ¶
Get count for an element.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
element
|
Element
|
Element to query. |
required |
Returns:
| Type | Description |
|---|---|
int
|
Count for the element, or the default count if not present. |
get_top_k ¶
Get top-k most frequent elements.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
k
|
int
|
Number of top elements to return. |
required |
Returns:
| Type | Description |
|---|---|
list[tuple[Element, int]]
|
List of |
get_elements_with_count_range ¶
Get elements with counts in a specific range.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
min_count
|
int
|
Minimum count (inclusive). |
required |
max_count
|
int
|
Maximum count (inclusive). |
required |
Returns:
| Type | Description |
|---|---|
list[Element]
|
Elements whose counts fall within the specified range. |
get_count_histogram ¶
Get histogram of counts (count -> frequency of that count).
Returns:
| Type | Description |
|---|---|
dict[int, int]
|
Histogram mapping each count value to how many elements have it. |
items ¶
Iterate over (element, count) pairs for non-default elements.
get_memory_usage ¶
Get memory usage information.
Returns:
| Type | Description |
|---|---|
dict[str, int]
|
Memory usage breakdown in bytes, including a |
DistributionMemoryAnalyzer ¶
Analyzer for comparing memory usage of different distribution representations.
This class helps analyze and compare memory usage between regular dictionaries, compressed distributions, sparse distributions, and other representations.
Note
compare_representations additionally contrasts compressed and sparse
encodings, returning per-representation savings.
Examples:
Measure the memory footprint of a small distribution:
>>> analyzer = DistributionMemoryAnalyzer()
>>> freqdist = {"word1": 1000, "word2": 500, "word3": 1}
>>> measurement = analyzer.measure_distribution_memory(freqdist)
>>> sorted(measurement.keys())
['bytes_per_element', 'num_elements', 'total_mb']
>>> measurement["num_elements"]
3
Initialize distribution memory analyzer.
measure_distribution_memory ¶
Measure memory usage of a frequency distribution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqdist
|
FrequencyDistribution
|
Frequency distribution to measure. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, float]
|
Memory usage measurements, including total MB, element count, and |
dict[str, float]
|
bytes per element. |
compare_representations ¶
Compare memory usage of different distribution representations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqdist
|
FrequencyDistribution
|
Original frequency distribution. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Comparison results with per-representation memory usage, savings, |
dict[str, Any]
|
and profiling metrics. |
benchmark_scoring_methods ¶
benchmark_scoring_methods(freqdist: FrequencyDistribution, test_elements: list[str], methods_to_test: list[str] | None = None) -> dict[str, Any]
Benchmark memory usage and performance of different scoring methods.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqdist
|
FrequencyDistribution
|
Frequency distribution for testing. |
required |
test_elements
|
list[str]
|
Elements to score for performance testing. |
required |
methods_to_test
|
list[str] | None
|
Scoring methods to test, or |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Benchmark results keyed by method name. |
MemoryMonitor ¶
Continuous memory monitor for long-running processes.
This class provides continuous monitoring of memory usage patterns and can trigger alerts when memory usage exceeds thresholds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
memory_threshold_mb
|
float
|
Memory threshold in MB for triggering alerts.
Defaults to |
1000.0
|
monitoring_interval
|
float
|
Monitoring interval in seconds. Defaults to
|
5.0
|
Examples:
Take a memory reading against a high threshold (no alert triggered):
>>> monitor = MemoryMonitor(memory_threshold_mb=1e9)
>>> monitor.check_memory() is None
True
>>> report = monitor.get_monitoring_report()
>>> report["threshold_violations"]
0
>>> report["total_snapshots"]
1
Initialize memory monitor.
check_memory ¶
Check current memory usage and trigger alerts if needed.
Returns:
| Type | Description |
|---|---|
dict[str, Any] | None
|
Alert information if the threshold was exceeded, otherwise |
dict[str, Any] | None
|
|
get_monitoring_report ¶
Get a comprehensive monitoring report.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Monitoring report with memory statistics, alerts, and trend. |
MemoryProfiler ¶
Memory profiler for analyzing memory usage patterns.
This class provides utilities for monitoring memory usage during various operations and analyzing memory efficiency.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enable_tracemalloc
|
bool
|
Whether to enable detailed Python memory tracing.
Defaults to |
True
|
snapshot_interval
|
float
|
Interval in seconds for automatic snapshots.
Defaults to |
1.0
|
Examples:
Profile an operation and inspect the recorded metrics:
>>> profiler = MemoryProfiler()
>>> with profiler.profile_operation("build_list"):
... data = [i * i for i in range(10000)]
>>> metrics = profiler.get_latest_metrics()
>>> metrics.operation_name
'build_list'
>>> isinstance(metrics.execution_time, float)
True
>>> isinstance(metrics.memory_delta_mb, float)
True
>>> len(profiler.get_all_metrics())
1
Initialize memory profiler.
take_snapshot ¶
Take a memory usage snapshot.
Returns:
| Type | Description |
|---|---|
MemorySnapshot
|
The current memory usage snapshot. |
profile_operation ¶
Context manager for profiling an operation.
Uses time.perf_counter() for high-precision timing measurements,
ensuring accurate execution time tracking even for fast operations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operation_name
|
str
|
Name of the operation being profiled. |
required |
Yields:
| Type | Description |
|---|---|
None
|
|
get_latest_metrics ¶
Get the latest performance metrics.
Returns:
| Type | Description |
|---|---|
PerformanceMetrics | None
|
The most recent metrics, or |
PerformanceMetrics | None
|
profiled. |
get_all_metrics ¶
Get all performance metrics.
Returns:
| Type | Description |
|---|---|
list[PerformanceMetrics]
|
A copy of all recorded metrics. |
get_snapshots ¶
Get all memory snapshots.
Returns:
| Type | Description |
|---|---|
list[MemorySnapshot]
|
A copy of all recorded snapshots. |
get_memory_summary ¶
Get a summary of memory usage patterns.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Memory usage summary aggregating the recorded snapshots. |
StreamingDataProcessor ¶
StreamingDataProcessor(scoring_methods: dict[str, IncrementalScoringMethod], batch_size: int = 1000, auto_save_interval: int | None = None)
High-level processor for streaming text data.
This class provides utilities for processing streaming text data and maintaining multiple frequency distributions efficiently.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scoring_methods
|
dict[str, IncrementalScoringMethod]
|
Mapping of names to scoring methods to maintain. |
required |
batch_size
|
int
|
Batch size for processing text streams. Defaults to
|
1000
|
auto_save_interval
|
int | None
|
Interval for automatic state saving, or |
None
|
Examples:
Process a batch through several methods and query one of them:
>>> methods = {
... "mle": StreamingMLE(logprob=False),
... "laplace": StreamingLaplace(logprob=False),
... }
>>> processor = StreamingDataProcessor(methods)
>>> processor.process_batch(["word1", "word2", "word1"])
>>> round(processor.get_score("mle", "word1"), 4)
0.6667
>>> sorted(processor.get_statistics().keys())
['methods', 'processed_count']
Initialize streaming data processor.
process_element ¶
Process a single element.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
element
|
Element
|
Element to process. |
required |
count
|
int
|
Count for the element. Defaults to |
1
|
process_batch ¶
Process a batch of elements.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
elements
|
list[Element]
|
Elements to process. |
required |
counts
|
list[int] | None
|
Count for each element, or |
None
|
process_text_stream ¶
Process a stream of text tokens.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text_stream
|
Iterator[str]
|
Stream of text tokens. |
required |
get_score ¶
Get score for an element from a specific method.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
method_name
|
str
|
Name of the scoring method. |
required |
element
|
Element
|
Element to score. |
required |
Returns:
| Type | Description |
|---|---|
float
|
Score for the element from the named method. |
get_statistics ¶
Get statistics for all scoring methods.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Statistics for each method, keyed by method name. |
save_all_states ¶
Save states of all scoring methods.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_filename
|
str
|
Base filename for saving states. |
required |
StreamingFrequencyDistribution ¶
StreamingFrequencyDistribution(max_vocabulary_size: int | None = None, min_count_threshold: int = 1, decay_factor: float | None = None, compression_threshold: int = 10000)
Memory-efficient streaming frequency distribution with incremental updates.
This class supports real-time updates to frequency distributions while maintaining memory efficiency through various optimization strategies.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_vocabulary_size
|
int | None
|
Maximum support size to maintain, or |
None
|
min_count_threshold
|
int
|
Minimum count for elements to be retained.
Defaults to |
1
|
decay_factor
|
float | None
|
Exponential decay factor for aging old observations, or
|
None
|
compression_threshold
|
int
|
Number of updates between compression
operations. Defaults to |
10000
|
Examples:
Counts accumulate across single and batch updates:
>>> stream_dist = StreamingFrequencyDistribution(max_vocabulary_size=1000)
>>> stream_dist.update("word1")
>>> stream_dist.update("word1", count=5)
>>> stream_dist.update_batch(["word2", "word3", "word1"])
>>> stream_dist.get_count("word1")
7.0
>>> len(stream_dist)
3
>>> stream_dist.get_total_count()
9.0
>>> round(stream_dist.get_frequency("word1"), 4)
0.7778
Initialize streaming frequency distribution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_vocabulary_size
|
int | None
|
Maximum support size, or |
None
|
min_count_threshold
|
int
|
Minimum count threshold for retention. |
1
|
decay_factor
|
float | None
|
Decay factor for time-based forgetting
( |
None
|
compression_threshold
|
int
|
Number of updates before triggering compression. |
10000
|
update ¶
Update count for a single element.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
element
|
Element
|
Element to update. |
required |
count
|
int
|
Count increment. Defaults to |
1
|
update_batch ¶
Update counts for multiple elements efficiently.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
elements
|
list[Element]
|
Elements to update. |
required |
counts
|
list[int] | None
|
Count for each element, or |
None
|
get_count ¶
Get count for an element.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
element
|
Element
|
Element to query. |
required |
Returns:
| Type | Description |
|---|---|
float
|
Count for the element. |
get_frequency ¶
Get relative frequency for an element.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
element
|
Element
|
Element to query. |
required |
Returns:
| Type | Description |
|---|---|
float
|
Relative frequency ( |
to_dict ¶
Convert to regular dictionary with integer counts.
Returns:
| Type | Description |
|---|---|
dict[Element, int]
|
Dictionary representation with integer counts. |
get_statistics ¶
Get statistics about the streaming distribution.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Statistics dictionary with vocabulary size, total count, update |
dict[str, Any]
|
count, and configuration parameters. |
StreamingLaplace ¶
StreamingLaplace(initial_freqdist: dict[Element, int] | None = None, max_vocabulary_size: int | None = None, bins: int | None = None, logprob: bool = True)
Bases: StreamingMLE
Streaming Laplace smoothing with incremental updates.
Extends StreamingMLE to provide Laplace (add-one) smoothing in a streaming context.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
initial_freqdist
|
dict[Element, int] | None
|
Initial frequency distribution, or |
None
|
max_vocabulary_size
|
int | None
|
Maximum support size to maintain, or |
None
|
bins
|
int | None
|
Total number of possible bins, or |
None
|
logprob
|
bool
|
Return log-probabilities if |
True
|
Examples:
Unobserved elements receive smoothed probability mass:
>>> laplace = StreamingLaplace(
... initial_freqdist={"a": 3, "b": 1}, logprob=False
... )
>>> round(laplace("a"), 4)
0.6667
>>> round(laplace("unseen"), 4)
0.1667
Initialize streaming Laplace scorer.
StreamingMLE ¶
StreamingMLE(initial_freqdist: dict[Element, int] | None = None, max_vocabulary_size: int | None = None, unobs_prob: float | None = None, logprob: bool = True)
Bases: ScoringMethod, IncrementalScoringMethod
Streaming Maximum Likelihood Estimation with incremental updates.
This implementation maintains a streaming frequency distribution and updates probability estimates incrementally as new data arrives.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
initial_freqdist
|
dict[Element, int] | None
|
Initial frequency distribution, or |
None
|
max_vocabulary_size
|
int | None
|
Maximum support size to maintain, or |
None
|
unobs_prob
|
float | None
|
Reserved probability mass for unobserved elements, or
|
None
|
logprob
|
bool
|
Return log-probabilities if |
True
|
Examples:
Probabilities are updated incrementally as observations arrive:
>>> streaming_mle = StreamingMLE(logprob=False)
>>> streaming_mle.update_single("word1", 5)
>>> streaming_mle.update_batch(["word2", "word3"])
>>> round(streaming_mle("word1"), 4)
0.7143
>>> streaming_mle.get_update_count()
3
Initialize streaming MLE scorer.
update_single ¶
Update with a single element observation.
update_batch ¶
Update with multiple element observations.
get_streaming_statistics ¶
Get statistics about the streaming distribution.
save_state ¶
Save the current state to disk.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filepath
|
str
|
Path to save the state. |
required |
load_state
classmethod
¶
Load state from disk.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filepath
|
str
|
Path to load the state from. |
required |
Returns:
| Type | Description |
|---|---|
StreamingMLE
|
The loaded streaming MLE instance. |
BatchScorer ¶
Efficient batch scoring using multiple scoring methods.
This class allows scoring elements using multiple methods simultaneously, with optimized operations for processing large batches of data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scorers
|
dict[str, ScoringMethod]
|
Mapping of scorer names to scoring methods. |
required |
Examples:
Score the same elements with several methods at once:
>>> from freqprob import MLE, Laplace
>>> scorers = {
... "mle": MLE({"a": 3, "b": 2}, logprob=False),
... "laplace": Laplace({"a": 3, "b": 2}, logprob=False),
... }
>>> batch_scorer = BatchScorer(scorers)
>>> results = batch_scorer.score_batch(["a", "b", "c"])
>>> [round(float(x), 4) for x in results["mle"]]
[0.6, 0.4, 0.0]
>>> [round(float(x), 4) for x in results["laplace"]]
[0.5714, 0.4286, 0.1429]
Initialize batch scorer with multiple scoring methods.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scorers
|
dict[str, ScoringMethod]
|
Mapping of scorer names to scoring methods. |
required |
score_batch ¶
Score elements using all configured scoring methods.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
elements
|
Sequence[Element]
|
Elements to score. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, ndarray[Any, Any]]
|
Mapping of scorer names to score arrays. |
score_and_compare ¶
Score elements and provide comparison statistics.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
elements
|
Sequence[Element]
|
Elements to score. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dictionary with per-method scores plus aggregate statistics |
dict[str, Any]
|
(mean, std, min, max) and per-method rankings. |
benchmark_methods ¶
Benchmark the performance of different scoring methods.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
elements
|
Sequence[Element]
|
Elements to use for benchmarking. |
required |
num_iterations
|
int
|
Number of iterations for timing. Defaults to
|
100
|
Returns:
| Type | Description |
|---|---|
dict[str, float]
|
Mapping of method names to average execution time in seconds. |
VectorizedScorer ¶
Wrapper class that adds vectorized operations to any scoring method.
This class wraps existing scoring methods to provide efficient batch operations using numpy arrays and vectorized computations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scorer
|
ScoringMethod
|
The underlying scoring method to wrap. |
required |
Examples:
Score many elements at once, returning a numpy array:
>>> from freqprob import MLE
>>> scorer = MLE({"a": 3, "b": 2, "c": 1}, logprob=False)
>>> vectorized = VectorizedScorer(scorer)
>>> scores = vectorized.score_batch(["a", "b", "c", "d"])
>>> scores.shape
(4,)
>>> [round(float(x), 4) for x in scores]
[0.5, 0.3333, 0.1667, 0.0]
Initialize vectorized scorer wrapper.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scorer
|
ScoringMethod
|
Scoring method to wrap with vectorized operations. |
required |
score_batch ¶
Score a batch of elements efficiently using vectorized operations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
elements
|
Sequence[Element] | ndarray[Any, Any]
|
Elements to score, as a sequence or numpy array. |
required |
Returns:
| Type | Description |
|---|---|
ndarray[Any, Any]
|
Array of scores for the elements, in the same order as the input. |
Examples:
score_parallel ¶
Score multiple batches in parallel (when available).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
element_batches
|
Sequence[Sequence[Element]]
|
List of element batches to score. |
required |
Returns:
| Type | Description |
|---|---|
list[ndarray[Any, Any]]
|
List of score arrays, one per input batch. |
score_matrix ¶
Score a 2D array of elements, returning a matrix of scores.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
elements_2d
|
Sequence[Sequence[Element]]
|
2D list of elements to score. |
required |
Returns:
| Type | Description |
|---|---|
ndarray[Any, Any]
|
2D array of scores with shape |
ndarray[Any, Any]
|
|
ndarray[Any, Any]
|
with the default probability. |
top_k_elements ¶
Get the top-k highest scoring elements.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
k
|
int
|
Number of top elements to return. |
required |
Returns:
| Type | Description |
|---|---|
Sequence[Element]
|
A tuple of |
ndarray[Any, Any]
|
from highest to lowest score. |
percentile_scores ¶
Compute percentile ranks for element scores.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
elements
|
Sequence[Element]
|
Elements to compute percentile ranks for. |
required |
percentiles
|
list[float]
|
Percentiles to compute (0-100). |
required |
Returns:
| Type | Description |
|---|---|
ndarray[Any, Any]
|
Percentile rank for each element. |
clear_all_caches ¶
Clear all global computation caches.
Empties both the Simple Good-Turing cache and the general-purpose cache used to memoize expensive smoothing computations. Useful for freeing memory or forcing recomputation.
Examples:
get_cache_stats ¶
Get statistics about global cache usage.
Reports the current number of entries held in each global cache.
Returns:
| Type | Description |
|---|---|
dict[str, int]
|
A mapping with the keys |
dict[str, int]
|
each giving the number of cached entries in the corresponding cache. |
Examples:
cross_entropy ¶
Calculate the cross-entropy of a model on test data.
Cross-entropy is the average negative log-probability the model assigns to the test data. Lower values indicate a better fit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
ScoringMethod
|
A fitted probability model. Must be configured with
|
required |
test_data
|
Iterable[Element]
|
Iterable of test elements to score. |
required |
Returns:
| Type | Description |
|---|---|
float
|
The cross-entropy value (in nats, since natural logs are used). |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
kl_divergence ¶
kl_divergence(p_model: ScoringMethod, q_model: ScoringMethod, test_data: Iterable[Element]) -> float
Calculate the Kullback-Leibler divergence between two models.
KL divergence measures how much the approximate distribution Q diverges
from the reference distribution P. It is not symmetric:
KL(P||Q) != KL(Q||P).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
p_model
|
ScoringMethod
|
The reference probability model |
required |
q_model
|
ScoringMethod
|
The approximate probability model |
required |
test_data
|
Iterable[Element]
|
Iterable of test elements over which to accumulate the divergence. |
required |
Returns:
| Type | Description |
|---|---|
float
|
The KL divergence value. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If either model is not configured for log-probabilities. |
Examples:
model_comparison ¶
model_comparison(models: dict[str, ScoringMethod], test_data: Iterable[Element]) -> dict[str, dict[str, float]]
Compare multiple models using perplexity and cross-entropy.
Each model is evaluated on the same test data, producing a nested mapping of model name to its metric values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
models
|
dict[str, ScoringMethod]
|
Mapping of model names to fitted models. Every model must use
|
required |
test_data
|
Iterable[Element]
|
Iterable of test elements. It is materialized once and reused across all models. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, dict[str, float]]
|
A mapping of each model name to a ``{"perplexity": ..., "cross_entropy": |
dict[str, dict[str, float]]
|
...}`` dictionary. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If any model is not configured for log-probabilities. |
Examples:
>>> from freqprob import MLE, Laplace, model_comparison
>>> models = {
... "mle": MLE({"a": 3, "b": 2, "c": 1}, logprob=True),
... "laplace": Laplace({"a": 3, "b": 2, "c": 1}, logprob=True),
... }
>>> results = model_comparison(models, ["a", "b", "a", "c"])
>>> sorted(results)
['laplace', 'mle']
>>> round(results["mle"]["perplexity"], 4)
2.913
>>> round(results["laplace"]["cross_entropy"], 4)
1.0561
perplexity ¶
Calculate the perplexity of a model on test data.
Perplexity is exp(H(p)) where H(p) is the cross-entropy. Lower
perplexity indicates a model that better predicts the test data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
ScoringMethod
|
A fitted probability model. Must be configured with
|
required |
test_data
|
Iterable[Element]
|
Iterable of test elements to score. |
required |
Returns:
| Type | Description |
|---|---|
float
|
The perplexity value. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
create_lazy_laplace ¶
create_lazy_laplace(freqdist: FrequencyDistribution, bins: int | None = None, logprob: bool = True) -> LazyScoringMethod
Create a lazy Laplace (add-one) smoothing scorer.
Builds a LazyScoringMethod that computes Laplace-smoothed probabilities
on demand, fitted to the given distribution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqdist
|
FrequencyDistribution
|
Mapping of elements to observed counts. |
required |
bins
|
int | None
|
Total number of possible elements (vocabulary size). When
|
None
|
logprob
|
bool
|
Return log-probabilities if |
True
|
Returns:
| Type | Description |
|---|---|
LazyScoringMethod
|
A fitted lazy Laplace scorer. |
Examples:
create_lazy_mle ¶
create_lazy_mle(freqdist: FrequencyDistribution, unobs_prob: float | None = None, logprob: bool = True) -> LazyScoringMethod
Create a lazy Maximum Likelihood Estimation scorer.
Builds a LazyScoringMethod that computes MLE probabilities on demand,
fitted to the given distribution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqdist
|
FrequencyDistribution
|
Mapping of elements to observed counts. |
required |
unobs_prob
|
float | None
|
Reserved probability mass for unobserved elements, or
|
None
|
logprob
|
bool
|
Return log-probabilities if |
True
|
Returns:
| Type | Description |
|---|---|
LazyScoringMethod
|
A fitted lazy MLE scorer. |
Examples:
create_compressed_distribution ¶
create_compressed_distribution(freqdist: FrequencyDistribution, quantization_levels: int | None = None, use_compression: bool = True) -> CompressedFrequencyDistribution
Create a compressed frequency distribution from a regular one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqdist
|
FrequencyDistribution
|
Original frequency distribution. |
required |
quantization_levels
|
int | None
|
Number of quantization levels for compression, or
|
None
|
use_compression
|
bool
|
Whether to use data compression when serializing.
Defaults to |
True
|
Returns:
| Type | Description |
|---|---|
CompressedFrequencyDistribution
|
A compressed distribution populated from |
Examples:
create_sparse_distribution ¶
create_sparse_distribution(freqdist: FrequencyDistribution, default_count: int = 0) -> SparseFrequencyDistribution
Create a sparse frequency distribution from a regular one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqdist
|
FrequencyDistribution
|
Original frequency distribution. |
required |
default_count
|
int
|
Default count for unobserved elements. Defaults to
|
0
|
Returns:
| Type | Description |
|---|---|
SparseFrequencyDistribution
|
A sparse distribution populated from |
Examples:
create_vectorized_batch_scorer ¶
Create a batch scorer handling multiple scoring methods efficiently.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scorers
|
dict[str, ScoringMethod]
|
Mapping of scorer names to scoring methods. |
required |
Returns:
| Type | Description |
|---|---|
BatchScorer
|
A |
Examples:
generate_ngrams ¶
Generate n-grams from text.
A string input is treated as a sequence of characters, while a list input is treated as a sequence of tokens. Each n-gram is returned as a tuple.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str | list[str]
|
Input text, either a string (split into characters) or a list of tokens. |
required |
n
|
int
|
Size of the n-grams to generate. Must be positive. |
required |
Returns:
| Type | Description |
|---|---|
list[tuple[str, ...]]
|
A list of n-gram tuples. Empty if the input has fewer than |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
Character n-grams from a string:
>>> from freqprob import generate_ngrams
>>> generate_ngrams("abcd", 2)
[('a', 'b'), ('b', 'c'), ('c', 'd')]
Token n-grams from a list:
ngram_frequency ¶
ngram_frequency(text: str | list[str], n: int, normalize: bool = False) -> dict[tuple[str, ...], int | float]
Compute n-gram frequency from text.
Generates n-grams with :func:generate_ngrams and counts them. Counts are
returned by default, or relative frequencies when normalize is set.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str | list[str]
|
Input text, either a string (split into characters) or a list of tokens. |
required |
n
|
int
|
Size of the n-grams to count. Must be positive. |
required |
normalize
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
dict[tuple[str, ...], int | float]
|
A mapping of each n-gram tuple to its frequency (an |
dict[tuple[str, ...], int | float]
|
|
Examples:
word_frequency ¶
Compute word frequency from text.
A string input is split on whitespace into words; a list input is used as-is.
Counts are returned by default, or relative frequencies when normalize is
set.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str | list[str]
|
Input text, either a whitespace-delimited string or a list of tokens. |
required |
normalize
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
dict[str, int | float]
|
A mapping of each word to its frequency (an |
dict[str, int | float]
|
relative frequency when |
Examples:
Raw counts:
>>> from freqprob import word_frequency
>>> word_frequency("hello world hello")
{'hello': 2, 'world': 1}
Normalized frequencies: