底下使用 PyTorch 計算 $(y=x^2)$ 的梯度下降。
import torch as tc
import pylab as plt
import numpy as np
def f(x):
if tc.is_tensor(x):
return x.square()
else:
return np.square(x)
x = np.linspace(-5, 5, 100)
y=f(x)
fig=plt.figure(figsize=(9,6))
ax=fig.subplots()
epochs=100
lr=0.2
tx=tc.tensor([-5.], requires_grad=True)
history=[]
for i in range(epochs):
f(tx).backward()
with tc.no_grad():
now_x = tx.numpy()[0]
history.append(now_x)
a = tx.grad.numpy()[0]
b=f(now_x) - a * now_x
ax.clear()
plt.plot(x, y, c='b')
plt.scatter(history, f(history), c='r')
ax.set_xlim(-10, 10)
ax.set_ylim(-2, 35)
xl = now_x - 2
xr = now_x + 2
plt.plot([xl, xr], [a * xl + b, a * xr + b], c='orange')
tx -= tx.grad * lr
tx.grad.zero_()
plt.pause(0.01)
plt.show()

一元四次迴歸線逼近法
底下使用 PyTorch 梯度下降求一元四次迴歸線。
import numpy as np
import torch as tc
import pylab as plt
def f(a, b, c, d, e):
return tc.sum(
tc.square(
ty-(
a * tx.pow(4) +
b * tx.pow(3) +
c * tx.pow(2) +
d * tx +
e
)
)
)
n=20
np.random.seed(10)
x = np.linspace(-10,10 ,n)
y = 0.5*x+3 + np.random.randint(-5,5, n)
tx = tc.tensor(x)
ty = tc.tensor(y)
fig = plt.figure(figsize=(9,6))
ax = fig.subplots()
epochs=400
lr=2.5e-3
a=tc.tensor([0.], requires_grad=True)
b=tc.tensor([0.], requires_grad=True)
c=tc.tensor([0.], requires_grad=True)
d=tc.tensor([0.], requires_grad=True)
e=tc.tensor([0.], requires_grad=True)
for i in range(epochs):
ax.clear()
ax.set_xlim(-15, 15)
ax.set_ylim(-8, 15)
ax.plot([-15,15], [0,0], c='k', linewidth=0.5)
ax.plot([0,0], [-8,15], c='k', linewidth=0.5)
ax.scatter(x, y, c='g')
reg = np.poly1d(np.polyfit(x, y, 4))
ax.plot(x, reg(x), c="b", linewidth=5)
f(a,b,c,d,e).backward()
with tc.no_grad():
a -= a.grad * lr * 1e-6
b -= b.grad * lr * 1e-5
c -= c.grad * lr * 1e-3
d -= d.grad * lr * 1e-1
e -= e.grad * lr
ax.plot(x,
a.numpy()[0] * np.power(x, 4)+
b.numpy()[0] * np.power(x, 3) +
c.numpy()[0] * np.power(x ,2) +
d.numpy()[0] * x +
e.numpy()[0],
c='r', linewidth=1)
a.grad.zero_();b.grad.zero_();c.grad.zero_();d.grad.zero_();e.grad.zero_();
ax.text(-14, 12, f'epoch : {i+1:03d}')
ax.text(
-14,-7,
f'a={a.numpy()[0]:.7f}, b={b.numpy()[0]:.7f}, c={c.numpy()[0]:.7f}, d={d.numpy()[0]:.7f}, e={e.numpy()[0]:.7f}'
)
plt.pause(0.01)
plt.show()

一元 n 次迴歸線逼近法
底下代碼是經由上述代碼進行優化,可以計算一元 n 次迴歸線逼近。只要更改 order (次方) 及 scale 的值即可,scale 的 size 必需是 order + 1 。
import numpy as np
import torch as tc
import pylab as plt
#from celluloid import Camera
def f(params):
str_f='tc.mean(tc.square(tc.tensor(y)-('
for i in range(order+1):
str_f += f'params[{i}] * tx.pow({order-i}) +'
str_f = str_f[:-1]+ ")))"
return eval(str_f)
order=5
scale=[1e-7, 1.8e-5, 1.2e-3, 5.0e-2, 2.0e-0, 1.2e2]
n=100
np.random.seed(10)
x=np.linspace(-10,10 ,n)
y=0.5*x+3 + np.random.randint(-5,5, n)
tx = tc.tensor(x)
ty = tc.tensor(y)
fig=plt.figure(figsize=(9,6))
ax=fig.subplots()
#camera = Camera(fig)
epochs=1500
lr=2.6e-3
params=[tc.tensor([0.], requires_grad=True) for i in range(order+1)]
for epoch in range(epochs):
ax.clear()
ax.set_xlim(-15, 15)
ax.set_ylim(-8, 15)
ax.plot([-15,15], [0,0], c='k', linewidth=0.5)
ax.plot([0,0], [-8,15], c='k', linewidth=0.5)
ax.scatter(x, y, c='g', s=5)
reg = np.poly1d(np.polyfit(x, y, order))
ax.plot(x, reg(x), c="b", linewidth=5)
f(params).backward()
with tc.no_grad():
for i in range(order+1):
params[i] -= params[i].grad * lr * scale[i]
str_f=""
for i in range(order+1):
str_f += f'params[{i}].numpy()[0] * np.power(x, {order-i})+'
str_f = str_f[:-1]
ax.plot(x,eval(str_f),c='r', linewidth=1)
ax.text(-14, 12, f'epoch : {epoch+1:03d}')
txt=''
for i in range(order+1):
txt += f'p[{i}]:{params[i].numpy()[0]:10.7f}\n'
params[i].grad.zero_()
ax.text(-14,-8,txt)
for i in range(order+1):
params[i].grad.zero_()
#camera.snap()
plt.pause(0.01)
# animation = camera.animate()
# animation.save('pytorch_reg.gif', fps=30)
plt.show()

