tensorflow – 透过数学理解model

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

什么是神经网络?

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

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

我们可以坚信一件事情:

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

这里f1,f2,f3,f4代表了4组数学变换,具体是y=wx+b还是y=wlog(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:

我们关注一下每一层的权重参数个数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函数的完整数学表达式(计算过程)用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要小,说明优化过程有效。

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

 

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~