root/platform/tdiary/lib/bayes.rb @ 5383

Revision 5383, 4.4 kB (checked in by hsbt, 7 years ago)

platform/tdiary/filter/spambayes: move directory.

Line 
1# Copyright (C) 2007, KURODA Hiraku <hiraku@hinet.mydns.jp>
2# You can redistribute it and/or modify it under GPL2.
3
4require "pstore"
5
6module Bayes
7        module CHARSET
8                def self.setup_re(m)
9                        o = $KCODE
10                        $KCODE = m::KCODE
11                        m.const_set(:RE_MESSAGE_TOKEN, Regexp.union(m::RE_KATAKANA, m::RE_KANJI, /[a-zA-Z]+/))
12                        $KCODE=o
13                end
14
15                module EUC
16                        KCODE = "e"
17                        KATAKANA = "\xa5\xa2-\xa5\xf3"
18                        KANJI = "\xb0\xa1-\xfc\xfe"
19                        RE_KATAKANA = /[#{KATAKANA}]{2,}/eo
20                        RE_KANJI = /[#{KANJI}]{2,}/eo
21
22                        CHARSET.setup_re(self)
23                end
24
25                module UTF8
26                        KCODE = "u"
27                        def self.c2u(c)
28                                [c].pack("U")
29                        end
30                        def self.utf_range(a, b)
31                                "#{c2u(a)}-#{c2u(b)}"
32                        end
33                        KATAKANA = utf_range(0x30a0, 0x30ff)
34                        KANJI = utf_range(0x4e00, 0x9faf)
35                        RE_KATAKANA = /[#{KATAKANA}]{2,}/uo
36                        RE_KANJI = /[#{KANJI}]{2,}/uo
37
38                        CHARSET.setup_re(self)
39                end
40        end
41
42        class TokenList < Array
43                attr_reader :charset
44
45                def initialize(charset=nil)
46                        unless charset
47                                charset =
48                                        case $KCODE
49                                        when /^e/i
50                                                CHARSET::EUC
51                                        else
52                                                CHARSET::UTF8
53                                        end
54                        end
55                        @charset = charset
56                end
57
58                alias _concat concat
59                def concat(array, prefix=nil)
60                        if prefix
61                                _concat(array.map{|i| "#{prefix} #{i.to_s}"})
62                        else
63                                _concat(array)
64                        end
65                end
66
67                alias _push push
68                def push(item, prefix=nil)
69                        if prefix
70                                _push("#{prefix} #{item.to_s}")
71                        else
72                                _push(item)
73                        end
74                end
75
76                def add_host(host, prefix=nil)
77                        if /^(?:\d{1,3}\.){3}\d{1,3}$/ =~ host
78                                while host.size>0
79                                        push(host, prefix)
80                                        host = host[/^(.*?)\.?\d+$/, 1]
81                                end
82                        else
83                                push(host, prefix)
84
85                                h = host
86                                while /^(.*?)[\.\-](.*)$/=~h
87                                        h = $2
88                                        push($1, prefix)
89                                        push(h, prefix)
90                                end
91                        end
92                        self
93                end
94
95                def add_url(url, prefix=nil)
96                        if %r[^(?:https?|ftp)://(.*?)(?::\d+)?/(.*?)\/?(\?.*)?$] =~ url
97                                host = $1
98                                path = $2
99
100                                add_host(host, prefix)
101
102                                if path.size>0
103                                        push(path, prefix)
104
105                                        p = path
106                                        re = %r[^(.*)[/\-\.](.*?)$]
107                                        while re=~p
108                                                p = $1
109                                                push($2, prefix)
110                                                push(p, prefix)
111                                        end
112                                end
113                        end
114                        self
115                end
116
117                def add_message(message, prefix=nil)
118                        concat(message.scan(@charset::RE_MESSAGE_TOKEN), prefix)
119                        self
120                end
121
122                def add_mail_addr(addr, prefix=nil)
123                        push(addr, prefix)
124
125                        name, host = addr.split(/@/)
126                        return self if (name||"").empty?
127                        host ||= ""
128                        push(name, prefix)
129                        add_host(host, prefix)
130                        self
131                end
132        end
133
134        class FilterBase
135                attr_reader :spam, :ham, :db_name
136
137                def initialize(db_name=nil)
138                        @spam = self.class::Corpus.new
139                        @ham = self.class::Corpus.new
140
141                        @db_name = db_name
142                        if db_name && File.exist?(db_name)
143                                PStore.new(db_name).transaction(true) do |db|
144                                        @spam = db["spam"]
145                                        @ham = db["ham"]
146                                end
147                        end
148                end
149
150                def save(db_name=nil)
151                        db_name ||= @db_name
152                        @db_name ||= db_name
153                        return unless @db_name
154                        PStore.new(@db_name).transaction do |db|
155                                db["spam"] = @spam
156                                db["ham"] = @ham
157                                yield(db) if block_given?
158                        end
159                end
160
161                def [](token)
162                        score(token)
163                end
164        end
165
166        class PlainBayes < FilterBase
167                class Corpus < Hash
168                        def initialize
169                                super(0.0)
170                        end
171
172                        def <<(src)
173                                s = src.size.to_f
174                                src.each do |i|
175                                        self[i] += 1/s
176                                end
177                        end
178                end
179
180                def score(token)
181                        return nil unless @spam.include?(token) || @ham.include?(token)
182                        s = @spam[token]
183                        h = @ham[token]
184                        s/(s+h)
185                end
186
187                def estimate(tokens, take=15)
188                        s = tokens.uniq.map{|i| score(i)}.compact.sort{|a, b| (0.5-a).abs <=> (0.5-b)}.reverse[0...take]
189                        return nil if s.empty? || s.include?(1.0) && s.include?(0.0)
190
191                        prod = s.inject(1.0){|r, i| r*i}
192                        return prod/(prod+s.inject(1.0){|r, i| r*(1-i)})
193                end
194        end
195
196        class PaulGraham < FilterBase
197                class Corpus < Hash
198                        attr_reader :count
199                        def initialize
200                                super(0)
201                                @count = 0
202                        end
203
204                        def <<(src)
205                                @count += 1
206                                src.each do |i|
207                                        self[i] += 1
208                                end
209                        end
210                end
211
212                def score(token)
213                        return 0.4 unless @spam.include?(token) or @ham.include?(token)
214                        g = @ham.count==0 ? 0.0 : [1.0, 2*@ham[token]/@ham.count.to_f].min
215                        b = @spam.count==0 ? 0.0 : [1.0, @spam[token]/@spam.count.to_f].min
216                        if g+b==0
217                                raise "OOO"
218                        end
219                        r = [0.01, [0.99, b/(g+b)].min].max
220                        r
221                end
222
223                def estimate(tokens, take=15)
224                        s = tokens.uniq.map{|i| score(i)}.compact.sort{|a, b| (0.5-a).abs <=> (0.5-b)}.reverse[0...take]
225                        return nil if s.empty? || s.include?(1.0) && s.include?(0.0)
226
227                        prod = s.inject(1.0){|r, i| r*i}
228                        return prod/(prod+s.inject(1.0){|r, i| r*(1-i)})
229                end
230        end
231end
Note: See TracBrowser for help on using the browser.