关于DNN,CNN,RNN的一些思考

商嘉木
2023-12-01

       关于DNN(这里指的dense),CNN,RNN之前一直没搞清楚这三种网络到底本质的区别在哪里?经过一些反复的思考和实验,结合tensorflow提供的API,将一些感悟和想法记录一下。

       首先,创建一个 batch_size=1 的序列,假设该序列是经过词嵌入处理后的,如下: 

x_inputs = tf.constant(np.random.random(10*6), dtype=tf.float32, shape=(1, 10, 6))
# <tf.Tensor 'Const_1:0' shape=(1, 10, 6) dtype=float32>

       此处第一维度代表 batch_size 大小,第二维度代表time_steps,第三维度代表embedding_size。(为了不造成歧义,此处采用比较官方的命名

  • DNN 稠密神经网络
fnn = tf.layers.Dense(units=5, name="fnn")
fnn(inputs=x_inputs)
# <tf.Tensor 'fnn_2/BiasAdd:0' shape=(1, 10, 5) dtype=float32>
fnn.weights
# [<tf.Variable 'fnn_1/kernel:0' shape=(6, 5) dtype=float32_ref>,
#  <tf.Variable 'fnn_1/bias:0' shape=(5,) dtype=float32_ref>]

      这里设置神经元输出节点数为5,输入的则是一个(10, 6) 的矩阵,通过weights shape=(6, 5)的权值矩阵,和bias shape=(5, )的偏置,得到一个shape=(1, 10, 5)的输出。

  • CNN 卷积神经网络
cnn = tf.layers.Conv1D(filters=5, kernel_size=1, strides=1, dtype=tf.float32, name="cnn")
cnn(inputs=x_inputs)
# <tf.Tensor 'conv1d_3/BiasAdd:0' shape=(1, 10, 5) dtype=float32>
cnn.weights
# [<tf.Variable 'conv1d_2/kernel:0' shape=(1, 6, 5) dtype=float32_ref>,
#  <tf.Variable 'conv1d_2/bias:0' shape=(5,) dtype=float32_ref>]

     这里用1D卷积,设置卷积核为5,卷积窗口为1,步长为1,通过五个卷积核按照窗口大小为1,滑动步长为1,从序列第一位滑动到第十位得到 shape=(1, 10, 5) 的输出。 

  • RNN 循环神经网络
rnn = tf.nn.rnn_cell.BasicRNNCell(num_units=5, name="rnn")
cell_init = rnn.zero_state(1, dtype=tf.float32)
# <tf.Tensor 'BasicRNNCellZeroState/zeros:0' shape=(1, 5) dtype=float32>
out_cat = []
for i in range(10):
    out, state = rnn(inputs=x_inputs[:, i, :], state=cell_init)
    out_cat.append(out)
output = tf.concat(out_cat, axis=0)
# <tf.Tensor 'concat_1:0' shape=(10, 5) dtype=float32>
rnn.weights
# [<tf.Variable 'rnn/kernel:0' shape=(11, 5) dtype=float32_ref>,
#  <tf.Variable 'rnn/bias:0' shape=(5,) dtype=float32_ref>]

       这里设置rnnCell的num_units为5,因为RNN的特殊性,需要初始化一个zero_state作为和x_inputs拼接的向量,同时rnn既然是循环神经网络,所以要按照time_steps的维度做循环,最终得到一个 shape=(10, 5)的输出

       三种网络,给定了同样的输入shape,最终也给出了同样的输出shape,这是因为我们给定了同样的num_units(卷积这里为filters),其实这个值的大小就是所谓隐藏层神经元的个数,也就是说一个卷积核,一个循环cell都等价于一个神经元,这里三种网络殊途同归。(不信可自行调整该值的大小,看输出情况)如果这个结论从你了解神经网络一开始就有了,那么恭喜你在思维的坎上少走很多弯路,个人在学习的时候因没能很好的理解到这个本质,造成了很多思维上的困惑,当领悟到这个本质后,觉得一切都豁然开朗。

       这里对神经元下个定义,所有神经元都接受一个向量作为输入,输出一个单值,确保该神经云是否被激活。那么输入一个矩阵会怎么样?那么它就输出一个向量,向量中每一个元素代表输入矩阵当中每一行向量与神经元中权值矩阵对应列向量点积的结果。即 W'X (这里忽略bias), 不管是DNN,CNN,RNN他们内部神经元在做的就是这个计算。这里把X当作列向量的在axis=1上的拼接,即行代表特征维度,列代表样本数,那么W的行即是num_units, 就是神经元输出的个数。忘记神经网络那些经典的网络图,事实上,前馈的神经网络本质就是在做张量乘法, 一层过一层的本质就是构造一个W和上一层输入相乘满足要求的输出,直到最后一个输出层和label一样的shape,再构造一个损失函数,通过反向传播更新构造的这些W来逼近真实样本分布。

       三者的共性如上,那么三者的区别在哪里呢?DNN作为基础网络,输出size给定,输入shape给定,基本输出结果就已经确定了,如上代码所示,仅仅设置了num_units一个参数就完成了一层网络。CNN可以看到多了不少参数,其实这些参数的作用就是在确定W的shape,此时W不再是二维矩阵,而是多维张量,但是计算本质是一样的。RNN算是比较难理解的,本人学习过程中花费了好长的时间去理解,其实很简单(真理往往都格外的简洁),它和DNN的唯一区别在于,它会把每一次输出的结果向量拼接到输入,从上代码可以看到它的W第一维度要高一些,其实就是embedding_size和num_units相加的结果。因为当前做张量乘法依赖于上一个time_steps的输出,所以需要循环每个time_steps, 在具体应用一般用LSTM代替单纯的RNN,LSTM相比与RNN在内部又多做了几次运算,简而之,多设计了几个W,工作原理还是一样的。

  •   总结

       写博客,还是件挺费力费神的事情,自己理解问题是一方面,想要说清楚又是另一方面。因为自己也是在学习中摸爬滚打,若有理解不当之处,请海涵,也请给予指正!

 

 类似资料: