Press "Enter" to skip to content

tensorflow – 透过数学理解model

本文通过一个tensorflow例子,通俗的说明神经网络是如何工作的,以便我们更自信的驾驭它。

 

什幺是神经网络?

 

神经网络就是一个数学问题,无论我们的网络结构多幺复杂,它仍旧可以通过一个y与x之间的很复杂的数学公式进行表达。

 

当我们看到一张神经网络图结构时,

 

 

我们可以坚信一件事情:

 

当我们输入x时,一定可以经过某个数学表达式得到y,其数学形式可以高度简化为:y=f1(f2(f3(f4(x)))),嵌套函数越多则网络越深。

 

这里f1,f2,f3,f4代表了4组数学变换,具体是y= w x+ b 还是y= w log(x)还是y=x^2+ b ,这就取决于各种模型理论的发明了,我们完全不必展开。

 

当神经网络输出y之后,我们可以计算与真实y之间的误差(loss),此时误差函数表达为:

 

loss = y’ – f1(f2(f3(f4(x))))

 

此时我们会发现loss比较大,证明模型拟合样本不是很好,怎幺优化呢?

 

在相同的输入x情况下,要想要loss值变小,只能调整f1()、f2()、f3()、f4()这些数学变换中的权重系数(例如:上面的w和b)。

 

每个系数调整多少呢?变大还是变小呢?如果随机碰运气那就训练不出什幺模型了。

 

所以出现了梯度下降调整权重系数的方案:

 

为了让loss可以在当前输入的x情况下更小,我们可以将x视为常量,将f1中某个权重系数w视为变量,对loss函数求关于该w系数的导数,这样就可以明确w系数如何调整才能让loss函数图像向更低的位置移动,这就是梯度下降的原理。

 

loss函数本身是数学公式表达的,因此对loss求各个系数的导数也是数学公式推导问题,因此网络中所有权重系数均可以得到调整,即向样本进一步拟合。

 

经过反复用不同的x输入到模型,进行loss计算与所有权重的梯度下降,模型整体上会不断的更好拟合训练样本,达到一种”全局最优”。

 

以tensorflow为例

 

我们借由tensorflow的functional api构造模型,以此来帮助到工作实践。

 

(代码地址: https://github.com/owenliang/tf-graph-explore/blob/main/tf-graph-explore.ipynb

 

我实现了一个简单的”双塔”网络结构:

 

 

图中的input、dense1、dense2、concat、dense3共5个layer,也就是我们之前说的f1()、f2()、f3()等数学函数…

 

输入层接受输入x,其形状为2列的向量:

 

inputs=tf.keras.Input(shape=(2,),dtype=tf.float32,name=’input’)

 

定义网络要使用的3个全连接层:

 

dense1=tf.keras.layers.Dense(1,activation=None,name=’dense1′)  dense2=tf.keras.layers.Dense(2,activation=None,name=’dense2′)  dense3=tf.keras.layers.Dense(1,activation=None,name=’dense3′)

 

三个神经元均不使用激活函数,其中dense1有1个神经元、dense2有2个神经元、dense3有1个神经元,神经元的个数决定了输出向量的维度:

 

dense层的数学公式都是y=wx+b,不同神经元个数的区别是w和b系数的形状。
dense1有1个神经元,假设w=[ [0.3], [0.4] ],b=0.2,输出y长相为[0.24]
dense2有2个神经元,假设w=[ [0.3, 0.6], [0.4, 0.12]],b=[0.1, 0.35],输出y长相为[0.1, 0.2]
w的行数与x的列数相等,w的列数与神经元的个数相等,这些w和b矩阵的初始化都由tensorflow自行判断生成。

 

然后我们按网络结构连接这些layer:

 

outputs1=dense1(inputs)  outputs2=dense2(inputs)  outputs3=tf.keras.layers.concatenate([outputs1,outputs2],axis=1,name=’concat’)  outputs4=dense3(outputs3)

 

这里将dense1和dense2的输出向量进行了concatenate连接,这里让人困惑的在于向量连接似乎并不能用数学符号表达,这似乎违背了神经网络是数学问题的事实,但其实连接2个向量是可以用数学公式表达的:

 

 

这里dense1输出是1维向量,dense2输出的2维向量,连接后应该是3维向量。

 

在数学中只需要准备一个长度为3的全1向量K,让它的第1维与dense1向量做乘法,另外2维与dense2向量做乘法,最后加起来就连接起来的效果了,总之是数学可表达的,那幺就不会影响我们对神经网络的认知。

 

接下来可以定义出model:

 

model=tf.keras.Model(inputs=inputs,outputs=outputs4)
model.summary()

 

查看到所有layer:

 

Model: "functional_1"
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input (InputLayer)              [(None, 2)]          0                                            
__________________________________________________________________________________________________
dense1 (Dense)                  (None, 1)            3           input[0][0]                      
__________________________________________________________________________________________________
dense2 (Dense)                  (None, 2)            6           input[0][0]                      
__________________________________________________________________________________________________
concat (Concatenate)            (None, 3)            0           dense1[0][0]                     
                                                                 dense2[0][0]                     
__________________________________________________________________________________________________
dense3 (Dense)                  (None, 1)            4           concat[0][0]                     
==================================================================================================
Total params: 13
Trainable params: 13
Non-trainable params: 0

 

我们关注一下每一层的权重参数个数params,可以看到dense1有3个参数需要学习,也就是w带来的2个和b带来的1个;dense2则是w带来的4个和b带来的2个;

 

concat层没有需要学习的参数,因为K就是一个长度为3的[1,1,1]常量向量。

 

画出网络结构会更加清晰(图片在上面已经贴过了):

 

tf.keras.utils.plot_model(model, “graph.png”, show_shapes=True)

 

我们随机生成10个样本,x是2维的,y是1维的:

 

x=tf.random.normal(shape=(10,2))  y=tf.random.normal(shape=(10,1))  print(x)  print(y)
tf.Tensor(  [[-0.8464397 -0.3152412 ]  [ 0.9817092 -0.57270414]  [ 0.86039394 0.57590604]  [-1.5055276 0.45981622]  [ 1.40179 1.0307338 ]  [ 0.5882102 2.671993 ]  [ 0.5666892 -0.33787787]  [ 0.36999676 0.5678155 ]  [ 2.131917 0.33147094]  [-0.23225114 0.84211487]], shape=(10, 2), dtype=float32)  tf.Tensor(  [[-0.9018226 ]  [-0.83541167]  [-0.70780784]  [ 0.43620512]  [-1.2712636 ]  [ 0.39236164]  [ 0.11044435]  [ 2.7505376 ]  [ 0.64985305]  [-1.4352192 ]], shape=(10, 1), dtype=float32)

 

然后向model输入10行样本x,计算返回10个y:

 

pred_y = model(data)  print(pred_y)
tf.Tensor(  [[ 1.4824426 ]  [ 0.5423604 ]  [-1.0151732 ]  [ 2.2172787 ]  [-1.9427378 ]  [-1.8751484 ]  [ 2.486527 ]  [ 0.03885526]  [-0.62673503]  [ 0.23725389]], shape=(10, 1), dtype=float32)

 

为了优化模型,我们需要计算pred_y与y之间的误差loss,并对loss函数在当前x输入的情况下对各个权重系数进行梯度求导:

 

loss_f = tf.keras.losses.MeanAbsoluteError()
with tf.GradientTape() as tape:
    pred_y = model(data) # 模型计算
    loss = loss_f(y, pred_y) # 计算误差
    print(loss) # 打印损失
grads=tape.gradient(loss, model.trainable_variables) # 反向传播误差到各层, 对各权重系数求梯度
print(grads)

 

我们将整个loss函数的完整数学表达式(计算过程)用tape录制下来,这样tensorflow可以自动帮我们求出所有各个权重系数的导数,grads是网络中所有权重参数的梯度:

 

tf.Tensor(1.6826286, shape=(), dtype=float32)  [<tf.Tensor: shape=(2, 1), dtype=float32, numpy=  array([[ 0.37924558],  [-0.17607355]], dtype=float32)>, <tf.Tensor: shape=(1,), dtype=float32, numpy=array([0.], dtype=float32)>, <tf.Tensor: shape=(2, 2), dtype=float32, numpy=  array([[-0.28494048, 1.0743711 ],  [ 0.13229021, -0.4988016 ]], dtype=float32)>, <tf.Tensor: shape=(2,), dtype=float32, numpy=array([0., 0.], dtype=float32)>, <tf.Tensor: shape=(3, 1), dtype=float32, numpy=  array([[ 0.02184648],  [ 1.1978055 ],  [-0.9128995 ]], dtype=float32)>, <tf.Tensor: shape=(1,), dtype=float32, numpy=array([0.], dtype=float32)>]

 

打印当前Loss是1.6826286。

 

打印grads是按某种顺序排列好的模型中的各个权重的导数,稍后将这些导数应用到对应的权重参数上即可。

 

optimizer = tf.keras.optimizers.Adam(learning_rate=0.01)

 

optimizer.apply_gradients(zip(grads, model.trainable_variables)) # 将梯度应用到各个权重系数

 

利用zip将各个权重参数的梯度与对应的权重参数绑定到一起,然后交给optimizer完成最终的修改,此时模型已经得到了一轮优化,理论上更加拟合训练数据,为此我们重新进行一次预测:

 

pred_y = model(data) # 使用学习后的模型再预测  print(pred_y)
tf.Tensor(  [[ 1.4320835 ]  [ 0.53110886]  [-0.9584385 ]他们说是  [ 2.1270993 ]  [-1.888143 ]  [-1.8089024 ]  [ 2.3986485 ]  [ 0.03384402]  [-0.5806757 ]  [ 0.22927463]], shape=(10, 1), dtype=float32)

 

看一下现在的loss是否更小了:

 

loss = loss_f(y, pred_y)  print(loss)
tf.Tensor(1.6360016, shape=(), dtype=float32)

 

优化过一次的模型loss为1.6360016,比之前的1.6826286要小,说明优化过程有效。

 

本篇博客对你有帮助幺?请留言让我知道。

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注