mirlo 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,13 @@
1
+ module Mirlo
2
+ class AndDataSet < Dataset
3
+ def initialize
4
+ @feature_names = ['x', 'y']
5
+ @title = "Logical AND dataset"
6
+
7
+ samples = [[0,0], [0,1], [1,0], [1,1]]
8
+ targets = [ZERO, ZERO, ZERO, ONE]
9
+
10
+ super(samples: samples, targets: targets)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,43 @@
1
+ module Mirlo
2
+ class DoubleMoonDataSet < Dataset
3
+ attr_reader :radius, :width, :distance
4
+
5
+ DEFAULT_RADIUS = 10
6
+ DEFAULT_WIDTH = 6
7
+ DEFAULT_DISTANCE = 2
8
+
9
+ UPPER_MOON = [1]
10
+ LOWER_MOON = [0]
11
+
12
+ def initialize(n_points: 500, radius: DEFAULT_RADIUS, width: DEFAULT_WIDTH, distance: DEFAULT_DISTANCE)
13
+ feature_names = ['x', 'y']
14
+ title = "Double Moon Dataset with radius:=#{radius}, width:=#{width}, distance:=#{distance}"
15
+ @radius, @width, @distance = radius, width, distance
16
+ samples = n_points.times.collect { random_point }
17
+
18
+ labels = {
19
+ UPPER_MOON => 'Upper moon',
20
+ LOWER_MOON => 'Lower moon'
21
+ }
22
+
23
+ super(samples: samples, feature_names: feature_names, title: title, labels: labels)
24
+ end
25
+
26
+ def random_point
27
+ angle_coord = rand * Math::PI
28
+ radial_coord = radius + width * rand(-0.5..0.5)
29
+
30
+ target = rand(2) == 1 ? UPPER_MOON : LOWER_MOON
31
+
32
+ if target == UPPER_MOON
33
+ x = radial_coord * Math.cos(angle_coord)
34
+ y = radial_coord * Math.sin(angle_coord)
35
+ else
36
+ x = radial_coord * Math.cos(angle_coord) + radius
37
+ y = - radial_coord * Math.sin(angle_coord) - distance
38
+ end
39
+
40
+ SampleWithBias.new(target: target, features: [x, y])
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,13 @@
1
+ module Mirlo
2
+ class OrDataSet < Dataset
3
+ def initialize
4
+ @feature_names = ['x', 'y']
5
+ @title = "Logical OR dataset"
6
+
7
+ samples = [[0,0], [0,1], [1,0], [1,1]]
8
+ targets = [ZERO, ONE, ONE, ONE]
9
+
10
+ super(samples: samples, targets: targets)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Mirlo
2
+ class XorDataSet < Mirlo::Dataset
3
+ def initialize
4
+ @feature_names = ['x', 'y']
5
+ @title = "Logical XOR dataset"
6
+
7
+ samples = [[0,0], [0,1], [1,0], [1,1]]
8
+ targets = [ZERO, ONE, ONE, ZERO]
9
+
10
+ super(samples: samples, targets: targets)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,27 @@
1
+ require 'matrix'
2
+
3
+ class Matrix
4
+
5
+ def shape
6
+ [row_count, column_count]
7
+ end
8
+
9
+ #
10
+ # Public: given two matrices of equal dimensions, apply an operation elementwise.
11
+ #
12
+ # Returns a new matrix with the results of the operation.
13
+ #
14
+ def apply_elementwise(other, &op)
15
+ unless shape == other.shape
16
+ raise ArgumentError.new 'To perform an element wise operation, matrices must be of the same dimension.'
17
+ end
18
+
19
+ new_rows = row_count.times.collect do |row|
20
+ column_count.times.collect do |column|
21
+ op.call(self[row, column], other[row, column])
22
+ end
23
+ end
24
+
25
+ Matrix.rows(new_rows)
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ module Mirlo
2
+ module Plotting
3
+ def plot(x_feature = nil, y_feature = nil)
4
+ Gnuplot.open do |gp|
5
+ Gnuplot::Plot.new(gp) do |plot|
6
+ plot.title title
7
+ plot.xlabel 'x'
8
+ plot.ylabel 'y'
9
+
10
+ plot.data = to_gnu_plot_datasets
11
+ end
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def to_gnu_plot_datasets
18
+ target_set.each_with_index.collect do |target, i|
19
+ subset = subset_with_target(target)
20
+ x = subset.feature(0)
21
+ y = subset.feature(1)
22
+
23
+ Gnuplot::DataSet.new([x, y]) do |ds|
24
+ ds.title = label_for(target)
25
+ ds.with = "points ls #{i+1} lc rgb \"red\""
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,34 @@
1
+ class Mirlo::Sample
2
+ attr_reader :target, :features
3
+
4
+ def initialize(target: [], features: [])
5
+ @target = target.is_a?(Array) ? target : [target]
6
+ @features = features
7
+ end
8
+
9
+ def [](index)
10
+ @features[index]
11
+ end
12
+
13
+ def has_features?(some_features)
14
+ features == some_features
15
+ end
16
+
17
+ def feature_size
18
+ features.size
19
+ end
20
+
21
+ def target_size
22
+ target.size
23
+ end
24
+
25
+ def biased?
26
+ false
27
+ end
28
+
29
+ def ==(other_sample)
30
+ target == other_sample.target &&
31
+ features == other_sample.features &&
32
+ biased? == other.biased?
33
+ end
34
+ end
@@ -0,0 +1,19 @@
1
+ class Mirlo::SampleWithBias < Mirlo::Sample
2
+
3
+ def initialize(target: [], features: [])
4
+ super(target: target)
5
+ @features = features.dup.unshift(-1)
6
+ end
7
+
8
+ def [](index)
9
+ super(index+1)
10
+ end
11
+
12
+ def has_features?(some_features)
13
+ features == some_features.dup.unshift(-1)
14
+ end
15
+
16
+ def biased?
17
+ true
18
+ end
19
+ end
@@ -0,0 +1,49 @@
1
+ class Mirlo::TestResult
2
+ attr_reader :n_samples
3
+
4
+ def initialize(possible_classes = [])
5
+ @possible_classes = possible_classes
6
+ @confusion_matrix = Hash.new { 0 }
7
+ @n_samples = 0
8
+ end
9
+
10
+ def add(sample, prediction)
11
+ @possible_classes << sample.target unless @possible_classes.include?(sample.target)
12
+ @confusion_matrix[[sample.target, prediction]] += 1
13
+ @n_samples += 1
14
+ end
15
+
16
+ def confusion_matrix(expected, prediction)
17
+ @confusion_matrix[[expected, prediction]]
18
+ end
19
+
20
+ def mean_squared_error
21
+ errors = @confusion_matrix.collect do |results, times|
22
+ expected, prediction = results
23
+ error_for(expected, prediction, times)
24
+ end
25
+
26
+ errors.inject(:+)
27
+ end
28
+
29
+ def n_errors
30
+ errors = @confusion_matrix.select do |results, times|
31
+ expected, prediction = results
32
+ expected != prediction
33
+ end
34
+
35
+ errors.collect { |results, times| times }.inject(:+)
36
+ end
37
+
38
+ def error_percentage
39
+ n_errors.to_f/n_samples
40
+ end
41
+
42
+ private
43
+
44
+ def error_for(expected, prediction, times)
45
+ diffs = expected.each_with_index.collect { |expected_val, i| expected_val - prediction[i] }
46
+ squared_errors = diffs.collect { |diff| diff ** 2 }
47
+ squared_errors.inject(:+) * times
48
+ end
49
+ end
@@ -0,0 +1,3 @@
1
+ module Mirlo
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mirlo/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "mirlo"
8
+ spec.version = Mirlo::VERSION
9
+ spec.authors = ["Alberto F. Capel"]
10
+ spec.email = ["afcapel@gmail.com"]
11
+ spec.description = %q{Machine Learning experiments}
12
+ spec.summary = %q{Implementation of some Machine Learning algorithms}
13
+ spec.homepage = "https://github.com/afcapel/mirlo"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+
25
+ spec.add_dependency "gnuplot"
26
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'ANN DSL' do
4
+ before :each do
5
+ @ann = Mirlo::ANN.build do
6
+ learning_rate 0.25
7
+
8
+ input_layer 3
9
+ hidden_layer 3
10
+ hidden_layer 2
11
+ output_layer 3
12
+ end
13
+ end
14
+
15
+ it "should build a multilayer perceptron" do
16
+ expect(@ann).to be_kind_of(Mirlo::MultilayerPerceptron)
17
+ end
18
+
19
+ it "can set the learning rate of the neural network" do
20
+ expect(@ann.learning_rate).to eq 0.25
21
+ end
22
+
23
+ it "can set the number of inputs on the input layer" do
24
+ expect(@ann.input_layer.size).to eq 4 # 3 inputs plus the bias
25
+ end
26
+
27
+ it "can define hidden layers on the network" do
28
+ expect(@ann.hidden_layers).to be_kind_of(Array)
29
+ expect(@ann.hidden_layers.size).to eq 2
30
+
31
+ expect(@ann.hidden_layers[0].size).to eq 3
32
+ expect(@ann.hidden_layers[1].size).to eq 2
33
+ end
34
+
35
+ it "set the connections between layers" do
36
+ expect(@ann.layers[0].next_layer).to eq @ann.layers[1]
37
+ expect(@ann.layers[1].next_layer).to eq @ann.layers[2]
38
+ expect(@ann.layers[2].next_layer).to eq @ann.layers[3]
39
+
40
+ expect(@ann.layers[1].previous_layer).to eq @ann.layers[0]
41
+ expect(@ann.layers[2].previous_layer).to eq @ann.layers[1]
42
+ expect(@ann.layers[3].previous_layer).to eq @ann.layers[2]
43
+ end
44
+
45
+ it "can set the number of outputs on the output layer" do
46
+ expect(@ann.output_layer.size).to eq 3
47
+ end
48
+
49
+ it "defines the weight matrices between layers" do
50
+ weights1 = @ann.layers[1].weights
51
+ expect(weights1.row_count).to eq 4 # 3 inputs plus the bias
52
+ expect(weights1.column_count).to eq 3
53
+
54
+
55
+
56
+ weights2 = @ann.layers[2].weights
57
+ expect(weights2.row_count).to eq 3
58
+ expect(weights2.column_count).to eq 2
59
+ end
60
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mirlo::MultilayerPerceptron do
4
+
5
+ let(:mlp) do
6
+ Mirlo::ANN.build do
7
+ input_layer 2
8
+ hidden_layer 3
9
+ output_layer 1
10
+ end
11
+ end
12
+
13
+ it "can classify all data points of the OR logical function" do
14
+ data_set = Mirlo::OrDataSet.new
15
+
16
+ mlp.train_until(data_set, max_error: 0.0, max_iterations: 50_000)
17
+
18
+ expect(mlp.classify([0,0])).to eq [0]
19
+ expect(mlp.classify([0,1])).to eq [1]
20
+ expect(mlp.classify([1,0])).to eq [1]
21
+ expect(mlp.classify([1,1])).to eq [1]
22
+
23
+ test_result = mlp.test_with(data_set)
24
+ expect(test_result.mean_squared_error).to eq 0.0
25
+ end
26
+
27
+ it "can classify all data points of the AND logical function" do
28
+ data_set = Mirlo::AndDataSet.new
29
+
30
+ mlp.train_until(data_set, max_error: 0.0, max_iterations: 50_000)
31
+
32
+ expect(mlp.classify([0,0])).to eq [0]
33
+ expect(mlp.classify([0,1])).to eq [0]
34
+ expect(mlp.classify([1,0])).to eq [0]
35
+ expect(mlp.classify([1,1])).to eq [1]
36
+
37
+ test_result = mlp.test_with(data_set)
38
+ expect(test_result.mean_squared_error).to eq 0.0
39
+ end
40
+
41
+ it "can classify all data points of the XOR logical function" do
42
+ data_set = Mirlo::XorDataSet.new
43
+
44
+ mlp.train_until(data_set, max_error: 0.0, max_iterations: 50_000)
45
+
46
+ expect(mlp.classify([0,0])).to eq [0]
47
+ expect(mlp.classify([0,1])).to eq [1]
48
+ expect(mlp.classify([1,0])).to eq [1]
49
+ expect(mlp.classify([1,1])).to eq [0]
50
+
51
+ test_result = mlp.test_with(data_set)
52
+ expect(test_result.mean_squared_error).to eq 0.0
53
+ end
54
+
55
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mirlo::NeuronLayer do
4
+ let(:previous_layer) do
5
+ input_layer = Mirlo::InputLayer.new(2)
6
+ input_layer.input = [0.5, 1] # A first -1 bias component will be added
7
+ input_layer
8
+ end
9
+
10
+ let(:hidden_layer) do
11
+ hidden_layer = Mirlo::NeuronLayer.new(2)
12
+ hidden_layer.previous_layer = previous_layer
13
+ hidden_layer
14
+ end
15
+
16
+ it "has a matrix of weights" do
17
+ expect(hidden_layer.weights.shape).to eq [3, 2]
18
+ end
19
+
20
+ context "with given weights" do
21
+ before :each do
22
+ hidden_layer.build_weight_function = -> { 0.5 }
23
+ end
24
+
25
+ it "allows to set a function to build the weights matrix" do
26
+ hidden_layer.weights.each do |elm|
27
+ expect(elm).to eq 0.5
28
+ end
29
+ end
30
+
31
+ it "can calculate the total input for each neuron" do
32
+ total_inputs = hidden_layer.inputs_matrix.row(0)
33
+
34
+ expect(total_inputs[0]).to be_within(0.00001).of 0.25
35
+ expect(total_inputs[1]).to be_within(0.00001).of 0.25
36
+ end
37
+
38
+ it "can calculate the activation of each neuron" do
39
+ activations = hidden_layer.activation_matrix.row(0)
40
+
41
+ expect(activations[0]).to be_within(0.00001).of 1.0/(1 + Math.exp(-0.25))
42
+ expect(activations[1]).to be_within(0.00001).of 1.0/(1 + Math.exp(-0.25))
43
+ end
44
+ end
45
+ end