| 1 | #!/usr/bin/env python |
|---|
| 2 | #-*- coding:utf-8 -*- |
|---|
| 3 | |
|---|
| 4 | import MeCab, operator, math, collections |
|---|
| 5 | from chi2p import chi2p |
|---|
| 6 | |
|---|
| 7 | class Samples (object): |
|---|
| 8 | """ |
|---|
| 9 | シリアライズ化のために切り離して使うことにする。 |
|---|
| 10 | """ |
|---|
| 11 | def __init__(self): |
|---|
| 12 | self.spam_dict = collections.defaultdict(float) # スパム文章の単語辞書 |
|---|
| 13 | self.nonspam_dict = collections.defaultdict(float) # 非スパム文章の単語辞書 |
|---|
| 14 | self.n_spam = 0.0 # スパム文章のサンプル数 |
|---|
| 15 | self.n_nonspam = 0.0 # 非スパム文章のサンプル数 |
|---|
| 16 | |
|---|
| 17 | def getNumberOfWord(self, word): |
|---|
| 18 | """ |
|---|
| 19 | Robinson方式で用います。 |
|---|
| 20 | 単語wordにおける辞書の出現回数を返します。 |
|---|
| 21 | """ |
|---|
| 22 | return self.spam_dict[word] + self.nonspam_dict[word] |
|---|
| 23 | |
|---|
| 24 | class Bayes (object): |
|---|
| 25 | LIMIT_MAX = 1.0 - 1.0e-5 |
|---|
| 26 | LIMIT_MIN = 1.0e-5 |
|---|
| 27 | DEFAULT_PERCENTAGE = 0.4 |
|---|
| 28 | |
|---|
| 29 | def __init__(self, samples=None): |
|---|
| 30 | if samples: |
|---|
| 31 | self._s = samples |
|---|
| 32 | else: |
|---|
| 33 | self._s = Samples() |
|---|
| 34 | self.tagger = MeCab.Tagger("-Ochasen") |
|---|
| 35 | |
|---|
| 36 | def addSample(self, sentence, isSpam): |
|---|
| 37 | """ |
|---|
| 38 | 予め判別されたセンテンスを追加し、ベイズ学習器に学習させます。 |
|---|
| 39 | """ |
|---|
| 40 | n_list = self.parseToNoun(sentence) |
|---|
| 41 | if isSpam: |
|---|
| 42 | self._s.n_spam += 1.0 |
|---|
| 43 | for n in n_list: self._s.spam_dict[n] += 1.0 |
|---|
| 44 | else: |
|---|
| 45 | self._s.n_nonspam += 1.0 |
|---|
| 46 | for n in n_list: self._s.nonspam_dict[n] += 1.0 |
|---|
| 47 | |
|---|
| 48 | def evaluateByGraham(self, sentence, bias=2.0): |
|---|
| 49 | """ |
|---|
| 50 | 学習機での学習結果を元にスパムかどうかを判定し、スパムである確率を返します。 |
|---|
| 51 | """ |
|---|
| 52 | n_list = self.parseToNoun(sentence) |
|---|
| 53 | p_list = [self.getPercentage(n, bias) for n in n_list] |
|---|
| 54 | p_spam = reduce( operator.mul, p_list ) |
|---|
| 55 | p_nonspam = reduce( operator.mul, [1.0-p for p in p_list] ) |
|---|
| 56 | p = p_spam / (p_spam + p_nonspam) |
|---|
| 57 | return p |
|---|
| 58 | |
|---|
| 59 | def evaluateByRobinson(self, sentence, s=1.0, x=0.5): |
|---|
| 60 | """ |
|---|
| 61 | 学習機での学習結果を元にスパムかどうかを判定し、スパムである指標を返します。 |
|---|
| 62 | 基本的にRobinson方式のほうがGraham方式よりも優れています。 |
|---|
| 63 | """ |
|---|
| 64 | n_list = self.parseToNoun(sentence) |
|---|
| 65 | twoN = 2.0*len(n_list) |
|---|
| 66 | ds = [self.getDegreeOfBelief(w,s,x) for w in n_list] |
|---|
| 67 | dsi = [1.0-d for d in ds] |
|---|
| 68 | H = chi2p(-2.0*math.log(reduce(operator.mul, ds)), twoN) |
|---|
| 69 | S = chi2p(-2.0*math.log(reduce(operator.mul, dsi)), twoN) |
|---|
| 70 | I = (1.0+H-S)/2.0 |
|---|
| 71 | return I |
|---|
| 72 | |
|---|
| 73 | def getSamples(self): |
|---|
| 74 | """ |
|---|
| 75 | 学習に使われているサンプルを取得します。 |
|---|
| 76 | """ |
|---|
| 77 | return self._s |
|---|
| 78 | |
|---|
| 79 | def getDegreeOfBelief(self, word, s, x): |
|---|
| 80 | """ |
|---|
| 81 | Robinson方式で用います。 |
|---|
| 82 | p(w_i)における信頼度の具合を返します。 |
|---|
| 83 | |
|---|
| 84 | x:今まで1度もメールの中に出現していない単語が初めてメールに出現したときに、 |
|---|
| 85 | そのメールがスパムメールである予測確率([0,1]) |
|---|
| 86 | s:xの予測に与える強さ(strength) |
|---|
| 87 | """ |
|---|
| 88 | n = self._s.getNumberOfWord(word) |
|---|
| 89 | |
|---|
| 90 | if n == 0.0: |
|---|
| 91 | return s*x / (s+n) |
|---|
| 92 | else: |
|---|
| 93 | return (s*x + n*self.getPercentage(word, 1.0))/(s+n) |
|---|
| 94 | |
|---|
| 95 | def getPercentage(self, word, bias): |
|---|
| 96 | """ |
|---|
| 97 | 各単語のスパム確率を返します。 |
|---|
| 98 | """ |
|---|
| 99 | # 各確率の計算 |
|---|
| 100 | pgood = 0.0 |
|---|
| 101 | pbad = 0.0 |
|---|
| 102 | if (word in self._s.nonspam_dict): |
|---|
| 103 | pgood = min( (1.0, self._s.nonspam_dict[word] / self._s.n_nonspam * bias) ) |
|---|
| 104 | if (word in self._s.spam_dict): |
|---|
| 105 | pbad = min( (1.0, self._s.spam_dict[word] / self._s.n_spam) ) |
|---|
| 106 | # スパム確率の算出 |
|---|
| 107 | p = 0.0 |
|---|
| 108 | if (pgood == 0.0 and pbad == 0.0): |
|---|
| 109 | p = Bayes.DEFAULT_PERCENTAGE # 判定できないのでデフォルト値を返す |
|---|
| 110 | else: |
|---|
| 111 | p = pbad / (pgood + pbad) |
|---|
| 112 | if p < Bayes.LIMIT_MIN : p = Bayes.LIMIT_MIN # 確率pが0に収束しないためにする。 |
|---|
| 113 | elif p > Bayes.LIMIT_MAX : p = Bayes.LIMIT_MAX # 同様。 |
|---|
| 114 | return p |
|---|
| 115 | |
|---|
| 116 | def parseToNoun(self, sentence): |
|---|
| 117 | """ |
|---|
| 118 | 文章から、名詞から成るリストを返します。 |
|---|
| 119 | """ |
|---|
| 120 | n_list = [] |
|---|
| 121 | m = self.tagger.parseToNode(sentence) |
|---|
| 122 | while m: |
|---|
| 123 | if m.feature.startswith("名詞"): |
|---|
| 124 | n_list.append(m.surface) |
|---|
| 125 | m = m.next |
|---|
| 126 | return n_list |
|---|