【算法】从头开始编写任何机器学习算法的6个步骤:感知器案例研究

笔者邀请您,先思考:

1 您如何学习机器学习算法?

2 您如何应用机器学习算法?

从头开始编写机器学习算法是一种非常有益的学习体验。 我们在此过程中强调了6个步骤。

有些算法比其他算法更复杂,所以从一些简单的算法开始,从一些非常简单的算法开始,比如单层感知器。

我将以感知器为例,带您经历以下6步过程,从头开始编写算法:

  1. 对算法有一个基本的了解
  2. 找到一些不同的学习来源
  3. 将算法分解成块
  4. 从一个简单的例子开始
  5. 使用可信的实现进行验证
  6. 写下你的过程

获得基本了解

这又回到了我最初所说的。如果你不了解基础知识,不能从头开始处理算法。

至少,你应该能够回答以下问题:

  • 它是什么?
  • 它的典型用途是什么?
  • 我什么时候不能用这个?

对于感知器,让我们继续回答这些问题:

  • 单层感知器是最基本的神经网络。它通常用于二进制分类问题(1或0,“是”或“否”)。
  • 一些简单的用法可能是情感分析(正面或负面反应)或贷款违约预测(“将违约”,“将不违约”)。对于这两种情况,决策边界都必须是线性的。
  • 如果决策边界是非线性的,你就不能用感知器。对于这些问题,您需要使用不同的方法。

使用不同的学习资源

在你对模型有了基本的了解之后,是时候开始你的研究了。 有些人用教科书学得更好,有些人用视频学得更好。 就我个人而言,我喜欢到处转转,使用各种各样的资源。

对于数学细节,教科书做得很好,但对于更实际的例子,我更喜欢博客帖子和YouTube视频。 对于感知器,这里有一些很好的来源:

教科书

  • The Elements of Statistical Learning 4.5.1节
  • Understanding Machine Learning: From Theory To Algorithms 21.4 节

博客

  • How To Implement The Perceptron Algorithm From Scratch In Python, by Jason Brownlee
  • Single-Layer Neural Networks and Gradient Descent, by Sebastian Raschka

视频

  • Perceptron Training How the Perceptron Algorithm Works

将算法分解成块

现在我们已经收集了资料,是时候开始学习了。 与其从头到尾读一章或一篇博客文章,不如先浏览一下章节标题和其他重要信息。 写下要点,并试着概述算法

在浏览了这些资料之后,我将感知器分为以下5个部分:

  • 初始化权重
  • 将权重乘以输入,然后求和
  • 将结果与阈值进行比较以计算输出(1或0)
  • 更新权重
  • 重复

让我们详细讨论每一个问题。

1 初始化权重 首先,我们将初始化权向量。 权重的数量需要与特征的数量匹配。假设我们有三个特征,这是权重向量的样子

权重向量通常是用零初始化的,所以我将在这个例子中继续使用它。

2 将权重乘以输入,然后求和

接下来,我们将权重乘以输入,然后求和。 为了更容易理解,我在第一行中对权重及其对应的特征进行了着色

在我们把权重乘以特征之后,我们把它们加起来。这也被称为点积。

最后的结果是0。我将把这个临时结果称为“f”。

3 比较阈值

在计算出点积之后,我们需要将它与阈值进行比较。 我选择用0作为我的阈值,但是你可以试着用一些不同的数字。

由于我们计算出来的点积f不大于我们的阈值(0)我们的估计值等于0。 我将估计值表示为带帽的y(又名“y帽”),下标为0以对应第一行。你可以在第一行用1,这无所谓。我选择从0开始。 如果我们将这个结果与实际值进行比较,我们可以看到我们当前的权重没有正确地预测实际输出。

由于我们的预测是错误的,我们需要更新权重,这将我们带到下一步。

4 更新权重

接下来,我们要更新权重。下面是我们要用到的方程:

基本思想是,我们在迭代“n”处调整当前权重,以便在下一个迭代中得到一个新的权重“n+1”。 为了调整权重,我们需要设置一个“学习率”。这是用希腊字母“eta”表示的。 我选择用0.1表示学习速率,但是你可以用不同的数字,就像用临界值一样。 以下是我们到目前为止的总结:

现在让我们继续计算迭代n=2的新权重。

我们已经成功地完成了感知器算法的第一次迭代。

5 重复

由于我们的算法没有计算正确的输出,我们需要继续。 通常我们需要多次迭代。遍历数据集中的每一行,我们将每次更新权重。 对数据集的一次完整扫描称为“epoch”。 因为我们的数据集有3行,我们需要3次迭代才能完成1epoch。

我们可以设置总的迭代次数或epoch以继续执行算法。也许我们想指定30次迭代(或10次epochs)。 与阈值和学习率一样,epoch的数量是一个可以随意使用的参数。 在下一个迭代中,我们将继续讨论第二行特征。

我不会重复每一步,但这是下一个点积的计算:

接下来,我们将比较点积和阈值,以计算新的估计值,更新权值,然后继续。如果数据是线性可分的,感知器就会收敛。

从一个简单的例子开始

现在我们已经手工将算法分解成块,现在是开始在代码中实现它的时候了。 为了简单起见,我总是喜欢从一个非常小的“玩具数据集”开始。

对于这种类型的问题,一个漂亮的小的线性可分离数据集是NAND门。这是数字电子学中常用的逻辑门。

由于这是一个相当小的数据集,我们可以手动将其输入到Python中。 我要添加一个虚拟的特征“x0”它是一列1。我这样做是为了让我们的模型计算偏差项。 您可以将偏差看作是截距项,它正确地允许我们的模型分离这两个类。 以下是输入数据的代码:

 1# Importing libraries
 2# NAND Gate
 3# Note: x0 is a dummy variable for the bias term
 4#     x0  x1  x2
 5x = [[1., 0., 0.],
 6     [1., 0., 1.],
 7     [1., 1., 0.],
 8     [1., 1., 1.]]
 9
10y =[1.,
11    1.,
12    1.,
13    0.]

与前一节一样,我将逐步详细介绍算法,编写代码并测试它。

1 初始化权重 第一步是初始化权重。

1# Initialize the weights
2import numpy as np
3w = np.zeros(len(x[0]))
1Out:
2[ 0.  0.  0.]

请记住,权重向量的长度需要与特征的数量匹配。对于这个NAND门的例子,长度是3。

2 将权重乘以输入,然后求和

接下来,我们将权重乘以输入,然后求和。 它的另一个名字是“点积” 同样,我们可以使用Numpy轻松地执行此操作。我们将使用的方法是.dot()。

我们从权向量和第一行特征的点积开始。

1# Dot Product
2f = np.dot(w, x[0])
3print f

正如预期的那样,结果是0。 为了与上一节的笔记保持一致,我将点积赋给变量f。

3 与阈值比较

在计算了点积之后,我们准备将结果与阈值进行比较,从而对输出进行预测。 同样,我将保持与上一节的笔记一致。 我要让临界值z等于0。如果点积f大于0,我们的预测是1。否则,它就是零。 记住,这个预测通常是顶部一横来表示的,也被称为“帽子”。我将把预测赋给的变量是yhat。

1# Activation Function
2z = 0.0
3if f > z:
4    yhat = 1.
5else:
6    yhat = 0.
7
8print yhat

正如预期的那样,预测为0。 您会注意到,在上面的注释中,我将其称为“激活函数”。这是对我们正在做的更正式的描述。 查看NAND输出的第一行,我们可以看到实际值是1。由于我们的预测是错误的,我们需要继续更新权重。

4 更新权重

现在我们已经做出了预测,我们准备更新权重。 我们需要设定一个学习速度才能做到这一点。为了与前面的示例一致,我将学习速率“eta”赋值为0.1。 我将对每个权重的更新进行硬编码,使其更易于阅读。

1eta = 0.1
2w[0] = w[0] + eta*(y[0] - yhat)*x[0][0]
3w[1] = w[1] + eta*(y[0] - yhat)*x[0][1]
4w[2] = w[2] + eta*(y[0] - yhat)*x[0][2]
5
6print w

我们可以看到我们的权重现在已经更新了,所以我们准备继续。

5 重复

现在我们已经完成了每一个步骤,现在是时候把所有的东西放在一起了。 最后一个我们没有讨论的是我们的损失函数。这是我们要最小化的函数,在我们的例子中,这将是平方和(SSE)误差。

这就是我们用来计算误差的方法,看看模型是如何运行的。 把所有这些都联系起来,完整的函数是这样的:

 1import numpy as np
 2
 3
 4# Perceptron function
 5def perceptron(x, y, z, eta, t):
 6    '''
 7    Input Parameters:
 8        x: data set of input features
 9        y: actual outputs
10        z: activation function threshold
11        eta: learning rate
12        t: number of iterations
13    '''
14
15    # initializing the weights
16    w = np.zeros(len(x[0]))      
17    n = 0                        
18
19    # initializing additional parameters to compute sum-of-squared errors
20    yhat_vec = np.ones(len(y))     # vector for predictions
21    errors = np.ones(len(y))       # vector for errors (actual - predictions)
22    J = []                         # vector for the SSE cost function
23
24    while n < t: for i in xrange(0, len(x)): # dot product f = np.dot(x[i], w) # activation function if f >= z:                               
25                yhat = 1.                               
26            else:                                   
27                yhat = 0.
28            yhat_vec[i] = yhat
29
30            # updating the weights
31            for j in xrange(0, len(w)):             
32                w[j] = w[j] + eta*(y[i]-yhat)*x[i][j]
33
34        n += 1
35        # computing the sum-of-squared errors
36        for i in xrange(0,len(y)):     
37           errors[i] = (y[i]-yhat_vec[i])**2
38        J.append(0.5*np.sum(errors))
39
40    return w, J

现在我们已经编写了完整感知器的代码,让我们继续运行它:

 1#     x0  x1  x2
 2x = [[1., 0., 0.],
 3     [1., 0., 1.],
 4     [1., 1., 0.],
 5     [1., 1., 1.]]
 6
 7y =[1.,
 8    1.,
 9    1.,
10    0.]
11
12z = 0.0
13eta = 0.1
14t = 50
15
16print "The weights are:"
17print perceptron(x, y, z, eta, t)[0]
18
19print "The errors are:"
20print perceptron(x, y, z, eta, t)[0]

看一看错误,我们可以看到错误在第6次迭代时趋于0。对于迭代的其余部分,它保持在0。 当误差趋于0时,我们知道模型收敛了。这告诉我们,我们的模型已经正确地“学习”了适当的权重。 在下一节中,我们将使用对较大数据集的计算权重来进行预测。

使用可信的实现进行验证

到目前为止,我们已经找到了不同的学习资源,手工完成了算法,并通过一个简单的例子在代码中测试了它。 现在是时候将我们的结果与可信的实现进行比较了。为了比较,我们将使用scikit-learn中的感知器。 我们将使用以下步骤进行比较:

  • 导入数据
  • 将数据分成训练集/测试集
  • 训练我们的感知器
  • 测试感知器
  • 和scikit-learn的感知器相比

1 导入数据

让我们从导入数据开始。您可以在这里获得数据集的副本。 这是一个我创建的线性可分离数据集以确保感知器能够工作。为了确认,让我们继续对数据画图。

1import pandas as pd
2import numpy as np
3import matplotlib.pyplot as plt
4
5df = pd.read_csv("dataset.csv")
6plt.scatter(df.values[:,1], df.values[:,2], c = df['3'], alpha=0.8)

看看这个图,很容易看出我们可以用一条直线将这些数据分开。 在继续之前,我将在上面解释我的绘图代码。 我使用panda导入csv,它自动将数据放入dataframe中。

为了绘制数据,我必须从dataframe中提取值,所以我使用了.values方法。 特征在第1和第2列中,所以我在散点图函数中使用了这些特征。第0列是我包含的1的虚拟特征,这样就能计算出截距。这应该与我们在前一节中对NAND gate所做的事情一样。

最后,我在scatterplot函数中使用c = df['3']和alpha = 0.8为两个类着色。输出是第3列(0或1)中的数据,因此我告诉函数使用第3列为两个类着色。 你可以在这里找到关于Matplotlib的散点图函数的更多信息。

2 将数据分成训练集/测试集

既然我们已经确定了数据可以线性分离,那么现在就该分割数据了。 在单独的数据集上训练模型和另一个数据上测试模型是很好的实践。这有助于避免过度拟合。 做这个有不同的方法,但为了简单起见,我将使用一个训练集和一个测试集。 我扰乱一下我们的数据。如果您查看原始文件,您会看到数据是按输出(第三列)中0的行进行分组的,然后是所有的1。我想要改变一下,增加一些随机性,所以我要洗牌。

1df = df.values  
2
3np.random.seed(5)
4np.random.shuffle(df)

我首先将数据从dataframe改为numpy数组。这将使我更容易地使用许多numpy函数,例如.shuffle。 为了让结果重现,我设置了一个随机种子(5)。完成后,尝试改变随机种子,看看结果如何变化。 接下来我将把70%的数据分成训练集,30%分成测试集。

1train = df[0:int(0.7*len(df))]
2test = df[int(0.7*len(df)):int(len(df))]

最后一步是分离训练和测试集的特征和输出。

1x_train = train[:, 0:3]
2y_train = train[:, 3]
3
4x_test = test[:, 0:3]
5y_test = test[:, 3]

我选择了70%/30%作为训练集/测试集,只是为了这个示例,但我鼓励您研究其他方法,比如k-fold交叉验证。

3 训练我们的感知器 接下来,我们要训练感知器。 这非常简单,我们将重用在前一节中构建的代码。

 1def perceptron_train(x, y, z, eta, t):
 2    '''
 3    Input Parameters:
 4        x: data set of input features
 5        y: actual outputs
 6        z: activation function threshold
 7        eta: learning rate
 8        t: number of iterations
 9    '''
10
11    # initializing the weights
12    w = np.zeros(len(x[0]))      
13    n = 0                        
14
15    # initializing additional parameters to compute sum-of-squared errors
16    yhat_vec = np.ones(len(y))     # vector for predictions
17    errors = np.ones(len(y))       # vector for errors (actual - predictions)
18    J = []                         # vector for the SSE cost function
19
20    while n < t:          for i in xrange(0, len(x)):                                           # dot product             f = np.dot(x[i], w)                                   # activation function             if f >= z:                               
21                yhat = 1.                               
22            else:                                   
23                yhat = 0.
24            yhat_vec[i] = yhat
25
26            # updating the weights
27            for j in xrange(0, len(w)):             
28                w[j] = w[j] + eta*(y[i]-yhat)*x[i][j]
29
30        n += 1
31        # computing the sum-of-squared errors
32        for i in xrange(0,len(y)):     
33           errors[i] = (y[i]-yhat_vec[i])**2
34        J.append(0.5*np.sum(errors))
35
36    return w, J
37
38z = 0.0
39eta = 0.1
40t = 50
41
42perceptron_train(x_train, y_train, z, eta, t)

让我们来看看权重和平方和误差。

1w = perceptron_train(x_train, y_train, z, eta, t)[0]
2J = perceptron_train(x_train, y_train, z, eta, t)[1]
3
4print w
5print J

权值现在对我们来说意义不大,但我们将在下一节中使用这些数字来测试感知器。我们还将使用权重来比较我们的模型和scikit-learn模型。 看一下平方求和误差,我们可以看到感知器已经收敛,这是我们期望的,因为数据是线性可分离的。

4测试我们的感知器

现在是测试感知器的时候了。为此,我们将构建一个小型的perceptron_test函数。 这和我们已经看到的很相似。这个函数取我们使用perceptron_train函数计算的权值的点积,以及特征,以及激活函数,来进行预测。 我们唯一没有看到的是accuracy_score。这是一个来自scikitlearn的评价度量函数。你可以在这里了解更多。 把所有这些放在一起,下面是代码的样子:

 1from sklearn.metrics import accuracy_score
 2
 3w = perceptron_train(x_train, y_train, z, eta, t)[0]
 4
 5def perceptron_test(x, w, z, eta, t):
 6    y_pred = []
 7    for i in xrange(0, len(x-1)):
 8        f = np.dot(x[i], w)   
 9
10        # activation function
11        if f > z:                               
12            yhat = 1                               
13        else:                                   
14            yhat = 0
15        y_pred.append(yhat)
16    return y_pred
17
18y_pred = perceptron_test(x_test, w, z, eta, t)
19
20print "The accuracy score is:"
21print accuracy_score(y_test, y_pred)

得分为1.0表明我们的模型正确地预测了所有的测试数据。这个数据集显然是可分离的,所以我们期望这个结果。 5 和学过的感知器相比 最后一步是将我们的结果与scikit-learn的感知器进行比较。下面是这个模型的代码:

1from sklearn.linear_model import Perceptron
2
3# training the sklearn Perceptron
4clf = Perceptron(random_state=None, eta0=0.1, shuffle=False, fit_intercept=False)
5clf.fit(x_train, y_train)
6y_predict = clf.predict(x_test)

现在我们已经训练了模型,让我们将权重与模型计算的权重进行比较。

scikit-learn模型中的权重与我们的相同。这意味着我们的模型工作正常,这是个好消息。 在我们结束之前,有几个小问题需要复习一下。在scikit-learn模型中,我们必须将随机状态设置为“None”并关闭变换。我们已经设置了一个随机种子并打乱了数据,所以我们不需要再这样做了。 我们还必须将学习速率“eta0”设置为0.1,以与我们的模型相同。

最后一点是截距。因为我们已经包含了一个虚拟的特征列1s,我们正在自动拟合截距,所以我们不需要在scikit-learn感知器中打开它。 这些看起来都是次要的细节,但如果我们不设置这些,我们就无法复制与我们的模型相同的结果。 这一点很重要。在使用模型之前,阅读文档并理解所有不同设置的作用是非常重要的

写下你的过程

这个过程中的最后一步可能是最重要的。 您已经完成了所有的工作,包括学习、记笔记、从头开始编写算法,并将其与可信的实现进行比较。不要让所有的好工作白白浪费掉! 写下这个过程很重要,原因有二:

  • 你会得到更深的理解,因为你正在教导别人你刚刚学到的东西。
  • 你可以向潜在雇主展示它。

证明你可以从机器学习库中实现一个算法是一回事,但如果你可以自己从头实现它,那就更令人印象深刻了。 一个展示你作品的好方法是使用GitHub页面组合。

总结

在这篇文章中,我们学习了如何从零开始实现感知器。 更重要的是,我们学习了如何找到有用的学习资源,以及如何将算法分解成块。 然后,我们学习了如何使用一个玩具数据集在代码中实现和测试算法。 最后,我们通过比较我们的模型和可信实现的结果来结束本文。

这是在更深层次上学习算法的一个很好的方法,这样您就可以自己实现它了。 大多数情况下,您将使用可信的实现,但如果您真的想深入了解底层的情况,从头实现它是一个很好的练习。 请务必在下面留下您的评论,如果您在学习过程中还有其他的帮助您的技巧,请告诉我!

作者:John Sullivan 原文链接:https://www.dataoptimal.com/machine-learning-from-scratch/

版权声明:作者保留权利,严禁修改,转载请注明原文链接。

本文分享自微信公众号 - 数据科学与人工智能(DS_AI_shujuren)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-10-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码字搬砖

一个菜鸟的经验

1.刚毕业或者毕业没多久不要去小公司,特别是没有牛人的小公司,有牛人又有很好前景的小公司可以去 2.基础很重要,基础很重要,基础很重要

10220
来自专栏后端云

OPNFV项目生命周期

各个项目的成熟度从一个级别提升至另一个级别只和自身有关,是独立于OPNFV的版本发布的。

11460
来自专栏程序员宝库

趣图:程序员的生活写照

11940
来自专栏程序员互动联盟

有些程序员辞职后,被老板大骂没道德,对此你怎么看?

作为一个在这个行业已经混迹了十几年的老程序员,程序员这个职业和别的职业没有什么特别之处,也会产生流动性,至于已经辞职的人聪明人基本上会选择送上祝福给人留下美好的...

12930
来自专栏新智元

微软竞标美国防部价值100亿美元“杀人合同”遭员工抗议

MSPoweruser网站10月13日发表了题为《微软员工抗议微软竞标一个使美国国防部“更具杀伤力”的100亿美元合同》的报道。

11130
来自专栏程序员互动联盟

是不是互联网公司都会压榨年轻的程序员,等年纪大后,就再换一批年轻人?

作为一个在编程行业从业十几年的程序员,分析这个问题来看这种问题的确存在,在项目成立之初会请大量的高手入驻,在项目差不多的时候直接让一些年轻人接班,继续进行维护,...

24840
来自专栏挖数

小鲜肉崩盘!吴亦凡、鹿晗人气下滑超50%

这个时间点,跟EXO四子归国日期不谋而合,因此大致可以把EXO四子称为第一届小鲜肉。

1.1K30
来自专栏挖数

分析106万数据后,发现杨超越粉丝是这样一群人

1.2K10
来自专栏程序员互动联盟

如何看待不会写代码的架构师?

入行十几年和很多架构师打过交到,绝大部分的架构师在具备超强的架构能力的同时,同时还兼具强大的编码能力,而且会的编程语言还挺多,从程序员的角度认知,架构师还是需要...

20150
来自专栏程序员互动联盟

程序员国庆节期间在火车站候车室写代码,程序员这个职业幸福指数是不是很差

作为一个写了十几年代码的老程序员,在火车站候车室写代码基本不算是什么稀奇的事情,特别是在互联网公司的程序员,即使放假回家也会带着电脑,因为可能随时更新版本解决问...

11740

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励