NNDL 作业13 优化算法3D可视化

编程实现优化算法,并3D可视化

1. 函数3D可视化

分别画出x[0]^{2}+x[1]^{2}+x[1]^{3}+x[0]*x[1] 和 x^{2} /20+y^{2}的3D图

from mpl_toolkits.mplot3d import Axes3D
import numpy as np
from matplotlib import pyplot as plt
import torch
from nndl.op import Op

# 定义一个优化的二元函数类,计算梯度
class OptimizedFunction3D(Op):
    def __init__(self):
        super(OptimizedFunction3D, self).__init__()
        self.params = {'x': 0}
        self.grads = {'x': 0}
 
    def forward(self, x):
        self.params['x'] = x
        return x[0] ** 2 + x[1] ** 2 + x[1] ** 3 + x[0] * x[1]
 
    def backward(self):
        x = self.params['x']
        gradient1 = 2 * x[0] + x[1]
        gradient2 = 2 * x[1] + 3 * x[1] ** 2 + x[0]
        grad1 = torch.Tensor([gradient1])
        grad2 = torch.Tensor([gradient2])
        self.grads['x'] = torch.cat([grad1, grad2])

# 使用numpy.meshgrid生成x1,x2矩阵,矩阵的每一行为[-3, 3],以0.1为间隔的数值
x1 = np.arange(-3, 3, 0.1)
x2 = np.arange(-3, 3, 0.1)
x1, x2 = np.meshgrid(x1, x2)
init_x = torch.Tensor(np.array([x1, x2]))

model = OptimizedFunction3D()

# 绘制 f_3d函数 的 三维图像
fig = plt.figure()
ax = plt.axes(projection='3d')
X = init_x[0].numpy()
Y = init_x[1].numpy()
Z = model(init_x).numpy()
ax.plot_surface(X, Y, Z, cmap='rainbow')

ax.set_xlabel('x1')
ax.set_ylabel('x2')
ax.set_zlabel('f(x1,x2)')
plt.show()
 
# 定义一个简单的二元函数
def func(x, y):
    return x * x / 20 + y * y

# 绘制损失函数的三维图像
def paint_loss_func():
    x = np.linspace(-50, 50, 100)  # x的绘制范围是-50到50,从该区间均匀取100个数
    y = np.linspace(-50, 50, 100)  # y的绘制范围是-50到50,从该区间均匀取100个数

    X, Y = np.meshgrid(x, y)
    Z = func(X, Y)

    fig = plt.figure()
    ax = Axes3D(fig)
    plt.xlabel('x')
    plt.ylabel('y')

    ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='rainbow')
    plt.show()

paint_loss_func()

2.加入优化算法,画出轨迹

分别画出x[0]^{2}+x[1]^{2}+x[1]^{3}+x[0]*x[1] 和 x^{2} /20+y^{2}的3D轨迹图

结合3D动画,用自己的语言,从轨迹、速度等多个角度讲解各个算法优缺点

import torch
import math
import matplotlib.pyplot as plt

# 定义优化器类
class Optimizer:
    def __init__(self, params, lr=0.01):
        self.params = list(params)
        self.lr = lr

    # 在子类中实现具体的优化算法
    def step(self):
        raise NotImplementedError

    # 在子类中实现具体的学习率调整算法
    def lr_decay(self):
        raise NotImplementedError

# 定义梯度下降优化器
class SGD(Optimizer):
    def __init__(self, params, lr=0.01, momentum=0):
        super().__init__(params, lr)
        self.momentum = momentum
        self.velocity = [torch.zeros_like(param) for param in self.params]

    # 实现梯度下降优化算法
    def step(self):
        for i, param in enumerate(self.params):
            if self.momentum > 0:
                # 带动量的更新公式
                self.velocity[i] = self.momentum * self.velocity[i] + (1 - self.momentum) * param.grad
                param.data -= self.lr * self.velocity[i]
            else:
                # 普通的梯度下降更新公式
                param.data -= self.lr * param.grad

    # 实现学习率衰减算法
    def lr_decay(self):
        self.lr *= 0.1

# 定义Adagrad优化器
class Adagrad(Optimizer):
    def __init__(self, params, lr=0.01, eps=1e-6):
        super().__init__(params, lr)
        self.eps = eps
        self.cache = [torch.zeros_like(param) for param in self.params]

    # 实现Adagrad优化算法
    def step(self):
        for i, param in enumerate(self.params):
            # 更新参数的缓存
            self.cache[i] += param.grad.pow(2)
            # 计算学习率调整系数
            lr_t = self.lr / torch.sqrt(self.cache[i] + self.eps)
            # 更新参数
            param.data -= lr_t * param.grad

    # 实现学习率衰减算法
    def lr_decay(self):
        self.lr *= 0.1

# 定义RMSprop优化器
class RMSprop(Optimizer):
    def __init__(self, params, lr=0.01, alpha=0.9, eps=1e-6):
        super().__init__(params, lr)
        self.alpha = alpha
        self.eps = eps
        self.cache = [torch.zeros_like(param) for param in self.params]

    # 实现RMSprop优化算法
    def step(self):
        for i, param in enumerate(self.params):
            # 更新参数的缓存
            self.cache[i] = self.alpha * self.cache[i] + (1 - self.alpha) * param.grad.pow(2)
            # 计算学习率调整系数
            lr_t = self.lr / torch.sqrt(self.cache[i] + self.eps)
            # 更新参数
            param.data -= lr_t * param.grad

    # 实现学习率衰减算法
    def lr_decay(self):
        self.lr *= 0.1

# 定义Momentum优化器
class Momentum(Optimizer):
    def __init__(self, params, lr=0.01, momentum=0.9):
        super().__init__(params, lr)
        self.momentum = momentum
        self.velocity = [torch.zeros_like(param) for param in self.params]

    # 实现Momentum优化算法
    def step(self):
        for i, param in enumerate(self.params):
            # 带动量的更新公式
            self.velocity[i] = self.momentum * self.velocity[i] + (1 - self.momentum) * param.grad
            param.data -= self.lr * self.velocity[i]

    # 实现学习率衰减算法
    def lr_decay(self):
        self.lr *= 0.1

# 定义Nesterov优化器
class Nesterov(Optimizer):
    def __init__(self, params, lr=0.01, momentum=0.9):
        super().__init__(params, lr)
        self.momentum = momentum
        self.velocity = [torch.zeros_like(param) for param in self.params]

    # 实现Nesterov优化算法
    def step(self):
        for i, param in enumerate(self.params):
            if self.momentum > 0:
                # 计算Nesterov加速梯度
                p = param - self.lr * self.momentum * self.velocity[i]
                # 计算梯度
                p.grad = param.grad
                # 带动量的更新公式
                self.velocity[i] = self.momentum * self.velocity[i] + (1 - self.momentum) * p.grad
                # 更新参数
                param.data -= self.lr * self.velocity[i]

    # 实现学习率衰减算法
    def lr_decay(self):
        self.lr *= 0.1

# 定义Adam优化器
class Adam(Optimizer):
    def __init__(self, params, lr=0.01, beta1=0.9, beta2=0.999, eps=1e-8):
        super().__init__(params, lr)
        self.beta1 = beta1
        self.beta2 = beta2
        self.eps = eps
        self.m = [torch.zeros_like(param) for param in self.params]
        self.v = [torch.zeros_like(param) for param in self.params]
        self.t = 0

    # 实现Adam优化算法
    def step(self):
        self.t += 1
        for i, param in enumerate(self.params):
            # 更新第一阶动量和第二阶动量
            self.m[i] = self.beta1 * self.m[i] + (1 - self.beta1) * param.grad
            self.v[i] = self.beta2 * self.v[i] + (1 - self.beta2) * param.grad.pow(2)
            # 计算修正后的一阶动量和二阶动量
            m_hat = self.m[i] / (1 - self.beta1 ** self.t)
            v_hat = self.v[i] / (1 - self.beta2 ** self.t)
            # 计算学习率调整系数
            lr_t = self.lr / torch.sqrt(v_hat + self.eps)
            # 更新参数
            param.data -= lr_t * m_hat

    # 实现学习率衰减算法
    def lr_decay(self):
        self.lr *= 0.1

# 定义要优化的函数
def f(x):
    return x ** 2 + 10 * torch.sin(x)

# 定义可视化函数
def plot_fn():
    x = torch.arange(-10, 10, 0.1)
    y = f(x)
    plt.plot(x.numpy(), y.numpy())

# 初始化参数
x = torch.tensor([3.], requires_grad=True)

# 定义优化器列表
optimizers = [
    SGD([x], lr=0.01),
    Adagrad([x], lr=1),
    RMSprop([x], lr=0.1),
    Momentum([x], lr=0.01),
    Nesterov([x], lr=0.01),
    Adam([x], lr=0.1)
]

# 定义循环次数和学习率衰减周期
num_iters = 100
lr_decay_epoch = 50

# 训练并可视化结果
for optimizer in optimizers:
    # 初始化参数
    x = torch.tensor([3.], requires_grad=True)
    # 记录每一次训练的损失值
    losses = []
    # 记录每一次训练后的参数值
    params = []
    for i in range(num_iters):
        # 计算函数值和梯度
        y = f(x)
        y.backward()
        # 更新参数
        optimizer.step()
        # 学习率衰减
        if (i + 1) % lr_decay_epoch == 0:
            optimizer.lr_decay()
        # 清空梯度
        optimizer.zero_grad()
        # 记录损失值和参数值
        losses.append(y.item())
        params.append(x.item())
    # 绘制图像
    plt.figure(figsize=(12, 4))
    plt.subplot(1, 2, 1)
    plot_fn()
    plt.plot(params, losses, 'o-', alpha=0.7)
    plt.title(optimizer.__class__.__name__)
    plt.xlabel('x')
    plt.ylabel('y')
    plt.subplot(1, 2, 2)
    plt.plot(params, 'o-')
    plt.title('Parameter')
    plt.xlabel('Iteration')
    plt.ylabel('Parameter value')
    plt.show()

3.复现CS231经典动画
结合3D动画,用自己的语言,从轨迹、速度等多个角度讲解各个算法优缺点

import torch
import numpy as np
import copy
from matplotlib import pyplot as plt
from matplotlib import animation
from itertools import zip_longest
from matplotlib import cm
 
 
class Op(object):
    def __init__(self):
        pass
 
    def __call__(self, inputs):
        return self.forward(inputs)
 
    # 输入:张量inputs
    # 输出:张量outputs
    def forward(self, inputs):
        # return outputs
        raise NotImplementedError
 
    # 输入:最终输出对outputs的梯度outputs_grads
    # 输出:最终输出对inputs的梯度inputs_grads
    def backward(self, outputs_grads):
        # return inputs_grads
        raise NotImplementedError
 
 
class Optimizer(object):  # 优化器基类
    def __init__(self, init_lr, model):
        """
        优化器类初始化
        """
        # 初始化学习率,用于参数更新的计算
        self.init_lr = init_lr
        # 指定优化器需要优化的模型
        self.model = model
 
    def step(self):
        """
        定义每次迭代如何更新参数
        """
        pass
 
 
class SimpleBatchGD(Optimizer):
    def __init__(self, init_lr, model):
        super(SimpleBatchGD, self).__init__(init_lr=init_lr, model=model)
 
    def step(self):
        # 参数更新
        if isinstance(self.model.params, dict):
            for key in self.model.params.keys():
                self.model.params[key] = self.model.params[key] - self.init_lr * self.model.grads[key]
 
 
class Adagrad(Optimizer):
    def __init__(self, init_lr, model, epsilon):
        """
        Adagrad 优化器初始化
        输入:
            - init_lr: 初始学习率 - model:模型,model.params存储模型参数值  - epsilon:保持数值稳定性而设置的非常小的常数
        """
        super(Adagrad, self).__init__(init_lr=init_lr, model=model)
        self.G = {}
        for key in self.model.params.keys():
            self.G[key] = 0
        self.epsilon = epsilon
 
    def adagrad(self, x, gradient_x, G, init_lr):
        """
        adagrad算法更新参数,G为参数梯度平方的累计值。
        """
        G += gradient_x ** 2
        x -= init_lr / torch.sqrt(G + self.epsilon) * gradient_x
        return x, G
 
    def step(self):
        """
        参数更新
        """
        for key in self.model.params.keys():
            self.model.params[key], self.G[key] = self.adagrad(self.model.params[key],
                                                               self.model.grads[key],
                                                               self.G[key],
                                                               self.init_lr)
 
 
class RMSprop(Optimizer):
    def __init__(self, init_lr, model, beta, epsilon):
        """
        RMSprop优化器初始化
        输入:
            - init_lr:初始学习率
            - model:模型,model.params存储模型参数值
            - beta:衰减率
            - epsilon:保持数值稳定性而设置的常数
        """
        super(RMSprop, self).__init__(init_lr=init_lr, model=model)
        self.G = {}
        for key in self.model.params.keys():
            self.G[key] = 0
        self.beta = beta
        self.epsilon = epsilon
 
    def rmsprop(self, x, gradient_x, G, init_lr):
        """
        rmsprop算法更新参数,G为迭代梯度平方的加权移动平均
        """
        G = self.beta * G + (1 - self.beta) * gradient_x ** 2
        x -= init_lr / torch.sqrt(G + self.epsilon) * gradient_x
        return x, G
 
    def step(self):
        """参数更新"""
        for key in self.model.params.keys():
            self.model.params[key], self.G[key] = self.rmsprop(self.model.params[key],
                                                               self.model.grads[key],
                                                               self.G[key],
                                                               self.init_lr)
 
 
class Momentum(Optimizer):
    def __init__(self, init_lr, model, rho):
        """
        Momentum优化器初始化
        输入:
            - init_lr:初始学习率
            - model:模型,model.params存储模型参数值
            - rho:动量因子
        """
        super(Momentum, self).__init__(init_lr=init_lr, model=model)
        self.delta_x = {}
        for key in self.model.params.keys():
            self.delta_x[key] = 0
        self.rho = rho
 
    def momentum(self, x, gradient_x, delta_x, init_lr):
        """
        momentum算法更新参数,delta_x为梯度的加权移动平均
        """
        delta_x = self.rho * delta_x - init_lr * gradient_x
        x += delta_x
        return x, delta_x
 
    def step(self):
        """参数更新"""
        for key in self.model.params.keys():
            self.model.params[key], self.delta_x[key] = self.momentum(self.model.params[key],
                                                                      self.model.grads[key],
                                                                      self.delta_x[key],
                                                                      self.init_lr)
 
class Nesterov(Optimizer):
    def __init__(self, init_lr, model, rho):
        """
        Nesterov优化器初始化
        输入:
            - init_lr:初始学习率
            - model:模型,model.params存储模型参数值
            - rho:动量因子
        """
        super(Nesterov, self).__init__(init_lr=init_lr, model=model)
        self.delta_x = {}
        for key in self.model.params.keys():
            self.delta_x[key] = 0
        self.rho = rho
 
    def nesterov(self, x, gradient_x, delta_x, init_lr):
        """
        Nesterov算法更新参数,delta_x为梯度的加权移动平均
        """
        delta_x_prev = delta_x
        delta_x = self.rho * delta_x - init_lr * gradient_x
        x += -self.rho * delta_x_prev + (1 + self.rho) * delta_x
        return x, delta_x
 
    def step(self):
        """参数更新"""
        for key in self.model.params.keys():
            self.model.params[key], self.delta_x[key] = self.nesterov(self.model.params[key],
                                                                      self.model.grads[key],
                                                                      self.delta_x[key],
                                                                      self.init_lr)
 
 
class Adam(Optimizer):
    def __init__(self, init_lr, model, beta1, beta2, epsilon):
        """
        Adam优化器初始化
        输入:
            - init_lr:初始学习率
            - model:模型,model.params存储模型参数值
            - beta1, beta2:移动平均的衰减率
            - epsilon:保持数值稳定性而设置的常数
        """
        super(Adam, self).__init__(init_lr=init_lr, model=model)
        self.beta1 = beta1
        self.beta2 = beta2
        self.epsilon = epsilon
        self.M, self.G = {}, {}
        for key in self.model.params.keys():
            self.M[key] = 0
            self.G[key] = 0
        self.t = 1
 
    def adam(self, x, gradient_x, G, M, t, init_lr):
        """
        adam算法更新参数
        输入:
            - x:参数
            - G:梯度平方的加权移动平均
            - M:梯度的加权移动平均
            - t:迭代次数
            - init_lr:初始学习率
        """
        M = self.beta1 * M + (1 - self.beta1) * gradient_x
        G = self.beta2 * G + (1 - self.beta2) * gradient_x ** 2
        M_hat = M / (1 - self.beta1 ** t)
        G_hat = G / (1 - self.beta2 ** t)
        t += 1
        x -= init_lr / torch.sqrt(G_hat + self.epsilon) * M_hat
        return x, G, M, t
 
    def step(self):
        """参数更新"""
        for key in self.model.params.keys():
            self.model.params[key], self.G[key], self.M[key], self.t = self.adam(self.model.params[key],
                                                                                 self.model.grads[key],
                                                                                 self.G[key],
                                                                                 self.M[key],
                                                                                 self.t,
                                                                                 self.init_lr)
 
 
class OptimizedFunction3D(Op):
    def __init__(self):
        super(OptimizedFunction3D, self).__init__()
        self.params = {'x': 0}
        self.grads = {'x': 0}
 
    def forward(self, x):
        self.params['x'] = x
        return - x[0] * x[0] / 2 + x[1] * x[1] / 1  # x[0] ** 2 + x[1] ** 2 + x[1] ** 3 + x[0] * x[1]
 
    def backward(self):
        x = self.params['x']
        gradient1 = - 2 * x[0] / 2
        gradient2 = 2 * x[1] / 1
        grad1 = torch.Tensor([gradient1])
        grad2 = torch.Tensor([gradient2])
        self.grads['x'] = torch.cat([grad1, grad2])
 
 
class Visualization3D(animation.FuncAnimation):
    """    绘制动态图像,可视化参数更新轨迹    """
 
    def __init__(self, *xy_values, z_values, labels=[], colors=[], fig, ax, interval=100, blit=True, **kwargs):
        """
        初始化3d可视化类
        输入:
            xy_values:三维中x,y维度的值
            z_values:三维中z维度的值
            labels:每个参数更新轨迹的标签
            colors:每个轨迹的颜色
            interval:帧之间的延迟(以毫秒为单位)
            blit:是否优化绘图
        """
        self.fig = fig
        self.ax = ax
        self.xy_values = xy_values
        self.z_values = z_values
 
        frames = max(xy_value.shape[0] for xy_value in xy_values)
 
        self.lines = [ax.plot([], [], [], label=label, color=color, lw=2)[0]
                      for _, label, color in zip_longest(xy_values, labels, colors)]
        self.points = [ax.plot([], [], [], color=color, markeredgewidth=1, markeredgecolor='black', marker='o')[0]
                       for _, color in zip_longest(xy_values, colors)]
        # print(self.lines)
        super(Visualization3D, self).__init__(fig, self.animate, init_func=self.init_animation, frames=frames,
                                              interval=interval, blit=blit, **kwargs)
 
    def init_animation(self):
        # 数值初始化
        for line in self.lines:
            line.set_data_3d([], [], [])
        for point in self.points:
            point.set_data_3d([], [], [])
        return self.points + self.lines
 
    def animate(self, i):
        # 将x,y,z三个数据传入,绘制三维图像
        for line, xy_value, z_value in zip(self.lines, self.xy_values, self.z_values):
            line.set_data_3d(xy_value[:i, 0], xy_value[:i, 1], z_value[:i])
        for point, xy_value, z_value in zip(self.points, self.xy_values, self.z_values):
            point.set_data_3d(xy_value[i, 0], xy_value[i, 1], z_value[i])
        return self.points + self.lines
 
 
def train_f(model, optimizer, x_init, epoch):
    x = x_init
    all_x = []
    losses = []
    for i in range(epoch):
        all_x.append(copy.deepcopy(x.numpy()))  # 浅拷贝 改为 深拷贝, 否则List的原值会被改变。 Edit by David 2022.12.4.
        loss = model(x)
        losses.append(loss)
        model.backward()
        optimizer.step()
        x = model.params['x']
    return torch.Tensor(np.array(all_x)), losses
 
 
# 构建5个模型,分别配备不同的优化器
model1 = OptimizedFunction3D()
opt_gd = SimpleBatchGD(init_lr=0.05, model=model1)
 
model2 = OptimizedFunction3D()
opt_adagrad = Adagrad(init_lr=0.05, model=model2, epsilon=1e-7)
 
model3 = OptimizedFunction3D()
opt_rmsprop = RMSprop(init_lr=0.05, model=model3, beta=0.9, epsilon=1e-7)
 
model4 = OptimizedFunction3D()
opt_momentum = Momentum(init_lr=0.05, model=model4, rho=0.9)
 
model5 = OptimizedFunction3D()
opt_adam = Adam(init_lr=0.05, model=model5, beta1=0.9, beta2=0.99, epsilon=1e-7)
 
model6 = OptimizedFunction3D()
opt_Nesterov = Nesterov(init_lr=0.1, model=model6, rho=0.9)
 
models = [model1, model2, model3, model4, model5, model6]
opts = [opt_gd, opt_adagrad, opt_rmsprop, opt_momentum, opt_adam, opt_Nesterov]
 
x_all_opts = []
z_all_opts = []
 
# 使用不同优化器训练
 
for model, opt in zip(models, opts):
    x_init = torch.FloatTensor([0.00001, 0.5])
    x_one_opt, z_one_opt = train_f(model, opt, x_init, 100)  # epoch
    # 保存参数值
    x_all_opts.append(x_one_opt.numpy())
    z_all_opts.append(np.squeeze(z_one_opt))
 
# 使用numpy.meshgrid生成x1,x2矩阵,矩阵的每一行为[-3, 3],以0.1为间隔的数值
x1 = np.arange(-1, 2, 0.01)
x2 = np.arange(-1, 1, 0.05)
x1, x2 = np.meshgrid(x1, x2)
init_x = torch.Tensor(np.array([x1, x2]))
 
model = OptimizedFunction3D()
 
# 绘制 f_3d函数 的 三维图像
fig = plt.figure()
ax = plt.axes(projection='3d')
X = init_x[0].numpy()
Y = init_x[1].numpy()
Z = model(init_x).numpy()  # 改为 model(init_x).numpy() David 2022.12.4
surf = ax.plot_surface(X, Y, Z, edgecolor='grey', cmap=cm.coolwarm)
# fig.colorbar(surf, shrink=0.5, aspect=1)
ax.set_zlim(-3, 2)
ax.set_xlabel('x1')
ax.set_ylabel('x2')
ax.set_zlabel('f(x1,x2)')
 
labels = ['SGD', 'AdaGrad', 'RMSprop', 'Momentum', 'Adam', 'Nesterov']
colors = ['#8B0000', '#0000FF', '#000000', '#008B00', '#FF0000']
 
 
animator = Visualization3D(*x_all_opts, z_values=z_all_opts, labels=labels, colors=colors, fig=fig, ax=ax)
ax.legend(loc='upper right')
 
plt.show()
# animator.save('teaser' + '.gif', writer='imagemagick',fps=10) 

Animations that may help your intuitions about the learning process dynamics. 

Left: Contours of a loss surface and time evolution of different optimization algorithms. Notice the "overshooting" behavior of momentum-based methods, which make the optimization look like a ball rolling down the hill. 

Right: A visualization of a saddle point in the optimization landscape, where the curvature along different dimension has different signs (one dimension curves up and another down). Notice that SGD has a very hard time breaking symmetry and gets stuck on the top. Conversely, algorithms such as RMSprop will see very low gradients in the saddle direction. Due to the denominator term in the RMSprop update, this will increase the effective learning rate along this direction, helping RMSProp proceed. 
 

  • 20
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值