线性回归的原理,如果有需要,可以单独拿出来讲。
一维线性回归的代码实现
首先我们随便给出一些点:
x_train = np.array([[3.3], [4.4], [5.5], [6.71], [6.93], [4.168],[9.779], [6.182], [7.59], [2.167], [7.042],[10.791], [5.313], [7.997], [3.1]], dtype=np.float32)y_train = np.array([[1.7], [2.76], [2.09], [3.19], [1.694], [1.573],[3.366], [2.596], [2.53], [1.221], [2.827],[3.465], [1.65], [2.904], [1.3]], dtype=np.float32)
通过matplotlib画出来就是这个样子,如图3.4所示。
我们想要做的事情就是找一条直线去逼近这些点,也就是希望这条直线离这些点的距离之和最小,先将 numpy.array 转换成 Tensor,因为 PyTorch 里面的处理单元是 Tensor,按之前讲的方法,这就特别简单了:
x_train = torch.from_numpy(x_train).cuda()
y_train = torch.from_numpy(y_train).cuda()
接着需要建立模型,根据上一节 PyTorch 的基础知识,这样来定义一个简单的模型:
class LinearRegression(nn.Module):def __init__(self):super(LinearRegression, self).__init__()self.linear = nn.Linear(1, 1) # input and output is 1 dimensiondef forward(self, x):out = self.linear(x)return outif torch.cuda.is_available():model = LinearRegression().cuda()
else:model = LinearRegression()
这里我们就定义了一个超级简单的模型 y=wx+b,输入参数是一维,输出参数也是一维,这就是一条直线,当然这里可以根据你想要的输入输出维度进行更改,我们希望去优化参数 w 和 b 能够使得这条直线尽可能接近这些点,如果支持 GPU 加速,可以通过 model.cuda() 将模型放到 GPU上。
然后定义损失函数和优化函数,这里使用均方误差作为优化函数,使用梯度下降进行优化:
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=1e-4)
接着就可以开始训练我们的模型了:
num_epochs = 1000
for epoch in range(num_epochs):if torch.cuda.is_available():inputs = Variable(x_train).cuda()target = Variable(y_train).cuda()else:inputs = Variable(x_train)target = Variable(y_train)# forwardout = model(inputs)loss = criterion(out, target)# backwardoptimizer.zero_grad()loss.backward()optimizer.step()if (epoch + 1) % 20 == 0:print('Epoch[{}/{}], loss: {:.6}'.format(epoch + 1, num_epochs, loss.item()))
定义好我们要跑的 epoch 个数,然后将数据变成 Variable 放入计算图,然后通过 out = model(inputs) 得到网络前向传播得到的结果,通过 loss = criterion(out,target) 得到损失函数,然后归零梯度,做反向传播和更新参数,特别要注意的是,每次做反向传播之前都要归零梯度,optimizer.zero_grad()。不然梯度会累加在一起,造成结果不收敛。在训练的过程中隔一段时间就将损失函数的值打印出来看看,确保我们的模型误差越来越小。注意 loss.data[0],首先 loss 是一个 Variable,所以通过 loss.data 可以取出一个 Tensor,再通过 loss.data[0] 得到一个 int 或者 float 类型的数据,这样我们才能够打印出相应的数据。
做完训练之后可以预测一下结果。
model.eval()
predict = model(Variable(x_train))
predict = predict.data.cpu().numpy()
plt.plot(x_train.cpu().numpy(), y_train.cpu().numpy(), 'ro', label='Original data')
plt.plot(x_train.cpu().numpy(), predict, label='Fitting Line')
plt.show()
首先需要通过 model.eval() 将模型变成测试模式,这是因为有一些层操作,比如 Dropout 和 BatchNommalization 在训练和测试的时候是不一样的,所以我们需要通过这样一个操作来转换这些不一样的层操作。然后将测试数据放入网络做前向传播得到结果,最后画出的结果如图 3.5所示。
这样我们就通过 PyTorch 解决了一个简单的一元回归问题,得到了一条直线去尽可能逼近这些离散的点。
多项式回归
对于一般的线性回归,由于该函数拟合出来的是一条直线,所以精度欠佳,我们可以考虑多项式回归,也就是提高每个属性的次数,而不再是只使用一次去回归目标函数。
原理和之前的线性回归是一样的,只不过这里用的是高次多项式而不是简单的一次线性多项式。
首先给出我们想要拟合的方程:
然后可以设置参数方程:
我们希望每一个参数都能够学习到和真实参数很接近的结果。
下面来看看如何用PyTorch 实现这个简单的任务。
首先需要预处理数据,也就是需要将数据变成一个矩阵的形式:
在 PyTorch 里面使用 torch.cat() 函数来实现 Tensor 的拼接:
def make_features(x):"""Builds features i.e.a matrix with colums [x,x^2,x^3]."""x = x.unsqueeze(1)return torch.cat([x ** i for i in range(1, 4)], 1)
对于输入的几个数据,我们将其扩展成上面矩阵所示的样子。
然后定义好真实的函数:
W_target = torch.FloatTensor([0.5, 3, 2.4]).unsqueeze(1)
b_target = torch.FloatTensor([0.9])def f(x):"""Approximated function."""return x.mm(W_target) + b_target[0]
这里的权重已经定义好了,unsqueeze(1) 是将原来的 tensor 大小由3变成(3,1),x.mm(W_target) 表示做矩阵乘法,f(x) 就是每次输入一个 x 得到一个 y 的真实函数。
在进行训练的时候我们需要采样一些点,可以随机生成一些数来得到每次的训练集:
def get_batch(batch_size=32):"""Builds a batch i.e.(x,f(x))pair."""random = torch.randn(batch_size)x = make_features(random)y = f(x)if torch.cuda.is_available():return Variable(x).cuda(), Variable(y).cuda()else:return Variable(x), Variable(y)
通过上面这个雨数我们每次取 batch_size 这么多个数据点,然后将其转换成矩阵的形式,再把这个值通过函数之后的结果也返回作为真实的目标。
然后可以定义多项式模型:
# Define model
class poly_model(nn.Module):def __init__(self):super(poly_model, self).__init__()self.poly = nn.Linear(3, 1)def forward(self, x):out = self.poly(x)return outif torch.cuda.is_available():model = poly_model().cuda()
else:model = poly_model()
这里的模型输入是3维,输出是1维,跟之前定义的线性模型只有很小的差别
然后我们定义损失函数和优化器:
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=1e-3)
同样使用均方误差来衡量模型的好坏,使用随机梯度下降来优化模型,然后开始训练模型:
epoch = 0
while True:# Get databatch_x, batch_y = get_batch()# forward passoutput = model(batch_x)loss = criterion(output, batch_y)print_loss = loss.data[0]# Reset gradientsoptimizer.zero_grad()# Backward passloss.backward()# update parametersoptimizer.step()epoch += 1if print_loss < 1e-3:break
这里我们希望模型能够不断地优化,直到实现我们设立的条件,取出的32个点的均方误差能够小于 0.001。
运行程序可以得到如图3.6所示的结果。
将真实函数的数据点和拟合的多项式画在同一张图上,我们可以得到如图3.7所示的结果。
从两个结果来看,我们已经很接近真实的函数了。现实世界中很多问题都不是简单的线性回归,涉及很多复杂的非线性模型,而线性模型是机器学习中最重要的模型之一,它的统计思想及其非常直观的解释性仍然可以给我们一些启发。