1. 梯度消失和梯度爆炸
这是DL中有关数值稳定性的经典问题, 当神经网络的层数较多时容易出现. 例如, 在激活函数为恒等变换时 (), 给定输入,多层感知机的第层的输出, 若所有层的权重都是标量如0.2和5, 则在第30层处的输出为 (消失) 和 (爆炸). 梯度的计算同理.
2. 初始化模型参数
2.1 其必要性
(输出单元设为1) 整个网络具有对称性, 如果不进行参数随机初始化, 即每个隐藏单元参数的初始值都是一样, 那么无论在正向/反向传播时, 同层所有单元运算结果和梯度都会是一样的, 因此本质上仅有1个隐藏单元在发挥作用
2.2 初始化方法
之前的例子中使用的都是PyTorch自带的torch.nn.init.normal()正态分布随机初始化, 另外nn.Module模块针对具体的layer类型采取了较为合理的初始化方法, 一般无需我们考虑.
还有一种常用的初始化方法Xavier随机初始化. 假设某全连接层的输入个数为, 输出个数为,Xavier随机初始化将使该层中权重参数的每个元素都随机采样于均匀分布:
它的设计主要考虑到, 模型参数初始化后, 每层输出的方差不该受该层输入个数影响, 且每层梯度的方差也不该受该层输出个数影响.
- 训练集-测试集数据差异的环境因素
主要是: 协变量偏移, 标签偏移, 概念偏移
环境因素 | 特点 | 举例 |
---|---|---|
协变量偏移 | 改变, 不变 | 特征整体偏移: 例如同地不同医院的肺炎病例特征, 因为检测设备 (例如分别为核酸检测和CT影像) 不一样, 所提取特征的分布也不一样, 但是基于各自的特征预测出来的病例分布是一样的 |
标签偏移 | 改变, 不变 | 出现新的标签或标签分布变化: 例如一家医院在流感高发季节和肺炎疫情时期来医院就诊的各种病患的比例是不一样的, 或者直接地, nCoV-2019出现以前和之后的疾病种类不一样 (多了一种标签), 但是每种疾病有自己特有的特征分布 |
概念偏移 | 变化 | 对某一概念的定义出现了变化: 例如美国对软饮料称呼的定义在不同地区的变化, 分别指代苏打水/可乐/汽水. 需要引入其他协变量来避免这个问题, 另一个可取之处是通常只是逐渐变化. |
4. Kaggle房价预测实战
kaggle比赛的一般流程:
- 获取数据集
- 数据预处理
- 模型设计
- 模型验证和模型调整(调参)
- 模型预测以及提交
- 前两步主要使用pandas, 对数据先进行一轮描述统计, 以熟悉数据特点
- 数据标准化可以使用sklearn.preprocessing模块, 有不同的标准化方法可供使用
- 标准化时, 把离散特征转变为离散数值 (0/1) 作为指示特征, 即onehot
- 比赛一般会规定评价指标, 以此作为优化目标函数 (房价预测比赛的目标函数是对数均方根误差, 如下)
一些有用的代码
def log_rmse(net, features, labels):
with torch.no_grad():
# 将小于1的值设成1,使得取对数时数值更稳定
clipped_preds = torch.max(net(features), torch.tensor(1.0))
rmse = torch.sqrt(2 * loss(clipped_preds.log(), labels.log()).mean())
return rmse.item()
- K折交叉验证
def get_k_fold_data(k, i, X, y):
# 返回第i折交叉验证时所需要的训练和验证数据
assert k > 1
fold_size = X.shape[0] // k
X_train, y_train = None, None
for j in range(k):
idx = slice(j * fold_size, (j + 1) * fold_size)
X_part, y_part = X[idx, :], y[idx]
if j == i:
X_valid, y_valid = X_part, y_part
elif X_train is None:
X_train, y_train = X_part, y_part
else:
X_train = torch.cat((X_train, X_part), dim=0)
y_train = torch.cat((y_train, y_part), dim=0)
return X_train, y_train, X_valid, y_valid
def k_fold(k, X_train, y_train, num_epochs,
learning_rate, weight_decay, batch_size):
# 返回K次训练和验证的平均误差
train_l_sum, valid_l_sum = 0, 0
for i in range(k):
data = get_k_fold_data(k, i, X_train, y_train)
net = get_net(X_train.shape[1])
train_ls, valid_ls = train(net, *data, num_epochs, learning_rate,
weight_decay, batch_size)
train_l_sum += train_ls[-1]
valid_l_sum += valid_ls[-1]
if i == 0:
d2l.semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'rmse',
range(1, num_epochs + 1), valid_ls,
['train', 'valid'])
print('fold %d, train rmse %f, valid rmse %f' % (i, train_ls[-1], valid_ls[-1]))
return train_l_sum / k, valid_l_sum / k
- 按比赛要求所需, 提交结果代码
def train_and_pred(train_features, test_features, train_labels, test_data,
num_epochs, lr, weight_decay, batch_size):
net = get_net(train_features.shape[1])
train_ls, _ = train(net, train_features, train_labels, None, None,
num_epochs, lr, weight_decay, batch_size)
d2l.semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'rmse')
print('train rmse %f' % train_ls[-1])
preds = net(test_features).detach().numpy()
test_data['SalePrice'] = pd.Series(preds.reshape(1, -1)[0])
submission = pd.concat([test_data['Id'], test_data['SalePrice']], axis=1)
submission.to_csv('./submission.csv', index=False)