专栏/机器学习科普 (3) 非线性回归

机器学习科普 (3) 非线性回归

2019年08月20日 11:56--浏览 · --喜欢 · --评论
OriginLab
粉丝:19.8万文章:33

前文回顾

    在第一篇文章中,介绍了如何用机器学习来处理线性回归问题。

    我们用Keras搭建了一个模型,这个模型只有一个计算层,如下图所示。

线性回归模型

这个计算层只有一个计算单元,它带有参数𝑤和𝑏,执行𝑦 = 𝑤𝑥 + 𝑏的计算,并输出𝑦。初始时,𝑤和𝑏的值可以是任意的。我们往里面输入一个数据𝑥,计算单元就会计算出一个𝑦。这样计算得到的𝑦与真实的𝑦值(用𝑦₀表示)之间可能会有较大的差别,于是我们改变𝑤和𝑏,以缩小𝑦与𝑦₀之间的差距。通过这种方法不断地改变𝑤和𝑏,最终我们就能得到一条合理的拟合线。

一些术语

    在这个例子中,数据点的𝑥坐标和𝑦坐标之间存在着联系。我们告诉计算机对于每一个𝑥,𝑦应该是多少;目标是让计算机帮我们找出𝑥和𝑦之间的关系,这样的话,我们就可以通过𝑥来预测𝑦。在机器学习中,𝑥叫做数据的特征(feature),𝑦叫做标签(label)。我们对模型进行训练时,告诉计算机数据的特征,及其对应的标签,这样的学习就叫做监督式学习(supervised Learning)。回归问题是典型的监督式学习问题,另一类常见的监督式学习是分类,比如给定一个垃圾,想要训练电脑判断它是干垃圾、湿垃圾、有害垃圾,还是可回收垃圾?这里的类别就是垃圾的标签。

非线性回归 (1):初步尝试

    现在我们稍稍增加一点复杂度,让机器学习来处理非线性回归问题。假如我们有如下图所示的一组数据(共200个),想要通过机器学习来得到这组数据的拟合线。

非线性回归数据

剧透一下:这些数据点是利用𝑦 = 2𝑥² - 1加上随机误差生成的。随机误差的标准差为0.06。

    首先,我们还是把这些数据分成训练组(160个)和测试组(40个)。代码如下:

        # 导入必要的库

        import tensorflow as tf

        import numpy as np

        # 将数据分成训练组和测试组,_train表示训练数据,_test表示测试数据

        split = 160

        x_train = x[:split]

        y_train = y[:split]

        x_test = x[split:len(x)]

        y_test = y[split:len(y)]

我们姑且直接用第一讲使用的模型来训练这些数据,看看会有什么结果。

构建模型

        model2 = tf.keras.models.Sequential()

        model2.add(tf.keras.layers.Dense(1, input_dim=1))

编译模型

        model2.compile(optimizer='Adam', loss='mse')

这三行代码都是从第一讲中复制过来的,不过稍稍做了修改:此处我们把模型命名为model2,另外将优化器改用为“Adam”优化器,因为经过试验发现它的表现要更好。(继续使用SGD优化器也没问题,只不过学习过程会更漫长一些。)

训练模型

    将训练数据输入到模型中,进行训练:

        model2.fit(x_train, y_train, epochs=100, batch_size=10)

括号中第一个参数是特征,第二个参数是标签。另外我们曾讲过,epochs是控制训练次数的参数,epochs=100表示所有数据都要放到模型中训练100遍。batch_size则是每一批使用数据的数量。我们总共有160个数据,每一次使用10个数据对模型进行训练,16次之后所有数据都被使用过,这就完成了一个训练周期。我们先让计算机完成100个训练周期。

    在训练时,我们要注意查看loss的输出。在训练100个周期之后,loss降低到了0.39左右(由于初始𝑤和𝑏的不确定性,这个结果可能会稍有不同),但查看loss的变化趋势,发现它并没有到达一个稳定值。因此再次运行训练代码,模型会在之前训练的基础上再度训练100个周期。(如果想要让模型从头开始训练,则先执行编译代码。)这样,在训练200个周期之后,我们发现loss基本上稳定在0.35左右。这就意味着模型训练完成了。

评估训练结果

    我们将测试数据放入训练过的模型之中,看看模型预测的𝑦值与真实𝑦值的差异。

        model2.evaluate(x_test, y_test)

结果为:0.3165011167526245。这个值是均方差,也就是说,预测值与真实值之间差值的平方。开根号之后,结果是0.56,这就意味着平均下来,每个数据的误差超过了0.5。读者可以回过头去看看上面给出的数据散点图,𝑦的取值范围在-1到1之间,0.56的均方差无疑太大了。

    我们用图像来直观地查看一下训练结果:

训练结果

红色是我们得到的拟合线,它是一条直线。得到这样的结果,怪不得误差会很大。不过这也难怪,我们使用的简单模型只有𝑤和𝑏两个参数,它只能做𝑦 = 𝑤𝑥 + 𝑏这样的线性拟合。

非线性回归 (2):稍稍增加一点复杂度

    我们必须让模型稍稍变得复杂一些,以让它能够拟合更复杂的数据。如下图所示,我们在输入和输出之间增加了一个计算层。

稍微复杂一点的模型

新增加的计算层可以有不止一个计算单元,图中画了4个。每个计算单元都从输入层获取数据𝑥,执行一个𝑤𝑥 + 𝑏的计算(各计算单元的𝑤和𝑏值可以是不一样的)。我们用𝑟₁, 𝑟₂, 𝑟₃, 𝑟₄来表示各计算单元的计算结果。这些结果都被输送到右边一层,该层执行计算:𝑦 = 𝑤₁𝑟₁ + 𝑤₂𝑟₂ + 𝑤₃𝑟₃ + 𝑤₄𝑟₄ + 𝑏 (下标不同的𝑤值一般来说是不同的),最终输出结果𝑦。各个𝑤决定了各输入数据对输出值的影响大小,因此称𝑤为权重(weight)。比如,𝑤₁ = 0, 𝑤₂ = 𝑤₃ = 0.1, 𝑤₄ = 10,则无论𝑟₁如何变化,它都不会影响到𝑦;𝑟₂和𝑟₃每变化1,𝑦就会变化0.1;而𝑟₄则具有较大的权重,它的变化会十倍影响于𝑦。

    电脑程序将输出的𝑦与实际的𝑦做比较,然后调整各个计算单元的𝑤和𝑏,以降低输出结果的偏差,至于具体如何调整,我们暂且全权交给优化器去做,就不再关心了。最终,电脑将找到各个计算单元的最优𝑤和𝑏值,在这样训练好的模型中,我们输入𝑥,会得到偏差最小的𝑦值。

一些术语

    图中的各个计算单元称作神经元,各神经元之间的输入输出联系构建了一张关系网,这就是神经网络。中间的计算层并不直接与外界发生关系,对外界来说,这就好像是一个黑盒,我们往输入层输入数据,从输出层获取输出数据,并不和中间的计算层打交道,因此称中间的计算层为隐藏层。图中只有一个隐藏层,对于复杂的学习过程,往往需要多个隐藏层。(随手百度了一下,AlphaGo共有13层。)隐藏层数超过3的机器学习就可叫做深度学习

构建模型

    现在让我们来构建模型。代码如下:

        model3 = tf.keras.models.Sequential()

        model3.add(tf.keras.layers.Dense(16, input_dim=1))

        model3.add(tf.keras.layers.Dense(1))

第1行仍旧是先搭建一个空模型,并命名其为model3。

第2行,向模型model3中添加一层。括号中的第一个参数,是数字16,表示该计算层共有16个神经元,这样的话该层也就有16个输出数据;而参数imput_dim=1表示输入数据只有一个 (𝑥),告诉计算机这一信息,计算机就会为每个神经元只准备1个𝑤。

第3行,再向模型中添加一层。括号中的数字1表示该层只有1个神经元,因而只输出一个数据 (即𝑦)。这一层我们没有指定输入数据的数量,这是因为根据上一层的情况,程序可以自动获知输入数据的数目为16,因此会准备好16个𝑤,即16个输入数据所对应的权重。

编译模型

        model3.compile(optimizer='Adam', loss='mse')

训练模型

        model3.fit(x_train, y_train, epochs=100, batch_size=10)

训练了100个周期后,loss值收敛到了0.35左右。(其实从10个周期开始就已经收敛了;对比上一个模型,我们训练了100遍之后loss值还没有收敛,看来增加的一个层大大增加了机器学习的效率。)但是loss值仍然和之前差不多;对测试数据做评估,得到的loss为0.3164690971374512,和先前相差无几,让我们感觉有点不太妙。画出图来看看:

训练结果

拟合线依然是一条直线。不过想想也是:哪怕添加再多的计算层、计算单元,这些计算单元归根到底做的都是线性运算,而线性运算的组合仍旧是线性运算。比如:假如𝑟 = 𝑤₁𝑥 + 𝑏₁,  𝑦 = 𝑤₂𝑟 + 𝑏₂,则𝑦 = 𝑤₂(𝑤₁𝑥 + 𝑏₁)+𝑏₂ = (𝑤₁𝑤₂)𝑥 + (𝑤₂𝑏₁ + 𝑏₂),最右边表达式中,括号括起来的都是常量,可见𝑦和𝑥说到底仍然是线性关系。

非线性回归 (3):引入非线性

    目前为止,我们的模型只是一个线性模型,无法处理非线性问题。如果只是这样的话,机器学习的应用范围就太狭隘了,毕竟世界上还有大量的非线性问题。为了能够处理这些问题,我们必须在模型中引入非线性。

激活函数

    机器学习通过激活函数来引入非线性。

激活函数

    如上图所示,我们在计算层之后增加了一个激活函数,计算层的所有输出都会进入该激活函数层,经过一个函数变换后再进入右边一层进行计算。比如说,我们选取双曲正切函数tanh为激活函数,则左边一层得到的4个结果𝑟₁, 𝑟₂, 𝑟₃, 𝑟₄都要求双曲正切之后再输入右边,最终给出的输出结果是𝑦 = 𝑤₁tanh(𝑟₁) + 𝑤₂tanh(𝑟₂) + 𝑤₃tanh(𝑟₃) + 𝑤₄tanh(𝑟₄) + 𝑏。

    (右边那一层也可以加激活函数。如果在右边一层也加一个双曲正切激活函数的话,则最终输出将是𝑦 = tanh[𝑤₁tanh(𝑟₁) + 𝑤₂tanh(𝑟₂) + 𝑤₃tanh(𝑟₃) + 𝑤₄tanh(𝑟₄) + 𝑏]。但在下面的示例中,我们将只加一层激活函数。)

    机器学习中的常用激活函数有ReLu,Sigmoid,tanh等。最后一个大家可能比较熟悉前三个字母,但加个h之后就陌生了;前面两个对于初涉机器学习领域读者估计还是第一次遇见。我们先盲目地使用它们,看看效果,然后再做具体分析。

使用ReLu激活函数

        model4 = tf.keras.models.Sequential()

        model4.add(tf.keras.layers.Dense(8, input_dim=1, activation = 'relu'))

        model4.add(tf.keras.layers.Dense(1))

        model4.compile(optimizer='Adam', loss='mse')

第2行添加的计算层包含8个神经元。activation = 'relu'表示对该层的输出使用ReLu激活函数。对模型进行训练,同时对loss进行监控。我们发现训练400个周期之后,loss收敛到0.0045,也就是说标准差为0.067,应该相当不错了。看看图像结果:

用ReLu激活函数的训练结果

确实,这个拟合结果还算不错。不过细看拟合线,不难发现它是由多段直线组成的。

使用Sigmoid激活函数

    摘录一段网上的文字:“Sigmoid函数曾经被使用的很多,不过近年来,用它的人越来越少了。主要是因为它的一些缺点……”可见这曾经是一个非常流行的激活函数。虽然因为一些缺点而不再那么流行,但做这样一个简单的回归问题应该小菜一碟吧?让我们来看看效果。

        model5 = tf.keras.models.Sequential()

        model5.add(tf.keras.layers.Dense(16, input_dim=1, activation = 'sigmoid')) # 16个神经元,使用Sigmoid激活函数

        model5.add(tf.keras.layers.Dense(1))

        model5.compile(optimizer='Adam', loss='mse')

训练之后,loss很快就收敛,收敛值是……0.35左右?看来不太妙。作图看看:

用Sigmoid激活函数的训练结果

怎么这个激活函数好像没什么效果呢?我们先不管它(后文会做分析),还是再来试试别的激活函数。

使用tanh激活函数

        model6 = tf.keras.models.Sequential()

        model6.add(tf.keras.layers.Dense(8, input_dim=1, activation = 'tanh')) # 8个神经元

        model6.add(tf.keras.layers.Dense(1))

        model6.compile(optimizer='Adam', loss='mse')

直到训练800个周期后,loss终于收敛在0.0036附近。我们用均方差来描述loss,因此对loss开根号,就得到了标准差,约为0.06。回想一下,生成这些数据时,我们设定的标准差正是0.06,看来我们这次应该得到了一个非常好的结果。作图结果如下:

用tanh激活函数的训练结果

确实,从图像上来看,这条拟合曲线也是令人满意的。

    对测试数据作评估,得到loss为0.0027,甚至比训练数据的loss更小。这显然是测试数据集不够多所导致的,这些测试数据恰好误差偏小。而在有些情况下,测试数据的loss明显比训练数据来得大,这时候就要小心了,有可能发生了过拟合现象;我们将在后面介绍这种情况。

    我们所使用的数据是通过函数𝑦 2𝑥² − 1加上随机误差生成的。而在对数据作拟合之后,我们得到了一条贯穿整个数据集的曲线。那么,我们是否获得了𝑦 2𝑥² − 1抛物线呢?并非如此。由我们搭建的模型可知,我们在对数据做拟合时,实际上用的是这个函数:

通过寻找最优的𝑤𝑏参数(公式中有16个𝑤和9个𝑏,共计25个可调参数),使得该函数能较好地拟合数据。最终得到的拟合结果并非我们预设的抛物线,下图画出了拟合函数(红)与抛物线(蓝)的示意图,可以看到它们的明显区别。无论如何,在数据集范围[1,1]内,拟合曲线与抛物线一样能够很好地描述数据规律。

拟合函数(红)与我们设置数据时所用的抛物线(蓝)的对比

    机器学习的原理,归根到底就是构造具有大量参数的函数,通过对各个参数进行调整,以使得函数能够拟合数据。在最简单的线性回归问题中(如第一篇文章所介绍的),人类一下子就能看出数据点所满足的线性规律,但计算机看不出来,必须从一条任意直线出发,一步步逼近数据分布的直线(参见第二篇文章中的动图)。在本文所讲的非线性回归问题中,可能人类一下子就会猜测这是条抛物线,然后写一个抛物线方程𝑦=𝑎𝑥² 𝑏 𝑐,通过调整参数𝑎, 𝑏, 𝑐来拟合数据。但计算机只会根据我们所搭建的模型(实际上是给计算机预先提供了一个有很多参数函数形式),通过调整参数来得到一条拟合曲线,最终得到的也并非抛物线,但能很好地拟合给定的数据集。在这些简单问题中,计算机的处理方式未免显得有点“笨拙”。但是,对于一些更复杂的问题,人类可能很难去找出数据背后的规律,这时候计算机却有可能利用一个复杂的函数对数据进行拟合,强行找出一个规律来。冯·诺依曼曾说过:给我四个参数,我能拟合出一头大象;再给我一个参数,我能让大象的鼻子动起来。而机器学习动辄就有成百上千个参数,能找出各种数据背后的规律来也就不足为奇了。

总结

    在本文中,我们利用激活函数实现了非线性回归。目前我们只是盲目地使用激活函数,查看它们的效果。tanh函数最终给出的模型最好;ReLu“不太弯”,用来拟合曲线难免让人感觉别扭,但最终结果也算不错;Sigmoid则似乎完全不管用。在下一篇文章中,我们将对激活函数做进一步的考查,以理解这些结果。

投诉或建议

玻璃钢生产厂家莱芜不锈钢花盆定制东营商业美陈多少钱安阳玻璃钢浮雕定做抚顺不锈钢花盆定制郑州玻璃钢设备外壳价格莱芜玻璃钢雕塑哪家好莆田玻璃钢人物雕塑厂张掖玻璃钢人物雕塑厂家直销深圳不锈钢花盆制造资阳玻璃钢动物雕塑制作白城玻璃钢造型厂家钦州商业美陈定做嘉兴商场美陈价格昌都玻璃钢树池坐凳加工怀化玻璃钢树池坐凳制作山南玻璃钢休闲椅东莞玻璃钢花箱厂家玉林玻璃钢花钵价格杭州玻璃钢座椅公司西安玻璃钢价格江苏玻璃钢医疗外壳加工汕尾玻璃钢浮雕定制湖南玻璃钢花槽加工朔州玻璃钢花箱定制临沂玻璃钢树池批发肇庆商业美陈公司大庆玻璃钢沙发定制郴州玻璃钢浮雕哪家好滁州玻璃钢人物雕塑加工遵义玻璃钢景观雕塑厂香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化