通过高维多项式,可以拟合任意维度超平面上分布的数据,精度随多项式阶次升高而升高。
如果指导数据分布的具体形式,可以使用自定义的函数进行拟合,通常会有更好的效果。
有一组数据分布如图
不难看出这是一个马鞍面,所以我们以二元二次的拟合函数来进行演示。
首先写出拟合函数的表达形式
f p r e d i c t = F ( x , y ) = c 00 + c 10 x + c 01 y + c 20 x 2 + c 11 x y + c 02 y 2 . f_{predict} = F(x,y) =c_{00}+c_{10}x+c_{01}y+c_{20}x^2+c_{11}xy+c_{02}y^2 . fpredict=F(x,y)=c00+c10x+c01y+c20x2+c11xy+c02y2.
不难看出上面的式子一共有 6 个未知参数。一般来说,对于 m m m维 n n n次的多项式函数,最多含有 C m n n C_{mn}^n Cmnn个参数。
为了方便描述拟合函数与原数值值间的差异,我们使用均方差函数作为损失函数,损失函数越小,说明拟合函数越准确。
l o s s = 1 k ∑ i = 1 k ( f i − F ( x i , y i ) ) 2 loss = \frac{1}{k} \sum_{i=1}^{k}(f_{i}-F(x_i,y_i))^2 loss=k1i=1∑k(fi−F(xi,yi))2
因为 c a b c_{ab} cab属于 f = F ( x , y ) f=F(x,y) f=F(x,y)内的参数,所以:
l o s s = L ( c 00 , c 10 , c 01 , c 20 , c 11 , c 02 ) loss=L(c_{00},c_{10},c_{01},c_{20},c_{11},c_{02}) loss=L(c00,c10,c01,c20,c11,c02)
至此, 多项式回归问题 转化为了 求损失函数的极小值问题。
可以使用梯度下降法或粒子群算法等一系列最优化求极值的方法进行求解。具体的原理可以看其他博主写的文章。
使用 python3 语言实现,使用了sko、random、numpy、matplotlib第三方库。
用于产生训练用的数据集,按照numpy格式储存并产生三维散点图。
在这里我们设计的数据分布函数是:
f = 3 + x − 2 y − 1.5 x 2 − x y + y 2 f=3+x-2y-1.5x^2-xy+y^2 f=3+x−2y−1.5x2−xy+y2
为了增加数据的真实性,在产生数据过程中增加了1%的噪声
完整代码如下:
import random
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
'----------set data formation----------'
X, Y, R = [], [], []
for i in range(1000):
X.append(10*random.random() - 5) # 变量 X
Y.append(10*random.random() - 5) # 变量 Y
R.append(0.1*random.random() - 0.05) # 噪声,1%
# 构建方程模型(数据分布函数)
F0 = 3 + np.array(R)
F1 = np.array(X) - 2*np.array(Y)
F2 = -np.array(X)*np.array(Y) + np.array(Y)**2 - 1.5*np.array(X)**2
F = F0 + F1 + F2
Data = np.array([X, Y, list(F)])
np.save('data.npy', Data)
'----------Data visualization----------'
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('f')
ax.scatter(X, Y, F)
plt.savefig('Data.png',dpi=600,bbox_inches='tight')
plt.show()
def pre(X, Y, Parameters):
# X, Y 是输入函数的自变量,Parameters是一个列表,写入c00-c02 这6给参数
p0 = Parameters[0]
# 常数项
p1 = Parameters[1]*np.array(X) + Parameters[2]*np.array(Y)
# 一次项
p2 = Parameters[3]*np.array(X)**2 + Parameters[4]*np.array(X)*np.array(Y) + Parameters[5]*np.array(Y)**2
# 二次项
return np.array(p0) + np.array(p1) + np.array(p2)
均方差函数以会Parameters为自变量,调用多项式拟合函数(预测函数)。
def sqrloss(Parameters):
global Data
return np.sum((np.array(Data[2]) - pre(Data[0], Data[1], Parameters))**2) / len(Data[0])
这里展示获得数据后进行函数构建并优化的全部代码。
梯度下降法是最经典的凸优化方法之一,在函数场中顺着梯度负方向进行收敛,收敛步长与梯度成正比,在这里使用学习速率调节二者比例。
import random
import numpy as np
import matplotlib.pyplot as plt
'----------set data formation----------'
Data = np.load('data.npy')
'----------compose fitting system----------'
# pre(x, y) = a1x + a2x2 + b1y1 + b2y2 + c
def pre(X, Y, Parameters):
p0 = Parameters[0]
p1 = Parameters[1]*np.array(X) + Parameters[2]*np.array(Y)
p2 = Parameters[3]*np.array(X)**2 + Parameters[4]*np.array(X)*np.array(Y) + Parameters[5]*np.array(Y)**2
return np.array(p0) + np.array(p1) + np.array(p2)
# initialization parameters = [a1, a2, b1, b2, c]
Parameters = [0, 0, 0, 0, 0, 0]
# initializations
# 导数差分值
d = 1e-8
# 循环次数
maxConvergence = 10000
# 学习速率
LearnRatio = [0.0011, 0.0011, 0.0011, 0.0011, 0.0011, 0.0011]
# loss calculation
def sqrloss(Parameters):
global Data
return np.sum((np.array(Data[2]) - pre(Data[0], Data[1], Parameters))**2) / len(Data[0])
# grad(sqrloss)
def grad(Data, Parameters, d):
# initial grad
grd = [0, 0, 0, 0, 0, 0]
p1 = sqrloss(Parameters)
for i in range(len(Parameters)):
Parameters[i] = Parameters[i] + d
p2 = sqrloss(Parameters)
grd[i] = p2 - p1
# normalization
grd = np.array(grd) / d
return grd
# refresh the weight
def traning(Data, Parameters, LearnRatio, d):
grd = grad(Data, Parameters, d)
Parameters = np.array(Parameters) - np.array(LearnRatio)*np.array(grd)
return Parameters
# main loop of calculation
lenth = len(Data[0])
Sqrloss = []
for i in range(maxConvergence):
ParametersNew = traning(Data, Parameters, LearnRatio, d)
Sqrloss.append(sqrloss(Parameters))
Parameters = ParametersNew
else:
print(Parameters, sqrloss())
plt.plot(range(len(Sqrloss)), Sqrloss)
plt.savefig('梯度下降方差变化.png', dpi=600)
plt.show()
from sko.PSO import PSO
import random
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
'----------set data formation----------'
Data = np.load('data.npy')
'----------set data complete----------'
'----------compose lossfunction system----------'
# pre(x, y) = a1x + a2x2 + b1y1 + b2y2 + c
def pre(X, Y, Parameters):
p0 = Parameters[0]
p1 = Parameters[1]*np.array(X) + Parameters[2]*np.array(Y)
p2 = Parameters[3]*np.array(X)**2 + Parameters[4]*np.array(X)*np.array(Y) + Parameters[5]*np.array(Y)**2
return np.array(p0) + np.array(p1) + np.array(p2)
# loss calculation
def sqrloss(Parameters):
global Data
return np.sum((np.array(Data[2]) - pre(Data[0], Data[1], Parameters))**2)/len(Data[0])
'----------lossfunction complete----------'
# Generating Object of PSO, 传参函数和维度
pso = PSO(
func = sqrloss,
dim = 6,
pop = 300,
max_iter = 100,
)
fitness = pso.run()
print('coefficient of fitting equation = ', pso.gbest_x)
print('sqrloss = ', pso.gbest_y)
plt.plot(pso.gbest_y_hist)
plt.savefig('PSO方差变化.png', dpi=600)
plt.show()
参数 | c 00 c_{00} c00 | c 10 c_{10} c10 | c 01 c_{01} c01 | c 20 c_{20} c20 | c 11 c_{11} c11 | c 02 c_{02} c02 | 均方差 |
---|---|---|---|---|---|---|---|
数 据 | 3 | 1 | -2 | -1.5 | -1 | 1 | - - - |
拟合结果 | 2.996 | 1.000 | -2.000 | -1.500 | -1.000 | 1.000 | 0.00081 |
参数 | c 00 c_{00} c00 | c 10 c_{10} c10 | c 01 c_{01} c01 | c 20 c_{20} c20 | c 11 c_{11} c11 | c 02 c_{02} c02 | 均方差 |
---|---|---|---|---|---|---|---|
数 据 | 3 | 1 | -2 | -1.5 | -1 | 1 | - - - |
拟合结果 | 3.001 | 1.000 | -2.000 | -1.500 | -1.000 | 1.000 | 0.00080 |
总的来讲这两种方法都能达到不错的收敛结果,其中粒子群以算法以较低的计算量和较快的运算速度更胜一筹。在这种均方差形式的损失函数中,仅存在一个极值点,所以不用担心梯度下降法会收敛于非全局的最小值点;倘若损失函数存在不止一个极值点,这时候梯度下降法不一定总能收敛到正确结果,粒子群算法相对有着更好的全局性,获得正确结果的概率要高于梯度下降法。