<基础原理进阶>机器学习算法python实现【0】回归简谈&三种梯度下降算法

来源:互联网 发布:软件界面显示不全 编辑:程序博客网 时间:2024/06/03 13:39

#####################

适合对算法的数学描述有一定理解的童鞋看~~

对TensorFlow中直接封装好的算法的具体python实现,补充另一个分类中的知识

参考书籍: 《机器学习实战》等

有些代码我自己也有问题,思考中已经解答的问题和一些坑我会注意说明的。也欢迎大家提出问题,相互交流进步

学习之后记录博客,对自己起到supervised learning的作用 :)

大家一起学习,进步,享受深度学习的乐趣吧!

需要引用的数据集,以及完整代码,放在github上,与这个的标题一一对应的~

https://github.com/Skyorca/csdn-

#####################

1. 万物伊始

作为机器学习算法的开篇之谈,让我们简单谈一下相关的背景知识。注意,我用的词语是“机器学习”,而不是“深度学习”!也就意味着我更多地会去实现传统的内容,而涉及到搭建各种神经网络慢慢地会用TF去做。


这张图大概告诉了我们,机器学习是要做什么。而目前我的进度还停留在回归上。所谓的回归,就是找出一条数据的最佳拟合曲线。找到曲线之后,我们可以做预测,也可以做分类。比如经典的sigmoid函数,我们可以以0.5为准,上下分成两类来实现最简单的分类器。回归又分为线性的和非线性的。当然,线性回归基本只停留在课本的前几页上,因为它的描述能力太弱了。比如加一个二次项,它的描述力就会极大增强。这里我们选择对线性模型施加非线性激励的方法。

说到非线性激励,它就有很多模型可以选择了。最常见的,除了sigmoid外,还有ReLU,softmax等等,不过那些都是后话。这里还是谈到sigmoid函数。它有两个好处:

1.输出值都在【0,1】区间,很实用

2.求导简单,s'=s(1-s)这样。稍后就会看到它的优势。


2.过渡

那么就有一个问题:为什么梯度下降算法被封装在了TF中呢?大概就是:对于一个网络,训练时有前传和后传两个过程。向后传递(反向传播)时,就需要用到梯度下降去最小化损失函数,完成一个训练过程。之后用得到的新参数继续投入轰轰烈烈的“前传--后传”训练中来。所以我们有必要亲自实现一遍如此基本重要的算法,而不是直接用它封装的东西就草草了事。大家在我的TF分类中也会看到它在BP神经网络中的应用。


3.简单复习梯度下降的数学基础

谈一下数学推导,适合有基础的朋友们过一遍印象~

注意,这里并没有任何和非线性激励有关的内容,是基于线性回归的普适过程


这是我们的线性部分


OK,我们得到了成本函数,目标是最小化它


核心:梯度下降的更新参数公式


这是我们写算法的核心。梯度我们写不了,所以要找一个等价的形式。

它有两种写法:批处理和随机。一会儿再说


3.python实战

总之,不要想多,牢记最基本的更新公式核心

实现第一个梯度下降函数BatchGradientDescent,它的原理如下


也就是说,对参数每更新一次,就要遍历整个数据集。这是很不方便的。

import numpy as npimport timeprint '\nMLAlgo1: Different Versions of Gradient Descent by ORCA\n'# import datadef LoadDataSet():    fr = open('testset1.txt')    data = []      #like placeholder in TF    label = []    for line in fr.readlines():        line_row = line.strip().split()  #split testset as different rows        data.append([1.0, float(line_row[0]), float(line_row[1])])     #1.0 for easy doing        label.append(int(line_row[2]))  #why ([int(line_row[2])]) is wrong when transposing?    return data, label#so we get two list: data(100*1)(every elem is [x,y,z]), label(100*1)#but in python grammer there is no matrix, so we need to turn them into matrix by Numpydef sigmoid(x):    return 1.0/(1+np.exp(-x))def BatchGradientDescent(data_in, label_in):   #remeber: here is not real matrix    data_matrix = np.mat(data_in)    label_matrix = np.mat(label_in).transpose()    m, n = np.shape(data_matrix)    learning_rate = 0.05    training_time = 1000    weight = np.ones([n,1])  #weight is a 3*1 matrix    print m,n    for i in range(training_time):        h = sigmoid(data_matrix*weight)  #h is a 100*1 matrix        error = (label_matrix-h)  #error is a 100*1 matrix        weight = weight+learning_rate*data_matrix.transpose()*error  #update weight    return weightstart = time.clock()print 'ver1: Batch Gradient Descent\n'data_in, label_in = LoadDataSet()print 'weight=:', BatchGradientDescent(data_in, label_in), 'using time', time.clock()-start, 'seconds'

这里需要一点处理数据的知识,不懂的函数可以去查一下,增长知识学习一个~~这里暂不提供说明


问题注意:

所有的变量都是以矩阵形式出现,因为我们要一次引用整个数据集嘛!而python本身不支持矩阵,所以我们引入numpy库就变得很自然很方便。

一个问题:数学公式里的sigma求和在算法里如何体现?仔细观察如下这行代码:

weight = weight+learning_rate*data_matrix.transpose()*error

转置后的data_matrix矩阵是3*100的,error是100*1的列向量,它们相乘就是3*1的列向量,对应3个参数,也就是weight矩阵中的三个元素。而矩阵相乘时每个元素等于对应位置乘积相加......所以一次完成了对所有参数的求和操作。

还有一个问题:怎么去理解这样的update过程?

我们可以放空大脑,回到梯度下降的核心公式上来。每次更新就是迭代一次公式。而它在具体算法中有一些不同的体现,根据数据集不同用一些不太相同的手法去表现它。这里的batch就是一种。稍后我们会谈到stochastic以及advanced_stochastic。


坑注意:

对这行代码:

label.append(int(line_row[2])) 

我一开始写成了
label.append([int(line_row[2])]) 

之后转换成numpy矩阵做转置后运算时就出了莫名其妙的结果。于是我做了小实验,直接打印这样的矩阵是正常的,而打印它的转置就不对劲了。看来一定要熟悉numpy库才行。估计和它里面的实现有关,暂时放着。


实现第二个梯度下降函数StochasticGradientDescent:


看一个数据就产生一次结果,有时候当然比一次遍历整个数据集才出结果更好啦

def StomaticGradientDescent(data_in, label_in):    m, n = np.shape(data_in)    learning_rate = 0.05    weight = np.ones(n)      for j in range(m):        h = sigmoid(np.sum(data_in[j]*weight))  #weight is a 3*1 vector        error = (label_in[j]-h)          weight = weight+learning_rate*error*data_in[j]    return weightstart = time.clock()print 'ver2: Stomatic Gradient Descent\n'data_in, label_in = LoadDataSet()
print 'weight=:', StomaticGradientDescent(np.array(data_in), label_in)

print 'using time', time.clock()-start, 'seconds'

1.不再一次使用全部数据,因此变量都退化成了一个具体数值。weight是一个3*1的列向量。

2.按序使用每个数据并更新一次参数

坑注意:

print 'weight=:', StomaticGradientDescent(np.array(data_in), label_in)


如果data_in不以np数组传入的话会出错


一个问题:怎么每次更新weight向量看起来对每个参数都更新相同的值啊?


 weight = weight+learning_rate*error*data_in[j]


注意看,右边的因子data_in[j]表示第j个数据,它的三个元素x1,x2,x3也不一样啊~所以每次更新对不同参数的是不同的值。

微笑


累了吗?放松一下,还有最后一环~~

最后一个梯度下降AdvancedStochasticGradientDescent算法:

【为什么每次名字都写这么全啊?你看人家TF封装的函数名字就是gradientdescent......多写点熟练熟练】

相比前一点,它的优化如下:

1.学习率可变!随着每次迭代而改变!为什么要改变?书中写到“缓解数据波动”。由于我还没学到可视化处理,这里暂时放着,以后会专门做一篇可视化专题来补充的。

2.增加了迭代次数!每一次迭代使用随机梯度下降算法!

3.随机梯度下降中不再有序选择样本!而是随机选取(uniform,等概率的选取),这样可以减少周期性波动。


def AdvancedStomaticGradientDescent(data_in, label_in, iter_time):    m, n = np.shape(data_in)    weight = np.ones(n)    for i in range(iter_time):        dataset = range(m)        for j in range(m):            learning_rate = 4/(1.+i+j)+0.05            rand_index = int(np.random.uniform(0, len(dataset), 1))            h = sigmoid(np.sum(data_in[rand_index]*weight))            error = label_in[rand_index]-h            wieght = weight+learning_rate*error*data_in[rand_index]            del(dataset[rand_index])    return weightstart = time.clock()print 'ver3: Adanced Stomatic Gradient Descent\n'print '1. changeable learning rate  2. randomly pick up data in one iteration\n'data_in, label_in = LoadDataSet()print 'weight=:', AdvancedStomaticGradientDescent(np.array(data_in), label_in, 500)print 'using time', time.clock()-start, 'seconds'

至于学习率与发散收敛的关系......没有仔细研究,我们可以有空一同google一下~


看到这里可能有童鞋有疑问:对于weight的更新,你给的适用写算法的公式是针对线性的啊,可是这里不是用了sigmoid激励么......是的没错。但是,我们可以用最大似然估计改写J(theta),然后对它求梯度,结果依然还是这样......大概就是说,不论用了怎样的激励,这个适用算法的公式一直是对的,只不过把相应的函数替换一下就成。


这里我有一个问题:对于第三个算法,不知为什么,更换迭代次数不论多少,得到的值永远都是[1,1,1],就像根本没更新一样!怀疑是哪里出了问题,但现在通宵之后不是很相信自己的脑子,先放着。欢迎大家指点~~~


终于......结束了??!嘛,这就是机器学习里面最基本的算法之一,梯度下降......不得不佩服这个思想简单而超级实用。忘记什么,核心公式也不能忘......


天空鱼

Every Machine owns a ❤



                                             
0 0
原创粉丝点击