Monday, 24 August 2015

Machine Learning using MLlib: Part 1 - Decision Trees

Machine learning is the talk of the tech industry, as organisations are faced with an explosion of data and are looking for ways to gain a competitive advantage through monetising this data. Examples of such monetisation are targeted marketing, fraud detection, churn prevention, network analysis, predictive maintenance etc.  This series of articles describes various machine-learning techniques using the fantastic open source library Apache Spark MLlib. MLlib is a machine-learning library that transformation of large amounts of data into meaningful patterns and rules that can be used for both statistical and predictive analytics. Combined with Sparks powerful support for horizontally scalable machine learning through Resilient Distributed Data Structures (RDD) , MLlib provides incorporates dozens of algorithms to solve various classification, regression, and clustering problems. 

This first part deals with one of the oldest and popular technique of machine learning - Decision Trees. As they are intuitive and easily readable, decision trees are perfect algorithms to start machine learning.  We will focus on basic Decision Trees but advise exploring extensions of this technique, which are more likely to be used in practical applications i.e. Ensembles or Multiple Decision Trees (random forests). We will use IPython Notebook to execute load the data set and execute the algorithm. For details on how to install spark on Ubuntu or OS-X, please refer to

We will attempt to use this technique to classification of medical problems in medical diagnoses.  In medical diagnoses the role of Classification algorithms such as Decision Trees is very important for medical practitioners because they are very fast to train and produce transparent models.  This particular application of classification relates to breast cancer data. Breast cancer is the second most important cause of death among women and is characterised by malignant tumours when cells in the breast tissue divide and grow without normal controls on cell death and cell division. It affects approximately 10% of all women at some period of their life. Mammography screening is the dominant method of early detection of breast cancer. Digital mammogram images are sometimes difficult to read due to their low contrast and differences in the types of tissues and the diagnoses has to be supported with fine needle aspiration cytology in order to extract tissue for a biopsy. However, statistics show that only 20-30 percentages of breast biopsy cases prove cancerous (false positives). However, a false negative carries much greater risk in terms of potential loss of human life. Therefore, it is necessary to develop better identification methods to recognise breast cancer. Machine learning methods can help to reduce the number of false positives and false negative decisions [1]. 

The objective is to assign patients to either a ‘Benign’ group/class that does not have breast cancer or a ‘Malignant’ group that has strong evidence of having breast cancer based on cytological proven tumour data. For this illustration, we will use the Wisconsin Breast Cancer Data set. The attributes in the data set are: Clump Thickness, Uniformity of Cell Size, Uniformity of Cell Shape, Marginal Adhesion, Single Epithelial Cell Size, Bare Nuclei, Bland Chromatin, Normal Nucleoli, Mitoses, and Class (Benign or Malignant)

We will first load the csv into a raw data set and split it into the attribute fields.

path = "breast-cancer-wisconsin.csv"
raw_data = sc.textFile(path)
num_data = raw_data.count()
data = raw_data.map(lambda x: x.split(","))
first = data.first()
print first
print num_data

Out:
[u'5', u'1', u'1', u'1', u'2', u'1', u'3', u'1', u'1', u'2']
683

We will next parse the raw data to create an RDD that we can subsequently use to create an the training, validation and test data sets. This is essential to overcome the problem of 'over-fitting: If we supply too much training data, the model will actually be created perfectly, but just for that data. The model should be able to predict future unknowns and not perfectly predict values that are already known. This is why we do a random split of the raw data set into 2 buckets - training (create the model) and test (validate the model).

Initially we translate the raw records into labelled points, which are key-value pairs. The key or label is the target classification or class of the observation and the values is a feature vector stored as an array. The feature values as converted to floats. Note that decision tree models can work on raw features i.e. it does not matter if the feature is categorical (Country: US, UK) or numerical (Income: 5000). In other methods, which we will discuss in later articles, the categorical features need to be encoded to convert into a numeric feature and reduce the side effects such as intrinsic ordering.


from pyspark.mllib.regression import LabeledPoint
import numpy as np

def extract_features_dt(record):
    return np.array(map(float, record[0:9]))

def extract_label(record):
    return float(record[-1])

data_dt = data.map(lambda r: LabeledPoint(extract_label(r), extract_features_dt(r)))
first_point_dt = data_dt.first()
print "Decision Tree feature vector: " + str(first_point_dt.features)
print "Decision Tree feature vector length: " + str(len(first_point_dt.features))

train_data_dt, test_data_dt = data_dt.randomSplit([0.7, 0.3])
train_data_dt.cache()
test_data_dt.cache


The following line illustrates the first feature vector which has 9 numeric features. 

Out: 
Decision Tree feature vector: [5.0,1.0,1.0,1.0,2.0,1.0,3.0,1.0,1.0]
Decision Tree feature vector length: 9


We will first train a classification tree including every single feature. We can then use our results for model selection and perform model pruning.  Pruning, as the name suggests, involves removing branches of the classification tree.  This again is to avoid over-fitting. As the data set grows larger and the number of features grows, the classification trees can get increasingly complex (depending on the depth and number of buckets configured for the model creation). We want our tree to be as simple as possible, with as few nodes and depth as possible without sacrificing too much accuracy. This can be a balancing act and leave it as an exercise for the reader (http://spark.apache.org/docs/latest/mllib-decision-tree.html)

We now proceed with building the decision model. Here, the use of trainClassifier method of DecisionTree instead of trainRegressor is because the class or target of the LabeledPoint should be treated as a distinct category number, not a numeric feature value.

from pyspark.mllib.tree import DecisionTree

dt_model = DecisionTree.trainClassifier(train_data_dt,5,{},"gini",4,10)
print dt_model.toDebugString()

The resultant decision model is traced below. You can experiment with different depths and maxBins to see how it impacts the complexity and time required to train the model.

Out:
DecisionTreeModel classifier of depth 4 with 25 nodes
  If (feature 1 <= 3.0)
   If (feature 5 <= 5.0)
    If (feature 5 <= 2.0)
     Predict: 2.0
    Else (feature 5 > 2.0)
     If (feature 2 <= 2.0)
      Predict: 2.0
     Else (feature 2 > 2.0)
      Predict: 4.0
   Else (feature 5 > 5.0)
    If (feature 0 <= 1.0)
     Predict: 2.0
    Else (feature 0 > 1.0)
     If (feature 8 <= 2.0)
      Predict: 4.0
     Else (feature 8 > 2.0)
      Predict: 4.0
  Else (feature 1 > 3.0)
   If (feature 3 <= 1.0)
    If (feature 0 <= 6.0)
     If (feature 6 <= 3.0)
      Predict: 2.0
     Else (feature 6 > 3.0)
      Predict: 4.0
    Else (feature 0 > 6.0)
     Predict: 4.0
   Else (feature 3 > 1.0)
    If (feature 1 <= 4.0)
     If (feature 5 <= 7.0)
      Predict: 4.0
     Else (feature 5 > 7.0)
      Predict: 4.0
    Else (feature 1 > 4.0)
     If (feature 7 <= 2.0)
      Predict: 4.0
     Else (feature 7 > 2.0)
      Predict: 4.0

Finally we can use the created model against the test set to predict the class ("Benign=2" or "Malignant=4") of the test observation.

preds = dt_model.predict(test_data_dt.map(lambda p: p.features))
actual = test_data_dt.map(lambda p: p.label)
true_vs_predicted_dt = actual.zip(preds)
print "Decision Tree predictions: " + str(true_vs_predicted_dt.take(50))
print "Decision Tree depth: " + str(dt_model.depth())
print "Decision Tree number of nodes: " + str(dt_model.numNodes())

Out:
Decision Tree predictions: [(2.0, 4.0), (2.0, 2.0), (4.0, 4.0), (4.0, 4.0), (2.0, 2.0), (2.0, 2.0), (2.0, 2.0), (2.0, 2.0), (2.0, 2.0), (4.0, 4.0), (4.0, 4.0), (2.0, 2.0), (4.0, 4.0), (4.0, 4.0), (4.0, 4.0), (4.0, 4.0), (4.0, 4.0), (4.0, 2.0), (4.0, 2.0), (4.0, 4.0), (2.0, 2.0), (2.0, 2.0), (2.0, 2.0), (2.0, 2.0), (4.0, 4.0), (2.0, 2.0), (4.0, 4.0), (4.0, 4.0), (4.0, 4.0), (4.0, 4.0), (4.0, 4.0), (4.0, 4.0), (4.0, 4.0), (4.0, 4.0), (2.0, 2.0), (2.0, 2.0), (2.0, 2.0), (4.0, 4.0), (4.0, 4.0), (2.0, 2.0), (2.0, 2.0), (2.0, 2.0), (2.0, 2.0), (2.0, 2.0), (2.0, 2.0), (2.0, 2.0), (2.0, 2.0), (2.0, 2.0), (2.0, 2.0), (4.0, 4.0)]
Decision Tree depth: 4
Decision Tree number of nodes: 25


Is the error is low enough?

te_count = true_vs_predicted_dt.count()
err_1 = true_vs_predicted_dt.filter(lambda (v, p): v != p).count() / float(te_count)
print "Error percentage: " + str(err_1)

In this case, the model seems to be about 97% accurate, which is pretty good for a prediction model.

Out:
Error percentage: 3.30188679245%

Based on a quick scan of the main splits in the decision tree, we will illustrate the classification using a 3D plot of  3 significant parameters (Clump Thickness, Uniformity of Cell Size, Bare Nuclei). The colours indicate the predicted class - Blue for Benign and Red for Malignant. A word of caution: it is certainly not accurate to say that these are the most significant features as there are other splits in the tree and tuning the model may reveal other significant features. It is left as an exercise to try pruning the model and build a minimal tree using just a few features and compare the performance and accuracy against the full set of predictors and different model hyper-parameters.


3D Plot of Classification based on 3 significant features


References:

[1]: Diagnosis of Breast Cancer using Decision Tree Models and SVM, Alaa. M. Elsayad,H. A. Elsalamony

Wednesday, 3 December 2014

Digital Transformation at Uk.Gov

This week I attended the “Digital Transformation” event hosted by Dare and it did not disappoint. Leading off was Russell Davies – Head of Creative Strategy for the UK Government Digital Services initiative and a proponent of “Usability trumps Persuasion” paradigm in digital service platforms. In his own inimitable style with a dramatic opening to the 20th Century Fox sound track, he described the process they had gone through at uk.gov to transform the experience for the citizen.

According to Russell, Digital Transformation is about "
making services better for everyone, not just people who use the website”


The strategic vision for the GDS programme can be summarised as:


The estimated economic impact of this vision, driving the Citizen towards self-discovery and self-service has been about 60 millions pounds of annual savings.

The process of digital transformation started with pulling together multi-disciplinary teams to map all the hundreds of services and microsites and identify essential services and weeding out stuff that wasn’t needed.  The transformation delivery paradigm was built around following key themes:

1
Understand user needs. Research to develop a deep knowledge of who the service users are and what that means for digital and assisted digital service design.
2
Put in place a sustainable multidisciplinary team that can design, build and operate the service, led by a suitably skilled and senior service manager with decision-making responsibility.
3
Analyse the prototype service’s success, and translate user feedback into features and tasks for the next phase of development.
4
Consistent User experience:  use a common design framework to ensure a intuitive, responsive experience which
5
No link left behind. Use tools to re-wire the service to the new platform without fan-fare so that the users do not notice.



Incremental Service Delivery

The platform delivery lifecycle was agile, user-centred and multidisciplinary teams distributed across several sites, delivering digital services in an incremental seamless manner.


Discovery: A short phase, in which you start researching the needs of your service’s users, find out what you should be measuring, and explore technological or policy-related constraints.

Alpha: A short phase in which you prototype solutions for your users needs. You’ll be testing with a small group of users or stakeholders, and getting early feedback about the design of the service.

Beta: You’re developing against the demands of a live environment, understanding how to build and scale while meeting user needs. You’ll also be releasing a version to test in public.

Live: The work doesn’t stop once your service is live. You’ll be iteratively improving your service, reacting to new needs and demands, and meeting targets set during its development.

Retirement: Even the best services may eventually reach retirement. That should be treated with the same care as went into the building and maintaining of that service.

One of the more controversial but immensely sensible thoughts Russell left us with was:


“ Don’t innovate, first get the basics right!”



Sunday, 5 October 2014

We don’t know anything about programming!
Moore’s Law has hit a limit with processing power on single processor peaking and computing systems shipping with multiple cores. Functional programming is the hot new topic in industries dealing with massive scale in date or internet scale loads. Functional programming is better suited to handling side effects of concurrency and distributed computing when compared to Imperative programming models. The lack of adequate supports on the tools programming particularly in debugging and testing have slowed the advance of functional programming and its widely anticipated growth. However, I find that the bigger challenge is the developers struggling to come to grips with the functional programming paradigms. Most programs written in functional languages (at least initially) seem to be clones of their Imperative cousins. Perhaps the fault lies in the indoctrination of the programming community about a particular way of programming (in this case Imperative) as the Universal truth. I came across this great video about the Future of Programming by Bret Victor. 



In his inimitable style he introduces hot paradigms today i.e. Dynamic Programming, Functional Programming (Can Programming Be Liberated from the von Neumann Style), Actor Model (embodied in Erlang, Akka), Massively Parallel Programming Array (FPGA). It does open your eyes to how many of these ‘hot’ paradigms have been around for so long. But the most compelling part of the talk was the last bit where he advocates keeping an open mind [1]It's good to learn how to do something. It's better to learn many ways of doing something. But it's best to learn all these ways as suggestions or hints, not truth.” [2] “Learn tools, and use tools, but don't accept tools. Always distrust them; always be alert for alternative ways of thinking.” [3]“I don't know how to make a machine that builds a person out of a cell. But I think the problem is that we've been stuck for too long diddling with our details. We've been sitting here worrying about our type system, when we should be worrying about how to get flexible machines and flexible programming.”
Like Bret says – ‘do not be ignorant, but we need to be aware of the limitations and strengths of different programming paradigms, and be willing to explore flexible approaches to programming and not get stuck too long diddling with the details’
Parallelism from ‘Scratch’ 
A year ago, my son aged 8 started showing an interest in programming after we installed a desktop in his bedroom. He loves the Scratch Programing tools, a fabulous Interactive Programming Environment from MIT Labs (remember Grail from Bret’s talk) create stories, games, and animations. Condescendingly, I decided to teach him the ‘right way’ to learn programming. Normally when you are teaching programmers, they are happy if their programs can display some web pages and interact with a data store, but to say that children are demanding would be a massive understatement, They want their characters to fight dragons, morph into aliens, scream, jump, jump, spin, get new weapons, and have multiple lives… It is a chaotic ‘Omniverse’ which would stress the best of programmers. It was amazing to see him use concepts from parallel programming coordinating game characters using messages without the concept of shared state or threading to manage concurrency. It just reinforces Bret Victor’s advocacy of free thinking – children are not limited by particular paradigms and aided by the right (visual and interactive) tools can surprise you with the complexity of their programs. Imagine creating some of these games (which are so simple for a kid to ideate, create and program) in a mainstream programming environment.

His First Program