这里把按 [1] 推导的BP算法(Backpropagation)步骤整理一下。突然想整理这个的原因是知乎上看到了一个帅呆了的求矩阵微分的方法(也就是 [2]),不得不感叹作者的功力。[1] 中直接使用矩阵微分的记号进行推导,整个过程十分简洁。而且这种矩阵形式有一个非常大的优势就是对照其进行编程实现时非常方便。

但其实用标量计算推导也有一定的好处,比如可以清楚地知道某个权重是被谁所影响的。

前向传播过程:多层Logistic回归

记号约定:

$L$:神经网络的层数。输入层不算。

$n^l$:第 $l$ 层神经元的个数。偏置神经元不算在内。

$W^{l}\in\mathbb R^{n^l\times n^{l-1}}$:第 $l-1$ 层到第 $l$ 层的权重矩阵。其中,$W_{ij}^{(l)}$ 表示第 $l-1$ 层第 $j$ 个神经元到第 $l$ 层第 $i$ 个神经元的权重。

$\textbf b^{(l)}\in\mathbb R^{n^l}$ :第 $l$ 层的偏置。

$\textbf z^{(l)}\in\mathbb R^{n^l}$ :第 $l$ 层各个神经元的输入。

$f_l(\cdot)$ :第 $l$ 层的激活函数。对于分类任务来说,最后一层为softmax。

$\textbf a^{(l)}\in\mathbb R^{n^l}$ :第 $l$ 层各个神经元的输出,也就是活性值。对于输入层(第0层),$\textbf a^{(0)}=\textbf x$。

下图给出了相应的图示。

图片来源:[1]

下面可以开始推导了。首先是前向传播,计算网络输出:

$$\textbf z^{(l)}=W^{(l)}\textbf a^{(l-1)}+\textbf b^{(l)}$$

$$\textbf a^{(l)}=f_l(\textbf z^{(l)})=f_l(W^{(l)}\textbf a^{(l-1)}+\textbf b^{(l)})$$

$$\textbf a^{(L)}=f(\textbf x;W,\textbf b)$$

整个网络实际上就可以看作是一个函数 $f$ ,1989年已经有人证明了单隐层网络可以逼近任意函数。

既然是完成从输入到输出的映射,那么网络除了可以看作分类器之外,还可以看作是特征提取器,再将网络的输出作为分类器的输入,相当于把原特征 $\textbf x$ 映射成了新的特征 $\textbf a^{(L)}$ 。

如果把网络看作分类器,那么最后一层的神经元个数 $n^L$ 应等于类别个数 $C$ ,且 $f_L(\cdot)=\text{softmax}(\cdot)$ ,归一化成概率分布:

$$\hat{\textbf y}=\textbf a^{(L)}=\text{softmax}(\textbf z^{(L)})=\frac{\exp(\textbf z^{(L)})}{\textbf 1^{\top}\exp(\textbf z^{(L)})}\in\mathbb R^C$$

需要注意的是分子是列向量,分母是标量。$\hat{\textbf y}$ 的每一维 $\hat{\textbf y}_i$ 表示的是网络 $f$ 给出的样本 $\textbf x$ 属于第 $i$ 类的概率。

给定样本 $(\textbf x,\textbf y)$ ,其中 $\textbf y\in\mathbb R^C$ 是one-hot向量,那么使用交叉熵损失函数,得到网络对一个样本的损失为

$$\mathcal L(\textbf y,f(\textbf x;W,\textbf b))=-\textbf y^{\top}\ln\hat{\textbf y}$$

设训练样本数为 $N$ ,那么经验风险为

$$R=\frac1N\sum_{i=1}^N\mathcal L(\textbf y_i,f(\textbf x_i;W,\textbf b))$$

如果使用权重矩阵的Frobenius范数作为正则项

$$||W||_F=\biggl(\sum_{l=1}^L\sum_{i=1}^{n^l}\sum_{j=1}^{n^{l-1}}(W_{ij}^{(l)})^2\biggr)^{\frac12}$$

那么结构风险为

$$R=\frac1N\sum_{i=1}^N\mathcal L(\textbf y_i,f(\textbf x_i;W,\textbf b))+\frac12\lambda||W||_F^2$$

一般来说,偏置是不加正则的。

现在考虑结构风险最小化,使用梯度下降法来更新权重矩阵和偏置。只需要求取模型对一个样本的损失的梯度 $\frac{\partial \mathcal L(\textbf y,f(\textbf x;W,\textbf b))}{\partial W^{(l)}}$(因为对多个样本的梯度就是对一个样本的梯度的累加):

$$W^{(l)}=W^{(l)}-\alpha \frac{\partial R}{\partial W^{(l)}}$$

$$\frac{\partial R}{\partial W^{(l)}}=\frac1N\sum_{i=1}^N\frac{\partial \mathcal L(\textbf y,f(\textbf x;W,\textbf b))}{\partial W^{(l)}}+\lambda W^{(l)}$$

对偏置的就不写了。

反向传播(Backpropagation,BP)算法

好了,墨迹了半天,重点终于来了。下面就是要求解 $\frac{\partial\mathcal L(\textbf y,f(\textbf x;W,\textbf b))}{\partial W^{(l)}}$ 、$\frac{\partial\mathcal L(\textbf y,f(\textbf x;W,\textbf b))}{\partial \textbf b^{(l)}}$ 。为了简单起见,直接记作 $\frac{\partial \mathcal L}{\partial W^{(l)}}$ 、$\frac{\partial \mathcal L}{\partial \textbf b^{(l)}}$ 。

我所有的符号都是沿用的 [1] ,后面的推导过程也是。

首先摆上一些公式复习一下:

$$\frac{\partial A^{\top}\textbf x}{\partial\textbf x}=\frac{\partial \textbf x^{\top}A}{\partial\textbf x}=A$$

$$\frac{\partial \textbf y^{\top}\textbf z}{\partial\textbf x}=\frac{\partial \textbf y}{\partial\textbf x}\textbf z+\frac{\partial \textbf z}{\partial\textbf x}\textbf y$$

$$\frac{\partial \textbf y^{\top}A\textbf z}{\partial\textbf x}=\frac{\partial \textbf y}{\partial\textbf x}A\textbf z+\frac{\partial \textbf z}{\partial\textbf x}A^{\top}\textbf y$$

$$\frac{\partial y\textbf z}{\partial\textbf x}=\frac{\partial y}{\partial\textbf x}\textbf z^{\top}+y\frac{\partial \textbf z}{\partial\textbf x}$$

$$\frac{\partial \text{tr}AB}{\partial A}=B^{\top}\quad\quad\frac{\partial \text{tr}AB}{\partial A^{\top}}=B$$

$$\frac{\partial f(A)}{\partial A^{\top}}=(\frac{\partial f(A)}{\partial A})^{\top}$$

然后是链式法则(chain rule):

$$\frac{\partial \textbf z}{\partial \textbf x}=\frac{\partial \textbf y}{\partial \textbf x}\frac{\partial \textbf z}{\partial \textbf y}$$

$$\frac{\partial z}{\partial X_{ij}}=(\frac{\partial z}{\partial\textbf y})^{\top}\frac{\partial\textbf y}{\partial X_{ij}}$$

$$\frac{\partial z}{\partial X_{ij}}=\text{tr}\biggl((\frac{\partial z}{\partial Y})^{\top}\frac{\partial Y}{\partial X_{ij}}\biggr)$$

以及逐元素计算的函数 $f$ (其导函数为 $f'$ )及其梯度:

$$\frac{\partial f(\textbf x)}{\partial\textbf x}=\text{diag}(f'(\textbf x))$$

这里需要声明一点,上面这套计算体系里,都是使用的分母布局,也就是说:维度为 $p$ 的列向量对维度为 $q$ 的列向量求导后得到的矩阵维数为 $q\times p$ 。关于矩阵求导,可以参考 [3] 。

又墨迹了半天,下面开始求 $\frac{\partial \mathcal L}{\partial W^{(l)}}$ 、$\frac{\partial \mathcal L}{\partial \textbf b^{(l)}}$ 。

      BP的四个重要公式

下面这第一步在我认为是最重要的一步,因为它的思路很巧妙 —— 现在的目标是求标量对矩阵的导数,直接用chain rule拆成两块的话会发现:一块是标量对向量的导数,好办;还有一块是向量对矩阵的导数,不好办。所以 [1] 使用了一种很巧的方式,求标量对矩阵单个元素(标量)的导数,然后再归纳成矩阵形式。

根据链式法则,

$$\frac{\partial\mathcal L}{\partial W_{ij}^{(l)}}=(\frac{\partial\mathcal L}{\partial \textbf z^{(l)}})^{\top}\frac{\partial \textbf z^{(l)}}{\partial W_{ij}^{(l)}}$$

现在定义误差项 $\delta^{(l)}=\dfrac{\partial\mathcal L}{\partial \textbf z^{(l)}}\in\mathbb R^{n^l}$ ,用来表征第 $l$ 层的神经元对于误差的敏感程度。

先求 $\frac{\partial \textbf z^{(l)}}{\partial W_{ij}^{(l)}}$ :

$$\frac{\partial \textbf z^{(l)}}{\partial W_{ij}^{(l)}}=\frac{\partial W^{(l)}\textbf a^{(l-1)}}{\partial W_{ij}^{(l)}}=(0,...,a_j^{(l-1)},...,0)^{\top}$$

其中只有第 $i$ 行的元素非零。既然这样,那么

$$\frac{\partial\mathcal L}{\partial W_{ij}^{(l)}}=\delta_i^{(l)}a_j^{(l-1)}$$

从这个式子可以看出,如果神经元的输出值 $a_j^{(l-1)}$ 很小,那么它连向下一层的权重 $W_{ij}^{(l)}$ 的更新就会很缓慢。

写成矩阵的形式,就是下面的式子:

$$\frac{\partial\mathcal L}{\partial W^{(l)}}=\delta^{(l)}(\textbf a^{(l-1)})^{\top}$$

$$\frac{\partial\mathcal L}{\partial \textbf b^{(l)}}=\delta^{(l)}$$

下面的问题就是如何计算误差项 $\delta^{(l)}$ 。对于误差项的计算,需要分两种情况考虑,一种情况是输出层,另一种情况是隐层。

对于输出层来说,

$$\begin{aligned}\delta^{(L)}&=\frac{\partial \mathcal L}{\partial \textbf z^{(L)}}\\&=\frac{\partial \textbf a^{(L)}}{\partial \textbf z^{(L)}}\frac{\partial \mathcal L}{\partial \textbf a^{(L)}}\\&=\text{diag}(f_L'(\textbf z^{(L)}))\frac{\partial \mathcal L}{\partial \textbf a^{(L)}}\\&=f_L'(\textbf z^{(L)})\odot \frac{\partial \mathcal L}{\partial \textbf a^{(L)}}\end{aligned}$$

对于隐层来说,

$$\begin{aligned}\delta^{(l)}&=\frac{\partial \mathcal L}{\partial \textbf z^{(l)}}\\&=\frac{\partial \textbf a^{(l)}}{\partial \textbf z^{(l)}}\frac{\partial\textbf z^{(l+1)}}{\partial \textbf a^{(l)}}\frac{\partial \mathcal L}{\partial \textbf z^{(l+1)}}\\&=\text{diag}(f_l'(\textbf z^{(l)}))(W^{(l+1)})^{\top}\delta^{(l+1)}\\&=f_l'(\textbf z^{(l)})\odot \bigl((W^{(l+1)})^{\top}\delta^{(l+1)}\bigr)\end{aligned}$$

从这个式子就可以看出,误差项可以逐层往输入的方向传递,这就是所谓的误差反向传播。

多提一句,这里出现了激活函数的导函数。有以下两个常用关系:

$$\sigma'(\cdot)=\sigma(\cdot)\odot (\textbf 1-\sigma(\cdot))$$

$$\text{softmax}'(\cdot)=\text{softmax}(\cdot)\odot (\textbf 1-\text{softmax}(\cdot))$$

其中 $\sigma(\cdot)$ 代表logistic函数。

所以,BP算法归结到最后其实只有四个式子比较重要:

$$\delta^{(L)}=f_L'(\textbf z^{(L)})\odot \frac{\partial \mathcal L}{\partial \textbf a^{(L)}}$$

$$\delta^{(l)}=f_l'(\textbf z^{(l)})\odot \bigl((W^{(l+1)})^{\top}\delta^{(l+1)}\bigr)$$

$$\frac{\partial \mathcal L}{\partial W^{(l)}}=\delta^{(l)}(\textbf a^{(l-1)})^{\top}$$

$$\frac{\partial \mathcal L}{\partial \textbf b^{(l)}}=\delta^{(l)}$$

BP算法与Delta规则的比较

1. 与二类感知器(只有一层,所以不需要看第二个式子)的Delta规则的比较:BP算法 $\delta^{(L)}$ 表达式中的 $f_L'(\textbf z^{(L)})$ 这项是额外多出来的。这也不难理解,因为二类感知器的最后是经过一个与0.5比较大小的阈值函数,这里则是Softmax函数(多分类)或Logistic函数(二分类)。

二类感知器中准则函数对参数的梯度就是用误差(被错误分类的样本为-1,被正确分类的样本为0,对应到这里就是 $\dfrac{\partial \mathcal L}{\partial \textbf a^{(L)}}$ )乘以样本的特征(对应到这里就是上一层输出 $\textbf a^{(L-1)}$ )。

2. 另外可以看出,网络的各个层的误差项之间是相乘的关系,那么当层数很深的时候就可能会出现梯度消失/梯度爆炸的问题。

我也用标量的形式推导过,过程如下……

无论标量形式还是矩阵形式,关键就是一点 —— chain rule。

输出层的误差项 $\delta^{(L)}$求解

上面四个式子只是形式化地给出了反向传播算法的梯度形式。下面求取:对于分类问题,也就是 $f_L(\cdot)=\text{softmax}(\cdot)$ ,并且使用交叉熵损失函数 $\mathcal L(\textbf y,f(\textbf x;W,\textbf b))=-\textbf y^{\top}\log\hat{\textbf y}$ 时,输出层的误差项 $\delta^{(L)}=\dfrac{\partial \mathcal L}{\partial \textbf z^{(L)}}$ 到底是个什么形式。

其实到这里问题已经很清楚了:这和Softmax回归模型是一样一样的,这里的 $\delta^{(L)}$ 在Softmax回归模型的求梯度过程中是一个中间结果。有三种方法:

(1)普通方法,按部就班地一步步推,可以参考我很早之前写的一篇关于word2vec的博客

(2)[1] 中的方法(第三章,在Softmax回归那部分);

(3)[2] 中的方法。

下面是基于 [2] 所给方法的完整求解过程。为了简洁起见,所有的 $\textbf z^{(L)}$ 都记作 $\textbf z$:

[2]介绍的是这样形式的求导:已知矩阵 $X$ ,函数 $f(X)$ 的函数值为标量,求 $\dfrac{\partial f}{\partial X}$ 。一种典型的例子就是求取损失对权重矩阵的导数。

对于一元微积分,$\text{d}f=f'(x)\text{d}x$ ;多元微积分,$\text{d}f=\sum_i\dfrac{\partial f}{\partial x_i}\text{d}x_i=(\dfrac{\partial f}{\partial \textbf x})^{\top}\text{d}\textbf x$;由此建立矩阵导数和微分的联系:

$$\text{d}f=\sum_{i,j}\frac{\partial f}{\partial X_{ij}}\text{d}X_{ij}=\text{tr}((\frac{\partial f}{\partial X})^{\top}\text{d}X)$$

上式第二个等号成立是因为对于两个同阶方阵有 $\text{tr}(A^{\top}B)=\sum_{i,j}A_{ij}B_{ij}$ 。求解的流程就是,先求微分 $\text{d}f$ 表达式,然后再套上迹(因为标量的迹等于标量本身),然后再把表达式 $\text{tr}(\text{d}f)$ 和 $\text{tr}((\dfrac{\partial f}{\partial X})^{\top}\text{d}X)$ 进行比对,进而把 $\dfrac{\partial f}{\partial X}$ 给“挖”出来。

所以,问题就从求梯度转化成了求微分。求微分当然少不了很多法则和技巧,下面随着讲随着介绍。

首先求取 $\text{d} \mathcal L$ 。

$$\begin{aligned} \mathcal L&=-\textbf y^{\top}\ln\frac{\exp(\textbf z)}{\textbf 1^{\top}\exp(\textbf z)}\\&=-\textbf y^{\top}(\textbf z-\ln\begin{pmatrix}\textbf 1^{\top}\exp(\textbf z) \\ \textbf 1^{\top}\exp(\textbf z) \\ \vdots \\ \textbf 1^{\top}\exp(\textbf z)\end{pmatrix})\quad \textbf 1^{\top}\exp(\textbf z)\text{是标量}\\&=\ln(\textbf 1^{\top}\exp(\textbf z))-\textbf y^{\top}\textbf z\end{aligned}$$

根据法则 $\text{d}(g(X))=g'(X)\odot\text{d}X$ 、$\text{d}(XY)=(\text{d}X)Y+X(\text{d}Y)$,可得

$$\text{d}(\ln(\textbf 1^{\top}\exp(\textbf z)))=\frac{1}{\textbf 1^{\top}\exp(\textbf z)}\odot\text{d}(\textbf 1^{\top}\exp(\textbf z))$$

$$\text{d}(\textbf 1^{\top}\exp(\textbf z))=\textbf 1^{\top}\text{d}(\exp(\textbf z))=\textbf 1^{\top}(\exp(\textbf z)\odot\text{d}\textbf z)$$

所以

$$\text{d} \mathcal L=\frac{\textbf 1^{\top}(\exp(\textbf z)\odot\text{d}\textbf z)}{\textbf 1^{\top}\exp(\textbf z)}-\textbf y^{\top}\text{d}\textbf z$$

现在可以套上迹,根据恒等式 $\text{tr}(A^{\top}(B\odot C))=\text{tr}((A\odot B)^{\top}C)=\sum_{i,j}A_{ij}B_{ij}C_{ij}$ ,可得

$$\begin{aligned}\text{d}  \mathcal L&=\text{tr}(\frac{(\textbf 1\odot \exp(\textbf z))^{\top}\text{d}\textbf z}{\textbf 1^{\top}\exp(\textbf z)})-\text{tr}(\textbf y^{\top}\text{d}\textbf z)\\&=\text{tr}(\biggl(\frac{(\exp(\textbf z))^{\top}}{\textbf 1^{\top}\exp(\textbf z)}-\textbf y^{\top}\biggr)\text{d}\textbf z)\\&=\text{tr}((\hat{\textbf y}-\textbf y)^{\top}\text{d}\textbf z)\\&=\text{tr}((\frac{\partial\mathcal L}{\partial\textbf z})^{\top}\text{d}\textbf z)\end{aligned}$$

这样就得到了 $\delta^{(L)}$ 的形式:

$$\delta^{(L)}=\frac{\partial \mathcal L}{\partial \textbf z^{(L)}}=\hat{\textbf y}-\textbf y$$

这样就不难看出为什么将 $\delta^{(l)}$ 称为误差项了。

学习率自适应

众所周知,现在有很多算法用来改进SGD,以求更好的训练效果(时间上 & 精度上)。下面这张图总结了包括加动量、自适应学习率等众多改进算法的更新公式,如果想详细学习其中的理论,可以看 [1] ,也可以看Yoshua Bengio的那本最新出版的《Deep Learning》。对于这些算法,我认为没有最好,只有最合适。在自己的任务上何者可以取得最满意的效果,还是要多跑几组实验来确定。反正在TensorFlow里就是改个函数的事情,但是挑出最适合自己任务的那一个,还是挺不容易的(其实我都是直接用Adam,逃。。。

图片来源:http://weibo.com/1657470871/EACKVoLTI?type=comment#_rnd1490771184496

从这开始的内容与标题无关……

之前做过一个小实验:裸写一个单隐层神经网络(实在是很简单),隐层激活函数使用tanh、损失函数使用平方损失函数,样本特征为三维、共三个类,每类10个点。只做训练用,没有测试集。

1. 改变隐层节点个数:使用批处理梯度下降(因为数据量小到可以忽略),迭代500轮后,训练集上的loss变化(图里iters改成epoch更合适)如下图,可以看出来网络表示能力大体上增强(由于随机初始化的原因,每次实验结果可能不同)。

2. 改变学习率:较大的学习率在早期会让loss下降很快,但是后期可能会震荡。

参考资料:

[1] 《神经网络与深度学习讲义》

[2] 《矩阵求导术(上)》

[3] Matrix_calculus

机器学习 —— 基础整理(七)前馈神经网络的BP反向传播算法步骤整理的更多相关文章

  1. 【深度学习】BP反向传播算法Python简单实现

    转载:火烫火烫的 个人觉得BP反向传播是深度学习的一个基础,所以很有必要把反向传播算法好好学一下 得益于一步一步弄懂反向传播的例子这篇文章,给出一个例子来说明反向传播 不过是英文的,如果你感觉不好阅读 ...

  2. 手推机器学习公式(一) —— BP 反向传播算法

    方便起见,本文仅以三层的神经网络举例. f(⋅):表示激励函数 xi:表示输入层: yj:表示中间的隐层: yj=f(netj) netj=∑i=0nvijxi ok:表示输出层,dk 则表示期望输出 ...

  3. 【神经网络】BP反向传播神经网络

    BP算法细节 参数说明:假设有n层.J表示代价函数,和上面的E是同样的意思,只不过用不同的字母写而已. 分析:要想知道第l层的第i个结点的残差,必须知道层已经计算出来了残差,你只要把后面一层的每个结点 ...

  4. BP反向传播算法的工作原理How the backpropagation algorithm works

    In the last chapter we saw how neural networks can learn their weights and biases using the gradient ...

  5. 神经网络(NN)+反向传播算法(Backpropagation/BP)+交叉熵+softmax原理分析

    神经网络如何利用反向传播算法进行参数更新,加入交叉熵和softmax又会如何变化? 其中的数学原理分析:请点击这里.

  6. 循环神经网络(RNN)模型与前向反向传播算法

    在前面我们讲到了DNN,以及DNN的特例CNN的模型和前向反向传播算法,这些算法都是前向反馈的,模型的输出和模型本身没有关联关系.今天我们就讨论另一类输出和模型间有反馈的神经网络:循环神经网络(Rec ...

  7. 神经网络与机器学习 笔记—反向传播算法(BP)

    先看下面信号流图,L=2和M0=M1=M2=M3=3的情况,上面是前向通过,下面部分是反向通过. 1.初始化.假设没有先验知识可用,可以以一个一致分布来随机的挑选突触权值和阈值,这个分布选择为均值等于 ...

  8. 神经网络训练中的Tricks之高效BP(反向传播算法)

    神经网络训练中的Tricks之高效BP(反向传播算法) 神经网络训练中的Tricks之高效BP(反向传播算法) zouxy09@qq.com http://blog.csdn.net/zouxy09 ...

  9. 深度神经网络(DNN)反向传播算法(BP)

    在深度神经网络(DNN)模型与前向传播算法中,我们对DNN的模型和前向传播算法做了总结,这里我们更进一步,对DNN的反向传播算法(Back Propagation,BP)做一个总结. 1. DNN反向 ...

随机推荐

  1. Vulkan Tutorial 13 Render passes

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 Setup 在我们完成管线的创建工作,我们接下来需要告诉Vulkan渲染时候使用的f ...

  2. 关于cas server无法通过session持久化方式实现集群的问题

    最近在搭建cas单点登录系统 ,在建立集群时发生一个问题. 搭建的环境是tomcat+tomcat-redis-session-manager+redis+cas 在对tomcat的session进行 ...

  3. vue初学实践之路——vue简单日历组件(1)

    ---恢复内容开始--- 最近做的项目有一个需求,需要有一个日历组件供预定功能使用,之前的代码过于繁琐复杂,所以我采用vue重写了这个组件. npm.vue等等安装. 只是一个简单的日历组件,所以并不 ...

  4. 将页面内容转换Pdf\Word\Excel格式

    项目中用到了将邮件内容转换为Pdf.Word.Excel格式,做为邮件附件发送. 查了一些解决方案,走了一些弯路.以此代码记录下. 转换PDF需要下载NReco.PdfGenerator.dll 以下 ...

  5. .NET链接Oracle 参数绑定问题

    在.NET项目中链接Oracle使用的驱动是 Oracle.ManagedDataAccess.dll ,这里下载 所遇到的问题 使用存储过程一个参数没有问题,发现两个或两个以上会有参数没传过来的现象 ...

  6. Perl格式化输出

    Perl格式化输出 问题阐述 有时我们需要大量的重复数据,使用手工易于出错及比较繁琐.抓取特征,可以使用Perl脚本轻松搞定. 输出数据格式 主要特点 随机数生成 格式化输出 序列递增 Perl脚本 ...

  7. PHPStorm中的快捷键

    1.Ctrl+Alt+L 格式化代码 2.windows下按下快捷`Ctrl`+`Shift`+`-`,这样就能折叠所有代码了. 3.windows下按下快捷`Ctrl`+`Shift`+`+`,这样 ...

  8. 使用jQuery修改动态修改超链接

    以下是修改a元素标签的href链接和文字的代码: <script type="text/javascript" src="jquery-1.9.1.min.js&q ...

  9. 【转载】Static 关键字的作用

    原始日期:2016-07-16 17:53   一   普通的static关键字 1.  静态全局变量 在全局变量前,加上关键字static,该变量就被定义成为一个静态全局变量.我们先举一个静态全局变 ...

  10. 解决kubuntu(KDE4.8.5桌面环境)找不到中文语言包

    原始日期:2013-12-30 23:16 接触linux的想必都知道KDE平台,kde精美的界面是其一大特色,不过美中不足的是,很多新手在安装完KDE后,界面包括菜单选项等都是英文界面,对于英语水平 ...