美文网首页生物信息学与算法我是程序员机器学习
6个步骤从头开始编写机器学习算法:感知器案例研究

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

作者: 阿里云云栖号 | 来源:发表于2018-11-12 17:49 被阅读121次

    摘要: 通用版学习机器学习算法的方法,你值得拥有!

    从0开始编写机器学习算法是一种非常好的体验

    点击这里你可以查看本文所用到的Python代码

    当你点击之后你会感到压力,因为其中有些算法比其他算法更复杂,所以我建议你从一些简单甚至更简单的算法开始,比如单层感知器

    以感知器为例从头开始编写算法,主要为以下6个步骤:

    1.对算法有一个基本的了解;

    2.找到一些不同的学习来源;

    3.将算法分解成块;

    4.从一个简单的例子开始;

    5.使用可行的实现进行验证;

    6.写下你的过程。

    1、对算法进行基本的了解

    如果你不了解基础知识,不要从零开始编写算法。

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

    1.它是什么?

    2.它的典型用途是什么?

    3.使用条件是什么?

    对于感知器,至少能够回答以下问题:

    1.单层感知器是最基本的神经网络。它通常用于二分类问题(1或0,“是”或“否”)。

    2.一些简单的用法可能是情绪分析(积极或消极反应)或贷款违约预测(“将违约”或“将不违约”)。对于这两种情况,决策边界都必须是线性的。

    3.如果决策边界是非线性的,你就不能用感知器。对于这些问题,需要使用其他不同的方法。

    2、使用不同的学习资源

    在你对模型有了基本的了解之后,这个时候可以开始你的研究了。

    有些人用教材学得更好,有些人用视频学得更好。我个人喜欢到处转转,使用各种各样的资源。

    对于数学细节,教材讲的比较详细,但对于更实际的例子,我更喜欢博客帖子和视频。

    对于感知器,这里有一些很好的学习资源:

    教材

    统计学习基础

    机器学习理解:从理论到算法

    博客

    如何在Python中从Scratch开始实现感知器算法,Jason Brownlee

    单层神经网络与梯度下降,Sebastian Raschka

    视频

    感知器训练

    感知器算法的原理

    3、将算法分解成块

    现在我们已经收集了各种学习资源,是时候开始学习了。

    与其从头到尾读一篇博客文章,不如先浏览一下章节标题和其他重要信息,写下要点,并试着概述算法。

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

    1.初始化权重;

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

    3.将结果与阈值进行比较以计算输出(1或0);

    4.更新权重;

    5.重复以上步骤;

    让我们详细讨论每一个部分。

    1. 初始化权重

    首先初始化权重向量。权重的数量需要与特征的数量匹配。假设我们有三个特征,则权重向量如下所示:

    权重向量通常被初始化为0,在本文中我们将继续使用它。

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

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

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

    最后的结果为0,将这个零时的结果设为f。

    3. 和阈值进行比较

    在计算出点积之后,我们需要将它与阈值进行比较。

    这里选择用0作为阈值,但也可以用其他值作为阈值。

    由于我们计算出来的点积f不大于我们的阈值(0),所以估计值等于0。

    我将估计值表示为带帽的y(又名“y hat”),下标0表示第一行,也可以用1表示第一行,这无所谓。这里选择从0开始。

    如果我们将这个结果与实际值进行比较,可以看到当前的权重没有正确地预测实际输出。

    因为我们的预测不正确,所以进行下一步来更新权重。

    4. 更新权重

    接下来更新权重,以下是要使用的方程:

    基本原理是在迭代“n”处调整当前权重,以便在下一个迭代中得到一个新的权重“n+1”。

    为了调整权重,我们需要设置一个“学习率”。这是用希腊字母“eta”表示。

    这里选择用0.1表示学习率,也可以用其他值表示学习率,就像阈值的设置一样。

    以下是到目前为止的总结:

    继续计算在n=2时的权重。

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

    5. 重复以上步骤

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

    因为数据集有3行,我们需要3次迭代才能完成1个epoch。

    我们可以设置总的迭代次数或epoch来继续执行算法,比如指定30次迭代(或10个epochs)。

    与阈值和学习率一样,epoch的数量是一个可以随意使用的参数。

    在下一个迭代中,我们将继续讨论第二行特征。

    这里不一一重复每一步了,以下是下一个点积的计算。

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

    4、从一个简单的例子开始

    现在我们已经手工将算法分解成块,现在用代码开始实现它。为了简单起见,从一个非常小的“玩具数据集”开始。对于这种类型的问题,一个好的小的线性可分离数据集是NAND门。这是数电中常用的逻辑门。

    因为这是一个非常小的数据集,我们可以手动将其输入到Python中。

    为了让模型计算偏差项,添加一个虚拟的特征“x0”表示第一列。

    可以将偏差看作是截距项,模型可以正确地分离这两个类。

    以下是输入数据的代码:

    # Importing libraries
    # NAND Gate
    # Note: x0 is a dummy variable for the bias term
    #     x0  x1  x2
    x = [[1., 0., 0.],
         [1., 0., 1.],
         [1., 1., 0.],
         [1., 1., 1.]]
    
    y =[1.,
        1.,
        1.,
        0.]
    

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

    1.初始化权重

    第一步是初始化权重

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

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

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

    接下来,我们将权重乘以输入,然后求和(即点积)。

    同样,我们可以使用Numpy的dot()函数轻松地执行此操作。

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

    # Dot Product
    f = np.dot(w, x[0])
    print f
    Out:0.0 
    
    

    正如预期的那样,结果是0。

    为了与上一节保持一致,我将点积赋给变量f。

    3.与阈值进行比较

    在计算了点积之后,将结果与阈值进行比较,从而对输出进行预测。

    设定阈值z等于0。如果点积f大于0,我们的预测是1。否则,它就是零。

    记住,这个预测通常是用一克拉的顶部来表示的,也被称为“帽子”,把预测值赋给变量yhat。

    # Activation Function
    z = 0.0
    if f > z:
        yhat = 1.
    else:
        yhat = 0.
    print yhat
    Out:0.0 
    

    正如预期的那样,预测为0。

    在上面的注释中,将这些代码称为“激活函数”,是更正式的名称。

    查看NAND输出的第一行,可以看到实际值是1,由于我们的预测是错误的,所以需要继续更新权重。

    4.更新权重

    现在已经得出了预测值,准备更新权重。

    我们需要设定一个学习率才能做到这一点。为了与前面的例子保持一致,将学习率“eta”赋值为0.1。

    我将对每个权重的更新进行硬编码,使其更容易阅读。

    # Update the weights
    eta = 0.1
    w[0] = w[0] + eta*(y[0] - yhat)*x[0][0]
    w[1] = w[1] + eta*(y[0] - yhat)*x[0][1]
    w[2] = w[2] + eta*(y[0] - yhat)*x[0][2]
    print w
    Out:[ 0.1  0.   0. ]
    

    可以看到权重现在已经更新了,继续下去。

    5.重复以上步骤

    现在我们已经完成了每一个步骤,现在是时候把所有的东西放在一起了。

    最后一个还没有讨论的是损失函数,即实现最小化的函数。在例子中,这将是平方和(SSE)误差。

    这就是我们用来计算误差的方法,看看模型是如何运行的。

    把所有这些都联系起来,完整的函数如下所示:

    import numpy as np
    # Perceptron function
    def perceptron(x, y, z, eta, t):
        '''
        Input Parameters:
            x: data set of input features
            y: actual outputs
            z: activation function threshold
            eta: learning rate
            t: number of iterations
        '''
        # initializing the weights
        w = np.zeros(len(x[0]))      
        n = 0                        
        # initializing additional parameters to compute sum-of-squared errors
        yhat_vec = np.ones(len(y))     # vector for predictions
        errors = np.ones(len(y))       # vector for errors (actual - predictions)
        J = []                         # vector for the SSE cost function
    
        while n < t: for i in xrange(0, len(x)): # dot product f = np.dot(x[i], w) # activation function if f >= z:                               
                    yhat = 1.                               
                else:                                   
                    yhat = 0.
                yhat_vec[i] = yhat
    
                # updating the weights
                for j in xrange(0, len(w)):             
                    w[j] = w[j] + eta*(y[i]-yhat)*x[i][j]
    
            n += 1
            # computing the sum-of-squared errors
            for i in xrange(0,len(y)):     
               errors[i] = (y[i]-yhat_vec[i])**2
            J.append(0.5*np.sum(errors))
    
        return w, J 
    

    现在已经编写了感知器的所有代码,开始运行它:

    #     x0  x1  x2
    x = [[1., 0., 0.],
         [1., 0., 1.],
         [1., 1., 0.],
         [1., 1., 1.]]
    y =[1.,
        1.,
        1.,
        0.]
    
    z = 0.0
    eta = 0.1
    t = 50
    
    print "The weights are:"
    print perceptron(x, y, z, eta, t)[0]
    
    print "The errors are:"
    print perceptron(x, y, z, eta, t)[0]
    Out:The weights are:
    [ 0.2 -0.2 -0.1]
    The errors are:
    [0.5, 1.5, 1.5, 1.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
    
    

    看一看上方的误差,可以看到误差在第6次迭代时趋于0,对于之后的迭代,始终为0。当误差趋于0时,表示模型收敛了。也就是说模型已经正确地“学习”了适当的权重。

    在下一节中,我们将在更大的数据集上使用计算过的权重来做预测。

    5、使用可行的实现进行验证

    到目前为止,我们已经找到了不同的学习资源,手动完成了算法,并通过一个简单的例子在代码中测试了它。

    现在是时候将结果与可行的实现进行比较了。为了比较,我们将使用scikit-learn中的感知器

    步骤如下:

    1.导入数据;

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

    3.训练我们的感知器;

    4.测试感知器;

    5.和scikit-learn的感知器相比;

    1.导入数据

    从导入数据开始,可以在这里获得数据集的副本。

    为了确保感知器能够正常工作,所创建的数据集是线性可分的。为了验证,继续绘制数据。

    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    df = pd.read_csv("dataset.csv")
    plt.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。我想要改变一下,增加一些随机性,所以我要洗牌。

    df = df.values              
    np.random.seed(5)
    np.random.shuffle(df)
    
    

    我首先将数据从dataframe改为numpy数组。这将更容易地使用许多numpy函数,例如.shuffle。

    为了让结果重现,我设置了一个随机种子(5)。完成后,尝试改变随机种子,看看结果如何变化。

    接下来把70%的数据分成训练集,30%分成测试集。

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

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

    x_train = train[:, 0:3]
    y_train = train[:, 3]
    x_test = test[:, 0:3]
    y_test = test[:, 3]
    
    

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

    3. 训练感知器

    接下来,我们要训练感知器。

    这非常简单,我们将重用在前一节中构建的代码。

    def perceptron_train(x, y, z, eta, t):
        '''  Input Parameters:
            x: data set of input features
            y: actual outputs
            z: activation function threshold
            eta: learning rate
            t: number of iterations
        '''
        # initializing the weights
        w = np.zeros(len(x[0]))      
        n = 0                        
        # initializing additional parameters to compute sum-of-squared errors
        yhat_vec = np.ones(len(y))     # vector for predictions
        errors = np.ones(len(y))       # vector for errors (actual - predictions)
        J = []                         # vector for the SSE cost function
    
        while n < t:          for i in xrange(0, len(x)):                                           # dot product             f = np.dot(x[i], w)                                   # activation function             if f >= z:                               
                    yhat = 1.                               
                else:                                   
                    yhat = 0.
                yhat_vec[i] = yhat
    
                # updating the weights
                for j in xrange(0, len(w)):             
                    w[j] = w[j] + eta*(y[i]-yhat)*x[i][j]
    
            n += 1
            # computing the sum-of-squared errors
            for i in xrange(0,len(y)):     
               errors[i] = (y[i]-yhat_vec[i])**2
            J.append(0.5*np.sum(errors))
        return w, J
    z = 0.0
    eta = 0.1
    t = 50
    perceptron_train(x_train, y_train, z, eta, t)
    

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

    w = perceptron_train(x_train, y_train, z, eta, t)[0]
    J = perceptron_train(x_train, y_train, z, eta, t)[1]
    print w
    print J
    Out:
    [-0.5        -0.29850122  0.35054929]
    [4.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
    
    

    权值现在对我们来说意义不大,但我们将在下一节中使用这些数字来测试感知器,还将使用权重来比较我们的模型和scikit-learn模型。

    看一下平方误差之和,我们可以看到感知器已经收敛,这是我们期望的,因为数据是线性可分的。

    4.测试感知器

    现在是测试感知器的时候了。为此,我们将构建一个小型的perceptron_test函数。这和我们已经看到的很相似。这个函数使用perceptron_train函数计算的权值的点积,以及特征,以及激活函数来进行预测。

    我们唯一没有看到的是accuracy_score,这是一个来自scikit-learn的评估度量函数,你可以在这里了解更多。

    把所有这些放在一起,以下是具体的代码实现:

    from sklearn.metrics import accuracy_score
    w = perceptron_train(x_train, y_train, z, eta, t)[0]
    def perceptron_test(x, w, z, eta, t):
        y_pred = []
        for i in xrange(0, len(x-1)):
            f = np.dot(x[i], w)   
            # activation function
            if f > z:                               
                yhat = 1                               
            else:                                   
                yhat = 0
            y_pred.append(yhat)
        return y_pred
    
    y_pred = perceptron_test(x_test, w, z, eta, t)
    
    print "The accuracy score is:"
    print accuracy_score(y_test, y_pred)
    
    Out:
    The accuracy score is:
    1.0
    
    

    准确度为1.0表明我们的模型正确地预测了所有的测试数据。这个数据集显然是可分离的,所以我们期望这个结果。

    5. 和scikit-learn的感知器相比

    最后一步是将我们的结果与scikit-learn的感知器进行比较。下面是这个模型的代码:

    from sklearn.linear_model import Perceptron
    # training the sklearn Perceptron
    clf = Perceptron(random_state=None, eta0=0.1, shuffle=False, fit_intercept=False)
    clf.fit(x_train, y_train)
    y_predict = clf. Predict(x_test)
    
    

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

    Out: sklearn weights: [-0.5 -0.29850122 0.35054929] my perceptron weights: [-0.5-0.29850122 0.35054929]
    

    scikit-learn模型中的权重与我们的相同,这意味着我们的模型工作正常。

    在我们结束之前,有几个小问题需要解决一下。在scikit-learn模型中,我们必须将随机状态设置为“None”并关闭变换,但我们已经设置了一个随机种子并打乱了数据,所以我们不需要再这样做了。

    我们还必须将学习率“eta0”设置为0.1,以与我们的模型相同。

    最后一点是截距。因为我们已经包含了一个虚拟的特征列1s,我们正在自动拟合截距,所以我们不需要在scikit-learn感知器中打开它。

    这些看起来都是次要的细节,但如果我们不设置这些,就无法达到与我们的模型相同的结果。

    这一点很重要。在使用模型之前,阅读文档并理解所有不同设置的作用是非常重要的。

    6、写下你的过程

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

    写下这个过程很重要,原因有二:

    1、你会得到更深的理解,因为你正在教导别人你刚刚学到的东西。

    2、你可以向潜在雇主展示它。

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

    结论

    在这篇文章中,我们学习了如何从零开始编写实现感知器。更重要的是,我们学习了如何找到有用的学习资源,以及如何将算法分解成块。

    然后,我们学习了如何使用一个玩具数据集在代码中实现和测试算法。

    最后,我们通过比较我们的模型和可行实现的结果来结束本文。要获得使用的Python代码的完整副本,单击下面的绿色按钮。

    这是在更深层次上学习算法的一个很好的方法,这样就可以自己实现它了。

    大多数情况下,你将使用可行的实现,但如果你真的想深入了解底层的情况,从头实现它是一个很好的练习。



    本文作者:【方向】

    阅读原文

    本文为云栖社区原创内容,未经允许不得转载。

    相关文章

      网友评论

        本文标题:6个步骤从头开始编写机器学习算法:感知器案例研究

        本文链接:https://www.haomeiwen.com/subject/lngqfqtx.html