Perceptron#
Perceptron algoritması, iki sınıfı ayıran bir hiperdüzlem için en uygun ağırlıkları bulur. Bu problem ikili sınıflandırma olarak da bilinir.
Kitaplıkları içe aktaralım.
import numpy as np
# %matplotlib inline
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from celluloid import Camera
import scienceplots
from IPython.display import Image
np.random.seed(0)
plt.style.use(["science", "no-latex"])
Eğitim Veri Kümesi#
Etiketin doğrusal bir karar sınırıyla belirlendiği bir veri kümesi oluşturalım. Perceptron’umuz, bu veri kümesini iki sınıfa ayıracak normal vektörün ağırlıklarını öğrenecek.
def generate_dataset(dims, normal_vector):
# create 3D grid of points
points = np.linspace(-1, 1, dims)
X, Y, Z = np.meshgrid(points, points, points)
# features are the x, y, z coordinates
features = np.column_stack((X.ravel(), Y.ravel(), Z.ravel()))
# labels are the side each point is on the hyperplane
distances = np.dot(features, normal_vector)
labels = np.where(distances >= 0, 1, -1)
return X, Y, Z, features, labels
# normalized normal vector
target_normal_vector = np.array([1.0, 1.0, 1.0])
target_normal_vector = target_normal_vector / np.linalg.norm(target_normal_vector)
scaling = 5
X, Y, Z, features, labels = generate_dataset(scaling, target_normal_vector)
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
# plot the points
ax.scatter(features[:,0], features[:,1], features[:,2], marker='o', alpha=0.3, c=labels)
<mpl_toolkits.mplot3d.art3d.Path3DCollection at 0x10b154950>
Hiperdüzlem#
Hiperdüzlem, bulunduğu uzaydan bir boyut daha düşük olan düz bir alt uzaydır. Bir veri kümesini doğrusal olarak ayırmak için kullanılabilir. Bir hiperdüzlemin denklemi, ona dik olan \(\vec{w}\) normal vektörüyle tanımlanır.
\( \begin{align*} \vec{w} \cdot \vec{x} = w_1 x_1 + ... + w_n x_n = 0 \end{align*} \)
Bizim örneğimizde \(\vec{x}\), x, y, z koordinatlarını ifade eder.
\( \begin{align*} \vec{w} \cdot \vec{x} &= 0 \\ &= w_1 x + w_2 y + w_3 z \end{align*} \)
İkili sınıflandırma yaparken bir noktanın hiperdüzlemin hangi tarafında kaldığını kullanmak istediğimiz için, z değeri tahmin ettiğimiz etiket olabilir.
\( \begin{align*} z = -(w_1 x + w_2 y) / w_3 \end{align*} \)
def generate_hyperplane(scaling, normal_vector):
# create 2D points
points = np.linspace(-1, 1, scaling)
xx, yy = np.meshgrid(points, points)
# the z value is the defined by the hyperplane
zz = -(normal_vector[0] * xx + normal_vector[1] * yy) / normal_vector[2]
return xx, yy, zz
xx, yy, zz = generate_hyperplane(scaling, target_normal_vector)
# visualize the hyperplane
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
# plot the hiper düzlem defined by the normal vector
ax.plot_surface(xx, yy, zz, alpha=0.2, color="gray")
ax.quiver(
0,
0,
0,
target_normal_vector[0],
target_normal_vector[1],
target_normal_vector[2],
color="green",
length=1,
arrow_length_ratio=0.2,
)
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
ax.set_title("Hiper Düzlem")
Text(0.5, 0.92, 'Hiper Düzlem')
Kayıp Fonksiyonu: Menteşe Kaybı#
Kayıp fonksiyonları, bir tahminin hatasını sayısallaştırmak için kullanılır.
Perceptron, doğru sınıflandırılan örneklerde 0; yanlış sınıflandırılan ya da sınırda kalan örneklerde ise pozitif bir değer üreten menteşe kaybını kullanır.
\( \begin{align*} L(\vec{w}, b) = max(0, -y(\vec{w} \cdot \vec{x} + b)) \end{align*} \)
def hinge_loss(w, x, b, y):
return max(0.0, -y * (np.dot(w, x) + b))
Menteşe Kaybının Gradyanı#
Parametreleri gradyan inişiyle güncelleyebilmek için, kaybın \(W\) ve \(b\)’ye göre türevlerini bulmamız gerekir.
Menteşe Kaybının \(B\) Cinsinden Türevi#
Kayıp fonksiyonunun \(b\)’ye göre türevi:
\( \frac{\partial L}{\partial b} = \begin{cases} 0 & -y(\vec{w} \cdot \vec{x} + b) > 1 \\ -y & \text{aksi halde} \end{cases} \)
def hinge_loss_db(w, x, b, y):
if y * (np.dot(w, x) + b) <= 0.0:
return -y
return 0
Menteşe Kaybının \(W\) Cinsinden Türevi#
Kayıp fonksiyonunun \(\vec{w}\)’ye göre türevi:
\( \nabla_{W} [L(\vec{w}, b)] = \begin{cases} 0 & -y(\vec{w} \cdot \vec{x} + b) > 1 \\ -y x & \text{aksi halde} \end{cases} \)
def hinge_loss_dw(w, x, b, y):
if y * (np.dot(w, x) + b) <= 0.0:
return -y * x
return np.zeros_like(x)
Grafik Fonksiyonları#
Görselleştirmeleri üretmek için yardımcı fonksiyonlar.
def create_plots():
fig, ax = plt.subplots(2, 3, figsize=(16 / 9.0 * 4, 4 * 1), layout="constrained")
fig.suptitle("Perceptron")
ax[0, 0].set_xlabel("Epok", fontweight="normal")
ax[0, 0].set_ylabel("Hata", fontweight="normal")
ax[0, 0].set_title("Menteşe Kaybı")
ax[1, 0].set_xlabel("Z, Hiper Düzleme Uzaklık", fontweight="normal")
ax[1, 0].set_ylabel("", fontweight="normal")
ax[1, 0].set_title("Doğrusal Dönüşüm")
ax[0, 1].axis("off")
ax[0, 2].axis("off")
ax[1, 1].axis("off")
ax[1, 2].axis("off")
ax[1, 2] = fig.add_subplot(1, 2, 2, projection="3d")
ax[1, 2].set_xlabel("X")
ax[1, 2].set_ylabel("Y")
ax[1, 2].set_zlabel("Z")
ax[1, 2].set_title("Hiper Düzlem Karar Sınırı")
ax[1, 2].view_init(20, -35)
ax[1, 2].set_xlim(-1, 1)
ax[1, 2].set_ylim(-1, 1)
ax[1, 2].set_zlim(-1, 1)
camera = Camera(fig)
return ax[0, 0], ax[1, 0], ax[1, 2], camera
def plot_graphs(
ax0,
ax1,
ax2,
idx,
visible_err,
err_idx,
errors,
scaling,
target_normal_vector,
predictions,
features,
labels,
weights,
):
ax0.plot(
err_idx[visible_err][: idx + 1],
errors[visible_err][: idx + 1],
color="red",
)
# Ground truth
xx_target, yy_target, zz_target = generate_hyperplane(scaling, target_normal_vector)
ground_truth_legend = ax2.plot_surface(
xx_target,
yy_target,
zz_target,
color="red",
alpha=0.2,
label="Gerçek Etiket",
)
ax2.quiver(
0,
0,
0,
target_normal_vector[0],
target_normal_vector[1],
target_normal_vector[2],
color="red",
length=1,
arrow_length_ratio=0.1,
)
# Perceptron predictions using 2D graph to show linear transformation
def generate_colors(arr):
return ["green" if d >= 0 else "orange" for d in arr]
ground_truth_colors = generate_colors(labels)
ax1.scatter(
predictions,
np.zeros(predictions.shape),
c=ground_truth_colors,
marker="o",
alpha=0.3,
)
# Perceptron predictions using 3D graph to show hyperplane
predictions_colors = generate_colors(predictions)
predictions_norm = np.maximum(1 - np.exp(-(predictions**2)), 0.2)
ax2.scatter(
features[:, 0],
features[:, 1],
features[:, 2],
c=predictions_colors,
marker="o",
alpha=predictions_norm,
)
xx, yy, zz = generate_hyperplane(scaling, weights)
predictions_legend = ax2.plot_surface(
xx,
yy,
zz,
color="blue",
alpha=0.2,
label="Tahmin",
)
ax2.quiver(
0,
0,
0,
weights[0],
weights[1],
weights[2],
color="blue",
length=1,
arrow_length_ratio=0.1,
)
# Legend
ax2.legend(
(ground_truth_legend, predictions_legend),
("Gerçek Etiket", "Tahminler"),
loc="upper left",
)
Gradyan İnişi#
Ağırlıkları ve bias terimini güncellemek için gradyan inişini kullanacağız.
Bias terimi için gradyan inişi:
\( \begin{align*} b &= b - \eta \frac{\partial}{\partial b} [L(\vec{w}, b)] \\ &= b - \eta \begin{cases} 0 & -y(\vec{w} \cdot \vec{x} + b) > 1 \\ -y & \text{aksi halde} \end{cases} \end{align*} \)
Ağırlıklar için gradyan inişi:
\( \begin{align*} \vec{w} &= \vec{w} - \eta \nabla_{W} [L(\vec{w}, b)] \\ &= \vec{w} - \eta \begin{cases} 0 & -y(\vec{w} \cdot \vec{x} + b) > 1 \\ -y x & \text{aksi halde} \end{cases} \end{align*} \)
def gradient_descent(weights, x, bias, y, learning_rate):
weights = weights - learning_rate * hinge_loss_dw(weights, x, bias, y)
bias = bias - learning_rate * hinge_loss_db(weights, x, bias, y)
return weights, bias
Modeli Eğitmek#
def fit(
weights,
bias,
target_normal_vector,
features,
labels,
X,
Y,
Z,
scaling,
epochs,
learning_rate,
optimizer,
output_filename,
):
err_idx = np.arange(1, epochs + 1)
errors = np.full(epochs, -1)
ax0, ax1, ax2, camera = create_plots()
for idx in range(epochs):
error = 0
predictions = np.array([])
for x, y in zip(features, labels):
# Forward Propagation
output = np.dot(weights, x) + bias
predictions = np.append(predictions, output)
# Store Hata
error += hinge_loss(weights, x, bias, y)
# Gradyan İnişi
weights, bias = optimizer(weights, x, bias, y, learning_rate)
error /= len(X)
weights = weights / np.linalg.norm(weights)
if (
idx < 5
or (idx < 15 and idx % 2 == 0)
or (idx <= 50 and idx % 10 == 0)
or (idx <= 1000 and idx % 20 == 0)
or idx % 250 == 0
):
print(f"epok: {idx}, MSE: {error}")
# Plot MSE
errors[idx] = error
visible_err = errors != -1
plot_graphs(
ax0,
ax1,
ax2,
idx,
visible_err,
err_idx,
errors,
scaling,
target_normal_vector,
predictions,
features,
labels,
weights,
)
camera.snap()
animation = camera.animate()
animation.save(output_filename, writer="pillow")
plt.show()
Şimdi her şeyi bir araya getirip Perceptron modelimizi eğitelim.
weights = np.array([1.0, -1.0, -1.0])
weights = weights / np.linalg.norm(weights)
bias = 0
epochs = 301
learning_rate = 0.0005
output_filename = "perceptron.gif"
fit(
weights,
bias,
target_normal_vector,
features,
labels,
X,
Y,
Z,
scaling,
epochs,
learning_rate,
gradient_descent,
output_filename,
)
epok: 0, MSE: 9.314269414709884
epok: 1, MSE: 9.227024830601328
epok: 2, MSE: 9.145087443313994
epok: 3, MSE: 9.054781136556876
epok: 4, MSE: 8.960407022428585
epok: 6, MSE: 8.758853058777223
epok: 8, MSE: 8.53921460232333
epok: 10, MSE: 8.301374396907338
epok: 12, MSE: 8.08268967439665
epok: 14, MSE: 7.848010649913292
epok: 20, MSE: 7.2399656649458395
epok: 30, MSE: 6.264254344109916
epok: 40, MSE: 5.307156953825982
epok: 50, MSE: 4.404348110031334
epok: 60, MSE: 3.583744302061389
epok: 80, MSE: 2.0340687540122784
epok: 100, MSE: 0.9393759997635176
epok: 120, MSE: 0.3544588382632633
epok: 140, MSE: 0.12744345824321832
epok: 160, MSE: 0.018715050957434115
epok: 180, MSE: 0.0
epok: 200, MSE: 0.0
epok: 220, MSE: 0.0
epok: 240, MSE: 0.0
epok: 250, MSE: 0.0
epok: 260, MSE: 0.0
epok: 280, MSE: 0.0
epok: 300, MSE: 0.0
/var/folders/x3/xmq8mst51mjb124_9bh98jy80000gn/T/ipykernel_57426/2015002447.py:73: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown
plt.show()
Çıktı Animasyonu#
Image(filename=output_filename)