单变量线性回归算法
学习完吴恩达老师机器学习课程的单变量线性回归算法,简单回顾并做个笔记。
单变量线性回归
单变量线性回归(Linear Regression with one variable)是只有一个变量的线性回归函数。定义如下:
参数 | 含义 |
---|---|
( x , y ) (x, y) (x,y) | 一个样本 |
( x ( i ) , y ( i ) ) (x^{(i)}, y^{(i)}) (x(i),y(i)) | 第 i i i个样本 |
x x x | 输入变量的特征 |
y y y | 输出变量\目标变量 |
m m m | 训练样本的数量 |
(1)假设函数 Hypothesis Function
使用某种学习算法对训练集的数据进行训练,可以得到假设函数(Hypothesis Function)。
假设函数的表达式为:
h
θ
(
x
)
=
θ
0
+
θ
1
x
h_{θ}(x)=θ_{0}+θ_{1}x
hθ(x)=θ0+θ1x
它只有一个特征/输入变量
x
x
x,
θ
0
\theta_{0}
θ0 和
θ
1
\theta_{1}
θ1 为参数,同时它拟合的曲线是一条直线,因此这个问题叫做单变量线性回归问题。
选择不同的
θ
0
\theta_{0}
θ0 和
θ
1
\theta_{1}
θ1值会产生不同的假设函数:
算法原理:
下图为一个监督学习算法的工作方式,竖向的流程是算法学习和训练的过程,即给出一个训练集,将它“交”给学习算法,最终训练得到一个模型的输出;横向的流程为算法预测的过程,比如说给出房屋的Size,经过上一步训练得到的模型,输出房屋Price的预测值。
(2)代价函数 Cost Function
衡量一个假设函数的“损失”,又称作“平方和误差函数”(Square Error Function),它是解决回归问题最常用的手段,定义如下:
J
(
θ
0
,
θ
1
)
=
1
2
m
∑
i
=
1
m
(
h
θ
(
x
(
i
)
)
−
y
(
i
)
)
2
J(θ_{0},θ_{1})=\frac{1}{2m}\sum_{i=1}^{m}(h_{θ}(x^{(i)})−y^{(i)})^{2}
J(θ0,θ1)=2m1i=1∑m(hθ(x(i))−y(i))2
通俗来说,就是对所有样本的假设值与真实值之差的平方求和,除以两倍的样本数量。若总体的真实值与总体的假设值差别巨大,会造成代价函数 J ( θ 0 , θ 1 ) J(\theta_{0},\theta_{1}) J(θ0,θ1) 的值较大,所以 优化训练的目标 就是使得 J ( θ 0 , θ 1 ) J(θ_{0},θ_{1}) J(θ0,θ1)最小。
将
J
(
θ
0
,
θ
1
)
J(θ_{0},θ_{1})
J(θ0,θ1)函数画在一张三维的图上,可以看出它是一个凸面(有最小值点或者局部最小值点):
如下图所示。越靠近中心表示
J
(
θ
0
,
θ
1
)
J(θ_{0},θ_{1})
J(θ0,θ1)的值越小(对应3D图中越靠近最低点的位置)。左图表示当
θ
0
θ_{0}
θ0=800,
θ
1
θ_{1}
θ1=0.15的时候对应的
h
θ
(
x
)
h_{θ}(x)
hθ(x),通过
θ
0
θ_{0}
θ0,
θ
1
θ_{1}
θ1的值可以找到右图中
J
(
θ
0
,
θ
1
)
J(θ_{0},θ_{1})
J(θ0,θ1)的值。对于不同的拟合直线,对应不同的
J
(
θ
0
,
θ
1
)
J(θ_{0},θ_{1})
J(θ0,θ1)。
斜率为负:
斜率为零:
斜率为正,即最优拟合的情况:
在这个过程中不断由外向内趋近,直至找到一个最接近最佳
h
θ
(
x
)
h_{θ}(x)
hθ(x),这个过程就是梯度下降的过程。
(3)梯度下降算法 Gradient Descent
梯度的含义: 某一函数在该点处的方向导数沿该方向取得最大值,即在该点变化率(斜率)最大。
梯度下降算法的思想: 开始随机选择一个参数的组合 ( θ 0 , θ 1 , . . . , θ n ) (θ_{0},θ_{1},...,θ_{n}) (θ0,θ1,...,θn),计算代价函数,然后我们寻找下一个能让代价函数下降最多的参数组合。一直持续计算,直到到达一个局部最小值。由于全部值都计算是不可能的,所以不能确定得到的结果是局部最小值还是全局最小值。
梯度下降的概念及数学公式:使得自变量 θ 0 、 θ 1 \theta_{0}、\theta_{1} θ0、θ1 沿着使 J ( θ 0 , θ 1 ) J(\theta_{0},\theta_{1}) J(θ0,θ1) 下降最快的方向移动,尽快取得 J ( θ 0 , θ 1 ) J(\theta_{0},\theta_{1}) J(θ0,θ1) 的最小值,定义如下: θ j : = θ j − α ∂ ∂ θ j J ( θ 0 , θ 1 ) \theta _{j}:=\theta _{j}-\alpha \frac{\partial }{\partial \theta _{j}}J(\theta_{0},\theta_{1}) θj:=θj−α∂θj∂J(θ0,θ1) 对于 j = 0 j=0 j=0或者 j = 1 j=1 j=1 ,在该方向上,若 J ( θ 0 , θ 1 ) J(\theta_{0},\theta_{1}) J(θ0,θ1)斜率(导数)为正数,则应该减去一个值(使得 θ j \theta _{j} θj左移),若 J ( θ 0 , θ 1 ) J(\theta_{0},\theta_{1}) J(θ0,θ1)斜率为负数,则反之。
对于
j
=
0
j=0
j=0 :
∂
∂
θ
0
J
(
θ
0
,
θ
1
)
=
1
m
∑
i
=
1
m
(
h
θ
(
x
(
i
)
)
−
y
(
i
)
)
\frac{\partial }{\partial \theta _{0}}J(\theta_{0},\theta_{1}) =\frac{1}{m}\sum_{i=1}^{m}(h_{θ}(x^{(i)})−y^{(i)})
∂θ0∂J(θ0,θ1)=m1i=1∑m(hθ(x(i))−y(i))
对于
j
=
1
j=1
j=1 :
∂
∂
θ
1
J
(
θ
0
,
θ
1
)
=
1
m
∑
i
=
1
m
(
h
θ
(
x
(
i
)
)
−
y
(
i
)
)
⋅
x
(
i
)
\frac{\partial }{\partial \theta _{1}}J(\theta_{0},\theta_{1}) =\frac{1}{m}\sum_{i=1}^{m}(h_{θ}(x^{(i)})−y^{(i)})\cdot x^{(i)}
∂θ1∂J(θ0,θ1)=m1i=1∑m(hθ(x(i))−y(i))⋅x(i)
PS:需要同时更新 θ 0 \mathbf{\theta_{0}} θ0 和 θ 1 \mathbf{\theta_{1}} θ1 ,即对于每一次迭代,应先算完 θ 0 \mathbf{\theta_{0}} θ0 和 θ 1 \mathbf{\theta_{1}} θ1 ,再对它们进行赋值。
错误的原因是,
θ
0
\mathbf{\theta_{0}}
θ0已经被赋值,temp1计算出来的值就是错误的。因此,需要同时更新
θ
0
\mathbf{\theta_{0}}
θ0 和
θ
1
\mathbf{\theta_{1}}
θ1。
梯度下降的步骤:
- 设定初始的 θ 0 \theta_{0} θ0 和 θ 1 \theta_{1} θ1 ;
- 不断更新 θ 0 \theta_{0} θ0 和 θ 1 \theta_{1} θ1 ,使得 J ( θ 0 , θ 1 ) J(\theta_{0},\theta_{1}) J(θ0,θ1) 减小,直到达到我们所期望的最小值。
这就是梯度下降算法,也称为批量梯度下降法(Batch Gradient Descent),因为在梯度下降的每一步中,都用到了所有的训练样本。
PS:对于梯度下降算法而言,选取不同的初始值,移动的方向可能会不同,导致最后收敛的值不同,会造成局部最优解(local minimum)。但是,对于线性回归而言,其代价函数总是凸函数(convex function),局部最优解就是全局最优解(global optimum)。
补充知识点:
-
α为学习率(learning rate):
决定了沿着能让代价函数下降程度最大的方向向下迈出的步子有多大,它会影响算法收敛速度的快慢,因此选择合适的学习率 α \alpha α很重要。 -
如何选择合适的学习率α:
如上图所示。若 α \alpha α 太小,则梯度下降速度很慢,若 α \alpha α 太大,则无法收敛甚至发散。 -
求学习率α的步骤:
在开始时我们不确定最合适的 α α α值是多少,一般我们会选取等比的一系列 α α α值做尝试,如0.001,0.003,0.01,0.03,0.1,0.3,1,… 每次尝试间隔大约是3倍左右。
最终比较不同学习率下,算法的收敛速度,选取合适的学习率 α α α。
(4)python代码实现
首先,用python对数据集进行导入和查看等操作:
# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
path = 'ex1data1.txt'
# names添加列名,header用指定的行来作为标题,若原无标题且指定标题则设为None
#读取CSV(逗号分割)文件到 DataFrame
#data = pd.read_csv(path, header=None, names=['Population', 'Profit'])
#查看数据集的前 5 行
#data.head()
#查看数据的分布情况
#data.describe()
#绘制数据的图形
#data.plot(kind='scatter', x='Population', y='Profit', figsize=(8,5))
data = np.loadtxt(path,delimiter=',') #导入
X = data[:,0]
y = data[:,1]
m = len(y) #m是样本数
X = X.reshape((m,1))
y = y.reshape((m,1)) #需要将数据变成二维数组
plt.figure() #绘制点图
plt.scatter(X,y,color = 'r',marker = '*',linewidths = 1)
plt.xlabel('Population')
plt.ylabel('Profit')
plt.title('Scatter Plot')
plt.show()
plt.savefig('scatter plot.png')
实验结果:
下面进行单变量线性回归算法的实验编写:
(ex1data1.txt 包含的数据有 人口量 和 利润)
# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
def computeCost(X, y, theta):
sum_all = np.power(((X.dot(theta.T))-y),2) #数组元素求n次方
J = np.sum(sum_all)/(2*X.shape[0])
return J
def GradientDescent(X, y, theta): # 初始化一个theta临时矩阵
#temp = np.array(np.zeros(theta.shape))
# 参数theta的数量
#parameters = int(theta.flatten().shape[0])
# 初始化一个ndarray,包含每次 iterations 的 cost
#cost = np.zeros((iterations,1))
alpha = 0.01
iterations = 1000
# cost存放每次修改theta后代价函数的值
cost=[]
#样本数量 m
m = X.shape[0]
for i in range(iterations): #进一步向量化求解
temp = theta - (alpha/m) * (X.dot(theta.T) - y).T.dot(X) # theta 是一个行向量,计算得出的结果也是行向量
theta = temp
cost.append(computeCost(X, y, theta)) # theta 是未知数,X和y是已知数
return theta,cost
# 画原始数据
def draw_data(data):
X = data[:,0]
y = data[:,1]
plt.scatter(X,y,color = 'r',marker = '*',linewidths = 1)
'''
获取最大值最小值,设置坐标的
print(max(x),min(x))
print(max(y),min(y))
'''
plt.axis([4, 25, -3, 25])
plt.title('Raw Data')
plt.xlabel('Population')
plt.ylabel('Profit')
plt.figure()
plt.show()
return plt
# 画出迭代次数和代价函数的关系
def draw_iteration(cost,iteration=1000):
plt.plot(range(iteration),cost)
plt.title('Iteration-Cost')
plt.xlabel('Iteration')
plt.ylabel('Cost')
plt.show()
# 画出回归方程
def draw_final(theta,data):
plt=draw_data(data)
X=np.arange(4,25,0.01)
y=theta[0]+X*theta[1]
plt.plot(x,y,c='r')
plt.title('Final')
plt.show()
path = 'ex1data1.txt'
data = np.loadtxt(path,delimiter=',')
X = data[:,0]
y = data[:,1]
#theta=np.zeros((2,1))
m = len(y) #m是样本数
X = X.reshape((m,1))
y = y.reshape((m,1)) #需要将数据变成二维数组
draw_data(data)
#X=np.concatenate((np.ones((m,1)),X),axis=0)
X = np.c_[np.ones(X.size),X] #np.c_是按行连接两个矩阵,就是把两矩阵左右相加,要求行数相等。
theta = np.ones(X.shape[1]) #X.shape[1]图片水平尺寸,X.shape[0]图片垂直尺寸,X.shape[2]图片通道数
J = computeCost(X,y,theta)
final_theta,cost = GradientDescent(X, y, theta)
print('J = ',J)
#print('cost = ',cost)
#print('theta = ',final_theta)
plt.figure(num=2)
draw_iteration(cost)
plt.figure(num=3)
#draw_final(final_theta,cost)
plt.title('Predicted Profit vs. Population Size')
plt.plot(X[:,1],np.dot(X,theta),'b-')
plt.legend(['Linear regression'])
plt.show()
实验结果:
原始数据的散点图:
迭代次数与代价函数的关系图:
数据点的拟合直线:
代价函数的值:
需要注意的是,这个代码需要运行两次才可以得到“拟合直线”。 设置的迭代次数是1000次,当设置1500次时,报错是X和y的维度不匹配。
针对迭代次数不能超过1000次的解决方案:
在之前的代码里,把迭代次数设置在函数里面了,由于cost里面设定的迭代最大值为1500,当输入的迭代大于1500会出现不匹配。这里只需要把迭代次数改成了全局变量即可。
修改后的代码:
# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
iterations = 1500
def computeCost(X, y, theta):
sum_all = np.power(((X.dot(theta.T))-y),2) #数组元素求n次方
J = np.sum(sum_all)/(2*X.shape[0])
return J
def GradientDescent(X, y, theta): # 初始化一个theta临时矩阵
#temp = np.array(np.zeros(theta.shape))
# 参数theta的数量
#parameters = int(theta.flatten().shape[0])
# 初始化一个ndarray,包含每次 iterations 的 cost
#cost = np.zeros((iterations,1))
alpha = 0.01
#iterations = 1000
# cost存放每次修改theta后代价函数的值
cost=[]
#样本数量 m
m = X.shape[0]
for i in range(iterations): #进一步向量化求解
temp = theta - (alpha/m) * (X.dot(theta.T) - y).T.dot(X) # theta 是一个行向量,计算得出的结果也是行向量
theta = temp
cost.append(computeCost(X, y, theta)) # theta 是未知数,X和y是已知数
return theta,cost
# * 在matrix类型中是矩阵的叉乘,multiply是对应元素相乘
# * 在ndarray类型中,dot或 @ 是叉乘,* 是对应元素相乘
# 画原始数据
def draw_data(data):
X = data[:,0]
y = data[:,1]
plt.scatter(X,y,color = 'r',marker = '*',linewidths = 1)
'''
获取最大值最小值,设置坐标的
print(max(x),min(x))
print(max(y),min(y))
'''
plt.axis([4, 25, -3, 25])
plt.title('Raw Data')
plt.xlabel('Population')
plt.ylabel('Profit')
plt.show()
return plt
# 画出迭代次数和代价函数的关系
def draw_iteration(cost,iteration=1500):
plt.plot(range(iteration),cost)
plt.title('Iteration-Cost')
plt.xlabel('Iteration')
plt.ylabel('Cost')
plt.show()
# 画出回归方程
def draw_final(theta,data):
#plt=draw_data(data)
X=np.arange(4,25,0.01)
y=theta[0,0]+X*theta[0,1] #需要将theta转变成相同的维度
plt.plot(X,y,c='y')
plt.title('Regression Plot')
plt.show()
def Linear_regression(X,y):
plt=draw_data(data)
plt.title('Predicted Profit vs. Population Size')
plt.plot(X[:,1],np.dot(X,theta),'b-')
plt.legend(['Linear regression'])
plt.show()
path = 'ex1data1.txt'
data = np.loadtxt(path,delimiter=',')
draw_data(data)
X = data[:,0]
y = data[:,1]
#theta=np.zeros((2,1))
m = len(y) #m是样本数
X = X.reshape((m,1))
y = y.reshape((m,1)) #需要将数据变成二维数组
#X=np.concatenate((np.ones((m,1)),X),axis=0)
X = np.c_[np.ones(X.size),X] #np.c_是按行连接两个矩阵,就是把两矩阵左右相加,要求行数相等。
theta = np.ones(X.shape[1]) #X.shape[1]图片水平尺寸,X.shape[0]图片垂直尺寸,X.shape[2]图片通道数
J = computeCost(X,y,theta)
final_theta,cost = GradientDescent(X, y, theta)
print('J = ',J)
#print('cost = ',cost)
#print('theta = ',final_theta)
plt.figure()
draw_iteration(cost)
plt.figure()
draw_final(final_theta,cost)
plt.figure()
Linear_regression(X,y)
修改后的实验结果:
原始数据散点图:
计算得到的代价函数值:
迭代次数与代价函数的关系图:
回归方程:
数据点的拟合直线:
以上的代码解决之前的超过迭代次数超过1000次会出错的问题和不需要第二次运行操作就可以得到“拟合曲线”。
如果您在阅读之中发现文章错误之处或者出现疑问,欢迎在评论指出。