natural language toolkit classifier utility functions c 20012023 nltk project edward loper edlopergmail com steven bird stevenbird1gmail com minor additions url https www nltk org for license information see license txt utility functions and classes for classifiers from nltk util import deprecated helper functions alternative name possibility mapfeaturefunc alternative name possibility detectfeatures alternative name possibility mapfeaturedetect or just have users use lazymap directly use the lazymap class to construct a lazy listlike object that is analogous to mapfeaturefunc toks in particular if labeledfalse then the returned listlike object s values are equal to featurefunctok for tok in toks if labeledtrue then the returned listlike object s values are equal to featurefunctok label for tok label in toks the primary purpose of this function is to avoid the memory overhead involved in storing all the featuresets for every token in a corpus instead these featuresets are constructed lazily asneeded the reduction in memory overhead can be especially significant when the underlying list of tokens is itself lazy as is the case with many corpus readers param featurefunc the function that will be applied to each token it should return a featureset i e a dict mapping feature names to feature values param toks the list of tokens to which featurefunc should be applied if labeledtrue then the list elements will be passed directly to featurefunc if labeledfalse then the list elements should be tuples tok label and tok will be passed to featurefunc param labeled if true then toks contains labeled tokens i e tuples of the form tok label default autodetect based on types return a list of all labels that are attested in the given list of tokens rtype list of immutable param tokens the list of classified tokens from which to extract labels a classified token has the form token label type tokens list a helper class that implements cutoff checks based on number of iterations and log likelihood accuracy cutoffs are also implemented but they re almost never a good idea to use demos construct a list of classified names using the names corpus randomly split the names into a test train set train up a classifier run the classifier on the test data for classifiers that can find probabilities show the log likelihood and some sample probability distributions return the classifier create a list of male names to be used as positivelabeled examples for training create a list of male and female names to be used as unlabeled examples create a test set with correctlylabeled male and female names train up a classifier run the classifier on the test data for classifiers that can find probabilities show the log likelihood and some sample probability distributions return the classifier get the instances randomly split the names into a test train set train up a classifier run the classifier on the test data for classifiers that can find probabilities show the log likelihood and some sample probability distributions return the classifier checks whether the megam binary is configured natural language toolkit classifier utility functions c 2001 2023 nltk project edward loper edloper gmail com steven bird stevenbird1 gmail com minor additions url https www nltk org for license information see license txt utility functions and classes for classifiers from nltk util import deprecated for accuracy log_likelihood helper functions alternative name possibility map_featurefunc alternative name possibility detect_features alternative name possibility map_featuredetect or just have users use lazymap directly use the lazymap class to construct a lazy list like object that is analogous to map feature_func toks in particular if labeled false then the returned list like object s values are equal to feature_func tok for tok in toks if labeled true then the returned list like object s values are equal to feature_func tok label for tok label in toks the primary purpose of this function is to avoid the memory overhead involved in storing all the featuresets for every token in a corpus instead these featuresets are constructed lazily as needed the reduction in memory overhead can be especially significant when the underlying list of tokens is itself lazy as is the case with many corpus readers param feature_func the function that will be applied to each token it should return a featureset i e a dict mapping feature names to feature values param toks the list of tokens to which feature_func should be applied if labeled true then the list elements will be passed directly to feature_func if labeled false then the list elements should be tuples tok label and tok will be passed to feature_func param labeled if true then toks contains labeled tokens i e tuples of the form tok label default auto detect based on types return a list of all labels that are attested in the given list of tokens rtype list of immutable param tokens the list of classified tokens from which to extract labels a classified token has the form token label type tokens list a helper class that implements cutoff checks based on number of iterations and log likelihood accuracy cutoffs are also implemented but they re almost never a good idea to use iteration cutoff log likelihood cutoff log likelihood delta cutoff log likelihood cutoff log likelihood delta cutoff no cutoff reached demos construct a list of classified names using the names corpus randomly split the names into a test train set train up a classifier run the classifier on the test data for classifiers that can find probabilities show the log likelihood and some sample probability distributions return the classifier create a list of male names to be used as positive labeled examples for training create a list of male and female names to be used as unlabeled examples create a test set with correctly labeled male and female names train up a classifier run the classifier on the test data for classifiers that can find probabilities show the log likelihood and some sample probability distributions return the classifier get the instances randomly split the names into a test train set train up a classifier run the classifier on the test data for classifiers that can find probabilities show the log likelihood and some sample probability distributions return the classifier checks whether the megam binary is configured
import math


import nltk.classify.util  
from nltk.util import LazyMap









def apply_features(feature_func, toks, labeled=None):
    
    if labeled is None:
        labeled = toks and isinstance(toks[0], (tuple, list))
    if labeled:

        def lazy_func(labeled_token):
            return (feature_func(labeled_token[0]), labeled_token[1])

        return LazyMap(lazy_func, toks)
    else:
        return LazyMap(feature_func, toks)


def attested_labels(tokens):
    
    return tuple({label for (tok, label) in tokens})


def log_likelihood(classifier, gold):
    results = classifier.prob_classify_many([fs for (fs, l) in gold])
    ll = [pdist.prob(l) for ((fs, l), pdist) in zip(gold, results)]
    return math.log(sum(ll) / len(ll))


def accuracy(classifier, gold):
    results = classifier.classify_many([fs for (fs, l) in gold])
    correct = [l == r for ((fs, l), r) in zip(gold, results)]
    if correct:
        return sum(correct) / len(correct)
    else:
        return 0


class CutoffChecker:
    

    def __init__(self, cutoffs):
        self.cutoffs = cutoffs.copy()
        if "min_ll" in cutoffs:
            cutoffs["min_ll"] = -abs(cutoffs["min_ll"])
        if "min_lldelta" in cutoffs:
            cutoffs["min_lldelta"] = abs(cutoffs["min_lldelta"])
        self.ll = None
        self.acc = None
        self.iter = 1

    def check(self, classifier, train_toks):
        cutoffs = self.cutoffs
        self.iter += 1
        if "max_iter" in cutoffs and self.iter >= cutoffs["max_iter"]:
            return True  

        new_ll = nltk.classify.util.log_likelihood(classifier, train_toks)
        if math.isnan(new_ll):
            return True

        if "min_ll" in cutoffs or "min_lldelta" in cutoffs:
            if "min_ll" in cutoffs and new_ll >= cutoffs["min_ll"]:
                return True  
            if (
                "min_lldelta" in cutoffs
                and self.ll
                and ((new_ll - self.ll) <= abs(cutoffs["min_lldelta"]))
            ):
                return True  
            self.ll = new_ll

        if "max_acc" in cutoffs or "min_accdelta" in cutoffs:
            new_acc = nltk.classify.util.log_likelihood(classifier, train_toks)
            if "max_acc" in cutoffs and new_acc >= cutoffs["max_acc"]:
                return True  
            if (
                "min_accdelta" in cutoffs
                and self.acc
                and ((new_acc - self.acc) <= abs(cutoffs["min_accdelta"]))
            ):
                return True  
            self.acc = new_acc

            return False  







def names_demo_features(name):
    features = {}
    features["alwayson"] = True
    features["startswith"] = name[0].lower()
    features["endswith"] = name[-1].lower()
    for letter in "abcdefghijklmnopqrstuvwxyz":
        features["count(%s)" % letter] = name.lower().count(letter)
        features["has(%s)" % letter] = letter in name.lower()
    return features


def binary_names_demo_features(name):
    features = {}
    features["alwayson"] = True
    features["startswith(vowel)"] = name[0].lower() in "aeiouy"
    features["endswith(vowel)"] = name[-1].lower() in "aeiouy"
    for letter in "abcdefghijklmnopqrstuvwxyz":
        features["count(%s)" % letter] = name.lower().count(letter)
        features["has(%s)" % letter] = letter in name.lower()
        features["startswith(%s)" % letter] = letter == name[0].lower()
        features["endswith(%s)" % letter] = letter == name[-1].lower()
    return features


def names_demo(trainer, features=names_demo_features):
    import random

    from nltk.corpus import names

    
    namelist = [(name, "male") for name in names.words("male.txt")] + [
        (name, "female") for name in names.words("female.txt")
    ]

    
    random.seed(123456)
    random.shuffle(namelist)
    train = namelist[:5000]
    test = namelist[5000:5500]

    
    print("Training classifier...")
    classifier = trainer([(features(n), g) for (n, g) in train])

    
    print("Testing classifier...")
    acc = accuracy(classifier, [(features(n), g) for (n, g) in test])
    print("Accuracy: %6.4f" % acc)

    
    
    try:
        test_featuresets = [features(n) for (n, g) in test]
        pdists = classifier.prob_classify_many(test_featuresets)
        ll = [pdist.logprob(gold) for ((name, gold), pdist) in zip(test, pdists)]
        print("Avg. log likelihood: %6.4f" % (sum(ll) / len(test)))
        print()
        print("Unseen Names      P(Male)  P(Female)\n" + "-" * 40)
        for ((name, gender), pdist) in list(zip(test, pdists))[:5]:
            if gender == "male":
                fmt = "  %-15s *%6.4f   %6.4f"
            else:
                fmt = "  %-15s  %6.4f  *%6.4f"
            print(fmt % (name, pdist.prob("male"), pdist.prob("female")))
    except NotImplementedError:
        pass

    
    return classifier


def partial_names_demo(trainer, features=names_demo_features):
    import random

    from nltk.corpus import names

    male_names = names.words("male.txt")
    female_names = names.words("female.txt")

    random.seed(654321)
    random.shuffle(male_names)
    random.shuffle(female_names)

    
    positive = map(features, male_names[:2000])

    
    unlabeled = map(features, male_names[2000:2500] + female_names[:500])

    
    test = [(name, True) for name in male_names[2500:2750]] + [
        (name, False) for name in female_names[500:750]
    ]

    random.shuffle(test)

    
    print("Training classifier...")
    classifier = trainer(positive, unlabeled)

    
    print("Testing classifier...")
    acc = accuracy(classifier, [(features(n), m) for (n, m) in test])
    print("Accuracy: %6.4f" % acc)

    
    
    try:
        test_featuresets = [features(n) for (n, m) in test]
        pdists = classifier.prob_classify_many(test_featuresets)
        ll = [pdist.logprob(gold) for ((name, gold), pdist) in zip(test, pdists)]
        print("Avg. log likelihood: %6.4f" % (sum(ll) / len(test)))
        print()
        print("Unseen Names      P(Male)  P(Female)\n" + "-" * 40)
        for ((name, is_male), pdist) in zip(test, pdists)[:5]:
            if is_male == True:
                fmt = "  %-15s *%6.4f   %6.4f"
            else:
                fmt = "  %-15s  %6.4f  *%6.4f"
            print(fmt % (name, pdist.prob(True), pdist.prob(False)))
    except NotImplementedError:
        pass

    
    return classifier


_inst_cache = {}


def wsd_demo(trainer, word, features, n=1000):
    import random

    from nltk.corpus import senseval

    
    print("Reading data...")
    global _inst_cache
    if word not in _inst_cache:
        _inst_cache[word] = [(i, i.senses[0]) for i in senseval.instances(word)]
    instances = _inst_cache[word][:]
    if n > len(instances):
        n = len(instances)
    senses = list({l for (i, l) in instances})
    print("  Senses: " + " ".join(senses))

    
    print("Splitting into test & train...")
    random.seed(123456)
    random.shuffle(instances)
    train = instances[: int(0.8 * n)]
    test = instances[int(0.8 * n) : n]

    
    print("Training classifier...")
    classifier = trainer([(features(i), l) for (i, l) in train])

    
    print("Testing classifier...")
    acc = accuracy(classifier, [(features(i), l) for (i, l) in test])
    print("Accuracy: %6.4f" % acc)

    
    
    try:
        test_featuresets = [features(i) for (i, n) in test]
        pdists = classifier.prob_classify_many(test_featuresets)
        ll = [pdist.logprob(gold) for ((name, gold), pdist) in zip(test, pdists)]
        print("Avg. log likelihood: %6.4f" % (sum(ll) / len(test)))
    except NotImplementedError:
        pass

    
    return classifier


def check_megam_config():
    
    try:
        _megam_bin
    except NameError as e:
        err_msg = str(
            "Please configure your megam binary first, e.g.\n"
            ">>> nltk.config_megam('/usr/bin/local/megam')"
        )
        raise NameError(err_msg) from e