Perceptron

感知器#

本笔记本是 AI for Beginners Curricula 的一部分。访问该仓库以获取完整的学习资料。

正如我们所讨论的,感知器可以用来解决二分类问题,即将输入样本分类为两类——我们可以称之为正类负类

首先,让我们导入一些必要的库。

In [2]:
import pylab
from matplotlib import gridspec
from sklearn.datasets import make_classification
import numpy as np
from ipywidgets import interact, interactive, fixed
import ipywidgets as widgets
import pickle
import os
import gzip

# pick the seed for reproducability - change it to explore the effects of random variations
np.random.seed(1)
import random

玩具问题#

首先,让我们从一个简单的玩具问题开始,这里我们有两个输入特征。例如,在医学中,我们可能希望根据肿瘤的大小和年龄将其分类为良性或恶性。

我们将使用 SciKit Learn 库中的 make_classification 函数生成一个随机分类数据集:

In [3]:
n = 50
X, Y = make_classification(n_samples = n, n_features=2,
                           n_redundant=0, n_informative=2, flip_y=0)
Y = Y*2-1 # convert initial 0/1 values into -1/1
X = X.astype(np.float32); Y = Y.astype(np.int32) # features - float, label - int

# Split the dataset into training and test
train_x, test_x = np.split(X, [ n*8//10])
train_labels, test_labels = np.split(Y, [n*8//10])
print("Features:\n",train_x[0:4])
print("Labels:\n",train_labels[0:4])
Features:
 [[-1.7441838  -1.3952037 ]
 [ 2.5921783  -0.08124504]
 [ 0.9218062   0.91789985]
 [-0.8437018  -0.18738253]]
Labels:
 [-1 -1  1 -1]

让我们也绘制数据集:

In [4]:
def plot_dataset(suptitle, features, labels):
    # prepare the plot
    fig, ax = pylab.subplots(1, 1)
    #pylab.subplots_adjust(bottom=0.2, wspace=0.4)
    fig.suptitle(suptitle, fontsize = 16)
    ax.set_xlabel('$x_i[0]$ -- (feature 1)')
    ax.set_ylabel('$x_i[1]$ -- (feature 2)')

    colors = ['r' if l>0 else 'b' for l in labels]
    ax.scatter(features[:, 0], features[:, 1], marker='o', c=colors, s=100, alpha = 0.5)
    fig.show()

plot_dataset('Training data', train_x, train_labels)
<ipython-input-4-ade59d2ee664>:11: UserWarning: Matplotlib is currently using module://ipykernel.pylab.backend_inline, which is a non-GUI backend, so cannot show the figure.
  fig.show()
Notebook 输出图像

感知机#

由于感知机是一个二分类器,对于每个输入向量 $x$,感知机的输出将根据类别为 +1 或 -1。输出通过以下公式计算:

$$y(\mathbf{x}) = f(\mathbf{w}^{\mathrm{T}}\mathbf{x})$$

其中,$\mathbf{w}$ 是权重向量,$f$ 是阶跃激活函数: $$ f(x) = \begin{cases} +1 & x \geq 0 \ -1 & x < 0 \end{cases} \ $$

然而,一个通用的线性模型通常还需要一个偏置项,也就是说,理想情况下我们应该通过 $y=f(\mathbf{w}^{\mathrm{T}}\mathbf{x}+\mathbf{b})$ 来计算 $y$。为了简化我们的模型,我们可以通过在输入特征中添加一个始终等于 1 的维度来去掉这个偏置项:

In [5]:
pos_examples = np.array([ [t[0], t[1], 1] for i,t in enumerate(train_x) 
                          if train_labels[i]>0])
neg_examples = np.array([ [t[0], t[1], 1] for i,t in enumerate(train_x) 
                          if train_labels[i]<0])
print(pos_examples[0:3])
[[ 0.92180622  0.91789985  1.        ]
 [-1.06435513  1.49764717  1.        ]
 [ 0.32839951  2.25677919  1.        ]]

训练算法#

为了训练感知机,我们需要找到能够最小化误差的权重 $\mathbf{w}$。误差通过感知机准则定义为:

$$E(\mathbf{w}) = -\sum_{n \in \mathcal{M}}\mathbf{w}^{\mathrm{T}}\mathbf{x}{n}t{n}$$

  • $t_{n} \in {-1, +1}$ 分别表示负样本和正样本
  • $\mathcal{M}$ - 错误分类样本的集合

我们将使用梯度下降的过程。从一些初始随机权重 $\mathbf{w}^{(0)}$ 开始,我们将在训练的每一步通过 $E$ 的梯度来调整权重:

$$\mathbf{w}^{\tau + 1}=\mathbf{w}^{\tau} - \eta \nabla E(\mathbf{w}) = \mathbf{w}^{\tau} + \eta\sum_{n \in \mathcal{M}}\mathbf{x}{n} t{n}$$

其中 $\eta$ 是学习率,$\tau\in\mathbb{N}$ 表示迭代次数。

我们用 Python 来定义这个算法:

In [6]:
def train(positive_examples, negative_examples, num_iterations = 100, learning_rate = 0.01):
    num_dims = positive_examples.shape[1]
    
    # Initialize weights. 
    # We initialize with 0 for simplicity, but random initialization is also a good idea
    weights = np.zeros((num_dims,1)) 
    
    pos_count = positive_examples.shape[0]
    neg_count = negative_examples.shape[0]
    
    report_frequency = 10
    
    for i in range(num_iterations):
        # Pick one positive and one negative example
        pos = random.choice(positive_examples)
        neg = random.choice(negative_examples)

        z = np.dot(pos, weights)   
        if z < 0: # positive example was classified as negative
            weights = weights + learning_rate * pos.reshape(weights.shape)

        z  = np.dot(neg, weights)
        if z >= 0: # negative example was classified as positive
            weights = weights - learning_rate * neg.reshape(weights.shape)
            
        # Periodically, print out the current accuracy on all examples 
        if i % report_frequency == 0:             
            pos_out = np.dot(positive_examples, weights)
            neg_out = np.dot(negative_examples, weights)        
            pos_correct = (pos_out >= 0).sum() / float(pos_count)
            neg_correct = (neg_out < 0).sum() / float(neg_count)
            print("Iteration={}, pos correct={}, neg correct={}".format(i,pos_correct,neg_correct))

    return weights

关于学习率的说明learning_rate 参数(默认值为 0.01)控制我们在每次训练步骤中调整权重的幅度。这实现了梯度下降更新公式:

$$\mathbf{w}^{\tau + 1}=\mathbf{w}^{\tau} + \eta \mathbf{x}{n} t{n}$$

  • 较大的学习率(例如 1.0)会使感知器学习速度更快,但可能会超过最优解
  • 较小的学习率(例如 0.001)学习速度较慢,但可能更精确地收敛
  • 你可以通过调用以下代码进行实验:train(pos_examples, neg_examples, learning_rate=0.1)

现在让我们在我们的数据集上运行训练:

In [7]:
wts = train(pos_examples,neg_examples)
print(wts.transpose())
Iteration=0, pos correct=0.2631578947368421, neg correct=0.6190476190476191
Iteration=10, pos correct=0.8947368421052632, neg correct=0.8571428571428571
Iteration=20, pos correct=0.8421052631578947, neg correct=1.0
Iteration=30, pos correct=0.8947368421052632, neg correct=0.9523809523809523
Iteration=40, pos correct=0.8947368421052632, neg correct=0.9523809523809523
Iteration=50, pos correct=0.9473684210526315, neg correct=0.9047619047619048
Iteration=60, pos correct=0.8947368421052632, neg correct=0.9523809523809523
Iteration=70, pos correct=0.8947368421052632, neg correct=0.9047619047619048
Iteration=80, pos correct=0.8947368421052632, neg correct=0.6190476190476191
Iteration=90, pos correct=0.8421052631578947, neg correct=1.0
[[-0.66042328  4.90850882 -1.        ]]

如你所见,初始准确率约为50%,但很快就提升到接近90%的较高值。

让我们来可视化类的分隔情况。我们的分类函数形式为 $\mathbf{w}^Tx$,对于一个类别,它的值大于0;对于另一个类别,它的值小于0。因此,类别分隔线由 $\mathbf{w}^Tx = 0$ 定义。由于我们只有两个维度 $x_0$ 和 $x_1$,这条线的方程为 $w_0x_0+w_1x_1+w_2 = 0$(记住我们明确定义了一个额外维度 $x_2=1$)。让我们绘制这条线:

In [8]:
def plot_boundary(positive_examples, negative_examples, weights):
    if np.isclose(weights[1], 0):
        if np.isclose(weights[0], 0):
            x = y = np.array([-6, 6], dtype = 'float32')
        else:
            y = np.array([-6, 6], dtype='float32')
            x = -(weights[1] * y + weights[2])/weights[0]
    else:
        x = np.array([-6, 6], dtype='float32')
        y = -(weights[0] * x + weights[2])/weights[1]

    pylab.xlim(-6, 6)
    pylab.ylim(-6, 6)                      
    pylab.plot(positive_examples[:,0], positive_examples[:,1], 'bo')
    pylab.plot(negative_examples[:,0], negative_examples[:,1], 'ro')
    pylab.plot(x, y, 'g', linewidth=2.0)
    pylab.show()
In [9]:
plot_boundary(pos_examples,neg_examples,wts)
Notebook 输出图像

探索学习率#

现在让我们来研究不同的学习率如何影响训练过程。学习率控制梯度下降中的步长——这是一个关键的超参数,影响着收敛速度和稳定性。

In [ ]:
# Compare different learning rates
learning_rates = [0.001, 0.01, 0.1, 1.0]
fig, axes = pylab.subplots(2, 2, figsize=(12, 10))
fig.suptitle('Effect of Different Learning Rates', fontsize=16)

for idx, lr in enumerate(learning_rates):
    ax = axes[idx // 2, idx % 2]
    
    # Train with this learning rate
    weights_lr = train(pos_examples, neg_examples, num_iterations=100, learning_rate=lr)
    
    # Plot decision boundary
    if np.isclose(weights_lr[1], 0):
        if np.isclose(weights_lr[0], 0):
            x = y = np.array([-6, 6], dtype='float32')
        else:
            y = np.array([-6, 6], dtype='float32')
            x = -(weights_lr[1] * y + weights_lr[2])/weights_lr[0]
    else:
        x = np.array([-6, 6], dtype='float32')
        y = -(weights_lr[0] * x + weights_lr[2])/weights_lr[1]
    
    ax.set_xlim(-6, 6)
    ax.set_ylim(-6, 6)
    ax.plot(pos_examples[:, 0], pos_examples[:, 1], 'bo', label='Positive', alpha=0.7)
    ax.plot(neg_examples[:, 0], neg_examples[:, 1], 'ro', label='Negative', alpha=0.7)
    ax.plot(x, y, 'g-', linewidth=2)
    ax.set_title(f'Learning Rate = {lr}')
    ax.set_xlabel('Feature 1')
    ax.set_ylabel('Feature 2')
    ax.legend()
    ax.grid(True, alpha=0.3)

pylab.tight_layout()
pylab.show()

交互式学习率实验#

使用下面的滑块,交互式地尝试不同的学习率,观察它们如何影响决策边界:

In [ ]:
def train_and_plot_with_lr(learning_rate=0.01):
    """Train perceptron with specified learning rate and plot results"""
    weights_lr = train(pos_examples, neg_examples, num_iterations=100, learning_rate=learning_rate)
    
    fig, (ax1, ax2) = pylab.subplots(1, 2, figsize=(14, 5))
    
    # Plot 1: Decision boundary
    if np.isclose(weights_lr[1], 0):
        if np.isclose(weights_lr[0], 0):
            x = y = np.array([-6, 6], dtype='float32')
        else:
            y = np.array([-6, 6], dtype='float32')
            x = -(weights_lr[1] * y + weights_lr[2])/weights_lr[0]
    else:
        x = np.array([-6, 6], dtype='float32')
        y = -(weights_lr[0] * x + weights_lr[2])/weights_lr[1]
    
    ax1.set_xlim(-6, 6)
    ax1.set_ylim(-6, 6)
    ax1.plot(pos_examples[:, 0], pos_examples[:, 1], 'bo', label='Positive', s=100, alpha=0.6)
    ax1.plot(neg_examples[:, 0], neg_examples[:, 1], 'ro', label='Negative', s=100, alpha=0.6)
    ax1.plot(x, y, 'g-', linewidth=3, label='Decision Boundary')
    ax1.set_title(f'Decision Boundary (lr={learning_rate})', fontsize=14)
    ax1.set_xlabel('Feature 1')
    ax1.set_ylabel('Feature 2')
    ax1.legend()
    ax1.grid(True, alpha=0.3)
    
    # Plot 2: Weight values
    ax2.bar(['w0', 'w1', 'bias'], weights_lr.flatten(), color=['blue', 'green', 'red'], alpha=0.7)
    ax2.set_title('Final Weight Values', fontsize=14)
    ax2.set_ylabel('Weight Value')
    ax2.grid(True, alpha=0.3, axis='y')
    ax2.axhline(y=0, color='black', linestyle='-', linewidth=0.5)
    
    pylab.tight_layout()
    pylab.show()
    
    print(f"Final weights: {weights_lr.flatten()}")

# Create interactive widget
interact(train_and_plot_with_lr, 
         learning_rate=widgets.FloatSlider(value=0.01, min=0.001, max=1.0, step=0.001, 
                                          description='Learning Rate:', continuous_update=False))

在测试数据集上评估#

一开始,我们将一部分数据分离出来作为测试数据集。现在让我们看看我们的分类器在这个测试数据集上的准确性如何。为此,我们同样需要为测试数据集扩展一个额外的维度,与权重矩阵相乘,并确保得到的值与标签(+1 或 -1)符号相同。然后将所有布尔值相加,并除以测试样本的长度,以获得准确率:

In [10]:
def accuracy(weights, test_x, test_labels):
    res = np.dot(np.c_[test_x,np.ones(len(test_x))],weights)
    return (res.reshape(test_labels.shape)*test_labels>=0).sum()/float(len(test_labels))

accuracy(wts, test_x, test_labels)
1.0

观察训练过程#

我们之前已经看到,训练过程中准确率会下降。能够观察分隔线在训练过程中的变化会很有趣。下面的代码将在一张图上可视化所有内容,你可以通过移动滑块来“穿越时间”查看训练过程。

In [11]:
def train_graph(positive_examples, negative_examples, num_iterations = 100, learning_rate = 0.01):
    num_dims = positive_examples.shape[1]
    weights = np.zeros((num_dims,1)) # initialize weights
    
    pos_count = positive_examples.shape[0]
    neg_count = negative_examples.shape[0]
    
    report_frequency = 15;
    snapshots = []
    
    for i in range(num_iterations):
        pos = random.choice(positive_examples)
        neg = random.choice(negative_examples)

        z = np.dot(pos, weights)   
        if z < 0:
            weights = weights + learning_rate * pos.reshape(weights.shape)

        z  = np.dot(neg, weights)
        if z >= 0:
            weights = weights - learning_rate * neg.reshape(weights.shape)
            
        if i % report_frequency == 0:             
            pos_out = np.dot(positive_examples, weights)
            neg_out = np.dot(negative_examples, weights)        
            pos_correct = (pos_out >= 0).sum() / float(pos_count)
            neg_correct = (neg_out < 0).sum() / float(neg_count)
            snapshots.append([np.copy(weights).flatten(), (pos_correct+neg_correct)/2.0])

    return np.array(snapshots, dtype=object)

snapshots = train_graph(pos_examples,neg_examples)

def plotit(pos_examples,neg_examples,snapshots,step):
    fig = pylab.figure(figsize=(10,4))
    fig.add_subplot(1, 2, 1)
    plot_boundary(pos_examples, neg_examples, snapshots[step][0])
    fig.add_subplot(1, 2, 2)
    pylab.plot(np.arange(len(snapshots[:,1])), snapshots[:,1])
    pylab.ylabel('Accuracy')
    pylab.xlabel('Iteration')
    pylab.plot(step, snapshots[step,1], "bo")
    pylab.show()
def pl1(step): plotit(pos_examples,neg_examples,snapshots,step)
In [12]:
interact(pl1, step=widgets.IntSlider(value=0, min=0, max=len(snapshots)-1))
interactive(children=(IntSlider(value=0, description='step', max=6), Output()), _dom_classes=('widget-interact…
<function __main__.pl1(step)>

感知机的局限性#

如上所述,感知机是一种线性分类器。如果两个类别是线性可分的,即可以用一条直线分开,那么它可以很好地区分这两个类别。否则,感知机的训练过程将无法收敛。

一个最明显的例子是感知机无法解决的所谓XOR问题。我们希望感知机学习XOR布尔函数,其真值表如下:

0 1
0 0 1
1 1 0

让我们试试吧!我们将手动填充所有正样本和负样本,然后调用我们上面定义的训练函数:

In [13]:
pos_examples_xor = np.array([[1,0,1],[0,1,1]])
neg_examples_xor = np.array([[1,1,1],[0,0,1]])

snapshots_xor = train_graph(pos_examples_xor,neg_examples_xor,1000)
def pl2(step): plotit(pos_examples_xor,neg_examples_xor,snapshots_xor,step)
In [14]:
interact(pl2, step=widgets.IntSlider(value=0, min=0, max=len(snapshots)-1))
interactive(children=(IntSlider(value=0, description='step', max=6), Output()), _dom_classes=('widget-interact…
<function __main__.pl2(step)>

正如上图所示,准确率从未超过75%,因为不可能画出一条直线来正确分类所有可能的样本。

XOR问题是感知机局限性的经典例子,这一问题由Marvin Minsky和Seymour Papert在1969年的著作《Perceptrons》中指出。这一观察限制了神经网络领域的研究近10年,尽管——我们将在课程的下一部分看到——多层感知机完全能够解决此类问题。

复杂示例 - MNIST#

尽管感知机无法解决XOR问题,但它可以解决许多更复杂的问题,例如手写字符识别。

一个在学习机器学习时经常使用的数据集叫做MNIST。它由美国国家标准与技术研究院(Modified National Institute of Standards and Technology)创建,包含一个由60000个手写数字组成的训练集,这些数字来自约250名学生和研究院员工。此外,还有一个由10000个数字组成的测试数据集,这些数字来自不同的个人。

所有数字都以28x28像素的灰度图像表示。

MNIST数据集作为一个训练竞赛可在Kaggle上找到,这是一个举办机器学习竞赛和比赛的网站。一旦你学会如何分类MNIST数字,可以将你的解决方案提交到Kaggle,看看它在其他参与者中的排名。

我们从加载MNIST数据集开始:

In [ ]:
# If you are not running this notebook from a cloned repository, you may need to grab the binary dataset file first
# !wget https://github.com/microsoft/AI-For-Beginners/raw/main/data/mnist.pkl.gz?raw=true
# In this case correct the link to the dataset below as well.

with gzip.open('../../../data/mnist.pkl.gz', 'rb') as mnist_pickle:
    MNIST = pickle.load(mnist_pickle, encoding='latin1')

现在我们来绘制数据集:

In [16]:
print(MNIST['Train']['Features'][0][130:180])
print(MNIST['Train']['Labels'][0])
features = MNIST['Train']['Features'].astype(np.float32) / 256.0
labels = MNIST['Train']['Labels']
fig = pylab.figure(figsize=(10,5))
for i in range(10):
    ax = fig.add_subplot(1,10,i+1)
    pylab.imshow(features[i].reshape(28,28))
pylab.show()
[  0   0 188 255  94   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0 191 250 253  93   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0]
1
Notebook 输出图像

因为感知器是一个二分类器,我们将把问题限制为只识别两个数字。下面的函数将用两个给定的数字填充正样本和负样本数组(并且还会显示这些数字的样本以便于理解)。

In [17]:
def set_mnist_pos_neg(positive_label, negative_label):
    positive_indices = [i for i, j in enumerate(MNIST['Train']['Labels']) 
                          if j == positive_label]
    negative_indices = [i for i, j in enumerate(MNIST['Train']['Labels']) 
                          if j == negative_label]

    positive_images = MNIST['Train']['Features'][positive_indices]
    negative_images = MNIST['Train']['Features'][negative_indices]

    fig = pylab.figure()
    ax = fig.add_subplot(1, 2, 1)
    pylab.imshow(positive_images[0].reshape(28,28), cmap='gray', interpolation='nearest')
    ax.set_xticks([])
    ax.set_yticks([])
    ax = fig.add_subplot(1, 2, 2)
    pylab.imshow(negative_images[0].reshape(28,28), cmap='gray', interpolation='nearest')
    ax.set_xticks([])
    ax.set_yticks([])
    pylab.show()
    
    return positive_images, negative_images

我们将从尝试在0和1之间进行分类开始:

In [19]:
pos1,neg1 = set_mnist_pos_neg(1,0)
Notebook 输出图像
In [20]:
def plotit2(snapshots_mn,step):
    fig = pylab.figure(figsize=(10,4))
    ax = fig.add_subplot(1, 2, 1)
    pylab.imshow(snapshots_mn[step][0].reshape(28, 28), interpolation='nearest')
    ax.set_xticks([])
    ax.set_yticks([])
    pylab.colorbar()
    ax = fig.add_subplot(1, 2, 2)
    ax.set_ylim([0,1])
    pylab.plot(np.arange(len(snapshots_mn[:,1])), snapshots_mn[:,1])
    pylab.plot(step, snapshots_mn[step,1], "bo")
    pylab.show()
def pl3(step): plotit2(snapshots_mn,step)
def pl4(step): plotit2(snapshots_mn2,step)    
In [21]:
snapshots_mn = train_graph(pos1,neg1,1000)    
interact(pl3, step=widgets.IntSlider(value=0, min=0, max=len(snapshots_mn) - 1))
interactive(children=(IntSlider(value=0, description='step', max=66), Output()), _dom_classes=('widget-interac…
<function __main__.pl3(step)>

请注意,准确率会非常快地接近100%。

请将滑块移动到训练结束时的某个位置,并观察左侧绘制的权重矩阵。这个矩阵将帮助您理解感知器的实际工作原理。您可以看到字段中间的高权重值,这些值对应于数字1通常出现的像素,而两侧的低负值则对应于数字0的部分。因此,如果呈现给感知器的数字确实是1,中间部分的像素会与高权重值相乘,产生正结果。相反,当感知器观察到数字0时,相应的像素会与负数相乘。

您可能会注意到,如果我们给感知器一个稍微水平偏移的数字1,使其像素占据了数字0的垂直部分位置,我们可能会得到错误的结果。由于我们的MNIST数据集的特点是所有数字都居中并正确定位,感知器依赖于这一点来区分数字。

现在让我们尝试不同的数字:

In [22]:
pos2,neg2 = set_mnist_pos_neg(2,5)
Notebook 输出图像
In [23]:
snapshots_mn2 = train_graph(pos2,neg2,1000)
interact(pl4, step=widgets.IntSlider(value=0, min=0, max=len(snapshots_mn2) - 1))
interactive(children=(IntSlider(value=0, description='step', max=66), Output()), _dom_classes=('widget-interac…
<function __main__.pl4(step)>

讨论#

由于某些原因,数字 2 和 5 并不容易区分。尽管我们获得了相对较高的准确率(超过 85%),但我们可以清楚地看到感知器在某个点停止了学习。

为了理解为什么会发生这种情况,我们可以尝试使用主成分分析 (PCA)。这是一种机器学习技术,用于降低输入数据集的维度,从而获得类别之间的最佳可分性。

在我们的例子中,输入图像有 784 个像素(输入特征),我们希望使用 PCA 将参数数量减少到仅 2 个,以便可以在图表上绘制它们。这两个参数将是原始特征的线性组合,我们可以将这一过程视为“旋转”我们原始的 784 维空间,并观察其在二维空间中的投影,直到我们获得最佳视角来区分类别。

In [24]:
from sklearn.decomposition import PCA

def pca_analysis(positive_label, negative_label):
    positive_images, negative_images = set_mnist_pos_neg(positive_label, negative_label)
    M = np.append(positive_images, negative_images, 0)

    mypca = PCA(n_components=2)
    mypca.fit(M)
    
    pos_points = mypca.transform(positive_images[:200])
    neg_points = mypca.transform(negative_images[:200])

    pylab.plot(pos_points[:,0], pos_points[:,1], 'bo')
    pylab.plot(neg_points[:,0], neg_points[:,1], 'ro')
In [25]:
pca_analysis(1,0)
Notebook 输出图像Notebook 输出图像
In [26]:
pca_analysis(2,5)
Notebook 输出图像Notebook 输出图像

正如你所见,0 和 1 可以通过一条直线清晰地分开。这表明在原始的784维空间中,与数字对应的点也是线性可分的。而对于数字2和5,我们无法找到一个能够清晰分开这两个数字的良好投影,因此会出现一些错误分类的情况。

在本课程的后续部分,我们将学习如何使用神经网络创建非线性分类器,以及如何处理数字未正确对齐的问题。很快,我们将在MNIST数字分类中达到超过99%的准确率,同时将数字分类为10个不同的类别。

收获总结#

  • 我们学习了最简单的神经网络架构——单层感知机。
  • 我们通过手动实现了感知机,使用基于梯度下降的简单训练过程。
  • 尽管简单,单层感知机仍然可以解决相当复杂的手写数字识别问题。
  • 单层感知机是一个线性分类器,因此它的分类能力与逻辑回归相同。
  • 在样本空间中,感知机可以通过超平面分开两类输入数据。

致谢#

本笔记本是 AI for Beginners Curricula 的一部分,由 Dmitry Soshnikov 编写。灵感来源于微软剑桥研究院的神经网络研讨会。一些代码和示例材料取自 Katja HoffmannMatthew JohnsonRyoto Tomioka 的演示文稿,以及 NeuroWorkshop 仓库。


免责声明
本文档使用AI翻译服务Co-op Translator进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。原始语言的文档应被视为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而产生的任何误解或误读不承担责任。