简单线性回归模型的 Python 实现

本文以简单的一元线性模型为例,演示梯度下降法的基本实现流程。

一个一元线性模型的基本形式为:

y = w x + b y = w x + b

但是与控制理论中所介绍的一样,数据的获取过程可能存在着噪声误差,理想状态下噪声服从某种分布:

y = w x + b + ϵ , ϵ N ( μ , σ 2 ) y = w x + b + \epsilon , \epsilon \sim \mathcal { N } \left( \mu , \sigma ^ { 2 } \right)

所以训练集数据是通过模拟含噪声的线性模型获取的:

def get_data(b = 0.089, w = 1.477, x_range=(-10.,10.), gauss=0.1):
	data = []# 保存样本集的列表
	for i in range(100): # 循环采样 100 个点
		x = np.random.uniform(x_range[0], x_range[1]) # 随机采样输入 x
		# 采样高斯噪声
		eps = np.random.normal(0., gauss)
		# 得到模型的输出
		y = w * x + b + eps
		data.append([x, y]) # 保存样本点
	data = np.array(data) # 转换为 2D Numpy 数组
	return data

对于一个模型的训练,需要一个评价标准判断模型的好坏,这里选择均方误差公式:

L = 1 n i = 0 n ( w x ( i ) + b y ( i ) ) 2 \mathcal { L } = \frac { 1 } { n } \sum _ { i = 0 } ^ { n } \left( w x ^ { ( i ) } + b - y ^ { ( i ) } \right) ^ { 2 }

均方误差的获取代码实现如下:

def mse(b, w, points):
	# 根据当前的 w,b 参数计算均方差损失
	totalError = 0
	for i in range(0, len(points)): # 循环迭代所有点
		x = points[i, 0] # 获得 i 号点的输入 x
		y = points[i, 1] # 获得 i 号点的输出 y
		# 计算差的平方,并累加
		totalError += (y - (w * x + b)) ** 2
	# 将累加的误差求平均,得到均方差
	return totalError / float(len(points))

梯度下降法的核心就是沿着误差梯度下降的方向进行查找,当步长很小,只要迭代次数够多便可以找到最小点,迭代公式如下:

x = x η d y d x x ^ { \prime } = x - \eta \cdot \frac { \mathrm { d } y } { \mathrm { d } x }

其中 y 是优化的目标函数,x 是目标函数的参数。所以现在需要求出梯度,并设定步长后对其修改,由于线性模型含有两个参数 w 和 b。那么在使用平方误差进行评价模型时,优化目标便是平方误差也就是说,两个参数的迭代优化公式为:

w = w η L w b = b η L b \begin{aligned} w ^ { \prime } & = w - \eta \frac { \partial \mathcal { L } } { \partial w } \\ b ^ { \prime } & = b - \eta \frac { \partial \mathcal { L } } { \partial b } \end{aligned}

对于线性模型来说,该公式可以进一步改写为:

L w = 2 n i = 1 n ( w x ( i ) + b y ( i ) ) x ( i ) L b = 2 n i = 1 n ( w x ( i ) + b y ( i ) ) \begin{aligned} \frac { \partial \mathcal { L } } { \partial w } & = \frac { 2 } { n } \sum _ { i = 1 } ^ { n } \left( w x ^ { ( i ) } + b - y ^ { ( i ) } \right) \cdot x ^ { ( i ) }\\ \frac { \partial \mathcal { L } } { \partial b } & = \frac { 2 } { n } \sum _ { i = 1 } ^ { n } \left( w x ^ { ( i ) } + b - y ^ { ( i ) } \right) \end{aligned}

那么现在便可以根据本代梯度对其进行更新:

def step_gradient(b_current, w_current, points, lr):
	# 计算误差函数在所有点上的导数,并更新 w,b
	b_gradient = 0
	w_gradient = 0
	M = float(len(points)) # 总样本数
	for i in range(0, len(points)):
		x = points[i, 0]
		y = points[i, 1]
		# 误差函数对 b 的导数:grad_b = 2(wx+b-y),参考公式(2.3)
		b_gradient += (2/M) * ((w_current * x + b_current) - y)
		# 误差函数对 w 的导数:grad_w = 2(wx+b-y)*x,参考公式(2.2)
		w_gradient += (2/M) * x * ((w_current * x + b_current) - y)
	# 根据梯度下降算法更新 w',b',其中 lr 为学习率
	new_b = b_current - (lr * b_gradient)
	new_w = w_current - (lr * w_gradient)
	return [new_b, new_w]

使用梯度下降法对系数 w 和 b 进行 num_iterations 代更新:

def gradient_descent(points, starting_b, starting_w, lr, num_iterations):
	# 循环更新 w,b 多次
	b = starting_b # b 的初始值
	w = starting_w # w 的初始值
	# 根据梯度下降算法更新多次
	for step in range(num_iterations):
		# 计算梯度并更新一次
		b, w = step_gradient(b, w, np.array(points), lr)
		loss = mse(b, w, points) # 计算当前的均方差,用于监控训练进度
		if step%50 == 0: # 打印误差和实时的 w,b 值
			print(f"iteration:{step}, loss:{loss}, w:{w}, b:{b}")
	return [b, w],loss # 返回最后一次的 w,b

最终的线性模型训练主程序为:

def main():
	lr = 0.01 # 学习率
	original_b = 0.089 # 生成数据的初始 b
	original_w = 1.477 # 生成数据的初始 w
	initial_b = 0 # 初始化 b 为 0
	initial_w = 0 # 初始化 w 为 0
	num_iterations = 1000 # 训练次数为 100
	data = get_data(original_b,original_w) # 获取二维数据
	[b, w], loss = gradient_descent(data, initial_b, initial_w, lr, num_iterations) # 返回最优 w*,b*,loss*和训练 Loss 的下降过程
	print(f'Final loss:{loss}, w:{w}, b:{b}')

猜你喜欢

转载自blog.csdn.net/Flame_alone/article/details/106177025
今日推荐