1. 应用:计算机图片识别、自然语言处理、视频分析、药物发现、AlphaGo
2. 如何运作:
神经网络是由一连串的神经元组成,每一层神经层里存在很多的神经元,这些神经元也是神经网络识别事物的关键,当输入值是图片时,图片被转换成像素值矩阵;卷积是指神经网络不再对每个像素的输入信息做处理,而是对图片上每一小块像素区域的处理,这种做法加强了图片信息的连续性,使得神经网络能看到图形,而非一个点,同时也加深了神经网络对图片的理解。
具体地,CNN有一个批量过滤器,持续不断地在图片上滚动收集图片里的信息,每一次收集的信息只是一小块像素区域,然后将收集来的信息进行整理,整理后的信息有一些实际上的呈现,比如说,这时的神经网络能看到一些边缘的图片信息,然后再以同样的步骤,用类似的批量过滤器扫过产生的这些边缘信息,神经网络从而总结出更高层的信息结构。例如,边缘信息能画出眼睛、鼻子等,再经过一次过滤,脸部的信息也就从眼睛、鼻子等信息中被总结出来,最后,把这些信息套入几层普通的全连接神经网络进行分类,由此可得到输入的图片被分为哪一类的结果。
3. 池化(pooling):在每一次卷积时,神经层可能会无意地丢失信息,池化可以解决这个问题,在卷积时,不压缩长宽,尽量的保留更多的信息, 压缩的工作交给池化,可以有效地提高准确性。
4. CNN搭建结构(popular):image->convolution->max pooling->convolution->max pooling->fully connected->fully connected->classifier(可以不用池化)
5. MNIST —— 利用 CNN 进行手写数字识别
DOWNLOAD_MNIST = False # 初次下载数据时改为True
train_data = torchvision.datasets.MNIST(
root='./mnist',
train=True, # 如果为True则为训练集,如果为False则为测试集
transform=torchvision.transforms.ToTensor(), # 将图片转化成取值[0,1]的Tensor用于网络处理
download=DOWNLOAD_MNIST
)
# training data
train_loader = Data.DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
# test data
test_data = torchvision.datasets.MNIST(root='./mnist/', train=False)
# test_x = Variable(torch.unsqueeze(test_data.test_data, dim=1), volatile=True).type(torch.FloatTensor)[:2000]/255.
# UserWarning: volatile was removed and now has no effect. Use `with torch.no_grad():
# 删掉 volatile = True
test_x = Variable(torch.unsqueeze(test_data.test_data, dim=1)).type(torch.FloatTensor)[:2000]/255.
# test.data是[0:255],现在还没有压缩,所以要/255.手动压缩
test_y = test_data.test_labels[:2000]
【WinError 10060】参考:https://zhuanlan.zhihu.com/p/116482019
# 建立CNN
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
# 搭建卷积网络的一层conv1
self.conv1 = nn.Sequential(
# 卷积层,Conv2d是过滤器(卷积核)
nn.Conv2d( # 图片信息(1,28,28)维度(高度)——1,长*宽——28*28
in_channels=1, # 灰度图只有 1 层,如果为彩图,则为3(r、g、b)
out_channels=16, # filter 的个数(输出的高度),16 个 filter 同时在一个区域提取出16个特征
kernel_size=5, # filter 是5*5的
stride=1, # 跳度/步长,扫描下一次时只跳一个 pixel (像素)
padding=2, # 补全边缘像素点,在图谱外围补两圈 0,获得更多边缘像素点信息
# if stride = 1, padding = (kernel_size-1)/2 = (5-1)/2
# padding大小就是卷积核中心元素到边缘的长度
),
# ——> 图片信息变成(16,28,28)
nn.ReLU(), # 非线性激活层
# ——> 图片信息还是(16,28,28)
nn.MaxPool2d(kernel_size=2), # 池化层
# ——> 图片信息变成(16,14,14)
)
# ——> 输入的图片信息为(16,14,14)
self.conv2 = nn.Sequential(
nn.Conv2d(16, 32, 5, 1, 2), # ——> 图片信息变成(32,14,14)
nn.ReLU(), # ——> 图片信息为(32,14,14)
nn.MaxPool2d(2) # ——> 图片信息变为(32,7,7)
)
self.out = nn.Linear(32 * 7 * 7, 10) # 得到一个三维数据
# 将三维数据展平成二维数据
# forward() 的参数 x 是包括一个 batch 的数据,形状是 batch*1*28*28 的四维数据
# batch 是样本数,1 是通道数,28*28 是图片分辨率
# 一个 batch 同时运算,矩阵运算可以充分并行,相比 for 循环加速巨大
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x) # (batch, 32, 7, 7)
x = x.view(x.size(0), -1) # 展平的操作,(batch, 32 * 7 * 7)
output = self.out(x)
return output
cnn = CNN()
print(cnn) # net architecture
# 输出 CNN 结构
CNN(
(conv1): Sequential(
(0): Conv2d(1, 16, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(1): ReLU()
(2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(conv2): Sequential(
(0): Conv2d(16, 32, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(1): ReLU()
(2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(out): Linear(in_features=1568, out_features=10, bias=True)
)
Process finished with exit code 0
# training and testing
for epoch in range(EPOCH):
for step, (x, y) in enumerate(train_loader): # give batch data, normalize x when
output = cnn(x) # cnn output
loss = loss_func(output, y) # cross entropy loss
optimizer.zero_grad() # clear gradients for this training step
loss.backward() # backpropagation, compute gradients
optimizer.step()
if step % 50 == 0:
test_output = cnn(test_x)
pred_y = torch.max(test_output, 1)[1].data.squeeze()
accuracy = ((pred_y == test_y).sum().item()) / (test_y.size()[0])
# accuracy = torch.floor_divide(sum(pred_y == test_y), test_y.size(0))
print('Epoch: ', epoch, '| train loss:%.4f' % loss.item(), '| test accuracy: %。2f' % accuracy)
# print 10 predictions from test data
test_output = cnn(test_x[:10])
# pred_y = torch.max(test_output, 1)[1].data.squeeze()
pred_y = torch.max(test_output, 1)[1].data.numpy()
print(pred_y, 'prediction number')
print(test_y[:10].numpy(), 'real number')
1. NN 与 RNN:普通的神经网络无法了解数据间的关联,RNN 是 generation model,它不仅考虑前一时刻的输入,而且赋予了网络对前面的内容的一种“记忆功能”,一个序列当前的输出与前面的输出也有关。具体地,网络会对前面的信息进行记忆并应用于当前输出的计算中,即隐藏层之间的节点不再无连接而是有连接的,并且隐藏层的输入不仅包括输入层的输出还包括上一时刻隐藏层的输出。
2. 长短期记忆 LSTM(Long Short-Term Memory):RNN 形式之一,RNN 存在梯度消失、梯度弥散、梯度爆炸等问题,因此,普通 RNN 没有办法回忆起久远记忆。LSTM 则可以解决这个问题,它比普通的 RNN 多出了三个控制器 —— 输入控制、输出控制、忘记控制。
具体的,参考https://blog.csdn.net/qq_32241189/article/details/80461635
3. RNN —— 分类 —— MNIST
time_step,时间步长,也可以理解为展开的 RNN 或者 LSTM 的 block 的个数。举个例子,如果是用 RNN 来做预测,输入1000条数据进行训练,这1000条数据分为了10个 batch,那么每个 batch 的 batchsize 就是100,然后我们如果是用前四个数值来预测第五个值的话,输入的数据 shape 就是(100,4),这四个数值在时间上是连续的,所以 time_step就是4。
(time_step解释来源:https://www.jianshu.com/p/635c4ef1bdd7)
# RNN 两个特殊的超参数
TIME_STEP = 28 # run time step / image height
# time_step 可以相当于一次输入过程中的多个时间序列输入
INPUT_SIZE =28 # run input size / image width
# input_size 每一个时间序列输入多少个数据点
# MNIST 图片是 28*28 的长宽,每 28 步当中的一步,就读取图片当中的一行 28 个像素点
# 定义 RNN
class RNN(nn.Module):
def __init__(self):
super(RNN, self).__init__()
# RNN 体系
self.rnn = nn.LSTM( # nn.RNN 较难收敛,train 不动
input_size=INPUT_SIZE,
hidden_size=64, # 表示隐藏层节点个数
num_layers=1, # 表示隐藏层的层数
# 隐藏层层数越多,准确率越高,但计算速度越慢
batch_first=True, # 输入数据以(batch, time_step, input)形式设置为True
# 输入数据以(time_step, batch, input)形式设置为False
)
self.out = nn.Linear(64, 10) # 输出接入到fully connected
def forward(self, x):
r_out, (h_n, h_c) = self.rnn(x, None) # x 的shape (batch, time_step, input_size)
# RNN 输出首先有output,其次有hidden state(hidden state n,hidden state c)
# hidden state n 是分线程的hidden state,hidden state c 是主线程的hidden state
# 具体地,参考莫烦 LSTM :https://mofanpy.com/tutorials/machine-learning/torch/intro-LSTM/
# input 产生 output + hidden state
# 下一次 input 要 + hidden state 产生新的 output + hidden state
# 即每一时刻的输入还包括上一时刻的输出
out = self.out(r_out[:, -1, :]) # (batch, time_step, input_size)
# -1 是选取最后一个时刻的 output
return out
rnn = RNN()
print(rnn) # net architecture
# 输出 RNN 结构
RNN(
(rnn): LSTM(28, 64, batch_first=True)
(out): Linear(in_features=64, out_features=10, bias=True)
)
Process finished with exit code 0
optimizer = torch.optim.Adam(rnn.parameters(), lr=LR) # optimize all rnn parameters
loss_func = nn.CrossEntropyLoss() # the target label is not one-hotted
# training and testing
for epoch in range(EPOCH):
for step, (x, y) in enumerate(train_loader): # give batch data, normalize x when
x = x.view(-1, 28, 28) # reshape x to (batch, time_step, input_size)
# batch 设为‘-1’是因为 view 函数不能缺少参数,且这个位置的实际值可以根据后面两个参数,计算机自己推导出来
output = rnn(x)
loss = loss_func(output, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if step % 50 == 0:
test_output = rnn(test_x.view(-1, 28, 28)) # (samples, time_step, input_size)
pred_y = torch.max(test_output, 1)[1].data.numpy().squeeze()
accuracy = sum(pred_y == test_y) / test_y.size
# accuracy = torch.floor_divide(sum(pred_y == test_y), test_y.size(0))
print('Epoch: ', epoch, '| train loss:%.4f' % loss.item(), '| test accuracy: %.2f' % accuracy)
# print 10 predictions from test data
test_output = rnn(test_x[:10].view(-1, 28, 28))
pred_y = torch.max(test_output, 1)[1].data.numpy().squeeze()
print(pred_y, 'prediction number')
print(test_y[:10], 'real number')
4. RNN —— 回归
用 RNN 来及时预测时间序列 —— 用 sin 曲线预测 cos 曲线
TIME_STEP = 10 # run time step
INPUT_SIZE = 1 # run input size
# sin 曲线预测 cos 曲线,在一个时间点上,input_size 和 output_size 都是 1
LR = 0.01
class RNN(nn.Module):
def __init__(self):
super(RNN, self).__init__()
self.rnn = nn.RNN(
input_size=INPUT_SIZE,
hidden_size=32, # hidden_state有 32 个神经元
num_layers=1,
batch_first=True,
)
self.out = nn.Linear(32, 1) # 全连接层,32 个输入,1 个输出
def forward(self, x, h_state):
# RNN 的隐藏层接收两个输入,一个当前时刻的 input_x,一个前一时刻的 h_state
# x (batch, time_step, input_size)
# h_state (n_layers, batch, hidden_size)
# r_out (batch, time_step, hidden_size)
r_out, h_state = self.rnn(x, h_state)
outs = [] # 保存所有时间点的预测值
# r_out 是经过hidden layer处理的结果,size为32,outs是nn.Linear对r_out调整维度后输出的结果,size为1
for time_step in range(r_out.size(1)):
# size(1)是r_out第二个维度的大小,即time_step=1
# 对每一个时间点计算 output
outs.append(self.out(r_out[:, time_step, :]))
return torch.stack(outs, dim=1), h_state # stack()把 outs 的列表形式转换成 tensor 形式
optimizer = torch.optim.Adam(rnn.parameters(), lr=LR)
loss_func = nn.MSELoss()
h_state = None # 把第一次输入的 hidden_state 设置为 None,会自动生成全 0 的 hidden_state
for step in range(60):
# data
start, end = step * np.pi, (step + 1) * np.pi # time steps,截取一段距离
# use sin predicts cos
steps = np.linspace(start, end, TIME_STEP, dtype=np.float32)
# 将 time steps 按照 TIME_STEP 截取数据,把数据放进 sin 和 cos 中
x_np = np.sin(steps) # float32 for converting torch FloatTensor
y_np = np.cos(steps)
# 把一维的 x 、 y 转换成三维的
x = torch.from_numpy(x_np[np.newaxis, :, np.newaxis]) # shape (batch, time_step, input_size)
# np.newaxis : 增加一个维度
y = torch.from_numpy(y_np[np.newaxis, :, np.newaxis])
prediction, h_state = rnn(x, h_state)
h_state = h_state.data # !!更新下一次输入的 hidden_state
loss = loss_func(prediction, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
Encode 和 Decode 将数据进行压缩和解压,x 经过 Encode 和 Decode 变成另一个 x ,x 和另一个 x 进行对比,即 x 自身进行学习, 不需要 y。
Encoder 可以得到源数据的精髓(给特征属性降维)。
# 定义 AutoEncoder
class AutoEncoder(nn.Module):
def __init__(self):
super(AutoEncoder, self).__init__()
self.encoder = nn.Sequential(
nn.Linear(28*28, 128), # 图片数据是 28 个像素点 * 28 个像素点,放到 128 的隐藏层中
nn.Tanh(),
nn.Linear(128, 64), # 把 128 压缩成 64 个
nn.Tanh(),
nn.Linear(64, 12),
nn.Tanh(),
nn.Linear(12, 3), # 最后输出 3 个特征向量,可以在三维空间可视化
)
self.decoder = nn.Sequential(
nn.Linear(3, 12),
nn.Tanh(),
nn.Linear(12, 64),
nn.Tanh(),
nn.Linear(64, 128),
nn.Tanh(),
nn.Linear(128, 28*28),
nn.Sigmoid(), # train_data的范围是在(0,1),Sigmoid()可以把输出值转换成(0,1)
)
def forward(self, x):
encoded = self.encoder(x)
decoded = self.decoder(encoded)
return encoded, decoded
# 训练
autoencoder = AutoEncoder()
optimizer = torch.optim.Adam(autoencoder.parameters(), lr=LR)
loss_func = nn.MSELoss()
for epoch in range(EPOCH):
for step, (x, y) in enumerate(train_loader):
b_x = x.view(-1, 28*28) # batch x, shape (batch, 28*28)
b_y = x.view(-1, 28*28) # batch y, shape (batch, 28*28)
# 不需要用到 y ,实际上用的还是 x
b_label = y # batch label 用于判断结果
encoded, decoded = autoencoder(b_x)
# BackPropagation
loss = loss_func(decoded, b_y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
DQN = Neural Network + Q-learning
Q-learning 算法详解参考:https://blog.csdn.net/qq_30615903/article/details/80739243
Q 值是累计奖励,即执行当前动作后未来一系列状态总共可以获得多少奖励。
DQN —— 支持因素(提升手段):
DQN —— 小车立杆实例
# Hyper Parameters
BATCH_SIZE = 32
LR = 0.01
EPSILON = 0.9 # greedy policy
GAMMA = 0.9 # reward discount
TARGET_REPLACE_ITER = 100 # target update frequency
MEMORY_CAPACITY = 5000
# 导入 openai gym 模拟场所,CartPole —— 杆子立起来实验
env = gym.make('CartPole-v0')
env = env.unwrapped
# 可以观测到环境中小车的动作等
# 环境状态有 4 个:小车位置、小车速率、杆子角度、杆子角速度
N_ACTIONS = env.action_space.n
N_STATES = env.observation_space.shape[0]
# 定义神经网络
class Net(nn.Module):
def __init__(self, ):
super(Net, self).__init__()
self.fc1 = nn.Linear(N_STATES, 10) # 输入观测值,观测现在的 state
# 使用正态分布(normal_distribution)随机生成初始参数值,会有更好的效果
self.fc1.weight.data.normal_(0, 0.1) # initialization
self.out = nn.Linear(10, N_ACTIONS) # 输出每个 action 的价值
self.out.weight.data.normal_(0, 0.1) # initialization
def forward(self, x):
x = self.fc1(x)
x = F.relu(x)
actions_value = self.out(x)
return actions_value
# 定义 DQN 机器人跟环境的互动
class DQN(object):
def __init__(self):
# 有两个神经网络 ———— q-target 和 q-eval
self.eval_net, self.target_net = Net(), Net()
# eval_net 的参数要转换到 target_net 的参数,使得 target_net 有一个延迟的更新效果
self.learn_step_counter = 0 # for target updating
self.memory_counter = 0 # for storing memory
# memory 记忆库采取覆盖更新,每次覆盖最老的数据,用 counter 来定位最老数据
self.memory = np.zeros((MEMORY_CAPACITY, N_STATES * 2 + 2)) # initialization memory 全 0
# 每次存储两个 state(当前 state 和 next state)数值 + action + reward
self.optimizer = torch.optim.Adam(self.eval_net.parameters(), lr=LR)
self.loss_func = nn.MSELoss()
# 机器人跟环境互动,接收环境中观测值 x ,然后采取动作
def choose_action(self, x):
x = torch.unsqueeze(torch.FloatTensor(x), 0)
# 随机选取动作的概率(EPSILON):
if np.random.uniform() < EPSILON: # greedy 当随机数小于 epsilon 时(即90%的情况),采取 value 更高的 action
# 90% 概率按以往经验选最优解,10% 概率去探索其他解是否可能更好
actions_value = self.eval_net.forward(x)
action = torch.max(actions_value, 1)[1].data.numpy()[0]
else: # random 选取 action
action = np.random.randint(0, N_ACTIONS)
return action
# 记忆库,存储之前的记忆
def store_transition(self, s, a, r, s_):
transition = np.hstack((s, [a, r], s_))
# replace the old memory with new memory
index = self.memory_counter % MEMORY_CAPACITY
# 如果 memory_counter 超过了记忆库容量上限,就重新开始 index ,即覆盖老的记忆
self.memory[index, :] = transition
self.memory_counter += 1
# 从记忆库中提取记忆进行学习
# 随机从记忆库中提取记忆进行学习比按时间顺序学习更有效率
def learn(self):
# target net update
if self.learn_step_counter % TARGET_REPLACE_ITER == 0:
# eval_net 每一次learn的时候都在更新,target_net 是隔 TARGET_REPLACE_ITER 步更新
self.target_net.load_state_dict(self.eval_net.state_dict())
# 批训练
# eval_net 每一步都更新
sample_index = np.random.choice(MEMORY_CAPACITY, BATCH_SIZE)
b_memory = self.memory[sample_index, :]
b_s = torch.FloatTensor(b_memory[:, :N_STATES])
b_a = torch.LongTensor(b_memory[:, N_STATES:N_STATES + 1].astype(int))
b_r = torch.FloatTensor(b_memory[:, N_STATES + 1:N_STATES + 2])
b_s_ = torch.FloatTensor(b_memory[:, -N_STATES:])
# 学习过程
q_eval = self.eval_net(b_s).gather(1, b_a)
# 输入现在的状态,根据输出的 action_value,选择 action,并返回该 action 的 value
q_next = self.target_net(b_s_).detach()
# detach() :
# (1)返回一个新的从当前图中分离的Variable。
# (2)返回的 Variable 不会梯度更新。
# (3)被detach 的Variable volatile=True, detach出来的volatile也为True。
# (4)返回的Variable和被detach的Variable指向同一个tensor。
# 即在每一步计算时不进行反向传播更新 target,target 有自己预设的更新阶段
q_target = b_r + GAMMA * q_next.max(1)[0]
# 下一步 q 的value * γ + 之前获得的 reward,对未来价值的递减
loss = self.loss_func(q_eval, q_target)
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
# 训练
for i_episode in range(400):
s = env.reset() # 跟环境互动,得到环境的反馈
while True:
env.render() # 环境渲染
a = dqn.choose_action(s) # 根据现在的 state 来采取 action
# take action
s_, r, done, info = env.step(a) # 环境根据采取的 action 给我另外的反馈
# modify the reward
# 杆子和车越靠近中间 reward 值越大,越偏离中间 reward 值越小
x, x_dot, theta, theta_dot = s_
r1 = (env.x_threshold - abs(x)) / env.x_threshold - 0.8
r2 = (env.theta_threshold_radians - abs(theta)) / env.theta_threshold_radians - 0.5
r = r1 + r2
dqn.store_transition(s, a, r, s_) # 存储现在的反馈
if dqn.memory_counter > MEMORY_CAPACITY:
dqn.learn()
if done:
break
s = s # 现在的状态变成下一回合的初始状态
利用随机数“凭空捏造”结果
GAN —— 实例(光滑曲线代表著名画家的画作)
Generator 是新手画家,Discriminator 是新手鉴赏家,你是高级鉴赏家。你将著名画家的品和新手画家的作品都给新手鉴赏家评定,并告诉新手鉴赏家哪些是新手画家画的,哪些是著名画家画的,新手鉴赏家就慢慢学习怎么区分新手画家和著名画家的画,但是新手画家和新手鉴赏家是好朋友,新手鉴赏家会告诉新手画家要怎么样画得更像著名画家,新手画家就能将自己的突然来的灵感 (random noise) 画得更像著名画家。—— 莫烦(https://mofanpy.com/tutorials/machine-learning/torch/GAN/)
# Hyper Parameters
BATCH_SIZE = 64
LR_G = 0.0001 # learning rate for generator (生成器:新手画家)
LR_D = 0.0001 # learning rate for discriminator (判别器:新手鉴赏家)
N_IDEAS = 5 # think of this as number of ideas for generating an art work(Gene)
ART_COMPONENTS = 15 # it could be total point G can draw in the canvas
PAINT_POINTS = np.vstack([np.linspace(-1, 1, ART_COMPONENTS) for _ in range(BATCH_SIZE)])
# x 从(-1,1)的线段上有 15 个点,每一个点产生一个一元二次函数
# 生成一批著名画家的画
def artist_works():
a = np.random.uniform(1, 2, size=BATCH_SIZE)[:, np.newaxis]
paintings = a * np.power(PAINT_POINTS, 2) + (a-1)
paintings = torch.from_numpy(paintings).float()
return paintings
# 生成器,学习著名画家
G = nn.Sequential(
nn.Linear(N_IDEAS, 128), # N_IDEAS 是随机灵感
nn.ReLU(),
nn.Linear(128, ART_COMPONENTS)
)
# 判别器
D = nn.Sequential(
nn.Linear(ART_COMPONENTS, 128),
nn.ReLU(),
nn.Linear(128, 1), # 判别接收到的画是新手画家的画还是著名画家的画
nn.Sigmoid(), # sigmoid()将结果转换成百分比
)
# 学习
for step in range(10000):
artist_paintings = artist_works()
G_ideas = torch.randn(BATCH_SIZE, N_IDEAS) # 新手画家的 idea 是随机产生的
G_paintings = G(G_ideas)
prob_artist0 = D(artist_paintings)
prob_artist1 = D(G_paintings)
# 反向传回误差
D_loss = - torch.mean(torch.log(prob_artist0) + torch.log(1 - prob_artist1))
# discriminator 的目标就是最大化 log(d) + log(1-d),另外,pytorch 只有最小值,所以前面加了“-”
# 尽量增加著名画家的画概率
G_loss = torch.mean(torch.log(1 - prob_artist1))
# 增加新手画家是著名画家的画的概率
# discriminator 的目标就是最大化 log(d) + log(1-d)
opt_D.zero_grad()
D_loss.backward(retain_graph=True)
# backpropagation以后要保留之前网络当中的参数,然后给下一次在这一批的另外一个propagation(G_loss)
opt_D.step()
opt_G.zero_grad()
G_loss.backward()
opt_G.step()