机器学习的数学基础

目录

介绍 

需要了解的计算

四则运算

乘方

求和

函数

函数的单调性

微积分

函数的极限

无穷大

无穷小量

阶的比较

高阶无穷小和低阶无穷小

同阶无穷小

微分

微分的定义 

微分的运算法则

 基本初等函数的微分公式

举例

导数

极值和极值点

通过导数判断函数单调性

链式求导

积分

积分的运算法则

基本积分表

举例

多元函数

向量

梯度

梯度下降

梯度下降的问题

目标函数

损失函数

举个例子


 

介绍 

机器学习需要一定的数学基础,包括基础数学知识,和微积分、线性代数和概率论等相关知识。本文将尝试横跨这些领域,详细地讲解机器学习的数学基础。

需要了解的计算

四则运算

加减乘除,如果不知道这个的话还是翻翻小学课本吧。另外,乘号也可以用·表示,或在两个字母之间省略不写。除法一般写成分数的形式。

乘方

乘方就是将某个数自己乘自己多少次,记作eq?a%5Eb,如:

eq?a%5Eb%3Da%20%5Ccdot%20a%5Ccdot%20a...

等号后面共有b个a相乘

求和

假设有一个数据,共有N个样本,第i个样本可以表示为eq?x_i,则这些样本之和可以表示为:

eq?%5Csum%5Climits%5EN_%7Bi%3D1%7Dx_i

具体可以参考这张图片:

70019134c1794c958fcfcc320bac0bbe.png

函数

函数的定义很简单,这里给出它在初中的定义,够用即可。

一般地,在一个变化的过程中,如果有两个变量x与y,满足对于x的每一个取值,y都有唯一确定的值与之对应,则称x为自变量,y是x的函数。

注意这里边的“唯一确定”指的是与一个x对应的y只有一个。比如,如果当x=-2和x=2时y=1是可以的,但是如果x=2时y=1或2是不行的。

另外,x的取值范围称为定义域,y的取值范围称为值域。定义域和值域可以用区间表示:

  • (a, b)表示a<x<b
  • [a, b)表示a≤x<b
  • (a, b]表示a<x≤b
  • [a, b]表示a≤x≤b

函数可以通过解析式和图象表示:

  • 解析式:形如y=f(x)的代数式就是函数的解析式,其中,f(x)表示对应关系,比如f(x)=2x.
  • 图象:对于一个函数,如果把每个自变量和对应的函数分别设为点的横、纵坐标,则组成的图形成为函数的图象
6e4cbe1f75eb4aed8eaa670cfe6d25d9.png

图为一个函数的图象,可以看到它的定义域为一切实数,值域为y≥1

图为函数eq?y%3D3x%5E%7B_%7B2%7D%7D&plus;6x&plus;4的图象,可以看到,它在直线y=1以下没有取值,因此,它的定义域为一切实数,值域为y≥1

通过函数图象,我们可以直观的看出函数的一些性质。

函数的单调性

如果对于在函数eq?y%3Df%28x%29定义域内的一个区间内的任意两个数eq?x_1eq?x_2,其中eq?x_1%3Cx_2,都满足eq?f%28x_1%29%3Cf%28x_2%29,则称函数eq?y%3Df%28x%29单调递增,是增函数;如果都满足eq?f%28x_1%29%3Ef%28x_2%29,则称函数eq?y%3Df%28x%29单调递减,是减函数

4edb3da1ee6c4e7ea6c3cd4ccd514e99.png

增函数

2e42c3f63b044a30985b9595a4a73f5e.png

减函数

 

微积分

微积分,是建立在函数上的一个数学工具,微积分分为微分和积分,但是在介绍微分和积分之前,需要先介绍函数的极限。

函数的极限

我们现在换一个函数y=2x,它的图象如下:

99a2d033e4c64c82a9fee80a9fb2e159.png

我们假设让x无限接近于2,y就无限接近于4。因此我们说,当x趋于2时,y=2x的极限为4,记作:

eq?y%20%5Cto%204%20%28x%20%5Cto%202%29 

eq?%5Clim%5Climits_%7Bx%20%5Cto%202%7D%202x%3D4

在这里给出函数的极限的定义:

对于一个函数y=f(x),当x无限接近于m时,y无限接近于n,则称当x趋于m时,y的极限为n.记作:

eq?y%20%5Cto%20n%20%28x%20%5Cto%20m%29 

eq?%5Clim%5Climits_%7Bx%20%5Cto%20m%7D%20f%28x%29%3Dn

有些人可能会说,这有什么用呢?我求极限直接让x等于m求y的值不就行了?其实,这里边最大的意义就是给出了“无限接近”、“趋近”的概念以及“lim”运算符。

无穷大

这里引用高中必修一的定义:

实数集R可以用区间表示为(-∞, +∞),“∞”读作“无穷大”,“-∞”读作负无穷大,“+∞”读作正无穷大

这就是说,对于任意实数r,都满足-∞<r<+∞。

另外∞既可以表示正无穷大,也可以表示负无穷大。 

无穷小量

无穷小量是一个变量或函数,如果:

eq?%5Clim%5Climits_%7Bx%20%5Cto%20m%7D%20y%20%3D%200

则称y是当x趋于m时的无穷小量。

注意,无穷小量必须有x趋于m的条件。

这里以eq?y%3D%5Cfrac%7B1%7D%7Bx%7D举例:

5577548a015a472ebb0af019f2c97347.png

可以发现,当x无限接近于∞时,y无限接近于0,因此:

eq?%5Clim%5Climits_%7Bx%20%5Cto%20%5Cinfty%7D%20%5Cfrac%7B1%7D%7Bx%7D%20%3D%200

即,当x趋于∞时,y为无穷小量。

阶的比较

很容易发现,不同的无穷小量趋近于0的速度有快有慢。因此两个无穷小量之间又分为高阶无穷小、低阶无穷小和同阶无穷小。

高阶无穷小和低阶无穷小

对于两个当x趋于x0时的无穷小量a和b,如果:

eq?%5Clim%5Climits_%7Bx%20%5Cto%20x_0%7D%20%5Cfrac%7Ba%7D%7Bb%7D%20%3D%200

 则称a是b的高阶无穷小,b是a的低阶无穷小。记作:

eq?a%3Do%28b%29%28x%20%5Cto%20x_0%29

意味着a趋于0的速度更快。 

同阶无穷小

对于两个当x趋于x0时的无穷小量a和b,如果:

eq?%5Clim%5Climits_%7Bx%20%5Cto%20x_0%7D%20%5Cfrac%7Ba%7D%7Bb%7D%20%3D%20c%28c%20%5Cneq%200%29

则称a和b是x趋于x0的同阶无穷小。

特别的,如果c=1,则称a和b是x趋于x0的等价无穷小,记作:

eq?a%20%5Csim%20b

意味着a与b趋于0的速度相同 

微分

有了以上的概念,就可以研究微分了。考虑这个函数eq?y%3Df%28x%29%3Dx%5E2

我们让x增加eq?%5CDelta%20x(又叫x取得增量eq?%5CDelta%20x),计算一下y的改变量:

eq?%5CDelta%20y%20%3D%20f%28x&plus;%5CDelta%20x%29%20-%20f%28x%29%5C%5C%20%3D%28x&plus;%5CDelta%20x%29%5E2-x%5E2%5C%5C%20%3D%28x&plus;%5CDelta%20x%20&plus;%20x%29%28x%20&plus;%20%5CDelta%20x%20-%20x%29%5C%5C%20%3D%282x%20&plus;%20%5CDelta%20x%29%20%5Ccdot%20%5CDelta%20x%5C%5C%20%3D2x%20%5Ccdot%20%5CDelta%20x%20&plus;%20%28%5CDelta%20x%29%5E2%5C%5C

可以发现,y的改变量Δy为:

 eq?%5CDelta%20y%20%3D%202x%20%5Ccdot%20%5CDelta%20x%20&plus;%20%28%5CDelta%20x%29%5E2%5C%5C

其中我们可以发现,Δx的平方是Δx的高阶无穷小,即:

eq?%28%5CDelta%20x%29%5E2%20%3D%20o%28%5CDelta%20x%29

由于其趋于0的速度更快,因此,当改变量非常微小时,它可以被忽略不计,所以函数y的改变量可以近似为 

eq?%5CDelta%20y%20%5Capprox%202x%20%5Ccdot%20%5CDelta%20x

如果我们让式中的x=m,则 eq?2m%20%5Ccdot%20%5CDelta%20x是函数y在点m处对应于自变量的改变量eq?%5CDelta%20x的微分。

微分的定义 

接下来给出微分的定义:

对于定义在一个区间上的函数eq?y%3Df%28x%29,且eq?x_0eq?x_0%20&plus;%20%5CDelta%20x在这个区间上,如果函数的增量可以表示为 eq?%5CDelta%20y%20%3D%20A%20%5Ccdot%20%5CDelta%20x%20&plus;%20o%28%5CDelta%20x%29,其中A是与 eq?%5CDelta%20x无关的常数(可能与eq?x_0有关),则称函数eq?y%3Df%28x%29在点eq?x%3Dx_0处可微,且称eq?A%20%5Ccdot%20%5CDelta%20x 为函数y在点eq?x_0处对应于自变量的改变量eq?%5CDelta%20x的微分,记作dy。

因此我们可以得出:

eq?dy%3DA%20%5Ccdot%20%5CDelta%20x 

且当eq?%7C%5CDelta%20x%7C很小时:

 eq?%5CDelta%20y%20%5Capprox%20dy

因此我们的例子可以表示为:

eq?dy%3D2x%20%5Ccdot%20%5CDelta%20x

或者说:

 eq?d%28x%5E2%29%3D2x%20%5Ccdot%20%5CDelta%20x

另外,eq?%5CDelta%20x也可以看成是函数y=x的微分,且函数的增加量就等于自变量的增加量,因此我们可以说:

eq?dx%3D%5CDelta%20x

因此,我们对微分的定义式可以写成:

eq?dy%3DA%20%5Ccdot%20dx

微分的运算法则

微分有以下运算法则:

(1)eq?d%28f%28x%29&plus;g%28x%29%29%3Ddf%28x%29&plus;dg%28x%29

(2)eq?d%28f%28x%29-g%28x%29%29%3Ddf%28x%29-dg%28x%29

(3)eq?d%28f%28x%29%20%5Ccdot%20g%28x%29%29%3Dg%28x%29%20%5Ccdot%20df%28x%29&plus;f%28x%29%20%5Ccdot%20dg%28x%29特别地,eq?d%28Cf%28x%29%29%3DC%20%5Ccdot%20d%28f%28x%29%29 

(4)eq?d%28%5Cfrac%7Bf%28x%29%7D%7Bg%28x%29%7D%29%3D%5Cfrac%7Bg%28x%29%20%5Ccdot%20df%28x%29-f%28x%29%20%5Ccdot%20dg%28x%29%7D%7Bg%5E2%28x%29%7D

 基本初等函数的微分公式

由于我们没有讲对数等运算,这里边只介绍两个微分公式,其他的微分公式可以上网查到:

(1)eq?d%28C%29%3D0,其中C为常数

(2)eq?d%28x%5Ea%29%3Dax%5E%7Ba-1%7Ddx

举例

我们计算一下前面提到的eq?y%3D3x%5E%7B_%7B2%7D%7D&plus;6x&plus;4的微分:

eq?d%283x%5E%7B_%7B2%7D%7D&plus;6x&plus;4%29%5C%5C%20%3Dd%283x%5E2%29&plus;d%286x%29&plus;d%284%29%5C%5C%20%3D3d%28x%5E2%29&plus;6dx%5C%5C%20%3D6x%20%5Ccdot%20dx%20&plus;%206dx%5C%5C

导数

导数,又叫微商,是函数的微分与自变量的微分的商,即eq?%5Cfrac%7Bdy%7D%7Bdx%7D,因此又叫微商。

函数y=f(x)的导数可以记作eq?y%27eq?f%27%28x%29eq?%5Cfrac%7Bdy%7D%7Bdx%7Deq?%5Cfrac%7Bd%7D%7Bdx%7Df%28x%29

导数可以表示函数的变化快慢,比如 eq?y%3D3x%5E%7B_%7B2%7D%7D&plus;6x&plus;4的导数就是:

eq?y%27%3D%5Cfrac%7Bd%283x%5E2&plus;6x&plus;4%29%7D%7Bdx%7D%5C%5C%20%3D%5Cfrac%7B6x%20%5Ccdot%20dx%20&plus;%206dx%7D%7Bdx%7D%5C%5C%20%3D6x&plus;6

我们发现,导数是一个自变量为x的函数,因此,导数的全称是“导函数”。 

我们把它的导数和函数画出来:

943dc55f74b64c76bd940052ae4a2090.png

这是它的导数

 

2231b8b9c52c4dbd92ce8b841c13fbd9.png

这是函数图象

我们可以看到,导数在x=-1时穿过x轴,并变为负数;而函数在x=-1时从减函数变为增函数。另外,导数在不断变大,而函数在x=-1前减少的速度越来越慢,在x=-1后的增长的速度越来越快。因此,导数可以表示函数的变化快慢。

极值和极值点

对于一个函数y=f(x),如果在x=m附近有确定的值,且f(m)为x=m附近的最大值(或最小值),则这个最大值(或最小值)就是函数的极大值(或极小值),m为极大值点(或极小值点)。极大值和极小值统称极值,极大值点和极小值点统称极值点。

33860d8649d147089ecae6707968a0a3.png

极大值

2424392480f34309abf45324c7557b39.png

极小值

函数的极值一定在导数为0的点或不存在导数的点。但是,导数为0的点或不存在导数的点不一定是函数的极值:

b71c08f9cec14d3ba0c56d5072735b9e.png

函数的极值为0,但不可导

1a4bf71b1b2f491a81f9b6d8e31f53ea.png

函数在x=0时导数为0,但不是极值

通过导数判断函数单调性

如果导数小于0,则函数单调递减;如果函数大于0,则函数单调递增。

链式求导

链式求导的公式非常简单:

eq?%5Cfrac%7Bdy%7D%7Bdx%7D%3D%5Cfrac%7Bdy%7D%7Bdu%7D%20%5Ccdot%20%5Cfrac%7Bdu%7D%7Bdx%7D

具体来说,如果要求:

eq?%5Cfrac%7Bd%7D%7Bdx%7D%20%5Ccdot%20%28x&plus;2%29%5E2

可以这样求:

eq?%5Cfrac%7Bd%7D%7Bdx%7D%20%5Ccdot%20%28x&plus;2%29%5E2%20%3D%20%5Cfrac%7Bd%28x&plus;2%29%5E2%7D%7Bd%28x&plus;2%29%7D%20%5Ccdot%20%5Cfrac%7Bd%28x&plus;2%29%7D%7Bdx%7D%20%3D%202%28x&plus;2%29%20%5Ccdot%201%20%3D%202x&plus;4

积分

积分是微分的逆运算,可以根据微分的结果还原函数。 但是,由于对常数的微分恒等于0,因此,还原出来的函数可以加上任意常数。

如果函数y=f(x)的微分是A(A与x的取值有关),则称A的积分是函数y=f(x),记作:

eq?%5Cint%20A%3Df%28x%29

一般地,因为还原出来的函数可以加上任意常数,所以:

eq?%5Cint%20A%3Df%28x%29&plus;C

其中C是任意常数

积分的运算法则

积分的运算法则与微分一样,只是要在结果后面加一个常数。

基本积分表

由于我们没有讲对数等运算,这里边只介绍两个积分公式,其他的微分公式可以上网查到:

(1)eq?%5Cint%20kdx%3Dkx&plus;C

(2)eq?%5Cint%20x%5Eadx%3D%5Cfrac%7Bx%5E%7Ba&plus;1%7D%7D%7Bx&plus;1%7D&plus;C%28a%5Cneq%20-1%29注意当a=-1时是存在积分的,但不是这么算的。

举例

计算y=6xdx+6dx的积分:

 eq?%5Cint%206xdx&plus;6dx%5C%5C%20%3D6%5Cint%20xdx&plus;6%5Cint%20dx%5C%5C%20%3D3x%5E2&plus;6x&plus;C

当C取4时,与我们前面例子的原函数eq?y%3D3x%5E2&plus;6x&plus;4相同。因此可以看出,积分是微分的逆运算。

积分的结果又叫积分曲线。

多元函数

有多个自变量的函数叫多元函数,多元函数可以表示为:

eq?y%3Df%28a%2C%20b%2C%20c%2C%20...%29

每多一个自变量,多元函数的图象就要多一条坐标轴与所有原坐标轴垂直:

221f1dc9e77946c68dceffe49b9fa7f5.png

图为f(x, y)=x^2+y^2的图象

多元函数的微分和导数必须要规定对谁求微分或导数,因此,多元函数的微分和导数叫偏微分和偏导数。多元函数求导是,可以把其他自变量看成常数。下面的运算过程展示了如何求eq?f%28x%2C%20y%29%3Dx%5E2&plus;y%5E2对x的偏微分和偏导数:

eq?%5Cpartial%20f%28x%2C%20y%29%5C%5C%20%3D%5Cpartial%20%28x%5E2&plus;y%5E2%29%5C%5C%20%3D%5Cpartial%20x%5E2%20&plus;%20%5Cpartial%20y%5E2%5C%5C%20%3D2x%20%5Cpartial%20x 

 eq?%5Cfrac%7B%5Cpartial%20f%28x%2C%20y%29%7D%7B%5Cpartial%20x%7D%5C%5C%20%3D%5Cfrac%7B2x%20%5Cpartial%20x%7D%7B%5Cpartial%20x%7D%5C%5C%20%3D2x

它的偏导数图象如下:

fe8c7aaa82cb4629b92740913dad619f.png

 可以看到,当x相同时,y的取值对函数值z得取值没有影响。因此,对某一个自变量求导,就说明其他自变量的取值对导函数没有影响。

这里附上画二元函数的python代码,注意需要使用python的数学表达式定义函数:

import numpy as np  
import matplotlib.pyplot as plt  

s = input('请输入二元函数:f(x, y)=')

# 定义二元函数  
def f(x, y):  
    return eval(s) 
  
# 生成x和y的取值范围  
x = np.linspace(-10, 10, 400)  
y = np.linspace(-10, 10, 400)  
X, Y = np.meshgrid(x, y)  
  
# 计算函数值  
Z = f(X, Y)  
  
# 绘制三维曲面图  
fig = plt.figure()  
ax = fig.add_subplot(111, projection='3d')  
ax.plot_surface(X, Y, Z, cmap='viridis')  
ax.set_xlabel('X')  
ax.set_ylabel('Y')  
ax.set_zlabel('Z')  
ax.set_title('Function Surface')  
plt.show()

向量

向量,是既有大小又有方向的量,因此,可以使用带箭头的线段表示向量。向量没有箭头的一端称为起点,有箭头的一端称为终点:

11b68c8a8df641629f0461b90c2b3d61.png

向量的加减法遵循三角形法则和平行四边形法则:

47c11a85d7234687a78cddae118f31a9.png

 

65cddb94e1d94c98aa1072c1868cb791.png

向量与一个数的乘积就是将向量延长多少倍,称为向量的数乘:

 

e555788ec4024ab0878899f9e62ab2b3.png

 当然,机器学习中讲的向量,通常要放在一个直角坐标系里:

aa282cf0ee2b4551b34541967b8d1027.png

图中向量a就是向量(x, y)

一般,向量的运算遵循以下规则:

eq?%28x_1%2C%20y_1%29%20%5Cpm%20%28x_2%2C%20y_2%29%3D%28x_1%20%5Cpm%20x_2%2C%20y_1%20%5Cpm%20y_2%29

eq?%28x_1%2C%20y_1%29%20%5Ccdot%20a%3D%28x_1%20%5Ccdot%20a%2C%20y_1%20%5Ccdot%20a%29

另外,向量可以有多个数在括号里,如:

eq?%28x%2C%20y%2C%20z%29 

梯度

接下来可以介绍一些机器学习中的概念了,先来介绍以下梯度。

考虑一个多元函数:

eq?r%3Df%28x%2C%20y%2C%20z%29

它对每一个自变量都有一个偏导,如果把这些偏导写成向量:

eq?%28%5Cfrac%7B%5Cpartial%20r%7D%7B%5Cpartial%20x%7D%2C%20%5Cfrac%7B%5Cpartial%20r%7D%7B%5Cpartial%20y%7D%2C%20%5Cfrac%7B%5Cpartial%20r%7D%7B%5Cpartial%20z%7D%29

那么就说,这个向量是函数r在点eq?%28x%2C%20y%2C%20z%29处的梯度。(当然,既然我们可以说点eq?%28x%2C%20y%2C%20z%29,那就也可以把多元函数的自变量看成一个向量。)

因此,梯度就是一个函数对所有自变量的导数组成的向量。

我们来求一下函数eq?y%3Dx%5E2在点x处的梯度:

eq?%28%5Cfrac%7Bdy%7D%7Bdx%7D%29%3D%282x%29

因此,原函数的梯度就是(2x)

梯度下降

梯度下降是一种找极值的办法:

观察函数eq?y%3Dx%5E2

0866e355470645e5878d0ab1dfad6787.png

可以看到,它的最小值就是它的极值。我们来看一下如何找到最小值。

首先可以发现,从(2, 4)点看,越靠近极值点,它的增长速度越慢,即导数越小。因此,我们只需要找出一种办法,使它的导数最小。梯度下降的原理就是这样。梯度下降的过程为:

  1.  在函数上取任意一点,并求出它的梯度
  2. 对梯度乘上学习率(学习率是我们定义的一个参数),使对应自变量减去得到的结果,并更新自变量。
  3. 循环执行上述操作,直到梯度的每个偏导数的绝对值都小于指定的值,或干脆重复执行指定次数。

我们以上述函数为例,取x=2,学习率为0.1:

  1. 求出x=2处的梯度:eq?%28%5Cfrac%7B%5Cpartial%20x%5E2%7D%7B%5Cpartial%20x%7D%29%3D%282x%29%3D%284%29
  2. 对梯度乘上学习率: eq?A%20%3D%20%284%29%20%5Ccdot%200.1%20%3D%20%280.4%29,使对应的自变量减去得到的结果(注意这里把自变量看成了一个向量):eq?%28x_%7Bnew%7D%29%3D%28x_%7Bold%7D%29-%28A%29%3D%28x_%7Bold%7D-0.4%29%3D%281.6%29,并更新自变量:eq?%28x%29%3D%28x_%7Bnew%7D%29%3D%281.6%29
  3. 求出x=1.6处的梯度:eq?%28%5Cfrac%7B%5Cpartial%20x%5E2%7D%7B%5Cpartial%20x%7D%29%3D%282x%29%3D%283.2%29
  4. 对梯度乘上学习率: eq?A%20%3D%20%283.2%29%20%5Ccdot%200.1%20%3D%20%280.32%29,使对应的自变量减去得到的结果:eq?%28x_%7Bnew%7D%29%3D%28x_%7Bold%7D%29-%28A%29%3D%28x_%7Bold%7D-0.32%29%3D%281.28%29,并更新自变量:eq?%28x%29%3D%28x_%7Bnew%7D%29%3D%281.28%29
  5. 求出x=1.28处的梯度... 

我们可以利用这个方法,求出一个二次函数(形如eq?y%3Dax%5E2&plus;bx&plus;c的函数,其中只有x是自变量,y是函数,其他都是参数)或类似的函数的最值。当然,机器学习中一般都是求最小值。

 以下代码展示了这个过程:

#include <stdio.h>  
#include <math.h>  
  
#define EPSILON 0.000001  
#define MAX_ITER 10000 
  
double f(double x) {  
    return x * x; // 定义二次函数  
}  
  
double df(double x) {  
    return 2 * x; // 定义二次函数的导数  
}  
  
void gradient_descent(double x) {  
    double alpha = 0.1; // 定义学习率  
    int iter = 0; // 定义迭代次数  
    double delta = EPSILON + 1; // 初始梯度值大于epsilon,使得可以进入循环  
  
    while (iter < MAX_ITER && delta > EPSILON) {  
        double dx = df(x); // 计算梯度值  
        double x_new = x - alpha * dx; // 更新x值  
        delta = fabs(x - x_new); // 计算梯度下降的幅度  
        x = x_new; // 更新x值  
        iter++; // 增加迭代次数  
    }  
  
    printf("函数极值附近的x值为:%f\n", x);  
    printf("函数极值为:%f\n", f(x));  
}  
  
int main() {  
    double x = 1.0; // 从x=1处开始梯度下降法  
    gradient_descent(x);  
    return 0;  
}

输出结果:

函数极值附近的x值为:0.000004
函数极值为:0.000000

梯度下降的问题

有些时候,使用梯度下降法可能会导致NaN出现。我们稍加改动上述代码,将二次函数从eq?y%3Dx%5E2改成eq?y%3D100x%5E2

double f(double x) {  
    return 100 * x * x; // 定义二次函数  
}  
  
double df(double x) {  
    return 200 * x; // 定义二次函数的导数  
}

输出:

函数极值附近的x值为:-nan
函数极值为:-nan

可以发现,程序输出了nan。

遇到这种情况,只需要调低学习率即可。将学习率从0.1改成0.001,可以看到,输出正常:

函数极值附近的x值为:0.000004
函数极值为:0.000000

目标函数

传统的机器学习程序实现的效果是:给定一个输入,计算并返回输出。这个计算过程需要一个函数,这个函数就被称为目标函数。

我们可以考虑这样一个问题:一辆汽车以60公里每小时的速度行驶,t小时后它行驶了多少公里。在这个问题中,t是我们给定的输入(可以为任意数),计算机需要将t带入一个函数(目标函数),最终将函数值输出给我们,这样我们就知道了问题的答案。很容易发现,这个问题中,目标函数就是:

eq?f%28t%29%3D60t

我们向机器输入t=3,那么它就会计算:

eq?f%283%29%3D60%20%5Ccdot%203%3D180

然后输出:

eq?180

这样,机器就利用目标函数,完成了数据的计算。

但是,在大部分的问题中,程序员并不知道具体的目标函数,这就需要先设定目标函数的形式,然后对参数进行梯度下降,这个过程就称为训练:

比如我们定义目标函数的形式如下(k, b都是参数,不是自变量,需要在使用前定义)

eq?y%3Df%28x%29%3Dkx&plus;b

但是,我们需要一个函数评估当前参数是否与实际匹配,这就需要引入损失函数

损失函数

假设我们有一堆数据,以及这些数据的预测值,我们就可以定义一个损失函数,评估预测值的准确性。一般,损失函数值越小,数据的损失越小,也就越准确。

最常用的损失函数是均方误差函数:

eq?l%3D%20%5Csum%5Climits_%7Bi%3D1%7D%5EN%20%28%5Cwidehat%7By_i%7D-y_i%29%5E2

其中,N表示样本的个数,eq?%5Cwidehat%7By_i%7D表示第i个样本的预测值(其实就是第i个样本作为自变量的目标函数值),eq?y_i表示第i个样本的真正的值。这个函数非常好理解,对于目标函数的任意参数a,易求出它的偏导(可以利用链式求导法快速求出它的偏导):

eq?%5Cfrac%7B%5Cpartial%20l%7D%7B%5Cpartial%20a%7D%3D%5Csum%20%5Climits_%7Bi%3D1%7D%5E%7BN%7D%5B2%28%5Cwidehat%7By_i%7D-y_i%29%5Ccdot%5Cfrac%7B%5Cpartial%5Cwidehat%7By_i%7D%7D%7B%5Cpartial%20a%7D%5D

可以发现,式中的eq?%5Cfrac%7B%5Cpartial%5Cwidehat%7By_i%7D%7D%7B%5Cpartial%20a%7D其实就是目标函数对它的任意参数a的偏导。也就是说,均方误差损失函数的梯度就是对目标函数梯度做了一些运算。设自变量的值为第i个样本时,目标函数的梯度为eq?A_i

eq?%5Cfrac%7B%5Cpartial%20l%7D%7B%5Cpartial%20a%7D%3D%5Csum%20%5Climits_%7Bi%3D1%7D%5E%7BN%7D%5B2%28%5Cwidehat%7By_i%7D-y_i%29%5Ccdot%20A_i%5D

如果我们还是使用原来的目标函数:

eq?y%3Df%28x%29%3Dkx&plus;b

将它的梯度求出:

eq?A%3D%28x%2C%201%29

设第1个样本为eq?x_i,则损失函数的梯度为:

eq?%282%5Ccdot%5Csum%5Climits_%7Bi%3D1%7D%5E%7BN%7D%5B%28%5Cwidehat%7By_i%7D-y_i%29%5Ccdot%20x_i%5D%2C%202%5Ccdot%5Csum%20%5Climits_%7Bi%3D1%7D%5E%7BN%7D%28%5Cwidehat%7By_i%7D-y_i%29%29

然后我们随机设置参数的初始值,就可以对损失函数进行梯度下降了。

举个例子

假设我给出一个数据:

时间(t) 自变量距离(y) 因变量
04
164
2124
3184

然后我们来简单地写个程序,确定目标函数:

#include <iostream>
#include <vector>
#include <cmath>
using namespace std;

class LinearModel{
private:
	int count; // 迭代次数
	double w, b; // y=wx+b
	double alpha; // 学习率
public:
	LinearModel(int c=10000, double a=0.001):count(c),alpha(a){
		w = b = 1;
	} 
	void train(vector<double> X, vector<double> Y){
		// 梯度下降 
		for(int i=0; i < count; i++){
			double kw = 0; // 损失函数在点(w, b)处对w的偏导数
			double kb = 0; // 损失函数在点(w, b)处对b的偏导数
			int n = X.size();
			// 计算两个偏导数 
			for(int i=0; i<n; i++){
				kw += (predict(X[i])-Y[i])*X[i];
				kb += (predict(X[i])-Y[i]);
			}
			kw*=2;
			kb*=2;
			// 根据梯度进行迭代 
			w = w - alpha * kw;
			b = b - alpha * kb;
		}
		return;
	}
	double predict(double x){
		return w*x+b;
	}
	vector<double> get_coefficients(void){
		return vector<double>{w, b};
	}
};

int main(void){
	vector<double> X{0, 1, 2, 3};
	vector<double> Y{4, 64, 124, 184};
	LinearModel model(1000, 0.001);
	model.train(X, Y);
	cout << model.predict(4) << endl;
	vector<double> coe = model.get_coefficients();
	cout << "w = " << coe[0] << "\tb = " << coe[1] << endl;
	return 0;
}

以下是程序的流程:

  1. 初始化

    • 创建一个LinearModel对象,设置默认的迭代次数count为10000,学习率alpha为0.001。初始化权重w和偏置b都为1。
  2. 数据准备

    • main函数中,定义了输入特征X和目标值Y,分别包含四个数据点。
  3. 训练模型

    • 调用train函数,传入特征X和目标值Y进行训练。
    • train函数中,使用梯度下降法进行迭代更新。
      • 初始化两个偏导数变量kwkb为0。
      • 对于每个样本点,计算偏导数
      • 根据偏导数更新权重和偏置
  4. 预测

    • 在训练完成后,使用训练得到的权重和偏置进行预测。
  5. 输出结果

    • 输出预测的值为4时的结果。
    • 输出权重和偏置的值。
  6. 结束:返回0表示程序正常结束。

输出:

242.371
w = 59.1263     b = 5.86537

可以看到,它较好的完成了任务 

 

  • 19
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值