作者主页(文火冰糖的硅基工坊):https://blog.csdn.net/HiWangWenBing
本文网址:https://blog.csdn.net/HiWangWenBing/article/details/119978818
目录
前置文章
第1章 导数与偏导
1.1 一元简单函数求导
1.2 N元简单函数求偏导
1.3 一元复合函数链式求导
1.4 多元复合函数链式求偏导
1.5 多元复合函数线性相加函数的链式求偏导
第2章 用最小二乘法的损失函数进行曲线拟合
2.0 前置条件
2.1 步骤1: 构建样本
2.2 步骤2:构建拟合函数
2.3 步骤3:利用python库提供算法求拟合函数的参数 (仅供参考)
2.4 步骤3:利用自定义的梯度下降法计算拟合函数的参数
2.4.1 使用最小二乘定义损失函数(残差函数)
2.4.2 链式求导
2.4.3 梯度下降法求解最佳w,b参数值
2.4.4 可视化迭代过程
2.5 步骤4:利用获得的拟合函数进行数据预测
2.6 步骤5:可视化拟合函数
[数值计算-15]:函数近似值的线性与非线性拟合的原理与Python代码示例
https://blog.csdn.net/HiWangWenBing/article/details/119973082
[数值计算-16]:最小二乘的解法1 - 一元二次方程解析法求解
https://blog.csdn.net/HiWangWenBing/article/details/119978799
[数值计算-17]:最小二乘的解法2 - 二元二次线性方程组求解
https://blog.csdn.net/HiWangWenBing/article/details/119977443
一元函数:就是只有一个自变量的函数。
简单函数:就是初等函数的线性加减得到的函数。
导数(Derivative),也叫导函数值。又名微商,是微积分中的重要基础概念。当函数y=f(x)的自变量x在一点x0上产生一个增量Δx时,函数输出值的增量Δy与自变量增量Δx的比值在Δx趋于0时的极限a如果存在,a即为在x0处的导数,记作f'(x0)或df(x0)/dx。
一元函数的切线和导数,有现成的公式求导数,如下图所示:
例题1:
; x为自变量
导数:f '(x) = 2*2x^1 + 4 = 4x^1 + 4 = 4x + 4
例题2:
; x为自变量,w,b为常量
导数:f '(x) = w + 0 = w
多元函数:所谓多元函数,求是有包含有多个自变量的初等函数。
简单函数:就是初等函数的线性加减得到的函数。
那么,对于二元函数或多元函数,求导数,该如何计算呢?
这就需要用到偏导数的概念:通过临时固化其他维度分量的数值,用来求解多元函数在某个维度方向的导数或切线斜率。
然后,把所有维度方向的导数组成一个向量,就得到多元函数在空间中某一点处的导数或梯度P = {P1, P2, P3.....Pn}; 其中Pi是在i维度方向上的偏导数。
其中:
x1, x2,....Xi......表示的是维度方向,而不是指在单个维度方向上的数值序列.
{x1.0, x1.1, x1.2.....x1.n..} 表示在x1的维度方向的序列
{x2.0, x2.1, x2.2.....x2.n..} 表示在x2的维度方向的序列
{xi.0, xi.1, xi.2 .....xi.n..} 表示在x2的维度方向的序列
因此,多元简单函数与一元简单函数的导数基本是一致的,唯一的区别就是:每个变量都有各自的偏导数,在求解某个变量Xi的偏导数是,需要假定其他变量为常数。
例题1: ; x,y为自变量
例题2:z = f(w,b) = wx + b #变量为w和b
例题1:
例题1:w,b为参数,(xi,yi为常数)
例题1:w,b为参数,(xi,yi)为常数
(1) z对w的偏导数:
(2)z对b的偏导数:
#导入库
from math import *
import time
import numpy as np
import matplotlib.pyplot as plt #画图工具
from pylab import mpl #中文字体
from scipy import optimize #最小二乘算法的算法库
#步骤1:构建样本
#(1) 采用np, 直接手工生成样本的输入:一组等距离的分布在[-1,1]之间的100个点
sample_numbers = 50
x_data = np.linspace(0, 1, sample_numbers)
#(2) 为这些数据手工打上理论输出值(标签值):y = 2x + 1
y_data_pure = 2 * x_data + 1.0
#(3)为了模拟现实情况,通过随机数来模拟数据噪声
noise_range = 0.4
np.random.seed(10) #设置随机种子, 确保不同时候,执行结果是相同的
#randn(n)生成的0为均值,1为标准差的正态分布的n个随机数。
y_noise = np.random.randn(*x_data.shape) * noise_range # *x_data.shape:输入样本的维度或个数
#(4)人工生成样本的输出:理论值 + 噪声
y_data_noise = y_data_pure + y_noise
#(5) 显示样本数据
# 样本的散点图
plt.scatter(x_data, y_data_noise, label="sample", color="black")
# 内在的、理论的曲线图
plt.plot(x_data, y_data_pure, label="f_pure(x)", color="blue", linewidth = 4)
#设置属性
mpl.rcParams['font.sans-serif'] = ['SimHei']
mpl.rcParams['axes.unicode_minus'] = False
plt.title("线性拟合")
plt.legend(loc="upper left")
plt.show()
#步骤2:构建拟合函数:二元一次拟合函数
def f_line_wb(x, w, b):
return (w*x + b)
#步骤3-1:利用python库提供的最小二乘算法来计算拟合函数的参数
print("使用无噪声数据:")
popt, pcov = optimize.curve_fit(f_line_wb, x_data, y_data_pure)
print(popt)
print(pcov)
w_scipy = popt[0]
b_scipy = popt[1]
print("参数w=", w_scipy)
print("参数b=", b_scipy)
print("\n使用有噪声数据:")
popt, pcov = optimize.curve_fit(f_line_wb, x_data, y_data_noise)
print(popt)
print(pcov)
w_scipy = popt[0]
b_scipy = popt[1]
print("参数w=", w_scipy)
print("参数b=", b_scipy)
使用无噪声数据: [2. 1.] [[ 0. -0.] [-0. 0.]] 参数w= 2.0 参数b= 1.0 使用有噪声数据: [1.91826746 1.08186076] [[ 0.0310567 -0.01552835] [-0.01552835 0.01045787]] 参数w= 1.9182674578022025 参数b= 1.0818607577986927
(1)定义损失函数
# 定义残差函数或损失
def f_loss(input_f, w, b, input_x_data, input_y_data_noise):
loss = np.mean((input_f(input_x_data, w, b) - input_y_data_noise)**2)
return (loss)
(2)测试损失函数
# 测试残差函数或损失函数
err0 = f_loss(f_line_wb, 2, 1, x_data, y_data_pure)
print("Mean Err under Case0:",err0)
err1 = f_loss(f_line_wb, 2, 1, x_data, y_data_noise)
print("Mean Err under Case1:",err1)
err2 = f_loss(f_line_wb, 2, 1.1, x_data, y_data_noise)
print("Mean Err under Case2:",err2)
err3 = f_loss(f_line_wb, 2, 0.9, x_data, y_data_noise)
print("Mean Err under Case3:",err3)
err4 = f_loss(f_line_wb, 2.1, 1, x_data, y_data_noise)
print("Mean Err under Case4:",err4)
err5 = f_loss(f_line_wb, 1.9, 1, x_data, y_data_noise)
print("Mean Err under Case5:",err5)
err6 = f_loss(f_line_wb, 1, 2, x_data, y_data_noise)
print("Mean Err under Case6:",err6)
# 问题:w, b 为多少时?残差值最小呢?
Mean Err under Case0: 0.0 Mean Err under Case1: 0.1315572426174743 Mean Err under Case2: 0.13335834500408647 Mean Err under Case3: 0.14975614023086212 Mean Err under Case4: 0.1322429501791569 Mean Err under Case5: 0.13760622893334273 Mean Err under Case6: 0.41311935413207634
(3)可视化损失函数
# 可视化空间线性数据
w_line = np.linspace(-10, 10, 10)
b_line = np.linspace(-10, 10, 10)
# 假定z轴的loss数值恒定为: z = 1
loss = 1
fig = plt.figure()
ax = plt.axes(projection='3d') #使用matplotlib.pyplot创建坐标系
ax.scatter3D(w_line, b_line, loss, cmap='Blues') #绘制三维散点图
plt.show()
#二元(w,b)残差函数的线性图形
w_line = np.linspace(-10, 10, 30)
b_line = np.linspace(-10, 10, 30)
size = len(w_line)
loss = []
# 假定z轴的loss数值来自残差函数: z = f_loss
for i in range(size):
err = f_loss(f_line_wb, w_line[i], b_line[i], x_data, y_data_noise)
loss.append(err)
fig = plt.figure()
ax = plt.axes(projection='3d') #使用matplotlib.pyplot创建坐标系
ax.scatter3D(w_line, b_line, loss, cmap='Blues') #绘制三维散点图
ax.plot3D(w_line , b_line, loss,'gray') #绘制三维曲线图
plt.show()
# 可视化空间线性数据到空间网格数据的转换
w_line = np.linspace(-10, 10, 10)
b_line = np.linspace(-10, 10, 10)
w_line_grid ,b_line_grid = np.meshgrid(w_line, b_line) #空间的点序列转换成网格点
# 假定z轴的loss数值恒定为: z = 1
loss = 1
fig = plt.figure()
ax = plt.axes(projection='3d') #使用matplotlib.pyplot创建坐标系
ax.scatter3D(w_line_grid, b_line_grid, loss, cmap='rainbow') #绘制三维散点图
plt.show()
# 可视化空间图形
w_line = np.linspace(-10, 10, 20)
b_line = np.linspace(-10, 10, 20)
w_line_grid ,b_line_grid = np.meshgrid(w_line, b_line) #空间的点序列转换成网格点
# 假定z轴的loss数值来自于 z = x^2 + y^2
loss = w_line_grid**2 + b_line_grid**2
fig = plt.figure()
ax = plt.axes(projection='3d') #使用matplotlib.pyplot创建坐标系
ax.scatter3D(w_line_grid, b_line_grid, loss, cmap='rainbow') #绘制三维散点图
plt.show()
# 可视化空间图形
w_line = np.linspace(-10, 10, 20)
b_line = np.linspace(-10, 10, 30)
w_line_grid ,b_line_grid = np.meshgrid(w_line, b_line) #空间的点序列转换成网格点
print("w_line_grid shape", w_line_grid.shape)
print("b_line_grid shape", b_line_grid.shape)
# 假定z轴的loss数值来自于: z = 3*x^2 - y^2 + 1
loss = 3*w_line_grid**2 - b_line_grid**2 + 1
print("loss shape", loss.shape)
fig = plt.figure()
ax = plt.axes(projection='3d') #使用matplotlib.pyplot创建坐标系
ax.scatter3D(w_line_grid, b_line_grid, loss, cmap='rainbow') #绘制三维散点图
plt.show()
w_line_grid shape (30, 20) b_line_grid shape (30, 20) loss shape (30, 20)
# 可视化空间图形
w_line = np.linspace(-10, 10, 20)
b_line = np.linspace(-10, 10, 30)
w_line_grid ,b_line_grid = np.meshgrid(w_line, b_line) #空间的点序列转换成网格点
print("w_line_grid shape", w_line_grid.shape)
print("b_line_grid shape", b_line_grid.shape)
# 假定z轴的loss数值来自于: z = (1/3) * x^2 + x * y + y^2 + 1
loss = (1/3)*w_line_grid**2 + w_line_grid * b_line_grid + b_line_grid**2 + 1
print("loss shape", loss.shape)
fig = plt.figure()
ax = plt.axes(projection='3d') #使用matplotlib.pyplot创建坐标系
ax.scatter3D(w_line_grid, b_line_grid, loss, cmap='rainbow') #绘制三维散点图
plt.show()
w_line_grid shape (30, 20) b_line_grid shape (30, 20) loss shape (30, 20)
# 可视化空间图形
w_line = np.linspace(-10, 10, 20)
b_line = np.linspace(-10, 10, 30)
print("w_line len:" ,len(w_line))
print("b_Line len:", len(b_line))
w_line_grid ,b_line_grid = np.meshgrid(w_line, b_line) #空间的点序列转换成网格点
print("w_line_grid shape", w_line_grid.shape)
print("b_line_grid shape", b_line_grid.shape)
# 假定z轴的loss数值来自于残差函数: z = f_loss
loss = np.zeros((len(w_line_grid),len(w_line_grid[0])))
print("loss shape:", loss.shape)
for i in range (len(w_line_grid)):
for j in range (len(w_line_grid[0])):
loss[i][j] = f_loss(f_line_wb, w_line_grid[i][j], b_line_grid[i][j], x_data, y_data_noise)
fig = plt.figure()
ax = plt.axes(projection='3d') #使用matplotlib.pyplot创建坐标系
ax.scatter3D(w_line_grid, b_line_grid, loss, label="loss", cmap='rainbow') #绘制三维散点图
plt.show()
w_line len: 20 b_Line len: 30 w_line_grid shape (30, 20) b_line_grid shape (30, 20) loss shape: (30, 20)
(1)一元线性函数求导
# 原函数yi=f(xi) = w*xi + b
# 对x的导函数: x为参数,w,b为常量
def fv1(xi, w, b):
num = len(xi)
rst = np.ones(num) * w
return (rst)
#显示上述函数图形
x_line = np.linspace(0, 1, 30)
y_fv1 = fv1(x_line, 2, 1)
#对w的偏导函数
plt.scatter(x_line, y_fv1, label="sample", color="black")
plt.plot (x_line, y_fv1, label="curve", color="blue", linewidth = 4)
(2)二元线性求偏导
# 残差:err = f(w,b, xi, yi) = w*xi + b - yi
# 对w求偏导数:w为变量,xi为常量, b为常量
def fv1_w(w, b, xi, yi):
num = len(w)
rst = np.ones(num) * xi #对w偏导,其值为xi
return (rst)
# 对b求偏导数:w为常量,xi为常量, b为变量
def fv1_b(w, b, xi, yi):
num = len(b)
rst = np.ones(num) #对w偏导,其值恒定为1
return (rst)
#显示上述函数图形
x_line = np.linspace(0, 1, 30)
y_fv1_w = fv1_w(x_line, 0, 2, 3) #在点(2,2)的偏导函数
y_fv1_b = fv1_b(0, x_line, 2, 3) #在点(2,2)的偏导函数
# 在b=0, (xi=0, yi=0)处对w的偏导函数
plt.scatter(x_line, y_fv1_w, label="w sample", color="black")
plt.plot (x_line, y_fv1_w, label="w cruve", color="blue", linewidth = 4)
# 在w=0, (xi=0, yi=0)处对b的偏导函数
# 在b=0, (xi=0, yi=0)处对w的偏导函数
plt.scatter(x_line, y_fv1_b, label="b sample", color="black")
plt.plot (x_line, y_fv1_b, label="b curve", color="blue", linewidth = 4)
(3)二元二次非线性函数求偏导
# loss = np.mean( w * xdata + b - y_data_noise)**2)
# 残差函数对 w 的一阶偏导数
def f_loss_fv1_w(w, b, input_x_data, input_y_data_noise):
loss_fv = 2 * np.mean(input_x_data * input_x_data) * w + 2 * np.mean(input_x_data) * b - 2 * np.mean(input_x_data * input_y_data_noise)
return (loss_fv)
# 残差函数对 b 的一阶偏导数
def f_loss_fv1_b(w, b, input_x_data, input_y_data_noise):
loss_fv = 2 * np.mean(input_x_data) * w + 2 * b - 2 * np.mean(input_y_data_noise)
return (loss_fv)
(4)通过偏导求在某一点的梯度
# 计算指定(w,b)点的梯度
gradient_w = f_loss_fv1_w(2,1, x_data, y_data_pure)
print(gradient_w)
gradient_b = f_loss_fv1_b(2,1, x_data, y_data_pure)
print(gradient_b)
gradient_w = f_loss_fv1_w(2,1, x_data, y_data_noise)
print(gradient_w)
gradient_b = f_loss_fv1_b(2,1, x_data, y_data_noise)
print(gradient_b)
4.440892098500626e-16 4.440892098500626e-16 -0.026816393770928926 -0.08198897613387857
备注:当w=2,b=1时,梯度接近为0
(1)定义梯度下降法的迭代函数
# 用梯度下降法求w,b的值
#自定义最小二乘求解拟合函数参数:偏导 + 梯度下降法
def usr_gradient_descent_fit(input_f, input_x_data, input_y_data_noise, learning_rate = 0.1, max_loop = 300):
w_data = [] #存放w值的迭代序列
b_data = [] #存放b值的迭代序列
z_data = [] #存放loss值的迭代序列
w_diff_data = []
b_diff_data = []
w_k = 0.
b_k = 0.
z_k = 0.
#保存初始信息
w_data.append(w_k)
b_data.append(b_k)
z_k = f_loss(input_f, w_k, b_k, input_x_data, input_y_data_noise)
z_data.append(z_k)
w_diff_data.append(w_k)
b_diff_data.append(b_k)
for k in range(max_loop):
# (1)学习率与计算梯度!!!
step_x = learning_rate * f_loss_fv1_w(w_k, b_k , input_x_data, input_y_data_noise)
step_y = learning_rate * f_loss_fv1_b(w_k, b_k , input_x_data, input_y_data_noise)
# (2)最重要的梯度下降迭代!!!
w_k1 = w_k - step_x
b_k1 = b_k - step_y
# (3)计算迭代后的残差 !!!
z_k1 = f_loss(input_f, w_k1, b_k1, input_x_data, input_y_data_noise)
# (4)记录中间迭代过程
# 计算迭代前后w,b的差(作为x的偏差)
w_diff = step_x
b_diff = step_y
z_diff = z_k1 - z_k
# 保存当前的迭代点信息
w_data.append(w_k1)
b_data.append(b_k1)
z_data.append(z_k1)
w_diff_data.append(w_diff)
b_diff_data.append(b_diff)
# (5)为新一轮迭代做准备
w_k = w_k1
b_k = b_k1
z_k = z_k1
#返回 w,b值以及中间迭代过程的数据
return ((w_k, b_k, z_k), (w_data, b_data, z_data), (w_diff_data, b_diff_data))
(2)调用迭代函数接近w和b的最佳值
print("使用无噪声数据:")
rst = usr_gradient_descent_fit(f_line_wb, x_data, y_data_pure)
w_grad, b_grad, e_grad = rst[0]
print("参数w=", w_grad)
print("参数b=", b_grad)
print("残差e=", e_grad)
print("\n使用有噪声数据:")
rst = usr_gradient_descent_fit(f_line_wb, x_data, y_data_noise)
w_grad, b_grad, e_grad = rst[0]
print("参数w=", w_grad)
print("参数b=", b_grad)
print("残差e=", e_grad)
使用无噪声数据: 参数w= 1.9817535011137024 参数b= 1.0097929273554476 残差e= 2.9325459666068766e-05 使用有噪声数据: 参数w= 1.9015879580969595 参数b= 1.0908126753128762 残差e= 0.12932179354125223
备注:
对于无噪声样本,残差值接近于0,: 2.9325459666068766e-05
对于有噪声样本,残差值不可能为0,一定有一定的残差值:0.12932179354125223
(1) w,b,残差的并发收敛过程
# w,b,残差的并发收敛过程
w_data, b_data, e_data = rst[1]
w_diff, b_diff = rst[2]
fig = plt.figure()
ax = plt.axes(projection='3d') #使用matplotlib.pyplot创建坐标系
ax.scatter3D(w_data, b_data, e_data, label="loss", cmap='rainbow') #绘制三维散点图
plt.show()
(2)w参数的迭代过程:反映的是w值本身数值的变化过程
print("w参数的迭代过程")
print("参数w=", w_grad)
num = len(w_data)
x_seq = np.array(range(0,num))
plt.scatter(x_seq, w_data, label="sample", color="blue")
(3)w的偏差变化过程:反映的是相邻两次w值之间的迭代步长的变化。
print("w的偏差变化过程")
print("final w_diff = ", w_diff[-1])
num = len(w_diff)
x_seq = np.array(range(0, num))
plt.scatter(x_seq, w_diff, label="sample", color="blue")
w的偏差变化过程 final w_diff = -0.00023128483534975432
(4)b参数的迭代过程:反映的是b值本身数值的变化过程
print("b参数的迭代过程")
print("参数b=", b_grad)
num = len(w_data)
x_seq = np.array(range(0,num))
plt.scatter(x_seq, b_data, label="sample", color="blue")
b参数的迭代过程 参数b= 1.0908126753128762
(5)b的偏差变化过程:反映的是相邻两次b值之间的迭代步长的变化。
print("b的偏差变化过程")
print("final b_diff = ", b_diff[-1])
num = len(b_diff)
x_seq = np.array(range(0, num))
plt.scatter(x_seq, b_diff, label="sample", color="blue")
b的偏差变化过程 final b_diff = 0.0001241309691854653
(5)残差的变化过程
print("残差的变化过程")
print("残差e=", e_grad)
num = len(w_data)
x_seq = np.array(range(0,num))
plt.scatter(x_seq, e_data, label="sample", color="blue")
# 步骤4:利用获得的拟合函数进行数据预测
print("scipy:", w_scipy,b_scipy)
print("user :", w_usr, b_usr)
print("grad :", w_grad, b_grad)
# scipy算法的拟合数据
y_data_scipy = f_line_wb(x_data, w_scipy, b_scipy)
# 线性方程组求解的拟合数据
# y_data_usr = f_line_wb(x_data, w_usr, b_usr)
# 最小二乘+梯度下降算法的拟合数据
y_data_grad = f_line_wb(x_data, w_grad, b_grad)
scipy: 1.9182674578022025 1.0818607577986927 grad : 1.9015879580969595 1.0908126753128762
#步骤5: 图形化展示
#(1) 显示样本数据曲线
plt.scatter(x_data, y_data_noise, label="sample", color="black")
#(2) 显示理论数据曲线
plt.plot(x_data, y_data_pure, label="intrinsic", color="blue", linewidth = 2)
#(3-1) 显示预测数据曲线 - scipy库实现
plt.plot(x_data, y_data_scipy, label="predict", color="red", linewidth = 2)
#(3-2) 显示预测数据曲线 - 自定义实现
# plt.plot(x_data, y_data_usr, label="predict", color="green", linewidth = 2)
#(3-3) 显示预测数据曲线 - 自定义梯度下降法
plt.plot(x_data, y_data_grad, label="predict", color="green", linewidth = 2)
#设置属性
mpl.rcParams['font.sans-serif'] = ['SimHei']
mpl.rcParams['axes.unicode_minus'] = False
plt.title("线性拟合")
plt.legend(loc="upper left")
plt.show()
备注:
作者主页(文火冰糖的硅基工坊):https://blog.csdn.net/HiWangWenBing
本文网址:https://blog.csdn.net/HiWangWenBing/article/details/119978818