型态操作
# random np array - shape = 1,2,2
test = np.random.rand(1,2,2)
# 转成 torch 可以操作的 Tensor
nn_v = torch.from_numpy(test)
# 转成 tf 可以操作的 Tensor
tf_v = tf.convert_to_tensor(test)
# 从 torch tensor 转回 np
nn_v.numpy()
# 从 tf tensor 转回 np
tf_v.numpy()
在 pytorch 里你时常会看到 XXX.to(device) 这样的操作,其中 device = "cpu" 或者是 "cuda:0"
model.to(device)
这句话的意思是 从一开始读取数据的地方 "复制一份到指定 device" 上,放在哪个 device 上运算的时候就是在那边运行,在训练 model 的时候通常都会指定到 gpu 上,但在 Inference 的时候想转回使用 numpy() 这类型数据的话,就只能在 cpu 上操作,如果 torch tensor 还在 gpu 上的话,是不能够像上面一样直接透过 .numpy() 转回来的。
这时候只需要把数据从 device 上 detach() 再指定回 cpu 就可以了
nn_v.detach().cpu().numpy()
拓展维度,你可以感受到 tf 的操作跟 np 一模一样
# shape(1,2,2) -> (1,1,2,2)
# 在 torch 里头 axis 都写作 dim
nn_v.unsqueeze(dim=0)
tf.expand_dims(tf_v,axis=0)
np.expand_dims(test,axis=0)
Transpose 也是
# 意思是 0 跟 1 维互换
nn_v.transpose(0,1)
# 要把交换的维度明确地写上去
tf.transpose(tf_v,(1,0,2))
np.transpose(test,(1,0,2))
Expand (broadcast_to)
# shape(1,2,2) -> (3,2,2)
# torch 允许用 -1 来表示照原本的 shape
nn_v.expand(3,-1,-1)
# tf 要写出来,这点 np 也是一样
tf.broadcast_to(tf_v,[3,2,2])
np.broadcast_to(test,[3,2,2])
Concatenate 的话变成三个的操作方式都一样
torch.cat([nn_v,nn_target],dim=0)
tf.concat([tf_v,tf_target],axis=0)
np.concatenate([test,target],axis=0)
接着我们要证明一下 AutoVC 里头用的 layers 可以在 TF 上得到几乎一样的结果
AutoVC 主要用的 layers 只有 4 种:
验证方法 -> 让双方的 weights init 为 1, bias init 为 0
LinearNorm = Dense
# In Pytorch
class LinearNorm(torch.nn.Module):
def __init__(self, in_dim, out_dim, bias=True, w_init_gain='linear'):
super(LinearNorm, self).__init__()
self.linear_layer = torch.nn.Linear(in_dim, out_dim, bias=bias)
torch.nn.init.constant_(self.linear_layer.weight.data, 1)
torch.nn.init.constant_(self.linear_layer.bias.data, 0)
def forward(self, x):
return self.linear_layer(x)
结果
ConvNorm = Conv1d 加上 transpose,以一组三维数据(EX:shape(1,2,2)) 而言 pytorch 的操作维度是 axis = 1,TF 是 axis = 2
class ConvNorm(torch.nn.Module):
def __init__(self, in_channels, out_channels, kernel_size=5, stride=1,
padding=2, dilation=1, bias=True, w_init_gain='linear'):
super(ConvNorm, self).__init__()
if padding is None:
assert(kernel_size % 2 == 1)
padding = int(dilation * (kernel_size - 1) / 2)
self.conv = torch.nn.Conv1d(in_channels, out_channels,
kernel_size=kernel_size, stride=stride,
padding=padding, dilation=dilation,
bias=bias)
torch.nn.init.constant_(self.conv.weight.data, 1)
torch.nn.init.constant_(self.conv.bias.data, 0)
def forward(self, signal):
conv_signal = self.conv(signal)
return conv_signal
结果在注意 padding=same 的情况下可以一样,想了解他们 padding 的话可以看这篇,不过我们这次做的 padding 都会等於 same。
LSTM 这边的问题是它们的 Bidirectional 初始化方式不同,我找了半天也没找到初始化的方法 (也许是无法更改),不过它们是做一样的事,最後为了更方便检查我把它们的值加起来。
最後的 BatchNormalization 花了我不少时间研究,後来我发现他们在 "训练" 的时候应是一样的东西,不过我们先看一下直接呼叫会发生什麽事。
torch.nn.BatchNorm1d 的 init 方式就是 tf 预设的 init 方式
也有试过把 bc 变成 eval() 的模式,得到的结果一样不同,但是查了很久还是没有结果 QAQ。
最後乾脆自己写一个 BatchNormalization 让它可以在 TF 上得到跟 Pytorch 几乎一样的结果
class tfBatchNormalize1d(tf.keras.layers.Layer):
def __init__(self, num_features,size, epsilon,name):
super(tfBatchNormalize1d, self).__init__()
one_init = tf.keras.initializers.Ones()
zero_init = tf.keras.initializers.Zeros()
v_o = one_init(shape=(size,))
v_z = zero_init(shape=(size,))
self.gamma = tf.Variable(initial_value=v_o, name =f"gamma_{name}")
self.beta = tf.Variable(initial_value=v_z, name =f"beta_{name}")
self.num_features = num_features
self.epsilon = epsilon
def call(self, x):
mean, variance = tf.nn.moments(x, [0, 2])
n = tf.cast(tf.size(x)/tf.shape(x)[1],tf.float32)
## 这里我有点不太确定他们怎麽处理超出的情况
tmp = None
for i in range(self.num_features):
if self.num_features+i > self.num_features:
break
res = (x - mean[None, i:self.num_features+i, None]) / (tf.sqrt(variance[None, i:self.num_features+i, None] + self.epsilon))
if tmp == None:
tmp = res
else:
tf.concat([tmp,res],axis=0)
x = tmp
return x*self.gamma + self.beta
结果就可以得到跟 Pytorch 差不多的结果,至少小数点後 4 位会一样
AutoVC 用了以下两种 Loss Function
import torch.nn.functional as F
F.mse_loss.(y_true, y_predict)
# 就是 MAE
F.l1_loss(y_true, y_predict)
在 TF 里等价於
def mse_loss(y_true, y_pred):
err = tf.keras.losses.mean_squared_error(y_true, y_pred)
return tf.reduce_mean(err)
def l1_loss(y_true, y_pred):
err = tf.keras.losses.mean_absolute_error(y_true, y_pred)
return tf.reduce_mean(err)
在 pytorch 里头更新 Gradient 的方法是
g_optimizer = torch.optim.Adam(model.parameters(), 0.0001)
for i in range(num_iters):
...
...
...
g_loss = l1_loss + mse_loss
g_optimizer.zero_grad()
g_loss.backward()
g_optimizer.step()
在 TF 里 optimizer 是固定 zero_grad 的
g_optimizer = tf.keras.optimizers.Adam(0.0001)
for i in range(num_iters):
...
...
with tf.GradientTape() as autovc_tape:
...
...
g_loss = l1_loss + mse_loss
gradients_of_autovc = autovc_tape.gradient(g_loss,model.trainable_variables)
g_optimizer.apply_gradients(zip(gradients_of_autovc,model.trainable_variables))
到此我们就可以开始着手重写了,检查 layer 的方法也适用在其他不同框架的操作喔!
<<: D8 新增使用google account 登入的功能
>>: Day 14 ( 中级 ) 平衡灯 ( 旋转感测 )
无论是大型组织还是小型组织,无论是正常运营还是糟糕的运营,都可以通过RBAC提高授予特权(授予角色)...
现在我们可以用各种方法将资料读取出来,不过通常读取後还要将资料做一些转换才适用,举个例子像是 boo...
首先,先来看看一个简单、特殊的创造物件的模式。 In software engineering, t...
前言 来到了困住我好几天的储存结构,希望可以让大家很快地看明白,假如看不懂可以再参考大话资料结构的7...
前情提要 前几编文章里,大家已经知道如何利用 Vaadin-on-Kotlin 简单快速的新增、查询...