Part of iPyMacLern project.
Copyright (C) 2016 by Eka A. Kurniawan
eka.a.kurniawan(ta)gmail(tod)com
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.
In [1]:
# Display graph inline
%matplotlib inline
# Display graph in 'retina' format for Mac with retina display. Others, use PNG or SVG format.
%config InlineBackend.figure_format = 'retina'
#%config InlineBackend.figure_format = 'PNG'
#%config InlineBackend.figure_format = 'SVG'
In [3]:
import sys
print("Python %s" % sys.version)
In [3]:
import numpy as np
print("NumPy %s" % np.__version__)
In [4]:
import matplotlib
import matplotlib.pyplot as plt
print("matplotlib %s" % matplotlib.__version__)
In [5]:
import scipy
import scipy.io as sio
print("SciPy %s" % scipy.__version__)
Plot classification boundary.
In [6]:
def plot_classification_boundary(X, w, J, labels):
# Get indices for negative/positive labels and true/false cost (J)
neg_label_idx = np.where(labels == False)[0]
pos_label_idx = np.where(labels == True)[0]
true_cost_idx = np.where(J == True)[0]
false_cost_idx = np.where(J == False)[0]
# Intersect indives for negative/positive labels to the true/false cost (J)
neg_true_idx = np.intersect1d(neg_label_idx, true_cost_idx)
neg_false_idx = np.intersect1d(neg_label_idx, false_cost_idx)
pos_true_idx = np.intersect1d(pos_label_idx, true_cost_idx)
pos_false_idx = np.intersect1d(pos_label_idx, false_cost_idx)
# Plot:
# - Negative labels with circle marker
# - Positive labels with triangle marker
# - True cost with green color
# - False cost with red color
# - Boundary in balck dashed line
plt.scatter(X[neg_true_idx,0], X[neg_true_idx,1], marker='o', color='green', s=80)
plt.scatter(X[neg_false_idx,0], X[neg_false_idx,1], marker='o', color='red', s=80)
plt.scatter(X[pos_true_idx,0], X[pos_true_idx,1], marker='^', color='green', s=80)
plt.scatter(X[pos_false_idx,0], X[pos_false_idx,1], marker='^', color='red', s=80)
if len(w):
plt.plot([-5.0,5.0], [(-w[2]+5*w[0])/w[1],(-w[2]-5*w[0])/w[1]], '--', color='black')
# Limit the plot in between -1 and 1 boundary for both x and y axis.
plt.ylim([-1.0,1.0])
plt.xlim([-1.0,1.0])
plt.show()
Plot training history.
In [7]:
def plot_training_history(ttl_errors_history, w_dist_history):
iteration_vector = np.arange(1, len(ttl_errors_history) + 1)
fig, ax1 = plt.subplots()
ax1.set_xlabel('Iteration')
if len(w_dist_history):
ax1.plot(iteration_vector, w_dist_history, marker='o', color='blue')
ax1.set_ylabel('Distance', color='blue')
ax2 = ax1.twinx()
ax2.plot(iteration_vector, ttl_errors_history, marker='*', color='red')
ax2.set_ylabel('Number of errors', color='red')
plt.show()
Load dataset from MATLAB formated data.$^{[1]}$
In [8]:
dataset = sio.loadmat('dataset1.mat')
In [9]:
dataset
Out[9]:
Get total features.
In [10]:
ttl_features = dataset['neg_examples_nobias'].shape[1]
print("Total features: ", ttl_features)
Multiple examples of two input variables (without bias) under negative class (target output is 0).
In [11]:
neg_examples_nobias = dataset['neg_examples_nobias']
In [12]:
neg_examples_nobias
Out[12]:
In [13]:
ttl_neg_examples = neg_examples_nobias.shape[0]
print("Total negative examples: ", ttl_neg_examples)
Construct negative examples with bias.
In [14]:
neg_examples = np.hstack([neg_examples_nobias, np.ones((ttl_neg_examples, 1))])
In [15]:
neg_examples
Out[15]:
Construct negative labels.
In [16]:
neg_labels = np.zeros(ttl_neg_examples, dtype=bool)
In [17]:
neg_labels
Out[17]:
Multiple examples of two input variables (without bias) under positive class (target output is 1).
In [18]:
pos_examples_nobias = dataset['pos_examples_nobias']
In [19]:
pos_examples_nobias
Out[19]:
In [20]:
ttl_pos_examples = pos_examples_nobias.shape[0]
print("Total positive examples: ", ttl_pos_examples)
Construct positive examples with bias.
In [21]:
pos_examples = np.hstack([pos_examples_nobias, np.ones((ttl_pos_examples, 1))])
In [22]:
pos_examples
Out[22]:
Construct positive labels.
In [23]:
pos_labels = np.ones(ttl_pos_examples, dtype=bool)
In [24]:
pos_labels
Out[24]:
Combined both negative and positive examples into $x$.
In [25]:
X = np.vstack([neg_examples, pos_examples])
In [26]:
X
Out[26]:
Combined both negative and positive labels.
In [27]:
labels = np.hstack([neg_labels, pos_labels])
In [28]:
labels
Out[28]:
Initial weight for two input variables with bias.
In [29]:
w_init = dataset.get('w_init', None)
In [30]:
w_init
Out[30]:
Get weight from initial weight or random generator.
In [31]:
if w_init is not None:
w = np.copy(np.transpose(w_init))[0]
else:
w = np.random.random_sample(ttl_features + 1)
In [32]:
w
Out[32]:
Provided or expected feasible wight with bias for comparison purposes.
In [33]:
w_gen_feas = dataset.get('w_gen_feas', [])
if len(w_gen_feas):
w_gen_feas = np.transpose(w_gen_feas)[0]
In [34]:
w_gen_feas
Out[34]:
In [35]:
def read_dataset(filename):
dataset = sio.loadmat(filename)
ttl_features = dataset['neg_examples_nobias'].shape[1]
neg_examples_nobias = dataset['neg_examples_nobias']
ttl_neg_examples = neg_examples_nobias.shape[0]
neg_examples = np.hstack([neg_examples_nobias, np.ones((ttl_neg_examples, 1))])
neg_labels = np.zeros(ttl_neg_examples, dtype=bool)
pos_examples_nobias = dataset['pos_examples_nobias']
ttl_pos_examples = pos_examples_nobias.shape[0]
pos_examples = np.hstack([pos_examples_nobias, np.ones((ttl_pos_examples, 1))])
pos_labels = np.ones(ttl_pos_examples, dtype=bool)
X = np.vstack([neg_examples, pos_examples])
labels = np.hstack([neg_labels, pos_labels])
w_init = dataset.get('w_init', None)
if w_init is not None:
w = np.copy(np.transpose(w_init))[0]
else:
w = np.random.random_sample(ttl_features + 1)
w_gen_feas = dataset.get('w_gen_feas', [])
if len(w_gen_feas):
w_gen_feas = np.transpose(w_gen_feas)[0]
return ttl_features, X, labels, w, w_gen_feas
Weighted sum $z$ of input neurons is calculated as follow.$^{[2,3]}$
Calculate weighted sum $z$.
In [36]:
Z = np.dot(X, w)
Z
Out[36]:
Decision unit of output $y$ from weighted sum $z$.$^{[2,3]}$
Output clustering $y$ using binary threshold neuron model.
In [37]:
Y = Z >= 0
Y
Out[37]:
In [38]:
labels
Out[38]:
Evaluate cost/error/mistake $J$. True means the clustering result agrees with given label. Whereas, False means the opposite.
In [39]:
J = Y == labels
J
Out[39]:
Overall perceptron evaluation function.
In [40]:
def evaluate_perceptron(X, w, labels):
Z = np.dot(X, w) # weighted sum
Y = Z >= 0 # binary threshold clustering
J = Y == labels # cost function
return J
Evaluate perceptron using initial weight.
In [41]:
J = evaluate_perceptron(X, w, labels)
plot_classification_boundary(X, w, J, labels)
Evaluate perceptron using feasible weight.
In [42]:
J = evaluate_perceptron(X, w_gen_feas, labels)
plot_classification_boundary(X, w_gen_feas, J, labels)
Perceptron learning algorithm$^{[2,3]}$:
In [43]:
def update_weights(X, w, labels):
for i, x in enumerate(X):
z = np.dot(x,w)
if labels[i] == False:
if z >= 0.0:
w = w - X[i]
else:
if z < 0.0:
w = w + X[i]
return w
Initial weight.
In [44]:
ttl_features, X, labels, w, w_gen_feas = read_dataset('dataset1.mat')
J = evaluate_perceptron(X, w, labels)
plot_classification_boundary(X, w, J, labels)
First iteration.
In [45]:
w = update_weights(X, w, labels)
J = evaluate_perceptron(X, w, labels)
plot_classification_boundary(X, w, J, labels)
Second iteration.
In [46]:
w = update_weights(X, w, labels)
J = evaluate_perceptron(X, w, labels)
plot_classification_boundary(X, w, J, labels)
In [47]:
def perform_perceptron_training(dataset_filename, max_iteration):
ttl_features, X, labels, w, w_gen_feas = read_dataset(dataset_filename)
ttl_labels = len(labels)
J = evaluate_perceptron(X, w, labels)
ttl_errors = ttl_labels - np.count_nonzero(J)
print("Number of errors in iteration %d: %2d" % (0, ttl_errors))
plot_classification_boundary(X, w, J, labels)
ttl_errors_history = []
w_dist_history = []
for i in range(1, max_iteration):
w = update_weights(X, w, labels)
J = evaluate_perceptron(X, w, labels)
ttl_errors = ttl_labels - np.count_nonzero(J)
print("Number of errors in iteration %d: %2d" % (i, ttl_errors))
ttl_errors_history.append(ttl_errors)
if len(w_gen_feas):
w_dist_history.append(np.linalg.norm(w - w_gen_feas))
plot_classification_boundary(X, w, J, labels)
if ttl_errors <= 0:
break
return ttl_errors_history, w_dist_history
In [48]:
ttl_errors_history, w_dist_history = perform_perceptron_training('dataset1.mat', 21)
In [49]:
plot_training_history(ttl_errors_history, w_dist_history)