来自 B 站刘二大人的《PyTorch深度学习实践》P7 的学习笔记
回顾
上一篇逻辑斯蒂回归中,分类的最终结果是输出概率对应的分类,是离散的集合,虽然神经网络预测的概率的分布拟合的是 Sigmoid,但这些概率是用于决策分类的,类别是离散的集合;
而线性回归的输出结果是实数集,它们拟合目标函数。
数据表示
对于关系型数据表,在 PyTorch 中读取时是以下面的方式表示的:
- 一行是一个 Sample(样本):
- 一列是一个 Feature(特征):
多维数据
需要转换成矩阵运算
-
Mini-Batch(N Samples)
得益于 PyTorch Tensor 的矩阵广播能力,我们可以很轻松地构建矩阵输入进行 Mini-Batch 运算:
由于输入的矩阵特征维度(列数)增加了,从上一章的 N × 1 N \times 1 N×1 变成了 N × 8 N \times 8 N×8 ,我们在模型 Linear 层中也需要更改相应参数:
-
Linear Layer
torch.nn.Linear(in_features, out_features)
:对输入数据应用线性变换: y = x ⋅ A T + b y = x \cdot A^T+b y=x⋅AT+b
- 输入维度:
(
N
,
∗
,
H
i
n
)
(N, *, H_{in})
(N,∗,Hin)。
*
表示 sample 数(行数), H i n = H_{in} = Hin= in_features - 输出维度: ( N , ∗ , H o u t ) (N, *, H_{out}) (N,∗,Hout)。除了最后一个维度外,其他所有维度都与输入的形状相同, H o u t = H_{out} = Hout= out_features
Examples:
>>> m = nn.Linear(20,30) >>> input = torch.randn(128,20) >>> output = m(input) >>> print(output.size()) torch.size([128,30])
对于以下矩阵,每一行是一个 sample,
size of each sample
表示一行的大小,也即列数(Feature):
所以说神经网络可以做空间变换, σ ( ⋅ ) \sigma(\cdot) σ(⋅) 提供了非线性变换:
- 输入维度:
(
N
,
∗
,
H
i
n
)
(N, *, H_{in})
(N,∗,Hin)。
神经网络构造
多个非线性变换的链接可以拟合更复杂的函数,网络层的输出可降维也可升维,是升是降,这是超参数的哲学。
这里老师即兴穿插读书的哲学 🐶 :
三层 Linear Layer 构建的神经网络:
代码与计算图一一对应,一目了然
试验不同的激活函数
不同的激活函数的输出区间不同,由于我们最后要计算 log(x)
,x 不能为 0(ReLU 把小于 0 的都取为 0),所以要注意最后输出的仍然要经过 sigmoid()
:
经过测试的完整代码如下:
import copy
import numpy as np
import torch
from torch import nn, optim
from torch.nn import functional as F
xy = np.loadtxt("../datasets/diabetes/diabetes.csv.gz", delimiter=",", dtype=np.float32)
# 除了最后一列,都是 x_data
x_data = torch.from_numpy(xy[:, :-1])
# 只取最后一列,得到 759 × 1 的矩阵,若是这样取 xy[:, -1],则是 1 × 759 的向量
y_data = torch.from_numpy(xy[:, [-1]])
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.linear1 = nn.Linear(8, 6)
self.linear2 = nn.Linear(6, 4)
self.linear3 = nn.Linear(4, 1)
# 可以任意选择一个激活函数,当然还有更多
self.activate = nn.Sigmoid()
# self.activate = nn.Tanh()
# self.activate = nn.ReLU()
# self.activate = nn.LeakyReLU()
# self.activate = nn.CELU()
# self.activate = nn.Softplus()
def forward(self, x):
x = self.activate(self.linear1(x))
x = self.activate(self.linear2(x))
x = F.sigmoid((self.linear3(x)))
return x
model = Model()
criterion = nn.BCELoss()
optimizer = optim.Adam(model.parameters(), lr=0.03) # SGD 的效果很不好,我换成了 Adam
npz_dict = {"loss": [], "acc": []} # 用于保存 loss 和 acc 数值,便于后面可视化比较
for epoch in range(1000):
# forward
y_pred = model(x_data)
loss = criterion(y_pred, y_data)
# 我添加了训练过程中的精度测试
y_pred_hat = copy.copy(y_pred.data.numpy())
y_pred_hat[y_pred_hat >= 0.5] = 1.0
y_pred_hat[y_pred_hat < 0.5] = 0.0
acc = np.sum(y_pred_hat.flatten() == y_data.data.numpy().flatten()) / len(y_data)
print("epoch:", epoch, "loss:", loss.item(), "acc:", acc)
# 持久化数值
npz_dict["loss"].append(loss.item())
npz_dict["acc"].append(acc)
# backward
optimizer.zero_grad()
loss.backward()
# update
optimizer.step()
np.savez("./npz_file/sigmoid.npz", loss = npz_dict["loss"], acc = npz_dict["acc"]) # 保存数值
注意我们虽然输入训练的是多维特征的数据,但是我们实际上还没有用 Mini-Batch 来训练,这部分详细内容在下一篇:PyTorch 加载数据集(Dataset、DataLoader)
读取 npz 文件得到数值后绘图结果如下:
读取文件的绘图函数如下:
import numpy as np
import matplotlib.pyplot as plt
def draw(name, title="acc"):
loss_acc = np.load(f"./npz_file/{name}.npz")
y = loss_acc[f"{title}"]
x = np.arange(len(y))
plt.plot(x, y, label=f"best:{max(y)}" if title == "acc" else None)
plt.title(title)
plt.xlabel("x")
plt.ylabel(name)
plt.legend()
plt.grid()
plt.show()
filename = ["sigmoid", "relu", "leakyrelu", "tanh", "softplus"]
draw(filename[0], title="acc") # title = "loss" 或 "acc"