【Day14】 Pytorch 转 Tensorflow

Part1 - Function

  • 我们从几个常用的操作开始吧!

型态操作

# 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)

Part2 - Layers

  • 接着我们要证明一下 AutoVC 里头用的 layers 可以在 TF 上得到几乎一样的结果

  • AutoVC 主要用的 layers 只有 4 种:

    1. LinearNorm -> 自定义权重初始化的 nn.Linear
    2. ConvNorm -> 自定义权重初始化的 nn.Conv1d
    3. LSTM -> nn.LSTM
    4. BatchNormalize -> nn.BatchNorm1d
  • 验证方法 -> 让双方的 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 位会一样

Part3 - Loss

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)
    

Part4 - Gradient

在 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 的方法也适用在其他不同框架的操作喔!

/images/emoticon/emoticon09.gif/images/emoticon/emoticon13.gif/images/emoticon/emoticon14.gif/images/emoticon/emoticon22.gif/images/emoticon/emoticon28.gif


<<:  D8 新增使用google account 登入的功能

>>:  Day 14 ( 中级 ) 平衡灯 ( 旋转感测 )

角色的访问控制(RBAC)

无论是大型组织还是小型组织,无论是正常运营还是糟糕的运营,都可以通过RBAC提高授予特权(授予角色)...

Eloquent ORM - Model 资料转换

现在我们可以用各种方法将资料读取出来,不过通常读取後还要将资料做一些转换才适用,举个例子像是 boo...

Singleton 单例模式

首先,先来看看一个简单、特殊的创造物件的模式。 In software engineering, t...

图的储存结构 - 十字链结串列 - DAY 22

前言 来到了困住我好几天的储存结构,希望可以让大家很快地看明白,假如看不懂可以再参考大话资料结构的7...

CRUD的UD / ICON / confirmDialog - day06

前情提要 前几编文章里,大家已经知道如何利用 Vaadin-on-Kotlin 简单快速的新增、查询...