├── 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 = ''.format(src) 73 | 74 | return HTML(html) 75 | 76 | --------------------------------------------------------------------------------