Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

# Natural Language Toolkit: Naive Bayes Classifiers 

# 

# Copyright (C) 2001-2012 NLTK Project 

# Author: Edward Loper <edloper@gradient.cis.upenn.edu> 

# URL: <http://www.nltk.org/> 

# For license information, see LICENSE.TXT 

 

""" 

A classifier based on the Naive Bayes algorithm.  In order to find the 

probability for a label, this algorithm first uses the Bayes rule to 

express P(label|features) in terms of P(label) and P(features|label): 

 

|                       P(label) * P(features|label) 

|  P(label|features) = ------------------------------ 

|                              P(features) 

 

The algorithm then makes the 'naive' assumption that all features are 

independent, given the label: 

 

|                       P(label) * P(f1|label) * ... * P(fn|label) 

|  P(label|features) = -------------------------------------------- 

|                                         P(features) 

 

Rather than computing P(featues) explicitly, the algorithm just 

calculates the denominator for each label, and normalizes them so they 

sum to one: 

 

|                       P(label) * P(f1|label) * ... * P(fn|label) 

|  P(label|features) = -------------------------------------------- 

|                        SUM[l]( P(l) * P(f1|l) * ... * P(fn|l) ) 

""" 

from __future__ import print_function 

 

from collections import defaultdict 

 

from nltk.probability import FreqDist, DictionaryProbDist, ELEProbDist, sum_logs 

from .api import ClassifierI 

 

##////////////////////////////////////////////////////// 

##  Naive Bayes Classifier 

##////////////////////////////////////////////////////// 

 

class NaiveBayesClassifier(ClassifierI): 

    """ 

    A Naive Bayes classifier.  Naive Bayes classifiers are 

    paramaterized by two probability distributions: 

 

      - P(label) gives the probability that an input will receive each 

        label, given no information about the input's features. 

 

      - P(fname=fval|label) gives the probability that a given feature 

        (fname) will receive a given value (fval), given that the 

        label (label). 

 

    If the classifier encounters an input with a feature that has 

    never been seen with any label, then rather than assigning a 

    probability of 0 to all labels, it will ignore that feature. 

 

    The feature value 'None' is reserved for unseen feature values; 

    you generally should not use 'None' as a feature value for one of 

    your own features. 

    """ 

    def __init__(self, label_probdist, feature_probdist): 

        """ 

        :param label_probdist: P(label), the probability distribution 

            over labels.  It is expressed as a ``ProbDistI`` whose 

            samples are labels.  I.e., P(label) = 

            ``label_probdist.prob(label)``. 

 

        :param feature_probdist: P(fname=fval|label), the probability 

            distribution for feature values, given labels.  It is 

            expressed as a dictionary whose keys are ``(label, fname)`` 

            pairs and whose values are ``ProbDistI`` objects over feature 

            values.  I.e., P(fname=fval|label) = 

            ``feature_probdist[label,fname].prob(fval)``.  If a given 

            ``(label,fname)`` is not a key in ``feature_probdist``, then 

            it is assumed that the corresponding P(fname=fval|label) 

            is 0 for all values of ``fval``. 

        """ 

        self._label_probdist = label_probdist 

        self._feature_probdist = feature_probdist 

        self._labels = label_probdist.samples() 

 

    def labels(self): 

        return self._labels 

 

    def classify(self, featureset): 

        return self.prob_classify(featureset).max() 

 

    def prob_classify(self, featureset): 

        # Discard any feature names that we've never seen before. 

        # Otherwise, we'll just assign a probability of 0 to 

        # everything. 

        featureset = featureset.copy() 

        for fname in list(featureset.keys()): 

            for label in self._labels: 

                if (label, fname) in self._feature_probdist: 

                    break 

            else: 

                #print 'Ignoring unseen feature %s' % fname 

                del featureset[fname] 

 

        # Find the log probabilty of each label, given the features. 

        # Start with the log probability of the label itself. 

        logprob = {} 

        for label in self._labels: 

            logprob[label] = self._label_probdist.logprob(label) 

 

        # Then add in the log probability of features given labels. 

        for label in self._labels: 

            for (fname, fval) in featureset.items(): 

                if (label, fname) in self._feature_probdist: 

                    feature_probs = self._feature_probdist[label,fname] 

                    logprob[label] += feature_probs.logprob(fval) 

                else: 

                    # nb: This case will never come up if the 

                    # classifier was created by 

                    # NaiveBayesClassifier.train(). 

                    logprob[label] += sum_logs([]) # = -INF. 

 

        return DictionaryProbDist(logprob, normalize=True, log=True) 

 

    def show_most_informative_features(self, n=10): 

        # Determine the most relevant features, and display them. 

        cpdist = self._feature_probdist 

        print('Most Informative Features') 

 

        for (fname, fval) in self.most_informative_features(n): 

            def labelprob(l): 

                return cpdist[l,fname].prob(fval) 

            labels = sorted([l for l in self._labels 

                             if fval in cpdist[l,fname].samples()], 

                            key=labelprob) 

            if len(labels) == 1: continue 

            l0 = labels[0] 

            l1 = labels[-1] 

            if cpdist[l0,fname].prob(fval) == 0: 

                ratio = 'INF' 

            else: 

                ratio = '%8.1f' % (cpdist[l1,fname].prob(fval) / 

                                  cpdist[l0,fname].prob(fval)) 

            print(('%24s = %-14r %6s : %-6s = %s : 1.0' % 

                   (fname, fval, str(l1)[:6], str(l0)[:6], ratio))) 

 

    def most_informative_features(self, n=100): 

        """ 

        Return a list of the 'most informative' features used by this 

        classifier.  For the purpose of this function, the 

        informativeness of a feature ``(fname,fval)`` is equal to the 

        highest value of P(fname=fval|label), for any label, divided by 

        the lowest value of P(fname=fval|label), for any label: 

 

        |  max[ P(fname=fval|label1) / P(fname=fval|label2) ] 

        """ 

        # The set of (fname, fval) pairs used by this classifier. 

        features = set() 

        # The max & min probability associated w/ each (fname, fval) 

        # pair.  Maps (fname,fval) -> float. 

        maxprob = defaultdict(lambda: 0.0) 

        minprob = defaultdict(lambda: 1.0) 

 

        for (label, fname), probdist in self._feature_probdist.items(): 

            for fval in probdist.samples(): 

                feature = (fname, fval) 

                features.add( feature ) 

                p = probdist.prob(fval) 

                maxprob[feature] = max(p, maxprob[feature]) 

                minprob[feature] = min(p, minprob[feature]) 

                if minprob[feature] == 0: 

                    features.discard(feature) 

 

        # Convert features to a list, & sort it by how informative 

        # features are. 

        features = sorted(features, 

            key=lambda feature: minprob[feature]/maxprob[feature]) 

        return features[:n] 

 

    @staticmethod 

    def train(labeled_featuresets, estimator=ELEProbDist): 

        """ 

        :param labeled_featuresets: A list of classified featuresets, 

            i.e., a list of tuples ``(featureset, label)``. 

        """ 

        label_freqdist = FreqDist() 

        feature_freqdist = defaultdict(FreqDist) 

        feature_values = defaultdict(set) 

        fnames = set() 

 

        # Count up how many times each feature value occurred, given 

        # the label and featurename. 

        for featureset, label in labeled_featuresets: 

            label_freqdist.inc(label) 

            for fname, fval in featureset.items(): 

                # Increment freq(fval|label, fname) 

                feature_freqdist[label, fname].inc(fval) 

                # Record that fname can take the value fval. 

                feature_values[fname].add(fval) 

                # Keep a list of all feature names. 

                fnames.add(fname) 

 

        # If a feature didn't have a value given for an instance, then 

        # we assume that it gets the implicit value 'None.'  This loop 

        # counts up the number of 'missing' feature values for each 

        # (label,fname) pair, and increments the count of the fval 

        # 'None' by that amount. 

        for label in label_freqdist: 

            num_samples = label_freqdist[label] 

            for fname in fnames: 

                count = feature_freqdist[label, fname].N() 

                feature_freqdist[label, fname].inc(None, num_samples-count) 

                feature_values[fname].add(None) 

 

        # Create the P(label) distribution 

        label_probdist = estimator(label_freqdist) 

 

        # Create the P(fval|label, fname) distribution 

        feature_probdist = {} 

        for ((label, fname), freqdist) in feature_freqdist.items(): 

            probdist = estimator(freqdist, bins=len(feature_values[fname])) 

            feature_probdist[label,fname] = probdist 

 

        return NaiveBayesClassifier(label_probdist, feature_probdist) 

 

##////////////////////////////////////////////////////// 

##  Demo 

##////////////////////////////////////////////////////// 

 

def demo(): 

    from nltk.classify.util import names_demo 

    classifier = names_demo(NaiveBayesClassifier.train) 

    classifier.show_most_informative_features() 

 

if __name__ == '__main__': 

    demo()