scoring problems

"Eric S. Johansson" <[email protected]>
Newsgroups gmane.mail.spam.crm114
Message-ID <[email protected]>
many moons ago I complained about score distributions and it took me a while but 
I finally have a method where I can run various testing scenarios on a set of 
data.  My dataset is approximately 12,000 messages that have been pre-scored, 
mostly accurately, as a result of having passed through twopenny blue and its 
filtering processes.  If there are any mis-scored messages is because CRM 114 
placed good messages deep within the red zone and I never saw them.  (No, I'm 
not manually going over 12,000 messages to try and correct these mistakes.  I 
view this as a real-world problem that I have to deal with at some point.)

anyway, the problem I've been having is that I'm getting way too many messages 
in the range of 0 to -14  and that makes for a rather unpleasant user 
experience.  If you look at the scores below (some help with gnuplot for 
graphing this data would be welcome), you'll see there is a bimodal distribution 
of scores.  The green messages peak in the range of 21-23 and the Reds peak in 
the range of -6 to -7.  This is not good.  In the past the peaks had occurred 
well outside of range twopenny blue considers "uncertain" which is +-14.  I have 
started moving the uncertain range to +-7 in the hopes of cutting off some of 
the peak but it seems to have had the unfortunate effect of moving the red peak 
closer to zero.  another curious thing is I'm getting some message scores of 
999.  It's probably something in my code that I'm blind about but, I'm still 
puzzled.

I've attached the code I used to drive CRM114 (ignore main, it's the remnants of 
a test program I haven't finished).  And below I've included the scores I get 
from my test suite.  So, any clues as to what the heck is going on I would 
appreciate it.  I am puzzled because I have been using fundamentally the same 
code for years and things went south before I started changing my code to try 
and compensate.

-304, 4
-277, 0
-260, 0
-249, 0
-244, 0
-237, 0
-171, 0
-162, 0
-160, 0
-158, 0
-151, 0
-144, 0
-140, 2
-136, 0
-133, 0
-132, 0
-131, 0
-130, 0
-128, 1
-126, 0
-125, 0
-116, 1
-111, 0
-107, 1
-104, 1
-102, 0
-100, 1
-92, 0
-90, 0
-89, 1
-88, 0
-86, 0
-83, 0
-82, 0
-79, 0
-78, 3
-77, 8
-76, 9
-75, 6
-74, 5
-73, 1
-72, 1
-70, 3
-69, 0
-68, 1
-64, 0
-63, 2
-62, 2
-61, 8
-60, 22
-59, 14
-58, 5
-57, 4
-56, 13
-55, 5
-54, 13
-53, 29
-52, 55
-51, 95
-50, 108
-49, 60
-48, 42
-47, 24
-46, 20
-45, 17
-44, 13
-43, 12
-42, 13
-41, 7
-40, 8
-39, 31
-38, 25
-37, 37
-36, 51
-35, 46
-34, 24
-33, 26
-32, 10
-31, 21
-30, 19
-29, 20
-28, 52
-27, 52
-26, 46
-25, 56
-24, 65
-23, 55
-22, 61
-21, 123
-20, 134
-19, 170
-18, 205
-17, 169
-16, 175
-15, 136
-14, 164
-13, 235
-12, 289
-11, 394
-10, 427
-9, 463
-8, 557
-7, 794
-6, 815
-5, 603
-4, 389
-3, 195
-2, 81
-1, 43
0, 45
1, 29
2, 16
3, 22
4, 36
5, 39
6, 74
7, 56
8, 77
9, 65
10, 71
11, 56
12, 40
13, 51
14, 49
15, 41
16, 71
17, 75
18, 88
19, 100
20, 176
21, 196
22, 174
23, 180
24, 134
25, 110
26, 119
27, 83
28, 79
29, 86
30, 75
31, 67
32, 82
33, 59
34, 52
35, 49
36, 47
37, 41
38, 19
39, 17
40, 7
41, 11
42, 6
43, 3
44, 3
45, 3
46, 0
47, 1
48, 0
49, 3
50, 5
52, 0
54, 1
55, 2
57, 3
58, 0
59, 4
60, 0
61, 1
63, 0
66, 0
67, 0
69, 0
71, 0
74, 1
76, 0
82, 0
95, 0
97, 0
99, 0
105, 0
118, 1
120, 0
123, 0
128, 0
129, 0
133, 0
167, 0
168, 0
169, 1
171, 0
173, 0
189, 0
196, 0
199, 0
200, 0
221, 0
999, 10

-------------------------------------------------------------------------
This SF.net email is sponsored by the 2008 JavaOne(SM) Conference 
Don't miss this year's exciting event. There's still time to save $100. 
Use priority code J8TL2D2. 
http://ad.doubleclick.net/clk;198757673;13503038;p?http://java.sun.com/javaone

_______________________________________________
Crm114-general mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/crm114-general
crm114.py (text/x-python, 8.1 KB)
#!/usr/bin/python

# Title:
# spam_discriminator.py
#
# Description:
# interface between tpblue and CRM 114.  Handles train as good,
# train as bad, and scoring.
#
# Copyright:
# Eric S. Johansson ([email protected])
# BSD License: see LICENSE or http://www.opensource.org/licenses/bsd-license.php
#
# Requires:

#import email
from email.Errors import BoundaryError,HeaderParseError, MessageError 
import binascii
import sys
import traceback
import subprocess
#from esjtools.simple_locker import simple_locker
import syslog
#from tpblue.log import log
#from tpblue import tpblue_utils
# from tpblue import brown_utils
#from tpblue import common_services
#from tpblue import message_pool


####### just code #####

def dump_stack(preamble="default"):
    etype, value, tb = sys.exc_info()
    exception_strings = traceback.format_exception(etype, value, tb)
    for i in exception_strings:
        syslog.syslog("%s: %s"%(preamble,i))
    
    del tb
    sys.last_traceback = None


class CRM114 (object):
    def __init__(self,  common_CRM, user_CRM):
        """remember where everything is located """
        self.common_CRM = common_CRM
        self.user_CRM = user_CRM
        # print common_CRM, user_CRM
 
    def build_crm_command(self, command_type):
        """build a complete crm114 command line
        """
        command_table = { "stats": "--stats_only",
                          "spam": "--learnspam",
                          "nonspam": "--learnnonspam",
                          }

        # first string is path to normalizemime.
        # 2ed string is path to crm file.
        # 3ed is command,
        # 4th is user path
        # 5th is error log
    

        command_list = [self.common_CRM,
                        command_table[command_type],
                        "--fileprefix=%s"%self.user_CRM,
                        "-u /usr/local/crm/"
                        ]

        return command_list

    # ['/usr/local/crm/mailfilter.crm', '--stats_only', '--fileprefix=/var/tpblue/esj/crm114/', '-u /usr/local/crm/']

    def CRM114_train(self, learn_as, message):

        result = True

        learn_command = {"red": "spam",
                         "green":"nonspam",
                         }
        command_list = self.build_crm_command(learn_command[learn_as])

        try: 
            # train as not spam
            p = subprocess.Popen(command_list, shell=False, bufsize=4000,
                                 stdin=subprocess.PIPE,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE,
                                 close_fds=True)

            message_text = message  #.as_string (1)
            (crm114_output, crm114_error) = p.communicate(message_text[:10000])
            
            syslog.syslog("CRM 114_output %s"% crm114_output)
            syslog.syslog("CRM 114_error %s"% crm114_error)

        except:
            # crm114 woofed again.  fake a 0 return and return the message
            syslog.syslog("crm114 call failed")
            dump_stack()
            result = False

        return result


    def train_as_good(self, message,  given_score):
        """ """

        result = 0 # preseeed success
        try:
            if given_score <= 3:
                syslog.syslog( "train it green %s"% (given_score,))
            
                # retrain as green because the user said so.
                result = self.CRM114_train("green", message)
        
                if not result:
                    syslog.syslog("TAG failed to learn %s" % result)

        except Exception, error:
            syslog.syslog("train as good error: %s" % (error,))
            dump_stack('TAG:')

            # invert the sense of result so it'll make more sense outside of
            # this method.  True equal success on training, false equals
            # failure

        return not result



    def train_as_bad(self, message, given_score):

        """for a given user, train messages as bad.
        """
        result = True #indicate failure.  Yes, it's weird.
        try:

            if given_score >= -3:
                # retrain as bad because the user said so.
                syslog.syslog( "train it red %s"% (given_score,))
                result = self.CRM114_train("red", message)

                if not result:
                    syslog.syslog("TAB failed to learn %s" % result)

        except Exception, error:
            dump_stack('TAG:')
            syslog.syslog("train as bad error %s" % (error,))

        return not result

    def score_message(self, message):

        """run the message through CRM114 return the priority code as well
        as modifying the message to contain the codes as a header"""

        try:

            command_list = self.build_crm_command("stats")
            message_text = message

            # print command_list
            p = subprocess.Popen(command_list, shell=False, bufsize=4000,
                                 stdin=subprocess.PIPE,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE,
                                 close_fds=True)

            message_text = message #.as_string (1)
            (crm114_output, crm114_error) = p.communicate(message_text[:10000])
            syslog.syslog("CRM 114_output %s"% crm114_output)
            syslog.syslog("CRM 114_error %s"% crm114_error)

            try:
                # convert string to float if it dies, well, it's dead Jim
                #print "returned result from CRM 114 = %s" % str(result),
                result = float (crm114_output.rstrip())

            except Exception, error:
                # blech dog food!
                syslog.syslog("bad score, error = %s %s"% (crm114_output, error))
                syslog.syslog("command =  %s"% (command_list))

                result = "-0.13"

        except (BoundaryError, HeaderParseError, binascii.Error ), error_message:
            syslog.syslog("score_message failed %s" % error_message )

        return result



####################  test code ##################    
def main():


    #development_path = "/usr/local/tpblue/src"

    #sys.path.insert(1,development_path)
    from tpblue import configuration
    from tpblue import tpblue_email

    #import rpdb2; rpdb2.start_embedded_debugger('1234', fAllowRemote = True)

    tpblue_ID = 'esj'
    config = configuration.configuration(tpblue_ID)
    crm_path = config["crm114_command"]
    user_path = config["crm114_base"]

    content_analyzer = CRM114(crm_path, user_path)

    #start of the main code    
    try:

        # go grab message from standard in
        my_message = tpblue_email.tpblue_message()
        my_message.message_from_file(sys.stdin)
        my_message.meta['tpblue_ID'] = tpblue_ID
        my_message.meta['recipient_list'] = ['[email protected]']
        score = content_analyzer.score_message(my_message.message)
                
                # print "%s score is %s"% (tpblue_ID, score)
            

    except (StandardError),e:
        # exception was thrown, log it and return original message
        # spamtrap these messages marking them as exception fodder
        
        syslog.syslog("tpblue filter threw exception: %s" % e)
        # print ("tpblue filter threw exception: %s" % e)

        etype, value, tb = sys.exc_info()
        exception_strings = traceback.format_exception(etype, value, tb)
        for i in exception_strings:
            syslog.syslog(i)
            # print(i)

        del tb
        sys.last_traceback = None

    except MessageError,e:
        # this is highly likely an ill formed piece of spam.  It did
        # not even create a message object.  So, I say dump it on the
        # floor after logging the event. 

        syslog.syslog("junk message threw exception: %s" % e)

        etype, value, tb = sys.exc_info()
        exception_strings = traceback.format_exception(etype, value, tb)
        for i in exception_strings:
            syslog.syslog(i)

        del tb
        sys.last_traceback = None

            
if __name__ == "__main__":

    main()
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.