├── README.md ├── cs231n ├── assignment │ ├── assignment1 │ │ ├── knn.md │ │ ├── softmax.md │ │ ├── svm.md │ │ └── two_layer_network.md │ ├── assignment2 │ │ ├── BatchNorm.md │ │ ├── CNN.md │ │ ├── Dropout.md │ │ ├── FCN.md │ │ └── MaxPool.md │ └── readme.md ├── note1.md ├── note2.md ├── note3.md ├── note4.md ├── note5.md ├── note6.md ├── note7.md ├── note8.py └── note9.md ├── 数学知识点 └── 知识点总结1.md ├── 数据结构 ├── DP.py ├── array.py ├── back_trace.py ├── bin_tree.py ├── lst.py ├── num.py ├── search.py ├── sort.py ├── step.py ├── str.py ├── sum.py ├── t.cpp ├── tree.py ├── tree_sym.py └── 经典例题.txt ├── 概率论 └── 概率论-智力题.md ├── 深度学习知识点 ├── 大佬知识点.md ├── 知识点整理1.md ├── 知识点整理2.md └── 知识点整理3.md ├── 计算机学科基础知识 └── 计网操作系统等.md └── 语言基础 ├── C++ └── 面试常见问题.md ├── Python └── 面试常见问题.md └── shell └── 学习资料.txt /README.md: -------------------------------------------------------------------------------- 1 | ### INTERVIEW 2 | 这是我在找工作期间做的一些笔记和一些准备,数据结构部分比较混乱,可以自己直接去刷题。我本人也比较菜有什么问题欢迎交流。 3 | 4 | ### 面试问题 5 | [深度学习岗位面试问题整理笔记](https://blog.csdn.net/lj6052317/article/details/78242026) 6 | 7 | [视觉计算/深度学习/人工智能 笔试面试 汇总(腾讯、网易、yy、美图等)](https://blog.csdn.net/u014722627/article/details/77938703) 8 | 9 | [Must Know Tips/Tricks in Deep Neural Networks](http://lamda.nju.edu.cn/weixs/project/CNNTricks/CNNTricks.html) 10 | 11 | [那些深度学习《面试》你可能需要知道的](https://zhuanlan.zhihu.com/p/29965072) 12 | 13 | [2017秋招面试总结:计算机视觉/深度学习算法](https://zhuanlan.zhihu.com/p/33020995) 14 | 15 | [详解深度学习中的Normalization,不只是BN](https://zhuanlan.zhihu.com/p/33173246) 16 | 17 | [2018 商汤科技深度学习方向暑期实习电话面试](https://blog.csdn.net/seniusen/article/details/80182728) 18 | 19 | [本人整理的知乎上的一些问题](https://www.zhihu.com/collection/289996791) 20 | 21 | [专题 | 2019 年算法面试梳理](https://mp.weixin.qq.com/s?__biz=MzI4Mzc5NDk4MA==&mid=2247484840&idx=3&sn=054bd9444b39259dfa032aa1ed61abd3&chksm=eb840ee3dcf387f579aff3d39aeab8dbbc5ffe2511b2997c15c5003a0fe3900abc36d32ec35a&mpshare=1&scene=23&srcid=1104dqyASLohk7kCF8Fy7VfN#rd) 22 | 23 | 这部分链接比较混乱,我没有整理太多,计算机视觉方面的面试主要还是考 算法基础+项目经历深挖 24 | 25 | ### 简历书写 26 | [超级简历](https://www.wondercv.com) 27 | 28 | [软件(技术类)岗位简历的撰写要点](https://blog.csdn.net/cccccttttyyy/article/details/79253138) 29 | 30 | [如何制作高水平简历](https://www.zhihu.com/question/21187514) 31 | -------------------------------------------------------------------------------- /cs231n/assignment/assignment1/knn.md: -------------------------------------------------------------------------------- 1 | ## 使用KNN进行分类 2 | ### NumPy及其他API技巧 3 | 4 | **flatnonzero**在第四个代码框中,使用```np.flatnonzero(y_train == y)```来选出特定的样本: 5 | ```python 6 | classes = ['planes', 'car', ...] 7 | for y, cls in enumerate(classes): 8 | # y_train (50000L,) 9 | idxs = np.flatnonzero(y_train == y) 10 | ``` 11 | 12 | ### 矩阵编程技巧 13 | **一个循环计算两个矩阵之间的l2距离** knn需要计算测试数据和训练数据之间的l2距离来决定前k个和测试数据相近的图片。l2距离:两点先做差得到一个向量,然后向量每个值平方,然后求和,然后开方,可以用二维的例子来想想。训练数据:X\_train(num\_train, 3072L),测试数据:X(num\_test, 3072L)。产生的矩阵的大小就应该是(num\_train, num\_test),使用一个循环的情况如下: 14 | ```python 15 | dists = np.zeros((num_test, num_train)) 16 | for i in range(num_test): 17 | dists[i] = np.sqrt(np.sum(np.square(X_train-X[i]), axis=1)) 18 | ``` 19 | 20 | **无需循环计算两个矩阵之间的l2距离。** X\_train(num\_train, 3072L)训练数据X(num\_test, 3072L)。产生的矩阵的大小就应该是(num\_test, num\_train)。l2距离的公式$ \sqrt{(x-y)^2} = \sqrt{x^2 + y^2 - 2xy} $。所以先进行矩阵相乘得到$ -2xy $,然后加上$ x^2 \quad y^2 $: 21 | ```python 22 | dists = np.zeros((num_test, num_train)) 23 | dists = np.sqrt(-2*np.dot(X, X_train.T) + np.sum(np.square(X_train), axis=1) + np.transpose([np.sum(np.square(X), axis=1)])) 24 | ``` 25 | 其中```np.sum(np.square(X_train), axis=1)```先对训练数据中每个元素都取平方,然后求和得到维度为(num\_train,)的一维向量,在和前面的$ -2xy $相加的时候会自动进行广播变为(num_test, num_train),其中每行是一样的。同样```np.transpose([np.sum(np.square(X), axis=1)])```每个元素先取平方,然后求和得到(num\_train,)的一维向量,再进行转置变成列向量,相加的时候也会进行广播变为(num\_test, num\_train)其中每列是一样的。 26 | 27 | 在使用numpy的时候一维的向量转置还是一维的向量,只有二维及以上才能进行转置。 28 | ```python 29 | print(np.array([1,2,3]).T) 30 | # array([1,2,3]) 31 | print(np.array([[1,2,3]]).T) 32 | # array([[1], 33 | # [2], 34 | # [3]]) 35 | ``` -------------------------------------------------------------------------------- /cs231n/assignment/assignment1/softmax.md: -------------------------------------------------------------------------------- 1 | ## 使用softmax进行分类 2 | 大体上和线性svm分类差不多,只是loss函数不一样。softmax的公式: $ loss = -log(\frac{e^{score_j}}{\sum_{i}e^{score_i}}) $变形一下$ loss = -score_j + log(\sum_{i}e^{score_i}) $。 3 | 4 | 至于导数,因为是单层的线性分类器,所以用softmax的输出(一个向量(10,))对softmax求导得到(一个向量,(10,)),然后用这个向量乘以这条训练数据得到softmax对于权重的导数。$ \frac{\partial{softmax}}{\partial{W}} = \frac{\partial{softmax}}{\partial{Z}} \frac{\partial{Z}}{\partial{W}} (Z = X.dot(W)) $。其中$ \frac{\partial{softmax}}{\partial{Z}} = softmax[j]-1 (j \ is \ correct \ class) $如果不是正确类别的话$ \frac{\partial{softmax}}{\partial{Z}} = softmax[j] (j \ is \ not \ correct \ class) $ 5 | 6 | 7 | ### 使用循环来计算softmax的loss和梯度 8 | ```python 9 | # 第i个数据 10 | for i in xrange(num_train): 11 | scores = X[i].dot(W) 12 | shift_scores = scores - max(scores) 13 | # 这条数据计算出来的损失 14 | loss_i = - shift_scores[y[i]] + np.log(sum(np.exp(shift_scores))) 15 | loss += loss_i 16 | # 对于这条数据得到的score,每个类别的score都计算导数 17 | # 单个的score计算导数相当于更新这个类别对应的那个分类器的导数所以有[:, j] 18 | for j in xrange(num_classes): 19 | softmax_output = np.exp(shift_scores[j])/sum(np.exp(shift_scores)) 20 | # 如果这个类别是正确的类别 21 | if j == y[i]: 22 | dW[:,j] += (-1 + softmax_output) *X[i] 23 | else: 24 | dW[:,j] += softmax_output *X[i] 25 | 26 | loss /= num_train 27 | loss += 0.5* reg * np.sum(W * W) 28 | dW = dW/num_train + reg* W 29 | ``` 30 | 31 | ### 使用矩阵来计算softmax的loss和梯度 32 | ```python 33 | num_classes = W.shape[1] 34 | num_train = X.shape[0] 35 | # 计算得到每个训练数据每个类别的score 36 | scores = X.dot(W) # (num_train, 10) 37 | # 下面的reshape是为了让计算得到的max在和score相减的时候进行广播,每行的max都是一样的 38 | shift_scores = scores - np.max(scores, axis = 1).reshape(-1,1) # (num_train, 10) 39 | # 下面的reshape是为了能够进行广播 40 | softmax_output = np.exp(shift_scores)/np.sum(np.exp(shift_scores), axis = 1).reshape(-1,1) # (num_train, 10) 41 | loss = -np.sum(np.log(softmax_output[range(num_train), list(y)])) # 正确类别的占比的负数 42 | loss /= num_train 43 | loss += 0.5* reg * np.sum(W * W) 44 | 45 | dS = softmax_output.copy() 46 | # 正确类别的减一,其他的直接和X矩阵相乘 47 | dS[range(num_train), list(y)] += -1 48 | dW = (X.T).dot(dS) 49 | dW = dW/num_train + reg* W 50 | ``` 51 | 52 | ### 其他问题 53 | **在刚初始化的时候,为什么希望计算出来的loss接近-log(0.1)?** 因为刚初始化的时候,每个类别的概率都应该相同,所以正确类别的概率应该是0.1,那么loss就应该是-log(0.1)。 -------------------------------------------------------------------------------- /cs231n/assignment/assignment1/svm.md: -------------------------------------------------------------------------------- 1 | ## 利用线性SVM进行分类 2 | train_data: (train_num, 3072) 3 | ### 训练流程 4 | 1. 初始化权重W: (3072, 10) 梯度dW: (3072, 10) 5 | 2. train_data和权重相乘得到score(10,)对应每个类别的分数 6 | 2.1 对于每个score中的分数i,如果是正确的类别对应的score跳过 7 | 2.2 如果是其他的类别,计算margin=score[i]-correct_score+1 8 | 2.3 如果其他的margin大于零则```loss+=margin; dW[:, i]+=X[i].T; dW[:, correct]-=X[i].T``` 9 | 3. 得到平均的loss```loss /= num_train; loss += 0.5 * reg * np.sum(W * W)``` 10 | 4. 得到最终的梯度```dW /= num_train; dW += reg * W``` 11 | 12 | ### 向量化上述过程 13 | ```python 14 | scores = X.dot(W) # (num_train, num_class) 15 | # 之前的correct类的下标是一个一个获取的,这里直接作为列向量 16 | correct_class_scores = scores[range(num_train), list(y)].reshape(-1,1) #(num_train, 1) 17 | # score和correct相减的时候会进行广播,这里要注意正确类别的margin也变成了1,而在上面的流程中是不计算的 18 | margins = np.maximum(0, scores - correct_class_scores +1) # (num_train, num_class) 19 | # 将正确类别的margin置为0 20 | margins[range(num_train), list(y)] = 0 21 | loss = np.sum(margins) / num_train + 0.5 * reg * np.sum(W * W) 22 | 23 | # 用coeff_mat和训练数据矩阵相乘来得到梯度 24 | coeff_mat = np.zeros((num_train, num_classes)) 25 | # 那些需要更新参数的位置的系数就是1,上面的流程中是dW[:, i]+=X[i].T,这里系数为1,然后下面再进行矩阵相乘,效果一样 26 | coeff_mat[margins > 0] = 1 27 | coeff_mat[range(num_train), list(y)] = 0 28 | # 正确类别的梯度的是其他类别之和 29 | coeff_mat[range(num_train), list(y)] = -np.sum(coeff_mat, axis=1) 30 | 31 | dW = (X.T).dot(coeff_mat) 32 | dW = dW/num_train + reg*W 33 | ``` 34 | 35 | ### 问题 36 | **为什么在梯度检查的时候会出现个别较大的差错?** 因为svm loss不是完全可导的,就像relu函数在0附近一样,越靠近0,分析梯度和数值梯度的差就越多。 37 | 38 | **将参数W进行可视化的结果是什么样的?** W: (3072, 10), 重新reshape到(32, 32, 3, 10)最后的10代表10个分类器,前面的就是每个分类器可视化的结果,它像是每个类别的所有图像的一个模板(取了平均值,因为只有数值相近的时候平方才最大),如果某个类别的分数较高,那么它就越接近这个类图片的模板。 -------------------------------------------------------------------------------- /cs231n/assignment/assignment1/two_layer_network.md: -------------------------------------------------------------------------------- 1 | ## 使用两层的神经网络来分类 2 | 3 | ### 关于bias 4 | bias的两种方式: 5 | 6 | **第一种,将bias添加到数据中去。**```np.hstack([X, np.ones((X.shape[0], 1))])```将X从(num_train, 3072)变为(num_train, 3072+1),然后在权重W处同样增加维度由原来的(3072, 10)变为(3072+1, 10)。assignment1中svm和softmax都是使用这种方法的。初始化的时候bias和W一起使用了较小的随机数值进行了初始化。 7 | 8 | **第二种,分离式计算bias。**```hidden = np.maximum(0, X.dot(W1) + b1)```其中```b1 = np.zeros(hidden_size); W1.shape=(3072, hidden_size)```。hidden中的每一行对应一条数据,hidden[i, j]就是隐藏层中第i条数据的第j个神经元的值,在计算hidden[i, j]的时候要加上bias偏置,同时hidden[i, j+1]第j+1个神经元的bias偏置与第j个神经元的bias偏置不同。隐藏层有多少个神经元就有多少套连接到这个神经元的权重,同时也就有多少个偏置。里面的那个maximum是ReLU函数。 9 | 10 | ### 计算loss以及导数 11 | 关键就在于将矩阵的size对应就可以,矩阵相乘的时候,还有导数矩阵必须和参数矩阵的shape一样。 12 | ```python 13 | dscores = softmax_output.copy() # d(loss)/d(score) (num_train, 10) 14 | dscores[range(N), list(y)] -= 1 15 | dscores /= N 16 | # h_output: (num_train, hidden_size) 17 | # dW2: (hidden_size, 10) 18 | grads['W2'] = h_output.T.dot(dscores) + reg * W2 # d(loss)/d(W2) = d(loss)/d(score) * d(score)/d(W2) 19 | # bias本身是一个行向量,一整列的值都是一样的,而之前就已经dscores /= N,所以这里就不再需要求平均了 20 | grads['b2'] = np.sum(dscores, axis = 0) # d(score)/d(b2)=1 thus d(loss)/d(b2)=d(loss)/d(score) 21 | 22 | # h是隐藏层的输入(num_train, hidden_size) 23 | # h本身并不需要更新,它只是一个中间的数据,只是需要对他求导然后反向传播到上一层 24 | dh = dscores.dot(W2.T) # (num_train, hidden_size) 25 | dh_ReLu = (h_output > 0) * dh # (num_train, hidden_size)对ReLU求导,如果是大于零的导数就是1 如果小于零那么导数为0 26 | # X: (num_train, 3072) 27 | grads['W1'] = X.T.dot(dh_ReLu) + reg * W1 # (3072, hidden_size) 28 | grads['b1'] = np.sum(dh_ReLu, axis = 0) 29 | ``` 30 | 31 | ### 前向传播 32 | ```python 33 | h = np.maximum(0, X.dot(self.params['W1']) + self.params['b1']) 34 | scores = h.dot(self.params['W2']) + self.params['b2']) 35 | y_pred = np.argmax(scores, axis=1) 36 | ``` 37 | 38 | ### 编程技巧 39 | **随机选择batch** 40 | ```python 41 | idx = np.random.choice(num_train, batch_size, replace=True) 42 | X_batch = X[idx] 43 | y_batch = y[idx] 44 | ``` 45 | 46 | **记录学习率、loss、eval准确率的变化** 47 | ```python 48 | loss_history.append(loss) 49 | train_acc_history.append(train_acc) 50 | val_acc_history.append(val_acc) 51 | 52 | return { 53 | 'loss_history': loss_history, 54 | 'train_acc_history': train_acc_history, 55 | 'val_acc_history': val_acc_history, 56 | } 57 | ``` -------------------------------------------------------------------------------- /cs231n/assignment/assignment2/BatchNorm.md: -------------------------------------------------------------------------------- 1 | ## Batch Normalization 2 | ### 原理 3 | 在机器学习中让输入的数据之间相关性越少越好,最好输入的每个样本都是均值为0方差为1。在输入神经网络之前可以对数据进行处理让数据消除共线性,但是这样的话输入层的激活层看到的是一个分布良好的数据,但是较深的激活层看到的的分布就没那么完美了,分布将变化的很严重。这样会使得训练神经网络变得更加困难。所以添加BatchNorm层,在训练的时候BN层使用batch来估计数据的均值和方差,然后用均值和方差来标准化这个batch的数据,并且随着不同的batch经过网络,均值和方差都在做累计平均。在测试的时候就直接作为标准化的依据。 4 | 5 | 这样的方法也有可能导致降低神经网络的表示能力,因为某些层的全局最优的特征可能不是均值为0或者方差为1的。所以BN层也是能够进行学习每个特征维度的缩放gamma和平移beta的来避免这样的情况。 6 | 7 | ### BN层前向传播 8 | ```python 9 | def batchnorm_forward(x, gamma, beta, bn_param): 10 | """先进行标准化再进行平移缩放 11 | running_mean = momentum * running_mean + (1 - momentum) * sample_mean 12 | running_var = momentum * running_var + (1 - momentum) * sample_var 13 | 14 | Input: 15 | - x: (N, D) 输入的数据 16 | - gamma: (D,) 每个特征维度数据的缩放 17 | - beta: (D,) 每个特征维度数据的偏移 18 | - bn_param: 字典,有如下键值: 19 | - mode: 'train'/'test' 必须指定 20 | - eps: 一个常量为了维持数值稳定,保证不会除0 21 | - momentum: 动量 22 | - running_mean: (D,) 积累的均值 23 | - running_var: (D,) 积累的方差 24 | 25 | Returns: 26 | - out: (N,D) 27 | - cache: 反向传播时需要的数据 28 | """ 29 | mode = bn_param['mode'] 30 | eps = bn_param.get('eps', 1e-5) 31 | momentum = bn_param.get('momentum', 0.9) 32 | 33 | N, D = x.shape 34 | running_mean = bn_param.get('running_mean', np.zeros(D, dtype=x.dtype)) 35 | running_var = bn_param.get('running_var', np.zeros(D, dtype=x.dtype)) 36 | 37 | out, cache = None, None 38 | if mode == 'train': 39 | sample_mean = np.mean(x, axis=0) 40 | sample_var = np.var(x, axis=0) 41 | # 先标准化 42 | x_hat = (x - sample_mean)/(np.sqrt(sample_var + eps)) 43 | # 再做缩放偏移 44 | out = gamma * x_hat + beta 45 | cache = (gamma, x, sample_mean, sample_var, eps, x_hat) 46 | running_mean = momentum * running_mean + (1-momuntum)*sample_mean 47 | running_var = momentum * running_var + (1-momentum)*sample_var 48 | elif mode == 'test': 49 | # 先标准化 50 | #x_hat = (x - running_mean)/(np.sqrt(running_var+eps)) 51 | # 再做缩放偏移 52 | #out = gamma * x_hat + beta 53 | # 或者是下面的骚写法 54 | scale = gamma/(np.sqrt(running_var + eps)) 55 | out = x*scale + (beta - running_mean*scale) 56 | else: 57 | raise ValueError('Invalid forward batchnorm mode "%s"' % mode) 58 | 59 | bn_param['running_mean'] = running_mean 60 | bn_param['running_var'] = running_var 61 | 62 | return out, cache 63 | ``` 64 | 65 | ### BN层反向传播 66 | ```python 67 | def batchnorm_barckward(out, cache): 68 | """反向传播的简单写法,易于理解 69 | Inputs: 70 | - dout: (N,D) dloss/dout 71 | - cache: (gamma, x, sample_mean, sample_var, eps, x_hat) 72 | 73 | Returns: 74 | - dx: (N,D) 75 | - dgamma: (D,) 每个维度的缩放和平移参数不同 76 | - dbeta: (D,) 77 | """ 78 | dx, dgamma, dbeta = None, None, None 79 | # unpack cache 80 | gamma, x, u_b, sigma_squared_b, eps, x_hat = cache 81 | N = x.shape[0] 82 | 83 | dx_1 = gamma * dout # dloss/dx_hat = dloss/dout * gamma (N, D) 84 | dx_2_b = np.sum((x - u_b) * dx_1, axis=0) 85 | dx_2_a = ((sigma_squared_b + eps)**-0.5)*dx_1 86 | dx_3_b = (-0.5) * ((sigma_squared_b + eps)**-1.5)*dx_2_b 87 | dx_4_b = dx_3_b * 1 88 | dx_5_b = np.ones_like(x)/N * dx_4_b 89 | dx_6_b = 2*(x-u_b)*dx_5_b 90 | dx_7_a = dx_6_b*1 + dx_2_a*1 91 | dx_7_b = dx_6_b*1 * dx_2_a*1 92 | dx_8_b = -1*np.sum(dx_7_b, axis=0) 93 | dx_9_b = np.ones_like(x)/N * dx_8_b 94 | dx_10 = dx_9_b + dx_7_a 95 | 96 | dgamma = np.sum(x_hat * dout, axis=0) 97 | dbeta = np.sum(dout, axis=0) 98 | dx = dx_10 99 | 100 | return dx, dgamma, dbeta 101 | ``` 102 | 下面是直接使用公式来计算: 103 | ```python 104 | def batchnorm_backward_alt(dout, cache): 105 | dx, dgamma, dbeta = None, None, None 106 | # unpack cache 107 | gamma, x, u_b, sigma_squared_b, eps, x_hat = cache 108 | N = x.shape[0] 109 | dx_hat = dout * gamma 110 | dvar = np.sum(dx_hat* (x - sample_mean) * -0.5 * np.power(sample_var + eps, -1.5), axis = 0) 111 | 112 | dmean = np.sum(dx_hat * -1 / np.sqrt(sample_var +eps), axis = 0) + dvar * np.mean(-2 * (x - sample_mean), axis =0) 113 | 114 | dx = 1 / np.sqrt(sample_var + eps) * dx_hat + dvar * 2.0 / N * (x-sample_mean) + 1.0 / N * dmean 115 | 116 | dgamma = np.sum(x_hat * dout, axis = 0) 117 | dbeta = np.sum(dout , axis = 0) 118 | 119 | return dx, dgamma, dbeta 120 | ``` 121 | 122 | ### BN有什么作用 123 | 1. 对于不好的权重初始化有更高的鲁棒性,仍然能得到较好的效果。 124 | 2. 能更好的避免过拟合。 125 | 3. 解决梯度消失/爆炸问题,BN防止了前向传播的时候数值过大或者过小,这样就能让反向传播时梯度处于一个较好的区间内。 126 | 127 | ## 卷积神经网络中的BN 128 | ### 前向传播 129 | ```python 130 | def spatial_batchnorm_forward(x, gamma, beta, bn_param): 131 | """利用普通神经网络的BN来实现卷积神经网络的BN 132 | Inputs: 133 | - x: (N, C, H, W) 134 | - gamma: (C,)缩放系数 135 | - beta: (C,)平移系数 136 | - bn_param: 包含如下键的字典 137 | - mode: 'train'/'test'必须的键 138 | - eps: 数值稳定需要的一个较小的值 139 | - momentum: 一个常量,用来处理running mean和var的。如果momentum=0 那么之前不利用之前的均值和方差。momentum=1表示不利用现在的均值和方差,一般设置momentum=0.9 140 | - running_mean: (C,) 141 | - running_var: (C,) 142 | 143 | Returns: 144 | - out: (N, C, H, W) 145 | - cache: 反向传播需要的数据,这里直接使用了普通神经网络的cache 146 | """ 147 | N, C, H, W = x.shape 148 | # transpose之后(N, W, H, C) channel在这里就可以看成是特征 149 | temp_out, cache = batchnorm_forward(x.transpose(0, 3, 2, 1).reshape((N*H*W, C)), gamma, beta, bn_param) 150 | # 再恢复shape 151 | out = temp_output.reshape(N, W, H, C).transpose(0, 3, 2, 1) 152 | return out, cache 153 | ``` 154 | ### 反向传播 155 | ```python 156 | def spatial_batchnorm_backward(dout, cache): 157 | """利用普通神经网络的BN反向传播实现卷积神经网络中的BN反向传播 158 | Inputs: 159 | - dout: (N, C, H, W) 反向传播回来的导数 160 | - cache: 前向传播时的中间数据 161 | 162 | Returns: 163 | - dx: (N, C, H, W) 164 | - dgamma: (C,) 缩放系数的导数 165 | - dbeta: (C,) 偏移系数的导数 166 | """ 167 | dx, dgamma, dbeta = None, None, None 168 | N, C, H, W = dout.shape 169 | # 利用普通神经网络的BN进行计算 (N*H*W, C)channel看成是特征维度 170 | dx_temp, dgamma, dbeta = batchnorm_backward_alt(dout.transpose(0, 3, 2, 1).reshape((N*H*W, C)), cache) 171 | # 将shape恢复 172 | dx = dx_temp.reshape(N, W, H, C).transpose(0, 3, 2, 1) 173 | return dx, dgamma, dbeta 174 | ``` -------------------------------------------------------------------------------- /cs231n/assignment/assignment2/CNN.md: -------------------------------------------------------------------------------- 1 | ## Convolutional Neural Network 2 | 3 | ### 卷积层前向传播 4 | ```python 5 | def conv_forward_naive(x, w, b, conv_param): 6 | """用循环实现的卷积层 7 | Inputs: 8 | - x: 输入的数据(N, C, H, W) 9 | - w: filter的数值(F, C, HH, WW) F是输出的channel 10 | - b: 偏置单元 (F,) 11 | - conv_param: 包含如下键的字典: 12 | - 'stride': 相邻感受野之间的像素值个数,垂直方向上和水平方向上 13 | - 'pad': zero-pad的个数 14 | 15 | Returns: 16 | - out: (N, F, H', W')其中 17 | H' = (H + 2*pad - HH)/stride + 1 18 | W' = (W + 2*pad - WW)/stride + 1 19 | - cache: (x, w, b, conv_param) 20 | """ 21 | out = None 22 | N, C, H, W = x.shape 23 | F, _, HH, WW = w.shape 24 | stride, pad = conv_param['stride'], conv_param['pad'] 25 | H_out = 1 + (H + 2*pad - HH)/stride 26 | W_out = 1 + (W + 2*pad - WW)/stride 27 | out = np.zeros((N, F, H_out, W_out)) 28 | 29 | # np.pad(data, ((before, after), (before, after)...)) 30 | # 这里pad仍然是两边pad 31 | x_pad = np.pad(x, ((0,), (0,), (pad,), (pad,)), mode='constant', constant_values=0) 32 | for i in range(H_out): 33 | for j in range(W_out): 34 | # 确定feature map上的这个位置 35 | x_pad_masked = x_pad[:, :, i*stride:i*stride+HH, j*stride:j*stride+WW] # (N, C, HH, WW) 36 | # 每个卷积核都对这个位置进行相乘,如果不考虑batch,那么相乘的结果就是一个点 37 | # 卷积的结果(那个点)输出的就是第k个channel,第i, j的位置 38 | # sum(..., axis=(1,2,3))让对应位置相乘之后求和 39 | for k in range(F): 40 | out[:, k, i, j] = np.sum(x_pad_masked*w[k, :, :, :], axis=(1,2,3)) 41 | 42 | out = out + (b)[None, :, None, None] 43 | cache = (x, w, b, conv_param) 44 | return out, cache 45 | ``` 46 | ### 卷积层反向传播 47 | ```python 48 | def conv_backward_navie(dout, cache): 49 | """ 50 | Inputs: 51 | - dout: 反向传播过来的导数 52 | - cache: (x, w, b, conv_param) 53 | 54 | Return: 55 | - dx: x的导数 56 | - dw: w的导数 57 | - db: b的导数 58 | """ 59 | dx, dw, db = None, None, None 60 | x, w, b, conv_param = cache 61 | 62 | N, C, H, W = x.shape 63 | F, _, HH, WW = w.shape 64 | stride, pad = conv_param['stride'], conv_param['pad'] 65 | H_out = (H + 2*pad - HH)/stride + 1 66 | W_out = (H + 2*pad - WW)/stride + 1 67 | 68 | # 卷积的中间数据 69 | x_pad = np.pad(x, ((0,), (0,), (pad,), (pad,)), mode='constant', constant_values=0) 70 | # 先生成各种导数的数据 71 | dx = np.zeros_like(x) 72 | dx_pad = np.zeros_like(x_pad) 73 | dw = np.zeros_like(w) 74 | db = np.zeros_like(b) 75 | 76 | for i in range(H_out): 77 | for j in range(W_out): 78 | # 对于每个卷积核和feature map相乘的位置 79 | x_pad_masked = x_pad[:, :, i*stride:i*stride+HH, j*stride:j*stride+WW] 80 | # 对于每个卷积核,输出的channel为F,那么就有F个卷积核 81 | for k in range(F): 82 | # 这个卷积核参数的导数,是每个卷积位置的数据和dout相乘后求和(+=),那个sum是为了缩减batch 83 | dw[k, :, :, :] += np.sum(x_pad_masked * (dout[:, k, i, j][:, None, None, None], axis=0)) 84 | # 对于这个位置上数据的每个batch中的导数[n, :, i*stride:i*stride+HH, j*stride:j*stride+WW] 85 | for n in range(N): 86 | # 这个位置上的数据的导数就是权重和dout相乘后求和(+=),sum是为了缩减batch 87 | dx_pad[n, :, i*stride:i*stride+HH, j*stride:j*stride+WW] += np.sum((w[:, :, :, :]*(dout[n, :, i, j])[:, None, None, None]), axis=0) 88 | # 使用dx_pad得到dx 89 | dx = dx_pad[:, :, pad:-pad, pad:-pad] 90 | return dx, dw, db 91 | ``` -------------------------------------------------------------------------------- /cs231n/assignment/assignment2/Dropout.md: -------------------------------------------------------------------------------- 1 | ## Dropout 2 | Dropout(Improving neural networks by preventing co-adaptation of feature detectors)是一个regularization技术,随机让某些神经元进行失效来获得更好的效果。 3 | 4 | ### Dropout前向传播 5 | ```python 6 | def dropout_forward(x, dropout_param): 7 | """ 8 | Inputs: 9 | - x: 输入的数据,可以是任何的shape 10 | - dropout_param: dict包含如下的键: 11 | - p: dropout概率,每个神经元被失活的概率 12 | - mode: 'test'/'train'如果是'test'则不进行失活 13 | - seed: 随机数生成种子,这个是为了梯度检验用,正常使用中不应该指定这个参数 14 | 15 | Outputs: 16 | - out: 输出数据shape同x 17 | - cache: (dropout_param, mask) 在'train'mode中,mask作用于输入x得到输出,在'test'mode中mask为None 18 | """ 19 | p, mode = dropout['p'], dropout_param['mode'] 20 | if 'seed' in deopout_param: 21 | np.random.seed(dropout_param['seed']) 22 | 23 | mask = None 24 | out = None 25 | if mode == 'train': 26 | # 注意/(1-p) 前向传播的时候均值得稳定 27 | mask = (np.random.rand(*x.shape)>=p)/(1-p) 28 | out = x*mask 29 | elif mode == 'test': 30 | out = x 31 | 32 | cache = (dropout_param, mask) 33 | out = out.astype(x.dtype, copy=False) 34 | return out, cache 35 | ``` 36 | 37 | ### Dropout反向传播 38 | ```python 39 | def dropout_backward(dout, cache): 40 | """ 41 | Inputs: 42 | - dout: 反向传播回来的导数 43 | - cache: (dropout_param, mask) 44 | 45 | Output: 46 | - dx: 导数 47 | """ 48 | dropout_param, mask = cache 49 | mode = dropout_param['mode'] 50 | 51 | dx = None 52 | if mode == 'train': 53 | # dloss/dx = dloss/dout * dout/dx = dloss/dout * mask 54 | # 被失活的神经元的mask处为0,其余为1 55 | dx = dout * mask 56 | elif mode == 'test': 57 | dx = dout 58 | return dx 59 | ``` 60 | 61 | ### Dropout的作用 62 | Dropout可以有效地抑制过拟合,一般来说神经元失活的概率越大在训练集上和在验证集上的区别就越小,但是较大的失活概率会导致神经网络的容量下降,更难拟合数据。 -------------------------------------------------------------------------------- /cs231n/assignment/assignment2/FCN.md: -------------------------------------------------------------------------------- 1 | ## Fully Connected Neural Network 2 | 之前的两层神经网络是用非常简单的方式进行组织的,没有模块化。模块化的前向传播应该是下面的这个样子的,返回的值还应该有中间数据: 3 | ```python 4 | def layer_forward(x, w): 5 | # compute forward 6 | z = ... 7 | out = ... 8 | cache = (x, w, z, out) 9 | return out, cache 10 | ``` 11 | 一个反向传播应该是类似这样的,返回的值还应该有局部权重的梯度以及再向前的梯度: 12 | ```python 13 | def layer_backward(dout, cache): 14 | # Unpack cache 15 | x, w, z, out = cache 16 | dz = ... 17 | dx = ... 18 | dw = ... 19 | return dx, dw 20 | ``` 21 | 22 | ### 带ReLU的前向传播和反向传播 23 | 前向传播 24 | ```python 25 | def affine_relu_forward(x, w, b): 26 | """ 27 | 先进行权重相乘(affine),然后apply一个relu函数 28 | Inputs: 29 | - x: 输入的数据(N, D) 30 | - w, b: 权重相乘的权重(D, M) (M,) 31 | 32 | Returns: 33 | - out: ReLU之后的输出(N, M) 34 | - cache: 反向传播需要的中间数据 35 | """ 36 | N = x.shape[0] 37 | x_rsp = np.reshape(x, (N, -1)) # (N, D) 38 | affine = x_rsp.dot(w) + b # (N, M) 39 | out = affine * (affine >= 0) 40 | cache = (x, affine) 41 | return out, cache 42 | ``` 43 | 反向传播 44 | ```python 45 | def affine_relu_backforward(dout, cache): 46 | """ 47 | 反向传播 48 | Inputs: 49 | - dout: 输出的倒数(N, M) 50 | - cache: 缓存的中间数据(x, affine) 51 | 52 | Returns: 53 | - dw: w的导数(D, M) 54 | - db: b的导数(M,) 55 | """ 56 | # x: (N, D) affine: (D, M) 57 | x, affine = cache 58 | drelu = dout * (affine >=0 ) # (N, M) 59 | dw = x.T.dot(drelu) # x.T=>(D, N) dot=>(D, M) 60 | db = np.sum(drelu, axis=0) 61 | return dw, db 62 | ``` 63 | 64 | ### 将优化权重过程集成到一个类中 65 | 之前的优化逻辑都是直接写的没有进行封装,现在将其封装在Solver类中。只有针对导数进行优化的代码在Solver中,其他的如计算loss、梯度都是模型本身的功能。模型计算完梯度之后将梯度给Solver类,然后 Solver类选择算法进行优化。 -------------------------------------------------------------------------------- /cs231n/assignment/assignment2/MaxPool.md: -------------------------------------------------------------------------------- 1 | ## 最大池化 2 | 3 | ### 最大池化前向传播 4 | 在前向传播的过程中注意保存中间数据,为了反向传播的时候方便计算。 5 | ```python 6 | def max_pool_forward_naive(x, pool_param): 7 | """MaxPool前向传播的一个简单实现版本 8 | Inputs: 9 | - x: (N, C, H, W) 10 | - pool_param: 包含最大池化参数的字典 11 | - 'pool_height': 池化区域的高度 12 | - 'pool_width': 池化区域的宽度 13 | - 'stride': 相邻池化区域的距离 14 | 15 | Returns: 16 | - out: 输出的数据 17 | - cache: (x, pool_param) 18 | """ 19 | out = None 20 | N, C, H, W = x.shape 21 | HH, WW, stride = pool_param['pool_height'], pool_param['pool_width'] 22 | H_out = (H-HH)/stride+1 23 | W_out = (W-WW)/stride+1 24 | out = np.zeros((N, C, H_out, W_out)) 25 | # 先确定每个做最大池化的区域 26 | for i in xrange(H_out): 27 | for j in xrange(W_out): 28 | # 取出这个区域的数据 29 | x_masked = x[:, :, i*stride:i*stride+HH, j*stride:j*stride+WW] # (N, C, HH, WW) 30 | # 取最大值 (N, C) 31 | out[:, :, i, j] = np.max(x_masked, axis=(2,3)) 32 | cache = (x, pool_param) 33 | return out, cache 34 | ``` 35 | 36 | ### 最大池化反向传播 37 | ```python 38 | def max_pool_backward_naive(dout, cache): 39 | """MaxPool反向传播的一个简单版本 40 | Inputs: 41 | - dout: 反向传播过来的导数 42 | - cache: (x, pool_param) 43 | 44 | Returns: 45 | - dx: 导数 46 | """ 47 | dx = None 48 | x, pool_param = cache 49 | N, C, H, W = x.shape 50 | HH, WW, stride = pool_param['pool_height'], pool_param['pool_width'], pool_param['stride'] 51 | H_out = (H-HH)/stride+1 52 | W_out = (W-WW)/stride+1 53 | # 最大值处的导数能够进行传播,其余的地方导数为0 54 | dx = np.zeros_like(x) 55 | 56 | # 先确定之前池化的每个区域 57 | for i in xrange(H_out): 58 | for j in xrange(W_out): 59 | # 取出池化这个区域的数据 60 | x_masked = x[:, :, i*stride:i*stride+HH, j*stride:j*stride+WW] # (N, C, HH, WW) 61 | # 取得最大值 (N,C) 62 | max_x_masked = np.max(x_masked, axis=(2,3)) 63 | # 得到最大值的mask (N, C, HH, WW) 最大值为1 其余为0 64 | temp_binary_mask = (x_masked == (max_x_masked)[:,:,None,None]) 65 | # 最大值处的导数能够进行传播,其余的地方导数为0 66 | dx[:, :, i*stride:i*stride+HH, j*stride:j*stride+WW] += temp_binary_mask * (dout[:,:,i,j])[:,:,None,None] 67 | ``` -------------------------------------------------------------------------------- /cs231n/assignment/readme.md: -------------------------------------------------------------------------------- 1 | ## cs231n assignment的笔记 2 | 没有太多的代码记录,只对重点内容进行记录,目的是把cs231n assignment梳理一遍。 3 | 4 | 什么是重点内容? 5 | 使用NumPy的技巧、矩阵编程的技巧、神经网络编程的技巧。 -------------------------------------------------------------------------------- /cs231n/note1.md: -------------------------------------------------------------------------------- 1 | ## 图片分类 2 | **Motivation** :将图片进行分类是计算机视觉中的一个核心问题,其他的一些高级的计算机视觉的任务也都可以由图片分类延展开。 3 | 4 | **Example** :对于计算机来说图片并不是人眼看到的一样,而是一个3维矩阵,一个RGB模式的400X300的图片,在计算机看来就是一个400X300X3的矩阵。分类的任务就是将这些成千上万的矩阵中的数字转化为一个标签。 5 | 6 | **Challenges** :视角变换Viewpoint variation,尺度不同Scale variation,仿射变换Deformation,覆盖Occlusion,亮度条件Illumination conditions,背景聚集Background clutter,类内不同Intra-class variation 7 | 8 | 一个好的分类器必须能都处理上述问题,同时对类间的差别保持敏感。 9 | 10 | **Data-driven approach**:实现分类问题并不是通过在代码中一个特征一个特征进行判断,而是通过给计算机看很多的图片来训练学习算法,这被称为数据驱动的方式。 11 | 12 | **The image classifiaction pipline** :Input --> Learning --> Evalution 13 | 14 | ### Nearset Neighbor Classifier 15 | KNN算法没有涉及神经网络,而是通过大量的数据进行对比来得到的结果,一般的KNN算法使用L2距离也就是欧式距离来衡量,也可以使用L1距离,当训练数据和测试数据都进行过标准化的时候L1距离和L2距离的结果相同。 16 | 17 | **不使用循环来计算L2距离的方法** : 18 | 19 | 两个向量之间的L2距离可以变为d(X, Y) = sqrt((X1-Y1)^2 + (X2-Y2)^2 + ...)= sqrt(X1^2 + X2^2 + ... + Y1^2 + Y2^2 + ... -2\*(X1\*Y1+ X2\*Y2+...))。 20 | dot点积就是矩阵乘法,内积是两个向量求出一个数字。 21 | np.multiply(x1, x2)是将两个矩阵进行元素与元素之间的相乘。 22 | 必须矩阵的大小一致或者可以广播。 23 | 先求出X和X_train.T 的矩阵乘法。 24 | 25 | ```python 26 | dists = np.multiply(np.dot(X, self.X_train.T), -2) 27 | # 对X求平方和,求和后的shape(num_test, 1) 28 | sq1 = np.sum(np.square(X), axis=1, keepdim=True) 29 | # 对X_train求平方和 30 | sq2 = np.sum(np.square(self.X_train), axis=1) 31 | dists = np.add(dists, sq1) 32 | dists = np.add(dists, sq2) 33 | dists = np.sqrt(dists) 34 | ``` 35 | **超参数的设置**:在KNN中的唯一参数就是参考的样本个数也就是K的大小,一般通过5折验证。对于不同的k,将这5个数据子集依次留出一个子集作为验证集计算准确率,这样每个k就得到了5个准确率。但是实际中通常只留出一个数据子集作为固定的验证集,因为训练多个模型的开销太大了。但是不能使用测试集来进行超参数的设置,那样的话就相当于在测试集上进行训练了。 36 | 37 | 使用KNN的时候,K特别小的时候会导致过拟合,K较大则泛化能力较好。 38 | 39 | KNN的优点: 40 | 1. 容易实现和理解 41 | 2. 不需要时间去训练,在测试的时候直接计算距离即可 42 | 43 | KNN的缺点: 44 | 1. 测试时间耗费较长 45 | 2. 准确度低 46 | 3. 高维数据开销巨大,所以不适合图像分类 47 | 4. 对于图像的移动、缺失、亮度的变化不能正确分类 48 | 5. 需要存储全部的训练数据 49 | 50 | 在实践中使用KNN: 51 | 1. 对数据进行预处理,归一化 52 | 2. 如果数据的维度很高,进行降维PCA 53 | 3. 将数据随机分为train/val 70-90%的数据一般被分为训练集。如果有很多的超参数,那么应该有一个较大的验证集,最好进行交叉验证,虽然开销较大。 54 | 4. 使用不同的距离衡量方法L1 L2 在多个K的情况下建立模型 55 | 5. 如果KNN运行时间过长,可以考虑使用ANN 56 | 6. 记录给出最好结果的超参数,最后一般不在验证集上再进行训练 57 | 58 | ANN:Approximate Nearest Neighbor,可以加速临近数据点的寻找,建立索引或者以空间换时间的方法。 59 | -------------------------------------------------------------------------------- /cs231n/note2.md: -------------------------------------------------------------------------------- 1 | ## 线性分类器 2 | 线性分类器主要包含 3 | 1. score function:将原始数据映射到某个类别上。 4 | 2. loss function:计算预测结果和真实结果之间的误差。 5 | 6 | 假设在CIFAR-10数据集上进行分类,则score function线性映射 $ f(x_i, W, b) = W*x_i + b,从32*32* $3=3072维映射到K=10类上去,$ x_i $就是3072维的列向量,W是(10, 3072)的权重矩阵,b是10维的列向量它影响输出但是不直接和x相互作用。 7 | 8 | 注意到: 9 | 1. $ W*x_i $在10个类别上同时进行分类 10 | 2. 输入是固定的$ (x_i, y_i) $ 11 | 3. 整个过程在学习W b 12 | 13 | ### 线性分类器-高维空间 14 | 可以认为线性分类器中的每行都是针对10个输出的某个类别的分类器,将这个类别和其他的数据分割开来。 15 | 16 | ### 线性分类器作为模版匹配 17 | 一个对线性分类器的解释就是W(输出的类别的个数, 输入的x的维度)中的每一行都是一个这个类别的模板。输入的数据和每个模板都作内积(点积)来找到响应值最高的那个 18 | 19 | ### bias技巧 20 | 可以在W中增加一列作为bias的值,同时输入的x中增加一个恒为1的特征。这样就不用再加上一列bias。 21 | 22 | ### 图像预处理 23 | **center your data**,减去均值将数据的分布从[0, 255]转到[-127, 127],更进一步将数据变为[-1, 1]。 24 | 25 | ## 损失函数 26 | 使用损失函数来衡量分类器的分类效果,也称作cost function。 27 | 28 | ### MultiClass SVM loss 29 | 一种常用的损失函数是**Multiclass Support Vector Machine loss**。它被称为SVM loss的原因是它希望数据的正确类别的score比其他的类别的score多一个Δ。准确来说,SVM 分类器使用的是hinge loss(也称为max-margin loss) 30 | 31 | $ L_i = \sum \max(0, s_j - s_{yi} + Δ) $计算第i个样本的loss,$ score_j $是输出的正确类别的score,$ score_{yi} $是其他类别的score。 32 | 33 | 相比于MSE: 34 | 1. 如果出现一个较大的error,那么MSE的反向传播力度会很大,扩大的这个error的影响。 35 | 2. 只要正确类别的score相比其他的类别有了Δ,higine loss就不再关心这个样本了,而MSE则会一直进行优化。 36 | 37 | ### 正则化 38 | 对于能够正确分类的参数W不是唯一的,若W可以正确分类那么2W 3W...也能正确分类。在loss function上增加一个正则项$ 1/2R(W)=\sum_k \sum_l W_{kl}^2 $。L2 norm正则项。 39 | 40 | $ L = 1/N * (\sum L_i) + λR(W) $data loss加上regularization loss 41 | 42 | 正则化的好处: 43 | 1. 压缩权重的值。 44 | 2. 对大权值的惩罚可以增加泛化的能力,减少过拟合。 45 | 46 | 比如w1=[1,0,0,0] w2=[0.25,0.25,0.25,0.25]可以得到相同的效果,但是增加了正则化那么w2会更守青睐,其实是因为w2利用了所有维度的数据。 47 | 48 | 一般不对bias进行正则化,bias并不直接和输入进行交互。加入了正则化之后loss永远也不可能变为0。 49 | 50 | ### 实践事项 51 | **设置Δ** 一般不使用交叉验证来查找Δ的值,设置为1就可以。 52 | 53 | **优化** 很多在NN中的函数都是不可导的比如max(0, x) 但是可以使用次梯度优化的方法解决 54 | 55 | ### Softmax classifier 56 | 另一种常用的分类器是softmax分类器。softmax是使得输出归一化变成概率的函数,这样交叉熵损失函数才能apply,一般而言不会说softmax loss。 57 | 58 | **信息论解释** 交叉熵计算出来的分布可以用$ H(p, q) = - \sum p(x)log(q(x)) $来模拟。其中p(x)是标签的分布 q(x)是预测的分布。也可写成KL Diverse表示$ H(p, q) = H(p) + D_{kl}(p||q) $。 59 | 60 | **概率预测** $ P(y | x, W) = e^{score_j} / \sum e^{score_i} $ softmax的输出可以看成是概率。 61 | 62 | **数值稳定** 计算的时候$ e^{score_j} 、\sum e^{score_i} $也许会非常大,除以非常大的数字可能会引起数值不稳定,所以需要normalization trick。 63 | ```python 64 | f = np.array([123, 456, 789]) # 假设有3个类别,预测的每个类别的输出都很大 65 | p = np.exp(f) / np.sum(np.exp(f)) # exp之后数值会很大 66 | 67 | # 将这些数字减去最大的,让所有数字<=0 68 | f -= np.max(f) # f becomes [-666, -333, 0] 69 | p = np.exp(f) / np.sum(np.exp(f)) # 不会导致数值不稳定 70 | ``` 71 | 72 | 交叉熵$ L = -log(e^{score_j}/ \sum_i e^{score_i}) $特点: 73 | - 为什么要进行exp:这样可以将负的预测值转成正的。 74 | - 为什么要用预测某个类别的exp值比上所有的值之和:这样是为了计算$ P(j | x_i) $概率 i是第i个样本 j是这个样本的真实类别 75 | - softmax loss可以看成是最大似然函数 这个函数希望正确的类别概率是1。 76 | - 除以所有的exp值可以看成是标准化,让所有exp值都变成0-1之间,并且和为1。 77 | - 为什么最前面又要一个负号:因为这个是损失函数,用来度量分类器做的有多不好,最终计算的时候只计算-log(真实类别的概率)作为loss的值。当真实的概率接近1的时候,-log(p)就为0,当真实的概率很低的时候-log(p)就很高。 78 | - softmax loss的最大值和最小值是多少:最小是0, 最大是无穷。 79 | 80 | ### softmax vs. SVM loss 81 | 当得到相同的score时,两个函数看score的角度不同: 82 | 1. SVM loss鼓励正确的类别比其他的类别多一个Δ 83 | 2. softmax则将输出的score 转换成概率,然后鼓正确的类别的概率要高一点。 84 | 当对同样的score使用两个损失函数的时候计算得到的loss可能不一样,但是这是没法做比较的,loss的数值只有使用同样的loss function的时候比较才有意义。 85 | 86 | 两个loss函数的性能其实差不多。SVM loss只关心那个margin,不关心数值具体是什么情况比如[10, -100, -100]。而softmax则会一直优化下去loss会不断降低。举个例子,对于SVM loss中的一个car classifier会把大部分精力放在区分car和trucks(更难),而不是区分car和frog(更容易)上。 87 | 88 | ### softmax中的概率 89 | softmax分类器将所有输出的socre转化成所谓的“概率”,但是这个概率的数值是受到正则化的力度λ影响的,假如没有正则化的时候输出的socre为 90 | [1, -2, 0] --> [e^1, e^-2, e^0] --> [2.71, 0.14, 1] --> [0.7, 0.04, 0.26] 91 | 然而使用了正则化之后,输出的score也许会减小一半 92 | [0.5, -1, 0] --> [e^0.5, e^-1, e^0] --> [1.65, 0.37, 1] --> [0.55, 0.12, 0.33] 93 | **这时概率变化了!** 94 | 当使用正则化的时候会使得输出的概率分布更加靠近均匀分布,所以**softmax输出的概率也只能作为参考,只能确定哪个类别可能性的排名,不是绝对的数值** -------------------------------------------------------------------------------- /cs231n/note3.md: -------------------------------------------------------------------------------- 1 | ### Optimize 优化 2 | 3 | ### 可视化损失函数 4 | 任何一个神经网络的参数的维度都是非常高的,所以直接可视化参数是不现实的,但是可以随机产生一个参数矩阵W,然后再沿着一个方向W1,来回移动就可以得到损失函数的一维的可视化$ L(W + a*W1) $。那么沿着两个方向可以得到损失函数的二维可视化$ L(W + a*W1 + b*W2) $,[具体见](http://cs231n.github.io/optimization-1/)。一般来说损失函数可视化之后都是碗状的(bowl),中心的损失低,四周的损失高。 5 | 6 | **为什么是碗状的?** 假设存在一个高维超平面A(维度和参数的维度相同)使得loss函数取得最小值,取一个维度Xi,沿着这个维度移动超平面B,在维度Xi上和超平面A之间的距离就是loss,loss值就是AB之间的距离。同样的分别移动其他不同的维度,可以想象出来损失函数为什么是碗状的了。 7 | 8 | 当只有一层神经网络也就是线性分类器的时候,损失函数很明显是一个凸函数,可以用凸优化的各种方法来得到最优解。但是当叠加了多层神经网络的时候,损失函数就变成了一个非凸函数,不能再使用凸优化的方法了。 9 | 10 | **方法 #1** 随机搜索 11 | ```python 12 | # assume X_train is the data where each column is an example (e.g. 3073 x 50,000) 13 | # assume Y_train are the labels (e.g. 1D array of 50,000) 14 | # assume the function L evaluates the loss function 15 | 16 | bestloss = float("inf") # Python assigns the highest possible float value 17 | for num in xrange(1000): 18 | W = np.random.randn(10, 3073) * 0.0001 # generate random parameters 19 | loss = L(X_train, Y_train, W) # get the loss over the entire training set 20 | if loss < bestloss: # keep track of the best solution 21 | bestloss = loss 22 | bestW = W 23 | print 'in attempt %d the loss was %f, best %f' % (num, loss, bestloss) 24 | ``` 25 | 这种方法不用多说,非常坏,因为参数的维度过高,搜索的范围太大。 26 | 27 | **方法 #2** 随机本地搜索Random Local Search 28 | 从一个随机的W开始,每次尝试增加不同的δW,如果W+δW使得loss更低那么就记录下来。 29 | ```python 30 | W = np.random.randn(10, 3073) * 0.001 # generate random starting W 31 | bestloss = float("inf") 32 | for i in xrange(1000): 33 | step_size = 0.0001 34 | Wtry = W + np.random.randn(10, 3073) * step_size 35 | loss = L(Xtr_cols, Ytr, Wtry) 36 | if loss < bestloss: 37 | W = Wtry 38 | bestloss = loss 39 | print 'iter %d loss is %f' % (i, bestloss) 40 | ``` 41 | 42 | **方法 #3** 跟随梯度 43 | 随机出初始的W之后不用随机的search,只要找到loss下降最快的方向就可以,那就是loss function的梯度,对每个参数求偏导,求出这个参数使得loss下降最快的方向。 44 | 45 | ### 计算梯度 46 | 有两种方法计算梯度 47 | 1. 数值解numerical gradient 费时计算量大就是在x附近进行大量得到输出,然后对梯度进行模拟。计算数值解的时候最好用$ [f(x + h) - f(x - h)]/2h $来进行模拟。 48 | 2. 解析解 analytic gradient 快速 计算量小,利用微积分得到导函数的表达式直接计算。使用解析解计算梯度的时候通常要检查gradient计算的是否正确,这称为gradient check 49 | 50 | 计算完梯度,得到每个使loss下降最快的每个参数的变化方向之后就可以更新梯度了。更新梯度的时候需要注意**setp size**也就是learning rate。梯度只告诉我们哪个方向可以让loss下降,但是没有说应该往这个方向走多少的路。如果步子过大的话,反而可能导致loss增加。通常都会使用mini-batch。 51 | ```python 52 | while True: 53 | data_batch = sample_training_data(data, 256) # sample 256 examples 54 | weights_grad = evaluate_gradient(loss_fun, data_batch, weights) 55 | weights += - step_size * weights_grad # perform parameter update 56 | ``` 57 | 更进一步,除了mini-batch,还有SGD(or also some on-line gradient descent)。SGD每次只使用一个样本对参数进行更新。 -------------------------------------------------------------------------------- /cs231n/note4.md: -------------------------------------------------------------------------------- 1 | ### 反向传播 2 | **Problem** 一个函数f(x) 它的输入是一个向量或者矩阵,在这样的情况下进行求导。对于反向传播,X*W,本地对W求偏导,那么就是W的梯度了,然后为了反向,对X求偏导,让对X的导数进行传播。 3 | 4 | **Motivation** 进行反向传播的原因是为了降低loss function的值,根据梯度下降最快的方向进行学习。一般来说只对参数W b计算导数,然而某些时候对输入的数据X计算导数也有用,可以进行一些可视化。 5 | 6 | #### Simple expressions and interpretation of the gradient 7 | $$ \frac{df(x)}{dx} = \lim{h\ \to 0} \frac{f(x + h) - f(h)}{h} $$值得注意的是,左边的分号不能像右边的分号一样看成是除法,左边的分号代表的是$ \frac{d}{dx} $这个算子被应用在函数$ f $上,并且返回这个函数的微分函数。右边的话,可以将$ h $看成是非常小的数值,右边的函数用一个直线来模拟在$ x $处的导数。 8 | 9 | 整个方程也可以看做$ f(x + h) = f(x) + h \frac{df(x)}{dx} $,当x变化a的时候,f(x)变化了ah。 10 | 11 | **每个变量的导数表示了整个方程对这个变量的敏感程度** 12 | 13 | 因为输入输出是矩阵,所以求导的结果也是一个矩阵$ \nabla f = [\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}] = [y, x] $。并且使用“对x的导数”来代替标准的说法“对x的偏导”。 14 | 15 | **加法求导** 16 | $$ f(x,y) = x + y \hspace{0.5in} \rightarrow \hspace{0.5in} \frac{\partial f}{\partial x} = 1 \hspace{0.5in} \frac{\partial f}{\partial y} = 1 $$ 17 | 对x, y的导数忽略了具体的数值,直接等于1,也就是当反向传播回来一个值的时候,经过加法计算导数不变直接向后传播。Sum operation distributes gradients equally to all its inputs. 18 | 19 | **max(x, y)求导** 20 | $$ f(x,y) = \max(x, y) \hspace{0.5in} \rightarrow \hspace{0.5in} \frac{\partial f}{\partial x} = \mathbb{1}(x >= y) \hspace{0.5in} \frac{\partial f}{\partial y} = \mathbb{1}(y >= x) $$ 21 | 如果x最大的话的那么对x的偏导就是1,对y的偏导就是0。但是这个导数并没有体现出函数随输入的它的变化程度,只体现了函数对哪个变量更敏感一点。反向传播的时候只有最大的那个变量的梯度传播回去了,其他的都是0。Max operation routes the gradient to the higher input. 22 | 23 | ### 对反向传播的直观理解 24 | 反向传播是一个良好的local process,它从别的地方接受一个数字,并且输出两个东西: 25 | 1. 经过这个局部计算之后的导数 26 | 2. 本地的导数 27 | 它不需要知道整个计算图的样子,就可以进行工作。在方向传播的时候,链式法则通过让局部的导数乘以之前的导数来完成通过这个计算的传播。反向传播可以看成是每个local计算或者称作门之间进行相互交流,每个局部计算希望他们的输出上升或者下降,以及他们的意愿有多强烈,最终使得loss下降。 28 | 29 | **不那么直观的角度以及微妙的结果**如果输入加法门的值x1很小但是值x2很大,反向传播的时候将会给较小的值x1一个很大的导数,给很大的值x2一个很小的导数。在多层感知机中,通常W*X.T,也就是说X的范围scale决定了权重W的导数值的量级。假如说给X都乘1000,那么影响W的导数也增加了1000倍,这就必须降低学习率来适应。这也体现了预处理的重要性! 30 | 31 | 对反向传播的直观理解有助于debug! 32 | 33 | #### Sigmoid例子 34 | 每个可微分的函数都可以看成一个门计算,也可以把一个加法看成一个门计算,或者将一个函数拆分开来每个都看成一个门计算。以sigmoid为例: 35 | $$ f(w,x) = \frac{1}{1+e^{-(w_0x_0 + w_1x_1 + w_2)}} $$ 36 | 可以被拆分为: 37 | $$ f(x) = \frac{1}{x} 38 | \hspace{1in} \rightarrow \hspace{1in} 39 | \frac{df}{dx} = -1/x^2 40 | \\\\ 41 | f_c(x) = c + x 42 | \hspace{1in} \rightarrow \hspace{1in} 43 | \frac{df}{dx} = 1 44 | \\\\ 45 | f(x) = e^x 46 | \hspace{1in} \rightarrow \hspace{1in} 47 | \frac{df}{dx} = e^x 48 | \\\\ 49 | f_a(x) = ax 50 | \hspace{1in} \rightarrow \hspace{1in} 51 | \frac{df}{dx} = a $$ 52 | 它的导数为 53 | $$ \sigma(x) = \frac{1}{1+e^{-x}} \\\\ 54 | \rightarrow \hspace{0.3in} \frac{d\sigma(x)}{dx} = \frac{e^{-x}}{(1+e^{-x})^2} = \left( \frac{1 + e^{-x} - 1}{1 + e^{-x}} \right) \left( \frac{1}{1+e^{-x}} \right) 55 | = \left( 1 - \sigma(x) \right) \sigma(x)) $$ 56 | sigmoid的导函数是非常简单的,直接用导函数计算导数而不是每个简单的局部计算来叠加可以减少很多数值问题,所以很多框架中都是直接将每个单元的导函数给保存下来,直接计算。 57 | ```python 58 | w = [2,-3,-3] # assume some random weights and data 59 | x = [-1, -2] 60 | 61 | # forward pass 62 | dot = w[0]*x[0] + w[1]*x[1] + w[2] 63 | f = 1.0 / (1 + math.exp(-dot)) # sigmoid function 64 | 65 | # backward pass through the neuron (backpropagation) 66 | ddot = (1 - f) * f # gradient on dot variable, using the sigmoid gradient derivation 67 | dx = [w[0] * ddot, w[1] * ddot] # backprop into x 68 | dw = [x[0] * ddot, x[1] * ddot, 1.0 * ddot] # backprop into w 69 | # we're done! we have the gradients on the inputs to the circuit 70 | ``` 71 | **实现细节** 如上面的代码所示,一般在实现反向传播的时候,都是分阶段来计算导数的,使得每个阶段各自很容易计算导数。 72 | 73 | ### 一个较为复杂的例子 74 | $$ f(x,y) = \frac{x + \sigma(y)}{\sigma(x) + (x+y)^2} $$ 75 | 前向传播 76 | ```python 77 | x = 3 # example values 78 | y = -4 79 | 80 | # forward pass 81 | sigy = 1.0 / (1 + math.exp(-y)) # sigmoid in numerator #(1) 82 | num = x + sigy # numerator #(2) 83 | sigx = 1.0 / (1 + math.exp(-x)) # sigmoid in denominator #(3) 84 | xpy = x + y #(4) 85 | xpysqr = xpy**2 #(5) 86 | den = sigx + xpysqr # denominator #(6) 87 | invden = 1.0 / den #(7) 88 | f = num * invden # done! #(8) 89 | ``` 90 | 反向传播 91 | ```python 92 | # backprop f = num * invden 93 | dnum = invden # gradient on numerator #(8) 94 | dinvden = num #(8) 95 | # backprop invden = 1.0 / den 96 | dden = (-1.0 / (den**2)) * dinvden #(7) 97 | # backprop den = sigx + xpysqr 98 | dsigx = (1) * dden #(6) 99 | dxpysqr = (1) * dden #(6) 100 | # backprop xpysqr = xpy**2 101 | dxpy = (2 * xpy) * dxpysqr #(5) 102 | # backprop xpy = x + y 103 | dx = (1) * dxpy #(4) 104 | dy = (1) * dxpy #(4) 105 | # backprop sigx = 1.0 / (1 + math.exp(-x)) 106 | dx += ((1 - sigx) * sigx) * dsigx # Notice += !! #(3) 107 | # backprop num = x + sigy 108 | dx += (1) * dnum #(2) 109 | dsigy = (1) * dnum #(2) 110 | # backprop sigy = 1.0 / (1 + math.exp(-y)) 111 | dy += ((1 - sigy) * sigy) * dsigy #(1) 112 | # done! phew 113 | ``` 114 | **注意事项** 115 | 1. 缓存几个前向传播时的值,用于反向传播,如上面的num invden xpy等 116 | 2. 当函数f包含好几个关于x的部分的时候,分叉的关于x的导数要进行相加 117 | 118 | ### 矩阵的导数计算 119 | ```python 120 | # forward pass 121 | W = np.random.randn(5, 10) 122 | X = np.random.randn(10, 3) 123 | D = W.dot(X) 124 | 125 | # now suppose we had the gradient on D from above in the circuit 126 | dD = np.random.randn(*D.shape) # same shape as D 127 | dW = dD.dot(X.T) #.T gives the transpose of the matrix 128 | dX = W.T.dot(dD) 129 | ``` 130 | 记住矩阵的导数和这个矩阵的行列数都必须相同! 131 | -------------------------------------------------------------------------------- /cs231n/note5.md: -------------------------------------------------------------------------------- 1 | ### Modeling one neuron 2 | 一开始神经网络是生物医学上对神经系统进行建模产生的,后来被应用到工程和机器学习上。大脑中的基本计算单元是神经元,一个人的神经系统中大约有860亿的神经元,这些神经元之间有大约10^14-10^15个突触。神经元可以看做是激活函数,突触可以看做是权重系数相乘。一个神经元可能收到多个突触传来的信息,同样一个激活函数可能同时收到多个权重系数相乘之和$ \sum_{i}w_{i}x_{i}+b $。经过神经元的激活,如果传来的激活信息达到了则会通过轴突继续向下发送信息,同样的若经过激活函数例如ReLU,没有被抑制则继续传播。 3 | 4 | ### Single neron as a linear classifier 5 | 一个简单的线性回归可以看作是一个神经元,他可以表现出对输入的喜欢(激活)或者不喜欢(抑制)。 6 | 7 | **Binary Softmax classifier**:$ \sigma(\sum_{i}w_{i}x_{i} + b) $将输出转换成一个类别的概率$ P(y_i = 1 \mid x_i; w) $。而另一个类别的概率就表示为$ P(y_i = 0 \mid x_i; w) = 1 - P(y_i = 1 \mid x_i; w) $。 8 | 9 | **Binary SVM classifier**:使用max-margin loss来得到神经元的输出并进行训练,使之成为一个二分类SVM。 10 | 11 | **Regularization interpretation**:在SVM softmax loss中的正则化部分在生物的角度可以看作是遗忘的作用,它会将某个伸向神经元的突触的权重变为接近0. 12 | 13 | ### Commonly used activation functions 14 | **Sigmoid** 15 | $ \sigma(x) = 1/(1 + e^{-x}) $。它接受一个实数并且将这个数压缩到0-1之间,特别大的数字压缩为1,特别小的数字压缩为0。现在通常不使用sigmoid,之前使用sigmoid是将它的输出为0表示为抑制状态,输出为1表示为激活状态。 16 | 17 | - Sigmoid会导致梯度消失。当数值特别大或者特别小的时候局部梯度会变成0,乘以反向传播回来的导数,导致了前面的参数无法得到更新。使用sigmoid的时候需要特别小心进行初始化,如果初始化不好就会梯度消失。 18 | - Sigmoid的输出不是以0为中心的,是以0.5为中心的。这个特性导致后续的神经元都会接收到不以0为中心的数据。如果输入神经元的数据都是正数$ f = w^Tx + b $,那么反向传播的时候对w的导数将一直是正数,或者一直是负数。这会导致**loss难以下降**。会让loss呈Z字形变化。但是一个batch的数据都进行更新某种程度上缓和了这个影响。总之这是个不方便但是影响也没那么大的坏处。 19 | 20 | **Tanh** 21 | 双曲正切函数,也可以由sigmoid函数来表示:$ tanh(x) = a\sigma(2x)-1 $tanh将输入的实数压缩到-1到1之间,不同于Sigmoid,它的输出是以0为中心的zero-center。两者相比的话tanh更好。 22 | 23 | **ReLU** 24 | 线性整流函数,Rectified Linear Unit非常流行,表达式为$ f(x) = max(0,x) $。 25 | - 它可以加速神经网络的训练。相比sigmoid和tanh它的训练速度更快 26 | - 计算量小。相比sigmoid tanh它的计算量更小 27 | - 但是会导致dead unit。如果输入的数据是负数,那么这个神经元将不再发挥作用,反向传播的时候也不会更新参数。如果学习率太高,在训练的时候可能高达40%的神经元会dead(这些神经元对整个数据集都不会激活)。 28 | 29 | **Leaky ReLU** 30 | Leaky ReLU试图去修复ReLU中抑制神经元的问题,若输入的数据是负数,Leaky ReLU将不会给它一个0的导数,而是会给一个很小的导数如0.01。在有些数据集上Leaky ReLU取得了较好的效果,但是并不总是比ReLU好。还有[PReLU](https://arxiv.org/abs/1502.01852) 31 | 32 | **Maxout** 33 | 表达式为$ max(w_1^Tx+b_1, w_2^Tx+b_2) $,可以看到ReLU和Leaky ReLU是这个函数的特殊形式(对于ReLU w1,b1=0)它拥有ReLU的所有优势,并且没有它的缺点。但是这个激活函数会导致计算量增大。 34 | 35 | **应该使用什么激活函数** 36 | 一般可以直接试一试ReLU,并且注意学习率和dead的神经元。如果担心dead的神经元的话可以试一试Leaky ReLU或者Maxout。千万不要使用Maxout,也许可以尝试tanh,但是它的效果一定会比较差的。 37 | 38 | ### 神经网络架构 39 | **Naming conventions**当说N层神经网络的时候一般不算上输入层,logistic回归和SVM分类是特殊的单层神经网络。多层神经网络有时候也被称为ANN MLP。为了不和大脑的神经系统混淆,一般把神经元neuron称作单元unit。 40 | 41 | **Output Layer**输出层一般没有激活函数,或者认为他们有一个恒等的激活函数。这是因为最后一层需要输出例如分类的score,不能被squash,或者是用于回归最后也不能被squash。 42 | 43 | **Sizing neural networks**衡量神经网络大小的标准一般是衡量神经网络中的神经元有多少,或者更长见的是衡量可学习的参数有多少。现代的CNN一般有1亿个参数,10-20层。 44 | 45 | ### 神经网路的表示能力 46 | 理论上来说单层的神经网络加上一个非线性激活函数可以[模拟任何的连续函数](http://neuralnetworksanddeeplearning.com/chap4.html)。为什么单层的神经网络就可以模拟任何的函数,现在的很多任务都用很深的神经网络。因为单层的神经网络从理论上是可以模拟,但是实现的时候是两层神经网络比一层好。通常3层神经网络比2层好,但是更深的话就没有更好的表现了。但是再卷积神经网络中是越深越好。 47 | 48 | ### 使用什么样的神经网络架构 49 | 当增加神经网络的层数或是增加更多的隐藏层神经元的时候,它的容量capacity也增加了。它所能表示的函数范围也增加了,但是也更容易过拟合。**过拟合**一个模型拥有很高的容量,它同时学会了数据中的噪音而不仅仅是数据之间的关系。 50 | 51 | 似乎当数据没那么复杂的时候小型的网络更受青睐,但实际上小型的神经网络更难以进行训练,很容易陷入局部最优解,一般采用的是较大的神经网络并采取一些正则化手段如L2-norm,而不是减少神经元的数量来控制过拟合。 52 | 53 | 小的神经网络有相对少的局部最优解,大的神经网络虽然有更多的局部最优解,但是它更容易克服这一点,更容易从中走出来。参考[ The Loss Surfaces of Multilayer Networks](https://arxiv.org/abs/1412.0233)。并且大的神经网络的全局最优比小的神经网络的全局最优更好。 -------------------------------------------------------------------------------- /cs231n/note6.md: -------------------------------------------------------------------------------- 1 | ### Data Preprocessing 2 | 假设输入的数据是一个矩阵X,(N, D)N是数据的数目,D是特征数。一般来说有三种方法对数据进行预处理。 3 | 4 | **Mean subtraction** 5 | 在每个特征上独立减去各自的均值,实现对数据zero-center以零为中心。 6 | ```python 7 | # 在numpy中可以直接减去均值 8 | X -= np.mean(X, axis=0) 9 | 10 | # 对于图片可以直接减去一个值,或者各个通道自己减去均值 11 | X -= np.mean(X) 12 | ``` 13 | 14 | **Normalization** 15 | 两种方法: 16 | 1. 当数据变成zero-center之后,```X /= np.std(X, axis=0)``` 17 | 2. 标准化各个维度的数据使得最大的值为1 最小的值为-1.只有当不同的特征有不同的scale尺度的时候使用这种方法,然而对图片来说都是0-255.所以一般在图像中不适用这种方法。 18 | 19 | **注意**标准化只能在训练集上应用,并提取出均值和方差,再应用于测试集和验证集。而不是先在所有数据中提取均值和方差,然后应用于各个集合中。 20 | 21 | **PCA以及白化** 22 | 数据先中心化zero-center,然后计算相关系数矩阵。 23 | ```python 24 | # Assume input data matrix X of size [N x D] 25 | X -= np.mean(X, axis = 0) # zero-center the data (important) 26 | cov = np.dot(X.T, X) / X.shape[0] # get the data covariance matrix 27 | ``` 28 | cov[i,j]包含了第i个特征和第j个特征之间的相关系数,cov的对角线上是方差,cov是对称矩阵、正定矩阵。然后进行奇异值分解SVD 29 | ```python 30 | U,S,V = np.linalg.svd(cov) 31 | ``` 32 | U中的列向量就是奇异向量,S是一个包含奇异值的一维向量(已经排序),为了去相关性,将中心化之后的数据乘以U。 33 | ```python 34 | Xrot = np.dot(X, U) # 去相关性 X rotate (N, D) 35 | ``` 36 | 注意到U中的列向量(奇异向量)是互相正交的,可以看作是一个新的坐标体系。和中心化之后的数据相乘相当于让数据进行旋转。相乘后Xrot矩阵的相关系数组成的矩阵是对角矩阵,这意味着Xrot中的每列已经不再互相关联了。同时可以使用奇异值进行降维,S中的每个奇异值都和U中奇异向量一一对应,只需要少数几个U中的奇异向量就可以代表整个数据。 37 | ```python 38 | Xrot_reduced = np.dot(X, U[:, :100]) # (N, 100) 39 | ``` 40 | PCA是针对方阵的,而SVD是PCA的更普遍版本,对于非方阵也可以处理。 41 | 42 | **白化 whitening** 43 | 白化操作接受PCA之后的数据并且将每个特征都除以对应的奇异值来统一尺度scale。 44 | ```python 45 | Xwhite = Xrot / np.sqrt(S + 1e-5) # (N, D) 46 | ``` 47 | 白化效果: 48 | 1. 这个矩阵将变成均值为0,方差都相同的矩阵。加上1e-5是为了防止下溢和除以零,同时进行平滑作用。 49 | 2. 可视化白化的效果,就好像所有维度的数据都被压缩squash进了一个圈中(二维看来是一个圈)。 50 | 51 | 白化的缺点: 52 | 1. 会极大的扩大噪音。白化会在所有的特征维度都进行,尤其是那些有微小方差的与其他维度相关性小的维度也会收到影响。这一点可以通过更强的平滑系数,即将1e-5增大来改善。 53 | 2. 白化计算量大,对于图像来说协方差矩阵太大计算量太大。所以卷积神经网路中一般不使用白化,但是标准化是需要的。 54 | 55 | ### Weight Initialization 56 | **不能全部初始化为同样的数字/0** 57 | 一开始我们并不知道权重最后会被优化成什么样子,但是预处理过后我们可以假设到初始化的权重大概有一半为负数一半为正数。但是这不代表可以将所有的权重都初始化为零,这会导致所有神经元输出同样的数据,反向传播同样的数据,这导致所有的神经元都是相同的。 58 | 59 | **随机的小数值** 60 | 我们仍希望初始化的权重接近零,但又不是零。所以使用随机的互相不同的小数值。这也被看作是_产生不同的神经元_。```W = 0.01*np.random.rand(D, H)```rand函数从正态分布(高斯分布)中进行采样。 61 | 但是随机的小数值也不一定会产生好的效果,这可能会导致很小的梯度,可能会导致梯度消失。 62 | 63 | **使用1/sqrt(n)校准方差** 64 | 使用随机的小数值进行初始化的一个缺点就是,每个神经元的输入都是有方差的,并且会随着神经元数目的增多而累计。所以通过除以sqrt(fan\_in)来消除方差(fan\_in是输入的神经元的数目)。 65 | ```python 66 | w = np.random.rand(n) / sqrt(n) 67 | ``` 68 | 1. 保证所有神经元的输出都处于一个相同的分布中 69 | 2. 提高了收敛的速度 70 | 为什么除以sqrt(n)的推导过程,$ s = \sum_i^nw_ix_i $,下面是$ s $的方差推导 71 | $$ % $$ 79 | 第一二步是方差的性质,第三步因为假设输入的x的均值是0所以$ E(x_i) = E(w_i) = 0 $,最后一步是因为假设了$ w_i x_i $是同样的分布。所以如果希望神经元的输出$ s $是都处于同样分布中的,那么就需要使得$ w $的方差是$ 1/n $。且因为$ Var(aX) = a^2Var(X) $(a是标量 X是随机变量),所以从正态分布中采样之后就就除以$ a = \sqrt(1/n) $。 80 | 81 | Glorot Xavier提出了从$ Var(w) = 2/(n_{in} + n_{out}) $中进行初始化,n\_in是输入的神经元的数目 n\_out是输出的神经元的数目。 82 | ```python 83 | w=np.random.rand(n_int, n_out) / np.sqrt(2/(n_in+n_out)) 84 | ``` 85 | 86 | 何凯明的针对ReLU的MSAR初始化方法,他们证明了使用ReLU的神经网络方差应该为$ 2.0/n_{in} $所以他们的初始化方法为: 87 | ```python 88 | w = np.random.rand(n) * sqrt(2.0/n) 89 | ``` 90 | 91 | **Sparse Initialization** 92 | 稀疏初始化,另一个解决不统一的方差。把所有的权重都设置为随机的小数值,并且随机让他们进行联结固定数目的下一层神经元,一般是10个神经元。 93 | 94 | **Initializing the biases** 95 | 把bias初始化为0是很常见的方法。使用ReLU的时候很多人会把bias初始化为固定的较小的常数如0.01,这样可以使得一开始ReLU能够被激活,并且得到一些导数值,但是目前并不清楚这种方法有什么好处,好像训练的效果还会变坏。所以一般还是将bias初始化为0. 96 | 97 | **Batch Normalization** 98 | BN可以消除很多的初始化问题,在全连接层或者卷积层之后加上激活函数。BN对于差的初始化有更好的鲁棒性。BN也可以看作是每一层都进行了预处理,但是这个预处理以可微分的形式融合进了神经网络本身里。 99 | 100 | ### Regularization 101 | 控制神经网路的容量来防止过拟合。 102 | 103 | **L2 Regularization** 104 | L2正则化比较常见,它意味着直接在每个权重$ w $上加上$ \frac{1}{2}\lambdaw^2 $其中$ \lambda $是正则化的程度。前面的1/2是为了求导后得到$ \lambdaw $。L2正则化直观的看出来,它对于某个值较大的权重的惩罚力度很大,并且倾向于更分散的权重,让神经网络的每一层利用更多的输入神经元。而使用L2正则化意味着每个权重都是线性下降的 ```W += -lambda * W``` 105 | 106 | **L1 Regularization** 107 | 另一个常见的正则化方法。对每个权重$ w $都加上$ \lambda|w| $。也有把L1 L2正则化结合起来的$ \lambda_1|w| + \lambda_2w^2 $,这也称作 Elastic net regularization。L1正则化有个很有意思的特性,在优化的过程中,它会导致神经网络的权重变得稀疏(非常接近0),换而言之就是最后只有一部分权重能进行激活,这使得对噪音有着更强的鲁棒性。通常来说使用L2的效果要好于L1。 108 | 109 | **Max norm constrains** 110 | 另一种正则化就是限制每个权重的最大值,$ \lVert\overrightarrow{x}\rVert_2 < c $。 111 | 112 | **Dropout** 113 | 对于全连接神经网络单元,按照一定的概率使其暂时失效,故而每个mini-batch相当于在训练不同的神经网络,它强迫一个神经单元和随机挑选出来的其他神经单元共同工作,消除了神经元节点之间的联合适应性,增强了泛化能力,是CNN中防止过拟合的一个重要方法。 114 | ```python 115 | p = 0.5 # probability of keeping a unit active. higher = less dropout 116 | 117 | def train_step(X): 118 | """ X contains the data """ 119 | 120 | # forward pass for example 3-layer neural network 121 | H1 = np.maximum(0, np.dot(W1, X) + b1) # 使用ReLU作为激活函数,生成hidden layer 122 | U1 = np.random.rand(*H1.shape) < p # first dropout mask 123 | H1 *= U1 # drop! 124 | H2 = np.maximum(0, np.dot(W2, H1) + b2) 125 | U2 = np.random.rand(*H2.shape) < p # second dropout mask 126 | H2 *= U2 # drop! 127 | out = np.dot(W3, H2) + b3 128 | 129 | # backward pass: compute gradients... (not shown) 130 | # perform parameter update... (not shown) 131 | 132 | def predict(X): 133 | # ensembled forward pass 134 | H1 = np.maximum(0, np.dot(W1, X) + b1) * p # NOTE: scale the activations 135 | H2 = np.maximum(0, np.dot(W2, H1) + b2) * p # NOTE: scale the activations 136 | out = np.dot(W3, H2) + b3 137 | ``` 138 | 在predict的时候不再进行dropout,但是原来进行dropout的层都要乘以概率p。因为在predict的时候所有的神经元都能看到所有的输入,所以他们接受的输入尺度要和训练的时候一样。在训练的时候有dropout,每个神经元的输入为$ px + (1 - p)0 $,期望就是$ px $,所以在predict的时候就需要让输入变为$ px $. 139 | 140 | dropout也可以看作是训练了多个神经网络,然后在predict的时候将输入x乘p,在所有的单个神经网络上都遍历一遍,将他们集成起来计算集成后的输出。 141 | 142 | dropout有个缺点就是在测试的时候还需要计算,这增加了计算量。也有一种**inverted dropout**,训练的时候和普通dropout相同,只是在测试的时候不乘以p。 143 | 144 | 145 | 在更大的范围来说,dropout属于神经网络中加上随机化的一种方法。在前向传播的时候加上随机的噪音。两种形式的随机噪音: 146 | 1. 解析上,如dropout 147 | 2. 数值上,几种不同的随机采样方法,然后再对这几种方法进行平均,然后再前向传播。 148 | CNN也利用了这种形式的方法,例如Data argumentation,stochastic pooling等。 149 | 150 | **Bias regularization** 151 | 通常不对bias进行正则化,因为他们并没有和数据进行直接的交互。但是在实践中对bias进行正则化好像并没有什么坏处,也可能是因为bias相对其他的参数比较少,对整体的影响较小。 152 | 153 | **实践总结** 154 | 实践中一般使用L2正则化,正则化的强度通过交叉验证来得到。并且在所有层的后面加上p=0.5的dropout层,当然这个p也可以通过交叉验证来调整。 155 | 156 | ### Loss function 157 | loss一般是一个batch中的各个样本的loss的均值$ L = \frac{1}{N}\sum_iL_i $. 158 | 159 | **分类** 160 | SVM loss:$$ L_i = \sum_{j\neq y_i} \max(0, f_j - f_{y_i} + 1) $$,也有人说使用平方效果更好$ \max(0, f_j - f_{yi} + 1)^2 $。 161 | 162 | cross entropy loss:$$ L_i = -\log\left(\frac{e^{f_{y_i}}}{ \sum_j e^{f_j} }\right) $$ 163 | 164 | **问题:类别太多** 165 | 当数据集的类别太多的时候例如ImageNet 22000个类别,可以使用Hierarchical Softmax。它将标签解构成一个树,每一个标签代表了树上的一条路径,一个softmax分类器在每个节点上进行分类,区分左右分支,树的结构对结果性能很影响。 166 | 167 | **属性分类** 168 | SVM loss和交叉熵都假设每个样本只有一个标签。但是假如标签是一个二元的向量(只包含0/1)呢,代表每个样本是否含有这个属性。一个方法是对每个属性建立一个二分类器。每个类别独立的二分类器也许是这个样子的:$$ L_i = \sum_i\max(0, 1-u_{ij}f_j) $$ 169 | $ f_j $是一个对属性j的二分类score function,$ y_{ij} $取-1或+1.loss在所有需要分类的属性上进行求和。 170 | 171 | 另一个方法是对每个需要分类的属性训练一个logistic二分类器,只有两个类别输出为(0, 1)。输出这个属性为1的概率为:$$ P(y = 1 \mid x; w, b) = \frac{1}{1 + e^{-(w^Tx +b)}} = \sigma (w^Tx + b) $$。所以如果输出$ \sigma(w^Tx + b) > 0.5 $那么就可以看作是1这个类别。loss函数只需要将似然函数极大化就可以了$$ L_i = \sum_j y_{ij} \log(\sigma(f_j)) + (1 - y_{ij}) \log(1 - \sigma(f_j)) $$。这个loss函数的导函数为$ \partial{L_i} / \partial{f_j} = y_{ij} - \sigma(f_j) $。 172 | 173 | **回归** 174 | 衡量输出和标签之间的差异,使用L2平方norm$ L_i = \Vert f - y_i \Vert_2^2 $或者L1 norm$ L_i = \Vert f - y_i \Vert_1 = \sum_j \mid f_j - (y_i)_j \mid $,L1 norm在输出的每个维度上进行求差的绝对值和(输出是一个向量)。只看第i个样本输出的第j维度,和标签之间的差异可以记作$ \delta_{ij} $。对于L2 norm来说这个维度上的导数就直接是$ \partial{L_i} / \partial{f_j} $,对于L1 norm来说就是$ sign(\delta_{ij}) $。总之导数很容易求得。 175 | 176 | **注意** 177 | 相比softmax loss等,L2 norm更难进行训练。并且它的鲁棒性也更差因为离群点会产生更大的导数,影响更大。当遇到一个回归问题的时候,首先思考能不能把它变成一个分类问题,例如给东西达1-5星,最好是训练5个独立的分类器而不是使用回归的方法。如果不能分解为分类问题的话,小心地使用L2 norm并且同时使用使用dropout可能不会很好。 178 | 179 | **结构化的预测** 180 | 结构化的预测指的是利用结构化的标签如图、树或者其他复杂的结构。并且也通常假设这个结构的空间非常大,没办法进行遍历。进行这类的预测通常梯度下降难以办到。 -------------------------------------------------------------------------------- /cs231n/note7.md: -------------------------------------------------------------------------------- 1 | ### Gradient Checks 2 | 梯度检查很重要,并且也很容易出错。 3 | 4 | **使用正确的表达式** 5 | 使用$ \frac{df(x)}{dx} = \frac{f(x+h)-f(x-h)}{2h} $而不是$ \frac{df(x)}{dx} = \frac{f(x+h)-f(x)}{h} $。h是非常小的数值,如1e-5。第一种表达式是中心化的,需要两个输出才能衡量梯度。 6 | 7 | **使用相对误差** 8 | 比较数值梯度和解析梯度的时候,如果是直接使用L1或者L2距离,然后超过一个阈值就算作出现误差,那么这是有问题的。假如数值梯度和解析梯度之间的误差是1e-4,如果梯度的范围在1附近,那么我们可以看做解析梯度没有问题,但是如果梯度的数值都在1e-5附近,那么就有问题了。所以要使用相对误差$$ \frac{|f'_a-f'_n|}{\max(|f'_a|, |f'_n|)} $$。相对误差考虑了两者个梯度的范围,分母将两者相加也可以,总之要避免除0(两者其中之一可能为0)。在实践中: 9 | - 相对误差>1e-2就是有问题的了 10 | - 1e-2>相对误差>1e-4也不是很好,可能也有问题 11 | - 1e-4>相对误差,如果神经网络中存在左右极限不相等的点那么这个结果是可以的,但是如果函数是处处可导的这个结果是有问题的。 12 | - 1e-7>相对误差,这个结果是好的 13 | - 神经网络的深度越深,相对误差也会越高,所以10层的神经网络1e-2的相对误差也许是可以的 14 | 15 | **使用Double类型** 16 | 使用单精度float检查梯度,会扩大误差,也许使用单精度相对误差是1e-2使用双精度就是1e-8了。 17 | 18 | **仔细观察网络中的数值** 19 | 输出每层的数据查看一下,当某一层的输出变得非常小1e-10,这会导致数值问题。 20 | 21 | **目标函数中的不可导点** 22 | 不可导点会引起准确度的问题,如ReLU中的(0, 0)点,或者SVM loss,Maxout神经元中的不可导点。 23 | 24 | **使用少数几个数据点** 25 | loss function中也有不可导的点,使用少数的数据进行梯度检查更快而且也不至于扩大不可导点的影响,2-3个数据就可以。 26 | 27 | **注意h的大小** 28 | h一般来说越小越好,越小就会引起数值问题。h为1e-4或者1e-6之间可以。 29 | 30 | **不要让正则化淹没数据** 31 | 32 | **关闭dropout和数据增强** 33 | 34 | **只在少数的维度进行检测** 35 | 36 | ### 在开始学习之前 37 | **检查loss** 38 | 先把正则化设置为0,然后看一下loss是不是合理的,例如在使用softmax的cifar-10中初始的loss应该是2.302左右,因为初始的时候神经网络相当于是乱猜测,每个类别的概率相同所以-ln(0.1)=2.302。对于SVM loss初始的loss值应该为9,因为每个错误的类别都有一个margin=1,如果初始的loss不是这个的话那么参数的初始化可能就有问题了。 39 | 40 | 然后检查增加正则化力度是否增加了loss的值。 41 | 42 | **先再小数据集上过拟合** 43 | 先试一试在只有20个样本的数据集上进行过拟合,检查神经网络是否能达到接近0的loss,同样这时候也设置正则化为0。当然也有可能在小数据集上能够过拟合,代码也许仍然是有问题的。 44 | 45 | ### 小心照顾学习的过程 46 | 在训练的过程中应该关注着神经网络,关注每个epoch中的变化,比关注每个batch要好一些,因为每个batch多少个数据是随着batch_size的设定的。关注随着epoch增加loss的下降过程。 47 | - 当loss来回震荡地很厉害的时候,有可能是batch_size有点小,每个batch中的噪声较大,当batch size是1的时候震荡会最严重(会让神经网络向局部最优的方向前进),当batch size是整个数据集的时候震荡会最小(会让神经网络向全局最优的方向前进)。 48 | - 非常高的学习率会导致loss一直上升没有下降 49 | - 较高的学习率会导致loss下降到一个程度后难以下降 50 | - 合适的学习率能够一直下降到全局最优 51 | - 太小的学习率会导致loss下降速度缓慢 52 | 有些人会将loss取log之后制成图,这样看起来更像一条直线而不是曲线。 53 | 54 | **训练/验证准确率** 55 | 如果验证集上的准确率比训练集上的小表示已经出现了过拟合。解决过拟合的方法当然是使用drpout或者L2正则化或者使用更多的数据。 56 | 57 | **权重的更新程度** 58 | 另一个要跟踪的数据就是权重更新的量级和权重本身的量级,权重更新的程度不是梯度本身的程度还要乘上学习率。更新的程度/权重本身的量级应该在1e-3附近。如果小于这个数字,那么是学习率太低了,如果高于这个数字就是学习率太高了。相较于使用最大最小值,更常用的是计算参数的norm和更新程度的norm。当然他们的效果都差不多。 59 | ```python 60 | # 参数矩阵W以及它的导数dW 61 | param_scale = np.linalg.norm(W.ravel()) # ravel()将矩阵展成一维 62 | update = -learning_rate*dW # simple SGD update 63 | update_scale = np.linalg.norm(update.ravel()) 64 | W += update # the actual update 65 | print update_scale / param_scale # want ~1e-3 66 | ``` 67 | 68 | **激活的神经元/梯度的分布** 69 | 一个不好的初始化可能会导致学习过程变慢,一个解决的方法就是绘制神经网络中每一层的activation/gradient直方图。如果看到什么奇怪的分布的话就不好了,比如使用tanh的话这一层的输出如果均匀的分步在[-1, 1]之间的话是正常的,如果全是0或者集中在-1 1这两个点的就不正常了。 70 | 71 | **第一层参数做可视化** 72 | 如果可视化的结果是很粗糙并且杂乱的,那么就不好。如果是光滑的,并且有明显条纹的就是好的。 73 | 74 | ### 更新参数 75 | 梯度的解析值一旦计算出来就是更新参数了。 76 | 77 | ## SGD及其各种变体 78 | 79 | **普通的SGD** 80 | ```python 81 | x += -learning_rate * dx 82 | ``` 83 | 84 | **加上动量** 85 | 增加动量之后有更快的拟合速度。动量可以从物理的角度来解释,loss的下降过程可以看做是在多山的丘陵地带往山底走。初始时随机参数相当于在山上随机找个位置静止站着,优化的过程可以看做是从山上滚下来的过程。在山上的每个位置收到的力和所处位置相关($ U = mgh, F = -\nabla U $),这个自然就是梯度了。但同时从原来的地方滚下来的时候,物体还在原来的方向上有动量(上次更新的方向)。所以滚到新的位置的时候,速度应该是现在收到的力和原来的动量相加。 86 | ```python 87 | v = mu * v - learning_rate * dx # 速度相加 88 | x += v # 更新后的位置进行相加 89 | ``` 90 | mu代表的是动量momentum,一般设置为0.9即可。交叉验证的时候一般在[0.5, 0.9, 0.95, 0.99]中进行查找。使用动量某种意义上也代表学习会一直进行,一旦开始动量会存在很长时间。动量和学习率的退火过程很像。一种方法是先设置动量为0.5然后逐步增加到0.99. 91 | 动量的方法让每次更新都不只是一个方向。 92 | 93 | **Nesterov动量** 94 | 这是动量的一种特殊版本,最近开始流行,在凸优化方法有着良好的理论和实践。并且它的效果比普通的动量要好。之前是先求得更新的程度(学习率先乘以导数),这里是先让x+=mu*v,相当于让x先沿着之前的向量走一步。然后再更新。 95 | ```python 96 | x_ahead = x + mu * v 97 | # evaluate dx_ahead (the gradient at x_ahead instead of at x) 98 | v = mu * v - learning_rate * dx_ahead 99 | x += v 100 | ``` 101 | 或者是这样写 102 | ```python 103 | v_prev = v # back this up 104 | v = mu * v - learning_rate * dx # velocity update stays the same 105 | x += -mu * v_prev + (1 + mu) * v # position update changes form 106 | ``` 107 | 108 | **学习率退火** 109 | 直观理解学习率退火:有着较高学习率的神经网络总有很高的能量,让它在山坡上难以下来,去到最优的点。但是何时降低学习率是一个问题,如果缓慢的一点一点降低学习率可能会浪费计算量。但是在关键点降低学习率将会加速学习的过程: 110 | - 阶段性降低,设置里程碑,如每隔20epoch降低0.1 111 | - 指数降低$ \alpha = \alpha_0e^{-kt} $,其中$ \alpha k $是超参数,t是迭代次数,当然也可以使用epoch数 112 | - 1/t降低$ \alpha = \alpha_0(1 + kt) $其中$\alpha k$是超参数 t是迭代次数 113 | 在实践的过程中一般阶段性降低更好。 114 | 115 | ### 二阶方法 116 | 流行的二阶优化方法是[牛顿法](https://en.wikipedia.org/wiki/Newton%27s_method_in_optimization):$$ x \leftarrow x - [Hf(x)]^{-1}\nabla f(x) $$其中$ Hf(x) $是黑塞矩阵,是方程的二阶导数方阵。但是目前使用牛顿法来优化不现实,因为涉及到的计算量太大了。 117 | 118 | ### 自动调整学习率 119 | 在训练的过程中根据情况全局调整学习率代价比较大,所以有了自动调整学习率的算法。大多数这种算法都是需要额外的超参数,但是也比一般的SGD效果好很多。 120 | 121 | **Adagrad** 122 | ```python 123 | # x是参数dx是导数 124 | cache += dx**2 125 | x += - learning_rate * dx / (np.sqrt(cache) + eps) 126 | ``` 127 | cache的size和x是一样的,它用于跟踪之前的所有梯度的平方。注意到当梯度变大的时候有效的学习率其实是降低的,当有小的梯度的时候学习率更有效。并且梯度的平方再开方这一点是很关键的,不是先平方再开方的话算法的效果会很差。eps通常设置为1e-4~1e-8之间,用于防止除零。缺点就是它的学习率不是单调下降的,这会导致学习得到提前终止。 128 | 129 | **RMSprop** 130 | 这个方法还没有正式的发表,如果工作中使用了的话需要去引用Geoff Hinton的Coursera课程。PMSProp对Adagrad进行了一点微调。 131 | ```python 132 | cache = decay_rate * cache + (1 - decay_rate) * dx**2 133 | x += - learning_rate * dx / (np.sqrt(cache) + eps) 134 | ``` 135 | decay_rate是一个超参数,通常为[0.9, 0.99, 0.999]. 136 | 137 | **Adam** 138 | Adam有点像RMSProp,最近才被提出。 139 | ```python 140 | m = beta1*m + (1-beta1)*dx 141 | v = beta2*v + (1-beta2)*(dx**2) 142 | x += - learning_rate * m / (np.sqrt(v) + eps) 143 | ``` 144 | 最后一行和RMSProp中的一样,只不过 使用了m而不是原始的梯度。论文中推荐的超参数是eps=1e-8, beta1=0.9, beta2=0.99。Adam是被推荐的默认的优化方法,比RMSProp要好,不过也可以尝试SGD+Nesterov Momentum试一试。 145 | 146 | 完全的Adam还包含了一个偏差纠正机制,是为了补偿m,v所以开始的几次m,v都是被初始化的,因此偏差是0,这样他们就完全warm-up了 147 | ```python 148 | # t is your iteration counter going from 1 to infinity 149 | m = beta1*m + (1-beta1)*dx 150 | mt = m / (1-beta1**t) 151 | v = beta2*v + (1-beta2)*(dx**2) 152 | vt = v / (1-beta2**t) 153 | x += - learning_rate * mt / (np.sqrt(vt) + eps) 154 | ``` 155 | 156 | ### 超参数优化 157 | 神经网络主要的的超参数: 158 | - 初始的学习率 159 | - 学习率下降的策略 160 | - 正则化力度labmda 161 | - dropout概率 162 | 当然除了这些还有其他的影响较小的超参数。 163 | 164 | **Implementation** 165 | 大型的神经网络训练时间很长,所以进行超参数的优化可能需要花费数周的时间。当然实现一个自动改变超参数并且进行训练的控制代码worder也是很方便的。控制代码worker自动追踪每个epoch之后在验证集上的准确率并且自动的保存checkpoint。然后有一个的master在服务器集群上对每个worker进行控制,及时检查checkpoint并且绘制训练相关的图表。 166 | 167 | **超参数的范围** 168 | 在log的尺度上搜寻合适的超参数,比如学习率的搜索可以```learning_rate = 10 ** uniform(-6, 1)```其实就是[1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 1, 10]中去寻找。正则化力度lambda同样可以这样搜索。 169 | 170 | **grid search** 171 | 虽然对于神经网络来说[随机搜索超参数比网格化搜素更有效](http://www.jmlr.org/papers/volume13/bergstra12a/bergstra12a.pdf),但是这也是一种常用的方法。 172 | 173 | **小心超参数范围的边界** 174 | 确保在范围内搜索到的超参数不是在范围的边界,如学习率在1e-6~10之间进行搜索,那么如果最优的学习率如果是1e-6的话,应该再去更小的数值进行搜索。 175 | 176 | **分阶段从粗糙到精细调整超参数** 177 | 在调整的过程中慢慢缩小范围,一开始是在10**[-6, 1]之间,然后缩小。最后找到最优的超参数。 178 | 179 | **利用贝叶斯来寻找最优超参数** 180 | 在卷积神经网络里使用的仍然较少。卷积神经网络使用的还是随机搜素更多一点。 181 | 182 | ### Evaluation 183 | 在实践中,最后能够提升一点效果的就是训练多个神经网络然后进行集成,测试的时候对多个结果取平均。当模型越不相同的时候集成的效果越好。 184 | - **相同的模型,不同的初始化**使用交叉验证决定最好的超参数,然后用最好的超参数去训练多个不同随机初始的模型。但是模型之间的不同仅仅在于初始化。 185 | - **交叉验证发现模型**使用交叉验证发现最好的几个模型进行集成。但是这样可能会有不达标的模型存在。但在实践中这样很方便。 186 | - **同一个模型的不同checkpoint**如果训练起来十分耗时,那么可以使用同一个模型的不同checkpoint进行集成。这样缺少了一些不同variety,但是实践中仍然是合理的,这种方法最方便。 187 | - **使用checkpoint的参数的均值**可以这么理解(/滑稽),loss函数是一个碗状,然后两边取平均就掉进最优点了。 -------------------------------------------------------------------------------- /cs231n/note8.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | # 这一节是一个两层的神经网络的例子 6 | 7 | N = 100 # 每个类别的数据 8 | D = 2 # 特征数 9 | K = 3 # 3个类别 10 | step_size = 1e-0 # 学习率 11 | reg = 1e-3 # 正则化程度 12 | 13 | def generating_data(): 14 | """ 15 | 产生数据 16 | """ 17 | X = np.zeros((N*K, D)) # 样本数据 每行是一条数据 18 | y = np.zeros(N*K, dtype='uint8') # label 19 | for j in xrange(K): 20 | ix = range(N*j, N*(j+1)) 21 | r = np.linspace(0.0, 1, N) 22 | t = np.linspace(j*4,(j+1)*4,N) + np.random.randn(N)*0.2 # theta 23 | X[ix] = np.c_[r*np.sin(t), r*np.cos(t)] 24 | y[ix] = j 25 | # lets visualize the data: 26 | # plt.scatter(X[:, 0], X[:, 1], c=y, s=40, cmap=plt.cm.Spectral) 27 | # plt.show() 28 | return X, y 29 | 30 | def train_linear(X, y): 31 | # 从正态分布中随机采样,并且乘以0.01 产生较小的数字 32 | # 但是这里没有考虑经过每层的输出是否是分布相同的 33 | # 1/sqrt(n_out) 34 | W = 0.01 * np.random.rand(D, K) 35 | b = np.zeros((1, K)) 36 | 37 | num_examples = X.shape[0] 38 | for i in xrange(200): 39 | scores = np.dot(X, W) + b # (N, K) 40 | # 计算概率 41 | exp_scores = np.exp(scores) 42 | probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True) # (N, K) 43 | # 得到正确的概率 索引得到正确的概率 44 | correct_logprobs = -np.log(probs[range(num_examples), y]) 45 | data_loss = np.sum(correct_logprobs)/num_examples 46 | reg_loss = 0.5*reg*np.sum(W*W) # 0.5是为了求导消去 47 | loss = data_loss + reg_loss 48 | if i % 10 == 0: 49 | print 'iteration {}: loss {}'.format(i, loss) 50 | 51 | # 对x*W的结果进行求导 52 | # sigmoid求导f'(x) = y(1-y) 53 | # crossEntropyLoss求导 if j=i f'(x)=y_ 54 | dscores = probs 55 | dscores[range(num_examples), y] -= 1 56 | dscores /= num_examples 57 | 58 | dW = np.dot(X.T, dscores) 59 | db = np.sum(dscores, axis=0, keepdims=True) 60 | 61 | dW += reg*W # 正则化的导数部分 62 | 63 | # 更新参数 64 | W += -step_size * dW 65 | b += -step_size * db 66 | 67 | scores = np.dot(X, W) + b 68 | predicted_class = np.argmax(scores, axis=1) 69 | print 'training acuracy: {}'.format(np.mean(predicted_class == y)) 70 | 71 | def train_neural(X, y): 72 | h = 100 # 隐藏层神经元数目 73 | W = 0.01 * np.random.rand(D, h) 74 | b = np.zeros((1, h)) 75 | W2 = 0.01 * np.random.rand(h, K) 76 | b2 = np.zeros((1,K)) 77 | num_examples = X.shape[0] 78 | for i in xrange(10000): 79 | 80 | # evaluate class scores, [N x K] 81 | hidden_layer = np.maximum(0, np.dot(X, W) + b) # note, ReLU activation 82 | scores = np.dot(hidden_layer, W2) + b2 83 | 84 | # compute the class probabilities 85 | exp_scores = np.exp(scores) 86 | probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True) # [N x K] 87 | 88 | # compute the loss: average cross-entropy loss and regularization 89 | correct_logprobs = -np.log(probs[range(num_examples),y]) 90 | data_loss = np.sum(correct_logprobs)/num_examples 91 | reg_loss = 0.5*reg*np.sum(W*W) + 0.5*reg*np.sum(W2*W2) 92 | loss = data_loss + reg_loss 93 | if i % 1000 == 0: 94 | print "iteration %d: loss %f" % (i, loss) 95 | 96 | # compute the gradient on scores 97 | dscores = probs 98 | dscores[range(num_examples),y] -= 1 99 | dscores /= num_examples 100 | 101 | # 计算W2 b2的导数 102 | dW2 = np.dot(hidden_layer.T, dscores) 103 | db2 = np.sum(dscores, axis=0, keepdims=True) 104 | # next backprop into hidden layer 105 | dhidden = np.dot(dscores, W2.T) 106 | # ReLU激活函数进行反向传播 107 | dhidden[hidden_layer <= 0] = 0 108 | # finally into W,b 109 | dW = np.dot(X.T, dhidden) 110 | db = np.sum(dhidden, axis=0, keepdims=True) 111 | 112 | # add regularization gradient contribution 113 | dW2 += reg * W2 114 | dW += reg * W 115 | 116 | # perform a parameter update 117 | W += -step_size * dW 118 | b += -step_size * db 119 | W2 += -step_size * dW2 120 | b2 += -step_size * db2 121 | 122 | hidden_layer = np.maximum(0, np.dot(X, W) + b) 123 | scores = np.dot(hidden_layer, W2) + b2 124 | predicted_class = np.argmax(scores, axis=1) 125 | print 'training accuracy: %.2f' % (np.mean(predicted_class == y)) 126 | 127 | if __name__ == '__main__': 128 | X, y = generating_data() 129 | train_neural(X, y) -------------------------------------------------------------------------------- /cs231n/note9.md: -------------------------------------------------------------------------------- 1 | ### CNNs/ConvNets 2 | 卷积神经网络和普通的神经网络的结构很一样,同样是由神经元构成,同样有可以学习的weights和bias。每个神经元接受输入进行点积并输入激活函数。整个网络表现的也像是一个可微分的score function,从由像素组成的图片映射到类别标签。然后再有一个loss function。 3 | 4 | 普通的神经网络的一个神经元由数据(weights bias)和操作组成(作点击,进行激活计算),最后的一层称为输出层,在分类人物中输出层输出的是每个类别的score。 5 | 6 | 普通的神经网络并不能很好的利用图片的scale,在cifar-10中图片的格式是32x32x3。普通的神经网络会把图片展开成3072维的向量,并在第一层设置3072个权重作点积。如果是200x200x3的图片那么将会有120000个权重,这很明显太多了。 7 | 8 | **3D形式的神经元** 9 | 卷积神经网络利用了一点,那就是输入是一个3D的矩阵。卷积神经网络的神经元由width、height、depth描述大小。每个神经元和输入的一小块区域作点积,求和,然后再进行激活。除了连接形式不一样之外,其他的和原来的神经元表现是一样的,先作点积,然后求和,然后激活 10 | 11 | ### CNNs中使用的Layers 12 | 卷积神经网络中使用的Layer通常有卷积层 池化层 全连接层。CNNs中的每一层并不总是有参数,如池化层 激活层没有参数,卷积层 BN层有参数。卷积神经网络中也并不是每一层都有超参数,激活层就没有。 13 | 14 | **卷积层** 15 | 卷积层是卷积神经网络中耗费最多计算量的地方。卷积核在图片上进行滑动,计算出一个2d的activation map,这个activation map中代表卷积核在每个位置上响应程度。直观地来说,每个卷积核都在学习一个局部特征,在每个位置上激活程度高的代表了在这个位置上存在这个特征的可能性高。这些特征可以是一些局部的曲线,或者局部的颜色块。将多个activation map堆叠在一起就组成了新的3D的feature map。 16 | 17 | *local connectivity*。卷积核只连接输入feature map的一小块区域。这种局部连接的大小范围也是一种超参数称为神经元的感受域receptive field(一般用width*height描述)。感受域只在width height是局部的,但是连接这这片区域后面的所有depth。 18 | 19 | *Spatial arrangement*。控制卷积层的输出的超参数是depth stride zero-padding。 20 | 21 | - depth对应的是卷积核的个数。一般称呼注视着同一片区域的卷积核为depth column也称为fibre。 22 | - stride,卷积核在feature map上进行滑动时的步长。 23 | - zero-padding,在输入feature map上添加zero的边的数目,可以通过添加zero-padding使得输入和输出的width height相同。 24 | 25 | 如果stride为1,那么可以使用公式$ P = (F-1)/2 $来计算zero-padding的数目使得输入输出相同。 26 | 27 | 输出尺度的计算公式$ \left \lfloor (W + 2P - D(F-1) -1)/S + 1 \right \rfloor $其中$ W $是输入的尺度,$ P $是zero-padding,$ F $是卷积核的尺度,$ S $是步长stride,$ D $是dilation膨胀程度。 28 | 29 | 在卷积层中对于每个卷积核划过的位置都认为是存在一个神经元(输出的同一个channel中的每个位置),不过这些神经元共享了同一套的参数。 30 | 31 | 但也要注意,有时候这种权重共享的形式并不合理。当输入的图片是某种中心化的结构的时候,如人脸一般就是在图片的中心。那么普通的卷积层就只能学习到发型或者其他的局部东西,这个时候可以稍微放松一点局部连接的规则。并且称这种改过的神经网络为Locally-Connected Layer。 32 | 33 | **用矩阵乘法实现卷积** 34 | 35 | - *im2col*将卷积核滑过的每个小块区域变成一个列向量,假如输入是(227, 227, 3)并且使用(11, 11, 3)的卷积核stride为4,那么原图上滑动过的每个位置变成列向量之后为11*11*3=363维,一行有(227-11)/4+1=55个,55*55=3025,那么就得到了X_col(363, 3025)的矩阵。 36 | - *展开卷积核*如果有96个(11, 11, 3)的卷积核,那么就有W_row(96, 363). 37 | - *进行卷积*卷积的结果就是np.dot(W_row, X_col)输出是(96, 3025) 38 | - *reshape*输出可以重新reshape到(55, 55, 96) 39 | 这个方法的缺点就是浪费很多的内存,但另一方面这样计算很高效,并且im2col的思想也可以用在池化层中。 40 | 41 | **反向传播** 42 | 同样反向传播也是卷积的形式。假如在feature map M0上进行卷积得到feature map M1,卷积核大小为k_s,那么M1[0, 0]进行反向传播,先将梯度复制成k_s*k_s份,对应位置相乘后的梯度,然后每个对应位置求对W的偏导,求对M0[i,j]的偏导。同时因为卷积核在整个M0上都进行了卷积,所以最终对W的参数是各个位置上的梯度相加。 43 | 44 | **Dilated conv** 45 | 在卷积核的每个位置之间可以有空洞,这被称为空洞卷积。空洞卷积可以扩大感受域。 46 | 47 | ### 池化层 48 | 在连续的卷积层中插入池化层可以降低特征的维度,并且可以控制过拟合。池化层在每个channel中单独进行操作,在池化层中使用zero-padding也很常见。最大池化是一种常见的池化操作,并且使用filter 2x2 步长为2 过滤掉75%的数据。有两种常见的池化参数:filter为3x3 步长为2、filter为2步长为2. 49 | 50 | 除了最大池化还有average pooling、L2-norm pooling。average pooling过去很常用但是最近它的效果经研究不如最大池化。 51 | 52 | **反向传播** 53 | 池化的反向传播,如果是最大池化那么将导数传播到最大的那个值即可,在前向传播的时候需要记住最大的那个值的下标。如果是average pooling那么把导数平均分给每个值即可。 54 | 55 | **不使用池化层** 56 | 一些人不喜欢池化操作,认为这损失了信息,并且主张使用大的卷积核来实现对特征的降维。但是使用池化是未来的趋势,并且可以加速训练。 57 | 58 | ### Normalization layer 59 | 卷积神经网络中使用了很多的normalization 层,但是这些normalization层的效果并不是很好。现在使用最多的还是batch normalization。 60 | 61 | ### 全连接层转为卷积层 62 | 全连接层和卷积层的区别就是局部连接、权值共享了。但是这些层中仍然是计算点积求和激活,所以两者可以进行连接: 63 | 先卷积变成类似(1, 1, 4096),然后使用1000个(1, 1, 4096)的卷积核变为需要的输出(1, 1, 1000)。 64 | 65 | 使用卷积层代替全连接层还有一个好处:假如训练的时候输入是(224, 224, 3)经过前面的卷积变成(7, 7, 512)然后7x7卷积变成类别数。测试的时候输入可能是(384, 384, 3)那么经过卷积变成(12, 12, 512)然后7x7卷积变成(6, 6, 类别数)这个6x6的feature map相当于对应原来图片的缩小版,每个位置相当于在原图上滑动做crop然后输入神经网络,这比在原图片上进行滑动更高效。如果是分类的话,这36个位置做平均进行输出。衍生出来的一个技巧就是将原图扩大,然后输入神经网络,相当于在每个位置上都进行卷积计算score最后平均,这样效果更好。 66 | 67 | 但是如果原图很小呢,经过前面的卷积feature map size已经小于7了,forwarding the converted ConvNet twice: First over the original image and second over the image but with the image shifted spatially by 16 pixels along both width and height. 68 | 69 | ### 神经网络的结构 70 | 一般来说是```INPUT -> [[CONV -> RELU]*N -> POOL]*M -> [FC -> RELU]*K -> FC```一般来说其中```0 <= N <= 3```并且```0 <= K < 3```。 71 | 72 | 一般来说**堆叠很多小的卷积核比直接使用一个大的卷积核要好**,堆叠三层3x3的卷积层,相对于初始的feature map来说第一层的感受域是3x3,第二层的感受域就是5x5,第三层就是7x7。好处与坏处: 73 | 74 | - 直接一层7x7的卷积相当于只进行了一次线性的计算然后输入非线性的激活函数,而三层的话其中就夹带有非线性,这会使得输出的特征更有表现力(特征更高级) 75 | - 假如有C个channel,那么7x7卷积有$ C \times (7 \times 7 \times C) = 49C^2 $个参数,而三个3x3卷积就只有$ 3 \times (C \times (3 \times 3 \times \timesC)) = 27C^2 $个参数。这样参数更少,并且表示能力也更强。 76 | - 但是这样也会浪费内存去存储中间的输出 77 | 78 | 在实践中最好是使用在ImageNet上表现的不错的神经网络。自己尝试设计新的神经网络可能会有各种的问题,最好是借鉴各种比赛的前几名。 79 | 80 | **每层的模式** 81 | 输入层,也就是输入的图片的高和款一般是2的倍数,如32 64 96 224 384 512等。 82 | 83 | 卷积层应该使用小的卷积核3x3 5x5,尽量使用1步长。如果非要用7x7的大卷积核的话,那一般也就是在第一个卷积层使用。 84 | 85 | 池化层一般使用最大池化,并且使用2x2filter,步长为2.或者3x3 filter步长为2.一般不使用大于3的filter,这将会导致很差的表现。 86 | 87 | 为什么步长一般为1?小的步长表现更好,并且卷积的时候使用小步长不改变feature map的size,让max pool改变size。 88 | 89 | 为什么进行padding?保持feature map的size不变。如果不进行padding,那么feature map越来越小,图像边缘的信息经过几次卷积就消失掉了。 90 | 91 | ### 训练时候所需要的计算资源 92 | 现代的GPU内存通常为3/4/6GB,最大的也就是128GB。为了有效利用内存,应该注意以下数据: 93 | 94 | - 中间层的输出。中间的卷积层以及激活层等等,这些输出需要保存为了进行反向传播。在训练完进行测试的时候就不用保存中间层的输出了。 95 | - 保存参数。当使用动量或者Adagrad RMSProp的时候存储参数所需要的内存更多,为了训练存储参数相关的内存需要是参数的三倍以上。 96 | - 各种其他的存储。如输入的图片需要存储,或者他们的data argumentation。 97 | 当计算完参数的个数,把这个数转成G为单位,如果是单精度float乘以4,如果是双精度double乘以8,换算成GB。如果内存不够可以减少batch size。 98 | 99 | ### 迁移学习 100 | 利用之前在其他数据集上学习到的知识在新的数据集上进行微调。当新的数据集较大的时候,可能只需要重新训练最后一个全连接层,而前面的层都冻结起来。当新的数据集较大的时候需要重新训练最后的所有全连接层(一般是三个)并且前面的卷积层也都需要用很小的学习率进行学习。 101 | | | 较相同的数据集 | 较不同的数据集 | 102 | | 很少的数据 | 重新训练最后一个全连接层 | 很麻烦,尝试从不同的地方开始重新训练 | 103 | | 很多的数据 | 微调全部层 | 微调全部层 | 104 | 105 | **注意事项** 106 | 进行迁移学习的时候要注意一下几点: 107 | 108 | - *原有模型的约束*。如果使用一个已经预训练的模型,那么会受限于这个模型。比如不能从这个模型中剥离某层,或者插入几层。但是如果是输入图片的尺寸大一点的话完全可以使用预训练的模型,因为权值共享,就相当于在原图上进行滑动了。 109 | - *学习率*。使用预训练的模型,对于前面的卷积层,使用较小的学习率。因为我们的期望是前面的卷积层已经学习到了足够的知识,所以我们并不像过于扰动他们。特别是新加上的全连接层是随机初始化的,这可能会导致一开始有很大的loss。 -------------------------------------------------------------------------------- /数学知识点/知识点总结1.md: -------------------------------------------------------------------------------- 1 | ### 矩阵的秩的含义是什么? 2 | 1. 从行列式的角度解释: 3 | 将行列式进行消元,最后剩下的非零的行列式的个数就是秩的个数,也就是说这几个等式进行组合表达出了其他的式子。 4 | 5 | 2. 从几何空间的角度解释: 6 | 将矩阵中的每行看成是一个向量,这些向量分布于一个几何空间中。其中一些向量可以由其他的向量组成,即向量间具有线性相关性。消除这些共线性,将这些向量分解到相互正交的坐标轴上,最后剩下的坐标轴个数就是秩。 7 | 8 | 参考[如何理解矩阵的秩?](https://www.zhihu.com/question/21605094) 9 | 10 | ### 矩阵低秩代表了什么? 11 | 一个矩阵低秩,代表它实际上比它看起来更简单,冗余程度高。如果一个图像的特征是低秩的,那么表示这个图像很简单,比如草原的图像的特征矩阵就比草原+蒙古包的图像低秩。现实中的画质高的图像都是低秩的,如果秩高的话,可能是因为有噪点。 12 | 13 | 在图像处理中,认为图像有一些公共的模式,所有图像都由这些基本的模式组成。假如说图像是一个交叉,那么基本元素就是一个正斜线和反斜线的叠加,只要我们找到所有的基本模式(基底)就能通过基底的线性组合表达出所有的图像。 14 | 15 | 一般来说在一个图像中,基底的数量是非常少的,如果图像是一面砖墙那么它很显然是周期重复的,也就是低秩的。 16 | 17 | **稀疏性**代表的是,任给一个图像,基底字典可能是过完备的,也就是可以使用很多不同方案的基底来表达这个图像,但是我们希望使用基底尽量少的那个方案,因为它简单。 18 | 19 | 对于一个图片来说,基底可以很容易地被理解为一个个小的像素矩阵。如果换个基底:进行傅立叶变换、余弦离散变换等就变成了另一个基底,可能有新的更好的性质。 20 | 21 | ### 如何理解矩阵的特征值? 22 | 特征值和特征向量的定义:向量v在矩阵A的作用下(v左乘A Av),保持方向不变进行比例为λ的伸缩。这个向量就是特征向量,这个比例就是特征向量对应的特征值。 23 | $$ A\overrightarrow{v} = \lambda \overrightarrow{v} $$ 24 | 25 | 一个矩阵可以有多个特征向量和特征值。 26 | 27 | 矩阵左乘一个向量(任意向量),如果这个矩阵是方阵,那么就是对向量做旋转和拉伸的操作。将这种操作展开,也就是将矩阵进行特征值分解,$ A = P \Lambda P^{-1} $,其中$ P $矩阵中的每一列就是特征向量,他们相互正交。$ \Lambda $是特征值组成的对角矩阵,第n个特征值对应的就是$ P $中第n个列向量。 28 | 29 | 在这里,因为$ P P^{-1} $中的向量都是单位向量并且都是正交的,所以这个矩阵左乘一个向量$ \overrightarrow{v} $后对这个向量的作用是旋转、变换基底。而$ \Lambda $的作用则是进行拉伸、缩放。 30 | 31 | 也可以理解为矩阵对向量做了拉伸,但是这个拉伸是有大小、方向的。矩阵的特征向量就是拉伸的方向,特征值就是拉伸的程度。 32 | 33 | 参考[如何理解矩阵特征值](https://www.zhihu.com/question/21874816) 34 | 35 | ### 如何理解奇异值? 36 | 奇异值分解: 37 | 38 | 对矩阵进行奇异值分解得到奇异值,设A是一个m\*n矩阵 m>=n。那么对A进行奇异值分解的结果就表示为(V.T的大小是r\*n):$ M_{m*n} = U_{m*r} \Sigma_{r*r} V_{r*n}^T $矩阵U中的列向量被称为左奇异向量,V中的行向量被成为右奇异向量,Σ是一个对角矩阵,里面的对角线上的值就是奇异值(特征值)。 39 | 40 | 如何理解奇异值参看[奇异值的物理意义是什么?](https://www.zhihu.com/question/22237507) 41 | 42 | ### 动态规划 43 | 参考[动态规划 (第1讲)](https://www.bilibili.com/video/av16544031/) 44 | 45 | ```python 46 | def dp(time, profit): 47 | """ 48 | time: list[[begin, stop]] 工作的顺序是按照结束的时间进行排序的。 49 | profit: list[int] 50 | 有几件工作,这些工作的时间有重叠,每件工作的时间不同(开始结束时间不同、有重叠)、报酬也不同,现在算出怎么选择工作使得报酬最大。 51 | 在计算最大报酬optimal的时候,倒着想首先考虑选不选最后一个工作,选了的话opt[n] = profit[n] + opt[prev(n)],不选就是opt[n] = opt[n-1]。 52 | 所以是opt = max(profit[n] + opt[prev(n)], opt[n-1]) 53 | opt[n]表示到第n项工作为止的最大报酬,prev(n)表示选了第n项工作后往前推能选的最近的那个工作的编号。 54 | 55 | 核心思想:选/不选;逆着想问题 正着写代码 56 | 57 | 测试用例 dp([[1, 4], [3, 5], [0, 6], [4, 7], [3, 8], [5, 9], [6, 10], [8, 11]], [5, 1, 8, 4, 6, 3, 2, 4]) 58 | 结果 [5, 5, 8, 9, 9, 9, 10, 13] 59 | """ 60 | opt = [0 for i in range(len(profit))] 61 | opt[0] = profit[0] # 表示选择第0个活动 62 | for i in range(1, len(profit)): 63 | # 循环开始之前要先把opt[1]填上去,并且把i开始置为1 64 | print('i: ', i, 'opt[i]: ',max(opt[i-1], opt[i]+opt[prev(time, i)]), 'prev(i): ', prev(time, i)) 65 | opt[i] = max(opt[i-1], profit[i]+opt[prev(time, i)]) 66 | print(opt) 67 | 68 | def prev(time, n): 69 | """ 70 | time: list[[begin, stop]] 71 | n: 第n项工作 72 | 得到 选了第n项工作后,往前算的那个最近的能做的工作的索引 73 | """ 74 | begin = time[n][0] 75 | for i in range(n-1, -1, -1): 76 | # 如果这个工作的结束时间在第n项工作的开始时间之前就行 77 | if time[i][1] <= begin: 78 | return i 79 | return -1 # opt[-1]是被初始化为0的,所以当第n个活动之前没有活动的时候就可以使用-1来表示 80 | ``` -------------------------------------------------------------------------------- /数据结构/DP.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | # 动态规划 Dynamic Programming简称DP 如果要求一个问题的最优解 4 | # 而且这个问题可以分解成若干个子问题,并且子问题之间也存在重叠的子问题,则考虑使用动态规划 5 | 6 | # 使用动态规划的特征 7 | # 1 求一个问题的最优价 8 | # 2 大问题可以分解成子问题,子问题还有重叠的更小的子问题 9 | # 3 整体问题最优解取决于子问题的最优解(状态转移方程) 10 | # 4 从上往下分析问题,从下往上解决问题 11 | # 5 讨论底层的边界问题 12 | 13 | def cut_repo(n): 14 | """ 15 | 剪绳子问题 16 | 一根长度为N的绳子,请把绳子剪成M段(m n都是整数)每段绳子的长度记为k[0] k[1]... 17 | 每段绳子的长度都是整数 请问如何剪使得绳子乘积最大 18 | n是绳子的长度 19 | """ 20 | # 先对边界问题进行求解 21 | if n < 2: 22 | return 0 23 | if n == 2: 24 | return 1 # 长度为2 只能剪成 1*1 25 | if n == 3: 26 | return 2 27 | 28 | # 如果绳子长于4则申请一个长度为50的数组 29 | 30 | h = [0 for i in range(50)] 31 | # 先罗列出切割的边界问题 如果绳子切割后的长度是2 这段绳子可以提供的最大的乘数就是2 32 | h[0] = 0 33 | h[1] = 1 34 | h[2] = 2 35 | h[3] = 3 36 | 37 | # 递归的问题是f(n) = max{f(i)*f(n-i)} 意为将n分成两段 使得这两段的乘积最大 38 | for i in range(4, n-1): 39 | maxs = 0 40 | for j in range(1, i/2+1): 41 | mult = h[j] * h[i-j] 42 | if maxs < mult: 43 | maxs = mult 44 | h[i] = maxs 45 | return h[n] -------------------------------------------------------------------------------- /数据结构/array.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | def find(arr, target): 4 | """ 5 | 一个二维数组,从左往右 从上往下都是有序的,在这个二维数组中进行查找目标 6 | 先和右上角的数字进行比较, 7 | 如果相等就结束查找 8 | 如果目标小于这个数字则剔除这一列 9 | 如果目标大于这个数字就剔除这一行 10 | 当然也可以选择左下角的数字 11 | 这样查找每次都能比较整行整列 很快 12 | """ 13 | row_idx = 0 # 右上角是(0, len(arr[0])-1) 14 | col_idx = len(arr[0]) - 1 15 | 16 | while row_idx <= len(arr)-1 and col_idx >= 0: 17 | # 找到了 18 | if arr[row_idx][col_idx] == target: 19 | return row_idx, col_idx 20 | # 目标小于这个数字 剔除掉这一列 col-=1 21 | elif arr[row_idx][col_idx] > target: 22 | col_idx -= 1 23 | # 目标大于这个数字剔除掉这一行 row+=1 24 | else: # arr[row_idx][col_idx] < target 25 | row_idx += 1 26 | return -1, -1 # 没有找到 27 | 28 | def maxsum_subarr_kadane(lists): 29 | """ 30 | 求最大的连续子串 lists是一个数组,这个数组中哪个连续的子数组的和最大 31 | 数组 A = [-2, -3, 4, -1, -2, 1, 5, -3], 最大子数组应为[4, -1, -2, 1, 5],其和为7。 32 | 首先如果A中的元素全为正数,那么最大连续子数组就是它本身,如果A中全为负数,那么最大连续子数组就是最小的那个负数构成的数组。一般来说有以下几种方法 33 | 1 暴力破解法 34 | 2 分治法 35 | 3 kadane算法 复杂度O(n)Kadane的算法是基于将一组可能的解决方案分解为互斥(不相交)集合。 36 | 利用任何子数组(即同一个子数组组中的任何一个成员)将始终具有最后一个元素i(这就是“在位置i结束的和”)的事实 37 | 4 动态规划 38 | """ 39 | size = len(lists) 40 | # 最长子串之和 41 | max_so_far = float('-inf') 42 | # 以这里为子数组的结束位置的话,最长子串之和是多少 43 | max_ending_here = 0 44 | ret_end = 0 # max_so_far代表的子数组的结束位置 45 | ret_begin = 0 # max_so_far代表的子数组的开始位置 46 | now_begin = 0 # max_ending_here代表的子数组的开始位置 47 | 48 | # 遍历每个位置,max_ending_here都加上 49 | for i in range(size): 50 | max_ending_here = max_ending_here + lists[i] 51 | # 如果是遇到了新的最长子串则进行记录 52 | # 这个最长子串可能是开头遇到的,也可能是前面都是负数中间遇到了小的负数 53 | # 这个最长的子串也是基于max_ending_here以这里结尾的子数组进行计算的 54 | if max_so_far < max_ending_here: 55 | max_so_far = max_ending_here 56 | ret_end = i 57 | ret_begin = now_begin 58 | 59 | # 如果是在开头或者是清零后遇到了负数,或者是加上这个位置上的数字变成了负数那么归零 60 | # 归零意味着,从下个位置重新开始计数,这个位置是max_ending_here代表的子数组的开始位置 61 | if max_ending_here < 0: 62 | max_ending_here = 0 63 | now_begin = i+1 64 | 65 | return max_so_far, ret_begin, ret_end 66 | 67 | # 动态规划 68 | def maxsum_subarr_DP(lists): 69 | t = len(lists) 70 | MS = [0]*t 71 | MS[0] = lists[0] 72 | # 对每个位置进行叠加 比较增加了这个位置的数据后子数组之和是否变大了 73 | # 如果加上这个元素 变成了比这个元素还小,那么冲新开始叠加 74 | for i in range(1, t): 75 | MS[i] = max(MS[i-1]+lists[i], lists[i]) 76 | 77 | return MS 78 | 79 | def reorder_odd_even(arr): 80 | """ 81 | 一个整数数组,设计方法,让所有奇数在前,所有偶数在后; 82 | 扩展:三种数,第一种放前,第二种放中间,第三种放最后 83 | 如果是扩展的话可以来两趟,第一第二种看成是一种和第三种进行区分,然后第一第二种再进行区分 84 | """ 85 | # 如果数组为空或者长度不够 86 | if arr is None or len(arr)<2: 87 | return arr 88 | # 下标左边从0 右边从len(arr)-1开始 89 | left, right = 0, len(arr)-1 90 | # 类似于快速排序中的写法 91 | while left < right: 92 | while left < right and arr[right]%2==0: 93 | right -= 1 94 | while left 10: 144 | return 0 145 | sum = 0 146 | while n > 0: 147 | sum += n 148 | n -= 1 149 | return n 150 | 151 | def left_first_bigger(lists): 152 | """ 153 | 给定一个整形数组,数组是无重复随机无序的,打印出所有元素左边第一个大于该元素的值 154 | 使用栈,从左往右,先把0号位置上的数字压栈,然后比较下一个位置的数字和栈顶的数字 155 | 如果栈顶的数字大于下个位置的数字则输出并将这个位置的数字压栈 156 | 如果栈顶的数字小于位置上的数字则出栈,出栈后再用栈顶进行比较 157 | """ 158 | ret = [] 159 | stack = [] 160 | i = 0 161 | stack.append(lists[0]) 162 | while i < len(lists): 163 | while len(stack) != 0 and lists[i] > stack[-1]: 164 | stack.pop() 165 | if len(stack) == 0: 166 | stack.append(lists[i]) 167 | elif lists[i] < stack[-1]: # 必须使用elif 168 | ret.append(stack[-1]) 169 | stack.append(lists[i]) 170 | i += 1 171 | return ret 172 | 173 | def merge(arr1, arr2): 174 | """ 175 | 归并两个有序数组 176 | 归并n个长度为k的数组: 177 | 1. 先把所有数字复制到长度为nk的数组中然后进行排序O(nlogn) 复杂度为O(nklognk) 178 | 2. 利用最小堆:O(nklogk) 179 | 1. 创建一个大小为n×k的数组保存最后的结果 180 | 2. 创建一个大小为k的最小堆,堆中元素为k个数组中的每个数组的第一个元素 181 | 3. 重复下列步骤n×k次: 182 | 1. 每次从堆中取出最小元素(堆顶元素),将其放入数组中 183 | 2. 用堆顶所在数组的下一元素将堆顶元素替换掉 184 | 3. 如果数组中元素被取光了,将堆顶元素替换为无穷大。每次取出堆顶后进行调整 185 | """ 186 | i = 0 187 | j = 0 188 | ret = [] 189 | while i < len(arr1) and j < len(arr2): 190 | if arr1[i] > arr2[j]: 191 | ret.append(arr2[j]) 192 | j += 1 193 | else: 194 | ret.append(arr1[i]) 195 | i += 1 196 | ret += arr1[i:] 197 | ret += arr1[j:] 198 | return ret 199 | 200 | def arry_split(arr): 201 | """ 202 | 一个长度为n的数组(n一定是偶数个),将其平均分成两部分,找出能使这两部分的和的乘积最大的数组平分方式. 203 | 这个问题可以看成是动态规划,或者是0-1背包问题 204 | 205 | """ 206 | 207 | def stack_order(pushV, popV): 208 | """ 209 | pushV是进栈的顺序 210 | popV是出栈的顺序 211 | 判断进栈出栈是否合理,这里使用一个真实的栈来进行模拟 212 | """ 213 | i, j = 0, 0 214 | stack = [] 215 | # 遍历进栈的顺序 216 | for i in range(len(pushV)): 217 | # 将数据压入栈中 218 | stack.append(pushV[i]) 219 | # 判断栈顶是否和下一个出栈的数据相同,如果相同则进行出栈 220 | while j < len(popV) and popV[i] == stack[-1]: 221 | stack.pop() 222 | j += 1 223 | i += 1 224 | # 如果栈为空则说明出栈入栈的顺序相同 225 | return len(stack) == 0 226 | 227 | def seq_of_bst(seq): 228 | """ 229 | 输入一个序列,判断是不是二叉排序树(二叉搜索树)的后序遍历结果 230 | 二叉搜索树:左子树上的数字都 < 根节点 < 右子树上的数字 231 | """ 232 | if seq is None or len(seq)==0: 233 | return False 234 | bst_helper(0, len(seq)-1, seq) 235 | 236 | def bst_helper(i, j, seq): 237 | """ 238 | 帮助进行检查后序遍历结果 239 | 先从左往右找到左右子树的分届,然后再往右如果出现了比根节点还小的数字则不是后序遍历的结果 240 | """ 241 | if i >= j: 242 | return True 243 | left, right = i, j 244 | while seq[i] > seq[right]: 245 | i += 1 246 | j = i 247 | while j < right: 248 | if seq[j] < seq[right]: 249 | return False 250 | j += 1 251 | return bst_helper(left, i-1, seq) and bst_helper(i, right-1, seq) 252 | 253 | def more_than_half_num(numbers): 254 | """ 255 | numbers是一个list,其中存放数字,找出出现次数超过numbers长度二分之一的数字 256 | 没有就返回0 257 | """ 258 | if numbers is None or len(numbers)==0: 259 | return 0 260 | num = numbers[0] 261 | count = 1 262 | for i in range(1, len(numbers)): 263 | if numbers[i] == num: 264 | count += 1 265 | else: 266 | count -= 1 267 | if count == 0: 268 | num = numbers[i] 269 | count = 1 270 | count = 0 271 | for i in range(len(numbers)): 272 | if numbers[i] == num: 273 | count += 1 274 | if count > len(numbers)/2: 275 | return count 276 | else: 277 | return 0 278 | 279 | if __name__ == '__main__': 280 | a = [1,2,3,4,5,6,7] 281 | print(reOrderArray(a)) 282 | -------------------------------------------------------------------------------- /数据结构/back_trace.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | class Solution: 3 | def hasPath(self, matrix, rows, cols, path): 4 | # write code here 5 | if matrix is None or path is None: 6 | return False 7 | if matrix == path: 8 | return True 9 | m = [matrix[i*cols:(i+1)*cols] for i in range(rows)] 10 | print(m) 11 | return self.move(m, 0, 0, path) 12 | 13 | def move(self, matrix, i, j, path): 14 | """ 15 | 这个函数用来全局进行移动 16 | i行数 j列数 17 | """ 18 | if i >= len(matrix) or j >= len(matrix[0]) or i < 0 or j < 0 : 19 | return False 20 | flag_core = [[0 for a in range(len(matrix[0]))] for b in range(len(matrix))] 21 | return self.move_core(matrix, i, j, 0, path, flag_core) or self.move(matrix, i+1, j, path) or self.move(matrix, i, j+1, path) 22 | 23 | def move_core(self, matrix, i, j, k, path, flag): 24 | """ 25 | matrix[i][j]与path的开头进行匹配 26 | """ 27 | #print('move_core: i', i, 'j', j) 28 | if i >= len(matrix) or j >= len(matrix[0]) or i < 0 or j < 0: 29 | return False 30 | if k == len(path): 31 | return True 32 | if flag[i][j] == 1: 33 | return False 34 | if matrix[i][j] != path[k]: 35 | return False 36 | else: 37 | print('move_core success: i', i, 'j', j, 'k', path[k]) 38 | flag[i][j] = 1 39 | return self.move_core(matrix, i+1, j, k+1, path, flag) or self.move_core(matrix, i, j+1, k+1, path, flag) or self.move_core(matrix, i-1, j, k+1, path, flag) or self.move_core(matrix, i, j-1, k+1, path, flag) 40 | 41 | if __name__ == '__main__': 42 | s = Solution() 43 | # "ABCESFCSADEE",3,4,"ABCCED" 44 | matrix = "ABCESFCSADEE" 45 | print(s.hasPath("ABCEHJIGSFCSLOPQADEEMNOEADIDEJFMVCEIFGGS",5,8,"SLHECCEIDEJFGGFIE")) -------------------------------------------------------------------------------- /数据结构/bin_tree.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | # 二叉树的每个节点至多只有二颗子树,不存在度大于2的节点,二叉树的子树有左右之分,不能颠倒 4 | # 二叉树的第i层有2^(i-1)个节点 5 | # 深度为k的二叉树至多有2^k-1个节点 6 | # 任何一个二叉树T 如果其叶节点数为N0,度为2的节点数为N2 则N0 = N2 + 1 7 | # https://www.cnblogs.com/freeman818/p/7252041.html 二叉树的各种遍历方式 8 | # 前序遍历就是先根节点 然后左子树 然后右子树 9 | # 中序遍历就是先左子树 然后根节点 然后右子树 10 | # 后序遍历就是先左子树 然后右子树 最后根节点 11 | # 区别就在于根节点在什么时候被访问 12 | 13 | 14 | # 用递归的形式遍历平均时间复杂度是O(n) 空间复杂度是O(log(n)) 但实际上编译器会做一些优化比如尾递归 所以空间复杂度实际上是小于O(log(n))的 但是逻辑上是O(log(n)) 这个log(n)实际上是二叉树的深度 h = log(n) 15 | # 最坏的情况下 时间复杂度是O(n) 空间复杂度是O(n) 这个树是一条线左边一直下来 16 | # 如果用Morris遍历算法 则空间复杂度只有O(1) 17 | 18 | class TreeNode(object): 19 | def __init__(self, x): 20 | self.val = x 21 | self.left = None 22 | self.right = None 23 | 24 | def construct(lst): 25 | """ 26 | 根据层次遍历来构造二叉树,用'#'来代替空 27 | 完全二叉树中层次遍历的数组中 28 | 节点i的子节点为2i 2i+1 (下标从1开始) 29 | """ 30 | if lst is None or len(lst) == 0 or lst[0]=='#': 31 | return None 32 | # i也是从1开始,作为索引的时候减1即可 33 | i, root = 2, TreeNode(lst[0]) 34 | root.right = construct_core(lst, 2*i) 35 | root.left = construct_core(lst, 2*i+1) 36 | return root 37 | 38 | def construct_core(lst, i): 39 | if i-1 >= len(lst) or lst[i] == '#': 40 | return None 41 | node = TreeNode(lst[i]) 42 | node.right = construct_core(lst, 2*i) 43 | node.left = construct_core(lst, 2*i+1) 44 | return node 45 | 46 | def preorder(root): 47 | if root == None: 48 | return 49 | print(root.val) 50 | preorder(root.left) 51 | preorder(root.right) 52 | 53 | def inorder(root): 54 | if root == None: 55 | return 56 | inorder(root.left) 57 | print(root.val) 58 | inorder(root.right) 59 | 60 | def postorder(root): 61 | if root == None: 62 | return 63 | postorder(root.left) 64 | postorder(root.right) 65 | print(root.val) 66 | 67 | # 二叉树中序遍历 分为递归和非递归两种 68 | def inorder_no_recursion(root): 69 | """ 70 | 中序遍历 非递归的形式 71 | 先沿着左边一路走下去,将所有左子树根节点都压到栈里 直到左子树为空 72 | 然后输出这个子树根节点,然后将右子树作为根节点继续进行遍历 73 | 同样的沿着左边压栈,一遇到空就输出 然后右子树作为根节点 74 | 当栈空的时候并且不再存在右子树的时候就停止 75 | """ 76 | ret = [] 77 | stack = [] 78 | while root or stack: 79 | # 沿着左边一路压栈 80 | while root: 81 | stack.append(root) 82 | root = root.left 83 | # 出栈一个输出并将右子树作为新的根节点 84 | if stack: 85 | t = stack.pop() 86 | ret.append(t.val) 87 | # 右子树可能是None,但是这不会影响 88 | root = t.right 89 | return ret 90 | 91 | def preorder_no_recursion(root): 92 | """ 93 | 前序遍历 非递归形式 94 | 首先将根节点压栈 95 | 如果栈不为空则 出栈 96 | 首先输出根节点 然后将右子树入栈 左子树入栈 97 | """ 98 | ret = [] 99 | stack = [] 100 | stack.append(root) 101 | while len(stack) != 0: 102 | t = stack.pop() 103 | if t is not None: 104 | ret.append(t.val) 105 | stack.append(t.right) 106 | stack.append(t.left) 107 | return ret 108 | 109 | def postorder_no_recursion(root): 110 | """ 111 | 后序遍历 非递归形式 112 | 首先将根节点压栈 113 | 如果栈不为空则 出栈 114 | 若出栈的节点有左右子树则再将这个节点压栈,并先将右子树压栈 再将左子树压栈 115 | 若出栈的节点没有左右子树则输出 116 | """ 117 | ret = [] 118 | stack = [] 119 | stack.append(root) 120 | while len(stack) != 0: 121 | t = stack.pop() 122 | if t.right is None and t.left is None: 123 | ret.append(t.val) 124 | else: 125 | stack.append(t) 126 | if t.right is not None: 127 | stack.append(t.right) 128 | if t.left is not None: 129 | stack.append(t.left) 130 | return ret 131 | 132 | def bsf(root): 133 | """ 134 | 广度优先遍历 需要借用到队列 135 | """ 136 | ret = [] 137 | queue = [] 138 | queue.insert(0, root) 139 | while queue: 140 | t = queue.pop(0) 141 | ret.append(t.val) 142 | if t.left is not None: 143 | queue.append(t.left) 144 | if t.right is not None: 145 | queue.append(t.right) 146 | return ret 147 | 148 | def convert_no_recurion(root): 149 | """ 150 | 输入一个二叉搜索树,将这个二叉搜索树变为一个排好序的双向链表 151 | 1. 使用非递归中序遍历 遍历的同时进行更改指针 152 | 2. 使用递归的形式 将左子树变为一个双向有序链表 右子树变为一个双向有序链表 然后进行连接 153 | """ 154 | if root is None: 155 | return None 156 | stack = [] 157 | r = None 158 | flag = True 159 | pre = None 160 | while root or stack: 161 | while root: 162 | stack.append(root) 163 | root = root.left 164 | if stack: 165 | t = stack.pop() 166 | if flag: 167 | r = t 168 | pre = t 169 | root = root.right 170 | flag = False 171 | else: 172 | t.left = pre 173 | pre.right = t 174 | pre = t 175 | root = root.right 176 | return r 177 | 178 | def convert_recursion(root): 179 | """ 180 | 递归的形式 181 | """ 182 | if root is None: 183 | return None 184 | if root.left is None and root.right is None: 185 | return root 186 | # 左子树改造成双向排序链表 187 | left = convert_recursion(root.left) 188 | p = left 189 | # p为左子树变为的链表的最后一个节点 190 | while p.right: 191 | p = p.right 192 | if left: 193 | p.right = root 194 | root.left = p 195 | # 右子树构造成排序双向链表 196 | right = convert_recursion(root.right) 197 | if right: 198 | root.right = right 199 | right.left = root 200 | return left if left is not None else root 201 | 202 | def tree_depth(root): 203 | """ 204 | 得到树的深度 205 | """ 206 | if root is None: 207 | return 0 208 | left = tree_depth(root.left) 209 | right = tree_depth(root.right) 210 | return max(left, right) + 1 211 | 212 | def is_balance(root): 213 | """ 214 | 判断是不是AVL 平衡二叉树(左子树和右子树的深度不超过1 递归下去也一样) 215 | 这里利用深度优先遍历 当左右子树的深度只差超过1 的时候就返回-1作为深度,那么无论是加-1 还是减-1 绝对值都超过1 了 216 | """ 217 | return balance_helper(root) != -1 218 | 219 | def balance_helper(root): 220 | if root is None: 221 | return 0 222 | depth_l = balance_helper(root.left) 223 | if depth_l == -1: 224 | return -1 225 | depth_r = balance_helper(root.right) 226 | if depth_r == -1: 227 | return -1 228 | return -1 if abs(depth_l - depth_r) > 1 else max(depth_l, depth_r)+1 229 | 230 | 231 | # class TreeLinkNode: 232 | # def __init__(self, x): 233 | # self.val = x 234 | # self.left = None 235 | # self.right = None 236 | # self.next = None 237 | def get_next(node): 238 | """ 239 | 给一个节点,找出这个节点的中序遍历的下一个节点 240 | 其中node.next是父节点 241 | 如果这个节点是一个子树的根节点,那么从右子树中找到最左边的那个节点 242 | 如果这个节点是叶子节点并且是根节点的左子树,那么返回根节点 243 | 如果这个节点是叶子节点并且是根节点的右子树,那么往上回溯直到不是右子树 244 | 或者回溯到根节点直接返回父节点 245 | """ 246 | if node is None: 247 | return node 248 | if node.right: 249 | node = node.right 250 | while node.left: 251 | node = node.left 252 | else: 253 | if node.next and node.next.left == node: 254 | return node.next 255 | while node.next and node.next.right == node: 256 | node = node.next 257 | return node.next 258 | 259 | def z_print(pRoot): 260 | """ 261 | z字形遍历二叉树 第一层从左往右 第二层从右往左 需要返回[[第一层]], [第二层]...] 262 | 层次遍历需要借助队列,两种方法 263 | 1. 全部正常遍历,然后某些层再倒过来 264 | 2. 遍历的时候设置两个栈queue是正在被遍历的节点 265 | l是下一层的节点,从左向右的时候l先append左然后右 266 | 从右向左的时候先右后左,通过flag来控制左右方向 267 | """ 268 | # write code here 269 | if pRoot is None: 270 | return [] 271 | queue, ret, temp, l, flag = [], [], [], [], False 272 | queue.append(pRoot) 273 | while queue: 274 | t = queue.pop() 275 | temp.append(t.val) 276 | if flag is False: # 从左 -> 右 277 | if t.left: 278 | l.append(t.left) 279 | if t.right: 280 | l.append(t.right) 281 | else: 282 | if t.right: 283 | l.append(t.right) 284 | if t.left: 285 | l.append(t.left) 286 | if len(queue) == 0: 287 | ret.append(temp) 288 | queue, l, temp, flag = l, [], [], False if flag else True 289 | return ret 290 | 291 | class Serialize: 292 | """ 293 | 对二叉树进行序列化与反序列化 294 | """ 295 | i = 0 296 | s = '' 297 | 298 | def Serialize(self, root): 299 | # write code here 300 | self.serialize_helper(root) 301 | s=self.s 302 | self.s='' 303 | return s 304 | 305 | def serialize_helper(self, root): 306 | if root is None: 307 | self.s += '#,' 308 | return 309 | self.s += str(root.val) 310 | self.s += ',' 311 | self.serialize_helper(root.left) 312 | self.serialize_helper(root.right) 313 | 314 | def Deserialize(self, s): 315 | # write code here 316 | if s is None or len(s)==0 or s[0] == '#': 317 | return None 318 | while s[self.i] != ',': 319 | self.i += 1 320 | root = TreeNode(int(s[0:self.i])) 321 | self.i += 1 322 | # 每次传入的i都是指向逗号的,所以这里直接指向了逗号的下一个 323 | root.left = self.deserialize_helper(s) 324 | print('i', self.i) 325 | root.right = self.deserialize_helper(s) 326 | return root 327 | 328 | def deserialize_helper(self, s): 329 | # 传入这里的i指向的不是逗号 330 | if s[self.i] == '#': 331 | self.i += 2 # 使i指向下一个数字字符 332 | return None 333 | j = self.i 334 | while s[j] != ',': 335 | j += 1 336 | print('i', self.i, 'j', j, 'i:j', s[self.i:j]) 337 | node = TreeNode(int(s[self.i:j])) 338 | self.i = j + 1 339 | # 指向逗号下一个字符 340 | node.left = self.deserialize_helper(s) 341 | node.right = self.deserialize_helper(s) 342 | return node 343 | 344 | if __name__ == '__main__': 345 | s = Serialize() 346 | root = s.Deserialize('8,6,5,#,#,7,#,#,10,9,#,#,11,#,#') 347 | preorder(root) 348 | print(s.Serialize(root)) -------------------------------------------------------------------------------- /数据结构/lst.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | class ListNode: 4 | def __init__(self, x): 5 | self.val = x 6 | self.next = None 7 | 8 | def construct(lst): 9 | if lst is None or len(lst)==0: 10 | return None 11 | head = ListNode(lst[0]) 12 | i, p = 1, head 13 | while i < len(lst): 14 | p.next = ListNode(lst[i]) 15 | i, p = i+1, p.next 16 | return head 17 | 18 | def show(head): 19 | while head: 20 | print(head.val) 21 | head = head.next 22 | 23 | def first_common_node(pHead1, pHead2): 24 | """ 25 | 找到第一个公共节点 26 | 两个链表的样子类似于 27 | ——————\\____ 28 | ____// 29 | 先算出两个链表的长度,然后长的链表先走一段距离 30 | 使得剩下的部分长度相同,然后一个一个比较判断 31 | """ 32 | if pHead1 is None or pHead2 is None: 33 | return None 34 | len1 = 0 35 | p = pHead1 36 | while p: 37 | len1 += 1 38 | p = p.next 39 | len2 = 0 40 | p = pHead2 41 | while p: 42 | len2 += 1 43 | p = p.next 44 | if len1 > len2: 45 | l = len1 - len2 46 | while l > 0: 47 | pHead1 = pHead1.next 48 | l -= 1 49 | if len2 > len1: 50 | l = len2 - len1 51 | while l > 0: 52 | pHead2 = pHead2.next 53 | l -= 1 54 | while pHead1 and pHead2: 55 | if pHead1 == pHead2: 56 | return pHead1 57 | pHead1 = pHead1.next 58 | pHead2 = pHead2.next 59 | return None 60 | 61 | def entry_of_loop(pHead): 62 | """ 63 | 一个链表尾部有一个环,找到这个环的入口 64 | 思路:设置一个快指针 慢指针 两者会在环内相遇 65 | 然后利用相遇的位置得到环的长度n 设总长度为m 66 | 然后设置两个指针p1 p2 p1先走n步 67 | 然后让p1 p2同时一步步走 那么他们会在环的入口相遇 68 | 因为p1接下来走m-n p2也会走m-n 69 | """ 70 | meet = meeting(pHead) 71 | if meet is None : 72 | return None 73 | loop_len = 1 74 | p = meet.next 75 | while p != meet: 76 | p = p.next 77 | loop_len += 1 78 | p1 = pHead 79 | i = 0 80 | while i < loop_len: 81 | p1 = p1.next 82 | i += 1 83 | p2 = pHead 84 | while p1 != p2: 85 | p1 = p1.next 86 | p2 = p2.next 87 | return p1 88 | 89 | def meeting(p): 90 | if p is None or p.next is None: 91 | return None 92 | slow = p 93 | fast = p.next 94 | while slow and fast: 95 | if slow == fast: 96 | return slow 97 | slow = slow.next 98 | fast = fast.next 99 | if slow != fast: 100 | fast = slow.next 101 | 102 | def deleteDuplication(pHead): 103 | """ 104 | 从链表中去掉重复的节点,先去掉前面的重复节点,然后去掉中间的重复节点 105 | """ 106 | if pHead is None: 107 | return None 108 | pre, now, flag = pHead, pHead.next, False 109 | while now and pre.val == now.val: 110 | if now.next and now.next.next: 111 | pHead, pre, now = now.next, now.next, now.next.next 112 | elif now.next: 113 | return now.next if now.val != now.next.val else None 114 | else: 115 | return now.next 116 | while now: 117 | while now.next and now.next.val == now.val: 118 | now, flag = now.next, True 119 | if flag: 120 | pre.next, now, flag = now.next, pre.next, False 121 | else: 122 | pre, now = now, now.next 123 | #print(pre.val, now.val) 124 | return pHead 125 | 126 | if __name__ == '__main__': 127 | head = construct([1,2,3,3,4,4,5]) 128 | show(deleteDuplication(head)) -------------------------------------------------------------------------------- /数据结构/num.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | def num_of_1(n): 4 | """ 5 | 求1~n之间的各个数字中1出现的次数 6 | 先设定整数点作为位置i (1 10 100 。。。)对这些点进行分析 7 | 利用整数点进行分割 高位部分n/i 地位部分n%i 8 | 假设i正在表示百位,且百位的数>=2例如n=31456 i=100 则a=314 b=56 9 | 此时百位为1的次数有a/10+1=32个,每一次都包含100个连续的点 10 | 所以共有(a%10+1)*100个点的百位为1 11 | 假设百位的数字为1 例如n=31156 i=100 则a=311 b=56 12 | 此时百位对应的就是1 则共有a%10次 是包含100个连续点 13 | 当i表示百位,且百位对应的数为0,如n=31056,i=100, 14 | 则a=310,b=56,此时百位为1的次数有a/10=31(最高两位0~30) 15 | 综合以上三种情况,当百位对应0或>=2时,有(a+8)/10次包含所有 16 | 100个点,还有当百位为1(a%10==1),需要增加局部点b+1 17 | 之所以补8,是因为当百位为0,则a/10==(a+8)/10, 18 | 当百位>=2,补8会产生进位位,效果等同于(a/10+1) 19 | """ 20 | if n <= 0: 21 | return 0 22 | count = 0 23 | i = 1 24 | while i <= n: 25 | a = n/i 26 | b = n%i 27 | count=count+(a+8)/10*i+(a%10==1)*(b+1) 28 | i *= 10 29 | return count 30 | 31 | def min_num(numbers): 32 | """ 33 | numbers是一个数组,把数组中的所有数字拼接起来排成一个数字 34 | 输出能拼接的数字中最小的那个 35 | 例如输入数组{3,32,321} 36 | ,则打印出这三个数字能排成的最小数字为321323。 37 | 下面的代码是python2.7的 38 | """ 39 | if numbers is None or len(numbers) == 0: 40 | return "" 41 | s = list(map(str, numbers)) 42 | s.sort(cmp=my_cmp) 43 | return ''.join(x for x in s) 44 | 45 | def my_cmp(x, y): 46 | """ 47 | 一个比较器,输入的x y都是字符串 48 | 比较两者拼接起来 哪个在前面数字更小 49 | """ 50 | if x+y > y+x: 51 | return -1 52 | elif x+y == y+x: 53 | return 0 54 | else: 55 | return 1 56 | 57 | def ugly_num(index): 58 | """ 59 | 寻找丑数 60 | 丑数是只包含质因子 2 3 5的数比如 6 8 是丑数 61 | 但是14因为包含了7质因子不是丑数,第一个丑数默认是1 62 | 解题思路: 63 | 每个丑数都可以由之前的丑数产生p = t2*2 + t3*3 + t5*5 64 | 所以用t2 t3 t5 跟踪丑数,从1开始 65 | 同一个数字*5>*3>*2,但是不同的数字就不一样了 66 | 每次需要从之前的丑数中进行组合找到新的最小的丑数(大于现有丑数) 67 | """ 68 | if index < 7: 69 | return index 70 | res = [0 for i in range(index)] 71 | res[0] = 1 72 | t2, t3, t5 = 0, 0, 0 73 | i = 1 74 | while i < index: 75 | # print(res) 76 | res[i] = min(res[t2]*2, res[t3]*3, res[t5]*5) 77 | if res[i] == res[t2]*2: 78 | t2 += 1 79 | if res[i] == res[t3]*3: 80 | t3 += 1 81 | if res[i] == res[t5]*5: 82 | t5 += 1 83 | i += 1 84 | # print(t2, t3, t5, i) 85 | return res[-1] 86 | 87 | def first_not_repeat(s): 88 | """ 89 | s是一个字符串 从中找出第一个没有重复的字符 90 | 使用字典来保存 如果重复出现就置为-1 第一次出现就置为位置 91 | 然后遍历字典 输出第一个不为-1的值 92 | """ 93 | if s is None or len(s) == 0: 94 | return -1 95 | d = {} 96 | for i in range(len(s)): 97 | if s[i] in d: 98 | d[s[i]] = -1 99 | else: 100 | d[s[i]] = i 101 | for key in d: 102 | if d[key] != -1: 103 | return d[key] 104 | return -1 105 | 106 | def get_num_of_k(data, k): 107 | """ 108 | data是一个排序数组,从中找出k的个数 109 | 先利用二分查找找到位置,然后去前后查找 110 | """ 111 | if data is None or len(data)==0: 112 | return 0 113 | pos = bin_search(data, k, 0, len(data)-1) 114 | if pos is None: 115 | return 0 116 | p = pos 117 | count = 1 118 | while p+1 < len(data) and data[p+1] == k: 119 | p += 1 120 | count += 1 121 | print(p, count) 122 | p = pos 123 | while p-1 >= 0 and data[p-1] == k: 124 | p -= 1 125 | count += 1 126 | print(p, count) 127 | return count 128 | 129 | def bin_search(data, k, start, end): 130 | """ 131 | 二分查找 132 | """ 133 | if start > end: 134 | return 135 | mid = (start+end)//2 136 | if data[mid] == k: 137 | return mid 138 | elif data[mid] > k: 139 | return bin_search(data, k, start, mid-1) 140 | elif data[mid] < k: 141 | return bin_search(data, k, mid+1, end) 142 | 143 | def find_two_once(array): 144 | """ 145 | 数组中其他数字都是出现了两次 找到其中值出现一次的两个数字 146 | 利用异或,两个数字异或的结果是0 那么整个数组进行异或因为两个只出现一次所以结果不是0 147 | 然后异或的结果就相当于是两个只出现一次的数字进行异或 那么肯定某个位数上出现了1 148 | 利用这个1把数组中的所有数字分为两个部分 那么在这两个部分中进行异或结果就是这两个数字 149 | """ 150 | if array is None or len(array)<2: 151 | return 152 | temp = array[0] 153 | for i in range(1, len(array)): 154 | temp = temp^array[i] 155 | index = 0 156 | while temp&1 == 0: 157 | temp = temp >> 1 158 | index += 1 159 | num1, num2 = 0, 0 160 | for i in range(len(array)): 161 | if is_bit(array[i], index): 162 | num1 = num1^array[i] 163 | else: 164 | num2 = num2 ^ array[i] 165 | return [num1, num2] 166 | 167 | def is_bit(num, index): 168 | num = num >> index 169 | return num&1 170 | 171 | def FindContinuousSequence(tsum): 172 | """ 173 | 找出连续的数组 数组之和为tsum 174 | """ 175 | import math 176 | if tsum <= 0: 177 | return [] 178 | ans = [] 179 | # 根据等差数列,数组中从1开始 最长 sum = (1+n)*n / 2 所以 n < sqrt(2*sum) 180 | n = int(math.sqrt(2 * tsum)) 181 | while n >= 2: 182 | # n&1==1表示n长度为奇数 这个时候数组最中间的数字就是平均值 那么sum%n==0 183 | # 当n为偶数的时候 184 | # m-1, m, m+1, m+2 m-1 m+1抵消 sum%4=2 185 | # m-2, m-1, m, m+1, m+2, m+3 186 | # m-3, m-2, m-1, m, m+1, m+2, m+3, m+4 187 | # 很明显sum对长度取余的结果就是长度的一半 188 | if (n&1==1 and tsum%n==0) or (tsum%n) == n/2: 189 | l = [] 190 | # j是l的下标 k是数组中j下标对应的数字 191 | # tsum/n得到中间值 减去 (n-1)/2得到最小的那个数字 192 | j, k = 0, tsum/n - (n-1)/2 193 | while j < n: 194 | l.append(k) 195 | j += 1 196 | k += 1 197 | ans.append(l) 198 | n -= 1 199 | return ans 200 | 201 | def last_remain(n, m): 202 | """ 203 | 约瑟夫环问题 在n个人循环数数选出第m个人, 204 | 剔除出去然后重新进行循环选出第m个人, 205 | 求最后选出的那个人在一开始的序列中的下标 从0开始 206 | 两种解法: 207 | 1. 利用数组进行模拟 208 | 2. 利用数学原理进行解: 209 | 设第一次循环选(这次循环中的各个数字下标记序列记为 序列1)出第m个人,这个人的下标是k=n%m, 210 | 然后把这个数字的下一个数字作为0号数字进行计数(各个数字序列记为 序列2)这次有n-1个人 211 | 从n-1个人中循环选出第m个 这个数在序列2中的下标为k'=(n-1)%m 212 | 那么第二个数字在第一个序列中的下标是多少呢?k'+ 213 | """ 214 | 215 | def str2int(s): 216 | """ 217 | s是一个数字字符串 不使用库函数转为数字 218 | """ 219 | if s is None or len(s)==0: 220 | return 0 221 | ret = 0 222 | i = 0 223 | for n in s[::-1]: 224 | if n not in ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '+']: 225 | return 0 226 | if n is '-': 227 | if i == len(s)-1: 228 | return -ret 229 | else: 230 | return 0 231 | if n is '+': 232 | if i == len(s)-1: 233 | return ret 234 | else: 235 | return 0 236 | ret += (ord(n)-ord('0'))*(10**i) 237 | i += 1 238 | print(ret, i) 239 | return ret 240 | 241 | def duplication(numbers): 242 | """ 243 | 从数组中找到第一个重复的数字,数组中的数字大小小于数组长度(数值从0开始) 244 | 两种方法: 245 | 1. 声明一个额外的数组来标记 246 | 2. 利用数组本身进行标记 当遍历到一个数字的时候就把numbers[numbers[i]] += len(numbers) 247 | 利用数字值小于数组长度,将这个数字作为下标,把那个位置上的值加n 248 | 如果下次再加的时候发现大于len(numbers)就是这个数字第一次重复 249 | 如果遍历到一个数字大于len(numbers)那么就减去来得到原本的数值 250 | """ 251 | for i in numbers: 252 | index = i 253 | if index >= len(numbers): 254 | index = index - len(numbers) 255 | if numbers[index] >= len(numbers): 256 | return index 257 | numbers[index] += len(numbers) 258 | return -1 259 | 260 | def multiply(A): 261 | """ 262 | 构建乘积数组 263 | A是一个数组,返回一个数组B,使得B[i] = A[1]*A[2]*...*A[i-1] * A[i+1]*...A[len(A)-1] 264 | 除了A[i]之外都乘上,不能用除法 265 | 很明显把B数组展开是一个矩阵 266 | | 1 | A[1] |...| A[n-1] | 267 | | A[0] | 1 | A[2] |...| A[n-1] | 268 | ... 269 | 这样先利用上三角矩阵的性质进行连乘 270 | 然后再在下三角矩阵中进行连乘 271 | """ 272 | if A is None or len(A) == 0: 273 | return None 274 | B = [0 for i in range(len(A))] 275 | B[0] = 1 276 | # 先乘上三角 277 | for i in range(1, len(B)): 278 | B[i] = B[i-1]*A[i-1] 279 | temp = 1 280 | print(B) 281 | # 下三角矩阵中连乘 282 | for i in range(len(B)-1)[::-1]: 283 | temp *= A[i+1] 284 | B[i] *= temp 285 | print(B, temp, i) 286 | return B 287 | 288 | def min_in_rotate_arr(arr): 289 | """ 290 | 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 291 | 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 292 | 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 293 | NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。 294 | 使用类似于二分查找法,一个区域内如果出现了非有序的部分那就是存在最小值的部分 295 | """ 296 | if len(rotateArray) == 0: 297 | return 0 298 | i, j = 0, len(rotateArray)-1 299 | while i < j: 300 | mid = (i+j)/2 301 | if rotateArray[mid] > rotateArray[i]: 302 | # 左边是有序的,忽略左边 303 | i = mid 304 | elif rotateArray[mid] < rotateArray[j]: 305 | # 右边是有序的,mid的位置可能就是最小的值,所以不能忽略 306 | j = mid 307 | else: 308 | # 因为Python2.7除法是地板除,所以只剩下两个数的时候mid==i 309 | i += 1 310 | return rotateArray[i] 311 | 312 | if __name__ == '__main__': 313 | #print(ugly_num(8)) 314 | #print(first_not_repeat('google')) 315 | #print(get_num_of_k([1,2,3,3,3,4,5], 3.5)) 316 | #print(bin_search([3], 3, 0, 0)) 317 | #print(str2int('+123')) 318 | #print(duplication([2,1,3,1,4])) 319 | print(multiply([1,2,3,4,5])) -------------------------------------------------------------------------------- /数据结构/search.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | def bin_search(lists, key): 4 | """ 5 | 二分查找相当于构建了一个树,在树上进行查找 6 | 时间复杂度为 O(log2(n)) 7 | """ 8 | low, high = 0, len(lists)-1 9 | while low <= high: 10 | mid = int(low+(high-low)/2) 11 | if key < lists[mid]: 12 | high = mid - 1 13 | elif key > lists[mid]: 14 | low = mid+1 15 | else: 16 | return mid 17 | return None 18 | 19 | def bin_search_recursion(lists, key, low, high): 20 | if low > high: 21 | return None 22 | mid = int(low+(high-low)/2) 23 | if lists[mid] == key: 24 | return mid 25 | elif lists[mid] > key: 26 | high = mid - 1 27 | else: 28 | low = mid + 1 29 | return bin_search_recursion(lists, key, low, high) 30 | 31 | if __name__ == '__main__': 32 | lists = [1, 3, 5, 6, 12, 15 ,16, 20] 33 | print(bin_search_recursion(lists, 3, 0, len(lists)-1)) -------------------------------------------------------------------------------- /数据结构/sort.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | # 八大排序算法 适用于Python2 4 | 5 | def insert_sort(lists): 6 | """ 7 | 插入排序: 8 | 将一个数据插入到已经排好序的有序数据中,从而获得一个新的、个数加一的有序数据 9 | 算法适用于少量数据的排序,时间复杂度为O(n^2) 是稳定排序方法 10 | 插入算法要把排序的数据分成两部分,第一部分包含这个数组的所有元素,但将最后一个元素除外 11 | 而第二部分就只包含这一个元素(待插入元素),在第一部分排序完成后,再将这个最后元素插入到 12 | 已经排好序的第一部分中 13 | """ 14 | count = len(lists) 15 | # 首先认为第0个元素已经是排好序的 16 | for i in range(1, count): 17 | # 对第i个元素进行排序,lists[:i]都是排好序的 18 | key = lists[i] 19 | j = i - 1 20 | # 在已经排好序的元素中一个一个比较 21 | # 如果出现插入的元素更小的情况就进行交换,然后一直比较下去 22 | while j >= 0: 23 | if lists[j] > key: 24 | lists[j+1] = lists[j] 25 | lists[j] = key 26 | j -= 1 27 | return lists 28 | 29 | def shell_sort(lists): 30 | """ 31 | 希尔排序: 32 | 希尔排序是插入排序的一种 也称为缩小增量排序,是直接插入排序算法的一种更高效改进版本 33 | 希尔排序是把记录按下表的一定增量分组,对每组使用直接插入排序算法排序 34 | 随着增量逐渐减少,每组包含的关键词越来越多,当增量减至一,整个列表被分成一组,排序结束 35 | 希尔排序是非稳定排序,最好的情况下时间复杂度为O(n^(1.3))元素已经排好序 最坏的情况下时间复杂度为O(n^2) 36 | https://www.cnblogs.com/chengxiao/p/6104371.html 37 | """ 38 | count = len(lists) 39 | step = 2 40 | # 进行分组 group的值是组的个数 也是一个组内两个元素的间隔 41 | group = count / step 42 | while group > 0: 43 | for i in range(0, group): 44 | j = i + group 45 | # 在组内进行插入排序 46 | # j之前的组内元素认为是已经排好序了 47 | while j < count: 48 | # 组内排好序的需要和待插入元素进行比较的元素 49 | k = j - group 50 | # 待插入元素 51 | key = lists[j] 52 | # 进行比较来插入 53 | while k >= 0: 54 | if lists[k] > key: 55 | lists[k + group] = lists[k] 56 | lists[k] = key 57 | k -= group 58 | # 待插入元素切换到下一个 59 | j += group 60 | # 缩小group的数目 61 | group /= step 62 | return lists 63 | 64 | def bubble_sort(lists): 65 | """ 66 | 冒泡排序: 67 | 将每个元素都和列表后面的元素进行比较,如果这个元素大了就放到后边 68 | 这样保证了最外层的for循环每次遍历之后前lists[:i]都是前i个最小的 69 | 平均算法复杂度O(n^2) 最坏也是O(n^2) 最好的情况(已经排好序)下是O(n) 70 | """ 71 | count = len(lists) 72 | for i in range(0, count): 73 | for j in range(i+1, count): 74 | if lists[i] > lists[j]: 75 | lists[i], lists[j] = lists[j], lists[i] 76 | print(lists) 77 | return lists 78 | 79 | def quick_sort(lists, left, right): 80 | """ 81 | 快速排序: 82 | 将要排序的数据分为两部分,其中一部分的所有数据都比另一部分的所有数据要小 83 | 然后再按此方法对这两部分数据分别进行快速排序,整个过程递归进行 84 | 平均算法复杂度为O(n*logn) 85 | 最坏算法复杂度为O(n^2) 86 | 每个排序算法都在努力突破O(n^2)的复杂度 87 | http://developer.51cto.com/art/201403/430986.htm 88 | """ 89 | # 两个哨兵的位置已经交错则直接返回 90 | if left >= right: 91 | return lists 92 | # 基数,排好序的左边的部分要小于基数 右边部分要大于基数 93 | key = lists[left] 94 | low = left 95 | high = right 96 | # 哨兵没有相遇的时候 97 | while left < right: 98 | # 右边的哨兵先出发 直到发现一个比基数小的数字 99 | while left < right and lists[right] >= key: 100 | right -= 1 101 | # 基数已经保存在了key中,所以直接将小小于基数的数字保存进基数的位置 102 | # 如果是再次循环的话,之前lists[left]的数字也已经转移位置了 103 | lists[left] = lists[right] 104 | # 左边的哨兵出发 直到发现一个比基数大的数字 105 | while left < right and lists[left] <= key: 106 | left += 1 107 | # 之前lists[right]已经被保存在了key的位置或者其他没有被占用的位置中 所以这里可以直接将lists[left]保存在里面 108 | lists[right] = lists[left] 109 | # 左右哨兵相遇 此时相遇位置上的元素已经被放到了其他的位置上 将基数放在这个位置上完成排序 110 | # right left就是相遇的位置 111 | assert right == left 112 | lists[left] = key 113 | quick_sort(lists, low, left-1) 114 | quick_sort(lists, left+1, high) 115 | return lists 116 | 117 | def select_sort(lists): 118 | """ 119 | 直接选择排序: 120 | 第一趟,在待排序记录r1~rn中选出最小的记录将它和r1交换 121 | 第二趟,载待排序记录r2~rn中选出最小记录和r2交换 122 | 以此类推经过r趟排序即可完成排序 123 | """ 124 | count = lists 125 | for i in range(count): 126 | min = i 127 | for j in range(i+1, count): 128 | if lists[min] > lists[j]: 129 | min = j 130 | lists[min], lists[j] = lists[j], lists[min] 131 | return lists 132 | 133 | def adjust_heap(lists, i, size): 134 | lchild = 2 * i + 1 135 | rchild = 2 * i + 2 136 | # max保存的是这个子树中的最大的节点的下标 137 | max = i 138 | # 当i是中间节点的时候进行调整 139 | if i < size // 2: 140 | # lchild lists[max]: 142 | max = lchild 143 | if rchild < size and lists[rchild] > lists[max]: 144 | max = rchild 145 | # 如果这个子树中的最大值不是根节点 交换两个节点 146 | if max != i: 147 | lists[max], lists[i] = lists[i], lists[max] 148 | # 交换过之后 将下沉的节点作为子树的根节点继续进行调整 149 | adjust_heap(lists, max, size) 150 | 151 | def build_heap(lists, size): 152 | # size//2得到树中的中间节点数 153 | # 而且这些中间节点都是在lists[:size//2]中 154 | # 调整的时候从最后的非叶节点开始 这样保证所有的根节点在子树内都是最大的 155 | for i in range(size//2)[::-1]: 156 | # 调整每个子树 i就是每个子树的根 157 | adjust_heap(lists, i, size) 158 | 159 | def heap_sort(lists): 160 | """ 161 | 堆排序: 162 | 堆排序是一种选择排序,最好 最坏 平均时间复杂度为O(n*logn) 163 | 将待排序序列构造成一个大顶堆,此时整个序列的最大值就是堆顶的根节点 164 | 再将根节点和末尾元素进行交换,此时末尾就为最大值,然后将剩余n-1个元素 165 | 重新构造成一个大顶堆,如此反复就能进行排序 166 | 167 | 当用数组表示树的时候: 168 | 大顶堆 arr[i] >= arr[2i+1] and arr[i] >= arr[2i+2] 169 | 小顶堆 arr[i] <= arr[2i+1] and arr[i] <= arr[2i+2] 170 | """ 171 | size = len(lists) 172 | # 构建大顶堆 173 | build_heap(lists, size) 174 | # 从最后的那个节点开始 交换根节点和它的位置 175 | for i in range(size)[::-1]: 176 | lists[0], lists[i] = lists[i], lists[0] 177 | # 交换过后继续进行调整堆 178 | adjust_heap(lists, 0, i) 179 | 180 | def merge(left, right): 181 | # left right都是有序子序列 182 | i, j = 0, 0 183 | result = [] 184 | while i < len(left) and j < len(right): 185 | if left[i] <= right[i]: 186 | result.append(left[i]) 187 | i += 1 188 | else: 189 | result.append(right[j]) 190 | j += 1 191 | result += left[i:] 192 | result += right[j:] 193 | return result 194 | 195 | def merge_sort(lists): 196 | """ 197 | 归并排序: 198 | 采用分治法 先使每个子序列有序 再将已经有序的子序列合并 199 | 如果是将两个有序列表合并则是二路归并 200 | 分治法的时间:分解时间 + 解决问题时间 + 合并时间 201 | 分解时间就是把待排序结合分成两部分 O(1) 202 | 解决问题时间是两个递归式:把N的问题规模分成两个 N/2的子问题 时间为2T(N/2) 203 | 构成一个递归树 只要解决树的叶节点上的问题时间复杂度为常数就可以 204 | 合并时间为O(N) 205 | 总时间为T(n) = 2T(n/2)+O(n) 解为O(n*logn) 206 | 时间复杂度最好 平均 最坏都是O(n*logn) 归并排序是稳定排序 207 | """ 208 | if len(lists) <= 1: 209 | return lists 210 | num = len(lists) // 2 211 | left = merge_sort(lists[:num]) 212 | right = merge_sort(lists[num:]) 213 | return merge(left, right) 214 | 215 | def radix_sort(lists, radix=10): 216 | """ 217 | 基数排序: 218 | 属于分配式排序 又称桶子法 219 | https://www.cnblogs.com/ECJTUACM-873284962/p/6935506.html 220 | """ 221 | import math 222 | k = int(math.ceil(math.log(max(lists), radix))) 223 | bucket = [[] for i in range(radix)] 224 | for i in range(1, k+1): 225 | for j in lists: 226 | bucket[j/(radix**(i-1)) % (radix**i)].append(j) 227 | del lists[:] 228 | for z in bucket: 229 | lists += z 230 | del z[:] 231 | return lists 232 | 233 | # 海量数据进行排序 或者找出Top K大/小 的数字 234 | # 这种情况下内存肯定是受限制的,不可能一次把所有的数字都加载到内存中 235 | # 1 局部淘汰法:用一个容器放置无序的前K个数字,再逐个将后面的数字与容器中的数字进行比较,交换 O(n+m^2) 236 | # 2 分治法 采用多路归并(外排序) 237 | # 3 hash方法,如果这大量的数据中有很多重复的,那么就可以去掉重复的,提高效率 238 | # 4 大/小顶堆:先读入前K个数字构建大/小顶堆,时间复杂度O(m*logm).然后遍历后续数字,与堆顶的数字进行比较. 239 | # 如果求前K个最小的数字,那么构建大顶堆,将后续数字和堆顶进行比较,如果小于堆顶则替换 调整堆,如果大于则进行下一个 240 | -------------------------------------------------------------------------------- /数据结构/step.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | def jump_floor(n): 4 | """ 5 | 普通的跳台阶题目是一次可以跳1个或者2个 6 | 这里是一次可以跳1 2 3 4。。。n都可以 7 | 那么从一个台阶开始 8 | f(1) = 1 9 | f(2) = 2 10 | f(3) = f(3-3) + f(3-2) + f(3-1) 11 | ... 12 | f(n) = f(n-n) + f(n-(n-1)) + ... + f(n-2) + f(n-1) 13 | n个台阶的最后一步有n种,注意到 14 | f(n) = f(n-1) + [ f((n-1)-1) + f((n-1)-2) + ... +f((n-1)-(n-2)) + f((n-1)-(n-1)) ] 15 | f(n) = 2 * f(n-1) 16 | """ 17 | if n <= 0: 18 | return 0 19 | if n == 1: 20 | return 1 21 | return 2*jump_floor(n-1) 22 | 23 | def rect_cover(target): 24 | """ 25 | 使用2*1的方块覆盖target*2的矩形,小方块可以竖着放也可以横着放 26 | 使用递归 27 | 1. 如果矩形只有一个小方块的面积直接返回1 28 | 2. 如果第一个是横着放的,那么剩余的面积就是 29 | | x | |。。。| 30 | | | |。。。| 31 | 那么剩余的放法就是f(n-2) 32 | 3. 如果第一个是竖着放的,那么剩余的面积就是 33 | |x| |...| 34 | |x| |...| 35 | 那么剩余的放法就是f(n-1) 36 | """ 37 | if target <= 0: 38 | return 0 39 | if target == 1: 40 | return 1 41 | if target == 2: 42 | return 2 43 | return rect_cover(target-1) + rect_cover(target-2) 44 | -------------------------------------------------------------------------------- /数据结构/str.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | def permutation(ss): 4 | """ 5 | ss是一个字符串,只包含了大小写的字母 6 | 现在让其中的字母重新进行排列组合 保存到返回的list中,list要有序 7 | 利用回溯法 每次按照顺序交换字母 然后固定 进行下一个字母的交换 如果到最后就放入ret中 8 | """ 9 | if ss is None or len(ss)==0: 10 | return [] 11 | ret = [] 12 | ss = list(ss) 13 | permu_helper(ss, 0, ret) 14 | return sorted(ret) 15 | 16 | def permu_helper(ss, i, ret): 17 | if i == len(ss)-1: 18 | s = ''.join(x for x in ss) 19 | if s not in ret: 20 | ret.append(s) 21 | else: 22 | j = i 23 | while j < len(ss): 24 | swap(ss, i, j) 25 | permu_helper(ss, i+1, ret) 26 | swap(ss, i, j) 27 | j += 1 28 | 29 | def swap(ss, i, j): 30 | ss[i], ss[j] = ss[j], ss[i] 31 | 32 | def match(s, pattern): 33 | """ 34 | 实现一个能够匹配包括 '.' '*' 的模式串 35 | 思路:模式串中的下一个是不是*,来进行区分 36 | 结束匹配后有四种情况 37 | 1. s还剩有没匹配 pattern还剩有 False 38 | 2. s匹配完 pattern还有 'aaa' 'a*a' 39 | 3. s还有 pattern没有 False 40 | 4. s匹配万 pattern匹配完 True 41 | """ 42 | # write code here 43 | if s is None or pattern is None: 44 | return False 45 | if s == pattern: 46 | return True 47 | i, j = 0, 0 48 | while j < len(pattern) and i < len(s): 49 | if j+1 < len(pattern) and pattern[j+1] == '*': 50 | if pattern[j] == '.': 51 | i += 1 52 | elif pattern[j] != s[i]: 53 | j += 2 54 | continue 55 | else: 56 | i += 1 57 | if i == len(s): 58 | j += 2 59 | elif pattern[j] == '.': 60 | i += 1 61 | j += 1 62 | else: 63 | if pattern[j] != s[i]: 64 | return False 65 | i += 1 66 | j += 1 67 | if i != len(s): 68 | return False 69 | # 最后匹配的是x* 再将j+=2 70 | if j+2 <= len(pattern) and pattern[j+1] == '*': 71 | j += 2 72 | print(i, j) 73 | if i == len(s) and j == len(pattern): 74 | return True 75 | if j+1 == len(pattern) and pattern[j]!='.' and pattern[j]==pattern[j-2] and pattern[j-1]=='*': 76 | return True 77 | return False 78 | 79 | class FirstAppear: 80 | l_1 = [] 81 | l_2 = [] 82 | # 返回对应char 83 | def FirstAppearingOnce(self): 84 | # write code here 85 | return self.l_1[0] if len(self.l_1)!=0 else '#' 86 | def Insert(self, char): 87 | # write code here 88 | if char in self.l_1: 89 | self.l_1.pop(self.l_1.index(char)) 90 | self.l_2.append(char) 91 | if char in self.l_2: 92 | return 93 | else: 94 | self.l_1.append(char) 95 | 96 | if __name__ == '__main__': 97 | #print(match("aaa", "a*a")) 98 | s = FirstAppear() 99 | s.Insert('h') 100 | s.Insert('e') 101 | s.Insert('l') 102 | s.Insert('l') 103 | print(s.FirstAppearingOnce()) -------------------------------------------------------------------------------- /数据结构/sum.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | # 从整数数组中找到三个数他们的和为0,找到所有的三元组 4 | # 这个三个元素必须是从小到大排序,这三个数字不能重复 5 | def three_sum(arr): 6 | """ 7 | arr: list[int] 8 | return list[list[int]] 9 | 先将问题转换成两个数之和,那么问题就变为了B+C=K-A 10 | 给定一个n个元素的集合S,找出S中满足条件的整数对B C, 使得B+C=K-A。 11 | 12 | 假定集合S已经排好序的话,则上面这个问题可以在O(n)的时间内解决。 13 | 使用2个索引值left和right,分别指向第一个元素和最后一个元素,设指向的第一个元素为B, 14 | 则我们的任务就是找到对应于B的元素C,C=K-B。如果arr[left]+arr[right]>target则right-=1 15 | 如果arr[left]+arr[right] target: 37 | right -= 1 38 | else: 39 | # 构造三元组,同时原来的数组中可能还有重复的数字,这里都进行排除 40 | triple = [arr[i], arr[left], arr[right]] 41 | while left < right and arr[left] == triple[1]: 42 | left += 1 43 | while left < right and arr[right] == triple[2]: 44 | right -= 1 45 | result.append(triple) 46 | 47 | # a也有可能重复,这里进行排除 48 | while i+1 < len(arr) and arr[i+1] == arr[i]: 49 | i += 1 50 | i += 1 51 | -------------------------------------------------------------------------------- /数据结构/t.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | using namespace std; 4 | 5 | int NumberOf1(int n) { 6 | // 输出数字中二进制中的1的个数,如果是负数则使用补码 7 | int count = 0; 8 | // 输出数字中二进制中的1的个数,如果是负数则使用补码 9 | // 计算机存负数的时候直接就是补码 10 | unsigned int flag = 1; 11 | while(flag){ 12 | // flag二进制中1的个数只有一个 13 | if(flag&n){ 14 | count ++; 15 | } 16 | // flag中的1右移一位 17 | flag = flag << 1; 18 | } 19 | return count; 20 | } 21 | 22 | int NumberOf1_(int n) 23 | { 24 | int count = 0; 25 | while(n){ 26 | count++; //只要n不为0则其至少有一个1 27 | // 假如说n=1110 那么n-1就是0111 28 | // 按位与的结果就是 0110 这样每次进行按位与都能消掉一个1 29 | n = n & (n - 1); 30 | } 31 | return count; 32 | } 33 | 34 | double Power(double base, int exponent) { 35 | if(exponent<0){ 36 | base = 1/base; 37 | exponent = -exponent; 38 | } 39 | double num = base; 40 | for(int i=0;i data) { 50 | int length=data.size(); 51 | if(length<=0) 52 | return 0; 53 | //vector copy=new vector[length]; 54 | vector copy; 55 | for(int i=0;i &data,vector ©,int start,int end) 62 | { 63 | if(start==end) 64 | { 65 | copy[start]=data[start]; 66 | return 0; 67 | } 68 | int length=(end-start)/2; 69 | long long left=InversePairsCore(copy,data,start,start+length); 70 | long long right=InversePairsCore(copy,data,start+length+1,end); 71 | 72 | int i=start+length; 73 | int j=end; 74 | int indexcopy=end; 75 | long long count=0; 76 | while(i>=start&&j>=start+length+1) 77 | { 78 | if(data[i]>data[j]) 79 | { 80 | copy[indexcopy--]=data[i--]; 81 | count=count+j-start-length; //count=count+j-(start+length+1)+1; 82 | } 83 | else 84 | { 85 | copy[indexcopy--]=data[j--]; 86 | } 87 | } 88 | for(;i>=start;i--) 89 | copy[indexcopy--]=data[i]; 90 | for(;j>=start+length+1;j--) 91 | copy[indexcopy--]=data[j]; 92 | return left+right+count; 93 | } 94 | }; 95 | 96 | int add(int num1, int num2){ 97 | // 不使用+ - * / 来加数字 98 | // 10101001 99 | // 11010010 100 | // 先计算两者的异或,这个部分可以算作相加,然后利用与运算&来得到哪些位需要进位 然后左移一位作为进位 101 | // 一直重复直到没有进位为止 102 | while(num2!=0){ 103 | int temp = num1^num2; 104 | num2 = (num1&num2)<<1; 105 | num1 = temp; 106 | } 107 | return num1; 108 | } 109 | 110 | int main(){ 111 | //int a = 5; 112 | //std::cout<<"binary number of 5 contains "<1-y 46 | 2. x<1-x 47 | 3. x+(1-y)>y-x 48 | 49 | 得到x<1/2,y>1/2,y>x+1/2画图得到结果的面积为1/8。 50 | 51 | **题目7** 52 | 53 | 世界上每十万人中就有一人是艾滋病患者。艾滋病的检测目前已经很准确但并非万无一失。它的检测准确率是99%假设你刚去做完艾滋病检验得到的了检测报告结果….是阳性!你会绝望或昏倒吗?或者说你会担心到什么程度? 54 | 55 | 解析:准确率为99%,也就是100个人中就有一个人诊断错误,也就是十万人中有一千个人会被诊断错误,其中为阳性又被诊断错误的概率太小可以忽略,那么即使被诊断为阳性,得病的概率也是非常小的。 56 | 57 | **题目8** 58 | 59 | 有一对夫妇先后生了两个孩子,其中一个是女孩,那么另一个孩子是男孩的概率多大? 60 | 61 | 解析:2/3,从观察者来说一上来就是两个孩子,所以需要从男男1/4,男女1/2,女女1/4中进行判断,排除了男男那么就是2/3了。如果是一对夫妇先生了一个女儿,问再生男生的概率那么就是1/2。 62 | 63 | **题目9** 64 | 65 | 一个国家人们只想要男孩每个家庭都会一直要孩子只到他们得到一个男孩。如果生的是女孩他们就会再生一个。如果生了男孩就不再生了。那么这个国家里男女比例如何? 66 | 67 | 解析:设一个家庭生男孩的期望值为S1,生女孩个数的期望值为S2.那么S1=1,来考虑S2。一个家庭的孩子数量可以为1,2,3...,对应的就是男,女男,女女男... 68 | 69 | 概率分布为1/2,1/4,1/8...,其中女孩个数为0,1,2,3,4... 70 | 71 | 那么S2=0\*1/2 + 1\*1/4 + 2\*1/8 + ... 72 | 73 | 2\*S2=1/2+2/4+3/8+4/16+5/32+.. 74 | 75 | 两边相减得到S2=1. 76 | 77 | **题目10** 78 | 79 | 在17世纪,有一个赌徒向法国著名数学家帕斯卡挑战,给他出了一道题目:甲乙两个人赌博,他们两人获胜的机率相等,比赛规则是先胜三局者为赢家,一共进行五局,赢家可以获得100法郎的奖励。当比赛进行到第四局的时候,甲胜了两局,乙胜了一局,这时由于某些原因中止了比赛,那么如何分配这100法郎才比较公平? 80 | 81 | 解析:甲分得75法郎,乙分得25法郎。因为甲获胜的概率是1-1/2*1/2=3/4,乙获胜的概率是1/4。所以甲赢得奖金的期望值就是100\*3/4=75,乙就是25了。 82 | 83 | ### 参考 84 | [程序员面试中常见的概率相关面试题](https://www.cnblogs.com/jinee/p/4420212.html) 85 | [互联网公司 概率面试题整理](https://blog.csdn.net/bertdai/article/details/78070092) -------------------------------------------------------------------------------- /深度学习知识点/大佬知识点.md: -------------------------------------------------------------------------------- 1 | ## Question: 2 | 3 | ### NN 4 | 1. 推导多层感知机的BP算法? 5 | 6 | 2. NN中激活函数有哪些? 7 | 8 | 3. NN中损失函数有哪些? 9 | 10 | 4. NN中初始化参数如何选择? 11 | 12 | 5. NN参数更新方式? 13 | 14 | ### CNN 15 | 1. 推导CNN的BP算法? 16 | 17 | 2. 卷积如何实现? 18 | 19 | 3. 卷积层权值共享如何实现? 20 | 21 | 4. 梯度弥散/爆炸问题的原因,如何解决? 22 | 23 | 5. 池化作用是什么,有那些种类? 24 | 25 | 6. CNN压缩/加速有哪些方法? 26 | 27 | ### DL 28 | 1. 深度学习中预防过拟合的方法? 29 | 30 | 2. Drop out 流程? 有哪些优点/为什么效果好? 为什么能预防过拟合? 31 | 32 | 3. 介绍白化预处理? 33 | 34 | 4. Batch Normalization有哪些优点? 35 | 36 | ## 代码 37 | 1. 写出LR的损失函数,如何推导? 38 | 39 | 2. 编写函数计算softmax中的cross entropy loss。 40 | 41 | ### 框架 42 | 1. 介绍tensorflow的计算图 43 | 44 | 2. 介绍tensorflow的自动求导机制 45 | 46 | ## Answer: 47 | ### NN 48 | 1. 《机器学习》101-104页 49 | 50 | 2. Sigmoid, Tanh, ReLU, Leaky ReLU, Maxout。 51 | 详见[Neural Networks Part 1: Setting up the Architecture](http://cs231n.github.io/neural-networks-1/#actfun) 52 | 53 | 3. - 损失函数组成:数据损失+正则化。 54 | - 分类:SVM / softmax,类别数目巨大用分层Softmax(Hierarchical Softmax); 55 | - 回归:L2平方范式或L1范式 56 | - RCNN: BCE + Smooth L1; 57 | - 详见[Neural Networks Part 2: Setting up the Data and the Loss](http://cs231n.github.io/neural-networks-2/#losses) 58 | 59 | 4. w = np.random.randn(n) * sqrt(2.0/n) 60 | 详见[Neural Networks Part 2: Setting up the Data and the Loss](http://cs231n.github.io/neural-networks-2/#init) 61 | 62 | 5. SGD, Momentum, Nesterov, Adagrad, RMSprop, Adam 63 | 详见[Neural Networks Part 3: Learning and Evaluation](http://cs231n.github.io/neural-networks-3/#update) 64 | 65 | ### CNN 66 | 1. [推导CNN的BP算法](https://www.zybuluo.com/hanbingtao/note/485480),注意卷积与cross-correlation(互相关)的区别。 67 | 68 | 2. - [caffe im2col](http://blog.csdn.net/jiongnima/article/details/69736844) 69 | 70 | - [Implementation as Matrix Multiplication](http://cs231n.github.io/convolutional-networks/) 71 | 72 | 3. 给一张输入图片,用一个filter去扫这张图,filter里面的数就叫权重,这张图每个位置是被同样的filter扫的,所以权重是一样的,也就是共享。每个滤波器学习一种feature。 73 | 74 | 4. - 原因解释: 75 | 梯度消失问题和梯度爆炸问题一般随着网络层数的增加会变得越来越明显。 76 | 反向传播公式中,hidden1的偏导由hidden2到hiddenL的权重乘积得到。当L较大时,w>1,连乘趋于无穷;w<0,连乘趋于0; 77 | sigmoid function,趋于两级,梯度小。连续乘,BP结果很小。 78 | - 解决方法: 79 | (1)激活函数的选择sigmoid -> relu-> prelu;(2)权重初始化;(3)逐层训练(4)Batch Normalization,Dropout;(5)RNN->LSTM; (6) Residual shortcut connection; (7)gradient clip 80 | 81 | 5. - **池化作用**:(1)逐渐降低数据体的空间尺寸,减少网络中参数的数量,使得计算资源耗费变少,(2)控制过拟合。 82 | - **种类**:max pooling,average pooling,L2-norm pooling。 83 | - **比较**:max pooling比avg pooling鲁棒性更好、处理过后边缘/平滑。 84 | 85 | 6. - 首先,CNN有冗余才能压缩/加速。其次,CNN中全连接、卷积层才有参数,主要针对这两个层,池化、ReLU没有参数。 86 | - (1)合理调超参数,在模型的损失函数中加入惩罚项,包括模型的Density和模型的Diversity。Density指的是模型参数的冗余度,就是零和极小值的多少;Diversity指的是参数的多样性,即如果参数能够聚类成为少数几个类别,那么就是多样性低,反之就是多样性丰富。 87 | - (2)全连接层参数较多,重点关注。可采用剪枝、量化(聚类存下标、编码、奇异值分解)。 88 | - (3)卷积层:低秩分解。 89 | 90 | ### DL 91 | 1. (1)数据:标注更多, augmentation; (2)正则化: 损失函数L1, L2 norm, Max norm, Dropout; (3)提前终止; (4)迁移学习; (5)ensemble 92 | 93 | 2. 94 | - 介绍: 对于某一层中的每个节点,dropout技术使得该节点以一定的概率p不参与到训练的过程中(即前向传导时不参与计算,bp计算时不参与梯度更新) 95 | - Drop out 训练流程: (1)对l层第j个神经元按照伯努利分布,生成一个随机数 (2)该神经元的输入乘上产生的随机数作为这个神经元新的输入 (3)再用该神经元的新的输入,卷积,得到输出 (4)**U1 = (np.random.rand(*H1.shape) < p) / p**, inverted dropout。 96 | - Drop out 测试流程: 不随机失活。 97 | - Drop out 优点: (1)通过dropout,节点之间的耦合度降低了,节点对于其他节点不再那么敏感了,这样就可以促使模型学到更加鲁棒的特征;(2)dropout layer层中的每个节点都没有得到充分的训练(因为它们只有p的出勤率),这样就避免了对于训练样本的过分学习;(3)在测试阶段,dropout layer的所有节点都用上了,这样就起到了ensemble的作用,ensemble能够有效地克服模型的过拟合。 98 | 99 | 3. 经过白化预处理后,数据满足条件:a、特征之间的相关性降低,这个就相当于pca;b、数据均值、标准差归一化,也就是使得每一维特征均值为0,标准差为1。缺点:然而白化计算量太大了,很不划算,还有就是白化不是处处可微的。 100 | 101 | 4. 102 | - 训练过程:对于每一个batch: 103 | ``` 104 | m = K.mean(X, axis=-1, keepdims=True)#计算均值 105 | std = K.std(X, axis=-1, keepdims=True)#计算标准差 106 | X_normed = (X - m) / (std + self.epsilon)#归一化 107 | out = self.gamma * X_normed + self.beta#重构变换 108 | ``` 109 | - 测试过程: 均值来说直接计算所有batch u值的平均值;然后对于标准偏差采用每个batch σB的无偏估计; 110 | 111 | - 优点:减小中间层数据分布发生的改变(Internal Covariate Shift) 112 | -------------------------------------------------------------------------------- /深度学习知识点/知识点整理1.md: -------------------------------------------------------------------------------- 1 | ### 梯度下降有哪些方法? 2 | - 随机梯度下降 SGD 3 | - 批量梯度下降 BGD 4 | - 小批量梯度下降 MBGD 5 | 6 | 批量梯度下降BGD:每次使用所有的样本来进行更新 7 | 8 | - 这样方便得到全局最优解 9 | - 但是很明显样本数多的时候难以实现 10 | 11 | 随机梯度下降SGD:每次只从全部样本中随机挑选出一个样本来更新参数 12 | 13 | - 样本很多的时候可能不需要全部的样本就能得到最优解 14 | - 噪音多,单个样本的影响大,可能会陷入局部最优解 15 | - 每次优化的方向不是向着全局最优去的 16 | 17 | 小批量梯度下降MBGD: 18 | 19 | - 每次更新使用一小部分样本 20 | - 为了克服上面两种方法的缺点,同时又兼顾两种方法的优点 21 | 22 | ### SGD中的S代表什么? 23 | stochastic要求会读,代表随机,在深度学习里每次随机抽取一个batch的样本来更新参数。 24 | 25 | ### 监督学习 / 迁移学习 / 半监督学习 / 弱监督学习 / 非监督学习 概念? 26 | - 监督学习:利用已经有标签的数据来训练模型,使得模型可以对新的没有标签的样本进行预测 27 | - 迁移学习:将一个环境中学到的知识用来帮助新环境中的学习任务。 28 | - 无(非)监督学习:没有标签的数据进行分类,通常是聚类的方法,将相似度高的放到同一类中。 29 | - 半监督学习:有两个样本集,一个有标记 一个没有标记,综合利用有标记和没有标签的数据集进行学习。 30 | - 弱监督学习:标注的成本很高,精细的标注很难得到,比如这个图片是什么类别 框住物体的坐标,但是粗粒度的标签很容易得到 比如这个图片不是什么类别,物体大概在什么地方。或者是标签里的标注并不总是正确的。 31 | 32 | ### softmax loss推导? 33 | softmax形如$ P(i) = \frac{e^{xj}}{\sum_ie^{xi}} $的函数。通过softmax可以将分类任务中输出的值转化成概率。P(i)中的i代表第i个样本,后面的i代表第i个类别,xj代表这个样本正确的类别的score 34 | 35 | CrossEntropyLoss = $ -log(\frac{e^{xj}}{\sum_ie^{xi}}) $ 36 | 37 | - 为什么要进行exp:这样可以将负的预测值转成正的。 38 | - 为什么要用预测某个类别的exp值比上所有的值之和:这样是为了计算P(j | x_i)概率 i是第i个样本 j是这个样本的真实类别,softmax loss可以看成是最大似然函数 这个函数希望正确的类别概率是1, 除以所有的exp值可以看成是标准化,让所有exp值都变成0-1之间,并且和为1。 39 | - 为什么最前面又要一个负号:因为这个是损失函数,用来度量分类器做的有多不好,最终计算的时候只计算-log(真实类别的概率)作为loss的值。当真实的概率接近1的时候,-log(p)就为0,当真实的概率很低的时候-log(p)就很高。 40 | - softmax loss的最大值和最小值是多少:最小是0, 最大是无穷。 41 | 42 | **交叉熵求导** 43 | 设p为经过归一化之后的概率,fk是正确的类别的score,那么对于一个样本的loss就是$$ p_k = \frac{e^{f_k}}{\sum_je^{f_j}} \qquad L_i = -log(p_{y_i}) $$经过求导 简化最终对正确类别的输出score的导数是$$ \frac{\partial L_i}{\partial f_k} = p_k - \mathbb{1}(y_i=k) $$。不正确的概率的导数仍是概率本身。 44 | 45 | 假如说输出的概率是[0.2, 0.3, 0.5]中间的那个类别是正确的概率,那么这三个输出的神经元的导数就是[0.2, -0.7, 0.5],这样子很直观,不正确的分类概率带来了loss,而正确分类则会降低loss。 46 | 47 | ### logistic 推导? 48 | sigmoid函数作图就得到logistic曲线,sigmoid因变量取值范围为(-∞, +∞)输出为(0, +1) 49 | 50 | $ sigmoid = \frac{1}{1+e^-x} $ 51 | 52 | sigmoid特点: 53 | 54 | - sigmoid是一个阈值函数 将输入限制在(0, 1)之间 55 | - sigmoid严格单调递增,而且其反函数(g(y)=x是反函数)也单调递增 56 | - sigmoid函数连续 处处光滑可导 57 | - sigmoid函数关于(0, 0.5)对称 58 | - f'(x) = F(f(x)) F(z) = z*(1-z) 59 | 60 | ### 为什么CNN也可以用在NLP中甚至于AlphaGo中也应用?CNN通过什么抓住了他们的共性? 61 | 62 | 以上的这几个应用都存在,**局部和整体的关系**。由低层次的特征经过组合成为高层次的特征,并且得到不同特征之间的空间相关性。如低层次的直线/曲线等特征,组合成为不同的形状,最后得到汽车的表示。 63 | 64 | CNN抓住此特性的手段: 65 | 66 | - 局部连接,提取数据的局部特征 67 | - 权值共享,降低了网络的训练难度,一个Filter提取一个特征,在整个图片(语音、文本)中进行卷积 68 | - 池化操作,实现了数据的降维 69 | - 多层次结构,将低层次的局部特征组合成较高层次的特征 70 | 71 | ### 什么样的数据集不适合用深度学习? 72 | - 数据集太小。样本不足时深度学习相对其他的机器学习方法没有明显优势。 73 | - 数据集没有局部相关特性。深度学习在图像/语音/自然语言等领域优势明显,他们的一个共同的特点是局部相关性。图像中像素组成图片,语音中音位组成单词,文本中单词组成句子,这些特征元素的组合一旦被打乱,表示的含义也会变化。而对于没有局部相关性的数据,如传统的放贷预测模型中的各个特征互相不关联,打乱他们的顺序不影响结果。 74 | 75 | ### 对所有优化问题来说,有没有可能找到比现在已知算法更好的算法? 76 | 没有免费午餐定理:对于所有的问题,就算某个算法A效果比算法B更好,则必然存在一些问题算法B的效果更好。他们的期望性能相同。在实际的应用中,不同的场景会有不同的问题分布,所以**在优化算法时,针对具体问题进行分析,是算法优化核心所在** 77 | 78 | ### Dropout 79 | **dropout是什么** 对于全连接神经网络单元,按照一定的概率使其暂时失效,故而每个mini-batch相当于在训练不同的神经网络,它强迫一个神经单元和随机挑选出来的其他神经单元共同工作,消除了神经元节点之间的联合适应性,增强了泛化能力,是CNN中防止过拟合的一个重要方法。 80 | 81 | **dropout和bagging**:dropout可以认为是一种极端的Bagging,每个模型都在单独的数据上训练,同时,通过和其他模型对应参数的共享,从而实现模型参数的高度正则化。 82 | 83 | **dropout有效的原因**: 84 | 85 | 1. 神经网络容易过拟合,除了使用dropout就是训练多个模型进行组合。 86 | 2. 但是训练多个神经网络耗费大量时间。 87 | 3. 每次进行完dropout都相当于从原网络采样出一个新的子网络。p=0.5时一层N个节点的网络,训练的参数数目不变,但是却相当于训练出了2^N个子网络。 88 | 4. 使用dropout相当于让这些子神经网络进行竞争。 89 | 5. 一般来说dropout的概率设置为0.5就可以,因为这个时候dropout随机生成的结构最多。 90 | 6. 数据量小的时候dropout效果不好,数据量大的时候效果好。 91 | 7. dropout的缺点是训练时间是没有dropout的2~3倍 92 | 93 | ### 什么是共线性?和过拟合有什么关系? 94 | **共线性**:多变量线性回归中,输入的变量之间由于存在高度相关关系而使回归估计不准确。一个变量可以由其他的变量计算得到。共线性导致了输入神经网络或者其他回归模型的数据冗余,导致过拟合。 95 | 96 | **为什么其他的学习器没有共线性?**:决策树和朴素贝叶斯中,前者的建模过程是逐步建立的,每次只有一个变量参与,这个建模机制导致了不会出现共线性。而朴素贝叶斯则直接假设各个输入的变量之间是独立的,因此也没有共线性的问题。 97 | 98 | **如何消除共线性**: 99 | 100 | - 对输入的数据进行降维,排除相关性,比如PCA 101 | - 增加惩罚项,比如岭回归 102 | 103 | ### 广义线性模型是怎样被应用再深度学习中的? 104 | **什么是广义线性模型**:Generalized Linear Model,是一种统计模型。普通的线性模型就是y=a0+a1x1+...+ε。在进行回归的时候,需要预测的y可能服从各种不同分布。比如普通的回归分析(预测房价 正态分布),二项分布的预测(0-1分类 Logistic回归),或者多项分布的预测(多类分类 softmax) 或者是泊松分布的预测(在某个时间点人流量的预测)等等。当预测的y的分布不属于正态分布的时候就需要其他的函数来作为联结函数来构成广义线性模型。比如sigmoid函数作联结函数来进行Logistic回归。 105 | 106 | **为什么要建立统计模型**:为了研究自变量和因变量之间的关系,但当自变量能够100%决定因变量的时候就不需要统计模型了。使用统计模型是为了在有噪音的数据中找到规律。因为噪音(随机误差)也是有规律的。 107 | 108 | **深度学习和广义线性模型关系**: 109 | 110 | - 深度学习从统计学角度,可以看做递归的广义线性模型 111 | - 广义线性模型相对于经典的线性模型,核心在于引入了联结函数g() 112 | - 深度学习看成广义线性模型的话,神经元的激活函数就相当于广义线性模型的联结函数 113 | - 不同的广义线性模型有不同的联结函数,[参考](http://blog.shakirm.com/2015/01/a-statistical-view-of-deep-learning-i-recursive-glms/) 114 | 115 | ### 什么造成了梯度消失/爆炸? 116 | **梯度消失**:假如使用了sigmoid作为激活函数,当反向传播的时候,若传回来的数值是正数很大、或者负数很小,这个时候sigmoid的导数几乎为零$ f'(x) = f(x)(1 - f(x)) $。那么继续反向传播,梯度几乎为零,参数得不到更新,学习停止。使用tanh函数作为激活函数的时候也一样,梯度很容易变成接近零。 117 | 118 | **梯度爆炸**:反向传播的时候当,一开始的梯度大于1,并且后续的梯度都大于1的时候,随着反向传播的进行,梯度会越来越大。sigmoid或者tanh的输出不是zero-center的,这会导致如果输入的数据x是正数$ f(w^Tx + b) $,那么对w的导数也都是正数或者都是负数。 119 | 120 | 121 | **如何解决** 122 | 123 | - 使用ReLU/leakrelu/elu等作为激活函数,ReLU的结构导致了梯度不会消失,ReLU的导数恒等于1。ReLU在图像处理方面尤其胜过其他的激活函数。 124 | - batchnorm,反向传播的时候相当于把过大过小的数值,重新归一化到了0-1之间 125 | - 残差结构,残差结构出现以后可以轻松构建几百层 几千层的网络,就是因为shortcut保证了梯度不会消失过快。 126 | 127 | ### 各种激活函数进行对比 128 | [聊一聊深度学习的activation function](https://zhuanlan.zhihu.com/p/25110450) 129 | 130 | ### 为什么要在神经网络中引入激活函数? 131 | - 增加神经网络的非线性,普通的矩阵相乘永远是线性的 132 | - 神经网络可以看做是用来模拟数据的分布函数,只需要一层神经网络加上非线性函数,可以理论上[模拟任何的连续函数](http://neuralnetworksanddeeplearning.com/chap4.html)。 133 | 134 | ### 什么是BatchNorm? 135 | 为了解决神经网络中出现梯度消失/爆炸的问题,提出了BatchNorm。每个BatchNorm针对每个channel(如果是全连接神经网络,那么一层学习$ \beta \gamma $)学习两个参数$ \beta \gamma $来对数据进行平移 缩放。极端的情况下这两个参数等于mini-batch的均值和方差,这个时候经过BN之后的数据和输入的数据相同。 136 | 137 | BN对于不好的初始化有很高的鲁棒性,并且相当于是给每一层的输入都进行了标准化预处理,但是这种预处理以一种可以微分的形式嵌入了神经网络中。 138 | 139 | **BN的计算过程** 140 | 141 | 1. 计算mini-batch的均值 $ \mu_B \leftarrow \frac{1}{m} \sum_{i=1}^m x_i $ 142 | 2. 计算mini-batch的方差 $ \sigma_B^2 \leftarrow \frac{1}{m} \sum_{i=1}^m (x_i - \mu_B)^2 $ 143 | 3. 进行标准化$ \hat{x_i} \leftarrow \frac{x_i - \mu_B}{\sqrt{\sigma_B^2 + \epsilon}} $ 144 | 4. 利用$ \beta \gamma $进行平移缩放。$ y_i \leftarrow \gamma \hat{x_i} + \beta $ 145 | 146 | **预测时的BN** 147 | 训练的时候使用mini-batch,因此可以计算出均值和方差,但是预测时一次只有一个数据,所以均值方差要改变的都是0,那么BN层什么也不干,按照原样输出,这样是有问题的。解决的方法就是使用训练的所有数据中的均值和方差来做标准化。 148 | 149 | ### 神经网络中的稀疏性? 150 | **稀疏性**:如果当神经元的输出接近于1的时候我们认为它被激活,当输出接近于0的时候认为它被抑制。那么使得神经元大部分的时间都是被抑制的限制被称为稀疏性限制。使用sigmoid的时候输出为0认为是抑制的,使用tanh输出为-1认为是抑制的,使用relu输出为0认为是抑制的。[参考Deep Sparse Rectifier Neural Networks](https://www.researchgate.net/publication/215616967_Deep_Sparse_Rectifier_Neural_Networks) 151 | 152 | **稀疏性的体现**: 153 | 154 | - 使用ReLU激活函数,在前向传播的时候,让神经网络很好的得到了稀疏性,一部分的神经元被抑制,产生了不同的激活路径。其他的激活函数如sigmoid、tanh会同时对正数部分 负数部分都进行抑制,这也会导致梯度的问题。 155 | - 使用dropout,这样就更直接的对神经元进行失效,直接实现了稀疏性。 156 | 157 | ### 有哪些权重初始化方法?各适用于什么情况? 158 | 初始化对网络训练有一定的影响,避免在某一层的forward或者backward中进入饱和区域,拖慢神经网络的训练。初始化的指导思想:**为了使得神经网络不出现梯度消失/爆炸,每一层输出的方差应该尽量相等**。最著名也是最常用的就是Xavier uniform初始化方法,它通过输入和输出的神经元数目自动确定权重矩阵初始化大小,[推导参考](https://blog.csdn.net/Fire_Light_/article/details/79556192)。 159 | 160 | **lecun_uniform** 161 | 从均匀分布$ [-limt, limt] $中进行采样,$ limt = \sqrt{\frac{3}{fan\_in}} $,其中$ fan\_in $是和这个权重矩阵进行相乘的神经网络单元数目,即输入的神经单元数目。 162 | 163 | **glorot_normal/Xavier_normal** 164 | 以0为中心的截断正态分布中抽取样本。$ stddev = \sqrt{\frac{2}{fan\_in + fan\_out}} $。其中$ fan\_in $是输入的神经单元数,$ fan\_out $是输出的神经单元数 165 | 166 | **glorot_uniform/Xavier_uniform** 167 | 从均匀分布$ [-limt, limt] $中进行采样,$ limt = \sqrt{\frac{6}{fan\_in + fan\_out}} $ 168 | 169 | **MSRA init** 170 | 出自何凯明[Delving Deep into Rectifiers:Surpassing Human-Level Performance on ImageNet Classification](http://arxiv.org/pdf/1502.01852.pdf)。Xavier的推导是假设激活函数是线性的,但是显然常用的激活函数都不是线性的。这种方法也针对于ReLU进行初始化。 171 | $$ w ~ G\Big[0, \sqrt{\frac{2}{n}}\Big] $$ 172 | $ G $为高斯分布,也就是正态分布。但是最常用的还是Xavier初始化方法。 173 | 174 | ### 为什么网络够深(Neurons 足够多)的时候,总是可以避开较差Local Optimal? 175 | 小的神经网络固然拥有较少的local optimal,但是它也总是更难以训练,并且小的神经网络的local optima的loss也更高。大的神经网络拥有更多的local optima,但是这些local optima更容易被克服,并且也比小神经网络的local optima表现的要好。参看[The Loss Surfaces of Multilayer Networks](https://arxiv.org/abs/1412.0233) 176 | 177 | ### K-L散度是什么? 178 | Kullback-Leibler Divergence,K-L散度是一种量化两种概率分布P和Q之间差异的方式,又称为相对熵。在概率和统计学上,经常会用一种更简单的近似分布来替代观察数据或太复杂的分布。K-L散度能够帮助我们度量使用一个分布来近似另一个分布时所损失的信息。 179 | 数据的熵Entropy,其中对数的底可以是任何的数字,当使用2的时候熵的值就表示用二进制来表示信息所需要的位数。 180 | $$ H = -\sum_{i=1}^Np(x_i)logp(x_i) $$ 181 | 182 | K-L散度度量信息损失,如果使用2为底,则K-L散度的值表示用q近似p的时候信息损失的二进制位数 183 | $$ D_{KL}(p||q) = \sum_{i=1}^Np(x_i)(logp(x_i) - logq(x_i)) $$ 184 | 也可以写作: 185 | $$ D_{KL}(p||q) = \sum_{i=1}^Np(x_i)log(\frac{p(x_i)}{q(x_i)}) $$ 186 | 187 | 但是散度不是距离,$ D_{kl}(P||Q) \neq D_{kl}(Q||P) $。用Q近似P的散度值和用P近似Q的散度值不相同。 188 | -------------------------------------------------------------------------------- /深度学习知识点/知识点整理2.md: -------------------------------------------------------------------------------- 1 | ### 这些层如何进行反向传播? 2 | 1. conv 3 | 2. pooling 4 | 3. deconv 5 | 6 | 对于卷积层,假如在feature map M0上进行卷积得到feature map M1,卷积核大小为k_s,那么M1[0, 0]进行反向传播,先将梯度复制成k_s*k_s份,对应位置相乘后的梯度,然后每个对应位置求对W的偏导,求对M0[i,j]的偏导。同时因为卷积核在整个M0上都进行了卷积,所以最终对W的参数是各个位置上的梯度相加。 7 | 8 | 对于Pooling层都没有需要学习的参数。因此在反向传播的时候pooling层仅仅是将梯度传递到上一层,而没有梯度的计算。如果是Max Pooling则将梯度传播到上一层对应的区域中最大值的神经元处,其他的神经元的梯度都是0.如果是Mean Pooling那么下一层的梯度会平均分配到上一层对应区域的所有神经元处。 9 | 10 | 反卷积Deconvolution也称作卷积的转置Transposed Convolution实际上就是先对原feature map M0进行加上边,然后进行卷积使得输出的size变成需要的输出M1。对于边上的值是不需要求出导数的,因为这些边上的值反向也没有地方可以传播。对于那些原来的值,求出导数后进行反向传播。 11 | 12 | ### 解释deconv的作用? 13 | 1. 在语义分割中使用,上采样upsampling,将较小的feature map扩大。 14 | 2. CNN可视化,通过deconv将CNN中conv得到的feature map还原到像素空间,以观察特定的feature map对哪些pattern敏感。 15 | 3. 无监督学习,或者是GAN中,进行重建图片。 16 | 17 | ### 什么是bottle-neck结构? 18 | bottle-neck是一种将信息压缩再放大的神经网络结构。这种结构很常见如:自编码器。bottle-neck的典型结构如下: 19 | 20 | - 1x1卷积,用来降低channel数,feature map的size不变 21 | - 3x3卷积,对小的feature map进行尺度不变channel不变的卷积 22 | - 1x1卷积,恢复channel数 23 | 24 | 一般而言,一个(1x1, 3x3, 1x1)的bottle-neck结构比两个连续的3x3卷积效果要好。虽然bottle-neck结构损失了信息,但数据中本来就冗余有大量的信息。bottle-neck获得的中间特征固然是低维的/去噪的,但是同自编码器一样可以用来恢复大部分的信息。 25 | 26 | ### 深度学习中有什么加快收敛/降低训练难度的方法? 27 | - 瓶颈结构 28 | - 残差结构 29 | - 使用高级的优化方法如Adam,或者SGD加上Nestrov动量 30 | - 适当调整学习率 31 | - 进行预训练 32 | 33 | ### 为什么机器学习中都希望数据独立同分布? 34 | - 如果数据样本之间不独立那么意味着有共线性的存在,这会导致过拟合。 35 | - 如果数据样本不处于同一个分布,那么会导致特征的各个维度的尺度不一(量纲不同)会导致训练速度减慢、局部最优等问题。 36 | 37 | 解决的方法有对数据进行PCA白化、深度学习中有BN(1加快学习 2防止过拟合 3预防梯度消失爆炸 4解决)。其实在采样得到mini-batch的时候也会导致batch中的数据分布和整体不太一样。 38 | 39 | ### 空洞卷积 dilation conv? 40 | 空洞卷积是在卷积核之间加入空洞,这样扩大感受域并且也没有增加参数。空洞卷积默认普通的卷积卷积核中每个cell之间相隔的位置是1.应用在图像分割领域中,在进行上采样的时候通过反卷积来增大feature map的size,当然这样会损失信息。但是使用空洞卷积,增大感受域的同时没有太多的信息损失。 41 | 42 | ### 池化层? 43 | 一般来说有max pooling、average pooling、L2norm pooling,最常用效果最好的还是max pooling。池化是为了降低特征的维度,去掉冗余的信息,控制过拟合。 44 | 45 | 最大池化为什么要取最大值呢?因为在这个filter的区域中,值越大表示和filter响应的程度越高,是特征的主要组成部分,而其他部分基本上都是冗余的信息。 46 | 47 | ### 迁移学习? 48 | 利用之前在其他数据集上学习到的知识在新的数据集上进行微调。当新的数据集较大的时候,可能只需要重新训练最后一个全连接层,而前面的层都冻结起来。当新的数据集较大的时候需要重新训练最后的所有全连接层(一般是三个)并且前面的卷积层也都需要用很小的学习率进行学习。 49 | | . | 较相同的数据集 | 较不同的数据集 | 50 | | 很少的数据 | 重新训练最后一个全连接层 | 很麻烦,尝试从不同的地方开始重新训练 | 51 | | 很多的数据 | 微调倒数几层 | 微调很多层 | 52 | 53 | ### Inception结构? 54 | 最简单的Inception结构:假如输入为(28, 28, 256),然后分成四个部分: 55 | 56 | - 1x1 conv 128 channel 57 | - 1x1 conv less channel, 3x3 conv 192 channel 58 | - 1x1 conv less channel, 5x5 conv 96 channel 59 | - 1x1 conv less channel, 3x3 pool 256 channel 60 | 61 | 这些层的输出size都不变,那么再把他们concatenation起来变成(28, 28, 672)。缺点是计算量太大,输出的channel数太多,但是这个可以通过bottle-neck结构来解决。在输入3x3 5x5卷积层之前进行1x1卷积,降低channel数。 62 | 63 | 对于卷积来说,卷积核可以看做一个三维的滤波器:通道维+空间维(Feature Map 的宽和高),常规的卷积操作其实就是实现通道相关性和空间相关性的联合映射。Inception 模块的背后存在这样的一种假设:卷积层通道间的相关性和空间相关性是可以退耦合的,将它们分开映射,能达到更好的效果 64 | 65 | 假设 Input 是 28×28×192 的 Feature Maps,在通道相关性上利用 32 个 1×1×192 的卷积核做线性组合,得到 28×28×32 大小的 Feature Maps,再对这些 Feature Maps 做 256 个 3×3×32 的卷积,即联合映射所有维度的相关性,就得到 28×28×256 的 Feature Maps 结果。可以发现,这个结果其实跟直接卷积 256 个3×3×192 大小的卷积核是一样。 66 | 67 | 也就是说,Inception 的假设认为用 32 个 1×1×192 和 256 个 3×3×32 的卷积核退耦级联的效果,与直接用 256个 3×3×192 卷积核等效。而两种方式的参数量则分别为32×1×1×192 + 256×3×3×32 = 79872 和 256×3×3×192 = 442368。 68 | 69 | 在**深度可分离卷积**方面,Inception的思想和MobileNet很相似,但是MobileNet(用的就是深度可分离卷积)是先在depth-wise上进行卷积再进行point-wise卷积。 70 | 71 | ### 存储神经网络消耗的内存和权重消耗的内存分别集中在哪些部分? 72 | 存储神经网络内存主要是存储每层的输出,集中在前个卷积层的输出中。而权重消耗的内存主要集中在最后的全连接层中。 73 | 74 | ### 训练一个神经网络,一开始loss没有下降,过了几个epoch开始快速下降,可能是什么原因? 75 | - 陷入局部最小值 76 | - 学习率过小,这个时候一开始实际上是从局部最小值中走出来的这个过程 77 | - 正则参数过高 78 | 79 | ### 什么造成了过拟合?用什么方法来预防过拟合? 80 | 过拟合就是在训练集上的效果比在验证集上的效果要好。 81 | 82 | 造成过拟合的原因: 83 | 84 | - 样本数量太多 85 | - 参数过多,模型太大 86 | - 迭代次数过多,模型学习了训练集上的噪音 87 | 88 | 消除过拟合的方法: 89 | 90 | - 提前终止训练 91 | - 使用较小的模型 92 | - 使用正则化(weight decay),每次更新的时候$ w_i \leftarrow w_i - \eta \frac{\partial{E}{\partial{w_i}} - \lambda W $其中$ \lambda $就是weight decay的程度。 93 | 94 | ### ResNet优缺点及适用范围? 95 | 对ResNet的解释: 96 | 97 | - 网络在某些层学习到了恒等变换。在某些层执行恒等变换是一种构造性解,使得更深的模型的性能至少不少于较浅层的模型。这也是原始论文中的解释。 98 | - 残差网络是很多浅层网络的集成,集成的模型的数量是层数的指数级那么多。将其中的某些层直接删掉对最终的结果影响不大。[Residual Networks Behave Like Ensembles of Relatively Shallow Networks](https://arxiv.org/abs/1605.06431) 99 | - 残差网络使得信息更容易在各层之间流动,包括在前向传播时提供特征重用,在反向传播时缓解梯度消失。[Identity Mappings in Deep Residual Networks](https://arxiv.org/pdf/1603.05027.pdf) 100 | 101 | 在《Identity Mappings in Deep Residual Networks》中何凯明又对shortcut进行和各种实验,实验结果是shortcut什么都不做进行恒等变换的效果最好,然后在主干道中进行```BN -> ReLU -> conv```的效果最好。 102 | 103 | ### InceptionNet优缺点及适用范围? 104 | 引入Inception module,但是并没有VGG ResNet那样出名。因为超参数太多,每个module具体要怎么进行设计都要考虑,但其实效果也很不错。 105 | 106 | ### DenseNet优缺点及适用范围? 107 | DenseNet很大的创新点就是各层的特征进行大融合,但是这些特征也是有冗余的。densenet也相当于浪费了很多的计算资源。而且DenseNet利用中间层就可以进行预测这一点更证实了这一点。 108 | 109 | ### 调参有什么技巧? 110 | 对于网络: 111 | 112 | - 先让网络在小数据集上进行过拟合,让准确率达到非常高,看看整体有没有问题。模型是否有足够的表示能力等。 113 | - 网络结构的话,激活函数一般就用ReLU,还有使用Batch Norm、dropout等,参照在各种比赛中取得名次的网络结构 114 | - 训练之前先观察数据,各个类别的分布,最好对数据有个直观的认识,然后考虑数据增强等 115 | 116 | 对于优化: 117 | 118 | - 先使用Adam,如果是从头训练的话,学习率一般设为1e-3,如果用的是预训练的模型则根据不同的情况来微调 119 | - Adam效果不好就换SGD+Momentum momentum一般我们会选择0.9-0.95之间,weight decay我们一般会选择0.005 120 | - 逐步下降学习率,如果当前的学习率已经不能让loss下降,那么就*1/10 121 | 122 | ### 为什么机器学习、深度学习都需要用概率论来进行解释? 123 | 因为机器学习通常必须处理不确定的量,如果是直接确定的话直接构造一个确定的分布就可以进行预测。不确定量主要来源于: 124 | 125 | - 被建模系统内在的随机性。如玩纸牌游戏,每次抽到的纸牌不可能是确定的。 126 | - 不完全观测。对数据的不完全观测。 127 | - 不完全建模。部分的数据无法进行预测。 128 | 129 | ### TensorFlow 130 | 使用数据流图来规划计算流程,它可以将计算映射到不同的硬件和操作系统平台。TensorFlow可以应用在大规模的深度学习中,也可以应用在小规模的应用中。TensorFlow抽象出来的数据流图也可以用在通用数值计算和符号计算上,比如分形图计算或者偏微分方程数值求解。 131 | 132 | ### RCNN系列的发展过程? 133 | Region based CNN,一开始是根据selective search用一个滑窗滑遍整个图片,对与每个滑窗滑到的位置输入CNN来判断是哪个物体以及物体的x、y、h、w,这样的方式会对部分区域进行大量的重复计算,开销太大。 134 | 135 | Fast RCNN,将经过几次卷积的高级特征看作是对图像的压缩,在高级特征上面做ROI Pooling:先selective-search,proposal出一个高级特征的区域,这个区域输入接下来的神经网络来进行判断。输入神经网络后分叉得到物体的类别和位置,这是个multi-task。 136 | 137 | Faster RCNN,Fast RCNN仍然是在feature map每个位置上proposal,然后都输入接下来的网络,这样是很低效的。于是有了RPN Region Proposal Network,把feature map输入RPN,让它进行proposal。RPN就是在feature map上进行滑动,输出这个位置是物体/背景,以及x、y、h、w。如果是物体才把feature map上的区域输入接下来的神经网络,如果是背景就不输入接下来的神经网络。物体的位置也会进一步修正。 138 | 139 | 典型的SSD中MultiBox的部分的计算量是比backbone的计算量要大的。在具体的应用场景中,要看设计了几个multibox,multibox少的时候计算量少。 140 | 141 | ### RNN LSTM GRU区别 142 | [LSTM原理及实现](https://blog.csdn.net/gzj_1101/article/details/79376798) 143 | 144 | [RNN LSTM与GRU深度学习模型学习笔记](https://blog.csdn.net/softee/article/details/54292102) 145 | 146 | RNN被称为是并发的,是因为传统的神经网络中,输入是相互独立的,但是在RNN中不是这样的,一个语句分多次,每次一个单词输入RNN,每次都是使用同样的方式进行处理,每次处理也都依赖于之前的计算。 147 | 148 | LSTM是为了解决RNN中的反馈小时问题而被提出的模型,它也可以被视为一个RNN的变种,增加了三个们输入门、遗忘门、输出门。门机制的存在,使得LSTM能够显示地为序列中长距离的依赖关系进行建模。 149 | 150 | GRU与LSTM类似,但是更为简化。GRU中只有两个门,一般认为LSTM和GRU之间没有明显的优胜者。但是GRU具有较少的参数,所以速度快,而且所需要的样本较少,LSTM具有较多的参数,比较适合大量样本的情况。 151 | 152 | ### SSD与Faster RCNN 153 | ssd是class aware,rpn是class agnostic。ssd分层出,其实就这些。class agnostic指的就是先判断这个anchor是不是物体,是物体就输入接下来的神经网络。而SSD则直接全部进行输入接下来的神经网络。然后进行非极大值抑制,训练的时候还要进行hard mining。 154 | 155 | SSD与Faster RCNN难在Loss部分,坐标回归可以使用SmoothL1Loss、分类可以使用交叉熵。但是预测出来的很多个位置和置信度怎么和标签对应上呢? 156 | 157 | 一般来说一个图片有1个标签或多个表现为```[num, x, y, w, h] [num, class]```,利用这些标签产生prior_box,prior_box的个数就是训练的时候输出的anchor的个数。假如说原图为300x300,有2个planes为50x50、15x15,每个位置有3个ratio的box,这样2个plane就会产生50x50x3+15x15x3=8175个anchor。在图片输入神经网络得到结果之前,这些anchor叫做prior box:```[8175, x, y, w, h]```。在输入神经网络之前,标签要和prior box进行match,计算每个标签和8175个prior box之间的重叠程度,只有达到一定的重叠程度才认为这个prior box有物体,然后就把这个prior box的```x, y, w, h```置为标签的值(实际计算位置loss的时候会忽略背景box),```class```置为重叠程度最大的标签的类别,如果重叠程度太低就是背景的类别。所以在我们的例子里用于计算loss的label实际上是prior box中的非背景的几个box。 158 | 159 | SSD中在计算loss的时候,先筛选出输出结果中的非背景box```[x, y, w, h]```,然后和prior box中的非背景box计算SmoothL1Loss。在计算类别损失的时候,所有的prior box全部和输出的anchor计算交叉熵,因为其中包含了太多的背景类所以不适合进行反向传播,所以对cross_entropy_loss进行hard mining选出一部分loss最大的背景类的box(表示这几个box容易被神经网络看错,难以进行区分),和非背景类的一起反向传播。 160 | 161 | [Faster-RCNN代码+理论——1](https://blog.csdn.net/g11d111/article/details/78823663) 162 | 163 | [ROI Pooling原理及实现](https://blog.csdn.net/u011436429/article/details/80279536) 164 | 165 | ### harding mining 166 | hard negative mining就是每次把那些顽固的棘手的错误,再送回去继续训练,从而加强分类器判别假阳性的能力。 167 | 168 | ### NMS非极大值抑制 169 | 使用目标检测算法检测出来会有很多框,并且很多框都是重叠的,这个时候需要将框合并。由于是在inference的时候进行NMS,所以对速度的要求是很高的,一般使用Cython或者CUDA编写NMS部分代码。输出的大量Bounding Box包含了位置和置信度,利用这些信息将重叠程度高的box消除掉。 170 | 171 | NMS算法流程: 172 | 173 | 1. 将box按照置信度由大到小进行排序,得到列表 174 | 2. 循环: 175 | - 从列表中取出置信度最大的box放进最终要保留的box集合中 176 | - 计算列表中剩余所有box和这个放进保留集合中的box的IOU 177 | - 去掉IOU超过一定阈值的box 178 | 3. IOU = 交集/并集 179 | 180 | ### 目标检测中的感受野 181 | 当目标检测中backbone中的某层feature map的一个cell的感受野匹配所要检测的物体的大小的时候就可以进行multibox输出来检测到这个物体,否则神经网络是看不到这个物体的。 182 | 183 | 因为越靠前的卷积,其感受野越小,越有利于小物体的识别。但是感受野小就检测不出来大物体。 184 | 185 | 在SSD中backbone一般使用的是VGG16,然后就是连续的卷积,其中不同大小的feature map作为候选的feature map set。然后MultiBox Layer作用于这些feature map上,输出每个位置不同比例的框的```x, y, w, h```以及类别。 186 | 187 | SSD认为目标检测中的物体,只与周围信息相关,它的感受野不是全局的,故没必要也不应该做全连接。全连接的话,每个神经元的感受野都是全局的。 188 | 189 | [关于感受野的总结](https://cloud.tencent.com/developer/article/1179175) 190 | 191 | [卷积神经网络物体检测之感受野大小计算](https://blog.csdn.net/u014696921/article/details/53791327) 192 | 193 | [带你深入AI(4)- 目标检测领域:R-CNN,faster R-CNN,yolo,SSD, yoloV2](https://blog.csdn.net/u013510838/article/details/79947553) 194 | 195 | ### 有效感受野和理论感受野 196 | 影响某个神经元输出的区域就是理论感受野,也就是平常说的感受野。但是输入区域的每个点对输出的影响程度不同,越靠近中心的像素点影响越大,呈高斯分布,也就是只有输入的中间的一小部分对最后的输出有重要影响,这个中间部分就是有效感受野。 197 | 198 | SSD中通过设置default box来人为确定一个神经网络想要看到的部分,所以default box的大小要和有效感受野大小匹配。由于default box只要匹配实际的有效感受野就可以了,而实际的有效感受野要比理论感受野小很多。所以SSD中每一层的default box的大小可以比理论感受野小很多。所以SSD继续优化的一个方向就是让default box匹配实际感受野。 199 | 200 | SSD的一个缺点就是对小物体的检测效果不好,因为感受野的原因。使用VGG中靠前的部分来进行预测可以解决这个问题。 201 | 202 | ### 为什么设置多种宽高比的default box 203 | default box实际上是SSD的标签,如果只设置了宽高比为1的default box,那么对于这个位置只有一个default box能够匹配到,如果设置更多的宽高比的default box,将会有更多的default box匹配到。 204 | 205 | [SSD原理解读-从入门到精通](https://blog.csdn.net/qianqing13579/article/details/82106664)这篇文章写的很好,一定要读一下。 206 | 207 | ### 感受野和设计anchor之间的关系 208 | 现在流行的目标检测网络大部分都是基于anchor的,比如SSD、faster rcnn。基于anchor的目标检测网络会预设一组不同大小的anchor,比如32x32,64x64,128x128,256x256,这么多的anchor应该放在卷积中的哪几层合适呢? 209 | 210 | 这个时候就要考虑感受野的问题了。放置anchor层的实际感受野应该和anchor大小匹配,实际感受野比anchor大太多不好,比anchor太小也不好。 211 | 212 | [设计深度卷积神经网络的实用技巧理论](https://blog.csdn.net/JosephPai/article/details/81235310) 213 | 214 | ### 目标检测中的数据增强 215 | [CV_ToolBox/DataAugForObjectDetection/DataAugmentForObejctDetection.py](https://github.com/maozezhong/CV_ToolBox/blob/master/DataAugForObjectDetection/DataAugmentForObejctDetection.py) 216 | 217 | ### Mobilenet 218 | MobileNet 是Google推出的一款高效的移动端轻量化网络,其核心就是深度可分离卷积。它的思想和Inception相同,卷积层通道间的相关性和空间相关性是可以退耦合的,将它们分开映射,能达到更好的效果。 219 | 220 | 假设输入的图片为(224, 224, 3),传统的3x3卷积的卷积核为(3, 3, 3, 32) (H, W, in_channel, output_channel)。而深度分离卷积则是: 221 | 222 | 1. (3, 3, 1, 3) -> output: (112, 112, 3) 深度卷积(depth-wise),每个channel有一个对应的卷积核(这个卷积核是2D的)。 223 | 2. (1, 1, 3, 32) -> output: (112, 112, 32) 点卷积(point-wise),所有channel用的是同一个卷积核(这个卷积核是3D的)。 224 | 225 | [mobilenet网络的理解](https://blog.csdn.net/wfei101/article/details/78310226) 226 | 227 | [深度可分离卷积(Xception 与 MobileNet 的点滴)](https://www.jianshu.com/p/38dc74d12fcf) 228 | 229 | [对深度可分离卷积、分组卷积、扩张卷积、转置卷积(反卷积)的理解](https://blog.csdn.net/chaolei3/article/details/79374563) 230 | 231 | [变形卷积核、可分离卷积?卷积神经网络中十大拍案叫绝的操作。](https://zhuanlan.zhihu.com/p/28749411) 232 | 233 | ### Mobilenet v1 v2 区别 234 | mobilenetv1 和v2的主要区别就是: 235 | 236 | V1: --> 3x3 DW --ReLU6--> 1x1 PW --ReLU6--> 237 | 238 | V2: --> 1x1 PW --ReLU6--> 3x3 DW --ReLU6--> 1x1 PW --Linear--> 239 | 240 | V2中: 241 | 242 | 1. 1x1卷积进行扩张,目的是为了提升通道数,获得更多的特征。 243 | 2. 最后不采用ReLU,而是Linear,目的是防止ReLU破坏特征。 244 | 245 | ### 深度可分离卷积节省的计算量 246 | 一个传统的卷积操作,假设卷积核大小是K,输入的feature map的channel是M,输出的feature map是(F, F, N)长和宽都是F,channel是N,那么这个卷积所需要的计算量是K\*K\*M\*F\*F\*N。 247 | 248 | 现在将卷积分解为depth wise和point wise的卷积。假设输入和输出都和上面是一样的,其中depth wise的卷积的计算量为K\*K\*1\*F\*F\*M,point wise的计算量是M\*F\*F\*N。 249 | 250 | ### 视频帧相关 251 | 国内的PAL制式是25帧,国外的NTSC制式是30帧。电影一般是24帧或者30帧。这里的帧指的都是关键帧。在进行检测的时候抽取的也都是关键帧。 -------------------------------------------------------------------------------- /深度学习知识点/知识点整理3.md: -------------------------------------------------------------------------------- 1 | ### 理解ROC 2 | ROC曲线被称为受试者工作曲线,反映了分类器模型或者其他模型的性能。 3 | 4 | ``` 5 | 预测 6 | P | N 7 | 真 P TP | FN 8 | 9 | 实 N FP | TN 10 | ``` 11 | ROC曲线的思想是,在不同的阈值下,预测出的正类的个数、负类的个数是不同的。当阈值为0的时候,分类器就直接把所有的数据都看作是正类。这个时候混淆矩阵就变为: 12 | ``` 13 | 预测 14 | P | N 15 | 真 P 90 | 0 16 | 17 | 实 N 10 | 0 18 | ``` 19 | 这个时候没有预测出来的负类。 20 | 21 | 那么在提高阈值的过程中,一些负类肯定是被识别出来,表现为在提升阈值的过程中FP减少的,TN增加。当阈值到达最大的时候,所有的数据都被认为是负类。在这个过程中一些性能不好的分类器,会将正类预测为负类,也就是TP减少,FP增加。我们当然是不想这样的情况出现。理想的情况下随着阈值的增加只有FP减少,TP不变,也就是随着阈值增加$ \frac{FP}{FP+TN} $减少,而$ \frac{TP}{TP+FN} $不变并且尽量接近1。 22 | 23 | 为了从这个角度评价分类器的性能,让$ \frac{FP}{FP+TN} $作为横轴,让$ \frac{TP}{TP+FN} $(召回率/灵敏度)作为纵轴,改变阈值得到点再将点连接起来构成ROC曲线。所以ROC曲线要尽量接近左上角。 24 | 25 | ### 召回率、准确率 26 | 召回率:真正的正例中有多少被预测出来了。 27 | 28 | 准确率:预测出来为正的数据中,有多少是真正的正例。 29 | 30 | 召回率和准确率最开始是在信息检索中的评价标准: 31 | ``` 32 | 预测 33 | P N 34 | 真 P TP FN → TP/(TP+FN) 召回率/灵敏度 35 | 实 N FP TN 36 | ↓ 37 | TP/(TP+FP) 准确率 38 | ``` 39 | 调整阈值会得到不同的召回率和准确率,将召回率作为横轴,准确率作为纵轴得到PR曲线。一般来说召回率越高准确率越低,准确率越高召回率越低。 40 | 41 | 阈值最小的时候,所有的数据都被预测为正例,此时召回率最大。随着阈值增大,一些反例被正确分类FP减少,准确率增大;但是一些正例可能被误分类为反例TP减少FN增加,召回率减小。 42 | 43 | ### 灵敏度 44 | $ sensitive = \frac{TP}{TP + FP} $,灵敏度就是召回率。 45 | 46 | ### FROC 47 | Free-response ROC曲线。经典的ROC曲线不能解决对一副图像上出现多个物体然后检测进行评价的实际问行评价。FROC以每个样本中的假阳性个数为x轴,y轴和ROC一样是召回率/灵敏度。所以提出了FROC,它允许对每幅图像上存在的任意多物体的情况进。 48 | 49 | ### 人脸检测heatmap 50 | 在进行物体分类(人脸识别)的时候,将最后的全连接层改为卷积层,获得的输出就是heatmap,heatmap中每个cell可以看作是对应原图中位置上属于物体(人脸)的概率,按照一定阈值进行筛选之后将每个点恢复到原始图像的尺寸可以得到一个矩形的区域就对应的是人脸的区域。 51 | 52 | ### tensorflow如何部署到移动端 53 | 1. 首先将保存的ckpt模型文件转为pb(只保留前向传播时需要的相关数据)模型。如果是pytorch的话也可以通过这样的方式来部署。[Tensorflow 模型持久化](https://blog.csdn.net/michael_yt/article/details/74737489) 54 | 2. 安卓端可以使用tensorflow mobile或者tensorflow lite。一般使用的是tensorflow mobile。在Android Studio的build.gradle 文件中添加 TensorFlow Mobile 依赖:implementation ‘org.tensorflow:tensorflow-android:+’ 然后使用gradle进行自动构建。 55 | 3. 将转换后的模型(net.pb)添加到应用程序的资源文件夹中assest/ 56 | 4. 调用tensorflow inference API来进行inference。 -------------------------------------------------------------------------------- /计算机学科基础知识/计网操作系统等.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TotallyFine/INTERVIEW/d991a45ba784103bf3b39d60c977e7ab65ea5304/计算机学科基础知识/计网操作系统等.md -------------------------------------------------------------------------------- /语言基础/C++/面试常见问题.md: -------------------------------------------------------------------------------- 1 | ### const有什么用途? 2 | 主要有三点: 3 | 4 | 1. 定义只读变量,即常量 5 | 2. 修饰函数的参数和函数的返回值,如果参数是```const int a```,表示a不能在函数内部更改,```const int *a```表示的是这个指针指向的数据不能被更改 但是指针可以被更改(指向其他的数据)。```int* const a```表示指针不能被更改,即不能指向其他的数据,但是指向的数据是可以被更改的。*的两边都有const的话就表示数据、指针都不能被更改。 6 | 3. 修饰函数的定义,被const修饰的成员函数代表不修改成员变量的值。 7 | 8 | ### static的作用? 9 | 1. 函数体内的局部变量如果声明为static,那么它的内存只被分配一次,下一次进入这个函数的时候,它的值仍然是上一次的值(下一次进入函数的时候的声明赋值语句是失效的)。 10 | 2. 在模块内(文件内)的static全局变量/函数可以被模块内的所有函数访问,但是不能被模块外的函数访问。(将函数隐藏的功能) 11 | 3. 一个类的static成员是这个类的所有对象共有的属性,值都是一样的,如果改变,那么所有对象的这个成员都改变。 12 | 4. 类中的static函数,可以直接调用,不用this。 13 | 14 | #### 指针和引用的区别? 15 | 1. 引用是变量的一个别名,内部实现是只读指针。 16 | 2. 引用不能为NULL,指针可以为NULL。 17 | 3. sizeof(引用)指向的变量的大小,sizeof(指针)指针的大小 18 | 4. 引用可以进行取地址操作,返回的是被引用变量本身的地址。 19 | 20 | ### C中的malloc/free与C++中的new/delete 21 | 1. malloc/free是库函数,new/delete是C++中的运算符。都可以用于动态申请内存和释放内存。 22 | 2. 对于非内部类型的对象而言,malloc/free无法满足需求(构造函数和析构函数需要存在)。在调用new/delete的时候会自动调用构造函数和析构函数。 23 | 24 | ### 空类里有哪些函数? 25 | 如果只声明一个空类,编译器会默认生成: 26 | 27 | 1. 默认构造函数 28 | 2. 默认拷贝构造函数 29 | 3. 默认析构函数 30 | 4. 默认赋值运算符 31 | 5. 取址运算符 32 | 33 | ### 空指针怎么表示以及优缺点(NULL, nullptr)? 34 | 在C++中,NULL作为参数传递的时候编译器会用0来代替,而不是一个真正的指针。而nullptr代表的就是一个空指针,在传递的时候不能被转换成数字。 35 | 36 | 参考[C++学习笔记之NULL vs nullptr](https://blog.csdn.net/pkgk2013/article/details/72809796) 37 | 38 | ### vector和list的异同点? 39 | vector底层是连续存储元素,所以随机存取很快。而list是用双向链表实现的,内存空间不连续,随机存取慢。 40 | 41 | vector重载了```[], <, >, +, +=```运算符,而list没有。 42 | 43 | 如果需要高效的随机存取,但是几乎不插入和删除,那么使用vector。如果大量插入和删除使用,很少随机存取,那么使用list。 44 | 45 | ### 构造函数 46 | 构造函数是在实例化一个对象的时候执行的,构造函数和析构函数都没有返回值(可以认为构造函数的返回值是这个对象的引用)。 47 | 48 | 构造函数不能为虚函数,析构函数可以通过父类指针调用子类而有意义,但是构造函数没必要也不应该是虚函数。 49 | ```cpp 50 | class base{ 51 | public: 52 | base(){std::cout<<"base construct"<< std::endl;} 53 | }; 54 | 55 | class son: public base{ 56 | public: 57 | son(){std::cout<<"son construct"<< std::endl;} 58 | }; 59 | 60 | int main(){ 61 | son s = son(); // 认为son()返回的是这个对象的引用 62 | son* ps = new son; // new对标C语言中的malloc,所以new返回的是指针。 63 | } 64 | ``` 65 | 66 | ### 什么时候要调用析构函数? 67 | 当销毁一个对象的时候调用析构函数,比如退出一个局部代码段,或者是使用delete主动销毁一个对象。在delete一个指针的时候,首先调用它的析构函数,所以如果类成员中有在堆上分配内存空间的,那么就要在析构函数中将内存释放掉。 68 | 69 | delete作用在指针上,当指针指向父类的时候会调用父类的析构函数,而不会调用子类的析构函数,这样可能会造成内存泄漏。delete子类指针的时候,会将父类和子类的内存空间同时释放这样就不会造成内存泄漏。 70 | 71 | 或者是将父类的析构函数声明为virtual,这样用父类指针指向子类实例,再delete父类指针,这个时候会调用子类的析构函数,会将父类和子类占用的内存都释放掉的。 72 | 73 | ### 虚函数表的作用? 74 | C++中的多态是通过虚函数实现的,也就是父类中声明一个virtual的函数,然后子类实现,再用父类的指针指向子类实例就可以多态了。但是这里用父类的指针调用函数的时候,会在父类的代码段里查找这个函数,这个时候虚函数指针_vptr就有作用了。 75 | 76 | 当类中有虚函数的时候,会多一个虚函数指针(64位机器下是8字节),指向一个虚函数表,虚函数表代表的是虚函数,里面存的是指向函数的指针。当父类指针调用子类实现的函数的时候,会在虚函数表中找到对应的函数指针,然后调用子类的函数。 77 | 78 | 当继承多个虚类的时候,这个子类中会有多个虚函数指针,指向多个虚函数表。 79 | 80 | ### 泛型 81 | 泛型值的是独立于具体的类型进行编程,泛型也可以认为是一种多态。利用泛型写函数模板和类模板,相关的类型可以推迟到调用的时候再声明。这样可以代码重用。 82 | 83 | [C++模板总结](https://blog.csdn.net/yzhang6_10/article/details/50839516) 84 | 85 | ### 函数模板 86 | 使用函数模板将传入的参数类型推迟到函数调用的时候再声明。 87 | 88 | 类型形参可以受用typename或者class来声明,不过一般使用的都是typename。 89 | ```cpp 90 | class A{ 91 | public: 92 | int value; 93 | A(int a){value= a;} 94 | }; 95 | 96 | class B{ 97 | public: 98 | int value; 99 | B(int a){value = a;} 100 | }; 101 | 102 | template 103 | bool isEqual(const T1& t1, const T2& t2){ 104 | return t1.value == t2.value; 105 | } 106 | 107 | int main(){ 108 | A a = A(1); 109 | B b = B(1); 110 | std::cout<< isEqual(a, b)<< std::endl; // 传入了模板参数,是显示调用 111 | std::cout<< isEqual(a, b)<< std::endl; // 没有传入模板函数,编译器进行参数推演,隐式调用 112 | } 113 | ``` 114 | 115 | ### 类模板 116 | 类模板也是一个模板,与函数模板不同的是,类模板在实例化的时候必须显示制定模板参数,不能进行隐式调用。 117 | ```cpp 118 | const size_t MAXSIZE = 100; 119 | template 120 | class Stack{ 121 | private: 122 | T elements[MAXSIZE]; 123 | }; 124 | 125 | int main(){ 126 | Stack s; 127 | Stack<1> s1; // error 128 | } 129 | ``` 130 | 131 | ### 堆和栈的区别是什么? 132 | 栈是由操作系统自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 133 | 134 | 堆一般是由程序员分配释放的,如果程序员不释放,程序结束时可能由OS回收,分配方式类似与链表。 135 | 136 | 静态变量区。 -------------------------------------------------------------------------------- /语言基础/Python/面试常见问题.md: -------------------------------------------------------------------------------- 1 | ### 数组为什么不能作为字典的key? 2 | 字典的工作方式就是哈希,python字典中的每个key都必须实现__hash__()函数,返回这个对象的hash值。 3 | 4 | 对一个对象进行哈希有两种思路: 5 | 6 | - 基于id,但是这样值相同的key也不能得到同样的value,不可取。 7 | - 基于内容,如tuple。 8 | 9 | 那么如果list实现了__hash__()就必须是基于内容的,但是list是可变的,所以当list变化之后就再也无法取出原来的value了,所以对list不支持__hash__()函数。 10 | 11 | ### lambda函数? 12 | lambda函数用来实现短小的不想费力去命名写的函数。格式为```fun = lambda x, y: x+y``` 13 | ```python 14 | # lambda [arguments]:expression 15 | 16 | a = lambda x,y:x+y 17 | 18 | print(a(3,11)) # 14 19 | ``` 20 | 21 | ### python中的赋值 22 | **浅拷贝** 使用```=```就是浅拷贝,只增加了对象的引用计数。 23 | 24 | **深拷贝**使用```object.copy()```进行深复制。 25 | 26 | ```python 27 | x = [1, 2, 3] 28 | print(x == x.copy()) # True 29 | print(x is x.copy()) # False 30 | ``` 31 | 32 | ### 异常处理 33 | 使用```try ... except ... [else...] [finally...]```来进行处理异常。 34 | ```python 35 | try: 36 | f = open('file') 37 | f.write('hello, world.') 38 | except IOError: 39 | print('IOError cannot write.') 40 | else: 41 | print('success.') 42 | f.close() 43 | ``` 44 | 当可能会发生多个错误的时候也可以 45 | ```python 46 | try: 47 | pass 48 | except Error1: 49 | pass 50 | except Error2: 51 | pass 52 | finally: 53 | pass 54 | ``` 55 | 也可以对多个错误进行相同的处理 56 | ```python 57 | try: 58 | pass 59 | except: Error1, Error2, ...: 60 | pass 61 | finally: 62 | pass 63 | ``` 64 | ```finally```是无论是否发生错误都执行的代码。也可以使用```raise```来主动抛出一个异常。 65 | ```python 66 | def calc(x): 67 | if x < 0: 68 | raise Exception('Invalid argument: ', x) # 后面的代码就不会再执行了 69 | ``` 70 | 71 | ### 猴子补丁 72 | “猴子补丁”就是指,在函数或对象已经定义之后,再去改变它们的行为。一般来说要尽力避免这种行为。例如: 73 | ```python 74 | import datetime 75 | datetime.datetime.now = lambda: datetime.datetime(2012, 12, 12) 76 | ``` 77 | 这里更改了库函数,这是非常不好的。在测试的时候可能会用到猴子补丁。 78 | 79 | ### 鸭子类型 80 | 81 | ### *args, **kwargs 82 | 如果不确定往函数中传入多少个参数,或者我们希望以列表和元祖的形式传递参数的时候使用*args。 83 | 84 | 如果不确定向函数中传递多少个关键词参数,或者想传入字典的值作为关键词参数时,那就使用**kwargs。 85 | ```python 86 | def f(*args,**kwargs): 87 | print args, kwargs 88 | 89 | l = [1,2,3] 90 | t = (4,5,6) 91 | d = {'a':7,'b':8,'c':9} 92 | 93 | f() 94 | f(1,2,3) # (1, 2, 3) {} 95 | f(1,2,3,"groovy") # (1, 2, 3, 'groovy') {} 96 | f(a=1,b=2,c=3) # () {'a': 1, 'c': 3, 'b': 2} 97 | f(a=1,b=2,c=3,zzz="hi") # () {'a': 1, 'c': 3, 'b': 2, 'zzz': 'hi'} 98 | f(1,2,3,a=1,b=2,c=3) # (1, 2, 3) {'a': 1, 'c': 3, 'b': 2} 99 | 100 | f(*l,**d) # (1, 2, 3) {'a': 7, 'c': 9, 'b': 8} 101 | f(*t,**d) # (4, 5, 6) {'a': 7, 'c': 9, 'b': 8} 102 | f(1,2,*t) # (1, 2, 4, 5, 6) {} 103 | f(q="winning",**d) # () {'a': 7, 'q': 'winning', 'c': 9, 'b': 8} 104 | f(1,2,*t,q="winning",**d) # (1, 2, 4, 5, 6) {'a': 7, 'q': 'winning', 'c': 9, 'b': 8} 105 | 106 | def f2(arg1,arg2,*args,**kwargs): 107 | print arg1,arg2, args, kwargs 108 | 109 | f2(1,2,3) # 1 2 (3,) {} 110 | f2(1,2,3,"groovy") # 1 2 (3, 'groovy') {} 111 | f2(arg1=1,arg2=2,c=3) # 1 2 () {'c': 3} 112 | f2(arg1=1,arg2=2,c=3,zzz="hi") # 1 2 () {'c': 3, 'zzz': 'hi'} 113 | f2(1,2,3,a=1,b=2,c=3) # 1 2 (3,) {'a': 1, 'c': 3, 'b': 2} 114 | 115 | f2(*l,**d) # 1 2 (3,) {'a': 7, 'c': 9, 'b': 8} 116 | f2(*t,**d) # 4 5 (6,) {'a': 7, 'c': 9, 'b': 8} 117 | f2(1,2,*t) # 1 2 (4, 5, 6) {} 118 | f2(1,1,q="winning",**d) # 1 1 () {'a': 7, 'q': 'winning', 'c': 9, 'b': 8} 119 | f2(1,2,*t,q="winning",**d) # 1 2 (4, 5, 6) {'a': 7, 'q': 'winning', 'c': 9, 'b': 8} 120 | ``` 121 | ### @classmethod, @staticmethod, @property 122 | @标记是一种语法糖,让人理解起来更容易。这些在python中都是装饰器。装饰器是一种特殊的函数,要么接受函数作为输入参数,并返回一个函数,要么接受一个类作为输入参数,并返回一个类。 123 | ```python 124 | @my_decorator 125 | def my_func(stuff): 126 | pass 127 | 128 | # 上面的形式和下面的形式相同 129 | 130 | def my_func(stuff): 131 | pass 132 | my_func = my_decorator(my_func) 133 | ``` 134 | 135 | ### 参考 136 | [Python面试必须要看的15个问题](https://www.cnblogs.com/Vito2008/p/5044251.html) -------------------------------------------------------------------------------- /语言基础/shell/学习资料.txt: -------------------------------------------------------------------------------- 1 | http://www.runoob.com/linux/linux-shell.html 菜鸟教程shell 2 | http://blog.51cto.com/zero01/2046242 101个shell程序 3 | http://blog.51cto.com/zero01/2045239 使用shell编写一个简单的警告系统 --------------------------------------------------------------------------------