二、黑箱:深层神经网络的不可解释性


首先从结构上来看,多层神经网络比单层神经网络多出了“中间层”。中间层常常被称为隐藏层(hidden layer),理论上来说可以有无限层,所以在图像表示中经常被省略。层数越多,神经网络的模型复杂度越高,一般也认为更深的神经网络可以解决更加复杂的问题。在学习中,通常我们最多只会设置3~5个隐藏层,但在实际工业场景中会更多。还记得这张图吗?当数据量够大时,现代神经网络层数越深,效果越好。

在一个神经网络中,更靠近输入层的层级相对于其他层级叫做"上层",更靠近输出层的则相对于其他层级叫做"下层"。若从输入层开始从左向右编号,则输入层为第0层,输出层为最后一层。除了输入层以外,每个神经元中都存在着对数据进行处理的数个函数。在我们的例子异或门(XOR)中,隐藏层中的函数是NAND函数和OR函数(也就是线性回归的加和函数+阶跃函数),输出层上的函数是AND函数。对于所有神经元和所有层而言,加和函数的部分都是一致的(都得到结果zzz),因此我们需要关注的是加和之外的那部分函数。在隐藏层中这个函数被称为激活函数,符号为h(z)h(z)h(z),在输出层中这个函数只是普通的连接函数,我们定义为是g(z)g(z)g(z)。我们的数据被逐层传递,每个下层的神经元都必须处理上层的神经元中的h(z)h(z)h(z)处理完毕的数据 ,整个流程本质是一个嵌套计算结果的过程。

在神经网络中,任意层上都有至少一个神经元,最上面的是常量神经元,连接常量神经元的箭头上的参数是截距bbb,剩余的是特征神经元,连接这些神经元的箭头上的参数都是权重www。神经元是从上至下进行编号,需要注意的是,常量神经元与特征神经元是分别编号的。和从0开始编号的层数不同,神经元是从1开始编号的。在异或门的例子中,含有1的偏差神经元是1号偏差神经元,含有特征的神经元则是1号特征神经元。

除了神经元和网络层,权重、偏差、神经元上的取值也存在编号。这些编号规律分别如下:

这些编号实在很复杂,因此在本次课程中,我将编号改写为如下情况:

有了这些编号说明,我们就可以用数学公式来表示从输入层传入到第一层隐藏层的信号了。以上一节中说明的XOR异或门为例子,对于仅有两个特征的单一样本而言,在第一层的第一个特征神经元中获得加和结果的式子可以表示为:
z11=b→1→1+x1w1→10→1+x2w2→10→1z_{1}^{1}=b_{\rightarrow 1}^{\rightarrow 1}+x_{1} w_{1 \rightarrow 1}^{0 \rightarrow 1}+x_{2} w_{2 \rightarrow 1}^{0 \rightarrow 1} z11​=b→1→1​+x1​w1→10→1​+x2​w2→10→1​
而隐藏层中被h(z)h(z)h(z)处理的公式可以写作:
σ11=h(z11)=h(b→11+x1w1→10→1+x2w2→10→1)\begin{aligned} \sigma_{1}^{1} &=h\left(z_{1}^{1}\right) \\ &=h\left(b_{\rightarrow 1}^{1}+x_{1} w_{1 \rightarrow 1}^{0 \rightarrow 1}+x_{2} w_{2 \rightarrow 1}^{0 \rightarrow 1}\right) \end{aligned} σ11​​=h(z11​)=h(b→11​+x1​w1→10→1​+x2​w2→10→1​)​
根据我们之前写的NAND函数,这里的h(z)h(z)h(z)为阶跃函数。
现在,我们用矩阵来表示数据从输入层传入到第一层,并在第一层的神经元中被处理成σ\sigmaσ的情况:
Z1=W1⋅X+B1\mathbf{Z}^{1}=\mathbf{W}^{1} \cdot \mathbf{X}+\mathbf{B}^{1} Z1=W1⋅X+B1
[z11z21]=[w1→10→1w1→20→1w2→10→1w2→20→1]∗[x1x2]+[b→1→1b→20]\left[\begin{array}{l} z_{1}^{1} \\ z_{2}^{1} \end{array}\right]=\left[\begin{array}{ll} w_{1 \rightarrow 1}^{0 \rightarrow 1} & w_{1 \rightarrow 2}^{0 \rightarrow 1} \\ w_{2 \rightarrow 1}^{0 \rightarrow 1} & w_{2 \rightarrow 2}^{0 \rightarrow 1} \end{array}\right] *\left[\begin{array}{l} x_{1} \\ x_{2} \end{array}\right]+\left[\begin{array}{l} b_{\rightarrow 1}^{\rightarrow 1} \\ b_{\rightarrow 2}^{0} \end{array}\right] [z11​z21​​]=[w1→10→1​w2→10→1​​w1→20→1​w2→20→1​​]∗[x1​x2​​]+[b→1→1​b→20​​]
矩阵结构表示为: (2,1)=(2,2)∗(2,1)+(2,1)\text { 矩阵结构表示为: }(2,1)=(2,2) *(2,1)+(2,1)  矩阵结构表示为: (2,1)=(2,2)∗(2,1)+(2,1)
=[x1w1→10→1+x2w1→20→1x1w2→10→1+x2w2→20→1]+[b→1→1b→1b→2]=[x1w1→10→1+x2w1→20→1+b→1→1x1w2→10→1+x2w2→20→1+b→2→1][σ11σ21]=[h(x1w1→10→1+x2w1→20→1+b→1→1)h(x1w2→10→1+x2w2→20→1+b→20)]\begin{aligned} &=\left[\begin{array}{l} x_{1} w_{1 \rightarrow 1}^{0 \rightarrow 1}+x_{2} w_{1 \rightarrow 2}^{0 \rightarrow 1} \\ x_{1} w_{2 \rightarrow 1}^{0 \rightarrow 1}+x_{2} w_{2 \rightarrow 2}^{0 \rightarrow 1} \end{array}\right]+\left[\begin{array}{l} b_{\rightarrow 1}^{\rightarrow 1} \\ b \rightarrow 1 \\ b \rightarrow 2 \end{array}\right] \\ &=\left[\begin{array}{l} x_{1} w_{1 \rightarrow 1}^{0 \rightarrow 1}+x_{2} w_{1 \rightarrow 2}^{0 \rightarrow 1}+b_{\rightarrow 1}^{\rightarrow 1} \\ x_{1} w_{2 \rightarrow 1}^{0 \rightarrow 1}+x_{2} w_{2 \rightarrow 2}^{0 \rightarrow 1}+b_{\rightarrow 2}^{\rightarrow 1} \end{array}\right] \\ \left[\begin{array}{c} \sigma_{1}^{1} \\ \sigma_{2}^{1} \end{array}\right] &=\left[\begin{array}{l} h\left(x_{1} w_{1 \rightarrow 1}^{0 \rightarrow 1}+x_{2} w_{1 \rightarrow 2}^{0 \rightarrow 1}+b_{\rightarrow 1}^{\rightarrow 1}\right) \\ h\left(x_{1} w_{2 \rightarrow 1}^{0 \rightarrow 1}+x_{2} w_{2 \rightarrow 2}^{0 \rightarrow 1}+b_{\rightarrow 2}^{0}\right) \end{array}\right] \end{aligned} [σ11​σ21​​]​=[x1​w1→10→1​+x2​w1→20→1​x1​w2→10→1​+x2​w2→20→1​​]+⎣⎡​b→1→1​b→1b→2​⎦⎤​=[x1​w1→10→1​+x2​w1→20→1​+b→1→1​x1​w2→10→1​+x2​w2→20→1​+b→2→1​​]=[h(x1​w1→10→1​+x2​w1→20→1​+b→1→1​)h(x1​w2→10→1​+x2​w2→20→1​+b→20​)​]​
相应的从中间层最下面的神经网络会得到的结果是σ12\sigma_{1}^{2}σ12​(如果是阶跃函数则是直接得到y)。σ\sigmaσ会作为中间层的结果继续传入下一层。如果我们继续向下嵌套,则可以得到:
z12=b→1→2+σ11w1→11→2+σ21w2→11→2σ12=g(z12)σ12=g(b→1−2+σ11w1→11→2+σ21w2→11→2)\begin{aligned} z_{1}^{2} &=b_{\rightarrow 1}^{\rightarrow 2}+\sigma_{1}^{1} w_{1 \rightarrow 1}^{1 \rightarrow 2}+\sigma_{2}^{1} w_{2 \rightarrow 1}^{1 \rightarrow 2} \\ \sigma_{1}^{2} &=g\left(z_{1}^{2}\right) \\ \sigma_{1}^{2} &=g\left(b_{\rightarrow 1}^{-2}+\sigma_{1}^{1} w_{1 \rightarrow 1}^{1 \rightarrow 2}+\sigma_{2}^{1} w_{2 \rightarrow 1}^{1 \rightarrow 2}\right) \end{aligned} z12​σ12​σ12​​=b→1→2​+σ11​w1→11→2​+σ21​w2→11→2​=g(z12​)=g(b→1−2​+σ11​w1→11→2​+σ21​w2→11→2​)​
由于第二层就已经是输出层了,因此第二层使用的函数是g(z)g(z)g(z),在这里,第二层的表达和第一层几乎一 模一样。相信各种编号在这里已经让人感觉到有些头疼了,虽然公式本身并不复杂,但涉及到神经网络不同的层以及每层上的神经元之间的数据流动,公式的编号会让人有所混淆。如果神经网络的层数继续增加,或每一层上神经元数量继续增加,神经网络的嵌套和计算就会变得更加复杂。

在实际中,我们的真实数据可能有超过数百甚至数千个特征,所以真实神经网络的复杂度是非常高,计算非常缓慢的。所以,当神经网络长成如下所示的模样,我们就无法理解中间过程了。我们不知道究竟有多少个系数,如何相互作用产生了我们的预测结果,因此神经网络的过程是一个“黑箱”。

在PyTorch中实现神经网络的时候,我们一般用不到这些复杂的数学符号,也不需要考虑这些嵌套流程,毕竟这些计算非常底层。那为什么我们还要学习这些符号呢?有以下几点原因:

1、利用数学的嵌套,我们可以很容易就理解深层神经网络为什么会随着层数的增多、每层上神经元个数的增多而变得复杂,从而理解“黑箱”究竟是怎样形成的

2、多层神经网络与单层神经网络在许多关键点上其实有所区别,这种区别使用代数表示形式会更容易显示。比如,单层神经网络(线性回归、逻辑回归)中直线的表现形式都是XwXwXw,且www是结构为(n_features,1)的列向量,但在多层神经网络中,随着“层”和神经元个数的增加,只有输入层与第一个隐藏层之间是特征与www的关系,隐藏层与隐藏层、隐藏层与输出层之间都是σ\sigmaσ与www的关系。并且,即便是在输入层与第一个隐藏层之间,单个特征所对应的www不再是列向量,而是结构为(上层特征神经元个数,下层特征神经元个数)的矩阵。并且,每两层神经元之间,都会存在一个权重矩阵,权重将无法直接追踪到特征x上,这也是多层神经网络无法被解释的一个关键原因。同时,为了让输出结果和都保持列向量的形式(与神经网络的图像匹配),XXX的结构也要顺应www的变化而变化(上文的推导中我们展现的是单 一样本仅有两个特征的情况,想想看多个样本多个特征会是什么样吧),相乘公式也变化为wXwXwX。这些细节在由单层推广到多层时,都会成为新手容易掉入的坑,基础不牢固的情况下,新手很可能在2层推广 到4层,2个特征推广到N个特征,甚至是1个样本推广到多个样本时被卡住。(W的结构会改变——权重矩阵,X从行变成列,Z变成W在前X在后)

3、嵌套的数学公式可以帮助我们更好地理解反向传播,以及更好地阅读其他教材。

不过,即便神经网络是一个神秘黑箱,我们依然可以对它进行一系列的探索。基于我们已经熟悉的三层神经网络XOR的结构,我们来提出问题。

三、探索多层神经网络:层 vs h(z)

我们首先想要发问的隐藏层的作用。在之前的XOR函数中,我们提出”多层神经网络能够描绘出一条曲线作为决策边界,以此为基础处理单层神经网络无法处理的复杂问题“,这可能让许多人产生了“是增加层数帮助了神经网络”的错觉。实际上并非如此。

在神经网络的隐藏层中,存在两个关键的元素,一个是加和函数∑\sum∑,另一个是h(z)h(z)h(z)。除了输入层之外,任何层的任何神经元上都会有加和的性质,因为神经元有“多进单出”的性质,可以一次性输入多个信号,但是输出只能有一个,因此输入神经元的信息必须以某种方式进行整合,否则神经元就无法将信息传递下去,而最容易的整合方式就是加和∑\sum∑。因此我们可以认为加和∑\sum∑是神经元自带的性质,只要增加更多的层,就会有更多的加和。但是h(z)h(z)h(z)的存在却不是如此,即便隐藏层上没有h(z)h(z)h(z)(或h(z)h(z)h(z)是一个恒等函数),神经网络依然可以从第一层走到最后一层。让我们来试试看,在XOR中,假设隐藏层上没有h(z)h(z)h(z)的话,会发生什么:

#回忆一下XOR数据的真实标签
xorgate = torch.tensor([0,1,1,0],dtype=torch.float32)def AND(X):w = torch.tensor([-0.2,0.15, 0.15], dtype = torch.float32)zhat = torch.mv(X,w)#下面这一行就是阶跃函数的表达式,注意AND函数是在输出层,所以保留输出层的阶跃函数g(z)andhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)return andhatdef OR(X):w = torch.tensor([-0.08,0.15,0.15], dtype = torch.float32) #在这里我修改了b的数值zhat = torch.mv(X,w)#注释掉阶跃函数,相当于h(z)是恒等函数#yhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)return zhatdef NAND(X):w = torch.tensor([0.23,-0.15,-0.15], dtype = torch.float32) zhat = torch.mv(X,w)#注释掉阶跃函数,相当于h(z)是恒等函数#yhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)return zhatdef XOR(X):#输入值:input_1 = X#中间层:sigma_nand = NAND(input_1)sigma_or = OR(input_1)x0 = torch.tensor([[1],[1],[1],[1]],dtype=torch.float32)#输出层:input_2 = torch.cat((x0,sigma_nand.view(4,1),sigma_or.view(4,1)),dim=1)y_and = AND(input_2)#print("NANE:",y_nand)#print("OR:",y_or)return y_andXOR(X)
#tensor([0., 0., 0., 0.])

很明显,此时XOR函数的预测结果与真实的xorgate不一致。当隐藏层的h(z)h(z)h(z)是恒等函数或不存在时,叠加层并不能够解决XOR这样的非线性问题。从数学上来看,这也非常容易理解。
从输入层到第1层:

从第1层到输出层:

不难发现,最终从输出层输出的结果和第一层的输出结果x1w111+x2w121+b11x_{1} w_{11}^{1}+x_{2} w_{12}^{1}+b_{1}^{1}x1​w111​+x2​w121​+b11​是类似的,只不过是乘以特征x1x_{1}x1​,x2x_{2}x2​的具体数值不同。在没有h(z)h(z)h(z)时,在层中流动的数据被做了仿射变换(affine transformation),仿射变换后得到的依然是一个线性方程,而这样的方程不能解决非线性问题。可见“层”本身不是神经网络解决非线性问题的关键,层上的h(z)h(z)h(z)才是。 从上面的例子和数学公式中可以看出,如果h(z)h(z)h(z)是线性函数,或不存在,那增加再多的层也没有用。

那是不是任意非线性函数作为h(z)h(z)h(z)都可以解决问题呢?让我们来试试看,在XOR例子中如果不使用阶跃函数,而使用sigmoid函数作为,会发生什么。

def AND(X):w = torch.tensor([-0.2,0.15, 0.15], dtype = torch.float32)zhat = torch.mv(X,w)#下面这一行就是阶跃函数的表达式,注意AND函数是在输出层,所以保留输出层的阶跃函数g(z)andhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)return andhatdef OR(X):w = torch.tensor([-0.08,0.15,0.15], dtype = torch.float32) #在这里我修改了b的数值zhat = torch.mv(X,w)#h(z), 使用sigmoid函数sigma = torch.sigmoid(zhat)return sigmadef NAND(X):w = torch.tensor([0.23,-0.15,-0.15], dtype = torch.float32) zhat = torch.mv(X,w)#h(z), 使用sigmoid函数sigma = torch.sigmoid(zhat)return sigmadef XOR(X):#输入值:input_1 = X#中间层:sigma_nand = NAND(input_1)sigma_or = OR(input_1)x0 = torch.tensor([[1],[1],[1],[1]],dtype=torch.float32)#输出层:input_2 = torch.cat((x0,sigma_nand.view(4,1),sigma_or.view(4,1)),dim=1)y_and = AND(input_2)#print("NANE:",y_nand)#print("OR:",y_or)return y_andXOR(X)
#tensor([0., 0., 0., 0.])

可以发现,如果将h(z)h(z)h(z)换成sigmoid函数,XOR结构的神经网络同样会失效!可见,即便是使用了h(z)h(z)h(z),也不一定能够解决曲线分类的问题。在不适合的非线性函数加持下,神经网络的层数再多也无法起效。所以,h(z)h(z)h(z)是真正能够让神经网络算法“活起来”的关键,没有搭配合适h(z)h(z)h(z)的神经网络结构是无用的,而h(z)h(z)h(z)正是神经网络中最关键的概念之一激活函数(activation function)。

四、激活函数


经过前面的介绍与铺垫,到这里相信大家已经充分理解激活函数的作用了。神经网络中可用的激活函数多达数十种(详情可以在激活函数的维基百科中找到:https://en.wikipedia.org/wiki/Activation_function),但机器学习中常用的激活函数只有恒等函数(identity function),阶跃函数(sign),sigmoid 函数,ReLU,tanh,softmax这六种,其中Softmax与恒等函数几乎不会出现在隐藏层上,Sign、Tanh 几乎不会出现在输出层上,ReLU与Sigmoid则是两种层都会出现,并且应用广泛。幸运的是,这6种函数我们在之前的课程中已经全部给大家介绍完毕。在这里,我们将总结性声明一下输出层的g(z)与隐藏层的h(z)之间的区别,以帮助大家获得更深的理解:

  1. 虽然都是激活函数,但隐藏层和输出层上的激活函数作用是完全不一样的。输出层的激活函数是为了让神经网络能够输出不同类型的标签而存在的。其中恒等函数用于回归,sigmoid函数用于二分类,softmax用于多分类。换句说,g(z)g(z)g(z)仅仅与输出结果的表现形式有关,与神经网络的效果无关,也因此它可以使用线性的恒等函数。但隐藏层的激活函数就不同了,如我们之前尝试的XOR,隐藏层上的激活函数h(z)h(z)h(z)的选择会影响神经网络的效果,而线性的是会让神经网络的结构失效的。
  2. 在同一个神经网络中,g(z)g(z)g(z)与h(z)h(z)h(z)可以是不同的,并且在大多数运行回归和多分类的神经网络时,他们也的确是不同的。每层上的h(z)h(z)h(z)可以是不同的,但是同一层上的激活函数必须一致。

我们可以通过下面的这段代码来实际体会一下,h(z)h(z)h(z)影响模型效果,而g(z)g(z)g(z)只影响模型输出结果的形式的事实。之前我们曾经尝试过以下几种情况:

现在我们来试试看,隐藏层上的h(z)h(z)h(z)是阶跃函数,而输出层的g(z)g(z)g(z)是sigmoid的情况。如果XOR网络依然有效,就证明了g(z)g(z)g(z)的变化对神经网络结果输出无影响。反之,则说明也影响神经网络输出结果。

#如果g(z)是sigmoid函数,而h(z)是阶跃函数
#输出层,以0.5为sigmoid的阈值
def AND(X):w = torch.tensor([-0.2,0.15, 0.15], dtype = torch.float32)zhat = torch.mv(X,w)sigma = torch.sigmoid(zhat)andhat = torch.tensor([int(x) for x in sigma >= 0.5],dtype=torch.float32)return andhat#隐藏层,OR与NAND都使用阶跃函数作为h(z)
def OR(X):w = torch.tensor([-0.08,0.15,0.15], dtype = torch.float32) #在这里我修改了b的数值zhat = torch.mv(X,w)yhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)return yhatdef NAND(X):w = torch.tensor([0.23,-0.15,-0.15], dtype = torch.float32) zhat = torch.mv(X,w)yhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)return yhatdef XOR(X):#输入值:input_1 = X#中间层:sigma_nand = NAND(input_1)sigma_or = OR(input_1)x0 = torch.tensor([[1],[1],[1],[1]],dtype=torch.float32)#输出层:input_2 = torch.cat((x0,sigma_nand.view(4,1),sigma_or.view(4,1)),dim=1)y_and = AND(input_2)#print("NANE:",y_nand)#print("OR:",y_or)return y_andXOR(X)
#tensor([0., 1., 1., 0.])

Lesson 9.29.39.4 黑箱:不可解释的深层神经网络探索多层神经网络:层vsh(z)相关推荐

  1. r语言 tunerf_R语言机器学习:caret包使用及其黑箱模型解释(连续变量预测)

    原标题:R语言机器学习:caret包使用及其黑箱模型解释(连续变量预测) 作者:黄天元,复旦大学博士在读,目前研究涉及文本挖掘.社交网络分析和机器学习等.希望与大家分享学习经验,推广并加深R语言在业界 ...

  2. 风控模型黑箱可解释,试下这个方法来演示

    模型的开发,目前在互金领域场景中因为变量多,开发周期短,目前用得最多的就是XGB.LGB这类的机器学习模型. 比如我们之前跟大家输出的关于个人信贷反欺诈评分卡的开发内容里,我们用的就是lightgbm ...

  3. Facebook:易于解释的神经元可能会阻碍深度神经网络的学习

    选自 Facebook AI 博客 作者:Matthew Leavitt.Ari Morcos 机器之心编译 编辑:张倩.杜伟 易于解释的神经元对于提升神经网络的性能来说是必要的吗?Facebook ...

  4. 详细解释卷积神经网络CNN中卷积层以及BN层的参数

    问题的提出 在做关于python的卷积神经网络的项目中,发现了一个卷积层加一个BN层竟然一共有6个参数.百思不得其解. if batch_norm:layers += [nn.Conv2d(in_ch ...

  5. Lesson 9.5 从0实现多层神经网络的正向传播

    五.从0实现深度神经网络的正向传播 学到这里,我们已经学完了一个普通深度神经网络全部的基本元素--用来构筑神经网络的结构的层与激活函数,输入神经网络的数据(特征.权重.截距),并且我们了解从左向右的过 ...

  6. rnn 递归神经网络_递归神经网络rnn的简单解释

    rnn 递归神经网络 Recurrent neural network is a type of neural network used to deal specifically with seque ...

  7. 解释:《深度探索C++对象模型》对NRV优化的讨论

    原文地址:http://blog.csdn.net/zha_1525515/article/details/7170059 感谢作者! 大纲: 函数返回局部对象的拷贝的一般实现方式. NRV(Name ...

  8. 【可解释】|深层网络的公理化属性(Axiomatic Attribution for Deep Networks)

    Axiomatic Attribution for Deep Networks, ICML 2017 研究了将深层网络的预测归因于其输入特征的问题, 简单的说就是通过研究输入与输出的关系,去理解模型的 ...

  9. 如何简单形象又有趣地讲解神经网络是什么?(知乎) 说的人很多,理解很充分

    https://www.zhihu.com/question/22553761 如何简单形象又有趣地讲解神经网络是什么? 有网友在 Quora 上提问:对于那些非计算机科学行业的人,你会如何向他们解释 ...

最新文章

  1. How to add and configure jetty server in maven pom.xml
  2. fatal: unable to access : The requested URL returned error: 403
  3. [创业经验] 白手起家的艺术
  4. wxWidgets:wxHtml 测试示例
  5. 如何在Mac上安装win10正版系统
  6. css多行超出时,超出高度,显示省略号
  7. arduino 光控灯_Arduino光控开关
  8. 页面字符编码不一致的处理
  9. excel制作一个信息录入系统_Excel数据总是重复录入?使用这招,让系统帮你做检查,非常实用...
  10. Silverlight 获得鼠标位置
  11. spring mvc 异常处理手动回滚 SQL log不回滚
  12. Git-第二篇廖雪峰Git教程学习笔记(1)基本命令,版本回退
  13. Gradle删除本地库文件
  14. 即刻VR 唯快不破——2021服贸会的黑科技应用侧记
  15. MacOS - MacBook - 推荐工具收集
  16. 开发板——在X210开发板上进行裸机开发的流程
  17. golang时间字符串转时间戳
  18. Win系统蓝牙设备删除失败 - 解决方案
  19. html中背景不平铺怎么写,css怎么让背景图片不平铺?
  20. 如何实现动易官方网站内容页的移动菜单效果?

热门文章

  1. 达梦数据库日期格式化_【干货分享】DM7中时间类型的使用介绍
  2. python-docx中文开发文档_使用Python语言-docx生成Word文档
  3. 互动事件之触摸屏互动
  4. 计算机网络考研复试速成 - 知识点精炼 - 背诵版
  5. 调频 调幅 调相 与 通信
  6. 汇编语言指令及七种寻址方式指令实现
  7. Delphi在FireMonkey下自动创建SQLite数据库
  8. 主题:关于Qt显示的刷新效率
  9. 报错Failed to decode param
  10. 安卓逆向_12 --- jeb工具的使用 ( 动态调试 smali 代码 【 普通调试 和 debug调试 】)...