Showing posts with label Machine-learning. Show all posts
Showing posts with label Machine-learning. Show all posts

Tuesday, October 23, 2012

Twitter Mood Predicts Stock Market Movement


Download: mood_dataset.zip

In this post, I try to predict the daily up and down movement of stock prices using twitter mood data and machine learning algorithms. Some time ago, I read a paper called “Twitter mood predicts the stock market.” They claimed to be able to predict stock price movement with an accuracy of over 86%. They used 9,853,498 tweets posted by 2.7 million English speaking users in 2008 and showed that general twitter mood could be used to predict the DJIA. 

My initial intention was to reproduce their results. However, their method would have required that I have access to large scale historical twitter data which is not free and probably not cheap. Instead, I found two companies that publish daily mood sentiment for individual stocks with historical data going back a couple of months. You can download here the anonymized dataset I used for IBM and AGN. I have anonymized the dataset for two reasons; I don’t know if it is legal to share this data and secondly, I might decide to use this method for my own financial gain – the results are impressive! 

For the first company that publishes mood data, I  combine the mood data for the previous 2, 5, and 8 days to predict whether a stock price goes up, down or stays flat. The accuracy is around 75% for AGN and 80% for IBM using 8 days of mood data. For the second company that publishes mood data the results where very impressive; around 90%-100% accuracy using just the previous day's mood data for all the stocks I tested. Below are the results summarized in a confusion matrix.

I used a decision tree and 5x cross-validation for all tests.

Company (1)

$AGN Confusion Matrix with 2 days worth of mood data
  down  
  flat  
  up  
down
55.1 %
0.0 %
44.9 %
205
flat
33.3 %
0.0 %
66.7 %
3
up
35.9 %
0.0 %
64.1 %
234
198
0
244
442
Note: columns represent predictions, row represent true classes

$AGN Confusion Matrix with 5 days worth of mood data
  down  
  flat  
  up  
down
71.7 % 
0.0 % 
28.3 % 
205
flat
66.7 % 
0.0 % 
33.3 % 
3
up
23.7 % 
0.0 % 
76.3 % 
232
204
0
236
440

Note: columns represent predictions, row represent true classes

$AGN Confusion Matrix with 8 days worth of mood data
  down  
  flat  
  up  
down
74.9 % 
0.0 % 
25.1 % 
203
flat
0.0 % 
0.0 % 
100.0 % 
3
up
23.5 % 
0.4 % 
76.1 % 
230
206
1
229
436
Note: columns represent predictions, row represent true classes

$IBM Confusion Matrix with 8 days worth of mood data
  down  
  flat  
  up  
down
82.8 % 
0.0 % 
17.2 % 
215
flat
N/A % 
N/A % 
N/A % 
0
up
20.1 % 
0.0 % 
79.9 % 
249
228
0
236
464
Note: columns represent predictions, row represent true classes

Company (2)

$AGN Confusion Matrix with 1 day worth of mood data
  down  
  flat  
  up  
down
100.0% 
0.0 % 
44.9 % 
460
flat
0.0 % 
100.0 % 
0.0 % 
46
up
0.0 % 
0.0 % 
100.0 % 
436
460
46
436
942
Note: columns represent predictions, row represent true classes






Wednesday, August 01, 2012

Clustering Through Decision Tree Construction



As is often the case, any idea you may have, no matter how novel you may think it is, has already been thought of by someone else. Some time ago I wanted to modify a decision tree induction algorithm for clustering. I thought that this could be a new method for clustering, but fortunately after an internet search, I came across a paper that described a method to do clustering using decision trees called CLTree. I like to re-use what other people have already done – it saves me a lot of time. Sadly, people writing papers do not always provide the source code. However, the paper did provide a fairly detailed description of the algorithm. In this post, I present an implementation of that algorithm.

Here are some of the benefits of this algorithm as described by the paper:

·         CLTree is able to find clusters in the full dimension space as well as in subspaces.
·         It provides descriptions of the resulting clusters in terms of hyper-rectangle regions.
·         It provides descriptions of the resulting empty (sparse) regions.
·         It deals with outliers effectively.

The basic idea is to assign each instance in the dataset a class Y. A decision tree requires at least two classes to partition a dataset. Assume that the data space is uniformly distributed with “other” instances of class N, called non-existing or virtual instances. By adding the N instances to the original dataset, the problem of partitioning the dataset into dense data regions and sparse empty regions becomes a classification problem. For the details please read the paper.

Below, the algorithm found three clusters for the trivial two dimensional dataset (gen.arff) that you can find with the source code I provide.

Please be aware that this implementation has only been tested on a couple of trivial datasets. One with two dimension and the other with five (provided with the source code). It probably will not scale very well with large datasets.

Friday, March 09, 2012

Use WEKA in your Python code

Weka is a collection of machine learning algorithms that can either be applied directly to a dataset or called from your own Java code. There is an article called “Use WEKA in your Java code” which as its title suggests explains how to use WEKA from your Java code. This is not a surprising thing to do since Weka is implemented in Java. As the title of this post suggests, I will describe how to use WEKA from your Python code instead.

If you have built an entire software system in Python, you might be reluctant to look at libraries in other languages. After all, there are a huge number of excellent Python libraries, and many good machine-learning libraries written in Python or C and C++ with Python bindings. However, as far as I am concerned, it would be a pity not to make use of Weka just because it is written in Java. It is one of the most well known machine-learning libraries around with an extensive number of implemented algorithms. What’s more, there are very few data stream mining libraries around and MOA, related to Weka and also written in Java is the best I have seen.

I use Jpype (http://jpype.sourceforge.net/) to access Weka class libraries. Once you have it installed, download the latest Weka & Moa versions and copy moa.jar, sizeofag.jar and weak.jar into your working directory. Below you can see the full Python listing of the test application. The code initializes the JVM, imports some Weka packages and classes, reads a data set, splits it into a training set and test set, trains a J48 tree classifier and then tests it. If you are familiar with Weka, this will all be very easy.

In a separate post, I will explore how easy it is to use MOA in the same way.

# Initialize the specified JVM
from jpype import *options = [
"-Xmx4G",
"-Djava.class.path=./moa.jar",
"-Djava.class.path=./weka.jar",
"-Djavaagent:sizeofag.jar",
]
startJVM(getDefaultJVMPath(), *options)

# Import java/weka packages and classes
Trees = JPackage("weka.classifiers.trees")
Filter = JClass("weka.filters.Filter")
Attribute = JPackage("weka.filters.unsupervised.attribute")
Instance = JPackage("weka.filters.unsupervised.instance")
RemovePercentage = JClass("weka.filters.unsupervised.instance.RemovePercentage")
Remove = JClass("weka.filters.unsupervised.attribute.Remove")
Classifier = JClass("weka.classifiers.Classifier")
NaiveBayes = JClass("weka.classifiers.bayes.NaiveBayes")
Evaluation = JClass("weka.classifiers.Evaluation")
FilteredClassifier = JClass("weka.classifiers.meta.FilteredClassifier")
Instances = JClass("weka.core.Instances")
BufferedReader = JClass("java.io.BufferedReader")
FileReader = JClass("java.io.FileReader")
Random = JClass("java.util.Random")


#Reading from an ARFF file
reader = BufferedReader(FileReader("./iris.arff"))
data = Instances(reader)
reader.close()
data.setClassIndex(data.numAttributes() - 1) # setting class attribute

# Standardizes all numeric attributes in the given dataset to have zero mean and unit variance, apart from the class attribute.
standardizeFilter = Attribute.Standardize()
standardizeFilter.setInputFormat(data)
data = Filter.useFilter(data, standardizeFilter)

# Randomly shuffles the order of instances passed through it.
randomizeFilter = Instance.Randomize()
randomizeFilter.setInputFormat(data)
data = Filter.useFilter(data, randomizeFilter)

# Creating train set
removeFilter = RemovePercentage()
removeFilter.setInputFormat(data)
removeFilter.setPercentage(30.0)
removeFilter.setInvertSelection(False)
trainData = Filter.useFilter(data, removeFilter)

# Creating test set
removeFilter.setInputFormat(data)
removeFilter.setPercentage(30.0)
removeFilter.setInvertSelection(True)
testData = Filter.useFilter(data, removeFilter)

# Create classifier
j48 = Trees.J48()
j48.setUnpruned(True) # using an unpruned J48
j48.buildClassifier(trainData)

print "Number Training Data", trainData.numInstances(), data.numInstances()
print "Number Test Data", testData.numInstances()

# Test classifier
for i in range(testData.numInstances()):
    pred = j48.classifyInstance(testData.instance(i))
    print "ID:", testData.instance(i).value(0),
    print "actual:", testData.classAttribute().value(int(testData.instance(i).classValue())),
    print "predicted:", testData.classAttribute().value(int(pred))

shutdownJVM()

Wednesday, July 27, 2011

Python Bindings for Sally (a machine learning tool)

Download:   sally-0.6.1-with-bindings.tar.gz

One of the tools I have used recently in my machine-learning projects is Sally. As Sally’s web page describes it: “There are many applications for Sally, for example, in the areas of natural language processing, bioinformatics, information retrieval and computer security”. You can look at the example page to see more details. It is written in C which makes it fast, but as is usually the case, using a tool like this directly from Python, would make life easier. It would make for faster prototyping and system development and since it is a tool that I think I will be using repeatedly in the future, I gave the library Python bindings. In this post I would like to outline the technique I use to create a python module from a C library. I use Swig for the bindings and you will have to be, to some extent, familiar with Swig to follow the rest of this post.

As input, SWIG takes a file containing ANSI C/C++ declarations, a special "interface file" (usually given an .i suffix). At its simplest, an interface file looks something like this (see below), where a module called "example" will be created with all C/C++ functions and variables in example.h available from Python.
%module example

%{
#include "example.h"
%}

%include "example.h"
 
Unfortunately, interface files are not usually that simple. There are limitations to what Swig will parse correctly. For example, complex declarations such as function pointers and arrays are problematic.

In the case of Sally, libconfig is used for its configuration management and one would need to include libconfig in the interface file. Take a look at the interface file below. Libconfig's config_lookup_string function is problematic. Swig can not deal with the char** without extra work from us. I created a function called config_lookup_string_2 that wraps config_lookup_string and with the help of the cstring.i library, this becomes useable from Python. Unfortunately, this is quite typical -- it often becomes a time consuming process to check every function and structure you want to provide bindings for, and look for ways of making problematic functions and structures work correctly from Python.
%module pysally

%{
#include <libconfig.h>
%}

%include <cstring.i>
%cstring_output_allocate(char **out1, free(*$1));

%{

void config_lookup_string_2(
    const config_t *config, const char *path, char **out1)
{
    *out1 = (char *) malloc(1024);
    (*out1)[0] = 0;   
    config_lookup_string(config, path, (const char *)out1);
}

%}
%include <libconfig.h>

The above interface file can quickly grow into a bit of a night-mare, in terms of development time and complexity as you add additional functions that Swig can’t deal with transparently. I take an alternative route. The approach I use is to create a facade over the api I want to use from Python. The facade consists of one or more C++ classes and it is the facade for which I provide bindings. The facade is made as complex as Swig's parser allows it to be without having to add complex Swig directives in the interface file.

Getting back to Sally. Essentially the library does three things:
1) Read a config file.
2) Read text from a file or files and process them.
3) Write features to an output file.

As far as Sally's configuration processing is concerned, one could provide some getter and setter member functions. It’s not strictly necessary to access the configuration from Python because the facade takes care of the configuration details in the load_config and init methods. All I have to do from Python is pass the name of the configuration file Sally is to use. Below, is my initial attempt at creating a facade and its interface file. You can pass the input and output paths (in the constructor), and configuration path (in load_config). As you can see, I make use of std::string because Swig deals with it semi-transparently by the addition of %include "std_string.i" in the interface file.

swig.i
%module pysally

%{
#include "pysally.h"
%}

%include "std_string.i"
%include "pysally.h"

pysally.h
class Sally
{
public:

    Sally(int verbose, std::string in, std::string out) :
        entries_(0), input_(in), output_(out) {}

    ~Sally();

    /// Load the configuration of Sally
    void load_config(const std::string& config_file);

    /// Init the Sally tool
    void init();

    /// Main processing routine of Sally.
    /// This function processes chunks of strings.
    void process();

    /// Get/Set configuration
    std::string getConfigAttribute(std::string name);

    void setConfigAttribute(std::string name, std::string value);

    // etc
    // ...
    // ...

private:
    config_t cfg_;
    int verbose_;
    long entries_;
    std::string input_;
    std::string output_;
};

From Python you would use it like this:
verbose = 0
in = "/tmp/input"
out = "/tmp/output"
config = "/tmp/sally.cfg"

s = Sally(verbose, in, out)
s.load_config(config)
s.init()
s.process()

As a result, I can now use Sally from Python, which is nice but it doesn’t really provide anything that I can’t already do with the C executable Sally provides. The Sally library allows you to configure its outputs for a specified format, such as plain text, in LibSVM or Matlab formats. Even though it’s not too difficult to add C code for other formats, it is even easier to do from Python. I provide two additional C++ classes; Reader and Writer (see the code below). The reader and writer facades use the underlying Sally library to read and write to files using the format specified in the configuration file, just as the original Sally binary does. But by extending these classes in Python, one could override the default behaviour -- read and write in other formats,  read and write to a database instead, or even write Sally's output directly to another machine-learning module or read its input directly from a web-scrapping python module instead of a file.

Below, you can see the final interface file, the C++ Reader/Writer classes that provide the default implementation and Python extension Reader/Writer classes. The interface file is still very simple. The only new additions are the directors directive. Directors allow C++ classes to be extended in Python, and from C++ these extensions look exactly like native C++ classes. Neither C++ code nor Python code needs to know where a particular method is implemented.

swig.i
%module(directors="1") pysally
%{
#include "pysally.h"
%}

%feature("director") Reader;        
%feature("director") Writer;       

%include "std_string.i"
%include "pysally.h"

pysally.h
class Writer
{
public:

    Writer(std::string out);

    virtual ~Writer();
   
    virtual void init();   
   
    virtual const std::string getName();

    virtual int write(const output_list& output, int len);
   
private:   
    config_t& cfg_;   
    std::string output_;
    bool hasout_;
};

class Reader
{
public:

    Reader(std::string in);

    virtual ~Reader();
   
    virtual void init();   
   
    virtual const std::string getName();
   
    virtual long getNrEntries();

    virtual int read(string_list& strs, int len);

private:   
    config_t& cfg_;   
    std::string input_; 
    long entries_;   
};

run.py
class MyReader(Reader):
    def __init__(self, input):
        super(MyReader, self).__init__(input)

    def read(self, strings, len):       
        return super(MyReader, self).read(strings, len)

    def init(self):
        super(MyReader, self).init()

    def getNrEntries(self):
        return super(MyReader, self).getNrEntries()


class MyWriter(Writer):
    def __init__(self, output):
        super(MyWriter, self).__init__(output)

    def init(self):        
        pass   

    def write(self, fvec, len):       
        for j in range(len):           
            print "l:", fvec.getFeaturesLabel(j),           
            for i in range(fvec.getListLength(j)):
                print fvec.getDimension(j, i), fvec.getValue(j, i),
                print  fvec.getValue(j, i)           
            print fvec.getFeaturesSource(j)           
            print       
        return 1

input = "/home/edimchr/reuters.zip"
output = "/home/edimchr/tmp/pyreuters.libsvm"
verbose = 0
r = MyReader(input)
w = MyWriter(output)
#r = Reader(input)
#w = Writer(output)

s = Sally(verbose, r, w)
s.load_config("./example.cfg")
s.init()
s.process()

From Python then, you can extend the Reader and/or Writer classes defined in C++. MyReader and MyWriter are passed to the Sally facade via its constructor, and from then-on the underlying C++ code uses the derived python implementations. MyReader simply defers to its base class i.e. Reader, and MyWriter prints the various output information Sally generated.

You may have noticed that the Reader class defines the member function:

    virtual int read(string_list& strs, int len);

And Writer defines the member function:

    virtual int write(const output_list& output, int len);

What are string_list and output_list ? Sally defines a couple of structures that it uses to store the text read (string_t) and output features calculated (fvec_t). These two structures are especially problematic for Swig. As a result, I create a facade over each one called string_list and output_list.
class string_list
{   
private:
    string_t* str_;
   
public:   
    string_list(string_t* str) :
        str_(str) {}
  
    /// Length for element i
    void setStringLength(int i, int len) 
      { str_[i].len = len ; }

    /// String data for element i
    void setStringData(int i, char* data) 
      { str_[i].str = strdup(data); } 
   
    /// Optional label of string
    void setLabel(int i, float label) 
      { str_[i].label = label; }
       
    /// Optional description of source
    void setSource(int i, char* src) 
      { str_[i].src = strdup(src); } 
   
    string_t* getString() const { return str_; }
};

class output_list
{   
private:
    fvec_t** vec_;
   
public:   
    output_list(fvec_t** vec) :
        vec_(vec) {}
  
    /// Length for element i
    unsigned long getListLength(int i) const 
      { return vec_[i]->len; }

    /// Nr of features for element i
    unsigned long getTotalFeatures(int i) const 
      { return vec_[i]->total; }
   
    /// Label for element i
    float getFeaturesLabel(int i) const 
      { return vec_[i]->label; }
   
    /// List of dimensions j
    unsigned long getDimension(int i, int j) 
      { return vec_[i]->dim[j]; }
   
    /// List of values for element i
    float getValue(int i, int j) 
      { return vec_[i]->val[j]; }   
   
    char* getFeaturesSource(int i) const 
      { return vec_[i]->src; } 
   
    fvec_t** getFvec() const { return vec_; }
};
By creating a simple C++ facade over the API, Swig can parse the interface file without difficulties. In general, one could use std::string, std::vector, and std::map.


Building the Python module

Sally is a C library and is built with Autotools.

1) You need additional Autoconf macros to enable SWIG and Python support. I added ac_pkg_swig.m4, ax_pkg_swig.m4 and ax_python_devel.m4 to the m4 subdirectory.
 
    sally-0.6.1/
        m4/
            ac_pkg_swig.m4
            ax_pkg_swig.m4
            ax_python_devel.m4
        pysally/
            Makefile.am
            swig.i
        src/
            Makefile.am
        Makefile.am
        configure.in
 
2) Add pysally to sally-0.6.1/Makefile.am
    ……
    SUBDIRS = src doc tests contrib pysally
    ……
    ……
 
3) Add the following to sally-0.6.1/configure.in
    AC_PROG_CXX
    AC_DISABLE_STATIC
    AC_PROG_LIBTOOL
    AX_PYTHON_DEVEL(>= '2.3')
    AM_PATH_PYTHON
    AC_PROG_SWIG(1.3.21)
    SWIG_ENABLE_CXX
    SWIG_PYTHON
 
4) Add pysally/Makefile to AC_CONFIG_FILES in sally-0.6.1/configure.in

    AC_CONFIG_FILES([
       Makefile \
       src/Makefile \
       src/input/Makefile \
       src/output/Makefile \
       src/fvec/Makefile \
       doc/Makefile \
       tests/Makefile \
       contrib/Makefile \
       pysally/Makefile \
    ])

5) Create the pysally subdirectory and add the files
    Makefile.am
    swig.i       <-- interface file
    globals.h 
    pysally.h    <-- wrapper facades
    pysally.cpp
    run.py       <-- example code to use the module

6) To build:
    cd sally-0.6.1
    ./autogen.sh
    ./configure --prefix=/home/yourhome/sally_install/ --enable-libarchive
    make
    make install

Tuesday, March 22, 2011

Automated Pattern Discovery From Network Traffic (2)

Last time, I described a way to find pattern strings in network traffic using machine-learning tools and techniques and it is the goal of this post to describe the method and results of applying these techniques to real network traffic. As an experiment or proof-of-concept, I looked for patterns in Bit-torrent traffic. The results look very promising. We will see how I uncovered a couple of patterns that could be used and probably are used by NIDS and DPI products to identify Bit-torrent traffic.

I will not go into any detail whatsoever on how to capture Bit-torrent traffic using Wireshark because I believe it is incidental to what I really want to show; how to apply machine-learning techniques using Sally and Cluto for pattern discovery. However, it is important to say that I will only be looking for patterns in the first packet of each flow or stream. I copied the contents of the first packet of each flow to a separate file. These files provide the input data to Sally. As I described in my last post, Sally maps strings into a vector space which we then use as input to Cluto, a toolkit for clustering.
One problem I did run into when I tried to combine these two tools is that Cluto's expected input file format is different to the format Sally provides. I hacked Sally's source code slightly so that its output matched Cluto's expected format. You can download the patch here: sally_patch. If you wish to reproduce my results, you can also download Sally's configuration file I used: sally_configuration. The script I used to glue together Sally and Cluto can be found here: glue_sally_cluto.py. This being a proof-of-concept, do not expect to find production ready code.

The glue_sally_cluto.py script first runs Sally, then Cluto, the results of which are then copied into a separate directory called "clusters". The directory holds the contents of each cluster Cluto generated and each cluster contains the packets Cluto lumped together in the clustering process. In my experiment, I used the tool chain to separate 750 packets into 10 clusters. As it turns out, a simple visual inspection of the clusters gave me the patterns. I found two: "BitTorrent protocol" and "d1:ad2:id20".

As an example, I looked at the contents of each packet in cluster "0":

dimitri@dimitri-laptop:/tmp/sig_analysis/clusters/0$ find -exec more {} \; 


BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol
BitTorrent protocol


The contents of each packet in cluster "5" indicate that an overwhelming number of packets start with the pattern "d1:ad2:id20":

dimitri@dimitri-laptop:/tmp/sig_analysis/clusters/5$ find -exec more {} \; 


L�RY � �O� 3�s!p�������� ��F� ;!7�����,��T������
�[��P ��7�ݞ �ڍQȝ�' ާ�                           ��V���v�� G�v=_/��@���G I���3 ]lV�˗|�� �t�f���Ż����ܐB32( ���>� �b C���#<y胀Y�:��v5��P�_��6[�5K󤜌�F `S
                     +�yp
d1:ad2:id20:
� h�B=A�~���lv� ,e1:q4:ping1:t4:�~
L6�J�A�\%<�c�e1:q4:ping1:t4:
d1:ad2:id20:Y�]{]� Pg=CA�,�B��0}e1:q4:ping1:t4:�6
d1:ad2:id20:{aB�Dǹ< d 0
                        A��e1:q4:ping1:t4:<b
d1:ad2:id20:hh ��Jb�νd��W%��|�e1:q4:ping1:t4:�(
d1:ad2:id20:���� �Y y���fc�] ���e1:q4:ping1:t4:�
d1:ad2:id20:<@˩
d1:ad2:id20:O�؇� ��"BG􃢅�e1:q4:ping1:t4:�l
d1:ad2:id20:CgC ���u ȡ��N�p���e1:q4:ping1:t4:rB
d1:ad2:id20:�T_}8 S��:�*�4���قe1:q4:ping1:t4:��
d1:ad2:id20:f$� �F0ik�D �_I k��e1:q4:ping1:t4:L�
�e1:q4:ping1:t4:e� 2~ג�=��ƷZ
d1:ad2:id20:�|IM��� ���r��E��e1:q4:ping1:t4:�m
d1:ad2:id20:JЅN�#�\bN�.|��}��e1:q4:ping1:t4:�+
d1:ad2:id20:��a�6ދO��˞ \��~ ��e1:q4:ping1:t4:}�
d1:ad2:id20: Z
d1:ad2:id20: ��� ^L!?b
d1:ad2:id20:
            D����{ �jUh�D



All the other clusters shared the same patterns; either "BitTorrent protocol" or "d1:ad2:id20".



Wednesday, March 16, 2011

Automated Pattern Discovery From Network Traffic

From time to time I browse the projects listed on freshmeat.net. I came accross Sally, a tool for mapping strings into vector spaces. This mapping allows one to apply machine learning techniques and data mining to the analysis of string data. I looked at two of the examples given; Sally can be used to map documents to a vector space and build a classifier to categorize text documents using Support Vector Machines and map DNA sequences to a vector space, then build a classifier to detect the start of DNA sequences.


I wondered whether this tool could be applied to automated pattern generation for network intrusion detection or network protocol identification. Patterns are used by various network intrusion detection systems (NIDS) and packet identifiers like Linux's Netfilter. For example, to detect bittorrent traffic, packet payloads are searched to find a string of characters that uniquely identify the bittorrent protocol. L7 (http://l7-filter.sourceforge.net/protocols) is a good source for patterns. L7 lists "bittorrent protocol" as one of the patterns that could be used for identifying bittorrent on your network. The idea therefore, would be to generate or discover patterns like "bittorrent protocol" automatically using Sally. This would be a little different to the Sally examples I looked at where a classifier was genereted by a supervised learner. In my push for an automated pattern generation tool, I will be using Sally together with an unsupervised machine learning technique, called clustering, to discover patterns in network traffic for any particular protocol. The idea would be to map bittorrent packets to a vector space using Sally, and then cluster those vector spaces. The generated clusters would each represent one or more possible patterns.


But first, how does Sally's mapping work ? I did'nt get it unitl I looked at the source code. It calculates the hash for every n-gram in the string and uses that hash value as a feature. The value for that feature is the total number of times that hash value is found in a string. If you chose to work with a byte 8-gram then you would have exactly 12 non-zero hash-value features for the string containing "bittorrent protocol", one each for "bittorre", "ittorren", "ttorrent", "torrent ", "orrent p", "rrent pr", "rent pro", "ent prot", "nt proto", "t protoc", " protoco" and "protocol". The hash values calculated for these n-grams with their corresponding counts look like this:


313166:1 575006:1 667871:1 1104474:1 1213512:1 1355539:1 1382445:1 1532866:1 1559069:1 2132221:1 2874899:1 2985843:1


A sparse vector of count values is generated and not all clustering algorithms can readily deal with a high-dimensional sparse feature set. The Cluto clustering toolkit can. It's not open-source but they do provide a binary for non-comercial use. Next time, I will be atempting to cluster the feature set Sally generates and hopefully use the clusters to discover patterns in network traffic.