├── README.md
├── .gitignore
├── LICENSE
├── validation.py
├── perceptron.py
└── chart.py
/README.md:
--------------------------------------------------------------------------------
1 | # Simple perceptron
2 |
3 | A simple perceptron example in Python
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # any checkpoint
2 | **/.ipynb_checkpoints
3 |
4 | # IntelliJ IDEA
5 | .idea/
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Rodrigo E. Principe
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/validation.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Author: Rodrigo E. Principe
4 | # Email: fitoprincipe82 at gmail
5 |
6 |
7 | def test_dataset(dataset, bias, weights):
8 | for row in dataset:
9 | vector = row[0]
10 | expected = row[1]
11 | prediction = predict(vector, bias, weights)
12 | print("{} Expected={}, Predicted={}".format(vector, expected, prediction))
13 |
14 | # Split a dataset into k folds
15 | def cross_validation_split(dataset, n_folds):
16 | dataset_split = list()
17 | dataset_copy = list(dataset)
18 | fold_size = int(len(dataset) / n_folds)
19 | for i in range(n_folds):
20 | fold = list()
21 | while len(fold) < fold_size:
22 | index = randrange(len(dataset_copy))
23 | fold.append(dataset_copy.pop(index))
24 | dataset_split.append(fold)
25 | return dataset_split
26 |
27 | # Calculate accuracy percentage
28 | def accuracy_metric(actual, predicted):
29 | correct = 0
30 | for i in range(len(actual)):
31 | if actual[i] == predicted[i]:
32 | correct += 1
33 | return correct / float(len(actual)) * 100.0
34 |
35 | # Evaluate an algorithm using a cross validation split
36 | def evaluate_algorithm(dataset, algorithm, n_folds, *args):
37 | folds = cross_validation_split(dataset, n_folds)
38 | scores = list()
39 | for fold in folds:
40 | train_set = list(folds)
41 | train_set.remove(fold)
42 | train_set = sum(train_set, [])
43 | test_set = list()
44 | for row in fold:
45 | row_copy = list(row)
46 | test_set.append(row_copy)
47 | row_copy[-1] = None
48 | predicted = algorithm(train_set, test_set, *args)
49 | actual = [row[-1] for row in fold]
50 | accuracy = accuracy_metric(actual, predicted)
51 | scores.append(accuracy)
52 | return scores
53 |
--------------------------------------------------------------------------------
/perceptron.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Author: Rodrigo E. Principe
4 | # Email: fitoprincipe82 at gmail
5 |
6 | # Make a prediction with weights
7 | # one vector has n elements
8 | # p = (element1*weight1) + (element2*weight2) + (elementn*weightn) + bias
9 | # if p >= 0; 1; 0
10 | def predict(vector, bias, weights):
11 | pairs = zip(vector, weights) # [[element1, weight1], [..]]
12 | for (element, weight) in pairs:
13 | bias += element * weight
14 | return 1.0 if bias >= 0.0 else 0.0
15 |
16 | # Estimate Perceptron weights using stochastic gradient descent
17 | def train_weights(train, l_rate, n_epoch, bias=0):
18 | first_vector = train[0][0]
19 | # weights = [0.0 for i in range(len(first_vector))]
20 | weights = [random() for i in range(len(first_vector))]
21 | # iterate over the epochs
22 | for epoch in range(n_epoch):
23 | # each epoch has a sum_error, starting at 0
24 | sum_error = 0.0
25 | for row in train:
26 | vector = row[0]
27 | expected = row[1]
28 | prediction = predict(vector, bias, weights)
29 | error = expected - prediction
30 | sum_error += error**2
31 |
32 | # update activation (weights[0]))
33 | bias = bias + (l_rate * error)
34 |
35 | # update weights
36 | for i in range(len(vector)):
37 | # for each element of the vector
38 | weights[i] = weights[i] + (l_rate * error * vector[i])
39 |
40 | # print('>epoch={}, lrate={}, error={}'.format(epoch, l_rate, sum_error))
41 | return bias, weights
42 |
43 | # Perceptron Algorithm With Stochastic Gradient Descent
44 | def perceptron(train, test, l_rate, n_epoch):
45 | predictions = list()
46 | weights = train_weights(train, l_rate, n_epoch)
47 | for row in test:
48 | prediction = predict(row, weights)
49 | predictions.append(prediction)
50 | return(predictions)
51 |
--------------------------------------------------------------------------------
/chart.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Author: Rodrigo E. Principe
4 | # Email: fitoprincipe82 at gmail
5 |
6 | import pygal
7 | import math
8 |
9 |
10 | class Chart(pygal.XY):
11 | def __init__(self, **kwargs):
12 | super(Chart, self).__init__(**kwargs)
13 |
14 | def fill_chart(self, dataset):
15 | """ fill data of the chart with a dataset containing:
16 | [[[x1, y1], category1], [[x2, y2], category2],.., [[xn, yn], categoryn]]
17 | """
18 | # get categories
19 | categories = {}
20 | for row in dataset:
21 | category = row[1]
22 | categories[category] = []
23 |
24 | for row in dataset:
25 | data = row[0]
26 | category = row[1]
27 | categories[category].append(data)
28 |
29 | for cat, vector in categories.items():
30 | self.add(str(cat), vector)
31 |
32 | def add_trend_line(self, bias, weights, name='trend line'):
33 | xvalues = self.get_xvals()
34 | values = []
35 | for x in xvalues:
36 | y = (-weights[0]*x - bias)/weights[1]
37 | values.append([x, y])
38 | self.add(name, values, stroke=True)
39 |
40 | def get_xvals(self):
41 | raw = self.raw_series
42 | min_xs = []
43 | max_xs = []
44 | for serie in raw:
45 | values = serie[0]
46 | title = serie[1]['title']
47 | xs = [e[0] for e in values]
48 | min_xs.append(min(xs))
49 | max_xs.append(max(xs))
50 | min_value = math.floor(min(min_xs))
51 | max_value = math.floor(max(max_xs))
52 | return list(range(min_value, max_value+1))
53 |
54 | def render_widget(self, width=None, height=None):
55 | """ Render a pygal chart into a Jupyter Notebook """
56 | from ipywidgets import HTML
57 | import base64
58 |
59 | b64 = base64.b64encode(chart.render()).decode('utf-8')
60 |
61 | src = 'data:image/svg+xml;charset=utf-8;base64,'+b64
62 |
63 | if width and not height:
64 | html = ''.format(src, width)
65 | elif height and not width:
66 | html = ''.format(src, height)
67 | elif width and height:
68 | html = ''.format(src,
69 | height,
70 | width)
71 | else:
72 | html = '