【TensorFlow-windows】keras接口——BatchNorm和ResNet

前言

之前学习利用Keras简单地堆叠卷积网络去构建分类模型的方法,但是对于很深的网络结构很难保证梯度在各层能够正常传播,经常发生梯度消失、梯度爆炸或者其它奇奇怪怪的问题。为了解决这类问题,大佬们想了各种办法,比如最原始的L1,L2正则化、权重衰减等,但是在深度学习的各种技巧中,批归一化(Batch Normalization,BN)和残差网(Residual Network,ResNet)还是比较有名的,看一波。
国际惯例,参考博客:
BN的原始论文
BN的知乎讨论
莫凡大神的BN讲解
Batch Normalization原理与实战
何凯明大佬的caffe-ResNet实现
大佬的keras-ResNet实现

基于keras的Resnet

批归一化BN

Keras中文文档中有总结过其作用:

  • 加速收敛
  • 控制过拟合,可以少用或不用Dropout和正则
  • 降低网络对初始化权重的敏感程度,因而允许使用较大的学习率
  • 可以试用饱和非线性函数(sigmoid等)

以下摘抄一下个人认为论文里面比较重要的语句:

动机

  • DNN训练的时候,每层输入的数据分布在不断变化,因为他们之前层的参数在不断更新,这就很大程度上降低训练速度,此时就需要较低的学习率,很小心地初始化模型参数,如果使用饱和非线性函数(saturating nonlinearities,比如tanhsigmoid)会更难训练,主要是因为两端梯度的影响;这个现象称为内部协方差偏移(internal convariate shift)现象。

  • 使用小批量训练模型的优势在于,相对于单样本学习,小批量学习的损失梯度是对整个训练集的估计,它的质量随着批大小的上升而提高,此外使用小批量学习的计算比计算m次单个样本来的更加高效,因为小批量训练可以利用计算机的并行计算。

  • 虽然随机梯度下降简单有效,但是需要很小心调整超参,尤其是学习率和模型参数初始化,并且每一层的输入都受到前面所有层的影响,这个导致训练比较复杂,网络参数任何很小的变化都可能在传播多层以后被放大。但是各层输入的分布又不得不变化,因为各层需要不断调整去适应新的分布(每次输入的样本分布一般不同)。当学习系统的输入分布发生变化,就发生了协方差偏移现象(covariate shift)。

  • 文章提出一个构想:
    假如一个网络结构是这样:
    l=F2(F1(u,Θ1),Θ2)l=F_2(F_1(u,\Theta_1),\Theta_2) l=F2(F1(u,Θ1),Θ2)
    那么梯度就是
    Θ2←Θ2−αm∑i=1m∂F2(xi,Θ2)Θ2\Theta_2\leftarrow \Theta_2-\frac{\alpha}{m}\sum_{i=1}^m \frac{\partial F_2(x_i,\Theta_2)}{\Theta_2} Θ2Θ2mαi=1mΘ2F2(xi,Θ2)
    ( α\alphaα 是学习率,mmm是批大小),这个梯度等价于一个具有输入为xxx的独立网络F2F_2F2,因此输入分布可以让训练变得更加高效,比如训练集和测试机的分布相同,这同样适用于子网络。因此随着时间的偏移,保证xxx的分布固定是有好处的,所以Θ2\Theta_2Θ2没必要重新调整去弥补xxx分布的变化,其实说白了,大家一起归一化,固定好分布(均值和方差)。

  • 通常情况下的饱和问题和梯度消失问题能够用ReLU、小心的初始化和较小学习率来解决,当然,我们也可以修正非线性输入的分布在训练时更加平稳,此时优化器陷入饱和状态的几率会降低,学习速度也会上升。

理论

仅仅是简单地对每层输入的归一化会改变该层所表示的东东,比如对sigmoid的输入数据归一化,会将其限制在非线性函数的线性区域(因为sigmoid靠近中心部分接近线性激活),解决它就需要保证插入到网络的变换能够代表恒等变换,文章使用缩放因子γ(k)\gamma^{(k)}γ(k)和平移因子β(k)\beta^{(k)}β(k)对归一化的值进行变换:
y(k)=γ(k)x^(k)+β(k)y^{(k)}=\gamma^{(k)}\hat{x}^{(k)}+\beta^{(k)} y(k)=γ(k)x^(k)+β(k)
实际上,如果γ(k)=Var[x(k)]\gamma^{(k)}=\sqrt{Var[x^{(k)}]}γ(k)=Var[x(k)]β(k)=E(x(k))\beta^{(k)}=E(x^{(k)})β(k)=E(x(k)),那么就是反归一化了,数据直接被恢复成未被归一化的状态。其实我当时在这里有一个疑问:批归一化的目的就是让神经元的激活值在sigmoid梯度较大的地方,那么为啥还要缩放回去,恢复了原始值,那么梯度不又是两端梯度么?就跟吹气球一样,先把气球吹得很大,感觉要炸了,就去将它缩小一点,但是又添加了个偏移,把气球吹回去了。 后来想想,这个问题不难解答,它相当于把较大的东东拆成了较小的东东,然后求导的时候,如果直接对较大的东东求导会发生两端梯度更新缓慢问题,但是如果由多个小东东组合起来,然后对每个小东东求导,梯度就不会出现在两端更新,具体看下面的推导,就可以发现每一个参数的梯度不会那么小。

【注】突然就感觉这个思想很像ResNet啊,都是为了解决对原始较大值直接求梯度发生两端梯度较小问题,只不过BN是将大的数据变成了归一化数据+缩放+平移,这些值都比较小,求梯度也不会发生两端梯度的情况;而ResNet是将大的数据变成了数据+残差项,对这个残差项求梯度很少情况会发生两端梯度现象。

前向计算(注意是针对批数据的同一个维度,而非是一个数据的所有维度):
input:B=x1,⋯ ,mparam:γ,βoutput:yi=BNγ,β(xi)μB=1m∑i=1mxiσB2=1m∑i=1m(xi−μB)2x^i=xi−μBσB2+ϵyi=γx^i+β\begin{aligned} input&: B={x_{1,\cdots,m}}\\ param&:\gamma,\beta \\ output&:y_i=BN_{\gamma,\beta}(x_i) \end{aligned} \\ \begin{aligned} \mu_B&=\frac{1}{m}\sum_{i=1}^m x_i\\ \sigma^2_B&=\frac{1}{m}\sum_{i=1}^m(x_i-\mu_B)^2\\ \hat{x}_i&=\frac{x_i-\mu_B}{\sqrt{\sigma ^2_B+\epsilon}}\\ y_i&=\gamma \hat{x}_i+\beta \end{aligned} inputparamoutput:B=x1,,m:γ,β:yi=BNγ,β(xi)μBσB2x^iyi=m1i=1mxi=m1i=1m(xiμB)2=σB2+ϵxiμB=γx^i+β
反向传播:梯度

  • 模型参数γ\gammaγβ\betaβ的梯度
    ∂l∂γ=∑i=1m∂l∂yi⋅x^i∂l∂β=∑i=1m∂l∂yi\begin{aligned} \frac{\partial l}{\partial \gamma}&=\sum_{i=1}^m\frac{\partial l}{\partial y_i}\cdot \hat{x}_i\\ \frac{\partial l}{\partial \beta}&=\sum_{i=1}^m\frac{\partial l}{\partial y_i} \end{aligned} γlβl=i=1myilx^i=i=1myil

  • 链式求导时,需要计算
    ∂l∂xi=∂l∂x^i⋅1σB2+ϵ+∂l∂σB2⋅2(xi−μB)m+∂l∂μB⋅1m\frac{\partial l}{\partial x_i}=\frac{\partial l}{\partial{\hat{x}_i}}\cdot\frac{1}{\sqrt{\sigma^2_B+\epsilon}}+\frac{\partial l}{\partial \sigma^2_B}\cdot \frac{2(x_i-\mu_B)}{m}+\frac{\partial l}{\partial \mu_B}\cdot\frac{1}{m} xil=x^ilσB2+ϵ1+σB2lm2(xiμB)+μBlm1
    其中
    ∂l∂x^i=∂l∂yi⋅γ∂l∂σB2=∑i=1m∂l∂x^i⋅(xi−μB)⋅−12(σB2+ϵ)−32∂l∂μB=(∑i=1m∂l∂x^i⋅−1σB2+ϵ)+∂l∂σB2⋅∑i=1m−2(xi−μB)m\begin{aligned} \frac{\partial l}{\partial \hat{x}_i}&=\frac{\partial l}{\partial y_i}\cdot \gamma \\ \frac{\partial l}{\partial \sigma^2_B}&=\sum_{i=1}^m\frac{\partial l}{\partial \hat{x}_i}\cdot(x_i-\mu_B)\cdot\frac{-1}{2}(\sigma^2_B+\epsilon)^{-\frac{3}{2}}\\ \frac{\partial l}{\partial \mu_B}&=\left(\sum_{i=1}^m\frac{\partial l}{\partial \hat{x}_i}\cdot\frac{-1}{\sigma^2_B+\epsilon}\right)+\frac{\partial l}{\partial \sigma^2_B}\cdot\frac{\sum_{i=1}^m-2(x_i-\mu_B)}{m} \end{aligned} x^ilσB2lμBl=yilγ=i=1mx^il(xiμB)21(σB2+ϵ)23=(i=1mx^ilσB2+ϵ1)+σB2lmi=1m2(xiμB)

【注】还有一个问题是BN到底是放在激活之前还是激活之后?知乎上的讨论戳这里,原论文的第3.2小节指出实验时采用的是z=g(BN(Wu))z=g(BN(Wu))z=g(BN(Wu))的方式,即先BN再激活。

Keras中的使用

先看看官方文档描述:

keras.layers.BatchNormalization(axis=-1, momentum=0.99, epsilon=0.001, center=True, scale=True, beta_initializer='zeros', gamma_initializer='ones', moving_mean_initializer='zeros', moving_variance_initializer='ones', beta_regularizer=None, gamma_regularizer=None, beta_constraint=None, gamma_constraint=None)

注意官方文档的一句话Normalize the activations of the previous layer at each batch,看样子是在激活后再BN。

ResNet

动机

  • 作者首先提出一个疑问:让模型学的更好是否等价于简单地堆叠更多的层?其中一个阻碍就是“梯度消失/爆炸”,通常解决方法是规范初始化、中间层归一化BN。
  • 当网络开始收敛的时候,又出现新问题:当网络层数加深的时候,准确率提高,但是随后降低地很快,但这并非是过拟合,增加更多的层导致了更高的训练误差。
  • 作者又做了个假设与实验:考虑浅层网络和它的深层结构,这个深层结构是在浅层网络的顶部添加恒等映射(identity mapping), 这种构造方法理论上应该能让深层模型产生的训练误差不差于浅层网络的训练误差。但是实验结果发现这种方案不能产生比理论上得到的构造解更好的结果,也就是说没达到理论预期。
  • 文章的解决方法就是:引入深度残差框架。思想是不期望每块堆叠的几层网络去学习低层映射,转而显式地让这些层去拟合一个残差。用公式来解释就是:假设正常情况下低层的映射是H(x)H(x)H(x), 让非线堆叠层拟合另一个映射F(x)=H(x)−xF(x)=H(x)-xF(x)=H(x)x,也就是说原始的映射变成了H(x)=F(x)+xH(x)=F(x)+xH(x)=F(x)+x, 但是这样的做法建立在一个假设上,即学习残差映射F(x)F(x)F(x)比直接学习原始的映射H(x)H (x)H(x)容易。极端情况下就是,恒等映射为最优情况,让残差趋近于0比让堆叠的非线性层 逼近0更容易。
    不信你试试计算这样两个东东:
    • Relu(w×x)=xRelu(w\times x)=xRelu(w×x)=xRelu(w×x)+x=xRelu(w\times x)+x=xRelu(w×x)+x=x,哪个容易求解,第二个不用思考就知道www为零矩阵,但是第一个还得想一下,它是斜对角元素为1的单位阵。再者,仿照神经网络随便对www进行初始化,然后计算www到零矩阵和单位阵的变换过程,哪个简单?
    • Relu(w×x)=x+ϵRelu(w\times x)=x+\epsilonRelu(w×x)=x+ϵRelu(w×x)+x=x+ϵRelu(w\times x)+x=x+\epsilonRelu(w×x)+x=x+ϵ,第二个方程对x的波动ϵ\epsilonϵ更加敏感,比如w×1000=1000.1→1000.2w\times 1000=1000.1\to 1000.2w×1000=1000.11000.2与$ (w\times 1000)+1000=1000.1\to 1000.2$,当输出只变动了0.10.10.1的时候,他们的权重www变化量是多少呢?第一个www变化为w=1000.11000→w′=1000.21000w=\frac{1000.1}{1000}\to w'=\frac{1000.2}{1000}w=10001000.1w=10001000.2,计算一下变权重变化量相对于原始权重的变化w′−ww=110001\frac{w'-w}{w}=\frac{1}{10001}www=100011,但是第二个www变化w=0.11000→w′=0.21000w=\frac{0.1}{1000}\to w'=\frac{0.2}{1000}w=10000.1w=10000.2, 相对于开始权重的变化量为w′−ww=1\frac{w'-w}{w}=1www=1,很明显学习残差时权重增长了一倍,但是直接映射时权重才增长了万分之一。
    • 有人可能跟我一样较真:为啥权重敏感性越高学习越好?很简单的想法,比如给人挠痒痒,一个人皮厚(对权重不敏感),能拿鸡毛(权重1)和刀子(权重2)给他挠,他都没感觉,下次他想让你挠的时候,你拿啥都没问题(即他感觉这两权重没啥区别);但是假设这个人皮薄(对权重敏感),拿刀子给他挠痒痒,他说疼,那下次挠的时候一拿刀子,他就会说错了错了,这东西不能挠,也就是让你学习了正确的知识;所以结果对权重越敏感,越能学到正确的知识。还有一个例子就是:比如同一个人戴眼镜和不戴眼镜都在人脸识别训练集中,这两张图片的残差就类似于这幅眼镜,如果残差学习到了这副眼镜,后来就能更准确地区分带不带眼镜的情况。

理论

可以通过添加连接捷径表示恒等映射,如下图所示:

在这里插入图片描述

假设这个残差块的输入为xxx,输出为yyy,那么,通常情况下:
y=F(x,Wi)+xy=F(x,{W_i})+x y=F(x,Wi)+x
F+x​F+x​F+x就代表连接捷径,即对应元素相加;F(x,Wi)F(x,W_i)F(x,Wi)代表输入经过FFF几层非线性层的映射结果,图中显示的是两层,类似于
F(x,Wi)=W2×Relu(W1×x)F(x,W_i)=W_2\times Relu(W_1\times x) F(x,Wi)=W2×Relu(W1×x)
【注】从图和公式来看,残差块不包含块中最后一层的激活,所以为了使残差块有非线性,必须至少两个层。从加法来看,残差块的输入和输出应该具有相同维度,不然不能相加。

以上说的是通常情况,还有不通常情况就是假设残差块的非线性映射部分输出的维度比输入维度小,那么对应元素相加就无法实现,论文就给出了想要维度匹配时的操作:
y=F(x,Wi)+Ws×xy=F(x,{W_i})+W_s\times x y=F(x,Wi)+Ws×x
没错,就是乘了一个矩阵WsW_sWs,变换xxx的维度就完事,而且因为是线性操作,影响不大

Keras中的使用

先看看何凯明大佬在caffe中搭建的ResNet是啥样的:
在这里插入图片描述在这里插入图片描述
两类残差块,一类是在左边捷径连接的时候接了个分支,第二类是直接恒等映射过来。

但是大致能知道残差块大致包含了两部分,一部分有较多的层块,一部分有较少的甚至是一个或者零个层块,除此之外还有一些细节就是,每个组成残差块的每个层块构造是:卷积->BN->缩放->Relu,为了保证维度相加的可能性,尽量使用卷积核大小为(1,1)步长为1,填充为0,或者卷积核大小为(3,3)步长为1,填充为1,文章的右边三个卷积块使用的卷积核大小分别是1,3,11,3,11,3,1,卷积核个数不同,计算卷积后特征图大小公式是:
n−m+2pS+1\frac{n-m+2p}{S}+1 Snm+2p+1
n是图像某个维度,m是对应的卷积核维度,p是对应的填充维度,S是步长

接下来在keras中搞事情。

#ResNet-第一类:有侧路卷积(大小1);主路卷积用1,3,1的卷积块(conv-BN-Relu)
def conv_block(input_x,kn1,kn2,kn3,side_kn):#主路:第一块x=Conv2D(filters=kn1,kernel_size=(1,1))(input_x)x=BatchNormalization(axis=-1)(x)x=Activation(relu)(x)#主路:第二块,注意paddingx=Conv2D(filters=kn2,kernel_size=(3,3),padding='same')(x)x=BatchNormalization(axis=-1)(x)x=Activation(relu)(x)#主路:第三块x=Conv2D(filters=kn3,kernel_size=(1,1))(x)x=BatchNormalization(axis=-1)(x)x=Activation(relu)(x)#侧路y=Conv2D(filters=side_kn,kernel_size=(1,1))(input_x)y=BatchNormalization(axis=-1)(y)y=Activation(relu)(y)#捷径output=keras.layers.add([x,y])#再激活一次output=Activation(relu)(output)return output
#ResNet-第二类:没有侧路卷积;主路卷积用1,3,1的卷积块(conv-BN-Relu)
def identity_block(input_x,kn1,kn2,kn3):#主路:第一块x=Conv2D(filters=kn1,kernel_size=(1,1))(input_x)x=BatchNormalization(axis=-1)(x)x=Activation(relu)(x)#主路:第二块,注意paddingx=Conv2D(filters=kn2,kernel_size=(3,3),padding='same')(x)x=BatchNormalization(axis=-1)(x)x=Activation(relu)(x)#主路:第三块x=Conv2D(filters=kn3,kernel_size=(1,1))(x)x=BatchNormalization(axis=-1)(x)x=Activation(relu)(x)#捷径output=keras.layers.add([x,input_x])#再激活一次output=Activation(relu)(output)return output

构建ResNet50

可以这里和这里的代码,主要是基于何凯明大佬的ResNet50写的结构:

  • 引入相关包

    import tensorflow as tf
    import tensorflow.keras as keras
    from tensorflow.keras.layers import Conv2D,AveragePooling2D,MaxPooling2D,BatchNormalization,Activation,Flatten,Dense
    from tensorflow.keras.activations import relu
    import numpy as np
    
  • 基于以上的BN和两类ResNet块结构构建ResNet50

    #按照何凯明大佬的ResNet50构建模型
    def ResNet50():input_data= keras.layers.Input(shape=(28,28,1))x=Conv2D(filters=64,kernel_size=(7,7),data_format='channels_last')(input_data)#由于不知道怎么padding=3,所以暂时不适用padding和stridex=BatchNormalization(axis=-1)(x)x=Activation(relu)(x)x=MaxPooling2D(pool_size=(3,3),strides=(2,2))(x)x=conv_block(x,64,64,256,256)#Res2a:有侧路x=identity_block(x,64,64,256)#Res2b:无侧路x=identity_block(x,64,64,256)#Res2c:无侧路x=conv_block(x,128,128,512,512)#Res3a:有侧路x=identity_block(x,128,128,512)#Res3b:无侧路x=identity_block(x,128,128,512)#Res3c:无侧路x=identity_block(x,128,128,512)#Res3d:无侧路x=conv_block(x,256,256,1024,1024)#Res4a:有侧路x=identity_block(x,256,256,1024)#Res4b:无侧路x=identity_block(x,256,256,1024)#Res4c:无侧路x=identity_block(x,256,256,1024)#Res4d:无侧路x=identity_block(x,256,256,1024)#Res4d:无侧路x=identity_block(x,256,256,1024)#Res4e:无侧路x=identity_block(x,256,256,1024)#Res4f:无侧路x=conv_block(x,512,512,2048,2048)#Res5a:有侧路x=identity_block(x,512,512,2048)#Res5b:无侧路x=identity_block(x,512,512,2048)#Res5c:无侧路x=AveragePooling2D(pool_size=(7,7),strides=1)(x)x=Flatten()(x)x=Dense(units=10)(x) #mnist的类别数目x=Activation(keras.activations.softmax)(x)model=keras.models.Model(inputs=input_data, outputs=x)return model
    
  • 读手写数字

    mnist_data=keras.datasets.mnist
    (train_x,train_y),(test_x,test_y)=mnist_data.load_data()
    train_y=keras.utils.to_categorical(train_y,10)
    test_y=keras.utils.to_categorical(test_y,10)
    train_x=train_x/255.0
    test_x=test_x/255.0
    train_x=train_x[...,np.newaxis]
    test_x=test_x[...,np.newaxis]
    
  • 构建模型并训练

    model=ResNet50()
    mnist_data=keras.datasets.mnist
    (train_x,train_y),(test_x,test_y)=mnist_data.load_data()
    train_y=keras.utils.to_categorical(train_y,10)
    test_y=keras.utils.to_categorical(test_y,10)
    train_x=train_x/255.0
    test_x=test_x/255.0
    train_x=train_x[...,np.newaxis]
    test_x=test_x[...,np.newaxis]
    model.compile(optimizer=tf.keras.optimizers.Adam(),loss=keras.losses.categorical_crossentropy,metrics=['accuracy'])
    model.fit(train_x,train_y,batch_size=20,epochs=20)
    

    太久了,我就不训练了,而且网络太大,没事就OutOfMemory

    Epoch 1/209080/60000 [===>..........................] - ETA: 53:37 - loss: 14.3559 - acc: 0.1073
    

    预测的话可以参考之前的博客model.predict之类的

后记

在深度神经网络中常用的两个解决梯度消失问题的技巧已经学了,后面再继续找找案例做,其实为最想要的是尝试如何把算法移植到手机平台,最大问题是模型调用和平台移植,目前可采用的方法有:

  • unity在做APP上效果还不错,能各种移植,且TensorFlow支持C#
  • 官方的ensorflow Lite也抽时间看看
  • OpenCVdnn模块可以调用TensorFlow模型,但是目前还没学会如何将自己的TensorFlow模型封装好,到OpenCV调用,只不过官方提供的模型可以调用,自己的模型一直打包出问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/246598.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【TensorFlow-windows】keras接口——卷积核可视化

前言 在机器之心上看到了关于卷积核可视化相关理论,但是作者的源代码是基于fastai写的,而fastai的底层是pytorch,本来准备自己用Keras复现一遍的,但是尴尬地发现Keras还没玩熟练,随后发现了一个keras-vis包可以用于做…

【TensorFlow-windows】投影变换

前言 没什么重要的,就是想测试一下tensorflow的投影变换函数tf.contrib.image.transform中每个参数的含义 国际惯例,参考文档 官方文档 描述 调用方法与默认参数: tf.contrib.image.transform(images,transforms,interpolationNEAREST,…

【TensorFlow-windows】扩展层之STN

前言 读TensorFlow相关代码看到了STN的应用,搜索以后发现可替代池化,增强网络对图像变换(旋转、缩放、偏移等)的抗干扰能力,简单说就是提高卷积神经网络的空间不变性。 国际惯例,参考博客: 理解Spatial Transformer…

【TensorFlow-windows】MobileNet理论概览与实现

前言 轻量级神经网络中,比较重要的有MobileNet和ShuffleNet,其实还有其它的,比如SqueezeNet、Xception等。 本博客为MobileNet的前两个版本的理论简介与Keras中封装好的模块的对应实现方案。 国际惯例,参考博客: 纵…

【TensorFlow-windows】keras接口——ImageDataGenerator裁剪

前言 Keras中有一个图像数据处理器ImageDataGenerator,能够很方便地进行数据增强,并且从文件中批量加载图片,避免数据集过大时,一下子加载进内存会崩掉。但是从官方文档发现,并没有一个比较重要的图像增强方式&#x…

【TensorFlow-windows】TensorBoard可视化

前言 紧接上一篇博客,学习tensorboard可视化训练过程。 国际惯例,参考博客: MNIST机器学习入门 Tensorboard 详解(上篇) Tensorboard 可视化好帮手 2 tf-dev-summit-tensorboard-tutorial tensorflow官方mnist_…

深度学习特征归一化方法——BN、LN、IN、GN

前言 最近看到Group Normalization的论文,主要提到了四个特征归一化方法:Batch Norm、Layer Norm、Instance Norm、Group Norm。此外,论文还提到了Local Response Normalization(LRN)、Weight Normalization(WN)、Batch Renormalization(BR)…

【TensorFlow-windows】keras接口——利用tensorflow的方法加载数据

前言 之前使用tensorflow和keras的时候,都各自有一套数据读取方法,但是遇到一个问题就是,在训练的时候,GPU的利用率忽高忽低,极大可能是由于训练过程中读取每个batch数据造成的,所以又看了tensorflow官方的…

骨骼动画——论文与代码精读《Phase-Functioned Neural Networks for Character Control》

前言 最近一直玩CV,对之前学的动捕知识都忘得差不多了,最近要好好总结一下一直以来学习的内容,不能学了忘。对2017年的SIGGRAPH论文《Phase-Functioned Neural Networks for Character Control》进行一波深入剖析吧,结合源码。 额…

颜色协调模型Color Harmoniztion

前言 最近做换脸,在肤色调整的那一块,看到一个有意思的文章,复现一波玩玩。不过最后一步掉链子了,有兴趣的可以一起讨论把链子补上。 主要是github上大佬的那个复现代码和原文有点差异,而且代码复杂度过高&#xff0…

Openpose推断阶段原理

前言 之前出过一个关于openpose配置的博客,不过那个代码虽然写的很好,而且是官方的,但是分析起来很困难,然后再opencv相关博客中找到了比较清晰的实现,这里分析一波openpose的推断过程。 国际惯例,参考博…

换脸系列——眼鼻口替换

前言 想着整理一下换脸相关的技术方法,免得以后忘记了,最近脑袋越来越不好使了。应该会包含三个系列: 仅换眼口鼻;换整个面部;3D换脸 先看看2D换脸吧,网上已经有现成的教程了,这里拿过来整理一…

换脸系列——整脸替换

前言 前面介绍了仅替换五官的方法,这里介绍整张脸的方法。 国际惯例,参考博客: [图形算法]Delaunay三角剖分算法 维诺图(Voronoi Diagram)分析与实现 Delaunay Triangulation and Voronoi Diagram using OpenCV (…

3D人脸重建——PRNet网络输出的理解

前言 之前有款换脸软件不是叫ZAO么,分析了一下,它的实现原理绝对是3D人脸重建,而非deepfake方法,找了一篇3D重建的论文和源码看看。这里对源码中的部分函数做了自己的理解和改写。 国际惯例,参考博客: 什…

tensorflow官方posenet模型解析

前言 tensorflow官方有个姿态估计项目,这个输入和openpose还有点不一样,这里写个单人情况下的模型输出解析方案。 国际惯例,参考博客: 博客: 使用 TensorFlow.js 在浏览器端上实现实时人体姿势检测 tensorflow中posnet的IOS代…

tensorflow2安装时候的一个dll找不到的错误

电脑环境: vs2015python3.7.6,使用anaconda安装的CUDA 10.1cuDnn 7.6.5tensorflow2.1.0 错误内容 File "C:\Users\zb116\anaconda3\lib\imp.py", line 242, in load_modulereturn load_dynamic(name, filename, file)File "C:\Users\z…

PCA、SVD、ZCA白化理论与实现

简介 在UFLDL中介绍了主成分分析这一块的知识,而且当时学机器学习的时候,老师是将PCA和SVD联系起来将的,同时UFLDL也讲到了使用PCA做数据白化whitening处理,这个词经常在论文里面看到。 国际惯例,参考博客&#xff1…

OpenCV使用Tensorflow2-Keras模型

前言 最近工作上需要在C上快速集成Tensorflow/Keras训练好的模型,做算法验证。首先想到的就是opencv里面的dnn模块了,但是它需要的格式文件比较郁闷,是pb格式的模型,但是keras通常保存的是h5文件,查阅了很多资料&…

3D人脸表情驱动——基于eos库

前言 之前出过三篇换脸的博文,遇到一个问题是表情那一块不好处理,可行方法是直接基于2D人脸关键点做网格变形,强行将表情矫正到目标人脸,还有就是使用PRNet的思想,使用目标人脸的顶点模型配合源人脸的纹理&#xff0c…

3D姿态估计——ThreeDPose项目简单易用的模型解析

前言 之前写过tensorflow官方的posenet模型解析,用起来比较简单,但是缺点是只有2D关键点,本着易用性的原则,当然要再来个简单易用的3D姿态估计。偶然看见了ThreeDPose的项目,感觉很强大的,所以把模型扒下来…