计算机系统应用教程网站

网站首页 > 技术文章 正文

使用Python理解基本的机器学习-感知器和人工神经元

btikc 2024-10-11 11:21:19 技术文章 3 ℃ 0 评论

如果你正在学习机器学习,你的目标很可能是应用尖端的算法和“深度学习”来创建强大的应用程序。有了直观的API的广泛可用性,就可以实现这一点,只需对正在发生的事情或深层底层的实际工作方式进行最少的了解。

在这篇文章中,我们将介绍人工神经元和分类的起点,包括Perceptron和Adaline模型。对于每个模型,我们将讨论主要组件,然后将它们组合成完整的Python类实现。

感知器模型

机器学习最基本的起点是人工神经元。简化脑细胞的第一个模型发表于1943年,被称为McCullock-Pitts(MCP)神经元。本质上,这是一个带二进制输出('0'或'1')的基本逻辑门。如果到达神经元的输入之和超过给定阈值,则产生'1',其中每个输入乘以相应的权重系数以产生该总和。因此,神经元的激活在很大程度上取决于输入集和相应的权重系数值。

Frank Rosenblatt在出版后不久就采用了基本的MCP神经元概念,并制作了Perceptron规则算法。这 允许自动学习神经元模型的最佳权重系数。通过这样的模型,我们可以预测给定的输入是属于一个类还是另一个类(二元分类)。

感知器模型可能有任意数量的输入,也称为特征。由于我们通常具有多个特征,因此有助于将输入和权重表示为向量和矩阵。不要将输入样本与输入特征混淆; 每个数据样本都有许多特征n,它们通过我们的模型提供并一次处理一个(顺序)。例如,如果我们有100个汽车数据样本,其中每个样本具有m个特征(表示诸如最高速度,bhp,成本等),则每个数据样本x将具有m个特征的向量。每个输入要素都有相应的权重,经过训练后,这些权重将针对我们训练过的数据进行优化。

对于每个数据样本,我们的输入特征和权重作为感知器的加权和来获取,这通常称为net input function z。使用x和w上面的向量的好处是我们可以使用线性代数来紧凑地表示它,如下所示:

我们转换权重向量w,然后用我们的输入向量x进行向量乘法,它形成了我们需要的总和。 还应该注意的是,我们还使用偏差项来允许感知器模型中的阈值,该阈值等于矢量中的第一个权重项。为了实现这一点,我们的输入矩阵中的第一个x项始终设置为1.这意味着我们需要在将偏差项添加到输入向量中之前将其添加到模型中。

在净输入求和之后,我们的值被传递到感知器阈值或决策函数。在这种情况下,该功能是单位阶跃函数。如果输入高于给定值,则输出“1”,否则输出“0”。

那么我们的感知器如何实际从一组数据中学习呢?整体感知器过程如下:

  • 将权重参数初始化为初始值(通常是小的随机值,而不是零)。
  • 针对每个训练样本输入预测给定特征的二进制输出。
  • 通过Perceptron学习规则更新权重。

Perceptron的成本函数过于简单,被称为感知器学习规则。在每次训练迭代后,我们的权重向量中的每个权重都会发生很小的变化,如下所示:

:=表示法意味着我们同时更新权重向量中的所有权重,而不是迭代地更新。因此,进行输出预测,然后将所有权重参数更新为一个训练周期的一部分。上面的alpha术语也等于学习率,它决定了我们的模型在每个训练周期中学习的速度。

提供我们用于训练的数据是线性可分的,如果受到足够的训练周期(时期),感知器会将权重更新为最佳值。

Python中的Perceptron

我们首先在Python中编写Perceptron的每个组件,然后在最后将它们组合成一个更大的Perceptron类。对于这个基本模型,我们需要的唯一Python包是numpy和matplotlib。

样本数据

为此,我们将生成一个具有两个不同输出类的简单数据集,以便我们的感知器进行训练。注意:这个随机训练数据是通过随机函数生成的,并且不是完全可复制的。

为了帮助理解我们正在尝试做的事情,可视化我们的数据是很有帮助的。在这种情况下,我们的输入示例只有两个特性,因此用散点图来可视化数据很容易。数据生成,然后在散点图上绘图,如下所示

import matplotlib.pyplot as plt

import numpy as np

import pandas as pd

# make up random data for bird wingspans and weights for golden eagles and horned owls

bird_wingspans = np.concatenate([np.random.randint(170, 230, size = 50)/100.0,

np.random.randint(60, 100, size = 50)/100.0])

bird_weights = np.concatenate([(11 - 10)*np.random.randn(50)+10, np.abs(np.random.randn(50))+1])

# combine X vectors into a 2-dimensional array with 100 rows and 2 columns

X = np.vstack((bird_wingspans, bird_weights)).T

# create labels for our input data - first 50 are Golden Eagles (binary 0), last 50 are Horned Owls (binary 1)

y = np.concatenate([np.zeros(50), np.ones(50)])

# confirm shapes of generated data

print("The shape of our input matrix, X, is: {0}.".format(X.shape))

print("The shape of our output vector, y, is: {0}.".format(y.shape))

# plot our data on a scatter graph using matplotlib

# first 50 samples = Golden Eagle - plot both input features (columns 0 and 1 of X)

plt.scatter(X[:50, 0], X[:50, 1], color='r', marker='o', label='Golden Eagle')

# last 50 samples = Horned Owls

plt.scatter(X[50:, 0], X[50:, 1], color='b', marker='x', label = "Horned Owl")

plt.title("Bird Classification Sample Data")

plt.xlabel("Wingspan (metres)")

plt.ylabel("Weight (kilograms)")

plt.legend(loc = 'upper left')

plt.xlim([0, 2.5])

plt.ylim([0, 14])

plt.show()

生成初始权重

我们希望将权重初始化为小的随机数,而不是将它们初始化为零。我们需要这样做,否则我们的学习率会降低培训期间我们的分类结果。这背后的理由不会被涵盖,但是这是因为如果权重被初始化为零,学习率仅影响权重向量的尺度,而不影响方向。

这些权重可以以许多不同的方式产生。使用numpy.random的一个这样的例子是:

向X添加偏差项

对于偏差项,我们需要做的就是在输入数据X中添加1列。在numpy中,这可以使用ones和hstack numpy函数来实现。

加权和函数(净输入)

如上所述,由于我们使用矢量作为每个样本的输入特征及其相关权重,因此我们可以非常容易地计算净输入总和。在Python中使用numpy的向量化,取决于我们是否在训练数据中添加了1的偏置列,净输入函数变为:

用于预测的单位步长函数

如果我们的净输入和大于或等于零,我们想返回1,否则返回0。

感知器学习规则

使用前面图6中定义的学习规则,我们可以执行完整的训练周期。这包括循环遍历所有训练样本并根据感知器学习规则更新权重,如下所示:

把它们放在一起

使用numpy形成的Perceptron的基本组件,我们可以将它们拼凑在一起并实现完整的Perceptron二元分类器。使用分类器,我们可以在训练周期中可视化我们的错误,并且(希望!)看到随着时间的推移,当权重被优化时,错误的数量会减少。

class Perceptron(object):

""" Perceptron for demonstrating a binary classifier """

def __init__(self, learn_rate = 0.01, iterations = 100):

self.learn_rate = learn_rate

self.iterations = iterations

def fit(self, X, y, biased_X = False):

""" Fit training data to our model """

X = self._add_bias(X)

self._initialise_weights(X)

self.errors = []

for cycle in range(self.iterations):

trg_error = 0

for x_i, output in zip(X, y):

output_pred = self.predict(x_i, biased_X=True)

trg_update = self.learn_rate * (output - output_pred)

self.weights += trg_update * x_i

trg_error += int(trg_update != 0.0)

self.errors.append(trg_error)

return self

def _net_input(self, X):

""" Net input function (weighted sum) """

return np.dot(X, self.weights)

def predict(self, X, biased_X=False):

""" Make predictions for the given data, X, using unit step function """

if not biased_X:

X = self._add_bias(X)

return np.where(self._net_input(X) >= 0.0, 1, 0)

def _add_bias(self, X):

""" Add a bias column of 1's to our data, X """

bias = np.ones((X.shape[0], 1))

biased_X = np.hstack((bias, X))

return biased_X

def _initialise_weights(self, X):

""" Initialise weigths - normal distribution sample with standard dev 0.01 """

random_gen = np.random.RandomState(1)

self.weights = random_gen.normal(loc = 0.0, scale = 0.01, size = X.shape[1])

return self

# create a perceptron classifier and train on our data

classifier = Perceptron(learn_rate = 0.1, iterations = 50)

classifier.fit(X, y)

# plot our misclassification error after each iteration of training

plt.plot(range(1, len(classifier.errors) + 1), classifier.errors, marker = 'x')

plt.title("Visualisation of errors")

plt.xlabel('Epochs')

plt.ylabel('Errors')

plt.show()

正如可以在训练周期的误差图(epochs)中看到的,我们得到的误差数量随着时间的推移而减少。在大约7个周期之后,我们的模型将误差数量减少到零。我们的感知器训练后的决策边界显示在右边,它有效地将Golden Eagles和Horned Owls区分开来。我们可以使用我们训练过的模型,用一组新的输入X调用分类器预测函数,用新的数据进行预测

Perceptrons的一个问题是,如果提供的数据完全可线性分离,它们只会正确地优化权重。对于上面使用的数据,它是线性可分的。但是,如果我们使用更直观的数据(通常不能通过直线分离),那么我们的Perceptron分类器将永远不会停止尝试优化权重并且会不断出现错误。这是一个巨大的缺点,也是不使用Perceptrons的一个原因。然而,它们确实是对基本神经元和更复杂模型的良好介绍。

Adaline-自适应线性神经元

对原始感知器模型的改进是Adaline,它增加了一个用于优化权重的线性激活函数。通过此添加,使用连续成本函数而不是单位步骤。Adaline非常重要,因为它为更先进的机器学习模型奠定了基础。

unit step仍然会出现,正如您上面看到的,但是它只用于模型末尾的输出分类(' 1 '或' 0 ')。线性激活模型的连续输出值用于成本(或目标)函数。这表明我们的算法在训练数据上做得有多糟糕(或多好),并且可以通过众所周知的算法(如梯度下降)来最小化它。

成本函数

在这种情况下,我们将使用平方误差和作为我们的成本函数,可以这样实现:

梯度下降法

为了应用梯度下降,我们需要确定成本函数对每个权重的偏导数。通过微分,我们发现这个偏导数等于:

为了优化我们的权重,我们希望以我们的学习率参数选择的速率向我们的梯度的相反方向迈出一步。这背后的逻辑是最小化成本并最终达到全局最小值。因此,对于每个权重优化,我们需要应用:

我们可以将这个权重优化步骤和我们的成本计算步骤合并到一个训练周期代码片段中,如下所示:

特征标准化

除了激活功能和梯度下降优化,我们还将通过特征标准化来提高模型的性能。在机器学习中,这是一种常见的实践,它使优化过程在给定的时间内更快、更有效。为此,我们将从训练数据中的每个相关特征值中减去每个特征值的平均值,然后将特征值除以它们各自的标准偏差。

把它们放在一起

随着Adaline模型的其他组件的制定,我们可以像在Perceptron中一样在Python中生成最终实现。此Adaline实现如下所示:

class Adaline(object):

""" Adaline (Adaptive Linear Neuron) for binary classification.

Minimises the cost function using gradient descent. """

def __init__(self, learn_rate = 0.01, iterations = 100):

self.learn_rate = learn_rate

self.iterations = iterations

def fit(self, X, y, biased_X = False, standardised_X = False):

""" Fit training data to our model """

if not standardised_X:

X = self._standardise_features(X)

if not biased_X:

X = self._add_bias(X)

self._initialise_weights(X)

self.cost = []

for cycle in range(self.iterations):

output_pred = self._activation(self._net_input(X))

errors = y - output_pred

self.weights += (self.learn_rate * X.T.dot(errors))

cost = (errors**2).sum() / 2.0

self.cost.append(cost)

return self

def _net_input(self, X):

""" Net input function (weighted sum) """

return np.dot(X, self.weights)

def predict(self, X, biased_X=False):

""" Make predictions for the given data, X, using unit step function """

if not biased_X:

X = self._add_bias(X)

return np.where(self._activation(self._net_input(X)) >= 0.0, 1, 0)

def _add_bias(self, X):

""" Add a bias column of 1's to our data, X """

bias = np.ones((X.shape[0], 1))

biased_X = np.hstack((bias, X))

return biased_X

def _initialise_weights(self, X):

""" Initialise weigths - normal distribution sample with standard dev 0.01 """

random_gen = np.random.RandomState(1)

self.weights = random_gen.normal(loc = 0.0, scale = 0.01, size = X.shape[1])

return self

def _standardise_features(self, X):

""" Standardise our input features with zero mean and standard dev of 1 """

X_norm = (X - np.mean(X, axis=0)) / np.std(X, axis = 0)

return X_norm

def _activation(self, X):

""" Linear activation function - simply returns X """

return X

# create a perceptron classifier and train on our data

classifier = Adaline(learn_rate = 0.001, iterations = 50)

classifier.fit(X, y)

# plot our misclassification error after each iteration of training

plt.plot(range(1, len(classifier.cost) + 1), classifier.cost)

plt.title("Adaline: learn-rate 0.001")

plt.xlabel('Epochs')

plt.ylabel('Cost (Sum-of-Squares)')

plt.show()

我们可以看到为我们的模型设置超参数的重要性来自以下图中的基本Adaline示例; 如果我们选择0.1的学习率,则成本倾向于无穷大并且不会收敛,而如果我们选择0.001的学习率,则我们的模型成功地收敛到成本函数的全局最小值。迭代次数也很重要,应该足够高以充分降低成本。机器学习模型的这些调整方面被称为超参数,以及这些优化是我们创建任何模型的一个重要方面。

进一步改进和结束语

这种模型有很多方法可以进一步改进,包括随机梯度下降,它可以更频繁地进行优化,而不是像上面的模型那样使用整批训练数据。我们还可以使用One vs. All Muticlass Classification将其扩展到二元分类以外。

此外,还有比Adaline更好的分类器。Adaline是一个起点,您可以将其作为理解更复杂分类模型的基础,例如Logistic回归和神经网络。

在这篇文章中,我们研究了基本神经元的特征,然后使用Python和numpy构建了一个基本的Rosenblatt Perceptron模型。然后,我们构建了一个可用的Adaline分类器,通过其激活函数和成本函数优化,以多种方式改进了Perceptron。在此期间,我们还应用了一些有价值的特征处理,其中包括标准化,使梯度下降更快,更有效。

虽然这些模型不能替代通过Sci-kit Learn和其他软件包提供的专业构建和优化的模型,但它们应该提供对基本分类器如何工作的理解。我希望我们遵循的流程有助于您理解基本的机器学习分类,并为您提供使用Python实现这些模型的见解。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表