当前位置: 首页 > 科技观察

从零开始实现一个机器学习算法只需要六个步骤_0

时间:2023-03-18 23:45:21 科技观察

从零开始写一个机器学习算法可以获得很多经验。当您最终完成时,您会感到惊喜,并且您了解幕后发生的事情。有些算法比较复杂,我们不是从简单的算法开始,而是从非常简单的算法开始,比如单层感知器。本文以感知器为例,通过以下6个步骤指导您从头开始编写算法:implementation写下你的过程1.基本理解不了解基础知识,就不可能从头开始处理算法。至少,您需要能够回答以下问题:它是什么?一般用在什么地方?什么时候不能用?就感知器而言,这些问题的答案如下:单层感知器是最基本的神经网络。对于二进制分类问题(1或0,“是”或“否”)。它可以应用于简单的地方,如情绪分析(正面或负面反应)、贷款违约预测(“会违约”、“不会违约”)。在这两种情况下,决策边界都是线性的。当决策边界是非线性的并且使用不同的方法时,不能使用感知器。2.借助不同的学习资源,对模型有了基本的了解后,就可以开始研究了。有些人通过教科书学得更好,而另一些人通过视频学得更好。就我而言,我喜欢四处探索并从各种资源中学习。书籍非常适合学习数学细节(参见:https://www.dataoptimal.com/data-science-books-2018/),但对于更实际的示例,我推荐博客和YouTube视频。这里有一些关于感知器的好资源:书:《统计学习基础》(统计学习的要素),第4.5.1节(https://web.stanford.edu/~hastie/Papers/ESLII.pdf)《深入理解机器学习:从原理到算法》,第21.4(https://www.cs.huji.ac.il/~shais/UnderstandingMachineLearning/understanding-machine-learning-theory-algorithms.pdf)博客:JasonBrownlee的《如何用 Python 从零开始实现感知器算法》(https://machinelearningmastery.com/implement-perceptron-algorithm-scratch-python/)《单层神经网络和梯度下降》(https://sebastianraschka.com/Articles/2015_singlelayer_neurons.html)SebastianRaschka的视频:感知器训练(https://www.youtube.com/watch?v=5g0TPrxKK6o)感知器算法的工作原理(https://www.youtube.com/watch?v=1XkjVl-j8MM)3.将算法分解成块现在我们已经收集了数据,是时候开始学习了。与其从头开始阅读章节或博客文章,不如从略读章节标题和其他重要信息开始。写下要点,并尝试概述算法。看完这些资料,我把感知机分为以下5个模块:初始化权重将输入和权重相乘然后对上面的结果和阈值求和比较,计算输出(1或0)更新权重Repeat接下来我们详细描述内容每个模块。1.初始化权重(1)首先,我们需要初始化权重向量。权重的数量应该与特征的数量相同。假设我们有三个特征,权重向量如下图所示。权重向量通常初始化为0,并且将始终在本示例中使用。(2)将输入和权重相乘并相加接下来,我们需要将输入和权重相乘并相加。为了更容易理解,我在行***中对权重及其相应的特征进行了着色。在我们将特征和权重相乘之后,我们对乘积求和。这通常称为点积。最后的结果是0,用“f”来表示这个临时结果。(3)与阈值的比较计算完点积后,我们需要将其与阈值进行比较。我将阈值设置为0,你可以使用这个阈值或者尝试其他值。由于前面计算的点积“f”为0,不大于阈值0,所以估计值也等于0。将估计值标记为“yhat”,其中yhat的下标0对应行***。当然第一行也可以用1,没关系,我选择从0开始。如果我们将这个结果与groundtruth进行比较,我们可以看到我们当前的权重并没有正确预测真实的输出。由于我们的预测是错误的,我们需要更新权重,这是下一步。(4)要更新权重,我们将使用以下等式:基本思想是在迭代“n”时调整当前权重,以便在下一次迭代“n+1”时获得新权重。为了调整权重,我们需要设置“学习率”,用希腊字母“eta(η)”表示。我将学习率设置为0.1,但与阈值一样,您可以使用不同的值。到目前为止,本教程已涵盖:现在我们继续计算迭代n=2时的新权重。我们成功完成了感知器算法的第一次迭代。(5)Repeat由于我们的算法未能计算出正确的输出,我们将继续。通常需要大量的迭代。遍历数据集中的每一行,更新每次迭代的权重。通常,完整遍历一个数据集称为一个“epoch”。我们的数据集有3行,因此需要3次迭代才能完成1个epoch。我们还可以设置执行算法的总迭代次数或epochs,比如指定30次迭代(或10个epochs)。与阈值和学习率一样,epoch是一个可以自由使用的参数。在下一次迭代中,我们将使用第二行特征。计算过程在此不再赘述,下图为下一次点积的计算:然后可以将点积与阈值进行比较,计算出新的估计值,更新权重,然后继续。如果我们的数据是线性可分的,感知器最终会收敛。5、从一个简单的例子开始,我们把算法分解成块,接下来就可以开始用代码来实现了。为简单起见,我通常从一个非常小的“玩具数据集”开始。对于此类问题,一个很好的小型线性可分数据集是NAND门。这是数字电路中常见的逻辑门。由于这个数据集很小,我们可以手动将其输入Python。我添加了一列值为1的虚拟特征“x0”,以便模型可以计算偏差项。您可以将偏差项视为驱动模型正确分类的截距项。这是输入数据的代码:#Importinglibraries#NANDGate#Note:x0isadummyvariableforthebiasterm#x0x1x2x=[[1.,0.,0.],[1.,0.,1.],[1.,1.,0。],[1.,1.,1.]]y=[1.,1.,1.,0.]和之前的章节一样,我会一步一步的算法,写代码,测试。1.初始化权重第一步是初始化权重。#Initializetheweightsimportnumpyasnpw=np.zeros(len(x[0]))Out:[0.0.0.]注意权重向量的长度必须匹配特征长度。以与非门为例,其长度为3。2.将权重与输入相乘并求和我们可以在Numpy中轻松地执行此操作,使用的方法是.dot()。从权重向量和第一行特征的点积开始。#DotProductf=np.dot(w,x[0])printfOut:0.0正如我们所料,结果为0。为了与前面的笔记保持一致,让点积为变量“f”。3.与阈值的比较为了与上一篇文章保持一致,将阈值“z”设置为0。如果点积“f”大于0,则预测值为1,否则,预测值为0。将预测值设置为变量yhat。#ActivationFunctionz=0.0iff>z:yhat=1.else:yhat=0.printyhatOut:0.0不出所料,预测值为0,大家可能已经注意到,这一步在代码的注释中被称为“激活函数”多于。这是对这部分内容的更正式的描述。从NAND输出的第一行可以看出实际值为1,由于预测值错误,需要继续更新权重。4.更新权重既然已经做出了预测,我们就可以更新权重了。#Updatetheweightseta=0.1w[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]printwOut:[0.10.0.]和以前一样设置学习率。为了与前面一致,学习率η的值设置为0.1。为了可读性,我将对每个权重更新进行硬编码。权重更新完成。5.重复现在我们已经完成了每个步骤,是时候将它们放在一起了。我们还没有讨论的最后一步是我们需要最小化的损失函数,在本例中是误差项平方和。我们将使用它来计算误差,然后查看模型的性能。把它们放在一起,完整的功能:len(x[0]))n=0#initializingadditionalparameterstocomputesum-of-squarederrorsyhat_vec=np.ones(len(y))#vectorforpredictionserrors=np.ones(len(y))#vectorforerrors(实际预测)J=[]#vectorfortheSSEcostfunctionwhile=z:yhat=1.else:yhat=0.yhat_vec[i]=yhat#updatingtheweightsforjinxrange(0,len(w)):w[j]=w[j]+eta*(y[i]-yhat)*x[i][j]n+=1#computingthesum-of-squarederrorsforiinxrange(0,len(y)):errors[i]=(y[i]-yhat_vec[i])**2J.append(0.5*np.sum(errors))returnw,此时J已经写完了Perceptron的完整代码,然后是运行代码:#x0x1x2x=[[1.,0.,0.],[1.,0.,1.],[1.,1.,0.],[1.,1.,1.]]y=[1.,1.,1.,0.]z=0.0eta=0.1t=50print"权重是:"printperceptron(x,y,z,eta,t)[0]打印“Theerrors是:"printperceptron(x,y,z,eta,t)[0]我们可以看到在第6次迭代中误差趋近于0,而在剩余的迭代中误差始终为0。当误差趋近于0时,当保持在0时模型已经收敛。这告诉我们模型已经正确地“学习”了适当的权重。在下一部分中,我们将使用计算出的权重对更大的数据集进行预测。5.使用可信实现进行验证所以到目前为止,我们已经找到了不同的学习资源,手动完成了算法,并用简单的例子测试了算法。现在是时候将我们的模型与可信实现进行比较了。我们正在使用来自scikit-learn(http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.Perceptron.html).我们将按照以下步骤进行比较:导入数据将数据拆分成训练集和测试集TrainperceptronTestperceptron和scikit-learnperceptron比较1.导入数据首先导入数据。你c在此处获取数据集的副本(https://github.com/dataoptimal/posts/blob/master/algorithmsfromscratch/dataset.csv)。这是我为确保感知器正常工作而创建的线性可分数据集。为了确认,我们还将数据绘制成图表。从图中很容易看出,我们可以用一条直线来分隔数据。importpandasaspdimportnumpyasnpimportmatplotlib.pyplotaspltdf=pd.read_csv("dataset.csv")plt.scatter(df.values[:,1],df.values[:,2],c=df['3'],alpha=0.8)text在继续之前,让我解释一下绘图的代码。我用Pandas导入csv,它会自动将数据放入DataFrame中。为了绘制数据,我想从DataFrame中取出值,所以我使用.values方法。这些特征在列***和2中,因此我在散点图函数中使用了这些特征。第0列是值为1的虚拟特征,以便可以计算截距。这类似于上一节中的与非门操作。***,让散点图函数中的c=df['3'],alpha=0.8给两个类上色。输出是第三列数据(0或1),所以我告诉函数用“3”列为两个类着色。您可以在此处找到有关Matplotlib的散点图函数的更多信息(https://matplotlib.org/api/_as_gen/matplotlib.pyplot.scatter.html)。2.将数据拆分为训练集/测试集现在我们已经确定数据是线性可分的,是时候拆分数据了。最好在与测试集不同的数据集上训练模型,以帮助避免过度拟合。有不同的方法,但为了简单起见,我将使用训练集和测试集。先打乱数据。dfdf=df.valuesnp.random.seed(5)np.random.shuffle(df)先把数据从DataFrame改成numpy数组。这使得使用.shuffle等numpy函数变得更加容易。为了结果的可重复性,我设置了一个随机种子(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-foldcross-validation等其他方法。3.训练感知器我们可以重用前面章节中构建的代码。defperceptron_train(x,y,z,eta,t):'''InputParameters:x:datasetofinputfeaturesy:actualoutputsz:activationfunctionthresholdeta:learningratet:numberofiterations'''#initializingtheweightsw=np.zeros(len(x[0]))n=0#initializingadditionalparameterstocomputesum-of-squaderrorsyhat_vec=np.ones(len(y))#vectorforpredictionserrors=np.ones(len(y))#vectorforerrors(实际预测)J=[]#vectorfortheSSEcostfunctionwhilen=z:yhat=1.else:yhat=0.yhat_vec[i]=yhat#updatingtheweightsforjinxrange(0,len(w)):w[j]=w[j]+eta*(y[i]-yhat)*x[i][j]n+=1#computingthesum-of-squadererrorsforiinxrange(0,len(y)):errors[i]=(y[i]-yhat_vec[i])**2J.append(0.5*np.sum(errors))returnw,Jz=0.0eta=0.1t=50perceptron_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]printwprintJOut:[-0.5-0.298501220.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中的评价指标函数。将所有这些放在一起,代码如下:fromsklearn.metricsimportaccuracy_scorew=perceptron_train(x_train,y_train,z,eta,t)[0]defperceptron_test(x,w,z,eta,t):y_pred=[]foriinxrange(0,len(x-1)):f=np.dot(x[i],w)#activationfunctioniff>z:yhat=1else:yhat=0y_pred.append(yhat)returny_predy_pred=perceptron_test(x_test,w,z,eta,t)print"Theaccuracyscoreis:"printaccuracy_score(y_test,y_pred)1.0的分数意味着我们的模型对所有测试数据做出了正确的预测。由于数据集显然是可分离的,因此结果符合预期。5.与scikit-learn感知器比较最后一步是将我们的感知器与scikit-learn的感知器进行比较。以下代码是scikit-learnPerceptron代码:fromsklearn.linear_modelimportPerceptron#trainingthesklearnPerceptronclf=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:sklearnweights:[-0.5-0.298501220.35054929]myperceptronweights:[-0.5-0.298501220.35054929]scikit-learn模型中的权重和我们模型中的权重完全一样。这意味着我们的模型可以正常工作,这是个好消息。在我们结束之前还有一些小问题。在scikit-learn模型中,我们将随机状态设置为“None”并且没有对数据进行打乱。这是因为我们已经设置了随机种子并打乱了数据,所以不需要再做一遍。我们还需要将学习率eta0设置为0.1,与我们的模型相同。***点是截距。由于我们将虚拟特征列设置为1,模型可以自动拟合截距,因此无需在scikit-learn感知器中打开它。这些看似很小的细节,但如果不设置它们,我们的模型就无法重复得到相同的结果。这才是重点。在使用模型之前,阅读文档并了解不同设置的作用非常重要。6.写下你的过程这是过程的最后一步,也可能是最重要的一步。您刚刚完成了学习、做笔记、从头编写算法并将其与可信实现进行比较的过程。不要浪费这些努力!写过程有两个原因:你想更深入地了解过程,因为你也想把你学到的东西教给别人。您需要向潜在雇主展示该过程。从机器学习库中实现算法是一回事,从头开始实现它又是另一回事,它会给人留下深刻印象。GitHub个人资料是展示您所做工作的好方法。总结本文描述了如何从头开始实现感知器。这是在更深层次上学习算法的好方法,同时仍然自己实现它。在大多数情况下,您将使用受信任的实现,但如果您真的想更深入地了解引擎盖下发生的事情,最好从头开始实现算法。原文链接:https://www.dataoptimal.com/machine-learning-from-scratch/【本文为《机器之心》专栏原文翻译,微信公众号《机器之心》(id:almosthuman2014)"]点此阅读作者更多好文