机器学习——线性回归编程训练
参考资料:
3.梯度下降法
本文是吴恩达机器学习课程中的第一个编程训练。关于线性回归的详细介绍可以参考吴恩达机器学习课程,参考资料1也介绍的较为详细
1.线性回归
线性回归其本质上就是对数据进行拟合,从大量的数据中,获得一个方程来近似描述这些数据,并用该方程对新的输入进行预测
举个例子就是:知晓了很多房子的面积与价格,我们可以拟合出面积与价格之间的一个函数关系,并用这个函数关系预测一个新房子的价格。
2.单变量(一元)线性回归
我们所要做的就是获得这么一个函数关系:
h
θ
(
x
)
=
θ
0
x
0
+
θ
1
x
h_θ(x) = θ_0x_0 + θ_1x
hθ(x)=θ0x0+θ1x
这里
x
0
=
1
x_0=1
x0=1,而
h
θ
(
x
)
h_θ(x)
hθ(x)就是我们要根据面积来预测的价格,其中
x
x
x 就是我们所说的面积
因此,我们已知 x 0 x_0 x0, x x x 可从数据集中获得,待求 θ 0 、 θ 1 θ_0、θ_1 θ0、θ1
2.1如何评价一个拟合结果的好坏?
预测价格和实际价格越接近,则拟合结果越好,即:
∣ h θ ( x ) − y ∣ |h_θ(x)-y| ∣hθ(x)−y∣ 的值越小越好。
其中 y y y 是实际价格,我们可以从数据集中获得。
因此我们定义一个函数如下,我们称这个函数为代价函数或损失函数:
J
(
θ
)
=
1
/
(
2
m
)
∗
∑
[
h
θ
(
x
)
−
y
]
2
J(θ)=1/(2m)*\sum{[h_θ(x)-y]^2}
J(θ)=1/(2m)∗∑[hθ(x)−y]2我们用平方来去掉绝对值的影响。
同上,如果 J ( θ ) J(θ) J(θ)取得到最小值,那么拟合结果应该就是最好的。通常情况下,为了方便计算,我们使用矩阵来表示这个方程。
2.2如何求取 J ( θ ) J(θ) J(θ)的最小值
通常采用梯度下降法来求取最小值。此外还有正规方程,但这里不做介绍。
梯度下降法的思想就是:从山顶向山脚走,每走一步就就下降一点
梯度下降法会对 J ( θ ) J(θ) J(θ)随机一个初始值A,然后根据步长计算一个值B,如果B<A,则我们就获得了一个新的低点,之后更新 θ 0 、 θ 1 θ_0、θ_1 θ0、θ1。如此迭代,直到找到最低点。
关于梯度下降法的介绍可以参考梯度下降法。
权重的更新规则:
θ
=
θ
−
α
∗
d
(
J
(
θ
)
)
/
d
θ
θ = θ -α*d(J(θ))/dθ
θ=θ−α∗d(J(θ))/dθ其中
d
(
J
(
θ
)
)
/
d
θ
d(J(θ))/dθ
d(J(θ))/dθ表示求偏导,
α
α
α表示学习速率
2.3学习速率的选取
通常要进行多次尝试才能够选取到合适的学习速率。
合适的学习速率可以使得代价函数 J ( θ ) J(θ) J(θ)获得较快的下降速度,使其快速收敛。
学习速率通常选取:
0.001,0.003,0.01,0.03,0.1,0.3,1等,通常采用3倍进行选取,不断尝试,选择最合理的学习速率。
在下文的多元线性回归编程训练中可以看到具体的实例。
2.4多元线性回归
多元线性回归和一元线性回归是类似,仅仅表现在其函数关系不同:
多元线性回归的函数关系:
h
θ
(
x
)
=
θ
0
x
0
+
θ
1
x
1
+
θ
2
x
2
+
θ
3
x
3
+
.
.
.
h_θ(x) = θ_0x_0 + θ_1x_1+θ_2x_2+θ_3x_3+...
hθ(x)=θ0x0+θ1x1+θ2x2+θ3x3+...
3.一元线性回归编程训练:
数据集:来自黄海广老师的GitHub仓库,见参考资料2,具体链接
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
def get_x(df): # 获取特征x
'''
h_θ(x) = θ_0*x_0 + θ_1*x_1 + θ_2*x_2 + ...
这里默认x_0为1,因此需要在原始的数据中增加一列1
并将该列和原始数据集中的特征合并,获得特征数组
:param df: 数据集
:return: 该操作返回的是特征数组
'''
ones = pd.DataFrame({'ones': np.ones(len(df))}) # 以字典的形式创建一个DataFrame,即x_0=1
data = pd.concat([ones, df], axis=1) # 合并数据,根据列合并
return data.iloc[:, :-1].values # 返回的是一组数组
def get_y(df): # 获取标签y
return np.array(df.iloc[:, -1]) # df.iloc[:, -1]是指df的最后一列
def normalize_feature(df): # 特征缩放,在本例中并没有应用
return df.apply(lambda column: (column - column.mean()) / column.std())
def lr_cost(theta, x, y):
'''
定义代价(损失、成本)函数:
J(θ) = 1/(2m) [h_θ(x) - y ]^2
:param theta: 权重(系数)θ
:param x: 特征x
:param y: 标签y
:return: 代价函数的值
'''
m = x.shape[0] # m为样本数
inner = x @ theta - y # h_θ(x) - y 其中 h_θ(x) = x @ theta,x @ theta等价于x.dot(theta)
square_sum = inner.T @ inner # [h_θ(x) - y ]^2
cost = square_sum / (2 * m) # J(θ) = 1/(2m) [h_θ(x) - y ]^2
return cost
def gradient(theta, x, y):
'''
代价函数求偏导
:param theta: 权重(系数)θ
:param x: 特征x
:param y: 标签y
:return: 代价函数的偏导
'''
m = x.shape[0]
inner = x.T @ (x @ theta - y) # 涉及到矩阵求偏导,这里不讨论具体的数学问题
return inner / m
def batch_gradient_decent(theta, x, y, epoch, alpha=0.01):
'''
批量梯度下降函数,求取使代价函数最小的权重θ
:param theta: 初始权重θ
:param x: 特征x
:param y: 标签y
:param epoch: 最大迭代次数
:param alpha: 学习速率,这里默认为0.01
:return: 最终权重θ,每次迭代的代价函数
'''
cost_data = [lr_cost(theta, x, y)] # 存放每次迭代的代价函数值的列表
for i in range(epoch): # 更新权重θ
theta = theta - alpha * gradient(theta, x, y) # θ = θ -α df/dθ
cost_data.append(lr_cost(theta, x, y))
return theta, cost_data
sns.set(context="notebook", style="whitegrid", palette="dark") # 设置图形界面的背景色等
df = pd.read_csv("C:/Users/Administrator/Desktop/data1.txt", names=["population", "profit"]) # 打开数据文件,给每列命名
x = get_x(df) # 特征x
y = get_y(df) # 标签y
theta = np.zeros(shape=x.shape[1]) # 权重(系数)θ,这里并没有对权重进行随机初始化
epoch = 500 # 迭代500次
final_theta, final_cost_data = batch_gradient_decent(theta, x, y, epoch) # 最终权重θ,每次迭代的代价函数
data_cost = pd.DataFrame(final_cost_data) # 将final_cost_data转为DataFrame类型
# 每次迭代的代价函数值可视化
ax = sns.lineplot(x=np.arange(epoch + 1), y=final_cost_data, data=data_cost) # 将每次迭代后的代价函数值绘制成折线图
ax.set_xlabel('epoch') # 设置横坐标标签
ax.set_ylabel('cost') # 设置纵坐标标签
plt.show()
# 一元特征,h_θ(x) = θ_0*x_0 + θ_1*x_1 = mx+b
b = final_theta[0]
m = final_theta[1]
plt.scatter(df.population, df.profit, label="Training data") # 画出散点图
plt.plot(df.population, df.population * m + b, label="Prediction") # 画出拟合直线
plt.legend(loc=2)
plt.show()
运行结果:
4.多元线性回归编程训练:
数据集:来自黄海广老师的GitHub仓库,见参考资料2,具体链接
对于多元函数仅仅是增加了一个特征缩放的归一化问题。
# 仅修改文件读取时的代码即可,修改如下:
row_df = pd.read_csv("C:/Users/Administrator/Desktop/data2.txt", names=['square', 'bedrooms', 'price']) # 打开数据文件,给每列命名
df = normalize_feature(row_df)
此外,由于多元变量是没有办法通过平面图像绘制的,因此在本例中仅可绘制代价函数的折线图,无法绘制出散点图和拟合(超)平面
运行结果:
由于在批量梯度下降过程中默认学习速率为0.01,可适当更改学习速率来减少收敛所需要的迭代次数,如更改学习速率为0.1,如下图:
final_theta, final_cost_data = batch_gradient_decent(theta, x, y, epoch, alpha=0.1) # 最终权重θ,每次迭代的代价函数
4.1学习速率的选取
上文已经说了,学习速率的通常选取的值,不妨做成列表,进行遍历,然后根据迭代次数与代价函数的关系图选取最合适的学习速率。
# 将代价函数可视化部分的代码修改如下:
# 每次迭代的代价函数值可视化
fig, ax = plt.subplots(figsize=(16, 9)) # 设置窗口大小
alphas = [0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 1] # 学习速率α待取值
for alpha in alphas: # 遍历学习速率α
_, final_cost_data = batch_gradient_decent(theta, x, y, epoch, alpha=alpha) # _表示此处有值,但是下文并不使用,用来占位
ax.plot(np.arange(epoch + 1), final_cost_data, label=alpha) # 将每次迭代后的代价函数值绘制成折线图
ax.set_xlabel('epoch') # 设置横坐标标签
ax.set_ylabel('cost') # 设置纵坐标标签
ax.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.) # 设置标签位置
ax.set_title('learning rate', fontsize=18) # 标题,字号
plt.show()
运行结果:
综上,我们可以选取的学习速率为1,之后可以尝试更高的学习速率,看是否存在更合适的。