人工智能入门-深度学习权重初始化-从公式到代码实践了解各种权重初始化,从0实现代码
1.权重初始化
1.1初始化意义
在神经网络中,权重的初始值会影响模型的训练效果。不恰当的初始化可能导致梯度消失或梯度爆炸问题,使得模型难以收敛或训练速度极慢。
1.2 Xavier 初始化的核心思想
Xavier 初始化的核心思想是确保输入信号在传递到下一层时,保持方差不变。这样可以避免信号的衰减或爆炸,使训练过程更加稳定。
具体来说,Xavier 初始化的目标是:
- 保持每一层的输入信号的方差稳定。
- 使激活函数的输出范围保持在合理范围内,避免梯度消失或梯度爆炸。
1.3 数学推导
假设某一层的输入为 x,权重矩阵为 W,输出为 y=Wx。
-
假设输入 x 的方差为 Var(x)。
-
为了保持输出 y 的方差与输入 x 的方差相同,权重矩阵 W 的方差需要满足:
Var(W)=1ni Var(W) =\frac1{n_i} Var(W)=ni1
其中,nin 是该层的输入神经元数量。
同理,如果考虑反向传播中的梯度,为了保持梯度的方差稳定,权重的方差应该满足:
Var(W)=1no
Var(W) =\frac1{n_o}
Var(W)=no1
其中,nout 是该层的输出神经元数量。
综合正向传播和反向传播的稳定性,Xavier 初始化取中间值:
Var(W)=nin+nout2
Var(W)=2no+ni
Var(W) =\frac2{n_o+n_i}
Var(W)=no+ni2
具体实现
在实际应用中,Xavier 初始化通常采用以下公式生成权重:
-
均匀分布:
U(−6nin+nout,6nin+nout). U\left(-\sqrt{\frac{6}{n_\mathrm{in} + n_\mathrm{out}}}, \sqrt{\frac{6}{n_\mathrm{in} + n_\mathrm{out}}}\right). U(−nin+nout6,nin+nout6). -
正态分布:
W∼N(0,nin+nout2)
其中,U 表示均匀分布,N 表示正态分布。
1.4 实战
将详细介绍数据预处理、模型设计和超参数选择
1.4.1过程总结
- 数据预处理
- 缺失值处理:数值型特征用均值填充,类别型特征用独热编码处理。
- 特征标准化:数值型特征通过均值和标准差进行标准化处理。
- 独热编码:将类别型特征转换为独热编码(One-Hot Encoding),以便模型能够处理非数值型数据。
- 模型设计
- 使用线性模型作为基础模型,通过
K折交叉验证调整超参数。 - 可以扩展到更复杂的模型(如多层感知机、神经网络等),但课程中以线性模型为例。
- 使用线性模型作为基础模型,通过
- 模型训练
- 损失函数:使用均方误差(MSE)作为损失函数。
- 优化器:使用 Adam 优化器进行训练。
- 评价指标:使用对数均方根误差(log-RMSE)来评估模型性能。
- K 折交叉验证
- 将数据集分为
K份,每次用其中一份作为验证集,其余作为训练集。 - 重复
K次,计算平均训练误差和验证误差。
- 将数据集分为
- 模型选择与超参数调整
- 调整超参数(如学习率、权重衰减、批量大小等),通过交叉验证评估模型性能。
- 优化超参数,寻找最佳模型配置。

1.4.2Log_rmse
房价就像股票价格一样,我们关心的是相对数量,而不是绝对数量。 因此,我们更关心相对误差y−y^y, 而不是绝对误差y−y^。 例如,如果我们在俄亥俄州农村地区估计一栋房子的价格时, 假设我们的预测偏差了10万美元, 然而那里一栋典型的房子的价值是12.5万美元, 那么模型可能做得很糟糕。 另一方面,如果我们在加州豪宅区的预测出现同样的10万美元的偏差, (在那里,房价中位数超过400万美元) 这可能是一个不错的预测。
解决这个问题的一种方法是用价格预测的对数来衡量差异。 事实上,这也是比赛中官方用来评价提交质量的误差指标。 即将δ for |logy−logy^|≤δ 转换为e−δ≤y^y≤eδ。 这使得预测价格的对数与真实标签价格的对数之间出现以下均方根误差:
1n∑i=1n(logyi−logy^i)2.
\sqrt{\frac{1}{n}\sum_{i=1}^n\left(\log y_i -\log \hat{y}_i\right)^2}.
n1i=1∑n(logyi−logy^i)2.
1.4.3关键代码
1.4.3.1分类变量
all_features = pd.get_dummies(all_features, dummy_na=True, dtype=int)
-
pd.get_dummies():- 这是
pandas提供的一个函数,用于将离散特征(categorical features)转换为独热编码(one-hot encoding)。 - 独热编码是一种常见的处理分类变量的方法,将每个类别值转换为一个二进制向量。例如,如果一个特征有三个类别(
A,B,C),独热编码会将其转换为三列二进制特征:feature_A,feature_B,feature_C,其中每一列只有一个值为1,其余为0。
- 这是
-
dummy_na=True:- 这是
pd.get_dummies()函数的一个参数,表示是否将缺失值(NaN)视为一个有效的特征值。 - 当设置为
True时,如果某个特征中存在缺失值(NaN),pd.get_dummies()会为NaN生成一个新的列,例如feature_NaN,并将缺失值对应的行在此列中设为1,而非缺失值对应的行设为0。
- 这是
-
:指定
dtype参数在调用
pd.get_dummies时,可以通过设置dtype=int参数来强制生成整数类型的独热编码列(0-1编码),而不是布尔值。举例说明
假设
all_features中有一个离散特征MSZoning,其数据如下:MSZoning Price RL 200000 RM 180000 NaN 150000 RL 220000 运行
pd.get_dummies(all_features, dummy_na=True)后,MSZoning特征会被转换为以下独热编码列:MSZoning_RL MSZoning_RM MSZoning_nan Price 1 0 0 200000 0 1 0 180000 0 0 1 150000 1 0 0 220000 这样,缺失值(
NaN)也被作为一个合法的类别值处理了。
1.4.3.2 K折分割数据集
def get_k_fold_data(k, i, X, y):
实现现了一个简单版本的 K 折交叉验证(K-fold cross-validation)来分割数据集。它的功能是在第 i 次迭代中,将数据集分为 K 个子集,并返回第 i 个子集作为验证集(validation set),其余数据作为训练集(training set)。
assert k > 1:- 确保
k至少大于 1,因为 K 折交叉验证需要至少 2 折。
- 确保
fold_size = X.shape[0] // k:- 计算每个子集的大小。
X.shape[0]是数据集的样本总数,// k表示将样本总数均分为k份。
- 计算每个子集的大小。
X_train, y_train = None, None:- 初始化训练集为空。
for j in range(k)::- 遍历
k个子集。
- 遍历
idx = slice(j \* fold_size, (j + 1) \* fold_size):- 确定第
j个子集的索引范围。
- 确定第
X_part, y_part = X[idx, :], y[idx]:- 提取第
j个子集的数据和标签。
- 提取第
if j == i::- 如果当前子集是第
i个子集,则将其作为验证集(X_valid, y_valid)。
- 如果当前子集是第
elif X_train is None::- 如果当前子集不是验证集且训练集尚未初始化,则将当前子集作为训练集的初始部分。
else::- 如果训练集已经初始化,则将当前子集拼接到训练集中。这里使用了
torch.cat函数,因为它适用于 PyTorch 张量。
- 如果训练集已经初始化,则将当前子集拼接到训练集中。这里使用了
return X_train, y_train, X_valid, y_valid:- 返回训练集和验证集。
2.代码
import hashlib
import os
import tarfile
import zipfile
import requests
import numpy as np
import pandas as pd
import torch
from torch import nn
from d2l import torch as d2l
from sklearn.metrics import accuracy_score
#@save
DATA_HUB = dict()
DATA_URL = 'http://d2l-data.s3-accelerate.amazonaws.com/'
def download(name, cache_dir=os.path.join('.', 'data')): #@save
"""下载一个DATA_HUB中的文件,返回本地文件名"""
assert name in DATA_HUB, f"{name} 不存在于 {DATA_HUB}"
url, sha1_hash = DATA_HUB[name]
os.makedirs(cache_dir, exist_ok=True)
fname = os.path.join(cache_dir, url.split('/')[-1])
if os.path.exists(fname):
sha1 = hashlib.sha1()
with open(fname, 'rb') as f:
while True:
data = f.read(1048576)
if not data:
break
sha1.update(data)
if sha1.hexdigest() == sha1_hash:
return fname # 命中缓存
print(f'正在从{url}下载{fname}...')
r = requests.get(url, stream=True, verify=True)
with open(fname, 'wb') as f:
f.write(r.content)
return fname
def download_extract(name, folder=None): #@save
"""下载并解压zip/tar文件"""
fname = download(name)
base_dir = os.path.dirname(fname)
data_dir, ext = os.path.splitext(fname)
if ext == '.zip':
fp = zipfile.ZipFile(fname, 'r')
elif ext in ('.tar', '.gz'):
fp = tarfile.open(fname, 'r')
else:
assert False, '只有zip/tar文件可以被解压缩'
fp.extractall(base_dir)
return os.path.join(base_dir, folder) if folder else data_dir
def download_all(): #@save
"""下载DATA_HUB中的所有文件"""
for name in DATA_HUB:
download(name)
DATA_HUB['kaggle_house_train'] = ( #@save
DATA_URL + 'kaggle_house_pred_train.csv',
'585e9cc93e70b39160e7921475f9bcd7d31219ce')
DATA_HUB['kaggle_house_test'] = ( #@save
DATA_URL + 'kaggle_house_pred_test.csv',
'fa19780a7b011d9b009e8bff8e99922a8ee2eb90')
#训练数据集包括1460个样本,每个样本80个特征和1个标签, 而测试数据集包含1459个样本,每个样本80个特征
train_data = pd.read_csv(download('kaggle_house_train'))
test_data = pd.read_csv(download('kaggle_house_test'))
print(train_data.shape)
print(test_data.shape)
all_features = pd.concat((train_data.iloc[:, 1:-1], test_data.iloc[:, 1:]))
all_features
# 若无法获得测试数据,则可根据训练数据计算均值和标准差
numeric_features = all_features.dtypes[all_features.dtypes != 'object'].index
all_features[numeric_features] = all_features[numeric_features].apply(
lambda x: (x - x.mean()) / (x.std()))
# 在标准化数据之后,所有均值消失,因此我们可以将缺失值设置为0
all_features[numeric_features] = all_features[numeric_features].fillna(0)
all_features = pd.get_dummies(all_features, dummy_na=True, dtype=int)
n_train = train_data.shape[0]
train_features = torch.tensor(all_features[:n_train].values, dtype=torch.float32)
test_features = torch.tensor(all_features[n_train:].values, dtype=torch.float32)
train_labels = torch.tensor(
train_data.SalePrice.values.reshape(-1, 1), dtype=torch.float32)
loss = nn.MSELoss()
in_features = train_features.shape[1]
def get_net():
net = nn.Sequential(nn.Linear(in_features,1))
return net
def log_rmse(net, features, labels):
# 为了在取对数时进一步稳定该值,将小于1的值设置为1
clipped_preds = torch.clamp(net(features), 1, float('inf'))
rmse = torch.sqrt(loss(torch.log(clipped_preds),
torch.log(labels)))
return rmse.item()
def calculate_accuracy(net, features, labels, threshold=0.1):
preds = net(features)
# 计算相对误差(确保labels不为零)
relative_error = torch.abs((preds - labels) / labels)
correct = (relative_error < threshold).float()
acc = correct.mean().item() * 100 # 转换为百分比形式
return acc
def train(net, train_features, train_labels, test_features, test_labels,
num_epochs, learning_rate, weight_decay, batch_size):
train_ls, test_ls = [], []
train_acc, test_acc = [], [] # 新增准确率记录
train_iter = d2l.load_array((train_features, train_labels), batch_size)
optimizer = torch.optim.Adam(net.parameters(),
lr=learning_rate,
weight_decay=weight_decay)
for epoch in range(num_epochs):
for X, y in train_iter:
optimizer.zero_grad()
l = loss(net(X), y)
l.backward()
optimizer.step()
# 计算训练集指标
train_rmse = log_rmse(net, train_features, train_labels)
train_ls.append(train_rmse)
train_acc_val = calculate_accuracy(net, train_features, train_labels)
train_acc.append(train_acc_val)
# 计算测试集指标
if test_labels is not None:
test_rmse = log_rmse(net, test_features, test_labels)
test_ls.append(test_rmse)
test_acc_val = calculate_accuracy(net, test_features, test_labels)
test_acc.append(test_acc_val)
return train_ls, test_ls, train_acc, test_acc
def get_k_fold_data(k, i, X, y):
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], 0)
y_train = torch.cat([y_train, y_part], 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):
train_l_sum, valid_l_sum = 0, 0
train_acc_sum, valid_acc_sum = 0, 0
for i in range(k):
data = get_k_fold_data(k, i, X_train, y_train)
net = get_net()
train_ls, valid_ls, train_acc, valid_acc = train(net, *data, num_epochs,
learning_rate, weight_decay,
batch_size)
# 累计最后一轮的指标
train_l_sum += train_ls[-1]
valid_l_sum += valid_ls[-1]
train_acc_sum += train_acc[-1]
valid_acc_sum += valid_acc[-1] if valid_acc else 0
# 绘制首折的准确率曲线
if i == 0:
d2l.plot(list(range(1, num_epochs+1)), [train_acc, valid_acc],
xlabel='epoch', ylabel='accuracy (%)',
legend=['train', 'valid'], xlim=[1, num_epochs])
d2l.plot(list(range(1, num_epochs + 1)), [train_ls, valid_ls],
xlabel='epoch', ylabel='rmse', xlim=[1, num_epochs],
legend=['train', 'valid'], yscale='log')
print(f'折{i + 1},训练log rmse{float(train_ls[-1]):f}, '
f'验证log rmse{float(valid_ls[-1]):f}')
return (train_l_sum/k, valid_l_sum/k,
train_acc_sum/k, valid_acc_sum/k)
k, num_epochs, lr, weight_decay, batch_size = 5, 100, 5, 0, 64
train_l, valid_l, train_acc, valid_acc = k_fold(k, train_features, train_labels,
num_epochs, lr, weight_decay, batch_size)
print(f'{k}-折验证:')
print(f'平均训练log rmse: {float(train_l):f}')
print(f'平均验证log rmse: {float(valid_l):f}')
print(f'平均训练准确率: {train_acc:.2f}%')
print(f'平均验证准确率: {valid_acc:.2f}%')
更多推荐




所有评论(0)