# 下面是从零开始的完整实现 features, labels = data_process.get_data_ch7() # 初始化参数 def init_rmsprop_states(): s_w = torch.zeros((features.shape[1], 1), dtype=torch.float32) s_b = torch.zeros(1, dtype=torch.float32) return (s_w, s_b) def rmsprop(params, states, hyperparams): # 关键在于超参数gamma的引入 gamma, eps = hyperparams['gamma'], 1e-6 for p, s in zip(params, states): # 新的s的生成带有了gamma的加权 s.data = gamma * s.data + (1 - gamma) * (p.grad.data)**2 p.data -= hyperparams['lr'] * p.grad.data / torch.sqrt(s + eps) # 新的训练可以看到即使在训练的末尾线条也可能出现一些波动了 train.train_ch7(rmsprop, init_rmsprop_states(), {'lr': 0.01, 'gamma': 0.9}, features, labels) print('————————————————————————————') # 简洁实现可以直接调用库中的RMSprop优化器,但是这里的gamma变为了alpha超参数 train.train_pytorch_ch7(torch.optim.RMSprop, { 'lr': 0.01, 'alpha': 0.9}, features, labels) print('————————————————————————————')
def init_adadelta_states(): s_w, s_b = torch.zeros( (features.shape[1], 1), dtype=torch.float32), torch.zeros(1, dtype=torch.float32) # 需要额外初始化参数delta,用来调整临时梯度的值 delta_w, delta_b = torch.zeros( (features.shape[1], 1), dtype=torch.float32), torch.zeros(1, dtype=torch.float32) return ((s_w, delta_w), (s_b, delta_b)) def adadelta(params, states, hyperparams): # 这里的rho相当于AdaGrad的gamma rho, eps = hyperparams['rho'], 1e-5 for p, (s, delta) in zip(params, states): s[:] = rho * s + (1 - rho) * (p.grad.data**2) # 对临时梯度使用了更复杂的更新方法,关键是使用了(delta + eps)来代替之前所需的 # 学习率 g = p.grad.data * torch.sqrt((delta + eps) / (s + eps)) p.data -= g # delta在使用后也得进行更新,更新方法类似于对中间值s的更新 delta[:] = rho * delta + (1 - rho) * g * g train.train_ch7(adadelta, init_adadelta_states(), {'rho': 0.9}, features, labels) print('————————————————————————————') # 简洁实现依然是使用库已经写好的Adadelta,库的版本一开始的时候loss就比较低了 train.train_pytorch_ch7(torch.optim.Adadelta, {'rho': 0.9}, features, labels)
def train_sgd(lr, batch_size, num_epochs=2): train.train_ch7(sgd, None, {'lr': lr}, features, labels, batch_size, num_epochs)
# 这里开始从零开始实现AdaGrad优化器,重点是维护控制学习率的变量 # 这里使用大一些的数据来测试 features, labels = data_process.get_data_ch7() # 初始化参数 def init_adagrad_states(): s_w = torch.zeros((features.shape[1], 1), dtype=torch.float32) s_b = torch.zeros(1, dtype=torch.float32) return (s_w, s_b) def adagrad(params, states, hyperparams): eps = 1e-6 # 对每个参数对应的学习率按公式设置然后应用到参数上 for p, s in zip(params, states): # 按照梯度大小提高s的值,也就是减小学习率,使用了平方使得s只增不减 s.data += (p.grad.data**2) # 用类似学习率的用法来使用s,s作为学习率的权重 p.data -= hyperparams['lr'] * p.grad.data / torch.sqrt(s + eps) # 对比上节的动量法可以看到这次下降很快而且收敛后也很稳定 train.train_ch7(adagrad, init_adagrad_states(), {'lr': 0.1}, features, labels) print('————————————————————————————') # 简洁实现只要使用库函数即可 train.train_pytorch_ch7(torch.optim.Adagrad, {'lr': 0.1}, features, labels) print('————————————————————————————')
beta1, beta2, eps = 0.9, 0.999, 1e-6 for p, (v, s) in zip(params, states): # 更新v和s v[:] = beta1 * v + (1 - beta1) * p.grad.data s[:] = beta2 * s + (1 - beta2) * p.grad.data**2 # 使用记录下来的超参数时间步t来进行偏差修正,这是因为由于加权平均和极限的原因, # 只截取一部分值进行估算既准确又不准确,一开始初始化的辅助参数0可能会导致很大 # 的偏差,尽管在计算链长的时候前面的值会被近似忽略,但是在数据刚开始计算的时候 # 影响可能很大,而且难以用多次加权来处理 # 对时间步的偏差修正大幅减少了前期数据对总体数据的影响,因而能得到更准确的移动 # 加权平均的结果 v_bias_corr = v / (1 - beta1**hyperparams['t']) s_bias_corr = s / (1 - beta2**hyperparams['t']) # 应用到参数上 p.data -= hyperparams['lr'] * v_bias_corr / \ (torch.sqrt(s_bias_corr) + eps) # 更新超参数时间步 hyperparams['t'] += 1 # 训练测试,现在的优化算法由于RMSprop部分使得下降比较稳定,又由于动量法部分下降很快 train.train_ch7(adam, init_adam_states(), { 'lr': 0.01, 't': 1 }, features, labels) print('————————————————————————————') # 简洁实现 train.train_pytorch_ch7(torch.optim.Adam, {'lr': 0.01}, features, labels) print('————————————————————————————')