(一)回归(关系拟合)
开始代码,首先定义class Net,令该class继承自torch.nn.Moudle。在class中要包含__init__和forward,在绝大多数的torch中都会包含这两个功能。下面的代码是搭建最简易的神经网络:
x = torch.unsqueeze(torch.linspace(-1, 1, 100), dim = 1)
y = x.pow(2) + 0.2*torch.rand(x.size())
x, y = Variable(x), Variable(y) #数据的准备
class Net(torch.nn.Module):
def __init__(self, n_features, n_hidden, n_output): #特征,隐藏层,输出层
super(Net, self).__init__() #这一步都得这么写
self.hidden = torch.nn.Linear(n_features, n_hidden)
#命名为hidden,这就是一层神经网络(隐藏层)
self.predict = torch.nn.Linear(n_hidden, n_output)
#预测层,n_hidden是隐藏层的神经元
def forward(self, x): #这一部分才是在搭建神经网络,x是输入信息(data等)
x = F.relu(self.hidden(x)) #x经过hidden处理后,再用激活函数激活信息
x = self.predict(x) #这里的x来自上一行,经predict后直接输出
return x
net = Net(1, 10, 1) #调用,1个特征点,10个隐藏层,1个输出层
print(net)
至此,神经网络已搭建完成。下面将对神经网络进行优化(optimizer):
optimizer = torch.optim.SGD(net.parameters(), lr=0.5) #lr是学习速率,通常都小于1
loss_func = torch.nn.MSELoss() #计算误差的方法loss,MSE方法足以用来处理回归问题
# ----------------- 开始训练 ----------------- #
for i in range(100):
prediction = net(x) #看每一步的prediction
loss = loss_func(prediction, y) #y是真实值,前者是预测值
optimizer.zero_grad() #这一步是将梯度降为0,防止先前的梯度造成影响
loss.backward() #进行loss值的反向传播
optimizer.step() #用optimizer以学习效率0.5来优化梯度
以上代码不包含可视化部分,有关可视化将单独开辟文章讲解。上面的两段代码拼接后即为一个完整的模型训练框架。
除此以外,在《PyTorch深度学习实践》完结合集 中也有另外一种代码方案,原理相同。
import numpy as np
import matplotlib.pyplot as plt
x_data = [1.0, 25.0, 36.0]
y_data = [3.1, 58.0, 73.0]
def forward(x):
return x * w
def loss(x, y):
y_pred = forward(x)
return (y_pred - y) * (y_pred - y)
w_list = []
mse_list = []
for w in np.arange(0.0, 4.1, 0.1):
print('w=', w)
l_sum = 0
for x_val, y_val in zip(x_data, y_data):
y_pred_val = forward(x_val)
loss_val = loss(x_val, y_val)
l_sum += loss_val
print('\t', x_val, y_pred_val, loss_val)
print('MSE=', l_sum / 3)
w_list.append(w)
mse_list.append(l_sum / 3)
plt.plot(w_list, mse_list)
plt.show()
绘制出图的结果如下:
(二)再探梯度下降
通常来讲,图2中这种较完美的曲线很少遇到。大部分情况下维度、权重不止一个,诸如这种有两个权值的,就不能用线性搜索的方法,否则会太麻烦。有一种方法称为分治法,比方说我我们有两个权重w1w2,在搜索的时候先进行一些比较稀疏的搜索,在w1与w2上分别找4个,这样总共就是找16个。在这16个点中,我们找哪个值是其中最小的,最小的点就是要找的大致范围了。在绿框范围内再进行一个4×4范围的搜索,这样跑上几轮就可以大大的缩小搜索范围。
但是这种方法也不好,原因在于最初定点的时候有可能错过全局最小值点而找到一个局部最小值。所以就引出了这节要讲的算法——梯度下降。梯度下降就是求导,具体来说是损失函数对权值求导,取负号的原因是要沿斜率的反方向才是下降。因此梯度下降的完整公式为,其中ω为权重,cost为损失函数,α为学习率通常比较小以让其小范围下降。
同样的,梯度下降算法也存在缺陷。首先,梯度下降容易陷入局部最小值和鞍点,局部最小值不难克服,但鞍点则较难突破,因为一旦进入鞍点就没法继续迭代了。
x_data = [1.0, 2.0, 3.0]
y_data = [2.0, 4.0, 6.0]
w = 1.0
def forward(x):
return x * w
def cost(xs, ys):
cost = 0
for x, y in zip(xs, ys):
y_pred = forward(x)
cost += (y_pred - y) ** 2
return cost / len(xs)
def gradient(xs, ys):
grad = 0
for x, y in zip(xs, ys):
grad += 2 * x * (x * w - y)
return grad / len(xs)
print('Predict(beforetraining)', 4, forward(4))
for epoch in range(100):
cost_val = cost(x_data, y_data)
grad_val = gradient(x_data, y_data)
w -= 0.01 * grad_val
print('Epoch:', epoch, 'w={:.2f}'.format(w), 'loss =', cost_val)
print('Predict(after training)', 4, forward(4))
本段代码采用梯度下降算法,比(一)中的更具有可解释性。在实际训练过程中loss曲线可能是很不平滑的,带有许多毛刺。为了得到平滑曲线我们采用指数均值的方法。
而在深度学习的应用中,梯度下降用的也不多。用的多的是随机梯度下降,选用随机梯度下降的原因在于解决鞍点问题。我们在数据集中加入随机噪声,即使陷入了鞍点,凭借随机噪声也有可能会推动梯度继续下降,以此解决鞍点问题。