diff --git a/.gitignore b/.gitignore index 42a1f04f..2bd5ff2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,20 @@ # IDE .idea/ +.vscode/ # folder __*/ +.ipynb_checkpoints/ +out/ + +# file +-* +*.zip +*.tgz +Untitled.ipynb +MyCV.pdf +MyCV.md +GSD_*.pdf # root /demo.py diff --git "a/A-\346\234\272\345\231\250\345\255\246\344\271\240/A-\346\234\272\345\231\250\345\255\246\344\271\240\345\237\272\347\241\200.md" "b/A-\346\234\272\345\231\250\345\255\246\344\271\240/A-\346\234\272\345\231\250\345\255\246\344\271\240\345\237\272\347\241\200.md" new file mode 100644 index 00000000..3f254e4b --- /dev/null +++ "b/A-\346\234\272\345\231\250\345\255\246\344\271\240/A-\346\234\272\345\231\250\345\255\246\344\271\240\345\237\272\347\241\200.md" @@ -0,0 +1,151 @@ +ML-机器学习基础 +=== + +Index +--- + + +- [偏差与方差](#偏差与方差) + - [导致偏差和方差的原因](#导致偏差和方差的原因) + - [深度学习中的偏差与方差](#深度学习中的偏差与方差) + - [偏差/方差 与 Boosting/Bagging](#偏差方差-与-boostingbagging) + - [偏差与方差的计算公式](#偏差与方差的计算公式) + - [偏差与方差的权衡(过拟合与模型复杂度的权衡)](#偏差与方差的权衡过拟合与模型复杂度的权衡) +- [生成模型与判别模型](#生成模型与判别模型) +- [先验概率与后验概率](#先验概率与后验概率) + + + + +## 偏差与方差 +> 《机器学习》 2.5 偏差与方差 - 周志华 +- **偏差**与**方差**分别是用于衡量一个模型**泛化误差**的两个方面; + - 模型的**偏差**,指的是模型预测的**期望值**与**真实值**之间的差; + - 模型的**方差**,指的是模型预测的**期望值**与**预测值**之间的差平方和; +- 在**监督学习**中,模型的**泛化误差**可**分解**为偏差、方差与噪声之和。 +
+ +- **偏差**用于描述模型的**拟合能力**;
+ **方差**用于描述模型的**稳定性**。 +
+ +### 导致偏差和方差的原因 +- **偏差**通常是由于我们对学习算法做了**错误的假设**,或者模型的复杂度不够; + - 比如真实模型是一个二次函数,而我们假设模型为一次函数,这就会导致偏差的增大(欠拟合); + - **由偏差引起的误差**通常在**训练误差**上就能体现,或者说训练误差主要是由偏差造成的 +- **方差**通常是由于**模型的复杂度相对于训练集过高**导致的; + - 比如真实模型是一个简单的二次函数,而我们假设模型是一个高次函数,这就会导致方差的增大(过拟合); + - **由方差引起的误差**通常体现在测试误差相对训练误差的**增量**上。 + +### 深度学习中的偏差与方差 +- 神经网络的拟合能力非常强,因此它的**训练误差**(偏差)通常较小; +- 但是过强的拟合能力会导致较大的方差,使模型的测试误差(**泛化误差**)增大; +- 因此深度学习的核心工作之一就是研究如何降低模型的泛化误差,这类方法统称为**正则化方法**。 + > ../深度学习/[正则化](../A-深度学习/C-专题-正则化) + +### 偏差/方差 与 Boosting/Bagging +> ./集成学习专题/[Boosting/Bagging 与 偏差/方差 的关系](./C-专题-集成学习#boostingbagging-与-偏差方差-的关系) + +### 偏差与方差的计算公式 +- 记在**训练集 D** 上学得的模型为 +
+ + 模型的**期望预测**为 +
+ +- **偏差**(Bias) +
+ + > **偏差**度量了学习算法的期望预测与真实结果的偏离程度,即刻画了学习算法本身的拟合能力; +- **方差**(Variance) +
+ + > **方差**度量了同样大小的**训练集的变动**所导致的学习性能的变化,即刻画了数据扰动所造成的影响(模型的稳定性); + + +- **噪声**则表达了在当前任务上任何学习算法所能达到的期望泛化误差的下界,即刻画了学习问题本身的难度。 + +- “**偏差-方差分解**”表明模型的泛化能力是由算法的能力、数据的充分性、任务本身的难度共同决定的。 + +### 偏差与方差的权衡(过拟合与模型复杂度的权衡) +- 给定学习任务, + - 当训练不足时,模型的**拟合能力不够**(数据的扰动不足以使模型产生显著的变化),此时**偏差**主导模型的泛化误差; + - 随着训练的进行,模型的**拟合能力增强**(模型能够学习数据发生的扰动),此时**方差**逐渐主导模型的泛化误差; + - 当训练充足后,模型的**拟合能力过强**(数据的轻微扰动都会导致模型产生显著的变化),此时即发生**过拟合**(训练数据自身的、非全局的特征也被模型学习了) + +- 偏差和方差的关系和**模型容量**(模型复杂度)、**欠拟合**和**过拟合**的概念紧密相联 +
+ + - 当模型的容量增大(x 轴)时, 偏差(用点表示)随之减小,而方差(虚线)随之增大 + - 沿着 x 轴存在**最佳容量**,**小于最佳容量会呈现欠拟合**,**大于最佳容量会导致过拟合**。 + > 《深度学习》 5.4.4 权衡偏差和方差以最小化均方误差 + +**Reference** +- [Understanding the Bias-Variance Tradeoff](http://scott.fortmann-roe.com/docs/BiasVariance.html) +- [机器学习中的Bias(偏差),Error(误差),和Variance(方差)有什么区别和联系?](https://www.zhihu.com/question/27068705) - 知乎 + + +## 生成模型与判别模型 +> 《统计学习方法》 1.7 生成模型与判别模型 +- 监督学习的任务是学习一个模型,对给定的输入预测相应的输出 +- 这个模型的一般形式为一个**决策函数**或一个**条件概率分布**(后验概率): +
+ + - **决策函数**:输入 X 返回 Y;其中 Y 与一个**阈值**比较,然后根据比较结果判定 X 的类别 + - **条件概率分布**:输入 X 返回 **X 属于每个类别的概率**;将其中概率最大的作为 X 所属的类别 +- 监督学习模型可分为**生成模型**与**判别模型** + - **判别模型**直接学习决策函数或者条件概率分布 + - 直观来说,**判别模型**学习的是类别之间的最优分隔面,反映的是不同类数据之间的差异 + - **生成模型**学习的是联合概率分布`P(X,Y)`,然后根据条件概率公式计算 `P(Y|X)` +
+ +**两者之间的联系** +- 由生成模型可以得到判别模型,但由判别模型得不到生成模型。 +- 当存在“**隐变量**”时,只能使用**生成模型** + > 隐变量:当我们找不到引起某一现象的原因时,就把这个在起作用,但无法确定的因素,叫“隐变量” + +**优缺点** +- **判别模型** + - 优点 + - 直接面对预测,往往学习的准确率更高 + - 由于直接学习 `P(Y|X)` 或 `f(X)`,可以对数据进行各种程度的抽象,定义特征并使用特征,以简化学习过程 + - 缺点 + - 不能反映训练数据本身的特性 + - ... +- **生成模型** + - 优点 + - 可以还原出联合概率分布 `P(X,Y)`,判别方法不能 + - 学习收敛速度更快——即当样本容量增加时,学到的模型可以更快地收敛到真实模型 + - 当存在“隐变量”时,只能使用生成模型 + - 缺点 + - 学习和计算过程比较复杂 + +**常见模型** +- 判别模型 + - K 近邻、感知机(神经网络)、决策树、逻辑斯蒂回归、**最大熵模型**、SVM、提升方法、**条件随机场** +- 生成模型 + - 朴素贝叶斯、隐马尔可夫模型、混合高斯模型、贝叶斯网络、马尔可夫随机场 + +**Reference** +- [机器学习---生成模型与判别模型](https://blog.csdn.net/u012101561/article/details/52814571) - CSDN博客 +- + +## 先验概率与后验概率 +> [先验概率,后验概率,似然概率,条件概率,贝叶斯,最大似然](https://blog.csdn.net/suranxu007/article/details/50326873) - CSDN博客 + +**条件概率**(似然概率) +- 一个事件发生后另一个事件发生的概率。 +- 一般的形式为 `P(X|Y)`,表示 y 发生的条件下 x 发生的概率。 +- 有时为了区分一般意义上的**条件概率**,也称**似然概率** + +**先验概率** +- 事件发生前的预判概率 +- 可以是基于历史数据的统计,可以由背景常识得出,也可以是人的主观观点给出。 +- 一般都是**单独事件**发生的概率,如 `P(A)`、`P(B)`。 + +**后验概率** +- 基于先验概率求得的**反向条件概率**,形式上与条件概率相同(若 `P(X|Y)` 为正向,则 `P(Y|X)` 为反向) + +**贝叶斯公式** +
diff --git "a/A-\346\234\272\345\231\250\345\255\246\344\271\240/A-\346\234\272\345\231\250\345\255\246\344\271\240\345\256\236\350\267\265.md" "b/A-\346\234\272\345\231\250\345\255\246\344\271\240/A-\346\234\272\345\231\250\345\255\246\344\271\240\345\256\236\350\267\265.md" new file mode 100644 index 00000000..d47e6491 --- /dev/null +++ "b/A-\346\234\272\345\231\250\345\255\246\344\271\240/A-\346\234\272\345\231\250\345\255\246\344\271\240\345\256\236\350\267\265.md" @@ -0,0 +1,252 @@ +专题-机器学习实践 +=== + +Reference +--- +- [CS229 课程讲义(中文)](https://github.com/Kivy-CN/Stanford-CS-229-CN) - Kivy-CN - GitHub + +Index +--- + + +- [超参数选择](#超参数选择) + - [Grid Search](#grid-search) + - [Random Search](#random-search) + - [相关库(未使用)](#相关库未使用) +- [几种参数估计的区别于联系: MLE、MAP、贝叶斯 TODO](#几种参数估计的区别于联系-mlemap贝叶斯-todo) +- [余弦相似度(Cos距离)与欧氏距离的区别和联系](#余弦相似度cos距离与欧氏距离的区别和联系) +- [监督学习和无监督学习](#监督学习和无监督学习) +- [熵,求投掷均匀正六面体骰子的熵](#熵求投掷均匀正六面体骰子的熵) +- [混淆矩阵、模型度量指标:准确率、精确率、召回率、F1 值等](#混淆矩阵模型度量指标准确率精确率召回率f1-值等) +- [如何处理数据中的缺失值](#如何处理数据中的缺失值) +- [介绍一个完整的机器学习项目流程](#介绍一个完整的机器学习项目流程) +- [数据清洗与特征处理](#数据清洗与特征处理) +- [关联规则挖掘的 3 个度量指标:支持度、置信度、提升度](#关联规则挖掘的-3-个度量指标支持度置信度提升度) + + + +## 超参数选择 + +### Grid Search +- 网格搜索 +- 在高维空间中对一定区域进行遍历 + +### Random Search +- 在高维空间中随机选择若干超参数 + +### 相关库(未使用) +- [Hyperopt](http://hyperopt.github.io/hyperopt/) + - 用于超参数优化的 Python 库,其内部使用 Parzen 估计器的树来预测哪组超参数可能会得到好的结果。 + - GitHub - https://github.com/hyperopt/hyperopt +- [Hyperas](http://maxpumperla.com/hyperas/) + - 将 Hyperopt 与 Keras 模型集成在一起的库 + - GitHub - https://github.com/maxpumperla/hyperas + + + +## 几种参数估计的区别于联系: MLE、MAP、贝叶斯 TODO + + +## 余弦相似度(Cos距离)与欧氏距离的区别和联系 +> geekcircle/machine-learning-interview-qa/[4.md](https://github.com/geekcircle/machine-learning-interview-qa/blob/master/questions/4.md) + +- 欧式距离和余弦相似度都能度量 2 个向量之间的相似度 +- 放到向量空间中看,欧式距离衡量两点之间的**直线距离**,而余弦相似度计算的是两个向量之间的**夹角** +- **没有归一化时**,欧式距离的范围是 [0, +∞],而余弦相似度的范围是 [-1, 1];余弦距离是计算**相似程度**,而欧氏距离计算的是**相同程度**(对应值的相同程度) +- **归一化的情况下**,可以将空间想象成一个超球面(三维),欧氏距离就是球面上两点的直线距离,而向量余弦值等价于两点的球面距离,本质是一样。 + +> [欧氏距离和余弦相似度的区别是什么?](https://www.zhihu.com/question/19640394) - 知乎 + +## 监督学习和无监督学习 +> geekcircle/machine-learning-interview-qa/[6.md](https://github.com/geekcircle/machine-learning-interview-qa/blob/master/questions/6.md) + + +## 熵,求投掷均匀正六面体骰子的熵 +> geekcircle/machine-learning-interview-qa/[7.md](https://github.com/geekcircle/machine-learning-interview-qa/blob/master/questions/7.md) + +什么是熵? +> 深度学习/理论知识/[信息熵、KL 散度(相对熵)与交叉熵**](../A-深度学习/《深度学习》整理#信息熵kl-散度相对熵与交叉熵) + +**求投掷均匀正六面体骰子的熵** + +- 问题描述:向空中投掷硬币,落地后有两种可能的状态,一个是正面朝上,另一个是反面朝上,每个状态出现的概率为1/2。如投掷均匀的正六面体的骰子,则可能会出现的状态有6个,每一个状态出现的概率均为1/6。试通过计算来比较状态的不确定性与硬币状态的不确定性的大小。 + +- 答: + + 硬币:
+ + + 六面体:
+ + +## 混淆矩阵、模型度量指标:准确率、精确率、召回率、F1 值等 + +**混淆矩阵** + +- True Positive(TP):将正类预测为正类的数量. +- True Negative(TN):将负类预测为负类的数量. +- False Positive(FP):将负类预测为正类数 → 误报 (Type I error). +- False Negative(FN):将正类预测为负类数 → 漏报 (Type II error). + +
+ +**准确率**(accuracy) +
+ +**精确率**(precision) +
+ +> 准确率与精确率的区别: +>> 在正负样本不平衡的情况下,**准确率**这个评价指标有很大的缺陷。比如在互联网广告里面,点击的数量是很少的,一般只有千分之几,如果用acc,即使全部预测成负类(不点击)acc 也有 99% 以上,没有意义。 + +**召回率**(recall, sensitivity, true positive rate) +
+ +**F1值**——精确率和召回率的调和均值 +
+ +> 只有当精确率和召回率都很高时,F1值才会高 + + +## 如何处理数据中的缺失值 +> geekcircle/machine-learning-interview-qa/[1.md](https://github.com/geekcircle/machine-learning-interview-qa/blob/master/questions/1.md) + +可以分为以下 2 种情况: + +1. **缺失值较多** + - 直接舍弃该列特征,否则可能会带来较大的噪声,从而对结果造成不良影响。 +1. **缺失值较少** + - 当缺失值较少(<10%)时,可以考虑对缺失值进行填充,以下是几种常用的填充策略: + 1. 用一个**异常值**填充(比如 0),将缺失值作为一个特征处理 + + ` data.fillna(0) ` + + 1. 用**均值**|**条件均值**填充 + > 如果数据是不平衡的,那么应该使用条件均值填充 + > + > 所谓**条件均值**,指的是与缺失值所属标签相同的所有数据的均值 + + `data.fillna(data.mean())` + + 1. 用相邻数据填充 + + ``` + # 用前一个数据填充 + data.fillna(method='pad') + # 用后一个数据填充 + data.fillna(method='bfill') + ``` + + 1. 插值 + + `data.interpolate()` + + 1. 拟合 + > 简单来说,就是将缺失值也作为一个预测问题来处理:将数据分为正常数据和缺失数据,对有值的数据采用随机森林等方法拟合,然后对有缺失值的数据进行预测,用预测的值来填充。 + + +## 介绍一个完整的机器学习项目流程 +> geekcircle/machine-learning-interview-qa/[2.md](https://github.com/geekcircle/machine-learning-interview-qa/blob/master/questions/2.md) + +1. 数学抽象 + + 明确问题是进行机器学习的第一步。机器学习的训练过程通常都是一件非常耗时的事情,胡乱尝试时间成本是非常高的。 + + 这里的抽象成数学问题,指的是根据数据明确任务目标,是分类、还是回归,或者是聚类。 + +1. 数据获取 + + 数据决定了机器学习结果的上限,而算法只是尽可能逼近这个上限。 + + 数据要有代表性,否则必然会过拟合。 + + 对于分类问题,数据偏斜不能过于严重(平衡),不同类别的数据数量不要有数个数量级的差距。 + + 对数据的量级要有一个评估,多少个样本,多少个特征,据此估算出内存需求。如果放不下就得考虑改进算法或者使用一些降维技巧,或者采用分布式计算。 + +1. 预处理与特征选择 + + 良好的数据要能够提取出良好的特征才能真正发挥效力。 + + 预处理/数据清洗是很关键的步骤,往往能够使得算法的效果和性能得到显著提高。归一化、离散化、因子化、缺失值处理、去除共线性等,数据挖掘过程中很多时间就花在它们上面。这些工作简单可复制,收益稳定可预期,是机器学习的基础必备步骤。 + + 筛选出显著特征、摒弃非显著特征,需要机器学习工程师反复理解业务。这对很多结果有决定性的影响。特征选择好了,非常简单的算法也能得出良好、稳定的结果。这需要运用特征有效性分析的相关技术,如相关系数、卡方检验、平均互信息、条件熵、后验概率、逻辑回归权重等方法。 + +1. 模型训练与调优 + + 直到这一步才用到我们上面说的算法进行训练。 + + 现在很多算法都能够封装成黑盒使用。但是真正考验水平的是调整这些算法的(超)参数,使得结果变得更加优良。这需要我们对算法的原理有深入的理解。理解越深入,就越能发现问题的症结,提出良好的调优方案。 + +1. 模型诊断 + + 如何确定模型调优的方向与思路呢?这就需要对模型进行诊断的技术。 + + 过拟合、欠拟合 判断是模型诊断中至关重要的一步。常见的方法如交叉验证,绘制学习曲线等。过拟合的基本调优思路是增加数据量,降低模型复杂度。欠拟合的基本调优思路是提高特征数量和质量,增加模型复杂度。 + + 误差分析也是机器学习至关重要的步骤。通过观察误差样本,全面分析误差产生误差的原因:是参数的问题还是算法选择的问题,是特征的问题还是数据本身的问题...... + + 诊断后的模型需要进行调优,调优后的新模型需要重新进行诊断,这是一个反复迭代不断逼近的过程,需要不断地尝试, 进而达到最优状态。 + +1. 模型融合/集成 + + 一般来说,模型融合后都能使得效果有一定提升。而且效果很好。 + + 工程上,主要提升算法准确度的方法是分别在模型的前端(特征清洗和预处理,不同的采样模式)与后端(模型融合)上下功夫。因为他们比较标准可复制,效果比较稳定。而直接调参的工作不会很多,毕竟大量数据训练起来太慢了,而且效果难以保证。 + +1. 上线运行 + + 这一部分内容主要跟工程实现的相关性更大。工程上是结果导向,模型在线上运行的效果直接决定模型的成败。不单纯包括其准确程度、误差等情况,还包括其运行的速度(时间复杂度)、资源消耗程度(空间复杂度)、稳定性是否可接受。 + + 这些工作流程主要是工程实践上总结出的一些经验。并不是每个项目都包含完整的一个流程。这里的部分只是一个指导性的说明,只有多实践,多积累项目经验,才会有自己更深刻的认识。 + + +## 数据清洗与特征处理 +> geekcircle/machine-learning-interview-qa/[8.md](https://github.com/geekcircle/machine-learning-interview-qa/blob/master/questions/8.md) + + + +> [机器学习中的数据清洗与特征处理综述](https://tech.meituan.com/machinelearning-data-feature-process.html) - 美团点评技术 + +## 关联规则挖掘的 3 个度量指标:支持度、置信度、提升度 + +**支持度**(Support) +- X → Y 的支持度表示项集 {X,Y} 在总项集中出现的概率 + +
+ +- 其中,I 表示总事务集,`num()`表示事务集中特定项集出现的次数,`P(X)=num(X)/num(I)` + +**置信度**(Confidence) +- X → Y 的置信度表示在先决条件 X 发生的情况下,由规则 X → Y 推出 Y 的概率。 + +
+ +**提升度**(Lift) +- X → Y 的提升度表示含有X的条件下,同时含有Y的概率,与Y总体发生的概率之比。 + +
+ +规则的有效性: +--- +- 满足最小支持度和最小置信度的规则,叫做“强关联规则” + > 最小支持度和最小置信度是人工设置的阈值 +- `Lift(X→Y) > 1` 的 X→Y 是有效的强关联规则 +- `Lift(X→Y) <=1` 的 X→Y 是无效的强关联规则 +- 特别地,`Lift(X→Y) = 1` 时,X 与 Y 相互独立。 + +**判断规则的有效性** +--- +问题:已知有1000名顾客买年货,分为甲乙两组,每组各500人,其中甲组有500人买了茶叶,同时又有450人买了咖啡;乙组有450人买了咖啡,如表所示,请问“茶叶→咖啡”是一条有效的关联规则吗? + +组次 | 买茶叶的人数 | 买咖啡的人数 +--- | ---------- | --------- + 甲组(500人) | 500 | 450 + 乙组(500人) | 0 | 450 + + 答: + - “茶叶→咖啡”的支持度:Support(X→Y) = 450 / 1000 = 45% + - “茶叶→咖啡”的置信度:Confidence(X→Y) = 450 / 500 = 90% + - “茶叶→咖啡”的提升度:Lift(X→Y) = 90% / 90% = 1 + + 由于提升度 `Lift(X→Y) = 1`,表示 X 与 Y 相互独立。也就是说,是否购买咖啡,与是否购买茶叶无关联。规则“茶叶→咖啡”不成立,或者说几乎没有关联,虽然它的置信度高达90%,但它不是一条有效的关联规则。 diff --git "a/A-\346\234\272\345\231\250\345\255\246\344\271\240/A-\346\234\272\345\231\250\345\255\246\344\271\240\347\256\227\346\263\225.md" "b/A-\346\234\272\345\231\250\345\255\246\344\271\240/A-\346\234\272\345\231\250\345\255\246\344\271\240\347\256\227\346\263\225.md" new file mode 100644 index 00000000..2bc1fa97 --- /dev/null +++ "b/A-\346\234\272\345\231\250\345\255\246\344\271\240/A-\346\234\272\345\231\250\345\255\246\344\271\240\347\256\227\346\263\225.md" @@ -0,0 +1,673 @@ +**RoadMap** +--- +- [逻辑斯蒂回归](#逻辑斯蒂回归) +- [支持向量机](#支持向量机) +- [决策树](#决策树) +- [AdaBoost 算法](#adaboost-算法) + - [梯度提升决策树 GBDT](#梯度提升决策树-gbdt) +- [机器学习实践](#机器学习实践) + + +**Index** +--- + + +- [符号说明](#符号说明) +- [信息论](#信息论) +- [逻辑斯蒂回归](#逻辑斯蒂回归) + - [逻辑斯蒂回归模型定义](#逻辑斯蒂回归模型定义) + - [逻辑斯蒂回归推导](#逻辑斯蒂回归推导) + - [多分类逻辑斯蒂回归模型 TODO](#多分类逻辑斯蒂回归模型-todo) +- [支持向量机](#支持向量机) + - [支持向量机简述](#支持向量机简述) + - [什么是支持向量](#什么是支持向量) + - [支持向量机的分类](#支持向量机的分类) + - [核函数与核技巧](#核函数与核技巧) + - [最大间隔超平面背后的原理](#最大间隔超平面背后的原理) + - [支持向量机推导](#支持向量机推导) + - [线性可分支持向量机推导](#线性可分支持向量机推导) +- [决策树](#决策树) + - [信息增益与信息增益比 TODO](#信息增益与信息增益比-todo) + - [分类树 - ID3 决策树与 C4.5 决策树 TODO](#分类树---id3-决策树与-c45-决策树-todo) + - [决策树如何避免过拟合 TODO](#决策树如何避免过拟合-todo) + - [回归树 - CART 决策树](#回归树---cart-决策树) + - [CART 回归树算法推导](#cart-回归树算法推导) + - [示例: 选择切分变量与切分点](#示例-选择切分变量与切分点) +- [集成学习](#集成学习) + - [集成学习的基本策略(3)](#集成学习的基本策略3) + - [1. Boosting](#1-boosting) + - [Boosting 策略要解决的两个基本问题](#boosting-策略要解决的两个基本问题) + - [2. Bagging](#2-bagging) + - [3. Stacking](#3-stacking) + - [AdaBoost 算法](#adaboost-算法) + - [AdaBoost 算法描述](#adaboost-算法描述) + - [AdaBoost 算法要点说明](#adaboost-算法要点说明) + - [前向分步算法](#前向分步算法) + - [加法模型](#加法模型) + - [前向分步算法描述](#前向分步算法描述) + - [前向分步算法与 AdaBoost](#前向分步算法与-adaboost) +- [梯度提升决策树 GBDT](#梯度提升决策树-gbdt) + - [提升树 Boosting Tree](#提升树-boosting-tree) + - [提升树算法描述](#提升树算法描述) + - [梯度提升(GB)算法](#梯度提升gb算法) + - [GBDT 算法描述](#gbdt-算法描述) + - [XGBoost 算法](#xgboost-算法) + - [XGBoost 与 GB 的主要区别](#xgboost-与-gb-的主要区别) + - [XGBoost 的一些内部优化](#xgboost-的一些内部优化) +- [随机森林](#随机森林) +- [机器学习实践](#机器学习实践) + - [Box–Muller 变换](#boxmuller-变换) +- [降维](#降维) + - [SVD](#svd) + - [PCA](#pca) + - [t-SNE](#t-sne) + - [Reference](#reference) + + + + +# 符号说明 +- 基本遵从《统计学习方法》一书中的符号表示。 +- 除特别说明,默认`w`为行向量,`x`为列向量,以避免在`wx`中使用转置符号;但有些公式为了更清晰区分向量与标量,依然会使用`^T`的上标,注意区分。 + + 输入实例`x`的特征向量记为: + +
+ + 注意:`x_i` 和 `x^(i)` 含义不同,前者表示训练集中第 i 个实例,后者表示特征向量中的第 i 个分量;因此,通常记训练集为: + +
+ + > 特征向量用小`n`表示维数,训练集用大`N`表示个数 + +- **公式说明** + + 所有公式都可以**点击**跳转至编辑页面,但是部分公式符号会与超链接中的转义冲突;如果编辑页面的公式与本页面中的不同,可以打开源文件,通过原链接打开。 + + +# 信息论 +> 《深度学习》 3.13 信息论 +- 信息论的基本想法是:一件不太可能的事发生,要比一件非常可能的事发生,提供更多的信息。 +- 该想法可描述为以下性质: + 1. 非常可能发生的事件信息量要比较少,并且极端情况下,一定能够发生的事件应该没有信息量。 + 2. 比较不可能发生的事件具有更大的信息量。 + 3. 独立事件应具有增量的信息。例如,投掷的硬币两次正面朝上传递的信息量,应该是投掷一次硬币正面朝上的信息量的两倍。 + +

信息熵 与 自信息

+ +- **自信息**(self-information)是一种量化以上性质的函数,定义一个事件`x`的自信息为: + +
+ + > 当该对数的底数为自然对数 e 时,单位为奈特(nats);当以 2 为底数时,单位为比特(bit)或香农(shannons) + +- 自信息只处理单个的输出。 +- **信息熵**(Information-entropy)用于对整个概率分布中的**不确定性总量**进行量化: + +
+ + > 信息论中,记 `0log0 = 0` + +

交叉熵 与 相对熵/KL散度

+ +- 定义 **P 对 Q** 的 **KL 散度**(Kullback-Leibler divergence): + +
+ +**KL 散度在信息论中度量的是哪个直观量?** +- 在离散型变量的情况下, KL 散度衡量的是:当我们使用一种被设计成能够使得概率分布 Q 产生的消息的长度最小的编码,发送包含由概率分布 P 产生的符号的消息时,所需要的额外信息量。 + +**KL散度的性质**: +- 非负;KL 散度为 0 当且仅当P 和 Q 在离散型变量的情况下是相同的分布,或者在连续型变量的情况下是“几乎处处”相同的 +- 不对称;D_p(q) != D_q(p) + +**交叉熵**(cross-entropy): + +
+ +> [信息量,信息熵,交叉熵,KL散度和互信息(信息增益)](https://blog.csdn.net/haolexiao/article/details/70142571) - CSDN博客 + +**交叉熵 与 KL 散度的关系** +- **针对 Q 最小化交叉熵等价于最小化 P 对 Q 的 KL 散度**,因为 Q 并不参与被省略的那一项。 + +
+ +- 最大似然估计中,最小化 KL 散度其实就是在最小化分布之间的交叉熵。 + > 《深度学习》 ch5.5 - 最大似然估计 + + +# 逻辑斯蒂回归 + +## 逻辑斯蒂回归模型定义 + +- **二项**逻辑斯蒂回归模型即如下的**条件概率分布** + + + [![](../_assets/公式_20180709152707.png)](http://www.codecogs.com/eqnedit.php?latex=P(Y=1|x)=\frac{\exp(wx)}{1+\exp(wx)}=\frac{1}{1+\exp(-wx)}) + + [![](../_assets/公式_20180709113237.png)](http://www.codecogs.com/eqnedit.php?latex=P(Y=0|x)=1-P(Y=1|x)) + > 简洁起见,省略了偏置 `b`;也可以看做将偏置扩充到了权重中 + + **其中** + + + + [![](../_assets/公式_20180709113801.png)](http://www.codecogs.com/eqnedit.php?latex=x\in&space;\mathbf{R}^n,Y\in&space;\left&space;\{&space;0,1&space;\right&space;\}) + +- 通常会将以上两个分布记作: + + [![](../_assets/公式_20180709153307.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;&P(Y=1|x)={\color{Blue}&space;\sigma(x)}\\&space;&P(Y=0|x)={\color{Blue}&space;1-\sigma(x)}&space;\end{aligned}) + +> 《统计学习方法》 6.1 逻辑斯蒂回归模型 +>> 原书中记作 `π(x)` 和 `1-π(x)`,这里为了跟神经网络中统一,使用 `σ` + +## 逻辑斯蒂回归推导 +> [逻辑回归推导](https://www.cnblogs.com/daguankele/p/6549891.html) - 罐装可乐 - 博客园 +- 推导的关键点 (3) + 1. 逻辑斯蒂回归的定义 + 1. 损失函数(极大似然) + 1. 参数优化(梯度下降) + +- 给定训练集 `T={(x1,y1),..,(xN,yN)}`,其中 `x ∈ R^n, y ∈ {0, 1}` +1. **逻辑斯蒂回归**的定义: + + [![](../_assets/公式_20180709161030.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;&P(Y=1|x)={\color{Blue}&space;\sigma(x)}\\&space;&P(Y=0|x)={\color{Blue}&space;1-\sigma(x)}&space;\end{aligned}) +2. **负对数函数**作为损失函数: + + [![](../_assets/公式_20180713114855.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;L(w)&=-\log\left&space;(&space;\prod_{i=1}^N&space;[{\color{Red}&space;\sigma(x_i)}]^{{\color{Blue}&space;y_i}}&space;[{\color{Red}&space;1-&space;\sigma(x_i)}]^{{\color{Blue}&space;1-y_i}}&space;\right&space;)\\&space;&=-\sum_{i=1}^N&space;\left&space;[&space;y_i\log\sigma(x_i)+(1-y_i)\log(1-\sigma(x_i))&space;\right&space;]\\&space;&=-\sum_{i=1}^N&space;\left&space;[&space;y_i\log\frac{\sigma(x_i)}{1-\sigma(x_i)}+\log(1-\sigma(x_i))&space;\right&space;]&space;\end{aligned}) + + 进一步代入 `σ(x)` 有: + + [![](../_assets/公式_20180713131851.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;L(w)&=-\sum_{i=1}^N&space;\left&space;[&space;{\color{Blue}&space;y_i}(w{\color{Red}&space;x_i})-\log(1+\exp(w{\color{Red}&space;x_i}))&space;\right&space;]&space;\end{aligned}) +3. **求梯度** + + [![](../_assets/公式_20180713132107.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{\partial&space;L(w)}{\partial&space;w}&=-\sum_{i=1}^N&space;\left&space;[&space;y_ix_i-\frac{\exp(wx_i)}{1+\exp(wx_i)}x_i&space;\right&space;]\\&space;&=\sum_{i=1}^N&space;[\sigma&space;(x_i)-y_i]x_i&space;\end{aligned}) +4. 使用**梯度下降法**求解参数 + > 深度学习/[梯度下降法](../深度学习/README.md#梯度下降法) + +## 多分类逻辑斯蒂回归模型 TODO +- 设 `Y ∈ {1,2,..K}`,则多项式逻辑斯蒂回归模型为: + + [![](../_assets/公式_20180709162840.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;P(Y=k|x)&=\frac{\exp(w_kx)}{1+\sum_{k=1}^{K-1}&space;\exp(w_kx)}&space;\quad&space;k=1,2,..,K-1&space;\\&space;P(Y=K|x)&=\frac{1}{1+\sum_{k=1}^{K-1}\exp(w_kx)}&space;\end{aligned}) +- 类似 `Softmax` + +# 支持向量机 + +## 支持向量机简述 +- 支持向量机(Support Vector Machines, SVM)是一种二分类模型。它的**基本模型**是定义在特征空间上的**间隔最大**的线性分类器,间隔最大使它有别于感知机;支持向量机还包括**核技巧**,这使其成为实质上的非线性分类器。 +- **SVM 的学习策略就是间隔最大化**,可形式化为一个求解**凸二次规划**的问题,也等价于正则化的**合页损失函数**的最小化问题。 +- SVM 的最优化算法是求解凸二次规划的最优化算法。 + +### 什么是支持向量 +- 训练数据集中与分离超平面距离最近的样本点的实例称为支持向量 +- 更通俗的解释: + - 数据集种的某些点,位置比较特殊。比如 `x+y-2=0` 这条直线,假设出现在直线上方的样本记为 A 类,下方的记为 B 类。 + - 在寻找找这条直线的时候,一般只需看两类数据,它们各自最靠近划分直线的那些点,而其他的点起不了决定作用。 + - 这些点就是所谓的“支持点”,在数学中,这些点称为**向量**,所以更正式的名称为“**支持向量**”。 + > [SVM中支持向量的通俗解释](https://blog.csdn.net/AerisIceBear/article/details/79588583) - CSDN博客 + +### 支持向量机的分类 +- 线性可分支持向量机 + - 当训练数据**线性可分**时,通过**硬间隔最大化**,学习一个线性分类器,即线性可分支持向量机,又称**硬间隔支持向量机**。 +- 线性支持向量机 + - 当训练数据**接近线性可分**时,通过**软间隔最大化**,学习一个线性分类器,即线性支持向量机,又称**软间隔支持向量机**。 +- 非线性支持向量机 + - 当训练数据**线性不可分**时,通过使用**核技巧**及软间隔最大化,学习非线性支持向量机。 + +### 核函数与核技巧 +- **核函数**表示将输入从输入空间映射到特征空间后得到的特征向量之间的内积 + +### 最大间隔超平面背后的原理 +> 机器学习技法 (1-5) - 林轩田 +- 相当于在**最小化权重**时对训练误差进行了约束——对比 L2 范数正则化,则是在最小化训练误差时,对权重进行约束 + + ![](../_assets/TIM截图20180710112848.png) + > 与 L2 正则化的区别 +- 相当于**限制了模型复杂度**——在一定程度上防止过拟合,具有更强的泛化能力 + +## 支持向量机推导 +- SVM 由简至繁包括:**线性可分支持向量机**、**线性支持向量机**以及**非线性支持向量机** + +### 线性可分支持向量机推导 +> 《统计学习方法》 & [支持向量机SVM推导及求解过程](https://blog.csdn.net/american199062/article/details/51322852#commentBox) - CSDN博客 +- 当训练数据**线性可分**时,通过**硬间隔最大化**,学习一个线性分类器,即线性可分支持向量机,又称**硬间隔支持向量机**。 +- 线性 SVM 的推导分为两部分 + 1. 如何根据**间隔最大化**的目标导出 SVM 的**标准问题**; + 1. 拉格朗日乘子法对偶问题的求解过程. + +**符号定义**: +--- +- 训练集 `T` + + [![](../_assets/公式_20180713132400.png)](http://www.codecogs.com/eqnedit.php?latex=T=\left&space;\{&space;(x_1,y_1),(x_2,y_2),\cdots,(x_N,y_N)&space;\right&space;\}) + +- **分离超平面** `(w,b)` + + [![](../_assets/公式_20180713111647.png)](http://www.codecogs.com/eqnedit.php?latex=w^*\cdot&space;x+b^*=0) + + 如果使用映射函数,那么分离超平面为 + + [![](../_assets/公式_20180713111746.png)](http://www.codecogs.com/eqnedit.php?latex=w^*\cdot&space;\Phi&space;(x)+b^*=0) + > 映射函数 `Φ(x)` 定义了从输入空间到特征空间的变换,特征空间通常是更高维的,甚至无穷维;方便起见,这里假设 `Φ(x)` 做的是恒等变换。 + +- 分类决策函数 `f(x)` + + [![](../_assets/公式_20180713132655.png)](http://www.codecogs.com/eqnedit.php?latex=f(x)=\mathrm{sign}(w^*\cdot&space;x+b^*)) + +**SVM 标准问题的推导**(2) +--- +1. **从“函数间隔”到“几何间隔”** + + 给定训练集`T`和超平面`(w,b)`,定义**函数间隔`γ^`**: + + [![](../_assets/公式_20180713134514.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\hat{\gamma}&=\underset{i=1,\cdots,N}{\min}\,y_i(wx_i+b)&space;\\&space;&=\underset{i=1,\cdots,N}{\min}\,\hat{\gamma}_i\end{aligned}) + + 对 `w` 作规范化,使函数间隔成为**几何间隔`γ`** + + [![](../_assets/公式_20180713134322.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\gamma&=\underset{i=1,\cdots,N}{\min}\,y_i(\frac{w}{{\color{Red}&space;\left&space;\|&space;w&space;\right&space;\|}}x_i+\frac{b}{{\color{Red}&space;\left&space;\|&space;w&space;\right&space;\|}})\\&space;&=\underset{i=1,\cdots,N}{\min}\,\frac{\gamma_i}{{\color{Red}&space;\left&space;\|&space;w&space;\right&space;\|}}&space;\end{aligned}) + +1. **最大化几何间隔** + + [![](../_assets/公式_20180713142726.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;&{\color{Red}&space;\underset{w,b}{\max}}&space;\quad\gamma&space;\\&space;&\&space;\mathrm{s.t.}\quad\,&space;y_i(\frac{w}{{\color{Red}&space;\left&space;\|&space;w&space;\right&space;\|}}x_i+\frac{b}{{\color{Red}&space;\left&space;\|&space;w&space;\right&space;\|}})&space;\geq&space;\gamma,\quad&space;i=1,2,\cdots,N&space;\end{aligned}) + + 由函数间隔与几何间隔的关系,等价于 + + [![](../_assets/公式_20180808201943.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;&\underset{w,b}{\max}&space;\quad{\color{Red}&space;\frac{\hat{\gamma}}{\left&space;|&space;w&space;\right&space;|}}&space;\&space;&&space;\mathrm{s.t.}\quad,&space;y_i(wx_i+b)&space;\geq&space;{\color{Red}&space;\hat{\gamma}},\quad&space;i=1,2,\cdots,N&space;\end{aligned}) + + 函数间隔`γ^`的取值不会影响最终的超平面`(w,b)`:取`γ^=1`;又最大化 `1/||w||` 等价于最小化`1/2*||w||^2`,于是有 + + [![](../_assets/公式_20180713143622.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;&{\color{Red}&space;\underset{w,b}{\max}&space;}&space;\quad\frac{\hat{\gamma}}{{\color{Red}&space;\left&space;\|&space;w&space;\right&space;\|}}&space;\\&space;&\&space;\mathrm{s.t.}\quad\,&space;y_i(wx_i+b)&space;\geq&space;\hat{\gamma}_i,\quad&space;i=1,2,\cdots,N&space;\end{aligned}) + > 为什么令`γ^=1`?——比例改变`(ω,b)`,超平面不会改变,但函数间隔`γ^`会成比例改变,因此可以通过等比例改变`(ω,b)`使函数间隔`γ^=1` + +- 该约束最优化问题即为**线性支持向量机**的标准问题——这是一个**凸二次优化**问题,可以使用商业 QP 代码完成。 + + 理论上,线性 SVM 的问题已经解决了;但在高等数学中,**带约束的最优化问题**还可以用另一种方法求解——**拉格朗日乘子法**。该方法的优点一是更容易求解,而是自然引入**核函数**,进而推广到非线性的情况。 + +**SVM 对偶算法的推导**(5) +--- +1. 构建**拉格朗日函数** + + [![](../_assets/公式_20180713202306.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;L(w,b,{\color{Red}&space;\alpha})=&\frac{1}{2}w^Tw-\sum_{i=1}^N{\color{Red}&space;\alpha_i}[y_i(w^Tx_i+b)-1]\\&space;&{\color{Red}&space;\alpha_i&space;\geq&space;0},\quad&space;i=1,2,\cdots,N&space;\end{aligned}) + +1. 标准问题是求极小极大问题: + + [![](../_assets/公式_20180713152150.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;{\color{Red}&space;\underset{w,b}{\min}}\&space;{\color{Blue}&space;\underset{\alpha}{\max}}\&space;L(w,b,\alpha)&space;\end{aligned}) + + 其对偶问题为: + + [![](../_assets/公式_20180713152255.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;{\color{Blue}&space;\underset{\alpha}{\max}}\&space;{\color{Red}&space;\underset{w,b}{\min}}\&space;L(w,b,\alpha)&space;\end{aligned}) + +1. 求 `L` 对 `(w,b)` 的极小 + + [![](../_assets/公式_20180713193142.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\mathrm{set}\quad&space;\frac{\partial&space;L}{\partial&space;w}=0&space;\;\;&\Rightarrow\;&space;w-\sum_{i=1}^N&space;{\color{Red}&space;\alpha_i&space;y_i&space;x_i}=0\\&space;&\Rightarrow\;&space;w=\sum_{i=1}^N&space;{\color{Red}&space;\alpha_i&space;y_i&space;x_i}&space;\end{aligned}) + + [![](../_assets/公式_20180713193328.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\mathrm{set}\quad&space;\frac{\partial&space;L}{\partial&space;b}=0&space;\;\;&\Rightarrow\;&space;\sum_{i=1}^N&space;{\color{Red}&space;\alpha_i&space;y_i}=0&space;\end{aligned}) + + 结果代入`L`,有: + + [![](../_assets/公式_20180713195055.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;L(w,b,{\color{Red}&space;\alpha})&space;&=\frac{1}{2}w^Tw-\sum_{i=1}^N{\color{Red}&space;\alpha_i}[y_i(w^Tx_i+b)-1]\\&space;&=\frac{1}{2}w^Tw-w^T\sum_{i=1}^N&space;\alpha_iy_ix_i-b\sum_{i=1}^N&space;\alpha_iy_i+\sum_{i=1}^N&space;\alpha_i\\&space;&=\frac{1}{2}w^Tw-w^Tw+\sum_{i=1}^N&space;\alpha_i\\&space;&=-\frac{1}{2}w^Tw+\sum_{i=1}^N&space;\alpha_i\\&space;&=-\frac{1}{2}\sum_{i=1}^N\sum_{j=1}^N&space;\alpha_i\alpha_j\cdot&space;y_iy_j\cdot&space;{\color{Red}&space;x_i^Tx_j}+\sum_{i=1}^N&space;\alpha_i&space;\end{aligned}) + + 即 + + [![](../_assets/公式_20180713195135.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;L(w,b,{\color{Red}&space;\alpha})&space;&=\frac{1}{2}w^Tw-\sum_{i=1}^N{\color{Red}&space;\alpha_i}[y_i(w^Tx_i+b)-1]\\&space;&=\frac{1}{2}w^Tw-w^T\sum_{i=1}^N&space;\alpha_iy_ix_i-b\sum_{i=1}^N&space;\alpha_iy_i+\sum_{i=1}^N&space;\alpha_i\\&space;&=\frac{1}{2}w^Tw-w^Tw+\sum_{i=1}^N&space;\alpha_i\\&space;&=-\frac{1}{2}w^Tw+\sum_{i=1}^N&space;\alpha_i\\&space;&=-\frac{1}{2}\sum_{i=1}^N\sum_{j=1}^N&space;\alpha_i\alpha_j\cdot&space;y_iy_j\cdot&space;{\color{Red}&space;x_i^Tx_j}+\sum_{i=1}^N&space;\alpha_i&space;\end{aligned}) + +1. 求 `L` 对 `α` 的极大,即 + + [![](../_assets/公式_20180713200756.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;&\underset{\alpha}{\max}&space;\quad&space;-\frac{1}{2}\sum_{i=1}^N\sum_{j=1}^N&space;\alpha_i\alpha_j\cdot&space;y_iy_j\cdot&space;x_i^Tx_j+\sum_{i=1}^N&space;\alpha_i\\&space;&\&space;\mathrm{s.t.}\quad\;&space;\sum_{i=1}^N&space;\alpha_i&space;y_i=0,\&space;\&space;{\color{Red}&space;\alpha_i&space;\geq&space;0},\quad&space;i=1,2,\cdots,N&space;\end{aligned}) + + 该问题的对偶问题为: + + [![](../_assets/公式_20180713200840.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;&{\color{Red}&space;\underset{\alpha}{\min}&space;}&space;\quad\&space;\frac{1}{2}\sum_{i=1}^N\sum_{j=1}^N&space;\alpha_i\alpha_j\cdot&space;y_iy_j\cdot&space;x_i^Tx_j-\sum_{i=1}^N&space;\alpha_i\\&space;&\&space;\mathrm{s.t.}\quad\;&space;\sum_{i=1}^N&space;\alpha_i&space;y_i=0,\&space;\&space;{\color{Red}&space;\alpha_i&space;\geq&space;0},\quad&space;i=1,2,\cdots,N&space;\end{aligned}) + + 于是,标准问题最后等价于求解该**对偶问题** + > 继续求解该优化问题,有 [SMO 方法](https://blog.csdn.net/ajianyingxiaoqinghan/article/details/73087304#t11);因为《统计学习方法》也只讨论到这里,故推导也止于此 + +1. 设 `α` 的解为 `α*`,则存在下标`j`使`α_j > 0`,可得标准问题的解为: + + [![](../_assets/公式_20180713203827.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;w^*&=\sum_{i=1}^N&space;\alpha_i^*y_ix_i\\&space;b^*&={\color{Red}&space;y_j}-\sum_{i=1}^N&space;\alpha_i^*y_i(x_i^T{\color{Red}&space;x_j})&space;\end{aligned}) + + 可得分离超平面及分类决策函数为: + + [![](../_assets/公式_20180713111647.png)](http://www.codecogs.com/eqnedit.php?latex=w^*\cdot&space;x+b^*=0) + + [![](../_assets/公式_20180713132655.png)](http://www.codecogs.com/eqnedit.php?latex=f(x)=\mathrm{sign}(w^*\cdot&space;x+b^*)) + + +# 决策树 +- 决策树的训练通常由三部分组成:**特征选择**、**树的生成**、**剪枝**。 + +## 信息增益与信息增益比 TODO +## 分类树 - ID3 决策树与 C4.5 决策树 TODO +- ID3 决策树和 C4.5 决策树的**区别**在于:前者使用**信息增益**来进行特征选择,而后者使用**信息增益比**。 + +## 决策树如何避免过拟合 TODO + +## 回归树 - CART 决策树 +> 《统计学习方法》 5.5 CART 算法 +- CART 算法是在给定输入随机变量 _`X`_ 条件下输出随机变量 _`Y`_ 的**条件概率分布**的学习方法。 +- CART 算法假设决策树是**二叉树**,内部节点特征的取值为“**是**”和“**否**”。 + + 这样的决策树等价于递归地二分每个特征,**将输入空间/特征空间划分为有限个单元**,然后在这些单元上确定在输入给定的条件下输出的**条件概率分布**。 +- CART 决策树**既可以用于分类,也可以用于回归**; + + 对回归树 CART 算法用**平方误差最小化**准则来选择特征,对分类树用**基尼指数最小化**准则选择特征 + +### CART 回归树算法推导 +- 一个回归树对应着输入空间/**特征空间**的一个**划分**以及在划分单元上的**输出值**; +- 假设已将输入空间划分为 `M` 个单元:`{R_1,..,R_m,..,R_M}`,并在每个单元上对应有输出值 `c_m`,则该回归树可表示为 +
+ + > `I(x)` 为指示函数 +- **如果已经划分好了输入空间**,通常使用**平方误差**作为损失函数来表示回归树对于训练数据的预测误差,通过最小化损失函数来求解每个划分单元的**最优输出值**。 +- 如果使用**平方误差**,易知**最优输出值**即每个划分单元上所有实例的均值 +
+ + > 选用**平方误差**作为损失的原因 + +

如何划分输入空间

+ +- 一个启发式方法是:**以特征向量中的某一个特征为标准进行切分**。 + + 假设选择**特征向量中第 `j` 个变量**作为**切分变量**,然后选择**某个实例中第 `j` 个值 `s`** 作为**切分点**,则定义如下两个划分单元 +
+ + > 原书中这里表述不够清楚,需要结合 8.4.2 节中的示例一起看。 +- 遍历**每个实例**的第`j`个值`s`,选择满足以下条件的作为**最优切分变量`j`和切分点`s`** +
+ + 其中输出值 `c1` 和 `c2` 分别为 +
+ + > 示例: [选择切分变量与切分点](#示例-选择切分变量与切分点) +- 接着,继续对两个子空间重复以上步骤,直到满足条件为止;得到将输入空间划分为`M`个区域的决策树 +
+ +### 示例: 选择切分变量与切分点 +> 《统计学习方法》 8.4.2 +- 训练集 + + x_i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 +-----|---|---|---|---|---|---|---|---|---|--- + y_i |5.56|5.70|5.91|6.40|6.80|7.05|8.90|8.70|9.00|9.05 +- 这里只有一个特征,即`j=1`;然后遍历每个实例的值作为**切分点** + + `s = {1, 2, 3, 4, 5, 6, 7, 8, 9}` + > 原书使用的切分点为 `{1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5}`,即相邻两个点的均值;因为切分点并没有参与运算,所以我觉得两者没有区别; + > + > 最后一个点无法将数据划分为两个空间,所以不需要 +- 以 `s=1` 为例 +
+ + 所有 `m(s)` 的计算结果如下 + + s | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 + ----|---|---|---|---|---|---|---|---|--- + m(s)|15.72|12.07|8.36|5.78|3.91|1.93|8.01|11.73|15.74 + +- 当 `s=6` 时 `m(s)` 达到最小值,此时 +
+ +- 所以第一棵决策树为 +
+ + +# 集成学习 +- 基本思想:由多个学习器组合成一个性能更好的学习器 +- **集成学习为什么有效?**——不同的模型通常会在测试集上产生不同的误差。平均上,集成模型能至少与其任一成员表现一致;并且**如果成员的误差是独立的**,集成模型将显著地比其成员表现更好。 +> 《深度学习》 7.11 Bagging 和其他集成方法 + +## 集成学习的基本策略(3) +### 1. Boosting +- **Boosting**(提升)方法从某个**基学习器**出发,反复学习,得到一系列基学习器,然后组合它们构成一个强学习器。 +- Boosting 基于**串行策略**:基学习器之间存在依赖关系,新的学习器需要依据旧的学习器生成。 +- **代表算法/模型**: + - [提升方法 AdaBoost](#提升方法-adaboost) + - 提升树 + - 梯度提升树 GBDT + +#### Boosting 策略要解决的两个基本问题 +1. 每一轮如何改变数据的权值或概率分布? +1. 如何将弱分类器组合成一个强分类器? + +### 2. Bagging +- Bagging 基于**并行策略**:基学习器之间不存在依赖关系,可同时生成。 +- **代表算法/模型**: + - [随机森林](#随机森林) + - 神经网络的 **Dropout** 策略 + +### 3. Stacking +- 介绍不多,有时间再整理 + +## AdaBoost 算法 +- AdaBoost 是 Boosting 策略的一种具体算法 + +**AdaBoost 算法解决 [Boosting 两个基本问题](#boosting-策略要解决的两个基本问题)的方法** +1. 每一轮如何改变数据的权值或概率分布?——开始时,每个样本的权值是一样的,AdaBoost 的做法是提高上一轮弱分类器错误分类样本的权值,同时降低那些被正确分类样本的权值。 +1. 如何将弱分类器组合成一个强分类器?—— AdaBoost 采取加权表决的方法([加法模型](#加法模型))。具体的,AdaBoost 会加大分类误差率小的基学习器的权值,使其在表决中起到更大的作用,同时减小分类误差率大的基学习器的权值。 + +### AdaBoost 算法描述 +- 输入:训练集 `T={(x1,y1),..,(xN,yN)}, xi ∈ R^n, yi ∈ {-1,+1}`,基学习器 `G1(x)` +- 输出:最终学习器 `G(x)` + +1. 初始化训练数据的全职分布 + + [![](../_assets/公式_20180715133122.png)](http://www.codecogs.com/eqnedit.php?latex=D_1=(w_{1,1},\cdots,w_{1,i},\cdots,w_{1,N}),\quad&space;w_{1,i}=\frac{1}{N},\quad&space;i=1,2,\cdots,N) + +1. 对 `m=1,2,..,M` + 1. 使用权值分布为`D_m`的训练集,得到基分类器: + + [![](../_assets/公式_20180715132515.png)](http://www.codecogs.com/eqnedit.php?latex=G_m(x):\chi&space;\rightarrow&space;\{-1,+1\}) + 1. 计算 `G_m(x)` 在训练集上的分类误差率 + + [![](../_assets/公式_20180715142042.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;e_m&=P(G_m(x_i)\neq&space;y_i)\\&=\sum_{i=1}^Nw_{m,i}\cdot&space;{\color{Red}&space;I(G_m(x_i)\neq&space;y_i)}&space;\end{aligned}) + > `I(x)` 为指示函数:若`G(x)!=y`为真,则`I(G(x)!=y)=1`,反之为 `0` + > + > 实际上分类误差率就等于所有**分类错误的数据的权值之和** + 1. 计算 `G_m(x)` 的系数 + + [![](../_assets/公式_20180715133256.png)](http://www.codecogs.com/eqnedit.php?latex=\alpha_m=\frac{1}{2}\ln\frac{1-e_m}{e_m}) + 1. 更新训练集的权值分布 + + + + [![](../_assets/公式_20180715140328.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;D_{{\color{Red}m+1}}&=(w_{m+1,1},\cdots,w_{m+1,i},\cdots,w_{m+1,N})\\&space;w_{{\color{Red}m+1},i}&=\frac{w_{{\color{Red}m},i}\cdot\exp(-\alpha_{\color{Red}m}\cdot{\color{Blue}y_iG_m(x_i)&space;})}{Z_{\color{Red}m}}&space;\end{aligned}) + + 其中 `Z_m` 为**规范化因子**,使 `D_m+1` 成为一个**概率分布**,类似 `Softmax` 函数 + + + + 因为 `y, G(x) ∈ {-1, 1}`,所以实际上 + + [![](../_assets/公式_20180715134916.png)](http://www.codecogs.com/eqnedit.php?latex={\color{Blue}y_iG_m(x_i)&space;}=\left\{\begin{matrix}&space;1,&&space;G_m(x_i)=y_i&space;\\&space;-1,&&space;G_m(x_i)\neq&space;y_i&space;\end{matrix}\right.) + + 因此 `w_{m+1,i}` 也可以写作 + + [![](../_assets/公式_20180715135945.png)](http://www.codecogs.com/eqnedit.php?latex=\dpi{120}&space;w_{m+1,i}=\left\{\begin{matrix}&space;\frac{w_{m,i}}{Z_m}e^{\color{Red}&space;{-\alpha_m}},&space;&&space;G_m(x_i)=y_i&space;\\&space;\frac{w_{m,i}}{Z_m}e^{\color{Red}&space;{\alpha_m}},&&space;G_m(x_i)\neq&space;y_i&space;\end{matrix}\right.) + +1. 构建基学习器的**线性组合** + + [![](../_assets/公式_20180715141210.png)](http://www.codecogs.com/eqnedit.php?latex=G(x)=\mathrm{sign}(\sum_{m=1}^M\alpha_mG_m(x))) + +### AdaBoost 算法要点说明 +- 开始时,训练集中所有数据具有均匀的权值分布 +- 计算分类误差率,实际上就是计算所有分类错误的数据的权值之和 +- `G_m(x)` 的系数 `α_m` 表示该学习器在最终学习器中的重要性;公式 + [![](../_assets/公式_20180715133256.png)](http://www.codecogs.com/eqnedit.php?latex=\alpha_m=\frac{1}{2}\ln\frac{1-e_m}{e_m}) 表明当分类错误率 `e_m <= 1/2` 时,`α_m >= 0`,并且 `α_m` 随 `e_m` 的减小而增大 +- 被基分类器分类错误的样本权值会扩大,而分类正确的权值会缩小——**不改变训练数据,而不断改变训练数据权值的分布,使训练数据在基学习器的学习中起到不同的作用**,这是 AdaBoost 的一个特点。 + +## 前向分步算法 +### 加法模型 +- 定义加法模型: + + [![](../_assets/公式_20180715212915.png)](http://www.codecogs.com/eqnedit.php?latex=f(x)=\sum_{m=1}^M\beta_m\,b(x;\gamma_m)) + + 其中`b(x;γ)`为基函数,`γ`为基函数的参数;`β`为基函数的系数 +- 在给定训练数据和损失函数`L(y,f(x))`的情况下,学习加法模型相当于损失函数的最小化问题 + + [![](../_assets/公式_20180715230941.png)](http://www.codecogs.com/eqnedit.php?latex={\color{Red}&space;\underset{\beta_m,\gamma_m}{\min}}\sum_{i=1}^N&space;L\left&space;(&space;y_i,{\color{Blue}&space;\sum_{m=1}^M\beta_m\,b(x;\gamma_m)}&space;\right&space;)) + +### 前向分步算法描述 +前向分步算法求解加法模型的想法是:如果能够从前向后,每一步只学习一个基函数及其系数,逐步优化目标函数 +- 输入:训练集 `T={(x1,y1),..,(xN,yN)}`,损失函数 `L(y,f(x))`,基函数集 `{b(x;γ)}` +- 输出:加法模型 `f(x)` +1. 初始化 `f_0(x)=0` +1. 对 `m=1,2,..,M` + 1. 极小化损失函数,得到 `(β_m,γ_m)` + + [![](../_assets/公式_20180716142855.png)](http://www.codecogs.com/eqnedit.php?latex=(\beta_m,\gamma_m)=\arg\underset{\beta,\gamma}{\min}\sum_{i=1}^NL\left&space;(&space;y_i,{\color{Red}&space;f_{m-1}(x_i)+\beta&space;b(x_i;\gamma)}&space;\right&space;)) + + 1. 更新模型 `f_m(x)` + + [![](../_assets/公式_20180716143232.png)](http://www.codecogs.com/eqnedit.php?latex=f_m(x)={\color{Red}&space;f_{m-1}(x)+\beta&space;b(x;\gamma)}) +1. 得到加法模型 + + [![](../_assets/公式_20180716143509.png)](http://www.codecogs.com/eqnedit.php?latex=f(x)=f_M(x)={\color{Red}&space;\sum_{m=1}^M}{\color{Blue}&space;\beta_m}b(x;\gamma_m)) + +- 前向分步算法将**同时**求解`m=1,2,..,M`所有参数`(β_m,γ_m)`的问题**简化**为**逐次**求解各`(β_m,γ_m)`的优化问题——思想上有点像**梯度下降** + +### 前向分步算法与 AdaBoost +- AdaBoost 算法是前向分步算法的特例。 +- 此时,基函数为基分类器,损失函数为指数函数`L(y,f(x)) = exp(-y*f(x))` + +# 梯度提升决策树 GBDT +- GBDT 是以**决策树**为基学习器、采用 Boosting 策略的一种集成学习模型 +- **与提升树的区别**:残差的计算不同,提升树使用的是真正的残差,梯度提升树用当前模型的负梯度来拟合残差。 + +## 提升树 Boosting Tree +- 以**决策树**为基学习器,对分类问题使用二叉分类树,回归问题使用二叉回归树。 +- 解决回归问题时,通过不断拟合残差得到新的树。 +- 提升树模型可表示为**决策树的加法模型**: +
+ +- 首先初始化提升树 `f_0(x)=0`,则第 m 步的模型为 +
+ +- 然后通过最小化损失函数决定下一个决策树的参数 +
+ +- 对于二分类问题,提升树算法只需要将[AdaBoost 算法](#adaboost-算法描述)中的基学习器限制为二叉分类树即可 + +### 提升树算法描述 +在回归问题中,新的树是通过不断拟合**残差**(residual)得到的。 +- 输入:训练集 `T={(x1,y1),..,(xN,yN)}, xi ∈ R^n, yi ∈ R` +- 输出:回归提升树 `f_M(x)` +1. 初始化 `f_0(x)=0` +1. 对 `m=1,2,..,M` + 1. 计算**残差** +
+ + 1. **拟合残差**学习下一个回归树的参数 +
+ + > [回归树算法推导](#回归树算法推导) TODO + 1. 更新 `f_m(x)` +
+ +1. 得到回归提升树 +
+ +- 以平凡损失为例 TODO + +## 梯度提升(GB)算法 +- 当损失函数为平方损失或指数损失时,每一步的优化是很直观的;但对于一般的损失函数而言,不太容易——梯度提升正是针对这一问题提出的算法; +- 梯度提升是梯度下降的近似方法,其关键是利用损失函数的**负梯度作为残差的近似值**,来拟合下一个决策树。 + +## GBDT 算法描述 +- 输入:训练集 `T={(x1,y1),..,(xN,yN)}, xi ∈ R^n, yi ∈ R`;损失函数 `L(y,f(x))`; +- 输出:回归树 `f_M(x)` +1. 初始化回归树 +
+ +1. 对 `m=1,2,..,M` + 1. 对 `i=1,2,..,N`,计算残差/负梯度 +
+ + 1. 对 `r_mi` 拟合一个回归树,得到第 `m` 棵树的叶节点区域 +
+ + > [CART 回归树算法推导](#cart-回归树算法推导) + 1. 对 `j=1,2,..,J`,计算 +
+ + 1. 更新回归树 +
+1. 得到回归树 +
+ +- 说明: + - 算法第 1 步初始化,估计使损失函数最小的常数值,得到一棵只有一个根节点的树 + - 第 2(i) 步计算损失函数的负梯度,将其作为残差的估计 + - 对平方损失而言,负梯度就是残差;对于一般的损失函数,它是残差的近似 + - 第 2(ii) 步估计回归树的节点区域,以拟合残差的近似值 + - 第 2(iii) 步利用线性搜索估计叶节点区域的值,使损失函数最小化 + + +## XGBoost 算法 +> [一步一步理解GB、GBDT、xgboost](https://www.cnblogs.com/wxquare/p/5541414.html) - wxquare - 博客园 +- XGBoost 是改进的[梯度提升(GB)算法](#梯度提升GB算法); +- [XGBoost 库](https://github.com/dmlc/xgboost)是 XGBoost 算法的高效实现 + +### XGBoost 与 GB 的主要区别 +> [Introduction to Boosted Trees](http://xgboost.readthedocs.io/en/latest/model.html) — xgboost 0.72 documentation +- 首先,定义一棵树 `f(x)` 为 +
+ + > Here `w` is the vector of scores on leaves, `q` is a function assigning each data point to the corresponding leaf, and `T` is the number of leaves. + +- 对损失函数加入**正则项**,包括 L2 权重衰减和对叶子数的限制 +
+ +- 使用**牛顿法**代替**梯度下降法**寻找最优解 + + 前者使用一阶+二阶导数作为残差,后者只使用了一阶导数 +- 传统 CART树寻找最优切分点的标准是**最小化均方差**; + + XGBoost 通过最大化**得分公式**来寻找最优切分点: +
+ + > This formula can be decomposed as 1). the score on the new left leaf 2). the score on the new right leaf 3). The score on the original leaf 4). regularization on the additional leaf. + + 这同时也起到了“**剪枝**”的作用——如果分数小于`γ`,则不会增加分支; + +### XGBoost 的一些内部优化 +- 在寻找最佳分割点时,传统的方法会枚举每个特征的所有可能切分点。XGBoost 实现了一种近似的算法,大致的思想是根据百分位法列举几个可能成为分割点的候选者,然后从候选者中根据上面求分割点的公式计算找出最佳的分割点。 +- XGBoost 考虑了训练数据为稀疏值的情况,可以为缺失值或者指定的值指定分支的默认方向,这能大大提升算法的效率,paper 提到能提高 50 倍。 +- **特征列**排序后以块的形式存储在内存中,在迭代中可以重复使用;虽然 Boosting 算法迭代必须串行,但是在处理每个特征列时可以做到并行。 +- 按照**特征列**方式存储能优化寻找最佳的分割点,但是当**以行计算梯度数据**时会导致内存的不连续访问,严重时会导致 **cache miss**,降低算法效率。Paper 中提到,可先将数据收集到线程内部的 buffer,然后再计算,提高算法的效率。 +- XGBoost 还考虑了数据量比较大的情况,当内存不够时怎么有效的使用磁盘,主要是结合多线程、数据压缩、分片的方法,尽可能的提高算法的效率。 + + +# 随机森林 +TODO + + +# 机器学习实践 + +## Box–Muller 变换 +> [Box-Muller变换原理详解](http://shishuai.org/index.php/2018/06/28/1-2/) – 史帅个人网站 +- Box–Muller 变换是一个从**均匀分布**中得到**正态分布**采样的算法 + +- Box–Muller 变换定理: + + 假设随机变量`U1`和`U2`是 IID(独立同分布) 的,且 `U1,U2 ∽ U(0,1)`,令`Z1,Z2`满足 +
+ + + 则 `Z1,Z2 ∽ N(0, 1)`,即 `Z1,Z2` 服从标准正态分布。 + + +# 降维 + +TODO +## SVD + +## PCA +- + +## t-SNE +- 在高维中接近,在低维中也接近 +- 目标函数:KL 散度(非对称) + +## Reference +- [Visualizing MNIST: An Exploration of Dimensionality Reduction](http://colah.github.io/posts/2014-10-Visualizing-MNIST/) - colah's blog \ No newline at end of file diff --git "a/A-\346\234\272\345\231\250\345\255\246\344\271\240/C-\344\270\223\351\242\230-\351\233\206\346\210\220\345\255\246\344\271\240.md" "b/A-\346\234\272\345\231\250\345\255\246\344\271\240/C-\344\270\223\351\242\230-\351\233\206\346\210\220\345\255\246\344\271\240.md" new file mode 100644 index 00000000..c6c89237 --- /dev/null +++ "b/A-\346\234\272\345\231\250\345\255\246\344\271\240/C-\344\270\223\351\242\230-\351\233\206\346\210\220\345\255\246\344\271\240.md" @@ -0,0 +1,129 @@ +ML-专题-集成学习 +=== + +Index +--- + + +- [集成学习基本问题](#集成学习基本问题) + - [集成学习的基本思想](#集成学习的基本思想) + - [集成学习为什么有效?](#集成学习为什么有效) + - [集成学习的基本策略](#集成学习的基本策略) + - [Boosting 方法](#boosting-方法) + - [Bagging 方法(Booststrap AGGregatING)](#bagging-方法booststrap-aggregating) + - [Stacking 方法](#stacking-方法) + - [为什么使用决策树作为基学习器?](#为什么使用决策树作为基学习器) + - [为什么不稳定的学习器更适合作为基学习器?](#为什么不稳定的学习器更适合作为基学习器) + - [还有哪些模型也适合作为基学习器?](#还有哪些模型也适合作为基学习器) + - [Bagging 方法中能使用线性分类器作为基学习器吗? Boosting 呢?](#bagging-方法中能使用线性分类器作为基学习器吗-boosting-呢) + - [Boosting/Bagging 与 偏差/方差 的关系](#boostingbagging-与-偏差方差-的关系) +- [AdaBoost 算法](#adaboost-算法) +- [GBDT 算法](#gbdt-算法) + + + +## 集成学习基本问题 +- 集成学习的核心是将多个 + +### 集成学习的基本思想 +- 结合多个学习器组合成一个性能更好的学习器 + +### 集成学习为什么有效? +- 不同的模型通常会在**测试集**上产生不同的误差;如果成员的误差是独立的,集成模型将显著地比其成员表现更好。 + +### 集成学习的基本策略 + +#### Boosting 方法 +- 基于**串行策略**:基学习器之间存在依赖关系,新的学习器需要根据上一个学习器生成。 +- **基本思路**: + - 先从**初始训练集**训练一个基学习器;初始训练集中各样本的权重是相同的; + - 根据上一个基学习器的表现,**调整样本权重**,使分类错误的样本得到更多的关注; + - 基于调整后的样本分布,训练下一个基学习器; + - 测试时,对各基学习器**加权**得到最终结果 +- **特点**: + - 每次学习都会使用全部训练样本 +- **代表算法**: + - [AdaBoost 算法](#adaboost-算法) + - [GBDT 算法](#gbdt-算法) + +#### Bagging 方法(Booststrap AGGregatING) +- 基于**并行策略**:基学习器之间不存在依赖关系,可同时生成。 +- **基本思路**: + - 利用**自助采样法**对训练集随机采样,重复进行 `T` 次; + - 基于每个采样集训练一个基学习器,并得到 `T` 个基学习器; + - 预测时,集体**投票决策****。 + > **自助采样法**:对 m 个样本的训练集,有放回的采样 m 次;此时,样本在 m 次采样中始终没被采样的概率约为 `0.368`,即每次自助采样只能采样到全部样本的 `63%` 左右。 +
+ +- **特点**: + - 训练每个基学习器时只使用一部分样本; + - 偏好**不稳定**的学习器作为基学习器; + > 所谓不稳定的学习器,指的是对**样本分布**较为敏感的学习器。 + +#### Stacking 方法 +- 基于**串行策略**:初级学习器与次级学习器之间存在依赖关系,初学习器的输出作为次级学习器的输入。 +- **基本思路**: + - 先从初始训练集训练 `T` 个**不同的初级学习器**; + - 利用每个初级学习器的**输出**构建一个**次级数据集**,该数据集依然使用初始数据集的标签; + - 根据新的数据集训练**次级学习器**; + - **多级学习器**的构建过程类似。 +> 周志华-《机器学习》中没有将 Stacking 方法当作一种集成策略,而是作为一种**结合策略**,比如**加权平均**和**投票**都属于结合策略。 + +- 为了降低过拟合的风险,一般会利用**交叉验证**的方法使不同的初级学习器在**不完全相同的子集**上训练 + ```tex + 以 k-折交叉验证为例: + - 初始训练集 D={(x_i, y_i)} 被划分成 D1, D2, .., Dk; + - 记 h_t 表示第 t 个学习器,并在除 Dj 外的数据上训练; + - 当 h_t 训练完毕后,有 z_it = h_t(x_i); + - T 个初级学习器在 x_i 上共产生 T 个输出; + - 这 T 个输出共同构成第 i 个次级训练数据 z_i = (z_i1, z_i2, ..., z_iT),标签依然为 y_i; + - 在 T 个初级学习器都训练完毕后,得到次级训练集 D'={(z_i, y_i)} + ``` + +### 为什么使用决策树作为基学习器? +- **类似问题** + - 基学习器有什么特点? + - 基学习器有什么要求? + +- 使用决策树作为基学习器的原因: + ```tex + (1). 决策树的表达能力和泛化能力,可以通过剪枝快速调整; + (2). 决策树可以方便地将**样本的权重**整合到训练过程中; + (3). 决策树是一种**不稳定**的学习器; + 所谓不稳定,指的是数据样本的扰动会对决策树的结果产生较大的影响; + ``` + - 后两点分别适合 Boosting 策略和 Bagging 策略;所以它们一般都使用决策树作为基学习器。 + +#### 为什么不稳定的学习器更适合作为基学习器? +- 不稳定的学习器容易受到**样本分布**的影响(方差大),很好的引入了**随机性**;这有助于在集成学习(特别是采用 **Bagging** 策略)中提升模型的**泛化能力**。 +- 为了更好的引入随机性,有时会随机选择一个**属性子集**中的最优分裂属性,而不是全局最优(**随机森林**) + +#### 还有哪些模型也适合作为基学习器? +- **神经网络** + - 神经网络也属于**不稳定**的学习器; + - 此外,通过调整神经元的数量、网络层数,连接方式初始权重也能很好的引入随机性和改变模型的表达能力和泛化能力。 + +#### Bagging 方法中能使用线性分类器作为基学习器吗? Boosting 呢? +- Bagging 方法中**不推荐** + - 线性分类器都属于稳定的学习器(方差小),对数据不敏感; + - 甚至可能因为 Bagging 的采样,导致在训练中难以收敛,增大集成分类器的**偏差** +- Boosting 方法中可以使用 + - Boosting 方法主要通过降低**偏差**的方式来提升模型的性能,而线性分类器本身具有方差小的特点,所以两者有一定相性 + - XGBoost 中就支持以线性分类器作为基学习器。 + +### Boosting/Bagging 与 偏差/方差 的关系 +> ./机器学习基础/[偏差与方差](./ML-A-机器学习基础.md#偏差与方差) + +- 简单来说,**Boosting** 能提升弱分类器性能的原因是降低了**偏差**;**Bagging** 则是降低了**方差**; +- **Boosting** 方法: + - Boosting 的**基本思路**就是在不断减小模型的**训练误差**(拟合残差或者加大错类的权重),加强模型的学习能力,从而减小偏差; + - 但 Boosting 不会显著降低方差,因为其训练过程中各基学习器是强相关的,缺少独立性。 +- **Bagging** 方法: + - 对 `n` 个**独立不相关的模型**预测结果取平均,方差是原来的 `1/n`; + - 假设所有基分类器出错的概率是独立的,**超过半数**基分类器出错的概率会随着基分类器的数量增加而下降。 +- 泛化误差、偏差、方差、过拟合、欠拟合、模型复杂度(模型容量)的关系图: +
+ +## AdaBoost 算法 + +## GBDT 算法 \ No newline at end of file diff --git "a/A-\346\267\261\345\272\246\345\255\246\344\271\240/A-\346\267\261\345\272\246\345\255\246\344\271\240\345\237\272\347\241\200.md" "b/A-\346\267\261\345\272\246\345\255\246\344\271\240/A-\346\267\261\345\272\246\345\255\246\344\271\240\345\237\272\347\241\200.md" new file mode 100644 index 00000000..b67839ce --- /dev/null +++ "b/A-\346\267\261\345\272\246\345\255\246\344\271\240/A-\346\267\261\345\272\246\345\255\246\344\271\240\345\237\272\347\241\200.md" @@ -0,0 +1,393 @@ +深度学习基础 +=== + +相关专题 +--- +- [《深度学习》整理](./《深度学习》整理) +- [CNN 专题](./B-专题-CNN) +- [RNN 专题](./B-专题-RNN) +- [优化算法专题](./C-专题-优化算法) + - **随机梯度下降** + - **动量**算法 + - **自适应学习率**算法 + - 基于**二阶梯度**的优化算法 + +Index +--- + + +- [过拟合与欠拟合](#过拟合与欠拟合) + - [降低过拟合风险的方法](#降低过拟合风险的方法) + - [降低欠拟合风险的方法](#降低欠拟合风险的方法) +- [反向传播算法](#反向传播算法) + - [反向传播的作用/目的/本质](#反向传播的作用目的本质) + - [反向传播的公式推导](#反向传播的公式推导) +- [激活函数](#激活函数) + - [激活函数的作用——为什么要使用非线性激活函数?](#激活函数的作用为什么要使用非线性激活函数) + - [常见的激活函数](#常见的激活函数) + - [整流线性单元 `ReLU`](#整流线性单元-relu) + - [`ReLU` 的拓展](#relu-的拓展) + - [`sigmoid` 与 `tanh`](#sigmoid-与-tanh) + - [其他激活函数](#其他激活函数) + - [`ReLU` 相比 `sigmoid` 的优势 (3)](#relu-相比-sigmoid-的优势-3) +- [正则化](#正则化) + - [Batch Normalization(批标准化)](#batch-normalization批标准化) + - [动机](#动机) + - [基本原理](#基本原理) + - [BN 在训练和测试时分别是怎么做的?](#bn-在训练和测试时分别是怎么做的) + - [为什么训练时不采用移动平均?](#为什么训练时不采用移动平均) + - [相关阅读](#相关阅读) + - [L1/L2 范数正则化](#l1l2-范数正则化) + - [L1/L2 范数的作用、异同](#l1l2-范数的作用异同) + - [为什么 L1 和 L2 正则化可以防止过拟合?](#为什么-l1-和-l2-正则化可以防止过拟合) + - [为什么 L1 正则化可以产生稀疏权值,而 L2 不会?](#为什么-l1-正则化可以产生稀疏权值而-l2-不会) + - [Dropout](#dropout) + - [Bagging 集成方法](#bagging-集成方法) + - [Dropout 策略](#dropout-策略) + - [Dropout 与 Bagging 的不同](#dropout-与-bagging-的不同) +- [深度学习实践](#深度学习实践) + - [参数初始化](#参数初始化) + + + +# 过拟合与欠拟合 +> 《深度学习》 5.2 容量、过拟合和欠拟合 + +- **欠拟合**指模型不能在**训练集**上获得足够低的**训练误差**; +- **过拟合**指模型的**训练误差**与**测试误差**(泛化误差)之间差距过大; + - 反映在**评价指标**上,就是模型在训练集上表现良好,但是在测试集和新数据上表现一般(**泛化能力差**); + +## 降低过拟合风险的方法 +> 所有为了**减少测试误差**的策略统称为**正则化方法**,这些方法可能会以增大训练误差为代价。 + +- **数据增强** + - 图像:平移、旋转、缩放 + - 利用**生成对抗网络**(GAN)生成新数据 + - NLP:利用机器翻译生成新数据 +- **降低模型复杂度** + - 神经网络:减少网络层、神经元个数 + - 决策树:降低树的深度、剪枝 + - ... +- **权值约束**(添加正则化项) + - L1 正则化 + - L2 正则化 +- **集成学习** + - 神经网络:Dropout + - 决策树:随机森林、GBDT +- **提前终止** + +## 降低欠拟合风险的方法 +- 加入新的特征 + - 交叉特征、多项式特征、... + - 深度学习:因子分解机、Deep-Crossing、自编码器 +- 增加模型复杂度 + - 线性模型:添加高次项 + - 神经网络:增加网络层数、神经元个数 +- 减小正则化项的系数 + - 添加正则化项是为了限制模型的学习能力,减小正则化项的系数则可以放宽这个限制 + - 模型通常更倾向于更大的权重,更大的权重可以使模型更好的拟合数据 + + +# 反向传播算法 + +## 反向传播的作用/目的/本质 +- **反向传播概述**: + + **梯度下降法**中需要利用损失函数对所有参数的梯度来寻找局部最小值点; + + 而**反向传播算法**就是用于计算该梯度的具体方法,其本质是利用**链式法则**对每个参数求偏导。 + +## 反向传播的公式推导 +> 数学/深度学习的核心/[反向传播的 4 个基本公式](../C-数学/B-深度学习的核心#反向传播的-4-个基本公式) +- 可以用 4 个公式总结反向传播的过程 + + **标量形式**: + + [![](../_assets/公式_20180705190236.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{\partial&space;C}{\partial&space;{\color{Red}&space;a_j^{(L)}}}=\frac{\partial&space;C({\color{Red}&space;a_j^{(L)}},y_j)}{\partial&space;{\color{Red}&space;a_j^{(L)}}}&space;\end{aligned}) + + [![](../_assets/公式_20180705134851.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{\partial&space;C}{{\color{Red}&space;\partial&space;a_j^{(l)}}}={\color{Teal}\sum_{k=0}^{n_l-1}}&space;\frac{\partial&space;z_k^{(l+1)}}{{\color{Red}&space;\partial&space;a_j^{(l)}}}&space;\frac{{\color{Blue}&space;\partial&space;a_k^{(l+1)}}}{\partial&space;z_k^{(l+1)}}&space;\frac{\partial&space;C}{{\color{Blue}&space;\partial&space;a_k^{(l+1)}}}&space;\end{aligned}) + + [![](../_assets/公式_20180705154543.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{\partial&space;C}{{\color{Magenta}&space;\partial&space;w_{j,k}^{(l)}}}=\frac{\partial&space;z_j^{(l)}}{{\color{Magenta}&space;\partial&space;w_{j,k}^{(l)}}}\frac{{\color{Red}\partial&space;a_j^{(l)}}}{\partial&space;z_j^{(l)}}\frac{\partial&space;C}{{\color{Red}\partial&space;a_j^{(l)}}}&space;\end{aligned}) + + [![](../_assets/公式_20180705154650.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{\partial&space;C}{{\color{Magenta}&space;\partial&space;b_{j}^{(l)}}}=\frac{\partial&space;z_j^{(l)}}{{\color{Magenta}&space;\partial&space;b_{j}^{(l)}}}\frac{{\color{Red}\partial&space;a_j^{(l)}}}{\partial&space;z_j^{(l)}}\frac{\partial&space;C}{{\color{Red}\partial&space;a_j^{(l)}}}&space;\end{aligned}) + + > 上标 `(l)` 表示网络的层,`(L)` 表示输出层(最后一层);下标 `j` 和 `k` 指示神经元的位置;`w_jk` 表示 `l` 层的第 `j` 个神经元与`(l-1)`层第 `k` 个神经元连线上的权重 + +- **符号说明**,其中: + - `(w,b)` 为网络参数:权值和偏置 + - `z` 表示上一层激活值的线性组合 + - `a` 即 "activation",表示每一层的激活值,上标`(l)`表示所在隐藏层,`(L)`表示输出层 + - `C` 表示激活函数,其参数为神经网络输出层的激活值`a^(L)`,与样本的标签`y` + + ![](../_assets/TIM截图20180704193955.png) + +- 以 **均方误差(MSE)** 损失函数为例,有 + + [![](../_assets/公式_20180705190536.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{\partial&space;C}{\partial&space;{\color{Red}&space;a_j^{(L)}}}&=\frac{\partial&space;C({\color{Red}&space;a_j^{(L)}},y_j)}{\partial&space;{\color{Red}&space;a_j^{(L)}}}&space;\\&space;&=\frac{\partial&space;\left&space;(&space;\frac{1}{2}({\color{Red}a_j^{(L)}}-y_j)^2&space;\right&space;)&space;}{\partial&space;{\color{Red}a_j^{(L)}}}={\color{Red}a_j^{(L)}}-y&space;\end{aligned}) + +- Nielsen 的课程中提供了另一种更利于计算的表述,本质上是一样的。 + + ![](../_assets/TIM截图20180705162841.png) + + > [The four fundamental equations behind backpropagation](http://neuralnetworksanddeeplearning.com/chap2.html#the_four_fundamental_equations_behind_backpropagation) + + +# 激活函数 + +## 激活函数的作用——为什么要使用非线性激活函数? +- 使用**激活函数**的目的是为了向网络中加入**非线性因素**;加强网络的表示能力,解决**线性模型**无法解决的问题 + > [神经网络激励函数的作用是什么?有没有形象的解释?](https://www.zhihu.com/question/22334626) - 知乎 + +**为什么加入非线性因素能够加强网络的表示能力?——神经网络的万能近似定理** +- 神经网络的万能近似定理认为主要神经网络具有至少一个非线性隐藏层,那么只要给予网络足够数量的隐藏单元,它就可以以任意的精度来近似任何**从一个有限维空间到另一个有限维空间**的函数。 +- 如果不使用非线性激活函数,那么每一层输出都是上层输入的**线性组合**; + + 此时无论网络有多少层,其整体也将是线性的,这会导致失去万能近似的性质 + > 《深度学习》 6.4.1 万能近似性质和深度; +- 但仅**部分层是纯线性**是可以接受的,这有助于**减少网络中的参数**。 + > 《深度学习》 6.3.3 其他隐藏单元 + +## 常见的激活函数 +> 《深度学习》 6.3 隐藏单元 + +### 整流线性单元 `ReLU` +- ReLU 通常是激活函数较好的默认选择 + +
+
+ +#### `ReLU` 的拓展 +- `ReLU` 及其扩展都基于以下公式: + +
+ + 当 `α=0` 时,即标准的线性整流单元 +- **绝对值整流**(absolute value rectification) + + 固定 `α = -1`,此时整流函数即**绝对值函数** `g(z)=|z|` + +- **渗漏整流线性单元**(Leaky ReLU, Maas et al., 2013) + + 固定 `α` 为一个小值,比如 0.01 + +- **参数化整流线性单元**(parametric ReLU, PReLU, He et al., 2015) + + 将 `α` 作为一个可学习的参数 + +- **`maxout` 单元** (Goodfellow et al., 2013a) + + `maxout` 单元 进一步扩展了 `ReLU`,它是一个可学习的 `k` 段函数 + + **Keras 简单实现** + ``` + # input shape: [n, input_dim] + # output shape: [n, output_dim] + W = init(shape=[k, input_dim, output_dim]) + b = zeros(shape=[k, output_dim]) + output = K.max(K.dot(x, W) + b, axis=1) + ``` + > 参数数量是普通全连接层的 k 倍 + >> [深度学习(二十三)Maxout网络学习](https://blog.csdn.net/hjimce/article/details/50414467) - CSDN博客 + +### `sigmoid` 与 `tanh` +- `sigmoid(z)`,常记作 `σ(z)`; +- `tanh(z)` 的图像与 `sigmoid(z)` 大致相同,区别是**值域**为 `(-1, 1)` + +
+
+ +### 其他激活函数 +> 很多未发布的非线性激活函数也能表现的很好,但没有比流行的激活函数表现的更好。比如使用 `cos` 也能在 MNIST 任务上得到小于 1% 的误差。通常新的隐藏单元类型只有在被明确证明能够提供显著改进时才会被发布。 + +- **线性激活函数**: + + 如果神经网络的每一层都由线性变换组成,那么网络作为一个整体也将是线性的,这会导致失去万能近似的性质。但是,仅**部分层是纯线性**是可以接受的,这可以帮助**减少网络中的参数**。 + +- **softmax**: + + softmax 单元常作为网络的输出层,它很自然地表示了具有 k 个可能值的离散型随机变量的概率分布。 + +- **径向基函数(radial basis function, RBF)**: + +
+ + 在神经网络中很少使用 RBF 作为激活函数,因为它对大部分 x 都饱和到 0,所以很难优化。 + +- **softplus**: + + `softplus` 是 `ReLU` 的平滑版本。 + +
+
+ + 通常不鼓励使用 softplus 函数,大家可能希望它具有优于整流线性单元的性质,但根据经验来看,它并没有。 + > (Glorot et al., 2011a) 比较了这两者,发现 ReLU 的结果更好。 + +- **硬双曲正切函数(hard tanh)**: + +
+ + 它的形状和 tanh 以及整流线性单元类似,但是不同于后者,它是有界的。 + +## `ReLU` 相比 `sigmoid` 的优势 (3) + +1. **避免梯度消失***** + - `sigmoid`函数在输入取绝对值非常大的正值或负值时会出现**饱和**现象——在图像上表现为变得很平,此时函数会对输入的微小变化不敏感——从而造成梯度消失; + - `ReLU` 的导数始终是一个常数——负半区为 0,正半区为 1——所以不会发生梯度消失现象 +2. **减缓过拟合**** + - `ReLU` 在负半区的输出为 0。一旦神经元的激活值进入负半区,那么该激活值就不会产生梯度/不会被训练,造成了网络的稀疏性——**稀疏激活** + - 这有助于减少参数的相互依赖,缓解过拟合问题的发生 +3. **加速计算*** + - `ReLU` 的求导不涉及浮点运算,所以速度更快 + +> 总结自知乎两个答案 [Ans1](https://www.zhihu.com/question/52020211/answer/152378276) & [Ans2](https://www.zhihu.com/question/29021768/answer/43488153) + +**为什么 ReLU 不是全程可微/可导也能用于基于梯度的学习?** +- 虽然从数学的角度看 ReLU 在 0 点不可导,因为它的左导数和右导数不相等; +- 但是在实现时通常会返回左导数或右导数的其中一个,而不是报告一个导数不存在的错误。从而避免了这个问题 + + +# 正则化 + +## Batch Normalization(批标准化) +- BN 是一种**正则化**方法(减少泛化误差),主要作用有: + - **加速网络的训练**(缓解梯度消失,支持更大的学习率) + - **防止过拟合** + - 降低了**参数初始化**的要求。 + +### 动机 +- **训练的本质是学习数据分布**。如果训练数据与测试数据的分布不同会**降低**模型的**泛化能力**。因此,应该在开始训练前对所有输入数据做归一化处理。 +- 而在神经网络中,因为**每个隐层**的参数不同,会使下一层的输入发生变化,从而导致每一批数据的分布也发生改变;**致使**网络在每次迭代中都需要拟合不同的数据分布,增大了网络的训练难度与**过拟合**的风险。 + +### 基本原理 +- BN 方法会针对**每一批数据**,在**网络的每一层输入**之前增加**归一化**处理,使输入的均值为 `0`,标准差为 `1`。**目的**是将数据限制在统一的分布下。 +- 具体来说,针对每层的第 `k` 个神经元,计算**这一批数据**在第 `k` 个神经元的均值与标准差,然后将归一化后的值作为该神经元的激活值。 +
+ +- BN 可以看作在各层之间加入了一个新的计算层,**对数据分布进行额外的约束**,从而增强模型的泛化能力; +- 但同时 BN 也降低了模型的拟合能力,破坏了之前学到的**特征分布**; +- 为了**恢复数据的原始分布**,BN 引入了一个**重构变换**来还原最优的输入数据分布 +
+ + 其中 `γ` 和 `β` 为可训练参数。 + +**小结** +- 以上过程可归纳为一个 **`BN(x)` 函数**: +
+ + 其中 +
+ +- **完整算法**: +
+ +### BN 在训练和测试时分别是怎么做的? +- **训练时**每次会传入一批数据,做法如前述; +- 当**测试**或**预测时**,每次可能只会传入**单个数据**,此时模型会使用**全局统计量**代替批统计量; + - 训练每个 batch 时,都会得到一组`(均值,方差)`; + - 所谓全局统计量,就是对这些均值和方差求其对应的数学期望; + - 具体计算公式为: + +
+ + > 其中 `μ_i` 和 `σ_i` 分别表示第 i 轮 batch 保存的均值和标准差;`m` 为 batch_size,系数 `m/(m-1)` 用于计算**无偏方差估计** + >> 原文称该方法为**移动平均**(moving averages) + +- 此时,`BN(x)` 调整为: + +
+ +- **完整算法**: +
+ +#### 为什么训练时不采用移动平均? +> 群里一位同学的面试题 +- 使用 BN 的目的就是为了保证每批数据的分布稳定,使用全局统计量反而违背了这个初衷; +- BN 的作者认为在训练时采用移动平均可能会与梯度优化存在冲突; + > 【**原文**】"It is natural to ask whether we could simply **use the moving averages** µ, σ to perform the normalization **during training**, since this would remove the dependence of the normalized activations on the other example in the minibatch. This, however, has been observed to lead to the model blowing up. As argued in [6], such use of moving averages would cause the gradient optimization and the normalization to counteract each other. For example, the gradient step may increase a bias or scale the convolutional weights, in spite of the fact that the normalization would cancel the effect of these changes on the loss. This would result in unbounded growth of model parameters without actually improving the loss. It is thus crucial to use the minibatch moments, and to backpropagate through them." + >> [1702.03275] [Batch Renormalization](https://arxiv.org/abs/1702.03275) + +### 相关阅读 +- [深入理解Batch Normalization批标准化 - 郭耀华](https://www.cnblogs.com/guoyaohua/p/8724433.html) - 博客园 +- [深度学习中批归一化的陷阱](http://ai.51cto.com/art/201705/540230.htm) - 51CTO + + +## L1/L2 范数正则化 +> 《深度学习》 7.1.1 L2 参数正则化 & 7.1.2 - L1 参数正则化 +> +> [机器学习中正则化项L1和L2的直观理解](https://blog.csdn.net/jinping_shi/article/details/52433975) - CSDN博客 + +### L1/L2 范数的作用、异同 +**相同点** +- 限制模型的学习能力——通过限制参数的规模,使模型偏好于**权值较小**的目标函数,防止过拟合。 + +**不同点** +- **L1 正则化**可以产生更**稀疏**的权值矩阵,可以用于特征选择,同时一定程度上防止过拟合;**L2 正则化**主要用于防止模型过拟合 +- **L1 正则化**适用于特征之间有关联的情况;**L2 正则化**适用于特征之间没有关联的情况。 + +### 为什么 L1 和 L2 正则化可以防止过拟合? +- L1 & L2 正则化会使模型偏好于更小的权值。 +- 更小的权值意味着**更低的模型复杂度**;添加 L1 & L2 正则化相当于为模型添加了某种**先验**,限制了参数的分布,从而降低了模型的复杂度。 +- 模型的复杂度降低,意味着模型对于噪声与异常点的抗干扰性的能力增强,从而提高模型的泛化能力。——直观来说,就是对训练数据的拟合刚刚好,不会过分拟合训练数据(比如异常点,噪声)——**奥卡姆剃刀原理** + +### 为什么 L1 正则化可以产生稀疏权值,而 L2 不会? +- 对目标函数添加范数正则化,训练时相当于在范数的约束下求目标函数 `J` 的最小值 +- 带有**L1 范数**(左)和**L2 范数**(右)约束的二维图示 + + ![](../_assets/TIM截图20180608171710.png) + ![](../_assets/TIM截图20180608172312.png) + - 图中 `J` 与 `L1` 首次相交的点即是最优解。`L1` 在和每个坐标轴相交的地方都会有“**顶点**”出现,多维的情况下,这些顶点会更多;在顶点的位置就会产生稀疏的解。而 `J` 与这些“顶点”相交的机会远大于其他点,因此 `L1` 正则化会产生稀疏的解。 + - `L2` 不会产生“**顶点**”,因此 `J` 与 `L2` 相交的点具有稀疏性的概率就会变得非常小。 + +## Dropout +> 《深度学习》 7.12 Dropout +### Bagging 集成方法 +- **集成方法**的主要想法是分别训练不同的模型,然后让所有模型**表决**最终的输出。 + + 集成方法奏效的原因是不同的模型**通常不会**在测试集上产生相同的误差。 + + 集成模型能至少与它的任一成员表现得一样好。**如果成员的误差是独立的**,集成将显著提升模型的性能。 + +- **Bagging** 是一种集成策略——具体来说,Bagging 涉及构造 k 个**不同的数据集**。 + + 每个数据集从原始数据集中**重复采样**构成,和原始数据集具有**相同数量**的样例——这意味着,每个数据集以高概率缺少一些来自原始数据集的例子,还包含若干重复的例子 + + > 更具体的,如果采样所得的训练集与原始数据集大小相同,那所得数据集中大概有原始数据集 `2/3` 的实例 + +**集成方法与神经网络**: +- 神经网络能找到足够多的不同的解,意味着他们可以从模型平均中受益——即使所有模型都在同一数据集上训练。 + + 神经网络中**随机初始化**的差异、**批训练数据**的随机选择、**超参数**的差异等**非确定性**实现往往足以使得集成中的不同成员具有**部分独立的误差**。 + +### Dropout 策略 +- 简单来说,Dropout 通过**参数共享**提供了一种廉价的 Bagging 集成近似—— Dropout 策略相当于集成了包括所有从基础网络除去部分单元后形成的子网络。 +- 通常,**隐藏层**的采样概率为 `0.5`,**输入**的采样概率为 `0.8`;超参数也可以采样,但其采样概率一般为 `1` + +
+ +**权重比例推断规则** +- 权重比例推断规则的目的是确保在测试时一个单元的期望总输入与在训练时该单元的期望总输入大致相同。 +- 实践时,如果使用 `0.5` 的采样概率,**权重比例规则**相当于在训练结束后**将权重乘 `0.5`**,然后像平常一样使用模型;等价的,另一种方法是**在训练时**将单元的状态乘 2。 + +#### Dropout 与 Bagging 的不同 +- 在 Bagging 的情况下,所有模型都是独立的;而在 Dropout 的情况下,所有模型**共享参数**,其中每个模型继承父神经网络参数的不同子集。 +- 在 Bagging 的情况下,每一个模型都会在其相应训练集上训练到收敛。而在 Dropout 的情况下,通常大部分模型都没有显式地被训练;取而代之的是,在单个步骤中我们训练一小部分的子网络,参数共享会使得剩余的子网络也能有好的参数设定。 + + +# 深度学习实践 + +## 参数初始化 +- 一般使用服从的**高斯分布**(`mean=0, stddev=1`)或**均匀分布**的随机值作为**权重**的初始化参数;使用 `0` 作为**偏置**的初始化参数 +- 一些**启发式**方法会根据**输入与输出的单元数**来决定初始值的范围 + + 比如 `glorot_uniform` 方法 (Glorot and Bengio, 2010) + +
+ + > Keras 全连接层默认的**权重**初始化方法 + +- **其他初始化方法** + - 随机正交矩阵(Orthogonal) + - 截断高斯分布(Truncated normal distribution) + +- Keras 提供的参数初始化方法:Keras/[Initializers](https://keras.io/initializers/) diff --git "a/A-\346\267\261\345\272\246\345\255\246\344\271\240/A-\346\267\261\345\272\246\345\255\246\344\271\240\345\256\236\350\267\265.md" "b/A-\346\267\261\345\272\246\345\255\246\344\271\240/A-\346\267\261\345\272\246\345\255\246\344\271\240\345\256\236\350\267\265.md" new file mode 100644 index 00000000..b12d1550 --- /dev/null +++ "b/A-\346\267\261\345\272\246\345\255\246\344\271\240/A-\346\267\261\345\272\246\345\255\246\344\271\240\345\256\236\350\267\265.md" @@ -0,0 +1,35 @@ +深度学习实践 +=== + +Index +--- + + +- [加速训练的方法](#加速训练的方法) + - [内部方法](#内部方法) + - [外部方法](#外部方法) + + + +## 加速训练的方法 + +### 内部方法 + +- 网络结构 + - 比如 CNN 与 RNN,前者更适合并行架构 +- 优化算法的改进:动量、自适应学习率 + > [./专题-优化算法](./C-专题-优化算法) +- 减少参数规模 + - 比如使用 GRU 代替 LSTM +- 参数初始化 + - Batch Normalization + + +### 外部方法 +> [深度学习训练加速方法](https://blog.csdn.net/xuqiaobo/article/details/60769330) - CSDN博客 +- GPU 加速 +- 数据并行 +- 模型并行 +- 混合数据并行与模型并行 +- CPU 集群 +- GPU 集群 \ No newline at end of file diff --git "a/A-\346\267\261\345\272\246\345\255\246\344\271\240/B-\344\270\223\351\242\230-CNN.md" "b/A-\346\267\261\345\272\246\345\255\246\344\271\240/B-\344\270\223\351\242\230-CNN.md" new file mode 100644 index 00000000..727d3a1a --- /dev/null +++ "b/A-\346\267\261\345\272\246\345\255\246\344\271\240/B-\344\270\223\351\242\230-CNN.md" @@ -0,0 +1,262 @@ +DL-专题-CNN +=== + +Index +--- + + +- [CNN 的基本特征](#cnn-的基本特征) + - [动机](#动机) + - [意义](#意义) +- [卷积的内部实现](#卷积的内部实现) + - [Theano 中的实现](#theano-中的实现) + - [Caffe 中的实现](#caffe-中的实现) +- [卷积的反向传播](#卷积的反向传播) +- [卷积的结构](#卷积的结构) + - [卷积核的结构及其数量](#卷积核的结构及其数量) + - [基本卷积](#基本卷积) + - [转置卷积](#转置卷积) + - [空洞卷积](#空洞卷积) + - [可分离卷积](#可分离卷积) + - [Keras 实现](#keras-实现) +- [门卷积](#门卷积) + - [门卷积的作用](#门卷积的作用) + - [门卷积是如何防止梯度消失的](#门卷积是如何防止梯度消失的) +- [其他](#其他) + - [为什么使用 CNN 代替 RNN?](#为什么使用-cnn-代替-rnn) +- [Reference](#reference) + + + +## CNN 的基本特征 +- **稀疏交互**和**参数共享** + +### 动机 +- **局部特征**——卷积的核心思想 +- 平移等变性 + +### 意义 +- **提高统计效率** + - 当处理一张图像时,输入的图像可能包含成千上万个像素点,但是我们可以通过只占用几十到上百个像素点的核来检测一些局部但有意义的特征,例如图像的边缘。 +- **减少参数数量** + - 减少存储需求 + - 加速计算 + + +## 卷积的内部实现 + +### Theano 中的实现 +> [Convolution as a matrix operation](http://deeplearning.net/software/theano/tutorial/conv_arithmetic.html#convolution-as-a-matrix-operation) + +- 先把二维 `input` 展开成一维向量(`[in_h, in_w] -> [in_h * in_w]`);如果是一批 `inputs`,则依次堆叠为一个矩阵 `[N, in_h * in_w]`; +- 然后将 `kernel` 按 stride **循环**展开成一个**稀疏矩阵**; +
+ +- 然后将卷积的计算转化为**矩阵相乘**。 +
+ +### Caffe 中的实现 +> [High Performance Convolutional Neural Networks for Document Processing](https://hal.inria.fr/inria-00112631/document) (2006) + +- 先对 `inputs` 做 **im2col** 操作得到**输入矩阵**,再将 `kernel` 转化为**权值矩阵**,然后将两个矩阵相乘得到输出矩阵: +
+ + 其中 +
+ + - 其中 `o` 表示 out;`i` 表示 in;`k` 表示 kernel; + - 不包括 `batch_size` 的维度; + - `[o_height, o_width]` 的大小由 `[i_height, i_width]` 及 stride、padding 等参数共同决定。 + +- **im2col** 操作 + > 先将一个输入矩阵(图像),重叠地划分为多个**子矩阵**(子区域),对每个子矩阵序列化成向量,然后将所有子向量**纵向**拼接成另一个矩阵;如果存在多个输入矩阵,则进一步将新生成矩阵横向拼接,最终构成一个大矩阵 +
+ + > 这里可以看作是三张**单通道**图像,也可以看作是一张**三通道**的图像 + >> 更直观的图示 > [caffe im2col 详解](https://blog.csdn.net/mrhiuser/article/details/52672824) - CSDN博客 + >> + >> 具体的代码实现更复杂一些,因为这个图示中的操作并不能直接循环,具体请参考这篇 > [caffe源码深入学习6:超级详细的im2col绘图解析,分析caffe卷积操作的底层实现](https://blog.csdn.net/jiongnima/article/details/69736844) - CSDN博客 +- 完整的计算过程 +
+ + - 上半部分是卷积的直观操作,下半部分转换为矩阵乘法的操作 + - 原文:“**输入为 `N=3` 个 `3*3` 的特征矩阵;输出为 `M=2` 个 `2*2` 的特征矩阵;kernel 的形状为 `2*2`,数量为 `N*M = 6` 个**”; + > **个人认为原文的表述不正确**:如果把 `N` 看作 batch_size 的话,输出 `M` 也应该是 3 才对。 + > + > 更正确的说法应该是“**输入为 1 个 `3*3*3` 的三通道特征矩阵;输出为 1 个 `2*2*2` 的双通道特征矩阵;kernel 的形状为 `2*2*3`,数量为 `2` 个**” + >> [卷积核的结构及其数量](#卷积核的结构及其数量) + - **注意**:因为是按照 stride 来切“子矩阵”,所以可能存在重复。 + +## 卷积的反向传播 +- 卷积的内部实现实际上是**矩阵相乘**, +- 因此,卷积的**反向传播**过程实际上跟普通的全连接是类似的 +
+ + > [High Performance Convolutional Neural Networks for Document Processing](https://hal.inria.fr/inria-00112631/document) + +**相关阅读** +- [CNN的反向传播](http://jermmy.xyz/2017/12/16/2017-12-16-cnn-back-propagation/) | Jermmy's Lazy Blog +- https://www.zhihu.com/question/56865789/answer/150785351 + +## 卷积的结构 +> [一文了解各种卷积结构原理及优劣](https://zhuanlan.zhihu.com/p/28186857) - 知乎 +> +> vdumoulin/[conv_arithmetic](https://github.com/vdumoulin/conv_arithmetic) - GitHUub + +### 卷积核的结构及其数量 + +**以 Conv2D 为例** +- Tensorflow 中构造 conv2d 层的参数形状为: +
+ + > `[k_height, k_width]` 表示卷积核的大小;`n_in` 表示“输入的通道数”,`n_out` 表示“输出的通道数” +- 通常在描述卷积核的大小时,只会说 `[k_height, k_width]` 这一部分;比如 “这一层使用的是 `3*3` 的卷积核”。 +- 但卷积核应该还包括**输入的通道数** `n_in`,即 `[k_height, k_width, n_in]` 这部分;从几何的角度来看,卷积核应该是一个“长方体”,而不是“长方形/矩形” +- 因此,卷积核的数量应该是 `n_out`,而不是 `n_in * n_out` + +### 基本卷积 + + + + + + + + + + + + + + + + + + + + + + + +
No padding, no stridesArbitrary padding, no stridesHalf padding, no stridesFull padding, no strides
No padding, stridesPadding, stridesPadding, strides (odd)
+ + + +### 转置卷积 +- 转置卷积(Transposed Convolution),又称反卷积(Deconvolution)、Fractionally Strided Convolution + > 反卷积的说法不够准确,数学上有定义真正的反卷积,两者的操作是不同的 +- 转置卷积是卷积的**逆过程**,如果把基本的卷积(+池化)看做“缩小分辨率”的过程,那么转置卷积就是“**扩充分辨率**”的过程。 + - 为了实现扩充的目的,需要对输入以某种方式进行**填充**。 +- 转置卷积与数学上定义的反卷积不同——在数值上,它不能实现卷积操作的逆过程。其内部实际上执行的是常规的卷积操作。 + - 转置卷积只是为了**重建**先前的空间分辨率,执行了卷积操作。 +- 虽然转置卷积并不能还原数值,但是用于**编码器-解码器结构**中,效果仍然很好。——这样,转置卷积可以同时实现图像的**粗粒化**和卷积操作,而不是通过两个单独过程来完成。 + + + + + + + + + + + + + + + + + + + + + + + + +
No padding, no strides, transposedArbitrary padding, no strides, transposedHalf padding, no strides, transposedFull padding, no strides, transposed
No padding, strides, transposedPadding, strides, transposedPadding, strides, transposed (odd)
+ +### 空洞卷积 +- 空洞卷积(Atrous Convolutions)也称扩张卷积(Dilated Convolutions)、膨胀卷积。 +

No padding, no strides.
+ +**空洞卷积的作用** +- 空洞卷积使 CNN 能够**捕捉更远的信息,获得更大的感受野**(NLP 中可理解为获取更长的上下文);同时不增加参数的数量,也不影响训练的速度。 +- 示例:Conv1D + 空洞卷积 +
+ + + +### 可分离卷积 +- 可分离卷积(separable convolution) +- TODO + +### Keras 实现 +- Keras 中通过在卷积层中加入参数 `dilation_rate`实现 + ```Python + Conv1D(filters=config.filters, + kernel_size=config.kernel_size, + dilation_rate=2) + ``` + TODO: 维度变化 + +## 门卷积 +> [卷积新用之语言模型](https://blog.csdn.net/stdcoutzyx/article/details/55004458) - CSDN博客 + +- 类似 LSTM 的过滤机制,实际上是卷积网络与**门限单元**(Gated Linear Unit)的组合 +- 核心公式 +
+ + + > 中间的运算符表示**逐位相乘**—— Tensorflow 中由 `tf.multiply(a, b)` 实现,其中 a 和 b 的 shape 要相同;后一个卷积使用`sigmoid`激活函数 + +- 一个门卷积 Block +
+ + > `W` 和 `V` 表明参数不共享 +- 实践中,为了防止梯度消失,还会在每个 Block 中加入残差 + +### 门卷积的作用 +- 减缓梯度消失 +- 解决语言顺序依存问题(? TODO) + +### 门卷积是如何防止梯度消失的 +- 因为公式中有一个卷积没有经过激活函数,所以对这部分求导是个常数,所以梯度消失的概率很小。 +- 如果还是担心梯度消失,还可以加入**残差**——要求输入输出的 shape 一致 +
+ + + 更直观的理解: +
+ + + 即信息以 `1-σ` 的概率直接通过,以 `σ` 的概率经过变换后通过——类似 GRU + > 因为`Conv1D(X)`没有经过激活函数,所以实际上它只是一个线性变化;因此与 `Conv1D(X) - X` 是等价的 + > + > [基于CNN的阅读理解式问答模型:DGCNN](https://kexue.fm/archives/5409#门机制) - 科学空间|Scientific Spaces + +## 其他 + +### 为什么使用 CNN 代替 RNN? +> [关于序列建模,是时候抛弃RNN和LSTM了](https://www.jiqizhixin.com/articles/041503) | 机器之心 [[原文]](https://towardsdatascience.com/the-fall-of-rnn-lstm-2d1594c74ce0) + +**RNN/LSTM 存在的问题(3)** +1. RNN 与目前的硬件加速技术不匹配 + > 训练 RNN 和 LSTM 非常困难,因为计算能力受到内存和带宽等的约束。简单来说,每个 LSTM 单元需要四个仿射变换,且每一个时间步都需要运行一次,这样的仿射变换会要求非常多的内存带宽。**添加更多的计算单元很容易,但添加更多的内存带宽却很难**——这与目前的硬件加速技术不匹配,一个可能的解决方案就是让计算在存储器设备中完成。 +1. RNN 容易发生**梯度消失**,包括 LSTM + > 在长期信息访问当前处理单元之前,需要按顺序地通过所有之前的单元。这意味着它很容易遭遇梯度消失问题;LSTM 一定程度上解决了这个问题,但 LSTM 网络中依然存在顺序访问的序列路径;实际上,现在这些路径甚至变得更加复杂 +1. **注意力机制模块**(记忆模块)的应用 + - 注意力机制模块可以同时**前向预测**和**后向回顾**。 + - **分层注意力编码器**(Hierarchical attention encoder) +
+ + - 分层注意力模块通过一个**层次结构**将过去编码向量**汇总**到一个**上下文向量**`C_t` ——这是一种更好的**观察过去信息**的方式(观点) + - **分层结构**可以看做是一棵**树**,其路径长度为 `logN`,而 RNN/LSTM 则相当于一个**链表**,其路径长度为 `N`,如果序列足够长,那么可能 `N >> logN` + > [放弃 RNN/LSTM 吧,因为真的不好用!望周知~](https://blog.csdn.net/heyc861221/article/details/80174475) - CSDN博客 + +**任务角度(1)** +1. 从任务本身考虑,我认为也是 CNN 更有利,LSTM 因为能记忆比较长的信息,所以在推断方面有不错的表现(直觉);但是在事实类问答中,并不需要复杂的推断,答案往往藏在一个 **n-gram 短语**中,而 CNN 能很好的对 n-gram 建模。 + + +## Reference +- [首次超越LSTM : Facebook 门卷积网络新模型能否取代递归模型?](http://www.dataguru.cn/article-10314-1.html) - 炼数成金 \ No newline at end of file diff --git "a/A-\346\267\261\345\272\246\345\255\246\344\271\240/B-\344\270\223\351\242\230-RNN.md" "b/A-\346\267\261\345\272\246\345\255\246\344\271\240/B-\344\270\223\351\242\230-RNN.md" new file mode 100644 index 00000000..680602c7 --- /dev/null +++ "b/A-\346\267\261\345\272\246\345\255\246\344\271\240/B-\344\270\223\351\242\230-RNN.md" @@ -0,0 +1,239 @@ +DL-专题-RNN +=== + +Index +--- + + +- [RNN 的基本结构](#rnn-的基本结构) +- [RNN 常见的几种设计模式(3)](#rnn-常见的几种设计模式3) +- [RNN 的反向传播(BPTT) TODO](#rnn-的反向传播bptt-todo) +- [RNN 相关问题](#rnn-相关问题) + - [RNN 相比前馈网络/CNN 有什么特点?](#rnn-相比前馈网络cnn-有什么特点) + - [RNN 为什么会出现梯度消失/梯度爆炸?](#rnn-为什么会出现梯度消失梯度爆炸) + - [RNN 中能否使用 `ReLU` 作为激活函数?](#rnn-中能否使用-relu-作为激活函数) + - [如果使用 `ReLU` 作为 RNN 的激活函数,应该注意什么?](#如果使用-relu-作为-rnn-的激活函数应该注意什么) + - [梯度爆炸的解决方法](#梯度爆炸的解决方法) + - [梯度消失的解决方法(针对 RNN)](#梯度消失的解决方法针对-rnn) +- [LSTM 相关问题](#lstm-相关问题) + - [LSTM 的内部结构](#lstm-的内部结构) + - [完整的 LSTM 前向传播公式](#完整的-lstm-前向传播公式) + - [LSTM 是如何实现长短期记忆的?(遗忘门和输入门的作用)](#lstm-是如何实现长短期记忆的遗忘门和输入门的作用) + - [LSTM 里各部分使用了不同的激活函数,为什么?可以使用其他激活函数吗?](#lstm-里各部分使用了不同的激活函数为什么可以使用其他激活函数吗) + - [窥孔机制](#窥孔机制) + - [GRU 与 LSTM 的关系](#gru-与-lstm-的关系) + - [完整的 GRU 前向传播公式](#完整的-gru-前向传播公式) + + + +## RNN 的基本结构 +- RNN 本质上是一个**递推函数** +
+ +- 考虑当前输入 `x(t)` +
+ +- 以上计算公式可展开为如下**计算图**(无输出单元) +
+ +- RNN 的**前向传播**公式 +
+ + > 一般 `h(0)` 会初始化为 0 向量;并使用 `tanh` 作为激活函数 `f` + +## RNN 常见的几种设计模式(3) + +**RNN 一般包括以下几种设计模式** +- **每个时间步都有输出,且隐藏单元之间有循环连接** + - 即通常所说 RNN + - 这种结构会在每个时间步产生一个输出,所以通常用于“**Seq2Seq**”任务中,比如序列标注、机器翻译等。这些任务通常都比较复杂。 +
+ +- **每个时间步都有输出,但是隐藏单元之间没有循环连接,只有当前时刻的输出到下个时刻的隐藏单元之间有循环连接** + - 这种模型的表示能力弱于第一种,但是它**更容易训练** + - 因为每个时间步可以与其他时间步单独训练,从而实现**并行化** + - 具体来说,就是使用 `y(t)` 代替 `o(t)` 输入下一个时间步。 +
+ +- **隐藏单元之间有循环连接,但只有最后一个时间步有输出** + - 忽略模式 1 中的中间输出,即可得到这种网络; + - 这种网络一般用于**概括序列**。具体来说,就是产生**固定大小的表示**,用于下一步处理; + - 在一些“**Seq2One**”中简单任务中,这种网络用的比较多;因为这些任务只需要关注序列的全局特征。 +
+ +> 其中前两种 RNN 分别被称为 Elman network 和 Jordan network;通常所说的 RNN 指的是前者 +>
+> +>> [Recurrent neural network](https://en.wikipedia.org/wiki/Recurrent_neural_network#Elman_networks_and_Jordan_networks) - Wikipedia + +## RNN 的反向传播(BPTT) TODO + + +## RNN 相关问题 + +### RNN 相比前馈网络/CNN 有什么特点? + +- **前馈网络/CNN 处理序列数据时存在的问题:** + - 一般的**前馈网络**,通常接受一个**定长**向量作为输入,然后输出一个定长的表示;它需要一次性接收所有的输入,因而忽略了序列中的顺序信息; + - **CNN** 在处理**变长序列**时,通过**滑动窗口+池化**的方式将输入转化为一个**定长的向量表示**,这样做可以捕捉到序列中的一些**局部特征**,但是很难学习到序列间的**长距离依赖**。 + +- **RNN 处理时序数据时的优势:** + - RNN 很适合处理序列数据,特别是带有**时序关系**的序列,比如文本数据; + - RNN 把**每一个时间步**中的信息编码到**状态变量**中,使网络具有一定的记忆能力,从而更好的理解序列信息。 + - 由于 RNN 具有对序列中时序信息的刻画能力,因此在处理序列数据时往往能得到更准确的结果。 + +- **展开后的 RNN**(**无输出**) +
+ + - 一个长度为 `T` 的 RNN 展开后,可以看做是一个 **T 层的前馈网络**;同时每一层都可以有**新的输入** + - 通过对当前输入 `x_t` 和上一层的隐状态 `h_{t-1}` 进行编码,**第 `t` 层的隐状态** `h_t` 记录了序列中前 `t` 个输入的信息。 + > 普通的前馈网络就像火车的**一节车厢**,只有一个入口,一个出口;而 RNN 相当于**一列火车**,有多节车厢接收**当前时间步**的输入信息并输出**编码后的状态信息**(包括**当前的状态和之前的所有状态**)。 + - **最后一层隐状态 `x_T`** 编码了整个序列的信息,因此可以看作**整个序列的压缩表示**。 + - 常见的文本分类任务中,将 `h_T` 通过一个 Softmax 层,即可获得作为每个类别的概率: + +
+ + 其中 + - `U` 为输入层到隐藏层的权重矩阵 + - `W` 为隐藏层从上一时刻到下一时刻的状态转移权重矩阵 + - `f` 为隐藏层激活函数,通常可选 `tanh` 或 `ReLU`; + - `g` 为输出层激活函数,可以采用 `Softmax`、`Sigmoid` 或线性函数(回归任务) + - 通常 `h_{-1}` 初始化为 0 向量。 + +### RNN 为什么会出现梯度消失/梯度爆炸? +- 最大步长为 `T` 的 RNN 展开后相当于一个**共享参数**的 T 层前馈网络 + + - RNN 的前向传播过程 +
+ + - 展开前一层 +
+ + - RNN 的梯度计算公式 +
+ + 其中 +
+ + > 上标 `(t)` 表示时间步,下标 `n` 表示隐藏层的单元数(维度);`diag()` 为对角矩阵 + +#### RNN 中能否使用 `ReLU` 作为激活函数? +> [RNN中为什么要采用tanh而不是ReLu作为激活函数?](https://www.zhihu.com/question/61265076) - 知乎 + +- 答案是肯定的。但是会存在一些问题。 + > 其实 ReLU 最早就是为了解决 RNN 中的**梯度消失**问题而设计的。 + +- 假设使用 `ReLU` 并始终处于**激活状态**(`a_{t-1} > 0`),则 `f(x) = x`,即 +
+ + - 按照以上的步骤继续展开,最终结果中将包含 `t` 个 `W` 连乘,如果 `W` 不是单位矩阵,最终结果将趋于 `0` 或无穷。 + - 因此,在 RNN 中使用 ReLU 应该注意使用**单位矩阵**来初始化权重矩阵。 + + > 为什么普通的前馈网络或 CNN 中不会出现这中现象? + >> 因为他们每一层的 `W` 不同,且在初始化时是独立同分布的,因此可以在一定程度相互抵消。即使多层之后一般也不会出现数值问题。 + +- **使用 `ReLU` 也不能完全避免 RNN 中的梯度消失/爆炸问题**,问题依然在于存在 `t` 个 `W` 的连乘项。 + + - 注意**最后一步**,假设所有神经元都处于**激活状态**,当 `ReLU` 作为 `f` 时,有 +
+ + - 可见**只要 `W` 不是单位矩阵**,还是可能会出现梯度消失/爆炸。 + +#### 如果使用 `ReLU` 作为 RNN 的激活函数,应该注意什么? +- 综上所述,RNN 因为每一个时间步都**共享参数**的缘故,容易出现**数值溢出问题** +- 因此,推荐的做法是将 `W` 初始化为**单位矩阵**。 +- 有实践证明,使用单位矩阵初始化 `W` 并使用 `ReLU` 作为激活函数在一些应用中,与 LSTM 有相似的结果 + > [RNN中为什么要采用tanh而不是ReLu作为激活函数? - chaopig 的回答](https://www.zhihu.com/question/61265076/answer/260492479) - 知乎 + +#### 梯度爆炸的解决方法 +- 梯度截断 + +#### 梯度消失的解决方法(针对 RNN) +- 残差结构 +- 门控机制(LSTM、GRU) + +## LSTM 相关问题 +> [Understanding LSTM Networks](http://colah.github.io/posts/2015-08-Understanding-LSTMs/) - colah's blog + +### LSTM 的内部结构 + +- LSTM 在传统 RNN 的基础上加入了**门控机制**来限制**信息的流动**。 +- LSTM (上)与传统 RNN(下) 的内部结构对比 +
+
+ +- 具体来说,LSTM 中加入了三个“门”:**遗忘门** `f`、**输入门** `i`、**输出门** `o`,以及一个内部记忆状态 `C` + - “**遗忘门 `f`**”控制前一步记忆状态中的信息有多大程度被遗忘; +
+ + - “**输入门 `i`**”控制当前计算的新状态以多大的程度更新到**记忆状态**中; +
+ + - “**记忆状态 `C`**”间的**状态转移**由输入门和遗忘门共同决定 +
+ + - “**输出门 `o`**”控制当前的输出有多大程度取决于当前的记忆状态 +
+ +#### 完整的 LSTM 前向传播公式 +
+ +> 其中运算符 `o`(`\circ`) 表示向量中的元素**按位相乘**;有的地方也使用符号 `⊙`(`\odot`)表示 + +### LSTM 是如何实现长短期记忆的?(遗忘门和输入门的作用) +- LSTM 主要通过**遗忘门**和**输入门**来实现长短期记忆。 + - 如果当前时间点的状态中没有重要信息,遗忘门 `f` 中各分量的值将接近 1(`f -> 1`);输入门 `i` 中各分量的值将接近 0(`i -> 0`);此时过去的记忆将会被保存,从而实现**长期记忆**; + - 如果当前时间点的状态中出现了重要信息,且之前的记忆不再重要,则 `f -> 0`,`i -> 1`;此时过去的记忆被遗忘,新的重要信息被保存,从而实现**短期记忆**; + - 如果当前时间点的状态中出现了重要信息,但旧的记忆也很重要,则 `f -> 1`,`i -> 1`。 + + +### LSTM 里各部分使用了不同的激活函数,为什么?可以使用其他激活函数吗? +- 在 LSTM 中,所有**控制门**都使用 sigmoid 作为激活函数(遗忘门、输入门、输出门); +- 在计算**候选记忆**或**隐藏状态**时,使用双曲正切函数 tanh 作为激活函数 + +**sigmoid 的“饱和”性** +- 所谓饱和性,即输入超过一定范围后,输出几乎不再发生明显变化了 +- sigmoid 的值域为 `(0, 1)`,符合**门控**的定义: + - 当输入较大或较小时,其输出会接近 1 或 0,从而保证门的开或关; + - 如果使用非饱和的激活函数,将难以实现**门控/开关**的效果。 +- sigmoid 是现代**门控单元**中的共同选择。 + +**为什么使用 tanh?** +- 使用 tanh 作为计算状态时的激活函数,主要是因为其**值域**为 `(-1, 1)`: + - 一方面,这与多数场景下特征分布以 0 为中心相吻合; + - 另一方面,可以避免在前向传播的时候发生**数值问题**(主要是上溢) +- 此外,tanh 比 sigmoid 在 0 附近有更大的梯度,通常会使模型收敛更快。 + > 早期,使用 `h(x) = 2*sigmoid(x) - 1` 作为激活函数,该激活函数的值域也是 `(-1, 1)` + +**Hard gate** +- 在一些对计算能力有限制的设备中,可能会使用 hard gate +- 因为 sigmoid 求指数时需要一定的计算量,此时会使用 0/1 门(hard gate)让门控输出 0 或 1 的离散值。 + +### 窥孔机制 +> Gers F A, Schmidhuber J. Recurrent nets that time and count[C]. 2000. +- LSTM 通常使用输入 `x_t` 和上一步的隐状态 `h_{t-1}` 参与门控计算; +
+ +- **窥孔机制**指让记忆状态也参与门控的计算中 +
+ +### GRU 与 LSTM 的关系 +- GRU 认为 LSTM 中的**遗忘门**和**输入门**的功能有一定的重合,于是将其合并为一个**更新门**。 + > 其实,早期的 LSTM 中本来就是没有**遗忘门**的 [1],因为研究发现加入遗忘门能提升性能 [2],从而演变为如今的 LSTM. + >> [1] Hochreiter S, Schmidhuber J. Long short-term memory[J]. 1997.
+ >> [2] Gers F A, Schmidhuber J, Cummins F. Learning to forget: Continual prediction with LSTM[J]. 1999. + +- GRU 相比 LSTM 的**改动**: + - GRU 把遗忘门和输入门合并为**更新门(update)** `z`,并使用**重置门(reset)** `r` 代替输出门; + - **合并**了记忆状态 `C` 和隐藏状态 `h` +
+ + 其中 + - **更新门** `z`用于控制前一时刻的状态信息被**融合**到当前状态中的程度 + - **重置门** `r`用于控制忽略前一时刻的状态信息的程度 + +#### 完整的 GRU 前向传播公式 +
+ +> 遵从原文表示,没有加入偏置 diff --git "a/A-\346\267\261\345\272\246\345\255\246\344\271\240/C-\344\270\223\351\242\230-\344\274\230\345\214\226\347\256\227\346\263\225.md" "b/A-\346\267\261\345\272\246\345\255\246\344\271\240/C-\344\270\223\351\242\230-\344\274\230\345\214\226\347\256\227\346\263\225.md" new file mode 100644 index 00000000..60609d84 --- /dev/null +++ "b/A-\346\267\261\345\272\246\345\255\246\344\271\240/C-\344\270\223\351\242\230-\344\274\230\345\214\226\347\256\227\346\263\225.md" @@ -0,0 +1,255 @@ +专题-优化算法 +=== + +Reference +--- +- 【**必读**】[An overview of gradient descent optimization algorithms](http://ruder.io/optimizing-gradient-descent/) - Sebastian Ruder + +Index +--- + + +- [梯度下降](#梯度下降) +- [随机梯度下降](#随机梯度下降) + - [小批量随机梯度下降](#小批量随机梯度下降) + - [小批量 SGD 的更新过程](#小批量-sgd-的更新过程) + - [“批”的大小对优化效果的影响](#批的大小对优化效果的影响) + - [随机梯度下降存在的问题](#随机梯度下降存在的问题) +- [随机梯度下降的改进方向](#随机梯度下降的改进方向) +- [动量(Momentum)算法](#动量momentum算法) + - [带动量的 SGD](#带动量的-sgd) + - [NAG 算法(Nesterov 动量)](#nag-算法nesterov-动量) +- [自适应学习率的优化算法](#自适应学习率的优化算法) + - [AdaGrad](#adagrad) + - [RMSProp](#rmsprop) + - [AdaDelta](#adadelta) + - [Adam](#adam) + - [AdaMax](#adamax) + - [Nadam](#nadam) +- [如何选择这些优化算法?](#如何选择这些优化算法) + - [各优化算法的可视化](#各优化算法的可视化) +- [基于二阶梯度的优化算法](#基于二阶梯度的优化算法) + - [牛顿法](#牛顿法) + - [为什么牛顿法比梯度下降收敛更快?](#为什么牛顿法比梯度下降收敛更快) + - [牛顿法的优缺点](#牛顿法的优缺点) + - [拟牛顿法 TODO](#拟牛顿法-todo) + + + + +## 梯度下降 +> ../数学/[梯度下降法](../C-数学/B-深度学习的核心#梯度下降法) + +- 梯度下降是一种**优化算法**,通过**迭代**的方式寻找模型的**最优参数**; + - 所谓最优参数指的是使**目标函数**达到最小值时的参数; + - 当目标函数是**凸函数**时,梯度下降的解是全局最优解;但在一般情况下,**梯度下降无法保证全局最优**。 +- 微积分中使用**梯度**表示函数增长最快的方向;因此,神经网络中使用**负梯度**来指示目标函数下降最快的方向。 + - **梯度**实际上是损失函数对网络中每个参数的**偏导**所组成的向量; + - **梯度**仅仅指示了对于每个参数各自增长最快的方向;因此,梯度无法保证**全局方向**就是函数为了达到最小值应该前进的方向。 + - **梯度**的具体计算方法即**反向传播**。 +- **负梯度**中的每一项可以认为传达了**两个信息**: + - 正负号在告诉输入向量应该调大还是调小(正调大,负调小) + - 每一项的相对大小表明每个参数对函数值达到最值的**影响程度**; +
+ +## 随机梯度下降 +- 基本的梯度下降法每次使用**所有训练样本**的**平均损失**来更新参数; + - 因此,经典的梯度下降在每次对模型参数进行更新时,需要遍历所有数据; + - 当训练样本的数量很大时,这需要消耗相当大的计算资源,在实际应用中基本不可行。 +- **随机梯度下降**(SGD)每次使用单个样本的损失来近似平均损失 + +### 小批量随机梯度下降 +- 为了降低随机梯度的**方差**,使模型迭代更加稳定,实践中会使用**一批**随机数据的损失来近似平均损失。 + > ../机器学习基础/[偏差与方差](../A-机器学习/A-机器学习基础#偏差与方差) +- 使用批训练的另一个主要目的,是为了利用高度优化的**矩阵运算**以及**并行计算框架**。 + +### 小批量 SGD 的更新过程 +1. 在训练集上抽取指定大小(batch_size)的一批数据 `{(x,y)}` +1. 【**前向传播**】将这批数据送入网络,得到这批数据的预测值 `y_pred` +1. 计算网络在这批数据上的损失,用于衡量 `y_pred` 和 `y` 之间的距离 +1. 【**反向传播**】计算损失相对于所有网络中**可训练参数**的梯度 `g` +1. 将参数沿着**负梯度**的方向移动,即 `W -= lr * g` + > `lr` 表示学习率 learning rate +
+ +### “批”的大小对优化效果的影响 +> 《深度学习》 8.1.3 批量算法和小批量算法 +- **较大的批能得到更精确的梯度估计**,但回报是小于线性的。 +- **较小的批能带来更好的泛化误差**,泛化误差通常在批大小为 1 时最好。 + - 原因可能是由于小批量在学习过程中带来了**噪声**,使产生了一些正则化效果 (Wilson and Martinez, 2003) + - 但是,因为梯度估计的高方差,小批量训练需要**较小的学习率**以保持稳定性,这意味着**更长的训练时间**。 +- 当批的大小为 **2 的幂**时能充分利用矩阵运算操作,所以批的大小一般取 32、64、128、256 等。 + + +### 随机梯度下降存在的问题 +- 随机梯度下降(SGD)放弃了**梯度的准确性**,仅采用一部分样本来估计当前的梯度;因此 SGD 对梯度的估计常常出现偏差,造成目标函数收敛不稳定,甚至不收敛的情况。 +- 无论是经典的梯度下降还是随机梯度下降,都可能陷入**局部极值点**;除此之外,SGD 还可能遇到“**峡谷**”和“**鞍点**”两种情况 + - **峡谷**类似一个带有**坡度**的狭长小道,左右两侧是“**峭壁**”;在**峡谷**中,准确的梯度方向应该沿着坡的方向向下,但粗糙的梯度估计使其稍有偏离就撞向两侧的峭壁,然后在两个峭壁间来回**震荡**。 + - **鞍点**的形状类似一个马鞍,一个方向两头翘,一个方向两头垂,而**中间区域近似平地**;一旦优化的过程中不慎落入鞍点,优化很可能就会停滞下来。 +
+ +## 随机梯度下降的改进方向 +- SGD 的改进遵循两个方向:**惯性保持**和**环境感知** + > 这两个提法来自《百面机器学习》 +- **惯性保持**指的是加入**动量** SGD 算法; + > [动量(Momentum)方法](#动量momentum方法) +- **环境感知**指的是根据不同参数的一些**经验性判断**,**自适应**地确定**每个参数的学习速率** + > [自适应学习率的优化算法](#自适应学习率的优化算法) + +**训练词向量的例子** +- 不同词出现的频率是不同的,**数据的稀疏性会影响其参数的稀疏性**; +- 具体来说,**对低频词如果不加措施**,其参数的梯度在多数情况下为 0;换言之,这些参数更新的频率很低,导致难以收敛。 +- 在实践中,我们希望学习**低频词**的参数时具有**较大的学习率**,而高频词其参数的更新幅度可以小一些。 + +## 动量(Momentum)算法 + +### 带动量的 SGD +- 引入**动量**(Momentum)方法一方面是为了解决“峡谷”和“鞍点”问题;一方面也可以用于SGD 加速,特别是针对**高曲率**、小幅但是方向一致的梯度。 + - 如果把原始的 SGD 想象成一个**纸团**在重力作用向下滚动,由于**质量小**受到山壁弹力的干扰大,导致来回震荡;或者在鞍点处因为**质量小**速度很快减为 0,导致无法离开这块平地。 + - **动量**方法相当于把纸团换成了**铁球**;不容易受到外力的干扰,轨迹更加稳定;同时因为在鞍点处因为**惯性**的作用,更有可能离开平地。 + - 动量方法以一种廉价的方式模拟了二阶梯度(牛顿法) +
+ +- **参数更新公式** + +
+ + - 从形式上看, 动量算法引入了变量 `v` 充当速度角色,以及相相关的超参数 `α`。 + - 原始 SGD 每次更新的步长只是梯度乘以学习率;现在,步长还取决于**历史梯度序列**的大小和排列;当许多连续的梯度指向**相同的方向**时,步长会被不断增大; + +- **动量算法描述** +
+ + - 如果动量算法总是观测到梯度 `g`,那么它会在 `−g` 方向上不断加速,直到达到**最终速度**。 + +
+ + - 在实践中, `α` 的一般取 `0.5, 0.9, 0.99`,分别对应**最大** `2` 倍、`10` 倍、`100` 倍的步长 + - 和学习率一样,`α` 也可以使用某种策略在训练时进行**自适应调整**;一般初始值是一个较小的值,随后会慢慢变大。 + > [自适应学习率的优化方法](#自适应学习率的优化方法) + +### NAG 算法(Nesterov 动量) +- **NAG 把梯度计算放在对参数施加当前速度之后**。 +- 这个“**提前量**”的设计让算法有了对前方环境“**预判**”的能力。Nesterov 动量可以解释为往标准动量方法中添加了一个**修正因子**。 +- **NAG 算法描述** +
+ + +## 自适应学习率的优化算法 +> 《深度学习》 8.5 自适应学习率算法 + +### AdaGrad +> [Duchi et al., 2011](http://jmlr.org/papers/v12/duchi11a.html) +- 该算法的思想是独立地适应模型的每个参数:具有较大偏导的参数相应有一个较大的学习率,而具有小偏导的参数则对应一个较小的学习率 +- 具体来说,每个参数的学习率会缩放各参数反比于其**历史梯度平方值总和的平方根** +- **AdaGrad 算法描述** +
+ + - 注意:全局学习率 `ϵ` 并没有更新,而是每次应用时被缩放 + +**AdaGrad 存在的问题** +- 学习率是单调递减的,训练后期学习率过小会导致训练困难,甚至提前结束 +- 需要设置一个全局的初始学习率 + +### RMSProp +> Hinton, 2012 +- RMSProp 主要是为了解决 AdaGrad 方法中**学习率过度衰减**的问题—— AdaGrad 根据平方梯度的**整个历史**来收缩学习率,可能使得学习率在达到局部最小值之前就变得太小而难以继续训练; +- RMSProp 使用**指数衰减平均**(递归定义)以丢弃遥远的历史,使其能够在找到某个“凸”结构后快速收敛;此外,RMSProp 还加入了一个超参数 `ρ` 用于控制衰减速率。 + > ./术语表/[指数衰减平均](./备忘-术语表#指数加权平均指数衰减平均) +- 具体来说(对比 AdaGrad 的算法描述),即修改 `r` 为 +
+ 记 +
+ 则 +
+ + > 其中 `E` 表示期望,即平均;`δ` 为平滑项,具体为一个小常数,一般取 `1e-8 ~ 1e-10`(Tensorflow 中的默认值为 `1e-10`) +- **RMSProp** 建议的**初始值**:全局学习率 `ϵ=1e-3`,衰减速率 `ρ=0.9` +- **RMSProp 算法描述** +
+ +- 带 **Nesterov 动量**的 **RMSProp** +
+ +- 经验上,RMSProp 已被证明是一种有效且实用的深度神经网络优化算法。 +- RMSProp 依然需要设置一个全局学习率,同时又多了一个超参数(推荐了默认值)。 + +### AdaDelta +- AdaDelta 和 RMSProp 都是为了解决 AdaGrad 对学习率过度衰减的问题而产生的。 +- AdaDelta 和 RMSProp 是独立发现的,AdaDelta 的前半部分与 RMSProp 完全一致; +- AdaDelta 进一步解决了 AdaGrad 需要设置一个全局学习率的问题 +- 具体来说,即 +
+ + 此时,AdaDelta 已经不需要设置全局学习率了 + > 关于 `RMS` 的定义,见 [RMSProp](#rmsprop) + +### Adam +> Kingma and Ba, 2014 +- Adam 在 RMSProp 方法的基础上更进一步: + - 除了加入**历史梯度平方的指数衰减平均**(`r`)外, + - 还保留了**历史梯度的指数衰减平均**(`s`),相当于**动量**。 + - Adam 行为就像一个带有摩擦力的小球,在误差面上倾向于平坦的极小值。 + > ./术语表/[指数衰减平均](./备忘-术语表#指数加权平均指数衰减平均) +- **Adam 算法描述** +
+ +**偏差修正** +- 注意到,`s` 和 `r` 需要初始化为 `0`;且 `ρ1` 和 `ρ2` 推荐的初始值都很接近 `1`(`0.9` 和 `0.999`) +- 这将导致在训练初期 `s` 和 `r` 都很小(偏向于 0),从而训练缓慢。 +- 因此,Adam 通过修正偏差来抵消这个倾向。 + +### AdaMax +- Adam 的一个变种,对梯度平方的处理由**指数衰减平均**改为**指数衰减求最大值** + +### Nadam +- Nesterov 动量版本的 Adam + + +## 如何选择这些优化算法? +- 各自适应学习率的优化算法表现不分伯仲,没有哪个算法能在所有任务上脱颖而出; +- 目前,最流行并且使用很高的优化算法包括 SGD、带动量的 SGD、RMSProp、带动量的 RMSProp、AdaDelta 和 Adam。 +- 具体使用哪个算法取决于使用者对算法的熟悉程度,以便调节超参数。 + +### 各优化算法的可视化 +- SGD 各优化方法在损失曲面上的表现 +
+ +- SGD 各优化方法在**鞍点**处上的表现 +
+ + +## 基于二阶梯度的优化算法 + +### 牛顿法 +- 梯度下降使用的梯度信息实际上是**一阶导数** +- 牛顿法除了一阶导数外,还会使用**二阶导数**的信息 +- 根据导数的定义,一阶导描述的是函数值的变化率,即**斜率**;二阶导描述的则是斜率的变化率,即曲线的弯曲程度——**曲率** + > 数学/[泰勒级数](../C-数学/B-微积分的本质#泰勒级数) + +**牛顿法更新过程** TODO +> 《统计学习方法》 附录 B + +#### 为什么牛顿法比梯度下降收敛更快? +> [常见的几种最优化方法(梯度下降法、牛顿法、拟牛顿法、共轭梯度法等)](https://www.cnblogs.com/shixiangwan/p/7532830.html) - 蓝鲸王子 - 博客园 + +**几何理解** +- 牛顿法就是用一个**二次曲面**去拟合你当前所处位置的局部曲面;而梯度下降法是用一个平面去拟合当前的局部曲面。 +- 通常情况下,二次曲面的拟合会比平面更好,所以牛顿法选择的**下降路径**会更符合真实的最优下降路径。 + +**通俗理解** +- 比如你想找一条最短的路径走到一个盆地的最底部, +- 梯度下降法每次只从你当前所处位置选一个坡度最大的方向走一步; +- 牛顿法在选择方向时,不仅会考虑坡度是否够大,还会考虑你走了一步之后,坡度是否会变得更大。 +- 所以,牛顿法比梯度下降法看得更远,能**更快**地走到最底部。 + +#### 牛顿法的优缺点 +- **优点** + - 收敛速度快,能用更少的迭代次数找到最优解 +- **缺点** + - 每一步都需要求解目标函数的 **Hessian 矩阵**的逆矩阵,计算复杂 + > Hessian 矩阵即由二阶偏导数构成的方阵 + +### 拟牛顿法 TODO +- 用其他近似方法代替求解 **Hessian 矩阵**的逆矩阵 diff --git "a/A-\346\267\261\345\272\246\345\255\246\344\271\240/D-\344\270\223\351\242\230-\345\272\217\345\210\227\345\273\272\346\250\241.md" "b/A-\346\267\261\345\272\246\345\255\246\344\271\240/D-\344\270\223\351\242\230-\345\272\217\345\210\227\345\273\272\346\250\241.md" new file mode 100644 index 00000000..18b299b6 --- /dev/null +++ "b/A-\346\267\261\345\272\246\345\255\246\344\271\240/D-\344\270\223\351\242\230-\345\272\217\345\210\227\345\273\272\346\250\241.md" @@ -0,0 +1,163 @@ +专题-序列建模 +=== + +**相关专题** +- [专题-RNN](./B-专题-RNN.md) +- [专题-RNN](./B-专题-RNN.md) + +Index +--- + + +- [序列建模简述](#序列建模简述) +- [Seq2Seq](#seq2seq) + - [解码方法(贪心、Beam Search、维特比算法)](#解码方法贪心beam-search维特比算法) + - [Beam Search(集束搜索)](#beam-search集束搜索) + - [维特比(Viterbi)算法 TODO](#维特比viterbi算法-todo) + - [其他最短路径算法](#其他最短路径算法) + - [构建 Seq2Seq 一般做法](#构建-seq2seq-一般做法) +- [序列的表示学习](#序列的表示学习) + - [学习任务无关的 Sentence Embedding](#学习任务无关的-sentence-embedding) +- [CNN 与序列建模](#cnn-与序列建模) + - [一维卷积](#一维卷积) +- [时间卷积网络(TCN)](#时间卷积网络tcn) + - [WaveNet](#wavenet) + - [因果卷积](#因果卷积) + - [空洞卷积](#空洞卷积) + - [Highway 网络](#highway-网络) + - [残差模块](#残差模块) +- [Reference](#reference) + + + +## 序列建模简述 +> [从循环到卷积,探索序列建模的奥秘](https://mp.weixin.qq.com/s/f0sv7c-H5o5L_wy2sUonUQ) - 机器之心 +- 序列建模就是将一个**输入/观测**序列映射到一个**输出/标记**序列 + > 《统计学习方法》中称之为标注问题 +- 在**传统机器学习**方法中,常用的模型有:隐马尔可夫模型(HMM),条件随机场(CRF)等 + > 机器学习专题 TODO +- 在**深度学习领域**的很长一段时间里,RNN/LSTM 都是序列建模的首选。 + > 《深度学习》 10 序列建模:循环和递归网络 +- 最近,CNN 开始在序列建模领域流行,一个**关键想法**是——在一维时间序列上使用**一维卷积运算** +
+ + > [CNN for Sentence Classification](https://arxiv.org/abs/1408.5882) (Kim, 2014) + + +## Seq2Seq +- Seq2Seq 的核心思想是把一个输出序列,通过**编码**(Encode)和**解码**(Decode)两个过程映射到一个新的输出序列。 +
+ + > [Translation with a Sequence to Sequence Network and Attention](https://pytorch.org/tutorials/intermediate/seq2seq_translation_tutorial.html) — PyTorch +- 经典的 Seq2Seq 模型中,**编码器**(Encoder)和**解码器**(Decoder)都使用 **RNN** 进行建模 + +
+ + > 上图是一次**机器翻译**的过程,输入是一个源语言的一个句子 "A B C",Encoder 一次读入每个单词直到结束符 ``(End of Sequence);
+ > 在解码的第一步,Decoder 先读取 **Encoder 的最终状态**,生成目标语言的第一个词 'W',接着 Decoder 读取第一步的输出 'W' 作为第二步的输入,进而生成第二个词 'X',如此直到生成 `` 或达到指定**最大长度**。 + >> Decoder 生成每个词还要结合当前时间步的隐状态(如果是 LSTM 还有 记忆状态),更深入的细节暂时略过。 +- Seq2Seq 之所以流行,是因为它为不同的问题提供了一套**端到端**(End to End)的解决方案,免去了繁琐的中间步骤,从输入直接得到结果. +- 根据任务的输入输出差异,编码器和解码器的设计也不尽相同,但是“Encoder-Decoder”的结构都是一致的。 + - **机器翻译**:输入源语言的一个句子,输出目标语言的句子; + - **机器问答**:输入问题/查询,输出答案; + - **文本摘要**:输入一个长句或段落,输出一个摘要短句; + - **语音识别**:输入是音频序列信号,输出为识别出的文本; + - **图像描述**:输入是图像经过视觉网络的特征,输出是图像的描述文本。 + - ... + +### 解码方法(贪心、Beam Search、维特比算法) + +- Seq2Seq 中的解码方法主要有三种:**贪心**、**Beam Search**、**维特比算法**(动态规划) +- 这三种方法的思想本质上是一致的,假设选取相同的评价标准(比如概率最大、路径最短等) + - **贪心**每到达一个节点,只选择当前状态的**最优结果**,其他都忽略,直到最后一个节点;贪心法只能得到某个局部最优解; + - **Beam Search** 会在每个节点保存当前**最优的 k 个结果**(排序后),其他结果将被“剪枝”,因为每次都有 k 个分支进入下一个状态。Beam Search 也不能保证全局最优,但能以较大的概率得到全局最优解。 + - **维特比算法**利用**动态规划**的方法可以保证得到全局最优解,但是当候选状态极大时,需要消耗大量的时间和空间搜索和保存状态,因此维特比算法只适合状态集比较小的情况。 + +#### Beam Search(集束搜索) +- Beam Search 是一种启发式算法 +- 该方法会保存前 `beam_size` 个最佳状态,每次解码时会根据所有保存的状态进行下一步**扩展**和**排序**,依然只保留前 `beam_size` 个最佳状态;循环迭代至最后一步,保存最佳选择。 +- Beam Search 图示 +
+ +- 当 `beam_size = 1` 时,Beam Search 即退化为贪心搜索 +- 一般为了计算资源和性能的平衡,`beam_size` 会选择一个适中的范围;通常 `beam_size` 取 `8~12` 即可(机器翻译、文本摘要) + +#### 维特比(Viterbi)算法 TODO +> [维特比算法通俗理解](http://www.hankcs.com/program/algorithm/%E7%BB%B4%E7%89%B9%E6%AF%94%E7%AE%97%E6%B3%95%E9%80%9A%E4%BF%97%E7%90%86%E8%A7%A3.html)-码农场 +- 利用**动态规划**可以求解任何图中的最短路径问题; +- **维特比算法**是针对一种特殊的图结构——“**篱笆网络**”——而提出的算法,用于在概率图模型中求解**概率最大路径**; + +#### 其他最短路径算法 +- Dijkstra 算法(迪杰斯特拉算法) + - 基于贪心 + - 用于求解某个顶点到其他所有顶点之间的最短路径 + - 时间复杂度 `O(N^2)` + - Dijkstra 算法的使用范围比 Viterbi 算法更广,可用于求解大部分图结构中的最短路径。 +- Floyd 算法(弗洛伊德算法) + - 求解的是每一对顶点之间的最短路径 + - 时间复杂度 `O(N^3)` + + +### 构建 Seq2Seq 一般做法 +- 堆叠 RNN/CNN + > [CNN 与序列建模](#cnn-与序列建模) +- Dropout 机制 +- **残差**连接 +- **Attention 机制** + + + +## 序列的表示学习 +- 序列的表示学习指学习单个序列的特征表示,通常作为另一个任务的子过程,或者用于迁移学习等。 +- 整个学习的过程相当于 Seq2Seq 中的 Encoder 部分 + +### 学习任务无关的 Sentence Embedding +> [1703.03130] [A Structured Self-attentive Sentence Embedding](https://arxiv.org/abs/1703.03130) + +**模型基本结构**,更多细节参考原文 +- 待学习的句子 + + + + +## CNN 与序列建模 +- 一般认为 CNN 擅长处理**网格结构的数据**,比如图像(二维像素网络) + - 卷积层试图将神经网络中的每一小块进行更加深入的分析,从而得出抽象程度更高的特征。 + - 一般来说通过卷积层处理的神经元结点矩阵会变得更深,即神经元的组织在第三个维度上会增加。 +- **时序数据**同样可以认为是在时间轴上有规律地采样而形成的一维网格 +
+ + > [CNN for Sentence Classification](https://arxiv.org/abs/1408.5882) (Kim, 2014) + +### 一维卷积 +- 适用于序列建模的卷积网络一般就是采用的是一维卷积 +
+ + - 最下层的 `x_i` 可视为句子的输入序列 + - 最上层的 `g_j` 即输出序列 + - 流行的网络中一般使用 **embedding** 作为输入,也就说每个 `x_i` 其实是一个多维向量 `v(x_i)` + > ../自然语言处理/[词向量](../B-自然语言处理/B-专题-词向量.md) + + +## 时间卷积网络(TCN) + +### WaveNet + +### 因果卷积 + +**Reference** +- [WaveNet: A Generative Model for Raw Audio](https://deepmind.com/blog/wavenet-generative-model-raw-audio/) | DeepMind +- ibab/[tensorflow-wavenet](https://github.com/ibab/tensorflow-wavenet) - GitHub + +### 空洞卷积 + +### Highway 网络 +- 一种门限机制 + +### 残差模块 +> [Deep Residual Learning for Image Recognition](https://arxiv.org/abs/1512.03385) (He, et al., 2015) + + +## Reference +- 自然语言处理之序列模型 - 小象学院 +- [从循环到卷积,探索序列建模的奥秘](https://mp.weixin.qq.com/s/f0sv7c-H5o5L_wy2sUonUQ) - 机器之心 \ No newline at end of file diff --git "a/A-\346\267\261\345\272\246\345\255\246\344\271\240/\343\200\212\346\267\261\345\272\246\345\255\246\344\271\240\343\200\213\346\225\264\347\220\206.md" "b/A-\346\267\261\345\272\246\345\255\246\344\271\240/\343\200\212\346\267\261\345\272\246\345\255\246\344\271\240\343\200\213\346\225\264\347\220\206.md" new file mode 100644 index 00000000..ba35aae6 --- /dev/null +++ "b/A-\346\267\261\345\272\246\345\255\246\344\271\240/\343\200\212\346\267\261\345\272\246\345\255\246\344\271\240\343\200\213\346\225\264\347\220\206.md" @@ -0,0 +1,1651 @@ +**问题列表** +--- + + +- [如何设置网络的初始值?*](#如何设置网络的初始值) +- [梯度爆炸的解决办法***](#梯度爆炸的解决办法) +- [神经网络(MLP)的万能近似定理*](#神经网络mlp的万能近似定理) +- [神经网络中,深度与宽度的关系,及其表示能力的差异**](#神经网络中深度与宽度的关系及其表示能力的差异) +- [在深度神经网络中,引入了隐藏层(非线性单元),放弃了训练问题的凸性,其意义何在?**](#在深度神经网络中引入了隐藏层非线性单元放弃了训练问题的凸性其意义何在) +- [稀疏表示,低维表示,独立表示*](#稀疏表示低维表示独立表示) +- [局部不变性(平滑先验)及其在基于梯度的学习上的局限性*](#局部不变性平滑先验及其在基于梯度的学习上的局限性) +- [为什么交叉熵损失相比均方误差损失能提高以 sigmoid 和 softmax 作为激活函数的层的性能?**](#为什么交叉熵损失相比均方误差损失能提高以-sigmoid-和-softmax-作为激活函数的层的性能) +- [分段线性单元(如 ReLU)代替 sigmoid 的利弊***](#分段线性单元如-relu代替-sigmoid-的利弊) +- [在做正则化过程中,为什么只对权重做正则惩罚,而不对偏置做权重惩罚*](#在做正则化过程中为什么只对权重做正则惩罚而不对偏置做权重惩罚) +- [列举常见的一些范数及其应用场景,如 L0、L1、L2、L∞、Frobenius等范数**](#列举常见的一些范数及其应用场景如-l0l1l2l∞frobenius等范数) +- [L1 和 L2 范数的异同***](#l1-和-l2-范数的异同) +- [为什么 L1 正则化可以产生稀疏权值,L2 正则化可以防止过拟合?**](#为什么-l1-正则化可以产生稀疏权值l2-正则化可以防止过拟合) + - [为什么 L1 正则化可以产生稀疏权值,而 L2 不会?](#为什么-l1-正则化可以产生稀疏权值而-l2-不会) + - [为什么 L1 和 L2 正则化可以防止过拟合?](#为什么-l1-和-l2-正则化可以防止过拟合) +- [简单介绍常用的激活函数,如 sigmoid、relu、softplus、tanh、RBF 及其应用场景***](#简单介绍常用的激活函数如-sigmoidrelusoftplustanhrbf-及其应用场景) + - [整流线性单元(ReLU)](#整流线性单元relu) + - [sigmoid 与 tanh(双曲正切函数)](#sigmoid-与-tanh双曲正切函数) + - [其他激活函数(隐藏单元)](#其他激活函数隐藏单元) + - [sigmoid 和 softplus 的一些性质](#sigmoid-和-softplus-的一些性质) +- [Jacobian 和 Hessian 矩阵及其在深度学习中的重要性*](#jacobian-和-hessian-矩阵及其在深度学习中的重要性) +- [信息熵、KL 散度(相对熵)与交叉熵**](#信息熵kl-散度相对熵与交叉熵) + - [自信息与信息熵](#自信息与信息熵) + - [相对熵(KL 散度)与交叉熵](#相对熵kl-散度与交叉熵) +- [如何避免数值计算中的上溢和下溢问题,以 softmax 为例*](#如何避免数值计算中的上溢和下溢问题以-softmax-为例) +- [训练误差、泛化误差;过拟合、欠拟合;模型容量,表示容量,有效容量,最优容量的概念; 奥卡姆剃刀原则*](#训练误差泛化误差过拟合欠拟合模型容量表示容量有效容量最优容量的概念-奥卡姆剃刀原则) + - [过拟合的一些解决方案***](#过拟合的一些解决方案) +- [高斯分布的广泛应用的原因**](#高斯分布的广泛应用的原因) + - [高斯分布(Gaussian distribution)](#高斯分布gaussian-distribution) + - [为什么推荐使用高斯分布?](#为什么推荐使用高斯分布) +- [表示学习、自编码器与深度学习**](#表示学习自编码器与深度学习) +- [L1、L2 正则化与 MAP 贝叶斯推断的关系*](#l1l2-正则化与-map-贝叶斯推断的关系) +- [什么是欠约束,为什么大多数的正则化可以使欠约束下的欠定问题在迭代过程中收敛*](#什么是欠约束为什么大多数的正则化可以使欠约束下的欠定问题在迭代过程中收敛) +- [为什么考虑在模型训练时对输入 (隐藏单元或权重) 添加方差较小的噪声?*](#为什么考虑在模型训练时对输入-隐藏单元或权重-添加方差较小的噪声) +- [多任务学习、参数绑定和参数共享***](#多任务学习参数绑定和参数共享) + - [多任务学习](#多任务学习) + - [参数绑定和参数共享](#参数绑定和参数共享) +- [Dropout 与 Bagging 集成方法的关系,Dropout 带来的意义与其强大的原因***](#dropout-与-bagging-集成方法的关系dropout-带来的意义与其强大的原因) + - [Bagging 集成方法](#bagging-集成方法) + - [Dropout](#dropout) +- [批梯度下降法(Batch SGD)更新过程中,批的大小会带来怎样的影响**](#批梯度下降法batch-sgd更新过程中批的大小会带来怎样的影响) +- [如何避免深度学习中的病态,鞍点,梯度爆炸,梯度弥散?***](#如何避免深度学习中的病态鞍点梯度爆炸梯度弥散) + - [病态(ill-conditioning)](#病态ill-conditioning) + - [鞍点(saddle point)](#鞍点saddle-point) + - [长期依赖与梯度爆炸、消失](#长期依赖与梯度爆炸消失) +- [SGD 以及学习率的选择方法、带动量的 SGD***](#sgd-以及学习率的选择方法带动量的-sgd) + - [(批)随机梯度下降(SGD)与学习率](#批随机梯度下降sgd与学习率) + - [带动量的 SGD](#带动量的-sgd) +- [自适应学习率算法:AdaGrad、RMSProp、Adam 等***](#自适应学习率算法adagradrmspropadam-等) + - [AdaGrad](#adagrad) + - [RMSProp](#rmsprop) + - [Adam](#adam) +- [基于二阶梯度的优化方法:牛顿法、共轭梯度、BFGS 等的做法*](#基于二阶梯度的优化方法牛顿法共轭梯度bfgs-等的做法) +- [批标准化(Batch Normalization)的意义**](#批标准化batch-normalization的意义) +- [神经网络中的卷积,以及卷积的动机:稀疏连接、参数共享、等变表示(平移不变性)***](#神经网络中的卷积以及卷积的动机稀疏连接参数共享等变表示平移不变性) + - [稀疏连接(sparse connectivity)](#稀疏连接sparse-connectivity) + - [参数共享(parameter sharing)](#参数共享parameter-sharing) + - [平移等变|不变性(translation invariant)](#平移等变不变性translation-invariant) +- [卷积中不同零填充的影响**](#卷积中不同零填充的影响) +- [基本卷积的变体:反卷积、空洞卷积***](#基本卷积的变体反卷积空洞卷积) + - [转置卷积|反卷积(Transposed convolution)](#转置卷积反卷积transposed-convolution) + - [空洞卷积|扩张卷积(Dilated convolution)](#空洞卷积扩张卷积dilated-convolution) +- [池化、池化(Pooling)的作用***](#池化池化pooling的作用) +- [卷积与池化的意义、影响(作为一种无限强的先验)**](#卷积与池化的意义影响作为一种无限强的先验) +- [RNN 的几种基本设计模式](#rnn-的几种基本设计模式) +- [RNN 更新方程(前向传播公式),包括 LSTM、GRU 等***](#rnn-更新方程前向传播公式包括-lstmgru-等) +- [BPTT(back-propagation through time,通过时间反向传播)**](#bpttback-propagation-through-time通过时间反向传播) +- [自编码器在深度学习中的意义*](#自编码器在深度学习中的意义) +- [自编码器一些常见的变形与应用:正则自编码器、稀疏自编码器、去噪自编码器*](#自编码器一些常见的变形与应用正则自编码器稀疏自编码器去噪自编码器) +- [半监督的思想以及在深度学习中的应用*](#半监督的思想以及在深度学习中的应用) +- [分布式表示的概念、应用,与符号表示(one-hot 表示)的区别***](#分布式表示的概念应用与符号表示one-hot-表示的区别) + - [什么是分布式表示?](#什么是分布式表示) + - [分布式表示为什么强大?——分布式表示与符号表示](#分布式表示为什么强大分布式表示与符号表示) +- [如何理解维数灾难?***](#如何理解维数灾难) +- [迁移学习相关概念:多任务学习、一次学习、零次学习、多模态学习**](#迁移学习相关概念多任务学习一次学习零次学习多模态学习) +- [图模型|结构化概率模型相关概念*](#图模型结构化概率模型相关概念) +- [深度生成模型、受限玻尔兹曼机(RBM)相关概念*](#深度生成模型受限玻尔兹曼机rbm相关概念) +- [深度学习在图像、语音、NLP等领域的常见作法与基本模型**](#深度学习在图像语音nlp等领域的常见作法与基本模型) + - [计算机视觉(CV)](#计算机视觉cv) + - [语音识别](#语音识别) + - [自然语言处理](#自然语言处理) + + + +# 如何设置网络的初始值?* +> 《深度学习》 8.4 参数初始化策略 + +一般总是使用服从(截断)高斯或均匀分布的随机值,具体是高斯还是均匀分布影响不大,但是也没有详细的研究。 + +但是,**初始值的大小**会对优化结果和网络的泛化能力产生较大的影响。 + + + +一些启发式初始化策略通常是根据输入与输出的单元数来决定初始权重的大小,比如 Glorot and Bengio (2010) 中建议建议使用的标准初始化,其中 m 为输入数,n 为输出数 + +[![](../_assets/公式_20180610212719.png)](http://www.codecogs.com/eqnedit.php?latex=W_{i,j}&space;\sim&space;U(-\sqrt{\frac{6}{m+n}},\sqrt{\frac{6}{m+n}})) + +还有一些方法推荐使用随机正交矩阵来初始化权重 (Saxe et al., 2013)。 + +> 常用的初始化策略可以参考 Keras 中文文档:[初始化方法Initializers](http://keras-cn.readthedocs.io/en/latest/other/initializations/) + + +# 梯度爆炸的解决办法*** +> [27. 如何避免深度学习中的病态,鞍点,梯度爆炸,梯度弥散?***](#27-如何避免深度学习中的病态鞍点梯度爆炸梯度弥散) + +**梯度爆炸**: + +![](../_assets/TIM截图20180611172657.png) + +1. **梯度截断**(gradient clipping)——如果梯度超过某个阈值,就对其进行限制 + + > 《深度学习》 10.11.1 截断梯度 + + 下面是 Tensorflow 提供的几种方法: + + - `tf.clip_by_value(t, clip_value_min, clip_value_max)` + - `tf.clip_by_norm(t, clip_norm)` + - `tf.clip_by_average_norm(t, clip_norm)` + - `tf.clip_by_global_norm(t_list, clip_norm)` + + 这里以`tf.clip_by_global_norm`为例: + + ``` + To perform the clipping, the values `t_list[i]` are set to: + + t_list[i] * clip_norm / max(global_norm, clip_norm) + + where: + + global_norm = sqrt(sum([l2norm(t)**2 for t in t_list])) + ``` + + 用法: + + ``` + train_op = tf.train.AdamOptimizer() + params = tf.trainable_variables() + gradients = tf.gradients(loss, params) + + clip_norm = 100 + clipped_gradients, global_norm = tf.clip_by_global_norm(gradients, clip_norm) + + optimizer_op = train_op.apply_gradients(zip(clipped_gradients, params)) + ``` + + > clip_norm 的设置视 loss 的大小而定,如果比较大,那么可以设为 100 或以上,如果比较小,可以设为 10 或以下。 + +2. 良好的参数初始化策略也能缓解梯度爆炸问题(权重正则化) + + > [1. 如何设置网络的初始值?*](#1-如何设置网络的初始值) + +3. 使用线性整流激活函数,如 ReLU 等 + + +# 神经网络(MLP)的万能近似定理* +> 《深度学习》 6.4.1 万能近似性质和深度 + +一个前馈神经网络如果具有至少一个非线性输出层,那么只要给予网络足够数量的隐藏单元,它就可以以任意的精度来近似任何从一个有限维空间到另一个有限维空间的函数。 + + +# 神经网络中,深度与宽度的关系,及其表示能力的差异** +> 《深度学习》 6.4 - 架构设计;这一节的内容比较分散,想要更好的回答这个问题,需要理解深度学习的本质——学习多层次组合(ch1.2),这才是现代深度学习的基本原理。 + +隐藏层的数量称为模型的**深度**,隐藏层的维数(单元数)称为该层的**宽度**。 + +**万能近似定理**表明一个单层的网络就足以表达任意函数,但是该层的维数可能非常大,且几乎没有泛化能力;此时,使用更深的模型能够减少所需的单元数,同时增强泛化能力(减少泛化误差)。参数数量相同的情况下,浅层网络比深层网络更容易过拟合。 + + +# 在深度神经网络中,引入了隐藏层(非线性单元),放弃了训练问题的凸性,其意义何在?** +> 《深度学习》 6 深度前馈网络(引言) & 6.3 隐藏单元 + +放弃训练问题的凸性,简单来说,就是放弃寻求问题的最优解。 + +**非线性单元**的加入,使训练问题不再是一个**凸优化**问题。这意味着神经网络很难得到最优解,即使一个只有两层和三个节点的简单神经网络,其训练优化问题仍然是 NP-hard 问题 (Blum & Rivest, 1993). + +> [深度学习的核心问题——NP-hard问题](http://baijiahao.baidu.com/s?id=1561255903377484&wfr=spider&for=pc%EF%BC%89) - 百家号 + +但即使如此,使用神经网络也是利大于弊的: +- 人类设计者只需要寻找正确的**函数族**即可,而不需要去寻找精确的函数。 +- 使用简单的梯度下降优化方法就可以高效地找到足够好的局部最小值 +- 增强了模型的学习/拟合能力,如原书中所说“ maxout 单元可以以任意精度近似任何凸函数”。至于放弃凸性后的优化问题可以在结合工程实践来不断改进。 “似乎传统的优化理论结果是残酷的,但我们可以通过**工程方法**和**数学技巧**来尽量规避这些问题,例如启发式方法、增加更多的机器和使用新的硬件(如GPU)。” + +> [Issue #1](https://github.com/elviswf/DeepLearningBookQA_cn/issues/1#issuecomment-396061806) · elviswf/DeepLearningBookQA_cn + + +# 稀疏表示,低维表示,独立表示* +> 《深度学习》 5.8 无监督学习算法 + +无监督学习任务的目的是找到数据的“最佳”表示。“最佳”可以有不同的表示,但是一般来说,是指该表示在比本身表示的信息更简单的情况下,尽可能地保存关于 x 更多的信息。 + +低维表示、稀疏表示和独立表示是最常见的三种“简单”表示:1)低维表示尝试将 x 中的信息尽可能压缩在一个较小的表示中;2)稀疏表示将数据集嵌入到输入项大多数为零的表示中;3)独立表示试图分开数据分布中变化的来源,使得表示的维度是统计独立的。 + +这三种表示不是互斥的,比如主成分分析(PCA)就试图同时学习低维表示和独立表示。 + +表示的概念是深度学习的核心主题之一。 + + +# 局部不变性(平滑先验)及其在基于梯度的学习上的局限性* +> 《深度学习》 5.11.2 局部不变性与平滑正则化 + +局部不变性:函数在局部小区域内不会发生较大的变化。 + +为了更好地**泛化**,机器学习算法需要由一些先验来引导应该学习什么类型的函数。 + +其中最广泛使用的“隐式先验”是平滑先验(smoothness prior),也称局部不变性先验(local constancy prior)。许多简单算法完全依赖于此先验达到良好的(局部)泛化,一个极端例子是 k-最近邻系列的学习算法。 + +但是仅依靠平滑先验**不足以**应对人工智能级别的任务。简单来说,区分输入空间中 O(k) 个区间,需要 O(k) 个样本,通常也会有 O(k) 个参数。最近邻算法中,每个训练样本至多用于定义一个区间。类似的,决策树也有平滑学习的局限性。 + +以上问题可以总结为:是否可以有效地表示复杂的函数,以及所估计的函数是否可以很好地泛化到新的输入。该问题的一个关键观点是,只要我们通过额外假设生成数据的分布来建立区域间的依赖关系,那么 O(k) 个样本足以描述多如 O(2^k) 的大量区间。通过这种方式,能够做到**非局部的泛化**。 + +> 一些其他的机器学习方法往往会提出更强的,针对特定问题的假设,例如周期性。通常,神经网络不会包含这些很强的针对性假设——深度学习的核心思想是假设数据由因素或特征组合产生,这些因素或特征可能来自一个层次结构的多个层级。许多其他类似的通用假设进一步提高了深度学习算法。这些很温和的假设允许了样本数目和可区分区间数目之间的**指数增益**。深度的分布式表示带来的指数增益有效地解决了维数灾难带来的挑战 +>> 指数增益:《深度学习》 ch6.4.1、ch15.4、ch15.5 + + +# 为什么交叉熵损失相比均方误差损失能提高以 sigmoid 和 softmax 作为激活函数的层的性能?** +> 《深度学习》 6.6 小结 中提到了这个结论,但是没有给出具体原因(可能在前文)。 + +简单来说,就是使用均方误差(MSE)作为损失函数时,会导致大部分情况下**梯度偏小**,其结果就是权重的更新很慢,且容易造成“梯度消失”现象。而交叉熵损失克服了这个缺点,当误差大的时候,权重更新就快,当误差小的时候,权重的更新才慢。 + +具体推导过程如下: + +> https://blog.csdn.net/guoyunfei20/article/details/78247263 - CSDN 博客 +> +> 这里给出了一个具体的[例子](https://blog.csdn.net/shmily_skx/article/details/53053870) + + +# 分段线性单元(如 ReLU)代替 sigmoid 的利弊*** +> 《深度学习》 6.6 小结 + +- 当神经网络比较小时,sigmoid 表现更好; +- 在深度学习早期,人们认为应该避免具有不可导点的激活函数,而 ReLU 不是全程可导/可微的 +- sigmoid 和 tanh 的输出是有界的,适合作为下一层的输入,以及整个网络的输出。实际上,目前大多数网络的输出层依然使用的 sigmoid(单输出) 或 softmax(多输出)。 + + > 为什么 ReLU 不是全程可微也能用于基于梯度的学习?——虽然 ReLU 在 0 点不可导,但是它依然存在左导数和右导数,只是它们不相等(相等的话就可导了),于是在实现时通常会返回左导数或右导数的其中一个,而不是报告一个导数不存在的错误。 + >> 一阶函数:可微==可导 + +- 对于小数据集,使用整流非线性甚至比学习隐藏层的权重值更加重要 (Jarrett et al., 2009b) +- 当数据增多时,在深度整流网络中的学习比在激活函数具有曲率或两侧**饱和**的深度网络中的学习更容易 (Glorot et al., 2011a):传统的 sigmoid 函数,由于两端饱和,在传播过程中容易丢弃信息 +- ReLU 的过程更接近生物神经元的作用过程 + + > 饱和(saturate)现象:在函数图像上表现为变得很平,对输入的微小改变会变得不敏感。 + +> https://blog.csdn.net/code_lr/article/details/51836153 - CSDN博客 +>> 答案总结自该知乎问题:https://www.zhihu.com/question/29021768 + + +# 在做正则化过程中,为什么只对权重做正则惩罚,而不对偏置做权重惩罚* +> 《深度学习》 7.1 参数范数惩罚 + +在神经网络中,参数包括每一层仿射变换的**权重**和**偏置**,我们通常只对权重做惩罚而不对偏置做正则惩罚。 + +精确拟合偏置所需的数据通常比拟合权重少得多。每个权重会指定两个变量如何相互作用。我们需要在各种条件下观察这两个变量才能良好地拟合权重。而每个偏置仅控制一个单变量。这意味着,我们不对其进行正则化也不会导致太大的方差。另外,正则化偏置参数可能会导致明显的欠拟合。 + + +# 列举常见的一些范数及其应用场景,如 L0、L1、L2、L∞、Frobenius等范数** +> 《深度学习》 2.5 范数(介绍) + +L0: 向量中非零元素的个数 + +L1: 向量中所有元素的绝对值之和 + +[![](../_assets/公式_20180610213145.png)](http://www.codecogs.com/eqnedit.php?latex=\left&space;\|&space;x&space;\right&space;\|_1=\sum_i{\left&space;|&space;x_i&space;\right&space;|}) + +L2: 向量中所有元素平方和的开放 + +[![](../_assets/公式_20180610213218.png)](http://www.codecogs.com/eqnedit.php?latex=\left&space;\|&space;x&space;\right&space;\|_2=\sqrt{\sum_i{\left&space;|&space;x_i&space;\right&space;|^2}}) + +其中 L1 和 L2 范数分别是 Lp (p>=1) 范数的特例: + +[![](../_assets/公式_20180904113355.png)](http://www.codecogs.com/eqnedit.php?latex=\left&space;\|&space;x&space;\right&space;\|_p=(\sum_i{\left&space;|&space;x_i&space;\right&space;|^2})^{\frac{1}{p}}) + +L∞: 向量中最大元素的绝对值,也称最大范数 + +[![](../_assets/公式_20180610213349.png)](http://www.codecogs.com/eqnedit.php?latex=\left&space;\|&space;x&space;\right&space;\|_\infty=\max_i\left&space;|&space;x&space;\right&space;|) + +Frobenius 范数:相当于作用于矩阵的 L2 范数 + +[![](../_assets/公式_20180610213428.png)](http://www.codecogs.com/eqnedit.php?latex=\left&space;\|&space;A&space;\right&space;\|_F=\sqrt{\sum_{i,j}A_{i,j}^2}) + +**范数的应用**:正则化——权重衰减/参数范数惩罚 + +**权重衰减的目的** + +限制模型的学习能力,通过限制参数 θ 的规模(主要是权重 w 的规模,偏置 b 不参与惩罚),使模型偏好于权值较小的目标函数,防止过拟合。 + +> 《深度学习》 7.1 参数范数惩罚 + + +# L1 和 L2 范数的异同*** +> 《深度学习》 7.1.1 L2 参数正则化 & 7.1.2 - L1 参数正则化 + +**相同点** +- 限制模型的学习能力,通过限制参数的规模,使模型偏好于权值较小的目标函数,防止过拟合。 + +**不同点** +- L1 正则化可以产生稀疏权值矩阵,即产生一个稀疏模型,可以用于特征选择;一定程度上防止过拟合 +- L2 正则化主要用于防止模型过拟合 +- L1 适用于特征之间有关联的情况;L2 适用于特征之间没有关联的情况 + +> [机器学习中正则化项L1和L2的直观理解](https://blog.csdn.net/jinping_shi/article/details/52433975) - CSDN博客 + + +# 为什么 L1 正则化可以产生稀疏权值,L2 正则化可以防止过拟合?** + +## 为什么 L1 正则化可以产生稀疏权值,而 L2 不会? + +添加 L1 正则化,相当于在 L1范数的约束下求目标函数 J 的最小值,下图展示了二维的情况: + +![](../_assets/TIM截图20180608171710.png) + +图中 J 与 L 首次相交的点就是最优解。L1 在和每个坐标轴相交的地方都会有“角”出现(多维的情况下,这些角会更多),在角的位置就会产生稀疏的解。而 J 与这些“角”相交的机会远大于其他点,因此 L1 正则化会产生稀疏的权值。 + +类似的,可以得到带有 L2正则化的目标函数在二维平面上的图形,如下: + +![](../_assets/TIM截图20180608172312.png) + +相比 L1,L2 不会产生“角”,因此 J 与 L2 相交的点具有稀疏性的概率就会变得非常小。 + +> [机器学习中正则化项L1和L2的直观理解](https://blog.csdn.net/jinping_shi/article/details/52433975) - CSDN博客 + +## 为什么 L1 和 L2 正则化可以防止过拟合? + +L1 & L2 正则化会使模型偏好于更小的权值。 + +简单来说,更小的权值意味着更低的模型复杂度,也就是对训练数据的拟合刚刚好(奥卡姆剃刀),不会过分拟合训练数据(比如异常点,噪声),以提高模型的泛化能力。 + +此外,添加正则化相当于为模型添加了某种**先验**(限制),规定了参数的分布,从而降低了模型的复杂度。模型的复杂度降低,意味着模型对于噪声与异常点的抗干扰性的能力增强,从而提高模型的泛化能力。 + +> [机器学习中防止过拟合的处理方法](https://blog.csdn.net/heyongluoyao8/article/details/49429629) - CSDN博客 + + +# 简单介绍常用的激活函数,如 sigmoid、relu、softplus、tanh、RBF 及其应用场景*** +> 《深度学习》 6.3 隐藏单元 + +## 整流线性单元(ReLU) + +[![](../_assets/公式_20180610213451.png)](http://www.codecogs.com/eqnedit.php?latex=g(z)=\max(0,z)) + +![](../_assets/TIM截图20180608212808.png) + +整流线性单元(ReLU)通常是激活函数较好的默认选择。 + +整流线性单元易于优化,因为它们和线性单元非常类似。线性单元和整流线性单元的唯一区别在于整流线性单元在其一半的定义域上输出为零。这使得只要整流线性单元处于激活状态,它的导数都能保持较大。它的梯度不仅大而且一致。整流操作的二阶导数几乎处处为 0,并且在整流线性单元处于激活状态时,它的一阶导数处处为 1。这意味着相比于引入二阶效应的激活函数来说,它的梯度方向对于学习来说更加有用。 + +**ReLU 的拓展** + +ReLU 的三种拓展都是基于以下变型: + +[![](../_assets/公式_20180610214123.png)](http://www.codecogs.com/eqnedit.php?latex=g(z,\alpha)&space;=\max(0,z)+\alpha\min(0,z)) + +ReLU 及其扩展都是基于一个原则,那就是如果它们的行为更接近线性,那么模型更容易优化。 + +- 绝对值整流(absolute value rectification) + + 固定 α == -1,此时整流函数即一个绝对值函数 + + [![](../_assets/公式_20180610214502.png)](http://www.codecogs.com/eqnedit.php?latex=g(z)&space;=\left&space;|&space;z&space;\right&space;|) + + 绝对值整流被用于图像中的对象识别 (Jarrett et al., 2009a),其中寻找在输入照明极性反转下不变的特征是有意义的。 + +- 渗漏整流线性单元(Leaky ReLU, Maas et al., 2013) + + 固定 α 为一个类似于 0.01 的小值 + +- 参数化整流线性单元(parametric ReLU, PReLU, He et al., 2015) + + 将 α 作为一个参数学习 + +- maxout 单元 (Goodfellow et al., 2013a) + + maxout 单元 进一步扩展了 ReLU,它是一个可学习的多达 k 段的分段函数 + + 关于 maxout 网络的分析可以参考论文或网上的众多分析,下面是 Keras 中的实现: + ``` + # input shape: [n, input_dim] + # output shape: [n, output_dim] + W = init(shape=[k, input_dim, output_dim]) + b = zeros(shape=[k, output_dim]) + output = K.max(K.dot(x, W) + b, axis=1) + ``` + > [深度学习(二十三)Maxout网络学习](https://blog.csdn.net/hjimce/article/details/50414467) - CSDN博客 + +## sigmoid 与 tanh(双曲正切函数) + +在引入 ReLU 之前,大多数神经网络使用 sigmoid 激活函数: + +[![](../_assets/公式_20180610214846.png)](http://www.codecogs.com/eqnedit.php?latex=g(z)=\sigma(z)=\frac{1}{1+\exp(-z)}) + +![](../_assets/TIM截图20180608195851.png) + +或者 tanh(双曲正切函数): + +[![](../_assets/公式_20180610214926.png)](http://www.codecogs.com/eqnedit.php?latex=g(z)&space;=&space;\tanh(z)) + +tanh 的图像类似于 sigmoid,区别在其值域为 (-1, 1). + +这两个函数有如下关系: + +[![](../_assets/公式_20180610215029.png)](http://www.codecogs.com/eqnedit.php?latex=\tanh(z)=2\sigma&space;(2z)-1) + +**sigmoid 函数要点**: +- sigmoid 常作为输出单元用来预测二值型变量取值为 1 的概率 + > 换言之,sigmoid 函数可以用来产生**伯努利分布**中的参数 ϕ,因为它的值域为 (0, 1). +- sigmoid 函数在输入取绝对值非常大的正值或负值时会出现**饱和**(saturate)现象,在图像上表现为开始变得很平,此时函数会对输入的微小改变会变得不敏感。仅当输入接近 0 时才会变得敏感。 + > 饱和现象会导致基于梯度的学习变得困难,并在传播过程中丢失信息。——[为什么用ReLU代替sigmoid?](#8.-分段线性单元(如-ReLU)代替-sigmoid-的利弊) +- 如果要使用 sigmoid 作为激活函数时(浅层网络),tanh 通常要比 sigmoid 函数表现更好。 + > tanh 在 0 附近与单位函数类似,这使得训练 tanh 网络更容易些。 + +## 其他激活函数(隐藏单元) + +很多未发布的非线性激活函数也能表现的很好,但没有比流行的激活函数表现的更好。比如使用 cos 也能在 MNIST 任务上得到小于 1% 的误差。通常新的隐藏单元类型只有在被明确证明能够提供显著改进时才会被发布。 + +**线性激活函数**: + +如果神经网络的每一层都由线性变换组成,那么网络作为一个整体也将是线性的,这会导致失去万能近似的性质。但是,仅**部分层是纯线性**是可以接受的,这可以帮助**减少网络中的参数**。 + +**softmax**: + +softmax 单元常作为网络的输出层,它很自然地表示了具有 k 个可能值的离散型随机变量的概率分布。 + +**径向基函数(radial basis function, RBF)**: + +[![](../_assets/公式_20180610215150.png)](http://www.codecogs.com/eqnedit.php?latex=h_i=\exp(-\frac{1}{\sigma_i^2}\left&space;\|&space;W_{:,i}-x&space;\right&space;\|^2)) + +在神经网络中很少使用 RBF 作为激活函数,因为它对大部分 x 都饱和到 0,所以很难优化。 + +**softplus**: + +[![](../_assets/公式_20180610215222.png)](http://www.codecogs.com/eqnedit.php?latex=g(z)=\zeta(z)=\log(1+\exp(z))) + +![](../_assets/TIM截图20180608204913.png) + +softplus 是 ReLU 的平滑版本。通常不鼓励使用 softplus 函数,大家可能希望它具有优于整流线性单元的点,但根据经验来看,它并没有。 +> (Glorot et al., 2011a) 比较了这两者,发现 ReLU 的结果更好。 + +**硬双曲正切函数(hard tanh)**: + +[![](../_assets/公式_20180610215308.png)](http://www.codecogs.com/eqnedit.php?latex=g(z)=\max(-1,\min(1,a))) + +它的形状和 tanh 以及整流线性单元类似,但是不同于后者,它是有界的。 +> Collobert, 2004 + +## sigmoid 和 softplus 的一些性质 + +![](../_assets/TIM截图20180608205223.png) + +> 《深度学习》 3.10 常用函数的有用性质 + + +# Jacobian 和 Hessian 矩阵及其在深度学习中的重要性* +> 《深度学习》 4.3.1 梯度之上:Jacobian 和 Hessian 矩阵 + + +# 信息熵、KL 散度(相对熵)与交叉熵** +> 《深度学习》 3.13 信息论 + +信息论的基本想法是一个不太可能的事件居然发生了,要比一个非常可能的事件发生,能提供更多的信息。 + +该想法可描述为以下性质: +1. 非常可能发生的事件信息量要比较少,并且极端情况下,确保能够发生的事件应该没有信息量。 +2. 比较不可能发生的事件具有更高的信息量。 +3. 独立事件应具有增量的信息。例如,投掷的硬币两次正面朝上传递的信息量,应该是投掷一次硬币正面朝上的信息量的两倍。 + +## 自信息与信息熵 + +自信息(self-information)是一种量化以上性质的函数,定义一个事件 x 的自信息为: + +[![](../_assets/公式_20180610215339.png)](http://www.codecogs.com/eqnedit.php?latex=I(x)=-\log&space;P(x)) + +> 当该对数的底数为 e 时,单位为奈特(nats,本书标准);当以 2 为底数时,单位为比特(bit)或香农(shannons) + +自信息只处理单个的输出。此时,用信息熵(Information-entropy)来对整个概率分布中的不确定性总量进行量化: + +[![](../_assets/公式_20180610215417.png)](http://www.codecogs.com/eqnedit.php?latex=H(\mathrm{X})=\mathbb{E}_{\mathrm{X}&space;\sim&space;P}[I(x)]=-\sum_{x&space;\in&space;\mathrm{X}}P(x)\log&space;P(x)) + +> 信息熵也称香农熵(Shannon entropy) +> +> 信息论中,记 `0log0 = 0` + +## 相对熵(KL 散度)与交叉熵 + +P 对 Q 的 **KL散度**(Kullback-Leibler divergence): + +[![](../_assets/公式_20180610215445.png)](http://www.codecogs.com/eqnedit.php?latex=D_P(Q)=\mathbb{E}_{\mathrm{X}\sim&space;P}\left&space;[&space;\log&space;\frac{P(x)}{Q(x)}&space;\right&space;]=\sum_{x&space;\in&space;\mathrm{X}}P(x)\left&space;[&space;\log&space;P(x)-\log&space;Q(x)&space;\right&space;]) + +**KL 散度在信息论中度量的是那个直观量**: + +在离散型变量的情况下, KL 散度衡量的是,当我们使用一种被设计成能够使得概率分布 Q 产生的消息的长度最小的编码,发送包含由概率分布 P 产生的符号的消息时,所需要的额外信息量。 + +**KL 散度的性质**: +- 非负;KL 散度为 0 当且仅当P 和 Q 在离散型变量的情况下是相同的分布,或者在连续型变量的情况下是“几乎处处”相同的 +- 不对称;D_p(q) != D_q(p) + +**交叉熵**(cross-entropy): + +[![](../_assets/公式_20180610215522.png)](http://www.codecogs.com/eqnedit.php?latex=H_P(Q)=-\mathbb{E}_{\mathrm{X}\sim&space;P}\log&space;Q(x)=-\sum_{x&space;\in&space;\mathrm{X}}P(x)\log&space;Q(x)) + +> [信息量,信息熵,交叉熵,KL散度和互信息(信息增益)](https://blog.csdn.net/haolexiao/article/details/70142571) - CSDN博客 + +**交叉熵与 KL 散度的关系**: + +[![](../_assets/公式_20180610215554.png)](http://www.codecogs.com/eqnedit.php?latex=H_P(Q)=H(P)+D_P(Q)) + +**针对 Q 最小化交叉熵等价于最小化 P 对 Q 的 KL 散度**,因为 Q 并不参与被省略的那一项。 + +最大似然估计中,最小化 KL 散度其实就是在最小化分布之间的交叉熵。 + +> 《深度学习》 ch5.5 - 最大似然估计 + + +# 如何避免数值计算中的上溢和下溢问题,以 softmax 为例* +> 《深度学习》 4.1 上溢与下溢 + +- **上溢**:一个很大的数被近似为 ∞ 或 -∞; +- **下溢**:一个很小的数被近似为 0 + +必须对上溢和下溢进行**数值稳定**的一个例子是 **softmax 函数**: + +[![](../_assets/公式_20180610215623.png)](http://www.codecogs.com/eqnedit.php?latex=\mathrm{softmax}(x)=\frac{\exp(x_i)}{\sum_{j=1}^n&space;\exp(x_j)}) + +因为 softmax 解析上的函数值不会因为从输入向量减去或加上**标量**而改变, +于是一个简单的解决办法是对 x: + +[![](../_assets/公式_20180610215656.png)](http://www.codecogs.com/eqnedit.php?latex=x=x-\max_ix_i) + +减去 `max(x_i)` 导致 `exp` 的最大参数为 `0`,这排除了上溢的可能性。同样地,分母中至少有一个值为 `1=exp(0)` 的项,这就排除了因分母下溢而导致被零除的可能性。 + +**注意**:虽然解决了分母中的上溢与下溢问题,但是分子中的下溢仍可以导致整体表达式被计算为零。此时如果计算 log softmax(x) 时,依然要注意可能造成的上溢或下溢问题,处理方法同上。 + +当然,大多数情况下,这是底层库开发人员才需要注意的问题。 + + +# 训练误差、泛化误差;过拟合、欠拟合;模型容量,表示容量,有效容量,最优容量的概念; 奥卡姆剃刀原则* +> 《深度学习》 5.2 容量、过拟合和欠拟合 + +## 过拟合的一些解决方案*** +- 参数范数惩罚(Parameter Norm Penalties) +- 数据增强(Dataset Augmentation) +- 提前终止(Early Stopping) +- 参数绑定与参数共享(Parameter Tying and Parameter Sharing) +- Bagging 和其他集成方法 +- Dropout +- 批标准化(Batch Normalization) + +# 高斯分布的广泛应用的原因** +> 《深度学习》 3.9.3 高斯分布 + +## 高斯分布(Gaussian distribution) + +高斯分布,即正态分布(normal distribution): + +[![](../_assets/公式_20180610215732.png)](http://www.codecogs.com/eqnedit.php?latex=N(x;\mu,\sigma^2)=\sqrt\frac{1}{2\pi\sigma^2}\exp\left&space;(&space;-\frac{1}{2\sigma^2}(x-\mu)^2&space;\right&space;)) + +概率密度函数图像: + +![](../_assets/TIM截图20180610131620.png) + +其中峰的 `x` 坐标由 `µ` 给出,峰的宽度受 `σ` 控制;特别的,当 `µ = 0, σ = 1`时,称为标准正态分布 + +正态分布的均值 `E = µ`;标准差 `std = σ`,方差为其平方 + +## 为什么推荐使用高斯分布? +当我们由于缺乏关于某个实数上分布的先验知识而不知道该选择怎样的形式时,正态分布是默认的比较好的选择,其中有两个原因: +1. 我们想要建模的很多分布的真实情况是比较接近正态分布的。**中心极限定理**(central limit theorem)说明很多独立随机变量的和近似服从正态分布。这意味着在实际中,很多复杂系统都可以被成功地建模成正态分布的噪声,即使系统可以被分解成一些更结构化的部分。 +2. 第二,在具有相同方差的所有可能的概率分布中,正态分布在实数上具有最大的不确定性。因此,我们可以认为正态分布是对模型加入的先验知识量最少的分布。 + > 关于这一点的证明:《深度学习》 ch19.4.2 - 变分推断和变分学习 + +**多维正态分布** + +正态分布可以推广到 n 维空间,这种情况下被称为**多维正态分布**。 + +![](../_assets/TIM截图20180610132602.png) + +参数 `µ` 仍然表示分布的均值,只不过现在是一个向量。参数 Σ 给出了分布的协方差矩阵(一个正定对称矩阵)。 + + +# 表示学习、自编码器与深度学习** +> 《深度学习》 1 引言 + +**表示学习**: + +对于许多任务来说,我们很难知道应该提取哪些特征。解决这个问题的途径之一是使用机器学习来发掘表示本身,而不仅仅把表示映射到输出。这种方法我们称之为**表示学习**(representation learning)。学习到的表示往往比手动设计的表示表现得更好。并且它们只需最少的人工干预,就能让AI系统迅速适应新的任务。 + +**自编码器**: + +表示学习算法的典型例子是 自编码器(autoencoder)。自编码器由一个**编码器**(encoder)函数和一个**解码器**(decoder)函数组合而成。 +- 编码器函数将输入数据转换为一种不同的表示; +- 解码器函数则将这个新的表示转换到原来的形式。 + +我们期望当输入数据经过编码器和解码器之后**尽可能多地保留信息**,同时希望新的表示有一些好的特性,这也是自编码器的训练目标。 + +**深度学习**: + +深度学习(deep learning)通过简单的表示来表达复杂的表示,以解决表示学习中的核心问题。 + +![](../_assets/TIM截图20180610194353.png) + +深度学习模型的示意图 + +计算机难以理解原始感观输入数据的含义,如表示为像素值集合的图像,将一组像素映射到对象标识的函数非常复杂。深度学习将所需的复杂映射分解为一系列嵌套的简单映射(每个由模型的不同层描述)来解决这一难题。 + +输入展示在**可见层**(visible layer),这样命名的原因是因为它包含我们能观察到的变量。然后是一系列从图像中提取越来越多抽象特征的**隐藏层**(hidden layer),称为“隐藏”的原因是因为它们的值不在数据中给出。 + +模型必须确定哪些概念有利于解释观察数据中的关系。这里的图像是每个隐藏单元表示的特征的可视化。给定像素,**第一隐藏层**可以轻易地通过比较相邻像素的亮度来**识别边缘**。有了第一隐藏层描述的边缘,**第二隐藏层**可以容易地**搜索轮廓和角**。给定第二隐藏层中关于角和轮廓的图像描述,**第三隐藏层**可以找到轮廓和角的特定集合来**检测整个特定对象**。最后,根据图像描述中包含的对象部分,可以识别图像中存在的对象。 + +> 实际任务中并不一定具有这么清晰的可解释性,很多时候你并不知道每个隐藏层到底识别出了哪些特征。 + +学习数据的正确表示的想法是**解释深度学习**的一个视角。 + +> 另一个视角是深度促使计算机学习一个多步骤的计算机程序。——《深度学习》 ch1 - 引言 +> +> 早期的深度学习称为神经网络,因为其主要指导思想来源于生物神经学。从神经网络向深度学习的术语转变也是因为指导思想的改变。 + + +# L1、L2 正则化与 MAP 贝叶斯推断的关系* +> 《深度学习》 5.6.1 最大后验 (MAP) 估计 + +许多正则化策略可以被解释为 MAP 贝叶斯推断: +- L2 正则化相当于权重是高斯先验的 MAP 贝叶斯推断 +- 对于 L1正则化,用于正则化代价函数的惩罚项与通过 MAP 贝叶斯推断最大化的**对数先验项**是等价的 + + +# 什么是欠约束,为什么大多数的正则化可以使欠约束下的欠定问题在迭代过程中收敛* +> 《深度学习》 7.3 正则化与欠约束问题 + + +# 为什么考虑在模型训练时对输入 (隐藏单元或权重) 添加方差较小的噪声?* +> 《深度学习》 7.5 噪声鲁棒性 + +对于某些模型而言,向输入添加方差极小的噪声等价于对权重施加范数惩罚 (Bishop, 1995a,b)。 + +在一般情况下,**注入噪声比简单地收缩参数强大**。特别是噪声被添加到**隐藏单元**时会更加强大,**Dropout** 方法正是这种做法的主要发展方向。 + +另一种正则化模型的噪声使用方式是将其加到**权重**。这项技术主要用于循环神经网络 (Jim et al., 1996; Graves, 2011)。这可以被解释为关于权重的贝叶斯推断的随机实现。贝叶斯学习过程将权重视为不确定的,并且可以**通过概率分布表示这种不确定性**。向权重添加噪声是反映这种不确定性的一种实用的随机方法。 + + +# 多任务学习、参数绑定和参数共享*** +> [45. 迁移学习相关概念](#45-迁移学习相关概念) + +## 多任务学习 +> 《深度学习》 7.7 多任务学习 + +多任务学习 (Caruana, 1993) 是通过**合并多个任务中的样例**(可以视为对参数施加软约束)来提高泛化的一种方式。 + +正如额外的训练样本能够将模型参数推向具有更好泛化能力的值一样,当**模型的一部分被多个额外的任务共享**时,这部分将被约束为良好的值(如果共享合理),通常会带来更好的泛化能力。 + +**多任务学习中一种普遍形式**: + +![](../_assets/TIM截图20180610203703.png) + +多任务学习在深度学习框架中可以以多种方式进行,该图展示了一种普遍形式:任务共享相同输入但涉及不同语义的输出。 + +在该示例中,额外假设顶层隐藏单元 h(1) 和 h(2) 专用于不同的任务——分别预测 y(1) 和 y(2),而一些**中间层表示** h(shared) 在所有任务之间共享;h(3) 表示无监督学习的情况。 + +这里的基本假设是存在解释输入 x 变化的**共同因素池**,而每个任务与这些因素的**子集**相关联。 + +该模型通常可以分为两类相关的参数: +1. 具体任务的参数(只能从各自任务的样本中实现良好的泛化) +2. 所有任务共享的通用参数(从所有任务的汇集数据中获益)——参数共享 + +因为**共享参数**,其统计强度可大大提高(共享参数的样本数量相对于单任务模式增加的比例),并能改善泛化和泛化误差的范围 (Baxter, 1995)。 + +参数共享仅当不同的任务之间存在某些统计关系的假设是合理(意味着某些参数能通过不同任务共享)时才会发生这种情况 + +## 参数绑定和参数共享 +> 《深度学习》 7.9 参数绑定和参数共享 + +**参数绑定**: + +有时,我们可能无法准确地知道应该使用什么样的参数,但我们根据相关领域和模型结构方面的知识得知模型参数之间应该存在一些相关性。 + +考虑以下情形:我们有两个模型执行相同的分类任务(具有相同类别),但输入分布稍有不同。 + +形式地,我们有参数为 w(A) 的模型 A 和参数为 w(B) 的模型 B。这两种模型将输入映射到两个不同但相关的输出: y(A) = f(x;w(A)) 和 y(B) = f(x;w(B)) + +可以想象,这些任务会足够相似(或许具有相似的输入和输出分布),因此我们认为模型参数 w(A) 和 w(B) 应彼此靠近。具体来说,我们可以使用以下形式的参数范数惩罚(这里使用的是 L2 惩罚,也可以使用其他选择): + +[![](../_assets/公式_20180610215812.png)](http://www.codecogs.com/eqnedit.php?latex=\Omega&space;(w^{(A)},w^{(B)})=\left&space;\|&space;w^{(A)}-w^{(B)}&space;\right&space;\|^2_2) + +**参数共享**是这个思路下更流行的做法——强迫部分参数相等 + +和正则化参数使其接近(通过范数惩罚)相比,参数共享的一个显著优点是能够“减少内存”——只有参数(唯一一个集合)的子集需要被存储在内存中,特别是在 CNN 中。 + + +# Dropout 与 Bagging 集成方法的关系,Dropout 带来的意义与其强大的原因*** + +## Bagging 集成方法 +> 《深度学习》 7.11 Bagging 和其他集成方法 + +**集成方法**: + +其主要想法是分别训练几个不同的模型,然后让所有模型表决测试样例的输出。这是机器学习中常规策略的一个例子,被称为**模型平均**(model averaging)。采用这种策略的技术被称为**集成方法**。 + +模型平均(model averaging)**奏效的原因**是不同的模型通常不会在测试集上产生完全相同的误差。平均上, 集成至少与它的任何成员表现得一样好,并且**如果成员的误差是独立的**,集成将显著地比其成员表现得更好。 + +**Bagging**: + +Bagging(bootstrap aggregating)是通过结合几个模型降低泛化误差的技术 (Breiman, 1994)。 + +具体来说,Bagging 涉及构造 k 个**不同的数据集**。每个数据集从原始数据集中**重复采样**构成,和原始数据集具有**相同数量**的样例。这意味着,每个数据集以高概率缺少一些来自原始数据集的例子,还包含若干重复的例子(更具体的,如果采样所得的训练集与原始数据集大小相同,那所得数据集中大概有原始数据集 **2/3** 的实例) + +![](../_assets/TIM截图20180611150734.png) + +**图像说明**:该图描述了 Bagging 如何工作。假设我们在上述数据集(包含一个 8、一个 6 和一个 9)上**训练数字 8** 的检测器。假设我们制作了两个不同的重采样数据集。 Bagging 训练程序通过有放回采样构建这些数据集。第一个数据集忽略 9 并重复 8。在这个数据集上,检测器得知数字**顶部有一个环就对应于一个 8**。第二个数据集中,我们忽略 6 并重复 9。在这种情况下,检测器得知数字**底部有一个环就对应于一个 8**。这些单独的分类规则中的每一个都是不可靠的,但如果我们平均它们的输出,就能得到鲁棒的检测器,**只有当 8 的两个环都存在时才能实现最大置信度**。 + + + +## Dropout +> 《深度学习》 7.12 Dropout + +**Dropout 的意义与强大的原因**: + +简单来说,Dropout (Srivastava et al., 2014) 通过**参数共享**提供了一种廉价的 **Bagging** 集成近似,能够训练和评估**指数级数量**的神经网络。 + +![](../_assets/TIM截图20180611152559.png) + +Dropout 训练的集成包括所有从基础网络除去部分单元后形成的子网络。具体而言,只需将一些单元的**输出乘零**就能有效地删除一个单元。 + +通常,**隐藏层**的采样概率为 0.5,**输入**的采样概率为 0.8;超参数也可以采样,但其采样概率一般为 1 + +**Dropout与Bagging的不同点**: +- 在 Bagging 的情况下,所有模型都是独立的;而在 Dropout 的情况下,所有模型**共享参数**,其中每个模型继承父神经网络参数的不同子集。 +- 在 Bagging 的情况下,每一个模型都会在其相应训练集上训练到收敛。而在 Dropout 的情况下,通常大部分模型都没有显式地被训练;取而代之的是,在单个步骤中我们训练一小部分的子网络,参数共享会使得剩余的子网络也能有好的参数设定。 + +**权重比例推断规则**: + +简单来说,如果我们使用 0.5 的包含概率(keep prob),权重比例规则相当于在训练结束后**将权重除 2**,然后像平常一样使用模型;等价的,另一种方法是在训练期间将单元的状态乘 2。 + +无论哪种方式,我们的目标是确保在测试时一个单元的期望总输入与在训练时该单元的期望总输入是大致相同的(即使近半单位在训练时丢失)。 + + +# 批梯度下降法(Batch SGD)更新过程中,批的大小会带来怎样的影响** +> 《深度学习》 8.1.3 批量算法和小批量算法 + +特别说明:本书中,“**批量**”指使用使用全部训练集;“**小批量**”才用来描述小批量随机梯度下降算法中用到的小批量样本;而**随机梯度下降**(SGD)通常指每次只使用单个样本 + +**批的大小**通常由以下几个因素决定: +- **较大的批能得到更精确的梯度估计**,但回报是小于线性的。 +- **较小的批能带来更好的泛化误差**,泛化误差通常在批大小为 1 时最好。但是,因为梯度估计的高方差,小批量训练需要**较小的学习率**以保持稳定性,这意味着**更长的训练时间**。 + > 可能是由于小批量在学习过程中加入了噪声,它们会有一些正则化效果 (Wilson and Martinez, 2003) +- **内存消耗和批的大小成正比**,如果批量处理中的所有样本可以并行地处理(通常确是如此)。 +- 在某些硬件上使用特定大小可以减少运行时间。尤其是在使用 GPU 时,通常使用 **2 的幂数**作为批量大小可以获得更少的运行时间。一般,2 的幂数的**取值范围是 32 到 256**,16 有时在尝试大模型时使用。 +- 小批量更容易利用**多核架构**,但是太小的批并不会减少计算时间,这促使我们使用一些**绝对最小批量** + +很多机器学习上的优化问题都可以分解成并行地计算不同样本上单独的更新。换言之,我们在计算小批量样本 X 上最小化 J(X) 的更新时,同时可以计算其他小批量样本上的更新。 +> 异步并行分布式方法 -> 《深度学习》 12.1.3 大规模的分布式实现 + + +# 如何避免深度学习中的病态,鞍点,梯度爆炸,梯度弥散?*** +> 《深度学习》 8.2 神经网络优化中的挑战 + +## 病态(ill-conditioning) +> 《深度学习》 8.2.1 病态 + +**什么是病态?** +> [神经网络优化中的病态问题](https://blog.csdn.net/foolsnowman/article/details/51614862) - CSDN博客 +> +> [什么是 ill-conditioning 对SGD有什么影响?](https://www.zhihu.com/question/56977045) - 知乎 + +简单来说,深度学习中的病态问题指的就是学习/优化变的困难,需要更多的迭代次数才能达到相同的精度。 + +> 病态问题普遍存在于数值优化、凸优化或其他形式的优化中 -> ch4.3.1 - 梯度之上:Jacobian 和 Hessian 矩阵 + +更具体的,导致病态的原因是问题的**条件数**(condition number)非常大,其中`条件数 = 函数梯度最大变化速度 / 梯度最小变化速度`(对于二阶可导函数,条件数的严格定义是:Hessian矩阵最大特征值的上界 / 最小特征值的下界)。 + +**条件数大**意味着目标函数在有的地方(或有的方向)变化很快、有的地方很慢,比较不规律,从而很难用当前的局部信息(梯度)去比较准确地预测最优点所在的位置,只能一步步缓慢的逼近最优点,从而优化时需要更多的迭代次数。 + +**如何避免病态?** + +知道了什么是病态,那么所有有利于**加速训练**的方法都属于在避免病态,其中最主要的还是优化算法。 + +深度学习主要使用的优化算法是**梯度下降**,所以避免病态问题的关键是改进梯度下降算法: +- 随机梯度下降(SGD)、批量随机梯度下降 +- 动态的学习率 +- **带动量的 SGD** +> [28. SGD 以及学习率的选择方法,带动量的 SGD 对于 Hessian 矩阵病态条件及随机梯度方差的影响***](#28-sgd-以及学习率的选择方法带动量的-sgd-对于-hessian-矩阵病态条件及随机梯度方差的影响) + +## 鞍点(saddle point) + +对于很多高维非凸函数(神经网络)而言,局部极小值/极大值事实上都**远少于**另一类梯度为零的点:鞍点 + +**什么是鞍点?** + +二维和三维中的鞍点: + +![](../_assets/TIM截图20180611171103.png) +![](../_assets/TIM截图20180611171213.png) +> 《深度学习》 4.3 基于梯度的优化方法 + +**鞍点激增对于训练算法来说有哪些影响?** + +对于只使用梯度信息的一阶优化算法(随机梯度下降)而言,目前情况还不清楚。不过,虽然鞍点附近的梯度通常会非常小,但是 Goodfellow et al. (2015) 认为连续的梯度下降**会逃离而不是吸引到鞍点**。 + +对于牛顿法(二阶梯度)而言,鞍点问题会比较明显。不过神经网络中很少使用二阶梯度进行优化。 + +## 长期依赖与梯度爆炸、消失 +> 《深度学习》 10.11 优化长期依赖 + +当计算图变得很深时(循环神经网络),神经网络优化算法会面临的另外一个难题就是**长期依赖**,由于变深的结构使模型丧失了学习到先前信息的能力,让优化变得极其困难;具体来说,就是会出现**梯度消失**和**梯度爆炸**问题。 + +**如何避免梯度爆炸?** +> [2. 梯度爆炸的解决办法***](#2-梯度爆炸的解决办法) + +**如何缓解梯度消失?** + +梯度截断有助于处理爆炸的梯度,但它无助于梯度消失。 + +一个想法是:在展开循环架构的计算图中,沿着与弧边相关联的梯度乘积接近 1 的部分创建路径——LSTM, GRU 等**门控机制**正是该想法的实现。 +> 《深度学习》 10.10 长短期记忆和其他门控 RNN + +另一个想法是:正则化或约束参数,以引导“信息流”;或者说,希望**梯度向量**在反向传播时能维持其幅度。形式上,我们要使 + +[![](../_assets/公式_20180611194550.png)](http://www.codecogs.com/eqnedit.php?latex=(\nabla_{h^{(t)}}L)\frac{\partial&space;h^{(t)}}{\partial&space;h^{(t-1)}}) + +与梯度向量 + +[![](../_assets/公式_20180611194704.png)](http://www.codecogs.com/eqnedit.php?latex=\nabla_{h^{(t)}}L) + +一样大。 + +**一些具体措施**: +1. 批标准化(Batch Normalization) + > [31. 批标准化(Batch Normalization)的意义**](#31-批标准化batch-normalization的意义) + +2. 在这个目标下, Pascanu et al. (2013a) 提出了以下正则项: + + ![](../_assets/TIM截图20180611194754.png) + + 这种方法的一个主要弱点是,在处理数据冗余的任务时如语言模型,它并不像 LSTM 一样有效。 + + +# SGD 以及学习率的选择方法、带动量的 SGD*** +> 《深度学习》 8.3 基本算法 + +## (批)随机梯度下降(SGD)与学习率 + +![](../_assets/TIM截图20180611195027.png) + +SGD 及相关的小批量亦或更广义的基于梯度优化的在线学习算法,一个重要的性质是每一步更新的计算时间不依赖训练样本数目的多寡。因为它每个 step 的样本数是固定的。 + +所以即使训练样本数目非常大时,它们也能收敛。对于足够大的数据集, SGD 可能会在处理整个训练集之前就收敛到最终测试集误差的某个固定容差范围内。 + +**SGD 与学习率** + +SGD 算法中的一个关键参数是学习率。在实践中,有必要**随着时间的推移逐渐降低学习率**。 + +实践中,一般会线性衰减学习率直到第 τ 次迭代: + +[![](../_assets/公式_20180611195622.png)](http://www.codecogs.com/eqnedit.php?latex=\epsilon_k=(1-\alpha)\epsilon_0+\alpha\epsilon_\tau) + +其中 α=k/τ。在 τ 步迭代之后,一般使 ϵ 保持常数。 + +使用线性策略时,需要选择的参数有 ϵ_0, ϵ_τ 和 τ +- 通常 τ 被设为需要反复遍历训练集几百次的迭代次数(?) +- 通常 ϵ_τ 应设为大约 ϵ_0 的 1% + +**如何设置 ϵ_0?** + +若 ϵ_0 太大,学习曲线将会剧烈振荡,代价函数值通常会明显增加。温和的振荡是良好的,容易在训练随机代价函数(例如使用 Dropout 的代价函数)时出现。**如果学习率太小**,那么学习过程会很缓慢。**如果初始学习率太低**,那么学习可能会卡在一个相当高的代价值。通常,就总训练时间和最终代价值而言,最优初始学习率会高于大约迭代 100 次左右后达到最佳效果的学习率。**因此,通常最好是检测最早的几轮迭代,选择一个比在效果上表现最佳的学习率更大的学习率,但又不能太大导致严重的震荡。** + +学习率可通过试验和误差来选取,通常最好的选择方法是**监测目标函数值随时间变化的学习曲线**——与**其说是科学,这更像是一门艺术**。 +> [29. 自适应学习率算法: AdaGrad,RMSProp,Adam 等***](#29-自适应学习率算法-adagradrmspropadam-等) + +## 带动量的 SGD + +![](../_assets/TIM截图20180611203427.png) + +从形式上看, 动量算法引入了变量 v 充当速度角色——它代表参数在参数空间移动的方向和速率。速度被设为负梯度的指数衰减平均。 + +之前,步长只是梯度范数乘以学习率。现在,步长取决于梯度序列的大小和排列。当许多连续的梯度指向相同的方向时,步长最大。如果动量算法总是观测到梯度 g,那么它会在方向 −g 上不停加速,直到达到最终速度,其中步长大小为 + +[![](../_assets/公式_20180611210928.png)](http://www.codecogs.com/eqnedit.php?latex=\frac{\epsilon\left\|g\right\|}{1-\alpha}) + +在实践中,α 的一般取值为 0.5, 0.9 和 0.99,分别对应**最大速度** 2倍,10倍和100倍于普通的 SGD 算法。和学习率一样, α 也应该随着时间不断调整(变大),但没有收缩 ϵ 重要。 + +**为什么要加入动量?** + +加入的动量主要目的是解决两个问题: Hessian 矩阵的**病态**条件和**随机梯度的方差**。简单来说,就是为了加速学习。 + +虽然动量的加入有助于缓解这些问题,但其代价是引入了另一个超参数。 +> [29. 自适应学习率算法: AdaGrad,RMSProp,Adam 等***](#29-自适应学习率算法-adagradrmspropadam-等) + +带有动量的 SGD(左/上) 和不带动量的 SGD(右/下): + +![](../_assets/TIM截图20180611204215.png) +![](../_assets/TIM截图20180611204503.png) +> 《深度学习》 4.3.1 梯度之上: Jacobian 和 Hessian 矩阵 + +此图说明动量如何克服病态的问题:等高线描绘了一个二次损失函数(具有病态条件的 Hessian 矩阵)。一个病态条件的二次目标函数看起来像一个长而窄的山谷或具有陡峭边的峡谷。带动量的 SGD 能比较正确地纵向穿过峡谷;而普通的梯度步骤则会浪费时间在峡谷的窄轴上来回移动,因为梯度下降无法利用包含在 Hessian 矩阵中的曲率信息。 + +**Nesterov 动量** + +受 Nesterov 加速梯度算法 (Nesterov, 1983, 2004) 启发, Sutskever et al. (2013) 提出了动量算法的一个变种。其更新规则如下: + +![](../_assets/TIM截图20180611211649.png) + +其中参数 α 和 ϵ 发挥了和标准动量方法中类似的作用。Nesterov 动量和标准动量之间的**区别体现在梯度计算**上。下面是完整的 Nesterov 动量算法: + +![](../_assets/TIM截图20180611211753.png) + +Nesterov 动量中,梯度计算在施加当前速度之后。因此,Nesterov 动量可以解释为往标准动量方法中添加了一个校正因子。 + +在凸批量梯度的情况下, Nesterov 动量将额外误差收敛率从 O(1/k) 改进到 O(1/k^2)。可惜,在随机梯度的情况下, Nesterov 动量没有改进收敛率。 + + +# 自适应学习率算法:AdaGrad、RMSProp、Adam 等*** +> 《深度学习》 8.5 自适应学习率算法 + +Delta-bar-delta (Jacobs, 1988) 是一个早期的自适应学习率算法。该方法基于一个很简单的想法,如果损失对于某个给定模型参数的偏导保持相同的符号,那么学习率应该增加。如果对于该参数的偏导变化了符号,那么学习率应减小。当然,这种方法只能应用于全批量优化中(?)。 + +最近,提出了一些增量(或者基于小批量)的算法来自适应模型参数的学习率。 + +## AdaGrad + +![](../_assets/TIM截图20180611214508.png) + +AdaGrad 会独立地适应所有模型参数的学习率。具体来说,就是缩放每个参数反比于其所有梯度历史平方值总和的平方根 (Duchi et al., 2011)。效果上具有损失最大偏导的参数相应地有一个快速下降的学习率,而具有小偏导的参数在学习率上有相对较小的下降。 + +**不过**,对于训练深度神经网络模型而言,**从训练开始时**就积累梯度平方会导致有效学习率过早和过量的减小。AdaGrad 在某些深度学习模型上效果不错,但不是全部。 + +## RMSProp + +![](../_assets/TIM截图20180611215422.png) + +RMSProp 修改自 AdaGrad。AdaGrad 旨在应用于**凸问题**时快速收敛,而 RMSProp 在**非凸**设定下效果更好,改变梯度积累为指数加权的移动平均。 + +RMSProp 使用指数衰减平均以丢弃遥远过去的历史,使其能够在找到凸碗状结构后快速收敛,它就像一个初始化于该碗状结构的 AdaGrad 算法实例。 + +相比于 AdaGrad,使用移动平均引入了一个**新的超参数 ρ**,用来控制移动平均的长度范围。 + +经验上, RMSProp 已被证明是一种有效且实用的深度神经网络优化算法。目前它是深度学习从业者经常采用的优化方法之一。 + +**结合 Nesterov 动量的 RMSProp** + +![](../_assets/TIM截图20180611215923.png) + +## Adam + +![](../_assets/TIM截图20180611220109.png) + +Adam (Kingma and Ba, 2014) 是另一种学习率自适应的优化算法。 + +首先,在 Adam 中,动量直接并入了梯度一阶矩(指数加权)的估计。将动量加入 RMSProp 最直观的方法是将动量应用于缩放后的梯度。但是结合缩放的动量使用没有明确的理论动机。其次, Adam 包括偏置修正,修正从原点初始化的一阶矩(动量项)和(非中心的)二阶矩的估计。RMSProp 也采用了(非中心的)二阶矩估计,然而缺失了修正因子。因此,不像 Adam, RMSProp 二阶矩估计可能在训练初期有很高的偏置。Adam 通常被认为对超参数的选择相当鲁棒,尽管学习率有时需要从建议的默认修改。 + +**如何选择自适应学习率算法?** + +目前在这一点上没有明确的共识。选择哪一个算法似乎主要取决于使用者对算法的熟悉程度(以便调节超参数)。 + +如果不知道选哪个,就用 AdamSGD 吧。 + + +# 基于二阶梯度的优化方法:牛顿法、共轭梯度、BFGS 等的做法* +> 《深度学习》 8.6 二阶近似方法:8.6.1 牛顿法,8.6.2 共轭梯度,8.6.3 BFGS + +推导很难实际上也很少用,如果你不是数学系的,可以跳过这部分。 + + +# 批标准化(Batch Normalization)的意义** +> 《深度学习》 8.7.1 批标准化 + +批标准化(Batch Normalization, BN, Ioffe and Szegedy, 2015)是为了克服神经网络**层数加深导致难以训练**而出现的一个算法。 + +说到底,BN 还是为了解决**梯度消失/梯度爆炸**问题,特别是梯度消失。 + +**BN 算法**: + +![](../_assets/TIM截图20180612104740.png) + +BN 算法需要学习两个参数 γ 和 β. +> Ioffe and Szegedy, 2015, Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift + +**批标准化为什么有用?** +> [深度学习(二十九)Batch Normalization 学习笔记](https://blog.csdn.net/hjimce/article/details/50866313) - CSDN博客 +> +> [深度学习中 Batch Normalization为什么效果好?](https://www.zhihu.com/question/38102762/answer/85238569) - 知乎 + + +# 神经网络中的卷积,以及卷积的动机:稀疏连接、参数共享、等变表示(平移不变性)*** +> 《深度学习》 9.2 动机 + +注意:本书所谈的卷积,是包括卷积层、激活层和池化层的统称 + +**神经网络中的卷积**: + +当我们在神经网络中提到卷积时,通常是指由**多个并行卷积**组成的运算。一般而言,每个核只用于提取一种类型的特征,尽管它作用在多个空间位置上。而我们通常希望网络的每一层能够在多个位置提取多种类型的特征。 + +> 《深度学习》 9.5 基本卷积函数的变体 + +卷积的一些基本概念:通道(channel)、卷积核(kernel、filter)、步幅(stride,下采样)、填充(padding) +> [33. 卷积中零填充的影响,基本卷积的变体](#33-卷积中零填充的影响基本卷积的变体) + +**为什么使用卷积?(卷积的动机)** + +卷积运算通过三个重要的思想来帮助改进机器学习系统:**稀疏交互**(sparseinteractions)、**参数共享**(parameter sharing)、**等变表示**(equivariant representations)。 + +## 稀疏连接(sparse connectivity) + +稀疏连接,也称稀疏交互、稀疏权重。 + +传统的神经网络中每一个输出单元会与每一个输入单元都产生交互。卷积网络改进了这一点,使具有稀疏交互的特征。CNN 通过使核(kernel、filter)的大小远小于输入的大小来达到的这个目的。 + +![](../_assets/TIM截图20180612120851.png) + +举个例子,当处理一张图像时,输入的图像可能包含成千上万个像素点,但是我们可以通过只占用几十到上百个像素点的核来**检测一些小的、有意义的特征**,例如图像的边缘。 + +**稀疏交互的好处**: +- 提高了模型的统计效率:原本一幅图像只能提供少量特征,现在每一块像素区域都可以提供一部分特征 +- 减少了模型的存储需求和计算量,因为参数更少 + +如果有 m 个输入和 n 个输出,那么矩阵乘法需要 `m × n` 个参数并且相应算法的时间复杂度为 `O(m × n)`;如果限制每一个输出拥有的连接数为 k,那么稀疏的连接方法只需要 `k × n` 个参数以及 `O(k × n)` 的运行时间。而在实际应用中,**k 要比 m 小几个数量级**。 + +虽然看似减少了隐藏单元之间的交互,但实际上处在深层的单元可以间接地连接到全部或者大部分输入。 + +![](../_assets/TIM截图20180612121301.png) + +## 参数共享(parameter sharing) + +参数共享是指在一个模型的多个函数中使用相同的参数。作为参数共享的同义词,我们可以说 +一个网络含有**绑定的权重**(tied weights) + +在传统的神经网络中,当计算一层的输出时,权重矩阵的每一个元素只使用一次,当它乘以输入的一个元素后就再也不会用到了。卷积运算中的参数共享保证了我们只需要学习一个参数集合,而不是对于每一位置都需要学习一个单独的参数集合。 + +考虑一个具体的例子——**边缘检测**——来体会稀疏连接+参数共享带来的效率提升: + +![](../_assets/TIM截图20180612154333.png) + +两个图像的高度均为 280 个像素。输入图像的宽度为 320 个像素,而输出图像的宽度为 319 个像素(padding='VALID')。对于边缘检测任务而言,只需要一个包含**两个元素的卷积核**就能完成;而为了用矩阵乘法描述相同的变换,需要一个包含 320 × 280 × 319 × 280 ≈ 80亿个元素的矩阵(40亿倍)。 + +同样,使用卷积只需要 319 × 280 × 3 = 267,960 次浮点运算(每个输出像素需要两次乘法和一次加法);而直接运行矩阵乘法的算法将执行超过 160 亿次浮点运算(60000倍) + +## 平移等变|不变性(translation invariant) + +(局部)平移不变性是一个很有用的性质,尤其是当我们关心某个特征**是否出现**而不关心它出现的具体位置时。 + +**参数共享**(和池化)使卷积神经网络具有一定的**平移不变性**。这就意味着即使图像经历了一个小的平移,依然会产生相同的特征。例如,分类一个 MNIST 数据集的数字,对它进行任意方向的**平移**(不是旋转),无论最终的位置在哪里,都能正确分类。 +> 池化操作也能够帮助加强网络的平移不变性 > [35. 池化、池化(Pooling)的作用***](#35-池化池化pooling的作用) + +**什么是等变性?** +- 如果一个函数满足**输入改变,输出也以同样的方式改变**这一性质,我们就说它是等变 (equivariant) 的。 +- 对于卷积来说,如果令 g 是输入的任意平移函数,那么卷积函数对于 g 具有等变性。 + +当处理**时间序列数据**时,这意味着通过卷积可以得到一个由输入中出现不同特征的时刻所组成的时间轴。如果我们把输入中的一个事件向后延时,在输出中仍然会有完全相同的表示,只是时间延后了。 + +图像与之类似,卷积产生了一个 2 维映射来表明某些特征在输入中出现的位置。如果我们移动输入中的对象,它的表示也会在输出中移动同样的量。 + +卷积对其他的一些变换并不是天然等变的,例如对于图像的**放缩**或者**旋转**变换,需要其他的一些机制来处理这些变换。 + +> [池化的不变性](http://ufldl.stanford.edu/wiki/index.php/%E6%B1%A0%E5%8C%96#.E6.B1.A0.E5.8C.96.E7.9A.84.E4.B8.8D.E5.8F.98.E6.80.A7) - Ufldl + + +# 卷积中不同零填充的影响** +> 《深度学习》 9.5 基本卷积函数的变体 + +在任何卷积网络的实现中都有一个重要性质,那就是能够隐含地**对输入用零进行填充**使得它加宽。如果没有这个性质,会极大得限制网络的表示能力。 + +三种零填充设定,其中 `m` 和 `k` 分别为图像的宽度和卷积核的宽度(高度类似): +1. 有效(valid)卷积——不使用零填充,卷积核只允许访问那些图像中能够**完全包含整个核**的位置,输出的宽度为 `m − k + 1`. + - 在这种情况下,输出的所有像素都是输入中相同数量像素的函数,这使得输出像素的表示更加规范。 + - 然而,输出的大小在每一层都会缩减,这限制了网络中能够包含的卷积层的层数。(一般情况下,影响不大,除非是上百层的网络) +2. 相同(same)卷积——只进行足够的零填充来**保持输出和输入具有相同的大小**,即输出的宽度为 `m`. + - 在这种情况下,只要硬件支持,网络就能包含任意多的卷积层。 + - 然而,输入像素中靠近边界的部分相比于中间部分对于输出像素的影响更小。这可能会导致边界像素存在一定程度的欠表示。 +3. 全(full)卷积——进行足够多的零填充使得每个像素都能被访问 k 次(非全卷积只有中间的像素能被访问 k 次),最终输出图像的宽度为 `m + k − 1`. + - 因为 same 卷积可能导致边界像素欠表示,从而出现了 Full 卷积; + - 但是在这种情况下,输出像素中靠近边界的部分相比于中间部分是更少像素的函数。这将导致学得的卷积核不能再所有所有位置表现一致。 + - 事实上,很少使用 Full 卷积 + > 注意:如果以“全卷积”作为关键词搜索,返回的是一个称为 FCN(Fully Convolutional Networks)的卷积结构,而不是这里描述的填充方式。 + +通常**零填充的最优数量**(对于测试集的分类正确率)处于 “有效卷积”和 “相同卷积” 之间。 + + +# 基本卷积的变体:反卷积、空洞卷积*** + +原书中也描述一些基本卷积的变体:局部卷积、平铺卷积; + +![](../_assets/TIM截图20180612213144.png) +> 从上到下一次为局部卷积、平铺卷积和标准卷积; +> +> 《深度学习》 9.5 基本卷积函数的变体 + +不过这跟我想的“变体”不太一样(百度都搜不到这两种卷积),下面介绍的是一些我认识中比较流行的卷积变体: + +## 转置卷积|反卷积(Transposed convolution) + +![](../_assets/conv_no_padding_no_strides_transposed.gif) +> No padding, no strides, transposed + +> [如何理解深度学习中的deconvolution networks?](https://www.zhihu.com/question/43609045) - 知乎 + +## 空洞卷积|扩张卷积(Dilated convolution) + +![](../_assets/conv_dilation.gif) +> No padding, no stride, dilation + +> [如何理解空洞卷积(dilated convolution)?](https://www.zhihu.com/question/54149221) - 知乎 + +> 卷积、转置卷积、空洞卷积动图演示:vdumoulin/[conv_arithmetic](https://github.com/vdumoulin/conv_arithmetic): A technical report on convolution arithmetic in the context of deep learning + + +# 池化、池化(Pooling)的作用*** +> 《深度学习》 9.3 池化 + +一次典型的卷积包含三层:第一层并行地计算多个卷积产生一组线性激活响应;第二层中每一个线性激活响应将会通过一个非线性的激活函数;第三层使用**池化函数**(pooling function)来进一步调整这一层的输出。 + +``` +# Keras +from keras.layers import Input, Conv2D, Activation, MaxPooling2D + +net = Input([in_w, in_h, input_dim]) +net = Conv2D(output_dim, kernel_size=(3, 3))(net) +net = Activation('relu')(net) +net = MaxPooling2D(pool_size=(2, 2))(net) +""" +卷积层中,一般 strides=1, padding='valid' +池化层中,一般 strides=pool_size, padding='valid' +""" +``` + +池化函数使用某一位置的相邻输出的**总体统计特征**来代替网络在该位置的输出。 + +常见的池化函数: +- *最大池化(Max pooling) +- *平均值池化(Mean pooling) +- L2 范数 +- 基于中心像素距离的加权平均 + +池化操作有助于卷积网络的平移不变性 +> [32.3. 平移等变/不变性(translation invariant)](#323-平移等变不变性translation-invariant) + +**使用池化可以看作是增加了一个无限强的先验**:这一层学得的函数必须具有对少量平移的不变性。当这个假设成立时,池化可以极大地提高网络的统计效率。 + +(最大)池化对平移是天然不变的,但池化也能用于学习其他不变性: + +![](../_assets/TIM截图20180613101802.png) + +这三个过滤器都旨在检测手写的数字 5。每个过滤器尝试匹配稍微**不同方向**的 5。当输入中出现 5 时,无论哪个探测单元被激活,最大池化单元都将产生较大的响应。 + +这种多通道方法只在学习其他变换时是必要的。这个原则在 maxout 网络 (Goodfellow et al., 2013b) 和其他卷积网络中更有影响。 + +池化综合了区域内的 k 个像素的统计特征而不是单个像素,这种方法提高了网络的计算效率,因为下一层少了约 k 倍的输入。 + +在很多任务中,池化还有助于对于处理不同大小的输入:例如我们想对不同大小的图像进行分类时,分类层的输入必须是固定的大小,而这通常通过调整池化区域的偏置大小来实现。 + +其他参考: +- 一些理论工作对于在不同情况下应当使用哪种池化函数给出了一些指导 (Boureau et al., 2010) +- 将特征一起动态地池化:对于感兴趣特征的位置运行聚类算法 (Boureau et al., 2011)、先学习一个单独的池化结构,再应用到全部的图像中 (Jia et al., 2012) +- 《深度学习》 20.6 卷积玻尔兹曼机、20.10.6 卷积生成网络 + + +# 卷积与池化的意义、影响(作为一种无限强的先验)** +> 《深度学习》 9.4 卷积与池化作为一种无限强的先验 + +一个无限强的先验需要对一些参数的概率置零并且完全禁止对这些参数赋值,无论数据对于这些参数的值给出了多大的支持。 + +如果把卷积网络类比成全连接网络,那么对于这个全连接网络的权重有一个无限强的先验:隐藏单元的权重必须和它邻居的权重相同,但可以在空间上移动;同时要求除了那些处在“感受野”内的权重以外,其余的权重都为零。 + +类似的,使用**池化**也是一个无限强的先验:每一个单元都具有对少量平移的不变性。 + +**卷积与池化作为一种无限强先验的影响**: +1. 卷积和池化可能导致**欠拟合** + - 与任何其他先验类似,卷积和池化只有当先验的假设合理且正确时才有用。 + - 如果一项任务涉及到要**对输入中相隔较远的信息进行合并**时,那么卷积所利用的先验可能就不正确了。 + - 如果一项任务依赖于保存**精确的空间信息**,那么在所有的特征上使用池化将会增大训练误差。 + > 因此,一些卷积网络结构 (Szegedy et al., 2014a) 为了既获得具有较高不变性的特征又获得当平移不变性不合理时不会导致欠拟合的特征,被设计成在一些通道上使用池化而在另一些通道上不使用。 +2. 当我们比较卷积模型的统计学习表现时,只能以基准中的其他卷积模型作为比较的对象 + +> 《深度学习》 5.6 贝叶斯统计(先验概率分布) + + +# RNN 的几种基本设计模式 +> 《深度学习》 10.2 循环神经网络 + +循环神经网络中一些重要的设计模式包括以下几种: +1. (*)每个时间步都有输出,并且隐藏单元之间有循环连接的循环网络 + + ![](../_assets/TIM截图20180613150111.png) + + - 将 x 值的输入序列映射到输出值 o 的对应序列 + - 损失 L 衡量每个 o 与相应的训练目标 y 的距离 + - 损失 L 内部计算 y^ = softmax(o),并将其与目标 y 比较 + - 输入 x 到隐藏 h 的连接由权重矩阵 U 参数化 + - 隐藏 h(t-1) 到隐藏 h(t) 的循环连接由权重矩阵 W 参数化 + - 隐藏到输出的连接由权重矩阵 V 参数化 + +2. 每个时间步都产生一个输出,只有当前时刻的输出到下个时刻的隐藏单元之间有循环连接的循环网络 + + ![](../_assets/TIM截图20180613150711.png) + + - 此类 RNN 的唯一循环是从输出 o 到隐藏层 h 的反馈连接 + - 表示能力弱于 RNN_1,单更容易训练 + +3. 隐藏单元之间存在循环连接,但读取整个序列后产生单个输出的循环网络 + + ![](../_assets/TIM截图20180613151014.png) + + 这样的网络可以用于概括序列并产生用于进一步处理的固定大小的表示 + +一般所说的 RNN(循环神经网络)指的是**第一种**设计模式 + +这些循环网络都将一个输入序列映射到**相同长度**的输出序列 + +# RNN 更新方程(前向传播公式),包括 LSTM、GRU 等*** +> 《深度学习》 10 序列建模:循环和递归网络 +> +> [RNN, LSTM, GRU 公式总结](https://blog.csdn.net/zhangxb35/article/details/70060295) - CSDN博客 + +**基本 RNN** +> [Recurrent neural network](https://en.wikipedia.org/wiki/Recurrent_neural_network#Elman_networks_and_Jordan_networks) - Wikipedia + +根据隐层 h(t) 接受的是上时刻的隐层 h(t−1) 还是上时刻的输出 y(t−1),分为两种 RNN: +- Elman RNN + + [![](../_assets/公式_20180613164438.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;h^{(t)}&=\tanh\left(&space;W_hx^{(t)}+U_hh^{(t-1)}+b_h&space;\right)\\&space;y^{(t)}&={\rm&space;softmax}\left(&space;W_yh^{(t)}+b_y&space;\right)\\&space;\end{aligned}) + +- Jordan RNN + + [![](../_assets/公式_20180613164637.png)](http://www.codecogs.com/eqnedit.php?latex={\displaystyle&space;{\begin{aligned}&space;h^{(t)}&=\tanh\left(&space;W_hx^{(t)}+U_hy^{(t-1)}+b_h&space;\right)\\&space;y^{(t)}&={\rm&space;softmax}\left(&space;W_yh^{(t)}+b_y&space;\right)\&space;\end{aligned}}}) + +> 《深度学习》 默认的 RNN 是 Elman RNN > [37. RNN(循环神经网络) 的几种基本设计模式**](#37-rnn循环神经网络-的几种基本设计模式) + +**门限 RNN**(LSTM、GRU)与基本 RNN 的主要区别在于 Cell 部分 + +**LSTM** +> [Long short-term memory](https://en.wikipedia.org/wiki/Long_short-term_memory#Variants) - Wikipedia + +[![](../_assets/公式_20180613170734.png)](http://www.codecogs.com/eqnedit.php?latex={\displaystyle&space;{\begin{aligned}&space;f_{t}&=\sigma(W_{f}x_{t}+U_{f}h_{t-1}+b_{f})\\&space;i_{t}&=\sigma(W_{i}x_{t}+U_{i}h_{t-1}+b_{i})\\&space;\tilde{c}_{t}&=\tanh(W_{c}x_{t}+U_{c}h_{t-1}+b_{c})\\&space;c_{t}&=f_{t}\circ&space;c_{t-1}+i_{t}\circ&space;\tilde{c}_{t}\\&space;o_{t}&=\sigma(W_{o}x_{t}+U_{o}h_{t-1}+b_{o})\\&space;h_{t}&=o_{t}\circ&space;\tanh(c_{t})&space;\end{aligned}}}) + +- 其中 f 为遗忘门(forget),i 为输入门(input),o 为输出门(output)。 +- 每个门的输入都是 x 和 h,但是参数都是独立的(参数数量是基本 RNN 的 4 倍) +- c 表示 cell state(如果用过 tensorflow 中的 RNN,会比较熟悉) +- 如果遗忘门 f 取 0 的话,那么上一时刻的状态就会全部被清空,只关注此时刻的输入 +- 输入门 i 决定是否接收此时刻的输入 +- 输出门 o 决定是否输出 cell state + +类似基本 RNN,LSTM 也有另一个版本,将公式中所有 h(t-1) 替换为 c(t-1),但不常见 + +**GRU** +> [Gated recurrent unit](https://en.wikipedia.org/wiki/Gated_recurrent_unit#Architecture) - Wikipedia + +[![](../_assets/公式_20180613171757.png)](http://www.codecogs.com/eqnedit.php?latex={\displaystyle&space;{\begin{aligned}&space;z_{t}&=\sigma(W_{z}x_{t}+U_{z}h_{t-1}+b_{z})\\&space;r_{t}&=\sigma(W_{r}x_{t}+U_{r}h_{t-1}+b_{r})\\&space;\tilde{h}_t&=\tanh(W_{h}x_{t}+U_{h}(r_{t}\circ&space;h_{t-1})+b_{h})\\&space;h_{t}&=(1-z_{t})\circ&space;h_{t-1}+z_{t}\circ&space;\tilde{h}_t&space;\end{aligned}}}) + +- 其中 z 为更新门(update),r 为重置门(reset) +- GRU 可以看作是将 LSTM 中的遗忘门和输入门合二为一了 + +# BPTT(back-propagation through time,通过时间反向传播)** +> 《深度学习》 10.2.2 计算循环神经网络的梯度 + + +# 自编码器在深度学习中的意义* + +**自编码器的意义**: +- 传统自编码器被用于降维或特征学习 +- 近年来,自编码器与潜变量模型理论的联系将自编码器带到了生成式建模的前沿 + - 几乎任何带有潜变量并配有一个推断过程(计算给定输入的潜在表示)的生成模型,都可以看作是自编码器的一种特殊形式。 + > 《深度学习》 20 深度生成模型,20.10.3 变分自编码器,20.12 生成随机网络 + +**自编码器的一般结构** + +![](../_assets/TIM截图20180613200023.png) +- 自编码器有两个组件:**编码器** f(将 x 映射到 h)和**解码器** g(将 h 映射到 r) +- 一个简单的自编码器试图学习 `g(f(x)) = x`;换言之,自编码器尝试将输入复制到输出 +- 单纯将输入复制到输出没什么用,相反,训练自编码器的目标是获得有用的特征 h。 + +自编码器的学习过程就是最小化一个损失函数: + +[![](../_assets/公式_20180613201829.png)](http://www.codecogs.com/eqnedit.php?latex=L(\boldsymbol{x},g(f(\boldsymbol{x})))) + + +# 自编码器一些常见的变形与应用:正则自编码器、稀疏自编码器、去噪自编码器* +> [40. 自编码器在深度学习中的意义](#40-自编码器在深度学习中的意义) +> +> 《深度学习》 14.2 正则自编码器 + +**欠完备自编码器** +- 从自编码器获得有用特征的一种方法是**限制 h 的维度比 x 小**,这种编码维度小于输入维度的自编码器称为**欠完备**(undercomplete)自编码器; +- 相反,如果 h 的维度大于 x,此时称为过完备自编码器。 +- **学习欠完备的表示将强制自编码器捕捉训练数据中最显著的特征** +- 当解码器是线性的且 L 是均方误差,欠完备的自编码器会学习出与 PCA 相同的生成子空间 +- 而拥有**非线性**编码器函数 f 和**非线性**解码器函数 g 的自编码器能够学习出更强大的 PCA 非线性推广 +- 但如果编码器和解码器被赋予**过大的容量**,自编码器会执行复制任务而捕捉不到任何有关**数据分布**的有用信息。 + - 过完备自编码器就可以看作是被赋予过大容量的情况 + +**正则自编码器** +- 通过加入正则项到损失函数可以限制模型的容量,同时鼓励模型学习除了复制外的其他特性。 +- 这些特性包括稀疏表示、表示的小导数、以及对噪声或输入缺失的鲁棒性。 +- 即使模型的容量依然大到足以学习一个无意义的恒等函数,正则自编码器仍然能够从数据中学到一些关于数据分布的信息。 + +**稀疏自编码器** +- 稀疏自编码器一般用来学习特征 +- 稀疏自编码器简单地在训练时结合编码层的**稀疏惩罚** Ω(h) 和重构误差: + + [![](../_assets/公式_20180613211004.png)](http://www.codecogs.com/eqnedit.php?latex=L(\boldsymbol{x},g(f(\boldsymbol{x})))+\Omega(\boldsymbol{h})&space;=&space;L(\boldsymbol{x},g(f(\boldsymbol{x})))+\lambda\sum_i\left|h_i\right|) + +- 稀疏惩罚不算是一个正则项。这仅仅影响模型关于潜变量的分布。这个观点提供了训练自编码器的另一个动机:这是近似训练生成模型的一种途径。这也给出了为什么自编码器学到的特征是有用的另一个解释:它们描述的潜变量可以解释输入。 + > 《深度学习》 14.2.1 稀疏自编码器 + +**去噪自编码器(DAE)** +- 去噪自编码器试图学习**更具鲁棒性的**特征 +- 与传统自编码器不同,去噪自编码器(denoising autoencoder, DAE)最小化: + + [![](../_assets/公式_20180613211437.png)](http://www.codecogs.com/eqnedit.php?latex=L(\boldsymbol{x},g(f(\boldsymbol{\tilde{x}})))) + +- 这里的 x~ 是**被某种噪声损坏**的 x 的副本,去噪自编码器需要预测原始未被损坏数据 +- 破坏的过程一般是以某种概率分布(通常是二项分布)将一些值置 0. + + ![](../_assets/TIM截图20180613211935.png) + > 《深度学习》 14.2.2 去噪自编码器,14.5 去噪自编码器 + +**为什么DAE有用?** +- 对比使用非破损数据进行训练,破损数据训练出来的权重噪声比较小——在破坏数据的过程中去除了真正的噪声 +- 破损数据一定程度上减轻了训练数据与测试数据的代沟——使训练数据更接近测试数据 + > [降噪自动编码器(Denoising Autoencoder)](https://www.cnblogs.com/neopenx/p/4370350.html) - Physcal - 博客园 + >> 感觉这两个理由很牵强,但是从数据分布的角度讲太难了 + + +# 半监督的思想以及在深度学习中的应用* +> 《深度学习》 15.3 半监督解释因果关系 + + +# 分布式表示的概念、应用,与符号表示(one-hot 表示)的区别*** +> 《深度学习》 15.4 分布式表示 + +## 什么是分布式表示? +- 所谓分布式表示就是用不同的特征,通过**组合**来表示不同的概念 + + ![](../_assets/TIM截图20180614103628.png) + ![](../_assets/TIM截图20180614104032.png) + + (左)是 one-hot 表示(一种稀疏表示),(右)为分布式表示 + > [神经网络如何学习分布式表示](http://baijiahao.baidu.com/s?id=1593632865778730392) - 百家号 + +## 分布式表示为什么强大?——分布式表示与符号表示 + +分布式表示之所以强大,是因为它能用具有 `d` 个值的 `n` 个**线性阀值特征**去描述 `d^n` 个不同的概念——换言之,在输入维度是 d 的一般情况下,具有 n 个特征的分布式表示可以给 O(n^d) 个不同区域分配唯一的编码 +> **线性阀值特征**:本身是一个**连续值**,通过划分阈值空间来获得对应的离散特征 + +**符号表示** +- 如果我们没有对数据做任何假设,并且每个区域使用唯一的符号来表示,每个符号使用单独的参数去识别空间中的对应区域,那么指定 `O(n^d)` 个区域需要 `O(n^d)` 个样本/参数。 + +- 举个例子:作为纯符号,“猫”和“狗”之间的距离和任意其他两种符号的距离是一样。 + + 然而,如果将它们与**有意义的分布式表示**相关联,那么关于猫的很多特点可以推广到狗,反之亦然——比如,某个分布式表示可能会包含诸如“具有皮毛”或“腿的数目”这类在猫和的**嵌入/向量**上具有相同值的项 + +**分布式表示与符号表示**(最近邻): + +![](../_assets/TIM截图20180614111241.png) +![](../_assets/TIM截图20180614111712.png) + +- 两者都在学习如何将输入空间分割成多个区域 +- 在输入维度相同的情况下,分布式表示能够比非分布式表示多分配指数级的区域——这一特性可用于解决**维度灾难**问题 + > [44. 如何理解维数灾难?](#44-如何理解维数灾难) + +**非线性特征**: + +上面假设了**线性阀值特征**,然而更一般的,分布式表示的优势还体现在其中的每个特征可以用非线性算法——**神经网络**——来提取。简单来说,表示能力又提升了一个级别。 + +一般来说,无论我们使用什么算法,需要学习的空间区域数量是固定的;但是使用分布式表示有效的减少了参数的数量——从 `O(n^d)` 到 `O(nd)`——这意味着我们需要拟合参数更少,因此只需要更少的训练样本就能获得良好的泛化。 + +**一些非分布式表示算法**: +- 聚类算法,比如 k-means 算法 +- k-最近邻算法 +- 决策树 +- 支持向量机 +- 基于 n-gram 的语言模型 + +**什么时候应该使用分布式表示能带来统计优势?** +当一个明显复杂的结构可以**用较少参数紧致地表示**时,使用分布式表示就会具有统计上的优势(避免维数灾难)。 +> [44. 如何理解维数灾难?](#44-如何理解维数灾难) + +一些传统的非分布式学习算法仅仅在**平滑先验**的情况下能够泛化。 + + +# 如何理解维数灾难?*** +> 《深度学习》 5.11.1 维数灾难 + +概括来说,就是当数据维数很高时,会导致学习变得困难。 + +这里的“困难”体现在两方面: +1. 当数据较多时,会使训练的周期变得更长 +2. 当数据较少时,对新数据的泛化能力会更弱,甚至失去泛化能力 +> 这两点对于任何机器学习算法都是成立的;但在维数灾难的背景下,会加剧这两个影响 + +对于第二点,书中使用了另一种描述:“由维数灾难带来的一个问题是统计挑战,所谓统计挑战指的是 **x 的可能配置数目远大于训练样本的数目**”。 + +为了充分理解这个问题,我们假设输入空间如图所示被分成单元格。 + +![](../_assets/TIM截图20180614203447.png) + +- 当数据的维度增大时(从左向右),我们感兴趣的配置数目会随指数级增长。 +- 当空间是低维时,我们可以用由少量单元格去描述这个空间。泛化到新数据点时,通过检测和单元格中的训练样本的相似度,我们可以判断如何处理新数据点。 +- 当空间的维数很大时,很可能发生大量单元格中没有训练样本的情况。此时,基于“**平滑先验**”的简单算法将无力处理这些新的数据。 + > [7. 局部不变性(平滑先验)及其在基于梯度的学习上的局限性*](#7-局部不变性平滑先验及其在基于梯度的学习上的局限性) +- 更一般的,O(nd) 个参数(d 个特征,每个特征有 n 种表示)能够明确表示输入空间中 O(n^d) 个不同区域。如果我们没有对数据做任何假设,并且**每个区域使用唯一的符号来表示**,每个符号使用单独的参数去识别空间中的对应区域,那么指定 O(n^d) 个区域将需要 O(n^d) 个样本。 + > 《深度学习》 15.4 分布式表示 + +**如何解决维数灾难?** +> [43.3. 什么时候应该使用分布式表示能带来统计优势?](#433-什么时候应该使用分布式表示能带来统计优势) + + +# 迁移学习相关概念:多任务学习、一次学习、零次学习、多模态学习** +> 《深度学习》 15.2 迁移学习和领域自适应 + +**什么是迁移学习?** +- 迁移学习和领域自适应指的是利用一个任务(例如,分布 P1)中已经学到的内容去改善另一个任务(比如分布 P2)中的泛化情况。 + - 例如,我们可能在第一个任务中学习了一组视觉类别,比如猫和狗,然后在第二种情景中学习一组不同的视觉类别,比如蚂蚁和黄蜂。 +- 除了共享**输出语义**(上面这个例子),有时也会共享**输出语义** + - 例如,语音识别系统需要**在输出层产生有效的句子**,但是输入附近的较低层可能需要识别相同音素或子音素发音的不同版本(这取决于说话人) + +**迁移学习与多任务学习** +- 因为目前迁移学习更流行,因此不少博客简介上,会将多任务学习归属于迁移学习的子类或者迁移学习的相关领域。 + +- 迁移学习与多任务学习的一些结构: + + ![](../_assets/TIM截图20180615111903.png) + ![](../_assets/TIM截图20180610203703.png) + > (左)> 《深度学习》 15.2 迁移学习和领域自适应;(右)> 《深度学习》 7.7 多任务学习 + +- 这两种都可能是迁移学习或者多任务学习的结构。~~迁移学习的输入在每个任务上具有不同的意义(甚至不同的维度),但是输出在所有的任务上具有**相同的语义**;多任务学习则相反~~ + +**迁移学习与领域自适应** +- 相比于迁移学习和多任务学习,领域自适应的提法比较少,也更简单一些,其在每个情景之间任务(和最优的输入到输出的映射)都是相同的,但是**输入分布**稍有不同。 +- 例如,考虑情感分析的任务:网上的评论有许多类别。在书、视频和音乐等媒体内容上训练的顾客评论情感预测器,被用于分析诸如电视机或智能电话的消费电子产品的评论时,领域自适应情景可能会出现。可以想象,存在一个潜在的函数可以判断任何语句是正面的、中性的还是负面的,但是词汇和风格可能会因领域而有差异 + +**one-shot learning 和 zero-shot learning** +- 迁移学习的两种极端形式是**一次学习**(one-shot learning)和**零次学习**(zero-shot learning) + - 只有少量标注样本的迁移任务被称为 one-shot learning;没有标注样本的迁移任务被称为 zero-shot learning. +- **one-shot learning** + - one-shot learning 稍微简单一点:在大数据上学习 general knowledge,然后在特定任务的小数据上有技巧的 fine tuning。 +- **zero-shot learning** + - 相比 one-shot,zero-shot learning 要更复杂。 + - 先来看一个 zero-shot 的例子:假设学习器已经学会了关于动物、腿和耳朵的概念。如果已知猫有四条腿和尖尖的耳朵,那么学习器可以在没有见过猫的情况下猜测该图像中的动物是猫。 + - (TODO) + +**多模态学习(multi-modal learning)** +- 与 zero-shot learning 相同的原理可以解释如何能执行**多模态学习**(multimodal learning) + + ![](../_assets/TIM截图20180615150955.png) + + > 《深度学习》 15.2 迁移学习和领域自适应 图 15.3 + + +# 图模型|结构化概率模型相关概念* +> 《深度学习》 16 深度学习中的结构化概率模型 +> +> 答案不完整,更多相关概念请阅读本章 + +**有向图模型** +- 有向图模型(directed graphical model)是一种结构化概率模型,也被称为**信念网络**(belief network)或者**贝叶斯网络**(Bayesian network) +- 描述接力赛例子的有向图模型 + + ![](../_assets/TIM截图20180617140439.png) + - Alice 在 Bob 之前开始,所以 Alice 的完成时间 t0 影响了 Bob 的完成时间 t1。 + - Carol 只会在 Bob 完成之后才开始,所以 Bob 的完成时间 t1 直接影响了 Carol 的完成时间 t2。 + +- 正式地说,变量 x 的有向概率模型是通过有向无环图 G(每个结点都是模型中的随机变量)和一系列**局部条件概率分布**(local conditional probability distribution)来定义的,x 的概率分布可以表示为: + + ![](../_assets/TIM截图20180617141056.png) + - 其中 大P 表示结点 xi 的所有父结点 + +- 上述接力赛例子的概率分布可表示为: + + ![](../_assets/TIM截图20180617141502.png) + +**无向图模型** +- 无向图模型(undirected graphical Model),也被称为**马尔可夫随机场**(Markov random field, MRF)或者是**马尔可夫网络**(Markov network) +- 当相互的作用并没有本质性的指向,或者是明确的双向相互作用时,使用无向模型更加合适。 + +**图模型的优点** +1. 减少参数的规模 + - 通常意义上说,对每个变量都能取 k 个值的 n 个变量建模,**基于查表的**方法需要的复杂度是 `O(k^n)`,如果 m 代表图模型的单个条件概率分布中最大的变量数目,那么对这个有向模型建表的复杂度大致为 `O(k^m)`。只要我们在设计模型时使其满足 `m ≪ n`,那么复杂度就会被大大地减小;换一句话说,只要图中的每个变量都只有少量的父结点,那么这个分布就可以用较少的参数来表示。 +1. 统计的高效性 + - 相比图模型,**基于查表的**模型拥有天文数字级别的参数,为了准确地拟合,相应的训练集的大小也是相同级别的。 +1. 减少运行时间 + - 推断的开销:计算分布时,避免对整个表的操作,比如求和 + - 采样的开销:类似推断,避免读取整个表格 + > 《深度学习》 16.3 从图模型中采样 + +**图模型如何用于深度学习** +- 受限玻尔兹曼机(RBM) + - RBM 是图模型如何用于深度学习的典型例子 + - RBM 本身不是一个深层模型,它有一层潜变量,可用于学习输入的表示。但是它可以被用来构建许多的深层模型。 + +**其他相关名词**: +- 信念网络(有向图模型) +- 马尔可夫网络(无向图模型) +- 配分函数 +- 能量模型(无向图模型) +- 分离(separation)/d-分离 +- 道德图(moralized graph)、弦图(chordal graph) +- 因子图(factor graph) +- Gibbs 采样 +- 结构学习(structure learning) + + +# 深度生成模型、受限玻尔兹曼机(RBM)相关概念* +> 《深度学习》 16.7.1 实例:受限玻尔兹曼机、20.1 玻尔兹曼机、20.2 受限玻尔兹曼机 + + +# 深度学习在图像、语音、NLP等领域的常见作法与基本模型** +> 《深度学习》 12 应用 + +## 计算机视觉(CV) +> 12.2 计算机视觉 + +**预处理** +许多应用领域需要复杂精细的预处理,但是 CV 通常只需要相对少的预处理。 + +通常,**标准化**是图像唯一必要的预处理——将图像格式化为具有相同的比例,比如 [0,1] 或者 [-1,1]. + +许多框架需要图像缩放到标准的尺寸。但这不是必须的,一些卷积模型接受可变大小的输入并动态地调整它们的池化区域大小以保持输出大小恒定。 + +其他预处理操作: + +**对比度归一化** +- 在许多任务中,对比度是能够安全移除的最为明显的变化源之一。简单地说,对比度指的是图像中亮像素和暗像素之间差异的大小。 +- 整个图像的对比度可以表示为: + + ![](../_assets/TIM截图20180619104551.png),其中 + + ![](../_assets/TIM截图20180619104649.png),整个图片的平均强度 + +**全局对比度归一化**(Global contrast normalization, GCN) +- GCN 旨在通过从每个图像中减去其平均值,然后重新缩放其使得其像素上的标准差等于某个常数 s 来防止图像具有变化的对比度。定义为: + + ![](../_assets/TIM截图20180619104914.png) + - 从大图像中剪切感兴趣的对象所组成的数据集不可能包含任何强度几乎恒定的图像。此时,设置 λ = 0 来忽略小分母问题是安全的。(Goodfellow et al. 2013c) + - 随机剪裁的小图像更可能具有几乎恒定的强度,使得激进的正则化更有用。此时可以加大 λ (ϵ = 0, λ = 10; Coates et al. 2011) + - 尺度参数 s 通常可以设置为 1 (Coates et al. 2011),或选择使所有样本上每个像素的标准差接近 1 (Goodfellow et al. 2013c) + +- **GCN 的意义** + - 式中的标准差可以看作是对图片 L2 范数的重新缩放(假设移除了均值),但我们倾向于标准差而不是 L2 范数来定义 GCN,是因为标准差包括除以像素数量这一步,从而基于标准差的 GCN 能够使用与图像大小无关的固定的 s. + - 而将标准差视为 L2 范数的缩放,可以将 GCN 理解成到球壳的一种映射。这可能是一个有用的属性,因为神经网络往往更好地响应空间方向,而不是精确的位置。 + + ![](../_assets/TIM截图20180619113501.png) + - (左) 原始的输入数据可能拥有任意的范数。 + - (中) λ = 0 时候的 GCN 可以完美地将所有的非零样本投影到球上。这里我们令 s = 1, ϵ = 10−8。由于我们使用的 GCN 是基于归一化标准差而不是 L2 范数,所得到的球并不是单位球。 + - (右) λ > 0 的正则化 GCN 将样本投影到球上,但是并没有完全地丢弃其范数中变化。 s 和 ϵ 的取值与之前一样。 + +- GCN 的问题: + - 全局对比度归一化常常不能突出我们想要突出的图像特征,例如边缘和角。 + - 例子:如果我们有一个场景,包含了一个大的黑暗区域和一个大的明亮的区域(例如一个城市广场有一半的区域处于建筑物的阴影之中),则全局对比度归一化将确保暗区域的亮度与亮区域的亮度之间存在大的差异。然而,它不能确保暗区内的边缘突出。 + +**局部对比度归一化**(local contrast normalization, LCN) +- GCN 存在的问题催生了 LCN +- LCN 确保对比度在每个小窗口上被归一化,而不是作为整体在图像上被归一化。 + + ![](../_assets/TIM截图20180619114844.png) + +- LCN 通常可以通过使用可分离卷积来计算特征映射的局部平均值和局部标准差,然后在不同的特征映射上使用逐元素的减法和除法。 +- LCN 是可微分的操作,并且还可以作为一种非线性作用应用于网 +络隐藏层,以及应用于输入的预处理操作。 + +**数据集增强** +数据集增强可以被看作是一种**只对训练集**做预处理的方式。 + +## 语音识别 +> 12.3 语音识别 +> +> 本章没什么实际内容,主要介绍了各阶段的主流模型 + +自动语音识别(Automatic Speech Recognition, ASR)任务指的是构造一个函数 f*,使得它能够在给定声学序列 X 的情况下计算最有可能的语言序列 y. + +令 X = (x(1), x(2), ..., x(T)) 表示语音的输入向量,传统做法以 20ms 左右为一帧分割信号;y = (y1, y2, ..., yN) 表示目标的输出序列(通常是一个词或者字符的序列。 + +许多语音识别的系统通过特殊的手工设计方法预处理输入信号,从而提取声学特征;也有一些深度学习系统 (Jaitly and Hinton, 2011) 直接从原始输入中学习特征。 + +## 自然语言处理 +> 12.4 自然语言处理 + +相关术语: +- n-gram 语言模型 +- 神经语言模型(Neural Language Model, NLM) + - 结合 n-gram 和神经语言模型 + - 分层 Softmax +- 神经机器翻译 + - 注意力机制 + +**n-gram 语言模型** +- 语言模型(language model)定义了自然语言中**标记序列**的概率分布。根据模型的设计,标记可以是词、字符、甚至是字节。标记总是离散的实体。 +- n-gram 是最早成功的语言模型 + - 一个 n-gram 是一个包含 n 个**标记**的序列。 + - 基于 n-gram 的模型定义一个条件概率——给定前 n − 1 个标记后的第 n 个标记的条件概率: + + ![](../_assets/TIM截图20180619193332.png) + + - 训练 n-gram 模型很简单,因为最大似然估计可以通过简单地统计每个可能的 n-gram 在训练集中**出现的频数**来获得。 + - 通常我们同时训练 n-gram 模型和 n − 1 gram 模型。这使得下式可以简单地通过查找两个存储的概率来计算。 + + ![](../_assets/TIM截图20180619193540.png) +- n-gram 模型的缺点: + - **稀疏问题**——n-gram 模型最大似然的基本限制是,在许多情况下从训练集计数估计得到的 Pn 很可能为零。由此产生了各种**平滑算法**。 + - **维数灾难**——经典的 n-gram 模型特别容易引起维数灾难。因为存在 |V|^n 可能的 n-gram,而且 |V| 通常很大。 + +**神经语言模型**(Neural Language Model, NLM) +- NLM 是一类用来克服维数灾难的语言模型,它使用词的**分布式表示**对自然语言序列建模 +- NLM 能在识别两个相似词的同时,不丧失将词编码为不同的能力。神经语言模型共享一个词(及其上下文)和其他类似词的统计强度。模型为每个词学习的分布式表示,允许模型处理具有类似共同特征的词来实现这种共享。因为这样的属性很多,所以存在许多泛化的方式,可以将信息从每个训练语句传递到指数数量的语义相关语句。 + - 例如,如果词 dog 和词 cat 映射到具有许多属性的表示,则包含词 cat 的句子可以告知模型对包含词 dog 的句子做出预测,反之亦然。 +- 使用分布式表示来改进自然语言处理模型的基本思想不必局限于神经网络。它还可以用于图模型,其中分布式表示是多个潜变量的形式 (Mnih and Hinton, 2007)。 + +**词嵌入**(word embedding) +- 词的分布时表示称为词嵌入,在这个解释下,我们将原始符号视为维度等于词表大小的空间中的点。词表示将这些点**嵌入到较低维的特征空间**中。 +- 在原始空间中,每个词由一个one-hot向量表示,因此每对词彼此之间的欧氏距离都是 √2。 +- 在**嵌入空间**中,经常出现在类似上下文中的词彼此接近。这通常导致具有相似含义的词变得邻近。 + + ![](../_assets/TIM截图20180619205103.png) + - 这些嵌入是为了可视化才表示为 2 维。在实际应用中,嵌入通常具有更高的维度并且可以同时捕获词之间多种相似性。 + +**高维输出** +- 对于大词汇表,由于词汇量很大,在词的选择上表示输出分布的计算和存储成本可能非常高。 +- 表示这种分布的朴素方法是应用一个仿射变换,将隐藏表示转换到输出空间,然后应用 softmax 函数。因为 softmax 要在所有输出之间归一化,所以需要执行全矩阵乘法,这是高计算成本的原因。因此,输出层的高计算成本在训练期间(计算似然性及其梯度)和测试期间(计算所有或所选词的概率)都有出现。 +- **一些解决方案** + - 使用**短列表**——简单来说,就是限制词表的大小 + - **分层 Softmax** + - **重要采样**——负采样就是一种简单的重要采样方式 + +**分层 Softmax** +- 减少大词汇表 V 上高维输出层计算负担的经典方法 (Goodman, 2001) 是**分层地分解概率**。|V| 因子可以降低到 log|V| 一样低,而无需执行与 |V| 成比例数量(并且也与隐藏单元数量成比例)的计算。 +- 我们可以认为这种层次结构是先建立词的类别,然后是词类别的类别,然后是词类别的类别的类别等等。这些嵌套类别构成一棵树,其叶子为词。 +- 选择一个词的概率是由路径(从树根到包含该词叶子的路径)上的每个节点通向该词分支概率的乘积给出。 + +**重要采样**/负采样 +- 加速神经语言模型训练的一种方式是,避免明确地计算所有未出现在下一位置的词对梯度的贡献。 +- 每个不正确的词在此模型下具有低概率。枚举所有这些词的计算成本可能会很高。相反,我们可以仅采样词的子集。 + +**结合 n-gram 和神经语言模型** +- n-gram 模型相对神经网络的主要优点是具有更高的模型容量(通过存储非常多的元组的频率),并且处理样本只需非常少的计算量。 +- 相比之下,将神经网络的参数数目加倍通常也大致加倍计算时间。 +- 增加神经语言模型容量的一种简单方法是将之与 n-gram 方法结合,集成两个模型 + +**神经机器翻译**(NMT) +- 编码器和解码器的想法 (Allen 1987; Chrisman 1991; Forcada and Ñeco 1997)很早就应用到了 NMT 中。 + + ![](../_assets/TIM截图20180619214629.png) + +- 基于 MLP 方法的缺点是需要将序列预处理为固定长度。为了使翻译更加灵活,我们希望模型允许可变的输入长度和输出长度。所以大量的 NMT 模型使用 RNN 作为基本单元。 + +**注意力(Attention)机制** +- 使用固定大小的表示概括非常长的句子(例如 60 个词)的所有语义细节是非常困难的。这需要使用足够大的 RNN。这会带来一些列训练问题。 +- 更高效的方法是先读取整个句子或段落(以获得正在表达的上下文和焦点),然后一次翻译一个词,每次聚焦于输入句子的不同部分来收集产生下一个输出词所需的语义细节 (Bahdanau et al., 2015)——**Attention** 机制 + + ![](../_assets/TIM截图20180619214735.png) + - 由 Bahdanau et al. (2015) 引入的现代注意力机制,本质上是**加权平均**。 + +**其他应用**: +- 推荐系统 +- 知识表示、推理和回答 + diff --git "a/A-\346\267\261\345\272\246\345\255\246\344\271\240/\345\244\207\345\277\230-\346\234\257\350\257\255\350\241\250.md" "b/A-\346\267\261\345\272\246\345\255\246\344\271\240/\345\244\207\345\277\230-\346\234\257\350\257\255\350\241\250.md" new file mode 100644 index 00000000..9299ed92 --- /dev/null +++ "b/A-\346\267\261\345\272\246\345\255\246\344\271\240/\345\244\207\345\277\230-\346\234\257\350\257\255\350\241\250.md" @@ -0,0 +1,41 @@ +术语表 +=== + +Index +--- + + +- [指数加权平均(指数衰减平均)](#指数加权平均指数衰减平均) + - [偏差修正](#偏差修正) + + + +## 指数加权平均(指数衰减平均) +> [什么是指数加权平均、偏差修正? - 郭耀华](http://www.cnblogs.com/guoyaohua/p/8544835.html) - 博客园 +- **加权平均** + - 假设 `θi` 的权重分别为 `ρi`,则 `θi` 的加权平均为: +
+ +- **指数加权平均** +
+ + > 注意到越久前的记录其权重呈**指数衰减**,因此指数加权平均也称**指数衰减平均** +- **示例**:设 `ρ=0.9, v0=0` + +
+ + > 其中 `v_t` 可以**近似**认为是最近 `1/1-ρ` 个值的滑动平均(`ρ=0.9`时,`0.1 * 0.9^9 ≈ 0.038`),更久前的记录其权重已近似为 0。 + +### 偏差修正 +- 指数加权平均在前期会存在较大的**误差** +
+ + - 注意到只有当 `t -> ∞` 时,所有权重的和才接近 1,当 `t` 比较小时,并不是标准的加权平均 +- **示例**:设 `ρ=0.9, v0=0` +
+ + - 当 `t` 较小时,与希望的加权平均结果差距较大 +- **引入偏差修正** +
+ + - 偏差修正只对**前期**的有修正效果,**后期**当 `t` 逐渐增大时 `1-ρ^t -> 1`,将不再影响 `v_t`,与期望相符 \ No newline at end of file diff --git "a/A-\346\267\261\345\272\246\345\255\246\344\271\240/\345\267\245\347\250\213\345\256\236\350\267\265/\346\250\241\345\236\213\345\216\213\347\274\251.md" "b/A-\346\267\261\345\272\246\345\255\246\344\271\240/\345\267\245\347\250\213\345\256\236\350\267\265/\346\250\241\345\236\213\345\216\213\347\274\251.md" new file mode 100644 index 00000000..12868398 --- /dev/null +++ "b/A-\346\267\261\345\272\246\345\255\246\344\271\240/\345\267\245\347\250\213\345\256\236\350\267\265/\346\250\241\345\236\213\345\216\213\347\274\251.md" @@ -0,0 +1,20 @@ +深度学习-工程实践-模型压缩 +=== + + +Index +--- + + +- [模型压缩](#模型压缩) +- [参考](#参考) + + + + +## 模型压缩 + + +## 参考 +- https://blog.csdn.net/zhongshaoyy/article/details/53582048 + - 知识就是一幅将输入向量导引至输出向量的地图。(在神经网络的背景下) \ No newline at end of file diff --git "a/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/A-NLP\345\217\221\345\261\225\350\266\213\345\212\277.md" "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/A-NLP\345\217\221\345\261\225\350\266\213\345\212\277.md" new file mode 100644 index 00000000..8f43f868 --- /dev/null +++ "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/A-NLP\345\217\221\345\261\225\350\266\213\345\212\277.md" @@ -0,0 +1,128 @@ +NLP 发展趋势 +=== + +Index +--- + + +- [【2018.11】预见未来|NLP将迎来黄金十年(MSRA)](#201811预见未来|nlp将迎来黄金十年msra) + - [NLP 新的发展基础](#nlp-新的发展基础) + - [NLP 的研究热点](#nlp-的研究热点) + - [NLP 技术的重要进展](#nlp-技术的重要进展) + - [值得关注的 NLP 技术](#值得关注的-nlp-技术) +- [【2019.02】对话 MSRA 副院长周明:回望过去,展望未来,NLP有哪些发展趋势?](#201902对话-msra-副院长周明回望过去展望未来nlp有哪些发展趋势) + - [MSRA 的重点](#msra-的重点) + - [NLP 领域进展](#nlp-领域进展) + - [中文 NLP 的突破](#中文-nlp-的突破) + - [2019 研究热点](#2019-研究热点) + + + + +## 【2018.11】预见未来|NLP将迎来黄金十年(MSRA) +> https://www.toutiao.com/a6628158223692071427 - 今日头条 + +### NLP 新的发展基础 +- 来自各个行业的文本大数据将会更好地采集、加工、入库; +- 来自搜索引擎、客服、商业智能、语音助手、翻译、教育、法律、金融等领域对NLP的需求会大幅度上升,对NLP质量也提出更高的要求; +- 文本数据和语音、图像数据的多模态融合成为未来机器人的刚需。 + +### NLP 的研究热点 +1. 将知识和常识引入目前基于数据的学习系统中; +1. 低资源的NLP任务的学习方法; +1. 上下文建模、多轮语义理解; +1. 基于语义分析、知识和常识的可解释 NLP。 + +### NLP 技术的重要进展 +- 神经机器翻译 + - (Hassan et al., 2018)高效利用大规模单语数据的联合训练、对偶学习技术;解决曝光偏差问题的一致性正则化技术、推敲网络 +- 智能人机交互 + - 聊天系统架构 + +
+ + - 三层引擎: + - 第一层,通用聊天机器人; + - 第二层,搜索和问答(Infobot); + - 第三层,面向特定任务对话系统(Bot) +- 机器阅读理解 + - NL-Net + - BERT +- 机器创作 + - 对联、 + - **歌词**的机器创作过程: + - 确定主题,比如希望创作一首与“秋”、“岁月”、“沧桑”、“感叹”相关的歌; + - 利用词向量表示技术找到相关的词,有“秋风”、“流年”、“岁月”、“变迁”等; + - 通过序列到序列的神经网络,用歌词的上一句去生成下一句;如果是第一句,则用一个特殊的序列作为输入去生成第一句歌词 + - **谱曲**:类似一个翻译过程,但更为严格 + +### 值得关注的 NLP 技术 +- 预训练神经网络 + - ElMo、BERT 等模型 + - 在什么粒度(word,sub-word,character)上进行预训练,用什么结构的语言模型(LSTM,Transformer等)训练,在什么样的数据上(不同体裁的文本)进行训练,以及如何将预训练的模型应用到具体任务,都是需要继续研究的问题。 +- 低资源 NLP 任务 + - 无监督、半监督学习 + - 迁移学习、多任务学习 +- 迁移学习 + - 不同的 NLP 任务虽然采用各自不同类型的数据进行模型训练,但在编码器(Encoder)端往往是同构的,即都会将输入的词或句子转化为对应的向量表示,然后再使用各自的解码器完成后续翻译、改写和答案生成等任务。 + - 因此,可以将通过不同任务训练得到的编码器看作是不同任务对应的一种向量表示模型;然后通过迁移学习(Transfer Learning)的方式将这类信息迁移到目前关注的目标任务上来 +- 多任务学习 + - 多任务学习(Multi-task Learning)可通过端到端的方式,直接在主任务中引入其他辅助任务的监督信息,用于保证模型能够学到不同任务间共享的知识和信息; + - McCann 等提出了利用**问答框架**使用多任务学习训练十项自然语言任务 + > [一个模型搞定十大自然语言任务](https://www.toutiao.com/a6569393480089469454) +- 知识和常识的引入 + - 应用:机器阅读理解、语义分析 + - 领域知识:维基百科、知识图谱 + - 常识的引入(缺乏深入研究) +- 多模态学习 + - 视觉问答 + - 基于问题生成的视觉问答方法(Li et al., 2018) + - 基于场景图生成的视觉问答方法(Lu et al., 2018) + - 视频问答 + + +## 【2019.02】对话 MSRA 副院长周明:回望过去,展望未来,NLP有哪些发展趋势? +> https://www.toutiao.com/i6656937082805551624 - 头条 + +### MSRA 的重点 +- 机器阅读理解(MRC) +- 神经机器翻译(NMT) + - 联合训练、对偶学习 + - 一致性规范、推敲网络 +- 语法检查(Grammar Check) + - 自动生成训练语料、多次解码逐轮求优 +- 文本语音转换(Text To Speech, TTS) +- 机器创作(写诗、谱曲、新闻) + +### NLP 领域进展 +- 新的神经 NLP 模型 +- 以 BERT 为首的**预训练模型** + - 大规模语料所蕴含的普遍语言知识与具体应用场景相结合的潜力 +- **低资源** NLP 任务 + +### 中文 NLP 的突破 +- 中文阅读理解 +- 以中文为中心的机器翻译 +- 聊天系统 + - 多模态聊天(小冰) +- 语音对话 + - 搜索引擎、语音助手、智能音箱、物联网、电子商务、智能家居等 + +### 2019 研究热点 +> [NLP将迎来黄金十年](#2019预见未来|nlp将迎来黄金十年)一文中有更详细的介绍,包括最新的论文推荐 +1. 预训练模型 + - 上下文建模 +1. 基于语义分析、知识和常识的可解释 NLP + - 将知识和常识引入到目前基于数据的学习模型中 +1. 多模态融合 + - 文本数据和语音、图像数据的多模态融合是未来机器人的刚需 +1. 低资源 NLP 任务(无语料或者小语料的场景) + - 半监督、无监督学习方法 + - Transfer Learning、Multi-task Learning + - 语言学在 NLP 研究中的作用 + > 语言学家在自然语言处理研究中大有可为 - 冯志伟 +1. 多模态融合 + - 在神经网络的框架下,可以用统一的模式来对多模态(语言、文字、图像、视频)进行建模(编码和解码),从而实现端到端的学习 + - 应用:Capturing、CQA/VQA、机器创作 + + diff --git "a/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/A-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206\345\237\272\347\241\200.md" "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/A-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206\345\237\272\347\241\200.md" new file mode 100644 index 00000000..006dd497 --- /dev/null +++ "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/A-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206\345\237\272\347\241\200.md" @@ -0,0 +1,336 @@ +NLP-NLP基础 +=== + +Index +--- + + +- [NLP 概述](#nlp-概述) + - [解决 NLP 问题的一般思路](#解决-nlp-问题的一般思路) + - [NLP 的历史进程](#nlp-的历史进程) + - [Seq2Seq 模型](#seq2seq-模型) + - [评价机制](#评价机制) + - [困惑度 (Perplexity, PPX)](#困惑度-perplexity-ppx) + - [BLEU](#bleu) + - [ROUGE](#rouge) +- [语言模型](#语言模型) + - [XX 模型的含义](#xx-模型的含义) + - [概率/统计语言模型 (PLM, SLM)](#概率统计语言模型-plm-slm) + - [参数的规模](#参数的规模) + - [可用的概率模型](#可用的概率模型) + - [N-gram 语言模型](#n-gram-语言模型) + - [可靠性与可区别性](#可靠性与可区别性) + - [OOV 问题](#oov-问题) + - [平滑处理 TODO](#平滑处理-todo) + - [神经概率语言模型 (NPLM)](#神经概率语言模型-nplm) + - [N-gram 神经语言模型](#n-gram-神经语言模型) + - [N-gram 神经语言模型的网络结构](#n-gram-神经语言模型的网络结构) + - [模型参数的规模与运算量](#模型参数的规模与运算量) + - [相比 N-gram 模型,NPLM 的优势](#相比-n-gram-模型nplm-的优势) + - [NPLM 中的 OOV 问题](#nplm-中的-oov-问题) + + + +# NLP 概述 +> 自然语言处理之序列模型 - 小象学院 +## 解决 NLP 问题的一般思路 +```tex +这个问题人类可以做好么? + - 可以 -> 记录自己的思路 -> 设计流程让机器完成你的思路 + - 很难 -> 尝试从计算机的角度来思考问题 +``` + +## NLP 的历史进程 +- **规则系统** + - 正则表达式/自动机 + - 规则是固定的 + - **搜索引擎** + ```tex + “豆瓣酱用英语怎么说?” + 规则:“xx用英语怎么说?” => translate(XX, English) + + “我饿了” + 规则:“我饿(死)了” => recommend(饭店,地点) + ``` +- **概率系统** + - 规则从数据中**抽取** + - 规则是有**概率**的 + - 概率系统的一般**工作方式** + ```tex + 流程设计 + 收集训练数据 + 预处理 + 特征工程 + 分类器(机器学习算法) + 预测 + 评价 + ``` +
+ + - 最重要的部分:数据收集、预处理、特征工程 + - 示例 + ```tex + 任务: + “豆瓣酱用英语怎么说” => translate(豆瓣酱,Eng) + + 流程设计(序列标注): + 子任务1: 找出目标语言 “豆瓣酱用 **英语** 怎么说” + 子任务2: 找出翻译目标 “ **豆瓣酱** 用英语怎么说” + + 收集训练数据: + (子任务1) + “豆瓣酱用英语怎么说” + “茄子用英语怎么说” + “黄瓜怎么翻译成英语” + + 预处理: + 分词:“豆瓣酱 用 英语 怎么说” + + 抽取特征: + (前后各一个词) + 0 茄子: < _ 用 + 0 用: 豆瓣酱 _ 英语 + 1 英语: 用 _ 怎么说 + 0 怎么说: 英语 _ > + + 分类器: + SVM/CRF/HMM/RNN + + 预测: + 0.1 茄子: < _ 用 + 0.1 用: 豆瓣酱 _ 英语 + 0.7 英语: 用 _ 怎么说 + 0.1 怎么说: 英语 _ > + + 评价: + 准确率 + ``` +- 概率系统的优/缺点 + - `+` 规则更加贴近于真实事件中的规则,因而效果往往比较好 + - `-` 特征是由专家/人指定的; + - `-` 流程是由专家/人设计的; + - `-` 存在独立的**子任务** + +- **深度学习** + - 深度学习相对概率模型的优势 + - 特征是由专家指定的 `->` 特征是由深度学习自己提取的 + - 流程是由专家设计的 `->` 模型结构是由专家设计的 + - 存在独立的子任务 `->` End-to-End Training + +## Seq2Seq 模型 +- 大部分自然语言问题都可以使用 Seq2Seq 模型解决 +
+ +- **“万物”皆 Seq2Seq** +
+ + +## 评价机制 + +### 困惑度 (Perplexity, PPX) +> [Perplexity](https://en.wikipedia.org/wiki/Perplexity) - Wikipedia +- 在信息论中,perplexity 用于度量一个**概率分布**或**概率模型**预测样本的好坏程度 + > ../机器学习/[信息论](../A-机器学习/A-机器学习基础#信息论) + +

基本公式

+ +- **概率分布**(离散)的困惑度 +
+ + > 其中 `H(p)` 即**信息熵** + +- **概率模型**的困惑度 +
+ + > 通常 `b=2` + +- **指数部分**也可以是**交叉熵**的形式,此时困惑度相当于交叉熵的指数形式 +
+ + > 其中 `p~` 为**测试集**中的经验分布——`p~(x) = n/N`,其中 `n` 为 x 的出现次数,N 为测试集的大小 + +**语言模型中的 PPX** +- 在 **NLP** 中,困惑度常作为**语言模型**的评价指标 +
+ +- 直观来说,就是下一个**候选词数目**的期望值—— + + 如果不使用任何模型,那么下一个候选词的数量就是整个词表的数量;通过使用 `bi-gram`语言模型,可以将整个数量限制到 `200` 左右 + +### BLEU +> [一种机器翻译的评价准则——BLEU](https://blog.csdn.net/qq_21190081/article/details/53115580) - CSDN博客 +- 机器翻译评价准则 +- 计算公式 +
+ + 其中 +
+
+ + + > `c` 为生成句子的长度;`r` 为参考句子的长度——目的是**惩罚**长度过短的候选句子 + +- 为了计算方便,会加一层 `log` +
+ + > 通常 `N=4, w_n=1/4` + +### ROUGE +> [自动文摘评测方法:Rouge-1、Rouge-2、Rouge-L、Rouge-S](https://blog.csdn.net/qq_25222361/article/details/78694617) - CSDN博客 +- 一种机器翻译/自动摘要的评价准则 + +> [BLEU,ROUGE,METEOR,ROUGE-浅述自然语言处理机器翻译常用评价度量](https://blog.csdn.net/joshuaxx316/article/details/58696552) - CSDN博客 + + +# 语言模型 + +## XX 模型的含义 +- 如果能使用某个方法对 XX **打分**(Score),那么就可以把这个方法称为 “**XX 模型**” + - **篮球明星模型**: `Score(库里)`、`Score(詹姆斯)` + - **话题模型**——对一段话是否在谈论某一话题的打分 + ``` + Score( NLP | "什么 是 语言 模型?" ) --> 0.8 + Score( ACM | "什么 是 语言 模型?" ) --> 0.05 + ``` + +## 概率/统计语言模型 (PLM, SLM) +- **语言模型**是一种对语言打分的方法;而**概率语言模型**把语言的“得分”通过**概率**来体现 +- 具体来说,概率语言模型计算的是**一个序列**作为一句话可能的概率 + ``` + Score("什么 是 语言 模型") --> 0.05 # 比较常见的说法,得分比较高 + Score("什么 有 语言 模型") --> 0.01 # 不太常见的说法,得分比较低 + ``` +- 以上过程可以形式化为: +
+ + 根据贝叶斯公式,有 +
+ +- 其中每个条件概率就是**模型的参数**;如果这个参数都是已知的,那么就能得到整个序列的概率了 + +### 参数的规模 +- 设词表的大小为 `N`,考虑长度为 `T` 的句子,理论上有 `N^T` 种可能的句子,每个句子中有 `T` 个参数,那么参数的数量将达到 `O(T*N^T)` + +### 可用的概率模型 +- 统计语言模型实际上是一个概率模型,所以常见的概率模型都可以用于求解这些参数 +- 常见的概率模型有:N-gram 模型、决策树、最大熵模型、隐马尔可夫模型、条件随机场、神经网络等 +- 目前常用于语言模型的是 N-gram 模型和神经语言模型(下面介绍) + + +## N-gram 语言模型 +- 马尔可夫(Markov)假设——未来的事件,只取决于有限的历史 +- 基于马尔可夫假设,N-gram 语言模型认为一个词出现的概率只与它前面的 n-1 个词相关 +
+ +- 根据**条件概率公式**与**大数定律**,当语料的规模足够大时,有 +
+ +- 以 `n=2` 即 bi-gram 为例,有 +
+ +- 假设词表的规模 `N=200000`(汉语的词汇量),模型参数与 `n· 的关系表 +
+ +### 可靠性与可区别性 +- 假设没有计算和存储限制,`n` 是不是越大越好? +- 早期因为计算性能的限制,一般最大取到 `n=4`;如今,即使 `n>10` 也没有问题, +- 但是,随着 `n` 的增大,模型的性能增大却不显著,这里涉及了**可靠性与可区别性**的问题 +- 参数越多,模型的可区别性越好,但是可靠性却在下降——因为语料的规模是有限的,导致 `count(W)` 的实例数量不够,从而降低了可靠性 + +### OOV 问题 +- OOV 即 Out Of Vocabulary,也就是序列中出现了词表外词,或称为**未登录词** +- 或者说在测试集和验证集上出现了训练集中没有过的词 +- 一般**解决方案**: + - 设置一个词频阈值,只有高于该阈值的词才会加入词表 + - 所有低于阈值的词替换为 UNK(一个特殊符号) +- 无论是统计语言模型还是神经语言模型都是类似的处理方式 + > [NPLM 中的 OOV 问题](#nplm-中的-oov-问题) + +### 平滑处理 TODO +- `count(W) = 0` 是怎么办? +- 平滑方法(层层递进): + - Add-one Smoothing (Laplace) + - Add-k Smoothing (k<1) + - Back-off (回退) + - Interpolation (插值法) + - Absolute Discounting (绝对折扣法) + - Kneser-Ney Smoothing (KN) + - Modified Kneser-Ney + > [自然语言处理中N-Gram模型的Smoothing算法](https://blog.csdn.net/baimafujinji/article/details/51297802) - CSDN博客 + + +## 神经概率语言模型 (NPLM) +> [专题-词向量](./B-专题-词向量) +- 神经概率语言模型依然是一个概率语言模型,它通过**神经网络**来计算概率语言模型中每个参数 +
+ + - 其中 `g` 表示神经网络,`i_w` 为 `w` 在词表中的序号,`context(w)` 为 `w` 的上下文,`V_context` 为上下文构成的特征向量。 + - `V_context` 由上下文的**词向量**进一步组合而成 + +### N-gram 神经语言模型 +> [A Neural Probabilistic Language Model](http://www.jmlr.org/papers/volume3/bengio03a/bengio03a.pdf) (Bengio, et al., 2003) +- 这是一个经典的神经概率语言模型,它沿用了 N-gram 模型中的思路,将 `w` 的前 `n-1` 个词作为 `w` 的上下文 `context(w)`,而 `V_context` 由这 `n-1` 个词的词向量拼接而成,即 +
+ + - 其中 `c(w)` 表示 `w` 的词向量 + - 不同的神经语言模型中 `context(w)` 可能不同,比如 Word2Vec 中的 CBOW 模型 +- 每个训练样本是形如 `(context(w), w)` 的二元对,其中 `context(w)` 取 w 的前 `n-1` 个词;当不足 `n-1`,用特殊符号填充 + - 同一个网络只能训练特定的 `n`,不同的 `n` 需要训练不同的神经网络 + +#### N-gram 神经语言模型的网络结构 +- 【**输入层**】首先,将 `context(w)` 中的每个词映射为一个长为 `m` 的词向量,**词向量在训练开始时是随机的**,并**参与训练**; +- 【**投影层**】将所有上下文词向量**拼接**为一个长向量,作为 `w` 的特征向量,该向量的维度为 `m(n-1)` +- 【**隐藏层**】拼接后的向量会经过一个规模为 `h` 隐藏层,该隐层使用的激活函数为 `tanh` +- 【**输出层**】最后会经过一个规模为 `N` 的 Softmax 输出层,从而得到词表中每个词作为下一个词的概率分布 +> 其中 `m, n, h` 为超参数,`N` 为词表大小,视训练集规模而定,也可以人为设置阈值 +- 训练时,使用**交叉熵**作为损失函数 +- **当训练完成时**,就得到了 N-gram 神经语言模型,以及副产品**词向量** +- 整个模型可以概括为如下公式: +

+
+ + > 原文的模型还考虑了投影层与输出层有有边相连的情形,因而会多一个权重矩阵,但本质上是一致的: + >>

+ >>
+ +### 模型参数的规模与运算量 +- 模型的超参数:`m, n, h, N` + - `m` 为词向量的维度,通常在 `10^1 ~ 10^2` + - `n` 为 n-gram 的规模,一般小于 5 + - `h` 为隐藏的单元数,一般在 `10^2` + - `N` 位词表的数量,一般在 `10^4 ~ 10^5`,甚至 `10^6` +- 网络参数包括两部分 + - 词向量 `C`: 一个 `N * m` 的矩阵——其中 `N` 为词表大小,`m` 为词向量的维度 + - 网络参数 `W, U, p, q`: + ``` + - W: h * m(n-1) 的矩阵 + - p: h * 1 的矩阵 + - U: N * h 的矩阵 + - q: N * 1 的矩阵 + ``` +- 模型的运算量 + - 主要集中在隐藏层和输出层的矩阵运算以及 SoftMax 的归一化计算 + - 此后的相关研究中,主要是针对这一部分进行优化,其中就包括 **Word2Vec** 的工作 + +### 相比 N-gram 模型,NPLM 的优势 +- 单词之间的相似性可以通过词向量来体现 + > 相比神经语言模型本身,作为其副产品的词向量反而是更大的惊喜 + > + > [词向量的理解](./B-专题-词向量#词向量的理解) +- 自带平滑处理 + +### NPLM 中的 OOV 问题 +- 在处理语料阶段,与 N-gram 中的处理方式是一样的——将不满阈值的词全部替换为 UNK +**神经网络**中,一般有如下几种处理 UNK 的思路 +- 为 UNK 分配一个随机初始化的 embedding,并**参与训练** + > 最终得到的 embedding 会有一定的语义信息,但具体好坏未知 +- 把 UNK 都初始化成 0 向量,**不参与训练** + > UNK 共享相同的语义信息 +- 每次都把 UNK 初始化成一个新的随机向量,**不参与训练** + > 常用的方法——因为本身每个 UNK 都不同,随机更符合对 UNK 基于最大熵的估计 + >> [How to add new embeddings for unknown words in Tensorflow (training & pre-set for testing)](https://stackoverflow.com/questions/45113130/how-to-add-new-embeddings-for-unknown-words-in-tensorflow-training-pre-set-fo) - Stack Overflow + >> + >> [Initializing Out of Vocabulary (OOV) tokens](https://stackoverflow.com/questions/45495190/initializing-out-of-vocabulary-oov-tokens) - Stack Overflow +- 基于 Char-Level 的方法 + > PaperWeekly 第七期 -- [基于Char-level的NMT OOV解决方案](https://zhuanlan.zhihu.com/p/22700538?refer=paperweekly) + diff --git "a/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/B-\344\270\223\351\242\230-\345\217\245\345\265\214\345\205\245.md" "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/B-\344\270\223\351\242\230-\345\217\245\345\265\214\345\205\245.md" new file mode 100644 index 00000000..cfed01c3 --- /dev/null +++ "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/B-\344\270\223\351\242\230-\345\217\245\345\265\214\345\205\245.md" @@ -0,0 +1,267 @@ +专题-句嵌入(Sentence Embedding) +=== + +Reference +--- +- [The Current Best of Universal Word Embeddings and Sentence Embeddings](https://medium.com/huggingface/universal-word-sentence-embeddings-ce48ddc8fc3a) + +Index +--- + + +- [基线模型](#基线模型) + - [基于统计的词袋模型(BoW)](#基于统计的词袋模型bow) + - [基于词向量的词袋模型](#基于词向量的词袋模型) + - [均值模型](#均值模型) + - [加权模型](#加权模型) + - [基于 RNN(任务相关)](#基于-rnn任务相关) + - [基于 CNN(任务相关)](#基于-cnn任务相关) +- [词袋模型](#词袋模型) + - [[2018] Power Mean 均值模型](#2018-power-mean-均值模型) + - [[2017] SIF 加权模型](#2017-sif-加权模型) + - [[]](#) +- [无监督模型](#无监督模型) + - [[2015] Skip-Thought Vector](#2015-skip-thought-vector) + - [[2018] Quick-Thought Vectors](#2018-quick-thought-vectors) +- [有监督模型](#有监督模型) + - [[2017] InferSent](#2017-infersent) + - [[2017] Self-Attention](#2017-self-attention) + - [[2015] DAN & RecNN](#2015-dan--recnn) +- [多任务学习](#多任务学习) + - [[2018] 基于多任务的 Sentence Embedding(微软)](#2018-基于多任务的-sentence-embedding微软) + - [[2018] Universal Sentence Encoder(谷歌)](#2018-universal-sentence-encoder谷歌) +- [参考文献](#参考文献) + + + + +## 基线模型 + +### 基于统计的词袋模型(BoW) +- 单个词的 One-Hot 表示 +- 基于频数的词袋模型 +- 基于 TF-IDF 的词袋模型 +- ... + +### 基于词向量的词袋模型 +#### 均值模型 +
+ +> 其中 `v_i` 表示维度为 `d` 的词向量,均值指的是对所有词向量**按位求和**后计算每一维的均值,最后 `s` 的维度与 `v` 相同。 + +#### 加权模型 +
+ +> 其中 `α` 可以有不同的选择,但一般应该遵循这样一个准则:**越常见的词权重越小** +>> [[2017] SIF 加权模型](#2017-sif-加权模型) + +### 基于 RNN(任务相关) +- 以最后一个隐状态作为整个句子的 Embedding +
+ +- 基于 RNN 的 Sentence Embedding 往往用于特定的有监督任务中,**缺乏可迁移性**,在新的任务中需要重新训练; +- 此外,由于 RNN 难以并行训练的缺陷,导致开销较大。 + + +### 基于 CNN(任务相关) +- 卷积的优势在于提取**局部特征**,利用 CNN 可以提取句子中类似 n-gram 的局部信息; +- 通过整合不同大小的 n-gram 特征作为整个句子的表示。 + +
+ + +## 词袋模型 + +### [2018] Power Mean 均值模型 +> [4] +- 本文是均值模型的一种推广;通过引入“幂均值”(Power Mean)来捕捉序列中的其他信息; +- 记句子 `s=(x_1, x_2, ..., x_n)` +
+ + - `x_i` 为每个词的词向量,维度为 `d` + - 普通的均值模型即 `p=1` 时的特例; + - 特别说明,`±∞` 实际上指的是 `max`/`min`,而不是绝对值最大/最小 + +- 本文通过**拼接**的方式来保留不同 `p` 的信息 +
+ + > 此时,Sentence Embedding 的维度应该是 **`K * d`** + +- 进一步的,文本还加入了在**不同词嵌入空间**上的词向量,依然通过**拼接**的方式保留信息 + - 所谓**不同词嵌入空间**,指的就是使用不同算法在不同语料上训练得到的词向量 +
+ + > 此时,Sentence Embedding 的维度应该是 **`K * L * d`** + - 本文使用了如下 **4 种词向量**: + - GloVe embeddings (GV) trained on Common Crawl + - Word2Vec trained on GoogleNews (GN) + - Attract-Repel (AR) (Mrksic et al., 2017) + - MorphSpecialized (MS) (Vulic et al., 2017) + + +### [2017] SIF 加权模型 +- **文献 [1]** 提出了一个简单但有效的**加权词袋模型 SIF** (**Smooth Inverse Frequency**),其性能超过了简单的 RNN/CNN 模型 + +- **SIF** 的计算分为两步:
+ **1)** 对句子中的每个词向量,乘以一个权重 `a/(a+p_w)`,其中 `a` 是一个常数(原文取 `0.0001`),`p_w` 为该词的词频;对于出现频率越高的词,其权重越小;
+ **2)** 计算**句向量矩阵**的第一个主成分 `u`,让每个句向量减去它在 `u` 上的投影(类似 PCA); + +- **完整算法描述** +
+ + + + +### [] + + +## 无监督模型 + +### [2015] Skip-Thought Vector +> [2] + +- 类似 Word2Vec/语言模型 的思想,Sentence Embedding 作为模型的副产品。 +- 给定一个三元组 `s_{i-1}, s_i, s_{i+1}` 表示 3 个连续的句子。 +- 模型使用 Encoder-Decoder 框架; +- 训练时,由 Encoder 对 `s_i` 进行编码;然后分别使用两个 Decoder 生成前一句 `s_{i-1}` 和下一句 `s_{i+1}` +
+ + - **Encoder**(GRU) +
+ + - **Decoder**(带窥孔的 GRU) +
+ + > 其中 `h_i` 为 Encoder 的输出,即表示 `s_i` 的 Sentence Embedding + - **Decoder** 可以看作是以 **Encoder** 输出为条件的**神经语言模型** +
+ + > 语言模型,`v` 表示词向量 + + - **目标函数** +
+ +- OOV 词的处理 TODO + +### [2018] Quick-Thought Vectors +> [4] + +- 本文是基于 Skip-Thought Vector 的改进 +- Skip-Thought Vector 中给出前一句**生成**上一句和下一句的任务,被重新描述为一个**分类任务**:Decoder 作为分类器从一组候选句子中选择正确的上一个/下一个句子。 + - **生成模型**(Skip-Thought Vector) +
+ + - **分类模型**(Quick-Thought Vectors) +
+ +- 该模型的一个主要优点是**训练速度**比 Skip-Thought Vector 快,后者需要训练 3 个 RNN 模块。 +- **一些细节**: + - `batch size = 400`,即一个 batch 为 400 个连续的句子; + - `context size = 3`,即对给定句子,预测其上一句和下一句; + - **负采样**:同一 batch 中除上下文句子,均作为 负例; + - 开始训练时,词向量被初始化为 `[-0.1, 0.1]`,没有使用预训练的词向量; + + +## 有监督模型 + +### [2017] InferSent +> [5] + +- 本文使用有监督的方法,在**自然语言推理**(NLI)数据集上训练 Sentence Embedding; +- 本文认为从 **NLI 数据集**(比如 **SNLI**)中训练得到的句向量也适合**迁移**到其他 NLP 任务中。 + - 就像在各种 **CV 任务**中使用基于 ImageNet 的模型(VGG, ResNet 等)来得到图像特征一样,在处理 **NLP 任务**之前可以先使用**本文公开的模型**来计算句子的特征。 + + +- **基本模型** + - 在 NLI 任务中,每个样本由三个元素构成 `(u, v, l)`——其中 `u` 表示前提(premise),`v` 表示假设(hypothesis),`l` 为类标(entailment 1, contradiction 2, neutral 3) + - 本文比较了 7 种编码器:1)LSTM, 2)GRU, 3)bi-GRU, 4)bi-LSTM(mean pooling), 5)bi-LSTM(max pooling), 6)self-attention, 7)CNN + +
+ + - 注意:在 NLI 数据集中,句子 `u` 和 `v` 的地位不是等价的 + + +### [2017] Self-Attention +> [3] + +- 本文提出使用**二维矩阵**作为句子表征,矩阵的行表示在句子不同位置的关注度,以解决句子被压缩成一维向量时的信息损失。 + +
+ + +### [2015] DAN & RecNN +> [9] + +- 原文模型仅用于分类,但也可用于有监督的学习 Sentence Embedding + > [Universal Sentence Encoder(谷歌)](#2018-universal-sentence-encoder谷歌) + +- 基本模型,其中比较常用的是 DAN + - DAN(Deep Averaging Network) +
+ + - RecNN +
+ + + +## 多任务学习 +- InferSent 模型的成功,使大家开始探索不同的有监督任务中得到的 Sentence Embedding 在下游任务中的效果。 +- 多任务学习试图在一次训练中组合不同的训练目标。 + +### [2018] 基于多任务的 Sentence Embedding(微软) +> [6] + +- 本文认为为了能够推广到各种不同的任务,需要对同一句话的多个方面进行编码。 +- 简单来说,模型同时在**多个任务**和**多个数据源**上进行训练,但是**共享**相同的 Sentence Embedding。 +- 任务及数据集包括: + - Skip-Thought(预测上一句/下一句)——BookCorpus + - 神经机器翻译(NMT)——En-Fr (WMT14) + En-De (WMT15) + - 自然语言推理(NLI)——SNLI + MultiNLI + - Constituency Parsing——PTB + 1-billion word + +- 本文模型与 [Skip-Thought Vector](#2015-skip-thought-vector) 基本一致 + - **主要区别**在于本文的 Encoder 部分使用的是 **Bi-GRU**,而 Decoder 部分完全一致; + - 使用 GRU 而非 LSTM 的原因主要是为了速度; + +### [2018] Universal Sentence Encoder(谷歌) +> [7] + +- 本文的目的是动态地适应各种的 NLP 任务,通过在不同的数据集和不同的任务上同时训练。 +- 本文使用**类似的多任务框架**,区别在于使用的 Encoder 不同。 + > [[2018] 基于多任务的 Sentence Embedding(微软)](#2018-基于多任务的-sentence-embedding微软) +- 本文以两种模型作为 Encoder + - **Transformer** [8]——更高的精度 + - **DAN** (Deep Averaging Network) [9]——更快的速度 + +- 一个可用的预训练版本 + ```python + embed = hub.Module("https://tfhub.dev/google/universal-sentence-encoder/2") + embeddings = embed([ + "The quick brown fox jumps over the lazy dog.", + "I am a sentence for which I would like to get its embedding"]) + + sess.run(embeddings) + ``` + > TensorFlow Hub  |  [universal-sentence-encoder](https://www.tensorflow.org/hub/modules/google/universal-sentence-encoder/2) + + +## 参考文献 +- [1] A Simple but Tough-to-Beat Baseline for Sentence Embeddings, ICLR 2016. +- [2] Skip-Thought Vectors, NIPS 2015. +- [3] A Structured Self-attentive Sentence Embedding, ICLR 2017. +- [4] An efficient framework for learning sentence representations, ICLR 2018. +- [5] Supervised Learning of Universal Sentence Representations from Natural Language Inference Data, ACL 2017. +- [6] Learning General Purpose Distributed Sentence Representations via Large Scale Multi-task Learning, ICLR 2018. +- [7] Universal Sentence Encoder, arXiv 2018. +- [8] Attention is all you need, NIPS 2017. +- [9] Deep unordered composition rivals syntactic methods for text classification, 2015 ACL. \ No newline at end of file diff --git "a/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/B-\344\270\223\351\242\230-\350\257\215\345\220\221\351\207\217.md" "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/B-\344\270\223\351\242\230-\350\257\215\345\220\221\351\207\217.md" new file mode 100644 index 00000000..ad623641 --- /dev/null +++ "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/B-\344\270\223\351\242\230-\350\257\215\345\220\221\351\207\217.md" @@ -0,0 +1,459 @@ +NLP-词向量 +=== + +Index +--- + + +- [背景知识](#背景知识) + - [神经语言模型](#神经语言模型) + - [什么是词向量/词嵌入](#什么是词向量词嵌入) + - [词向量的理解 TODO](#词向量的理解-todo) +- [Word2Vec](#word2vec) + - [基于层次 SoftMax 的 CBOW 模型](#基于层次-softmax-的-cbow-模型) + - [层次 SoftMax 的正向传播](#层次-softmax-的正向传播) + - [为什么层次 SoftMax 能加速](#为什么层次-softmax-能加速) + - [层次 Softmax 的反向传播 TODO](#层次-softmax-的反向传播-todo) + - [基于层次 Softmax 的 Skip-gram 模型](#基于层次-softmax-的-skip-gram-模型) + - [---](#---) + - [基于负采样的 CBOW 和 Skip-gram](#基于负采样的-cbow-和-skip-gram) + - [负采样算法](#负采样算法) + - [Word2Vec 中的做法](#word2vec-中的做法) + - [一些源码细节](#一些源码细节) + - [`σ(x)` 的近似计算](#σx-的近似计算) + - [低频词的处理](#低频词的处理) + - [高频词的处理](#高频词的处理) + - [自适应学习率](#自适应学习率) + - [参数初始化](#参数初始化) +- [GloVe](#glove) + - [共现矩阵](#共现矩阵) + - [构架共现矩阵的细节](#构架共现矩阵的细节) + - [GloVe 的基本思想](#glove-的基本思想) + - [GloVe 的目标函数](#glove-的目标函数) + - [GloVe 目标函数的推导过程](#glove-目标函数的推导过程) + - [GloVe 与 Word2Vec 的区别](#glove-与-word2vec-的区别) +- [FastText](#fasttext) + - [`gensim.models.FastText` 使用示例](#gensimmodelsfasttext-使用示例) + - [获取单个词的 ngrams 表示](#获取单个词的-ngrams-表示) + - [计算一个未登录词的词向量](#计算一个未登录词的词向量) +- [WordRank TODO](#wordrank-todo) +- [CharCNN 字向量](#charcnn-字向量) +- [其他实践](#其他实践) + - [一般 embedding 维度的选择](#一般-embedding-维度的选择) + + + +# 背景知识 +## 神经语言模型 +> [神经语言模型](./A-自然语言处理基础#神经概率语言模型-nplm) + +## 什么是词向量/词嵌入 +- 词向量(word embedding)是一个固定长度的实值向量 +- 词向量是神经语言模型的**副产品**。 +- 词向量是针对“词”提出的。事实上,也可以针对更细或更粗的粒度来进行推广——比如字向量、句向量、文档向量等 + +## 词向量的理解 TODO +> [word2vec 中的数学原理详解(三)背景知识](https://blog.csdn.net/itplus/article/details/37969817) - CSDN博客 +- 在 NLP 任务中,因为机器无法直接理解自然语言,所以首先要做的就是将语言**数学化**——词向量就是一种将自然语言数学化的方法。 + +**One-hot 表示** +- TODO + +**分布式表示** (distributed representation) +- 分布式假设 +- TODO +- 常见的分布式表示方法 + - 潜在语义分析 (Latent Semantic Analysis, LSA) + - SVD 分解 + - 隐含狄利克雷分布 (Latent Dirichlet Allocation, LDA),主题模型 + - 神经网络、深度学习 + +# Word2Vec +- Word2Vec 本质上也是一个神经语言模型,但是它的目标并不是语言模型本身,而是词向量;因此,其所作的一系列优化,都是为了更快更好的得到词向量 +- Word2Vec 提供了两套模型:**CBOW** 和 **Skip-Gram**(SG) + - CBOW 在已知 `context(w)` 的情况下,预测 `w` + - SG 在已知 `w` 的情况下预测 `context(w)` + +- 从训练集的构建方式可以更好的理解和区别 **CBOW** 和 **SG** 模型 + - 每个训练样本为一个二元组 `(x, y)`,其中 `x`为特征,`y`为标签 + + 假设上下文窗口的大小 `context_window =5`,即 +
+ + 或者说 `skip_window = 2`,有 `context_window = skip_window*2 + 1` + + - CBOW 的训练样本为: +
+ + - SG 的训练样本为: +
+ + - 一般来说,`skip_window <= 10` + +- 除了两套模型,Word2Vec 还提供了两套优化方案,分别基于 Hierarchical Softmax (层次SoftMax) 和 Negative Sampling (负采样) + +## 基于层次 SoftMax 的 CBOW 模型 +- 【**输入层**】将 `context(w)` 中的词映射为 `m` 维词向量,共 `2c` 个 +- 【**投影层**】将输入层的 `2c` 个词向量累加求和,得到新的 `m` 维词向量 +- 【**输出层**】输出层对应一棵**哈夫曼树**,以词表中词作为叶子节点,各词的出现频率作为权重——共 `N` 个叶子节点,`N-1` 个非叶子节点 +
+ +- 对比 [N-gram 神经语言模型的网络结构](#n-gram-神经语言模型的网络结构) + - 【输入层】前者使用的是 `w` 的前 `n-1` 个词,后者使用 `w` 两边的词 + > 这是后者词向量的性能优于前者的主要原因 + - 【投影层】前者通过拼接,后者通过**累加求和** + - 【隐藏层】后者无隐藏层 + - 【输出层】前者为线性结构,后者为树形结构 + +- 模型改进 + - 从对比中可以看出,CBOW 模型的主要改进都是为了**减少计算量**——取消隐藏层、使用**层Softmax**代替基本Softmax + +### 层次 SoftMax 的正向传播 +- 层 Softmax 实际上是把一个超大的多分类问题转化成一系列二分类问题 +- 示例:求 `P("足球"|context("足球"))` +
+ + - 从根节点到“足球”所在的叶子节点,需要经过 4 个分支,每次分支相当于一次**二分类**(逻辑斯蒂回归,二元Softmax) +
+ + > 这里遵从原文,将 0 作为正类,1 作为负类 + + - 而 `P("足球"|context("足球"))` 就是每次分类正确的概率之积,即 +
+ + > 这里每个非叶子都对应一个参数 `θ_i` + +#### 为什么层次 SoftMax 能加速 +- Softmax 大部分的计算量在于分母部分,它需要求出所有分量的和 +- 而层次 SoftMax 每次只需要计算两个分量,因此极大的提升了速度 + +### 层次 Softmax 的反向传播 TODO +> [word2vec 中的数学原理详解(四)基于 Hierarchical Softmax 的模型](https://blog.csdn.net/itplus/article/details/37969979) - CSDN博客 + +## 基于层次 Softmax 的 Skip-gram 模型 +- 这里保留了【投影层】,但实际上只是一个恒等变换 +
+ +- 从模型的角度看:CBOW 与 SG 模型的区别仅在于 `x_w` 的构造方式不同,前者是 `context(w)` 的词向量累加;后者就是 `w` 的词向量 +- 虽然 SG 模型用中心词做特征,上下文词做类标,但实际上两者的地位是等价的 + +## --- + +## 基于负采样的 CBOW 和 Skip-gram +- 层次 Softmax 还不够简单,于是提出了基于负采样的方法进一步提升性能 +- 负采样(Negative Sampling)是 NCE(Noise Contrastive Estimation) 的简化版本 + > [噪音对比估计(NCE)](https://blog.csdn.net/littlely_ll/article/details/79252064) - CSDN博客 +- CBOW 的训练样本是一个 `(context(w), w)` 二元对;对于给定的 `context(w)`,`w` 就是它的正样本,而其他所有词都是负样本。 +- 如果不使用**负采样**,即 N-gram 神经语言模型中的做法,就是对整个词表 Softmax 和交叉熵 +- 负采样相当于选取所有负例中的一部分作为负样本,从而减少计算量 +- Skip-gram 模型同理 + +## 负采样算法 +- 负采样算法,即对给定的 `w` ,生成相应负样本的方法 +- 最简单的方法是随机采样,但这会产生一点问题,词表中的词出现频率并不相同 + - 如果不是从词表中采样,而是从语料中采样;显然,那些高频词被选为负样本的概率要大于低频词 + - 在词表中采样时也应该遵循这个 +- 因此,负采样算法实际上就是一个**带权采样**过程 +
+ +### Word2Vec 中的做法 +- 记 +
+ +- 以这 `N+1` 个点对区间 `[0,1]` 做非等距切分 +- 引入的一个在区间 `[0,1]` 上的 `M` 等距切分,其中 `M >> N` +
+ + > 源码中取 `M = 10^8` +- 然后对两个切分做投影,得到映射关系 +
+ +- 采样时,每次生成一个 `[1, M-1]` 之间的整数 `i`,则 `Table(i)` 就对应一个样本;当采样到正例时,跳过(**拒绝采样**)。 + +- 特别的,Word2Vec 在计算 `len(w)` 时做了一些改动——为 `count(·)` 加了一个**指数** +
+ +## 一些源码细节 +### `σ(x)` 的近似计算 +- 类似带权采样的策略,用**查表**来代替计算 +
+ +- 具体计算公式如下 +
+ + > 因为 `σ(x)` 函数的饱和性,当 `x < -6 || x > 6` 时,函数值基本不变了 + +### 低频词的处理 +- 对于低频词,会设置阈值(默认 5),对于出现频次低于该阈值的词会直接舍弃,同时训练集中也会被删除 + +### 高频词的处理 +- 高频词提供的信息相对较少,为了提高低频词的词向量质量,有必要对高频词进行限制 +- 高频词对应的词向量在训练时,不会发生明显的变化,因此在训练是可以减少对这些词的训练,从而提升速度 + +**Sub-sampling 技巧** +- 源码中使用 Sub-sampling 技巧来解决高频词的问题,能带来 2~10 倍的训练速度提升,同时提高低频词的词向量精度 +- 给定一个词频阈值 `t`,将 `w` 以 `p(w)` 的概率舍弃,`p(w)` 的计算如下 +
+ +**Word2Vec 中的Sub-sampling** +- 显然,Sub-Sampling 只会针对 出现频次大于 `t` 的词 +- 特别的,Word2Vec 使用如下公式计算 `p(w)`,效果是类似的 +
+ +### 自适应学习率 +- 预先设置一个初始的学习率 `η_0`(默认 0.025),每处理完 `M`(默认 10000)个词,就根据以下公式调整学习率 +
+ +- 随着训练的进行,学习率会主键减小,并趋向于 0 +- 为了方式学习率过小,Word2Vec 设置了一个阈值 `η_min`(默认 `0.0001 * η_0`);当学习率小于 `η_min`,则固定为 `η_min`。 + +### 参数初始化 +- 词向量服从均匀分布 `[-0.5/m, 0.5/m]`,其中 `m` 为词向量的维度 +- 所有网络参数初始化为 `0` + +# GloVe +> CS224d - L2&3-词向量 + +## 共现矩阵 +- 共现矩阵的实现方式 + - 基于文档 - LSA 模型(SVD分解) + - 基于窗口 - 类似 skip-gram 模型中的方法 +
+ + > `skip_window = 1` 的共现矩阵 + +### 构架共现矩阵的细节 +- **功能词的处理** + - 功能词:如 "the", "he", "has", ... + - **法1**)直接忽略 + - 在一些分类问题上可以这么做;如果目标是词向量,则不建议使用这种方法 + - **法2**)设置阈值 `min(x, t)` + - 其中 `x` 为功能词语其他词的共现次数,`t` 为设置的阈值 +- 可以尝试使用一些方法代替单纯的计数,如**皮尔逊相关系数**,负数记为 0 + > 但是似乎没有人这么做 + +## GloVe 的基本思想 +- GloVe 模型的是基于**共现矩阵**构建的 +- GloVe 认为共现矩阵可以通过一些统计信息得到词之间的关系,这些关系可以一定程度上表达词的含义 +
+ + - **solid** related to **ice** but not **steam** + - **gas** related to **stream** but not **ice** + - **water** related to both + - **fashion** relate not to both + > 说明 TODO + +- GloVe 的基本思想: + - 假设词向量已知,如果这些词向量通过**某个函数**(目标函数)可以**拟合**共现矩阵中的统计信息,那么可以认为这些词向量也拥有了共现矩阵中蕴含的语义 + - 模型的训练过程就是拟合词向量的过程 + +## GloVe 的目标函数 +
+ +其中 +- `w_i` 和 `w_j` 为词向量 +- `x_ij` 为 `w_i` 和 `w_j` 的共现次数 +- `f(x)` 是一个权重函数,为了限制高频词和防止 `x_ij = 0` +
+ + - 当 `x_ij = 0` 时,有 +
+ +### GloVe 目标函数的推导过程 +> 以前整理在 OneNote 上的,有时间在整理 +- 目标函数 +
+
+
+
+
+ +- `w_i` 的权重函数 +
+ +## GloVe 与 Word2Vec 的区别 +- Word2Vec 本质上是一个神经网络;
+Glove 也利用了**反向传播**来更新词向量,但是结构要更简单,所以 GloVe 的速度更快 +- Glove 认为 Word2Vec 对高频词的处理还不够,导致速度慢;GloVe 认为共现矩阵可以解决这个问题 + > 实际 Word2Vec 已结有了一些对高频词的措施 > [高频词的处理](#高频词的处理) +- 从效果上看,虽然 GloVe 的训练速度更快,但是**词向量的性能**在通用性上要弱一些:
在一些任务上表现优于 Word2Vec,但是在更多的任务上要比 Word2Vec 差 + + +# FastText +- FastText 是从 Word2Vec 的 CBOW 模型演化而来的; + + 从网络的角度来看,两者的模型基本一致;**区别仅在于两者的输入和目标函数不同**; + +
+ + > [基于层次 SoftMax 的 CBOW 模型](#基于层次-softmax-的-cbow-模型) +- FastText 与 CBOW 的相同点: + - 包含三层:输入层、隐含层、输出层(Hierarchical Softmax) + - 输入都是多个单词的词向量 + - 隐藏层(投影层)都是对多个词向量的叠加平均 + - 输出都是一个特定的 target + - 从网络的角度看,两者基本一致 + +- 不同点: + - CBOW 的输入是中心词两侧`skip_window`内的上下文词;FastText 除了上下文词外,还包括这些词的字符级 **N-gram 特征** + +- **注意**,字符级 N-gram 只限制在单个词内,以英文为例 + ```Cpp + // 源码中计算 n-grams 的声明,只计算单个词的字符级 n-gram + compute_ngrams(word, unsigned int min_n, unsigned int max_n); + ``` + ```Python + # > https://github.com/vrasneur/pyfasttext#get-the-subwords + >>> model.args.get('minn'), model.args.get('maxn') + (2, 4) + # 调用源码的 Python 接口,源码上也会添加 '<' 和 '>' + >>> model.get_all_subwords('hello') # word + subwords from 2 to 4 characters + ['hello', '', 'lo', 'lo>', 'o>'] + >>> # model.get_all_subwords('hello world') # warning + ``` +- **值得一提的是**,因为 FastText 使用了字符级的 N-gram 向量作为额外的特征,使其能够对**未登录词**也能输出相应的词向量; + + **具体来说**,**未登录词**的词向量等于其 N-gram 向量的叠加 + + +## `gensim.models.FastText` 使用示例 +> ../codes/[FastText](../_codes/工具库/gensim/FastText.py) + +- 构建 FastText 以及获取词向量 + ```Python + # gensim 示例 + import gensim + import numpy as np + from gensim.test.utils import common_texts + from gensim.models.keyedvectors import FastTextKeyedVectors + from gensim.models._utils_any2vec import compute_ngrams, ft_hash + from gensim.models import FastText + + # 构建 FastText 模型 + sentences = [["Hello", "World", "!"], ["I", "am", "huay", "."]] + min_ngrams, max_ngrams = 2, 4 # ngrams 范围 + model = FastText(sentences, size=5, min_count=1, min_n=min_ngrams, max_n=max_ngrams) + + # 可以通过相同的方式获取每个单词以及任一个 n-gram 的向量 + print(model.wv['hello']) + print(model.wv['', '', ''] + ['', '', ''] + ['', ''] + ['', ''] + ['', '', ''] + ['', '', ''] + ['<.', '.>', '<.>'] + """ + assert sum_ngrams == len(model.wv.vectors_ngrams) + print(sum_ngrams) # 57 + print() + ``` + +### 计算一个未登录词的词向量 +- 未登录词实际上是已知 n-grams 向量的叠加平均 + ```Python + # 因为 "a", "aa", "aaa" 中都只含有 " `gensim.models.keyedvectors.FastTextKeyedVectors.word_vec(token)` 的内部实现 + ```Python + word_unk = "aam" + ngrams = compute_ngrams(word_unk, min_ngrams, max_ngrams) # min_ngrams, max_ngrams = 2, 4 + word_vec = np.zeros(model.vector_size, dtype=np.float32) + ngrams_found = 0 + for ngram in ngrams: + ngram_hash = ft_hash(ngram) % model.bucket + if ngram_hash in model.wv.hash2index: + word_vec += model.wv.vectors_ngrams[model.wv.hash2index[ngram_hash]] + ngrams_found += 1 + + if word_vec.any(): # + word_vec = word_vec / max(1, ngrams_found) + else: # 如果一个 ngram 都没找到,gensim 会报错;个人认为把 0 向量传出来也可以 + raise KeyError('all ngrams for word %s absent from model' % word_unk) + + print(word_vec) + print(model.wv["aam"]) + """ + [ 0.02210762 -0.10488641 0.05512805 0.09150169 0.00725085] + [ 0.02210762 -0.10488641 0.05512805 0.09150169 0.00725085] + """ + + # 如果一个 ngram 都没找到,gensim 会报错 + # 其实可以返回一个 0 向量的,它内部实际上是从一个 0 向量开始累加的; + # 但返回时做了一个判断——如果依然是 0 向量,则报错 + # print(model.wv['z']) + """ + Traceback (most recent call last): + File "D:/OneDrive/workspace/github/DL-Notes-for-Interview/code/工具库 /gensim/FastText.py", line 53, in + print(model.wv['z']) + File "D:\program\work\Python\Anaconda3\envs\tf\lib\site-packages\gensim\models \keyedvectors.py", line 336, in __getitem__ + return self.get_vector(entities) + File "D:\program\work\Python\Anaconda3\envs\tf\lib\site-packages\gensim\models \keyedvectors.py", line 454, in get_vector + return self.word_vec(word) + File "D:\program\work\Python\Anaconda3\envs\tf\lib\site-packages\gensim\models \keyedvectors.py", line 1989, in word_vec + raise KeyError('all ngrams for word %s absent from model' % word) + KeyError: 'all ngrams for word z absent from model' + """ + ``` + + +# WordRank TODO + +# CharCNN 字向量 +- CharCNN 的思想是通过字符向量得到词向量 + +> [1509] [Character-level Convolutional Networks for Text Classification](https://arxiv.org/abs/1509.01626) + + + +# 其他实践 +## 一般 embedding 维度的选择 +> [Feature Columns](https://www.tensorflow.org/versions/master/guide/feature_columns#indicator_and_embedding_columns)  |  TensorFlow +- 经验公式 `embedding_size = n_categories ** 0.25` +- 在大型语料上训练的词向量维度通常会设置的更大一些,比如 `100~300` + > 如果根据经验公式,是不需要这么大的,比如 200W 词表的词向量维度只需要 `200W ** 0.25 ≈ 37` + diff --git "a/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/C-\344\270\223\351\242\230-\345\244\232\346\250\241\346\200\201.md" "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/C-\344\270\223\351\242\230-\345\244\232\346\250\241\346\200\201.md" new file mode 100644 index 00000000..40f071e3 --- /dev/null +++ "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/C-\344\270\223\351\242\230-\345\244\232\346\250\241\346\200\201.md" @@ -0,0 +1,19 @@ +专题-多模态 +=== + +Index +--- + + +- [多模态词向量](#多模态词向量) + - [Papers](#papers) + + + +# 多模态词向量 + +## Papers +> [pdf](_papers/pdf/多模态(Multimodal)/多模态词向量) + + + diff --git "a/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/D-\350\247\206\350\247\211\351\227\256\347\255\224-1_\347\273\274\350\277\260.md" "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/D-\350\247\206\350\247\211\351\227\256\347\255\224-1_\347\273\274\350\277\260.md" new file mode 100644 index 00000000..d66ed947 --- /dev/null +++ "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/D-\350\247\206\350\247\211\351\227\256\347\255\224-1_\347\273\274\350\277\260.md" @@ -0,0 +1,570 @@ +视觉问答(VQA)综述 +=== + +**相关论文** +- [2016].Visual Question Answering + - 本文从**数据集**、**评价方法**、**算法**等方面讨论了 VQA 的现状及存在的问题,以及 VQA 的未来发展方向;特别讨论了现有**数据集**在训练和评价方面的**局限性**。 + +Index +--- + + +- [VQA 简述](#vqa-简述) + - [VQA 与其他图像任务](#vqa-与其他图像任务) + - [基于对象检测的任务](#基于对象检测的任务) + - [图像描述任务](#图像描述任务) + - [DenseCap](#densecap) + - [VQA 中的数据集](#vqa-中的数据集) + - [DAQUAR](#daquar) + - [COCO-QA](#coco-qa) + - [VQA Dataset](#vqa-dataset) + - [FM-IQA](#fm-iqa) + - [Visual Genome](#visual-genome) + - [Visual7W](#visual7w) + - [SHAPES](#shapes) + - [VQA 的评价方法 TODO](#vqa-的评价方法-todo) +- [主流模型与方法](#主流模型与方法) + - [基线模型](#基线模型) + - [分类模型](#分类模型) + - [生成模型](#生成模型) + - [贝叶斯模型](#贝叶斯模型) + - [基于 Attention 的模型](#基于-attention-的模型) + - [基于 Edge Boxes 的方法](#基于-edge-boxes-的方法) + - [基于 Uniform Grid 的方法](#基于-uniform-grid-的方法) + - [[49] Stacked Attention Networks for Image Question Answering(SAN)](#49-stacked-attention-networks-for-image-question-answeringsan) + - [[48] Ask, Attend and Answer: Exploring Question-Guided Spatial Attention for Visual Question Answering](#48-ask-attend-and-answer-exploring-question-guided-spatial-attention-for-visual-question-answering) + - [[52] Dynamic memory networks for visual and textual question answering](#52-dynamic-memory-networks-for-visual-and-textual-question-answering) + - [[54] Hierarchical Question-Image Co-Attention for Visual Question Answering](#54-hierarchical-question-image-co-attention-for-visual-question-answering) + - [[56] Dual attention networks for multimodal reasoning and matching](#56-dual-attention-networks-for-multimodal-reasoning-and-matching) + - [基于双线性池化的模型](#基于双线性池化的模型) + - [[46] Multimodal compact bilinear pooling for visual question answering and visual grounding](#46-multimodal-compact-bilinear-pooling-for-visual-question-answering-and-visual-grounding) + - [[57] Hadamard Product for Low-rank Bilinear Pooling](#57-hadamard-product-for-low-rank-bilinear-pooling) + - [组合模型](#组合模型) + - [[44] Deep Compositional Question Answering with Neural Module Networks](#44-deep-compositional-question-answering-with-neural-module-networks) + - [[55] Training recurrent answering units with joint loss minimization for VQA](#55-training-recurrent-answering-units-with-joint-loss-minimization-for-vqa) + - [其他模型 TODO](#其他模型-todo) +- [参考文献](#参考文献) + + + +# VQA 简述 +**VQA 的难点** +- 目前大多数关于图像的任务并不需要完全理解图像包含的信息。比如图像分类、物体检测、动作识别等。而解决 VQA 问题需要完全理解图像。 +- VQA 的问题可以使任意的,它实际上包含了一系列 CV 子问题: + - Object recognition - What is in the image? + - Object detection - Are there any cats in the image? + - Attribute classification - What color is the cat? + - Scene classification - Is it sunny? + - Counting - How many cats are in the image? +- 除此之外,还可以提出更多复杂的问题,比如: + - Spatial relationship - What is between the cat and the sofa? + - Common sense reasoning questions - Why is the the girl crying? + - ... +- 一个健壮的 VQA 系统应该能**同时**解决这些问题。 + > 显然,目前的技术还无法做到这一点;一个可行的办法是将一个 **Strong AI** 问题转化为多个 **Weak AI** 问题的集成。比如先对问题做一个分类,然后调用相应的图像模型去解答。 + +**VQA 系统的应用** +- 帮助弱视人群获取相关的图像信息; +- 改善人机交互 +- 图像检索 +- AI 基础问题:图灵测试 + +**本文结构** +- S2: VQA 与其他图像任务的比较 +- S3: 现有数据集存在的问题;这些数据集中的偏差(Bias)如何限制了它们评价算法的能力 +- S4: VQA 的评价指标 +- S5: 对目前 VQA 建模方法的分析 +- S6: VQA 未来可能的发展 + + +## VQA 与其他图像任务 +- VQA 的总体目标是从图像中提取与问题相关的语义信息,从细微物体的**检测**到抽象场景的**推理**。 +- 大多数 CV 任务都需要从图像中提取信息,但与 VQA 相比都存在某些局限性。 + +### 基于对象检测的任务 +- 对象识别、动作识别和场景分类都可以被定义为**图像分类任务**,现在最好的方法是使用 CNN 进行训练,将图像分类为特定的语义类别。 + - **对象识别**一般只需要对图像中的主要对象进行分类,而不用理解其在整个场景中的空间位置或作用。 + - **目标检测**通过对图像中每个对象实例放置一个边界框来定位特定的语义概念。 + - **语义分割**通过将每个**像素**分类为一个特定的语义类,使定位的任务更进一步。 + - **实例分割**(Instance segmentation)用于区分同一语义类的不同实例。 + + + > **相关论文** + >> 对象识别:[2] ResNet
+ >> 物体检测:[3]、[4] Faster R-CNN、[11]
+ >> 语义分割:[12] FCN、[13]
+ >> 实例分割:[14]、[15]、[16] + +**标签歧义** +
+ + > 【图1】目标检测 和 语义分割 +- **语义分割**或**实例分割**都不足以全面理解整个场景; +- 其中主要的问题在于**标签歧义**(label ambiguity) + - 比如上述图中“**黄叉**”的位置取 "**bag**"、"**black**"、"**person**" 之一都没有问题。 + - 一般来说,具体选取哪个标签,取决于具体的任务。 +- 此外,目前的主流方法(CNN+标签)不足以理解物体在整个场景下的作用(role) + - 比如,将“黄叉”位置标记为 "bag" 不足以了解该包与人的关系;或者标记为 "person" 也不能知道这个人的状态(跑、坐、...) +- 理想的 VQA 要求能够回答关于图像的任意问题,因此除了基本的检测问题,还需要理解对象彼此,以及和整个场景之间的关系。 + +### 图像描述任务 +- 除了 VQA 外,**图像描述**(image captioning)是另一个比较主流的、需要结合 CV 和 NLP 的任务。图像描述任务的目标是对给定图像生成相关的自然语言描述。 +- 结合 NLP 中的一些方法(RNN等),生成描述有不同的解决方案。 +- 但是,图像描述的另一个难点是**评价**。 + - 一些自动评价方法:BLEU、ROUGE、METEOR、CIDEr + - 这些方法中,除了 CIDEr,最初都是为了评价机器翻译的结果而提出的。 + - 这些方法每一个都存在一些局限性,它们常常将由机器生成的标题排在人工标题之前,但从人的角度看,这些结果并不够好,或者说**不是目标描述**。 +- 评价的一个难点在于,给定图像可以存在许多有效的标题,这些标题可以比较宽泛,也可能很具体。 + - 比如【图1】既可以描述为"A busy town sidewalk next to street parking and intersections."; + - 也可以使用 "A woman jogging with a dog on a leash." +- 如果不加限制,图像描述系统总是倾向于生成“得分”更高的表述。 + - 比如 "A person is walking down a street" 或 "Several cars are parked on the side of the road" 这些普适的描述总是会得到较高的排名(Rank)。 + - 事实上,一个简单图像描述系统,只要使用 KNN 等方法找到与给定图像比较**相似的图像**,并把它们的描述返回就能在部分评估指标下得到不错的分数。 + +> 相关论文:[5]、[17]、[18]、[19]、[20] + +#### DenseCap +- DenseCap 全称为 Dense image captioning,它在一定程度上缓解了普适标题的问题; +- DenseCap 会在图像的不同局部位置生成密集的注释; +
+ +- 即使如此,这些短描述也存在自动评价的问题; +- 此外,这些只针对局部信息的描述也不能提供空间信息或对象关系。 + +> 相关论文:[29] + +**小结** +- 总的来说,虽然图像描述系统也存在不确定性,但其描述粒度可以人工设定; +- 这一点与 VQA 不同,VQA 中回答的粒度是由提出的问题决定的。 + - "What season is this?" 需要理解整个场景 + - "What is the color of dog standing behind the girl with white dress?" 需要关注场景中某一局部的细节 +- 此外,因为**很多问题都具有明确的答案**,所以 VQA 的评价反而要简单一些。 +- 当然也存在一些可能存在歧义的问题(S4)。但对于多数问题,VQA 系统产生的答案可以通过与真实答案一对一匹配的方式进行评估。 + + +## VQA 中的数据集 +- 几个主要数据集: + - DAQUAR + - COCO-QA + - VQA Dataset + - FM-IQA + - Visual7W + - Visual Genome + +- 如果数据集在问题或答案(文本部分)的分布中包含**容易被利用的偏差**(Bias),那么算法可能在这份数据集上执行得很好,但不会真正解决 VQA 问题。 +- 下面主要讨论各数据集的创建过程及其**局限性** + +### DAQUAR +> Download: [Visual Turing Challenge](https://www.mpi-inf.mpg.de/departments/computer-vision-and-multimodal-computing/research/vision-and-language/visual-turing-challenge/) +- DAtaset for QUestion Answering on Real-world images (DAQUAR) ——第一个发布的主要 VQA 数据集 +- **数据规模**:6795 training and 5673 testing +- 只包含室内场景(indoor scenes),限制了问题的多样性; +- 部分图像比较混乱而且明暗比较极端,即使人类也仅做到 52.2% 的正确率。 + +### COCO-QA +> Download: [COCO-QA Dataset](http://www.cs.toronto.edu/~mren/imageqa/data/cocoqa/) + +- 该数据集中的 QA 对是使用一些 NLP 方法根据图像标题自动生成的; + - 比如一副图像的标题为 "A boy is playing Frisbee",那么可能的 Q 为 "What is the boy playing?",而答案 A 为 "Frisbee". +- **数据规模**:78,736 training and 38,948 testing QA pairs. +- **问题分布**:**object** (69.84%), **color** (16.59%), **counting** (7.47%) and **location** (6.10%) +- 所有答案都是一个词,且只有 435 个不同的答案;因此在这个数据集上评价比较简单; +- COCO-QA 的一个问题是用于生成 QA 的 NLP 方法存在缺陷: + - 为了便于处理,较长的句子被分解成较小的块,但在许多情况下,该算法不能很好地处理句子中存在的从句和语法变化。 + - 这导致一些 Q 存在语法错误甚至无法读懂 +- 这个数据集另一个问题是,它**只有 4 种简单的问题**(因为这些问题是基于图像的标题生成的);这些问题可能只需要捕捉到图像中的一些**局部信息**就能得出答案; + + +### VQA Dataset +> 这里的主要指该数据集的 1.0 版本(2015),目前已经发布了 2.0 版本(2017) +>> VQA: [Visual Question Answering](http://visualqa.org/) + +- 该数据集由两部分组成:COCO-VQA 和 SYNTH-VQA;前者为真实图像,后者为合成卡通图像; + - 其中 SYNTH-VQA 由 50,000 个不同的模拟场景组成,其中包含 100 多个不同的物体、30 个动物模型和 20 个人体模型。 +- VQA Dataset 为为每幅图片提供了三个问题,每个问题有十个答案; +- 数据规模: + - COCO-VQA: 614,163 total, with 248,349 for training, 121,512 for validation, and 244,302 for testing + - SYNTH-VQA: 150,000 QA pairs + +**对比** +- 通过使用合成图像,可以创建一个更加多样化和平衡的数据集。 +- 自然图像数据集往往具有**更一致的上下文**和偏见,例如,街头场景比斑马更有可能有狗的图片。 + +**COCO-VQA 的问题** +- 由于语言上的偏见,许多问题**无需使用图像**就能回答; +
+ + - Q: "What color are the trees?" —— A: "green." + - 在数据集中,这个问题出现了 73 次,其中 70 个的答案都是 "green" +- 存在许多主观问题,没有准确的答案; +
+ + - Q: "Would you like to fly in that?" —— A: "yes"(4), "No"(6) +- 许多问题需要解释或冗长的描述,这类问题难以自动评价; +
+ + - Q: "Why would you say this woman is strong?" —— A: "yes"(5), can lift up on arms, headstand, handstand, can stand on her head, she is standing upside down on stool. + +### FM-IQA +- Freestyle Multilingual Image Question Answering (FM-IQA) +- 最初是中文数据集,现在也提供了英文版本; +- FM-IQA 允许回答是一个句子; +- 这加大了自动评价的难度;作者建议人类评价来判断生成的答案是否由人类提供; + +### Visual Genome +- 同时使用了 YFCC100M 和 COCO 中的图像; +- 数据规模:108,249 images、1.7 million QA pairs; +- 本数据集包含六种 'W' 问题:What, Where, How, When, Who, and Why; +- 包含两种模式: + - **free-form method**:可以针对图像提出任意问题;人类标注者通常会趋向于提出类似的问题。 + - **regionspecific method**:针对图像的指定区域提问; +
+ + - Free form QA: What does the sky look like? + - Region based QA: What color is the horse? +- 答案多样性 + - Visual Genome 中最常见的 1000 个答案仅占数据集中所有答案的 65%,而 COCO-VQA 占82%,DAQUAR COCO-QA 占100% + - Visual Genome 只有 57% 的答案是**单个词**;而 COCO-VQA 中为 88%,COCO-QA 为100%,DAQUAR 为 90%。 + - Visual Genome 中没有是/否问题 + +### Visual7W +- Visual7W 是 Visual Genome 的一个子集 +- 7W 指的是 "What, Where, How, When, Who, Why, Which." +- 两类问题 + - ‘telling’ questions:答案是基于文本的 + - ‘pointing’ questions:以 Which 开头的问题,对于这些问题,算法必须在备选方案中选择正确的边界框。 +
+ + - Q: Which object can you stab food with? + +### SHAPES +- “形状”数据集由图形的不同排列组成,问题涉及形状的属性、关系和位置; +- 这种方法可以快速生成大量数据,同时消除一些其他数据集的偏见; +- 该数据集包括 244 个不同的问题,每个问题都会对所有的 64 幅图提出,且答案都是 "yes/no"。 +- 其中许多问题需要对形状的布局和属性进行位置推理。 +- 如果某个算法不能在 SHAPES 上表现良好,那么说明它只能以有限的方式分析图像。 + + +## VQA 的评价方法 TODO + +- 目前主要的评价方法可以参考如下几篇论文:[30], [32], [40] +- 但这些方法或是针对特定的数据集,或是存在语义偏差,并不完美。 + + + +# 主流模型与方法 + +- **基本流程** + - **提取图像特征**(图像特征化) + - VGGNet [1], ResNet [2], GoogLeNet [58] + - **提取问题特征**(问题特征化) + - LSTM, GRU, skip-thought vectors [61] + - **特征整合,输出答案** + - 目前主要有基于分类和生成两种方法 + +- **基于生成的基本框架** + - Encoder-Decoder 框架 + +- **基于分类的基本框架** + - **基于分类的 VQA 基本框架** +
+ +- 不同做法的差异主要体现在**如何整合图像和文本的特征** +- **整合图像和问题特征的方法** + - 简单机制 + - concatenation、elementwise multiplication、elementwise addition等 + - 相关论文:[36, 38, 32, 33] + - 双线性池化(bilinear pooling) + - 相关论文:[46, 53, 62] + - Attention机制 + - 相关论文:[49, 51, 48, 63] + - 贝叶斯模型:[36, 30] + - 问题分解:[50, 44] + +## 基线模型 + +### 分类模型 +**[38]** +- BoW + GoogLeNet; +- concat + LR + +**[32]** +- LSTM/BoW + VGGNet; +- multiply + MLP + + + +**[31]** +- LSTM + CNN + 概率模型; +- 本文**把图像特征当做 LSTM 的第一和最后一个时间步的输入**,中间则依然使用问题作为输入。 + +### 生成模型 + +**[40]** +- LSTM + GoogleNet; +- 本文将图像特征拼接到问题的每个词之后,共同作为 LSTM 的输入;问题结束后的时间步被用于生成答案。 +
+ +**[33]** +- Seq2Seq 生成模型,本文采用的是 Encoder-Decoder 框架 +- Encoder 部分与 [31] 类似,但是图像特征只作为 LSTM 最后一个时间步的输入 +- 此外,本文使用 LSTM 作为 Decoder 来生成答案。 + +## 贝叶斯模型 + +**[30]** +- 首次将贝叶斯框架用于 VQA; +- 使用**语义分割**来识别图像中的对象及其位置; +- 然后,利用贝叶斯算法对**目标的空间关系**进行建模,计算出每个答案的概率。 +- 本文是较早的 VQA 解决方案,但其有效性不如简单的基线模型;部分原因在于其依赖语义分割的结果 + +**[36]** +- 本文基于这样一个直觉(insight),通过问题可以预测**答案的类型**。 +- 基于此,将开放性问题转化为多选择问题; +- skip-thought vectors + ResNet-152 +
+ +## 基于 Attention 的模型 +> 相关论文: [63, 49, 52, 48, 54, 51, 46, 55] +- 使用全局特征可能会模糊输入空间中与任务相关的区域; +- VQA 中,使用**基于空间的 Attention 机制**来创建**特定区域**的 CNN 特征,而不像基线模型中那样直接使用全局特征。 + > 因为问题一般比较短,所以很少有模型在 VQA 中将注意力纳入文本表征 +- Attention 背后的基本思想是,图像中的某些视觉区域和问题中的某些单词对于回答给定的问题比其他区域或单词更能提供更多的信息。 + > The basic idea is that certain visual regions in an image and certain words in a question are more informative than others for answering a given question. +- 因此,直接使用 CNN 网络的最后一层和 Sentence Embedding 就不太合适。 + +**分割图像区域的基本方法** +- 一般有两种方法对局部区域建模 +- 一种是类似语义分割的方式,生成**边缘框**(Edge Boxes [68]),对每个框生成特征 +- 一种是使用**均匀网格**(**Uniform Grid**)把图像分成若干区域,然后利用 CNN 生成每个网格区域的图像特征,然后计算每个区域与问题中每个词的相关度得到 Attention 权重矩阵。 + > 这个过程实际上跟**卷积**本身很像。 +
+ + + +### 基于 Edge Boxes 的方法 +> [63, 51] + +**[63]** Where to look: Focus regions for visual question answering +- 模型输入:所有 box 区域的 CNN 特征、问题特征、答案 +- 模型对每个候选答案生成一个分数,经过**排序**后得到最终答案 +- 打分部分由一个全连接层完成。 +
+ +**[51]** A focused dynamic attention model for visual question answering(FDA) +- 模型建议只使用与问题相关的 box 区域。 +- 计算相关 box 的方法: + - 首先对每个 box 添加标签: + - 训练时,对象标签和包围框由 COCO 注释提供; + - 测试时,使用 ResNet [2] 分类后得到每个 box 的标签; + - 然后使用 Word2Vec 计算问题中的单词与对象标签之间的相似度。 + - 只要相似度大于 0.5 则认为是相关的。 +- 问题和图像序列分别使用 LSTM 建模,得到特征后送入全连接层分类得到答案。 +
+ + - 在最后一个时间步,模型还输入了图像的全局特征,用于访问全局以及局部特征。 + +### 基于 Uniform Grid 的方法 +> [49, 48, 52, 54, 56] + +#### [49] Stacked Attention Networks for Image Question Answering(SAN) +- 模型提取 VGG19 最后一个 Pooling 层的 feature map 作为区域特征,其大小为 `14*14*512`。 +- 相当于把原始 `448*448` 的图像均匀划分为 `14*14` 个网格(grid),每个网格使用一个 `512` 维的向量表示其特征。 +
+ +- **Attention 层** + - Attention 层的主要作用是计算每个网格与问题的相关度 + - 公式: +
+ + > 其中 `vI: [14*14,512]` 为图像特征,`vQ: [512]` 为问题特征;最后 `pI: [14*14]` 即问题在每个网格处的关注度 + +
Tensorflow 代码(点击展开) + + ```python + # m = 14*14, d = 512, k = + v_I # [N, d, m] + W_I # [k, d] + v_Q # [N, d] + W_Q # [k, d] + b_Q # [d] + W_p # [d] + + ``` + > `m`: grid 的数量,本文中即 14*14
+ > `d`: 特征维度,本文为 512
+ > `k`: 隐藏单元的数量,本文为 1024 + +
+ + - 在得到每个网格与问题的相关度后,对所有网格进行加权求和,从而得到整个图像加权后的全局特征; + - 整合图像特征与问题特征后进行分类(本文采用整合方法为按位求和) + +
+ +**Stacked Attention**(堆叠 Attention) + - 对于复杂的问题,**单一的 Attention 层并不足以定位正确的答案预测区域**。 +
+ + - 本文使用多个 Attention 层迭代上述过程 +
+ + > 本文取 `K=2` + +#### [48] Ask, Attend and Answer: Exploring Question-Guided Spatial Attention for Visual Question Answering +- 本文采用了与 [49] 类似的方法,区别在于本文计算的是**问题中的每个词**在每个网格的关注度 +- 记: + - `V = {v1, v2, .., vT}: [T, d_v]`,其中 v_i 为问题中每个词的词向量,`T` 表示问题的(最大)长度,不足该长度的补充全 0 词向量; + - `S = {s1, s2, .., sL}: [L, d_s]`,其中 s_i 为每个网格的特征向量,`L` 表示网格数; + - 则相关系数矩阵 `C: [T, L]` 为 +
+ +- 基于以上想法,本文提出了两种模型 "One-Hop Model" 和 "Two-Hop Model" + - One-Hop Model 使用整合后的问题特征和加权视觉特征来预测答案; + - Two-Hop Model 中将整合后的问题和加权视觉特征循环回注意力机制中,从而细化注意力分布。(做法与 [49] 类似) + +#### [52] Dynamic memory networks for visual and textual question answering +- DMN 已经被一些论文用于文本问答,本文尝试将 Dynamic Memory Network (DMN) 应用于 VQA; +- DMN 主要由三个模块构成:输入模块、**情景记忆模块**(episodic memory module)、回答模块 + > 问答论文摘要/[Dynamic Memory Networks](./问答-A-摘要.md#2016-icmldynamic-memory-networks) +- **输入模块** + - **文本问答**的输入为一系列**可能**与问题相关的**情景句子**(上下文); + - **视觉问答**的输入为**网格划分**后的图像,每一块网格作为**可能**与问题相关的**情景** + +- **情景记忆模块**用于提取输入中的相关事实;每次迭代时更新内部记忆单元; +- **回答模块**通过整合最终的记忆单元与问题的表示来生成答案(RNN)。 + +#### [54] Hierarchical Question-Image Co-Attention for Visual Question Answering +- 本文引入**层次协同注意模型**(Hierarchical Co-Attention model)来共同推理这两种不同的信息流。 + > Co-Attention 类似于 [48] 中的做法 +- 本文进一步细化了问题,基于词、短语、句子三个层级分别构建 Attention 权重 +- 本文提出了两种 Attenion 机制:parallel co-attention 和 alternative co-attention + - **parallel co-attention** 同时关注问题和图像; + - **alternative co-attention** 同时在关注问题或图像间交替进行; +- 最终的答案通过由低到高依次融合三个层级的特征来预测。 +
+ +#### [56] Dual attention networks for multimodal reasoning and matching +- 本文的主要思想是允许问题于图像互相 Attention,从而直接关注关键词或关键区域。 + > 思想跟 co-attention 类似,但是做法不同。 +- 为了实现这一点,本文先将图像特征和问题特征整合为**记忆向量**(按位乘),然后利用该记忆向量**分别**对问题和图像构建 Attention 向量。 +- 该过程可以递归的进行,下一轮的输入为上一轮得到两个 Attention 向量; +
+ + > 但是作者建议迭代 2 次即可。 + + +## 基于双线性池化的模型 +> [46, 57] + +- VQA 需要对图像和问题的联合分析; +- 除了一些简单的基线方法外,还可以利用**外积**(outer-product)对两者进行更复杂的交互。该方法被称为双线性池化。 +- **双线性池化**已经在图像识别领域取得了一定效果 [71]. + +### [46] Multimodal compact bilinear pooling for visual question answering and visual grounding +- 本文使用了 Multimodal Compact Bilinear pooling(MCB)作为整个图像与问题特征的新方法; +- 如果直接对图像和问题进行外积会导致特征维度不可控,因此 MCB 在一个**低维的空间**下进行外积运算; +- 文本计算 Attention 的做法类似 [[49]](#49-stacked-attention-networks-for-image-question-answeringsan),区别在于使用 **MCB 操作**代替**双线性 Attention** + > 双线性 Attention,即 `Q·W·V`——使用一个权重矩阵 `W` 作为两个向量 `Q` 和 `V` 的交互中介。 +
+ +- 本文模型是 2016 VQA 比赛的获胜模型 + +### [57] Hadamard Product for Low-rank Bilinear Pooling +- 本文认为 MCB 尽管只是使用了近似的外积,但计算代价依然比较高。 +- 本文建议使用 Multi-modal Low-rank Bilinear pooling (MLB) 来近似 MCB; +- 具体来说,即使用 **Hadamard Product** 和**线性映射**来近似外积。 + > Hadamard Product 即按位相乘 +- MLB 可以达到媲美 MCB 的效果,同时降低了计算复杂度和更少的参数。 + + +## 组合模型 +> [44, 50, 55] + +- 一些复杂的问题可能需要多个**推理**步骤; +- Neural Module Network(神经模块网络)使用外部解析器来寻找问题中的子问题 + +### [44] Deep Compositional Question Answering with Neural Module Networks +- 本文提出使用神经模块网络(Neural Module Network, NMN)来解决 VQA 问题 +- NMN 框架将 VQA 视为由**独立的子网络**执行的一系列子任务。 +- 每个子网络执行一个定义良好的任务。 + - 比如 find [X], describe [X], measure [X], transform[X] 等模块。 +- 这些模块必须组装成一个有意义的**布局**; +- 本文使用一个**自然语言解析器**来发现问题中的子问题,同时用于推断子任务需要的布局; + - 比如 "What color is the tie?" 可能会执行 find[tie] 模块和 describe[color] 模块。 + +### [55] Training recurrent answering units with joint loss minimization for VQA +- 本文提出的 Recurrent Answering Units(RAU)模型可以在不依赖外部语言分析器的情况下隐式地执行组合推理。 +- 本文将所有用于回答子问题的模块以 RNN 的方式顺序排列,利用类似门机制的方式来自动选择由哪些模块回答问题。 +
+ + +## 其他模型 TODO + + + +# 参考文献 +- [1] Very deep convolutional networks for large-scale image recognition, ICLR 2015 +- [2] Deep residual learning for image recognition, CVPR 2016. +- [3] You only look once: Unified, real-time object detection, CVPR 2016. +- [4] Faster R-CNN: Towards real-time object detection with region proposal networks, NIPS 2015. +- [5] Long-term recurrent convolutional networks for visual recognition and description, CVPR 2015. +- [11] Deep neural networks for object detection, NIPS 2013. +- [12] Fully convolutional networks for semantic segmentation, CVPR 2015. +- [13] Learning deconvolution network for semantic segmentation, CVPR 2015. +- [14] Instance segmentation of indoor scenes using a coverage loss. ECCV 2014. +- [15] Monocular object instance segmentation and depth ordering with CNNs, CVPR 2015. +- [16] Instance-level segmentation with deep densely connected MRFs, CVPR 2016. +- [17] Deep visual-semantic alignments for generating image descriptions, CVPR 2015. +- [18] Deep captioning with multimodal recurrent neural networks (m-rnn), ICLR 2015. +- [19] Show and tell: A neural image caption generator, CVPR 2015. +- [20] Show, attend and tell: Neural image caption generation with visual attention, ICML 2015. +- [29] Densecap: Fully convolutional localization networks for dense captioning, CVPR 2016. +- [30] A Multi-World Approach to Question Answering about Real-World Scenes based on Uncertain Input, NIPS 2014. +- [31] A multi-world approach to question answering about realworld scenes based on uncertain input, NIPS 2014. +- [32] VQA: Visual question answering, ICCV 2015. +- [33] Are you talking to a machine? Dataset and methods for multilingual image question answering, NIPS 2015. +- [36] Answer-type prediction for visual question answering, CVPR 2016. +- [38] Simple baseline for visual question answering, arXiv 2015. +- [40] Ask your neurons: A neural-based approach to answering questions about images, ICCV 2015. +- [44] Deep compositional question answering with neural module networks, CVPR 2016. +- [46] Multimodal compact bilinear pooling for visual question answering and visual grounding, EMNLP 2016. +- [48] Ask, attend and answer: Exploring question-guided spatial attention for visual question answering, ECCV 2016. +- [49] Stacked attention networks for image question answering, CVPR 2016. +- [50] Learning to Compose Neural Networks for Question Answering, NAACL 2016. +- [51] A focused dynamic attention model for visual question answering, arXiv 2016. +- [52] Dynamic memory networks for visual and textual question answering, ICML 2016. +- [53] Multimodal residual learning for visual qa, NIPS 2016. +- [54] Hierarchical Question-Image Co-Attention for Visual Question Answering, NIPS 2016. +- [55] Training recurrent answering units with joint loss minimization for VQA, arXiv 2016. +- [56] Dual attention networks for multimodal reasoning and matching, CVPR 2017. +- [57] Hadamard Product for Low-rank Bilinear Pooling, ICLR 2017. +- [58] Going deeper with convolutions, CVPR 2015. +- [61] Skip-thought vectors, NIPS 2015. +- [62] Dualnet: Domain-invariant network for visual question answering, IEEE 2017. +- [63] Where to look: Focus regions for visual question answering, CVPR 2016. +- [68] Edge boxes: Locating object proposals from edges, ECCV 2014. +- [71] Bilinear cnn models for fine-grained visual recognition, ICCV 2015. diff --git "a/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/\346\267\261\345\272\246\346\237\245\350\257\242\347\220\206\350\247\243/A-\347\273\274\350\277\260.md" "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/\346\267\261\345\272\246\346\237\245\350\257\242\347\220\206\350\247\243/A-\347\273\274\350\277\260.md" new file mode 100644 index 00000000..8fae178c --- /dev/null +++ "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/\346\267\261\345\272\246\346\237\245\350\257\242\347\220\206\350\247\243/A-\347\273\274\350\277\260.md" @@ -0,0 +1,150 @@ +DQU 综述 +=== + +Index +--- + + +- [Introduction](#introduction) + - [不同时期的搜索](#不同时期的搜索) + - [查询理解系统的特点](#查询理解系统的特点) +- [深度查询理解](#深度查询理解) + - [查询理解模块的作用与处理流程](#查询理解模块的作用与处理流程) +- [各组件的基本方法(2015)](#各组件的基本方法2015) +- [引用文献](#引用文献) + + + +

参考文献

+ +- (Prakash and Patel, 2015) Techniques for Deep Query Understanding + + +## Introduction + +- 在过去的 20 年中,搜索查询的解释、处理以及向用户展示结果的方式一直在发生改变; + - 从单纯基于文本匹配的检索到对查询的语义理解,包括上下文、位置、时间、用户之前的查询; + +- 查询方式的改变:关键词查询(电脑,键盘输入) -> 自然语言查询(手持设备,语音输入) + +**查询理解的应用** + +- 上下文查询 + - 比如“Where can I eat cheese cake right now”,此时搜索引擎需要理解用户的意图、了解用户的位置以及当前的时间才能返回正确的结果 + +- 交互式问答查询 + - 返回准确的答案,比如“who is the president of USA now” + - 当前查询可能与会话中的前一个问题有关 + +
+ + > 要实现上述示例,需要将第一个问题改为“who is the president of USA 2010”;因为目前(2019年)美国总统 Trump 有过三位妻子,所以第四个问题将无法返回正确结果;同理,如果搜索“who is the president of USA 2009”,那么到第二个问题就会出错,因为 2009 年美国总统换届,那年有两位总统。 + + - 此时,查询理解模块必须考虑上下文,并从答案生成模块中获取上一个问题的目标实体,以理解查询。 + +- AI 个人助理 + - 微软 Cortana、苹果 Siri、Google Now + - 他们必须了解哪些查询需要重定向到 Web 搜索引擎,哪些查询用于本地设备操作。 + + +### 不同时期的搜索 + +- 文本匹配 + - 使用 Lucene indexing 等方法构建从关键词到文档的反向索引; + - 通过人工添加的标签过滤返回的结果; + - 大多数购物网站依然在使用; + +
+ +- Rank 排序 + - 底层依然是基本的文本匹配,但是加入了根据相关性排序的概念(Rank) + - 相关性的计算基于一系列指标,如 TF-IDF、关键词相关性(keyword relevance)、网站权重(website authority)、流行度(popularity)、链接强度(link strength)等; + +- 基于 NLP 技术的查询理解 + - 通过一个查询理解模块,利用 NLP 技术,通过上下文信息了解用户以前的搜索和浏览活动,以及用户的兴趣、位置、查询时间、位置天气等,以准确预测用户的意图并获得最佳结果。 + +### 查询理解系统的特点 + +1. 理解查询的语义 + + 语义理解的层次 + +
+ +1. 理解上下文和历史任务 + + 示例:搜索 “michael jordan” 可能会返回篮球运动员或者伯克利大学的机器学习教授,如果用户之前搜索了机器学习相关的关键字,那么用户的意图很可能是后者; + +1. 个性化 + + 不同的用户有不同的兴趣,用户很可能会在他的兴趣领域或工作领域进行搜索。 + + +## 深度查询理解 + +### 查询理解模块的作用与处理流程 + +**作用**: +1. 推断查询的意图; +1. 通过建议,引导用户到达一个明确的意图; +1. 改进查询结果。 + +**处理流程**:(标号不代表处理顺序) + +
+ +- 首先由**查询细化子模块**中的**纠错组件**对原始查询进行处理,通常包括拼写更正、缩略语扩展、分词、合并、短语切分等; +- 修正后的查询被传递给**查询建议子模块**,该子模块用于发现那些可能导致更好结果的查询(下拉列表),并作为新的查询重新输入; +- 新的查询通过**查询拓展**组件找到同义查询,以避免术语不匹配带来的损失(因为搜索引擎的底层依然基于传统的文本匹配);以上过程可能会进行多轮; +- 将扩展后的查询集合传入**查询意图检测子模块**,用于推断查询的确切意图,其中 + - **查询分类**组件用于标记查询所属的类别(用于过滤不相关的查询),通常被定义为运动、天气、旅游或视频等不同类别之一,如 “brazil germany” 可能会被定义为运动(如果搜索了足球)或旅游(搜索了巴西到德国的航班); + - 一个查询可能会被划分为多个片段(Segment),特别是对于句子类型的搜索,不同的片段被分为不同的类别; + - **语义标注**组件对每个查询做语义标记,以进行精确的意图检测。 +- 最后将扩展的意图标记查询集合传递到答案生成模块 + +
+ +- **查询纠错**发现了原始查询的拼写错误,并进行了纠正; +- **查询建议**给出了两个不同领域的可能推荐,这里假设用户选择了 “Michael Jordan berkley” 作为他真正的目标; +- **查询拓展**找到更多的同义查询加入查询集; +- **查询分类**将得到的查询集分类为目标类别(这里是 "academic"); +- **语义标注**对查询中的不同片段进行标记,以获得准确的查询意图。 + + +## 各组件的基本方法(2015) + +**查询纠错**(Query Correction) +- CRF-OR(改进的 CRF) + - (Jiafeng et al., 2008) A Unified and Discriminative Model for Query Refinement + +**查询建议**(Query Suggestion) +- 利用 Click-Through 和 Session 数据进行上下文感知以解决歧义 + - (Huanhuan et al., 2008) Context-Aware Query Suggestion by Mining Click-Through and Session Data + + +**查询扩展**(Query Expansion) +- 路径约束随机游走(path-constrained random walks) + - (Gao et al., 2013) Query Expansion Using Path-Constrained Random Walks + +**查询分类**(Query Classification) +- 决策树 + - (Cao et al., 2009) Context-Aware Query Classification + + +**语义标注**(Semantic Tagging) +- 分层语法特征(Hierarchical Syntactic Features)、语义依赖特征(Semantic Dependency Features) + - (Liu et al., 2013) Query Understanding Enhanced by Hierarchical Parsing Structures + + +## 引用文献 +- [2] Learning to Personalize Query Auto-Completion + - 本文讨论了使用位置特征、年龄和性别等用户特征、区域特征、用户短历史特征和长历史特征进行查询自动完成的问题,并给出了使用该方法进行意图预测的思路。 +- [6] Context-Aware Query Classification + - 本文讨论了上下文的使用和概念序列(概念后缀树)的遍历。 +- [8] Evaluating the Effectiveness of Search Task Trails + - 本文讨论了交错查询的情况:30% 的会话包含多个任务,5% 的会话包含交叉任务。 + +
+ +- [9] Task-Aware Query Recommendation + - 给出了一种新的方法,用于识别交错的任务,并根据所识别的交错任务推荐查询。 \ No newline at end of file diff --git "a/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/\346\267\261\345\272\246\346\237\245\350\257\242\347\220\206\350\247\243/_assets/TIM\346\210\252\345\233\27620190212181144.png" "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/\346\267\261\345\272\246\346\237\245\350\257\242\347\220\206\350\247\243/_assets/TIM\346\210\252\345\233\27620190212181144.png" new file mode 100644 index 00000000..17ed15d1 Binary files /dev/null and "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/\346\267\261\345\272\246\346\237\245\350\257\242\347\220\206\350\247\243/_assets/TIM\346\210\252\345\233\27620190212181144.png" differ diff --git "a/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/\346\267\261\345\272\246\346\237\245\350\257\242\347\220\206\350\247\243/_assets/TIM\346\210\252\345\233\27620190213103134.png" "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/\346\267\261\345\272\246\346\237\245\350\257\242\347\220\206\350\247\243/_assets/TIM\346\210\252\345\233\27620190213103134.png" new file mode 100644 index 00000000..d55cf8ba Binary files /dev/null and "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/\346\267\261\345\272\246\346\237\245\350\257\242\347\220\206\350\247\243/_assets/TIM\346\210\252\345\233\27620190213103134.png" differ diff --git "a/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/\346\267\261\345\272\246\346\237\245\350\257\242\347\220\206\350\247\243/_assets/TIM\346\210\252\345\233\27620190213105813.png" "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/\346\267\261\345\272\246\346\237\245\350\257\242\347\220\206\350\247\243/_assets/TIM\346\210\252\345\233\27620190213105813.png" new file mode 100644 index 00000000..4cd878c0 Binary files /dev/null and "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/\346\267\261\345\272\246\346\237\245\350\257\242\347\220\206\350\247\243/_assets/TIM\346\210\252\345\233\27620190213105813.png" differ diff --git "a/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/\346\267\261\345\272\246\346\237\245\350\257\242\347\220\206\350\247\243/_assets/TIM\346\210\252\345\233\27620190213120835.png" "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/\346\267\261\345\272\246\346\237\245\350\257\242\347\220\206\350\247\243/_assets/TIM\346\210\252\345\233\27620190213120835.png" new file mode 100644 index 00000000..8e5d06e6 Binary files /dev/null and "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/\346\267\261\345\272\246\346\237\245\350\257\242\347\220\206\350\247\243/_assets/TIM\346\210\252\345\233\27620190213120835.png" differ diff --git "a/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/\346\267\261\345\272\246\346\237\245\350\257\242\347\220\206\350\247\243/_assets/TIM\346\210\252\345\233\27620190213144945.png" "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/\346\267\261\345\272\246\346\237\245\350\257\242\347\220\206\350\247\243/_assets/TIM\346\210\252\345\233\27620190213144945.png" new file mode 100644 index 00000000..a2908f3c Binary files /dev/null and "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/\346\267\261\345\272\246\346\237\245\350\257\242\347\220\206\350\247\243/_assets/TIM\346\210\252\345\233\27620190213144945.png" differ diff --git "a/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/\346\267\261\345\272\246\346\237\245\350\257\242\347\220\206\350\247\243/_assets/TIM\346\210\252\345\233\27620190213151159.png" "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/\346\267\261\345\272\246\346\237\245\350\257\242\347\220\206\350\247\243/_assets/TIM\346\210\252\345\233\27620190213151159.png" new file mode 100644 index 00000000..12ba9cfe Binary files /dev/null and "b/B-\350\207\252\347\204\266\350\257\255\350\250\200\345\244\204\347\220\206/\346\267\261\345\272\246\346\237\245\350\257\242\347\220\206\350\247\243/_assets/TIM\346\210\252\345\233\27620190213151159.png" differ diff --git "a/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/B-\344\270\223\351\242\230-\345\237\272\346\234\254\346\250\241\345\236\213.md" "b/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/B-\344\270\223\351\242\230-\345\237\272\346\234\254\346\250\241\345\236\213.md" new file mode 100644 index 00000000..f8018c73 --- /dev/null +++ "b/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/B-\344\270\223\351\242\230-\345\237\272\346\234\254\346\250\241\345\236\213.md" @@ -0,0 +1,44 @@ +专题-基本模型 +=== +> [Applications](https://keras.io/applications/) - Keras Documentation + +- [2015] VGGNet(16/19) [2] +- [2015] GoogleNet [10] +- [2016] Inception-v1/v2/v3 [4] +- [2016] ResNet [3] +- [2017] Xception [1] +- [2017] InceptionResNet-(v1/v2)、Inception-v4 [5] +- [2017] MobileNet [6] +- [2017] DenseNet [7] +- [2017] NASNet [8] +- [2018] MobileNetV2 [9] + +Index +--- + + +- [Inception-v1/v2/v3](#inception-v1v2v3) + - [相关阅读](#相关阅读) +- [Reference](#reference) + + + + +## Inception-v1/v2/v3 + + +### 相关阅读 +- [经典网络GoogLeNet(Inception V3)网络的搭建与实现](https://blog.csdn.net/loveliuzz/article/details/79135583) - CSDN博客 + + +## Reference +- [1] Xception: Deep Learning with Depthwise Separable Convolutions, CVPR 2017. +- [2] Very Deep Convolutional Networks for Large-Scale Image Recognition, ICLR 2015. +- [3] Deep Residual Learning for Image Recognition, CVPR 2016. +- [4] Rethinking the Inception Architecture for Computer Vision, CVPR 2016. +- [5] Inception-v4, Inception-ResNet and the Impact of Residual Connections on Learning, AAAI 2017. +- [6] MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications, arXiv 2017. +- [7] Densely Connected Convolutional Networks, CVPR 2017. +- [8] Learning Transferable Architectures for Scalable Image Recognition, arXiv 2017. +- [9] MobileNetV2: Inverted Residuals and Linear Bottlenecks, CVPR 2018. +- [10] \ No newline at end of file diff --git "a/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2015].VGGNet.(ICLR).pdf" "b/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2015].VGGNet.(ICLR).pdf" new file mode 100644 index 00000000..3258b740 Binary files /dev/null and "b/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2015].VGGNet.(ICLR).pdf" differ diff --git "a/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2016].Inception-(v1\343\200\201v2\343\200\201v3).(CVPR).pdf" "b/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2016].Inception-(v1\343\200\201v2\343\200\201v3).(CVPR).pdf" new file mode 100644 index 00000000..bcea9a4d Binary files /dev/null and "b/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2016].Inception-(v1\343\200\201v2\343\200\201v3).(CVPR).pdf" differ diff --git "a/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2016].ResNet.(CVPR).pdf" "b/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2016].ResNet.(CVPR).pdf" new file mode 100644 index 00000000..f1fb765b Binary files /dev/null and "b/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2016].ResNet.(CVPR).pdf" differ diff --git "a/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2016].ResNet.(arXiv).pdf" "b/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2016].ResNet.(arXiv).pdf" new file mode 100644 index 00000000..4b299338 Binary files /dev/null and "b/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2016].ResNet.(arXiv).pdf" differ diff --git "a/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2017].DenseNet.(CVPR Best Paper).pdf" "b/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2017].DenseNet.(CVPR Best Paper).pdf" new file mode 100644 index 00000000..20ae6afb Binary files /dev/null and "b/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2017].DenseNet.(CVPR Best Paper).pdf" differ diff --git "a/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2017].InceptionResNet-(v1\343\200\201v2\343\200\201Inception-v4).(AAAI).pdf" "b/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2017].InceptionResNet-(v1\343\200\201v2\343\200\201Inception-v4).(AAAI).pdf" new file mode 100644 index 00000000..96184a19 Binary files /dev/null and "b/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2017].InceptionResNet-(v1\343\200\201v2\343\200\201Inception-v4).(AAAI).pdf" differ diff --git "a/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2017].MobileNet.(arVix).pdf" "b/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2017].MobileNet.(arVix).pdf" new file mode 100644 index 00000000..4e0f23fd Binary files /dev/null and "b/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2017].MobileNet.(arVix).pdf" differ diff --git "a/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2017].NASNet.(arVix).pdf" "b/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2017].NASNet.(arVix).pdf" new file mode 100644 index 00000000..72bb3db1 Binary files /dev/null and "b/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2017].NASNet.(arVix).pdf" differ diff --git "a/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2017].Xception.(CVPR).pdf" "b/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2017].Xception.(CVPR).pdf" new file mode 100644 index 00000000..ed777700 Binary files /dev/null and "b/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2017].Xception.(CVPR).pdf" differ diff --git "a/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2018].MobileNetV2.(CVPR).pdf" "b/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2018].MobileNetV2.(CVPR).pdf" new file mode 100644 index 00000000..6063b280 Binary files /dev/null and "b/B-\350\256\241\347\256\227\346\234\272\350\247\206\350\247\211/pdf/\345\237\272\346\234\254\346\250\241\345\236\213/[2018].MobileNetV2.(CVPR).pdf" differ diff --git "a/C-\346\225\260\345\255\246/A-\346\246\202\347\216\207\350\256\272.md" "b/C-\346\225\260\345\255\246/A-\346\246\202\347\216\207\350\256\272.md" new file mode 100644 index 00000000..fd5bcef7 --- /dev/null +++ "b/C-\346\225\260\345\255\246/A-\346\246\202\347\216\207\350\256\272.md" @@ -0,0 +1,102 @@ +概率论 +=== +- 面试时如果被问了一个概率题,那就要小心了,面试官可能要刷人了! +- 面试概率题的特点: + - 题面不复杂 + - 短时间可以解答 + - 会者不难 + +RoadMap +--- +- [概率论基础](#概率论基础) TODO +- [常见面试题](#常见面试题) + +Index +--- + + +- [概率论基础](#概率论基础) + - [古典概型](#古典概型) + - [几何概型](#几何概型) +- [例题](#例题) + - [例题-古典概型](#例题-古典概型) + - [54 张牌,平均分成 6 份,...](#54-张牌平均分成-6-份) + - [例题-几何概型](#例题-几何概型) + - [一根棍子折成三段,求能组成 ...](#一根棍子折成三段求能组成-) + + + +# 概率论基础 + +## 古典概型 +- 在一个有限的集合 S 中随机抽取一个元素,求该元素属于子集 T 的概率; +- 概率 `p = 子集 T 中元素的数量 / 集合 S 中元素的数量` +- 示例: + ``` + 一枚均匀的骰子掷到 1 的概率: + S = {1,2,3,4,5,6}; + T = {1}; + p = |T|/|S| = 1/6 + 一枚均匀的骰子掷到 1 的概率: + S = {1,2,3,4,5,6}; + T = {1,3,5}; + p = |T|/|S| = 3/6 = 1/2 + ``` +- [例题-古典概型](#例题-古典概型) + +## 几何概型 +- 在一个集合形状 S 中随机选取一点,求该点属于子形状 T 的概率; +- 概率 `p = T 的面积 / S 的面积` +- 示例: + ``` + 在边长为 2 的正方形内随机选取一点,求该点属于其内切圆的概率: + S = 4 + T = π + p = T/S = π/4 + ``` +- [例题-几何概型](#例题-几何概型) + +# 例题 + +## 例题-古典概型 + +### 54 张牌,平均分成 6 份,... + +**问题描述** +``` +54 张牌,平均分成 6 份,求大小王在一起的概率? +``` + +**朴素方法** +- 将 54 张牌放入 1-54 的方法数:`a = 54!` +- 每份 9 张牌,大小王在一起的方法数:`b = 6 * 9 * 8 * 52!` + - 大小王同在一堆的概率:`9 * 8 * 52!` + - 共 6 堆 +- 概率 `p = b/a = 8/53` + +**简单方法** +- 无论大王在哪个位置,此时小王与大王在一起的位置有 8 个,共 53 个位置可选 +- 概率 `p = 8/53` + + +## 例题-几何概型 + +### 一根棍子折成三段,求能组成 ... + +**问题描述** +``` +一根棍子折成三段,求能组成三角形的概率? +``` + +**思路** +- 设棍子长度为 1,断点在 `x, y`,其中 `x, y` 服从 `[0,1]` 上的均匀分布,即 `(x, y)` 为单位正方形内随机一点; + +
+ +- 构成三角形的条件为每一段的长度都小于 `1/2`; + - `x < y` 时,即 `x < 1/2 && y - x < 1/2` + - `x > y` 时,即 `y < 1/2 && x - y < 1/2` + +
+ +- 概率 `p = (1/8 * 2) / 1 = 1/4` \ No newline at end of file diff --git "a/C-\346\225\260\345\255\246/B-\345\276\256\347\247\257\345\210\206\347\232\204\346\234\254\350\264\250.md" "b/C-\346\225\260\345\255\246/B-\345\276\256\347\247\257\345\210\206\347\232\204\346\234\254\350\264\250.md" new file mode 100644 index 00000000..d14c0e13 --- /dev/null +++ "b/C-\346\225\260\345\255\246/B-\345\276\256\347\247\257\345\210\206\347\232\204\346\234\254\350\264\250.md" @@ -0,0 +1,723 @@ +**说明** +--- +- 整理自“[3Blue1Brown - 微积分的本质系列视频](https://www.bilibili.com/video/av24325548)” +- 本系列的视频**目的**在于帮助你建立关于**微积分的基本直觉** + +**目录** +--- + + +- [微积分(Calculus)引言](#微积分calculus引言) + - [推导圆的面积 - 积分的直观理解](#推导圆的面积---积分的直观理解) + - [积分与导数](#积分与导数) +- [导数(Derivative)的意义](#导数derivative的意义) + - [“瞬时变化率”引起的歧义——导数的悖论](#瞬时变化率引起的歧义导数的悖论) + - [导数的定义与计算](#导数的定义与计算) +- [用几何来求导](#用几何来求导) +- [直观理解链式法则](#直观理解链式法则) + - [加法法则](#加法法则) + - [乘法法则](#乘法法则) + - [复合函数的求导法则——链式法则](#复合函数的求导法则链式法则) +- [指数函数的导数——自然常数 e 的定义](#指数函数的导数自然常数-e-的定义) +- [隐函数求导](#隐函数求导) +- [极限](#极限) + - [导数的正式定义](#导数的正式定义) + - [极限的 `(ϵ, δ)` 定义](#极限的-ϵ-δ-定义) + - [利用导数来求极限——洛必达法则](#利用导数来求极限洛必达法则) +- [积分与微积分基本定理](#积分与微积分基本定理) + - [积分与导数是一组互逆的运算](#积分与导数是一组互逆的运算) + - [微积分基本定理](#微积分基本定理) +- [连续变量的平均值](#连续变量的平均值) + - [适用“积分”的场景](#适用积分的场景) +- [泰勒级数](#泰勒级数) + - [高阶导数](#高阶导数) + - [泰勒多项式与泰勒级数](#泰勒多项式与泰勒级数) + + + +# 微积分(Calculus)引言 + +**微积分回忆** +- 求导公式 +- 乘积法则 +- 链式法则 +- 隐函数求导 +- 积分、微分的互逆关系 +- 泰勒级数 +- ... + +**微积分的三个中心思想:** +1. 积分 +1. 微分 +1. 积分与微分(导数)的互逆 + +**(几位)微积分之父** +- 发现微积分:巴罗(Barrow)、牛顿(Newton)、莱布尼茨(Leibniz) +- 给出严格定义:柯西(Cauchy)、魏尔施特拉斯(Weierstrass) + +本系列视频中,**微分的概念全部包含于导数之中**,所有导数都以 `df/dx` 的形式出现,也就是**微商**。 + +## 推导圆的面积 - 积分的直观理解 + +圆的面积公式: + +[![](../_assets/公式_20180625144700.png)](http://www.codecogs.com/eqnedit.php?latex=Aera=\pi&space;R^2) + +**如何从积分的角度推导出圆的面积公式?** + +不同的划分方法会带来不同的积分公式,下面考虑将圆划分为大量的同心圆环,这种方法保留了圆的对称性。 + +![](../_assets/TIM截图20180625150305.png) + +考虑其中一个环的面积,可以将其看做一个“类矩形” + +![](../_assets/TIM截图20180625150437.png) + +虽然这不是标准的矩形,但只要`dr`越小,它就越接近。它的面积可表示为: + +[![](../_assets/公式_20180625205349.png)](http://www.codecogs.com/eqnedit.php?latex=area=2\pi&space;r\,dr) + +于是,圆的面积可以看作是这一系列矩形面积的叠加。 + +![](../_assets/TIM截图20180625151928.png) + +这部分面积的求和可以等价于求“**函数`y = 2πr`图像在区间`[0, R]`下的面积**”。 + +这个推导的过程其实可以看作是对函数`y = 2πr`在`[0, R]`下的积分。 + +## 积分与导数 + +直观来说,对函数`f(x)`在`[a, b]`上**积分**就是求函数`f(x)`在区间`[a,b]`下的图像与坐标轴包围的面积。记作: + +[![](../_assets/公式_20180625205043.png)](http://www.codecogs.com/eqnedit.php?latex=\int_{a}^{b}f(x)dx) +> 这实际上是**定积分**的概念,此外还有不定积分。 + +如果是其他图像,比如抛物线,该怎么求这部分的面积呢? + +能不能找到一个函数 `A(x)` 表示 `0` 到 `x` 之间函数图像下的面积?——这个函数 `A(x)` 就是该函数的**积分**(函数)。 +> 这里强调 `0` 到 `x` 之间,是为了使问题具有实际意义 + +以抛物线 `f(x)=x^2` 为例。类似的,我们可以将这块区域划分成一系列细长的矩形。 + +![](../_assets/TIM截图20180625160257.png) + +将 `x` 增加 `dx`,增加的面积可以看做是一个长`f(x)`、宽`dx`的矩形,只要`dx`越小,这条窄带就越接近矩形。 + +![](../_assets/TIM截图20180625162938.png) + +把这部分面积记作 `dA`,表示面积的微小变化(difference in Area) + +通过这个矩形,可以得到 `A`、`f(x)` 与 `dx` 之间的关系: + +[![](../_assets/公式_20180625202144.png)](http://www.codecogs.com/eqnedit.php?latex={\displaystyle&space;{\begin{aligned}&space;&&space;dA&space;\approx&space;f(x)dx&space;\\&space;\Rightarrow&space;&&space;\frac{dA}{dx}\approx&space;f(x)&space;\\&space;\Rightarrow&space;&&space;\frac{A(x+dx)-A(x)}{dx}&space;\approx&space;f(x)&space;\end{aligned}}}) + +这里引出了微积分中另一个重要的概念——**导数**。`dA/dx` 就是 "A 的导数" +> 更严格的说法是:"A 的导数"是“当 `dx → 0` 时,`dA/dx` 所趋向的值”。(下一节会讨论导数的定义) +> +> 一般不会刻意区分**导数**和**导数函数**的区别,都统称为**导数**,具体含义视语境而定;同样,积分也是如此。 + +导数是解决积分问题的关键——积分需要还原出某个导数原本的函数——如果你能熟练的计算导数,那么你也能解决这个问题。 + +积分与导数之间的这种互相转化的关系,也就是“某个图像下方面积函数的导数能够还原出定义这个图像的函数”,就叫做**微积分基本定理**。该定理表明,“在某种意义上”,两者互为逆运算。 + + +# 导数(Derivative)的意义 + +## “瞬时变化率”引起的歧义——导数的悖论 + +“瞬时变化率”的歧义——只有在不同的时间点之间,变化才能发生;而将时间限制在某个瞬间点的时候,变化也就不存在了。 + +考虑这个示例:一辆车从 A 点起,先加速,再减速,至 100 米外的 B 点停下,整个过程花费 10 秒。 + +![](../_assets/TIM截图20180626114634.png) + +把车速的图像加入其中,可以发现:两者存在着某种联系:其中之一改变的话,也会引起另一个发生变化。 + +![](../_assets/TIM截图20180626111415.png) +![](../_assets/TIM截图20180626112123.png) + + + +**速度的大小是如何随着距离-时间函数的变化而变化的?——“瞬时”速度的矛盾**: +- 在绘制速度图像的时候,需要给每个单独的时间点关联一个速度值,但是计算速度却需要两个时间点上的距离。 +- 记时间差为 `dt`,距离差为 `ds`,那么这段时间内的速度就能用 `ds/dt` 表示 + + ![](../_assets/TIM截图20180626111112.png) + > 当年的微积分创始人们也经历了同样的思维冲突。 + +- 换言之,速度只有在一段会时间内的才有意义;“瞬时”的说法会带来矛盾 +- 实际的做法是:会选取一个很小的 `dt` 值,然后把 `ds/dt` 看做是这个瞬间的速度。 + +## 导数的定义与计算 + +**导数的定义** +- 在纯数学领域,**导数**不是 `dt` 为某个具体值时 `ds/dt` 的值 ,而是当 `dt` 的值**无限逼近 0 时**这个比值的**极限**。 + + [![](../_assets/公式_20180627210310.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{ds(t)}{dt}=\underbrace{\frac{s(t+dt)-s(t)}{dt}}_{dt\rightarrow&space;0}&space;\end{aligned}) + > 严格的定义在[[7. 极限](#7-极限)]一章给出 + +- 从图像的角度,(某一点的)导数有一个直观的含义:就是经过图像上该点切线的斜率。 +- 注意:这里的 `dt` 不是“无穷小”,也不是 0;它永远是一个有限小的量,接近 0 而不是 0。 + > 这种说法在试图规避“瞬时”带来的矛盾,使“某个时间点的变化率”有意义。 + +**导数的计算** +- 抛开求导公式,先来看一下面对一个实际的问题,该如何求解(在某一点处的)导数。 +- 对于 `s(t)=t^3` 在 `t=2` 处的导数,根据导数的定义,有 + + [![](../_assets/公式_20180626200536.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{ds(t)}{dt}&space;&=\frac{s(t+dt)-s(t)}{dt}\\&space;\Rightarrow&space;\frac{d(t^3)}{dt}&space;&=\frac{(t+dt)^3-t^3}{dt}\\&space;&=\frac{t^3+3t^2(dt)+st(dt)^2+(dt)^3-t^3}{dt}\\&space;&=3t^2+3t(dt)+(dt)^2&space;\end{aligned}) + +- 当 `dt` 趋向 0 时,后两项也会趋于 0,进而消去。代入 `t=2` 可以得到在该点处的导数为 12 +- 更一般的,称 `s'(t) = 3*t^2` 为 `s(t) = t^3` 的**导函数**。 + +> 对于常见的函数,有一系列总结出的**求导公式**可以快速计算(下一节会演示如何从几何的角度来推导这些公式) + +**导数的含义是“变化率的最佳近似”** +--- +- 回顾之前关于距离-速度的示例,思考这个问题:当 `t=0` 时,车在不在移动? +- 一方面,利用导函数公式可以得到 `t=0` 时的速度为 0——这似乎在说“车没有移动”;另一方面,如果车在 0 时刻没有移动,那么它是何时开始移动的?——关键在于这个问题本身就是没有意义的。 +- 因为**导数**并不是用来测量“瞬时变化”的。 +- `t=0`点的导数为 0 的真正含义是指“在第 0 秒附近,车速的**最佳近似**为 0 米/秒”——换句话说,就是当时间间隔 `dt` 越来越小时,表示速度的比值 `ds/dt` 就越趋向于 0——这并不表示车在 0 时刻就是静止的,只能说它此时的速度近似于 0. + + +# 用几何来求导 + +为什么导数很重要?——当需要使用微积分来解决现实中的实际问题时,需要将其抽象成各种代表性的函数来描述;而如果能掌握这些抽象函数的变化率,那你就学会了这门可以精准描述事物变化率的语言。 + +**从几何的角度看“微小变化量”** +--- +以 `f(x) = x^2` 为例: +- 从坐标轴上看,`x^2` 的图像是一条抛物线,我们已经知道,导数可以描述为切线的斜率 +- 此外,`x^2` 还有另一个更直接的含义:长为 `x` 的正方形的面积。 +- 假如给边长 `x` 一个微小的增量 `dx`,那么正方形的增量(变化量)是多少? + + ![](../_assets/TIM截图20180626211449.png) + +- 应该时刻记住的 `dx` 是一个微小的量——这意味着你可以**忽略所有次数高于 1 的 `dx` 项**——换言之,一个微小量的平方(或更高次方)是一个可以忽略的变化量 +- 由此,可以得到: + + [![](../_assets/公式_20180626212536.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;&df=2xdx\\&space;\Rightarrow&space;&&space;\frac{df}{dx}=2x&space;\end{aligned}) + +- 上一节给出了 `f(x) = x^3` 导数的代数推导过程,这里也可以作为立方体体积来用几何的方式推导。 + + ![](../_assets/TIM截图20180626213324.png) + +**负的变化量** +- 上述两个例子的变化量都是增量(正值),但实际上**变化量也可能是负值**,比如 `f(x) = 1/x`——考虑这样一个特殊的矩形,长 `x`,宽 `1/x`,它的面积恒为 1. + + ![](../_assets/TIM截图20180626221601.png) + > 注意:这里的变化量不再是矩形的面积了,而是**矩形的高** + +- 通过简单的几何知识,可知矩形**高**的变化量为: + + [![](../_assets/公式_20180626223957.png)](http://www.codecogs.com/eqnedit.php?latex=d(\frac{1}{x})=-(\frac{1}{x}-\frac{1}{x+dx})) + +- 从而得到 `1/x` 的导数为: + + [![](../_assets/公式_20180626224325.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;&&space;d(\frac{1}{x})=\frac{-dx}{x^2+xdx}&space;\\&space;\Rightarrow&space;&&space;\frac{d(\frac{1}{x})}{dx}=\frac{-1}{x^2+xdx}=-\frac{1}{x^2}&space;\end{aligned}) + +**幂函数的导数** +- 以上 `x^2`、`x^3` 和 `x^-1` 都遵循了幂函数的求导公式: + + [![](../_assets/公式_20180626213915.png)](http://www.codecogs.com/eqnedit.php?latex=\frac{d(x^n)}{dx}=nx^{n-1}) + +- 思考一下为什么这个公式也适用于 2 和 3 以外的指数——求导的一个关键点在于很大一部分项因为包含 `dx` 的高次幂,可以被忽略——因此有 + + ![](../_assets/TIM截图20180626214855.png) + +- 当指数小于 0 时,会更复杂一些——比如 `x^-2` 可以考虑一个边长为 `√x` 的正方形。 + +大多数时候,我们都有合适的求导公式来使用,但是以上利用几何求导的过程能锻炼我们借助**微小变化量**来考虑的导数的能力。 + +**三角函数的导数** +- 正弦函数的定义 + + ![](../_assets/TIM截图20180627100028.png) + ![](../_assets/TIM截图20180627103330.png) + - `θ=0.8` 表示单位圆弧长为 `0.8` 时对应的角度为 `θ`(单位圆的周长为 `2π`) + - `sin(θ)` 表示此时该点距离 x 轴的高度(可能为负) + - 当 `θ` 增加时,`sin(θ)` 的值会在 -1 到 1 之间上下摆动 + +- `sin(θ)` 当 `θ` 增加时的微小变化量 + + ![](../_assets/TIM截图20180627104200.png) + - 在近似的观点下,应该有这样的直觉——可以把那一段微小的圆弧`dθ`看作是直线 + +- 根据三角函数的定义,有 + + [![](../_assets/公式_20180627104751.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;&d(\sin\theta)=\cos\theta\,d\theta\\\Rightarrow&space;&\frac{d(\sin\theta)}{d\theta}=\cos\theta&space;\end{aligned}) + + +# 直观理解链式法则 + +**求导公式概览(3)**: +- 函数的和/差——加法法则 +- 函数的积/商——乘法法则 +- 复合函数的求导法则 + +## 加法法则 + +[![](../_assets/公式_20180627113753.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;&d(g(x)+h(x))=\frac{dg}{dx}+\frac{dh}{dx}\\&space;\end{aligned}) + +![](../_assets/TIM截图20180627111545.png) +> 加法法则比较简单,可以在坐标轴中展示 + +## 乘法法则 + +“左乘**右导** + 右乘**左导**”: + +[![](../_assets/公式_20180627154921.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{d}{dx}(g(x)h(x))=h(x)\frac{d\,g(x)}{dx}+g(x)\frac{d\,h(x)}{dx}&space;\end{aligned}) + +![](../_assets/TIM截图20180627114343.png) +> 在数学中,如果你要处理两项的乘积,用**面积**来理解会更方便 + +## 复合函数的求导法则——链式法则 + +**链式法则**: + +[![](../_assets/公式_20180627160852.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{d}{dx}g(h(x))=\frac{dg(h(x))}{dh(x)}\frac{dh(x)}{dx}=\frac{dg}{dh}\frac{dh}{dx}&space;\end{aligned}) + +![](../_assets/TIM截图20180627155759.png) +![](../_assets/TIM截图20180627155845.png) + +![](../_assets/TIM截图20180627155928.png) +![](../_assets/TIM截图20180627160125.png) +> 链式法则的表达式在说:看看输出值 g 的微小变化除以 h 的微小变化是多少(h 是要带入函数 g 中的值);然后乘以 h 微小变化与 x 微小变化的比值 + +![](../_assets/TIM截图20180627161532.png) +> 这些 `dh` 最终会被消去,结果就是输出值 g 的微小变化与输入值 x 的微小变化的比值 +> +> `dh` 的消去并不只是符号上的技巧,而是真实反映出在求导时,各微小变化量发生了什么(?) + +**了解这些求导法则 != 灵活的使用它们** + + +# 指数函数的导数——自然常数 e 的定义 + +**从`2^t`开始**: +- 根据导数的定义,有 + + [![](../_assets/公式_20180627205326.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{d2^t}{dt}&space;&=\frac{2^{t+dt}-2^t}{dt}\\&space;&=\frac{2^t2^{dt}-2^t}{dt}\\&space;&=2^t\cdot\frac{2^{dt}-1}{dt}\\&space;&=2^t\cdot&space;h&space;\end{aligned}) + +- 当 `dt → 0` 时,`h` 趋向于一个常数 `0.6931...` +- 也就是说,`2^t` 图像上各点处**切线的斜率 = 该点的函数值 * 一个常数** + + ![](../_assets/TIM截图20180627210731.png) + > 原视频对 `2^t` 赋予了一些实际意义(质量)来说明其**变化量与自身成比例** + +- 类似的,`3^t` 也存在这样一个 `h=1.0986...`;`8^t` 则是 `h=2.0794...` +- 注意到,`8^t` 的 `h` 大概是 `2^t` 的三倍,而 `8^t = 2^{3t}` + +**是否存在某个数,使 `h` 恰为 `1`?——这个数就是自然常数 `e`** +- 不要问为什么当 `e^t` 时,`h=1`;因为 `e` 就是如此定义的。 + + ![](../_assets/TIM截图20180627212338.png) + + > 和“为什么 π 正好等于圆的周长比直径”一样,π 就是这么定义的。 + > + > 自然常数不是完全这样发现的,但过程类似 + +**指数函数的导数** +- 有了自然常数 `e` 以及链式法则,就可以求出其他指数函数的导数了 +- 根据指数函数的性质与链式法则,有 + + [![](../_assets/公式_20180627213245.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{d(c^t)}{dt}&space;&=\frac{d(e^{\ln(c)t})}{dt}\\&space;&=\ln(c)e^{\ln(c)t}=\ln(c)c^t&space;\end{aligned}) + +- 事实上,在微积分中,指数函数基本都是以 `e^ct` 的形式出现的,很少会直接使用 `c^t` 的形式 + +**为什么称 `e` 为自然常数?** +- 现实世界中,很多自然现象里的**变化率**与**变化量**是成正比的 +- 比如:在室温环境下,热水变凉的速率与水和房间的温差成正比(或者说,温差的变化率与温差本身成正比) + + ![](../_assets/TIM截图20180627213951.png) + +- 尽管指数函数有多种写法,但是将其表达为以 `e` 为底的幂函数是非常自然的。 +- 因为 `e` 有着非常自然的含义,它就是变化率与数量本身的比例系数。(?) + + +# 隐函数求导 + +**什么是隐函数?** +- 如果方程 `F(x,y)=0` 能确定 y 是 x 的函数,那么称这种方式表示的函数是隐函数; +- 直观来说,就是满足某种关于变量 x 和 y 的关系的、所有 `(x, y)` 点的集合,相应的曲线就是“隐函数曲线”。 + +**示例 1:圆上某一点切线的斜率** +--- +- 圆的方程就是一个隐函数,比如 `x^2 + y^2 = 5^2`,下面需要求圆上一点的斜率(不考虑几何方法) + + ![](../_assets/TIM截图20180628104522.png) + +- 因为这里的曲线并不是一个函数图像,所以不能单纯对其求导——它不存在变量 x 的微小变化对函数值 y 的微小变化 +- 当然,如果我们的目标只是求 `dy/dx`,那么对等式两边同时求导可以解决: + + [![](../_assets/公式_20180628110309.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;&d(x^2+y^2)=d(5^2)\\\Rightarrow&space;&2xdx+2ydy=0\\\Rightarrow&space;&\frac{dy}{dx}=\frac{-x}{y}&space;\end{aligned}) + +- **为什么这么做?** + +**示例 2:相关变化率** +--- +- 这是一个更实际的例子:开始时,梯子上端距地面 4m(`y=4`),下端距墙 3m(`x=3`)。如果梯子的上端以 `1 m/s` 的速度下滑,那么下端左滑的速度是多少? + + ![](../_assets/TIM截图20180628110920.png) + +- 等式的左边可以看做是一个关于时间 `t` 的函数,于是对**表达式左边**求导的含义相当于在问“经过短暂的时间 `dt`,`y` 会减少一点,`x` 会增加一点,那么整体的变化量是多少?”,而**表达式右边**告诉我们这个变化量为 `0`。 +- 带入 `x(t)=3, y(t)=4, dy/dt=1`,就能求出 `dx/dt` 了。 + +**隐函数求导的含义** +- 从纯数学的角度看,示例 1 与示例 2 的做法没有区别。但是示例 2 求导时带有明确的物理意义——表达式随时间的变化率。但是求圆切线时似乎难以解释为什么这么做。 +- 其实**隐函数的导数也是一个隐函数**,它对 `dx` 和 `dy` 的关系做了限制——在圆的例子上,它要求整体变化量 `dS = 2xdx + 2ydy` 为 0;也就是说为了让 `S = x^2 + y^2` 始终保持在 25,那么 `dS` 就需要为 0. + > 严格来讲,这个条件实际上是保证每一步落在过该点的切线上,而不是落在圆本身。当 `dx` 和 `dy` 足够小时,这两者才没有区别。 + > + > 在隐函数的导数中,原来的变量看做常数,比如这里 `2xdx + 2ydy = 0` 中的 `x` 和 `y` + + ![](../_assets/TIM截图20180628120153.png) + ![](../_assets/TIM截图20180628120634.png) + > 显然,(左)的正体变化量就不满足使 S 保持不变 + +**示例 3:`sin(x)y^2 = x`** +--- +- 该函数的图像是一系列 U 型曲线 + + ![](../_assets/TIM截图20180628121250.png) + > 有了这个等式,就能方便求出 `dy/dx` 了。 + +- 对表达式的两边求导,就可以得到导数的隐函数,它对 `x` 和 `y` 的整体微小变化量作出了限制。 + +**示例 4:`ln(x)`的导数——从已有的导函数推算出其他函数的导函数** +- 根据指数函数与对数函数的关系,有 + + [![](../_assets/公式_20180628141926.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;&y=\ln(x)\\\Rightarrow&space;&e^y=x\\\Rightarrow&space;&e^ydy=dx\\\Rightarrow&space;&\frac{dy}{dx}=\frac{1}{e^y}=\frac{1}{e^{\ln(x)}}=\frac{1}{x}\\\Rightarrow&space;&\frac{d\ln(x)}{dx}=\frac{1}{x}&space;\end{aligned}) + +**多元微积分** +- 隐函数求导是**多元微积分**的入门。两者的要点是一样的,需要理解这多个变量是如何联系在一起变化的。 + + +# 极限 + +简单来说,“极限”就是逼近/趋近更“洋气”的说法。 + +## 导数的正式定义 +- 在[[2.2. 导数的定义与计算](#22-导数的定义与计算)]已经给出了导数的计算公式: + + [![](../_assets/公式_20180628143525.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{df(x)}{dx}=\underbrace{\frac{f(x+dx)-f(x)}{dx}}_{dx\rightarrow&space;0}&space;\end{aligned}) + + 导数的正式定义: + + [![](../_assets/公式_20180628144611.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{df(x)}{dx}=\lim_{\Delta&space;x&space;\to&space;0}\frac{f(x+\Delta&space;x)-f(x)}{\Delta&space;x}&space;\end{aligned}) + + 或 + + [![](../_assets/公式_20180628144731.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{df(x)}{dx}=\lim_{h&space;\to&space;0}\frac{f(x+h)-f(x)}{h}&space;\end{aligned}) + + > 为什么不用 `dx`?——`dx` 本身已经表达了求极限的含义——使用 `dx` 的表示容易将其理解成“无穷小的变化量”;**更正确的**解读应该是把它看做一个具体的、有限小的变化量,并时刻考虑 `dx → 0` 时的情况([3. 用几何来求导](#3-用几何来求导))。 + > + > 为什么用一个新的变量 `h`?——明确变化量 `h` 只是一个普通的数,跟“无穷小”没有任何关系。 + +## 极限的 `(ϵ, δ)` 定义 + +所谓极限,指的是变量逼近 0 时的影响,而非无穷小变量的影响 + +导数由极限定义,而极限本身由 `(ϵ, δ)` 定义,下面是比较书面的说法: + +> 设函数`f(x)`在点`x0`的某一**去心邻域**内有定义,如果存在常数`a`,对于任意给定的正数`ϵ`,都`∃δ>0`,使不等式`|f(x)-a| < ϵ`在`|x-x0| ∈ (0,δ)`恒成立,那么`a`就叫做函数`f(x)`当`x→x0`时的极限,记作 +> +> [![](../_assets/公式_20180628200345.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\lim_{x&space;\to&space;x_0}f(x)=a&space;\end{aligned}) + +**极限存在与不存在的两个例子** + +![](../_assets/TIM截图20180628201027.png) +![](../_assets/TIM截图20180628201344.png) +> 极限存在的前提是,你总能在距离极限点`x0`距离为`δ(>0)`的取值范围内,找到一系列取值点,使得范围内的任一取值点,它的函数值都在距离`f(x0)`为`ϵ(>0)`的范围内——关键在于这种情况对于任意`ϵ`都成立——无论`ϵ`多小,你总能找到与之对应的`δ` + +## 利用导数来求极限——洛必达法则 + +如果函数在点`x0`处是有定义的,那么该点的极限值 == 函数值本身。 + +如果函数在该点没有定义呢?比如 `sin(πx)/(x^2-1)`,该函数在`x0=±1`处就没有定义。 + +![](../_assets/TIM截图20180628203925.png) + +**洛必达法则** +- 洛必达法则专门用于求解 `0/0` 型和 `∞/∞` 的极限值。 +- 导数是由极限定义的,但反过来,也能利用导数来求极限 +- 洛必达法则利用了这样一个性质——当`f(a)=0`时,在`a`的附近,有`f(a+dx)=f'(a+dx)`。如图所示: + + ![](../_assets/TIM截图20180628211047.png) + > 当 `dx` 越小,这个比值就越精确 + +- 洛必达法则: + + [![](../_assets/公式_20180628210652.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\lim_{x&space;\to&space;a}\frac{f(x)}{g(x)}=\lim_{x&space;\to&space;a}\frac{f%27(x)}{g%27(x)}=A&space;\end{aligned}) + > 条件:1)`f(x)`和`g(x)`在点`a`处的极限都为 0;2)`f(x)`和`g(x)`在点`a`的某去心领域内可导,且`g'(x)!=0`;3)`A`可以为实数,也可以为`±∞` + > + > 注意,只要满足以上条件,洛必达法则是可以继续进行的 + + [![](../_assets/公式_20180628211807.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\lim_{x&space;\to&space;a}\frac{f(x)}{g(x)}=\lim_{x&space;\to&space;a}\frac{f%27(x)}{g%27(x)}=\lim_{x&space;\to&space;a}\frac{f%27%27(x)}{g%27%27(x)}=...&space;\end{aligned}) + + +- 这么看,实际上,求解导数的过程也是在使用洛必达法则 + + [![](../_assets/公式_20180628144731.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{df(x)}{dx}=\lim_{h&space;\to&space;0}\frac{f(x+h)-f(x)}{h}&space;\end{aligned}) + > 分子分母在 `h→0` 处的极限都为 `0` + + +# 积分与微积分基本定理 + +在[1.2. 积分与导数](#12-积分与导数)就提到过,积分其实就是求导的逆运算 + +![](../_assets/TIM截图20180629094906.png) + +**示例:距离是速度对时间的积分** +--- +- 已知速度-时间的关系 `v(t)=t(8-t)`,求这段时间驶过的距离。 +- 在[[2.1. 节](#21-瞬时变化率引起的歧义导数的悖论)]介绍这个例子时,是“已知距离函数`s(t)`,来推导速度函数`v(t)`”;现在“已知每个时间点的速度 `v(t)`,来求距离`s(t)`” +- 从数学的角度,前者的问题等价于求“`s(t)`的导数”,于是现在的问题也就是求“哪个函数的导数是`v(t)`”——这通常被称为“求函数的‘**原函数**’(或‘反导数’)” + + ![](../_assets/TIM截图20180629100103.png) + +- 类比[[1.1. 节](#11-推导圆的面积---积分的直观理解)]中推导圆面积的公式,这个问题可以转化成求解曲线下方的面积 + + ![](../_assets/TIM截图20180629101348.png) + +- 这个过程可以表示为 `v(t)` 的“积分”: + + ![](../_assets/TIM截图20180629102412.png) + > 为什么不适用`∑`?——`∫`指的并不是具体的`dt`的加和,而是当`dt→0`时,加和逼近的值,这个逼近的值就是曲线下方的面积;当`dt`越趋近 0,加和就越趋近精确解。 + > + > 积分的含义就是将所有小量“累积”在一起 + +- “求函数图像与横轴围成的面积”是许多不相干问题的共通之处——可以被拆成小量,然后近似为这些小量的和 + +## 积分与导数是一组互逆的运算 +- 根据以上的分析,距离实际上是速度对时间的积分,而速度是距离对时间的导数——这表明**积分与导数是一组互逆的运算** + +**面积函数的导数等于函数本身** +- 如果固定左端点,将右端点当做一个变量 `T`,那么这个积分可以看做以**上限为自变量**的函数 `s(T)`——这是距离关于时间的导数,同时也是图形下方的**面积函数** +- 这表明任一**函数图像下方面积函数的导函数等于该函数本身** + + ![](../_assets/TIM截图20180629150107.png) + + - **从几何的角度看**,矩形的面积为 `ds`,宽为 `dT`,它的高为 `v(T)`,于是有 `ds/dT = v(T)` + - 即**图像所表示的函数就是面积函数的导数** + +- 因此,求解积分的过程就是求导的**逆过程** + + ![](../_assets/TIM截图20180629150441.png) + ![](../_assets/TIM截图20180629150541.png) + ![](../_assets/TIM截图20180629151001.png) + +**每个函数都有无数个原函数** +--- +- 因为常数的导数为 0,所以在原函数的基础上加上任意常数,其导数不变——这意味着每个函数的原函数有无数个 +- 所以`v(t) = 8t - t^2`正确的原函数应该是 + + [![](../_assets/公式_20180629153014.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;s(t)=\int&space;v(t)dt=4t^2-\frac{1}{3}t^3+C&space;\end{aligned}) + +- 从图像上来看,曲线上下移动并不会影响其在每一点的斜率 + + ![](../_assets/TIM截图20180629152708.png) + +- 在不同的问题中,一般会有额外的条件帮你决定该使用哪个原函数 + - 比如,在本问题中,`t=0` 时,`s(t)=0`,于是 `C=0` + +## 微积分基本定理 +- 在对任意函数求(定)积分时,你是在对 `x` 在**一定范围内**的所有 `f(x)*dx` 求和,而积分值就是 `dx → 0` 时,这个和趋近的值。 +- 求积分的第一步是找出原函数 `F`,使其导数为积分内的函数。 +- 积分值就等于原函数在上限时的值减去其在下限时的值。 +- 这个过程就是“微积分基本定理” + + ![](../_assets/TIM截图20180629153515.png) + +- 积分异于常识的一点在于:它**连续地**遍历了从下限到上限中的每一个自变量的值,而我们在利用原函数求值时,只需要关注上限与下限两个自变量 + +**负面积** +--- +- 如果图像有部分出现在横轴的下方,那么这部分就是**负面积** + + ![](../_assets/TIM截图20180629160418.png) + > 有符号的面积 + +- 积分计算的不是真正的面积,而是图像与横轴围城的带有正负的面积。 + + +# 连续变量的平均值 + +结论:**区间上的平均斜率等于起点和终点连线的斜率** +--- +**如何求连续变量的平均值?** +- 问题等价于求图像下方面积的平均高度 +- 如果把面积作为一个整体来看,这是一个很简单的问题——`平均高度 = 积分面积 / 上下限宽度` + + ![](../_assets/TIM截图20180629193011.png) + +- 值得注意的是,`(F(b)-F(a))/(b-a)` 实际上也是原函数`F(x)`在 `x=a` 和 `x=b` 两点连线的斜率 +- 这表明在某一区间上所有点处切线的**平均斜率**等于起点和终点连线的斜率 + > 根据定义,`f(x)`是其原函数`F(x)`的导函数,也就是说它给出了`F(x)`在每个点上切线的斜率,所以`f(x)`在`(a,b)`上的平均值也就是原函数从`x=a`到`x=b`上所有切线斜率的平均值。 + +- 换言之,求解连续函数的平均值,可以转化为求解其原函数在各点切线的平均斜率;而两点间的平均斜率等于这两点的斜率。 + +## 适用“积分”的场景 +--- +第[8章](#8-积分与微积分基本定理)和第[9章](#9-连续变量的平均值)给出了两个适用于**积分**的场景 +- 可以通过细分然后相加的方式进行估算时 +- 求解连续变量的均值时(特别在概率中) + + +# 泰勒级数 + +## 高阶导数 + +这里介绍高阶导数的目的是帮助得到函数的近似——泰勒级数 + +**二阶导数描述的是曲线的弯曲程度** +- 根据导数的定义,二阶导数也就是导数的微小变化率,即斜率的变化率——直观来看,也就是**曲线的弯曲程度** + + ![](../_assets/TIM截图20180629210933.png) + - 考虑函数的一个取值 `x`,然后向右连续的增加两个小量 `dx` + - 第一个增量是函数产生了第一个变化量 `df1`,第二个同理,记 `df2` + - 这两个变化量的差也就是**函数值变化量的变化量**,记 `d(df)`. 它和 `(dx)^2` 成正比 + - 所谓二阶导数,就是 `d(df)` 和 `(dx)^2` 当 `dx → 0` 时比值的极限 + +- 用符号表示为 + + [![](../_assets/公式_20180629211135.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\underbrace{\frac{d(\frac{df}{dx})}{dx}=\frac{d(df)}{(dx)^2}=\frac{d^2f}{dx^2}}_{dx&space;\to&space;0}&space;\end{aligned}) + > 其实中间的写法才是最正确的,但为了书写方便,通常写成最右侧的形式 + +- 一个理解二阶导数的现实示例就是加速度 + + ![](../_assets/TIM截图20180629211550.png) + > 二阶导数为正,说明车在加速;反之,为负,说明在减速 + +## 泰勒多项式与泰勒级数 + +泰勒级数的作用——函数近似工具。比如,在 `x=0` 附近,有: + +![](../_assets/TIM截图20180630094515.png) +![](../_assets/TIM截图20180630094555.png) +![](../_assets/TIM截图20180630094715.png) +![](../_assets/TIM截图20180630094826.png) + +泰勒级数利用**多项式函数**去近似复杂抽象的函数,从而化简问题 +- 多项式函数的优势:易计算、易求导、易积分 +- 深度学习中的梯度下降(一阶)、牛顿法(二阶)也是利用的泰勒级数 + +**示例:`cos(x)` 在 `x=0` 附近如何用二次多项式近似?** +--- +- 问题等价于在所有可能的 `c0 + c1*x + c2*x^2` 中确定系数使其在 `x=0` 附近最近似 `cos(x)` +- 不考虑任何先验知识,看看只凭直觉应该怎么确定这三个系数 + - 首先,`cos(x)` 在 `x=0` 处等于 1,那么至少多项式这一点要满足,即 `c0=1` + - 如果多项式在 `x=0` 处切线的斜率也与 `cos(x)` 相同,那么近似程度应该会更高,于是 `c1=0` + - 二阶导数描述的是曲线的弯曲程度,那么让两个图像在 `x=0` 处的弯曲程度相同应该会更近似,`cos(x)` 的二阶导是 `-cos(x)`,于是 `2c2=-cos(0)=-1 -> c2=-1/2` + - 还可以用更高次的多项式去近似,比如 + + ![](../_assets/TIM截图20180630110837.png) + + - 在非 0 点,有类似的结果,如 `x=π` + + ![](../_assets/TIM截图20180630113240.png) + > 带入 `x=π` 就能消去所有无关项 + +- “**泰勒多项式**”小结 + - 在对高阶多项式求导时,自然而然的出现了**阶乘**的形式 + - 当然,真正的系数需要除以这个阶乘 + + ![](../_assets/TIM截图20180630111742.png) + + - 在向近似多项式中添加更高次的项时,不会影响低次项 + - 因此,多项式任意 n 阶的导数在 `x=0` 时的值都由唯一的系数控制 + + ![](../_assets/TIM截图20180630113040.png) + + - 这样的多项式就称为“泰勒多项式” + +**泰勒多项式**的本质 +--- +泰勒多项式实际上是利用函数在某点处的高阶导数,来近似该点附近的函数值 + +宏观来讲:泰勒级数把某一点处高阶导数的信息转化成了在那一点**附近**的函数值信息 +- `x=0` 时,它的常数项能让它与 `f(0)` 的值相等 + + ![](../_assets/TIM截图20180630153013.png) + +- 它的一次项让两者的斜率相等 + + ![](../_assets/TIM截图20180630153808.png) + +- 二次项让两者斜率的变化率相同 + + ![](../_assets/TIM截图20180630153907.png) + +- 以此类推,项数越多,近似就越精确 + + ![](../_assets/TIM截图20180630154029.png) + + ![](../_assets/TIM截图20180630154212.png) + +**从几何角度看二阶泰勒多项式** +- 考虑如何近似曲线下的面积函数 `f_area(x)` + + ![](../_assets/TIM截图20180630194836.png) + - 当 `dx → 0` 时,我们可以将增长的部分近似为矩形 + - 但如果想将面积的变化近似得更准确,就需要考虑那块**近似三角形**的部分 + +- 假设已知面积函数 `f` 在 `a` 点的导数信息,想要近似在 `x` 时的面积 + + ![](../_assets/TIM截图20180630201849.png) + > 三角形的高 = `斜率 * 底`,斜率 = 曲线在 a 点的导数 = `f''(a)` + +**任意函数的泰勒多项式** +--- +- 函数`f(x)`在`x=0`处的泰勒多项式 + + [![](../_assets/公式_20180630152255.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;P(x)=f(0)+\frac{f%27(0)}{1!}x+\frac{f%27%27(0)}{2!}x^2+\frac{f^{(3)}(0)}{3!}x^3+\cdots&space;\end{aligned}) + +- 函数`f(x)`在`x=a`处的泰勒多项式 + + [![](../_assets/公式_20180630152454.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;P(x)=f(a)+\frac{f%27(a)}{1!}(x-a)+\frac{f%27%27(a)}{2!}(x-a)^2+\frac{f^{(3)}(a)}{3!}(x-a)^3+\cdots&space;\end{aligned}) + +**级数的概念** +--- +**级数(series)的定义**——无限项的和 +- 当泰勒多项式无限累加下去,就成了泰勒级数 + +**级数的一些重要概念** +--- +- **级数的收敛(Converges)与发散(diverges)、收敛半径** + + ![](../_assets/TIM截图20180630202744.png) + > 无限累加下去的和就“等于”级数收敛到的值;这里 `x=1` + + - 一些级数无论你带入什么值,它的级数都会收敛,比如 `e^x` 在 `x=0` 处的泰勒级数 + + ![](../_assets/TIM截图20180630203750.png) + ![](../_assets/TIM截图20180630203959.png) + > 无论 `x` 取何值,`e^x` 都等于该泰勒级数 + + - 但有些级数就只会在一定取值范围内才会收敛,比如 `ln(x)` 在 `x=1` 处的泰勒级数 + + ![](../_assets/TIM截图20180630204148.png) + ![](../_assets/TIM截图20180630204303.png) + - 只有当 `x∈(0,2]`时,该级数才会收敛 + - 换言之,在 `x=1` 处取得的导数信息无法拓展到更广的取值范围 + - 这个泰勒级数的**收敛半径**为 `1`,即`x∈(0, 2]` + +**常见的泰勒级数** +> 泰勒级数_百度百科 https://baike.baidu.com/item/泰勒级数/7289427?fr=aladdin#6 + +**泰勒级数的更多内容** +- 拉格朗日余项 +- 判断级数收敛——审敛法 +- 求收敛半径 +- ... + +本系列的视频目的是帮你建立关于微积分的基本直觉,而微积分的道路才刚刚开始... +--- + + + + + + + + + + diff --git "a/C-\346\225\260\345\255\246/B-\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\346\240\270\345\277\203.md" "b/C-\346\225\260\345\255\246/B-\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\346\240\270\345\277\203.md" new file mode 100644 index 00000000..81488c27 --- /dev/null +++ "b/C-\346\225\260\345\255\246/B-\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\346\240\270\345\277\203.md" @@ -0,0 +1,769 @@ +**说明** +--- +该文档为“**3Blue1Brown - 深度学习系列视频**”的整理,主要包括三个视频 +- [神经网络的结构](https://www.bilibili.com/video/av15532370) +- [梯度下降法](https://www.bilibili.com/video/av16144388) +- [反向传播算法](https://www.bilibili.com/video/av16577449) + +让我们跟着 3Blue1Brown 从偏数学的角度来理解神经网络(原视频假设观众对神经网络没有任何背景知识) + +**目录** +--- + + +- [神经网络的结构](#神经网络的结构) + - [神经元(隐藏单元)与隐藏层](#神经元隐藏单元与隐藏层) + - [神经网络的运作机制](#神经网络的运作机制) + - [权重和偏置](#权重和偏置) + - [非线性激活函数](#非线性激活函数) +- [梯度下降法](#梯度下降法) + - [损失函数(Loss Function)](#损失函数loss-function) + - [梯度下降法(Gradient Descent)](#梯度下降法gradient-descent) + - [理解梯度下降法的另一种思路](#理解梯度下降法的另一种思路) + - [随机梯度下降(Stochastic Gradient Descent)](#随机梯度下降stochastic-gradient-descent) + - [再谈神经网络的运作机制](#再谈神经网络的运作机制) + - [推荐阅读](#推荐阅读) +- [反向传播算法(Backpropagation Algorithm, BP)](#反向传播算法backpropagation-algorithm-bp) + - [反向传播的直观理解](#反向传播的直观理解) + - [BP 算法小结](#bp-算法小结) + - [相关代码](#相关代码) + - [反向传播的微积分原理](#反向传播的微积分原理) + - [示例:每层只有一个神经元的网络](#示例每层只有一个神经元的网络) + - [更复杂的示例](#更复杂的示例) + - [反向传播的 4 个基本公式](#反向传播的-4-个基本公式) + + + +# 神经网络的结构 + +内容: +- 神经网络是什么? +- 神经网络的结构 +- 神经网络的工作机制 +- 深度学习中的“学习”指的是什么? +- 神经网络的不足 + +**示例:一个用于数字手写识别的神经网络** + +![](../_assets/TIM截图20180701210407.png) +> 这个示例相当于深度学习领域中的 "Hello World". + +## 神经元(隐藏单元)与隐藏层 + +**神经元(隐藏单元)** +- 简单来说,神经元可以理解为一个用来装数字的容器,而这个数称为激活值 + + 需要强调的是,激活值的值域取决于使用的激活函数,大多数激活函数的值域都是**正值** + + ![](../_assets/TIM截图20180701211429.png) + > 如果使用 sigmoid 激活函数,那么这个数字就在 0 到 1 之间;但通常来说,无论你使用哪种激活函数,这个数字都比较小 + + +- 输入层也可以看做是一组神经元,它的激活值就是输入本身 + + ![](../_assets/TIM截图20180701211850.png) + ![](../_assets/TIM截图20180701214914.png) + > 基本的神经网络只能处理向量型的输入,所以需要将这个 28*28 的像素图(矩阵),重排成长为 784 的向量 + > + > 如果使用卷积神经网络,则可以直接处理矩阵型的输入 + +- 对于分类问题,**输出层**中的激活值代表这个类别正确的可能性 + > 如果使用了 `softmax` 函数,那么整个输出层可以看作每个类别的概率分布 +- 所谓的“**神经元被激活**”实际上就是它获得了一个较大的激活值 + + ![](../_assets/TIM截图20180701212340.png) + +**隐藏层** +- 包含于输入层与输出层之间的网络层统称为“隐藏层” + + ![](../_assets/TIM截图20180701212657.png) + > 在这个简单的网络中,有两个隐藏层,每层有 16 个神经元 + > + > 为什么是两层和 16 个?——层数的大小与问题的复杂度有关,而神经元的数量目前来看是随机的——网络的结构在实验时有很大的调整余地 + +## 神经网络的运作机制 +- 神经网络在运作的时候,隐藏层可以视为一个“黑箱” +- 每一层的激活值将通过某种方式计算出下一层的激活值——神经网络处理信息的核心机制 + + ![](../_assets/TIM截图20180701213151.png) + > 每一层被激活的神经元不同,(可能)会导致下一层被激活的神经元也不同 + +**为什么神经网络的分层结构能起作用?** +--- +- 人在初识数字时是如何区分的?——**组合**数字的各个部分 + + ![](../_assets/TIM截图20180702092532.png) + +- **在理想情况下**,我们希望神经网络倒数第二层中的各隐藏单元能对应上每个**基本笔画**(pattern) + + ![](../_assets/TIM截图20180702093050.png) + - 当输入是 9 或 8 这种**顶部带有圆圈**的数字时,某个神经元将被激活(激活值接近 1) + - 不光是 9 和 8,所有顶部带有圆圈的图案都能激活这个隐藏单元 + - 这样从倒数第二层到输出层,我们的问题就简化成了“学习哪些部件能组合哪些数字” + +- 类似的,基本笔画也可以由更基础的部件构成 + + ![](../_assets/TIM截图20180702094246.png) + +- **理想情况下**,神经网络的处理过程 + + ![](../_assets/TIM截图20180702094455.png) + > 从输入层到输出层,**网络的抽象程度越来越高** + +**深度学习的本质:通过组合简单的概念来表达复杂的事物** +--- +- 神经网络是不是这么做的,我们不得而知(所以是一个“黑箱”),但大量实验表明:神经网络确实在做类似的工作——**通过组合简单的概念来表达复杂的事物** + + ![](../_assets/TIM截图20180702095428.png) + > 语音识别:原始音频 → 音素 → 音节 → 单词 + +**隐藏单元是如何被激活的?** +--- +- 我们需要设计一个机制,这个机制能够把像素拼成边,把边拼成基本图像,把基本图像拼成数字 +- 这个机制的基本处理方式是:通过上一层的单元激活下一层的单元 + +**示例:如何使第二层的单个神经元识别出图像中的某块区域是否存在一条边** +- 根据激活的含义,当激活值接近 1 时,表示该区域存在一条边,反之不存在 +- **怎样的数学公式能够表达出这个含义?** + + ![](../_assets/TIM截图20180702112135.png) + ![](../_assets/TIM截图20180702113031.png) + ![](../_assets/TIM截图20180702113631.png) + - 考虑对所有输入单元加权求和 + - 图中每条连线关联一个权值:绿色表示正值,红色表示负值,颜色越暗表示越接近 0 + - 此时,只需将需要关注的像素区域对应的权值设为正,其余为 0 + - 这样对所有像素的加权求和就只会累计我们关注区域的像素值 + - 为了使隐藏单元真正被“激活”,加权和还需要经过某个**非线性函数**,也就是“激活函数” + - 早期最常用的激活函数是 `sigmoid` 函数(又称 logistic/逻辑斯蒂曲线) + + ![](../_assets/TIM截图20180702114132.png) + > 从 `sigmoid` 的角度看,它实际上在对加权和到底有多“正”进行打分 + + - 但有时,可能加权和大于 10 时激活才有意义; + - 此时,需要加上“偏置”,保证不能随便激发,比如 -10。然后再传入激活函数 + +### 权重和偏置 +- 每个隐藏单元都会和**上一层的所有单元**相连,每条连线上都关联着一个**权重**; +- 每个隐藏单元又会各自带有一个**偏置** + > 偏置和权重统称为网络参数 + + ![](../_assets/TIM截图20180702150309.png) + ![](../_assets/TIM截图20180702151423.png) + > 每一层都带有自己的权重与偏置,这样一个小小的网络,就有 13002 个参数 + +**权重与偏置的实际意义** +--- +- 宏观来看,**权重**在告诉你当前神经元应该更关注来自上一层的哪些单元;或者说**权重指示了连接的强弱** + +- **偏置**则告诉你加权和应该多大才能使神经元的激发变得有意义;或者说**当前神经元是否更容易被激活** + +**矢量化编程** +--- +- 把一层中所有的激活值作为一列**向量** `a` +- 层与层之间的权重放在一个**矩阵** `W` 中:第 n 行就是上层所有神经元与下层第 n 个神经元的权重 +- 类似的,所有偏置也作为一列**向量** `b` +- 最后,将 `Wa + b` 一起传入激活函数 + + ![](../_assets/TIM截图20180702155142.png) + > `sigmoid`会对结果向量中的每个值都取一次`sigmoid` + +- 所谓“矢量化编程”,实际上就是将向量作为基本处理单元,避免使用 for 循环处理标量 +- 通过定制处理单元(GPU运算),可以大幅加快计算速度 + +**机器“学习”的实质** +--- +当我们在讨论机器如何“学习”时,实际上指的是机器如何正确设置这些参数 + +![](../_assets/TIM截图20180702152216.png) + + +## 非线性激活函数 +**神经网络本质上是一个函数** +- 每个神经元可以看作是一个函数,其输入是上一层所有单元的输出,然后输出一个激活值 +- 宏观来看,神经网络也是一个函数 + + ![](../_assets/TIM截图20180702151423.png) + > 一个输入 784 个值,输出 10 个值的函数;其中有 13000 个参数 + +- 早期最常用的激活函数是 `sigmoid` 函数,它是一个**非线性函数** +- 暂不考虑它其他优秀的性质(使其长期作为激活函数的首选)以及缺点(使其逐渐被弃用); + + 而只考虑其**非线性** + +**为什么要使用非线性激活函数?——神经网络的万能近似定理** +--- +> 视频中没有提到为什么使用非线性激活函数,但这确实是神经网络能够具有如此强大**表示能力**的关键 +- 使用**非线性激活函数**的目的是为了向网络中加入**非线性因素**,从而加强网络的表示能力 + +**为什么加入非线性因素能够加强网络的表示能力?** +- 首先要有这样一个认识,非线性函数具有比线性函数更强的表示能力。 +- 如果不使用非线性激活函数,那么每一层输出都是上层输入的线性组合; + + 容易验证,此时无论有多少层,神经网络都只是一个线性函数。 + +**万能近似定理** +- 神经网络如果具有至少一个非线性输出层,那么只要给予网络足够数量的隐藏单元,它就可以以任意的精度来近似任何从一个有限维空间到另一个有限维空间的函数。 +- 这极大的扩展了神经网络的表示空间 + > 《深度学习》 6.4.1 万能近似性质和深度 + +**新时代的激活函数——线性整流单元 ReLU** +--- +这里简单说下 sigmoid 的问题: +- `sigmoid` 函数在输入取绝对值非常大的正值或负值时会出现**饱和现象**,此时函数会对输入的微小改变会变得不敏感 + + ![](../_assets/TIM截图20180702114132.png) + > 饱和现象:在图像上表现为函数值随自变量的变化区域平缓(斜率接近 0) + +- 饱和现象会导致**基于梯度的学习**变得困难,并在传播过程中丢失信息(**梯度消失**) + +**线性整流单元 ReLU** + +- [![](../_assets/公式_20180702171411.png)](http://www.codecogs.com/eqnedit.php?latex=\text{ReLU}(a)=\max(0,a)) + + ![](../_assets/TIM截图20180702171146.png) + +- `ReLU` 取代 `sigmoid` 的主要原因就是:使神经网络更容易训练(**减缓梯度消失**) +- 此外,一种玄学的说法是,早期引入 `sigmoid` 的原因之一就是为了模仿生物学上神经元的激发 + + 而 `ReLU` 比 `sigmoid` 更接近这一过程。 + + +# 梯度下降法 + +内容: +- 梯度下降的思想 +- 网络的能力分析 +- 隐层神经元的真实目的 + +网络示例依然是那个手写识别的例子: + +![](../_assets/TIM截图20180701210407.png) + +**神经网络是怎样学习的?** +--- +- 我们需要一种算法:通过喂给这个网络大量的**训练数据**——不同的手写数字图像以及对应的数字标签 + + 算法会调整所有网络参数(权重和偏置)来提高网络对训练数据的表现 + + 此外,我们还希望这种分层结构能够举一反三,识别训练数据之外的图像——**泛化能力** + +- 虽然使用了“学习”的说法,但实际上训练的过程更像在解一道**微积分问题** + + 训练的过程实际上在寻找某个函数的(局部)最小值 + +- 在训练开始前,这些参数是随机初始化的 + > 确实存在一些随机初始化的策略,但目前来看,都只是“锦上添花” + +## 损失函数(Loss Function) +- 显然,随机初始化不会有多好的表现 +- 此时需要定义一个“**损失函数**”来告诉计算机:正确的输出应该只有标签对应的那个神经元是被激活的 +- 比如这样定义单个样本的损失: + + ![](../_assets/TIM截图20180702194825.png) + ![](../_assets/TIM截图20180702195301.png) + - 当网络分类正确时,这个值就越小 + - 这里使用的损失函数为“均方误差”(mean-square error, MSE) + +- 现在,我们就可以用**所有训练样本**的平均损失来评价整个网络在这个任务上的“**糟糕程度**” + > 在实践中,并不会每次都使用所有训练样本的平均损失来调整梯度,这样计算量太大了 + > + > 随机梯度下降 + + + 实际上,**神经网络学习的过程,就是最小化损失函数的过程** + +**神经网络与损失函数的关系** +- 神经网络本身相当于一个函数 + + ![](../_assets/TIM截图20180702195902.png) + > 输入是一个向量,输出是一个向量,参数是所有权重和偏置 + +- 损失函数在神经网络的基础上,还要再抽象一层: + + 所有权重和偏置作为它的输入,输出是单个数值,表示当前网络的性能;参数是所有训练样例(?) + + ![](../_assets/TIM截图20180702201513.png) + +- 从这个角度看,损失函数并不是神经网络的一部分,而是训练神经网络时需要用到的工具 + +## 梯度下降法(Gradient Descent) + +**如何优化这些网络参数?** +- 能够判断网络的“糟糕程度”并不重要,关键是如何利用它来**优化**网络参数 + +**示例 1:考虑只有一个参数的情况** +- 如果函数只有一个极值点,那么直接利用微积分即可 + + 如果函数很复杂的话,问题就不那么直接了,更遑论上万个参数的情况 + + ![](../_assets/TIM截图20180702202810.png) + ![](../_assets/TIM截图20180702203041.png) + +- **一个启发式的思路是**:先随机选择一个值,然后考虑向左还是向右,函数值会减小; + + 准确的说,如果你找到了函数在该点的斜率,**斜率为正就向左移动一小步,反之向右**; + + 然后每新到一个点就重复上述过程——计算新的斜率,根据斜率更新位置; + + 最后,就可能逼近函数的某个**局部极小值**点 + + ![](../_assets/TIM截图20180702203401.png) + +- 这个想法最明显的问题是:由于无法预知最开始的值在什么位置,导致最终会到达不同的局部极小值; + + 关键是无法保证落入的局部极小值就是损失函数可能达到的全局最小值; + + 这也是神经网络最大的问题: + + ![](../_assets/TIM截图20180702204047.png) + +**示例 2:考虑两个参数的情况** +- 输入空间是一个 XY 平面,代价函数是平面上方的曲面 + + 此时的问题是:在输入空间沿哪个方向移动,能使输出结果下降得最快 + + ![](../_assets/TIM截图20180702210202.png) + +- 如果你熟悉**多元微积分**,那么应该已经有了答案: + + 函数的**梯度**指出了函数的“**最陡**”增长方向,即沿着梯度的方向走,函数增长最快; + + 换言之,**沿梯度的负方向走,函数值也就下降得最快**; + + 此外,梯度向量的长度还代表了斜坡的“陡”的程度。 + +- 处理更多的参数也是同样的办法, + + 这种**按照负梯度的倍数**,不断调整函数输入值的过程,就叫作梯度下降法 + +- 直观来看,梯度下降法能够让函数值收敛到损失函数图像中的某一个“坑”中 + + ![](../_assets/TIM截图20180703102940.png) + +### 理解梯度下降法的另一种思路 +- 梯度下降法的一般处理方式: + - 将所有网络参数放在一个列向量中,那么损失函数的负梯度也是一个向量 + + ![](../_assets/TIM截图20180703103735.png) + +- 负梯度中的每一项实际传达了两个信息 + + 1. 正负号在告诉输入向量应该调大还是调小——因为是**负梯度**,所以正好对应调大,负号调小 + 1. 每一项的相对大小表明每个输入值对函数值的影响程度;换言之,也就是调整各权重对于网络的影响 + + ![](../_assets/TIM截图20180703103757.png) + ![](../_assets/TIM截图20180703104845.png) + +- 宏观来看,可以把梯度向量中的每个值理解为各参数(权重和偏置)的相对重要度, + + 同时指出了改变其中哪些参数的**性价比**最高 + +- 这个理解思路同样可以反馈到图像中 + + ![](../_assets/TIM截图20180703105249.png) + - 一种思路是在点 `(1,1)` 处沿着 `[3,1]` 方向移动,函数值增长最快 + - 另一种解读就是变量 `x` 的重要性是 `y` 的三倍; + + 也就是说,至少在这个点附近,改变 `x` 会造成更大的影响 + +**梯度下降法**描述: +--- +1. 计算损失函数对所有参数的(负)梯度 +1. 按梯度的负方向下降一定步长(直接加上负梯度) +1. 重复以上步骤,直到满足精度要求 + +- 其中计算梯度的算法——[反向传播算法](#3-反向传播算法backpropagation-algorithm)——是整个神经网络的核心 + +**梯度下降法**与**反向传播算法** +- 梯度下降法是寻找局部最小值的一种**策略** + + 其中最核心的部分利用**损失函数 `L(θ)` 的梯度**来更新所有参数 `θ` + +- **反向传播算法**是求解函数梯度的一种方法; + + 其本质上是利用**链式法则**对每个参数求偏导 + +**损失函数的平滑性** +- 为了能达到函数的局部最小值,损失函数有必要是**平滑**的 + + 只有如此,损失函数才能基于梯度下降的方法,找到一个局部最小值 + +- 这也解释了为什么要求神经元的激活值是连续的 + > 生物学中的神经元是二元式的 + +### 随机梯度下降(Stochastic Gradient Descent) +- 基本的梯度下降法要求每次使用**所有训练样本**的平均损失来更新参数,也称为“**批量梯度下降**” + + 原则上是这样,但是为了**计算效率**,实践中并不会这么做 + +- 一种常用的方法是每次只随机选取**单个样本**的损失来计算梯度,该方法称为“**随机梯度下降**”(Stochastic Gradient Descent, SGD),它比批量梯度下降法要快得多 + +- 但更常用的方法是**小批量梯度下降**,它每次随机选取**一批样本**,然后基于它们的平均损失来更新参数 + +- SGD 与 小批量梯度下降的优势在于:它们的计算复杂度与训练样本的数量无关 + > 很多情况下,并不区分 SGD 和小批量梯度下降;有时默认 SGD 就是小批量梯度下降,比如本视频 + +**批大小的影响**: +- **较大的批能得到更精确的梯度估计**,但回报是小于线性的。 +- **较小的批能带来更好的泛化误差**,泛化误差通常在批大小为 1 时最好。但是,因为梯度估计的高方差,小批量训练需要**较小的学习率**以保持稳定性,这意味着**更长的训练时间**。 + > 原因可能是由于小批量在学习过程中加入了噪声,它们会有一些正则化效果 (Wilson and Martinez, 2003) +- **内存消耗和批的大小成正比**,当批量处理中的所有样本可以并行处理时。 +- 在某些硬件上使用特定大小可以减少运行时间。尤其是在使用 GPU 时,通常使用 **2 的幂数**作为批量大小可以获得更少的运行时间。一般,2 的幂数的**取值范围是 32 到 256**,16 有时在尝试大模型时使用。 + + > 《深度学习》 8.1.3 批量算法和小批量算法 + +**神经网络的优化难题** +--- +- 有一个策略可以保证最终解**至少**能到达一个局部极小值点:使每次**移动的步幅和斜率成正比**; + + 因为在最小值附近的斜率会趋于平缓,这将导致每次移动步幅越来越小,防止跳出极值点 + + **但是**,这对于现代各种巨大的神经网络而言,是一个**负优化策略**——它反而会**限制网络的学习**,导致其陷入某个局部极小值点 + +- 当参数数量非常庞大时,可能存在**无数个极值点**,而其中某些极值点的结果可能非常差。 + > 优化问题是深度学习最核心的两个问题之一,另一个是正则化 + +- 也不要太过担心因为局部最小值点太多而无法优化; + + 事实上,只要你使用的数据不是完全随机,或者说有一定结构,那么最终网络倾向收敛到的各个局部最小值点,实际上都差不多 + + 你可以认为如果数据集已经**结构化**了,那么你可以更轻松的找到局部最小值点 + > [1412.0233] The Loss Surfaces of Multilayer Networks https://arxiv.org/abs/1412.0233 + +## 再谈神经网络的运作机制 +- 在第一章介绍[神经网络的运作机制](#12-神经网络的运作机制权重偏置激活函数)时,我们对神经网络的**期望**是: + + 第一个隐藏层能够识别短边,第二个隐藏层能够将短边拼成圆圈等基本笔画,最后将这些部件拼成数字 + + ![](../_assets/TIM截图20180702094455.png) + +- 利用**权重与所有输入像素对应相乘**的方法,可以还原每个神经元对应的图案 + + ![](../_assets/TIM截图20180703115843.png) + ![](../_assets/TIM截图20180703115823.png) + > 期望(左)与现实(右) + +- 事实上,与其说它们能识别各种散落的短边,它们不过都是一些松散的图案 + + 就像“在如深海般的 13000 维参数空间中,找到了一个不错的局部最小值位置住了下来” + + 尽管它们能成功识别大部分手写数字,但它不像我们期望的能识别各种图案的基本部件 + +- 一个明显的例子:传入一张随机的图案 + + 如果神经网络真的很“智能”,它应该对这个输入感到困惑——10个输出单元都没有明确的激活值 + + 但实际上,它总会会给出一个确切的答案 + + ![](../_assets/TIM截图20180703121303.png) + > 它能将一张真正的 "5" 识别成 5,也能把一张随机的噪声识别成 5 + +- 或者说,这个神经网络**知道怎么识别数字,但它不知道怎么写数字** + + 原因可能是,很大程度上它的训练被限制在了一个很窄的框架内; + + “从神经网络的视角来看,整个世界都是由网络内清晰定义的静止数字组成的,它的损失函数只会促使它对最后的判断有绝对的自信。” + +- 有一些观点认为,神经网络/深度学习并没有那么智能,它只是**记住了所有正确分类的数据**,然后**尽量**把那些跟训练数据类似的数据分类正确 + +- 但是,有些观点认为神经网络确实学到了某些更智能的东西: + + 如果训练时使用的是随机的数据,那么损失函数的下降会很慢,而且是接近线性的过程;也就是说网络很难找到可能存在的局部最小值点; + + 但如果你使用了**结构化**的数据,虽然一开始的损失会上下浮动,但接下来会快速下降;也就是很容易就找到了某个局部最小值——这表明神经网络能更快学习**结构化的数据**。 + + ![](../_assets/TIM截图20180703143800.png) + +## 推荐阅读 +- Neural Networks and Deep Learning: http://neuralnetworksanddeeplearning.com/ +- Chris Olah's blog: http://colah.github.io/ + - [Neural Networks, Manifolds, and Topology](http://colah.github.io/posts/2014-03-NN-Manifolds-Topology/) + - [Understanding LSTM Networks](http://colah.github.io/posts/2015-08-Understanding-LSTMs/) +- Distill — Latest articles about machine learning https://distill.pub/ +- Videos on machine learning: + - Learning To See [Part 1: Introduction] - YouTube https://www.youtube.com/watch?v=i8D90DkCLhI + - Neural Networks Demystified [Part 1: Data and Architecture] - YouTube https://www.youtube.com/watch?v=bxe2T-V8XRs + + +# 反向传播算法(Backpropagation Algorithm, BP) + +## 反向传播的直观理解 + +- 梯度下降法中需要用到损失函数的梯度来决定下降的方向, + + 其中 BP 算法正是用于求解这个复杂梯度的一种方法 + +- 在阐述 BP 算法之前,记住[梯度的另一种理解方式](#221-理解梯度下降法的另一种思路不借助空间图像)—— + + **梯度向量中的每一项不光告诉我们每个参数应该增大还是减小,还指示了调整每个参数的“性价比”** + + ![](../_assets/TIM截图20180703103757.png) + +**示例:单个训练样本对参数调整的影响** +--- +- 假设现在的网络还没有训练好,那么输出层的激活值看起来会很随机 + + 而我们希望的输出应该是正确类别对应的输出值最大,其他尽可能接近 0 + + 因此,我们希望正确类别对应的激活值应该增大,而其他都减小 + > 需要注意的是,激活值是由输入值和权重及偏置决定的;网络不会调整激活值本身,只能改变权重和偏置 + +- 一个简单的**直觉**是:激活值**变动的幅度**应该与**当前值和目标值之间的误差**成正比 + + ![](../_assets/TIM截图20180703210401.png) + ![](../_assets/TIM截图20180703210941.png) + > 我们希望第三个输出值增大,其他减小 + > + > 显然,这里增加 "2" 的激活值比减少 "8" 的激活值更重要;因为后者已经接近它的目标了 + +- 激活值的计算公式指明了调整的方向 + + ![](../_assets/TIM截图20180704095607.png) + +- 以 "2" 对应的神经元为例,如果需要**增大当前激活值**,可以: + 1. 增大偏置 + 1. 增大权重 + 1. 调整上一层的激活值 + > 如果使用的是 `sigmoid` 或 `ReLU` 作为激活函数,那么激活值都 `≥ 0`;但无论激活值正负,都是类似的调整方法 + +**如何调整权重和偏置?** +- 偏置只关注当前神经元,因此,可以正比于当前值和目标值之间的差来调整偏置 +- 权重指示了连接的强弱;换言之,与之相连的上层激活值越大,那么该权重对当前神经元的影响也越大。显然,着重调整这个参数的性价比更高 + + 因此,可以正比于与之关联的上层激活值来调整权重 + +- 这种学习的**偏好**将导致这样一个结果——**一同激活的神经元被关联在一起**;生物学中,称之为“赫布理论”(Hebbian theory) + + 这在深度学习中,并不是一个广泛的结论,只是一个粗略的对照;事实上,早期的神经网络确实在模仿生物学上大脑的工作;但深度学习兴起之后,其指导思想已经发生了重要转变——**组合表示** + +**如何改变上层激活值?** +- 因为权重带有正负,所以如果希望增大当前激活值,应该使所有通过**正权**连接的上层激活值增大,所有通过**负权**连接的上层激活值减小 + +- 类似的,与修改权重时类似,如果想造成更大的影响,应该对应权重的大小来对激活值做出**成比例**的改变; + + 但需要注意的是,上层激活值的大小也是由上层的权重和偏置决定的, + +- 所以更准确的说法是,每层的权重和偏置会根据下一层的权重和偏置来做出成比例的改变,而最后一层的权重个偏置会根据**当前值和目标值之间的误差**来成比例调整 + +**反向传播** +- 除了需要增大正确的激活值,同时还要减小错误的。而这些神经元对于如何改变上一层的激活值都有各自的想法; + + 因此,需要将这些神经元的期待全部相加,作为改变上层神经元的指示; + + 这些期待变化,不仅对应权重的倍数,也是每个神经元激活值改变量的倍数。 + + ![](../_assets/TIM截图20180704111141.png) + +- 这其实就是在实现“反向传播”的理念了—— + + 将所有期待的改变相加,就得到了希望对上层改动的变化量;然后就可以重复这个过程,直到第一层 + +- 上面只是讨论了单个样本对所有参数的影响,实践时,需要同时考虑每个样本对权重与偏置的修改,然后将它们期望的平均作为每个参数的变化量; + + 不严格的来说,这就是梯度下降中的需要的“**负梯度**” + + ![](../_assets/TIM截图20180704111911.png) + ![](../_assets/TIM截图20180704112743.png) + > `η` 表示倍数 + +### BP 算法小结 +- 反向传播算法计算的是**单个训练样本**对所有权重和偏置的调整——包括每个参数的正负变化以及**变化的比例**——使其能最快的降低损失。 +- 真正的梯度下降需要对训练集中的每个样本都做一次反向传播,然后计算平均变化值,继而更新参数。 + + 但这么操作会导致算法的复杂度与训练样本的数量相关。 + +- 实践时,会采用“**随机梯度下降**”的策略: + 1. 首先打乱所有样本; + 1. 然后将所有样本分发到各个 mini-batch 中; + 1. 计算每个 mini-batch 的梯度,调整参数 + 1. 循环至 Loss 值基本不再变化 + + 最终神经网络将会收敛到某个局部最小值上 + +### 相关代码 +- mnielsen/neural-networks-and-deep-learning/[network.py](https://github.com/mnielsen/neural-networks-and-deep-learning/blob/master/src/network.py) + + +## 反向传播的微积分原理 + +从数学的角度看,反向传播本质上就是**利用链式法则求导**的过程; + +本节的目标是展示机器学习领域是如何理解链式法则的。 + +### 示例:每层只有一个神经元的网络 + +![](../_assets/TIM截图20180704161115.png) + +- 从最后两个神经元开始: + + 记最后一个神经元的激活值为 `a^(L)` 表示在第 L 层,上层的激活值为 `a^(L-1)`; + + 给定一个训练样本,其目标值记为 `y`; + + 则该网络对于单个训练样本的损失,可以表示为: + + ![](../_assets/TIM截图20180704193558.png) + + + + 其中 + + ![](../_assets/TIM截图20180704193742.png) + + + + 为了方便,记加权和为 `z`,于是有 + + ![](../_assets/TIM截图20180704193955.png) + + + +- 整个流程可以概括为: + + ![](../_assets/TIM截图20180704164022.png) + - 先使用前一个激活值和权重 `w` 以及偏置 `b` 计算出 `z` + - 再将 `z` 传入激活函数计算出 `a` + - 最后利用 `a` 和目标值 `y` 计算出损失 + +- 我们的目标是理解**代价函数对于参数的变化有多敏感**; + + 从微积分的角度来看,这实际上就是在**求损失函数对参数的导数**。 + +- 以 `w^(L)` 为例,求 `C` 对 `w^(L)`的(偏)导数,记作: + + ![](../_assets/TIM截图20180704194111.png) + > 把 `∂w^(L)` 看作对 `w` 的微小扰动,比如 0.001;把 `∂C` 看作“**改变 `w` 对 `C` 造成的变化**” + + + + 这实际上就是在计算 `C` 对 `w^(L)` 的微小变化有多敏感; + > 3B1B - [微积分系列视频](./微积分的本质.md) + +- 根据链式法则,有 + + ![](../_assets/TIM截图20180704194548.png) + + + +- 进一步分解到每个比值,有 + + ![](../_assets/TIM截图20180704194635.png) + + + +- 类似的,对偏置的偏导: + + ![](../_assets/TIM截图20180704194726.png) + + 对上层激活值的偏导: + + ![](../_assets/TIM截图20180704200743.png) + + + + + +- 更上层的权重与偏置也是类似的方法,只是链的长度会更长而已 + + ![](../_assets/TIM截图20180704174134.png) + +- 一个直观的理解,考虑将它们分别对应到一个数轴上; + + ![](../_assets/TIM截图20180704165624.png) + - `w^(L)` 的微小变化会导致 `z^(L)` 的微小变化 + - `z^(L)` 的微小变化会导致 `a^(L)` 的微小变化 + - `a^(L)` 的微小变化最终会影响到损失值 `C` + - 反向传播的过程就是将 `C` 的微小变化传回去 + +- 以上只是单个训练的损失对 `w^(L)` 的导数,实践中需要求出一个 mini-batch 中所有样本的平均损失 + + ![](../_assets/TIM截图20180704194944.png) + + + +- 进一步的,`∂C/∂w^(L)` 只是梯度向量 `▽C` 的一个分量; + + 而梯度向量本身有损失函数对每个参数的偏导构成: + + ![](../_assets/TIM截图20180704172524.png) + +> 本系列视频称损失函数为“代价函数”,所以使用 `C` 表示代价(Cost);更多会用 `L` 表示损失(Loss);二者没有区别,但这里已经使用 L 表示网络的层数,所以使用 `C` 表示损失函数 + +### 更复杂的示例 +- 更复杂的神经网络在公式上并没有复杂很多,只有少量符号的变化 +- 首先,利用**下标**来区分同一层不同的神经元和权重 + + ![](../_assets/TIM截图20180704195120.png) + + 以下的推导过程几乎只是符号的改变: + + ![](../_assets/TIM截图20180704195859.png) + + 然后求偏导: + + ![](../_assets/TIM截图20180704200316.png) + + 唯一改变的是,对 `(L-1)` 层的偏导: + + ![](../_assets/TIM截图20180704200453.png) + > 此时激活值可以通过不同的途径影响损失函数 + + 只要计算出倒数第二层损失函数对激活值的敏感度,就可以重复以上过程,计算喂给倒数第二层的权重和偏置。 + +- 事实上,如果都使用矢量表示,那么整个推导公式跟单神经元的网络几乎是完全一样的 + +- 链式法则给出了决定梯度每个分量的偏导,使我们能不断下探,最小化神经网络的损失。 + +### 反向传播的 4 个基本公式 + +- **问题描述**: + + 反向传播算法的目标是求出损失函数**对所有参数的梯度**,具体可分解为**对每个权重和偏置的偏导** + + 可以用 4 个公式总结反向传播的过程 + +- **标量形式**: + + [![](../_assets/公式_20180705190236.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{\partial&space;C}{\partial&space;{\color{Red}&space;a_j^{(L)}}}=\frac{\partial&space;C({\color{Red}&space;a_j^{(L)}},y_j)}{\partial&space;{\color{Red}&space;a_j^{(L)}}}&space;\end{aligned}) + + [![](../_assets/公式_20180705134851.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{\partial&space;C}{{\color{Red}&space;\partial&space;a_j^{(l)}}}={\color{Teal}\sum_{k=0}^{n_l-1}}&space;\frac{\partial&space;z_k^{(l+1)}}{{\color{Red}&space;\partial&space;a_j^{(l)}}}&space;\frac{{\color{Blue}&space;\partial&space;a_k^{(l+1)}}}{\partial&space;z_k^{(l+1)}}&space;\frac{\partial&space;C}{{\color{Blue}&space;\partial&space;a_k^{(l+1)}}}&space;\end{aligned}) + + [![](../_assets/公式_20180705154543.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{\partial&space;C}{{\color{Magenta}&space;\partial&space;w_{j,k}^{(l)}}}=\frac{\partial&space;z_j^{(l)}}{{\color{Magenta}&space;\partial&space;w_{j,k}^{(l)}}}\frac{{\color{Red}\partial&space;a_j^{(l)}}}{\partial&space;z_j^{(l)}}\frac{\partial&space;C}{{\color{Red}\partial&space;a_j^{(l)}}}&space;\end{aligned}) + + [![](../_assets/公式_20180705154650.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{\partial&space;C}{{\color{Magenta}&space;\partial&space;b_{j}^{(l)}}}=\frac{\partial&space;z_j^{(l)}}{{\color{Magenta}&space;\partial&space;b_{j}^{(l)}}}\frac{{\color{Red}\partial&space;a_j^{(l)}}}{\partial&space;z_j^{(l)}}\frac{\partial&space;C}{{\color{Red}\partial&space;a_j^{(l)}}}&space;\end{aligned}) + + > 其中,上标 `(l)` 表示网络的层次,`(L)` 表示输出层(最后一层);下标 `j` 和 `k` 指示神经元的位置; + > + > `w_jk` 表示 `l` 层的第 `j` 个神经元与`(l-1)`层第 `k` 个神经元连线上的权重 + + 以 **均方误差(MSE)** 损失函数为例,有 + + [![](../_assets/公式_20180705190536.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{\partial&space;C}{\partial&space;{\color{Red}&space;a_j^{(L)}}}&=\frac{\partial&space;C({\color{Red}&space;a_j^{(L)}},y_j)}{\partial&space;{\color{Red}&space;a_j^{(L)}}}&space;\\&space;&=\frac{\partial&space;\left&space;(&space;\frac{1}{2}({\color{Red}a_j^{(L)}}-y_j)^2&space;\right&space;)&space;}{\partial&space;{\color{Red}a_j^{(L)}}}={\color{Red}a_j^{(L)}}-y&space;\end{aligned}) + + 根据以上公式,就可以反向求出所有损失函数对所有参数的偏导了 + +- **矢量形式**: + + 在标量形式的基础上,修改下标即可 + + [![](../_assets/公式_20180705190657.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{\partial&space;C}{&space;\partial&space;{\color{Red}&space;a_j^{(L)}}}=\frac{\partial&space;C({\color{Red}&space;a_j^{(L)}},y_j)}{\partial&space;{\color{Red}&space;a_j^{(L)}}}&space;\end{aligned}) + + [![](../_assets/公式_20180705162428.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{\partial&space;C}{{\color{Red}&space;\partial&space;a^{(l)}}}=\frac{\partial&space;z^{(l+1)}}{{\color{Red}&space;\partial&space;a^{(l)}}}&space;\frac{{\color{Blue}&space;\partial&space;a^{(l+1)}}}{\partial&space;z^{(l+1)}}&space;\frac{\partial&space;C}{{\color{Blue}&space;\partial&space;a^{(l+1)}}}&space;\end{aligned}) + + [![](../_assets/公式_20180705162521.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{\partial&space;C}{{\color{Magenta}&space;\partial&space;w_{j}^{(l)}}}=\frac{\partial&space;z^{(l)}}{{\color{Magenta}&space;\partial&space;w_{j}^{(l)}}}\frac{{\color{Red}\partial&space;a^{(l)}}}{\partial&space;z^{(l)}}\frac{\partial&space;C}{{\color{Red}\partial&space;a^{(l)}}}&space;\end{aligned}) + + [![](../_assets/公式_20180705162633.png)](http://www.codecogs.com/eqnedit.php?latex=\begin{aligned}&space;\frac{\partial&space;C}{{\color{Magenta}&space;\partial&space;b^{(l)}}}=\frac{\partial&space;z^{(l)}}{{\color{Magenta}&space;\partial&space;b^{(l)}}}\frac{{\color{Red}\partial&space;a^{(l)}}}{\partial&space;z^{(l)}}\frac{\partial&space;C}{{\color{Red}\partial&space;a^{(l)}}}&space;\end{aligned}) + + **注意**:其中向量相乘都是以**按元素相乘**的形式,一般用 `⊙` 符号表示。 + +- 以上是纯微积分形式的表达,一些机器学习相关书籍上总结了更简洁的形式,比如: + + ![](../_assets/TIM截图20180705162841.png) + > [The four fundamental equations behind backpropagation](http://neuralnetworksanddeeplearning.com/chap2.html#the_four_fundamental_equations_behind_backpropagation) + + **注意**:前两个公式为**矢量形式**,后两个具体到单个参数的是**标量形式**。 + + 其中,符号 `⊙` 按元素相乘: + + ![](../_assets/TIM截图20180705164018.png) \ No newline at end of file diff --git "a/C-\346\225\260\345\255\246/README.md" "b/C-\346\225\260\345\255\246/README.md" new file mode 100644 index 00000000..80e486c3 --- /dev/null +++ "b/C-\346\225\260\345\255\246/README.md" @@ -0,0 +1,33 @@ +**知识回顾** +--- +- [微积分的本质](./微积分的本质.md) +- [深度学习的核心-梯度下降与反向传播](./深度学习的核心.md) + +**数学问题** +--- + + +- [1. 在圆环上随机选取 3 个点,这 3 个点组成锐角三角形的概率](#1-在圆环上随机选取-3-个点这-3-个点组成锐角三角形的概率) + + + + +### 1. 在圆环上随机选取 3 个点,这 3 个点组成锐角三角形的概率 +> 今日头条-算法工程师-实习 + +- 答案:1/4 + +- 简单回答:易知,当A、B、C三点都在同一个半圆内时,三角形ABC必是直角或钝角三角形;只有当三点不在同一个半圆内,才可以组成锐角三角形。于是问题等价于“在圆周上任取三个不同的点,求它们不在同一半圆内的概率”。 + - 过圆心**任取**两条直线,并在圆上**任取**一点作为点 A; +
+ - 接着在候选的 P1~P4 中选择 B 和 C,有四种情况:`{P1, P2}、{P1, P2}, {P2, P3}, {P3, P4}`。当且仅当选中 `{P3, P4}` 时,能够成锐角三角形(或者说包含圆心),概率为 `1/4`. + +- 积分计算 + +
+ + > [在一圆周上任意取三个点构成锐角三角形的概率是多少](https://zhidao.baidu.com/question/1884315387170029428.html) - 百度知道 + +- 拓展:球面上任取 4 个点,求这 4 个点构成的四面体包含球心的概率。 + > [【官方双语】如何优雅地解答最难数学竞赛的压轴题?](https://www.bilibili.com/video/av17275211) - bilibili + >> 关于圆上取三个点的情况在 1:50-4:25, 5:40-7:50 diff --git "a/C-\347\256\227\346\263\225/README.md" "b/C-\347\256\227\346\263\225/README.md" new file mode 100644 index 00000000..8f4652d0 --- /dev/null +++ "b/C-\347\256\227\346\263\225/README.md" @@ -0,0 +1,80 @@ +**题解** +--- +- [剑指Offer 题解](./题解-剑指Offer.md) +- [Leetcode 题解](./题解-LeetCode.md) + +**专题** +--- +- A + - [数据结构](./专题-A-数据结构) + - [数据结构_Advanced](./专题-A-数据结构_Advanced) +- B + - [双指针](./专题-B-双指针) + - [动态规划](./专题-B-动态规划) +- C + - [排列组合](./专题-C-排列组合) + - [洗牌、采样、随机数](./专题-C-洗牌、采样、随机数) + +**备忘** +--- +- [IO 模板](./备忘-IO模板.md) +- [必备算法](./备忘-必备算法.md) + +Reference +--- +- f-zyj/[ACM 模板以及个人平时训练的代码](https://github.com/f-zyj/ACM) + > [ACM 在线模版 - 逐梦者](https://blog.csdn.net/f_zyj/article/details/51594851) - CSDN博客 +- [OI Wiki](https://github.com/24OI/OI-wiki/) + +Index +--- + + +- [排序](#排序) + - [稳定排序与不稳定排序](#稳定排序与不稳定排序) + - [堆排序-建堆的时间复杂度 `O(N)`](#堆排序-建堆的时间复杂度-on) +- [Trick](#trick) + - [时间复杂度](#时间复杂度) + - [敏感空间](#敏感空间) + + + + +# 排序 + +## 稳定排序与不稳定排序 +**稳定排序** +- 冒泡排序、插入排序、归并排序、基数排序 + +**不稳定排序** +- 选择排序、快速排序、希尔排序、堆排序 + +## 堆排序-建堆的时间复杂度 `O(N)` +- 堆排序的时间复杂度是 `O(NlogN)` 没有问题 +- 但是建堆的时间复杂度是 `O(N)` + > [为什么建立一个二叉堆的时间为O(N)而不是O(Nlog(N))?](https://www.zhihu.com/question/264693363/answer/291397356) - 知乎 + + + +# Trick + +## 时间复杂度 +> [百度:ACM trick](https://www.baidu.com/s?wd=ACM%20trick) +- 暴力枚举永远是第一个考虑的方法,但也有一些前提 + - 如果输入规模 `< 1e4`,那么可以尝试考虑 `O(n^2)` 的算法(两次循环暴力枚举);如果 `≥ 1e5`,那么可能就需要考虑 `O(nlogn)` 的算法了——这不是绝对的,也可以通过剪枝等操作加速。 + - [C++] string 拼接的速度依次为:1)使用 `stringstream`;2)使用 `append()`;3)使用 `s += c`;4)使用 `s = s + c`——如果有大量拼接操作,要避免使用最后一种方法。 + +## 敏感空间 +> [ACM Trick点&&常用操作记录(持续更新)(敏感空间)](https://blog.csdn.net/feynman1999/article/details/79588347) - CSDN博客 +- `long long` 上界:`2^63-1 = 9,223,372,036,854,775,807 ≈ 9.223372e+18` + - 阶乘:`20! = 2432902008176640000 ≈ 2.432902e+18` OK,`21!`超 + - Fibnacci 数列:`fib[92] = 7540113804746346429 ≈ 7.540114e+18` OK,`fib[93]` 超 + - Catalan 数: + - 整数划分: +- **数组大小**:如果内存限制为 2MB,那么最大可开辟的 int 数组为 `2*1024*1024/4 = 524288 ≈ 50,0000`,char 数组为 `2*1024*1024 = 2,097,152 ≈ 200,0000` + - 实际单个数组是远开不了那么大,比如 windows 默认的栈好像只有 1MB + - 在无法修改栈大小的情况下,可以使用 `new` 或 `malloc` 在**堆**上开辟内存 + ```C + int *pa = malloc(sizeof(int)*1024*1024); + int *pb = new int[1024*1024]; + ``` diff --git "a/C-\347\256\227\346\263\225/\344\270\223\351\242\230-A-\345\255\227\347\254\246\344\270\262.md" "b/C-\347\256\227\346\263\225/\344\270\223\351\242\230-A-\345\255\227\347\254\246\344\270\262.md" new file mode 100644 index 00000000..e3f1a32e --- /dev/null +++ "b/C-\347\256\227\346\263\225/\344\270\223\351\242\230-A-\345\255\227\347\254\246\344\270\262.md" @@ -0,0 +1,321 @@ +专题-字符串 +=== + +Index +--- + + +- [模式匹配 TODO](#模式匹配-todo) +- [进制转换](#进制转换) + - [10进制转任意进制](#10进制转任意进制) + - [任意进制转10进制](#任意进制转10进制) + - [长地址转短地址](#长地址转短地址) +- [功能函数](#功能函数) + - [`atoi()`](#atoi) +- [表达式转化(中缀,后缀,前缀)](#表达式转化中缀后缀前缀) + - [中缀转后缀](#中缀转后缀) + + + +## 模式匹配 TODO + +**模式匹配基本方法** +> [字符串模式匹配算法——BM、Horspool、Sunday、KMP、KR、AC算法 - 单车博客园](https://www.cnblogs.com/dancheblog/p/3517338.html) - 博客园 +- 双指针 +- KMP +- ... + +## 进制转换 + +### 10进制转任意进制 +```python +from string import digits, ascii_uppercase, ascii_lowercase + +Alphabet = digits + ascii_lowercase + ascii_uppercase +print(Alphabet) # "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +``` + +**递归方法** +```python +def ten2any(n, b=62): + """""" + assert b <= 62 + + n, index = divmod(n, b) # n = n // b, index = n % b + if n > 0: + return ten2any(n, b) + Alphabet[index] + else: + return Alphabet[index] +``` + +**迭代方法** +```python +def ten2any_2(n, b=62): + """""" + ret = "" + while n > 0: + n, index = divmod(n, b) + ret = Alphabet[index] + ret + + return ret +``` + +### 任意进制转10进制 + +**迭代方法** +```python +from string import digits, ascii_uppercase, ascii_lowercase + +Alphabet = digits + ascii_lowercase + ascii_uppercase +print(Alphabet) # "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + +def any2ten(s, base=62): + """""" + n = 0 + for i, c in enumerate(reversed(s)): # reversed(s) + index = Alphabet.index(c) + n += index * pow(base, i) + + return n +``` + +### 长地址转短地址 +> [短 URL 系统是怎么设计的?](https://www.zhihu.com/question/29270034/answer/46446911) - 知乎 + +**基本思路** +- 发号策略:给每一个收录的长地址,分配一个自增索引 +- 将分配的索引转换为一个 62 进制数(10数字+26大写字母+26小写字母) +- **注意**,发号机制不会判断是否重复 + +**重复长地址怎么处理?** +- 如果要求相同的长地址要对应唯一的短地址,那么唯一的方法就是维护一个映射表 +- 但是维护一个映射表可能比忽略重复需要更多的空间 +- 一个**折衷**的方法是——维护一个“最近”的长对短映射,比如采用一小时过期的机制来实现 LRU 淘汰 + - 当一个地址被频繁使用,那么它始终在这个 key-value 表中,总能返回当初生成那个短地址 + - 如果它使用并不频繁,那么长对短的 key 会过期,LRU 机制就会自动淘汰掉它 + +**如何保证发号器的大并发高可用?** +- 如果做成分布式的,那么多节点要保持同步加 1,多点同时写入,以 CAP 理论看,很难做到 +- 一个简单的处理方式是,使用多个发号器,以**奇偶**/**尾数**区分 + - 比如一个发单号,一个发双号; + - 或者实现 1000 个发号器,分别发尾号为 0 到 999 的号;每发一个号,每个发号器加 1000,而不是加 1 + + +## 功能函数 + +### `atoi()` +**功能简述** +- 将字符串(C风格)转换成整型; +- 会跳过前面的空格字符,直到遇上数字或正负号才开始转换; +- 如果遇到的第一个字符不是数字,则返回 0,并结束转化; +- 当遇到**非数字**或**结束符**('\0') 时结束转化,并将结果返回(整型) +- 如果发生溢出,则输出 INT_MAX 或 INT_MIN; +- 内置 atoi 不会处理 NULL 指针 + +**合法样例**: +``` +"123" -> 123 +"+123" -> 123 +"-123" -> -123 +"123abc" -> 123 +" 123abc" -> 123 +"a123" -> 0 +``` + +**核心代码(C++)** +```C++ +while (*p >= '0' && *p <= '9') { + ret = ret * 10 + (*p - '0'); + p++; +} +``` +- 除了核心代码,更重要的是**异常处理**和**溢出判断** + +**完整代码** +```C++ +int atoi_my(const char* const cs) { + if (cs == nullptr) return 0; + + int ret = 0; + auto *p = cs; // cs 为常指针 + + // 跳过前面的空格 + while (isspace(*p)) p++; + + // 判断正负 + int sign = 1; // 默认正数 + if (*p == '-') sign = -1; + if (*p == '-' || *p == '+') p++; + + // 核心代码:循环转换整数(加入溢出判断) + int tmp; // 保存临时结果,用于溢出判断 + while (*p >= '0' && *p <= '9') { + tmp = ret * 10 + (*p - '0'); + if (tmp / 10 != ret) { // 溢出判断 + return sign > 0 ? INT_MAX : INT_MIN; + } + ret = tmp; + p++; + } + // 核心代码(无溢出判断) + //while (*p >= '0' && *p <= '9') { + // ret = ret * 10 + (*p - '0'); + // p++; + //} + + return sign * ret; +} +``` + + +## 表达式转化(中缀,后缀,前缀) +> [前缀、中缀、后缀表达式](https://blog.csdn.net/antineutrino/article/details/6763722) - CSDN博客 + +**为什么要把中缀表达式转化为后缀,前缀?** +- 计算机无法直接计算带有括号,以及区分优先级的表达式,或者说很难计算。 +- 使用后缀,前缀,消除了括号和优先级。 + +**如何计算后缀,前缀表达式?** +- **手工求法**: + > [中缀表达式与前、后缀表达式转化简单的技巧[转] - Hslim](https://www.cnblogs.com/Hslim/p/5008460.html) - 博客园 + - 示例:`a+b*c-(d+e)` + - 第一步:按照运算符的优先级对所有的运算单位加括号 + ``` + ((a+(b*c))-(d+e)) + ``` + - 第二步:转换前缀与后缀表达式 + ``` + 后缀:把运算符号移动到对应的括号后面 + ((a(bc)* )+ (de)+ )- + 把括号去掉: + abc*+de+- + 前缀:把运算符号移动到对应的括号前面 + -( +(a *(bc)) +(de)) + 把括号去掉: + -+a*bc+de + ``` +- **计算机方法**: + - [中缀转后缀](#中缀转后缀) + +### 中缀转后缀 + +**思路** +- 从左到右遍历中缀表达式,遇到**操作数**则输出;遇到操作符,若当前操作符的**优先级大于**栈顶操作符优先级,进栈;否则,弹出栈顶操作符,当前操作符进栈。(这只是一段比较**粗糙**的描述,更多细节请参考链接或下面的源码) + > [中、前、后缀表达式](https://blog.csdn.net/lin74love/article/details/65631935) - CSDN博客 + +**C++**(未测试) + +```C++ +#include +#include +#include +#include +#include +#include +using namespace std; + +//set l1{ '+', '-' }; +//set l2{ '*', '/' }; +// +//vector> l{ l1, l2 }; + +int get_level(char c) { + switch (c) { + case '+': + case '-': + return 1; + case '*': + case '/': + return 2; + //case '(': + // return 3; + default: + return -1; + } +} + +string infix2suffix(const string& s) { + stack tmp; // 符号栈 + queue ans; // 必须使用 string 队列,因为可能存在多位数字 + //stringstream ret; // 用字符流模拟队列 + + bool num_flag = false; // 用于判断数字的末尾 + //初始设为 false 是为了避免第一个字符是括号 + //int v = 0; // 保存数值 + string v{ "" }; // 用字符串保存更好,这样还能支持字母形式的表达式 + for (auto c : s) { + // 处理数字 + if (isalnum(c)) { // 处理多位数字 + v.append(string(1, c)); // 注意,char 字符不能直接转 string + num_flag = true; + } + else { + if (num_flag) { // 因为可能存在多位数字,所以数字需要等遇到第一个非数字字符才入队 + ans.push(v); + //ret << v << ' '; + v.clear(); + num_flag = false; + } + + // 处理运算符的过程 + if (c == ')') { // 如果遇到右括号,则依次弹出栈顶符号,直到遇到**第一个**左括号并弹出(坑点 1:可能存在连续的左括号) + while (!tmp.empty()) { + if (tmp.top() == '(') { + tmp.pop(); + break; + } + ans.push(string(1, tmp.top())); + //ret << tmp.top() << ' '; + tmp.pop(); + } + } // 注意这两个判断的顺序(坑点 2:右括号是始终不用入栈的,所以应该先处理右括号) + else if (tmp.empty() || tmp.top() == '(' || c == '(') { // 如果符号栈为空,或栈顶为 ')',或遇到左括号 + tmp.push(c); // 则将该运算符入栈 + } + else { + while (!tmp.empty() && get_level(tmp.top()) >= get_level(c)) { // 如果栈顶元素的优先级大于等于当前运算符,则弹出 + if (tmp.top() == '(') // (坑点 3:左括号的优先级是大于普通运算符的,但它不应该在这里弹出) + break; + ans.push(string(1, tmp.top())); + //ret << tmp.top() << ' '; + tmp.pop(); + } + tmp.push(c); + } + } + } + + if (num_flag) { // 表达式的最后一个数字入栈 + ans.push(v); + //ret << v << ' '; + } + + while (!tmp.empty()) { // 字符串处理完后,依次弹出栈中的运算符 + if (tmp.top() == '(') // 这个判断好像是多余的 + tmp.pop(); + ans.push(string(1, tmp.top())); + //ret << tmp.top() << ' '; + tmp.pop(); + } + + //return ret.str(); + + stringstream ret; + while (!ans.empty()) { + ret << ans.front() << ' '; + ans.pop(); + } + return ret.str(); +} + +void solve() { + // 只测试了以下样例,如果有反例请告诉我 + + cout << infix2suffix("12+(((23)+3)*4)-5") << endl; // 12 23 3 + 4 * + 5 - + cout << infix2suffix("1+1+1") << endl; // 1 1 + 1 + + cout << infix2suffix("(1+1+1)") << endl; // 1 1 + 1 + + cout << infix2suffix("1+(2-3)*4+10/5") << endl; // 1 2 3 - 4 * + 10 5 / + + cout << infix2suffix("az-(b+c/d)*e") << endl; // az b c d / + e * - +} +``` diff --git "a/C-\347\256\227\346\263\225/\344\270\223\351\242\230-A-\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/C-\347\256\227\346\263\225/\344\270\223\351\242\230-A-\346\225\260\346\215\256\347\273\223\346\236\204.md" new file mode 100644 index 00000000..4588c03a --- /dev/null +++ "b/C-\347\256\227\346\263\225/\344\270\223\351\242\230-A-\346\225\260\346\215\256\347\273\223\346\236\204.md" @@ -0,0 +1,1349 @@ +专题-数据结构 +=== + +- 数据结构相关基本是**现场面试**中出现频率最高的问题。 + - 因为现场面试的时间限制,更难的问题需要大量的思考时间,所以一般只要求需要阐述思路(比如动态规划); + - 而**数据结构**相关的问题,因为有很强的先验知识,通常要求**手写代码**。 +- 本专题只收录**基础数据结构相关问题**,不包括高级数据结构及数据结构的设计,比如线段树或者 LRU 缓存,这些问题可以参考[数据结构_Advanced](./专题-A-数据结构_Advanced)。 + +Index +--- + + +- [二叉树](#二叉树) + - [二叉树的深度](#二叉树的深度) + - [二叉树的宽度](#二叉树的宽度) + - [二叉树最大宽度(LeetCode)](#二叉树最大宽度leetcode) + - [二叉树中的最长路径](#二叉树中的最长路径) + - [判断平衡二叉树 TODO](#判断平衡二叉树-todo) + - [判断树 B 是否为树 A 的子结构](#判断树-b-是否为树-a-的子结构) + - [利用前序和中序重建二叉树](#利用前序和中序重建二叉树) + - [二叉树的序列化与反序列化](#二叉树的序列化与反序列化) + - [最近公共祖先](#最近公共祖先) + - [如果树是二叉搜索树](#如果树是二叉搜索树) + - [如果树的节点中保存有指向父节点的指针](#如果树的节点中保存有指向父节点的指针) + - [如果只是普通的二叉树](#如果只是普通的二叉树) + - [获取节点的路径](#获取节点的路径) +- [链表](#链表) + - [旋转链表(Rotate List)](#旋转链表rotate-list) + - [反转链表](#反转链表) + - [合并排序链表](#合并排序链表) + - [两个链表的第一个公共节点](#两个链表的第一个公共节点) + - [链表排序](#链表排序) + - [链表快排](#链表快排) + - [链表归并](#链表归并) + - [链表插入排序](#链表插入排序) + - [链表选择排序](#链表选择排序) + - [链表冒泡排序](#链表冒泡排序) +- [二维数组](#二维数组) + - [二分查找](#二分查找) + - [搜索二维矩阵 1](#搜索二维矩阵-1) + - [搜索二维矩阵 2](#搜索二维矩阵-2) + - [打印二维数组](#打印二维数组) + - [回形打印](#回形打印) + - [蛇形打印](#蛇形打印) +- [堆](#堆) + - [堆的调整(自上而下)](#堆的调整自上而下) +- [栈](#栈) + - [用两个栈模拟队列](#用两个栈模拟队列) + + + +# 二叉树 + +## 二叉树的深度 +> [二叉树的深度](https://www.nowcoder.com/practice/435fb86331474282a3499955f0a41e8b?tpId=13&tqId=11191&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - 牛客 + +**C++** +```C++ +class Solution { +public: + int TreeDepth(TreeNode* root) { + if (root == NULL) return 0; + + return max(TreeDepth(root->left), TreeDepth(root->right)) + 1; + } +}; +``` + +## 二叉树的宽度 + +**思路** +- 层序遍历(队列) + +**C++** +```C++ +class Solution { +public: + int widthOfBinaryTree(TreeNode* root) { + if (root == nullptr) + return 0; + + queue Q; + Q.push(root); + + int ans = 1; + while(!Q.empty()) { + int cur_w = Q.size(); // 当前层的宽度 + ans = max(ans, cur_w); + + for (int i=0; ileft) + Q.push(p->left); + if (p->right) + Q.push(p->right); + } + } + + return ans; + } +}; +``` + +### 二叉树最大宽度(LeetCode) +> LeetCode - [662. 二叉树最大宽度](https://leetcode-cn.com/problems/maximum-width-of-binary-tree/description/) + +**问题描述** +``` +给定一个二叉树,编写一个函数来获取这个树的最大宽度。 +树的宽度是所有层中的最大宽度。 +这个二叉树与满二叉树(full binary tree)结构相同,但一些节点为空。 + +每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的null节点也计入长度)之间的长度。 + +示例 1: + 输入: + + 1 + / \ + 3 2 + / \ \ + 5 3 9 + + 输出: 4 + 解释: 最大值出现在树的第 3 层,宽度为 4 (5,3,null,9)。 + +示例 2: + 输入: + + 1 + / + 3 + / \ + 5 3 + + 输出: 2 + 解释: 最大值出现在树的第 3 层,宽度为 2 (5,3)。 +``` + +**思路** +- 本题在二叉树宽度的基础上加入了满二叉树的性质,即每层都有 2 ^ (n-1)个节点。某节点的左孩子的标号是2n, 右节点的标号是2n + 1。 +- **注**:如果在循环中会增删容器中的元素,则不应该在 `for` 循环中使用 `size()` 方法,该方法的返回值会根据容器的内容**动态改变**。 + +**C++** +``` +class Solution { +public: + int widthOfBinaryTree(TreeNode* root) { + if (root == nullptr) + return 0; + + deque> Q; // 记录节点及其在满二叉树中的位置 + Q.push_back({ root, 1 }); + + int ans = 0; + while (!Q.empty()) { + int cur_n = Q.size(); + int cur_w = Q.back().second - Q.front().second + 1; // 当前层的宽度 + ans = max(ans, cur_w); + + //for (int i = 0; ileft != nullptr) + Q.push_back({ p.first->left, p.second * 2 }); + if (p.first->right != nullptr) + Q.push_back({ p.first->right, p.second * 2 + 1 }); + } + } + + return ans; + } +}; +``` + + +## 二叉树中的最长路径 + +**思路** +- 基于[二叉树的深度](#二叉树的深度) +- 对任一子树而言,则经过该节点的一条最长路径为其`左子树的深度 + 右子树的深度 + 1` +- 遍历树中每个节点的最长路径,其中最大的即为整个树的最长路径 + > 为什么最长路径不一定是经过根节点的那条路径? + +## 判断平衡二叉树 TODO + + +## 判断树 B 是否为树 A 的子结构 +> [树的子结构](https://www.nowcoder.com/practice/6e196c44c7004d15b1610b9afca8bd88?tpId=13&tqId=11170&tPage=1&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - 牛客 + +**题目描述** +``` +输入两棵二叉树A,B,判断B是不是A的子结构。 +约定空树不是任意一个树的子结构。 +``` +- 图示 +
+ +**思路** +- 递归 +- 有两个递归的点:一、递归寻找树 A 中与树 B 根节点相同的子节点;二、递归判断子结构是否相同 + +**Code**(递归) +```C++ +class Solution { +public: + bool HasSubtree(TreeNode* p1, TreeNode* p2) { + if (p1 == nullptr || p2 == nullptr) // 约定空树不是任意一个树的子结构 + return false; + + return isSubTree(p1, p2) // 判断子结构是否相同 + || HasSubtree(p1->left, p2) // 递归寻找树 A 中与树 B 根节点相同的子节点 + || HasSubtree(p1->right, p2); + } + + bool isSubTree(TreeNode* p1, TreeNode* p2) { + if (p2 == nullptr) return true; // 注意这两个判断的顺序 + if (p1 == nullptr) return false; + + if (p1->val == p2->val) + return isSubTree(p1->left, p2->left) // 递归判断左右子树 + && isSubTree(p1->right, p2->right); + else + return false; + } +}; +``` + + +## 利用前序和中序重建二叉树 +> [重建二叉树](https://www.nowcoder.com/practice/8a19cbe657394eeaac2f6ea9b0f6fcf6?tpId=13&tqId=11157&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - 牛客 + +**题目描述** +``` +根据二叉树的前序遍历和中序遍历的结果,重建出该二叉树。 +假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 +``` + +**思路** +- 前序遍历的第一个值为根节点的值,使用这个值将中序遍历结果分成两部分,左部分为左子树的中序遍历结果,右部分为右子树的中序遍历的结果。 +- 根据左右子树的长度,可以从前序遍历的结果中划分出左右子树的前序遍历结果 +- 接下来就是递归过程 +- **注意**:必须序列中的值不重复才可以这么做 +- **示例** + ``` + 前序 + 1,2,4,7,3,5,6,8 + 中序 + 4,7,2,1,5,3,8,6 + + 第一层 + 根节点 1 + 根据根节点的值(不重复),划分中序: + {4,7,2} 和 {5,3,8,6} + 根据左右子树的长度,划分前序: + {2,4,7} 和 {3,5,6,8} + 从而得到左右子树的前序和中序 + 左子树的前序和中序:{2,4,7}、{4,7,2} + 右子树的前序和中序:{3,5,6,8}、{5,3,8,6} + + 第二层 + 左子树的根节点 2 + 右子树的根节点 3 + ... + ``` + +**Code**(Python) +> C++ 版本 > 题解-剑指Offer/[重建二叉树](./题解-剑指Offer#7-重建二叉树) +```Python +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None +class Solution: + # 返回构造的TreeNode根节点 + def reConstructBinaryTree(self, pre, tin): + if len(pre) < 1: + return None + + root = TreeNode(pre[0]) + index = tin.index(root.val) # 注意值不重复,才可以这么做 + + root.left = self.reConstructBinaryTree(pre[1: 1+index], tin[:index]) + root.right = self.reConstructBinaryTree(pre[1+index:], tin[index+1:]) + + return root +``` + + +## 二叉树的序列化与反序列化 +> [序列化二叉树](https://www.nowcoder.com/practice/cf7e25aa97c04cc1a68c8f040e71fb84?tpId=13&tqId=11214&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder + +**题目描述** +``` +请实现两个函数,分别用来序列化和反序列化二叉树。 +接口如下: + + char* Serialize(TreeNode *root); + TreeNode* Deserialize(char *str); + +空节点用 '#' 表示,节点之间用空格分开 +``` +- 比如中序遍历就是一个二叉树序列化 +- 反序列化要求能够通过序列化的结果还原二叉树 + +**思路** +- 前序遍历 + +**Code** +```C++ +class Solution { + stringstream ss_fw; + stringstream ss_bw; +public: + char* Serialize(TreeNode *root) { + + dfs_fw(root); + + char ret[1024]; + return strcpy(ret, ss_fw.str().c_str()); + // return (char*)ss.str().c_str(); // 会出问题,原因未知 + } + + void dfs_fw(TreeNode *node) { + if (node == nullptr) { + ss_fw << "#"; + return; + } + ss_fw << node->val; + + ss_fw << " "; + dfs_fw(node->left); + + ss_fw << " "; + dfs_fw(node->right); + } + + TreeNode* Deserialize(char *str) { + if (strlen(str) < 1) return nullptr; + + ss_bw << str; + return dfs_bw(); + } + + TreeNode* dfs_bw() { + if (ss_bw.eof()) + return nullptr; + + string val; // 因为 "#",用 int 或 char 接收都会有问题 + ss_bw >> val; + + if (val == "#") + return nullptr; + + TreeNode* node = new TreeNode{ stoi(val) }; + node->left = dfs_bw(); + node->right = dfs_bw(); + return node; + } +}; +``` + +## 最近公共祖先 +> 《剑指 Offer》 7.2 案例二 + +**问题描述** +``` +给定一棵树的根节点 root,和其中的两个节点 p1 和 p2,求它们的最小公共父节点。 +``` + +### 如果树是二叉搜索树 +- 找到第一个满足 `p1 < root < p2` 的根节点,即为它们的最小公共父节点; +- 如果寻找的过程中,没有这样的 `root`,那么 `p1` 和 `p2` 的最小公共父节点必是它们之一,此时遍历到 `p1` 或 `p2` 就返回。 + +### 如果树的节点中保存有指向父节点的指针 +- 问题等价于求两个链表的**第一个公共节点** + > [两个链表的第一个公共节点](#两个链表的第一个公共节点) + +### 如果只是普通的二叉树 +> [236. 二叉树的最近公共祖先](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/description/) - LeetCode + +- 利用两个辅助链表/数组,保存分别到 `p1` 和 `p2` 的路径; + > [获取节点的路径](#获取节点的路径) +- 则 `p1` 和 `p2` 的最小公共父节点就是这两个链表的**最后一个公共节点** +- **C++** + ```C++ + class Solution { + bool getPath(TreeNode* root, TreeNode* p, deque& path) { + if (root == nullptr) + return false; + + path.push_back(root); + if (p == root) + return true; + + bool found = false; + if (!found) + found = getPath(root->left, p, path); + if (!found) + found = getPath(root->right, p, path); + + if (!found) + path.pop_back(); + + return found; + } + + public: + TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { + + deque path_p; + auto found_p = getPath(root, p, path_p); + deque path_q; + auto found_q = getPath(root, q, path_q); + + TreeNode* ret = root; + if (found_p && found_q) { + auto it_p = path_p.begin(); + auto it_q = path_q.begin(); + + while (it_p != path_p.end() && it_q != path_q.end()) { + if (*it_p != *it_q) + return ret; + + ret = *it_p; + it_p++, it_q++; + } + return ret; + } + + return nullptr; + } + }; + ``` + + +## 获取节点的路径 +**二叉树** +```C++ +// 未测试 +#include + +bool getPath(TreeNode* root, TreeNode* p, deque& path) { + if (root == nullptr) + return false; + + path.push_back(root); + if (p == root) + return true; + + bool found = false; + if (!found) + found = getPath(root->left, p, path); + if (!found) + found = getPath(root->right, p, path); + + if (!found) + path.pop_back(); + + return found; +} +``` + +**非二叉树** +```C++ +// 未测试 +#include +struct TreeNode { + int val; + std::vector children; +}; + +bool getPath(const TreeNode* root, const TreeNode* p, deque& path) { + if (root == nullptr) + return false; + + path.push_back(root); + if (root == p) + return true; + + bool found = false; + auto i = root->children.begin(); // 顺序遍历每个子节点 + while (!found && i < root->children.end()) { + found = GetNodePath(*i, p, path); + ++i; + } + + if (!found) // 如果没有找到就,说明当前节点不在路径内,弹出 + path.pop_back(); + + return found; +} +``` + +# 链表 + +## 旋转链表(Rotate List) +> LeetCode/[61. 旋转链表](https://leetcode-cn.com/problems/rotate-list/description/) + +**问题描述** +``` +给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。 + +示例 1: + 输入: 1->2->3->4->5->NULL, k = 2 + 输出: 4->5->1->2->3->NULL + 解释: + 向右旋转 1 步: 5->1->2->3->4->NULL + 向右旋转 2 步: 4->5->1->2->3->NULL +示例 2: + 输入: 0->1->2->NULL, k = 4 + 输出: 2->0->1->NULL + 解释: + 向右旋转 1 步: 2->0->1->NULL + 向右旋转 2 步: 1->2->0->NULL + 向右旋转 3 步: 0->1->2->NULL + 向右旋转 4 步: 2->0->1->NULL +``` + +**思路** +- 双指针 `l, r` 记录两个位置,其中 `l` 指向倒数第 `k+1` 个节点,`r` 指向最后一个非空节点; +- 然后将 `r` 指向头结点 `h`,`h` 指向 `l` 的下一个节点,最后断开 `l` 与下一个节点; +- 注意 `k` 可能大于链表的长度,此时可能需要遍历两次链表 + +**代码 1** +- 比较直观的写法,代码量稍大 +```python +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def rotateRight(self, h, k): + """ + :type h: ListNode + :type k: int + :rtype: ListNode + """ + if not h or k == 0: + return h + + n = 1 # 记录链表的长度,因为只遍历到最后一个非空节点,所以从 1 开始 + l = h + r = h # tail + while r.next is not None and k > 0: + k -= 1 + n += 1 + r = r.next + + # print(k, n) + if k > 0: + k -= 1 # 这里要先减 1,因为 n 是从 1 开始计数的 + k = k % n + r = h + while k > 0: + k -= 1 + r = r.next + + # 找到倒数第 k 个节点 + while r.next is not None: + l = l.next + r = r.next + + r.next = h + h = l.next + l.next = None + + return h +``` + +**代码 2** +- 代码量少一点,但是遍历的长度要多一点。 +```python +class Solution: + def rotateRight(self, h, k): + """ + :type h: ListNode + :type k: int + :rtype: ListNode + """ + if not h or k == 0: + return h + + n = 1 # 记录链表的长度,因为只遍历到最后一个非空节点,所以从 1 开始 + r = h # tail + while r.next is not None: + n += 1 + r = r.next + + r.next = h # 构成环 + + k %= n + t = n - k + while t > 0: + r = r.next + t -= 1 + + h = r.next + r.next = None # 断开 链表 + + return h +``` + +## 反转链表 +> [反转链表](https://www.nowcoder.com/practice/75e878df47f24fdc9dc3e400ec6058ca?tpId=13&tqId=11168&tPage=1&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - 牛客 + +**题目描述** +``` +输入一个链表,反转链表后,输出新链表的表头。 +``` +- 要求:**不使用额外空间** + +**思路** +- 辅助图示思考 + +**Code**(迭代) +```C++ +class Solution { +public: + ListNode * ReverseList(ListNode* head) { + if (head == nullptr) + return nullptr; + + ListNode* cur = head; // 当前节点 + ListNode* pre = nullptr; // 前一个节点 + ListNode* nxt = cur->next; // 下一个节点 + cur->next = nullptr; // 断开当前节点及下一个节点(容易忽略的一步) + while (nxt != nullptr) { + pre = cur; // 把前一个节点指向当前节点 + cur = nxt; // 当前节点向后移动 + nxt = nxt->next; // 下一个节点向后移动 + cur->next = pre; // 当前节点的下一个节点指向前一个节点 + } + return cur; + } +}; +``` + +**Code**(递归) +```C++ +class Solution { +public: + ListNode * ReverseList(ListNode* head) { + if (head == nullptr || head->next == nullptr) + return head; + + auto nxt = head->next; + head->next = nullptr; // 断开当前节点及下一个节点 + auto new_head = ReverseList(nxt); + nxt->next = head; + return new_head; + } +}; +``` + +## 合并排序链表 +> [合并两个排序的链表](https://www.nowcoder.com/practice/d8b6b4358f774294a89de2a6ac4d9337?tpId=13&tqId=11169&tPage=1&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - 牛客 + +**问题描述** +``` +输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。 +``` + +**迭代** +```C++ +class Solution { +public: + ListNode* Merge(ListNode* p1, ListNode* p2) { + if (p1 == nullptr) return p2; + if (p2 == nullptr) return p1; + + // 选择头节点 + ListNode* head = nullptr; + if (p1->val <= p2->val) { + head = p1; + p1 = p1->next; + } else { + head = p2; + p2 = p2->next; + } + + auto cur = head; + while (p1 && p2) { + if (p1->val <= p2->val) { + cur->next = p1; + p1 = p1->next; + } else { + cur->next = p2; + p2 = p2->next; + } + cur = cur->next; + } + + // 别忘了拼接剩余部分 + // if (p1) cur->next = p1; + // if (p2) cur->next = p2; + if (p1) { + cur->next = p1; + } else if (p2) { + cur->next = p2; + } else { + cur->next = nullptr; + } + + return head; + } +}; +``` + +**递归** +```C++ +class Solution { +public: + ListNode* Merge(ListNode* p1, ListNode* p2){ + if (!p1) return p2; + if (!p2) return p1; + + if (p1->val <= p2->val) { + p1->next = Merge(p1->next, p2); + return p1; + } else { + p2->next = Merge(p1, p2->next); + return p2; + } + } +}; +``` + +## 两个链表的第一个公共节点 + +**思路 1** +- 先求出两个链表的长度 `l1` 和 `l2`,然后让长的链表先走 `|l1-l2|` 步,此时两个指针距离第一个公共节点的距离相同,再走相同的步数即可在第一个公共节点相遇 +- 时间复杂度 `O(m + n)` +- 代码(未测试) + ```C++ + ListNode* FindFirstCommonNode(ListNode *pHead1, ListNode *pHead2) { + ListNode *back1 = nullptr; + int l1 = GetListLength(pHead1, back1); // 返回链表的长度及尾节点指针 + ListNode *back2 = nullptr; + int l2 = GetListLength(pHead2, back2); + + if (back1 != back2) // 没有公共节点 + return nullptr; + + ListNode *p1 = pHead1; + ListNode *p2 = pHead2; + if (l1 > l2) { + int d = l1 - l2; + while (d--) + p1 = p1->next; + while (p1 != p2) { + p1 = p1->next; + p2 = p2->next; + } + } else { + int d = l2 - l1; + while (d--) + p2 = p2->next; + while (p1 != p2) { + p1 = p1->next; + p2 = p2->next; + } + } + return p1; + ``` + +**思路 2** +- 两个指针同时开始遍历, +- 当其中一个指针到达尾节点时,转到另一个链表继续遍历; +- 当另一个指针也到达尾节点时,也转到另一个链表继续遍历; +- 此时两个指针距离第一个公共节点的距离相同,再走相同的步数即可在第一个公共节点相遇 +- 时间复杂度 `O(m + n)` +- 代码(未测试) + ```C++ + ListNode* FindFirstCommonNode(ListNode *pHead1, ListNode *pHead2) { + ListNode *back1 = nullptr; + GetListLength(pHead1, back1); // 获取尾节点指针 + ListNode *back2 = nullptr; + GetListLength(pHead2, back2); + + if (back1 != back2) // 没有公共节点 + return nullptr; + + ListNode *p1 = pHead1; + ListNode *p2 = pHead2; + while(p1!=p2){ + p1 = (p1==NULL ? pHead2 : p1->next); // 游标到达尾部后,转到另一条链表 + p2 = (p2==NULL ? pHead1 : p2->next); + } + return p1; + ``` + +## 链表排序 +> [链表排序(冒泡、选择、插入、快排、归并、希尔、堆排序) - tenos](https://www.cnblogs.com/TenosDoIt/p/3666585.html) - 博客园 + +### 链表快排 +> LeetCode/[148. 排序链表](https://leetcode-cn.com/problems/sort-list/description/) + +**问题描述** +``` +在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。 + +示例 1: + 输入: 4->2->1->3 + 输出: 1->2->3->4 +示例 2: + 输入: -1->5->3->4->0 + 输出: -1->0->3->4->5 +``` + +**思路** +- 与数组快排几乎一致,只是 partition 操作需要从左向右遍历; +- 因为涉及指针,还是用 C++ 写比较方便; +- 另外 LeetCode 讨论区反映 Python 可能会超时; +- **时间复杂度**:最好 `O(NlogN)`,最坏 `O(N^2)` + +**代码 1 - 只交换节点内的值** +- 参考数组快排中的写法,这里选取**第一个元素**作为枢纽 +```C++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode(int x) : val(x), next(NULL) {} + * }; + */ + +class Solution { + + void qsort(ListNode* lo, ListNode* hi) { + if (lo == hi || lo->next == hi) // 至少有一个元素 + return; + + auto mid = partition(lo, hi); + qsort(lo, mid); + qsort(mid->next, hi); + } + + ListNode* partition(ListNode* lo, ListNode* hi) { // 链表范围为 [lo, hi) + int key = lo->val; // 以 low 作为枢纽 + auto mid = lo; + for (auto i=lo->next; i != hi; i = i->next) { + if (i->val < key) { + mid = mid->next; + swap(i->val, mid->val); // 交换节点内的值 + } + } + + swap(lo->val, mid->val); // 交换 low 与 mid + + return mid; + } + +public: + ListNode* sortList(ListNode* head) { + if (head == nullptr || head->next == nullptr) + return head; + + qsort(head, nullptr); // 传入首尾区间,是一个半开区间 + return head; + } +}; +``` + +**代码 2 - 交换节点** +- ~~需要要重写 `swap`,而且注意,因为是链表,所以传入的节点应该是需要交换节点的前置节点~~ +- 依然选择第一个节点作为枢纽;然后把小于枢纽的节点放到一个链中,不小于枢纽的及节点放到另一个链中,最后拼接两条链以及枢纽。 +```C++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode(int x) : val(x), next(NULL) {} + * }; + */ + +class Solution { + void qsort(ListNode* pre, ListNode* lo, ListNode* hi) { // 链表范围为 [lo, hi), pre 为 lo 的前置节点 + if (lo == hi || lo->next == hi) // 至少有一个元素 + return; + + auto mid = partition(pre, lo, hi); + qsort(pre, pre->next, mid); // qsort(pre, lo, mid); + qsort(mid, mid->next, hi); + } + + ListNode* partition(ListNode* pre, ListNode* lo, ListNode* hi) { + int key = lo->val; + auto mid = lo; // 不是必须的,直接使用 lo 也可以 + + ListNode ll(0), rr(0); // 创建两个新链表 + auto l = &ll, r = &rr; // ListNode *l = &ll, *r = &rr; + for (auto i=lo->next; i != hi; i = i->next) { // i 从 lo 的下一个节点开始遍历,因为 lo 是枢纽不参与遍历 + if (i->val < key) { + l = l->next = i; // python 中不能这么写 + } else { + r = r->next = i; // python 中不能这么写 + } + } + + // 拼接 + r->next = hi; + l->next = mid; // 这里的 mid 实际上就是 lo,即 l->next = lo + mid->next = rr.next; + pre->next = ll.next; + + return mid; // 返回中枢 + } + +public: + ListNode* sortList(ListNode* head) { + if(head == nullptr || head->next == nullptr) + return head; + + ListNode pre(0); // 设置一个新的头结点 + pre.next = head; + qsort(&pre, head, nullptr); + + return pre.next; + } +}; +``` + +### 链表归并 +> LeetCode/[148. 排序链表](https://leetcode-cn.com/problems/sort-list/description/) + +**问题描述** +``` +在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。 + +示例 1: + 输入: 4->2->1->3 + 输出: 1->2->3->4 +示例 2: + 输入: -1->5->3->4->0 + 输出: -1->0->3->4->5 +``` + +**思路** +- 用快慢指针的方法找到链表中间节点,然后递归的对两个子链表排序,把两个排好序的子链表合并成一条有序的链表 +- 归并排序比较适合链表,它可以保证了最好和最坏时间复杂度都是 `O(NlogN)`,而且它在数组排序中广受诟病的空间复杂度在链表排序中也从O(n)降到了 `O(1)` + - 因为链表快排中只能使用第一个节点作为枢纽,所以不能保证时间复杂度 +- 还是使用 C++ +- **时间复杂度**:最好/最坏 `O(NlogN)` + +**C++** +```C++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode(int x) : val(x), next(NULL) {} + * }; + */ + +class Solution { + ListNode* merge(ListNode *h1, ListNode *h2) { // 排序两个链表 + if (h1 == nullptr) return h2; + if (h2 == nullptr) return h1; + + ListNode* h; // 合并后的头结点 + if (h1->val < h2->val) { + h = h1; + h1 = h1->next; + } else { + h = h2; + h2 = h2->next; + } + + ListNode* p = h; + while (h1 && h2) { + if (h1->val < h2->val) { + p->next = h1; + h1 = h1->next; + } else { + p->next = h2; + h2 = h2->next; + } + p = p->next; + } + + if (h1) p->next = h1; + if (h2) p->next = h2; + + return h; + } + +public: + ListNode* sortList(ListNode* h) { + if (h == nullptr || h->next == nullptr) + return h; + + auto f = h, s = h; // 快慢指针 fast & slow + while (f->next && f->next->next) { + f = f->next->next; + s = s->next; + } + f = s->next; // 中间节点 + s->next = nullptr; // 断开 + + h = sortList(h); // 前半段排序 + f = sortList(f); // 后半段排序 + + return merge(h, f); + } +}; +``` + +### 链表插入排序 +> LeetCode/[147. 对链表进行插入排序](https://leetcode-cn.com/problems/insertion-sort-list/description/) +>> 注意:以下代码在[148. 排序链表](https://leetcode-cn.com/problems/sort-list/description/)也能 AC(要求时间复杂度 `O(NlogN)`) + +**问题描述** +``` +对链表进行插入排序。 + +插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。 +每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。 +重复直到所有输入数据插入完为止。 + +示例 1: + 输入: 4->2->1->3 + 输出: 1->2->3->4 +示例 2: + 输入: -1->5->3->4->0 + 输出: -1->0->3->4->5 +``` + +
+ +- 插入排序的动画演示如上。从第一个元素开始,该链表可以被认为已经部分排序(用黑色表示)。 +每次迭代时,从输入数据中移除一个元素(用红色表示),并原地将其插入到已排好序的链表中。 + +**思路** +- 见代码注释 +- **时间复杂度**:最好/最坏 `O(N^2)` + +**代码 1 - 非原地** +- 实际上,对链表来说,不存在是否原地的问题,不像数组 +- 这里所谓的非原地是相对数组而言的,因此下面的代码只针对链表,不适用于数组。 +```C++ +class Solution { +public: + ListNode* insertionSortList(ListNode* h) { + if (h == nullptr || h->next == nullptr) + return h; + + // 因为是链表,所以可以重新开一个新的链表来保存排序好的部分; + // 不存在空间上的问题,这一点不像数组 + auto H = new ListNode(0); + + auto pre = H; + auto cur = h; + ListNode* nxt; + while (cur) { + while (pre->next && pre->next->val < cur->val) { + pre = pre->next; + } + + nxt = cur->next; // 记录下一个要遍历的节点 + // 把 cur 插入 pre 和 pre->next 之间 + cur->next = pre->next; + pre->next = cur; + + // 重新下一轮 + pre = H; + cur = nxt; + } + + h = H->next; + delete H; + return h; + } +}; +``` + +**代码 2 - 原地** +- 即不使用新链表,逻辑与数组一致; +- 此时链表拼接的逻辑会复杂一些 +```C++ +class Solution { +public: + ListNode* insertionSortList(ListNode* h) { + if (h == nullptr || h->next == nullptr) + return h; + + auto beg = new ListNode(0); + beg->next = h; + auto end = h; // (beg, end] 指示排好序的部分 + + auto p = h->next; // 当前待排序的节点 + while (p) { + auto pre = beg; + auto cur = beg->next; // p 将插入到 pre 和 cur 之间 + while (cur != p && p->val >= cur->val) { + cur = cur->next; + pre = pre->next; + } + + if (cur == p) { + end = p; + } else { + end->next = p->next; + p->next = cur; + pre->next = p; + } + p = end->next; + } + + h = beg->next; + delete beg; + return h; + } +}; +``` + +### 链表选择排序 +> LeetCode/[147. 对链表进行插入排序](https://leetcode-cn.com/problems/insertion-sort-list/description/) +>> 注意:以下代码在[148. 排序链表](https://leetcode-cn.com/problems/sort-list/description/)也能 AC(要求时间复杂度 `O(NlogN)`) + +**思路** +- 见代码注释 +- **时间复杂度**:最好/最坏 `O(N^2)` + +**C++** +```C++ +class Solution { +public: + ListNode* sortList(ListNode* h) { + if (h == nullptr || h->next == nullptr) + return h; + + auto H = new ListNode(0); // 为了操作方便,添加一个头结点 + H->next = h; + + auto s = h; // 指向已经排好序的尾部 + ListNode* m; // 指向未排序部分的最小节点 min + ListNode* p; // 迭代器 + while (s->next) { + m = s; + p = s->next; + while (p) { // 寻找剩余部分的最小节点 + if (p->val < m->val) + m = p; + p = p->next; + } + + swap(s->val, m->val); // 交换节点内的值 + s = s->next; + } + + h = H->next; + delete H; + return h; + } +}; +``` + +### 链表冒泡排序 +> LeetCode/[147. 对链表进行插入排序](https://leetcode-cn.com/problems/insertion-sort-list/description/) + +**思路** +- 见代码注释 +- **时间复杂度**:最好 `O(N)`,最坏 `O(N^2)` + +**C++** +- 以下代码不能 AC [148. 排序链表](https://leetcode-cn.com/problems/sort-list/description/) +```C++ +class Solution { +public: + ListNode* insertionSortList(ListNode* h) { + if (h == nullptr || h->next == nullptr) + return h; + + ListNode* q = nullptr; // 开始时指向尾节点 + ListNode* p; // 迭代器 + bool changed = true; + while (q != h->next && changed) { + changed = false; + p = h; + + // 把大的元素“冒泡”到尾部去 + while (p->next && p->next != p) { + if (p->val > p->next->val) { // 如果已经有序,则退出循环 + swap(p->val, p->next->val); + changed = true; + } + p = p->next; + } + q = p; + } + + return h; + } +}; +``` + + +# 二维数组 + +## 二分查找 + +### 搜索二维矩阵 1 +> LeetCode - [74. 搜索二维矩阵](https://leetcode-cn.com/problems/search-a-2d-matrix/description/) + +**问题描述** +``` +编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性: + + 每行中的整数从左到右按升序排列。 + 每行的第一个整数大于前一行的最后一个整数。 + +示例 1: +输入: + matrix = [ + [1, 3, 5, 7], + [10, 11, 16, 20], + [23, 30, 34, 50] + ] + target = 3 +输出: true +``` + +**思路** +- 当做一维有序数组二分查找 + +**C++** +``` +class Solution { +public: + bool searchMatrix(vector>& M, int t) { + if (M.size() < 1 || M[0].size() < 1) + return false; + + int m = M.size(); + int n = M[0].size(); + + int lo = 0; + int hi = m * n; + + while (lo + 1 < hi) { + int mid = lo + (hi - lo) / 2; + if (M[mid / n][mid % n] > t) { + hi = mid; + } else { + lo = mid; + } + } + + return M[lo / n][lo % n] == t; + } +}; +``` + +### 搜索二维矩阵 2 +> LeetCode - [240. 搜索二维矩阵 II](https://leetcode-cn.com/problems/search-a-2d-matrix-ii/description/) + +**思路** +- 1)从右上角开始查找,时间复杂度 `O(M+N)` +- 2)每一行二分查找,时间复杂度 `O(MlogN)` + + +```C++ +class Solution { +public: + bool searchMatrix(vector>& M, int t) { + if (M.size() < 1 || M[0].size() < 1) + return false; + + auto m = M.size(); + auto n = M[0].size(); + + int row = 0; + int col = n - 1; + while(row <= m - 1 && col >= 0) { + if (M[row][col] < t) + row++; + else if (M[row][col] > t) + col--; + else + return true; + } + + return false; + } +}; +``` + +## 打印二维数组 + +### 回形打印 + +### 蛇形打印 + + +# 堆 + +## 堆的调整(自上而下) + + +# 栈 + +## 用两个栈模拟队列 +> [用两个栈实现队列](https://www.nowcoder.com/practice/54275ddae22f475981afa2244dd448c6?tpId=13&tqId=11158&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - 牛客 + +**题目描述** +``` +用两个栈来实现一个队列,完成队列的 Push 和 Pop 操作。 +``` + +**思路** +- 假设 `stack_in` 用于处理入栈操作,`stack_out`用于处理出栈操作 +- `stack_in` 按栈的方式正常处理入栈数据; +- 关键在于出栈操作 + - 当`stack_out`为空时,需要先将每个`stack_in`中的数据出栈后压入`stack_out` + - 反之,每次弹出`stack_out`栈顶元素即可 + +**Code**(C++) +```C++ +class Solution { + stack stack_in; + stack stack_out; +public: + void push(int node) { + stack_in.push(node); + } + + int pop() { + if(stack_out.size() <= 0) { + while (stack_in.size() > 0) { + auto tmp = stack_in.top(); + stack_in.pop(); + stack_out.push(tmp); + } + } + + auto ret = stack_out.top(); + stack_out.pop(); + return ret; + } +}; +``` diff --git "a/C-\347\256\227\346\263\225/\344\270\223\351\242\230-A-\346\225\260\346\215\256\347\273\223\346\236\204_Advanced.md" "b/C-\347\256\227\346\263\225/\344\270\223\351\242\230-A-\346\225\260\346\215\256\347\273\223\346\236\204_Advanced.md" new file mode 100644 index 00000000..0a41fd63 --- /dev/null +++ "b/C-\347\256\227\346\263\225/\344\270\223\351\242\230-A-\346\225\260\346\215\256\347\273\223\346\236\204_Advanced.md" @@ -0,0 +1,274 @@ +专题-数据结构_Advanced +=== + +Index +--- + + +- [树状数组](#树状数组) + - [树状数组的构建(以区间和问题为例)](#树状数组的构建以区间和问题为例) + - [树状数组的特点](#树状数组的特点) + - [相关问题](#相关问题) + - [相关阅读](#相关阅读) +- [线段树](#线段树) +- [字典树(Trie)](#字典树trie) +- [数据结构设计](#数据结构设计) + - [LRU 缓存](#lru-缓存) + + + + +## 树状数组 +- 树状数组是一种用于维护**前缀信息**的数据结构 +
+ +- 树状数组 `C` 在物理空间上是连续的; +- 对于数组中的两个位置 `C[x], C[y]`,若满足 `y = x + 2^k`(**其中 `k` 表示 `x` 二进制中末尾 0 的个数**),则定义 `C[x], C[y]` 为一组父子关系; + ``` + 4 的二进制为 100,则 k = 2 + 所以 4 是 4 + 2^2 = 8 的孩子 + 5 的二进制位 101,则 k = 0 + 所以 5 是 5 + 2^0 = 6 的孩子 + ``` +- 由以上定义,可知**奇数**下标的位置一定是叶子节点 + +**C[i] 的直观含义** +- `C[i]` 实际上表示原数组中一段**区间**内的**某个统计意义**(区间和、区间积、区间最值等等); +- 该区间为 `[i-2^k+1, i]`,是一个闭区间; +- 以**区间和**为例 + ``` + 1=(001) C[1]=A[1]; + 2=(010) C[2]=A[1]+A[2]; + 3=(011) C[3]=A[3]; + 4=(100) C[4]=A[1]+A[2]+A[3]+A[4]; + 5=(101) C[5]=A[5]; + 6=(110) C[6]=A[5]+A[6]; + 7=(111) C[7]=A[7]; + 8=(1000) C[8]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8]; + ``` + +### 树状数组的构建(以区间和问题为例) +> LeetCode - [307. 区域和检索 - 数组可修改](https://leetcode-cn.com/problems/range-sum-query-mutable/description/) + +**问题描述** + ``` + 给定一个数组,支持两种操作: + 1.查询区间和 + 2.修改某个元素的值 + + 示例: + Given nums = [1, 3, 5] + + sumRange(0, 2) -> 9 + update(1, 2) + sumRange(0, 2) -> 8 + ``` +- 构建树状数组的过程即初始化数组 `C` 的过程 +- 基本操作: + - `lowbit(x)` ——求 2^k,其中 k 表示 x 二进制位中后缀 0 的个数 + - `updateC(x, delta)` ——更新 C 数组中 A[x] 的祖先 + - 如果是初始化阶段 delta = A[i], + - 如果是更新 A[i],则 delta = new_val - A[i] + - `sumPrefix(x)` ——求前缀区间 [1, x] 的和 + - `update(i, val)` ——更新 A[i] = val,同时也会更新所有 A[i] 的祖先 + - `sumRange(lo, hi)` ——求范围 [lo, hi] 的区间和 + +**C++** +```C++ +class NumArray { + int n; + vector A; + vector C; + + // 求 2^k,其中 k 表示 x 二进制位中后缀 0 的个数 + int lowbit(int x) { + return x & (-x); + } + + // 更新 C 数组,对 A[x] 的每个祖先都加上 delta: + // 如果是初始化阶段 delta = A[i],如果是更新 A[i],则 delta = new_val - A[i] + void updateC(int x, int delta) { + for (int i = x; i <= n; i += lowbit(i)) { + C[i] += delta; + } + } + + // 求前缀区间 [1, x] 的和 + int sumPrefix(int x) { + int res = 0; + for (int i = x; i > 0; i -= lowbit(i)) { + res += C[i]; + } + return res; + } +public: + // 初始化 + NumArray(vector nums) { + n = nums.size(); + A.resize(n + 1, 0); + C.resize(n + 1, 0); + for (int i = 1; i <= n; i++) { + A[i] = nums[i - 1]; // 树状数组的内部默认从 1 开始计数 + updateC(i, A[i]); + } + } + + // 将 A[i] 的值更新为 val + void update(int i, int val) { + i++; // 树状数组的内部默认从 1 开始计数,如果外部默认从 0 开始计数,则需要 +1; + updateC(i, val - A[i]); // 更新 A[i] 的所有祖先节点,加上 val 与 A[i] 的差即可 + A[i] = val; + } + + // 求范围 [lo, hi] 的区间和 + int sumRange(int lo, int hi) { + lo++; hi++; // 树状数组的内部默认从 1 开始计数,如果外部默认从 0 开始计数,则需要 +1; + return sumPrefix(hi) - sumPrefix(lo - 1); + } +}; + +void solve() { + vector nums{1, 3, 5}; + auto na = NumArray(nums); + int ret; + ret = na.sumRange(0, 2); + na.update(1, 2); + ret = na.sumRange(0, 2); +} +``` + +### 树状数组的特点 +- 线段树不能解决的问题,树状数组也无法解决; +- 树状数组和线段树的时间复杂度相同:初始化 `O(n)`,查询和修改 `O(logn)`;但实际效率要高于线段树; +- 直接维护前缀信息也能解决查询问题,但是修改的时间复杂度会比较高; + + +### 相关问题 +- [665. 二维区域和检索 - 矩阵不可变](https://www.lintcode.com/problem/range-sum-query-2d-immutable/description) - LintCode +- [817. 二维区域和检索 - 矩阵可变](https://www.lintcode.com/problem/range-sum-query-2d-mutable/description) - LintCode +- [249. 统计前面比自己小的数的个数](https://www.lintcode.com/problem/count-of-smaller-number-before-itself/description) - LintCode +- [248. 统计比给定整数小的数的个数](https://www.lintcode.com/problem/count-of-smaller-number/description) - LintCode +- [532. 逆序对](https://www.lintcode.com/problem/reverse-pairs/description) - LintCode + +### 相关阅读 +- [夜深人静写算法(三)- 树状数组](https://blog.csdn.net/WhereIsHeroFrom/article/details/78922383) - CSDN博客 + + + +## 数据结构设计 + +### LRU 缓存 +> LeetCode/[146. LRU缓存机制](https://leetcode-cn.com/problems/lru-cache/description/) + +**思路** +- **双向链表** + haspmap + - 数据除了被保存在链表中,同时也保存在 map 中;前者用于记录数据的顺序结构,后者以实现 `O(1)` 的访问。 +- **更新过程**: + - 新数据插入到链表头部 + - 每当缓存命中(即缓存数据被访问),则将数据移到链表头部 + - 当链表满的时候,将链表尾部的数据丢弃 +- **操作**: + - `put(key, value)`:如果 key 在 hash_map 中存在,则先**重置对应的 value 值**,然后获取对应的节点,将节点从链表移除,并移动到链表的头部;若果 key 在 hash_map 不存在,则新建一个节点,并将节点放到链表的头部。当 Cache 存满的时候,将链表最后一个节点删除。 + - `get(key)`:如果 key 在 hash_map 中存在,则把对应的节点放到链表头部,并返回对应的value值;如果不存在,则返回-1。 + +**C++**(AC) +```C++ +// 缓存节点(双端队列) +struct CacheNode { + int key; + int value; + CacheNode *pre, *next; + CacheNode(int k, int v) : key(k), value(v), pre(nullptr), next(nullptr) {} +}; + +class LRUCache { + int size = 0; + CacheNode* head = nullptr; + CacheNode* tail = nullptr; + unordered_map dp; // hash_map + + void remove(CacheNode *node) { + if (node != head) { // 修改后序节点是需判断是否头结点 + node->pre->next = node->next; + } + else { + head = node->next; + } + + if (node != tail) { // 修改前序节点是需判断是否尾结点 + node->next->pre = node->pre; + } + else { + tail = node->pre; + } + + // remove 时不销毁该节点 + //delete node; + //node = nullptr; + } + + void setHead(CacheNode *node) { + node->next = head; + node->pre = nullptr; + + if (head != nullptr) { + head->pre = node; + } + head = node; + + if (tail == nullptr) { + tail = head; + } + } +public: + LRUCache(int capacity) : size(capacity) { } + + int get(int key) { + auto it = dp.find(key); + if (it != dp.end()) { + auto node = dp[key]; + + // 如果命中了,把该节点移动到头部 + remove(node); + setHead(node); + + return node->value; + } + + return -1; + } + + void put(int key, int value) { + auto it = dp.find(key); + if (it != dp.end()) { + auto node = dp[key]; + + node->value = value; // 更新 + remove(node); + setHead(node); + } + else { + auto node = new CacheNode(key, value); + setHead(node); + dp[key] = node; + + // 关键:判断容量 + //if (dp.size() >= size) { // 若先删除节点,则为 >= + if (dp.size() > size) { // 若先存入 dp,则为 > + auto it = dp.find(tail->key); + remove(tail); + + // 这里才销毁内存(即使不销毁也能过 LeetCode) + delete it->second; + it->second = nullptr; + + dp.erase(it); // 先销毁,在移除 + } + } + } +}; +``` \ No newline at end of file diff --git "a/C-\347\256\227\346\263\225/\344\270\223\351\242\230-B-\345\212\250\346\200\201\350\247\204\345\210\222.md" "b/C-\347\256\227\346\263\225/\344\270\223\351\242\230-B-\345\212\250\346\200\201\350\247\204\345\210\222.md" new file mode 100644 index 00000000..a02702a1 --- /dev/null +++ "b/C-\347\256\227\346\263\225/\344\270\223\351\242\230-B-\345\212\250\346\200\201\350\247\204\345\210\222.md" @@ -0,0 +1,1491 @@ +专题-动态规划 +=== + +DP 问题的一般思路 +--- +- **DP 定义** ——有时 DP 的更新很难严格遵循定义,需要额外变量保存全局最优结果 +- **初始化** ——初始值可以通过一个简单的特例来确定 +- **递推公式** + **边界条件** +- **DP 优化** (可选) + + + +Reference +--- +- [常见的动态规划问题分析与求解 - 五岳](https://www.cnblogs.com/wuyuegb2312/p/3281264.html) - 博客园 +- [什么是动态规划?动态规划的意义是什么?](https://www.zhihu.com/question/23995189 ) - 知乎 + +Index +--- + + +- [背包问题](#背包问题) + - [【注】关于“恰好装满”](#注关于恰好装满) + - [01 背包](#01-背包) + - [二维 DP(无优化)](#二维-dp无优化) + - [二维 DP(滚动数组)](#二维-dp滚动数组) + - [一维 DP](#一维-dp) + - [完全背包](#完全背包) + - [二维 DP(无优化)](#二维-dp无优化-1) + - [二维 DP(滚动数组)](#二维-dp滚动数组-1) + - [一维 DP](#一维-dp-1) + - [多重背包 TODO](#多重背包-todo) +- [硬币问题](#硬币问题) + - [硬币找零](#硬币找零) + - [硬币组合](#硬币组合) +- [最长公共子序列(LCS)](#最长公共子序列lcs) + - [最长公共子串](#最长公共子串) +- [最长递增子序列(LIS)](#最长递增子序列lis) +- [最长回文子序列](#最长回文子序列) + - [最长回文子串](#最长回文子串) +- [最大连续子序列和](#最大连续子序列和) +- [编辑距离](#编辑距离) +- [矩阵中的最大正方形](#矩阵中的最大正方形) +- [鹰蛋问题](#鹰蛋问题) +- [矩阵链乘法 TODO](#矩阵链乘法-todo) +- [有代价的最短路径 TODO](#有代价的最短路径-todo) +- [瓷砖覆盖(状态压缩DP) TODO](#瓷砖覆盖状态压缩dp-todo) +- [工作量划分 TODO](#工作量划分-todo) +- [三路取苹果 TODO](#三路取苹果-todo) + + + +## 背包问题 + +### 【注】关于“恰好装满” +- **如果要求恰好装满背包**,可以在初始化时将 `dp[0] / dp[i][0]` 初始化 `0`,其他初始化为 `-INF`。这样即可保证最终得到的 `dp[N] / dp[N][M]` 是一种恰好装满背包的解; +- **如果不要求恰好装满**,则全部初始化为 `0` 即可。 +- 可以这样理解:初始化的 dp 数组实际上就是在没有任何物品可以放入背包时的合法状态。 + - 如果要求背包恰好装满,那么此时只有**容量为 0** 的背包可能被**价值为 0** 的物品“**恰好装满**”,其它容量的背包均**没有合法的解**,属于未定义的状态,它们的值就都应该是 `-INF` 。 + - 如果背包并非必须被装满,那么任何容量的背包都有一个合法解,即“什么都不装”,这个解的价值为0,所以初始时状态的值也全部为 0 。 + +### 01 背包 +> [HDOJ - 2602](http://acm.hdu.edu.cn/showproblem.php?pid=2602) + +**问题描述** +``` +有 n 个重量个价值分别为 w_i, v_i 的物品。 +从这些物品中选出总重量不超过 W 的物品,使其总价值最大。 + +示例 +1 // 用例数 +5 10 // 物品数 背包容量 N <= 1000 , V <= 1000 +1 2 3 4 5 +5 4 3 2 1 + +14 +``` + +#### 二维 DP(无优化) + +- **定义**:`dp[i][j] := 从前 i 个物品中选取总重量不超过 j 的物品时总价值的最大值` + > `i` 从 1 开始计,包括第 `i` 个物品 +- **初始化** + ``` + dp[0][j] = 0 + ``` +- **状态转移** + ``` + dp[i][j] = dp[i-1][j] if j < w[i] (当前剩余容量不够放下第 i 个物品) + = max{ else (取以下两种情况的最大值) + dp[i-1][j], // 不拿第 i 个物品 + dp[i-1][j-w[i]] + w[j] // 拿第 i 个物品 + } + ``` +```C++ +// HDOJ 地址:http://acm.hdu.edu.cn/showproblem.php?pid=2602 +int solve(int N, int V, vector& v, vector& w) { + + vector > dp(N + 1, vector(V + 1, 0)); // 不要求装满,初始化为 0 即可 + + // 核心代码 + for (int i = 1; i <= N; i++) { + for (int j = 0; j <= V; j++) { // 可能存在重量为 0,但有价值的物品 + if (w[i] > j) // 如果当前物品的重量大于剩余容量 + dp[i][j] = dp[i - 1][j]; + else + dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]); + } + } + return dp[N][V]; +} + +int main() { + int T; // 用例数 + scanf("%d", &T); + while (T--) { + int N, V; // N: 物品数量;V: 背包容量 + scanf("%d%d", &N, &V); + vector v(N + 1, 0); // 保存每个物品的价值 + vector w(N + 1, 0); // 保存每个物品的重量 + for (int i = 1; i <= N; i++) + scanf("%d", &v[i]); + for (int i = 1; i <= N; i++) + scanf("%d", &w[i]); + + int ans = solve(N, V, v, w); + + printf("%d\n", ans); + } + return 0; +} +``` + +#### 二维 DP(滚动数组) + +- 在上述递推式中,`dp[i+1]` 的计算实际只用到了 `dp[i+1]` 和 `dp[i]`; +- 因此可以结合**奇偶**,通过两个数组滚动使用来实现重复利用。 + +```C++ +// HDOJ 地址:http://acm.hdu.edu.cn/showproblem.php?pid=2602 +int solve(int N, int V, vector& v, vector& w) { + + //vector > dp(N + 1, vector(V + 1, 0)); // 不要求装满,初始化为 0 即可 + vector > dp(2, vector(V + 1, 0)); // N+1 -> 2 + + // 核心代码 + for (int i = 1; i <= N; i++) { + for (int j = 0; j <= V; j++) { // 可能存在重量为 0,但有价值的物品 + if (w[i] > j) // 如果当前物品的重量大于剩余容量 + dp[i & 1][j] = dp[(i - 1) & 1][j]; + else + dp[i & 1][j] = max(dp[(i - 1) & 1][j], dp[(i - 1) & 1][j - w[i]] + v[i]); + } + } + return dp[N & 1][V]; // 这里别忘了 N & 1 +} + +// main 函数略 +``` + +#### 一维 DP +- **定义**:`dp[j] := 重量不超过 j 公斤的最大价值` +- **递推公式** + ``` + dp[j] = max{dp[j], dp[j-w[i]] + v[i]} 若 j > w[i] + ``` + +```C++ +// HDOJ 地址:http://acm.hdu.edu.cn/showproblem.php?pid=2602 +// 一维 DP(滚动数组) +int solve(int N, int V, vector& v, vector& w) { + + vector dp(V + 1, 0); + + // 核心代码 + for (int i = 1; i <= N; i++) { + for (int j = V; j >= w[i]; j--) { // 递推方向发生了改变 + dp[j] = max(dp[j], dp[j - w[i]] + v[i]); + } + } + + return dp[V]; +} + +// main 函数略 +``` + +### 完全背包 +> [NYOJ - 311](http://nyoj.top/problem/311) + +**问题描述** +``` +01 背包中每个物品只有一个,所以只存在选或不选; +完全背包中每个物品可以选取任意件。 + +注意:本题要求是背包恰好装满背包时,求出最大价值总和是多少。如果不能恰好装满背包,输出 NO +``` + +#### 二维 DP(无优化) + +- **直观思路**:在 01 背包的基础上在加一层循环 +- **递推关系**: + ``` + dp[0][j] = 0 + dp[i][j] = max{dp[i - 1][j - k * w[i]] + k * v[i] | 0 <= k} + ``` + ``` + for (int i = 1; i <= N; i++) { + for (int j = 0; j <= V; j++) { // 可能存在重量为 0 的物品 + for (int k = 0; k * w[i] <= j; k++) + dp[i][j] = max(dp[i][j], dp[i-1][j - k*w[i]] + k*v[i]); + } + } + ``` + - 关于 `k` 的循环最坏可能从 0 到 `V`,因此时间复杂度为 `O(N*V^2)` +- **注意到**: + ``` + dp[i][j] = max{dp[i - 1][j - k*w[i]] + k*v[i] | 0 <= k} + ------ + = max{dp[i - 1][j], max{dp[i - 1][j - k*w[i]] + k*v[i]} | 1 <= k} + ------ + = max{dp[i - 1][j], max{dp[i - 1][(j-w[i]) - k*w[i]] + k*v[i] | 0 <= k} + v[i]} + -------- ------ ------ + --------------------------------------------------- + = max{dp[i - 1][j], dp[i][j - w[i]] + v[i]} + --------------- + ``` + ``` + for (int i = 1; i <= N; i++) { + for (int j = 0; j <= V; j++) { + if (w[i] > j) + dp[i][j] = dp[i - 1][j]; + else + dp[i][j] = max(dp[i - 1][j], dp[i][j - w[i]] + v[i]); + // dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]); // 对比 01 背包 + // ---------(唯一区别) + } + } + ``` +- **完整代码** + - 注意,这里要求的是恰好装满时的情况,所以需要将 `dp[i][0]` 全部初始化为 0,其他初始化为 `-INF` + > 以下代码因**超内存**无法通过 NYOJ 311; + > + > 可以 AC 的代码,请参考 [完全背包(一维 DP)](#完全背包一维-dp) 和 [完全背包(滚动数组)](#完全背包滚动数组) + ```C++ + // NYOJ 311 会报超内存,所以无法测试 + #include + #include + #include + using namespace std; + + const int inf = 0x80000000; + + void solve() { + int T; + scanf("%d", &T); + while (T--) { + int N, V; // N 表示物品种类的数目,V 表示背包的总容量 + scanf("%d%d", &N, &V); + vector w(N + 1), v(N + 1); // w 表示重量,v 表示价值 + for (int i = 1; i <= N; i++) + scanf("%d%d", &w[i], &v[i]); + + vector > dp(N + 1, vector(V + 1, inf)); + for (int i = 0; i <= N; i++) + dp[i][0] = 0; + + for (int i = 1; i <= N; i++) { + for (int j = 0; j <= V; j++) { + if (j < w[i]) + dp[i][j] = dp[i - 1][j]; + else + dp[i][j] = max(dp[i - 1][j], dp[i][j - w[i]] + v[i]); + } + } + + if (dp[N][V] > 0) + printf("%d\n", dp[N][V]); + else + puts("NO"); + } + } + + int main() { + solve(); + return 0; + } + ``` + +#### 二维 DP(滚动数组) +```C++ +// NYOJ 311-完全背包: http://nyoj.top/problem/311 (未通过测试,报运行时错误) +#include +#include +#include +using namespace std; + +void solve3() { + const int MAX_V = 50000 + 10; + const int inf = 0x3f3f3f3f; + + int T; + scanf("%d", &T); + while (T--) { + int N, V; // M 表示物品种类的数目,V 表示背包的总容量 + scanf("%d%d", &N, &V); + //vector w(N + 1), v(N + 1); // w 表示重量,v 表示价值 + //for (int i = 1; i <= N; i++) + // scanf("%d%d", &w[i], &v[i]); + + //vector > dp(2, vector(V + 1, -inf)); + int dp[2][MAX_V]; + for (int i = 0; i < 2; i++) { + fill(dp[i], dp[i] + MAX_V, -inf); + dp[i][0] = 0; + } + + for (int i = 1; i <= N; i++) { + int w, v; + scanf("%d%d", &w, &v); + for (int j = 0; j <= V; j++) { + if (j < w) + dp[i & 1][j] = dp[(i - 1) & 1][j]; + else + dp[i & 1][j] = max(dp[(i - 1) & 1][j], dp[i & 1][j - w] + v); + } + } + + if (dp[N][V] > 0) + printf("%d\n", dp[N & 1][V]); + else + puts("NO"); + } +} + +int main() { + solve3(); + return 0; +} +``` + +#### 一维 DP +- 核心代码与 01 背包一致,只有第二层循环的**递推方向不同** +- **完整代码** + ```C++ + // NYOJ 311-完全背包: http://nyoj.top/problem/311 + #include + #include + #include + #include + using namespace std; + + const int MAX_V = 50000 + 10; + const int inf = 0x80000000; + + void solve2() { + int T; + scanf("%d", &T); + while (T--) { + int N, V; // M 表示物品种类的数目,V 表示背包的总容量 + scanf("%d%d", &N, &V); + //vector w(N + 1), v(N + 1); // w 表示重量,v 表示价值 + //for (int i = 1; i <= N; i++) + // scanf("%d%d", &w[i], &v[i]); + + //vector dp(V + 1, inf); // 注意 NYOJ 的系统开辟稍大的 vector 就会导致超时 + int dp[MAX_V]; + fill(dp, dp + MAX_V, inf); + dp[0] = 0; + + for (int i = 1; i <= N; i++) { + int w, v; + scanf("%d%d", &w, &v); // 避免开辟新的内存 + for (int j = w; j <= V; j++) { + dp[j] = max(dp[j], dp[j - w] + v); + } + } + + if (dp[V] > 0) + printf("%d\n", dp[V]); + else + puts("NO"); + } + } + + int main() { + solve2(); + return 0; + } + ``` + +### 多重背包 TODO + +## 硬币问题 + +### 硬币找零 +> LeetCode - [322. 零钱兑换](https://leetcode-cn.com/problems/coin-change/description/) + +**问题描述** +``` +给定不同面额的硬币 coins 和一个总金额 amount。 +编写一个函数来计算可以凑成总金额所需的最少的硬币个数。 +如果没有任何一种硬币组合能组成总金额,返回 -1。 + +示例 1: + + 输入: coins = [1, 2, 5], amount = 11 + 输出: 3 + 解释: 11 = 5 + 5 + 1 + +示例 2: + + 输入: coins = [2], amount = 3 + 输出: -1 + +说明: + 你可以认为每种硬币的数量是无限的。 +``` + +**思路** +- **定义**:`dp[i] := 组成总金额 i 时的最少硬币数` +- **初始化**: + ``` + dp[i] = 0 若 i=0 + = INF 其他 + ``` +- **状态转移** + ``` + dp[j] = min{ dp[j-coins[i]] + 1 | i=0,..,n-1 } + + 其中 coins[i] 表示硬币的币值,共 n 种硬币 + ``` + +**C++** +```C++ +class Solution { +public: + int coinChange(vector& coins, int n) { + int INF = n + 1; + + vector dp(n+1, INF); + dp[0] = 0; + + for (auto c: coins) { + for (int i=c; i<=n; i++) { // i >= c + dp[i] = min(dp[i], dp[i-c] + 1); + } + } + + return dp[n] < INF ? dp[n] : -1; + } +}; +``` + +### 硬币组合 +> LeetCode - [518. 零钱兑换 II](https://leetcode-cn.com/problems/coin-change-2/description/) + + +**C++** +```C++ +class Solution { +public: + int change(int n, vector& coins) { + int m = coins.size(); + + vector dp(n+1, 0); + dp[0] = 1; + + for (auto c: coins) { + for (int i = c; i <= n; i++) { + dp[i] += dp[i - c]; + } + } + + return dp[n]; + } +}; +``` + +## 最长公共子序列(LCS) +> [最长公共子序列](https://www.nowcoder.com/questionTerminal/c996bbb77dd447d681ec6907ccfb488a)_牛客网 +- 求两个序列的最长公共字序列 + - 示例:s1: "**B**D**C**A**BA**" 与 s2:"A**BCB**D**A**B" 的**一个**最长公共字序列为 "BCBA" + - 最长公共子序列不唯一,但是它们的长度是一致的 + - 子序列不要求连续 + +**思路** +- **DP 定义** + - **记** `s[0:i] := s 长度为 i 的**前缀**` + - **定义** `dp[i][j] := s1[0:i] 和 s2[0:j] 最长公共子序列的长度` +- **DP 初始化** + ``` + dp[i][j] = 0 当 i=0 或 j=0 时 + ``` +- **DP 更新** + - 当 `s1[i] == s2[j]` 时 + ``` + dp[i][j] = dp[i-1][j-1] + 1 + ``` + - 当 `s1[i] != s2[j]` 时 + ``` + dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + ``` +- **完整递推公式** + ``` + dp[i][j] = 0 当 i=0 或 j=0 时 + = dp[i-1][j-1] + 1 当 `s1[i-1] == s2[j-1]` 时 + = max(dp[i-1][j], dp[i][j-1]) 当 `s1[i-1] != s2[j-1]` 时 + ``` +- **Code - C++** + ```C++ + class LCS { + public: + int findLCS(string A, int n, string B, int m) { + vector > dp(n+1, vector(m+1, 0)); + // 已经初始化为全 0,就不必再手动初始化 DP 了 + + for (int i=1; i<=n; i++) + for (int j=1; j<=m; j++) + if (A[i-1] == B[j-1]) // 注意下标问题 + dp[i][j] = dp[i-1][j-1] + 1; + else + dp[i][j] = max(dp[i][j-1], dp[i-1][j]); + + return dp[n][m]; + } + }; + ``` + +### 最长公共子串 +> [最长公共子串](https://www.nowcoder.com/questionTerminal/02e7cc263f8a49e8b1e1dc9c116f7602)_牛客网 + +**题目描述** +``` +对于两个字符串,请设计一个时间复杂度为`O(m*n)`的算法,求出两串的最长公共子串的长度。 +(这里的 m 和 n 为两串的长度) +``` + +**思路 - 暴力求解** +
+ + > [Longest common substring problem](https://en.wikipedia.org/wiki/Longest_common_substring_problem) - Wikipedia + + 暴力求解思路:每当找到一对元素相同时就**斜向比较** + ```C++ + class LongestSubstring { + public: + int findLongest(string A, int n, string B, int m) { + int ret = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + int tmp_ret = 0; + if (A[i] == B[j]) { // 每当找到一对元素相同 + tmp_ret += 1; // 斜向比较 + int tmp_i = i + 1; + int tmp_j = j + 1; + while (tmp_i < n && tmp_j < m && A[tmp_i++] == B[tmp_j++]) // 注意边界 + tmp_ret++; + } + ret = max(ret, tmp_ret); // 记录最大 + } + } + + return ret; + } + }; + ``` + - 注意:如果两个串完全相同的话,时间复杂度将退化为 `O(N^3)` + +**思路 - DP** +- **DP 定义** + - **记** `s[0:i] := s 长度为 i 的**前缀**` + - ~~**定义** `dp[i][j] := s1[0:i] 和 s2[0:j] 最长公共子串的长度`~~ + - `dp[i][j]` 只有当 `s1[i] == s2[j]` 的情况下才是 `s1[0:i] 和 s2[0:j] 最长公共子串的长度` +- **DP 初始化** + ``` + dp[i][j] = 0 当 i=0 或 j=0 时 + ``` +- **DP 更新** + ``` + dp[i][j] = dp[i-1][j-1] + 1 if s[i] == s[j] + = ; else pass + ``` +- **Code** + ```C++ + class LongestSubstring { + public: + int findLongest(string A, int n, string B, int m) { + vector > dp(n + 1, vector(m + 1, 0)); + // 已经初始化为全 0,就不必再手动初始化 DP 了 + + int ret = 0; + for (int i = 1; i <= n; i++) + for (int j = 1; j <= m; j++) + if (A[i - 1] == B[j - 1]) { + dp[i][j] = dp[i - 1][j - 1] + 1; + ret = max(ret, dp[i][j]); // 相比最长公共子序列,增加了这行 + } + else + ; // 去掉了这行 + + return ret; + } + }; + ``` +- **DP 优化**:空间复杂度 `O(N)` + - 好不容易找到的优化为 `O(N)` 的代码;多数优化直接优化到了 `O(1)` + - 因为内层循环是逆序的,所以有点不好理解,可以画一个矩阵手推 DP 的更新过程,很巧妙 + ```C++ + class LongestSubstring { + public: + int findLongest(string A, int n, string B, int m) { + if (n < m) { + swap(n, m); + swap(A, B); + } + + vector dp(m, 0); + + int ret = 0; + for (int i = 0; i < n; i++) { + for (int j = m - 1; j >= 0; j--) { + if (A[i] != B[j]) { + dp[j] = 0; + } + else { + if (i != 0) { + dp[j] = dp[j - 1] + 1; + } + else { + dp[j] = 1; + } + } + ret = max(ret, dp[j]); + } + } + + return ret; + } + }; + ``` +- **DP 优化**:空间复杂度 `O(1)` + - 两个字符串的比较总是按一行一行或一列一列来比较,因此至少要保存一行的数据 + - 而如果是按照斜向遍历,其实只要保存一个数据即可 +
+ + 斜向遍历的策略很多,下面的代码是从右上角(`row=0, col=m-1`)开始遍历 + ```C++ + class LongestSubstring { + public: + int findLongest(string A, int n, string B, int m) { + int ret = 0; + + for (int row = 0, col = m - 1; row < n;) { + int i = row; + int j = col; + int dp = 0; + while (i < n && j < m) { + if (A[i++] == B[j++]) // 注意:无论走哪个分支,i 和 j 都会 ++ 一次 + dp += 1; + else + dp = 0; + + ret = max(ret, dp); + } + + if (col > 0) + col--; + else + row++; + } + + return ret; + } + }; + ``` + + 上述代码其实就是把下面的两段循环合并了 + ```C++ + class LongestSubstring { + public: + int findLongest(string A, int n, string B, int m) { + int ret = 0; + int dp; + + for (int col = m-1; col >= 0; col--) { + dp = 0; + for (int i = 0, j = col; i < n && j < m; i++, j++) { + if (A[i] == B[j]) + dp += 1; + else + dp = 0; + + ret = max(ret, dp); + } + } + + for (int row = 0; row < n; row++) { + dp = 0; + for (int i = row, j = 0; i < n && j < m; i++, j++) { + if (A[i] == B[j]) + dp += 1; + else + dp = 0; + + ret = max(ret, dp); + } + } + + return ret; + } + }; + ``` + + +## 最长递增子序列(LIS) +> [最长递增子序列](https://www.nowcoder.com/questionTerminal/585d46a1447b4064b749f08c2ab9ce66)_牛客网 +> +> [最长上升子序列](https://leetcode-cn.com/problems/longest-increasing-subsequence/description/) - LeetCode +>> 牛客假设给定的数组中不存在重复元素,LeetCode 可能存在重复元素 + +**问题描述** +``` +对于一个数字序列,请设计一个复杂度为O(nlogn)的算法,返回该序列的最长上升子序列的长度 + +测试样例: + [2,1,4,3,1,5,6],7 +返回: + 4 +说明: + [1,3,5,6] 是其中一个最长递增子序列 +``` + +**思路0 - `O(N^2)`** +- LIS 可以转化成 LCS (最长公共子序列) 问题 +- 用另一个序列保存给定序列的**排序**结果 - `O(NlogN)` +- 则问题转化为求这两个序列的 LCS 问题 - `O(N^2)` + +**思路1 - `O(N^2)`解法** +- **DP 定义** + - **记** `nums[0:i] := 序列 nums 的前 i 个元素构成的子序列` + - **定义** `dp[i] := nums[0:i] 中 LIS 的长度` + - 实际并没有严格按照这个定义,中间使用一个变量记录当前全局最长的 LIS +- **DP 初始化** + ``` + dp[:] = 1 // 最长上升子序列的长度最短为 1 + ``` +- **DP 更新 - `O(N^2)`的解法** + ``` + dp[i] = max{dp[j]} + 1, if nums[i] > nums[j] + = max{dp[j]}, else + where 0 <= j < i + ``` + 如果只看这个递推公式,很可能会写出如下的**错误代码** +
错误代码(点击展开) + + ```C++ + // 牛客网 + class AscentSequence { + public: + int findLongest(vector nums, int n) { + vector dp(n, 1); + + for (int i = 1; i < n; i++) { + for (int j = 0; j < i; j++) + if (nums[i] > nums[j]) + dp[i] = max(dp[i], dp[j] + 1); + else + dp[i] = max(dp[i], dp[j]); + } + + return dp[n-1]; + } + }; + ``` + - 这段代码的问题在于 `dp[i]` 应该等于 `max{dp[j]}` 对应的那个 `dp[j]+1`,且**只增加一次** + - 这么写可能会导致 `dp[i]` 被增加多次 + > [动态规划求解最长递增子序列的长度 - hapjin](https://www.cnblogs.com/hapjin/p/5597658.html) - 博客园 + +
+ +- 下面是网上比较流行的一种**递推公式** + ``` + dp[i] = dp[j] + 1, if nums[i] > nums[j] && dp[i] < dp[j] + 1 + = pass, else + where 0 <= j < i + ``` + - **注意**:此时并没有严格按照定义处理 dp,它只记录了当 `nums[i] > nums[j] && dp[i] < dp[j] + 1` 时的 LIS;不满足该条件的情况**跳过**了;所以需要额外一个变量记录当前已知全局的 LIS + +- **Code** + ```C++ + // 牛客网 + class AscentSequence { + public: + int findLongest(vector nums, int n) { + vector dp(n, 1); + + int ret = 1; + for (int i = 1; i < n; i++) { + for (int j = 0; j < i; j++) + if (nums[i] > nums[j] && dp[i] < dp[j] + 1) + dp[i] = dp[j] + 1; + + ret = max(ret, dp[i]); + } + + return ret; + } + }; + ``` + +**思路2 - `O(NlogN)`** +- 该解法的**思想**是:长度为 `i` 的 LIS 的**尾元素**应该大于长度为 `i-1` 的尾元素 +- **DP 定义** + - **定义** `dp[i] := 长度为 i 的 LIS 的最小尾元素` +- **DP 更新** + - 二分查找 nums[j] 在 dp 中的 ~~upper_bound 位置~~ **lower_bound 位置** + - upper_bound 位置指的是序列中第一个大于 nums[j] 的元素所在的位置 + - lower_bound 位置指的是序列中第一个大于等于 nums[j] 的元素所在的位置 + - C++ 中分别实现了 upper_bound 和 lower_bound,定义在 `` 中 + - 如果在末尾,则插入;反之则替换 + - upper_bound 只能用于不存在重复元素的情况;而 lower_bound 可以兼容两种情况 + +- **Code** + ```C++ + // 牛客网 + class AscentSequence { + public: + int findLongest(const vector& nums, int n) { + vector dp; + + for (int j = 0; j < n; j++) { + // 这里用 upper_bound 也可以 + auto it = lower_bound(dp.begin(), dp.end(), nums[j]); + if (it == dp.end()) + dp.push_back(nums[j]); + else + *it = nums[j]; + } + + return dp.size(); + } + }; + + // LeetCode + class Solution { + public: + int lengthOfLIS(vector& nums) { + int n = nums.size(); + vector dp; + + for (int j = 0; j < n; j++) { + // 这里只能使用 lower_bound + auto it_l = lower_bound(dp.begin(), dp.end(), nums[j]); + // auto it_u = upper_bound(dp.begin(), dp.end(), nums[j]); + + if (it_l == dp.end()) + dp.push_back(nums[j]); + else + *it_l = nums[j]; + } + + return dp.size(); + } + }; + ``` + + +## 最长回文子序列 +> [最长回文子序列](https://leetcode-cn.com/problems/longest-palindromic-subsequence/description/) - LeetCode + +**问题描述** +``` +给定一个字符串s,找到其中最长的回文子序列。可以假设s的最大长度为1000。 + +示例 1: + 输入: + "bbbab" + 输出: + 4 + 一个可能的最长回文子序列为 "bbbb"。 +``` + +**思路** +- 相比最长回文子串,最长回文子序列更像**最长公共子序列**,只是改变了循环方向 +- **DP 定义** + - **记** `s[i:j] := 字符串 s 在区间 [i:j] 上的子串` + - **定义** `dp[i][j] := s[i:j] 上回文序列的长度` +- **DP 初始化** + ``` + dp[i][i] = 1 // 单个字符也是一个回文序列 + ``` +- **DP 更新** + ``` + dp[i][j] = dp[i+1][j-1] + 2, if s[i] == s[j] + = max(dp[i+1][j], dp[i][j-1]), else + + 比较一下 LCS 的递推公式 + dp[i][j] = 0 当 i=0 或 j=0 时 + = dp[i-1][j-1] + 1 当 `s1[i-1] == s2[j-1]` 时 + = max(dp[i-1][j], dp[i][j-1]) 当 `s1[i-1] != s2[j-1]` 时 + ``` +- **Code** + ```C++ + class Solution { + public: + int longestPalindromeSubseq(string s) { + int n = s.length(); + + vector> dp(n, vector(n, 0)); + + for (int i = 0; i < n; i++) + dp[i][i] = 1; + + for (int j = 1; j < n; j++) // 子串结束位置 + for (int i = j-1; i >=0; i--) { // 子串开始位置 + if (s[i] == s[j]) + dp[i][j] = dp[i + 1][j - 1] + 2; + else + dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]); + } + + return dp[0][n - 1]; + } + }; + ``` + + +### 最长回文子串 +> [最长回文子串](https://www.nowcoder.com/questionTerminal/b4525d1d84934cf280439aeecc36f4af)_牛客网 +> +> [最长回文子串](https://leetcode-cn.com/problems/longest-palindromic-substring/description/) - LeetCode +>> 牛客网只需要输出长度;LeetCode 还需要输出一个具体的回文串 + +**问题描述** +``` +给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为1000。 + +示例 1: + 输入: "babad" + 输出: "bab" + 注意: "aba"也是一个有效答案。 +``` + +**思路 - `O(N^2)`** +- **DP 定义** + - **记** `s[i:j] := 字符串 s 在区间 [i:j] 上的子串` + - **定义** `dp[i][j] := s[i:j] 是否是一个回文串` +- **DP 初始化** + ``` + dp[i][i] = 1 // 单个字符也是一个回文串 + ``` +- **DP 更新** + ``` + dp[i][j] = dp[i+1][j-1], if s[i] == s[j] + = 0, else + + 注意到:如果 j - i < 2 的话(比如 j=2, i=1),dp[i+1][j-1]=dp[2][1] 会出现不符合 DP 定义的情况 + 所以需要添加边界条件 + + dp[i][i+1] = 1, if s[i] == s[i+1] + = 0, else + + 该边界条件可以放在初始化部分完成;但是建议放在递推过程中完成过更好(为了兼容牛客和LeetCode) + ``` +- **Code** + ```C++ + // 牛客网 AC + class Palindrome { + public: + int getLongestPalindrome(const string& s, int n) { + vector > dp(n, vector(n, 0)); + + // 初始化 + for (int i=0; i=0; i--) { // 子串开始位置 + if (j-i < 2) + dp[i][j] = (s[i]==s[j]) ? 1 : 0; + else if (s[i]==s[j]) + dp[i][j] = dp[i+1][j-1]; + else + dp[i][j] = 0; // 因为 dp 全局初始化就是 0,这里其实可以不写 + + if (dp[i][j] && j-i+1 > len) + len = j-i+1; + } + } + + return len; + } + }; + + // LeetCode - 只要添加一个记录开始位置的变量即可 + class Solution { + public: + string longestPalindrome(string s) { + int n = s.length(); + + vector > dp(n, vector(n, 0)); + + // 初始化 + for (int i=0; i=0; i--) { // 子串开始位置 + if (j-i < 2) + dp[i][j] = (s[i]==s[j]) ? 1 : 0; + else if (s[i]==s[j]) + dp[i][j] = dp[i+1][j-1]; + else + dp[i][j] = 0; // 因为 dp 全局初始化就是 0,这里其实可以不写 + + if (dp[i][j] && j-i+1 > len) { + beg = i; // 保存开始位置 + len = j-i+1; + } + + } + } + + return s.substr(beg, len); // 截取子串 + } + }; + ``` + +**Manacher 算法 - `O(N)`** +> [算法-最长回文子串(Manacher算法)](https://www.cnblogs.com/Stay-Hungry-Stay-Foolish/p/7622496.html) - 琼珶和予 - 博客园 + + +## 最大连续子序列和 +> [最大连续子序列](https://www.nowcoder.com/questionTerminal/afe7c043f0644f60af98a0fba61af8e7)_牛客网 +>> 牛客网要求同时输出最大子序列的首尾元素 + +**思路 - 基本问题:只输出最大连续子序列和** +- **DP 定义** + - **记** `a[0:i] := 序列 a 在区间 [0:i] 上的子序列` + - **定义** `dp[i] := a[0:i] 上的最大子序列和` + - 实际并没有严格按照上面的定义,中间使用一个变量记录当前全局的最大连续子序列和 +- **DP 初始化** + ``` + dp[0] = a[0] + ``` +- **DP 更新** + ``` + // 只要 dp[i] > 0 就一直累加下去,一旦小于 0 就重新开始 + dp[i] = dp[i-1] + a[i], if dp[i-1] > 0 + = a[i], else + + ret = max{ret, dp[i]} // 只要大于 0 就累加会导致 dp[i] 保存的并不是 a[0:i] 中的最大连续子序列和 + // 所以需要一个变量保存当前全局的最大连续子序列和 + ``` +
直观实现-无优化-空间复杂度`O(N)`(点击展开) + + ```C++ + void foo() { + int n; + while (cin >> n) { + vector a(n); + for (int i = 0; i> a[i]; + + vector dp(n); + dp[0] = a[0]; + + int ret = a[0]; + for (int i = 1; i < n; i++) { + if (dp[i - 1] > 0) + dp[i] = dp[i - 1] + a[i]; + else + dp[i] = a[i]; + + ret = max(ret, dp[i]); + } + cout << ret << endl; + } + } + /* + 输入 + 5 + 1 5 -3 2 4 + 6 + 1 -2 3 4 -10 6 + 4 + -3 -1 -2 -5 + + 输出 + 9 + 7 + -1 + */ + ``` + +
+ +- **DP 优化** + + 注意到每次递归实际只用到了 `dp[i-1]`,实际只要用到一个变量,空间复杂度 `O(1)` + ```C++ + void foo2() { + int n; + while (cin >> n) { + vector a(n); + for (int i = 0; i> a[i]; + + int ret = INT_MIN; + int max_cur = 0; + + for (int i = 0; i < n; i++) { + if (max_cur > 0) // 如果大于 0 就一直累加 + max_cur += a[i]; + else // 一旦小于 0 就重新开始 + max_cur = a[i]; + + if (max_cur > ret) // 保存找到的最大结果 + ret = max_cur; + + // 以上可以简写成下面两行代码 + //max_cur = max(max_cur + a[i], a[i]); + //ret = max(ret, max_cur); + } + cout << ret << endl; + } + } + ``` + +**思路 - 输出区间/首尾** +- 增加两个变量即可 +- 注意:题目要求,如果序列中全是负数,则输出 0,以及整个序列的首尾元素 + ```C++ + // 牛客网 AC + #include + #include + #include + #include + + using namespace std; + + void foo3() { + int n; + while (cin >> n && n) { + vector a(n); + for (int i = 0; i> a[i]; + + int ret = INT_MIN; + int max_cur = 0; + int beg = a[0], end = a[n-1]; // 输出首尾 + // int beg = 0, end = n-1; // 输出区间 + + int tmp_beg; // 保存临时 beg + for (int i = 0; i < n; i++) { + if (max_cur > 0) { + max_cur += a[i]; + } + else { + max_cur = a[i]; + tmp_beg = a[i]; + // tmp_beg = i; + } + + if (max_cur > ret) { // > 表明保存的是第一次出现的最大和,>= 则为最后一次(未验证) + ret = max_cur; + beg = tmp_beg; + end = a[i]; // 输出首尾 + // end = i; // 输出区间 + } + } + + if (ret < 0) + printf("%d %d %d\n", 0, a[0], a[n-1]); + // printf("%d %d %d\n", 0, 0, n-1); + else + printf("%d %d %d\n", ret, beg, end); + } + } + + int main() { + foo3(); + return 0; + } + ``` + + +## 编辑距离 +> LeetCode-[编辑距离](https://leetcode-cn.com/problems/edit-distance/description/) + +**问题描述** +``` +给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数。 + +你可以对一个单词进行如下三种操作: + 插入一个字符 + 删除一个字符 + 替换一个字符 + +示例: + 输入: word1 = "horse", word2 = "ros" + 输出: 3 + 解释: + horse -> rorse (将 'h' 替换为 'r') + rorse -> rose (删除 'r') + rose -> ros (删除 'e') +``` +- **注意**:编辑距离指的是将 **word1 转换成 word2** + +**思路** +- 用一个 dp 数组维护两个字符串的**前缀**编辑距离 +- **DP 定义** + - **记** `word[0:i] := word 长度为 i 的**前缀子串**` + - **定义** `dp[i][j] := 将 word1[0:i] 转换为 word2[0:j] 的操作数` +- **初始化** + ``` + dp[i][0] = i // 每次从 word1 删除一个字符 + dp[0][j] = j // 每次向 word1 插入一个字符 + ``` +- **递推公式** + - `word1[i] == word1[j]` 时 + ``` + dp[i][j] = dp[i-1][j-1] + ``` + - `word1[i] != word1[j]` 时,有三种更新方式,**取最小** + ``` + // word[1:i] 表示 word 长度为 i 的前缀子串 + dp[i][j] = min({ dp[i-1][j] + 1 , // 将 word1[1:i-1] 转换为 word2[1:j] 的操作数 + 删除 word1[i] 的操作数(1) + dp[i][j-1] + 1 , // 将 word1[0:i] 转换为 word2[0:j-1] 的操作数 + 将 word2[j] 插入到 word1[0:i] 之后的操作数(1) + dp[i-1][j-1] + 1 }) // 将 word1[0:i-1] 转换为 word2[0:j-1] 的操作数 + 将 word1[i] 替换为 word2[j] 的操作数(1) + ``` + +- **C++** + ```C++ + class Solution { + public: + int minDistance(string word1, string word2) { + int m = word1.length(); + int n = word2.length(); + vector > dp(m + 1, vector(n + 1, 0)); + + // 初始化 dp + for (int i = 1; i <= m; i++) + dp[i][0] = i; + for (int j = 1; j <= n; j++) + dp[0][j] = j; + + // 更新 dp + for (int i = 1; i <=m; i++) + for (int j = 1; j <= n; j++) + if (word1[i - 1] == word2[j - 1]) + dp[i][j] = dp[i - 1][j - 1]; + else + dp[i][j] = min({ dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1] }) + 1; + + return dp[m][n]; + } + }; + ``` + +- **DP 优化** + - 注意到每次更新 `dp[i][j]` 只需要用到 `dp[i - 1][j - 1], dp[i][j - 1], dp[i - 1][j]`。因此实际上不需要用到二维 DP + - 具体见下方代码 + +
Code - 优化为一维 DP(点击展开) + + ```C++ + class Solution { + public: + int minDistance(string word1, string word2) { + int m = word1.length(), n = word2.length(); + + vector cur(m + 1, 0); + for (int i = 1; i <= m; i++) + cur[i] = i; + + for (int j = 1; j <= n; j++) { + int pre = cur[0]; + cur[0] = j; + + for (int i = 1; i <= m; i++) { + int temp = cur[i]; + if (word1[i - 1] == word2[j - 1]) + cur[i] = pre; + else + cur[i] = min(pre + 1, min(cur[i] + 1, cur[i - 1] + 1)); + pre = temp; + } + } + return cur[m]; + } + }; + ``` + +
+ + +## 矩阵中的最大正方形 +> LeetCode-[221. 最大正方形](https://leetcode-cn.com/problems/maximal-square/description/) + +**问题描述** +``` +在一个由 0 和 1 组成的二维矩阵 M 内,找到只包含 1 的最大正方形,并返回其面积。 + +示例: + +输入: +1 0 1 0 0 +1 0 1 1 1 +1 1 1 1 1 +1 0 0 1 0 + +输出: +4 +``` + +**思路** +- **DP 定义**:`dp[i][j] := 以 M[i][j] 为正方形**右下角**所能找到的最大正方形的边长` + - 注意保存的是边长 + - 因为 `dp` 保存的不是全局最大值,所以需要用一个额外变量更新结果 +- **初始化** + ``` + dp[i][0] = M[i][0] + dp[0][j] = M[0][j] + ``` +- **递推公式** + ``` + dp[i][j] = min{dp[i-1][j], + dp[i][j-1], + dp[i-1][j-1]} + 1 若 M[i][j] == 1 + = 0 否则 + ``` + > 注意到,本题的递推公式与 [编辑距离](#编辑距离) 完全一致 + +**C++** +```C++ +class Solution { +public: + int maximalSquare(vector>& M) { + if (M.empty() || M[0].empty()) + return 0; + + auto row = M.size(); + auto col = M[0].size(); + vector > dp(row, vector(col, 0)); + + int mx = 0; + for (int i = 0; i < row; i++) { + dp[i][0] = M[i][0] - '0'; + mx = max(mx, dp[i][0]); // 别忘了这里也要更新 mx + } + + for (int j = 0; j < col; j++) { + dp[0][j] = M[0][j] - '0'; + mx = max(mx, dp[0][j]); // 别忘了这里也要更新 mx + } + + for (int i=1; i Power Eggs http://acm.zcmu.edu.cn/JudgeOnline/problem.php?id=1894 + +**问题描述** +``` +教授手上有`M`个一模一样的鹰蛋,教授想研究这些蛋的硬度`E`,测试方法是将蛋从高为`N`层的楼上不断自由落下; +每个蛋在`E+1`层及以上掉下都会碎,而在`E`层及以下不会碎;每个蛋可以重复测试直到它碎了为止。 + +例如:蛋从第 1 层掉下碎了,则`E=0`;蛋从第`N`层掉下未碎,则`E=N`。 + +求在给定`M`和`N`下为了确定`E`在**最坏情况下**需要测试的最少次数。 +如果比较的次数大于 32,输出 "Impossible"。 + +范围:1 ≤ N ≤ 2000000007,1 ≤ K ≤ 32 + +示例:`N=10, K=1`,则`ans=10` +说明:如果只有一个蛋,那么只能将这个蛋一层层往上尝试; + 因此在最坏情况下,它最少要测试 10 次才能确定 `E` +``` + +**分析** +- 如果只有 `M=1` 个蛋,那么只能从第一层开始一层一层往上尝试,最坏情况下的最少次数为 `N` +- 如果蛋的数量足够多,那么问题转变为二分查找,最坏情况下的最少次数为 **`logN` 上取整** + +**思路** +- **DP 定义**:`dp[i][j] := i 个蛋比较 j 次所能确定的最高楼层` +- **DP 初始化** + ``` + dp[i][1] = 1 // i 个蛋比较 1 次所能确定的最高楼层是 1 + dp[1][j] = j // 1 个蛋比较 j 次所能确定的最高楼层为 j + ``` +- **DP 更新** + ``` + dp[i][j] = dp[i][j-1] + dp[i-1][j-1] + 1 + ``` + **说明**:TODO(不理解是如何得到这个递推式的) + +- **C++** + ```C++ + // OJ 地址:http://acm.zcmu.edu.cn/JudgeOnline/problem.php?id=1894 + #include + + typedef long long LL; + + const int MAX_K = 32 + 1; + const int MAX_T = 32 + 1; + LL dp[MAX_K][MAX_T]; // 使用 LL 防止溢出,long 不保证比 int 更大 + // dp[i][j] := i 个蛋比较 j 次所能确定的最高楼层 + + void init() { + // 初始化 + for (int i = 1; i < MAX_K; i++) + dp[i][1] = 1; + for (int j = 1; j < MAX_T; j++) + dp[1][j] = j; + + // 更新 + for (int i = 2; i < MAX_K; i++) + for (int j = 2; j < MAX_T; j++) + dp[i][j] = dp[i][j - 1] + dp[i - 1][j - 1] + 1; + } + + void solve() { + init(); + //printf("%lld", dp[32][32]); // 4294967295 == 2^32 - 1,用 int 会溢出 + + int T; // 1 ≤ T ≤ 10000 + scanf("%d", &T); + while (T--) { + int N, K; // 1 ≤ N ≤ 2000000007 < 2^31, 1 ≤ K ≤ 32 + scanf("%d %d", &N, &K); + int ret = 0; + for (int j = 1; j < MAX_T; j++) { + // 注意:dp[i][j] 表示的是 i 个蛋比较 j 次所能确定的最高楼层 + if (dp[K][j] >= N) { + ret = j; + break; + } + } + + if (ret) printf("%d\n", ret); + else puts("Impossible"); + } + } + + int main() { + solve(); + return 0; + } + ``` + +**Reference** +- [从《鹰蛋》一题浅析对动态规划算法的优化](https://wenku.baidu.com/view/7d57940ef12d2af90242e6ac.html)_百度文库 + +## 矩阵链乘法 TODO + +## 有代价的最短路径 TODO + +## 瓷砖覆盖(状态压缩DP) TODO + +## 工作量划分 TODO + +## 三路取苹果 TODO diff --git "a/C-\347\256\227\346\263\225/\344\270\223\351\242\230-B-\345\217\214\346\214\207\351\222\210.md" "b/C-\347\256\227\346\263\225/\344\270\223\351\242\230-B-\345\217\214\346\214\207\351\222\210.md" new file mode 100644 index 00000000..c5b819be --- /dev/null +++ "b/C-\347\256\227\346\263\225/\344\270\223\351\242\230-B-\345\217\214\346\214\207\351\222\210.md" @@ -0,0 +1,1557 @@ +专题-双指针 +=== +- 双指针问题无论在笔试还是面试中出现的频率都非常高;是性价比非常高的一类问题。 + +模板小结 +--- +- **首尾双指针** +
+ + - 一般用于寻找数组/双向链表中满足条件的**两个节点**;如果是寻找多个数,则先固定前 n-2 个数; + - 为了不遗漏所有可能情况,可能要求数组**有序**; + - **典型问题**:[两数之和](#两数之和)、[三数之和](#三数之和)、[三角形计数](#三角形计数valid-triangle-number) +- **同向双指针** +
+ + - **数组**中,一般用于寻找满足某个条件的**连续区间**; + - **链表**相关问题中经常会使用**快慢双指针**来寻找某个节点; + - **典型问题**:[最小覆盖子串](#最小覆盖子串minimum-window-substring)、[数组中的最长山脉(同向双指针)](#数组中的最长山脉longest-mountain-in-array同向双指针) +- **反向双指针** +
+ + - 先依次遍历都某个节点,然后使用双指针从该节点反向判断是否满足条件。 + - **典型问题**:[最长回文子串](#最长回文子串longest-palindromic-substring)、[数组中的最长山脉(反向双指针)](#数组中的最长山脉longest-mountain-in-array反向双指针) +- **分离双指针** +
+ + - 输入是两个数组/链表,两个指针分别在两个容器中移动; + - 根据问题的不同,初始位置可能都在头部,或者都在尾部,或一头一尾。 + +
+ + - **典型问题**:[两个数组的交集](#两个数组的交集intersection-of-two-arrays)、[合并两个有序数组](#合并两个有序数组merge-sorted-array) + +RoadMap +--- +- [首尾双指针](#首尾双指针) +- [同向双指针](#同向双指针) +- [反向双指针](#反向双指针) +- [分离双指针](#分离双指针) +- [链表相关](#链表相关) +- [其他](#其他) + +Index +--- + + +- [首尾双指针](#首尾双指针) + - [两数之和](#两数之和) + - [三数之和](#三数之和) + - [N 数之和](#n-数之和) + - [最接近的三数之和](#最接近的三数之和) + - [两数之和 - 小于等于目标值的个数](#两数之和---小于等于目标值的个数) + - [三数之和 - 小于等于目标值的个数](#三数之和---小于等于目标值的个数) + - [三角形计数(Valid Triangle Number)](#三角形计数valid-triangle-number) + - [接雨水(Trapping Rain Water)(一维)](#接雨水trapping-rain-water一维) + - [盛最多水的容器(Container With Most Water)](#盛最多水的容器container-with-most-water) + - [反转字符串(Reverse String)](#反转字符串reverse-string) + - [颜色分类(Sort Colors)](#颜色分类sort-colors) +- [同向双指针](#同向双指针) + - [数组中的最长山脉(Longest Mountain in Array)(同向双指针)](#数组中的最长山脉longest-mountain-in-array同向双指针) + - [最小覆盖子串(Minimum Window Substring)](#最小覆盖子串minimum-window-substring) + - [长度最小的子数组(Minimum Size Subarray Sum)](#长度最小的子数组minimum-size-subarray-sum) + - [无重复字符的最长子串(Longest Substring Without Repeating Characters)](#无重复字符的最长子串longest-substring-without-repeating-characters) + - [水果成篮(Fruit Into Baskets)](#水果成篮fruit-into-baskets) +- [反向双指针](#反向双指针) + - [数组中的最长山脉(Longest Mountain in Array)(反向双指针)](#数组中的最长山脉longest-mountain-in-array反向双指针) + - [最长回文子串(Longest Palindromic Substring)](#最长回文子串longest-palindromic-substring) +- [分离双指针](#分离双指针) + - [实现 strstr()](#实现-strstr) + - [两个数组的交集(Intersection of Two Arrays)](#两个数组的交集intersection-of-two-arrays) + - [I](#i) + - [II](#ii) + - [合并两个有序数组(Merge Sorted Array)](#合并两个有序数组merge-sorted-array) +- [链表相关](#链表相关) + - [分隔链表(Partition List)](#分隔链表partition-list) + - [链表排序(Sort List)](#链表排序sort-list) + - [链表快排](#链表快排) + - [链表归并](#链表归并) + - [链表插入排序](#链表插入排序) + - [链表选择排序](#链表选择排序) + - [链表冒泡排序](#链表冒泡排序) + - [旋转链表(Rotate List)](#旋转链表rotate-list) +- [其他](#其他) + - [最小区间(Smallest Range)](#最小区间smallest-range) + + + +# 首尾双指针 + +## 两数之和 +> LeetCode/[167. 两数之和 II - 输入有序数组](https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted/description/) + +**问题描述**(167. 两数之和 II - 输入有序数组) +```python +给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。 + +函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。 + +说明: + 返回的下标值(index1 和 index2)不是从零开始的。 + 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。 + +示例: + 输入: numbers = [2, 7, 11, 15], target = 9 + 输出: [1,2] + 解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。 +``` + +**拓展** +- 如果存在多个答案,并要求输出所有不重复的可能 + > [三数之和](#三数之和) + +**思路 1** +- 首尾双指针 +- 因为是有序的,可以尝试使用首尾双指针解决该问题,时间复杂度为 `O(N)` +- **Python**(双指针) + ```python + class Solution: + def twoSum(self, A, t): + """ + :type A: List[int] + :type t: int + :rtype: List[int] + """ + n = len(A) + lo, hi = 0, n - 1 + + ret = [] + while lo < hi: + s = A[lo] + A[hi] + if s > t: + hi -= 1 + elif s < t: + lo += 1 + else: + ret.append(lo + 1) + ret.append(hi + 1) + break + + return ret + ``` + +**思路 2** +- 本题还可以利用 Hash 表解决,时间复杂度 `O(N)`,空间复杂度 `O(N)` +- 使用 Hash 表不要求数组有序 + > LeetCode/[1. 两数之和](https://leetcode-cn.com/problems/two-sum/description/) +- **Python**(Hash) + ```python + class Solution: + def twoSum(self, A, t): + """ + :type A: List[int] + :type t: int + :rtype: List[int] + """ + + d = dict() + + for i in range(len(A)): + if A[i] not in d: + d[t - A[i]] = i + else: + return [i, d[A[i]]] + ``` + + +## 三数之和 +> LeetCode/[15. 三数之和](https://leetcode-cn.com/problems/3sum/description/) + +**问题描述** +``` +给定一个包含 n 个整数的数组 nums, +判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ? +找出所有满足条件且不重复的三元组。 + +注意:答案中不可以包含重复的三元组。 + +例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4], + +满足要求的三元组集合为: +[ + [-1, 0, 1], + [-1, -1, 2] +] +``` + +**思路** +- 排序 + 首尾双指针 +- 将第三个数当作前两个数的目标和,在两数之和的基础上套一层循环 +- 难点在于如何**去重**(不借用 set) + +**python** +```python +class Solution: + def threeSum(self, A): + """ + :type A: List[int] + :rtype: List[List[int]] + """ + + A.sort() + n = len(A) + + ret = [] + for i in range(n - 2): + # 去重时注意判断条件 + if i > 0 and A[i] == A[i - 1]: # 对第一个数去重 + continue + + t = -A[i] + lo, hi = i + 1, n - 1 + + while lo < hi: + s = A[lo] + A[hi] + + if s < t: + lo += 1 + elif s > t: + hi -= 1 + else: + ret.append([A[i], A[lo], A[hi]]) + + # 先移动指针再去重 + lo += 1 + # hi -= 1 # 不必要 + + # 去重时注意判断条件 + while lo < hi and A[lo] == A[lo - 1]: # 对第二个数去重 + lo += 1 + # while lo < hi and A[hi] == A[hi + 1]: # 对第三个数去重(不必要) + # hi -= 1 + + return ret +``` + + +## N 数之和 +> LeetCode/[18. 四数之和](https://leetcode-cn.com/problems/4sum/description/) + +**题目描述**(四数之和) +``` +给定一个包含 n 个整数的数组 nums 和一个目标值 target, +判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等? +找出所有满足条件且不重复的四元组。 + +注意: + +答案中不可以包含重复的四元组。 + +示例: + +给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。 + +满足要求的四元组集合为: +[ + [-1, 0, 0, 1], + [-2, -1, 1, 2], + [-2, 0, 0, 2] +] +``` + +**Python**(N 数之和) +```python +def nSum(A, N, t, tmp, ret): + if len(A) < N or N < 2 or t < A[0] * N or t > A[-1] * N: # 结束条件 + return + + if N == 2: + lo, hi = 0, len(A) - 1 + while lo < hi: + s = A[lo] + A[hi] + + if s < t: + lo += 1 + elif s > t: + hi -= 1 + else: + ret.append(tmp + [A[lo], A[hi]]) + lo += 1 + while lo < hi and A[lo] == A[lo - 1]: # 去重 + lo += 1 + else: + for i in range(len(A) - N + 1): + if i > 0 and A[i] == A[i - 1]: # 去重 + continue + + nSum(A[i+1:], N-1, t-A[i], tmp + [A[i]], ret) + + +class Solution: + + def fourSum(self, A, t): + """ + :type A: List[int] + :type t: int + :rtype: List[List[int]] + """ + + A.sort() + ret = [] + + nSum(A, 4, t, [], ret) + + return ret +``` + + +## 最接近的三数之和 +> LeetCode/[16. 最接近的三数之和](https://leetcode-cn.com/problems/3sum-closest/description/) + +**问题描述** +``` +给定一个包括 n 个整数的数组 nums 和 一个目标值 target。 +找出 nums 中的三个整数,使得它们的和与 target 最接近。 +返回这三个数的和。假定每组输入只存在唯一答案。 + +例如,给定数组 nums = [-1,2,1,-4], 和 target = 1. + +与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2). +``` + +**思路** +- 排序 + 双指针 + +**Python** +```python +class Solution: + def threeSumClosest(self, A, t): + """ + :type A: List[int] + :type t: int + :rtype: int + """ + A.sort() # 先排序 + n = len(A) + + ans = A[0] + A[1] + A[2] # 用一个特殊的值初始化 + for i in range(n-2): + + lo, hi = i + 1, n - 1 # 首尾指针 + while lo < hi: + s = A[i] + A[lo] + A[hi] + if abs(s - t) < abs(ans - t): + ans = s + if ans == t: + return ans + + if s < t: + lo += 1 + else: + hi -= 1 + + return ans +``` + + +## 两数之和 - 小于等于目标值的个数 +> LintCode/[609. 两数和-小于或等于目标值](https://www.lintcode.com/problem/two-sum-less-than-or-equal-to-target) +>> 此为收费问题:[LintCode 练习-609. 两数和-小于或等于目标值](https://blog.csdn.net/qq_36387683/article/details/81460276) - CSDN博客 + +**问题描述** +``` +给定一个整数数组,找出这个数组中有多少对的和是小于或等于目标值。返回对数。 + +样例 + + 给定数组为 [2,7,11,15],目标值为 24 + 返回 5。 + 2+7<24 + 2+11<24 + 2+15<24 + 7+11<24 + 7+15<24 +``` + +**思路**: +- 排序 + 首尾双指针 + +**python** +```python +class Solution: + + def twoSum5(self, A, t): + """ + :type A: List[int] + :type t: int + :rtype: List[int] + """ + n = len(A) + lo, hi = 0, n - 1 + A.sort() # 如果是首尾双指针,一般要求有序 + + cnt = 0 + while lo < hi: + s = A[lo] + A[hi] + if s <= t: + cnt += hi-lo + lo += 1 + else: + hi -= 1 + + return cnt +``` + +**代码说明** +- 以 `[2,7,11,15]` 为例 +``` +第一轮:lo = 0, hi = 3 + s = A[lo] + A[hi] = 17 <= 24 + 因此 [2, 15], [2, 11], [2, 7] 均满足,hi - lo = 3 + lo += 1 +第二轮:lo = 1, hi = 3 + s = A[lo] + A[hi] = 22 <= 24 + 因此 [7, 15], [7, 11] 均满足,hi - lo = 2 + lo += 1 +第三轮:lo = 2, hi = 3 + s = A[lo] + A[hi] = 26 > 24 + hi -= 1 +不满足 lo < hi,退出,因此总数量为 3 + 2 = 5 +``` + +**时间复杂度** +- `O(NlogN) + O(N) = O(NlogN)` + + +## 三数之和 - 小于等于目标值的个数 +> LintCode/[918. 三数之和](https://www.lintcode.com/problem/3sum-smaller/description) + +**问题描述** +``` +给定一个n个整数的数组和一个目标整数target, +找到下标为i、j、k的数组元素0 <= i < j < k < n,满足条件nums[i] + nums[j] + nums[k] < target. + +样例 +给定 nums = [-2,0,1,3], target = 2, 返回 2. + +解释: + 因为有两种三个元素之和,它们的和小于2: + [-2, 0, 1] + [-2, 0, 3] +``` + +**思路** +- 排序 + 双指针 + +**Python** +```python +class Solution: + + def threeSumSmaller(self, A, t): + """ + :type A: List[int] + :type t: int + :rtype: List[int] + """ + A.sort() + n = len(A) + + cnt = 0 + for i in range(n - 2): + lo, hi = i + 1, n - 1 + + while lo < hi: + s = A[i] + A[lo] + A[hi] + + if s < t: + cnt += hi - lo + lo += 1 + else: + hi -= 1 + + return cnt +``` + + +## 三角形计数(Valid Triangle Number) +> LeetCode/[611. 有效三角形的个数](https://leetcode-cn.com/problems/valid-triangle-number/description/) + +**问题描述** +``` +给定一个包含非负整数的数组,你的任务是统计其中可以组成三角形三条边的三元组个数。 + +示例 1: + 输入: [2,2,3,4] + 输出: 3 +解释: + 有效的组合是: + 2,3,4 (使用第一个 2) + 2,3,4 (使用第二个 2) + 2,2,3 +注意: + 数组长度不超过1000。 + 数组里整数的范围为 [0, 1000]。 +``` + +**思路** +- 排序 + 首尾双指针 +- 相当于两数之和大于目标值的个数 + +**Python** +```python +class Solution: + def triangleNumber(self, A): + """ + :type A: List[int] + :rtype: int + """ + A.sort() + n = len(A) + + cnt = 0 + for i in range(2, n): # 注意:循环区间 + + lo, hi = 0, i - 1 + while lo < hi: + s = A[lo] + A[hi] + + if s > A[i]: + cnt += hi - lo + hi -= 1 + else: + lo += 1 + + return cnt +``` + + +## 接雨水(Trapping Rain Water)(一维) +> LeetCode/[42. 接雨水](https://leetcode-cn.com/problems/trapping-rain-water/description/) + +**问题描述** +``` +给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 + +示例: + 输入: [0,1,0,2,1,0,1,3,2,1,2,1] + 输出: 6 +``` +
+ +**思路 1** +- 一个简单的方法是**遍历两次**数组,分别记录每个位置左侧的最高点和右侧的最低点 +- **C++** + ```C++ + class Solution { + public: + int trap(vector& H) { + int n = H.size(); + + vector l_max(H); + vector r_max(H); + + for(int i=1; i=0; i--) + r_max[i] = max(r_max[i+1], r_max[i]); + + int ret = 0; + for (int i=1; i max_l: + max_l = A[l] + else: + ans += max_l - A[l] + + l += 1 + else: + if A[r] > max_r: + max_r = A[r] + else: + ans += max_r - A[r] + + r -= 1 + + return ans + ``` + + +## 盛最多水的容器(Container With Most Water) +> LeetCode/[11. 盛最多水的容器](https://leetcode-cn.com/problems/container-with-most-water/description/) + +**问题描述** +``` +给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 + +说明:你不能倾斜容器,且 n 的值至少为 2。 + +示例: + 输入: [1,8,6,2,5,4,8,3,7] + 输出: 49 +``` +
+ +**思路** +- 首尾双指针 + +**Python** +```python +class Solution: + def maxArea(self, A): + """ + :type A: List[int] + :rtype: int + """ + n = len(A) + + l, r = 0, n - 1 + res = (r - l) * min(A[l], A[r]) + while l < r: + if A[l] < A[r]: + l += 1 + else: + r -= 1 + + tmp = (r - l) * min(A[l], A[r]) + res = max(res, tmp) + + return res +``` + + +## 反转字符串(Reverse String) +> LeetCode/[344. 反转字符串](https://leetcode-cn.com/problems/reverse-string/description/) + +**问题描述** +``` +编写一个函数,其作用是将输入的字符串反转过来。 + +示例 1: + 输入: "hello" + 输出: "olleh" +示例 2: + 输入: "A man, a plan, a canal: Panama" + 输出: "amanaP :lanac a ,nalp a ,nam A" +``` + +**思路** +- 首尾双指针 + +**Python** +```python +class Solution: + def reverseString(self, s): + """ + :type s: str + :rtype: str + """ + n = len(s) + s = list(s) # python 不支持直接修改字符串中的字符 + + l, r = 0, n - 1 + while l < r: + s[l], s[r] = s[r], s[l] # swap + l += 1 + r -= 1 + + return ''.join(s) +``` + + +## 颜色分类(Sort Colors) +> LeetCode/[75. 颜色分类](https://leetcode-cn.com/problems/sort-colors/description/) + +**问题描述** +``` +给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。 + +此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。 + +注意: + 不能使用代码库中的排序函数来解决这道题。 + +示例: + 输入: [2,0,2,1,1,0] + 输出: [0,0,1,1,2,2] + +进阶: + 一个直观的解决方案是使用计数排序的两趟扫描算法。 + 首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。 + 你能想出一个仅使用常数空间的一趟扫描算法吗? +``` + +**思路** +- 首尾双指针 + - `l` 记录最后一个 0 的位置 + - `r` 记录第一个 2 的位置 + - `i` 表示当前遍历的元素 + +**Python** +```python +class Solution: + def sortColors(self, A): + """ + :type A: List[int] + :rtype: void Do not return anything, modify A in-place instead. + """ + n = len(A) + + l = 0 # l 指示最后一个 0 + r = n - 1 # r 指示第一个 2 + + i = 0 + while i <= r: + if A[i] == 0: + A[i] = A[l] + A[l] = 0 # 确定最后一个 0 + l += 1 + if A[i] == 2: + A[i] = A[r] + A[r] = 2 # 确定第一个 2 + r -= 1 + i -= 1 # 注意回退,因为不确定原 A[r] 处是什么 + i += 1 +``` + + +# 同向双指针 + +## 数组中的最长山脉(Longest Mountain in Array)(同向双指针) +> LeetCode/[845. 数组中的最长山脉](https://leetcode-cn.com/problems/longest-mountain-in-array/description/) + +**问题描述** +``` +我们把数组 A 中符合下列属性的任意连续子数组 B 称为 “山脉”: + +B.length >= 3 +存在 0 < i < B.length - 1 使得 B[0] < B[1] < ... B[i-1] < B[i] > B[i+1] > ... > B[B.length - 1] +(注意:B 可以是 A 的任意子数组,包括整个数组 A。) + +给出一个整数数组 A,返回最长 “山脉” 的长度。 + +如果不含有 “山脉” 则返回 0。 + +示例 1: + 输入:[2,1,4,7,3,2,5] + 输出:5 + 解释:最长的 “山脉” 是 [1,4,7,3,2],长度为 5。 + +示例 2: + 输入:[2,2,2] + 输出:0 + 解释:不含 “山脉”。 + +提示: + 0 <= A.length <= 10000 + 0 <= A[i] <= 10000 +``` + +**思路 - 同向双指针** +- 同向双指针(滑动窗口),只需遍历一遍数组 + +**Python** +```python +class Solution: + def longestMountain(self, A): + """ + :type A: List[int] + :rtype: int + """ + n = len(A) + + res = 0 + i = 1 + while i < n: + l = i - 1 # 左山脚,注意 i 是从 1 开始的 + while i < n and A[i] > A[i - 1]: + i += 1 + + if l == i - 1: # 不是山坡 + i += 1 + continue + + r = i - 1 # 右山脚 + while r < n - 1 and A[r] > A[r + 1]: + r += 1 + + if r == i - 1: # 不是山坡 + i += 1 + continue + else: + res = max(res, r - l + 1) + i = r + 1 # + + return res +``` + + +## 最小覆盖子串(Minimum Window Substring) +> LeetCode/[76. 最小覆盖子串](https://leetcode-cn.com/problems/minimum-window-substring/description/) + +**问题描述** +``` +给定一个字符串 S 和一个字符串 T,请在 S 中找出包含 T 所有字母的最小子串。 + +示例: + 输入: S = "ADOBECODEBANC", T = "ABC" + 输出: "BANC" +说明: + 如果 S 中不存这样的子串,则返回空字符串 ""。 + 如果 S 中存在这样的子串,我们保证它是唯一的答案。 +``` + +**思路** +- 同向双指针 + HaspMap + +**Python** +```python +from collections import Counter + +class Solution: + def minWindow(self, s, t): + """ + :type s: str + :type t: str + :rtype: str + """ + map_t = Counter(t) # 记录每个字符出现的次数 + + found = dict() # 保存滑动窗口 [i,j] 中出现的符号及其次数 + cnt_found = 0 # 记录已经匹配的数量 + min_len = len(s) + 1 # 记录最短结果 + + res = "" + l = 0 + for r in range(len(s)): + if s[r] in map_t: + found[s[r]] = found.get(s[r], 0) + 1 + if found[s[r]] <= map_t[s[r]]: + cnt_found += 1 + + if cnt_found >= len(t): # 如果存在这样的字串 + while (s[l] not in map_t) or (s[l] in found and found[s[l]] > map_t[s[l]]): + if s[l] in found and found[s[l]] > map_t[s[l]]: + found[s[l]] -= 1 + l += 1 + + if r - l + 1 < min_len: # 更新最小结果 + min_len = r - l + 1 + res = s[l: l + min_len] # 等价于 s[l: r+1] + + return res +``` + + +## 长度最小的子数组(Minimum Size Subarray Sum) +> LeetCode/[209. 长度最小的子数组](https://leetcode-cn.com/problems/minimum-size-subarray-sum/description/) + +**问题描述** +``` +给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回 0。 + +示例: + 输入: s = 7, nums = [2,3,1,2,4,3] + 输出: 2 + +解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。 + +进阶: 如果你已经完成了O(n) 时间复杂度的解法, 请尝试 O(n log n) 时间复杂度的解法。 +``` + +**思路** +- 同向双指针 + +**Python** +```python +class Solution: + def minSubArrayLen(self, s, A): + """ + :type s: int + :type A: List[int] + :rtype: int + """ + n = len(A) + + res = n + 1 + l = -1 + cur_sum = 0 + for r in range(n): + if A[r] > s: + return 1 + + cur_sum += A[r] + + if cur_sum >= s: + while cur_sum >= s: # 注意要使用 >= + l += 1 # 因为 l 初始化为 -1,所以要先 ++ + cur_sum -= A[l] + + res = min(res, r - l + 1) + + return res if res != n + 1 else 0 # 结果不存在时返回 0 +``` + + +## 无重复字符的最长子串(Longest Substring Without Repeating Characters) +> LeetCode/[3. 无重复字符的最长子串](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/description/) + +**问题描述** +``` +给定一个字符串,找出不含有重复字符的最长子串的长度。 + +示例 1: + 输入: "abcabcbb" + 输出: 3 + 解释: 无重复字符的最长子串是 "abc",其长度为 3。 +示例 2: + 输入: "bbbbb" + 输出: 1 + 解释: 无重复字符的最长子串是 "b",其长度为 1。 +示例 3: + 输入: "pwwkew" + 输出: 3 + 解释: 无重复字符的最长子串是 "wke",其长度为 3。 + 请注意,答案必须是一个子串,"pwke" 是一个子序列 而不是子串。 +``` + +**思路** +- 同向双指针 + Hash 表 + +**Python** +```python +class Solution: + def lengthOfLongestSubstring(self, s): + """ + :type s: str + :rtype: int + """ + n = len(s) + + res = 0 + bok = dict() + l, r = 0, 0 + while r < n: + if s[r] not in bok: + bok[s[r]] = r + else: + l = max(l, bok[s[r]] + 1) # ERR: l = bok[s[r]] + 1 + bok[s[r]] = r + + res = max(res, r - l + 1) + # print(res, '\t', r, l) + r += 1 + + return res +``` + + +## 水果成篮(Fruit Into Baskets) +> LeetCode/[904. 水果成篮](https://leetcode-cn.com/problems/fruit-into-baskets/description/) + +**问题描述** +``` +在一排树中,第 i 棵树产生 tree[i] 型的水果。 +你可以从你选择的任何树开始,然后重复执行以下步骤: + +把这棵树上的水果放进你的篮子里。如果你做不到,就停下来。 +移动到当前树右侧的下一棵树。如果右边没有树,就停下来。 +请注意,在选择一颗树后,你没有任何选择:你必须执行步骤 1,然后执行步骤 2,然后返回步骤 1,然后执行步骤 2,依此类推,直至停止。 + +你有两个篮子,每个篮子可以携带任何数量的水果,但你希望每个篮子只携带一种类型的水果。 +用这个程序你能收集的水果总量是多少? + +示例 1: + 输入:[1,2,1] + 输出:3 + 解释:我们可以收集 [1,2,1]。 +示例 2: + 输入:[0,1,2,2] + 输出:3 + 解释:我们可以收集 [1,2,2]. + 如果我们从第一棵树开始,我们将只能收集到 [0, 1]。 +示例 3: + 输入:[1,2,3,2,2] + 输出:4 + 解释:我们可以收集 [2,3,2,2]. + 如果我们从第一棵树开始,我们将只能收集到 [1, 2]。 +示例 4: + 输入:[3,3,3,1,2,1,1,2,3,3,4] + 输出:5 + 解释:我们可以收集 [1,2,1,1,2]. + 如果我们从第一棵树或第八棵树开始,我们将只能收集到 4 个水果。 + +提示: + 1 <= tree.length <= 40000 + 0 <= tree[i] < tree.length +``` + +**思路** +- 题目大意:寻找一个最大的子区间,该子区间中只包含两种元素(无顺序要求) +- 同向双指针 + +**Python** +```python +class Solution: + def totalFruit(self, T): + """ + :type T: List[int] + :rtype: int + """ + n = len(T) + + l, r = 0, 0 + res = 0 + q = [] # 模拟容量为 2 的队列 + t = dict() # 记录每个种类最后出现的位置 + while r < n: + t[T[r]] = r # 更新位置 + + if T[r] in q and q[-1] == T[r]: + pass + elif T[r] in q and q[0] == T[r]: + q.pop(0) + q.append(T[r]) + elif len(q) < 2 and T[r] not in q: + q.append(T[r]) + elif len(q) >= 2 and T[r] not in q: + l = t[q.pop(0)] + 1 + q.append(T[r]) + + res = max(res, r - l + 1) + # print(res, '\t', l, r) + r += 1 + + return res +``` + + +# 反向双指针 + +## 数组中的最长山脉(Longest Mountain in Array)(反向双指针) +> LeetCode/[845. 数组中的最长山脉](https://leetcode-cn.com/problems/longest-mountain-in-array/description/) + +**问题描述** +``` +我们把数组 A 中符合下列属性的任意连续子数组 B 称为 “山脉”: + +B.length >= 3 +存在 0 < i < B.length - 1 使得 B[0] < B[1] < ... B[i-1] < B[i] > B[i+1] > ... > B[B.length - 1] +(注意:B 可以是 A 的任意子数组,包括整个数组 A。) + +给出一个整数数组 A,返回最长 “山脉” 的长度。 + +如果不含有 “山脉” 则返回 0。 + +示例 1: + 输入:[2,1,4,7,3,2,5] + 输出:5 + 解释:最长的 “山脉” 是 [1,4,7,3,2],长度为 5。 + +示例 2: + 输入:[2,2,2] + 输出:0 + 解释:不含 “山脉”。 + +提示: + 0 <= A.length <= 10000 + 0 <= A[i] <= 10000 +``` + +**思路 - 反向双指针** +- 先找到“山峰”,然后向两侧移动指针,寻找左右“山脚” +- 极端情况下,可能会遍历**两次数组**(左指针有一个回溯的过程) + +**Python** +```python +class Solution: + def longestMountain(self, A): + """ + :type A: List[int] + :rtype: int + """ + n = len(A) + + res = 0 + i = 1 + while i < n - 1: + # for i in range(1, n - 1): + if A[i - 1] < A[i] > A[i + 1]: # 先找“山峰” + l = i - 1 + r = i + 1 + while l > 0 and A[l - 1] < A[l]: # 找左侧山脚(回溯) + l -= 1 + while r < n - 1 and A[r + 1] < A[r]: # 找右侧山脚 + r += 1 + + res = max(res, r - l + 1) + + i = r + 1 # 跳过右边的下坡,这一段一定不存在山脉 + else: + i += 1 + + return res +``` + + +## 最长回文子串(Longest Palindromic Substring) +> LeetCode/[5. 最长回文子串](https://leetcode-cn.com/problems/longest-palindromic-substring/description/) + +**问题描述** +``` +给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为1000。 + +示例 1: + 输入: "babad" + 输出: "bab" + 注意: "aba"也是一个有效答案。 +示例 2: + 输入: "cbbd" + 输出: "bb" +``` + +**思路** +- 搜索最长回文子串的方法很多;最主要的是动态规划和双指针方法 + > 这两种方法的时间复杂度都不是最优的, `O(N^2)`;此外还存在 `O(NlogN)` 和 `O(N)` 方法,但是这些方法并不存在普适性 + >> ./动态规划/[最长回文子串](./专题-B-动态规划#最长回文子串) +- 这里介绍的使用**双指针**的方法; +- 值得注意的一点是,回文子串的长度可能为奇或偶:一个简单的处理方法是对每个位置都判断奇偶两种情况 + +**Python** +```python +class Solution: + def longestPalindrome(self, s): + """ + :type s: str + :rtype: str + """ + if not s: + return "" + + self.b, self.l = 0, 0 # begin, length + + n = len(s) + for mid in range(n): + self.foo(s, mid, mid) # 奇数情况 + self.foo(s, mid, mid + 1) # 偶数的情况 + + return s[self.b: self.b + self.l] + + def foo(self, s, l, r): + n = len(s) + while l >=0 and r < n and s[l] == s[r]: + l -= 1 + r += 1 + + if r - l - 1 > self.l: # 注意这里是 r-l-1,因为退出循环时 s[l] != s[r] + self.b = l + 1 + self.l = r - l - 1 +``` + +# 分离双指针 + +## 实现 strstr() +> LeetCode/[28. 实现strStr()](https://leetcode-cn.com/problems/implement-strstr/description/) + +**问题描述** +``` +实现 strStr() 函数。 + +给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。 + +示例 1: + 输入: haystack = "hello", needle = "ll" + 输出: 2 +示例 2: + 输入: haystack = "aaaaa", needle = "bba" + 输出: -1 +说明: + +当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。 + +对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。 +``` + +**思路** +- 模式匹配,常见的高效算法有 KMP 算法和 Karp-Rabin(KR)算法 + > [字符串模式匹配算法——BM、Horspool、Sunday、KMP、KR、AC算法 - 单车博客园](https://www.cnblogs.com/dancheblog/p/3517338.html) - 博客园 +- 这里只介绍双指针方法 `O(nm)` 和 Karp-Rabin 算法 `O(n+m)` + +**双指针** +```python +class Solution: + def strStr(self, S, T): + """ + :type S: str + :type T: str + :rtype: int + """ + if not T: # 如果 T 为空,返回 0,与 C++/Java 行为一致 + return 0 + + n = len(S) + m = len(T) + + ans = -1 # 不存在返回 -1 + found = 0 + for i in range(n - m + 1): + for j in range(m): + if S[i + j] != T[j]: + break + + if j == m - 1: + ans = i + found = 1 + + if found: + break + + return ans +``` + +**KR 算法** +```python + +``` + + +## 两个数组的交集(Intersection of Two Arrays) + +### I +> LeetCode/[349. 两个数组的交集](https://leetcode-cn.com/problems/intersection-of-two-arrays/description/) + +**问题描述** +``` +给定两个数组,编写一个函数来计算它们的交集。 + +示例 1: + 输入: nums1 = [1,2,2,1], nums2 = [2,2] + 输出: [2] + +示例 2: + 输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4] + 输出: [9,4] + +说明: + 输出结果中的每个元素一定是唯一的。 + 我们可以不考虑输出结果的顺序。 +``` + +**思路** +- 1)排序 + 二分 +- 2)Hash +- 3)排序 + 双指针 + +**Python**(双指针) +```python +class Solution: + def intersection(self, A, B): + """ + :type A: List[int] + :type B: List[int] + :rtype: List[int] + """ + A.sort() + B.sort() + + m = len(A) + n = len(B) + + res = [] + i, j = 0, 0 + while i < m and j < n: + if A[i] < B[j]: + i += 1 + elif A[i] > B[j]: + j += 1 + else: + if (i > 0 and A[i - 1] == A[i]) or (j > 0 and B[j - 1] == B[j]): # 去重 + pass + else: + res.append(A[i]) + + i += 1 + j += 1 + + # i += 1 + # while i < m and A[i - 1] == A[i]: + # i += 1 + # j += 1 + # while j < n and B[j - 1] == B[j]: + # j += 1 + + return res +``` + +### II +> LeetCode/[350. 两个数组的交集 II](https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/description/) + +**问题描述** +``` +给定两个数组,编写一个函数来计算它们的交集。 + +示例 1: + 输入: nums1 = [1,2,2,1], nums2 = [2,2] + 输出: [2,2] +示例 2: + 输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4] + 输出: [4,9] +说明: + 输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。 + 我们可以不考虑输出结果的顺序。 +进阶: + 如果给定的数组已经排好序呢?你将如何优化你的算法? + 如果 nums1 的大小比 nums2 小很多,哪种方法更优? + 如果 nums2 的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办? +``` + +**思路** +- 相比 1 少了一个去重的步骤 + +**进阶** +- 相关谈论 > [Solution to 3rd follow-up question](https://leetcode.com/problems/intersection-of-two-arrays-ii/discuss/82243/Solution-to-3rd-follow-up-question) + + +**Python** +```python +class Solution: + def intersect(self, A, B): + """ + :type A: List[int] + :type B: List[int] + :rtype: List[int] + """ + A.sort() + B.sort() + + m = len(A) + n = len(B) + + res = [] + l, r = 0, 0 + while l < m and r < n: + if A[l] < B[r]: + l += 1 + elif B[r] < A[l]: + r += 1 + else: + res.append(A[l]) + l += 1 + r += 1 + + return res +``` + + +## 合并两个有序数组(Merge Sorted Array) +> LeetCode/[88. 合并两个有序数组](https://leetcode-cn.com/problems/merge-sorted-array/description/) + +**问题描述** +``` +给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。 + +说明: + 初始化 nums1 和 nums2 的元素数量分别为 m 和 n。 + 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。 + +示例: + 输入: + nums1 = [1,2,3,0,0,0], m = 3 + nums2 = [2,5,6], n = 3 + 输出: [1,2,2,3,5,6] +``` + +**思路** +- 从后往前遍历 + +**Python** +```python +class Solution: + def merge(self, A, m, B, n): + """ + :type A: List[int] + :type m: int + :type B: List[int] + :type n: int + :rtype: void Do not return anything, modify A in-place instead. + """ + + i = m - 1 # 指向 A 的末尾 + j = n - 1 # 指向 B 的末尾 + p = m + n - 1 # 指向总的末尾 + + while i >= 0 and j >= 0: # 注意结束条件 + if A[i] > B[j]: + A[p] = A[i] + p -= 1 + i -= 1 + else: + A[p] = B[j] + p -= 1 + j -= 1 + + while j >= 0: # 如果原 A 数组先排完,需要继续把 B 中的元素放进 A;如果 B 先排完,则不需要 + A[p] = B[j] + p -= 1 + j -= 1 +``` + + +# 链表相关 + +## 分隔链表(Partition List) +> LeetCode/[86. 分隔链表](https://leetcode-cn.com/problems/partition-list/description/) + +**问题描述** +``` +给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。 + +你应当保留两个分区中每个节点的初始相对位置。 + +示例: + 输入: head = 1->4->3->2->5->2, x = 3 + 输出: 1->2->2->4->3->5 +``` + +**思路** +- 链表快排的中间操作; +- 新建两个链表,分别保存小于 x 和大于等于 x 的,最后拼接; +- 因为要求节点的相对位置不变,所以这么写比较方便; +- 一般来说,链表快排有两种写法:一种是交换节点内的值,一种是交换节点;该写法适用于后者。 +- ~~如果是用在链表快排中,可以把头节点作为 x,最后把 x 插进 lo 和 hi 链表的中间;~~ +- ~~这种写法不适合用在链表快排中,因为这里有拼接操作;~~ +- ~~在实际链表快排中 partition 操作只对中间一部分执行,如果需要拼接,容易出错。~~ + +**Python** +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def partition(self, h, x): + """ + :type h: ListNode + :type x: int + :rtype: ListNode + """ + l = lo = ListNode(0) + r = hi = ListNode(0) + + while h: + if h.val < x: + l.next = h # Python 中不支持 l = l.next = h 的写法,C++ 指针可以 + l = l.next + else: + r.next = h # Python 中不支持 r = r.next = h 的写法,C++ 指针可以 + r = r.next + + h = h.next + + r.next = None # 因为尾节点可能不小于 x,所以需要断开 + l.next = hi.next + + return lo.next +``` + +## 链表排序(Sort List) + +### 链表快排 +> ./数据结构/[链表快排](./专题-A-数据结构#链表快排) + +### 链表归并 +> ./数据结构/[链表归并](./专题-A-数据结构#链表归并) + +### 链表插入排序 +> ./数据结构/[链表插入排序](./专题-A-数据结构#链表插入排序) + +### 链表选择排序 +> ./数据结构/[链表选择排序](./专题-A-数据结构#链表选择排序) + +### 链表冒泡排序 +> ./数据结构/[链表冒泡排序](./专题-A-数据结构#链表冒泡排序) + +## 旋转链表(Rotate List) +> ./数据结构/[旋转链表](./专题-A-数据结构#旋转链表rotate-list) + + +# 其他 + +## 最小区间(Smallest Range) +> LeetCode/[632. 最小区间](https://leetcode-cn.com/problems/smallest-range/) + +**问题描述** +``` +你有 k 个升序排列的整数数组。找到一个最小区间,使得 k 个列表中的每个列表至少有一个数包含在其中。 + +我们定义如果 b-a < d-c 或者在 b-a == d-c 时 a < c,则区间 [a,b] 比 [c,d] 小。 + +示例 1: + 输入:[[4,10,15,24,26], [0,9,12,20], [5,18,22,30]] + 输出: [20,24] + 解释: + 列表 1:[4, 10, 15, 24, 26],24 在区间 [20,24] 中。 + 列表 2:[0, 9, 12, 20],20 在区间 [20,24] 中。 + 列表 3:[5, 18, 22, 30],22 在区间 [20,24] 中。 +注意: + 给定的列表可能包含重复元素,所以在这里升序表示 >= 。 + 1 <= k <= 3500 + -10^5 <= 元素的值 <= 10^5 +``` + +**思路** +- 最小堆 + +**Python** +- 其中 `l, r` 表示区间;`i, j` 表示 `A[i][j]` +```python +import heapq + +class Solution: + def smallestRange(self, A): + """ + :type A: List[List[int]] + :rtype: List[int] + """ + pq = [(row[0], i, 0) for i, row in enumerate(A)] + heapq.heapify(pq) # 最小堆 + + ans = -1e5, 1e5 + r = max(row[0] for row in A) + while pq: + l, i, j = heapq.heappop(pq) + if r - l < ans[1] - ans[0]: + ans = l, r + if j + 1 == len(A[i]): + break + r = max(r, A[i][j+1]) + heapq.heappush(pq, (A[i][j+1], i, j+1)) + + return ans +``` \ No newline at end of file diff --git "a/C-\347\256\227\346\263\225/\344\270\223\351\242\230-C-\345\214\272\351\227\264\351\227\256\351\242\230.md" "b/C-\347\256\227\346\263\225/\344\270\223\351\242\230-C-\345\214\272\351\227\264\351\227\256\351\242\230.md" new file mode 100644 index 00000000..da060bc5 --- /dev/null +++ "b/C-\347\256\227\346\263\225/\344\270\223\351\242\230-C-\345\214\272\351\227\264\351\227\256\351\242\230.md" @@ -0,0 +1,219 @@ +专题-区间问题 +=== + +基本方法 +--- +- **双指针**(滑动窗口) + - 定长滑动 + - 变长滑动 +- **扫描线算法** + +Index +--- + + +- [会议室](#会议室) +- [会议室 II(扫描线算法)](#会议室-ii扫描线算法) +- [合并区间](#合并区间) +- [划分字母区间(双指针)](#划分字母区间双指针) + + + +### 会议室 +> LintCode - [920. 会议室](https://www.lintcode.com/problem/meeting-rooms/description) + +**问题描述** +``` +给定一系列的会议时间间隔,包括起始和结束时间[[s1,e1],[s2,e2],…(si < ei), +确定一个人是否可以参加所有会议。 + +样例 +给定区间=[[0,30],[5,10],[15,20]],返回false。 +``` + +**思路** +- 排序后判断左右区间 + +**C++** +```C++ +//class Interval { +//public: +// int start, end; +// Interval(int start, int end) { +// this->start = start; +// this->end = end; +// } +//}; + +class Solution { +public: + bool canAttendMeetings(vector &I) { + vector > tmp; + for (auto i : I) { + tmp.push_back({ i.start, i.end }); + // 原题使用的是自定义结构,需要自定义排序,为了方便使用 pair 重新存储 + } + + sort(tmp.begin(), tmp.end()); + + int end = 0; + for (auto i : tmp) { + if (i.first >= end) { + end = i.second; + } + else { + return false; + } + } + + return true; + } +}; +``` + + +### 会议室 II(扫描线算法) +> LintCode - [919. 会议室 II](https://www.lintcode.com/problem/meeting-rooms-ii/description) + +**问题描述** +``` +给定一系列的会议时间间隔intervals,包括起始和结束时间[[s1,e1],[s2,e2],...] (si < ei),找到所需的最小的会议室数量。 + +给出 intervals = [(0,30),(5,10),(15,20)], 返回 2. +``` + +**思路** +- 扫描线算法,这里应用了该算法的思想 + - 所有开始时间标记 `+1`,结束时间标记 `-1`; + - 每当扫描线经过一个节点(开始或结束)时,就累计该节点的标记值; + - 累计值的**最大值**即为答案; +
+ +**C++** +```C++ +class Solution { +public: + int minMeetingRooms(vector &I) { + vector > tmp; + for (auto i : I) { + tmp.push_back({ i.start, 1 }); + tmp.push_back({ i.end, -1 }); + } + + sort(tmp.begin(), tmp.end()); // 别忘了排序,此时不再区分开始与结束 + + int ret = 0; + int sum = 0; + for (auto i : tmp) { + sum += i.second; + ret = max(ret, sum); + } + + return ret; + } +}; +``` + + +### 合并区间 +> LeetCode - [56. 合并区间](https://leetcode-cn.com/problems/merge-intervals/description/) +
+LintCode - [156. 合并区间](https://www.lintcode.com/problem/merge-intervals/description) + +**问题描述** +``` +给出一个区间的集合,请合并所有重叠的区间。 + +示例 1: +输入: [[1,3],[2,6],[8,10],[15,18]] +输出: [[1,6],[8,10],[15,18]] +解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6]. + +示例 2: +输入: [[1,4],[4,5]] +输出: [[1,5]] +解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。 +``` + +**C++** +```C++ +class Solution { +public: + vector merge(vector& I) { + int n = I.size(); + if (n < 1) return vector(); + + sort(I.begin(), I.end(), [](const Interval& a, const Interval& b) { + return a.start != b.start ? a.start < b.start : a.end < b.end; + }); + + vector ret; + ret.push_back(I[0]); + for (int i = 1; i < n; i++) { + if (I[i].start <= ret.back().end) { + // ret.back().end = I[i].end; // err + ret.back().end = max(ret.back().end, I[i].end); + } + else { + ret.push_back(I[i]); + } + } + + return ret; + } +}; +``` + + +### 划分字母区间(双指针) +> LeetCode - [763. 划分字母区间](https://leetcode-cn.com/problems/partition-labels/description/) +
+LintCode - [1045. 分割标签](https://www.lintcode.com/problem/partition-labels/description) + +**问题描述** +``` +字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一个字母只会出现在其中的一个片段。返回一个表示每个字符串片段的长度的列表。 + +示例 1: +输入: S = "ababcbacadefegdehijhklij" +输出: [9,7,8] + +解释: +划分结果为 "ababcbaca", "defegde", "hijhklij"。 +每个字母最多出现在一个片段中。 +像 "ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。 + +注意: +S的长度在[1, 500]之间。 +S只包含小写字母'a'到'z'。 +``` + +**思路** +- 双指针 + map +- 先遍历一次,记录每个字符最后出现的位置 +- 再次遍历时不断更新高位指针的位置 + +**C++** +```C++ +class Solution { +public: + vector partitionLabels(string S) { + map bok; + + for (int i = 0; i < S.length(); i++) + bok[S[i]] = i; + + int lo = 0, hi = 0; + vector ret; + for (int i = 0; i < S.length(); i++) { + hi = max(hi, bok[S[i]]); + if (hi == i) { + ret.push_back(hi - lo + 1); + lo = hi + 1; + } + } + + return ret; + } +}; +``` diff --git "a/C-\347\256\227\346\263\225/\344\270\223\351\242\230-C-\346\216\222\345\210\227\347\273\204\345\220\210.md" "b/C-\347\256\227\346\263\225/\344\270\223\351\242\230-C-\346\216\222\345\210\227\347\273\204\345\220\210.md" new file mode 100644 index 00000000..64068d2a --- /dev/null +++ "b/C-\347\256\227\346\263\225/\344\270\223\351\242\230-C-\346\216\222\345\210\227\347\273\204\345\220\210.md" @@ -0,0 +1,860 @@ +专题-排列组合 +=== + +Index +--- + + +- [排列](#排列) + - [下一个排列](#下一个排列) + - [上一个排列](#上一个排列) + - [STL 提供的实现(下一个排列、上一个排列) TODO](#stl-提供的实现下一个排列上一个排列-todo) + - [第 k 个排列](#第-k-个排列) + - [全排列(无重复)](#全排列无重复) + - [基于插入的写法](#基于插入的写法) + - [基于交换的写法](#基于交换的写法) + - [全排列(有重复)](#全排列有重复) + - [基于插入的写法](#基于插入的写法-1) + - [基于交换的写法](#基于交换的写法-1) + - [【注】全排序的时间复杂度](#注全排序的时间复杂度) +- [组合](#组合) + - [组合(n 选 k,无重复)](#组合n-选-k无重复) + - [组合(n 选 k,有重复)](#组合n-选-k有重复) + - [组合总和(数字不重复但可重复使用)](#组合总和数字不重复但可重复使用) + - [组合总和 2(存在重复数字但每个数字只能使用一次)](#组合总和-2存在重复数字但每个数字只能使用一次) + - [组合总和 3(数字不重复且指定数量)](#组合总和-3数字不重复且指定数量) +- [【说明】](#说明) + - [字典序](#字典序) + - [关于 `for(i=0;..)` 与 `for(i=step;..)` 的说明](#关于-fori0-与-foristep-的说明) + - [【注】关于 `dfs(step+1)`、`dfs(i+1)`、`dfs(i)` 的说明](#注关于-dfsstep1dfsi1dfsi-的说明) + - [`dfs(step+1)` 和 `dfs(i+1)`](#dfsstep1-和-dfsi1) + - [`dfs(i+1)` 和 `dfs(i)`](#dfsi1-和-dfsi) + + + + +## 排列 + +### 下一个排列 +> LeetCode - [31. 下一个排列](https://leetcode-cn.com/problems/next-permutation/description/) + +**题目描述** +``` +实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。 + +如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。 + +必须原地修改,只允许使用额外常数空间。 + +以下是一些例子,输入位于左侧列,其相应输出位于右侧列。 +1,2,3 → 1,3,2 +3,2,1 → 1,2,3 +1,1,5 → 1,5,1 +``` + +**思路** +- **相邻的两个排列有最长公共前缀**,然后找到需要交换的**高位**和**低位** +- 根据字典序的定义,依照如下步骤寻找下一个排列 + > [字典序](#字典序) +1. 从后往前找需要改变的**高位** hi,即**第一个降序**元素的位置 + ``` + 1 5 8 4 7 6 5 3 1 + ↑ + hi + ``` +2. 从后往前找需要交换的**低位** lo,即**第一个大于** nums[hi] 的位置 + ``` + 1 5 8 4 7 6 5 3 1 + ↑ ↑ + hi lo + ``` +3. 交换 nums[lo] 与 nums[hi] + ``` + 1 5 8 4 7 6 5 3 1 + ↓ ↓ + 1 5 8 5 7 6 4 3 1 + ↑ ↑ + hi lo (hi 位置不变) + ``` +4. **反转** hi 之后的序列,即 nums[hi+1: n) + ``` + 1 5 8 5 7 6 4 3 1 + ↓ ↓ ↓ ↓ ↓ + 1 5 8 5 1 3 4 6 7 + ↑ ↑ + hi lo (hi 位置不变) + ``` +**C++** +```C++ +class Solution { +public: + void nextPermutation(vector& nums) { + int n = nums.size(); + if (n <= 1) return; + + int hi = n - 2; + // 1. 从后往前找需要改变的**高位** hi,即第一个降序元素的位置 + while (hi >= 0 && nums[hi + 1] <= nums[hi]) + hi--; + + if (hi >= 0) { + // 2. 从后往前找需要交换的**低位** lo,即第一个大于 nums[hi] 的位置 + int lo = n - 1; + while (lo >= 0 && nums[lo] <= nums[hi]) + lo--; + // 3. 交换 nums[lo] 与 nums[hi] + swap(nums[hi], nums[lo]); + } + + // 4. 反转 hi 之后的序列,即 nums[hi+1: n) + reverse(nums.begin() + hi + 1, nums.end()); + // 当 i == -1 时,该操作会使序列从字典序最大转为最小,这与 STL 中提供的 next_permutation 略有不同 + } +}; +``` + +### 上一个排列 +> LintCode - [51. 上一个排列](https://www.lintcode.com/problem/previous-permutation/description) + +**问题描述** +``` +给定一个整数数组来表示排列,找出其上一个排列。 +排列中可能包含重复的整数 + +样例 +给出排列[1,3,2,3],其上一个排列是[1,2,3,3] + +给出排列[1,2,3,4],其上一个排列是[4,3,2,1] +``` + +**思路** +- 实际上就是[下一个排列](#下一个排列)的逆过程 +1. 从右往左找**第一个升序**的位置 hi +1. 从右往左找**第一个小于** nums[hi] 的位置 lo +1. 交换 nums[lo] 和 nums[hi] +1. 反转 hi 之后的位置 + +**C++** +```C++ +class Solution { +public: + /* + * @param nums: A list of integers + * @return: A list of integers that's previous permuation + */ + vector previousPermuation(vector &nums) { + int n = nums.size(); + + if (n <= 1) return nums; + + int hi = n - 2; + // 1. 从右往左找**第一个升序**的位置 hi + while (hi >= 0 && nums[hi] <= nums[hi + 1]) + hi--; + + if (hi >= 0) { + int lo = n - 1; + // 2. 从右往左找**第一个小于** nums[hi] 的位置 lo + while (lo >= 0 && nums[lo] >= nums[hi]) + lo--; + // 3. 交换 nums[lo] 和 nums[hi] + swap(nums[lo], nums[hi]); + } + + // 4. 反转 hi 之后的位置 + reverse(nums.begin() + hi + 1, nums.end()); + + return nums; // 注意这里要你返回一个值 + } +}; +``` + +### STL 提供的实现(下一个排列、上一个排列) TODO +- STL 提供了两个函数用于生成排列 + ```C++ + bool next_permutation (BidirectionalIterator first, + BidirectionalIterator last); + + bool prev_permutation (BidirectionalIterator first, + BidirectionalIterator last ); + ``` +- 这两个函数均以**字典序**比较函数 `lexicographical_compare()`为基础生成下一个或上一个排列 +- 因此在使用这两个函数前,需要先对原序列进行**排序** + +**C++** +```C++ + +``` + +### 第 k 个排列 +> LeetCode - [60. 第k个排列](https://leetcode-cn.com/problems/permutation-sequence/description/) + +**问题描述** +```C +给出集合 [1,2,3,…,n],其所有元素共有 n! 种排列。 + +按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下: +"123" +"132" +"213" +"231" +"312" +"321" + +给定 n 和 k,返回第 k 个排列。 + +说明: + 给定 n 的范围是 [1, 9]。 + 给定 k 的范围是 [1, n!]。 +示例 1: + 输入: n = 3, k = 3 + 输出: "213" +示例 2: + 输入: n = 4, k = 9 + 输出: "2314" +``` + +**思路** +- 因为字典序的性质,实际不需要求出前 k-1 个序列 +- 整体思路有点像**桶排序** +- 以 `{1 2 3 4 5}` 为例,找出其中第 `14` 个序列 + ``` + 首先,可以先按第一个位置的元素,把所有序列依次到对应的桶中 + 在开始之前,先 k--,因为计算机计数是从 0 开始的,此时 k=13(下面会说明为什么需要减 1) + 第 1 轮:剩余 5 个元素,有 5 个桶 + 第0个桶:以 1 开头,剩余元素 {2 3 4 5} + 第1个桶:以 2 开头,剩余元素 {1 3 4 5} + 第2个桶:以 3 开头,剩余元素 {1 2 4 5} + 第3个桶:以 4 开头,剩余元素 {1 2 3 5} + 第4个桶:以 5 开头,剩余元素 {1 2 3 4} + 每个桶中有 4!=24 个序列,因为是有序的,显然第 k=13 个元素必然在第 `13/(4!) = 0` 个桶中 + 换言之,第 14 个元素必然以 1 开头 + 移除序列中的 1,剩余序列变为 {2 3 4 5},k = 13 % 24 = 13 + + 第 2 轮:剩余 4 个元素,有 4 个桶 + 第0个桶:以 2 开头,剩余元素 {3 4 5} + 第1个桶:以 3 开头,剩余元素 {2 4 5} + 第2个桶:以 4 开头,剩余元素 {2 3 5} + 第3个桶:以 5 开头,剩余元素 {2 3 4} + 每个桶中有 3!=6 个元素。显然,第 k=13 个元素应该在第 `13/(3!) = 2` 个桶中 + 即第 14 个元素的前缀为 14 + 移除序列中的 4,剩余序列变为 {2 3 5},k = 13 % 6 = 1 + + 第 3 轮:剩余 3 个元素,有 3 个桶 + 第0个桶:以 2 开头,剩余元素 {3 5} + 第1个桶:以 3 开头,剩余元素 {2 5} + 第2个桶:以 5 开头,剩余元素 {3 5} + 此时每个桶中有 2!=2 个元素。第 k=1 个元素应该在第 `1/(2!)=0` 个桶中(如果开始时 k 不减 1,这里就会出现问题) + 即第 14 个元素的前缀为 142 + 移除序列中的 2,剩余序列变为 {3 5},k = 1 % 2 = 1 + + 第 4 轮:剩余 2 个元素,有 2 个桶 + 第0个桶:以 3 开头,剩余元素 {5} + 第1个桶:以 5 开头,剩余元素 {3} + 此时每个桶中有 1!=1 个元素。第 k=1 个元素应该在第 `1/(1!)=1` 个桶中 + 即第 14 个元素的前缀为 1425 + 移除序列中的 5,剩余序列变为 {3},k = 1 % 1 = 0 + + 第 5 轮:剩余 1 个元素,有 1 个桶 + 第0个桶:以 3 开头,无剩余元素 + 此时每个桶中有 0!=1 个元素(实际上此时桶中没有元素)。 + 第 k=0 个元素应该在第 `0/(0!)=0` 个桶中(最后一轮利用 0!=1 的性质不需要特别处理) + 即第 14 个元素为 14253 + ``` + +**C++** +```C++ +class Solution { +public: + string getPermutation(int n, int k) { + + // nums: {1, 2, 3, ..., n} + // 换成其他字符,按字典序存放到对应位置即可 + vector nums(n + 1, 0); + for (int i = 0; i < n; i++) // 注意:桶的下标是从 0 开始的 + nums[i] = i + 1; + + // dp: {0!=1, 1!, 2!, ..., n!} + vector dp(n + 1, 1); // 根据上面的推导,dp[0]=1 正好可以处理最后一轮 + for (int i = 1; i <= n; i++) + dp[i] = dp[i - 1] * i; + + k--; + stringstream ss; + for (int i = 1; i <= n; i++) { // 从 1 开始 + int index = k / dp[n - i]; // 实际上没有用到 dp[n] = n! + ss << nums[index]; + nums.erase(nums.begin() + index); // 注意,每轮删除已处理的元素 + k = k % dp[n - i]; + } + + return ss.str(); + } +}; +``` + +### 全排列(无重复) +> LeetCode [46. 全排列](https://leetcode-cn.com/problems/permutations/description/) + +**题目描述** +``` +给定一个没有重复数字的序列,返回其所有可能的全排列。 + +示例: + +输入: [1,2,3] +输出: +[ + [1,2,3], + [1,3,2], + [2,1,3], + [2,3,1], + [3,1,2], + [3,2,1] +] +``` + +**思路 1** +- 利用下一个排列,先对数组排序,然后不断生成下一个排列 + +**思路 2** +- **深度优先搜索** +- 易知,当序列中的元素不重复时,存在 `n!` 种不同的排列; +- 考虑第一个位置,有 n 种可能 +- 当选定了第一个位置,第二个位置有 n-1 种可能 +- 因为**每次搜索的状态数**是递减的,所以这里的 dfs 是一个**循环递归**的过程 + +#### 基于插入的写法 +- 代码量多一点,但比较好理解 +```C++ +class Solution { + vector > ret; + vector tmp; + vector used; + int n = 0; + + void dfs(vector& nums, int step) { + if (tmp.size() == n) { + ret.push_back(tmp); + return; + } + + for (int i = 0; i < n; i++) { // i 每次从 0 开始,因为元素可以重复使用 + if (used[i]) continue; // 但是在每一轮中,如果用过了需要跳过 + // 每一轮指的是生成一次排列的过程 + used[i] = 1; // 标记使用 + tmp.push_back(nums[i]); + dfs(nums, step + 1); + tmp.pop_back(); // 回溯 + used[i] = 0; + } + } + +public: + vector > permute(vector& nums) { + n = nums.size(); + used.resize(n, 0); + + dfs(nums, 0); + return ret; + } +}; +``` +> [【注】关于 `for(i=0;..)` 与 `for(i=step;..)` 的说明](#注关于-fori0-与-foristep-的说明) + +#### 基于交换的写法 +- 基于交换的写法,代码比较简洁,但个人认为有一点不好理解 +```C++ +class Solution { + vector > ret; + + //void dfs(vector nums, int step) { // 值传递 + void dfs(vector& nums, int step) { // 引用传递 + if (step >= nums.size()) { + ret.push_back(nums); + return; + } + + for (int i = step; i < nums.size(); i++) { // 注意:这里 i 从 step 开始 + swap(nums[step], nums[i]); + dfs(nums, step + 1); + swap(nums[step], nums[i]); // 如果 nums 是值传入,则不需要这步;否则不能省略 + } + } + +public: + vector > permute(vector& nums) { + dfs(nums, 0); + return ret; + } +}; +``` + +### 全排列(有重复) +> LeetCode - [47. 全排列 II](https://leetcode-cn.com/problems/permutations-ii/description/) + +**题目描述** +``` +给定一个可包含重复数字的序列,返回所有不重复的全排列。 + +示例: + +输入: [1,1,2] +输出: +[ + [1,1,2], + [1,2,1], + [2,1,1] +] +``` + +**思路 1** +- 使用无重复时的方法,用 set 剔除重复(不推荐) + +**思路 2** +- 先对原序列**排序**,使相同的元素相邻;此时**只处理第一个相同元素**,其余跳过; + +#### 基于插入的写法 +```C++ +class Solution { + vector > ret; + vector tmp; + vector used; + int n = 0; + + void dfs(vector& nums, int step) { + if (tmp.size() == n) { + ret.push_back(tmp); + return; + } + + for (int i = 0; i < n; i++) { + if (used[i] || (i > 0 && nums[i] == nums[i - 1] && !used[i - 1])) + continue; // 这里的 !used[i - 1] 稍难理解,可以配合 IDE 或者手推一下整个过程 + + used[i] = 1; + tmp.push_back(nums[i]); + dfs(nums, step + 1); + tmp.pop_back(); + used[i] = 0; + } + } + +public: + vector > permuteUnique(vector& nums) { + n = nums.size(); + used.resize(n, 0); + sort(nums.begin(), nums.end()); + + dfs(nums, 0); + return ret; + } +}; +``` + +#### 基于交换的写法 +```C++ +class Solution { + vector > ret; + + //void dfs(vector& nums, int step) { // 传引用无法得出正确结果 + void dfs(vector nums, int step) { // 注意这里应该使用**值传递** + int n = nums.size(); + if (step >= n - 1) { + ret.push_back(nums); + return; + } + + for (int i = step; i < n; i++) { + if (i != step && nums[i] == nums[step]) + continue; + + swap(nums[i], nums[step]); + dfs(nums, step + 1); + //swap(nums[i], nums[step]); // 传引用配合回溯无法得出正确结果, + // 原因在于此时会破坏剩余数组的有序性 + } + } +public: + vector > permuteUnique(vector& nums) { + sort(nums.begin(), nums.end()); + dfs(nums, 0); + + return ret; + } +}; +``` + +### 【注】全排序的时间复杂度 +- 不重复情况下,n 个元素的不同全排列为 `n!` 个,所以算法的时间复杂度至少为 `O(N!)` +- 因此,全排列算法对大型的数据是无法处理的 + + +## 组合 + +### 组合(n 选 k,无重复) +> LeetCode - [77. 组合](https://leetcode-cn.com/problems/combinations/description/) + +**问题描述** +```C +给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。 + +示例: + +输入: n = 4, k = 2 +输出: + [ + [2,4], + [3,4], + [2,3], + [1,2], + [1,3], + [1,4], + ] +``` + +**思路** +- 带**回溯**的深度优先搜索,类似[全排列(无重复)](#全排列无重复) + +**C++** +```C++ +class Solution { + vector > ret; + vector tmp; // 保存中间结果 + int K; + + void dfs(vector& nums, int step) { + if (tmp.size() >= K) { + ret.push_back(tmp); + return; + } + + for (int i = step; i < nums.size(); i++) { + tmp.push_back(nums[i]); // nums[i] == i,所以这里直接 push(i) 也可以 + dfs(nums, i + 1); + tmp.pop_back(); + } + } + +public: + vector > combine(int n, int k) { + + K = k; + vector nums; + for (int i = 0; i < n; i++) + nums.push_back(i + 1); + + dfs(nums, 0); + return ret; + } +}; +``` + +### 组合(n 选 k,有重复) +(未验证) +- 如果要求每个组合中不重复,则可以先去重,再按照无重复的做法 +- 如果不要求去重,则直接按照无重复的做法即可 + +### 组合总和(数字不重复但可重复使用) +> LeetCode - [39. 组合总和](https://leetcode-cn.com/problems/combination-sum/description/) + +**思路** +- 深度优先搜索 +- 关键在于每个数字可以重复使用 + +**C++** +```C++ +class Solution { + vector > ret; + vector tmp; + int cur = 0; + int target = 0; + + void dfs(vector& nums, int step) { + if (cur >= target) { + if (cur == target) + ret.push_back(tmp); + return; + } + + for (int i = step; i < nums.size(); i++) { + cur += nums[i]; + tmp.push_back(nums[i]); + dfs(nums, i); // 因为每个数组可以重复使用,所以是 dfs(i) 而不是 dfs(i+1) + cur -= nums[i]; + tmp.pop_back(); + } + } + +public: + vector > combinationSum(vector& candidates, int target) { + this->target = target; + + //sort(candidates.begin(), candidates.end()); // 不需要 + dfs(candidates, 0); + + return ret; + } +}; +``` +> [【注】关于 `dfs(step+1)`、`dfs(i+1)`、`dfs(i)` 的说明](#注关于-dfsstep1dfsi1dfsi-的说明) + +### 组合总和 2(存在重复数字但每个数字只能使用一次) +> LeetCode - [40. 组合总和 II](https://leetcode-cn.com/problems/combination-sum-ii/description/) + +**思路** +- DFS,关键是如何去除重复情况 + +**C++** +```C++ +class Solution { + vector > ret; + vector tmp; + int cur = 0; + int target; + + void dfs(vector& nums, int step) { + if (cur >= target) { + if (cur == target) + ret.push_back(tmp); + return; + } + + for (int i = step; i < nums.size(); i++) { + if (i > step && nums[i] == nums[i - 1]) // 代码说明 1 + continue; + + cur += nums[i]; + tmp.push_back(nums[i]); + dfs(nums, i + 1); // i+1 而不是 i,因为不能重复使用 + tmp.pop_back(); + cur -= nums[i]; + } + } +public: + vector > combinationSum2(vector& candidates, int target) { + this->target = target; + + sort(candidates.begin(), candidates.end()); // 因为存在重复,需要先排序 + dfs(candidates, 0); + + return ret; + } +}; +``` + +**代码说明 1** +``` +if (i > step && nums[i] == nums[i - 1]) +``` +- 以 ![](../_assets/公式_20180905213339.png) 为例 +- 这段代码实际上并不会过滤 ![](../_assets/公式_20180905213323.png) ——`i == step` 的情况 +- 真正重复的情况是 ![](../_assets/公式_20180905213258.png) 和 ![](../_assets/公式_20180905213158.png),而这段代码的目的是过滤 ![](../_assets/公式_20180905213158.png) ——`i > step` 的情况 + +### 组合总和 3(数字不重复且指定数量) +> LeetCode - [216. 组合总和 III](https://leetcode-cn.com/problems/combination-sum-iii/description/) + +**问题描述** +```C +找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。 + +说明: + +所有数字都是正整数。 +解集不能包含重复的组合。 + +示例 1: +输入: k = 3, n = 7 +输出: [[1,2,4]] + +示例 2: +输入: k = 3, n = 9 +输出: [[1,2,6], [1,3,5], [2,3,4]] +``` + +**思路** +- 在[组合总和(数字不重复但可重复使用)](#组合总和数字不重复但可重复使用)上稍作修改即可 + +**C++** +```C++ +class Solution { + vector > ret; + vector tmp; + int cur = 0; + int K; + + void dfs(int n, int step) { + if (cur >= n) { + if (cur == n && tmp.size() == K) // 同时满足才加入结果集 + ret.push_back(tmp); + return; + } + + for (int i = step; i <= 9; i++) { + + cur += i; + tmp.push_back(i); + dfs(n, i + 1); // 因为数字不能重复使用,所以是 dfs(i+1) + tmp.pop_back(); + cur -= i; + } + } + +public: + vector > combinationSum3(int k, int n) { + K = k; + + dfs(n, 1); + return ret; + } +}; +``` + + + +## 【说明】 + +### 字典序 +- 在处理排列问题时,通常时根据**字典序**来生成下一个排列 +- 在字典序中,记序列的**升序**为第一个排列,**降序**为最后一个排列 + +**高位与低位** +- 对序列中任意两个位置而言,靠近左侧的为**高位**,靠近右侧的为低位 +- 生成排列的过程就是不断增大**高位**,减小**低位**的过程 + ``` + 1 2 3 + 1 3 2 + 2 1 3 + 2 3 1 + 3 1 2 + 3 2 1 + ``` + +### 关于 `for(i=0;..)` 与 `for(i=step;..)` 的说明 +- `for(i=0;..)` 需配合 `used` 标记使用 + - [全排列(无重复,基于插入的写法)](#基于插入的写法) + - [全排列(有重复,基于插入的写法)](#基于插入的写法-1) +- `for(i=step;..)` + - 所有组合问题 +- 简单来说,以 `{1 2 3 4}` 为例 + - `for(i=0;..)` 用于以下情况 + ``` + 1 + {2 3 4} + 2 + {1 3 4} + ... + ``` + - used 用于标记开头的 `1`、`2`等 + - `for(i=step;..)` 用于以下情况 + ``` + 1 + {2 3 4} + 2 + {3 4} + ... + ``` + - 一般不需要 `used` 标记 + +### 【注】关于 `dfs(step+1)`、`dfs(i+1)`、`dfs(i)` 的说明 +(以下均为个人小结,并没有严格验证) + +#### `dfs(step+1)` 和 `dfs(i+1)` +- 简单来说,`dfs(step+1)` 指的是生成 `tmp` 序列中的第 `step+1` 个位置;`dfs(i+1)` 指的是使用 `nums` 中的第 `i+1` 个元素 + - 在[排列(无重复)](#全排列无重复)问题中,使用的是 `dfs(step+1)` + - 在[组合(无重复)](#组合n-选-k无重复)问题中,使用的是 `dfs(i+1)` + - 相关代码段 + ```C++ + // 排列 + for (int i = step; i < nums.size(); i++) { + // ... + dfs(nums, step + 1); + // ... + } + + // 组合 + for (int i = step; i < nums.size(); i++) { + // ... + dfs(nums, i + 1); + // ... + } + ``` +- 以不重复集合 `{1 2 3 4}` 为例说明: + - 排列问题中用过的元素还可能被再次使用; + ``` + step = 0 时,即第一个位置是 1 + 所有的排列为 1 + {2 3 4} + step = 1 时,即第一个位置是 2 + 所有的排列为 2 + {1 3 4} # 1 又出现在了后序元素中 + ... + ``` + - 而组合问题中使用过的元素,之后不再使用 + ``` + step = 0 时,即第一个位置是 1 + 所有的组合为 1 + {2 3 4} + step = 1 时,即第一个位置是 2 + 所有的组合为 2 + {3 4} # 1 不再使用了 + ``` + - 正是由于这个区别导致**排列**中应该使用 `dfs(step+1)`,而**组合**中应该使用 `dfs(i+1)` + +#### `dfs(i+1)` 和 `dfs(i)` +- 在[组合总和](#组合总和)问题中,还用到了 `dfs(i)` + ``` + for (int i = step; i < nums.size(); i++) { + // ... + dfs(nums, i); + // ... + } + ``` + - 一方面,它跟组合问题类似,用过的数字不再使用;因此使用的是 `i` 而不是 `step` + - 另一方面,每个数字可以重复使用,因此使用的是 `dfs(i)` 而不是 `dfs(i+1)` \ No newline at end of file diff --git "a/C-\347\256\227\346\263\225/\344\270\223\351\242\230-C-\346\225\260\345\255\246\351\227\256\351\242\230.md" "b/C-\347\256\227\346\263\225/\344\270\223\351\242\230-C-\346\225\260\345\255\246\351\227\256\351\242\230.md" new file mode 100644 index 00000000..3d59c002 --- /dev/null +++ "b/C-\347\256\227\346\263\225/\344\270\223\351\242\230-C-\346\225\260\345\255\246\351\227\256\351\242\230.md" @@ -0,0 +1,66 @@ +专题-数学 +=== + +Index +--- + + +- [直线上最多的点数](#直线上最多的点数) + + + +### 直线上最多的点数 +> LeetCode - [149. 直线上最多的点数](https://leetcode-cn.com/problems/max-points-on-a-line/description/) + +**问题描述** + +**思路** +- ~~根据 `y=kx+b`,计算每两个点的 `(k, b)` 对,配合 map 存储~~ +- 使用 `(k,b)` 可能存在精度问题,更好的方法是使用 `ax+by+c=0` +- 两者本质上没有区别,实际上就是把 `k` 分为 `a/b` 存储 +- 注意:将 `{a, b}` 作为 key 时应该先利用**最大公约数**缩小 `a` 和 `b` + +**C++** +```C++ +class Solution { + int gcd(int a, int b) { + return b == 0 ? a : gcd(b, a%b); + } +public: + int maxPoints(vector& P) { + int n = P.size(); + if (n <= 2) return n; + + int ret = 0; + for (int i = 0; i < n; i++) { + map, int> line; + int tmp = 0; // 保存与 P[i] 共线的点 + int dup = 0; // 记录重复点 + int col = 0; // 跟 P[i] 垂直的点 + + for (int j = i + 1; j < n; j++) { + if (P[i].x == P[j].x && P[i].y == P[j].y) { + dup += 1; + } + else if (P[i].x == P[j].x) { + col += 1; + tmp = max(tmp, col); + } + else { + int a = P[i].y - P[j].y; + int b = P[i].x - P[j].x; + + int t = gcd(a, b); // 利用最大公约数缩小 + a /= t; + b /= t; + line[{a, b}]++; + tmp = max(tmp, line[{a, b}]); + } + } + ret = max(ret, tmp + dup + 1); + } + + return ret; + } +}; +``` \ No newline at end of file diff --git "a/C-\347\256\227\346\263\225/\344\270\223\351\242\230-C-\346\264\227\347\211\214\343\200\201\351\207\207\346\240\267\343\200\201\351\232\217\346\234\272\346\225\260.md" "b/C-\347\256\227\346\263\225/\344\270\223\351\242\230-C-\346\264\227\347\211\214\343\200\201\351\207\207\346\240\267\343\200\201\351\232\217\346\234\272\346\225\260.md" new file mode 100644 index 00000000..220159c5 --- /dev/null +++ "b/C-\347\256\227\346\263\225/\344\270\223\351\242\230-C-\346\264\227\347\211\214\343\200\201\351\207\207\346\240\267\343\200\201\351\232\217\346\234\272\346\225\260.md" @@ -0,0 +1,547 @@ +专题-洗牌、采样、随机数 +=== + +Reference +--- +- [关于乱序(shuffle)与随机采样(sample)的一点探究](https://www.cnblogs.com/xybaby/p/8280936.html) - xybaby - 博客园 + +Index +--- + + +- [洗牌算法](#洗牌算法) + - [Knuth-Durstenfeld Shuffle(Fisher–Yates Shuffle 改进版)](#knuth-durstenfeld-shufflefisheryates-shuffle-改进版) + - [Inside-Out Shuffle](#inside-out-shuffle) + - [Inside-Out Shuffle 无穷版](#inside-out-shuffle-无穷版) +- [采样(等概率)](#采样等概率) + - [无放回思路](#无放回思路) + - [有放回思路](#有放回思路) + - [蓄水池采样](#蓄水池采样) +- [采样(不等概率)](#采样不等概率) + - [查表法(有放回)](#查表法有放回) + - [如何采样,使 `n-1` 被采样 n 次?](#如何采样使-n-1-被采样-n-次) + - [无放回 TODO](#无放回-todo) +- [随机数](#随机数) + - [用 `rand_m()` 生成 `rand_n()`](#用-rand_m-生成-rand_n) + - [`m > n` 时](#m--n-时) + - [`m < n` 时](#m--n-时) + - [利用不等概率方法等概率产生随机数](#利用不等概率方法等概率产生随机数) + + + +## 洗牌算法 +> [Fisher–Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) - Wikipedia + +### Knuth-Durstenfeld Shuffle(Fisher–Yates Shuffle 改进版) +- Knuth-Durstenfeld Shuffle 是一个“原地”(in-place)算法 +- 伪代码
+ `To shuffle an array a of n elements (indices 0..n-1):` + ```Python + for i from n−1 downto 1 do + j ← random integer such that 0 ≤ j ≤ i + exchange(a[i], a[j]) + ``` +- Python 实现 + ```Python + def Knuth_Durstenfeld_shuffle(a): + """Fisher–Yates Shuffle + + Args: + a(list): + """ + for i in range(len(a) - 1, 0, -1): + j = random.randint(0, i) + a[i], a[j] = a[j], a[i] + + # return a + ``` +- **正确性证明**: + - 即证明:任何一个元素 shuffle 之后出现在任意位置的概率都是 `1/N` + - 证明: + ``` + 第1次:`i == n-1` + 在 `[0, n-1]` 这 `n` 个数中随机选取一个数 `j`, + 此时每个数**第1次**被抽到的概率为 `1/n` + 交换`(a[j], a[n-1])` + 该操作表明任何一个元素有 `1/n` 的概率被放在 `n-1` 位置 + 第2次:`i == n-2` + 在 `[0, n-2]` 这 `n-1` 个数中随机选取一个数 `j`, + 此时每个数**第2次**被抽到的概率为 `(n-1)/n * 1/(n-1) = 1/n` + (其中 `(n-1)/n` 是第1次没被抽到的概率) + 交换`(a[j], a[n-2])` + 该操作表明任何一个元素有 `1/n` 的概率被放在 `n-2` 位置 + 第3次:`i == n-3` + 在 `[0, n-3]` 这 `n-3` 个数中随机选取一个数 `j`, + 此时每个数**第3次**被抽到的概率为 `(n-1)/n * (n-2)/(n-1) * 1/(n-2) = 1/n` + (其中 `(n-1)/n` 是第1次没被抽到的概率,`(n-2)/(n-1)` 是第2次没被抽到的概率) + 交换`(a[j], a[n-3])` + 该操作表明任何一个元素有 `1/n` 的概率被放在 `n-3` 位置 + ... + ``` + +### Inside-Out Shuffle +- Inside-Out Shuffle 算法会返回一个打乱的副本 +- 伪代码
+ ```Python + Input: source + for i from 0 to n − 1 do + j ← random integer such that 0 ≤ j ≤ i + if j ≠ i + a[i] ← a[j] + a[j] ← source[i] + ``` +- Python 实现 + ```Python + def Inside_Out_shuffle(src): + """""" + a = src[:] # copy + for i in range(len(a)): + j = random.randint(0, i) + if j != i: # 比较的判断可以省略,有时赋值操作比比较更快 + a[i] = a[j] + a[j] = src[i] + + return a + ``` + ```Python + # 无比较版本 + def Inside_Out_shuffle(src): + """""" + a = src[:] # copy + for i in range(len(a)): + j = random.randint(0, i) + a[i] = a[j] + a[j] = src[i] + # src[i] == 被赋值前的 a[i] + # 可以看到 Inside_Out_shuffle 本质上就是 Knuth-Durstenfeld Shuffle 的拷贝版本 + + return a + ``` +- 正确性证明 TODO + + +### Inside-Out Shuffle 无穷版 +- 所谓无限,指的是 `n=len(src)` 未知的情况 + > [蓄水池采样](#蓄水池采样) +- Python 实现 + ```Python + def Inside_Out_shuffle_inf(src): + """""" + ret = [] + while True: + try: + j = random.randint(0, len(ret)) + if j == len(ret): + ret.append(next(src)) + else: + ret.append(ret[j]) + ret[j] = next(src) + except StopIteration: + break + + return ret + ``` + + +## 采样(等概率) +**问题描述** +``` +从 n 个数中等概率的抽取 m 个数(m < n),每个数被抽取的概率为 `m/n` +``` + +### 无放回思路 +- 无放回会改变原始数据,如果不想修改原始数据,需要额外复制一份 + +**思路 1**:类似 [Knuth-Durstenfeld Shuffle](#knuth-durstenfeld-shufflefisheryates-shuffle-改进版) +- 跟洗牌一样,随机从序列中取出一个元素,同时**从原序列中删除** +- 该方法需要修改原数据 +- 如果不能修改原数据,可以考虑复制一份数据(仅当 n 比较小时) + ```Python + def sample(src): + """""" + n = len(src) + ret = [] + for i in range(n): + j = random.randrange(n - i) # 0 <= j < n-i + ret.append(src[j]) + src.pop(j) + + return ret + ``` +- 证明 TODO + + +**思路 2**:类似 [Inside-Out Shuffle](#inside-out-shuffle) +- Python `random.sample()` 中**当 `n <= 4**ceil(log(3*m, 4))` 时**的采用的方法 +- 类似 Inside-Out 算法的方式从原数组中抽取 m 个数 + ```Python + def sample(arr, m): + """""" + src = list(arr) # 为了防止修改原数据,拷贝一个副本 + n = len(src) + + ret = [None] * m + for i in range(m): + j = random.randrange(n - i) # 0 <= j < n-i + ret[i] = src[j] + src[j], src[n - i - 1] = src[n - i - 1], src[j] # 保证每个值都可能被抽到 + # src[j] = src[n - i - 1] # 因为使用的是副本,所以直接覆盖也可以 + + return ret + ``` + +### 有放回思路 +**思路** +- Python `random.sample()` 中**当 `n > 4**ceil(log(3*m, 4))` 时**采用的策略 +- 使用一个 `set` 记录已经被抽到的位置,如果该位置已经存在,则继续 + ```Python + def sample(arr, m): + """""" + n = len(arr) + selected = set() + + ret = [None] * m + for i in range(m): + j = random.randrange(n) # 0 <= j < n + while j in selected: + j = random.randrange(n) + selected.add(j) + ret[i] = arr[j] + + return ret + ``` +- 如果 `n < 4**ceil(log(3*m, 4))` 时采用这个策略,可能会产生类似 Hash 冲突的问题,导致调用随机数方法的次数过多 + > 求解调用`rangd()`的次数是一个期望问题 + >> [蓄水池抽样及实现](https://www.cnblogs.com/hrlnw/archive/2012/11/27/2777337.html) - handspeaker - 博客园 + + +### 蓄水池采样 + +**问题描述** +``` +给出一个数据流,这个数据流的长度为`n`,`n`很大或者未知(在不断增加),并且对该数据流中每个数据只能访问一次。 +请写出一个抽样算法,从该数据流中抽取`m`个数据,使得所有数据被选中的概率相等(以概率`m/n`被选中)。 + +PS: 假设提供一个能返回**大随机整数**的函数`rand()` +``` +**基本思路** `->` [等概率采样](#等概率采样) + +**蓄水池采样算法** +- 伪代码 + ``` + 从`n`个元素中随机的等概率的抽取`m`个元素,其中`n`可能不确定 + + 1. 将第 1 到 m 个元素加入“蓄水池”(作为被选中的元素) + 2. 从第 m+i 个元素开始: + 每个元素有 m/(m+i) 的概率被选中,若选中则随机替换掉蓄水池中的一个元素 + 3. 重复以上操作直到遍历完所有元素 + + > 根据以上伪代码,似乎还是要得到全部数据才能完成采样,问题出在哪里?实际上第`m+i`个元素被选中的概率是`m/(m+i)`,与`n`无关;即使`n`在不断增加,你依然可以获得达到`n`之前每个状态的结果。 + ``` +- Python 实现 + ```Python + def Reservoir_sample(src, m): + """""" + ret = [] + for i, item in enumerate(src): + if i < m: + ret.append(item) + else: + j = random.randint(0, i) # i == m+k + if j < m: # 只要 j < m 就能加入 ret 表明 i >= m 之后的元素被选中的概率为 m/i = m/(m+k) + ret[j] = src[i] + + return ret + ``` +- **证明**:该算法保证每个元素以 `m/n` 的概率被选中 + - `n <= m` 时,每个元素被 100% 选中 + - `n > m` 时, + - `n=m+1`时,根据算法定义,第`m+1`个元素被选中的概率=`m/m+1`,而前`m`个元素被选中的概率=`1-被第m+1个元素替换的概率`=`1-(m/m+1)*(1/m)=m/m+1`;说明前`m+1`个元素被选中的概率相等。 + - `n=m+k`时,第`m+k`个元素被选中的概率=`m/m+k=m/n`,前`(n-1)`个元素被选中的概率=`1-被第n个元素替换的概率`=`1-(m/n)*(1/m)=m/n`;说明所有`n`个元素被选中的概率相等。 + > [蓄水池抽样及实现](https://www.cnblogs.com/hrlnw/archive/2012/11/27/2777337.html) - handspeaker - 博客园 + + +## 采样(不等概率) + +### 查表法(有放回) +- 图示 +
+ + - 上面的线段按频度进行分割,下面为等距分割 + - 往下方的线段随机打点(均匀分布),根据点所落在的区间对应到上方的线段,来确定采样的对象 + > Word2Vec 中负采样使用的方法 + +- Python 实现 + ```Python + import random + from collections import Counter + + def sample_non_balance(src, m): + """""" + cnt = Counter(src) # 样本计数 + # 维护一个反向字典 + table = dict() + i = 0 + for k, v in cnt.items(): + for _ in range(v): + table[i] = k + i += 1 + + # 采样 m 次,m 可能大于 n + n = len(src) + ret = [] + for _ in range(m): + j = random.randrange(n) + ret.append(table[j]) + + return ret + ``` +- 测试 + ```Python + src = [1] * 100 + [2] * 200 + [3] * 300 + ret = sample_non_balance(src, 100000) # 采样 100000 次 + ret_r = Counter(ret) + for k, v in ret_r.items(): + print(v / len(ret)) + + ''' + 0.49926 + 0.33362 + 0.16712 + ''' + ``` + +#### 如何采样,使 `n-1` 被采样 n 次? +- 0 被采样 1 次,1 被采样 2 次,2 被采样 3 次,... + +**法 1** +- 类似查表法的思路 + ```Python + def solve(n): + table = dict() + index = 0 + for i in range(0, n): + for _ in range(i + 1): + table[index] = i + index += 1 + + i = random.randrange((1 + n) * n / 2) + return table[i] + + cnt = 1000000 + ret = Counter([solve(4) for _ in range(cnt)]) + for k, v in sorted(ret.items()): + print(k, v / cnt) + ''' + 0 0.099951 + 1 0.200028 + 2 0.300225 + 3 0.399796 + ''' + ``` + +**法 2** +- Python + ```Python + def foo(n): + while True: + i = random.randrange(n) + j = random.randrange(n) + if i + j < n: + return i + j + + cnt = 1000000 + ret = Counter([foo(4) for _ in range(cnt)]) + for k, v in sorted(ret.items()): + print(k, v / cnt) + ''' + 0 0.099849 + 1 0.199983 + 2 0.299922 + 3 0.400246 + ''' + ``` + +### 无放回 TODO + + +## 随机数 + +### 用 `rand_m()` 生成 `rand_n()` +> [面试中随机数"等概率"vs"不等概率"生成问题](http://www.cnblogs.com/hellogiser/p/random-generator-with-equal-or-unequal-probability.html) - hellogiser - 博客园 + +**说明:** +- `rand_n()` 能够等概率生成 `[0, n)` 内的整数,`rand_m()` 同理 + +**假设 `rand_m()` 由以下方式实现** +```python +import random + +def rand_m(m): + """""" + o = random.randrange(m) + return o +``` + +#### `m > n` 时 +- 这种情况比较简单 +- 因为 `m >= n`,只要用 `rand_m()` 生成的数在 `[0, n)` 范围就返回,反之重新生成 + ```python + def rand_n(n): + o = rand_m(5) + while o >= n: + o = rand_m(5) + + return o + + # 测试代码 + cnt = 1000000 + d = Counter([rand_n(3) for _ in range(cnt)]) + for k, v in d.items(): + print(k, v / cnt) + print() + ''' + 0 0.33439 + 1 0.332293 + 2 0.333317 + ''' + ``` + +#### `m < n` 时 +- 以下方法要求 `n <= m*m` +- 先看代码再说思路(暂时忽略注释的代码): + ```python + def rand_n(n, m): + """""" + o = rand_m(m) * m + rand_m(m) # 错误写法: rand_m(m) * (m+1) + # t = (m*m) // n * n # t 为小于 m*m 的 n 的最大整数倍 + # while o >= t: + while o >= n: + o = rand_m(m) * m + rand_m(m) + + # o = o % n + return o + ``` +- **思路** + - `rand_m(m) * m + rand_m(m)` 的意思是先等概率生成 `0, m, 2m, .. (m-1)*m`,然后在加上 `rand_m(m)`;最终效果相当于等概率生成 `[0, m*m)` 之间的数 + - 然后再按照 [`m > n` 时](#m--n-时)的做法生成 `n` + +- 注意到,如果 `n << m*m` 时,这个方法会产生大量废操作 +- 一个优化方法就是等概率生成 `t*n` 中的数,然后对 `n` 取模 + ```python + def rand_n(n, m): + """""" + o = rand_m(m) * m + rand_m(m) # 错误写法: rand_m(m) * (m+1) + t = (m*m) // n * n # t 为小于 m*m 的 n 的最大整数倍 + while o >= t: + o = rand_m(m) * m + rand_m(m) + + o = o % n + return o + + # 测试代码 + cnt = 100000 + d = Counter([rand_n(7, 5) for _ in range(cnt)]) + for k, v in d.items(): + print(k, v / cnt) + print() + ''' + 3 0.14584 + 4 0.14105 + 0 0.14178 + 6 0.14287 + 1 0.14273 + 2 0.14088 + 5 0.14485 + ''' + ``` + + + +### 利用不等概率方法等概率产生随机数 + +**问题 1** +``` +non_rand_01() 能够以不同的概率的生成 0/1; +请利用 non_rand_01() 等概率产生 0/1。 +``` +- `non_rand_01()` 测试代码 + ```Python + def non_rand_01(p=0.6): + """以 p 的概率产生 1,1-p 的涵概率产生 0""" + r = random.random() + if r < p: + return 1 + else: + return 0 + ``` +- Python + ```Python + // non_rand_01() 能以不同的概率生成 0/1 + // rand_01(): 利用 non_rand_01() 等概率产生 0/1 + def rand_01(): + """利用 non_rand_01() 等概率产生 0/1""" + while True: + i1 = non_rand_01() + i2 = non_rand_01() + if i1 == 1 and i2 == 0: + return 1 + if i1 == 0 and i2 == 1: + return 0 + + # 测试代码 + cnt = 1000000 + ret = Counter([rand_01() for _ in range(cnt)]) + for k, v in ret.items(): + print(k, v / cnt) + ''' + 1 0.499732 + 0 0.500268 + ''' + ``` + +**问题 2** +``` +non_rand_01() 能够以不同的概率的生成 0/1; +给定 n,请利用 non_rand_01() 等概率产生 0~n 之间的数值。 +``` +- 计算 n 的二进制位数,记为 `k`;然后在**问题 1** 的基础上,产生长度为 k 的随机 0/1 数列 +- 注意,产生的随机数可能大于 `n`,当大于 `n` 时,则丢弃并重新生成 + ```Python + def rand_n(n): + """利用 rand_01 等概率产生 [0, n) 中的随机数""" + while True: + k = int(math.log2(n)) + 1 + ans = 0 + for i in range(k): + ans <<= 1 + ans += rand_01() + + if ans < n: + return ans + + # 测试代码 + cnt = 1000000 + ret = Counter([rand_n(10) for _ in range(cnt)]) + for k, v in sorted(ret.items()): + print(k, v / cnt) + ''' + 0 0.099953 + 1 0.100131 + 2 0.100158 + 3 0.100509 + 4 0.099724 + 5 0.099857 + 6 0.099917 + 7 0.099585 + 8 0.10014 + 9 0.100026 + ''' + ``` + \ No newline at end of file diff --git "a/C-\347\256\227\346\263\225/\344\270\223\351\242\230-D-\345\244\247\346\225\260\350\277\220\347\256\227.md" "b/C-\347\256\227\346\263\225/\344\270\223\351\242\230-D-\345\244\247\346\225\260\350\277\220\347\256\227.md" new file mode 100644 index 00000000..9bb59835 --- /dev/null +++ "b/C-\347\256\227\346\263\225/\344\270\223\351\242\230-D-\345\244\247\346\225\260\350\277\220\347\256\227.md" @@ -0,0 +1,79 @@ +专题-大数运算 +=== + +Index +--- + + +- [大数取模](#大数取模) + - [取模运算的性质](#取模运算的性质) + - [快速幂取模](#快速幂取模) +- [大数加/减/乘/除](#大数加减乘除) + + + +## 大数取模 + +### 取模运算的性质 +
+ +- 因为 `(a%n) - (b%n)` 可能小于 `n`,所以 `+n` +- 因为 `(a%n)(b%n)` 可能溢出,计算前应该强转为 `long long` + +**Code - C++** +- 输入 `a` 为长度小于 1000 的字符串,`b` 为小于 `100000` 的整数 + ```C++ + int big_mod(const string& a, int b) { + long ret = 0; // 防止 ret * 10 溢出 + for (auto c : a) { + ret = ((ret * 10) % b + (c - '0') % b) % b; // ret = ((ret * 10) + (c - '0')) % b + } + return (int)ret; + } + + /* 示例说明 + 1234 % 11 == ((((0*10 + 1)*10 + 2)*10 + 3)*10 + 4) % 11 + == ((((0*10 + 1)*10 + 2)*10 + 3)*10 % 11 + 4 % 11) % 11 + == ((((0*10 + 1)*10 + 2)*10 % 11 + 3 % 11)*10 % 11 + 4 % 11) % 11 + == ((((0*10 + 1)*10 % 11 + 2 % 11)*10 % 11 + 3 % 11)*10 % 11 + 4 % 11) % 11 + == ((((0*10 % 11 + 1 % 11)*10 % 11 + 2 % 11)*10 % 11 + 3 % 11)*10 % 11 + 4 % 11) % 11 + ``` + +### 快速幂取模 +- 计算 `a^n % b` +- **基本方法**:根据取模的性质 3 —— `ab % m == (a%m)(b%m) % m` + ```C++ + int big_mod(int a, int n, int b) { + long long ret = 1; + while(n--) { + ret *= a % b; + ret %= b; + } + return (int)ret; + + } + ``` + - 时间复杂度 `O(N)` +- **快速幂取模** + ```C++ + int big_mod(int a, int n, int b) { + long long ret = 1; + while(n) { + if (n & 1) + ret = (ret*a) % b; + a = (a*a) % b; + n >>= 1; + } + return (int)ret; + } + ``` + - 代码跟快速幂很像 + - 示例说明 + ``` + 2^10 % 11 == (2^5 % 11)(2^5 % 11) % 11 + == ((2 % 11)(2^4 % 11))((2 % 11)(2^4 % 11)) % 11 + == ... + ``` + +## 大数加/减/乘/除 +> [大数的四则运算(加法、减法、乘法、除法) - 落枫飘飘](https://www.cnblogs.com/wuqianling/p/5387099.html) - 博客园 \ No newline at end of file diff --git "a/C-\347\256\227\346\263\225/\344\270\223\351\242\230-D-\346\265\267\351\207\217\346\225\260\346\215\256\345\244\204\347\220\206.md" "b/C-\347\256\227\346\263\225/\344\270\223\351\242\230-D-\346\265\267\351\207\217\346\225\260\346\215\256\345\244\204\347\220\206.md" new file mode 100644 index 00000000..110d8de7 --- /dev/null +++ "b/C-\347\256\227\346\263\225/\344\270\223\351\242\230-D-\346\265\267\351\207\217\346\225\260\346\215\256\345\244\204\347\220\206.md" @@ -0,0 +1,65 @@ +专题-海量数据处理 +=== + +备忘 +--- +- `1 GB`: 十亿个字节(Byte) + > `1(B) * 10*10^8 / 1024 / 1024 ≈ 953.67(MB) ≈ 1000(MB) ≈ 1(GB)` +- `400 MB`: 一亿个 4 字节(Byte) int 整型占用的内存 + > `4(B) * 10^8 / 1024 / 1024 ≈ 381.57(MB) ≈ 382(MB) ≈ 400(MB)` + - 10 亿个整型 -> `400(MB) * 10 = 4(GB)` + - 40 亿个整型 -> `4(GB) * 4 = 16(GB)` +- `12 MB`: 一亿个比特(bit)占用的内存(相比于 int 型,节省了 32 倍内存) + > `1(b) * 10^8 / 8 / 1024 / 1024 ≈ 11.92(MB) ≈ 12(MB)` + - 10 亿个比特 -> `12(MB) * 10 = 120(MB) ≈ 4(GB)/32 = 128(MB)` + - 40 亿个整型 -> `120(MB) * 4 = 480(MB) ≈ 16(GB)/32 = 500(MB)` + + +Index +--- + + +- [判断一个整数是否在给定的 40 亿个(不重复)整数中出现过](#判断一个整数是否在给定的-40-亿个不重复整数中出现过) +- [Reference](#reference) + + + + +## 判断一个整数是否在给定的 40 亿个(不重复)整数中出现过 +> [腾讯面试题:给定 40 亿个不重复的...](https://blog.csdn.net/wenqiang1208/article/details/69669084) - CSDN博客 + +**思路 1** +- BitMap + +**思路 2** +- Hash 分桶 + 排序 + 二分查找 + +**思路 3** +- 外部排序 + 结构存储 + 二分查找 + - int 型整数的范围是 `2^32 ≈ 42亿`,那么对于 40 亿个整数,必然存在大量连续的范围 + - 排序后,必然存在大量以下情况: + ``` + 1 2 3 4 7 8 9 ... + ``` + - 对于这种形式的序列,可以构造如下结构 + ``` + struct { + start; // 记录连续序列的开头 + n_continue; // 连续字段的长度 + } + ``` + - 则上述示例,可以存储为 + ``` + (1, 4), (7, 3), ... + ``` +- 复杂度分析 + - 这样最差情况存在 `2(=42-40)` 亿个断点,即 `2` 亿个结构体,每个结构体占 `8` 个字节,共 `400(MB) * 4 = 1.6(GB)` + - 每次查找的时间复杂度为 `O(logN)` + +**思路 4** +- 多机分布式 + + +## Reference +- [海量数据处理面试题集锦](https://blog.csdn.net/v_july_v/article/details/6685962) - CSDN博客 +- [十道海量数据处理面试题与十个方法大总结](https://blog.csdn.net/v_JULY_v/article/details/6279498) - CSDN博客 \ No newline at end of file diff --git "a/C-\347\256\227\346\263\225/\345\244\207\345\277\230-IO\346\250\241\346\235\277.md" "b/C-\347\256\227\346\263\225/\345\244\207\345\277\230-IO\346\250\241\346\235\277.md" new file mode 100644 index 00000000..45aa0d69 --- /dev/null +++ "b/C-\347\256\227\346\263\225/\345\244\207\345\277\230-IO\346\250\241\346\235\277.md" @@ -0,0 +1,210 @@ +备忘-IO 模板 +=== +- 不少网络笔试不像 LeetCode 帮你完成 I/O,需要手动完成 +- 如果没有 ACM 经验,很可能会在这上面浪费不少时间 +- 这里总结了几种常见的 IO 模板,分别提供了 C/C++ 和 Python(TODO) 代码 + +Index +--- + + +- [输入不说明有多少个 Input,以 EOF 为结束标志](#输入不说明有多少个-input以-eof-为结束标志) + - [C](#c) + - [C++](#c) +- [输入不说明有多少个 Input,以某个特殊输入为结束标志](#输入不说明有多少个-input以某个特殊输入为结束标志) + - [C](#c-1) + - [C++](#c-1) +- [指示有 N 个 Input](#指示有-n-个-input) + - [C](#c-2) + - [C++](#c-2) + - [Python3](#python3) +- [指示有 N 组输入,并以某个特殊输入退出](#指示有-n-组输入并以某个特殊输入退出) + - [C/C++](#cc) +- [输入是一整行(包括空格)](#输入是一整行包括空格) + - [用 char[] 接收(C/C++)](#用-char-接收cc) + - [用 string 接收(C++)](#用-string-接收c) +- [输入是多行(包括空格)](#输入是多行包括空格) + - [C++](#c-3) +- [从文件读取](#从文件读取) + - [C](#c-3) + - [C++](#c-4) + + + +## 输入不说明有多少个 Input,以 EOF 为结束标志 + +### C +```C +int a, b; +// scanf 返回值为变量的个数,如果没有返回 -1,EOF 是一个预定义的常量 -1 +while (scanf("%d %d", &a, &b) != EOF) { + // ... +} +``` + +### C++ +```C++ +int a, b; +while (cin >> a >> b) { + // ... +} +``` + + +## 输入不说明有多少个 Input,以某个特殊输入为结束标志 + +### C +```C +// 示例 1 +int a, b; +while (scanf("%d %d", &a, &b) != EOF && (a != 0 && b != 0)) { + // ... +} + +// 或者 +while (scanf("%d %d", &a, &b) != EOF && (a || b)) { + // ... +} + +// 示例 2 +int n; +while (scanf("%d", &n) != EOF && n != 0) { + // ... +} +``` + +### C++ +```C++ +// 示例 1 +int a, b; +while (cin >> a >> b) { + if (a == 0 && b == 0) + break; + // ... +} + +// 示例 2 +int n; +while (cin >> n && n != 0) { + // ... +} +``` + + +## 指示有 N 个 Input + +### C +```C +int n; +scanf("%d", &n); + +int a, b; +for (int i = 0; i < n; i++) { + scanf("%d %d", &a, &b); + // ... +} +``` + +### C++ +```C++ +int n; +cin >> n; + +int a, b; +while(n--) { + cin >> a >> b; +} +``` + +### Python3 +```Python +n = int(input()) +for _ in range(n): + # ... +``` + + +## 指示有 N 组输入,并以某个特殊输入退出 + +### C/C++ +```C++ +int n; +while (cin >> n && n != 0) { + int a, b; + for (int i = 0; i < n; i++) { + cin >> a >> b; + // ... + } +} +``` + + +## 输入是一整行(包括空格) + +### 用 char[] 接收(C/C++) +```C++ +const int MAXN = 1000; +char buff[MAXN]; + +// C +gets(buff); +puts(buff); // 输出 + +// C++ +cin.getline(buff, MAXN); // 第三个参数默认是 '\n' +cin.getline(buff, MAXN, '\n'); +``` + +### 用 string 接收(C++) +```C++ +string s; +getline(cin, s); // 第三个参数默认是 '\n' +getline(cin, s, '\n'); +``` + + +## 输入是多行(包括空格) + +### C++ +```C++ +int n; +cin >> n; +cin.get(); // 否则,n 也会计入下面的 getline(),导致少一组数据 + +while (n--) { + string s; + getline(cin, s); +} +``` + + +## 从文件读取 + +### C +```C +FILE *cfin = fopen("in.txt", "r"); +FILE *cfout = fopen("out.txt", "w"); + +int a, b; +// 注意要传入文件指针 +while (fscanf(cfin, "%d %d", &a, &b) != EOF) { // 类似的,把 scanf 替换成 fscanf + fprintf(cfout, "%d\n", a + b); // 把 printf 替换为 fprintf +} + +fclose(cfin); +fclose(cfout); +``` + +### C++ +```C++ +ifstream fin("in.txt"); +ofstream fout("out.txt"); + +int a, b; +while (fin >> a >> b) { + fout << a + b << endl; +} + +fin.close(); +fout.close(); +``` \ No newline at end of file diff --git "a/C-\347\256\227\346\263\225/\345\244\207\345\277\230-\345\277\205\345\244\207\347\256\227\346\263\225.md" "b/C-\347\256\227\346\263\225/\345\244\207\345\277\230-\345\277\205\345\244\207\347\256\227\346\263\225.md" new file mode 100644 index 00000000..70f408f2 --- /dev/null +++ "b/C-\347\256\227\346\263\225/\345\244\207\345\277\230-\345\277\205\345\244\207\347\256\227\346\263\225.md" @@ -0,0 +1,99 @@ +备忘-必备算法 +=== +- 一些必备算法,主要是 C++ 版本 +Index +--- + + +- [二分查找](#二分查找) + - [离散版](#离散版) + - [`my_binary_search(vector, int)`](#my_binary_searchvectorint-int) + - [`my_lower_bound(vector, int)`](#my_lower_boundvectorint-int) + - [`my_upper_bound(vector, int)`](#my_upper_boundvectorint-int) +- [排序](#排序) + - [堆排序](#堆排序) + - [建堆的时间复杂度](#建堆的时间复杂度) + + + + +## 二分查找 + +### 离散版 + +#### `my_binary_search(vector, int)` +- 没有重复元素时,目标值若存在,则返回索引;若不存在,返回 -1 +- 存在重复元素时,目标值若存在,则返回最小索引;若不存在,返回 -1 + ```C++ + int my_binary_search(vector& nums, int v) { + if (nums.size() < 1) return - 1; + + int lo = -1, hi = nums.size(); // hi = nums.size() - 1 + + while (hi - lo > 1) { + int mid = lo + (hi - lo) / 2; + if (nums[mid] < v) + lo = mid; + else + hi = mid; + } + + return nums[lo + 1] == v ? lo + 1 : -1; + } + ``` + +#### `my_lower_bound(vector, int)` +- 返回大于、等于目标值的最小索引(第一个大于或等于目标值的索引) + ```C++ + int my_lower_bound(vector& nums, int v) { + if (nums.size() < 1) return -1; + + int lo = -1, hi = nums.size(); // hi = nums.size() - 1 + + while (hi - lo > 1) { // 退出循环时有:lo + 1 == hi + int mid = lo + (hi - lo) / 2; + if (nums[mid] < v) + lo = mid; // 因为始终将 lo 端当做开区间,所以没有必要 `lo = mid + 1;` + else + hi = mid; // 而在 else 中,mid 可能就是最后的结果,所以不能 `hi = mid - 1` + } + + return lo + 1; // 相比 binary_search,只有返回值不同 + } + ``` +- **为什么返回 `lo + 1`?** + - 模板开始时将 (lo, hi) 看做是一个开区间,通过不断二分,最终这个区间中只会含有一个值,即 (lo, hi] + - 返回 lo+1 的含义是,结果就在 lo 的下一个; + - 在迭代的过程中,hi 会从开区间变为闭区间,而 lo 始终是开区间,返回 lo+1 显得更加统一。 + - 当然,这跟迭代的写法是相关的,你也可以使最终的结果区间是 [lo, hi),这取决于个人习惯。 + +#### `my_upper_bound(vector, int)` +- 返回大于目标值的最小索引(第一个大于目标值的索引) + ```C++ + int my_upper_bound(vector& nums, int v) { + if (nums.size() < 1) return -1; + + int lo = -1, hi = nums.size(); // hi = nums.size() - 1 + + while (hi - lo > 1) { + int mid = lo + (hi - lo) / 2; + + if (nums[mid] <= v) // 相比 lower_bound,唯一不同点:`<` -> `<=` + lo = mid; + else + hi = mid; + } + + return lo + 1; + } + ``` + +## 排序 + +### 堆排序 +```C++ + +``` + +#### 建堆的时间复杂度 +> [为什么建立一个二叉堆的时间为O(N)而不是O(Nlog(N))?](https://www.zhihu.com/question/264693363/answer/291397356) - 知乎 diff --git "a/C-\347\256\227\346\263\225/\351\242\230\350\247\243-LeetCode.md" "b/C-\347\256\227\346\263\225/\351\242\230\350\247\243-LeetCode.md" new file mode 100644 index 00000000..690b1aef --- /dev/null +++ "b/C-\347\256\227\346\263\225/\351\242\230\350\247\243-LeetCode.md" @@ -0,0 +1,584 @@ +题解-LeetCode +=== + +RoadMap +--- +- [二叉树](#二叉树) + - [DFS](#dfs) +- [数组](#数组) + - [双指针](#双指针) + - [多级排序](#多级排序) + - [其他](#其他) +- [暴力搜索](#暴力搜索) + - [DFS](#dfs) + - [BFS](#bfs) + + +Index +--- + + +- [二叉树](#二叉树) + - [124. 二叉树中的最大路径和(DFS)](#124-二叉树中的最大路径和dfs) +- [数组](#数组) + - [560. 和为K的子数组(前缀和 + Map)](#560-和为k的子数组前缀和--map) + - [838. 推多米诺(双指针)](#838-推多米诺双指针) + - [15. 三数之和(双指针)](#15-三数之和双指针) + - [16. 最接近的三数之和(双指针)](#16-最接近的三数之和双指针) + - [729. 我的日程安排表 I(多级排序)](#729-我的日程安排表-i多级排序) + - [26. 删除排序数组中的重复项](#26-删除排序数组中的重复项) +- [暴力搜索](#暴力搜索) + - [200. 岛屿的个数(DFS | BFS)](#200-岛屿的个数dfs--bfs) + + + +## 二叉树 + +### 124. 二叉树中的最大路径和(DFS) +> https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/description/ + +**题目描述** +``` +给定一个非空二叉树,返回其最大路径和。 + +本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。 +该路径至少包含一个节点,且不一定经过根节点。 + +输入: [1,2,3] + + 1 + / \ + 2 3 + +输出: 6 +``` + +**暴力求解的思路** +- 利用一个子函数,求出每个节点**最大深度路径和**(做法类似求**树的深度**) + - 注意,因为节点中的值可能为负数,所以最大深度路径和不一定都会到达叶子 + - 同样,最大深度路径和也可能为负数,此时应该返回 0 +- 接着对每个节点,**经过该节点**的最大路径和为 + ``` + 该节点的值 + 左子树的最大深度路径和 + 右子树的最大深度路径和 + ``` +- **空树的最大路径和**应该为负无穷(作为递归基);但实际用例中没有空树的情况 + +**C++** +- 初始版本(AC) + ```C++ + class Solution { + const int inf = 0x3f3f3f3f; + + int maxDeep(TreeNode* root) { + if (!root) return 0; + + // 避免负数的情况 + return max(0, root->val + max({ 0, maxDeep(root->left), maxDeep(root->right) })); + } + public: + int maxPathSum(TreeNode* root) { + if (root == nullptr) return -inf; // 空树返回负无穷 + + int path_sum = root->val + maxDeep(root->right) + maxDeep(root->left); + + return max({ path_sum, maxPathSum(root->left), maxPathSum(root->right) }); + } + }; + ``` +- **改进** + - 使用一个变量保存中间结果 + ``` + class Solution { + // C++11 支持 就地 初始化 + const int inf = 0x3f3f3f3f; + int ret = -inf; + + int maxDeepSum(TreeNode* node) { + if (node == nullptr) + return 0; + + int l_sum = max(0, maxDeepSum(node->left)); + int r_sum = max(0, maxDeepSum(node->right)); + + ret = max(ret, node->val + l_sum + r_sum); + return node->val + max(l_sum, r_sum); + } + public: + int maxPathSum(TreeNode* root) { + maxDeepSum(root); + return ret; + } + }; + ``` + +**优化方案** +- 记忆化搜索(树DP);简单来说,就是保存中间结果 + + +## 数组 + +### 560. 和为K的子数组(前缀和 + Map) +> https://leetcode-cn.com/problems/subarray-sum-equals-k/description/ + +**问题描述** +``` +给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。 + +示例 1 : + +输入:nums = [1,1,1], k = 2 +输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。 +``` + +**思路** +- 前缀和 + map +- 难点在于重复的情况也要记录 + +**C++** +```C++ +class Solution { +public: + int subarraySum(vector& nums, int k) { + + int sum = 0; + int cnt = 0; + map bok; + bok[0] = 1; + for (int i=0; i https://leetcode-cn.com/problems/push-dominoes/description/ + +**问题描述** +``` +一行中有 N 张多米诺骨牌,我们将每张多米诺骨牌垂直竖立。 + +在开始时,我们同时把一些多米诺骨牌向左或向右推。 + +每过一秒,倒向左边的多米诺骨牌会推动其左侧相邻的多米诺骨牌。 + +同样地,倒向右边的多米诺骨牌也会推动竖立在其右侧的相邻多米诺骨牌。 + +如果同时有多米诺骨牌落在一张垂直竖立的多米诺骨牌的两边,由于受力平衡, 该骨牌仍然保持不变。 + +就这个问题而言,我们会认为正在下降的多米诺骨牌不会对其它正在下降或已经下降的多米诺骨牌施加额外的力。 + +给定表示初始状态的字符串 "S" 。如果第 i 张多米诺骨牌被推向左边,则 S[i] = 'L';如果第 i 张多米诺骨牌被推向右边,则 S[i] = 'R';如果第 i 张多米诺骨牌没有被推动,则 S[i] = '.'。 + +返回表示最终状态的字符串。 + +示例 1: + 输入:".L.R...LR..L.." + 输出:"LL.RR.LLRRLL.." + +示例 2: + 输入:"RR.L" + 输出:"RR.L" + 说明:第一张多米诺骨牌没有给第二张施加额外的力。 + +提示: + 0 <= N <= 10^5 + 表示多米诺骨牌状态的字符串只含有 'L','R'; 以及 '.'; +``` + +**思路** +- 如果给原始输入左右分别加上一个 "L" 和 "R",那么共有以下 4 种可能 + ``` + 'R......R' => 'RRRRRRRR' + 'L......L' => 'LLLLLLLL' + 'L......R' => 'L......R' + 'R......L' => 'RRRRLLLL' or 'RRRR.LLLL' + ``` + > [[C++/Java/Python] Two Pointers](https://leetcode.com/problems/push-dominoes/discuss/132332/C++JavaPython-Two-Pointers) - LeetCode + +**C++** +```C++ +class Solution { +public: + string pushDominoes(string d) { + string s = "L" + d + "R"; + string ret = ""; + + int lo = 0, hi = 1; + for (; hi < s.length(); hi++) { + if (s[hi] == '.') + continue; + + if (lo > 0) // 注意这一步操作 + ret += s[lo]; + + int delta = hi - lo - 1; + if (s[lo] == s[hi]) + ret += string(delta, s[lo]); // string 的一种构造函数,以 s[lo] 为每个字符,生成长度为 h_l 的字符串 + else if (s[lo] == 'L' && s[hi] == 'R') + ret += string(delta, '.'); + else if (s[lo] == 'R' && s[hi] == 'L') + ret += string(delta / 2, 'R') + string(delta & 1, '.') + string(delta / 2, 'L'); + + lo = hi; + } + + return ret; + } +}; +``` + +### 15. 三数之和(双指针) +> https://leetcode-cn.com/problems/3sum/description/ + +**问题描述** +``` +给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ? +找出所有满足条件且不重复的三元组。 +``` + +**思路** +- 因为需要输出所有结果,所以不推荐使用 map 来做 +- 判断 `a + b + c = 0`,实际上等价于判断 `-a = b + c` +- 基本思路:对数组排序后,对每个 `a`,用首尾双指针进行遍历,具体过程看代码更清晰 +- 去重的方法:排序后,跳过相同的数即可 +- 注意边界条件 + +**C++** +```C++ +class Solution { +public: + vector> threeSum(vector& nums) { + if (nums.size() < 3) return vector>(); // 输入数量小于 3 直接退出 + + sort(nums.begin(), nums.end()); // 排序 + + vector> ret; + for (int i = 0; i <= nums.size() - 3; i++) { + if (i > 0 && nums[i] == nums[i - 1]) continue; // 跳过第一个数相同的情况 + + int target = -nums[i]; + int lo = i + 1; + int hi = nums.size() - 1; + while (lo < hi) { + if (nums[lo] + nums[hi] < target) + lo++; + else if (nums[lo] + nums[hi] > target) + hi--; + else { + ret.push_back({ nums[i], nums[lo], nums[hi] }); + lo++, hi--; // 不要忘了这双指针都要移动 + + while (lo < hi && nums[lo] == nums[lo - 1]) lo++; // 跳过第二个数相同的情况 + while (lo < hi && nums[hi] == nums[hi + 1]) hi--; // 跳过第三个数相同的情况 + } + } + } + return ret; + } +}; +``` + +### 16. 最接近的三数之和(双指针) +> https://leetcode-cn.com/problems/3sum-closest/description/ + +**题目描述** +``` +给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。 + +例如,给定数组 nums = [-1,2,1,-4], 和 target = 1. + +与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2). +``` + +**思路** +- 同[三数之和(双指针)](#三数之和双指针) +- 区别仅在于循环内的操作不同 + +**C++** +```C++ +class Solution { +public: + int threeSumClosest(vector& nums, int target) { + if (nums.size() < 3) return 0; + + sort(nums.begin(), nums.end()); // 别忘了排序 + + auto ret = nums[0] + nums[1] + nums[2]; // 保存答案 + for (int i = 0; i <= nums.size()-3; i++) { + int lo = i + 1; + int hi = nums.size() - 1; + + while (lo < hi) { + auto sum = nums[i] + nums[lo] + nums[hi]; + if (abs(target - sum) < abs(target - ret)) { + ret = sum; + if (ret == target) + return ret; + } + sum < target ? lo++ : hi--; + } + } + return ret; + } +}; +``` + +### 729. 我的日程安排表 I(多级排序) +> https://leetcode-cn.com/problems/my-calendar-i/description/ + +**题目描述** +``` +实现一个 MyCalendar 类来存放你的日程安排。如果要添加的时间内没有其他安排,则可以存储这个新的日程安排。 + +MyCalendar 有一个 book(int start, int end)方法。它意味着在 start 到 end 时间内增加一个日程安排,注意,这里的时间是半开区间,即 [start, end), 实数 x 的范围为, start <= x < end。 + +当两个日程安排有一些时间上的交叉时(例如两个日程安排都在同一时间内),就会产生重复预订。 + +每次调用 MyCalendar.book方法时,如果可以将日程安排成功添加到日历中而不会导致重复预订,返回 true。否则,返回 false 并且不要将该日程安排添加到日历中。 + +请按照以下步骤调用 MyCalendar 类: MyCalendar cal = new MyCalendar(); MyCalendar.book(start, end) + +示例 1: + MyCalendar(); + MyCalendar.book(10, 20); // returns true + MyCalendar.book(15, 25); // returns false + MyCalendar.book(20, 30); // returns true + + 解释: + 第一个日程安排可以添加到日历中. 第二个日程安排不能添加到日历中,因为时间 15 已经被第一个日程安排预定了。 + 第三个日程安排可以添加到日历中,因为第一个日程安排并不包含时间 20 。 +``` + +**思路** +- 按 start 排序,利用二分查找(lower_bound),找到待插入位置 +- 关键是判断能否插入,需要同时判断前后位置,此时需要注意边界条件 + ``` + 假设 (s, e) 的待插入位置如下,利用 lower_bound 将返回指向 (s2, e2) 的迭代器 + (s1, e1) _ (s2, e2) + 此时,有 s1 < s < s2 + 此时如果 e > s2 || s < e1 则无法插入;反之则可以插入 + ``` +- **C++** + ```C++ + class MyCalendar { + vector > m_book; + + public: + MyCalendar() {} + + bool book(int s, int e) { + pair tmp{s, e}; + auto it = lower_bound(m_book.begin(), m_book.end(), tmp); + // 默认按 pair.first 排序, + // 所以虽然是多级排序,但实际上并没有额外的操作 + + // 注意下面两步的边界判断 + if (it != m_book.end() && e > it->first) + return false; + if (it != m_book.begin() && s < (it-1)->second) + return false; + m_book.insert(it, tmp); + return true; + } + }; + ``` +- **C++**-利用 STL 中的 set/map 结构自动排序 + ```C++ + // 使用 Set + class MyCalendar { + set > m_book; + + public: + MyCalendar() {} + + bool book(int s, int e) { + pair tmp{s, e}; + + auto it = m_book.lower_bound(tmp); + if (it != m_book.end() && it->first < e) + return false; + if (it != m_book.begin() && (--it)->second > s) // 注意 set 只支持 -- 操作符,而不是支持 - 操作符,即无法使用 (it-1)->second + return false; + m_book.insert(tmp); + return true; + } + }; + + // 使用 Map + class MyCalendar { + map books; + public: + bool book(int s, int e) { + auto next = books.lower_bound(s); + if (next != books.end() && next->first < e) + return false; + if (next != books.begin() && s < (--next)->second) + return false; + books[s] = e; + return true; + } + }; + ``` + +### 26. 删除排序数组中的重复项 +> https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/description/ + +**题目描述** +``` +给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 + +不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 +``` + +**C++** +```C++ +class Solution { +public: + int removeDuplicates(vector& nums) { + if (nums.size() <= 1) return nums.size(); + + int lo = 0; + int hi = lo + 1; + + int n = nums.size(); + while (hi < n) { + while (hi < n && nums[hi] == nums[lo]) hi++; + nums[++lo] = nums[hi]; + } + + return lo; + } +}; +``` + +## 暴力搜索 + +### 200. 岛屿的个数(DFS | BFS) +> https://leetcode-cn.com/problems/number-of-islands/description/ + +**问题描述** +``` +给定一个由 '1'(陆地)和 '0'(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。 + +示例 1: + 输入: + 11110 + 11010 + 11000 + 00000 + 输出: 1 +示例 2: + 输入: + 11000 + 11000 + 00100 + 00011 + 输出: 3 +``` + +**思路** +- 经典的 DFS | BFS 问题,搜索连通域的个数 + +**Code**: DFS +``` +class Solution { + int n, m; +public: + int numIslands(vector>& grid) { // 注意:是 char 不是 int + if (!grid.empty()) n = grid.size(); + else return 0; + if (!grid[0].empty()) m = grid[0].size(); + else return 0; + + int ret = 0; + for (int i=0; i>& grid, int i, int j) { + if (i < 0 || i >= n || j < 0 || j >= m ) // 边界判断(递归基) + return; + + if (grid[i][j] == '0') + return; + else { + grid[i][j] = '0'; // 如果不想修改原数据,可以复制一个 + // 4 个方向 dfs;一些问题会扩展成 8 个方向,本质上没有区别 + dfs(grid, i+1, j); + dfs(grid, i-1, j); + dfs(grid, i, j+1); + dfs(grid, i, j-1); + } + } +}; +``` + +**Code**: BFS +```C++ +class Solution { + int n, m; +public: + int numIslands(vector>& grid) { + if (!grid.empty()) n = grid.size(); + else return 0; + if (!grid[0].empty()) m = grid[0].size(); + else return 0; + + int ret = 0; + for (int i=0; i>& grid, int i, int j) { + queue > q; + + q.push({i,j}); + grid[i][j] = '0'; + while (!q.empty()) { + i = q.front()[0], j = q.front()[1]; + q.pop(); // 当前节点出队 + // 当前节点的四周节点依次入队 + if (i > 0 && grid[i-1][j] == '1') { + q.push({i-1,j}); + grid[i-1][j] = '0'; + } + if (i < n-1 && grid[i+1][j] == '1') { + q.push({i+1,j}); + grid[i+1][j] = '0'; + } + if (j > 0 && grid[i][j-1] == '1') { + q.push({i,j-1}); + grid[i][j-1] = '0'; + } + if (j < m-1 && grid[i][j+1] == '1') { + q.push({i,j+1}); + grid[i][j+1] = '0'; + } + } + } +}; +``` diff --git "a/C-\347\256\227\346\263\225/\351\242\230\350\247\243-\345\211\221\346\214\207Offer.md" "b/C-\347\256\227\346\263\225/\351\242\230\350\247\243-\345\211\221\346\214\207Offer.md" new file mode 100644 index 00000000..fdc2f09d --- /dev/null +++ "b/C-\347\256\227\346\263\225/\351\242\230\350\247\243-\345\211\221\346\214\207Offer.md" @@ -0,0 +1,3553 @@ +**说明** +--- +- 主要编程语言为 C/C++ +- 涉及**字符串**的问题可能会使用 Python +- 题目编号以原书为准,如“**面试题 3:数组中重复的数字**” + - 因为题目不多,所以就不做分类了 +- 所有代码均通过 OJ 测试 + > 在线 **OJ 地址**:[剑指Offer_编程题](https://www.nowcoder.com/ta/coding-interviews) - 牛客网 + +**Reference** +--- +- 《剑指 Offer(第二版)》 - 何海涛 +- Interview-Notebook/[剑指 offer 题解.md](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E5%89%91%E6%8C%87%20offer%20%E9%A2%98%E8%A7%A3.md) · CyC2018/Interview-Notebook +- 牛客网相关问题讨论区 + + +**Index** +--- + + +- [3.1 数组中重复的数字](#31-数组中重复的数字) +- [3.2 不修改数组找出重复的数字](#32-不修改数组找出重复的数字) +- [4. 二维数组中的查找](#4-二维数组中的查找) +- [5. 替换空格](#5-替换空格) +- [6. 从尾到头打印链表](#6-从尾到头打印链表) +- [7. 重建二叉树](#7-重建二叉树) +- [8. 二叉树的下一个结点](#8-二叉树的下一个结点) +- [9. 用两个栈实现队列](#9-用两个栈实现队列) +- [10.1 斐波那契数列](#101-斐波那契数列) +- [10.2 跳台阶(递归)](#102-跳台阶递归) +- [10.3 变态跳台阶(动态规划)](#103-变态跳台阶动态规划) +- [10.4 矩形覆盖(动态规划)](#104-矩形覆盖动态规划) +- [11. 旋转数组的最小数字(二分查找)](#11-旋转数组的最小数字二分查找) +- [12. 矩阵中的路径(DFS)](#12-矩阵中的路径dfs) +- [13. 机器人的运动范围(DFS) TODO](#13-机器人的运动范围dfs-todo) +- [14. 剪绳子(动态规划 | 贪心)](#14-剪绳子动态规划--贪心) +- [15. 二进制中 1 的个数(位运算)](#15-二进制中-1-的个数位运算) +- [16. 数值的整数次方(位运算)](#16-数值的整数次方位运算) +- [17. 打印从 1 到最大的 n 位数(字符串 + DFS)](#17-打印从-1-到最大的-n-位数字符串--dfs) +- [18.1 在 O(1) 时间内删除链表节点(链表)](#181-在-o1-时间内删除链表节点链表) +- [18.2 删除链表中重复的结点(链表)](#182-删除链表中重复的结点链表) +- [19. 正则表达式匹配(自动机:动态规划 | DFS)](#19-正则表达式匹配自动机动态规划--dfs) +- [20. 表示数值的字符串(自动机 | 正则)](#20-表示数值的字符串自动机--正则) +- [21. 调整数组顺序使奇数位于偶数前面(数组)](#21-调整数组顺序使奇数位于偶数前面数组) +- [22. 链表中倒数第 K 个结点(链表 + 双指针)](#22-链表中倒数第-k-个结点链表--双指针) +- [23. 链表中环的入口结点(链表 + 双指针)](#23-链表中环的入口结点链表--双指针) +- [24. 反转链表(链表)](#24-反转链表链表) +- [25. 合并两个排序的链表(链表)](#25-合并两个排序的链表链表) +- [26. 树的子结构(二叉树)](#26-树的子结构二叉树) +- [27. 二叉树的镜像(二叉树)](#27-二叉树的镜像二叉树) +- [28 对称的二叉树(二叉树)](#28-对称的二叉树二叉树) +- [29. 顺时针打印矩阵(二维数组)](#29-顺时针打印矩阵二维数组) +- [30. 包含 min 函数的栈(数据结构:栈)](#30-包含-min-函数的栈数据结构栈) +- [31. 栈的压入、弹出序列(数据结构:栈)](#31-栈的压入弹出序列数据结构栈) +- [32.1 从上往下打印二叉树(BFS)](#321-从上往下打印二叉树bfs) +- [32.2 分行从上到下打印二叉树(BFS)](#322-分行从上到下打印二叉树bfs) +- [32.3 按之字形顺序打印二叉树(BFS)](#323-按之字形顺序打印二叉树bfs) +- [33. 二叉搜索树的后序遍历序列(二叉树:递归)](#33-二叉搜索树的后序遍历序列二叉树递归) +- [34. 二叉树中和为某一值的路径(DFS)](#34-二叉树中和为某一值的路径dfs) +- [35. 复杂链表的复制(链表)](#35-复杂链表的复制链表) +- [36. 二叉搜索树与双向链表(DFS)](#36-二叉搜索树与双向链表dfs) +- [37. 序列化二叉树(DFS)***](#37-序列化二叉树dfs) +- [38. 字符串的排列(DFS)](#38-字符串的排列dfs) +- [39.1 数组中出现次数超过一半的数字(多数投票问题)](#391-数组中出现次数超过一半的数字多数投票问题) +- [40. 找出数组中第 k 大的数字(数据结构:堆)***](#40-找出数组中第-k-大的数字数据结构堆) +- [41. 数据流中的中位数(数据结构:堆)](#41-数据流中的中位数数据结构堆) +- [42. 连续子数组的最大和](#42-连续子数组的最大和) +- [43. 从 1 到 n 整数中 1 出现的次数(Trick)](#43-从-1-到-n-整数中-1-出现的次数trick) +- [44. 数字序列中的某一位数字(Trick)](#44-数字序列中的某一位数字trick) +- [45. 把数组排成最小的数(排序)](#45-把数组排成最小的数排序) +- [46. 把数字翻译成字符串(解码方法)(动态规划)](#46-把数字翻译成字符串解码方法动态规划) +- [47. 礼物的最大价值(年终奖)(动态规划)](#47-礼物的最大价值年终奖动态规划) +- [48. 最长不含重复字符的子字符串(动态规划)](#48-最长不含重复字符的子字符串动态规划) +- [49. 丑数(动态规划)](#49-丑数动态规划) +- [50.1 第一个只出现一次的字符位置(Hash)](#501-第一个只出现一次的字符位置hash) +- [50.2 字符流中第一个只出现一次的字符(数据结构:队列)](#502-字符流中第一个只出现一次的字符数据结构队列) +- [51. 数组中的逆序对](#51-数组中的逆序对) +- [](#) +- [](#-1) +- [](#-2) +- [](#-3) +- [](#-4) +- [](#-5) + + + + +## 3.1 数组中重复的数字 +> [数组中重复的数字](https://www.nowcoder.com/practice/623a5ac0ea5b4e5f95552655361ae0a8?tpId=13&tqId=11203&tPage=3&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder + +**题目描述** +``` +在一个长度为 n 的数组里的所有数字都在 0 到 n-1 的范围内。 +数组中某些数字是重复的,但不知道有几个数字是重复的,也不知道每个数字重复几次。 +请找出数组中任意一个重复的数字。 +``` +- 要求:时间复杂度`O(N)`,空间复杂度`O(1)` +- 示例 + ``` + Input: + {2, 3, 1, 0, 2, 5} + + Output: + 2 + ``` + +**思路** +- 复杂度要求表明**不能使用排序**,也不能使用 **map**/**set** +- 注意到 n 个数字的范围为 `0` 到 `n-1`,考虑类似**选择排序**的思路,通过一次遍历将每个数交换到排序后的位置,如果该位置已经存在相同的数字,那么该数就是重复的 +- 示例 + ``` + position-0 : (2,3,1,0,2,5) // 2 <-> 1 + (1,3,2,0,2,5) // 1 <-> 3 + (3,1,2,0,2,5) // 3 <-> 0 + (0,1,2,3,2,5) // already in position + position-1 : (0,1,2,3,2,5) // already in position + position-2 : (0,1,2,3,2,5) // already in position + position-3 : (0,1,2,3,2,5) // already in position + position-4 : (0,1,2,3,2,5) // nums[i] == nums[nums[i]], exit + ``` +**Code** +```C++ +class Solution { +public: + bool duplicate(int numbers[], int length, int* duplication) { + if(numbers == nullptr || length <= 0) + return false; + + for(int i = 0; i < length; ++i) { + while(numbers[i] != i) { + if(numbers[i] == numbers[numbers[i]]) { + *duplication = numbers[i]; + return true; + } + // 交换numbers[i]和numbers[numbers[i]] + swap(numbers[i], numbers[numbers[i]]); + } + } + return false; + } +}; +``` + + +## 3.2 不修改数组找出重复的数字 + +**题目描述** +``` +在一个长度为n+1的数组里的所有数字都在1到n的范围内,所以数组中至少有一个数字是重复的。 +请找出数组中任意一个重复的数字,但不能修改输入的数组。 +例如,如果输入长度为8的数组{2, 3, 5, 4, 3, 2, 6, 7},那么对应的输出是重复的数字2或者3。 +``` +- 要求:时间复杂度`O(NlogN)`,空间复杂度`O(1)` + +**思路** +- 二分查找 +- 以长度为 8 的数组 `{2, 3, 5, 4, 3, 2, 6, 7}` 为例,那么所有数字都在 `1~7` 的范围内。中间的数字 `4` 将 `1~7` 分为 `1~4` 和 `5~7`。统计 `1~4` 内数字的出现次数,它们一共出现了 5 次,说明 `1~4` 内必要重复的数字;反之,若小于等于 4 次,则说明 `5~7` 内必有重复的数字。 +- 因为不能使用额外的空间,所以每次统计次数都要重新遍历整个数组一次 + +**Code** +```C++ +int countRange(const int* numbers, int length, int start, int end); + +int getDuplication(const int* numbers, int length) +{ + if(numbers == nullptr || length <= 0) + return -1; + + int start = 1; + int end = length - 1; + while(end >= start) { + int middle = ((end - start) >> 1) + start; + int count = countRange(numbers, length, start, middle); + if(end == start) { + if(count > 1) + return start; + else + break; + } + + if(count > (middle - start + 1)) + end = middle; + else + start = middle + 1; + } + return -1; +} + +// 因为不能使用额外的空间,所以每次统计次数都要重新遍历整个数组一次 +int countRange(const int* numbers, int length, int start, int end) { + if(numbers == nullptr) + return 0; + + int count = 0; + for(int i = 0; i < length; i++) + if(numbers[i] >= start && numbers[i] <= end) + ++count; + return count; +} +``` + + +## 4. 二维数组中的查找 +> [二维数组中的查找](https://www.nowcoder.com/practice/abc3fe2ce8e146608e868a70efebf62e?tpId=13&tqId=11154&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder + +**题目描述** +``` +在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。 +请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 +``` +- 示例 + ```html + Consider the following matrix: + [ + [1, 4, 7, 11, 15], + [2, 5, 8, 12, 19], + [3, 6, 9, 16, 22], + [10, 13, 14, 17, 24], + [18, 21, 23, 26, 30] + ] + + Given target = 5, return true. + Given target = 20, return false. + ``` + +**思路** +- 从**左下角**开始查找,它左边的数都比它小,下边的数都比它大;因此可以根据 target 和当前元素的大小关系来**缩小查找区间** +- 同理,也可以从**右上角**开始查找 +- 时间复杂度:`O(M + N)` + +**Code** +```C++ +class Solution { +public: + bool Find(int target, vector > array) { + int N = array.size(); // 行数 + int M = array[0].size(); // 列数 + + int i = N - 1; + int j = 0; + while (i >= 0 && j < M) { + if (array[i][j] > target) + i--; + else if (array[i][j] < target) + j++; + else + return true; + } + return false; + } +}; +``` + + +## 5. 替换空格 +> [替换空格](https://www.nowcoder.com/practice/4060ac7e3e404ad1a894ef3e17650423?tpId=13&tqId=11155&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +请实现一个函数,将一个字符串中的空格替换成“%20”。 +例如,当字符串为 "We Are Happy". 则经过替换之后的字符串为 "We%20Are%20Happy"。 +``` + +**思路** +- 先遍历一次,找出空格的数量,得到替换后的长度;然后从后往前替换 + +**Code** +```C++ +class Solution { +public: + void replaceSpace(char *str, int length) { + if (str == nullptr || length < 0) + return; + + int l_old = strlen(str); // == length + int n_space = count(str, str + l_old, ' '); // + int l_new = l_old + n_space * 2; + str[l_new] = '\0'; + + int p_old = l_old-1; + int p_new = l_new-1; + while (p_old >= 0) { + if (str[p_old] != ' ') { + str[p_new--] = str[p_old--]; + } + else { + p_old--; + str[p_new--] = '0'; + str[p_new--] = '2'; + str[p_new--] = '%'; + } + } + } +}; +``` + + +## 6. 从尾到头打印链表 +> [从尾到头打印链表](https://www.nowcoder.com/practice/d0267f7f55b3412ba93bd35cfa8e8035?tpId=13&tqId=11156&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +输入链表的第一个节点,从尾到头反过来打印出每个结点的值。 +``` + +**思路** +- 栈 +- 头插法(双端队列或数组) + +**Code** +```C++ +class Solution { +public: + vector printListFromTailToHead(ListNode* head) { + vector ret; + + ListNode *p = head; + while (p != NULL) { + ret.insert(ret.begin(), p->val); // 头插 + p = p->next; + } + + return ret; + } +}; +``` + + +## 7. 重建二叉树 +> [重建二叉树](https://www.nowcoder.com/practice/8a19cbe657394eeaac2f6ea9b0f6fcf6?tpId=13&tqId=11157&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +根据二叉树的前序遍历和中序遍历的结果,重建出该二叉树。 +假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 +``` + +**思路** +- 涉及二叉树的问题,应该条件反射般的使用**递归**(无优化要求时) +- 前序遍历的第一个值为根节点的值,使用这个值将中序遍历结果分成两部分,左部分为左子树的中序遍历结果,右部分为右子树的中序遍历的结果。 +- **示例** + ``` + 前序 + 1,2,4,7,3,5,6,8 + 中序 + 4,7,2,1,5,3,8,6 + + 第一层 + 根节点 1 + 根据根节点的值(不重复),划分中序: + {4,7,2} 和 {5,3,8,6} + 根据左右子树的长度,划分前序: + {2,4,7} 和 {3,5,6,8} + 从而得到左右子树的前序和中序 + 左子树的前序和中序:{2,4,7}、{4,7,2} + 右子树的前序和中序:{3,5,6,8}、{5,3,8,6} + + 第二层 + 左子树的根节点 2 + 右子树的根节点 3 + ... + ``` + +**Code - 无优化** +```C++ +struct TreeNode { + int val; + TreeNode *left; + TreeNode *right; + TreeNode(int x) : val(x), left(NULL), right(NULL) {} +}; + +class Solution { +public: + TreeNode * reConstructBinaryTree(vector pre, vector vin) { + if (pre.size() <= 0) + return NULL; + + TreeNode* root = new TreeNode{ pre[0] }; + for (auto i = 0; i < vin.size(); i++) { + if (vin[i] == pre[0]) { + root->left = reConstructBinaryTree(vector(pre.begin() + 1, pre.begin() + 1 + i), vector(vin.begin(), vin.begin() + i)); + root->right = reConstructBinaryTree(vector(pre.begin() + 1 + i, pre.end()), vector(vin.begin() + 1 + i, vin.end())); + } + } + return root; + } +}; +``` + +**Code - 优化** +```C++ +class Solution { +public: + TreeNode * reConstructBinaryTree(vector pre, vector vin) { + return reConstructCore(pre, 0, pre.size(), vin, 0, vin.size()); + } + + TreeNode * reConstructCore(vector &pre, int pre_beg, int pre_end, vector &vin, int vin_beg, int vin_end) { + if (pre_end - pre_beg <= 0) + return NULL; + + TreeNode* root = new TreeNode{ pre[pre_beg] }; + for (auto i = 0; i < vin_end-vin_beg; i++) { + if (vin[i+vin_beg] == pre[pre_beg]) { + root->left = reConstructCore(pre, pre_beg+1, pre_beg+1+i, vin, vin_beg, vin_beg+i); + root->right = reConstructCore(pre, pre_beg+1+i, pre_end, vin, vin_beg+1+i, vin_end); + } + } + return root; + } +}; +``` + + +## 8. 二叉树的下一个结点 +> [二叉树的下一个结点](https://www.nowcoder.com/practice/9023a0c988684a53960365b889ceaf5e?tpId=13&tqId=11210&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。 +注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。 +``` + +**思路** +- 回顾中序遍历的顺序 +- 如果一个节点的右子树不为空,那么下一个节点是该节点右子树的最左叶子; +- 否则(右子树为空),沿父节点向上直到找到某个节点是其父节点的左孩子,那么该父节点就是下一个节点 + +**Code** +```C++ +struct TreeLinkNode { + int val; + struct TreeLinkNode *left; + struct TreeLinkNode *right; + struct TreeLinkNode *next; + TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) { + } +}; + +class Solution { +public: + TreeLinkNode * GetNext(TreeLinkNode* pNode) { + if (pNode == nullptr) + return nullptr; + + if(pNode->right != nullptr) { + auto p = pNode->right; + while(p->left != nullptr) + p = p->left; + return p; + } + else { + auto p = pNode; // 当前节点 + while(p->next != nullptr) { // 当前节点的父节点不为空 + if (p->next->left == p) // 当前节点是其父节点的左海子 + return p->next; // 那么下一个节点就是当前节点的父节点 + p = p->next; + } + } + return nullptr; // 当前节点是根节点且没有右孩子,即没有下一个节点 + } +}; +``` + +## 9. 用两个栈实现队列 +> [用两个栈实现队列](https://www.nowcoder.com/practice/54275ddae22f475981afa2244dd448c6?tpId=13&tqId=11158&tPage=1&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +用两个栈来实现一个队列,完成队列的 Push 和 Pop 操作。 +``` + +**思路** +- 假设 `stack_in` 用于处理入栈操作,`stack_out`用于处理出栈操作 +- `stack_in` 按栈的方式正常处理入栈数据; +- 关键在于出栈操作 + - 当`stack_out`为空时,需要先将每个`stack_in`中的数据出栈后压入`stack_out` + - 反之,每次弹出`stack_out`栈顶元素即可 + +**Code** +```C++ +class Solution { + stack stack_in; + stack stack_out; +public: + void push(int node) { + stack_in.push(node); + } + + int pop() { + if(stack_out.size() <= 0) { + while (stack_in.size() > 0) { + auto tmp = stack_in.top(); + stack_in.pop(); + stack_out.push(tmp); + } + } + + auto ret = stack_out.top(); + stack_out.pop(); + return ret; + } +}; +``` + + +## 10.1 斐波那契数列 +> [斐波那契数列](https://www.nowcoder.com/practice/c6c7742f5ba7442aada113136ddea0c3?tpId=13&tqId=11160&tPage=1&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +写一个函数,输入n,求斐波那契(Fibonacci)数列的第n项。 +数列的前两项为 0 和 1 +``` + +**思路** +- 递归 + - 递归可能会重复计算子问题——例如,计算 f(10) 需要计算 f(9) 和 f(8),计算 f(9) 需要计算 f(8) 和 f(7),可以看到 f(8) 被重复计算了 + - 可以利用额外空间将计算过的子问题存起来 +- 查表 + - 因为只需要前 40 项,所以可以先将值都求出来 + +**Code - 递归** +```C++ +// 该代码会因复杂度过大无法通过评测 +class Solution { +public: + int Fibonacci(int n) { + if(n <= 0) + return 0; + if(n == 1) + return 1; + return Fibonacci(n - 1) + Fibonacci(n - 2); + } +}; +``` + +**Code - 循环** +```C++ +class Solution { +public: + int Fibonacci(int n) { + int f = 0; + int g = 1; + while (n--) { + g = g + f; + f = g - f; + } + return f; + } +}; +``` + +**Code - 查表** +```C++ +class Solution { +public: + Solution(){ + fib = new int[40]; + fib[0] = 0; + fib[1] = 1; + for (int i = 2; i < 40; i++) + fib[i] = fib[i - 1] + fib[i - 2]; + } + int Fibonacci(int n) { + return fib[n]; + } + +private: + int* fib; +}; +``` + + +## 10.2 跳台阶(递归) +> [跳台阶](https://www.nowcoder.com/practice/8c82a5b80378478f9484d87d1c5f12a4?tpId=13&tqId=11161&tPage=1&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [变态跳台阶](https://www.nowcoder.com/practice/22243d016f6b47f2a6928b4313c85387?tpId=13&tqId=11162&tPage=1&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +一只青蛙一次可以跳上1级台阶,也可以跳上2级。 +求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。 +``` + +**思路** +- 递归 +- 记跳 n 级台阶有 `f(n)` 种方法 + - 如果第一次跳 1 级,那么之后的 n-1 级有 `f(n-1)` 种跳法 + - 如果第一次跳 2 级,那么之后的 n-2 级有 `f(n-2)` 种跳法 +- 实际上就是首两项为 1 和 2 的斐波那契数列 + +**Code** +```C++ +class Solution { +public: + int jumpFloor(int number) { + int f = 1; + int g = 2; + + number--; + while (number--) { + g = g + f; + f = g - f; + } + return f; + } +}; +``` + +## 10.3 变态跳台阶(动态规划) +> [变态跳台阶](https://www.nowcoder.com/practice/22243d016f6b47f2a6928b4313c85387?tpId=13&tqId=11162&tPage=1&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。 +求该青蛙跳上一个n级的台阶总共有多少种跳法。 +``` + +**思路** +- 动态规划 +- 递推公式 + ``` + f(1) = 1 + f(n) = 1 + f(1) + .. + f(n-1) + ``` + +**Code - DP** +```C++ +class Solution { +public: + int jumpFloorII(int number) { + vector dp(number+1, 1); + for (int i=2; i<=number; i++) + for(int j=1; j [矩形覆盖](https://www.nowcoder.com/practice/72a5a919508a4251859fb2cfb987a0e6?tpId=13&tqId=11163&tPage=1&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。 +请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法? +``` + +**思路** +- 动态规划 +- 递推公式 + ``` + f(1) = 1 + f(2) = 2 + f(n) = f(n-1) + f(n-2) + ``` +- 即前两项为 1 和 2 的斐波那契数列 + +**Code** +```C++ +class Solution { +public: + int rectCover(int number) { + if (number == 0) + return 0; + + int f = 1; + int g = 2; + for (int i = 2; i <= number; i++) { + g = g + f; + f = g - f; + } + return f; + } +}; +``` + +## 11. 旋转数组的最小数字(二分查找) +> [旋转数组的最小数字](https://www.nowcoder.com/practice/9f3231a991af4f55b95579b44b7a01ba?tpId=13&tqId=11159&tPage=1&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 +输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 + +例如数组 {3, 4, 5, 1, 2} 为 {1, 2, 3, 4, 5} 的一个旋转,该数组的最小值为 1。 +NOTE:给出的所有元素都大于 0,若数组大小为 0,请返回 0。 +``` + +**思路** +- 二分查找 +- 二分查找需要有一个目标值 target,这里的 target 可以选 `nums[hi]` 或 `nums[lo]`,这里使用过的是 `nums[hi]` +- 注意有重复的情况,特别是 `{3, 4, 5, 1, 2, 3}`,这里有一个简单的处理方法 + +**Code** +```C++ +class Solution { +public: + int minNumberInRotateArray(vector rotateArray) { + if (rotateArray.empty()) + return 0; + + int lo = 0; + int hi = rotateArray.size() - 1; + + // 完全旋转,或者说没有旋转(需要, e.g {1, 2}) + if (rotateArray[lo] < rotateArray[hi]) + return rotateArray[lo]; + + while (lo + 1 < hi) { + int mid = lo + (hi - lo) / 2; + + if (rotateArray[mid] > rotateArray[hi]) + lo = mid; + else if (rotateArray[mid] < rotateArray[hi]) + hi = mid; + else + hi--; // 防止这种情况 {3,4,5,1,2,3} + } + + return rotateArray[hi]; + } +}; +``` + +**Code(改进)** +```C++ +class Solution { +public: + int minNumberInRotateArray(vector rotateArray) { + if (rotateArray.empty()) + return 0; + + int n = rotateArray.size(); + + // 没有旋转的情况 + //if (rotateArray[0] < rotateArray[n-1]) + // return rotateArray[lo]; + + int lo = -1; // 如果初始化为 0 将无法处理 n == 2 的情况,初始化为 -1 就可以了 + int hi = n - 1; + + while (lo + 1 < hi) { + int mid = lo + (hi - lo) / 2; + + if (rotateArray[mid] > rotateArray[hi]) + lo = mid; + else if (rotateArray[mid] < rotateArray[hi]) + hi = mid; + else + hi--; // 防止这种情况 {3,4,5,1,2,3} + } + + return rotateArray[hi]; + } +}; +``` + +## 12. 矩阵中的路径(DFS) +> [矩阵中的路径](https://www.nowcoder.com/practice/c61c6999eecb4b8f88a98f66b273a3cc?tpId=13&tqId=11218&tPage=4&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 +``` +- 例如下面的矩阵包含了一条 bfce 路径。 +
+ +**思路** +- 深度优先搜索(DFS) +- 注意**边界判断** + +**Code** +```C++ +class Solution { +public: + bool hasPath(char* matrix, int rows, int cols, char* str) { + bool *visited = new bool[rows * cols]{false}; + for (int i=0; i=rows || j<0 || j>cols || matrix[i*cols+j]!=str[step] || visited[i*cols+j]) + return false; + + // 定义 4 个方向; + int next[][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; + // 最好的做法是作为成员变量,但是 C++ 成员变量的初始化比较麻烦, + // 而且这还是个二维数组,更麻烦,所以每次都重新定义一次,所幸不大 + + visited[i*cols+j] = true; // 访问标记 + for (auto k : next) + if (dfs(matrix, rows, cols, str, visited, i+k[0], j+k[1], step+1)) + return true; + visited[i*cols+j] = false; // 清除访问标记 + return false; + } +}; +``` + + +## 13. 机器人的运动范围(DFS) TODO +> [机器人的运动范围](https://www.nowcoder.com/practice/6e5207314b5241fb83f2329e89fdecc8?tpId=13&tqId=11219&tPage=4&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +地上有一个 m 行和 n 列的方格。 +一个机器人从坐标 (0, 0) 的格子开始移动,每一次只能向左右上下四个方向移动一格, +但是不能进入行坐标和列坐标的数位之和大于 k 的格子。 + +例如,当 k 为 18 时,机器人能够进入方格(35, 37),因为 3+5+3+7=18。 +但是,它不能进入方格(35, 38),因为 3+5+3+8=19。请问该机器人能够达到多少个格子? +``` + +**思路** +- 深度优先搜索(DFS) +- 注意边界条件判断 + +**Code** + + +## 14. 剪绳子(动态规划 | 贪心) +> [整数拆分](https://leetcode-cn.com/problems/integer-break/description/) - LeetCode + +**题目描述** +``` +把一根长度为 n 的绳子剪成 m 段,并且使得每段的长度的乘积最大(n, m 均为整数)。 +``` + +**思路** +- 动态规划 + - 递推公式 + ``` + f(n) = 0 n = 1 + f(n) = 1 n = 2 + f(n) = 2 n = 3 + f(n) = max{dp(i) * dp(n-i)} n > 3, 1<=i<=n-1 + ``` + - 注意:当 `n <= 3` 时因为必须剪至少一次的缘故,导致 `f(1)=0, f(2)=1*1=1, f(3)=1*2=2`;但是当 `n>=4` 时,将`n<=3`的部分单独作为一段能提供更大的乘积 + + 因此,初始化时应该 `dp[1]=1≠f(1), dp[2]=2≠f(2), dp[3]=3≠f(3)`,同时将 `f(1), f(2), f(3)` 单独返回 + - 时间复杂度:`O(N^2)`,空间复杂度:`O(N)` +- 贪心 + - 当 `n>=5` 时,尽可能多剪长度为 3 的绳子;当 `n=4` 时,剪成两段长度为 2 的绳子 + - 证明 + ``` + 当 n >= 5 时,可以证明: 3(n-3) > 2(n-2) > n + 当 n == 4 时,2*2 > 3*1 + ``` + - 时间复杂度:`O(1)`,空间复杂度:`O(1)` + +**Code - 动态规划** +```C++ +class Solution { +public: + int integerBreak(int n) { + if(n < 2) return 0; + if(n == 2) return 1; + if(n == 3) return 2; + + int dp[n+1]{0}; // 记得初始化为 0 + dp[1] = 1; + dp[2] = 2; + dp[3] = 3; + for (int i=4; i<=n; i++) { + for (int j=1; j<=i/2; j++) { + int p = dp[j] * dp[i-j]; + if (dp[i] < p) + dp[i] = p; + } + } + return dp[n]; + } + +}; +``` + +**Code - 贪心** +```C++ +class Solution { +public: + int integerBreak(int n) { + if(n < 2) return 0; + if(n == 2) return 1; + if(n == 3) return 2; + + int n3 = n / 3; // 切成 3 的数量 + + if (n%3 == 1) // 如果余下的长度为 4 + n3--; + + int n2 = (n - 3*n3) / 2; // 切成 2 的数量 + return (int)pow(3, n3) * (int)pow(2, n2); + } +}; +``` + + +## 15. 二进制中 1 的个数(位运算) +> [二进制中1的个数](https://www.nowcoder.com/practice/8ee967e43c2c4ec193b040ea7fbb10b8?tpId=13&tqId=11164&tPage=1&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +输入一个整数,输出该数二进制表示中1的个数。 +其中负数用补码表示。 +``` + +**思路** +- 位运算 - 移位计数 + - 时间复杂度:`O(N)`,N 为整型的二进制长度 + - 注意移位判断有两种方式:一是移动 n,一是移动"1",后者更好 + - 当 n 为 负数时,移动 n 可能导致死循环 +- 位运算 - 利用 `n&(n-1)` + - 该运算的效果是每次除去 n 的二进制表示中**最后一个 1** + ``` + n : 10110100 + n-1 : 10110011 + n&(n-1) : 10110000 + ``` + - 时间复杂度:`O(M)`,M 为二进制中 1 的个数 + +**Code - 移位计数** +```C++ +class Solution { +public: + int NumberOf1(int n) { + int ret = 0; + int N = sizeof(int) * 8; + while(N--) { + if(n & 1) + ret++; + n >>= 1; + } + return ret; + } +}; +``` + +**Code - 移位计数(改进)** +```C++ +class Solution { +public: + int NumberOf1(int n) { + int ret = 0; + int N = sizeof(int) * 8; + int flag = 1; + while(N--) { + if(n & flag) + ret++; + flag <<= 1; // 移动 1 而不是 n + } + return ret; + } +}; +``` + +**Code - `n&(n-1)`** +```C++ +class Solution { +public: + int NumberOf1(int n) { + int ret = 0; + while(n) { + ret++; + n = (n-1)&n; + } + return ret; + } +}; +``` + + +## 16. 数值的整数次方(位运算) +> [数值的整数次方](https://www.nowcoder.com/practice/1a834e5e3e1a4b7ba251417554e07c00?tpId=13&tqId=11165&tPage=1&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。 +``` + +**思路** +- 位运算 - 快速幂 +
+ +- 示例 + ``` + 求 `3^20 = 9^10 = 81^5 (= 81*81^4) = 81*6561^2 = 81*43046721` + 循环次数 = `bin(20)`的位数 = `len(10100)` = 5 + ``` +- 时间复杂度 `O(logN)` + +**Code** +```C++ +class Solution { +public: + double Power(double base, int exponent) { + int p = abs(exponent); + double ret = 1.0; + while (p != 0) { + if (p & 1) // 如果是奇数 + ret *= base; + base *= base; + p >>= 1; + } + + return exponent < 0 ? 1 / ret : ret; + } +}; +``` + + +## 17. 打印从 1 到最大的 n 位数(字符串 + DFS) + +**题目描述** +``` +输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。 +比如输入 3,则打印出 1、2、3 一直到最大的 3 位数即 999。 +``` + +**思路** +- 由于 n 可能会非常大,因此不能直接用 `int` 表示数字,包括 `long`, `long long` +- 正确的做法是用 `char` 数组进行存储。 +- 由于使用 `char` 存储数字,那么就不适合使用普通的运算操作了,此时可以使用 DFS 来获取所有的数字 + +**Code** +```C++ +void printOneToMax(int n) { + if (n <= 0) return; + + char* number = new char[n + 1]; + number[n] = '\0'; + + dfs(number, n, 0); // DFS + + delete[] number; +} + +void dfs(char* number, int length, int index) { + if (index == length) { // 递归最重要的就是结束条件要正确 + PrintNumber(number); + return; + } + + for (int i = 0; i < 10; ++i) { + number[index] = i + '0'; + dfs(number, length, index + 1); + } +} + +// 打印出这个数字,忽略开头的 0 +void PrintNumber(char* number) { + bool isBeginning0 = true; + int nLength = strlen(number); + + for (int i = 0; i < nLength; ++i) { + if (isBeginning0 && number[i] != '0') + isBeginning0 = false; + + if (!isBeginning0) { + printf("%c", number[i]); + } + } + printf("\t"); +} +``` + + +## 18.1 在 O(1) 时间内删除链表节点(链表) + +**题目描述** +``` +给定单向链表的头指针和需要删除的指针,定义一个函数在 O(1) 时间内删除该节点 +前提:该节点在链表中 +``` + +**思路** +- 因为不能遍历,所以只能通过修改节点的值来实现这个操作 +- 简单来说,就是将该节点的值修改为其下一个节点的值,**实际上删除的是该节点的下一个节点**(题目的描述可能会带来误导) +- 如果该节点不是尾节点,那么按上述操作即可——时间的复杂度为 `O(1)` +
+ +- 如果该节点是尾节点,此时必须通过遍历来找到该节点的前一个节点,才能完成删除——时间复杂度为 `O(N)` +
+ +- 如果是 C++,一定要注意 **delete 指针指向的内存后,必须将指针重新指向 nullptr** + ```C++ + delete p; + p = nullptr; + ``` +- 总的时间复杂度:`[(n-1)O(1) + O(n)] / n = O(1)` + +**Code** +```C++ +void DeleteNode(ListNode** pListHead, ListNode* pToBeDeleted) { + if(!pListHead || !pToBeDeleted) + return; + + if(pToBeDeleted->next != nullptr) { // 要删除的结点不是尾结点 + ListNode* p = pToBeDeleted->next; + pToBeDeleted->val = p->val; + pToBeDeleted->next = p->next; + + delete p; // delete 指针指向的内存后,必须将指针重新指向 nullptr + p = nullptr; + } + else if(*pListHead == pToBeDeleted) { // 链表只有一个结点,删除头结点 + delete pToBeDeleted; + pToBeDeleted = nullptr; + *pListHead = nullptr; + } + else { // 链表中有多个结点,删除尾结点 + ListNode* p = *pListHead; + while(p->next != pToBeDeleted) + p = p->next; + + p->next = nullptr; + delete pToBeDeleted; + pToBeDeleted = nullptr; + } +} +``` + + +## 18.2 删除链表中重复的结点(链表) +> [删除链表中重复的结点](https://www.nowcoder.com/practice/fc533c45b73a41b0b44ccba763f866ef?tpId=13&tqId=11209&tPage=3&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点; +重复的结点不保留,返回链表头指针。 +例如,链表1->2->3->3->4->4->5 处理后为 1->2->5 +``` + +**思路** +- 注意重复的节点不保留,所以要特别注意头结点也重复的情况——最好的做法是新设一个头结点 +- delete 指针指向的内存后,必须将指针重新指向 nullptr + +**Code** +```C++ +class Solution { +public: + ListNode * deleteDuplication(ListNode* pHead) + { + if (pHead == NULL) return pHead; + + ListNode* head = new ListNode{-1}; // 设置一个头结点 + head->next = pHead; + + ListNode* pre = head; + ListNode* cur = pHead; + while (cur != NULL && cur->next != NULL) { + if (cur->val != cur->next->val) { // 不重复时向后遍历 + pre = cur; + cur = cur->next; + } + else { // 发现重复 + int val = cur->val; + while (cur != NULL && cur->val == val) { // 循环删除重复 + auto tmp = cur; + cur = cur->next; + + delete tmp; // delete + nullptr + tmp = nullptr; + } + pre->next = cur; + } + } + + auto ret = head->next; + + delete head; // delete + nullptr + head = nullptr; + + return ret; + } +}; +``` + + +## 19. 正则表达式匹配(自动机:动态规划 | DFS) +> [正则表达式匹配](https://www.nowcoder.com/practice/45327ae22b7b413ea21df13ee7d6429c?tpId=13&tqId=11205&tPage=3&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +请实现一个函数用来匹配包括'.'和'*'的正则表达式。 +模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。 +在本题中,匹配是指字符串的所有字符匹配整个模式。 +例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配 +``` + +**思路** +- '.' 用于当做一个任意字符,'*' 用于重复前面的字符,注意两者区别 +- 下面提供 `dfs(C++)` 和 `dp(Java)` 两种做法 + +**Code - dfs** +```C++ +class Solution { +public: + bool match(char* str, char* pattern) { + if(str==NULL||pattern==NULL) + return false; + return dfs(str,pattern); + } + + bool dfs(char* str, char* pattern) { + if(*str=='\0'&&*pattern=='\0') + return true; + if(*str!='\0'&&*pattern=='\0') + return false; + if(*(pattern+1)=='*') { + if(*pattern==*str||(*pattern=='.'&&*str!='\0')) + /* + dfs(str,pattern+2): 模式串不匹配 + dfs(str+1,pattern): 模式串已经匹配成功,尝试匹配下一个字符串 + dfs(str+1,pat+2): 模式串已经成功匹配,并且不匹配下一个字符串内容 */ + return dfs(str+1,pattern)||dfs(str,pattern+2); + else + return dfs(str,pattern+2); + } + if(*str==*pattern||(*pattern=='.'&&*str!='\0')) + return dfs(str+1,pattern+1); + return false; + } +}; +``` + +**Code - dp** +```Java +public boolean match(char[] str, char[] pattern) { + int m = str.length, n = pattern.length; + boolean[][] dp = new boolean[m + 1][n + 1]; + + dp[0][0] = true; + for (int i = 1; i <= n; i++) + if (pattern[i - 1] == '*') + dp[0][i] = dp[0][i - 2]; + + for (int i = 1; i <= m; i++) + for (int j = 1; j <= n; j++) + if (str[i - 1] == pattern[j - 1] || pattern[j - 1] == '.') + dp[i][j] = dp[i - 1][j - 1]; + else if (pattern[j - 1] == '*') + if (pattern[j - 2] == str[i - 1] || pattern[j - 2] == '.') { + dp[i][j] |= dp[i][j - 1]; // a* counts as single a + dp[i][j] |= dp[i - 1][j]; // a* counts as multiple a + dp[i][j] |= dp[i][j - 2]; // a* counts as empty + } else + dp[i][j] = dp[i][j - 2]; // a* only counts as empty + + return dp[m][n]; +} +``` + + +## 20. 表示数值的字符串(自动机 | 正则) +> [表示数值的字符串](https://www.nowcoder.com/practice/6f8c901d091949a5837e24bb82a731f2?tpId=13&tqId=11206&tPage=3&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。 +例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 +但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。 +``` + +**思路** +- if 判断 - 自动机 +- 正则表达式 + +**Code - 自动机** +```C++ +class Solution { +public: + // 数字的格式可以用A[.[B]][e|EC]或者.B[e|EC]表示,其中A和C都是 + // 整数(可以有正负号,也可以没有),而B是一个无符号整数 + bool isNumeric(const char* str) { + if(str == nullptr) + return false; + + bool numeric = scanInteger(&str); + + // 如果出现'.',接下来是数字的小数部分 + if(*str == '.') { + ++str; + + // 下面一行代码用||的原因: + // 1. 小数可以没有整数部分,例如.123等于0.123; + // 2. 小数点后面可以没有数字,例如233.等于233.0; + // 3. 当然小数点前面和后面可以有数字,例如233.666 + numeric = scanUnsignedInteger(&str) || numeric; + } + + // 如果出现'e'或者'E',接下来跟着的是数字的指数部分 + if(*str == 'e' || *str == 'E') { + ++str; + // 下面一行代码用&&的原因: + // 1. 当e或E前面没有数字时,整个字符串不能表示数字,例如.e1、e1; + // 2. 当e或E后面没有整数时,整个字符串不能表示数字,例如12e、12e+5.4 + numeric = numeric && scanInteger(&str); + } + + return numeric && *str == '\0'; + } + + bool scanUnsignedInteger(const char** str) { + const char* before = *str; + while(**str != '\0' && **str >= '0' && **str <= '9') + ++(*str); + + // 当str中存在若干0-9的数字时,返回true + return *str > before; + } + + // 整数的格式可以用[+|-]B表示, 其中B为无符号整数 + bool scanInteger(const char** str) { + if(**str == '+' || **str == '-') + ++(*str); + return scanUnsignedInteger(str); + } +}; +``` + +**Code - 正则(Python)** +```Python +import re +class Solution: + # s字符串 + def isNumeric(self, s): + # Python 中完全匹配需要以 ^ 开头,以 $ 结尾 + # r"" 表示不转义 + # if re.match("^[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?$", s): + if re.match(r"^[+-]?\d*(\.\d+)?([eE][+-]?\d+)?$", s): + return True + else: + return False +``` + +**Code - 正则(C++)** +```C++ +#include + +class Solution { +public: + bool isNumeric(char* string) { + regex reg("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?"); + return regex_match(string, reg); + } +}; +``` + +**Code - 正则(Java)** +```Java +public class Solution { + public boolean isNumeric(char[] str) { + if (str == null) + return false; + return new String(str).matches("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?"); + } +} +``` + + +## 21. 调整数组顺序使奇数位于偶数前面(数组) +> [调整数组顺序使奇数位于偶数前面](https://www.nowcoder.com/practice/beb5aa231adc45b2a5dcc5b62c93f593?tpId=13&tqId=11166&tPage=1&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +输入一个整数数组,实现一个函数来调整该数组中数字的顺序, +使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分, +并保证奇数和奇数,偶数和偶数之间的相对位置不变。 +``` +- 要求:空间复杂度 `O(1)` +- 本题与原书不同,这里要求相对顺序不变,原书的侧重点在于函数指针 + +**思路** +- 如果可以使用额外空间,那么问题就很简单 +- 如果不想使用额外空间,那么~~只能通过循环移位来达到避免覆盖的目的~~,时间复杂度 `O(N^2)` + - 可以利用“冒泡排序”的思想避免循环位移 + +**Code - 使用额外空间** +```C++ +class Solution { +public: + void reOrderArray(vector &array) { + vector odd; // 存奇数 + vector eve; // 存偶数 + for (auto i : array) { + if (i & 1) // 是奇数 + odd.push_back(i); + else + eve.push_back(i); + } + + array.swap(odd); + array.insert(array.end(), eve.begin(), eve.end()); + } +}; +``` + +**Code - 不使用额外空间** +> 讨论区第二个回答 +```C++ +class Solution { +public: + void reOrderArray(vector &array) { + + for(int i = 0; i < array.size() / 2; i++) + for(int j = 0; j < array.size()-i; j++) + if((array[j]%2 == 0) && (array[j+1]%2 == 1)) + swap(array[j] ,array[j+1]); + } +}; +``` + + +## 22. 链表中倒数第 K 个结点(链表 + 双指针) +> [链表中倒数第k个结点](https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&tqId=11167&tPage=1&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +输入一个链表,输出该链表中倒数第k个结点。 +``` + +**思路** +- 设置快慢指针快指针先走 `k-1` 步,然后慢指针开始走,当快指针到达链表尾时,慢指针即指向倒数第 `k` 个节点 +- 健壮性检验: + - 输入是一个空链表 + - 链表长度小于 k + +**Code** +```C++ +class Solution { +public: + ListNode * FindKthToTail(ListNode* pListHead, unsigned int k) { + if(pListHead == nullptr) + return nullptr; + + ListNode * slow = pListHead; + ListNode * fast = pListHead; + + //先让 fast 走 k-1 步 + while (k && fast) { + fast = fast->next; + k--; + } + + // 如果 k > 0,说明 k 大于链表长度 + if (k > 0) + return nullptr; + + // 接着让两个指针一起往后走,当 fast 到最后时,slow 即指向倒数第 k 个 + while (fast) { + fast = fast->next; + slow = slow->next; + } + + return slow; + } +}; +``` + + +## 23. 链表中环的入口结点(链表 + 双指针) +> [链表中环的入口结点](https://www.nowcoder.com/practice/253d2c59ec3e4bc68da16833f79a38e4?tpId=13&tqId=11208&tPage=3&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。 +``` +- 要求:不使用额外空间 + +**思路** +- 快慢双指针 +- 快指针 fast 每次移动 2 步,慢指针 slow 每次 1 步;因为存在环,fast 和 slow 总会相遇,此时 fast 刚好比 slow 多走一圈(?) +
+ +- 如图,假设他们相遇在 z1 点,此时将 fast/slow 之一重新指向头结点,继续每次一步移动,它们再次相遇的点就是入口 + +**Code** +```C++ +class Solution { +public: + ListNode * EntryNodeOfLoop(ListNode* pHead) { + if (pHead == NULL) return nullptr; + + ListNode* slow = pHead; + ListNode* fast = pHead; + while (fast != NULL && fast->next != NULL) { + slow = slow->next; + fast = fast->next->next; + if (slow == fast) { // 找到环中相遇点 + slow = pHead; // 将 fast/slow 中的任一个重新指向头指针 + while (slow != fast) { // 直到他们再次相遇,相遇的这个点就是入口 + slow = slow->next; + fast = fast->next; + } + return slow; + } + } + return nullptr; + } +}; +``` + + +## 24. 反转链表(链表) +> [反转链表](https://www.nowcoder.com/practice/75e878df47f24fdc9dc3e400ec6058ca?tpId=13&tqId=11168&tPage=1&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +输入一个链表,反转链表后,输出新链表的表头。 +``` +- 要求:不使用额外空间 + +**思路** +- 辅助图示思考 + +**Code - 迭代** +```C++ +class Solution { +public: + ListNode * ReverseList(ListNode* head) { + if (head == nullptr) + return nullptr; + + ListNode* cur = head; // 当前节点 + ListNode* pre = nullptr; // 前一个节点 + ListNode* nxt = cur->next; // 下一个节点 + cur->next = nullptr; // 断开当前节点及下一个节点(容易忽略的一步) + while (nxt != nullptr) { + pre = cur; // 把前一个节点指向当前节点 + cur = nxt; // 当前节点向后移动 + nxt = nxt->next; // 下一个节点向后移动 + cur->next = pre; // 当前节点的下一个节点指向前一个节点 + } + return cur; + } +}; +``` + +**Code - 递归** +```C++ +class Solution { +public: + ListNode * ReverseList(ListNode* head) { + if (head == nullptr || head->next == nullptr) + return head; + + auto nxt = head->next; + head->next = nullptr; // 断开当前节点及下一个节点 + auto new_head = ReverseList(nxt); + nxt->next = head; + return new_head; + } +}; +``` + + +## 25. 合并两个排序的链表(链表) +> [合并两个排序的链表](https://www.nowcoder.com/practice/d8b6b4358f774294a89de2a6ac4d9337?tpId=13&tqId=11169&tPage=1&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。 +``` + +**思路** +- 迭代 +- 递归 + +**Code**(**迭代**) +```C++ +class Solution { +public: + ListNode* Merge(ListNode* p1, ListNode* p2) { + if (p1 == nullptr) return p2; + if (p2 == nullptr) return p1; + + // 选择头节点 + ListNode* head = nullptr; + if (p1->val <= p2->val) { + head = p1; + p1 = p1->next; + } else { + head = p2; + p2 = p2->next; + } + + auto cur = head; + while (p1 && p2) { + if (p1->val <= p2->val) { + cur->next = p1; + p1 = p1->next; + } else { + cur->next = p2; + p2 = p2->next; + } + cur = cur->next; + } + + // 别忘了拼接剩余部分 + if (p1) cur->next = p1; + if (p2) cur->next = p2; + + return head; + } +}; +``` + +**Code**(**递归**) +```C++ +class Solution { +public: + ListNode* Merge(ListNode* p1, ListNode* p2){ + if (!p1) return p2; + if (!p2) return p1; + + if (p1->val <= p2->val) { + p1->next = Merge(p1->next, p2); + return p1; + } else { + p2->next = Merge(p1, p2->next); + return p2; + } + } +}; +``` + + +## 26. 树的子结构(二叉树) +> [树的子结构](https://www.nowcoder.com/practice/6e196c44c7004d15b1610b9afca8bd88?tpId=13&tqId=11170&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) -NowCoder + +**题目描述** +``` +输入两棵二叉树A,B,判断B是不是A的子结构。 +约定空树不是任意一个树的子结构。 +``` +- 图示 +
+ +**思路** +- 递归 +- 有两个递归的点:一、递归寻找与子树根节点相同的点;二、递归判断子结构是否相同 + +**Code** +```C++ +class Solution { +public: + bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2) { + if (pRoot2 == NULL || pRoot1 == NULL) + return false; + + // 递归寻找与子树根节点相同的点 + return isSubTree(pRoot1, pRoot2) + || HasSubtree(pRoot1->left, pRoot2) + || HasSubtree(pRoot1->right, pRoot2); + } + + bool isSubTree(TreeNode* pRoot1, TreeNode* pRoot2) { + if (pRoot2 == NULL) return true; + if (pRoot1 == NULL) return false; + + // 递归判断子结构是否相同 + if (pRoot1->val == pRoot2->val) + return isSubTree(pRoot1->left, pRoot2->left) + && isSubTree(pRoot1->right, pRoot2->right); + else + return false; + } +}; +``` + + +## 27. 二叉树的镜像(二叉树) +> [二叉树的镜像](https://www.nowcoder.com/practice/564f4c26aa584921bc75623e48ca3011?tpId=13&tqId=11171&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - NowCoder + +**题目描述** +``` +操作给定的二叉树,将其变换为源二叉树的镜像。 +``` +- 图示 +
+ +**思路** +- 前序遍历,每次交换节点的左右子树;即必须先交换节点的左右子树后,才能继续遍历 + +**Code** +```C++ +class Solution { +public: + void Mirror(TreeNode *pRoot) { + if (pRoot == nullptr) return; + + auto tmp = pRoot->left; + pRoot->left = pRoot->right; + pRoot->right = tmp; + + Mirror(pRoot->left); + Mirror(pRoot->right); + } +}; +``` + + +## 28 对称的二叉树(二叉树) +> [对称的二叉树](https://www.nowcoder.com/practice/ff05d44dfdb04e1d83bdbdab320efbcb?tpId=13&tqId=11211&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) NowCoder + +**题目描述** +``` +请实现一个函数,用来判断一颗二叉树是不是对称的。 +注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。 +空树也认为是对称的 +``` + +**思路** +- 递归 +- 同时遍历左子树和右子树,然后是“左子树的左子树和右子树的右子树”,及左子树的右子树和右子树的左子树,递归以上步骤 + +**Code** +```C++ +class Solution { +public: + bool isSymmetrical(TreeNode* pRoot) { + if (pRoot == nullptr) return true; + + return dfs(pRoot->left, pRoot->right); + } + + bool dfs(TreeNode* l, TreeNode* r) { + if (l == nullptr && r == nullptr) + return true; + if (l == nullptr || r == nullptr) // 注意这个条件 + return false; + + if (l->val == r->val) + return dfs(l->left, r->right) + && dfs(l->right, r->left); + else + return false; + } +}; +``` + + +## 29. 顺时针打印矩阵(二维数组) +> [顺时针打印矩阵](https://www.nowcoder.com/practice/9b4c81a02cd34f76be2659fa0d54342a?tpId=13&tqId=11172&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder + +**题目描述** +``` +下图的矩阵顺时针打印结果为:1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10 +``` +- 图示 +
+ +- 注意,不是蛇形打印,而是一层一层顺时针打印 + +**思路** +- 二维数组遍历 + +**Code** +```C++ +class Solution { +public: + vector printMatrix(vector > matrix) { + vector ret; + + int rl = 0, rr = matrix.size()-1; + int cl = 0, cr = matrix[0].size()-1; + while(rl <= rr && cl <= cr) { + for (int i = cl; i <= cr; i++) + ret.push_back(matrix[rl][i]); + for (int i = rl+1; i <= rr; i++) + ret.push_back(matrix[i][cr]); + if (rl != rr) + for (int i = cr - 1; i >= cl; i--) + ret.push_back(matrix[rr][i]); + if (cl != cr) + for (int i = rr - 1; i > rl; i--) + ret.push_back(matrix[i][cl]); + rl++; rr--; cl++; cr--; + } + return ret; + } +}; +``` + + +## 30. 包含 min 函数的栈(数据结构:栈) +> [包含min函数的栈](https://www.nowcoder.com/practice/4c776177d2c04c2494f2555c9fcc1e49?tpId=13&tqId=11173&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder + +**题目描述** +``` +定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数 +``` +- 要求:时间复杂度 `O(1)` + +**思路** +- 因为要求在常数时间内完成所有操作,所以不能有排序操作 +- 使用一个辅助栈保存最小、次小、... + +**Code** +```C++ +class Solution { + stack s; + stack s_min; + +public: + void push(int value) { + s.push(value); + + if (s_min.empty()) + s_min.push(value); + + if (value <= s_min.top()) // 注意是小于等于 + s_min.push(value); + } + + void pop() { + if (s.top() == s_min.top()) + s_min.pop(); + s.pop(); + } + + int top() { + return s.top(); + } + + int min() { + return s_min.top(); + } + +}; +``` + + +## 31. 栈的压入、弹出序列(数据结构:栈) +> [栈的压入、弹出序列](https://www.nowcoder.com/practice/d77d11405cc7470d82554cb392585106?tpId=13&tqId=11174&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) -NowCoder + +**题目描述** +``` +输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。 +假设压入栈的所有数字均不相等。 +例如序列 1,2,3,4,5 是某栈的压入顺序,序列 4,5,3,2,1 是该压栈序列对应的一个弹出序列, +但 4,3,5,1,2 就不可能是该压栈序列的弹出序列。 +``` + +**思路** +- 使用一个辅助栈 +- 依次将入栈序列入栈,如果栈顶元素等于出栈序列的栈顶元素,则弹出 +- 当流程无法继续时,如果辅助栈是空的,则出栈序列是符合的 + +**Code** +```C++ +class Solution { +public: + bool IsPopOrder(vector pushV, vector popV) { + if (pushV.empty()) return false; + + stack tmp; + int j = 0; + for (int i = 0; i < pushV.size(); i++) { + tmp.push(pushV[i]); + while (!tmp.empty() && tmp.top() == popV[j]) { + tmp.pop(); + j++; + } + } + return tmp.empty(); + } +}; +``` + + +## 32.1 从上往下打印二叉树(BFS) +> [从上往下打印二叉树](https://www.nowcoder.com/practice/7fe2212963db4790b57431d9ed259701?tpId=13&tqId=11175&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder + +**题目描述** +``` +从上往下打印出二叉树的每个节点,同层节点从左至右打印。 +例如,以下二叉树层次遍历的结果为:1,2,3,4,5,6,7 +``` +- 图示 +
+ +**思路** +- 广度优先搜索 + 队列 +- 注意入队时先左子节点,后右节点 +- 注意不需要修改原二叉树 + +**Code** +```C++ +class Solution { + queue q; // 辅助队列 +public: + vector PrintFromTopToBottom(TreeNode* root) { + if (root == nullptr) + return vector(); + + q.push(root); + vector ret; + while (!q.empty()) { + auto cur = q.front(); + q.pop(); + ret.push_back(cur->val); + + if (cur->left != nullptr) + q.push(cur->left); + if (cur->right != nullptr) + q.push(cur->right); + } + + return ret; + } +}; +``` + + +## 32.2 分行从上到下打印二叉树(BFS) +> [把二叉树打印成多行](https://www.nowcoder.com/practice/445c44d982d04483b04a54f298796288?tpId=13&tqId=11213&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder + +**题目描述** +``` +从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。 +``` + +**思路** +- 除了利用队列和 BFS +- 为了分行输出,还需要两个变量:一个表示在当前层中还没有打印的节点数、一个表示下一层的节点数 +- 注意根节点为空的情况 + +**Code** +```C++ +class Solution { + queue q; +public: + vector> Print(TreeNode* pRoot) { + if (pRoot == nullptr) + return vector>(); + + q.push(pRoot); + int curL = 1; // 当前层的节点数,初始化为 1,根节点 + int nxtL = 0; // 下一层的节点数 + + vector> ret; + vector tmp; + while (!q.empty()) { + auto node = q.front(); + q.pop(); + curL--; + + tmp.push_back(node->val); + + if (node->left != nullptr) { + q.push(node->left); + nxtL++; + } + if (node->right != nullptr) { + q.push(node->right); + nxtL++; + } + + if (curL == 0) { + ret.push_back(tmp); + tmp.clear(); + curL = nxtL; + nxtL = 0; + } + } + return ret; + } +}; +``` + + +## 32.3 按之字形顺序打印二叉树(BFS) +> [按之字形顺序打印二叉树](https://www.nowcoder.com/practice/91b69814117f4e8097390d107d2efbe0?tpId=13&tqId=11212&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder + +**题目描述** +``` +请实现一个函数按照之字形打印二叉树, +即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印, +第三行按照从左到右的顺序打印,其他行以此类推。 +``` + +**思路** +1. ~~利用一个队列+一个栈,分奇偶讨论;~~ +1. 使用两个栈,根据奇偶,改变左右子树的入栈/入队顺序 +2. ~~利用双端队列,分奇偶改变入队/出队方向(C++ 不推荐,编码量大)~~(有坑,不好写) +3. 反转层结果:根据奇偶,判断是否反转中间结果(最直观的方法) + +**Code - 两个栈** +```C++ +class Solution { +public: + vector> Print(TreeNode* pRoot) { + if(pRoot == nullptr) + return vector>(); + + // 定义两个栈,s[0] 始终存放偶数层的节点,s[1] 始终存放奇数层的节点 + stack s[2]; + int cur = 0; // 当前层(假设根节点所在的第0层是偶数层) + + s[cur & 1].push(pRoot); // 第 0 层入栈 + vector> ret; + vector tmp; + while(!s[0].empty() || !s[1].empty()) { + auto pNode = s[cur & 1].top(); + s[cur & 1].pop(); + tmp.push_back(pNode->val); + + if(cur & 1) { // 当前是奇数层 + // 下一层是偶数层 + // 先压右节点 + if(pNode->right != nullptr) + s[0].push(pNode->right); + if(pNode->left != nullptr) + s[0].push(pNode->left); + } + else { + // 下一层是奇数层,压入 s1 + // 先压左节点 + if(pNode->left != nullptr) + s[1].push(pNode->left); + if(pNode->right != nullptr) + s[1].push(pNode->right); + } + + if(s[cur & 1].empty()) { + ret.push_back(tmp); + tmp.clear(); + cur++; // 累计层数 + } + } + return ret; + } +}; +``` + +**Code - 层反转** +```C++ +class Solution { + queue q; +public: + vector> Print(TreeNode* pRoot) { + if (pRoot == nullptr) + return vector>(); + + q.push(pRoot); + int cur = 0; // 当前层 + int curL = 1; // 当前层的节点数,初始化为 1,根节点 + int nxtL = 0; // 下一层的节点数 + + vector> ret; + vector tmp; + while (!q.empty()) { + auto node = q.front(); + q.pop(); + curL--; + + tmp.push_back(node->val); + + if (node->left != nullptr) { + q.push(node->left); + nxtL++; + } + if (node->right != nullptr) { + q.push(node->right); + nxtL++; + } + + if (curL == 0) { + if (cur & 1) // 如果是奇数层,就反转中间结果 + reverse(tmp.begin(), tmp.end()); + cur++; + ret.push_back(tmp); + tmp.clear(); + curL = nxtL; + nxtL = 0; + } + } + return ret; + } +}; +``` + + +## 33. 二叉搜索树的后序遍历序列(二叉树:递归) +> [二叉搜索树的后序遍历序列](https://www.nowcoder.com/practice/a861533d45854474ac791d90e447bafd?tpId=13&tqId=11176&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder + +**题目描述** +``` +输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。 +如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。 +``` + +**思路** +- 二叉搜索树:左子树都小于根节点,右子树都大于根节点,递归定义 +- 后序遍历:会先输出整个左子树,再输出右子树,最后根节点;也就是说,数组可以被划分为三个部分 + - 示例:`1,2,3 | 5,6,7 | 4` 第一部分都小于最后的元素,第二部分都大于最后的元素——虽然这不是一颗二叉搜索树,但是它满足第一次判断的结果,后序再递归判断左右子树 + +**Code** +```C++ +class Solution { +public: + bool VerifySquenceOfBST(vector s) { + if (s.empty()) return false; + + return dfs(s, 0, s.size()-1); + } + + bool dfs(vector &s, int l, int r) { + if (l >= r) return true; + + int base = s[r]; // 根节点 + + int mid = 0; // 寻找第一个大于根节点的元素 + for (; mid < r; mid++) + if (s[mid] > base) + break; + + bool flag = true; // 如果第一个大于根节点的元素到根节点之间的元素都大于根节点 + for (int i = mid; i [二叉树中和为某一值的路径](https://www.nowcoder.com/practice/b736e784e3e34731af99065031301bca?tpId=13&tqId=11177&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder + +**题目描述** +``` +输入一颗二叉树的跟节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。 +路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。 +(注意: 在返回值的list中,数组长度大的数组靠前) +``` + +**思路** +- 注意:必须要从根节点到叶子节点,才叫一条路径,中间结果都不算路径,这样的话问题的难度一下子降低了很多 + +**Code** +```C++ +class Solution { +public: + vector> ret; + vector trace; + + vector > FindPath(TreeNode* root, int expectNumber) { + if (root != nullptr) + dfs(root, expectNumber); + return ret; + } + + void dfs(TreeNode* cur, int n) { + trace.push_back(cur->val); + // 结束条件 + if (cur->left == nullptr && cur->right == nullptr) { + if (cur->val == n) + ret.push_back(trace); // C++ 默认深拷贝 + } + if (cur->left) + dfs(cur->left, n - cur->val); // 这里没有求和,而是用递减的方式 + if (cur->right) + dfs(cur->right, n - cur->val); + trace.pop_back(); + } +}; +``` + + +## 35. 复杂链表的复制(链表) +> [复杂链表的复制](https://www.nowcoder.com/practice/f836b2c43afc4b35ad6adc41ec941dba?tpId=13&tqId=11178&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder + +**题目描述** +``` +输入一个复杂链表—— +每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点, +返回结果为复制后链表的头节点。 +(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空) +``` +- 要求:时间复杂度 `O(N)` + +**思路** +- 基本思路 `O(N^2)` + - 第一步,依次复制每个节点 + - 第二步,对每个节点,寻找特殊指针指向的节点;因为特殊指针的位置不定,必须从头开始找 + - 假设经过 m 步找到了某个节点的特殊节点,那么在新链表中也走 m 步 +- 问题的难点在于不知道特殊指针所指的节点在新链表中位置 +- 一个**经典的方法**: + - 第一步,复制每个节点,如:原来是 `A->B->C` 变成 `A->A'->B->B'->C->C'`; +
+ - 第二步,遍历链表,使:`A'->random = A->random->next`; +
+ - 第三步,拆分链表 +
+ +**Code** +```C++ +class Solution { +public: + RandomListNode * Clone(RandomListNode* pHead) { + if (!pHead) return NULL; + + RandomListNode *cur = pHead; + // 1. 复制每个节点,如:原来是A->B->C 变成A->A'->B->B'->C->C' + while (cur) { + RandomListNode* node = new RandomListNode(cur->label); + node->next = cur->next; // 注意顺序 + cur->next = node; + cur = node->next; + } + + // 2. 遍历链表,使:A'->random = A->random->next; + cur = pHead; + RandomListNode* tmp; + while (cur) { + tmp = cur->next; + if (cur->random != nullptr) { + tmp->random = cur->random->next; + } + cur = cur->next->next; // 跳过复制的节点 + } + + // 3. 拆分链表 + cur = pHead; + RandomListNode* ret = cur->next; + while (cur->next) { + tmp = cur->next; + cur->next = tmp->next; + cur = tmp; + } + return ret; + } +}; +``` + + +## 36. 二叉搜索树与双向链表(DFS) +> [二叉搜索树与双向链表](https://www.nowcoder.com/practice/947f6eb80d944a84850b0538bf0ec3a5?tpId=13&tqId=11179&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder + +**题目描述** +``` +输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。 +要求不能创建任何新的结点,只能调整树中结点指针的指向。 +``` +
+ +**思路** +- 因为要求是有序链表,因此考虑**中序遍历** +- 利用两个额外的指针保存前一个节点和头结点,具体见代码中注释 + +**Code** +```C++ +class Solution { +public: + TreeNode * pre; // 记录上一个节点 + TreeNode * ret; // 双向链表的头结点 + + TreeNode * Convert(TreeNode* pRootOfTree) { + // C++ 小坑,不能在类类初始化,默认初始化不为 NULL + pre = nullptr; + ret = nullptr; + dfs(pRootOfTree); + return ret; + } + + // 中序遍历 + void dfs(TreeNode* node) { + if (node == nullptr) return; + + dfs(node->left); + if (ret == nullptr) // 到达最左叶子,即链表头;只会执行一次 + ret = node; + // 第一次执行该语句时,pre == nullptr;这并不矛盾。 + // 因为头节点的前一个指针就是指向 nullptr 的 + node->left = pre; + if (pre != nullptr) + pre->right = node; + pre = node; + dfs(node->right); + } +}; +``` + + +## 37. 序列化二叉树(DFS)*** +> [序列化二叉树](https://www.nowcoder.com/practice/cf7e25aa97c04cc1a68c8f040e71fb84?tpId=13&tqId=11214&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder + +**题目描述** +``` +请实现两个函数,分别用来序列化和反序列化二叉树。 +接口如下: + char* Serialize(TreeNode *root); + TreeNode* Deserialize(char *str); +``` +- 比如中序遍历就是一个二叉树序列化 +- 反序列化要求能够通过序列化的结果还原二叉树 +- 空节点用 '#' 表示,节点之间用空格分开 + +**思路** +- 一般在做树的遍历时,会以非空叶子节点作为最底层,此时还原二叉树必须要前序遍历+中序遍历或后序遍历 +- 如果以空节点作为树的最底层,那么只需要前序遍历就能还原二叉树,而且能与反序列化同步进行(这是最关键的一点) + +**Code** +```C++ +class Solution { + // 因为接口限制,所以需要使用了两个 ss + stringstream ss; + stringstream sd; + char ret[1024]; + //char* ret; + + void dfs_s(TreeNode *node) { + if (node == nullptr) { + ss << "#"; + return; + } + ss << node->val; + ss << " "; + dfs_s(node->left); + ss << " "; + dfs_s(node->right); + } + + TreeNode* dfs_d() { + if (sd.eof()) + return nullptr; + string val; // 只能用 string 接收,用 int 或 char 都会有问题 + sd >> val; + if (val == "#") + return nullptr; + TreeNode* node = new TreeNode{ stoi(val) }; // + node->left = dfs_d(); + node->right = dfs_d(); + return node; + } + +public: + char* Serialize(TreeNode *root) { + dfs_s(root); + // 这里耗了很久 + // return (char*)ss.str().c_str(); // 会出问题,原因未知 + return strcpy(ret, ss.str().c_str()); + } + + TreeNode* Deserialize(char *str) { + if (strlen(str) < 1) return nullptr; + sd << str; + return dfs_d(); + } +}; +``` + + +## 38. 字符串的排列(DFS) +> [字符串的排列](https://www.nowcoder.com/practice/fe6b651b66ae47d7acce78ffdd9a96c7?tpId=13&tqId=11180&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder +> +> 排列组合专题 TODO + +**题目描述** +``` +输入一个字符串,按字典序打印出该字符串中字符的所有排列。 +例如输入字符串 abc, 则打印出由字符 a,b,c 所能排列出来的所有字符串 abc, acb, bac, bca, cab 和 cba。 +``` + +**思路** +- 深度优先搜索 + +**Code** +```C++ +class Solution { + string s; + string tmp; + int strlen; + vector ret; + vector used; + + void dfs(int step) { + if (step == strlen) { + ret.push_back(tmp); + return; + } + for (int i = 0; i 0 && s[i] == s[i-1] && !used[i-1]) + continue; + tmp[step] = s[i]; + used[i] = 1; + dfs(step + 1); + used[i] = 0; + } + } + +public: + vector Permutation(string str) { + if (str.empty()) return vector(); + + // 当做全局变量 + s = str; + strlen = s.length(); + + sort(s.begin(), s.end()); // 因为可能存在重复,所以需要先排序,将重复的字符集合在一起 + // 初始化 + tmp.resize(strlen, '\0'); + used.resize(strlen, 0); + + dfs(0); + return ret; + } +}; +``` + + +## 39.1 数组中出现次数超过一半的数字(多数投票问题) +> [数组中出现次数超过一半的数字](https://www.nowcoder.com/practice/e8a1b01a2df14cb2b228b30ee6a92163?tpId=13&tqId=11181&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder + +**题目描述** +``` +数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。 +例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。 +由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。 +如果不存在则输出0。 +``` +- 要求:时间复杂度 `O(N)`,空间复杂度 `O(1)` + +**思路** +1. 多数投票问题(Majority Vote Algorithm) + - 设置一个计数器 cnt 和保存最多元素的变量 majority + - 如果 `cnt==0`,则将 majority 设为当前元素 + - 如果 majority 和当前元素值相同,则 `cnt++`,反之 `cnt--` + - 重复以上两步,直到扫描完数组 + - cnt 赋值为 0,再次扫描数组,如果数组元素与 majority 相同,`cnt++` + - 如果扫描结束后,`cnt > nums.size() / 2`,则返回 majority,否则返回 0。 +2. [找出数组中第 k 大的数字](#392-找出数组中第-k-大的数字) + +**Code** +```C++ +class Solution { + int cnt; + int majority; +public: + int MoreThanHalfNum_Solution(vector nums) { + if (nums.empty()) return 0; + + cnt = 0; + for (int i=0; i nums.size()/2 ? majority : 0; + } +}; +``` + + +## 40. 找出数组中第 k 大的数字(数据结构:堆)*** +> [数组中的第K个最大元素](https://leetcode-cn.com/problems/kth-largest-element-in-an-array/description/) - LeetCode +> +> [最小的K个数](https://www.nowcoder.com/practice/6a296eb82cf844ca8539b57c23e6e9bf?tpId=13&tqId=11182&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder +> +> 海量数据 Top K 专题 TODO + +**题目描述** +``` +找出数组中第 k 大的数/前 k 大的数/第 k 个最大的数 +``` +- 正序找第 k 大的元素和逆序找第 k 大的元素方法是一致的;牛客是前者,LeetCode 是后者 +- 可以改变原数组 + - 要求:时间复杂度 `O(N)`,空间复杂度 `O(1)` +- 不可以改变原数组 + - 要求:时间复杂度 `O(NlogK)`,空间复杂度 `O(K)` +- 实际上,[找出数组中出现次数超过一半的数字](#391-数组中出现次数超过一半的数字多数投票问题)可以看做是**找出数组中第 n/2 大的数字** + +**思路** +- 可以改变原数组时: + - 参考快速排序中的 `partition`(`[pɑ:ˈtɪʃn]`) 过程 + - 经过一次 partition 后,数组被 pivot 分成左右两部分:`l` 和 `r`。 + - 当 `|l| = k-1` 时,pivot 即是所找的第 k 大的数; + - 当 `|l| < k-1`,所找的数位于 `r` 中; + - 当 `|l| > k-1`,所找的数位于 `l` 中. +- 不可以改变原数组 + - 使用额外空间 - 优先队列(堆)或 multiset + +**Code - 优先队列(无优化 `O(NlogN)`)(牛客)** +```C++ +class Solution { + vector ret; +public: + vector GetLeastNumbers_Solution(vector& nums, int k) { + // 注意越界条件 + if (nums.empty() || k <= 0 || k > nums.size()) + return vector(); + if (k == nums.size()) + return vector(nums); + + // 构造最小堆,注意:priority_queue 默认是最小堆 + priority_queue, greater> p; + for (auto i : nums) // 缺点,需要完全放入数组,如果是从 100000 个中找前 2 个 + p.push(i); + + while (k--) { + ret.push_back(p.top()); + p.pop(); + } + + return ret; + } +}; +``` + +**Code - 优先队列(优化 `O(NlogK)`)(牛客)** +```C++ +class Solution { + vector ret; +public: + vector GetLeastNumbers_Solution(vector &nums, int k) { + // 注意越界条件 + if (nums.empty() || k <= 0 || k > nums.size()) + return vector(); + if (k == nums.size()) + return vector(nums); + + // 注意使用堆与无优化的方法不同,这里要使用最大堆 + priority_queue p; + + // 先把前 K 个数压入 + int i = 0; + for (; i < k; i++) + p.push(nums[i]); + // 判断后面的数 + for (; i < nums.size(); i++) { + if (nums[i] < p.top()) { + p.pop(); + p.push(nums[i]); + } + } + + // 导出结果 + while (!p.empty()) { + ret.push_back(p.top()); + p.pop(); + } + + return ret; + } +}; +``` + +**Code - 可以改变数组(牛客)** +```C++ +class Solution { + int partition(vector &nums, int lo, int hi) { + // 随机选择切分元素 + // srand(time(0)); + int base = rand() % (hi - lo + 1) + lo; // 随机选择 pivot + swap(nums[base], nums[hi]); // 把 pivot 交换到末尾 + auto& pivot = nums[hi]; // 注意是引用 + + int i = lo, j = hi; // j = hi-1; // err + while (i < j) { + while (nums[i] <= pivot && i < j) // 这里是求正序 + i++; + while (nums[j] >= pivot && i < j) + j--; + if (i < j) + swap(nums[i], nums[j]); + } + swap(nums[i], pivot); + + return i; + } +public: + vector GetLeastNumbers_Solution(vector &nums, int k) { + // 注意越界条件 + if (nums.empty() || k <= 0 || k > nums.size()) + return vector(); + if (k == nums.size()) + return vector(nums); + + int lo = 0, hi = nums.size() - 1; + int index = partition(nums, lo, hi); + while(index != k-1) { + if (index > k-1) { + hi = index - 1; + index = partition(nums, lo, hi); + } else { + lo = index + 1; + index = partition(nums, lo, hi); + } + } + + return vector(nums.begin(), nums.begin() + k); + } +}; +``` + +**Code - 第 k 个最大的数(LeetCode)** +```C++ +class Solution { + int partition(vector& nums, int lo, int hi) { + int base = rand() % (hi - lo + 1) + lo; // 随机选择 pivot + swap(nums[base], nums[hi]); // 把 pivot 交换到末尾 + auto& pivot = nums[hi]; // 注意是引用 + + int i = lo, j = hi; // j = hi-1; // err + while (i < j) { + while (nums[i] >= pivot && i < j) // 这里是求逆序 + i++; + while (nums[j] <= pivot && i < j) + j--; + if (i < j) + swap(nums[i], nums[j]); + } + swap(nums[i], pivot); + + return i; + } + +public: + int findKthLargest(vector& nums, int k) { + if (nums.empty() || k < 0) return 0; + + int lo = 0; + int hi = nums.size() - 1; + int index = partition(nums, lo, hi); + while (index != k - 1) { + if (index > k - 1) { + hi = index - 1; + index = partition(nums, lo, hi); + } + else { + lo = index + 1; + index = partition(nums, lo, hi); + } + + } + + return nums[k - 1]; + } +}; +``` + + +## 41. 数据流中的中位数(数据结构:堆) +> [数据流中的中位数](https://www.nowcoder.com/practice/9be0172896bd43948f8a32fb954e1be1?tpId=13&tqId=11216&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder + +**题目描述** +``` +如何得到一个数据流中的中位数? +如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。 +如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。 +我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。 +``` + +**思路** +- 使用平衡二叉树 AVL + - 不推荐,因为一般没有哪个语言会实现这个结构,它的综合性能不如红黑树 +- 使用两个堆:一个最大堆,一个最小堆 +- 保持两个堆的大小平衡 + +**Code - 使用\*_heap系列函数** +```C++ +class Solution { + // 用优先队列会更方便,这里试试使用 C++ 的 *_heap() 系列函数 + vector left; // 最大堆 + vector right; // 最小堆,最小堆中的元素都大于最大堆中的元素 + int N; // 记录读入的元素数 +public: + Solution(): N(0) {} // 这一步不用也没关系,默认会初始化为 0 + void Insert(int num) { + N++; // 从 1 开始计数 + if (N & 1) { // 通过奇偶确保两个堆中的元素数是平衡的 + // 如果是第奇数个就加入到 right + // 为了保证 right 永远大于 left,正确的添加方法是, + // 先加入到 left,然后弹出 left 的堆顶元素加入到 right + left.push_back(num); + push_heap(left.begin(), left.end()); // push 后要重新调整堆,默认是最大堆 + + num = left[0]; // 保存堆顶元素 + + pop_heap(left.begin(), left.end()); // 在 pop 前,需要将堆顶元素移到末尾 + left.pop_back(); + + right.push_back(num); + push_heap(right.begin(), right.end(), greater()); // 调整到最小堆,需要加入仿函数 + } else { + // 如果是第偶数个就加入到左边 + right.push_back(num); + push_heap(right.begin(), right.end(), greater()); + + num = right[0]; + + pop_heap(right.begin(), right.end(), greater()); + right.pop_back(); + + left.push_back(num); + push_heap(left.begin(), left.end()); + } + } + + double GetMedian() { + if (N & 1) { // 如果是奇数,那么中位数就是 right 的堆顶 + return (double)right[0]; + } else { + return (double)(left[0] + right[0]) / 2; + } + } +}; +``` + +**Code - 使用优先队列** +```C++ +class Solution { + priority_queue left; // 最大堆 + priority_queue, greater> right; // 最小堆,最小堆中的元素都大于最大堆中的元素 + int N; // 记录读入的元素数 +public: + Solution(): N(0) {} // 这一步不用也没关系,默认会初始化为 0 + void Insert(int num) { + N++; // 从 1 开始计数 + if(N & 1) { // 通过奇偶确保两个堆中的元素数是平衡的 + // 如果是第奇数个就加入到 right + // 为了保证 right 永远大于 left,正确的添加方法是, + // 先加入到 left,然后弹出 left 的堆顶元素加入到 right + left.push(num); + num = left.top(); + left.pop(); + right.push(num); + } else { // 如果是第偶数个就加入到左边 + right.push(num); + num = right.top(); + right.pop(); + left.push(num); + } + } + + double GetMedian() { + if (N & 1) { // 如果是奇数,那么中位数就是 right 的堆顶 + return (double)right.top(); + } else { + return (double)(left.top() + right.top()) / 2; + } + } +}; +``` + + +## 42. 连续子数组的最大和 +> [连续子数组的最大和](https://www.nowcoder.com/practice/459bd355da1549fa8a49e350bf3df484?tpId=13&tqId=11183&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder + +**题目描述** +``` +{6,-3,-2,7,-15,1,2,2},连续子数组的最大和为 8(从第 0 个开始,到第 3 个为止) +``` + +**思路** +- 因为不需要输出路径,所以不需要 DP +- 法1)暴力枚举-两层循环 +- 法2)实际上,只要遍历一次即可,首先用一个变量保存当前的最大值 + - 如果当前和 sum 为负数,那么直接舍弃,用下一个值作为当前和 + - 否则,加上下一个值(无论正负) + - 与当前最大值比较,保留最大的 + +**Code - 法1** +```C++ +class Solution { +public: + int FindGreatestSumOfSubArray(vector& array) { + + int _max = array[0]; // 存在全为负数的情况 + // 最安全的作法是赋值为数组的第一个数 + for (int i = 0; i < array.size(); i++) { + int _sum = 0; + for (int j = i; j < array.size(); j++) { + _sum += array[j]; + _max = max(_max, _sum); + } + } + return _max; + } +}; +``` + +**Code - 法2** +```Cpp +class Solution { +public: + int FindGreatestSumOfSubArray(vector& array) { + if (array.empty()) return int(); + if (array.size() == 1) return array[0]; + + int _max = array[0]; // 存在全为负数的情况 + // 最安全的作法是赋值为数组的第一个数 + int _sum = array[0]; + for (int i = 1; i < array.size(); i++) { + if (_sum < 0) { + _sum = array[i]; + } else { + _sum += array[i]; + } + + _max = max(_sum, _max); + } + return _max; + } +}; +``` + + +## 43. 从 1 到 n 整数中 1 出现的次数(Trick) +> [整数中1出现的次数(从1到n整数中1出现的次数)](https://www.nowcoder.com/practice/bd7f978302044eee894445e244c7eee6?tpId=13&tqId=11184&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder +> +> [数字 1 的个数](https://leetcode-cn.com/problems/number-of-digit-one/description/) - LeetCode + +**题目描述** +``` +给定一个整数 n,计算所有小于等于 n 的非负整数中数字 1 出现的个数。 +``` + +**思路** +- 暴力枚举,时间复杂度 `O(NlogN)` +- 找规律 `O(logN)` + +**Code - 暴力枚举** +```C++ +class Solution { + int numberOf1(int i) { + int cnt = 0; + while(i) { + if(i % 10 == 1) + cnt++; + i /= 10; + } + + return cnt; + } +public: + // int countDigitOne(int n) { // LeetCode + int NumberOf1Between1AndN_Solution(int n) { + int cnt = 0; + for (int i=1; i<=n; i++) + cnt += numberOf1(i); + + return cnt; + } +}; +``` +- LeetCode 会超时 + +**Code - 找规律** +```Cpp +class Solution { +public: + // int countDigitOne(int n) { // LeetCode + int NumberOf1Between1AndN_Solution(int n) { + int cnt = 0; + for (long m=1; m<=n; m*=10) { // 注意这里用 int m 在 LeetCode 会越界 + int a = n/m; + int b = n%m; + cnt += (a+8) / 10 * m + (a%10==1)*(b+1); + } + + return cnt; + } +}; +``` +> [LeetCode 讨论区](https://leetcode.com/problems/number-of-digit-one/discuss/64381/4+-lines-O(log-n)-C++JavaPython) + + +## 44. 数字序列中的某一位数字(Trick) + +**题目描述** +``` +数字以 0123456789101112131415... 的格式序列化到一个字符串中,求这个字符串的第 index 位。 +在这个序列中,第 5 位是 5(从 0 计数),第 13 位是 1,第 19 位是 4. +``` + +**思路** +- 暴力求解 + - 累加每个数的长度 +- Trick-类似二分的思想 + - 比如第 1001 位, + - 0~9 的长度为 10——10 < 1001 + - 10~99 的长度为 180——10+180 < 1001 + - 100~999 的长度 为 2700——10+180+2700 > 1001 + - (1001 - 10 - 180) = 811 = 270*3 + 1 + - 100 + 270 = 370 的第 1 位(从 0 计数),即 7 + +**Code** +```C++ +int countOfIntegers(int digits); +int digitAtIndex(int index, int digits); +int beginNumber(int digits); + +int digitAtIndex(int index) { + if(index < 0) + return -1; + + int digits = 1; + while(true) { + int numbers = countOfIntegers(digits); + if(index < numbers * digits) + return digitAtIndex(index, digits); + + index -= digits * numbers; + digits++; + } + + return -1; +} + +int countOfIntegers(int digits) { + if(digits == 1) + return 10; + + int count = (int) std::pow(10, digits - 1); + return 9 * count; +} + +int digitAtIndex(int index, int digits) { + int number = beginNumber(digits) + index / digits; + int indexFromRight = digits - index % digits; + + for(int i = 1; i < indexFromRight; ++i) + number /= 10; + return number % 10; +} + +int beginNumber(int digits) { + if(digits == 1) + return 0; + + return (int) std::pow(10, digits - 1); +} +``` + + +## 45. 把数组排成最小的数(排序) +> [把数组排成最小的数](https://www.nowcoder.com/practice/8fecd3f8ba334add803bf2a06af1b993?tpId=13&tqId=11185&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder + +**题目描述** +``` +输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。 +例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。 +``` + +**思路** +- 自定义排序 + - 在比较两个字符串 S1 和 S2 的大小时,应该比较的是 S1+S2 和 S2+S1 的大小, + - 如果 S1+S2 < S2+S1,那么应该把 S1 排在前面,否则应该把 S2 排在前面。 +- 利用 stringstream 拼接数字 +- C++ 中 int 转 string 的函数 + - `to_string()` + +**Code - 使用比较函数** +```C++ +class Solution { + static bool cmp(const int &l, const int &r) { + string ll = to_string(l) + to_string(r); + string rr = to_string(r) + to_string(l); + return ll < rr; + } +public: + string PrintMinNumber(vector numbers) { + sort(numbers.begin(), numbers.end(), cmp); + stringstream ss; + + for (auto i : numbers) + ss << to_string(i); + + return ss.str(); + } +}; +``` + +**Code - 使用Lambda表达式** +```Cpp +class Solution { +public: + string PrintMinNumber(vector numbers) { + sort(numbers.begin(), numbers.end(), [](const int &l, const int &r){ + return to_string(l) + to_string(r) < to_string(r) + to_string(l) + }); + + stringstream ss; + for (auto i : numbers) + ss << to_string(i); + + return ss.str(); + } +}; +``` + + +## 46. 把数字翻译成字符串(解码方法)(动态规划) +> [解码方法](https://leetcode-cn.com/problems/decode-ways/description/) - LeetCode + +**题目描述** +``` +给定一个数字,按照如下规则翻译成字符串:1 翻译成“a”,2 翻译成“b”... 26 翻译成“z”。 +给定一个只包含数字的非空字符串,请计算解码方法的总数。 +``` +- 剑指Offer 上是数字范围为 0~25,其他一致 + +**思路** +- 动态规划 +- 递推公式 + ``` + dp[0] = 1 + dp[1] = 0 int(s[0]) == 0 + = 1 其他 + dp[i] += dp[i-1] int(s[i-1: i]) != 0 && int(s[i-2: i]) > 26 + += dp[i-1] + dp[i-2] int(s[i-1: i]) != 0 && int(s[i-2: i]) <= 26 + ``` + +**Code(Python)** +```Python +class Solution: + def numDecodings(self, s): + """ + :type s: str + :rtype: int + """ + if len(s) < 1: + return 0 + + n = len(s) + dp = [0] * (n + 1) + dp[0] = 1 # 注意初始化 dp[0] = 1 + dp[1] = 1 if s[0] != '0' else 0 + + for i in range(2, n+1): + if int(s[i-1]) != 0: + dp[i] += dp[i-1] + if int(s[i-2]) == 0: + continue + if int(s[i-2: i]) <= 26: + dp[i] += dp[i-2] + + return dp[n] +``` + + +## 47. 礼物的最大价值(年终奖)(动态规划) +> [年终奖](https://www.nowcoder.com/questionTerminal/72a99e28381a407991f2c96d8cb238ab)_牛客网 + +**题目描述** +``` +在一个 m*n 的棋盘的每一个格都放有一个礼物,每个礼物都有一定价值(大于 0)。 +从左上角开始拿礼物,每次向右或向下移动一格,直到右下角结束。 +给定一个棋盘,求拿到礼物的最大价值。例如,对于如下棋盘 + 1 10 3 8 + 12 2 9 6 + 5 7 4 11 + 3 7 16 5 +礼物的最大价值为 1+12+5+7+7+16+5=53。 +``` + +**思路** +- 深度优先搜索-复杂度大 +- 动态规划 +- 二维递推公式 + ``` + 初始化 + dp[0][0] = board[0][0] + dp[i][0] = dp[i-1][0] + board[i][0] + dp[0][j] = dp[0][j-1] + board[0][j] + + dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + board[i][j] + ``` + - 注意边界条件 +- 一维递推公式(优化版) + ``` + TODO + ``` + +**Code - 二维DP** +```C++ +class Bonus { +public: + int getMost(vector>& board) { + if (board.empty() || board[0].empty()) + return 0; + + int m = board.size(); + int n = board[0].size(); + vector> dp(m, vector(n, 0)); + + dp[0][0] = board[0][0]; + for (int i=1; i>& board) { + if (board.empty() || board[0].empty()) + return 0; + + int n = board[0].size(); + vector dp(n, 0); // 注意不是 dp(0, n) + + for (auto& v: board) { + dp[0] += v[0]; + for (int i=1; i dp[i-1] + ``` + +**Code - DP** +```Cpp +int longestSubstringWithoutDuplication(const string& s) { + if (s.length() < 1) return 0; + + int n = s.length(); + + int maxLen = 0; + vector dp(n, 1); // 长度至少为 1 + vector book(26, -1); // 模拟字典 + + // dp[0] = 1; + book[s[0] - 'a'] = 0; + for (int i=1; i < n; i++) { + int pre = book[s[i] - 'a']; + if (pre < 0 || i - pre > dp[i-1]) { + dp[i] = dp[i-1] + 1; + maxLen = max(dp[i], maxLen); + } + else { + maxLen = max(dp[i-1], maxLen); + dp[i] = i - pre; + } + book[s[i] - 'a'] = i; + } + + return maxLen; +} + +int main() { + cout << longestSubstringWithoutDuplication("abcacfrar") << endl; // 4 + cout << longestSubstringWithoutDuplication("acfrarabc") << endl; // 4 + cout << longestSubstringWithoutDuplication("arabcacfr") << endl; // 4 + cout << longestSubstringWithoutDuplication("aaaa") << endl; // 1 + cout << longestSubstringWithoutDuplication("abcdefg") << endl; // 7 + cout << longestSubstringWithoutDuplication("") << endl; // 0 + cout << longestSubstringWithoutDuplication("aaabbbccc") << endl; // 2 + cout << longestSubstringWithoutDuplication("abcdcba") << endl; // 4 + cout << longestSubstringWithoutDuplication("abcdaef") << endl; // 6 + + return 0; +} +``` + +**Code - 优化** +```C++ +int longestSubstringWithoutDuplication(const std::string& s) { + if (s.length() < 1) return 0; + + int n = s.length(); + + int curLen = 0; + int maxLen = 0; + vector book(26, -1); // 模拟字典 + + for (int i=0; i < n; i++) { + int pre = book[s[i] - 'a']; + if (pre < 0 || i - pre > curLen) { + curLen++; + maxLen = max(curLen, maxLen); + } + else { + maxLen = max(curLen, maxLen); + curLen = i - pre; + } + book[s[i] - 'a'] = i; + } + + return maxLen; +} + +int main() { + cout << longestSubstringWithoutDuplication("abcacfrar") << endl; // 4 + cout << longestSubstringWithoutDuplication("acfrarabc") << endl; // 4 + cout << longestSubstringWithoutDuplication("arabcacfr") << endl; // 4 + cout << longestSubstringWithoutDuplication("aaaa") << endl; // 1 + cout << longestSubstringWithoutDuplication("abcdefg") << endl; // 7 + cout << longestSubstringWithoutDuplication("") << endl; // 0 + cout << longestSubstringWithoutDuplication("aaabbbccc") << endl; // 2 + cout << longestSubstringWithoutDuplication("abcdcba") << endl; // 4 + cout << longestSubstringWithoutDuplication("abcdaef") << endl; // 6 + + return 0; +} +``` + + +## 49. 丑数(动态规划) +> [丑数](https://www.nowcoder.com/practice/6aa9e04fc3794f68acf8778237ba065b?tpId=13&tqId=11186&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder + +**题目描述** +``` +把只包含质因子2、3和5的数称作丑数(Ugly Number)。 +例如6、8都是丑数,但14不是,因为它包含质因子7。 +习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。 +``` + +**思路** +- 动态规划 + +**Code** +```Cpp +class Solution { +public: + int GetUglyNumber_Solution(int n) { + // if (n <=6 ) return n; + + vector dp(n+1, 0); // dp[0] = 0; + int i2=1, i3=1, i5=1; + dp[1] = 1; + for (int i=2; i<=n; i++) { + int nxt2 = dp[i2] * 2; + int nxt3 = dp[i3] * 3; + int nxt5 = dp[i5] * 5; + + dp[i] = min({nxt2, nxt3, nxt5}); + + // 注意以下不能使用 else 结构,因为可能存在 nxtM == nxtN 的情况 + if (dp[i] == nxt2) i2++; + if (dp[i] == nxt3) i3++; + if (dp[i] == nxt5) i5++; + } + + return dp[n]; + } +}; +``` + +## 50.1 第一个只出现一次的字符位置(Hash) +> [第一个只出现一次的字符](https://www.nowcoder.com/practice/1c82e8cf713b4bbeb2a5b31cf5b0417c?tpId=13&tqId=11187&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder + +**题目描述** +``` +在一个字符串 (1 <= 字符串长度 <= 10000,全部由字母组成) 中找到第一个只出现一次的字符,并返回它的位置。 +``` + +**思路** +- Hash 表 +- 因为是字符,可以使用数组模拟哈希表 + +**Code - 数组** +```Cpp +class Solution { +public: + int FirstNotRepeatingChar(const string& s) { + vector m(256, 0); + + for (auto c : s) + if (m[c] < 1) + m[c] = 1; + else + m[c] += 1; + + for (int i=0; i < s.length(); i++) + if (m[s[i]] == 1) + return i; + + return -1; + } +}; +``` + +**Code - Hash(C++ map)** +```Cpp +class Solution { + map m; +public: + int FirstNotRepeatingChar(const string& s) { + for (auto c : s) + if (m.count(c) < 1) + m[c] = 1; + else + m[c] += 1; + + for (int i=0; i < s.length(); i++) + if (m[s[i]] == 1) + return i; + + return -1; + } +}; +``` + +**Code - Hash(Python dict)** +```Python +class Solution: + def FirstNotRepeatingChar(self, s): + d = dict() + for c in s: + if c in d: + d[c] += 1 + else: + d[c] = 1 + + for i in range(len(s)): + if d[s[i]] == 1: + return i + + return -1 +``` + + +## 50.2 字符流中第一个只出现一次的字符(数据结构:队列) +> [字符流中第一个不重复的字符](https://www.nowcoder.com/practice/00de97733b8e4f97a3fb5c680ee10720?tpId=13&tqId=11207&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder + +**题目描述** +``` +请实现一个函数用来找出字符流中第一个只出现一次的字符。 +例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。 +当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。 +``` + +**思路** +- 计数排序——使用一个数组数组保存出现元素的次数 +- 使用队列保存出现的元素 + +**Code - 队列** +```C++ +class Solution { + int book[256]; + queue q; + +public: + Solution() { + fill(book, book + 256, 0); // 初始化,实际不需要这步,默认全部初始化为 0 + } + + void Insert(char ch) { + book[ch] += 1; + q.push(ch); + while (!q.empty() && book[q.front()] > 1) { + q.pop(); + } + } + + char FirstAppearingOnce() { + return q.empty() ? '#' : q.front(); + } +}; +``` + + +## 51. 数组中的逆序对 +> [数组中的逆序对](https://www.nowcoder.com/practice/96bd6684e04a44eb80e6a68efc0ec6c5?tpId=13&tqId=11188&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder + +**题目描述** +``` +在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。 +输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007 +``` + +**思路** + + +**Code** +```Cpp + +``` + + +## +> + +**题目描述** +``` + +``` + +**思路** + + +**Code** +```Cpp + +``` + + +## +> + +**题目描述** +``` + +``` + +**思路** + + +**Code** +```Cpp + +``` + + +## +> + +**题目描述** +``` + +``` + +**思路** + + +**Code** +```Cpp + +``` + + +## +> + +**题目描述** +``` + +``` + +**思路** + + +**Code** +```Cpp + +``` + + +## +> + +**题目描述** +``` + +``` + +**思路** + + +**Code** +```Cpp + +``` + + +## +> + +**题目描述** +``` + +``` + +**思路** + + +**Code** +```Cpp + +``` diff --git "a/C-\347\256\227\346\263\225/\351\242\230\350\247\243-\351\235\242\350\257\225\347\234\237\351\242\230.md" "b/C-\347\256\227\346\263\225/\351\242\230\350\247\243-\351\235\242\350\257\225\347\234\237\351\242\230.md" new file mode 100644 index 00000000..dd238fc7 --- /dev/null +++ "b/C-\347\256\227\346\263\225/\351\242\230\350\247\243-\351\235\242\350\257\225\347\234\237\351\242\230.md" @@ -0,0 +1,39 @@ +题解-面试真题 +=== +- 记录一些暂时没找到原型的面试真题 + +Index +--- + + +- [给定 `n` 个`[0,n)`区间内的数,统计每个数出现的次数,不使用额外空间](#给定-n-个0n区间内的数统计每个数出现的次数不使用额外空间) + + + +## 给定 `n` 个`[0,n)`区间内的数,统计每个数出现的次数,不使用额外空间 +> 头条 + +**思路**: +- 基于两个基本运算: + ```tex + 若 i ∈ [0, n),则有 + (t*n + i) % n = i + (t*n + i) / n = t + ``` +- 顺序遍历每个数 i,i 每出现一次,则 nums[i] += n +- 遍历结束后,i 出现的次数,即 `nums[i] / n`,同时利用 `nums[i] % n` 可以还原之前 `nums[i]` 上的数。 + +**C++**(未测试) +```C++ +vector nums; + +void init(vector& nums) { + for (int i = 0; i < nums.size(); i++) { + nums[nums[i]] += n; + } +} + +int cnt(int k) { + return nums[k] / n; +} +``` \ No newline at end of file diff --git "a/C-\347\274\226\347\250\213\350\257\255\350\250\200/Cpp-A-\345\237\272\347\241\200.md" "b/C-\347\274\226\347\250\213\350\257\255\350\250\200/Cpp-A-\345\237\272\347\241\200.md" new file mode 100644 index 00000000..598eb5e9 --- /dev/null +++ "b/C-\347\274\226\347\250\213\350\257\255\350\250\200/Cpp-A-\345\237\272\347\241\200.md" @@ -0,0 +1,490 @@ +**RoadMap** +--- + + +- [指针与引用](#指针与引用) + - [左值引用与右值引用](#左值引用与右值引用) +- [static 与 const](#static-与-const) + - [const 相关代码](#const-相关代码) +- [this 指针](#this-指针) +- [inline 内联函数](#inline-内联函数) + - [编译器对 inline 函数的处理步骤](#编译器对-inline-函数的处理步骤) + - [inline 的优缺点](#inline-的优缺点) + - [虚函数可以内联吗?](#虚函数可以内联吗) +- [assert 与 sizeof](#assert-与-sizeof) +- [C++ 中 struct、union、class](#c-中-structunionclass) + - [C 与 C++ 中的结构体](#c-与-c-中的结构体) + - [C++ 中 struct 和 class 的区别](#c-中-struct-和-class-的区别) + - [联合体 union](#联合体-union) +- [用 C 实现 C++ 中的封装、继承和多态](#用-c-实现-c-中的封装继承和多态) +- [友元函数与友元类](#友元函数与友元类) +- [枚举类型 enum](#枚举类型-enum) +- [其他](#其他) + - [#pragma pack(n)](#pragma-packn) + - [位域 Bit mode](#位域-bit-mode) + - [关键字 volatile](#关键字-volatile) + - [关键字 extern "C"](#关键字-extern-c) + - [关键字 explicit](#关键字-explicit) + - [关键字 using](#关键字-using) + - [范围解析运算符 ::](#范围解析运算符-) + - [关键字 decltype](#关键字-decltype) + + + +## 指针与引用 + +### 左值引用与右值引用 +> [C++专题-左值与右值](./Cpp-C-左值与右值.md) + +## static 与 const +> https://github.com/huihut/interview#const + +**static 作用** +1. 修饰普通变量,修改变量的存储区域和生命周期,使变量存储在静态区,在 main 函数运行前就分配了空间,如果有初始值就用初始值初始化它,如果没有初始值系统用默认值初始化它。 +1. 修饰普通函数,表明函数的作用范围,仅在定义该函数的文件内才能使用。在多人开发项目时,为了防止与他人命令函数重名,可以将函数定位为 static。 +1. 修饰成员变量,修饰成员变量使所有的对象只保存一个该变量,而且不需要生成对象就可以访问该成员。 +1. 修饰成员函数,修饰成员函数使得不需要生成对象就可以访问该函数,但是在 static 函数内不能访问非静态成员。 + +**const 的作用** +1. 修饰变量,说明该变量不可以被改变; +1. 修饰指针,分为指向常量的指针和指针常量; +1. 常量引用,经常用于形参类型,即避免了拷贝,又避免了函数对值的修改; +1. 修饰成员函数,说明该成员函数内不能修改成员变量。 + +### const 相关代码 + + +```cpp +// 类 +class A { +private: + const int a; // 常对象成员,只能在初始化列表赋值 + +public: + // 构造函数 + A() { }; + A(int x) : a(x) { }; // 初始化列表赋值常对象成员 + + // const 可用于对重载函数的区分 + int getValue(); // 普通成员函数 + int getValue() const; // 常成员函数,不得修改类中的任何数据成员的值 + // 工程上为了安全性有时会分别实现 const 和 非const 两个版本 + // > 下标操作符为什么要定义const和非const两个版本?_百度知道 https://zhidao.baidu.com/question/517798128.html +}; + +// 全局函数 +void function() { + // 对象 + A b; // 普通对象,可以调用全部成员函数 + const A a; // 常对象,只能调用常成员函数、更新常成员变量 + const A *p = &a; // 常指针,注意 a 是一个常对象 + const A &q = a; // 常引用 + + // 指针 + char greeting[] = "Hello"; + char* p1 = greeting; // 指针变量,指向字符数组 变量 + const char* p2 = greeting; // 指针变量,指向字符数组 常量 + char* const p3 = greeting; // 常指针,指向字符数组 变量 + const char* const p4 = greeting; // 常指针,指向字符数组 常量 +} + +// 函数 +void function1(const int Var); // 传递过来的参数在函数内不可变 +void function2(const char* Var); // 参数指针所指内容为常量 +void function3(char* const Var); // 参数指针为常指针 +void function4(const int& Var); // 引用参数在函数内为常量 + +// 函数返回值 +const int function5(); // 返回一个常数 +const int* function6(); // 返回一个指向常量的指针变量,使用:const int *p = function6(); +int* const function7(); // 返回一个指向变量的常指针,使用:int* const p = function7(); +``` + +## this 指针 +> https://github.com/huihut/interview#this-指针 + +## inline 内联函数 +> https://github.com/huihut/interview#inline-内联函数 + +**内联函数的特点** +- 相当于把内联函数中的内容复制到了调用该函数的地方(编译时完成); + - 相当于避免了函数调用的开销,直接执行函数体; + - 加速了程序的运行,但是消耗了空间 +- 相当于宏,但比宏多了**类型检查**,使具有函数特性; +- 不能包含循环、递归、switch 等复杂操作; + - 如果包含了,那么相当于失去了内联的作用; + - 内联只是对编译器的**建议**,是否内联取决于编译器 +- 定义在类中的函数,除了虚函数,都会自动隐式地当成内联函数。 + > 但这不代表虚函数无法内联 > [虚函数可以内联吗?](#虚函数可以内联吗) +- 内联函数通常定义在头文件中 + - inline 函数对编译器而言必须是可见的,以便它能够在调用点展开该函数。 + - 如果定义在 cpp 文件中,那么在每个调用该内联函数的文件内都要再重新定义一次,且定义必须一致 + - inline 函数允许多次定义 + + +**`inline` 的使用** +```Cpp +// 定义在头文件中 +inline +int func() {} + +// 声明在头文件中 +inline +int func1(); +// 定义在源文件 1 中 +inline +int func1() {} +// 定义在源文件 2 中 +inline +int func1() {} +// 源文件 1 和 2 中的定义必须一致 +``` +- 关于 inline 关键字应该放在声明还是定义处,重说纷纭,保险起见都加上 +- 当然,最好的做法是直接定义在头文件中 + +### 编译器对 inline 函数的处理步骤 +- 将 inline 函数体复制到 inline 函数调用点处; +- 为所用 inline 函数中的局部变量分配内存空间; +- 将 inline 函数的的输入参数和返回值映射到调用方法的局部变量空间中; +- 如果 inline 函数有多个返回点,将其转变为 inline 函数代码块末尾的分支(使用 GOTO)。 + +### inline 的优缺点 +**优点(相比宏定义)** +- 内联函数类似宏函数,在调用处展开,省去了**调用开销**(参数压栈、栈帧开辟与回收,结果返回等),从而提高程序运行速度。 +- 相比宏函数来说,内联函数在代码展开时,会进行安全检查或自动类型转换(同普通函数),而宏定义不会。 +- 在类中声明同时定义的成员函数,会自动转化为内联函数,因此内联函数可以访问类的成员变量,宏定义则不能。 +- 内联函数在运行时可调试,宏定义不可以。 + +**缺点** +- 代码膨胀。 + - 内联是以代码膨胀(内存)为代价,来消除函数调用带来的开销。 + - 如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。 +- inline 函数无法随函数库升级。 + - inline 函数的改变需要重新编译,不像 non-inline 可以直接链接。 +- 是否内联,程序员不可控。 + - 内联函数只是对编译器的建议,是否对函数内联,决定权在于编译器。 + +### 虚函数可以内联吗? +> [Are "inline virtual" member functions ever actually "inlined"?](http://www.cs.technion.ac.il/users/yechiel/c++-faq/inline-virtuals.html) - C++ FAQ +- 虚函数可以是内联函数 +- 但是当虚函数表现多态性时不能内联。 + - inline 是在编译期建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。 +- 虚函数**唯一可以内联的情况**是: + - 编译器知道所调用的对象是哪个类,如 `Base::who()`,这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。 + +**虚函数的内联** +```Cpp +class Base { +public: + inline virtual + void who() { + cout << "I am Base\n"; + } + + virtual ~Base() { } +}; + +class Derived : public Base { +public: + inline void who() { // 不写 inline 时也会隐式内联 + cout << "I am Derived\n"; + } +}; + +int main() { + // 此处的虚函数 who(),是通过 Base类的具体对象 b来调用的, + // 因此编译期间就能确定,所以它可以是内联的,但最终是否内联取决于编译器。 + Base b; + b.who(); + + // 此处的虚函数是通过指针调用的,呈现多态性, + // 需要在运行时期间才能确定,所以不能为内联。 + Base *ptr = new Derived(); + ptr->who(); + + // 因为 Base 有虚析构函数(virtual ~Base() {}), + // 所以 delete 时,会先调用派生类(Derived)析构函数, + // 再调用基类(Base)析构函数,防止内存泄漏。 + delete ptr; + ptr = nullptr; + + system("pause"); + return 0; +} +``` + +## assert 与 sizeof +- 断言 `assert()` **是宏而非函数**,定义在头文件 `/` 中 +- `sizeof()` **是操作符而非函数** `sizeof int == sizeof(int)` + - sizeof 对数组,得到整个数组所占空间大小。 + - sizeof 对指针,得到指针本身所占空间大小。 + - 特别的 + ```Cpp + void foo(int arr[]) { // 实际上是 int* arr + cout << sizeof(arr) << endl; // 得到的是指针的大小,而非数组的大小 + } + ``` + + +## C++ 中 struct、union、class + +### C 与 C++ 中的结构体 +- C 中的结构体 + ```C + typedef + struct Student { + int age; + } Stu; + ``` + 等价于 + ```C + struct Student { + int age; + }; + typedef struct Student Stu; + ``` + 实际上就是为 `struct Student` 这个比较长的声明定义了一个别名 `Stu` + ```C + struct Student s1; + Stu s2; + ``` + 因为 C 中定义结构体,必须带上 `struct`,所以还可以定义 `void Student() {}` 不冲突 + +- C++ 中的结构体 + ```Cpp + typedef + struct Student { + int age; + } Stu; + + Student s1; // 正确,"struct" 关键字可省略 + struct Student s2; // 正确 + Stu s3; // 正确,使用别名 + + void f( Student me ); // 正确 + ``` + 如果定义了同名的函数,那么 struct 关键字不可省略 + ```Cpp + typedef + struct Student { + int age; + } Stu; + + void Student() {} // 正确,定义后名为 "Student" 的函数 + + // void Stu() {} // 错误,符号 "Stu" 已经被定义为一个 "struct Student" 的别名 + + int main() { + Student(); + struct Student s1; + Stu s2; + return 0; + } + ``` + +### C++ 中 struct 和 class 的区别 +- 一般来说,struct 适合作为一个数据结构的实现体,class 更适合作为一个对象的实现体 +- 但最本质的区别在于默认的**访问控制权限** + - 默认的继承访问权限—— struct 是 public,class 是 private + - 默认的成员访问权限—— struct 是 public,class 是 private + +### 联合体 union +- **联合体**(union)是一种节省空间的特殊的类 + - 一个 union 可以有多个数据成员,但是在任意时刻只有一个数据成员可以有值 + - 当某个成员被赋值后其他成员变为**未定义状态** +- 联合体的特点 + - 默认访问控制符为 public + - 可以含有构造函数、析构函数 + - 不能含有引用类型的成员 + - 不能继承自其他类,不能作为基类 + - 不能含有虚函数 + - 匿名 union 在定义所在作用域可直接访问 union 成员 + - 匿名 union 不能包含 protected 成员或 private 成员 + - 全局匿名联合必须是静态(static)的 + +```Cpp +union UnionTest { // 联合体 + UnionTest() : i(10) {}; + int i; + double d; +}; + +static union { // 全局静态匿名联合体 + int i; + double d; +}; + +int main() { + UnionTest u; + + union { // 局部匿名联合体 + int i; + double d; + }; + + std::cout << u.i << std::endl; // 输出 UnionTest 联合的 10 + + ::i = 20; // 匿名 union 在定义所在作用域可直接访问 + std::cout << ::i << std::endl; // 输出全局静态匿名联合的 20 + + i = 30; + std::cout << i << std::endl; // 输出局部匿名联合的 30 + + return 0; +} +``` + + +## 用 C 实现 C++ 中的封装、继承和多态 +> 董的博客 » [C语言实现封装、继承和多态](http://dongxicheng.org/cpp/ooc/) + + +## 友元函数与友元类 +> [友元(友元函数、友元类和友元成员函数) C++](https://www.cnblogs.com/zhuguanhao/p/6286145.html) - zhuguanhao - 博客园 + +**友元小结** +- 能访问私有成员 +- 破坏封装性 +- 友元关系不可传递 +- 友元关系的单向性 +- 友元声明的形式及数量不受限制 + +**友元函数** +- 不是类的成员函数,却能访问该类所有成员(包括私有成员)的函数 +- 类授予**它的友元函数**特别的访问权,这样该友元函数就能访问到类中的所有成员。 + ```Cpp + class A { + public: + friend void set_data(int x, A &a); // 友元函数的声明 + int get_data() { return data; } + private: + int data; + }; + + void set_data(int x, A &a) { // 友元函数的定义 + a.data = x; + cout << a.data << endl; // 无障碍读写类的私有成员 + } + + int main(void) { + class A a; + set_data(1, a); + // cout << a.data; // err + cout << a.get_data() << endl; + return 0; + } + ``` + +**友元类** +- 一个类的友元类可以访问该类的所有成员(包括私有成员) +- +- 注意点 + - 友元关系不能被继承 + - 友元关系不能传递 + - 友元关系是单向的 +```Cpp +class A { +public: + friend class C; // 友元类的声明:C 是 A 的友元类 +private: + int data; +}; + +class C { // 友元类的定义,可以访问 A 中的成员 +public: + void set_A_data(int x, A &a) { + a.data = x; + } + + int get_A_data(A& a) { + return a.data; + } +}; + +int main(void) { + class A a; + class C c; + + c.set_A_data(1, a); + cout << c.get_A_data(a) << endl; // 1 + + return 0; +} +``` + +**友元成员函数** +- 使类 B 中的成员函数成为类A的友元函数(但 B 不是 A 的友元类),这样只有类 B 的该成员函数可以访问类 A 的所有成员 +- 不推荐使用,原因如下 +- 注意声明与定义的顺序 + ```Cpp + class A; // 类 A 的声明,因为 A 的友元函数,即 B 的成员函数要用到 A,所以必须先声明类 A + // 但此时还不能定义 A,因为 B 还没有定义,B 的成员函数就无从谈起 + class B { // 类 B 的定义 + public: + void set_A_data(int x, A &a); // 类 A 的友元函数,同时是 B 的成员函数 + // 但只能声明,还不能定义,因为 A 还没有定义,访问 A 的成员就无从谈起 + }; + + class A { + public: + friend void B::set_A_data(int x, A &a); // 将 B 的成员函数声明为 A 的友元成员函数;所以必须先定义 B + private: + int data; + void print_data() { cout << data << endl; } + }; + + void B::set_A_data(int x, A &a) { // 只有在定义类 A 后才能定义该函数 + a.data = x; // 访问 A 的私有成员变量 + a.print_data(); // 访问 A 的私有成员函数 + } + + int main(void) { + class A a; + class B b; + + b.set_A_data(1, a); + + return 0; + } + ``` + + +## 枚举类型 enum +> [C++枚举类型](https://www.baidu.com/s?wd=Cpp枚举类型)_百度搜索 +- 限制作用域的枚举类型,使用关键字 `enum class` + ```Cpp + enum class open_modes { input, output, append }; + ``` +- 不限作用域的枚举类型 + ```Cpp + enum color { red, yellow, green }; + enum { floatPrec = 6, doublePrec = 10 }; + ``` + + + +## 其他 + +### #pragma pack(n) +> https://github.com/huihut/interview#pragma-packn + +### 位域 Bit mode +> https://github.com/huihut/interview#位域 + +### 关键字 volatile +> https://github.com/huihut/interview#volatile + +### 关键字 extern "C" +> https://github.com/huihut/interview#extern-c + +### 关键字 explicit +> https://github.com/huihut/interview#explicit显式构造函数 + +### 关键字 using +> https://github.com/huihut/interview#using + +### 范围解析运算符 :: +> https://github.com/huihut/interview#-范围解析运算符 + +### 关键字 decltype +> https://github.com/huihut/interview#decltype \ No newline at end of file diff --git "a/C-\347\274\226\347\250\213\350\257\255\350\250\200/Cpp-A-\351\235\242\345\220\221\345\257\271\350\261\241.md" "b/C-\347\274\226\347\250\213\350\257\255\350\250\200/Cpp-A-\351\235\242\345\220\221\345\257\271\350\261\241.md" new file mode 100644 index 00000000..8ef4a058 --- /dev/null +++ "b/C-\347\274\226\347\250\213\350\257\255\350\250\200/Cpp-A-\351\235\242\345\220\221\345\257\271\350\261\241.md" @@ -0,0 +1,102 @@ +C++-面向对象编程 +=== + +面向对象编程概述 +--- +- 面向对象编程的两个主要特征:**继承**、**多态** +- C++ 中是通过间接利用“**指向父类**”的**指针**或**引用**来操作其子类对象,从而达到多态的目的;如果直接操作某个实例,那么多态将无从谈起 +- 具体来说,**多态**是通过**动态绑定**机制,达到在**运行时**确定实际被调用的是哪个子类类型,进而调用对应的 **override** 方法 + +Reference +--- +- 《Essential C++》 第 4/5 章 - Lippman, 侯捷 + +Index +--- + + +- [不通过继承实现多态](#不通过继承实现多态) +- [抽象基类](#抽象基类) + + + +## 不通过继承实现多态 +> 4.11 指针,指向 Class member function +- 核心想法:手工调整函数指针,模拟**动态绑定**的效果 +
示例:动态序列(点击展开) + + ```Cpp + class num_sequence { + public: + // PtrType 是一个指针,指向 num_sequence 的成员函数, + // 该成员函数必须只接受一个 int 型参数,以及返回类型为 void + typedef void (num_sequence::*PtrType)(int); + + enum { cnt_seq = 2 }; // 预定义了两种序列 + enum ns_type { + ns_fibonacci, ns_square + }; + + // 构造函数:默认指向斐波那契数列 + num_sequence(): _pmf(func_tbl[ns_fibonacci]) { } + + // 调整指针指向 + void set_sequence(ns_type nst) { + switch (nst) { + case ns_fibonacci: case ns_square: + _pmf = func_tbl[nst]; + break; + default: + cerr << "invalid sequence type\n"; + } + } + + void print(int n) { + (this->*_pmf)(n); // 通过指针选择需要调用的函数 + } + + // _pmf 可以指向以下任何一个函数 + void fibonacci(int n) { + int f = 1; + int g = 1; + for (int i = 2; i <= n; i++) + g = g + f, f = g - f; + cout << f << endl; + } + + void square(int n) { + cout << n * n << endl; + } + + private: + PtrType _pmf; + static PtrType func_tbl[cnt_seq]; // 保存所有序列函数的指针 + // 为了兼容性,不推荐写成 `static vector没有空格> _seq;` + }; + + // static 成员变量初始化 + num_sequence::PtrType + num_sequence::func_tbl[cnt_seq] = { + &num_sequence::fibonacci, + &num_sequence::square, + }; + + int main() { + + auto ns = num_sequence(); + ns.print(5); // 5 + ns.set_sequence(num_sequence::ns_square); // 调整函数指针以获得多态的效果 + ns.print(5); // 25 + + cout << endl; + system("PAUSE"); + return 0; + } + ``` + +
+ + > [源文件](../code/cpp/面向对象-不通过继承实现多态-动态序列.cpp) + +## 抽象基类 +- 仅仅为了**设计**/**定义规范**而存在 \ No newline at end of file diff --git "a/C-\347\274\226\347\250\213\350\257\255\350\250\200/Cpp-C-\345\267\246\345\200\274\344\270\216\345\217\263\345\200\274.md" "b/C-\347\274\226\347\250\213\350\257\255\350\250\200/Cpp-C-\345\267\246\345\200\274\344\270\216\345\217\263\345\200\274.md" new file mode 100644 index 00000000..d85e298b --- /dev/null +++ "b/C-\347\274\226\347\250\213\350\257\255\350\250\200/Cpp-C-\345\267\246\345\200\274\344\270\216\345\217\263\345\200\274.md" @@ -0,0 +1,584 @@ +C++中的左值与右值 +=== + +说明 +--- +- 这一部分内容只是帮助理解 C++(11) 中左值与右值的概念。 +- 在编程实践中,因为**编译器优化**的存在,特别是其中的**返回值优化**(Return Value Optimization, RVO)使你不需要额外关注左值与右值的区别,像 C++(03) 一样编程即可。 + > [C++11 rvalues and move semantics confusion (return statement)](https://stackoverflow.com/questions/4986673/c11-rvalues-and-move-semantics-confusion-return-statement) - Stack Overflow +- 除非你在进行库的开发,特别是涉及模板元编程等内容时,需要实现[移动构造函数](#移动构造函数)(move constructor),或者[完美转发](#完美转发) + +Index +--- + + +- [小结](#小结) + - [左值引用类型 与 右值引用类型](#左值引用类型-与-右值引用类型) + - [当发生自动类型推断时,`T&&` 也能绑定左值](#当发生自动类型推断时t-也能绑定左值) + - [如何快速判断左值与右值](#如何快速判断左值与右值) + - [引用折叠规则](#引用折叠规则) + - [`move()` 与 `forward()`](#move-与-forward) +- [左值与右值的本质](#左值与右值的本质) + - [左值、消亡值、纯右值](#左值消亡值纯右值) +- [右值引用的特点](#右值引用的特点) + - [右值引用延长了临时对象的生命周期](#右值引用延长了临时对象的生命周期) + - [利用右值引用避免临时对象的拷贝和析构](#利用右值引用避免临时对象的拷贝和析构) + - [右值引用类型绑定的一定是右值,但 `T&&` 可能不是右值引用类型](#右值引用类型绑定的一定是右值但-t-可能不是右值引用类型) + - [当发生自动类型推断时,`T&&` 是未定的引用类型](#当发生自动类型推断时t-是未定的引用类型) +- [常量(左值)引用](#常量左值引用) +- [返回值优化 RVO](#返回值优化-rvo) +- [移动语义](#移动语义) + - [深拷贝带来的问题](#深拷贝带来的问题) + - [移动构造函数](#移动构造函数) + - [移动语义 与 `move()`](#移动语义-与-move) + - [`move()` 的本质](#move-的本质) + - [`move()` 的原型 TODO](#move-的原型-todo) +- [完美转发](#完美转发) + - [`forward()` 实现完美转发](#forwardt-实现完美转发) + - [`forward()`的原型 TODO](#forwardt的原型-todo) +- [Reference](#reference) + + + + +## 小结 +### 左值引用类型 与 右值引用类型 +```Cpp +T t1; // 类型 T +T& t2 = t1; // T& 表示 T 的左值引用类型,t2 是左值引用类型的变量,它引用的是一个左值 +T&& t3 = T(); // T&& 表示 T 的右值引用类型,t3 是右值引用类型的变量,它引用的是一个右值 + +T& t4 = T(); // err: 左值引用 不能绑定一个 右值 +T&& t5 = t1; // err: 右值引用 不能绑定一个 左值 + +const T& t6 = t1; // const T& 表示 T 的常量(左值)引用 +const T& t7 = T(); // 常量引用类型是“万能”的引用类型 + +// 不能把 常量类型 绑定到 非常量引用类型 上 +T&& t8 = t6; // err: 不能把常量类型绑定到 右值引用类型 +T& t9 = t6; // err: 也不能把常量类型绑定到 左值引用类型 +``` +> 这里的变量 t1~t9 都是左值,因为它们都有名字 + +### 当发生自动类型推断时,`T&&` 也能绑定左值 +```Cpp +template // 模板元编程 +void foo(T&& t) { } // 此时 T&& 不是右值引用类型,而是未定引用类型 + +void bar(int&& v) { } // 非模板编程,int&& 是明确的右值引用类型 + +foo(10); // OK: 未定引用类型 t 绑定了一个右值 +bar(10); // OK: 右值引用类型 v 绑定了一个右值 + +int x = 10; +foo(x); // OK: 未定引用类型 t 绑定了一个左值 +bar(x); // err: 右值引用类型 v 不能绑定一个左值 + +int&& p = x; // err +auto&& t = x; // OK +``` +- 此时,`T&&` 就不再是右值引用类型,而是**未定引用类型** + +### 如何快速判断左值与右值 +- **能被 `&` 取地址的就是左值** + ``` + int foo; // foo 是一个左值 + cout << &foo; // 可以被取地址 + foo = foo + 5; // foo + 5 是一个左值 + cout << &(foo+5); // err + cout << &1; // err + ``` + - 多数常数、字符等字面量都是右值,但**字符串是左值** + - 虽然**字符串字面量**是左值;但它是 **const 左值**(只读对象),所以也不能对它赋值 + ```Cpp + cout << &'a'; // err: lvalue required as unary '&' operand + cout << &"abc"; // OK, 可以对字符串取地址 + "abc" = "cba"; // err: assignment of read-only location + ``` + > 为什么字符串字面量是对象?——节省内存,同一份字符串字面量引用的是同一块内存 +- 所有的具名变量或对象都是左值,而匿名变量/临时变量则是右值 + - 匿名变量/临时变量的特点是表达式结束后就销毁了 + ```Cpp + int i = 5; // int 型字面量 + auto f = []{return 5;}; // lambda 表达式 + ``` + +### 引用折叠规则 +1. 所有的右值引用叠加到右值引用上仍然还是一个右值引用。(`T&& && 变成 T&&`) +2. 所有的其他引用类型之间的叠加都将变成左值引用。 (`T& &, T& &&, T&& & 都变成 T&`) +3. 对常量引用规则一致 +- 示例 + ```Cpp + typedef int & lRef; + typedef int && rRef; + + typedef const int & lcRef; + typedef const int && rcRef; + + int main() { + int a = 10; + + // 左值引用 + lRef b = a; // T& + lRef & c = a; // T& & + lRef && d = a; // T& && + rRef & e = a; // T&& & + + // 右值引用 + rRef f = 10; // T&& + rRef && g = 10; // T&& && + + // 左值引用 + lcRef b2 = a; // const T& + lcRef & c2 = a; // const T& & + lcRef && d2 = a; // const T& && + rcRef & e2 = a; // const T&& & + + // 右值引用 + rcRef f2 = 10; // const T&& + rcRef && g2 = 10; // const T&& && + + return 0; + } + ``` + +### `move()` 与 `forward()` +- `move()` 的主要作用是将一个左值转为 xvalue(右值), 其实现本质上是一个 `static_cast` +- `forward()` 主要用于实现完美转发,其作用是将一个类型为(左值/右值)引用的左值,转化为它的类型所对应的值类型(左值/右值) + > 觉得难以理解的话,就继续看下去吧 + + +## 左值与右值的本质 +- 左值表示是“**对象**”(object),右值表示“**值**”(value)——**“对象”内存储着“值”** +- 左值 `->` 右值的转换可看做“读取对象的值”(reading the value of an object) +- 其他说法: + - 左值是可以作为内存单元**地址的值**;右值是可以作为内存单元**内容的值** + - 左值是内存中持续存储数据的一个地址;右值是临时表达式结果 + +### 左值、消亡值、纯右值 +- C++11 开始,表达式一般分为三类:左值(lvalue)、消亡值(xvalue)和纯右值(prvalue); +- 其中左值和消亡值统称**泛左值**(glvalue); + + 消亡值和纯右值统称**右值**(rvalue)。 +
+ + +## 右值引用的特点 +### 右值引用延长了临时对象的生命周期 +``` +int i = getI(); // getI() 会返回一个 int 型的临时变量 +T&& t = getT(); // t 是一个右值引用 + // getT() 同样返回一个临时变量,但是该临时变量被“引用”了 + // 因此生命周期得到了延长 +``` +- `getI()` 和 `getT()` 都返回一个临时变量,但是 `getT()` 产生的临时变量不会在表达式结束后就马上销毁,而是会被“续命”——它的声明周期将和它的**引用类型变量 `t`** 一样长。 + +### 利用右值引用避免临时对象的拷贝和析构 + ```Cpp + int g_constructCount=0; + int g_copyConstructCount=0; + int g_destructCount=0; + + struct A { + A(){ // 基本构造 + cout<<"construct: "<<++g_constructCount< 利用常量引用也能避免临时对象的拷贝与析构 -> [常量(左值)引用](#常量左值引用) + > + > 返回值优化做的更彻底 -> [返回值优化 RVO](#返回值优化-rvo) + +### 右值引用类型绑定的一定是右值,但 `T&&` 可能不是右值引用类型 +```Cpp +int&& v1 = 1; // OK: v1 是右值引用类型,且 1 是右值 +int&& v2 = v1; // err: v2 是右值引用类型,但 v1 是左值 +``` + +### 当发生自动类型推断时,`T&&` 是未定的引用类型 +- `T&& t` 在发生**自动类型推断**时,是未定的引用类型 + - 比如模板元编程,auto 关键字等 + - 如果 `t` 被一个左值初始化,它就是一个左值;如果 `t` 被一个右值初始化,它就是一个右值 + ```Cpp + template // 模板元编程 + void foo(T&& t) { } // 此时 T&& 不是右值引用类型,而是未定引用类型 + + foo(10); // OK: 未定引用类型 t 绑定了一个右值 + + int x = 10; + foo(x); // OK: 未定引用类型 t 绑定了一个左值 + + int&& p = x; // err + auto&& t = x; // OK + ``` +- 仅当发生自动类型推导时(模板编程,auto 关键字),`T&&` 才是未定引用类型 + ```Cpp + void bar(int&& v) { } // 非模板编程,int&& 是明确的右值引用类型 + + bar(10); // OK: 右值引用类型 v 绑定了一个右值 + + int x = 10; + bar(x); // err: 右值引用类型 v 不能绑定一个左值 + ``` + + +## 常量(左值)引用 +- 右值引用是 C++11 引入的概念 +- 在 C++11 前,是如何避免临时对象的拷贝和析构呢?——利用**常量左值引用** + ```Cpp + const A& a = getA(); // OK: 常量左值引用可以接受右值 + ``` +- 常量左值引用是一个“万能”的引用类型,可以接受左值、右值、常量左值和常量右值 +- 普通的左值引用不能接受右值 + ```Cpp + A& a = getA(); // err: 非常量左值引用只能接受左值 + ``` + +## 返回值优化 RVO +- 利用右值引用可以避免临时对象的拷贝可析构 +- 但编译器的返回值优化(Return Value Optimization, RVO)做得“更绝”,直接回避了所有拷贝构造 + ```Cpp + int g_constructCount=0; + int g_copyConstructCount=0; + int g_destructCount=0; + + struct A { + A(){ // 基本构造 + cout<<"construct: "<<++g_constructCount< 所谓指针悬挂,指的是两个对象内部的成员指针变量指向了同一块地址,析构时这块内存会因被删除两次而发生错误 + ```Cpp + class A { + public: + A(): m_ptr(new int(0)) { // new 堆内存 + cout << "construct" << endl; + } + + A(const A& a):m_ptr(new int(*a.m_ptr)) { // 深拷贝构造函数 + cout << "copy construct" << endl; + } + + ~A(){ + // cout << "destruct" << endl; + delete m_ptr; // 析构函数,释放堆内存的资源 + } + private: + int* m_ptr; // 成员指针变量 + }; + + A getA() { + return A(); + } + + int main() { + A a = getA(); + return 0; + } + ``` + - 输出(关闭 RVO) + ```Cpp + construct + copy construct + copy construct + ``` + > 如果不关闭 RVO,只会输出 `construct` +- 提供深拷贝能够保证程序的正确性,但会带来额外的性能损耗——临时对象也会申请一块内存,然后又马上被销毁了;如果堆内存很大的话,这个性能损耗是不可忽略的 +- 对于临时对象而言,深拷贝不是必须的 +- 利用右值引用可以避免无谓的深拷贝——移动拷贝构造函数 + +### 移动构造函数 +- 相比上面的代码,这里只多了一个移动构造函数——一般会同时提供拷贝构造与移动构造 + ```Cpp + class A { + public: + A(): m_ptr(new int(0)) { // new 堆内存 + cout << "construct" << endl; + } + + A(const A& a): m_ptr(new int(*a.m_ptr)) { // 深拷贝构造函数 + cout << "copy construct" << endl; + } + + A(A&& a): m_ptr(a.m_ptr) { // 移动构造函数 + a.m_ptr = nullptr; // 把参数对象的指针指向 nullptr + cout << "move construct" << endl; + } + + ~A(){ + // cout << "destruct" << endl; + delete m_ptr; // 析构函数,释放堆内存的资源 + } + private: + int* m_ptr; // 成员指针变量 + }; + + A getA() { + return A(); + } + + int main() { + A a = getA(); + return 0; + } + ``` +- 输出(关闭返回值优化) + ```Cpp + construct + move construct // 没有调用深拷贝,值调用了移动构造函数 + move construct + ``` + > 如果不关闭 RVO,只会输出 `construct` +- 这里没有自动类型推断,所以 `A&&` 一定是右值引用类型,因此所有**临时对象**(右值)会匹配到这个构造函数,而不会调用深拷贝 +- 对于临时对象而言,没有必要调用深拷贝 +- 这就是所谓的**移动语义**——右值引用的一个重要目的就是为了支持移动语义 + +### 移动语义 与 `move()` +- 移动语义是通过右值引用来匹配临时值,从而避免深拷贝 +- 利用 `move()` 方法,可以将普通的左值转化为右值来达到**避免深拷贝**的目的 + ```Cpp + class A { + public: + A(): m_ptr(new int(0)) { // new 堆内存 + cout << "construct" << endl; + } + + A(const A& a): m_ptr(new int(*a.m_ptr)) { // 深拷贝构造函数 + cout << "copy construct" << endl; + } + + A(A&& a): m_ptr(a.m_ptr) { // 移动构造函数 + //a.m_ptr = nullptr; // 为了实验,这里没有把参数对象的指针指向 nullptr + cout << "move construct" << endl; + } + + ~A(){ + // cout << "destruct" << endl; + delete m_ptr; // 析构函数,释放堆内存的资源 + } + + int get_data() { + return *m_ptr; + } + + void set_data(int v) { + *m_ptr = v; + } + private: + int* m_ptr; // 成员指针变量 + }; + + int main() { + A a1; // construct + a1.set_data(1); + cout << a1.get_data() << endl; // 1 + + A a2 = a1; // copy construct + cout << a2.get_data() << endl; // 1 + a2.set_data(2); + cout << a2.get_data() << endl; // 2 + cout << a1.get_data() << endl; // 1 + + A a3 = move(a1); // move construct + a3.set_data(3); + cout << a3.get_data() << endl; // 3 + cout << a1.get_data() << endl; // 3: 因为没有深拷贝,指向的是同一块地址 + return 0; + } + ``` + - 运行结果 + ```Cpp + construct + 1 + copy construct + 1 + 2 + 1 + move construct + 3 + 3 + ``` +- STL 容器的移动语义 + ```Cpp + { + list tokens; + //省略初始化... + list t = tokens; // 这里存在深拷贝 + } + list tokens; + list t = move(tokens); // 这里没有深拷贝 + ``` + - C++11 中所有的容器都实现了移动语义 + +### `move()` 的本质 +- `move()` 实际上并没有移动任何东西,它唯一的功能是将一个左值**强制转换**为一个右值引用 +- 如果没有对应的移动构造函数,那么使用 `move()` 仍会发生深拷贝,比如基本类型,定长数组等 +- 因此,`move()` 对于含有资源(堆内存或句柄)的对象来说更有意义。 + +### `move()` 的原型 TODO +> [c++11 中的 move 与 forward - twoon](https://www.cnblogs.com/catch/p/3507883.html) - 博客园 + +## 完美转发 +- 右值引用的引入,使函数可以根据值的类型(左值或右值)进行不同的处理 +- 于是又引入了一个问题——如何正确的传递参数,保持参数作为左值或右值的特性 +- 转发失败的例子: + ```Cpp + void processValue(int& a) { cout << "lvalue" << endl; } + void processValue(int&& a) { cout << "rvalue" << endl; } + + template + void forwardValue(T&& val) { + processValue(val); // 因为 val 本身是一个左值 + // 所以无论 val 是左值引用类型还是右值引用类型的变量 + // 都只会调用 processValue(int& a) + } + + int main() { + int i = 1; + forwardValue(i); // 传入一个左值 + // val 会被推断为是一个左值引用类型 + + forwardValue(1); // 传入一个右值 + // 虽然 val 会被推断为是一个右值引用类型,但它本身是一个左值 + return 0; + } + ``` + - 输出 + ```Cpp + lvalue + lvalue + ``` + - 无论传入的是左值还是右值,val 都是一个左值 + +### `forward()` 实现完美转发 +> 这里写的不够详细,有时间在整理 +- 在函数模板中,`T&&` 实际上是未定引用类型,它是可以得知传入的对象是左值还是右值的 +- 这个特性使其可以成为一个参数的路由,利用 `forward()` 实现完美转发 +- `std::forward()` 可以保留表达式作为“对象”(左值)或“值”(右值)的特性 + +- 利用 `std::forward()` 实现完美转发: + > 不可以用变量接收 `forward()` 的返回值,因为所有具名变量都是左值 + ```Cpp + void processValue(int& a) { cout << "lvalue" << endl; } + void processValue(int&& a) { cout << "rvalue" << endl; } + + template + void forwardValue(T&& val) { + processValue(forward(val)); // 利用 forward 保持对象的左右值特性 + + // 必须把 forward(val) 打包作为参数,否则都达不到完美转发的目的 + // auto v = forward(val); + // processValue(v); + + // auto&& v = forward(val); + // processValue(v); + } + + int main() { + int i = 1; + forwardValue(i); // 传入一个左值 + + forwardValue(1); // 传入一个右值 + return 0; + } + ``` + - 输出 + ```Cpp + lvalue + rvalue + ``` + - 正确实现了转发 + +### `forward()`的原型 TODO + +## Reference +- [The lvalue/rvalue metaphor](https://josephmansfield.uk/articles/lvalue-rvalue-metaphor.html) — Joseph Mansfield +- [关于C++左值和右值区别有没有什么简单明了的规则可以一眼辨别?](https://www.zhihu.com/question/39846131/answer/85277628) - 知乎 +- [从4行代码看右值引用 - qicosmos(江南)](https://www.cnblogs.com/qicosmos/p/4283455.html) - 博客园 +- [c++11 中的 move 与 forward - twoon](https://www.cnblogs.com/catch/p/3507883.html) - 博客园 +- [左值右值的一点总结 - twoon](http://www.cnblogs.com/catch/p/5019402.html) - 博客园 \ No newline at end of file diff --git "a/C-\347\274\226\347\250\213\350\257\255\350\250\200/Python-A-\345\237\272\347\241\200.md" "b/C-\347\274\226\347\250\213\350\257\255\350\250\200/Python-A-\345\237\272\347\241\200.md" new file mode 100644 index 00000000..43fd9ce8 --- /dev/null +++ "b/C-\347\274\226\347\250\213\350\257\255\350\250\200/Python-A-\345\237\272\347\241\200.md" @@ -0,0 +1,26 @@ +Python-基础知识 +=== + +Index +--- + + + +- [Python 中的垃圾回收机制](#python-中的垃圾回收机制) +- [字符串格式化输出](#字符串格式化输出) + - [旧式(%)](#旧式%) + - [新式(`.format()`)](#新式format) + + + +## Python 中的垃圾回收机制 +> [Python垃圾回收机制详解 - Xjng](http://www.cnblogs.com/Xjng/p/5128269.html) - 博客园 + + +## 字符串格式化输出 + +### 旧式(%) + + +### 新式(`.format()`) + diff --git "a/C-\347\274\226\347\250\213\350\257\255\350\250\200/README.md" "b/C-\347\274\226\347\250\213\350\257\255\350\250\200/README.md" new file mode 100644 index 00000000..495fd4a2 --- /dev/null +++ "b/C-\347\274\226\347\250\213\350\257\255\350\250\200/README.md" @@ -0,0 +1,11 @@ +编程语言 +=== +- 以 C++ 和 Python 为主 + +**Index** +--- +- **C/C++** + - [左值与右值](./Cpp-C-左值与右值) + +## Reference +- - huihut/[interview: C/C++面试知识总结](https://github.com/huihut/interview) \ No newline at end of file diff --git "a/D-\347\254\224\350\257\225\351\235\242\347\273\217/README.md" "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/README.md" new file mode 100644 index 00000000..229b3560 --- /dev/null +++ "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/README.md" @@ -0,0 +1,262 @@ +笔试面经 +=== + +**回答一个面试问题的基本要点** +- 是什么、 +- 为什么(动机)、 +- 怎么做(原理)、 +- 使用场景、 +- 一些细节(如果使用过的话) + +Reference +--- +- [BAT机器学习面试1000题系列(第1~305题)](https://blog.csdn.net/v_JULY_v/article/details/78121924) - CSDN博客 + +笔试 +--- +- [字节跳动 180812](./笔试-字节跳动-180812.md) + +Index +--- + + +- [头条/字节跳动-深度学习/NLP 方向](#头条字节跳动-深度学习nlp-方向) + - [一面](#一面) + - [二面](#二面) + - [三面](#三面) + - [四面(非加面)](#四面非加面) +- [今日头条-算法工程师-实习](#今日头条-算法工程师-实习) + - [一面](#一面-1) + - [二面](#二面-1) + - [三面](#三面-1) +- [2019 美团 AI - NLP 提前批](#2019-美团-ai---nlp-提前批) + - [一面(NLP平台)](#一面nlp平台) + - [一面(广告平台)](#一面广告平台) + - [二面(广告)](#二面广告) +- [拼多多 180722 笔试](#拼多多-180722-笔试) + - [1. 数组中的最长山谷](#1-数组中的最长山谷) + - [2. 字符串构造](#2-字符串构造) + - [3. 到达指定位置](#3-到达指定位置) + - [4. 靓号](#4-靓号) + + + +## 头条/字节跳动-深度学习/NLP 方向 + +### 一面 +- 自我介绍 +- 聊项目 +- 深度学习基本问题 +- 【算法】手写 K-Means + > 磕磕绊绊算是写出来一个框架,内部细节全是问题,面试官比较宽容,勉强算过了 + +### 二面 +- 自我介绍 +- 聊项目 +- 深度学习基本问题 +- 【算法】找数组中前 k 大的数字 + > 我说了两个思路:最小堆和快排中的 partition 方法;让我选一个实现,我选的堆方法,然后又让我实现调整堆的方法 + +### 三面 +- 自我介绍 +- 为什么会出现梯度消失和梯度爆炸 + - 分别说了下前馈网络和 RNN 出现梯度消失的情况 +- 有哪些解决方法 + - 因为提到了残差和门机制,所以又问 + - 分别说下它们为什么能缓解梯度消失 + - 因为说残差的时候提到了 ResNet,让我介绍下 ResNet(没用过,随便说了几句) +- 其他加速网络收敛的方法(除了残差和门机制) + - 我从优化方法的角度说了一点(SGB 的改进:动量方法、Adam) + - 提示我 BN,然后我就把 BN 的做法说了一下 + - 然后问 BN 为什么能加速网络的收敛(从数据分布的角度随便说了几句) +- 传统的机器学习方法(简历上写用过 GBDT) + - 简单介绍下 XGBoost + - CART 树怎么选择切分点(基尼系数) + - 基尼系数的动机、原理(不会) +- 【算法】直方图蓄水问题 + > LeetCode [42. 接雨水](https://leetcode-cn.com/problems/trapping-rain-water/description/); + >> 当时太紧张没想出 `O(N)` 解法,面试一结束就想出来了,哎 + - 附 AC 代码 + ```C++ + class Solution { + public: + int trap(vector& H) { + int n = H.size(); + + vector dp_fw(H); + vector dp_bw(H); + + for(int i=1; i=0; i--) // 记录每个位置右边的最高点 + dp_bw[i] = max(dp_bw[i+1], dp_bw[i]); + + int ret = 0; + for (int i=1; i 因为流程出了问题,其实还是三面 +- 【算法】和为 K 的连续子数组,返回首尾位置 + > LeetCode [560. 和为K的子数组](https://leetcode-cn.com/problems/subarray-sum-equals-k/description/) + >> 很熟悉的题,但就是没想出来;然后面试官降低了难度,数组改成有序且为正整数,用双指针勉强写了出来;但是边界判断有问题,被指了出来;然后又问无序的情况或者有负数的情况能不能也用双指针做,尬聊了几分钟,没说出个所以然。 +- 如何无监督的学习句子表示 + - 我说 Self-Attention + - 让我把公式写出来,因为写的不清楚,让我写原始的 Attention + - 然后问怎么训练,损失函数是什么(没说出来,除了词向量我基本没碰过无监督任务,而且我认为词向量也算不上无监督...) +- 如何无监督的学习一个短视频的特征表示 + - 抽取关键帧,然后通过 ResNet 等模型对每一帧转化为特征表示,然后对各帧的特征向量做拼接或者直接保存为二维特征(瞎说的,别说视频,我连图像都没做过) + +## 今日头条-算法工程师-实习 +> [6.14今日头条算法工程师实习生](https://www.nowcoder.com/discuss/84462?type=2&order=0&pos=11&page=1)_笔经面经_牛客网 + +### 一面 +1. 自我介绍; +1. 二分查找; + > Algorithm_for_Interview/常用子函数/[二分查找模板.hpp](https://github.com/imhuay/Algorithm_for_Interview-Chinese/blob/master/Algorithm_for_Interview/_utils工具函数/二分查找模板.hpp) +1. 判断链表是否有环; + > Algorithm_for_Interview/链表/[链表中环的入口结点.hpp](https://github.com/imhuay/Algorithm_for_Interview-Chinese/blob/master/Algorithm_for_Interview/链表/链表中环的入口结点.hpp) +1. 将数组元素划分成两部分,使两部分和的差最小,数组顺序可变; + > Algorithm_for_Interview/查找与排序/[暴力搜索_划分数组使和之差最小.hpp](https://github.com/imhuay/Algorithm_for_Interview-Chinese/blob/master/Algorithm_for_Interview/查找与排序/暴力搜索_划分数组使和之差最小.hpp) +1. 智力题,在一个圆环上随机添加3个点,三个点组成一个锐角三角形的概率; + > ../数学问题/[#1](../数学/README.md#1-在圆环上随机选取-3-个点这-3-个点组成锐角三角形的概率) +1. 推导逻辑斯蒂回归、线性支持向量机算法; + > ../机器学习/[逻辑斯蒂回归推导](../机器学习/README.md#逻辑斯蒂回归推导) + > + > ../机器学习/[线性支持向量机推导](../机器学习/README.md#线性支持向量机推导) + +### 二面 +1. 在一个圆环上随机添加3点,三个点组成一个锐角三角形的概率; +1. 用积分计算上述概率; +1. 用程序解决上述问题 + > 多次采样求概率,关键是如何判断采样的三个点能否构成锐角三角形,不同的抽象会带来不同的复杂度。 + + > 最直接的想法是,根据边长关系,此时需要采样三个 x 坐标值,相应的 y 坐标通过计算得出,然后计算三边长度,再判断,循环以上过程,计算形成锐角的比例。 + + > 更简单的,根据 [../数学/#1](../数学/README.md#1-在圆环上随机选取-3-个点这-3-个点组成锐角三角形的概率) 中提到的简单思路,原问题可以等价于“抛两次硬币,求两次均为正面的概率”——此时,只需要采样两个`(0, 1)`之间的值,当两个值都小于 0.5 意味着能构成锐角三角形。 + +1. 深度学习,推导反向传播算法,知道什么激活函数,不用激活函数会怎么样,ROC与precesion/recall评估模型的手段有何区别,什么情况下应该用哪一种?深度学习如何参数初始化? + > ../深度学习/[反向传播算法](../深度学习/README.md#反向传播算法) + > + > ../深度学习/[激活函数](../深度学习/README.md#激活函数) + > + > ../深度学习/[参数初始化](../深度学习/README.md#参数初始化) +1. 介绍kaggle项目,titanic,用到了哪些框架,用到了哪些算法; + +### 三面 +1. 自我介绍; +1. 分层遍历二叉树,相邻层的遍历方向相反,如第一层从左到右遍历,下一层从右向左遍历; +1. 介绍AdaBoost算法; +1. 介绍梯度下降,随机梯度下降 + > ../深度学习/[梯度下降法](../深度学习/README.md#2-梯度下降法随机梯度下降) +1. 写出逻辑斯蒂回归的损失函数; +1. C++,虚函数,虚析构函数。 + + +## 2019 美团 AI - NLP 提前批 +> [2019美团AI算法提前批面试经验](https://www.nowcoder.com/discuss/85852?type=2&order=0&pos=7&page=1)_笔经面经_牛客网 + +### 一面(NLP平台) +**论文/项目相关** +- 意图识别数据怎么标注 +- 怎么样做实体抽取 +- 怎样进行 aspect-level 情感分析 +- 模型中增强学习的 reward 如何设计的;为什么这样设计 + +### 一面(广告平台) +**论文/项目相关** +- seq2seq 中 scheduled sampling 如何做的 +- RL部分训练过程中数据集如何构造 +- 如何防止过拟合,你都采用了哪些方法,还有哪些你没有用到的方法 + > 深度学习/[正则化](../深度学习/README.md#正则化) +- 【**编程题**】给定整数n,求离根号n最近的整数。 + > [二分查找最优解模板](https://github.com/imhuay/Algorithm_for_Interview-Chinese/blob/master/Algorithm_for_Interview/_必背算法/二分查找最优解模板.hpp) + +### 二面(广告) +**论文/项目相关** +- RL + Seq2seq相关问题 + - Seq2seq怎样和RL结合,这里的action与state都是什么 + - 如何设计reward,为什么选取这样的reward + - 具体训练流程是怎样的 + +**深度学习相关** +- BiLSTM 相比 LSTM有哪些 case 上的提升 +- Attention 是如何加的取得了哪些效果的提升 +- 能介绍几个传统的机器学习模型吗,列举了:决策树,SVM, RF等 + - 具体说明一下决策树如何划分,写出相应的公式 + - 具体解释一下RF +- 【**编程题**】类似求一个旋转数组的拐点位置 + > 二分查找;[153. 寻找旋转排序数组中的最小值](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/description/) - LeetCode + +## 拼多多 180722 笔试 +共 4 道编程题 +### 1. 数组中的最长山谷 +> 问题描述:LeetCode 845. [数组中的最长山脉](https://leetcode-cn.com/problems/longest-mountain-in-array/description/) +- 原题是找山脉,这里改成了山谷 + ``` + 示例: + 输入: + [4,3,2,5,3,1,4,8] + 输出: + 5 + 说明: + [5,3,1,4,8] + ``` +- **“坑”点说明** + - 输入就是字符串 "[4,3,2,5,3,1,4,8]" 包括括号和标点 + - 问题是,直接返回 0 也有 20% 的正确率,导致我一直没想到是输入上的问题,直到最后都卡在 20% +- 建议所有需要处理字符串的问题,都使用 Python,这里只要 `A = eval(input())` 就完事了;而 C++ 如果不熟悉 STL 的话,处理输入都比题目本身难了 +- **思路**:暴力枚举;看代码更直观 +- C++ 代码 [[code]](https://github.com/imhuay/Algorithm_for_Interview-Chinese/blob/master/Algorithm_for_Interview/_笔试/拼多多180722/1.数组中的最长山谷.hpp)(没做输入处理) + +### 2. 字符串构造 +- 问题描述 + ``` + 一个长串由一个字串循环构成,即 s[i]=t[i%n],比如 "abcabc" 由 "abc" 构成 + + 注意:"abcabcab" 也是由 "abc" 构成的,答题时没注意这个又只过了一部分 + + *建议使用 Python 解决字符串相关问题 + ``` +- **思路**:暴力枚举前缀 +- Python 代码 [[code]](https://github.com/imhuay/Algorithm_for_Interview-Chinese/blob/master/Algorithm_for_Interview/_笔试/拼多多180722/2.字符串构造.py) + +### 3. 到达指定位置 +- 题目描述:Leetcode 754. [到达终点数字](https://leetcode-cn.com/problems/reach-a-number/description/) +- 数学题 +- 思路:[一道乐视网的面试题,求解答?](https://www.zhihu.com/question/50790221/answer/125213696) - 知乎 +- C++ 代码 [[code]](https://github.com/imhuay/Algorithm_for_Interview-Chinese/blob/master/Algorithm_for_Interview/_笔试/拼多多180722/3.到达指定位置.hpp) + +### 4. 靓号 +- 问题描述 + ``` + A 国的手机号码由且仅由 N 位十进制数(0-9)组成,可以有前导 0,比如 000123456。一个手机号码中至少有 K 位数相同则被定义为靓号(不要求连续)。 + 如果想把自己的手机号修改为一个靓号,修改一个数字的金额为新数字与旧数字之间的差的绝对值,比如 1 修改为 6 或 6 修改为 1 都要花 5 块钱。 + 求对给定手机号,修改为靓号最少要花的钱数以及新的号码(如果有多个,输出字典序最小的)。 + + 输入: + 第一行包含两个整数 N 和 K,分别表示 手机号码的位数和靓号要求的位数 + 第二行为 N 个数字,数字之间没有空白符 + + 数据范围 2 <= K <= N <= 10000 + + 示例: + 输入: + 6 5 + 785785 + 输出: + 4 + 777577 + 说明: 777577 比 777775 字典序小 + ``` +- **思路**: + + 统计每个数字出现次数counter,以每个数字为基准,按照与基准差值对counter排序,优先替换差值小的数字;关于字典序的问题,如果替换的数比基准大则从前向后替换,如果替换的数比基准大,则从后向前替换,得到的就是字典序最小的字符串,时间复杂度O(n) + > [拼多多算法岗笔试python解决方案](https://www.nowcoder.com/discuss/87694)_笔经面经_牛客网 +- TODO 目前还没看到完全 AC 的代码 diff --git "a/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-360-180827.md" "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-360-180827.md" new file mode 100644 index 00000000..80d9a4f7 --- /dev/null +++ "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-360-180827.md" @@ -0,0 +1,181 @@ +笔试-360-180827 +=== +- 40 道选择题,范围包括大学所有课程... +- 3 道编程题 + +Index +--- + + +- [城市修建](#城市修建) +- [看花](#看花) +- [Array(选做)](#array选做) + + + +## 城市修建 +**题目描述** +``` +有一个城市需要修建,给你N个民居的坐标X,Y, +问把这么多民居全都包进城市的话,城市所需最小面积是多少 +(注意,城市为平行于坐标轴的正方形) + +输入 + 第一行为N,表示民居数目(2≤N≤1000) + 下面为N行,每行两个数字Xi,Yi,表示该居民的坐标(-1e9≤xi,yi≤1e9) +输出 + 城市所需最小面积 + +样例输入 +2 +0 0 +2 2 +样例输出 +4 +``` + +**思路** +- 找出最大长/宽即可 + +**Python**(AC) +```python +n = int(input()) + +xs = [] +ys = [] +for i in range(n): + x, y = list(map(int, input().split())) + xs.append(x) + ys.append(y) + +min_x = min(xs) +max_x = max(xs) +xx = max_x - min_x + +min_y = min(ys) +max_y = max(ys) +yy = max_y - min_y + +tt = max(xx, yy) +print(tt ** 2) +``` + +## 看花 +**题目描述** +``` +小明有一个花园,花园里面一共有m朵花,对于每一朵花,都是不一样的,小明用1~m中的一个整数表示每一朵花。 +他很喜欢去看这些花,有一天他看了n次,并将n次他看花的种类是什么按照时间顺序记录下来。 +记录用a[i]表示,表示第i次他看了a[i]这朵花。 +小红很好奇,她有Q个问题,问[l,r]的时间内,小明一共看了多少朵不同的花儿, +小明因为在忙着欣赏他的花儿,所以想请你帮他回答这些问题。 + +输入 + 输入两个数n,m;(1<=n<=2000,1<=m<=100);分别表示n次看花,m表示一共有m朵花儿。 + 接下来输入n个数a[1]~a[n],a[i]表示第i次,小明看的花的种类; + 输入一个数Q(1<=Q<=1000000);表示小红的问题数量。 + 输入Q行 每行两个数 l,r(1<=l<=r<=n); 表示小红想知道在第l次到第r次,小明一共看了多少不同的花儿。 +输出 + 一共Q行 + 每一行输出一个数 表示小明在[l,r]的时间内看了多少种花。 + +样例输入 +5 3 +1 2 3 2 2 +3 +1 4 +2 4 +1 5 +样例输出 +3 +2 +3 +``` + +**思路** +- ~~打表~~ + +**Python**(29%) +```python +n, m = list(map(int, input().split())) + +ns = list(map(int, input().split())) + +Q = int(input()) +lrs = [] +for i in range(Q): + l, r = list(map(int, input().split())) + print(len(set(ns[l - 1: r - 1]))) +``` + +**C++**(57%) +> Java 相同做法可 AC +```C++ +#include +using namespace std; + +int main() { + + int n, m; + cin >> n >> m; + vector ns(n+1, 0); + for (int i=1; i<=n; i++) + cin >> ns[i]; + + int Q; + cin >> Q; + set tmp; + for (int i=0; i> l >> r; + for (int k=l; k<=r; k++) + tmp.insert(ns[k]); + cout << tmp.size() << endl; + tmp.clear(); + } + + return 0; +} +``` + + +## Array(选做) +**题目描述** +``` +小红有两个长度为n的排列A和B。每个排列由[1,n]数组成,且里面的数字都是不同的。 +现在要找到一个新的序列C,要求这个新序列中任意两个位置(i,j)满足: +如果在A数组中C[i]这个数在C[j]的后面,那么在B数组中需要C[i]这个数在C[j]的前面。 +请问C序列的长度最长为多少呢? + +输入 + 第一行一个整数,表示N。 + 第二行N个整数,表示A序列。 + 第三行N个整数,表示B序列。 + 满足:N<=50000 +输出 + 输出最大的长度 + +样例输入 +5 +1 2 4 3 5 +5 2 3 4 1 +样例输出 +2(正确答案好像应该为 4) +``` + +**思路** +- 最长上升子序列(?) +- 最长公共子序列:将第二个序列逆转后,相当于求两个序列的最长公共子序列 + ``` + 1 2 4 3 5 + 1 4 3 2 5 <- 5 2 3 4 1 + + 最长公共子序列 {1 4 3 5} + ``` + +**Python**(18%) +``` +print(0) # 0% +print(2) # 18% +print(3) # 0% +print(4) # 9% +``` \ No newline at end of file diff --git "a/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-iHandy-180927.md" "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-iHandy-180927.md" new file mode 100644 index 00000000..e5036e04 --- /dev/null +++ "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-iHandy-180927.md" @@ -0,0 +1,80 @@ +笔试-iHandy-180927 +=== +- 单项选择题 8 道,不定项选择题 5 道,问答题 2 道,编程题 1 道 + +Index +--- + + +- [【问答】射击(概率题)](#问答射击概率题) +- [【编程】比大更大](#编程比大更大) + + + +## 【问答】射击(概率题) + +**问题描述** +``` +假设有一支手枪,每次扣动扳机,有 50% 的概率发射子弹; +现甲和乙轮流使用该手枪想对方射击,直到其中一方中弹; +问甲先射击,乙先中弹的概率? +``` + + +## 【编程】比大更大 +> 剑指 Offer:[把数组排成最小的数](https://www.nowcoder.com/practice/8fecd3f8ba334add803bf2a06af1b993?tpId=13&tqId=11185&tPage=2&rp=2&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) +
+
+ +**贪心**(80%) +```python +n = int(input()) + +s = [] +for _ in range(n): + s.append(input()) + +s.sort(reverse=True) +#print(s) + +ans = ''.join(s) + +print(int(ans)) # 转 int +``` + +**自定义排序**(80%) +- 代码应该没什么问题,最后把结果转成 `int/long long` 应该就能 AC 了——可能有一个用例是全 0 +- 上面用贪心的时候还记得转 int,这里就忘了... +```C++ +#include +#include +#include +#include +#include + +using namespace std; + +string foo(vector ns) { + sort(ns.begin(), ns.end(), [](const string &l, const string &r){ + return r + l < l + r; + }); + + stringstream ss; + for (auto i : ns) + ss << i; + + return ss.str(); +} + +int main() { + + int n; + cin >> n; + vector ns(n); + for (int i=0; i < n; i++) + cin >> ns[i]; + + cout << foo(ns); // 这里把结果转成整型应该就行了 + return 0; +} +``` \ No newline at end of file diff --git "a/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\344\275\234\344\270\232\345\270\256-180925.md" "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\344\275\234\344\270\232\345\270\256-180925.md" new file mode 100644 index 00000000..4b618d5e --- /dev/null +++ "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\344\275\234\344\270\232\345\270\256-180925.md" @@ -0,0 +1,95 @@ +笔试-作业帮-180925 +=== +- 单选 5,填空 5,编程 2,问答 2 +- 编程不允许跳出页面 + +Index +--- + + +- [1. 点对距离](#1-点对距离) +- [2. 扩展型表达式求值](#2-扩展型表达式求值) + + + + +## 1. 点对距离 + +
+
+ +**Python**(60%) +- 试了各种情况,不知道哪里有问题 +```python +from math import ceil, floor + +def foo(ax, ay, bx, by): + d = ((ax - bx)**2 + (ay - by)**2)**0.5 + #return round(d, 5) + return d + +k = int(input()) +n = int(input()) + +P = [] +for _ in range(n): + x, y = list(map(float, input().split())) + P.append([x, y]) + +D = [] +for i in range(n): + for j in range(i+1, n): + d = foo(P[i][0], P[i][1], P[j][0], P[j][1]) + D.append(d) + +D.sort() +#print(D) +N = len(D) - 1 +#print(N) +#print(ceil(N * (k / 100))) +ans = D[ceil(N * (k / 100))] +#print("%0.5f" % ans) +print(round(ans, 5)) +#print(ans) +``` + +## 2. 扩展型表达式求值 + +
+
+
+ +**Python**(57.14%) +- 不能跳出页面,实在不想写这种题,偷鸡过了 57% +```python +def bar(s): + while "%" in s: + i = s.find("%") + j = i - 1 + while s[j].isdigit(): + j -= 1 + + s = s[:j+1] + str(int(s[j+1: i]) / 100) + s[i+1:] + return s + +def foo(s): + if "**" in s or "++" in s or "--" in s: + return "error" + + s = bar(s) + + try: + ans = eval(s) + if isinstance(ans, int): + return "%d" % ans + else: + return "%0.3f" % ans + #return round(ans, 3) + except e: + return "error" + +s = input() +ans = foo(s) +#print(bar(s)) +print(ans) +``` \ No newline at end of file diff --git "a/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\345\255\227\350\212\202\350\267\263\345\212\250-180812.md" "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\345\255\227\350\212\202\350\267\263\345\212\250-180812.md" new file mode 100644 index 00000000..9bf430e5 --- /dev/null +++ "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\345\255\227\350\212\202\350\267\263\345\212\250-180812.md" @@ -0,0 +1,206 @@ +笔试-字节跳动-180812 +=== +共 5 道编程题 + +Reference +--- +- [官方题解](https://m.toutiao.com/i6589920344075665928/?wxshare_count=2&pbid=6587230296797464068)(只有思路) + +Index +--- + + +- [1. 世界杯开幕式](#1-世界杯开幕式) +- [2. 文章病句标识](#2-文章病句标识) +- [3. 积分卡牌游戏](#3-积分卡牌游戏) +- [4. 区间最大最小值 TODO](#4-区间最大最小值-todo) +- [5. 直播爱好者](#5-直播爱好者) + + + +## 1. 世界杯开幕式 +
+
+ +**思路** +- dfs 搜索联通区域 +- 原题只要搜索 4 个方向,这里改为搜索 8 个方向 + +**Code(Python)** +```Python +M, N = list(map(int, input().split(','))) + +book = [] +for i in range(M): + line = list(map(int, input().split(','))) + book.append(line) + + +class Solution: + def __init__(self, pos): + self.pos = pos + self.cnt = 0 # 记录当前区域的人数 + self.dp = [] # 保存所有区域的人数,返回其长度,及其中的最大值 + + def dfs(self, i, j): + if 0 <= i < M and 0 <= j < N: + if self.pos[i][j] == 1: + self.cnt += 1 + self.pos[i][j] = 0 # 遍历过的点就置 0,避免重复搜索 + # 八个方向搜索 + self.dfs(i - 1, j) + self.dfs(i + 1, j) + self.dfs(i, j - 1) + self.dfs(i, j + 1) + self.dfs(i - 1, j - 1) + self.dfs(i + 1, j + 1) + self.dfs(i + 1, j - 1) + self.dfs(i - 1, j + 1) + + def solve(self): + for i in range(M): + for j in range(N): + if self.pos[i][j] == 1: + self.cnt = 0 # 每新找到一个区域就清零人数,重新计数 + self.dfs(i, j) # 深度优先搜索每个点 + if self.cnt > 0: + self.dp.append(self.cnt) + return len(self.dp), max(self.dp) + + +s = Solution(book) +P, Q = s.solve() +print(str(P) + ',' + str(Q)) +``` + +## 2. 文章病句标识 +
+
+ +**思路** +- 区间合并 +- 排序 + 贪心 + ``` + 对 [l1,r1], [l2,r2],如果 r1 > l2,则 r1 = max(r1, r2) + ``` + +**Code(Python)** +```Python +# 输入处理 +m = int(input()) + +tmp = [] +for _ in range(m): + line = [list(map(int, item.split(','))) for item in input().split(';')] + tmp.extend(line) # 将所有病句存在一起 + +# 排序,按每段病句 [l, r] 的第一个位置 l 排序 +tmp = sorted(tmp, key=lambda x: x[0]) + +ret = [tmp[0]] +for item in tmp[1:]: + if ret[-1][1] >= item[0]: # 贪心:对 [l1,r1], [l2,r2],如果 r1 > l2,则 r1 = max(r1, r2) + ret[-1][1] = max(ret[-1][1], item[1]) + else: + ret.append(item) + +# 输出处理 +s = '' +for item in ret[:-1]: + s += str(item[0]) + ',' + str(item[1]) + ';' +s += str(ret[-1][0]) + ',' + str(ret[-1][1]) +print(s) +``` + +## 3. 积分卡牌游戏 +
+
+ +**思路** +- 动态规划 +- **DP 定义**:`d[i][j]​ := 前 i 张牌,两人所选择的牌的差值为 j 时的最大值` +- **转移方程** + ``` + ​d[i][j] = max(d[i-1][j], d[i-1][j-x[i]] + y[i], d[i-1][j+x[i]] + y[i])​ + ``` + +**Code**(90%) +```Python +# 输入处理 +n = int(input()) +x, y = [], [] +for i in range(n): + _x, _y = list(map(int, input().split())) + x.append(_x) + y.append(_y) + +xy = list(zip(x, y)) +xy = sorted(xy, key=lambda t: t[1]) + +ret = 0 +if sum(x) % 2 == 0: # 如果所有 x 的和为偶数 + print(sum(y)) # 直接输出所有 y 的和 +else: + for i in range(len(xy)): + if xy[i][0] % 2 == 1: # 去掉 x 中为奇数的那一项 + ret = sum([xy[j][1] for j in range(len(xy)) if j != i]) + print(ret) + break +``` +- 这段代码能过 90% 真是运气 + +## 4. 区间最大最小值 TODO +
+
+ +**思路** +- max(a[l,r]) +
+
+ +**思路** +- 贪心选择结束时间最早的直播 + +**Code**: 未测试 +```C++ +#include +using namespace std; + +int main() { + int n, m; + cin >> n >> m; + + vector> book; + for(int i=0; i r) // 坑点:可能存在第二天的情况 + r += m; + book.push_back({r, l}); // 把结束时间存在首位,排序时避免重新定义比较方法 + } + + sort(book.begin(), book.end()); // 按结束时间排序 + + int ret = 0; + int r = 0; // 保存当前结束时间 + for (int i=0; i m) // 只能在当天看完 + continue; + if (r < book[i].second) { // 如果当前直播在上一个直播结束之后开始 + ret += 1; + r = book[i].first; // 更新结束时间 + } + } + + cout << ret << endl; + return 0; +} +``` +> 《挑战程序设计(第二版)》 2.2.2 区间问题 + diff --git "a/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\345\260\217\347\261\263-180920.md" "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\345\260\217\347\261\263-180920.md" new file mode 100644 index 00000000..0b528e99 --- /dev/null +++ "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\345\260\217\347\261\263-180920.md" @@ -0,0 +1,126 @@ +笔试-小米-180920 +=== +- 单选 10,多选 10,编程 2 + +Reference +--- +- **小米大礼包**:https://blog.csdn.net/amusi1994/article/details/82793376 +- **最优分割**:https://blog.csdn.net/amusi1994/article/details/82793562 + > [小米算法编程题_笔经面经](https://www.nowcoder.com/discuss/114667?toCommentId=1934155)_牛客网 + >> 作者:[阿木寺-CSDN](https://blog.csdn.net/amusi1994) + +Index +--- + + +- [1. 小米大礼包](#1-小米大礼包) +- [2. 最优分割](#2-最优分割) + + + +## 1. 小米大礼包 +
+ +**思路** +- DFS + +**C++**(67%,TLE) +```C++ +#include +int n; +int p[210]; +int m; + +bool dfs(int i, int sum) { + if (i == n) return sum == m; + if (dfs(i + 1, sum + p[i])) return true; + if (dfs(i + 1, sum)) return true; + return false; +} + +int main() { + + scanf("%d", &n); + for (int i = 0; i < n; ++i) + scanf("%d", &p[i]); + scanf("%d", &m); + + if (dfs(0, 0)) + printf("1"); + else + printf("0"); + + return 0; +} +``` + +**加入剪枝**(未测试) +> [【小米2018-09-20在线笔试】小米大礼包](https://blog.csdn.net/amusi1994/article/details/82793376) - CSDN博客 +```C++ +#include +int n; +int p[210]; +int m; + +bool dfs(int i, int sum) { + if (sum > m) return false; // 剪枝 + if (i == n) return sum == m; + if (dfs(i + 1, sum + p[i])) return true; + if (dfs(i + 1, sum)) return true; + return false; +} + +int main() { + + scanf("%d", &n); + for (int i = 0; i < n; ++i) + scanf("%d", &p[i]); + scanf("%d", &m); + + if (dfs(0, 0)) + printf("1"); + else + printf("0"); + + return 0; +} +``` + + +## 2. 最优分割 +
+ +**思路** +- 二分查找、动态规划 +- LeetCode [410. 分割数组的最大值](https://leetcode-cn.com/problems/split-array-largest-sum/description/) + +**低保**(18%) +```python +n, m = list(map(int, input().split())) + +A = list(map(int, input().split())) + +if sum(A) % m == 0: + print(sum(A) // m) +``` + +**Python**(未测试) +```python +# 作者:Tercel818 +# 链接:https://www.nowcoder.com/discuss/114578?type=2&order=0&pos=23&page=1 +# 来源:牛客网 + +n, m = map(int, input().split()) +nums = list(map(int, input().split())) +acc_sum = [0] +for item in nums: + acc_sum.append(acc_sum[-1] + item) +dp = [[float("inf")] * (1 + len(nums)) for _ in range(m + 1)] +dp[0][0] = 0 +for i in range(1, m + 1): + for j in range(1, len(nums) + 1): + for k in reversed(range(i - 1, j)): + val = max(dp[i - 1][k], acc_sum[j] - acc_sum[k]) + dp[i][j] = min(val, dp[i][j]) +print(dp[m][len(nums)]) +``` diff --git "a/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\345\272\246\345\260\217\346\273\241-180913.md" "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\345\272\246\345\260\217\346\273\241-180913.md" new file mode 100644 index 00000000..bcebb6ac --- /dev/null +++ "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\345\272\246\345\260\217\346\273\241-180913.md" @@ -0,0 +1,120 @@ +笔试-度小满-180913 +=== +- 机器学习研发 +- 选择 30,编程 2 + +Index +--- + + +- [火车站台](#火车站台) +- [商品交易](#商品交易) + + + +## 火车站台 +
+
+ +**思路** +- 求区间最大重叠数 + +**暴力法**(36%) +```python +n = int(input()) + +tmp = dict() +mx = 0 +for _ in range(n): + x, y = list(map(int, input().split())) + for i in range(x, y): + if i in tmp: + tmp[i] += 1 + else: + tmp[i] = 1 + mx = max(tmp[i], mx) + +print(mx) +``` + +**扫描线法**(AC) +```python +n = int(input()) + +tmp = [] +mx = 0 +for _ in range(n): + x, y = list(map(int, input().split())) + tmp.append((x, 1)) + tmp.append((y, -1)) + +tmp.sort() + +mx = 0 +t = 0 +for i in tmp: + if i[1] == 1: + t += 1 + else: + t -= 1 + + mx = max(mx, t) + +print(mx) +``` + +## 商品交易 +
+ +**思路** +- LeetCode原题 +- 因为这里要求输出最小交易次数,所以贪心不可行,改用双指针 + +**贪心**(9%) +```python +n = int(input()) + +nums = list(map(int, input().split())) + +def foo(nums): + ans = 0 + cnt = 0 + if len(nums) <= 1: + return 0 + for x in range(1, len(nums)): + if nums[x] - nums[x - 1] >= 0: + ans += nums[x] - nums[x - 1] + cnt += 2 + return ans, cnt + +ans, cnt = foo(nums) +print(ans, cnt) +``` + +**双指针**(AC) +```python +n = int(input()) + +nums = list(map(int, input().split())) + +def foo(nums): + ans = i = 0 + cnt = 0 + while i < len(nums): + while i < len(nums) - 1 and nums[i + 1] <= nums[i]: + i += 1 + minima = nums[i] + i += 1 + + while i < len(nums) - 1 and nums[i + 1] >= nums[i]: + i += 1 + + if i < len(nums): + ans += nums[i] - minima + cnt += 2 + + return ans, cnt + +ans, cnt = foo(nums) +print(ans, cnt) +``` \ No newline at end of file diff --git "a/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\345\277\253\346\211\213-180910.md" "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\345\277\253\346\211\213-180910.md" new file mode 100644 index 00000000..01bbc34f --- /dev/null +++ "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\345\277\253\346\211\213-180910.md" @@ -0,0 +1,145 @@ +笔试-快手-180910 +=== +- 单选 20;编程 3 + + +Index +--- + + +- [字符串归一化](#字符串归一化) +- [魔法深渊](#魔法深渊) +- [善变的同伴](#善变的同伴) + + + + +### 字符串归一化 +
+ +**Python**(AC) +``` +from collections import Counter + +s = input() +c = Counter(s) + +res = "" +for key in sorted(c.keys()): + res += key + res += str(c[key]) + +print(res) +``` + +### 魔法深渊 +
+
+ +**Python**(AC) +``` +def foo(n): + dp = [0] * (n + 1) + dp[0] = 1 + + for i in range(1, n + 1): + j = 1 + while j <= n: + if i < j: + break + dp[i] = dp[i] + dp[i - j] + j = j * 2 + return dp[n] % 1000000003 + + +N = int(input()) +for i in range(N): + n = int(input()) + print(foo(n)) +``` + +### 善变的同伴 +
+
+
+ +**思路** +``` +作者:n不正 +链接:https://www.nowcoder.com/discuss/106768 +来源:牛客网 + +1、先把整个数组改成正负交错的数组,去掉首尾的负数(相邻的正数合并成一个正数,负数合并成一个负数) +2、如果正数个数<=M,输出所有的正数之和 +3、如果正数个数>M,将数组中[正负正]合并,该负数为数组中负数的最大值并且三者之和>三者最大值 +4、直到3不满足或者正数个数<=M,输出最大的M个正数之和 +``` + +**Python**(未测试) +- 按照上述思路实现的代码 +``` +# N, M = list(map(int, input().split())) +# ns = list(map(int, input().split())) +N, M = 7, 3 +ns = [1, 2, 3, -2, 3, -10, 3] + +new_ns = [ns[0]] + +for i in ns[1:]: + if i == 0: + continue + if (new_ns[-1] > 0) == (i > 0): + new_ns[-1] += i + else: + new_ns.append(i) + +# print(new_ns) # [6, -2, 3, -10, 3] + +# 去掉首尾的负数块 +if len(new_ns) >= 1 and new_ns[0] < 0: + new_ns.pop(0) + +if len(new_ns) >= 1 and new_ns[-1] < 0: + new_ns.pop(-1) + +# print(new_ns) # [6, -2, 3, -10, 3] + +# 按奇偶划分奇数和偶数块 +ns_pos = new_ns[0::2] +ns_neg = new_ns[1::2] +cnt_pos = len(ns_pos) +# print(cnt_pos) # 3 +# print(ns_pos) # [6, 3, 3] +# print(ns_neg) # [-2, -10] + +# 如果 M 的值小于正数块的数量则进行合并 +updated = True +while updated and M < cnt_pos: + """""" + updated = False + + mx_i = 0 + # mx = max(ns_pos[mx_i] + ns_pos[mx_i+1] + ns_neg[mx_i], ns_pos[mx_i], ns_pos[mx_i]) + mx = float("-inf") + for i in range(len(ns_neg)): + tmp = ns_pos[i] + ns_pos[i+1] + ns_neg[i] + if tmp < max(ns_pos[i], ns_pos[i+1]): # 如果合并后减小则不合并 + continue + if tmp > mx: + updated = True + mx = tmp + mx_i = i + + if updated: + # 更新合并后的数组 + ns_neg.pop(mx_i) + ns_pos[mx_i] = mx + ns_pos.pop(mx_i+1) + cnt_pos -= 1 + + # print(ns_pos) + # print(ns_neg) + +ns_pos.sort(reverse=True) +print(sum(ns_pos[:M])) +``` \ No newline at end of file diff --git "a/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\346\213\233\350\241\214-180830.md" "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\346\213\233\350\241\214-180830.md" new file mode 100644 index 00000000..238d07ae --- /dev/null +++ "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\346\213\233\350\241\214-180830.md" @@ -0,0 +1,29 @@ +笔试-招行-180830 +=== +15道单选、5道多选、3道问答、2道编程 + +Index +--- + + +- [重叠的装饰](#重叠的装饰) +- [推倒吧骨牌](#推倒吧骨牌) + + + +## 重叠的装饰 + +**题目描述** +
+ +**思路** +- 线段树 + > [统计颜色(线段树区间修改问题)](https://blog.csdn.net/bao___zi/article/details/80154839) - CSDN博客 + + +## 推倒吧骨牌 +> LeetCode 原题:https://leetcode-cn.com/problems/push-dominoes/description/ +>> **题解**:../算法/LeetCode题解/[838. 推多米诺](../算法/题解-LeetCode.md#838-推多米诺) +
+
+ diff --git "a/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\346\220\234\347\213\220\347\225\205\346\270\270-180915.md" "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\346\220\234\347\213\220\347\225\205\346\270\270-180915.md" new file mode 100644 index 00000000..fb6879c1 --- /dev/null +++ "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\346\220\234\347\213\220\347\225\205\346\270\270-180915.md" @@ -0,0 +1,115 @@ +笔试-搜狐畅游-180915 +=== + +- 考试题型: + - 基础能力-单选题 10 道, + - 基础能力-资料分析题一 5 道, + - 基础能力-资料分析题二 5 道, + - 专业能力-单选题 10 道, + - 专业能力-多选题 5 道, + - 专业能力-填空题 5 道, + - 专业能力-问答题 2 道, + - 专业能力-编程题 2 道 + + +Index +--- + + +- [分玩具](#分玩具) +- [多少个狗](#多少个狗) + + + + +## 分玩具 +
+
+ +**低保**(16.67%) +```python +m = int(input()) +n = list(map(int, input().split())) + +n_sum = sum(n) +n_len = len(n) +n_avg = n_sum // n_len + +if n_sum / n_len != n_avg: + print(-1) +``` + +**暴力**(83.33%) +```python +m = int(input()) +n = list(map(int, input().split())) + +n_sum = sum(n) +n_len = len(n) +n_avg = n_sum // n_len + +if n_sum / n_len != n_avg: + print(-1) + +ret = 0 +for i in n: + if i > n_avg: + ret += i - n_avg + +print(ret // 2) +``` + +**Python**(AC) +```python +m = int(input()) +n = list(map(int, input().split())) + +def foo(n): + n_sum = sum(n) + n_len = len(n) + n_avg = n_sum // n_len + + if n_sum / n_len != n_avg: + return -1 + + odd = 0 + eve = 0 + for i in n: + if i % 2 == 1: + odd = 1 # 存在奇数 + if i % 2 == 0: + eve = 1 # 存在偶数 + + if n_avg % 2 == 1 and eve: + return -1 + if n_avg % 2 == 0 and odd: + return -1 + + ret = 0 + for i in n: + if i > n_avg: + ret += i - n_avg + + return ret // 2 + +print(foo(n)) +``` + + +## 多少个狗 +
+ +**思路** +- 斐波那契数列 + +**Python**(AC) +```Python +n = int(input()) + +dp = [1, 1] + +for i in range(2, n): + dp.append(dp[i-1] + dp[i-2]) + +print(dp[n-1]) +``` \ No newline at end of file diff --git "a/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\346\273\264\346\273\264-180918.md" "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\346\273\264\346\273\264-180918.md" new file mode 100644 index 00000000..5e4b5774 --- /dev/null +++ "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\346\273\264\346\273\264-180918.md" @@ -0,0 +1,235 @@ +笔试-滴滴-180918 +=== +- 选择 30,编程 2 + + +Index +--- + + +- [排列小球](#排列小球) +- [交通轨迹分析](#交通轨迹分析) + + + +## 排列小球 +
+ +**思路** +- DFS(会超时) +- 多维 DP + > [Ways to arrange Balls such that adjacent balls are of different types](https://www.geeksforgeeks.org/ways-to-arrange-balls-such-that-adjacent-balls-are-of-different-types/) - GeeksforGeeks + +**C++**(67%,TLE) +```C++ +#include +#include + +using namespace std; + +int bs[3]; +int n; +int ans; +vector tmp; + +void dfs(int step) { + if (tmp.size() == n) { + ans += 1; + return; + } + + for (int i = 0; i < 3; i++) { + if (bs[i] > 0 && i != tmp.back()) { + tmp.push_back(i); + bs[i] -= 1; + dfs(step + 1); + bs[i] += 1; + tmp.pop_back(); + } + } +} + +void solve() { + cin >> bs[0] >> bs[1] >> bs[2]; + n = bs[0] + bs[1] + bs[2]; + ans = 0; + + for (int i = 0; i < 3; i++) { + if (bs[i] > 0) { + tmp.push_back(i); + bs[i] -= 1; + dfs(1); + bs[i] += 1; + tmp.pop_back(); + } + } + cout << ans; +} + +int main() { + + solve(); + + //cout << endl; + //system("PAUSE"); + return 0; +} +``` + +**多维 DP**(未测试) +> [Ways to arrange Balls such that adjacent balls are of different types](https://www.geeksforgeeks.org/ways-to-arrange-balls-such-that-adjacent-balls-are-of-different-types/) - GeeksforGeeks + +```C++ +#include +using namespace std; +#define MAX 100 + +// table to store to store results of subproblems +int dp[MAX][MAX][MAX][3]; + +// Returns count of arrangements where last placed ball is +// 'last'. 'last' is 0 for 'p', 1 for 'q' and 2 for 'r' +int countWays(int p, int q, int r, int last) +{ + // if number of balls of any color becomes less + // than 0 the number of ways arrangements is 0. + if (p<0 || q<0 || r<0) + return 0; + + // If last ball required is of type P and the number + // of balls of P type is 1 while number of balls of + // other color is 0 the number of ways is 1. + if (p==1 && q==0 && r==0 && last==0) + return 1; + + // Same case as above for 'q' and 'r' + if (p==0 && q==1 && r==0 && last==1) + return 1; + if (p==0 && q==0 && r==1 && last==2) + return 1; + + // If this subproblem is already evaluated + if (dp[p][q][r][last] != -1) + return dp[p][q][r][last]; + + // if last ball required is P and the number of ways is + // the sum of number of ways to form sequence with 'p-1' P + // balls, q Q Balls and r R balls ending with Q and R. + if (last==0) + dp[p][q][r][last] = countWays(p-1,q,r,1) + countWays(p-1,q,r,2); + + // Same as above case for 'q' and 'r' + else if (last==1) + dp[p][q][r][last] = countWays(p,q-1,r,0) + countWays(p,q-1,r,2); + else //(last==2) + dp[p][q][r][last] = countWays(p,q,r-1,0) + countWays(p,q,r-1,1); + + return dp[p][q][r][last]; +} + +// Returns count of required arrangements +int countUtil(int p, int q, int r) +{ + // Initialize 'dp' array + memset(dp, -1, sizeof(dp)); + + // Three cases arise: + return countWays(p, q, r, 0) + // Last required balls is type P + countWays(p, q, r, 1) + // Last required balls is type Q + countWays(p, q, r, 2); // Last required balls is type R +} + +// Driver code to test above +int main() +{ + int p = 1, q = 1, r = 1; + printf("%d", countUtil(p, q, r)); + return 0; +} +``` + +## 交通轨迹分析 +
+
+ +**思路** +- 并查集/暴力枚举 +- 判断相交 + ```python + class Point(object): + def __init__(self, x, y): + self.x = x + self.y = y + + + def is_cross(a, b, c, d): + fc = (c.y - a.y) * (a.x - b.x) - (c.x - a.x) * (a.y - b.y) + fd = (d.y - a.y) * (a.x - b.x) - (d.x - a.x) * (a.y - b.y) + + if fc * fd > 0: + return False + return True + ``` + +**暴力**(未测试) +```python +# 作者:牛客4807725号 +# 链接:https://www.nowcoder.com/discuss/112681?toCommentId=1905196 +# 来源:牛客网 + +class point(): + def __init__(self, x, y): + self.x = x + self.y = y + + +def isxiangjiao(a, b, c, d): + fc = (c.y - a.y) * (a.x - b.x) - (c.x - a.x) * (a.y - b.y) + fd = (d.y - a.y) * (a.x - b.x) - (d.x - a.x) * (a.y - b.y) + if fc * fd > 0: + return False + return True + + +import sys +import collections + +if __name__ == "__main__": + t = int(sys.stdin.readline().strip()) + for i in range(t): + n = int(sys.stdin.readline().strip()) + roaddict = collections.defaultdict(set) + roadpos = [] + count = 1 + for j in range(n): + line = sys.stdin.readline().strip().split() + if line[0] == 'T': + x1, y1, x2, y2 = int(line[1]), int(line[2]), int(line[3]), int(line[4]) + pointa = point(x1, y1) + pointb = point(x2, y2) + roadpos.append([pointa, pointb]) + if count == 1: + roaddict[count].add(count) + count += 1 + else: + visited = set() + for tmpi in range(1, len(roadpos) + 1): + pointc = roadpos[tmpi - 1][0] + pointd = roadpos[tmpi - 1][1] + if tmpi not in visited and isxiangjiao(pointa, pointb, pointc, pointd): + visited.add(tmpi) + roaddict[tmpi].add(count) + roaddict[count].add(count) + for tmp in roaddict[tmpi]: + visited.add(tmp) + roaddict[tmp].add(count) + roaddict[count].add(tmp) + for tmp in roaddict[count]: + roaddict[tmp] = roaddict[count] + count += 1 + if line[0] == 'Q': + # print(roaddict) + queryvale = int(line[1]) + print(len(roaddict[queryvale])) + print() +``` \ No newline at end of file diff --git "a/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\347\210\261\345\245\207\350\211\272-180915.md" "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\347\210\261\345\245\207\350\211\272-180915.md" new file mode 100644 index 00000000..bdf3a759 --- /dev/null +++ "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\347\210\261\345\245\207\350\211\272-180915.md" @@ -0,0 +1,78 @@ +笔试-爱奇艺-180915 +=== +- 单选 20,编程 2 + +Index +--- + + +- [局长的食物](#局长的食物) +- [库特君的面条](#库特君的面条) + + + +## 局长的食物 +
+ +**思路** +- 排序 + +**暴力**(AC) +```python +N, M, P = list(map(int, input().split())) +A = list(map(int, input().split())) +for i in range(M): + """""" + flag, t = input().split() + a = int(t) + + if flag == 'A': + A[a - 1] += 1 + else: + A[a - 1] -= 1 + +A = list(zip(A, range(1, len(A) + 1))) +# A = [(4, 1), (5, 2), (2, 3), (4, 4), (2, 5)] +# print(A) +A.sort(reverse=True) +# print(A) +ret = {A[0][1]: 1} # {2: 1} + +for i in range(1, len(A)): + if A[i][0] == A[i - 1][0]: + ret[A[i][1]] = ret[A[i-1][1]] + else: + ret[A[i][1]] = i + 1 + +# print(ret) +print(ret[P]) +``` + +## 库特君的面条 +
+ +**思路** +- 贪心 +- 最大不重叠区间 + +**贪心**(AC) +```python +N = int(input()) + +line = [] +for _ in range(N): + a, b = sorted(list(map(int, input().split()))) + line.append([a, b]) + +# line.sort() +line.sort(key=lambda a: a[1]) +# print(line) +ret = [line[0]] +for item in line[1:]: + if item[0] < ret[-1][1]: + pass + else: + ret.append(item) + +print(len(ret)) +``` \ No newline at end of file diff --git "a/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\347\231\276\345\272\246-180911.md" "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\347\231\276\345\272\246-180911.md" new file mode 100644 index 00000000..f24b6579 --- /dev/null +++ "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\347\231\276\345\272\246-180911.md" @@ -0,0 +1,36 @@ +笔试-百度-180911 +=== + + +Index +--- + +## 1. +
+ +样例说明: +``` +满足条件的两个序列为:{1 2 3 1} 和 {1 3 2 1} +``` + +> [百度A题_笔经面经](https://www.nowcoder.com/discuss/107402?type=2&order=0&pos=10&page=1)_牛客网 + +## 2. 蘑菇传奇 +
+ +样例: +``` +3 +100 5 +80 3 +90 10 + +698771049 + +3 +50 3 +50 6 +50 1 + +873463810 +``` \ No newline at end of file diff --git "a/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\347\231\276\345\272\246-180914.md" "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\347\231\276\345\272\246-180914.md" new file mode 100644 index 00000000..eebc3fdd --- /dev/null +++ "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\347\231\276\345\272\246-180914.md" @@ -0,0 +1,75 @@ +笔试-百度-180914 +=== +- 选择 30,简答 2,编程 2 + + +Index +--- + + +- [字符串计数](#字符串计数) +- [寻寻觅觅](#寻寻觅觅) + + + + +## 字符串计数 +
+ +**暴力 1**(9%,MLE) +```python +s = input() + +tmp = set() +for i in range(len(s)): + tmp.add(s) + s = s[1:] + s[0] + +print(len(tmp)) +``` + +**暴力 2**(AC) +```python +s = input() + +def foo(s): + for i in range(1, len(s) + 1): + if len(s) % i == 0: + if s[:i] * (len(s) // i) == s: + return i + +print(foo(s)) +``` + + +## 寻寻觅觅 +
+
+ +**暴力**(73%,TLE) +```python +A = input() +B = input() +Q = int(input()) + +for _ in range(Q): + l, r = list(map(int, input().split())) + print(A[l-1: r].count(B)) +``` + +**前缀数组**(36%,TLE) +``` +A = input() +B = input() +Q = int(input()) + +ret = [0] * len(A) +for i in range(len(A)): + ret[i] = A[:i].count(B) + +ret += [ret[-1]] + +for _ in range(Q): + l, r = list(map(int, input().split())) + print(ret[r] - ret[l]) +``` \ No newline at end of file diff --git "a/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\347\231\276\350\257\215\346\226\251-180920.md" "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\347\231\276\350\257\215\346\226\251-180920.md" new file mode 100644 index 00000000..f70927ef --- /dev/null +++ "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\347\231\276\350\257\215\346\226\251-180920.md" @@ -0,0 +1,26 @@ +笔试-百词斩-180920 +=== +- 编程 4 + +Index +--- + + +## 1. 时钟的转动 +
+
+ + +## 2. 星期合并 +
+ + +## 3. 解析 BCON +
+
+
+ + +## 4. 凸多边形的面积 +
+
diff --git "a/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\350\205\276\350\256\257-180916.md" "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\350\205\276\350\256\257-180916.md" new file mode 100644 index 00000000..249cb4ee --- /dev/null +++ "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\350\205\276\350\256\257-180916.md" @@ -0,0 +1,194 @@ +笔试-腾讯-180916 +=== +- 不定项 25,编程 3 + + +Index +--- + + +- [字符串系数](#字符串系数) +- [小Q与牛牛的游戏](#小q与牛牛的游戏) +- [三元组](#三元组) + + + + +## 字符串系数 + +
+ + +**暴力 KMP**(70%) +```python +def get_nxt(T): + n = len(T) + nxt = [0] * n + max_len = 0 + + for i in range(1, n): + while max_len > 0 and T[max_len] != T[i]: + max_len = nxt[max_len - 1] + if T[i] == T[max_len]: + max_len += 1 + nxt[i] = max_len + + return nxt + + +def kmp(S, T): + pos = [] + nxt = get_nxt(T) + + cnt = 0 + for i in range(len(S)): + while cnt > 0 and T[cnt] != S[i]: + cnt = nxt[cnt - 1] + + if T[cnt] == S[i]: + cnt += 1 + if cnt == len(T): + pos.append(i - len(T) + 1) + cnt = nxt[cnt - 1] + return pos + + +k = int(input()) +A = input() +B = input() + +res = dict() +for i in range(len(A) - k + 1): + p = A[i: i + k] + # print(p) + if p not in res: + res[p] = len(kmp(B, p)) + +print(sum(res.values())) +``` + +**暴力-使用C++库函数**(AC) +> [腾讯-计算机视觉笔试题 ac](https://www.nowcoder.com/discuss/110869)_笔经面经_牛客网 +```C++ +size_t str_count(const string& S, const string& T) { + + size_t cnt = 0; + for (size_t i = 0; (i = S.find(T, i)) != string::npos; i++, cnt++); + + return cnt; +} + +int main() { + int k = 2; + //cin >> k; + string A{"abab"}; + //cin >> A; + string B{"ababab"}; + //cin >> B; + + vector tmp; + for (int i = 0; i < A.length() - k + 1; i++) + tmp.push_back(A.substr(i, k)); + + cout << tmp.size() << endl; + unique(tmp.begin(), tmp.end()); // 去重,这里直接使用 set 应该也可以 + + size_t ans = 0; + for (auto T : tmp) { + ans += str_count(A, T); + } + + cout << ans << endl; + + //system("PAUSE"); + return 0; +} +``` + +## 小Q与牛牛的游戏 + +
+ +**Python**(AC) +```python +def solve(x, y): + """""" + ts = int(((x + y) * 2) ** 0.5) + + if (ts * (ts + 1)) != (x + y) * 2: + return -1 + + cnt = 0 + if x < ts: + return 1 + + while x > 0: + x -= ts + ts -= 1 + cnt += 1 + + return cnt + + +x, y = list(map(int, input().split())) + +print(solve(x, y)) +``` + + +## 三元组 + +
+ +**Python**(50%) +```python +def solve(x, y, z): + """""" + cnt = 0 + for i in range(1, x + 1): + for j in range(1, y + 1): + m_min = abs(i - j) + 1 + m_max = min(i+j-1, z) + cnt = (cnt + m_max - m_min + 1) % 1000000007 + + return cnt + + +x, y, z = sorted(list(map(int, input().split()))) + +print(solve(x, y, z)) +``` + +**C++**(60%) +```C++ +int solve(int x, int y, int z) +{ + int cnt = 0; + + for (int i = 1; i <= x; ++i) { + for (int j = 1; j <= y; ++j) { + int m_min = abs(i - j) + 1; + int m_max = min(i + j - 1, z); + cnt = (cnt + m_max - m_min + 1) % 1000000007; + } + } + return cnt; +} + +int main() +{ + vector xyz; + int times = 3; + while (times--) { + int num; + cin >> num; + xyz.push_back(num); + } + sort(xyz.begin(), xyz.end()); + int x = xyz[0]; + int y = xyz[1]; + int z = xyz[2]; + cout << solve(x, y, z) << endl; + return 0; +} +``` \ No newline at end of file diff --git "a/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\350\277\205\351\233\267-180912.md" "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\350\277\205\351\233\267-180912.md" new file mode 100644 index 00000000..0c842538 --- /dev/null +++ "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\350\277\205\351\233\267-180912.md" @@ -0,0 +1,60 @@ +笔试-迅雷-180912 +=== +- 单选 10,多选 10,编程 2 + +Index +--- + + +- [AI-A-01](#ai-a-01) +- [AI-A-02](#ai-a-02) + + + +## AI-A-01 +
+ +**Python**(AC) +```python +A = list(map(int, input().split(','))) + +def foo(A): + for i in range(1, len(A) - 1): + + if sum(A[:i]) == A[i] and A[i] == sum(A[i + 1:]): + return i + + return False + +ret = foo(A) +if ret: + print(A[ret]) +else: + print("False") +``` + + +## AI-A-02 +
+ +**Python**(AC) +``` +IN = input() + +a1, a2k = IN.split('-') +a1 = list(map(int, a1.split(','))) +a2, k = a2k.split(':') +a2 = list(map(int, a2.split(','))) +k = int(k) + +# print(a1) +# print(a2) +# print(k) + +tmp = [] +for i in a1: + tmp += [i + t for t in a2] + +print(','.join(list(map(str, sorted(tmp, reverse=True)))[:k])) + +``` diff --git "a/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\351\241\272\344\270\260-180917.md" "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\351\241\272\344\270\260-180917.md" new file mode 100644 index 00000000..a2fbc854 --- /dev/null +++ "b/D-\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\351\241\272\344\270\260-180917.md" @@ -0,0 +1,85 @@ +笔试-顺丰-180917 +=== +- 选择 30,编程 2 + + +Index +--- + + +- [编辑距离](#编辑距离) +- [分发糖果](#分发糖果) + + + +## 编辑距离 +> LeetCode [72. 编辑距离](https://leetcode-cn.com/problems/edit-distance/description/) + +**思路** +- 动态规划 + +**C++**(AC) +```C++ +class Solution { +public: + int minDistance(string word1, string word2) { + int m = word1.length(); + int n = word2.length(); + vector> dp(m + 1, vector(n + 1, 0)); + + // 初始化 dp + for (int i = 1; i <= m; i++) + dp[i][0] = i; + for (int j = 1; j <= n; j++) + dp[0][j] = j; + + // 更新 dp + for (int i = 1; i <=m; i++) + for (int j = 1; j <= n; j++) + if (word1[i - 1] == word2[j - 1]) + dp[i][j] = dp[i - 1][j - 1]; + else + dp[i][j] = min({ dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + 1 }); + + return dp[m][n]; + } +}; +``` + + +## 分发糖果 +> LeetCode [135. 分发糖果](https://leetcode-cn.com/problems/candy/description/) + +**思路** +- 贪心: + - 首先初始化每个人一个糖果,然后这个算法需要遍历两遍, + - 第一遍从左向右遍历,如果右边的小盆友的等级高,等加一个糖果,这样保证了一个方向上高等级的糖果多。 + - 第二遍从右向左遍历,如果相邻两个左边的等级高,而左边的糖果又少的话,则左边糖果数为右边糖果数加一。 + - 最后再把所有小盆友的糖果数都加起来返回即可。 + + +**C++**(AC) +```C++ +class Solution { +public: + int candy(vector& ratings) { + int n = ratings.size(); + + vector nums(n, 1); + for (int i = 0; i < n - 1; ++i) { + if (ratings[i + 1] > ratings[i]) + nums[i + 1] = nums[i] + 1; + } + + for (int i = n - 1; i > 0; --i) { + if (ratings[i - 1] > ratings[i]) + nums[i - 1] = max(nums[i - 1], nums[i] + 1); + } + + int res = 0 + for (auto num : nums) + res += num; + return res; + } +}; +``` diff --git a/Jobs.md b/Jobs.md new file mode 100644 index 00000000..28f0a450 --- /dev/null +++ b/Jobs.md @@ -0,0 +1,415 @@ +**RoadMap** +--- + + +- [机器学习/AI/算法](#机器学习ai算法) + - [网易游戏-智能运维算法工程师](#网易游戏-智能运维算法工程师) + - [远景-算法工程师-最优化/深度学习(上海)](#远景-算法工程师-最优化深度学习上海) + - [图森未来-深度学习框架研发工程师(2019校园招聘)](#图森未来-深度学习框架研发工程师2019校园招聘) + - [小红书-机器学习算法工程师(2019校招)](#小红书-机器学习算法工程师2019校招) + - [迅雷-人工智能工程师](#迅雷-人工智能工程师) + - [京东-算法工程师](#京东-算法工程师) + - [爱奇艺-算法工程师-校招-北京/上海](#爱奇艺-算法工程师-校招-北京上海) + - [网易-机器学习算法工程师(网易杭州)](#网易-机器学习算法工程师网易杭州) + - [顺丰-机器学习与人工智能工程师](#顺丰-机器学习与人工智能工程师) + - [比特大陆-AI算法工程师—上海](#比特大陆-ai算法工程师上海) + - [搜狗-搜狗输入法研究部校园招聘内推](#搜狗-搜狗输入法研究部校园招聘内推) + - [头条-算法工程师-上海](#头条-算法工程师-上海) + - [贝壳/链家-数据挖掘/机器学习工程师【2019校园招聘】-北京](#贝壳链家-数据挖掘机器学习工程师2019校园招聘-北京) +- [自然语言处理](#自然语言处理) + - [网易- NLP算法工程师(网易杭州)](#网易--nlp算法工程师网易杭州) + - [顺丰-NLP算法工程师](#顺丰-nlp算法工程师) + - [VIVO-人工智能自然语言处理(NLP)算法工程师](#vivo-人工智能自然语言处理nlp算法工程师) + - [百度-北京-机器学习/数据挖掘/自然语言处理工程师](#百度-北京-机器学习数据挖掘自然语言处理工程师) + - [贝壳/链家-NLP工程师【2019校园招聘】-北京](#贝壳链家-nlp工程师2019校园招聘-北京) + - [搜狗-【校招】智能问答-自然语言处理研究员(杭州)](#搜狗-校招智能问答-自然语言处理研究员杭州) + + + +# 机器学习/AI/算法 + +## 网易游戏-智能运维算法工程师 + +**您可以**: +``` +- 面对海量数据,设计和实现智能运维所需的策略和算法; +- 关注相关方向的前沿研究,并将相关成果工程化落地到智能运维平台。 +``` + +**我们希望你具备以下条件**: +``` +- 在数学、计算机、机器学习等领域有本科以上学位; +- 对数据敏感,熟悉各类数据分析和预测算法、机器学习算法,并精通其中至少一类; +- 熟练掌握至少一种常用编程语言,如Python、R、Java、Go等,具有算法工程化能力; +- 拥有持续学习的欲望和能力,对应用数据算法解决现实业务问题具有热情; +- 在机器学习、数据分析、预测方面具有实际项目经历优先。 +``` + +## 远景-算法工程师-最优化/深度学习(上海) +> [远景2019校园招聘](http://recruit.envisioncn.com/jobList.html?goType=EnvisioncnDigital) + +**工作内容** +``` +采用人工智能技术,将新能源开发过程中的设计过程算法化, +如风险特征提取、资源规划、物流规划、电器设计、结构设计等。设计算法,向软件团队交付模型。 +``` +**职位要求** +``` +1、计算机或软件工程专业硕士及以上学历; +2、扎实的数理建模基础、抽象思维和快速学习能力,数学、运筹学、计算机、自动化专业优先; +3、熟悉组合优化的经典算法和问题,理解原理,熟悉和掌握智能优化算法,如进化、模拟退火等; +4、掌握一种以上常用深度学习框架,如tensorflow、keras、torch,熟悉卷积神经网络类的原理,理解指针网络的原理; +5、熟练掌握C++、Python。 +``` + +## 图森未来-深度学习框架研发工程师(2019校园招聘) +> [北京图森未来科技有限公司](https://app.mokahr.com/campus_apply/tusenweilai#/job/687ec29b-5dd6-4cc8-80bb-a79a1337b884?_k=urfwgl) - 校园招聘 + +**职位描述** +``` +岗位职责: +针对公司内部需求,优化深度学习平台(MXNet)训练和部署性能; +基于MXNet,设计和研发大规模分布式训练平台; +为相关开源项目贡献新Feature。 +``` + +**任职要求** +``` +了解机器学习以及深度学习基本知识;熟悉计算机体系结构和性能优化基本知识; +在相关开源项目(MXNet,Caffe,TensorFlow,PyTorch)有贡献者优先。 +``` + +**加分项**: +``` +有并行分布式开发经验或ACMICPC/超算比赛等参赛经验。 +``` + +## 小红书-机器学习算法工程师(2019校招) +> 小红书校园招聘 https://campus.xiaohongshu.com/jobs/310177699 + +**工作职责** +``` +你要做的事: +• 开发产品代码; +• 跟其他的工程师一同分析,解决搜索、推荐上的难题; +• 应用数据分析、统计理论、机器学习来理解用户、笔记和商品之间的联系; +• 创建,评测,使用模型帮助用户更快的发现属于ta的好东西。 +``` +**任职资格** +``` + 我们希望你: +• 全日制计算机相关专业本科及以上学历; +• 熟练掌握或者深刻理解Java、C++、Python、Scala、Go其中一门或者多门语言; +• 对CS常见数据结构和算法有研究; +• 对处理、分析大量数据,以及建模有强烈兴趣,熟悉MapReduce,了解Hadoop、Spark系统; +• 有以下相关知识、经验:machine learning、information retrieval、 recommendation system、social network analysis; +• 积极而且有责任心。 +``` + + +## 迅雷-人工智能工程师 +> [招聘职位](http://campus.xunlei.com/position.html?tab=0) +- **岗位职责** + ``` + 根据用户海量稀疏信息构建用户Profile兴趣集,为用户发现有趣、好玩、涨知识的视频内容; + 在大数据基础之上对用户的复杂行为数据进行建模,利用机器学习,深度学习等算法建立智能模型,对用户进行个性化短视频推荐; + 负责推荐系统整体架构设计和实现,负责实现推荐引擎以及评价系统的离线以及在线核心算法。 + ``` +- **任职要求** + ``` + 硕士及以上学历,计算机(机器学习/AI/信息检索等方向)、数学等相关专业; + 熟悉常见机器学习算法,有应用SVM、XGBOOST、ANN等技术解决实际问题的经验 + 深刻理解计算机数据结构和算法设计,熟练掌握Java/Scala/Python/R语言,至少精通一种语言,熟练掌握至少一种机器学习框架:TensorFlow ,Spark MLlib,Caffe,Theano,Torch,Pytorch等; + 熟悉TCP/UDP网络协议及相关编程、进程间通讯编程;了解MySQL语言、编程,了解NoSQL, Key-Value存储原理; + 优秀的分析问题和解决问题的能力,较强的学习能力和沟通能力,具备良好的团队合作精神; + + 具有以下条件者优先考虑: + - 计算机领域相关的编程大赛获奖、专业期刊发表文章或者有发明专利等; + - 熟悉Spark内存计算框架,熟悉LR预测模型以及LTR(learning to rank)以及深度学习模型CNN RNN LSTM等模型。 + ``` + +## 京东-算法工程师 +> 招聘职位-[职位详情](http://campus.jd.com/web/job/job_detail?jobId=603) +``` +工作地点: + 北京,上海,深圳,武汉,成都,南京 + +职位方向:技术方向 + +公司: 京东集团 , 京东金融 , 京东商城 , 京东物流 + +岗位描述: + 【热招职位 大量招聘】 + 1-2019年毕业,本科及以上学历 + 2-研究及应用前沿互联网技术,如机器学习、深度学习、数据挖掘、推荐、搜索、自然语言处理、分布式并行算法、复杂网络、图像识别、计算机视觉、语音等 + 3-至少熟悉java, c++,python中的一种开发语言 + 4-良好的逻辑思维能力,良好的沟通能力、团队合作精神和学习能力 +``` + +## 爱奇艺-算法工程师-校招-北京/上海 +> http://zhaopin.iqiyi.com/job-detail-info-school.html?id=1831&isschool=1 +- **工作内容/职位描述**: + ``` + 负责爱奇艺算法相关技术研发。 + 任职资格: + 1、2019年应届毕业生,硕士及以上学历; + 2、扎实的数学和计算机科学功底,熟悉linux编程环境,至少精通一门编程语言,对数据结构和算法有较为深刻理解; + 3、有数据挖掘/机器学习/自然语言处理/信息抽取/音视频编解码算法/模式识别/视觉等相关背景优先; + 4、认真负责,热爱学习,善于思考,乐于创新,团结友爱,简单想、简单做。 + ``` + +## 网易-机器学习算法工程师(网易杭州) +> [网易校园招聘](https://campus.163.com/app/jobDetail/index?id=83) +- **岗位描述** + ``` + 1、负责业务相关的各类数据加工处理; + 2、负责个性化推荐、搜索等相关的算法研发工作,提升推荐精准度,优化用户体验。 + ``` +- **岗位要求** + ``` + 1、相关专业毕业,拥有扎实的计算机基础理论知识; + 2、熟悉机器学习的基本算法,对算法有较强的实现能力;对数据挖掘有足够的兴趣; + 3、有如下领域相关背景优先:推荐算法、自然语言处理、计算机视觉、数据挖掘; + 4、至少精通一门编程语言,熟练使用常用算法和数据结构,具备基本的工程编码能力; + 5、优秀的分析及解决问题能力,责任心强,优秀的沟通能力和团队精神。 + ``` +- 工作地点:杭州 +- 面试城市:杭州 + +## 顺丰-机器学习与人工智能工程师 +- **招聘人数**:100 +- **岗位职责**: + ``` + 1、基于顺丰各项业务的海量优质数据,进行数据分析、人工智能建模、训练,为顺丰业务的数据化智能化运营提供参考和具体应用; + 2、负责深度学习算法设计及开发; + 3、负责自然语言处理相关算法设计及开发。 + ``` +- **任职要求**: + ``` + 1、全日制硕士以上学历; + 2、扎实的数学基础和机器学习算法功底; + 3、熟悉机器学习常用算法:决策树,随机森林,协同过滤,SVM, 回归算法等; + 4、熟悉神经网络理论,熟练使用Caffe\TensorFlow\Keras其中至少一种神经网络框架进行建模和训练;熟悉CNN\RNN\LSTM\Reinforced Learning原理; + 5、熟悉Python编程,熟练使用 Numpy, Pandas, Scikit-learn; + 6、熟悉随机过程、马尔可夫链者优先。 + ``` + +## 比特大陆-AI算法工程师—上海 +> [比特大陆](https://app.mokahr.com/campus_apply/bitmain#/job/c5b4ad1b-31b1-4be2-9e75-ed72632b0aba?_k=rs4tpq) - 校园招聘 +- **职位描述** + ``` + 我们希望你可以和我们一起在专用芯片上开发软件,实现各类算法。 + 我们希望将你培养成足够优秀的算法工程师,不管是在研究深度学习、机器学习、图像处理算法方面,还是在音频/视频/计算机视觉、自然语言处理、射频/通信/信号算法、数据挖掘/推荐算法、搜索算法、控制算法等领域,都能有所建树。 + 我们希望你可以有机会和我们的大牛们共同探索更宽更广的领域。 + 我们相信算法影响世界。而你,和我们一起,足以改变世界。 + 我在比特大陆,等你。 + ``` +- **理想的求职者** + ``` + 1、 应届毕业生或毕业一年内海外留学者 + 2、 学习成绩优秀,对新生事物及专业相关领域知识具有极强的学习能力和知识迁移能力 + 3、 具备前沿人工智能技术的挑战热情 + 4、 至少会使用C/C++或脚本语言Python/Perl等其中一种开发语言 + 5、 喜欢挑战,具有良好的合作精神和沟通能力 + ``` +- **加分(非必备)项**: + ``` + 1、有linux下C/C++的开发经验。 + 2、有如下至少一个领域的算法背景(计算机视觉、机器学习、深度学习、视频编解码) + 3、有很强的代码能力者优先,比如获得过ACM或商业代码竞赛荣誉,或代码开源在GitHub上有较大影响者优先。 + 4、有很强的研发能力,在国际顶尖会议或者期刊(CVPR,ICCV,ECCV等)发表过论文的优先,在学术或者工业界算法竞赛中获得过奖项的优先。 + ``` + +## 搜狗-搜狗输入法研究部校园招聘内推 +- **岗位说明**: + ``` + 1. 输入法核心功能及由输入法展开的创新功能研究与技术转化; + 2. 海量数据下深度学习前沿技术(用户表示、对话系统、基于GPU的并行计算平台)的研究工作; + 3. 基于异构用户数据的用户画像研究及商业化。 + ``` + +- **任职资格**: + ``` + 1.深刻理解并熟练掌握机器学习、数据挖掘、统计学的相关算法和技术,在校期间有1年以上相关研究经验; + 2.c/c++编程背景和算法基础,有python/perl/shell等脚本处理经验。 + 3.善于学习新事物,善于表达和沟通,有较强的分析和解决问题能力,工作有激情。 + ``` + +- **加分项**: + ``` + 1. 在下列一个领域有较深入研究:分词、语言模型、机器翻译、词法句法分析、语义表示...... + 2. 输入深度学习模型和算法,如DNN、CNN、RNN、LSTM等,有Tensorflow,Theano等实际经验者优先; + 3. 熟练多线程编程,有在GPU下并行计算和分布式计算编程经验者优先; + 4. 在国际高水平学术会议由论文发表; + ``` + +## 头条-算法工程师-上海 +- **岗位描述**: + ``` + 1、利用机器学习技术,改进头条的推荐、广告系统,优化数亿用户的阅读体验; + 2、分析基础数据,挖掘用户兴趣、文章价值,增强推荐、广告系统的预测能力; + 3、分析用户商业意图,挖掘流量潜在商业价值,提升流量变现; + 4、研究计算机视觉算法,给用户提供更多更酷炫的功能; + 5、研发机器翻译与对话技术,促进跨语言内容理解与交流。 + ``` +- **岗位要求**: + ``` + 1、本科及以上学历,计算机、机器学习和模式识别相关专业; + 2、热爱计算机科学和互联网技术,对人工智能类产品有浓厚兴趣; + 3、具备强悍的编码能力,熟悉 linux 开发环境,熟悉 C++ 和 Python 语言优先; + 4、有扎实的数据结构和算法功底,熟悉机器学习、自然语言处理、数据挖掘、分布式计算、计算机视觉、计算机图形、语音识别与合成中一项或多项; + 5、对推荐系统、计算广告、搜索引擎、对话问答、图像和视频分析处理相关技术有经验者优先; + 6、优秀的分析问题和解决问题的能力,对解决具有挑战性问题充满激情。 + ``` + +## 贝壳/链家-数据挖掘/机器学习工程师【2019校园招聘】-北京 +> [贝壳找房校园招聘官网网申系统](http://campus.ke.com/zpdetail/190145556?r=&p=&c=1100&d=&k=)--招聘详细 +- **工作职责**: + ``` + 1、研究数据挖掘或统计学习领域的前沿技术,并用于实际问题的解决和优化; + 2、大规模机器学习算法研究及并行化实现,为各种大规模机器学习应用研发核心技术; + 3、通过对数据的敏锐洞察,深入挖掘产品潜在价值和需求,进而提供更有价值的产品和服务,通过技术创新推动产品成长。 + ``` +- **任职资格**: + ``` + 1、热爱互联网,对技术研究和应用抱有浓厚的兴趣,有强烈的上进心和求知欲,善于学习和运用新知识; + 2、具有以下一个或多个领域的理论背景和实践经验:机器学习/数据挖掘/深度学习/信息检索/自然语言处理/机制设计/博弈论; + 3、至少精通一门编程语言,熟悉网络编程、多线程、分布式编程技术,对数据结构和算法设计有较为深刻的理解 ; + 4、良好的逻辑思维能力,对数据敏感,能够发现关键数据、抓住核心问题 ; + 5、较强的沟通能力和逻辑表达能力,具备良好的团队合作精神和主动沟通意识 + 具有以下条件者优先: + 6、熟悉文本分类、聚类、机器翻译,有相关项目经验 ; + 7、熟悉海量数据处理、最优化算法、分布式计算或高性能并行计算,有相关项目经验 。 + ``` + +# 自然语言处理 + +## 网易- NLP算法工程师(网易杭州) +> [网易校园招聘](https://campus.163.com/app/jobDetail/index?id=81) +- **岗位描述** + ``` + 1、负责NLP技术在自动**问答**、人机对话、语义理解、文本分析、推荐等方向上的应用研究; + 2、负责NLP相关核心技术研发及前沿算法跟踪,根据产品需求完成技术转化,推动业务发展。 + ``` +- **岗位要求** + ``` + 1、正直诚信、有责任感、有激情; + 2、模式识别/人工智能/数学/计算机相关专业,硕士以上学历; + 3、熟悉基于统计和句法/语法分析的自然处理方法,包括分词、词性标注、命名实体识别、依存句法分析、文本分类、文本检索、Deep Learning在NLP领域中的应用等等; + 4、具有较强编程能力(熟悉C++/Java),熟练使用至少一种脚本语言(python/shell等),熟悉hadoop、spark框架者尤佳; + 5、在自动问答、人机对话、口语理解、知识库管理等领域有实际的开发经验者优先; + 6、学习能力强,能独立分析并解决问题。 + ``` +- **工作地点**:杭州 +- **面试城市**:杭州 + +## 顺丰-NLP算法工程师 +- **招聘人数**:2 +- **工作职责** + ``` + 1. 负责利用自然语言处理和机器学习算法对海量文本数据进行挖掘,包括但不限于,文本分词、分类、情感分析、语义理解、文本相关性等; + 2. 完善和优化算法现有业务中算法。 + ``` +- **任职要求**: + ``` + 1、计算机相关专业硕士以上学历; + 2、具有扎实的数学、计算机科学功底和良好的编程基础; + 3、对数据结构和算法设计有深刻理解,掌握C/C++/Java/Python/Scala等至少一门高级编程语言; + 4、掌握机器学习常用分类,回归,聚类模型和优化算法; + 5、理解自然语言处理常用的算法,如 WORD2VEC,CRF,LDA,LSA,SVD等,并有实践经验。 + ``` + +## VIVO-人工智能自然语言处理(NLP)算法工程师 +> [职位详情](https://hr.vivo.com/wt/vivo/web/templet1000/index/corpwebPosition1000vivo!getOnePosition?postIdEnc=484ce7f7d8e8fb23&brandCode=1&recruitType=1&lanType=1&showComp=true) - VIVO +- **岗位概况** + ``` + - 工作地点:深圳市,南京市,北京市,杭州市 + - 学历要求:硕士研究生 + - 招聘人数:50 + - 职位类型:研发类 + ``` +- **岗位职责**: + ``` + 1、自然语言处理领域的基础算法设计、开发和调优工作; + 2、构建自然语言处理相关的产品形态,包括:知识图谱、智能问答、语义理解等; + 3、用户行为分析、舆情分析等,为产品提供语义分析支持; + 4、探索NLP技术前沿领域知识,跟进国际顶级学术界成果和工业界项目。 + ``` +- **任职要求**: + ``` + 1、硕士及以上学历,计算机或相关专业; + 2、熟练掌握至少一种编程语言,包括但不限于:C/C++、Java、Python、Scala、R; + 3、具备自然语言处理领域项目经验,如:中文分词、专名识别、单词向量、主题模型、情感分析、智能问答、机器翻译; + 4、具备Hadoop、MapReduce、Spark、Storm、HBase、Kafka等大数据开发经验者优先。 + ``` + +## 百度-北京-机器学习/数据挖掘/自然语言处理工程师 +> 百度校园招聘 https://talent.baidu.com/external/baidu/campus.html#/jobDetail/1/120926 +``` +所属部门: 百度 +工作地点: 北京市 +招聘人数: 205 +公司: 百度 +职位类别: 技术 +发布时间: 2018-07-23 +``` + +- **工作职责**: + ``` + -研究数据挖掘或统计学习领域的前沿技术,并用于实际问题的解决和优化 + -大规模机器学习算法研究及并行化实现,为各种大规模机器学习应用研发核心技术 + -通过对数据的敏锐洞察,深入挖掘产品潜在价值和需求,进而提供更有价值的产品和服务,通过技术创新推动产品成长 + ``` + +- **职责要求**: + ``` + -热爱互联网,对技术研究和应用抱有浓厚的兴趣,有强烈的上进心和求知欲,善于学习和运用新知识 + -具有以下一个或多个领域的理论背景和实践经验:机器学习/数据挖掘/深度学习/信息检索/自然语言处理/机制设计/博弈论 + -至少精通一门编程语言,熟悉网络编程、多线程、分布式编程技术,对数据结构和算法设计有较为深刻的理解 + -良好的逻辑思维能力,对数据敏感,能够发现关键数据、抓住核心问题 + -较强的沟通能力和逻辑表达能力,具备良好的团队合作精神和主动沟通意识 + + 具有以下条件者优先: + -熟悉文本分类、聚类、机器翻译,有相关项目经验 + -熟悉海量数据处理、最优化算法、分布式计算或高性能并行计算,有相关项目经验 + ``` + +## 贝壳/链家-NLP工程师【2019校园招聘】-北京 +> [贝壳找房校园招聘官网网申系统](http://campus.ke.com/zpdetail/190145554?r=&p=&c=1100&d=&k=)--招聘详细 +- **工作职责**: + ``` + 1、参与房产领域自然语言处理基础平台建设,为搜索、推荐、问答、知识图谱等应用提供基础技术输出; + 2、参与房产领域智能问答系统研发工作,研究基于领域知识库和知识图谱的深度学习问答系统研发,打造智能的房产问答体系; + 3、参与房产领域智能对话系统研发工作,包括自然语言理解、对话管理、对话生成等核心模块的研发,持续提升语义理解,多轮对话,文本生成技术等能力,打造对话即应用的下一代智能产品。 + ``` +- **任职资格**: + ``` + 1、自然语言处理/机器学习/计算机/数学 相关专业,硕士及以上学历; + 2、优秀的算法基础和编码能力,熟悉linux环境,精通c/c++以及python、shell等脚本语言; + 3、在如下专业领域有丰富经验:分词、词性标注、新词发现、词义消歧、命名实体识别、句法分析、情感分析、主题模型、话题分析、事件发现、舆情分析、知识图谱,独立实现过复杂NLP系统; + 4、有很强的分析和解决问题的能力,思路清晰,学习能力强,善于归纳、总结、推理; + 5、能力强,心里素质佳,有优秀的团队合作意识,良好的沟通能力以及团队协调能力。 + ``` + +## 搜狗-【校招】智能问答-自然语言处理研究员(杭州) +> [搜狗](https://app.mokahr.com/recommendation-apply/sogou-inc?recommenderId=70344&from=singlemessage#/job/4ef70aff-8d0a-4331-bf49-34766070b94f?_k=r4e7vb) - 内部推荐 +- **职位描述** + ``` + 项目介绍: + 随着智能时代的到来,人们对信息获取的效率和方式有了更高期望,我们正在通过技术手段让信息的获取变得更加便捷。 + 基于深厚积淀的自然语言处理、大数据挖掘和深度学习等技术,智能问答能够准确理解用户的信息需求,高效提炼互联网海量数据形成优质知识库,通过语义理解和知识计算,以精准答案的方式响应用户的信息查询需求。我们正在搜索场景提供精准问答功能,每天服务亿万网民,我们努力打造最优秀的中文问答和对话服务平台,背靠智能问答服务的搜狗汪仔机器人在国内著名知识问答节目《一站到底》中战胜最强人类选手。我们正在更多场景更好提供问答和对话服务,我们的使命是“让信息获取更简单”。 + 项目亮点: + 公司战略重点项目,人工智能在自然语言处理领域的集中体现,搜索引擎的未来发展方向。 + ``` +- **岗位说明**: + ``` + 1.负责自然语言处理基础工具的开发、改进与应用,包括分词、词性标注、命名实体识别、句法分析等效果改进工作; + 2.负责自动摘要、情感分析、热点识别等效果改进工作; + 3.从事智能问答系统的研发,改进问题和答案语义相关性计算方法,为搜索用户提供精准答案。 + 任职资格: + 1.计算机相关专业研究生及以上学历; + 2.在自然语言处理、信息检索、机器学等领域有较好的研究经历,有NLP相关技术背景或开发经验优先; + 3.具备较好的C++或Java开发能力,具备较好的算法基础,逻辑归纳能力强; + 4.熟悉主流深度学习框架(tensorflow/caffe/mxnet)任一种者; + 5.有工作热情,沟通能力较好,团队意识强。 + ``` \ No newline at end of file diff --git a/README.md b/README.md index 3505676a..ff33395c 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,120 @@ -## 深度学习/机器学习面试笔记 +算法/深度学习/NLP面试笔记 +=== +**GitHub** 地址:https://github.com/imhuay/Algorithm_Interview_Notes-Chinese -深度学习/机器学习面试问题整理,其中大部分问题来自于这个[仓库](https://github.com/elviswf/DeepLearningBookQA_cn). +**说明**(2019-2-25):感谢大家的关注,但其实这些笔记远没有那么大的价值;深度学习以及自然语言处理的发展极其迅速,这里的很多内容已经年久失修,甚至很多都没有完成;本人也已经毕业入职,不再有那么多的时间继续维护这些笔记,大家酌情参考即可(不必 Star 收藏了)。 -个人认为该仓库中的部分问题过于抽象/理论,所以没有收录,如有需要可以浏览原仓库中的问题。 +RoadMap +--- -> 该仓库只列出了问题答案在《深度学习》实体书中的页码,与PDF版并不对应,我重新修改为了答案所在章节的名称。 -> -> [深度学习中文版 GitHub](https://github.com/exacity/deeplearningbook-chinese) + -此外,还包括我看到的所有机器学习/深度学习面经中的问题。除了其中 DL/ML 相关的,其他与算法岗相关的计算机知识也会记录。 + -但是不会包括如前端/测试/JAVA/Android等岗位中与具体语言、框架等有关的问题。 +- :soccer: [机器学习](./A-机器学习) +- :basketball: [深度学习](./A-深度学习) +- :hamburger: [自然语言处理](./B-自然语言处理) +- :fries: [计算机视觉](./B-计算机视觉) +- :cherries: [数学](./C-数学) +- :apple: [算法](./C-算法) +- :strawberry: [编程语言](./C-编程语言) +- :cookie: [笔试面经](./D-笔试面经) + -- #### [机器学习相关](/机器学习) + +欢迎分享你在面试中遇见的问题! +--- +- 你可以直接以你遇到的问题作为 issue 标题,然后分享你的回答或者其他参考资料。 +- 我会经常修改文档的结构。如果文中有链接失效,请告诉我! -### Reference - +推荐阅读 +--- - exacity/[deeplearningbook-chinese](https://github.com/exacity/deeplearningbook-chinese): 深度学习中文版 +- elviswf/[DeepLearningBookQA_cn](https://github.com/elviswf/DeepLearningBookQA_cn): 深度学习面试问题 +- huihut/[interview: C/C++面试知识总结](https://github.com/huihut/interview) +- CSDN博客/[结构之法 算法之道](https://blog.csdn.net/v_july_v) +- 牛客网/[笔试面经](https://www.nowcoder.com/discuss?type=2&order=0) +- GitHub 搜索:[Deep Learning Interview](https://github.com/search?q=deep+learning+interview) +- GitHub 搜索:[Machine Learning Interview](https://github.com/search?q=machine+learning+interview) -- elviswf/[DeepLearningBookQA_cn](https://github.com/elviswf/DeepLearningBookQA_cn): 深度学习面试问题 回答对应的DeepLearning中文版页码 +工具 +--- +- 在线 LaTeX 公式编辑器 http://www.codecogs.com/latex/eqneditor.php -- 在线 LaTeX 公式编辑器 http://www.codecogs.com/latex/eqneditor.php \ No newline at end of file + \ No newline at end of file diff --git a/ToDo.md b/ToDo.md new file mode 100644 index 00000000..a06fbd68 --- /dev/null +++ b/ToDo.md @@ -0,0 +1,221 @@ +Updates Log +=== + +## ToDo +- Tensorflow - char embedding layer +- Tensorflow - Pointer Layer +- 机器学习 - 专题-XGBoost +- 深度学习 - GAN 与 RL 基本模型 +- CRF 条件随机场 / 图模型 +- Linux Shell 常用命令 + - wc/sed/awk/grep/sort/uniq/paste/cat/head/tail +- DL - 专题 - 加速方法 +- 机器学习基础相关 + - 深度学习与机器学习的异同及联系 +- 机器学习实践相关 + - 数据预处理 + - 缺失值 + - 特征工程,特征选择的标志(为什么选择这些特征) + - XGB 调参的方法、LightGBM、CatBoost + - gbdt,xgboost,lgbm的区别 + - 模型融合的方法 + - 数据可视化分析 - 流形学习 +- 算法专题-排列与组合 +- 吉布斯采样 +- 算法-矩阵乘法 + - 稀疏矩阵 +- 显著性检测 +- 机器学习的体系、学派 +- 朴素贝叶斯与逻辑回归的区别 +- 为什么 RNN 中采用 tanh 作为激活函数 + > https://www.zhihu.com/question/61265076 +- 修订 FastText 中词向量生成的描述 +- 自定义用于快速测试代码的数据结构及相关方法 +- 并查集 +- 什么是梯度消失 +- HR面常见问题 +- 超参数调优 +- 机器学习实战-部分代码(K-Means等) +- 判断相似二叉树及优化 +- `max(x, y)` 的期望 +- 加速网络收敛的方法 +- 回归树、基尼指数 +- 判断两个链表是否相交(CSDN) +- 一阶 二阶泰勒展开 +- 梯度下降往往没有指向 + +## 2018-9-27 +- 笔试 - iHandy + +## 2018-9-26 +- 算法 - 专题 - 双指针 + +## 2018-9-25 +- 算法 - 专题 - 双指针 +- 笔试 - 作业帮 + +## 2018-9-24 +- 算法 - 专题 - 双指针 + +## 2018-9-23 +- 算法 - 专题 - 双指针 + +## 2018-9-21 +- 算法 - 专题 - 双指针 + +## 2018-9-15 +- 笔试 - 爱奇艺 +- 笔试 - 搜狐畅游 +- NLP - 专题-句向量 + +## 2018-9-8 +- 算法 - 高级数据结构 + - 树状数组 +- papers + - 视觉问答 + +## 2018-9-7 +- 算法 - 区间问题 +- 算法 - 数学 + - 最大共线点的个数 + +## 2018-9-5 +- 算法 - 排列组合专题整理 + +## 2018-9-4 +- 算法 - 数据结构 - 设计 + - LRU 缓存淘汰算法 +- 算法 - 排列组合 + - 下一个排列 + - 上一个排列 + - 全排列(无重复) + - 全排列(有重复) +- 算法 - 面试真题 - n个[0,n)的数,求每个数的出现次数(不能开辟额外空间) + +## 2018-9-3 +- 深度学习基础 - 正则化 - Batch Normalization(修订) + +## 2018-9-2 +- 集成学习专题整理 + +## 2018-9-1 +- 笔试面经 - 头条 4 面小结 +- 算法 - 字符串 - 进制转换/长短地址转换 +- 算法 - LeetCode - 连续子数组和 + +## 2018-8-31 +- 数据结构 - 字符串 - 中缀表达式转后缀表达式(逆波兰式) +- 数据结构 - 二叉树 - 最大路径和 +- 深度学习 - 基础 - 批量归一化(BN) +- 算法 - 动态规划 - 最大正方形 + +## 2018-8-30 +- 算法 - 海量数据处理专题 - 判断一个数是否在 40 亿个数中出现 +- 笔试面经 - 招行 - 推倒吧骨牌 + +## 2018-8-29 +- DL-专题-RNN + - RNN 的 3 种设计模式 +- DL-专题-序列建模 + - Seq2Seq + - Beam Search + +## 2018-8-28 +- 完善 K-Means 注释 +- 算法-随机数 + - 用 `rand_m()` 生成 `rand_n()` +- DL-专题-RNN + +## 2018-8-27 +- code + - K-Means +- 360 笔试 +- 算法-数据结构 + +## 2018-8-26 +- 深度学习基础 + - 梯度下降及其优化(修订) + +## 2018-8-25 +- 深度学习基础 + - 过拟合与欠拟合 + - 梯度下降(修订) + +## 2018-8-24 +- DL-专题-CNN + - 卷积的内部实现 + - 卷积的反向传播 +- NLP-专题-词向量修订 +- 算法-专题-数据结构 + - 二叉树 + - 最近公共祖先 + - 打印节点路径 + - 链表 + - 链表快排 + - 两个链表的第一个公共节点 + +## 2018-8-23 +- 算法-动态规划 + - 完全背包 + - 01背包、完全背包的一维 DP 优化、滚动数组优化 + +## 2018-8-22 +- proj-阅读理解-完形填空(改进三) +- LeetCode + - 暴力搜索 + - 岛屿的个数(DFS + BFS) +- 字节跳动 0812 笔试题回顾 + - 3 积分卡牌游戏(未测试) + - 5 直播爱好者(未测试) +- 算法-随机数 + - 利用不等概率方法产生等概率的随机数 + - 如何采样,使 n-1 被采样 n 次? +- 算法-动态规划 + - 01 背包 + +## 2018-8-21 +- proj-阅读理解-完形填空 +- LeetCode + - 数组 + - 删除排序数组中的重复项 + - 我的日程安排表 I +- 字节跳动 0812 笔试题回顾 + - 1 世界杯开幕式(DFS 搜索联通区域) + - 2 文章病句标识(多级排序,区间合并) + +## 2018-8-20 +- DP-鹰蛋问题 +- 基础-术语表 + - 指数衰减平均的说明 +- LeetCode + - 数据-双指针 + - 三数之和 + - 最接近的三数之和 + +## 2018-8-19 +- 算法-洗牌与采样 + - 带权采样(查表法) +- DL-专题-优化算法 + - 带动量的 SGD 算法 + - 自适应学习率的优化方法 + - AdaGrad + - RMSProp + - AdaDelta + - Adam + +## 2018-8-18 +- DL-专题-优化算法 + - 梯度下降法 + - SGD、小批量 SGD + - 牛顿法与梯度下降 +- 题解-LeetCode + - 数组 + - 三数之和 + - 最接近的三数之和 + +## 2018-8-17 +- 机器学习基础 - 方差与偏差 +- 机器学习基础 - 判别模型与生成模型 + +## 2018-8-16 +- 算法 - 洗牌与采样 \ No newline at end of file diff --git a/_assets/Insertion-sort-example-300px.gif b/_assets/Insertion-sort-example-300px.gif new file mode 100644 index 00000000..96c1b12d Binary files /dev/null and b/_assets/Insertion-sort-example-300px.gif differ diff --git a/_assets/LSTM3-SimpleRNN.png b/_assets/LSTM3-SimpleRNN.png new file mode 100644 index 00000000..9472592b Binary files /dev/null and b/_assets/LSTM3-SimpleRNN.png differ diff --git a/_assets/LSTM3-chain.png b/_assets/LSTM3-chain.png new file mode 100644 index 00000000..e962a3c7 Binary files /dev/null and b/_assets/LSTM3-chain.png differ diff --git a/_assets/LSTM3-focus-C.png b/_assets/LSTM3-focus-C.png new file mode 100644 index 00000000..7fc49f55 Binary files /dev/null and b/_assets/LSTM3-focus-C.png differ diff --git a/_assets/LSTM3-focus-f.png b/_assets/LSTM3-focus-f.png new file mode 100644 index 00000000..58086756 Binary files /dev/null and b/_assets/LSTM3-focus-f.png differ diff --git a/_assets/LSTM3-focus-i.png b/_assets/LSTM3-focus-i.png new file mode 100644 index 00000000..d3d82f05 Binary files /dev/null and b/_assets/LSTM3-focus-i.png differ diff --git a/_assets/LSTM3-focus-o.png b/_assets/LSTM3-focus-o.png new file mode 100644 index 00000000..40fc56bd Binary files /dev/null and b/_assets/LSTM3-focus-o.png differ diff --git a/_assets/LSTM3-var-GRU.png b/_assets/LSTM3-var-GRU.png new file mode 100644 index 00000000..6838a20a Binary files /dev/null and b/_assets/LSTM3-var-GRU.png differ diff --git "a/_assets/TIM\345\233\276\347\211\20720180811181406.png" "b/_assets/TIM\345\233\276\347\211\20720180811181406.png" new file mode 100644 index 00000000..fb144fd7 Binary files /dev/null and "b/_assets/TIM\345\233\276\347\211\20720180811181406.png" differ diff --git "a/_assets/TIM\345\233\276\347\211\20720180811182142.png" "b/_assets/TIM\345\233\276\347\211\20720180811182142.png" new file mode 100644 index 00000000..679bf1ff Binary files /dev/null and "b/_assets/TIM\345\233\276\347\211\20720180811182142.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180608171710.png" "b/_assets/TIM\346\210\252\345\233\27620180608171710.png" new file mode 100644 index 00000000..8eb676ae Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180608171710.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180608172312.png" "b/_assets/TIM\346\210\252\345\233\27620180608172312.png" new file mode 100644 index 00000000..04bed0ff Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180608172312.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180608195851.png" "b/_assets/TIM\346\210\252\345\233\27620180608195851.png" new file mode 100644 index 00000000..fdbb6e1f Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180608195851.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180608204913.png" "b/_assets/TIM\346\210\252\345\233\27620180608204913.png" new file mode 100644 index 00000000..d208b45f Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180608204913.png" differ diff --git "a/images/TIM\346\210\252\345\233\27620180608205223.png" "b/_assets/TIM\346\210\252\345\233\27620180608205223.png" similarity index 100% rename from "images/TIM\346\210\252\345\233\27620180608205223.png" rename to "_assets/TIM\346\210\252\345\233\27620180608205223.png" diff --git "a/_assets/TIM\346\210\252\345\233\27620180608212808.png" "b/_assets/TIM\346\210\252\345\233\27620180608212808.png" new file mode 100644 index 00000000..8143202f Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180608212808.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180610131620.png" "b/_assets/TIM\346\210\252\345\233\27620180610131620.png" new file mode 100644 index 00000000..087586ce Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180610131620.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180610132602.png" "b/_assets/TIM\346\210\252\345\233\27620180610132602.png" new file mode 100644 index 00000000..eb606575 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180610132602.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180610194353.png" "b/_assets/TIM\346\210\252\345\233\27620180610194353.png" new file mode 100644 index 00000000..2bb67e18 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180610194353.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180610203703.png" "b/_assets/TIM\346\210\252\345\233\27620180610203703.png" new file mode 100644 index 00000000..9d9314df Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180610203703.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180610210251.png" "b/_assets/TIM\346\210\252\345\233\27620180610210251.png" new file mode 100644 index 00000000..dde134b5 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180610210251.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180611150734.png" "b/_assets/TIM\346\210\252\345\233\27620180611150734.png" new file mode 100644 index 00000000..0d1c9ad1 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180611150734.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180611152559.png" "b/_assets/TIM\346\210\252\345\233\27620180611152559.png" new file mode 100644 index 00000000..28da3d0b Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180611152559.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180611171103.png" "b/_assets/TIM\346\210\252\345\233\27620180611171103.png" new file mode 100644 index 00000000..f15b1abf Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180611171103.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180611171213.png" "b/_assets/TIM\346\210\252\345\233\27620180611171213.png" new file mode 100644 index 00000000..5f9e3bcd Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180611171213.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180611172657.png" "b/_assets/TIM\346\210\252\345\233\27620180611172657.png" new file mode 100644 index 00000000..cb45ba67 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180611172657.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180611194754.png" "b/_assets/TIM\346\210\252\345\233\27620180611194754.png" new file mode 100644 index 00000000..d578c999 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180611194754.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180611195027.png" "b/_assets/TIM\346\210\252\345\233\27620180611195027.png" new file mode 100644 index 00000000..c58f414e Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180611195027.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180611203427.png" "b/_assets/TIM\346\210\252\345\233\27620180611203427.png" new file mode 100644 index 00000000..fcacdf62 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180611203427.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180611204215.png" "b/_assets/TIM\346\210\252\345\233\27620180611204215.png" new file mode 100644 index 00000000..61a4221a Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180611204215.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180611204503.png" "b/_assets/TIM\346\210\252\345\233\27620180611204503.png" new file mode 100644 index 00000000..85622b64 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180611204503.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180611211649.png" "b/_assets/TIM\346\210\252\345\233\27620180611211649.png" new file mode 100644 index 00000000..507afd06 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180611211649.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180611211753.png" "b/_assets/TIM\346\210\252\345\233\27620180611211753.png" new file mode 100644 index 00000000..e609e6c0 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180611211753.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180611214508.png" "b/_assets/TIM\346\210\252\345\233\27620180611214508.png" new file mode 100644 index 00000000..a787cf38 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180611214508.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180611215422.png" "b/_assets/TIM\346\210\252\345\233\27620180611215422.png" new file mode 100644 index 00000000..6254b66d Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180611215422.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180611215923.png" "b/_assets/TIM\346\210\252\345\233\27620180611215923.png" new file mode 100644 index 00000000..e95e5c3c Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180611215923.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180611220109.png" "b/_assets/TIM\346\210\252\345\233\27620180611220109.png" new file mode 100644 index 00000000..861a0af3 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180611220109.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180612104740.png" "b/_assets/TIM\346\210\252\345\233\27620180612104740.png" new file mode 100644 index 00000000..e5b5387b Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180612104740.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180612120851.png" "b/_assets/TIM\346\210\252\345\233\27620180612120851.png" new file mode 100644 index 00000000..8c441c61 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180612120851.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180612121301.png" "b/_assets/TIM\346\210\252\345\233\27620180612121301.png" new file mode 100644 index 00000000..c6464864 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180612121301.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180612154333.png" "b/_assets/TIM\346\210\252\345\233\27620180612154333.png" new file mode 100644 index 00000000..51c2def2 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180612154333.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180612213144.png" "b/_assets/TIM\346\210\252\345\233\27620180612213144.png" new file mode 100644 index 00000000..c38d17cc Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180612213144.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180613101802.png" "b/_assets/TIM\346\210\252\345\233\27620180613101802.png" new file mode 100644 index 00000000..5c165475 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180613101802.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180613150111.png" "b/_assets/TIM\346\210\252\345\233\27620180613150111.png" new file mode 100644 index 00000000..4494711b Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180613150111.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180613150711.png" "b/_assets/TIM\346\210\252\345\233\27620180613150711.png" new file mode 100644 index 00000000..601b911e Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180613150711.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180613151014.png" "b/_assets/TIM\346\210\252\345\233\27620180613151014.png" new file mode 100644 index 00000000..ef86c2c1 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180613151014.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180613151356.png" "b/_assets/TIM\346\210\252\345\233\27620180613151356.png" new file mode 100644 index 00000000..9d16bb85 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180613151356.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180613200023.png" "b/_assets/TIM\346\210\252\345\233\27620180613200023.png" new file mode 100644 index 00000000..957428fa Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180613200023.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180613211935.png" "b/_assets/TIM\346\210\252\345\233\27620180613211935.png" new file mode 100644 index 00000000..0afa476f Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180613211935.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180614103628.png" "b/_assets/TIM\346\210\252\345\233\27620180614103628.png" new file mode 100644 index 00000000..e398db3e Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180614103628.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180614104032.png" "b/_assets/TIM\346\210\252\345\233\27620180614104032.png" new file mode 100644 index 00000000..8f2e30fa Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180614104032.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180614111241.png" "b/_assets/TIM\346\210\252\345\233\27620180614111241.png" new file mode 100644 index 00000000..7ca5f380 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180614111241.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180614111712.png" "b/_assets/TIM\346\210\252\345\233\27620180614111712.png" new file mode 100644 index 00000000..920f2bcc Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180614111712.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180614203447.png" "b/_assets/TIM\346\210\252\345\233\27620180614203447.png" new file mode 100644 index 00000000..db4f48d8 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180614203447.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180615111903.png" "b/_assets/TIM\346\210\252\345\233\27620180615111903.png" new file mode 100644 index 00000000..9aa88feb Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180615111903.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180615150955.png" "b/_assets/TIM\346\210\252\345\233\27620180615150955.png" new file mode 100644 index 00000000..5b3a0f38 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180615150955.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180617140439.png" "b/_assets/TIM\346\210\252\345\233\27620180617140439.png" new file mode 100644 index 00000000..79502304 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180617140439.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180617141056.png" "b/_assets/TIM\346\210\252\345\233\27620180617141056.png" new file mode 100644 index 00000000..18f2907e Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180617141056.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180617141502.png" "b/_assets/TIM\346\210\252\345\233\27620180617141502.png" new file mode 100644 index 00000000..83d75f0b Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180617141502.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180619104551.png" "b/_assets/TIM\346\210\252\345\233\27620180619104551.png" new file mode 100644 index 00000000..af6382e5 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180619104551.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180619104649.png" "b/_assets/TIM\346\210\252\345\233\27620180619104649.png" new file mode 100644 index 00000000..a0855874 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180619104649.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180619104914.png" "b/_assets/TIM\346\210\252\345\233\27620180619104914.png" new file mode 100644 index 00000000..a0dc58e9 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180619104914.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180619113501.png" "b/_assets/TIM\346\210\252\345\233\27620180619113501.png" new file mode 100644 index 00000000..bc6f1fe2 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180619113501.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180619114844.png" "b/_assets/TIM\346\210\252\345\233\27620180619114844.png" new file mode 100644 index 00000000..f107f21a Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180619114844.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180619193332.png" "b/_assets/TIM\346\210\252\345\233\27620180619193332.png" new file mode 100644 index 00000000..0b24658a Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180619193332.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180619193540.png" "b/_assets/TIM\346\210\252\345\233\27620180619193540.png" new file mode 100644 index 00000000..344d2075 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180619193540.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180619205103.png" "b/_assets/TIM\346\210\252\345\233\27620180619205103.png" new file mode 100644 index 00000000..13eb8a11 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180619205103.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180619214629.png" "b/_assets/TIM\346\210\252\345\233\27620180619214629.png" new file mode 100644 index 00000000..b90b7651 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180619214629.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180619214735.png" "b/_assets/TIM\346\210\252\345\233\27620180619214735.png" new file mode 100644 index 00000000..ea25341f Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180619214735.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180620164337.png" "b/_assets/TIM\346\210\252\345\233\27620180620164337.png" new file mode 100644 index 00000000..80611115 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180620164337.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180620171300.png" "b/_assets/TIM\346\210\252\345\233\27620180620171300.png" new file mode 100644 index 00000000..d78954ea Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180620171300.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180620171915.png" "b/_assets/TIM\346\210\252\345\233\27620180620171915.png" new file mode 100644 index 00000000..c8cae20e Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180620171915.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180620190555.png" "b/_assets/TIM\346\210\252\345\233\27620180620190555.png" new file mode 100644 index 00000000..2f2161e7 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180620190555.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180620191137.png" "b/_assets/TIM\346\210\252\345\233\27620180620191137.png" new file mode 100644 index 00000000..fe27774d Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180620191137.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180620211714.png" "b/_assets/TIM\346\210\252\345\233\27620180620211714.png" new file mode 100644 index 00000000..770a5a28 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180620211714.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180625113016.png" "b/_assets/TIM\346\210\252\345\233\27620180625113016.png" new file mode 100644 index 00000000..9175f4ad Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180625113016.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180625150305.png" "b/_assets/TIM\346\210\252\345\233\27620180625150305.png" new file mode 100644 index 00000000..2226fb2d Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180625150305.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180625150437.png" "b/_assets/TIM\346\210\252\345\233\27620180625150437.png" new file mode 100644 index 00000000..6db7d490 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180625150437.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180625151928.png" "b/_assets/TIM\346\210\252\345\233\27620180625151928.png" new file mode 100644 index 00000000..b6faf532 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180625151928.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180625160257.png" "b/_assets/TIM\346\210\252\345\233\27620180625160257.png" new file mode 100644 index 00000000..1df0e0c3 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180625160257.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180625162938.png" "b/_assets/TIM\346\210\252\345\233\27620180625162938.png" new file mode 100644 index 00000000..dfcb2603 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180625162938.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180626105733.png" "b/_assets/TIM\346\210\252\345\233\27620180626105733.png" new file mode 100644 index 00000000..4c46f52b Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180626105733.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180626105919.png" "b/_assets/TIM\346\210\252\345\233\27620180626105919.png" new file mode 100644 index 00000000..407ad1f5 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180626105919.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180626111112.png" "b/_assets/TIM\346\210\252\345\233\27620180626111112.png" new file mode 100644 index 00000000..e6c6cf00 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180626111112.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180626111415.png" "b/_assets/TIM\346\210\252\345\233\27620180626111415.png" new file mode 100644 index 00000000..b9aa52b4 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180626111415.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180626111458.png" "b/_assets/TIM\346\210\252\345\233\27620180626111458.png" new file mode 100644 index 00000000..85e358ba Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180626111458.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180626112002.png" "b/_assets/TIM\346\210\252\345\233\27620180626112002.png" new file mode 100644 index 00000000..4d533230 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180626112002.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180626112123.png" "b/_assets/TIM\346\210\252\345\233\27620180626112123.png" new file mode 100644 index 00000000..dee63ac4 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180626112123.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180626114634.png" "b/_assets/TIM\346\210\252\345\233\27620180626114634.png" new file mode 100644 index 00000000..55c0dae2 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180626114634.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180626152514.png" "b/_assets/TIM\346\210\252\345\233\27620180626152514.png" new file mode 100644 index 00000000..2ff16b61 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180626152514.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180626211340.png" "b/_assets/TIM\346\210\252\345\233\27620180626211340.png" new file mode 100644 index 00000000..53ec57cd Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180626211340.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180626211449.png" "b/_assets/TIM\346\210\252\345\233\27620180626211449.png" new file mode 100644 index 00000000..eb330c04 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180626211449.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180626213324.png" "b/_assets/TIM\346\210\252\345\233\27620180626213324.png" new file mode 100644 index 00000000..04f26b0c Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180626213324.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180626214855.png" "b/_assets/TIM\346\210\252\345\233\27620180626214855.png" new file mode 100644 index 00000000..766e5c17 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180626214855.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180626221601.png" "b/_assets/TIM\346\210\252\345\233\27620180626221601.png" new file mode 100644 index 00000000..860d8ef7 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180626221601.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180627100028.png" "b/_assets/TIM\346\210\252\345\233\27620180627100028.png" new file mode 100644 index 00000000..18eb8d5c Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180627100028.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180627103330.png" "b/_assets/TIM\346\210\252\345\233\27620180627103330.png" new file mode 100644 index 00000000..45761ad5 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180627103330.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180627103720.png" "b/_assets/TIM\346\210\252\345\233\27620180627103720.png" new file mode 100644 index 00000000..ff5b409b Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180627103720.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180627104200.png" "b/_assets/TIM\346\210\252\345\233\27620180627104200.png" new file mode 100644 index 00000000..28a42b88 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180627104200.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180627111545.png" "b/_assets/TIM\346\210\252\345\233\27620180627111545.png" new file mode 100644 index 00000000..2b0997dd Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180627111545.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180627114343.png" "b/_assets/TIM\346\210\252\345\233\27620180627114343.png" new file mode 100644 index 00000000..ac4187bc Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180627114343.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180627155759.png" "b/_assets/TIM\346\210\252\345\233\27620180627155759.png" new file mode 100644 index 00000000..1cdc7679 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180627155759.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180627155845.png" "b/_assets/TIM\346\210\252\345\233\27620180627155845.png" new file mode 100644 index 00000000..05733632 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180627155845.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180627155928.png" "b/_assets/TIM\346\210\252\345\233\27620180627155928.png" new file mode 100644 index 00000000..4a4cfa17 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180627155928.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180627160125.png" "b/_assets/TIM\346\210\252\345\233\27620180627160125.png" new file mode 100644 index 00000000..0a3e7620 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180627160125.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180627161532.png" "b/_assets/TIM\346\210\252\345\233\27620180627161532.png" new file mode 100644 index 00000000..c7827807 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180627161532.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180627210731.png" "b/_assets/TIM\346\210\252\345\233\27620180627210731.png" new file mode 100644 index 00000000..63c4c800 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180627210731.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180627212338.png" "b/_assets/TIM\346\210\252\345\233\27620180627212338.png" new file mode 100644 index 00000000..84e206f0 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180627212338.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180627213951.png" "b/_assets/TIM\346\210\252\345\233\27620180627213951.png" new file mode 100644 index 00000000..a6d208a6 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180627213951.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180628104522.png" "b/_assets/TIM\346\210\252\345\233\27620180628104522.png" new file mode 100644 index 00000000..d6c6706c Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180628104522.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180628110615.png" "b/_assets/TIM\346\210\252\345\233\27620180628110615.png" new file mode 100644 index 00000000..4eb452ee Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180628110615.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180628110920.png" "b/_assets/TIM\346\210\252\345\233\27620180628110920.png" new file mode 100644 index 00000000..7275cb20 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180628110920.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180628120153.png" "b/_assets/TIM\346\210\252\345\233\27620180628120153.png" new file mode 100644 index 00000000..633564de Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180628120153.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180628120634.png" "b/_assets/TIM\346\210\252\345\233\27620180628120634.png" new file mode 100644 index 00000000..7c5ea259 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180628120634.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180628121250.png" "b/_assets/TIM\346\210\252\345\233\27620180628121250.png" new file mode 100644 index 00000000..4343e40a Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180628121250.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180628201027.png" "b/_assets/TIM\346\210\252\345\233\27620180628201027.png" new file mode 100644 index 00000000..fc8d40c5 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180628201027.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180628201344.png" "b/_assets/TIM\346\210\252\345\233\27620180628201344.png" new file mode 100644 index 00000000..0e5cd5a7 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180628201344.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180628203925.png" "b/_assets/TIM\346\210\252\345\233\27620180628203925.png" new file mode 100644 index 00000000..712249ac Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180628203925.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180628205802.png" "b/_assets/TIM\346\210\252\345\233\27620180628205802.png" new file mode 100644 index 00000000..792312e3 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180628205802.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180628211047.png" "b/_assets/TIM\346\210\252\345\233\27620180628211047.png" new file mode 100644 index 00000000..935c19c3 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180628211047.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180629094906.png" "b/_assets/TIM\346\210\252\345\233\27620180629094906.png" new file mode 100644 index 00000000..9c282664 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180629094906.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180629100103.png" "b/_assets/TIM\346\210\252\345\233\27620180629100103.png" new file mode 100644 index 00000000..5ef951d3 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180629100103.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180629101348.png" "b/_assets/TIM\346\210\252\345\233\27620180629101348.png" new file mode 100644 index 00000000..890fa71a Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180629101348.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180629102412.png" "b/_assets/TIM\346\210\252\345\233\27620180629102412.png" new file mode 100644 index 00000000..ccee19e7 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180629102412.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180629114258.png" "b/_assets/TIM\346\210\252\345\233\27620180629114258.png" new file mode 100644 index 00000000..ba428c60 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180629114258.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180629114604.png" "b/_assets/TIM\346\210\252\345\233\27620180629114604.png" new file mode 100644 index 00000000..6106f0dc Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180629114604.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180629150107.png" "b/_assets/TIM\346\210\252\345\233\27620180629150107.png" new file mode 100644 index 00000000..f3f6ac0a Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180629150107.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180629150441.png" "b/_assets/TIM\346\210\252\345\233\27620180629150441.png" new file mode 100644 index 00000000..5a034af8 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180629150441.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180629150541.png" "b/_assets/TIM\346\210\252\345\233\27620180629150541.png" new file mode 100644 index 00000000..98b43263 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180629150541.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180629150705.png" "b/_assets/TIM\346\210\252\345\233\27620180629150705.png" new file mode 100644 index 00000000..427c2ffa Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180629150705.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180629150816.png" "b/_assets/TIM\346\210\252\345\233\27620180629150816.png" new file mode 100644 index 00000000..a15f27e6 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180629150816.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180629151001.png" "b/_assets/TIM\346\210\252\345\233\27620180629151001.png" new file mode 100644 index 00000000..86cb16ac Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180629151001.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180629152708.png" "b/_assets/TIM\346\210\252\345\233\27620180629152708.png" new file mode 100644 index 00000000..a3b54459 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180629152708.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180629153515.png" "b/_assets/TIM\346\210\252\345\233\27620180629153515.png" new file mode 100644 index 00000000..55646324 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180629153515.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180629155530.png" "b/_assets/TIM\346\210\252\345\233\27620180629155530.png" new file mode 100644 index 00000000..2275e4b5 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180629155530.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180629160418.png" "b/_assets/TIM\346\210\252\345\233\27620180629160418.png" new file mode 100644 index 00000000..dbd7bc96 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180629160418.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180629164908.png" "b/_assets/TIM\346\210\252\345\233\27620180629164908.png" new file mode 100644 index 00000000..dda126f0 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180629164908.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180629165317.png" "b/_assets/TIM\346\210\252\345\233\27620180629165317.png" new file mode 100644 index 00000000..7b25a3e5 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180629165317.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180629193011.png" "b/_assets/TIM\346\210\252\345\233\27620180629193011.png" new file mode 100644 index 00000000..b661a958 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180629193011.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180629210220.png" "b/_assets/TIM\346\210\252\345\233\27620180629210220.png" new file mode 100644 index 00000000..70a64076 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180629210220.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180629210933.png" "b/_assets/TIM\346\210\252\345\233\27620180629210933.png" new file mode 100644 index 00000000..c85d094a Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180629210933.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180629211550.png" "b/_assets/TIM\346\210\252\345\233\27620180629211550.png" new file mode 100644 index 00000000..40238596 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180629211550.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180630094515.png" "b/_assets/TIM\346\210\252\345\233\27620180630094515.png" new file mode 100644 index 00000000..f9aae7b1 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180630094515.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180630094555.png" "b/_assets/TIM\346\210\252\345\233\27620180630094555.png" new file mode 100644 index 00000000..9f968211 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180630094555.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180630094715.png" "b/_assets/TIM\346\210\252\345\233\27620180630094715.png" new file mode 100644 index 00000000..70852b02 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180630094715.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180630094826.png" "b/_assets/TIM\346\210\252\345\233\27620180630094826.png" new file mode 100644 index 00000000..dc64dd79 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180630094826.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180630095121.png" "b/_assets/TIM\346\210\252\345\233\27620180630095121.png" new file mode 100644 index 00000000..6a78fdef Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180630095121.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180630095210.png" "b/_assets/TIM\346\210\252\345\233\27620180630095210.png" new file mode 100644 index 00000000..c8b517b0 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180630095210.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180630110837.png" "b/_assets/TIM\346\210\252\345\233\27620180630110837.png" new file mode 100644 index 00000000..fabf0204 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180630110837.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180630111742.png" "b/_assets/TIM\346\210\252\345\233\27620180630111742.png" new file mode 100644 index 00000000..3633cab6 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180630111742.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180630113040.png" "b/_assets/TIM\346\210\252\345\233\27620180630113040.png" new file mode 100644 index 00000000..d0c04ec9 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180630113040.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180630113240.png" "b/_assets/TIM\346\210\252\345\233\27620180630113240.png" new file mode 100644 index 00000000..6d5d41e6 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180630113240.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180630153013.png" "b/_assets/TIM\346\210\252\345\233\27620180630153013.png" new file mode 100644 index 00000000..3bf37f6d Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180630153013.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180630153808.png" "b/_assets/TIM\346\210\252\345\233\27620180630153808.png" new file mode 100644 index 00000000..ee7918ff Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180630153808.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180630153907.png" "b/_assets/TIM\346\210\252\345\233\27620180630153907.png" new file mode 100644 index 00000000..69a587d4 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180630153907.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180630154029.png" "b/_assets/TIM\346\210\252\345\233\27620180630154029.png" new file mode 100644 index 00000000..0fb814c4 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180630154029.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180630154212.png" "b/_assets/TIM\346\210\252\345\233\27620180630154212.png" new file mode 100644 index 00000000..bf02e995 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180630154212.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180630194836.png" "b/_assets/TIM\346\210\252\345\233\27620180630194836.png" new file mode 100644 index 00000000..dd8455e8 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180630194836.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180630201849.png" "b/_assets/TIM\346\210\252\345\233\27620180630201849.png" new file mode 100644 index 00000000..877b94e9 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180630201849.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180630202744.png" "b/_assets/TIM\346\210\252\345\233\27620180630202744.png" new file mode 100644 index 00000000..17ee9484 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180630202744.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180630203750.png" "b/_assets/TIM\346\210\252\345\233\27620180630203750.png" new file mode 100644 index 00000000..ae78d321 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180630203750.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180630203959.png" "b/_assets/TIM\346\210\252\345\233\27620180630203959.png" new file mode 100644 index 00000000..bb5dbaa8 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180630203959.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180630204148.png" "b/_assets/TIM\346\210\252\345\233\27620180630204148.png" new file mode 100644 index 00000000..7f915129 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180630204148.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180630204303.png" "b/_assets/TIM\346\210\252\345\233\27620180630204303.png" new file mode 100644 index 00000000..d060972f Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180630204303.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180701210407.png" "b/_assets/TIM\346\210\252\345\233\27620180701210407.png" new file mode 100644 index 00000000..fe6d22cb Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180701210407.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180701211429.png" "b/_assets/TIM\346\210\252\345\233\27620180701211429.png" new file mode 100644 index 00000000..4b4185c1 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180701211429.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180701211850.png" "b/_assets/TIM\346\210\252\345\233\27620180701211850.png" new file mode 100644 index 00000000..f527bdf2 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180701211850.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180701212340.png" "b/_assets/TIM\346\210\252\345\233\27620180701212340.png" new file mode 100644 index 00000000..9eaa7447 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180701212340.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180701212657.png" "b/_assets/TIM\346\210\252\345\233\27620180701212657.png" new file mode 100644 index 00000000..ce0ac097 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180701212657.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180701213151.png" "b/_assets/TIM\346\210\252\345\233\27620180701213151.png" new file mode 100644 index 00000000..4998609a Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180701213151.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180701214914.png" "b/_assets/TIM\346\210\252\345\233\27620180701214914.png" new file mode 100644 index 00000000..bfbb5a4d Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180701214914.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702092532.png" "b/_assets/TIM\346\210\252\345\233\27620180702092532.png" new file mode 100644 index 00000000..dc8cb70b Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702092532.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702093050.png" "b/_assets/TIM\346\210\252\345\233\27620180702093050.png" new file mode 100644 index 00000000..e399e9aa Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702093050.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702094246.png" "b/_assets/TIM\346\210\252\345\233\27620180702094246.png" new file mode 100644 index 00000000..17f651fd Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702094246.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702094455.png" "b/_assets/TIM\346\210\252\345\233\27620180702094455.png" new file mode 100644 index 00000000..1d3d2edb Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702094455.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702095428.png" "b/_assets/TIM\346\210\252\345\233\27620180702095428.png" new file mode 100644 index 00000000..2a7cc295 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702095428.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702111411.png" "b/_assets/TIM\346\210\252\345\233\27620180702111411.png" new file mode 100644 index 00000000..063218da Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702111411.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702112135.png" "b/_assets/TIM\346\210\252\345\233\27620180702112135.png" new file mode 100644 index 00000000..5ad30d7f Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702112135.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702113031.png" "b/_assets/TIM\346\210\252\345\233\27620180702113031.png" new file mode 100644 index 00000000..3fea4acd Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702113031.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702113631.png" "b/_assets/TIM\346\210\252\345\233\27620180702113631.png" new file mode 100644 index 00000000..97c6cb64 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702113631.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702114132.png" "b/_assets/TIM\346\210\252\345\233\27620180702114132.png" new file mode 100644 index 00000000..c2ff68aa Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702114132.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702150309.png" "b/_assets/TIM\346\210\252\345\233\27620180702150309.png" new file mode 100644 index 00000000..81a0de2e Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702150309.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702151423.png" "b/_assets/TIM\346\210\252\345\233\27620180702151423.png" new file mode 100644 index 00000000..be6c6c31 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702151423.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702152216.png" "b/_assets/TIM\346\210\252\345\233\27620180702152216.png" new file mode 100644 index 00000000..1efe444e Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702152216.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702155142.png" "b/_assets/TIM\346\210\252\345\233\27620180702155142.png" new file mode 100644 index 00000000..213e3789 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702155142.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702171146.png" "b/_assets/TIM\346\210\252\345\233\27620180702171146.png" new file mode 100644 index 00000000..03866eea Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702171146.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702194825.png" "b/_assets/TIM\346\210\252\345\233\27620180702194825.png" new file mode 100644 index 00000000..568bea62 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702194825.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702195301.png" "b/_assets/TIM\346\210\252\345\233\27620180702195301.png" new file mode 100644 index 00000000..0d79dacf Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702195301.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702195902.png" "b/_assets/TIM\346\210\252\345\233\27620180702195902.png" new file mode 100644 index 00000000..ad6a689e Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702195902.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702201513.png" "b/_assets/TIM\346\210\252\345\233\27620180702201513.png" new file mode 100644 index 00000000..11e5806c Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702201513.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702202810.png" "b/_assets/TIM\346\210\252\345\233\27620180702202810.png" new file mode 100644 index 00000000..7be3972a Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702202810.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702203041.png" "b/_assets/TIM\346\210\252\345\233\27620180702203041.png" new file mode 100644 index 00000000..f1ed8ef3 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702203041.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702203401.png" "b/_assets/TIM\346\210\252\345\233\27620180702203401.png" new file mode 100644 index 00000000..f845f272 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702203401.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702204047.png" "b/_assets/TIM\346\210\252\345\233\27620180702204047.png" new file mode 100644 index 00000000..16f544eb Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702204047.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180702210202.png" "b/_assets/TIM\346\210\252\345\233\27620180702210202.png" new file mode 100644 index 00000000..8621b29d Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180702210202.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180703102940.png" "b/_assets/TIM\346\210\252\345\233\27620180703102940.png" new file mode 100644 index 00000000..786f65a3 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180703102940.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180703103735.png" "b/_assets/TIM\346\210\252\345\233\27620180703103735.png" new file mode 100644 index 00000000..0da5e6f4 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180703103735.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180703103757.png" "b/_assets/TIM\346\210\252\345\233\27620180703103757.png" new file mode 100644 index 00000000..6cc3dc26 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180703103757.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180703104845.png" "b/_assets/TIM\346\210\252\345\233\27620180703104845.png" new file mode 100644 index 00000000..4729f074 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180703104845.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180703105249.png" "b/_assets/TIM\346\210\252\345\233\27620180703105249.png" new file mode 100644 index 00000000..60f76e4c Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180703105249.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180703115823.png" "b/_assets/TIM\346\210\252\345\233\27620180703115823.png" new file mode 100644 index 00000000..9671e854 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180703115823.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180703115843.png" "b/_assets/TIM\346\210\252\345\233\27620180703115843.png" new file mode 100644 index 00000000..e8246394 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180703115843.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180703121303.png" "b/_assets/TIM\346\210\252\345\233\27620180703121303.png" new file mode 100644 index 00000000..6ff149ff Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180703121303.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180703143800.png" "b/_assets/TIM\346\210\252\345\233\27620180703143800.png" new file mode 100644 index 00000000..399ffa8d Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180703143800.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180703210401.png" "b/_assets/TIM\346\210\252\345\233\27620180703210401.png" new file mode 100644 index 00000000..74d9e557 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180703210401.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180703210941.png" "b/_assets/TIM\346\210\252\345\233\27620180703210941.png" new file mode 100644 index 00000000..83736ebc Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180703210941.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704095607.png" "b/_assets/TIM\346\210\252\345\233\27620180704095607.png" new file mode 100644 index 00000000..0cca1a35 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704095607.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704111141.png" "b/_assets/TIM\346\210\252\345\233\27620180704111141.png" new file mode 100644 index 00000000..237190c5 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704111141.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704111911.png" "b/_assets/TIM\346\210\252\345\233\27620180704111911.png" new file mode 100644 index 00000000..675cf336 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704111911.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704112122.png" "b/_assets/TIM\346\210\252\345\233\27620180704112122.png" new file mode 100644 index 00000000..65207f4e Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704112122.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704112743.png" "b/_assets/TIM\346\210\252\345\233\27620180704112743.png" new file mode 100644 index 00000000..7708add9 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704112743.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704161115.png" "b/_assets/TIM\346\210\252\345\233\27620180704161115.png" new file mode 100644 index 00000000..5feb829c Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704161115.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704164022.png" "b/_assets/TIM\346\210\252\345\233\27620180704164022.png" new file mode 100644 index 00000000..3f8c3871 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704164022.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704165624.png" "b/_assets/TIM\346\210\252\345\233\27620180704165624.png" new file mode 100644 index 00000000..5a569a8f Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704165624.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704172524.png" "b/_assets/TIM\346\210\252\345\233\27620180704172524.png" new file mode 100644 index 00000000..88a022fc Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704172524.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704174134.png" "b/_assets/TIM\346\210\252\345\233\27620180704174134.png" new file mode 100644 index 00000000..f15e4900 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704174134.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704193558.png" "b/_assets/TIM\346\210\252\345\233\27620180704193558.png" new file mode 100644 index 00000000..6382f20c Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704193558.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704193742.png" "b/_assets/TIM\346\210\252\345\233\27620180704193742.png" new file mode 100644 index 00000000..3b4831cc Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704193742.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704193955.png" "b/_assets/TIM\346\210\252\345\233\27620180704193955.png" new file mode 100644 index 00000000..0d146936 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704193955.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704194111.png" "b/_assets/TIM\346\210\252\345\233\27620180704194111.png" new file mode 100644 index 00000000..43f17bff Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704194111.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704194548.png" "b/_assets/TIM\346\210\252\345\233\27620180704194548.png" new file mode 100644 index 00000000..30ef5a2f Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704194548.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704194635.png" "b/_assets/TIM\346\210\252\345\233\27620180704194635.png" new file mode 100644 index 00000000..bc1abcc1 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704194635.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704194726.png" "b/_assets/TIM\346\210\252\345\233\27620180704194726.png" new file mode 100644 index 00000000..7706a417 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704194726.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704194944.png" "b/_assets/TIM\346\210\252\345\233\27620180704194944.png" new file mode 100644 index 00000000..0717cdc1 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704194944.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704195120.png" "b/_assets/TIM\346\210\252\345\233\27620180704195120.png" new file mode 100644 index 00000000..95691e4c Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704195120.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704195711.png" "b/_assets/TIM\346\210\252\345\233\27620180704195711.png" new file mode 100644 index 00000000..304e2c96 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704195711.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704195859.png" "b/_assets/TIM\346\210\252\345\233\27620180704195859.png" new file mode 100644 index 00000000..2031e2e6 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704195859.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704200316.png" "b/_assets/TIM\346\210\252\345\233\27620180704200316.png" new file mode 100644 index 00000000..bcd6f68b Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704200316.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704200453.png" "b/_assets/TIM\346\210\252\345\233\27620180704200453.png" new file mode 100644 index 00000000..8342e845 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704200453.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180704200743.png" "b/_assets/TIM\346\210\252\345\233\27620180704200743.png" new file mode 100644 index 00000000..adb5ee53 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180704200743.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180705162841.png" "b/_assets/TIM\346\210\252\345\233\27620180705162841.png" new file mode 100644 index 00000000..32144c39 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180705162841.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180705164018.png" "b/_assets/TIM\346\210\252\345\233\27620180705164018.png" new file mode 100644 index 00000000..74ed2bf6 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180705164018.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180710112848.png" "b/_assets/TIM\346\210\252\345\233\27620180710112848.png" new file mode 100644 index 00000000..61b637e9 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180710112848.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180719220242.png" "b/_assets/TIM\346\210\252\345\233\27620180719220242.png" new file mode 100644 index 00000000..2ad87e43 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180719220242.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180720101423.png" "b/_assets/TIM\346\210\252\345\233\27620180720101423.png" new file mode 100644 index 00000000..27d51e5c Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180720101423.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180724200255.png" "b/_assets/TIM\346\210\252\345\233\27620180724200255.png" new file mode 100644 index 00000000..fe1c3a40 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180724200255.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180725213025.png" "b/_assets/TIM\346\210\252\345\233\27620180725213025.png" new file mode 100644 index 00000000..c72b3d33 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180725213025.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180728212444.png" "b/_assets/TIM\346\210\252\345\233\27620180728212444.png" new file mode 100644 index 00000000..b9ee6e54 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180728212444.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180728212554.png" "b/_assets/TIM\346\210\252\345\233\27620180728212554.png" new file mode 100644 index 00000000..a780138f Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180728212554.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180728213300.png" "b/_assets/TIM\346\210\252\345\233\27620180728213300.png" new file mode 100644 index 00000000..2662c8be Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180728213300.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180728215749.png" "b/_assets/TIM\346\210\252\345\233\27620180728215749.png" new file mode 100644 index 00000000..431f74c8 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180728215749.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180729232751.png" "b/_assets/TIM\346\210\252\345\233\27620180729232751.png" new file mode 100644 index 00000000..cd29e611 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180729232751.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180730145559.png" "b/_assets/TIM\346\210\252\345\233\27620180730145559.png" new file mode 100644 index 00000000..4292ed17 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180730145559.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180730155523.png" "b/_assets/TIM\346\210\252\345\233\27620180730155523.png" new file mode 100644 index 00000000..744dc4ac Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180730155523.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180730155611.png" "b/_assets/TIM\346\210\252\345\233\27620180730155611.png" new file mode 100644 index 00000000..abc25453 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180730155611.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180730195253.png" "b/_assets/TIM\346\210\252\345\233\27620180730195253.png" new file mode 100644 index 00000000..6596e188 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180730195253.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180731101152.png" "b/_assets/TIM\346\210\252\345\233\27620180731101152.png" new file mode 100644 index 00000000..0890f2b9 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180731101152.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180731103855.png" "b/_assets/TIM\346\210\252\345\233\27620180731103855.png" new file mode 100644 index 00000000..c58bae45 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180731103855.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180731113917.png" "b/_assets/TIM\346\210\252\345\233\27620180731113917.png" new file mode 100644 index 00000000..0824c93e Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180731113917.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180731145306.png" "b/_assets/TIM\346\210\252\345\233\27620180731145306.png" new file mode 100644 index 00000000..d12181fd Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180731145306.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180801113329.png" "b/_assets/TIM\346\210\252\345\233\27620180801113329.png" new file mode 100644 index 00000000..80d6e16b Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180801113329.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180801114036.png" "b/_assets/TIM\346\210\252\345\233\27620180801114036.png" new file mode 100644 index 00000000..c15fba64 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180801114036.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180801114059.png" "b/_assets/TIM\346\210\252\345\233\27620180801114059.png" new file mode 100644 index 00000000..537117f4 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180801114059.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180801150958.png" "b/_assets/TIM\346\210\252\345\233\27620180801150958.png" new file mode 100644 index 00000000..1340f901 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180801150958.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180803225602.png" "b/_assets/TIM\346\210\252\345\233\27620180803225602.png" new file mode 100644 index 00000000..6f722f56 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180803225602.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180805212441.png" "b/_assets/TIM\346\210\252\345\233\27620180805212441.png" new file mode 100644 index 00000000..e94861c6 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180805212441.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180805231056.png" "b/_assets/TIM\346\210\252\345\233\27620180805231056.png" new file mode 100644 index 00000000..5f6bbaf1 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180805231056.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180805234123.png" "b/_assets/TIM\346\210\252\345\233\27620180805234123.png" new file mode 100644 index 00000000..abc0c218 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180805234123.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180806141616.png" "b/_assets/TIM\346\210\252\345\233\27620180806141616.png" new file mode 100644 index 00000000..c3a7c1d5 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180806141616.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180806144052.png" "b/_assets/TIM\346\210\252\345\233\27620180806144052.png" new file mode 100644 index 00000000..e5869fcd Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180806144052.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180806151406.png" "b/_assets/TIM\346\210\252\345\233\27620180806151406.png" new file mode 100644 index 00000000..52934260 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180806151406.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180806161221.png" "b/_assets/TIM\346\210\252\345\233\27620180806161221.png" new file mode 100644 index 00000000..3bec0ae8 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180806161221.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180806165750.png" "b/_assets/TIM\346\210\252\345\233\27620180806165750.png" new file mode 100644 index 00000000..ad335f2d Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180806165750.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180806172158.png" "b/_assets/TIM\346\210\252\345\233\27620180806172158.png" new file mode 100644 index 00000000..2a2f1684 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180806172158.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180807094759.png" "b/_assets/TIM\346\210\252\345\233\27620180807094759.png" new file mode 100644 index 00000000..5cea6b02 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180807094759.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180807144435.png" "b/_assets/TIM\346\210\252\345\233\27620180807144435.png" new file mode 100644 index 00000000..a60fe611 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180807144435.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180807151212.png" "b/_assets/TIM\346\210\252\345\233\27620180807151212.png" new file mode 100644 index 00000000..0252d4de Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180807151212.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180807152158.png" "b/_assets/TIM\346\210\252\345\233\27620180807152158.png" new file mode 100644 index 00000000..3387124d Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180807152158.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180807152241.png" "b/_assets/TIM\346\210\252\345\233\27620180807152241.png" new file mode 100644 index 00000000..ebb2b130 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180807152241.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180807152306.png" "b/_assets/TIM\346\210\252\345\233\27620180807152306.png" new file mode 100644 index 00000000..c83ce980 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180807152306.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180807152431.png" "b/_assets/TIM\346\210\252\345\233\27620180807152431.png" new file mode 100644 index 00000000..bceeeae0 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180807152431.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180807152459.png" "b/_assets/TIM\346\210\252\345\233\27620180807152459.png" new file mode 100644 index 00000000..3a7774d6 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180807152459.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180807152530.png" "b/_assets/TIM\346\210\252\345\233\27620180807152530.png" new file mode 100644 index 00000000..25fa010b Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180807152530.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180807203305.png" "b/_assets/TIM\346\210\252\345\233\27620180807203305.png" new file mode 100644 index 00000000..8f15b894 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180807203305.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180807210029.png" "b/_assets/TIM\346\210\252\345\233\27620180807210029.png" new file mode 100644 index 00000000..3024f8b3 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180807210029.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180807210133.png" "b/_assets/TIM\346\210\252\345\233\27620180807210133.png" new file mode 100644 index 00000000..19156b0a Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180807210133.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180808105242.png" "b/_assets/TIM\346\210\252\345\233\27620180808105242.png" new file mode 100644 index 00000000..b7188cc0 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180808105242.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180808110903.png" "b/_assets/TIM\346\210\252\345\233\27620180808110903.png" new file mode 100644 index 00000000..34008fe8 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180808110903.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180808111835.png" "b/_assets/TIM\346\210\252\345\233\27620180808111835.png" new file mode 100644 index 00000000..cab40e9e Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180808111835.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180808112920.png" "b/_assets/TIM\346\210\252\345\233\27620180808112920.png" new file mode 100644 index 00000000..80ec73dc Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180808112920.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180808114753.png" "b/_assets/TIM\346\210\252\345\233\27620180808114753.png" new file mode 100644 index 00000000..8034a5cd Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180808114753.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180808135512.png" "b/_assets/TIM\346\210\252\345\233\27620180808135512.png" new file mode 100644 index 00000000..5a419c47 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180808135512.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180808232139.png" "b/_assets/TIM\346\210\252\345\233\27620180808232139.png" new file mode 100644 index 00000000..43db9fb6 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180808232139.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180812100103.png" "b/_assets/TIM\346\210\252\345\233\27620180812100103.png" new file mode 100644 index 00000000..3774079c Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180812100103.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180812100122.png" "b/_assets/TIM\346\210\252\345\233\27620180812100122.png" new file mode 100644 index 00000000..83cb8df8 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180812100122.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180812100344.png" "b/_assets/TIM\346\210\252\345\233\27620180812100344.png" new file mode 100644 index 00000000..4c6e19ed Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180812100344.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180812100356.png" "b/_assets/TIM\346\210\252\345\233\27620180812100356.png" new file mode 100644 index 00000000..a8f67ecb Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180812100356.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180812100416.png" "b/_assets/TIM\346\210\252\345\233\27620180812100416.png" new file mode 100644 index 00000000..df92879e Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180812100416.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180812100436.png" "b/_assets/TIM\346\210\252\345\233\27620180812100436.png" new file mode 100644 index 00000000..52af2d03 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180812100436.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180812100503.png" "b/_assets/TIM\346\210\252\345\233\27620180812100503.png" new file mode 100644 index 00000000..7733aa81 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180812100503.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180812100524.png" "b/_assets/TIM\346\210\252\345\233\27620180812100524.png" new file mode 100644 index 00000000..eee96fe4 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180812100524.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180812100550.png" "b/_assets/TIM\346\210\252\345\233\27620180812100550.png" new file mode 100644 index 00000000..38660cfc Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180812100550.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180812100606.png" "b/_assets/TIM\346\210\252\345\233\27620180812100606.png" new file mode 100644 index 00000000..9375407e Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180812100606.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180812100617.png" "b/_assets/TIM\346\210\252\345\233\27620180812100617.png" new file mode 100644 index 00000000..1434140d Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180812100617.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180816113922.png" "b/_assets/TIM\346\210\252\345\233\27620180816113922.png" new file mode 100644 index 00000000..c21e74b1 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180816113922.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180816144609.png" "b/_assets/TIM\346\210\252\345\233\27620180816144609.png" new file mode 100644 index 00000000..11c655df Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180816144609.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180817192259.png" "b/_assets/TIM\346\210\252\345\233\27620180817192259.png" new file mode 100644 index 00000000..e9420295 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180817192259.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180817204652.png" "b/_assets/TIM\346\210\252\345\233\27620180817204652.png" new file mode 100644 index 00000000..5c76d89d Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180817204652.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180817214034.png" "b/_assets/TIM\346\210\252\345\233\27620180817214034.png" new file mode 100644 index 00000000..487e870c Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180817214034.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180819110834.png" "b/_assets/TIM\346\210\252\345\233\27620180819110834.png" new file mode 100644 index 00000000..7cf37a24 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180819110834.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180819132752.png" "b/_assets/TIM\346\210\252\345\233\27620180819132752.png" new file mode 100644 index 00000000..51ee484c Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180819132752.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180822104132.png" "b/_assets/TIM\346\210\252\345\233\27620180822104132.png" new file mode 100644 index 00000000..f5098b29 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180822104132.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180822104942.png" "b/_assets/TIM\346\210\252\345\233\27620180822104942.png" new file mode 100644 index 00000000..31ddebf0 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180822104942.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180823213211.png" "b/_assets/TIM\346\210\252\345\233\27620180823213211.png" new file mode 100644 index 00000000..59cd9de2 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180823213211.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180823220850.png" "b/_assets/TIM\346\210\252\345\233\27620180823220850.png" new file mode 100644 index 00000000..befcf8bf Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180823220850.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180826113534.png" "b/_assets/TIM\346\210\252\345\233\27620180826113534.png" new file mode 100644 index 00000000..4287d8a7 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180826113534.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180828165510.png" "b/_assets/TIM\346\210\252\345\233\27620180828165510.png" new file mode 100644 index 00000000..0a4d8e7f Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180828165510.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180829133724.png" "b/_assets/TIM\346\210\252\345\233\27620180829133724.png" new file mode 100644 index 00000000..582f4a3e Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180829133724.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180829134331.png" "b/_assets/TIM\346\210\252\345\233\27620180829134331.png" new file mode 100644 index 00000000..7fedb9d0 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180829134331.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180829134659.png" "b/_assets/TIM\346\210\252\345\233\27620180829134659.png" new file mode 100644 index 00000000..e94a6dae Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180829134659.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180829145622.png" "b/_assets/TIM\346\210\252\345\233\27620180829145622.png" new file mode 100644 index 00000000..c700bb4a Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180829145622.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180829161149.png" "b/_assets/TIM\346\210\252\345\233\27620180829161149.png" new file mode 100644 index 00000000..150e529c Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180829161149.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180829162307.png" "b/_assets/TIM\346\210\252\345\233\27620180829162307.png" new file mode 100644 index 00000000..2b74148b Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180829162307.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180829172245.png" "b/_assets/TIM\346\210\252\345\233\27620180829172245.png" new file mode 100644 index 00000000..c010d621 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180829172245.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180830195819.png" "b/_assets/TIM\346\210\252\345\233\27620180830195819.png" new file mode 100644 index 00000000..173ccce7 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180830195819.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180830195912.png" "b/_assets/TIM\346\210\252\345\233\27620180830195912.png" new file mode 100644 index 00000000..dcfef54b Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180830195912.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180830195925.png" "b/_assets/TIM\346\210\252\345\233\27620180830195925.png" new file mode 100644 index 00000000..496851b6 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180830195925.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180903222433.png" "b/_assets/TIM\346\210\252\345\233\27620180903222433.png" new file mode 100644 index 00000000..a478e2e3 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180903222433.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180903224842.png" "b/_assets/TIM\346\210\252\345\233\27620180903224842.png" new file mode 100644 index 00000000..032beed7 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180903224842.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180907105826.png" "b/_assets/TIM\346\210\252\345\233\27620180907105826.png" new file mode 100644 index 00000000..dced177d Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180907105826.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180908103901.png" "b/_assets/TIM\346\210\252\345\233\27620180908103901.png" new file mode 100644 index 00000000..d9470d1a Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180908103901.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180908212911.png" "b/_assets/TIM\346\210\252\345\233\27620180908212911.png" new file mode 100644 index 00000000..8974814c Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180908212911.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180908224213.png" "b/_assets/TIM\346\210\252\345\233\27620180908224213.png" new file mode 100644 index 00000000..2f63a2f2 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180908224213.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180909132343.png" "b/_assets/TIM\346\210\252\345\233\27620180909132343.png" new file mode 100644 index 00000000..96aad36c Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180909132343.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180909132723.png" "b/_assets/TIM\346\210\252\345\233\27620180909132723.png" new file mode 100644 index 00000000..9235d3cc Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180909132723.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180909133015.png" "b/_assets/TIM\346\210\252\345\233\27620180909133015.png" new file mode 100644 index 00000000..35549177 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180909133015.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180909161143.png" "b/_assets/TIM\346\210\252\345\233\27620180909161143.png" new file mode 100644 index 00000000..f59e189d Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180909161143.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180909161708.png" "b/_assets/TIM\346\210\252\345\233\27620180909161708.png" new file mode 100644 index 00000000..06530f33 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180909161708.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180910135921.png" "b/_assets/TIM\346\210\252\345\233\27620180910135921.png" new file mode 100644 index 00000000..a51377e2 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180910135921.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180910193357.png" "b/_assets/TIM\346\210\252\345\233\27620180910193357.png" new file mode 100644 index 00000000..0a70f65e Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180910193357.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180910193451.png" "b/_assets/TIM\346\210\252\345\233\27620180910193451.png" new file mode 100644 index 00000000..47a1f1bd Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180910193451.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180910193521.png" "b/_assets/TIM\346\210\252\345\233\27620180910193521.png" new file mode 100644 index 00000000..1dac01f6 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180910193521.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180910193636.png" "b/_assets/TIM\346\210\252\345\233\27620180910193636.png" new file mode 100644 index 00000000..46acc173 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180910193636.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180910193716.png" "b/_assets/TIM\346\210\252\345\233\27620180910193716.png" new file mode 100644 index 00000000..fa3d2076 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180910193716.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180910193739.png" "b/_assets/TIM\346\210\252\345\233\27620180910193739.png" new file mode 100644 index 00000000..418ce97e Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180910193739.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180910220336.png" "b/_assets/TIM\346\210\252\345\233\27620180910220336.png" new file mode 100644 index 00000000..a72bf937 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180910220336.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180911151229.png" "b/_assets/TIM\346\210\252\345\233\27620180911151229.png" new file mode 100644 index 00000000..da70820d Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180911151229.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180911153132.png" "b/_assets/TIM\346\210\252\345\233\27620180911153132.png" new file mode 100644 index 00000000..f96ed776 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180911153132.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180911163940.png" "b/_assets/TIM\346\210\252\345\233\27620180911163940.png" new file mode 100644 index 00000000..f60c221f Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180911163940.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180911164443.png" "b/_assets/TIM\346\210\252\345\233\27620180911164443.png" new file mode 100644 index 00000000..6531ebab Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180911164443.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180911174700.png" "b/_assets/TIM\346\210\252\345\233\27620180911174700.png" new file mode 100644 index 00000000..abd5921e Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180911174700.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180911212438.png" "b/_assets/TIM\346\210\252\345\233\27620180911212438.png" new file mode 100644 index 00000000..93ec05f4 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180911212438.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180911212613.png" "b/_assets/TIM\346\210\252\345\233\27620180911212613.png" new file mode 100644 index 00000000..cdfbf2a5 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180911212613.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180911222910.png" "b/_assets/TIM\346\210\252\345\233\27620180911222910.png" new file mode 100644 index 00000000..fbe91d9f Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180911222910.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180912115327.png" "b/_assets/TIM\346\210\252\345\233\27620180912115327.png" new file mode 100644 index 00000000..b71c7168 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180912115327.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180912193245.png" "b/_assets/TIM\346\210\252\345\233\27620180912193245.png" new file mode 100644 index 00000000..4cfa1d3b Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180912193245.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180912193315.png" "b/_assets/TIM\346\210\252\345\233\27620180912193315.png" new file mode 100644 index 00000000..c0dec515 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180912193315.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180912202657.png" "b/_assets/TIM\346\210\252\345\233\27620180912202657.png" new file mode 100644 index 00000000..30ede8c0 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180912202657.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180912210803.png" "b/_assets/TIM\346\210\252\345\233\27620180912210803.png" new file mode 100644 index 00000000..6774fa43 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180912210803.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180912212748.png" "b/_assets/TIM\346\210\252\345\233\27620180912212748.png" new file mode 100644 index 00000000..d74e0564 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180912212748.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180912230713.png" "b/_assets/TIM\346\210\252\345\233\27620180912230713.png" new file mode 100644 index 00000000..a13c6c09 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180912230713.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180913154830.png" "b/_assets/TIM\346\210\252\345\233\27620180913154830.png" new file mode 100644 index 00000000..a4226611 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180913154830.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180913154842.png" "b/_assets/TIM\346\210\252\345\233\27620180913154842.png" new file mode 100644 index 00000000..a375daec Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180913154842.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180913154909.png" "b/_assets/TIM\346\210\252\345\233\27620180913154909.png" new file mode 100644 index 00000000..be46f518 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180913154909.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180913212155.png" "b/_assets/TIM\346\210\252\345\233\27620180913212155.png" new file mode 100644 index 00000000..9b253e1b Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180913212155.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180914010334.png" "b/_assets/TIM\346\210\252\345\233\27620180914010334.png" new file mode 100644 index 00000000..e6e90653 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180914010334.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180914013219.png" "b/_assets/TIM\346\210\252\345\233\27620180914013219.png" new file mode 100644 index 00000000..248aa188 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180914013219.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180914013449.png" "b/_assets/TIM\346\210\252\345\233\27620180914013449.png" new file mode 100644 index 00000000..5ce68be8 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180914013449.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180914133101.png" "b/_assets/TIM\346\210\252\345\233\27620180914133101.png" new file mode 100644 index 00000000..c497a29f Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180914133101.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180914151435.png" "b/_assets/TIM\346\210\252\345\233\27620180914151435.png" new file mode 100644 index 00000000..67deb82d Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180914151435.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180914151535.png" "b/_assets/TIM\346\210\252\345\233\27620180914151535.png" new file mode 100644 index 00000000..6709f601 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180914151535.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180914151641.png" "b/_assets/TIM\346\210\252\345\233\27620180914151641.png" new file mode 100644 index 00000000..7f9860d9 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180914151641.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180914152110.png" "b/_assets/TIM\346\210\252\345\233\27620180914152110.png" new file mode 100644 index 00000000..8837e360 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180914152110.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180914153455.png" "b/_assets/TIM\346\210\252\345\233\27620180914153455.png" new file mode 100644 index 00000000..891c0635 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180914153455.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180914202716.png" "b/_assets/TIM\346\210\252\345\233\27620180914202716.png" new file mode 100644 index 00000000..c60db659 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180914202716.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180914203206.png" "b/_assets/TIM\346\210\252\345\233\27620180914203206.png" new file mode 100644 index 00000000..e08c4d8a Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180914203206.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180914203218.png" "b/_assets/TIM\346\210\252\345\233\27620180914203218.png" new file mode 100644 index 00000000..626f241a Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180914203218.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180915103817.png" "b/_assets/TIM\346\210\252\345\233\27620180915103817.png" new file mode 100644 index 00000000..06b48752 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180915103817.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180915103846.png" "b/_assets/TIM\346\210\252\345\233\27620180915103846.png" new file mode 100644 index 00000000..922e492c Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180915103846.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180915151749.png" "b/_assets/TIM\346\210\252\345\233\27620180915151749.png" new file mode 100644 index 00000000..9183bc6b Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180915151749.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180915151811.png" "b/_assets/TIM\346\210\252\345\233\27620180915151811.png" new file mode 100644 index 00000000..28a9b188 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180915151811.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180915160225.png" "b/_assets/TIM\346\210\252\345\233\27620180915160225.png" new file mode 100644 index 00000000..1744bc13 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180915160225.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180915200149.png" "b/_assets/TIM\346\210\252\345\233\27620180915200149.png" new file mode 100644 index 00000000..9fda6552 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180915200149.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180915200200.png" "b/_assets/TIM\346\210\252\345\233\27620180915200200.png" new file mode 100644 index 00000000..497a08b2 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180915200200.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180915200225.png" "b/_assets/TIM\346\210\252\345\233\27620180915200225.png" new file mode 100644 index 00000000..c31db17a Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180915200225.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180915211348.png" "b/_assets/TIM\346\210\252\345\233\27620180915211348.png" new file mode 100644 index 00000000..b96d63b8 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180915211348.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180915211556.png" "b/_assets/TIM\346\210\252\345\233\27620180915211556.png" new file mode 100644 index 00000000..678fcfec Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180915211556.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180915211635.png" "b/_assets/TIM\346\210\252\345\233\27620180915211635.png" new file mode 100644 index 00000000..88c80678 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180915211635.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180916153203.png" "b/_assets/TIM\346\210\252\345\233\27620180916153203.png" new file mode 100644 index 00000000..572725fb Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180916153203.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180916153221.png" "b/_assets/TIM\346\210\252\345\233\27620180916153221.png" new file mode 100644 index 00000000..3e47dd39 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180916153221.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180916153234.png" "b/_assets/TIM\346\210\252\345\233\27620180916153234.png" new file mode 100644 index 00000000..eb08c411 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180916153234.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180918200953.png" "b/_assets/TIM\346\210\252\345\233\27620180918200953.png" new file mode 100644 index 00000000..57cd5527 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180918200953.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180918201038.png" "b/_assets/TIM\346\210\252\345\233\27620180918201038.png" new file mode 100644 index 00000000..a031f262 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180918201038.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180918201054.png" "b/_assets/TIM\346\210\252\345\233\27620180918201054.png" new file mode 100644 index 00000000..c2b12dc2 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180918201054.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180920192424.png" "b/_assets/TIM\346\210\252\345\233\27620180920192424.png" new file mode 100644 index 00000000..9a87ea02 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180920192424.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180920192513.png" "b/_assets/TIM\346\210\252\345\233\27620180920192513.png" new file mode 100644 index 00000000..acc6b133 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180920192513.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180920203251.png" "b/_assets/TIM\346\210\252\345\233\27620180920203251.png" new file mode 100644 index 00000000..2b4a608b Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180920203251.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180920203306.png" "b/_assets/TIM\346\210\252\345\233\27620180920203306.png" new file mode 100644 index 00000000..0ff2323e Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180920203306.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180920203336.png" "b/_assets/TIM\346\210\252\345\233\27620180920203336.png" new file mode 100644 index 00000000..290d85bb Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180920203336.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180920203412.png" "b/_assets/TIM\346\210\252\345\233\27620180920203412.png" new file mode 100644 index 00000000..89ae656c Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180920203412.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180920203431.png" "b/_assets/TIM\346\210\252\345\233\27620180920203431.png" new file mode 100644 index 00000000..469a81ae Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180920203431.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180920203444.png" "b/_assets/TIM\346\210\252\345\233\27620180920203444.png" new file mode 100644 index 00000000..2108cb1b Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180920203444.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180920203521.png" "b/_assets/TIM\346\210\252\345\233\27620180920203521.png" new file mode 100644 index 00000000..9116befc Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180920203521.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180920203532.png" "b/_assets/TIM\346\210\252\345\233\27620180920203532.png" new file mode 100644 index 00000000..908ef8cb Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180920203532.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180921215706.png" "b/_assets/TIM\346\210\252\345\233\27620180921215706.png" new file mode 100644 index 00000000..91b200a5 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180921215706.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180925203418.png" "b/_assets/TIM\346\210\252\345\233\27620180925203418.png" new file mode 100644 index 00000000..d555b393 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180925203418.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180925203432.png" "b/_assets/TIM\346\210\252\345\233\27620180925203432.png" new file mode 100644 index 00000000..09702032 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180925203432.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180925203442.png" "b/_assets/TIM\346\210\252\345\233\27620180925203442.png" new file mode 100644 index 00000000..054b1f33 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180925203442.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180925203454.png" "b/_assets/TIM\346\210\252\345\233\27620180925203454.png" new file mode 100644 index 00000000..261cbab2 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180925203454.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180925203504.png" "b/_assets/TIM\346\210\252\345\233\27620180925203504.png" new file mode 100644 index 00000000..1b9c5ea0 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180925203504.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180926095739.png" "b/_assets/TIM\346\210\252\345\233\27620180926095739.png" new file mode 100644 index 00000000..5306458b Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180926095739.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180927103607.png" "b/_assets/TIM\346\210\252\345\233\27620180927103607.png" new file mode 100644 index 00000000..133e611f Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180927103607.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180927103625.png" "b/_assets/TIM\346\210\252\345\233\27620180927103625.png" new file mode 100644 index 00000000..b5bc2b09 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180927103625.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180928102534.png" "b/_assets/TIM\346\210\252\345\233\27620180928102534.png" new file mode 100644 index 00000000..5af573ba Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180928102534.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180928102605.png" "b/_assets/TIM\346\210\252\345\233\27620180928102605.png" new file mode 100644 index 00000000..cc10cf14 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180928102605.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180928103003.png" "b/_assets/TIM\346\210\252\345\233\27620180928103003.png" new file mode 100644 index 00000000..8e15c13e Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180928103003.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180928103312.png" "b/_assets/TIM\346\210\252\345\233\27620180928103312.png" new file mode 100644 index 00000000..3630c186 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180928103312.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180930171307.png" "b/_assets/TIM\346\210\252\345\233\27620180930171307.png" new file mode 100644 index 00000000..10297dfe Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180930171307.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620180930171522.png" "b/_assets/TIM\346\210\252\345\233\27620180930171522.png" new file mode 100644 index 00000000..97e15bd9 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620180930171522.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620181001142309.png" "b/_assets/TIM\346\210\252\345\233\27620181001142309.png" new file mode 100644 index 00000000..306bcd7c Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620181001142309.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620181001142653.png" "b/_assets/TIM\346\210\252\345\233\27620181001142653.png" new file mode 100644 index 00000000..9f48dc2f Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620181001142653.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620181018151502.png" "b/_assets/TIM\346\210\252\345\233\27620181018151502.png" new file mode 100644 index 00000000..1b899731 Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620181018151502.png" differ diff --git "a/_assets/TIM\346\210\252\345\233\27620190212145247.png" "b/_assets/TIM\346\210\252\345\233\27620190212145247.png" new file mode 100644 index 00000000..eb26690a Binary files /dev/null and "b/_assets/TIM\346\210\252\345\233\27620190212145247.png" differ diff --git a/_assets/arbitrary_padding_no_strides.gif b/_assets/arbitrary_padding_no_strides.gif new file mode 100644 index 00000000..0f8a31de Binary files /dev/null and b/_assets/arbitrary_padding_no_strides.gif differ diff --git a/_assets/arbitrary_padding_no_strides_transposed.gif b/_assets/arbitrary_padding_no_strides_transposed.gif new file mode 100644 index 00000000..aa7c4358 Binary files /dev/null and b/_assets/arbitrary_padding_no_strides_transposed.gif differ diff --git a/_assets/confusion_matrix.png b/_assets/confusion_matrix.png new file mode 100644 index 00000000..7d76aaa1 Binary files /dev/null and b/_assets/confusion_matrix.png differ diff --git a/_assets/contours_evaluation_optimizers.gif b/_assets/contours_evaluation_optimizers.gif new file mode 100644 index 00000000..e9d54d0f Binary files /dev/null and b/_assets/contours_evaluation_optimizers.gif differ diff --git a/_assets/conv_dilation.gif b/_assets/conv_dilation.gif new file mode 100644 index 00000000..25955967 Binary files /dev/null and b/_assets/conv_dilation.gif differ diff --git a/_assets/conv_no_padding_no_strides_transposed.gif b/_assets/conv_no_padding_no_strides_transposed.gif new file mode 100644 index 00000000..a6aa05eb Binary files /dev/null and b/_assets/conv_no_padding_no_strides_transposed.gif differ diff --git a/_assets/dilation.gif b/_assets/dilation.gif new file mode 100644 index 00000000..25955967 Binary files /dev/null and b/_assets/dilation.gif differ diff --git a/_assets/full_padding_no_strides.gif b/_assets/full_padding_no_strides.gif new file mode 100644 index 00000000..60a8669b Binary files /dev/null and b/_assets/full_padding_no_strides.gif differ diff --git a/_assets/full_padding_no_strides_transposed.gif b/_assets/full_padding_no_strides_transposed.gif new file mode 100644 index 00000000..5916ffdd Binary files /dev/null and b/_assets/full_padding_no_strides_transposed.gif differ diff --git a/_assets/no_padding_no_strides.gif b/_assets/no_padding_no_strides.gif new file mode 100644 index 00000000..ee10e357 Binary files /dev/null and b/_assets/no_padding_no_strides.gif differ diff --git a/_assets/no_padding_no_strides_transposed.gif b/_assets/no_padding_no_strides_transposed.gif new file mode 100644 index 00000000..a6aa05eb Binary files /dev/null and b/_assets/no_padding_no_strides_transposed.gif differ diff --git a/_assets/no_padding_strides.gif b/_assets/no_padding_strides.gif new file mode 100644 index 00000000..1a050713 Binary files /dev/null and b/_assets/no_padding_strides.gif differ diff --git a/_assets/no_padding_strides_transposed.gif b/_assets/no_padding_strides_transposed.gif new file mode 100644 index 00000000..6c57c49f Binary files /dev/null and b/_assets/no_padding_strides_transposed.gif differ diff --git a/_assets/padding_strides.gif b/_assets/padding_strides.gif new file mode 100644 index 00000000..a319772b Binary files /dev/null and b/_assets/padding_strides.gif differ diff --git a/_assets/padding_strides_odd.gif b/_assets/padding_strides_odd.gif new file mode 100644 index 00000000..5d9c883e Binary files /dev/null and b/_assets/padding_strides_odd.gif differ diff --git a/_assets/padding_strides_odd_transposed.gif b/_assets/padding_strides_odd_transposed.gif new file mode 100644 index 00000000..e8ef8511 Binary files /dev/null and b/_assets/padding_strides_odd_transposed.gif differ diff --git a/_assets/padding_strides_transposed.gif b/_assets/padding_strides_transposed.gif new file mode 100644 index 00000000..7b225384 Binary files /dev/null and b/_assets/padding_strides_transposed.gif differ diff --git a/_assets/saddle_point_evaluation_optimizers.gif b/_assets/saddle_point_evaluation_optimizers.gif new file mode 100644 index 00000000..61db2465 Binary files /dev/null and b/_assets/saddle_point_evaluation_optimizers.gif differ diff --git a/_assets/same_padding_no_strides.gif b/_assets/same_padding_no_strides.gif new file mode 100644 index 00000000..d0057459 Binary files /dev/null and b/_assets/same_padding_no_strides.gif differ diff --git a/_assets/same_padding_no_strides_transposed.gif b/_assets/same_padding_no_strides_transposed.gif new file mode 100644 index 00000000..7bb114e2 Binary files /dev/null and b/_assets/same_padding_no_strides_transposed.gif differ diff --git a/_assets/seq2seq-attention.gif b/_assets/seq2seq-attention.gif new file mode 100644 index 00000000..27be0a30 Binary files /dev/null and b/_assets/seq2seq-attention.gif differ diff --git a/_assets/seq2seq-text.jpg b/_assets/seq2seq-text.jpg new file mode 100644 index 00000000..58ad53bb Binary files /dev/null and b/_assets/seq2seq-text.jpg differ diff --git a/_assets/seq2seq.png b/_assets/seq2seq.png new file mode 100644 index 00000000..af09c74a Binary files /dev/null and b/_assets/seq2seq.png differ diff --git "a/_assets/\345\205\254\345\274\217_20180610212719.png" "b/_assets/\345\205\254\345\274\217_20180610212719.png" new file mode 100644 index 00000000..02bda6d1 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610212719.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180610213145.png" "b/_assets/\345\205\254\345\274\217_20180610213145.png" new file mode 100644 index 00000000..58947e90 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610213145.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180610213218.png" "b/_assets/\345\205\254\345\274\217_20180610213218.png" new file mode 100644 index 00000000..6e1219ad Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610213218.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180610213349.png" "b/_assets/\345\205\254\345\274\217_20180610213349.png" new file mode 100644 index 00000000..2e201757 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610213349.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180610213428.png" "b/_assets/\345\205\254\345\274\217_20180610213428.png" new file mode 100644 index 00000000..4dbdecfa Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610213428.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180610213451.png" "b/_assets/\345\205\254\345\274\217_20180610213451.png" new file mode 100644 index 00000000..737ecbde Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610213451.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180610214123.png" "b/_assets/\345\205\254\345\274\217_20180610214123.png" new file mode 100644 index 00000000..7d19fd15 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610214123.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180610214502.png" "b/_assets/\345\205\254\345\274\217_20180610214502.png" new file mode 100644 index 00000000..d54dd909 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610214502.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180610214846.png" "b/_assets/\345\205\254\345\274\217_20180610214846.png" new file mode 100644 index 00000000..715dcdb7 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610214846.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180610214926.png" "b/_assets/\345\205\254\345\274\217_20180610214926.png" new file mode 100644 index 00000000..6482c375 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610214926.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180610215029.png" "b/_assets/\345\205\254\345\274\217_20180610215029.png" new file mode 100644 index 00000000..2695986b Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610215029.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180610215150.png" "b/_assets/\345\205\254\345\274\217_20180610215150.png" new file mode 100644 index 00000000..f49254bb Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610215150.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180610215222.png" "b/_assets/\345\205\254\345\274\217_20180610215222.png" new file mode 100644 index 00000000..b5b112ab Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610215222.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180610215308.png" "b/_assets/\345\205\254\345\274\217_20180610215308.png" new file mode 100644 index 00000000..341015d1 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610215308.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180610215339.png" "b/_assets/\345\205\254\345\274\217_20180610215339.png" new file mode 100644 index 00000000..6d4447c1 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610215339.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180610215417.png" "b/_assets/\345\205\254\345\274\217_20180610215417.png" new file mode 100644 index 00000000..53937d34 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610215417.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180610215445.png" "b/_assets/\345\205\254\345\274\217_20180610215445.png" new file mode 100644 index 00000000..4b96765e Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610215445.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180610215522.png" "b/_assets/\345\205\254\345\274\217_20180610215522.png" new file mode 100644 index 00000000..07f9bef8 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610215522.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180610215554.png" "b/_assets/\345\205\254\345\274\217_20180610215554.png" new file mode 100644 index 00000000..f9828a27 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610215554.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180610215623.png" "b/_assets/\345\205\254\345\274\217_20180610215623.png" new file mode 100644 index 00000000..ba6b0884 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610215623.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180610215656.png" "b/_assets/\345\205\254\345\274\217_20180610215656.png" new file mode 100644 index 00000000..2e620bf5 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610215656.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180610215732.png" "b/_assets/\345\205\254\345\274\217_20180610215732.png" new file mode 100644 index 00000000..0d318223 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610215732.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180610215812.png" "b/_assets/\345\205\254\345\274\217_20180610215812.png" new file mode 100644 index 00000000..f9840d0b Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180610215812.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180611194550.png" "b/_assets/\345\205\254\345\274\217_20180611194550.png" new file mode 100644 index 00000000..939113ec Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180611194550.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180611194704.png" "b/_assets/\345\205\254\345\274\217_20180611194704.png" new file mode 100644 index 00000000..bb2b756c Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180611194704.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180611195622.png" "b/_assets/\345\205\254\345\274\217_20180611195622.png" new file mode 100644 index 00000000..ba1ef1cd Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180611195622.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180611210928.png" "b/_assets/\345\205\254\345\274\217_20180611210928.png" new file mode 100644 index 00000000..cf04c8d2 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180611210928.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180613164438.png" "b/_assets/\345\205\254\345\274\217_20180613164438.png" new file mode 100644 index 00000000..35de41cc Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180613164438.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180613164637.png" "b/_assets/\345\205\254\345\274\217_20180613164637.png" new file mode 100644 index 00000000..528a0520 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180613164637.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180613170734.png" "b/_assets/\345\205\254\345\274\217_20180613170734.png" new file mode 100644 index 00000000..19d68ae0 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180613170734.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180613171757.png" "b/_assets/\345\205\254\345\274\217_20180613171757.png" new file mode 100644 index 00000000..b0615a94 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180613171757.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180613201829.png" "b/_assets/\345\205\254\345\274\217_20180613201829.png" new file mode 100644 index 00000000..74d0c595 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180613201829.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180613210141.png" "b/_assets/\345\205\254\345\274\217_20180613210141.png" new file mode 100644 index 00000000..42f7d3ba Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180613210141.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180613211004.png" "b/_assets/\345\205\254\345\274\217_20180613211004.png" new file mode 100644 index 00000000..ae20ec9d Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180613211004.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180613211437.png" "b/_assets/\345\205\254\345\274\217_20180613211437.png" new file mode 100644 index 00000000..a3b59ff8 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180613211437.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180620160408.png" "b/_assets/\345\205\254\345\274\217_20180620160408.png" new file mode 100644 index 00000000..23930323 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180620160408.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180620160538.png" "b/_assets/\345\205\254\345\274\217_20180620160538.png" new file mode 100644 index 00000000..e38a7c63 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180620160538.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180620204006.png" "b/_assets/\345\205\254\345\274\217_20180620204006.png" new file mode 100644 index 00000000..6bd6e350 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180620204006.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180620205055.png" "b/_assets/\345\205\254\345\274\217_20180620205055.png" new file mode 100644 index 00000000..75c05971 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180620205055.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180620210025.png" "b/_assets/\345\205\254\345\274\217_20180620210025.png" new file mode 100644 index 00000000..723e73df Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180620210025.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180620213601.png" "b/_assets/\345\205\254\345\274\217_20180620213601.png" new file mode 100644 index 00000000..5e57cd4a Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180620213601.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180624211704.png" "b/_assets/\345\205\254\345\274\217_20180624211704.png" new file mode 100644 index 00000000..b2556d4c Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180624211704.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180625144700.png" "b/_assets/\345\205\254\345\274\217_20180625144700.png" new file mode 100644 index 00000000..2573da34 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180625144700.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180625164050.png" "b/_assets/\345\205\254\345\274\217_20180625164050.png" new file mode 100644 index 00000000..1e240867 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180625164050.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180625191951.png" "b/_assets/\345\205\254\345\274\217_20180625191951.png" new file mode 100644 index 00000000..6ba9f1f1 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180625191951.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180625201230.png" "b/_assets/\345\205\254\345\274\217_20180625201230.png" new file mode 100644 index 00000000..c7667eb9 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180625201230.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180625201755.png" "b/_assets/\345\205\254\345\274\217_20180625201755.png" new file mode 100644 index 00000000..b28b758b Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180625201755.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180625202144.png" "b/_assets/\345\205\254\345\274\217_20180625202144.png" new file mode 100644 index 00000000..d13968df Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180625202144.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180625205043.png" "b/_assets/\345\205\254\345\274\217_20180625205043.png" new file mode 100644 index 00000000..9f49ad27 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180625205043.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180625205349.png" "b/_assets/\345\205\254\345\274\217_20180625205349.png" new file mode 100644 index 00000000..632a5d37 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180625205349.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180626195202.png" "b/_assets/\345\205\254\345\274\217_20180626195202.png" new file mode 100644 index 00000000..1857aa5f Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180626195202.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180626195929.png" "b/_assets/\345\205\254\345\274\217_20180626195929.png" new file mode 100644 index 00000000..1d7e571f Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180626195929.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180626200311.png" "b/_assets/\345\205\254\345\274\217_20180626200311.png" new file mode 100644 index 00000000..8e16e8d3 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180626200311.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180626200340.png" "b/_assets/\345\205\254\345\274\217_20180626200340.png" new file mode 100644 index 00000000..64d89a37 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180626200340.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180626200536.png" "b/_assets/\345\205\254\345\274\217_20180626200536.png" new file mode 100644 index 00000000..1400e7d6 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180626200536.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180626212313.png" "b/_assets/\345\205\254\345\274\217_20180626212313.png" new file mode 100644 index 00000000..7cac8243 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180626212313.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180626212536.png" "b/_assets/\345\205\254\345\274\217_20180626212536.png" new file mode 100644 index 00000000..77aae480 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180626212536.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180626213915.png" "b/_assets/\345\205\254\345\274\217_20180626213915.png" new file mode 100644 index 00000000..05526e25 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180626213915.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180626223957.png" "b/_assets/\345\205\254\345\274\217_20180626223957.png" new file mode 100644 index 00000000..6a031e1b Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180626223957.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180626224325.png" "b/_assets/\345\205\254\345\274\217_20180626224325.png" new file mode 100644 index 00000000..5e60a614 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180626224325.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180627104751.png" "b/_assets/\345\205\254\345\274\217_20180627104751.png" new file mode 100644 index 00000000..0e7356c5 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180627104751.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180627113753.png" "b/_assets/\345\205\254\345\274\217_20180627113753.png" new file mode 100644 index 00000000..2f39e9c6 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180627113753.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180627154353.png" "b/_assets/\345\205\254\345\274\217_20180627154353.png" new file mode 100644 index 00000000..1bbbe2e0 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180627154353.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180627154921.png" "b/_assets/\345\205\254\345\274\217_20180627154921.png" new file mode 100644 index 00000000..579972fb Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180627154921.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180627160852.png" "b/_assets/\345\205\254\345\274\217_20180627160852.png" new file mode 100644 index 00000000..e84e397e Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180627160852.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180627204933.png" "b/_assets/\345\205\254\345\274\217_20180627204933.png" new file mode 100644 index 00000000..ea324fd6 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180627204933.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180627205326.png" "b/_assets/\345\205\254\345\274\217_20180627205326.png" new file mode 100644 index 00000000..abeccd90 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180627205326.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180627210310.png" "b/_assets/\345\205\254\345\274\217_20180627210310.png" new file mode 100644 index 00000000..5c53f18f Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180627210310.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180627213245.png" "b/_assets/\345\205\254\345\274\217_20180627213245.png" new file mode 100644 index 00000000..2dd4af25 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180627213245.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180628110309.png" "b/_assets/\345\205\254\345\274\217_20180628110309.png" new file mode 100644 index 00000000..6f5b48f8 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180628110309.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180628141742.png" "b/_assets/\345\205\254\345\274\217_20180628141742.png" new file mode 100644 index 00000000..2393f0a8 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180628141742.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180628141926.png" "b/_assets/\345\205\254\345\274\217_20180628141926.png" new file mode 100644 index 00000000..171789d4 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180628141926.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180628143525.png" "b/_assets/\345\205\254\345\274\217_20180628143525.png" new file mode 100644 index 00000000..aee3d9e8 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180628143525.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180628144611.png" "b/_assets/\345\205\254\345\274\217_20180628144611.png" new file mode 100644 index 00000000..647c7d75 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180628144611.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180628144731.png" "b/_assets/\345\205\254\345\274\217_20180628144731.png" new file mode 100644 index 00000000..98b3550e Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180628144731.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180628200345.png" "b/_assets/\345\205\254\345\274\217_20180628200345.png" new file mode 100644 index 00000000..c7066e8e Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180628200345.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180628205959.png" "b/_assets/\345\205\254\345\274\217_20180628205959.png" new file mode 100644 index 00000000..ad524639 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180628205959.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180628210652.png" "b/_assets/\345\205\254\345\274\217_20180628210652.png" new file mode 100644 index 00000000..f50caec4 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180628210652.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180628211741.png" "b/_assets/\345\205\254\345\274\217_20180628211741.png" new file mode 100644 index 00000000..091d8bde Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180628211741.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180628211807.png" "b/_assets/\345\205\254\345\274\217_20180628211807.png" new file mode 100644 index 00000000..22cc4b1a Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180628211807.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180629153014.png" "b/_assets/\345\205\254\345\274\217_20180629153014.png" new file mode 100644 index 00000000..64dac884 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180629153014.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180629205004.png" "b/_assets/\345\205\254\345\274\217_20180629205004.png" new file mode 100644 index 00000000..7181e0d1 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180629205004.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180629205132.png" "b/_assets/\345\205\254\345\274\217_20180629205132.png" new file mode 100644 index 00000000..8051eb4f Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180629205132.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180629211135.png" "b/_assets/\345\205\254\345\274\217_20180629211135.png" new file mode 100644 index 00000000..cc794c0d Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180629211135.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180630152255.png" "b/_assets/\345\205\254\345\274\217_20180630152255.png" new file mode 100644 index 00000000..3b81fe7e Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180630152255.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180630152454.png" "b/_assets/\345\205\254\345\274\217_20180630152454.png" new file mode 100644 index 00000000..ddbf2faa Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180630152454.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180702171411.png" "b/_assets/\345\205\254\345\274\217_20180702171411.png" new file mode 100644 index 00000000..fb3935da Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180702171411.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180704162743.png" "b/_assets/\345\205\254\345\274\217_20180704162743.png" new file mode 100644 index 00000000..7dd6c9d6 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180704162743.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180704163428.png" "b/_assets/\345\205\254\345\274\217_20180704163428.png" new file mode 100644 index 00000000..8342c4dd Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180704163428.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180704163659.png" "b/_assets/\345\205\254\345\274\217_20180704163659.png" new file mode 100644 index 00000000..0a35d6e1 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180704163659.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180704170039.png" "b/_assets/\345\205\254\345\274\217_20180704170039.png" new file mode 100644 index 00000000..8c1fcb74 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180704170039.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180704170404.png" "b/_assets/\345\205\254\345\274\217_20180704170404.png" new file mode 100644 index 00000000..301c9a26 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180704170404.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180704171334.png" "b/_assets/\345\205\254\345\274\217_20180704171334.png" new file mode 100644 index 00000000..284e4523 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180704171334.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180704171742.png" "b/_assets/\345\205\254\345\274\217_20180704171742.png" new file mode 100644 index 00000000..c3821a44 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180704171742.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180704173348.png" "b/_assets/\345\205\254\345\274\217_20180704173348.png" new file mode 100644 index 00000000..19ef025e Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180704173348.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180704173511.png" "b/_assets/\345\205\254\345\274\217_20180704173511.png" new file mode 100644 index 00000000..c2670014 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180704173511.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180705113619.png" "b/_assets/\345\205\254\345\274\217_20180705113619.png" new file mode 100644 index 00000000..0df44688 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180705113619.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180705115436.png" "b/_assets/\345\205\254\345\274\217_20180705115436.png" new file mode 100644 index 00000000..fa5c5b02 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180705115436.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180705115935.png" "b/_assets/\345\205\254\345\274\217_20180705115935.png" new file mode 100644 index 00000000..601227c1 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180705115935.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180705120102.png" "b/_assets/\345\205\254\345\274\217_20180705120102.png" new file mode 100644 index 00000000..ae2d56d0 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180705120102.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180705120324.png" "b/_assets/\345\205\254\345\274\217_20180705120324.png" new file mode 100644 index 00000000..9027dc9c Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180705120324.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180705134851.png" "b/_assets/\345\205\254\345\274\217_20180705134851.png" new file mode 100644 index 00000000..58b31413 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180705134851.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180705154543.png" "b/_assets/\345\205\254\345\274\217_20180705154543.png" new file mode 100644 index 00000000..4f2a00ca Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180705154543.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180705154650.png" "b/_assets/\345\205\254\345\274\217_20180705154650.png" new file mode 100644 index 00000000..3ae910fc Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180705154650.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180705160142.png" "b/_assets/\345\205\254\345\274\217_20180705160142.png" new file mode 100644 index 00000000..ebaf6307 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180705160142.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180705161525.png" "b/_assets/\345\205\254\345\274\217_20180705161525.png" new file mode 100644 index 00000000..e74336b7 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180705161525.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180705161725.png" "b/_assets/\345\205\254\345\274\217_20180705161725.png" new file mode 100644 index 00000000..b26ebf15 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180705161725.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180705162428.png" "b/_assets/\345\205\254\345\274\217_20180705162428.png" new file mode 100644 index 00000000..e9437791 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180705162428.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180705162521.png" "b/_assets/\345\205\254\345\274\217_20180705162521.png" new file mode 100644 index 00000000..1f695ac8 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180705162521.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180705162633.png" "b/_assets/\345\205\254\345\274\217_20180705162633.png" new file mode 100644 index 00000000..70f2fd6c Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180705162633.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180705190236.png" "b/_assets/\345\205\254\345\274\217_20180705190236.png" new file mode 100644 index 00000000..27e05ffd Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180705190236.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180705190536.png" "b/_assets/\345\205\254\345\274\217_20180705190536.png" new file mode 100644 index 00000000..3bcd7a91 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180705190536.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180705190657.png" "b/_assets/\345\205\254\345\274\217_20180705190657.png" new file mode 100644 index 00000000..27e05ffd Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180705190657.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180706115540.png" "b/_assets/\345\205\254\345\274\217_20180706115540.png" new file mode 100644 index 00000000..8b694930 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180706115540.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180709113033.png" "b/_assets/\345\205\254\345\274\217_20180709113033.png" new file mode 100644 index 00000000..b626f62b Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180709113033.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180709113237.png" "b/_assets/\345\205\254\345\274\217_20180709113237.png" new file mode 100644 index 00000000..3ff63937 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180709113237.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180709113352.png" "b/_assets/\345\205\254\345\274\217_20180709113352.png" new file mode 100644 index 00000000..2980959a Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180709113352.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180709113552.png" "b/_assets/\345\205\254\345\274\217_20180709113552.png" new file mode 100644 index 00000000..b39d349a Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180709113552.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180709113801.png" "b/_assets/\345\205\254\345\274\217_20180709113801.png" new file mode 100644 index 00000000..72a817cb Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180709113801.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180709114801.png" "b/_assets/\345\205\254\345\274\217_20180709114801.png" new file mode 100644 index 00000000..484db50c Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180709114801.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180709114953.png" "b/_assets/\345\205\254\345\274\217_20180709114953.png" new file mode 100644 index 00000000..21b24554 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180709114953.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180709142838.png" "b/_assets/\345\205\254\345\274\217_20180709142838.png" new file mode 100644 index 00000000..b7d8da1e Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180709142838.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180709152101.png" "b/_assets/\345\205\254\345\274\217_20180709152101.png" new file mode 100644 index 00000000..77bba053 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180709152101.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180709152707.png" "b/_assets/\345\205\254\345\274\217_20180709152707.png" new file mode 100644 index 00000000..23207fd1 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180709152707.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180709153307.png" "b/_assets/\345\205\254\345\274\217_20180709153307.png" new file mode 100644 index 00000000..6d153ce8 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180709153307.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180709154956.png" "b/_assets/\345\205\254\345\274\217_20180709154956.png" new file mode 100644 index 00000000..7ba5a789 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180709154956.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180709155736.png" "b/_assets/\345\205\254\345\274\217_20180709155736.png" new file mode 100644 index 00000000..b87c3d86 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180709155736.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180709160115.png" "b/_assets/\345\205\254\345\274\217_20180709160115.png" new file mode 100644 index 00000000..a01506be Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180709160115.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180709160743.png" "b/_assets/\345\205\254\345\274\217_20180709160743.png" new file mode 100644 index 00000000..5111a559 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180709160743.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180709160950.png" "b/_assets/\345\205\254\345\274\217_20180709160950.png" new file mode 100644 index 00000000..20685072 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180709160950.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180709161030.png" "b/_assets/\345\205\254\345\274\217_20180709161030.png" new file mode 100644 index 00000000..7617dbdb Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180709161030.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180709162840.png" "b/_assets/\345\205\254\345\274\217_20180709162840.png" new file mode 100644 index 00000000..7643a383 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180709162840.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713111021.png" "b/_assets/\345\205\254\345\274\217_20180713111021.png" new file mode 100644 index 00000000..cecc38fa Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713111021.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713111140.png" "b/_assets/\345\205\254\345\274\217_20180713111140.png" new file mode 100644 index 00000000..029bc1c0 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713111140.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713111647.png" "b/_assets/\345\205\254\345\274\217_20180713111647.png" new file mode 100644 index 00000000..9c82daa2 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713111647.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713111746.png" "b/_assets/\345\205\254\345\274\217_20180713111746.png" new file mode 100644 index 00000000..1ab2d138 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713111746.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713112312.png" "b/_assets/\345\205\254\345\274\217_20180713112312.png" new file mode 100644 index 00000000..e77d662b Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713112312.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713114026.png" "b/_assets/\345\205\254\345\274\217_20180713114026.png" new file mode 100644 index 00000000..e536510b Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713114026.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713114504.png" "b/_assets/\345\205\254\345\274\217_20180713114504.png" new file mode 100644 index 00000000..927a899c Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713114504.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713114855.png" "b/_assets/\345\205\254\345\274\217_20180713114855.png" new file mode 100644 index 00000000..dfbef837 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713114855.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713131851.png" "b/_assets/\345\205\254\345\274\217_20180713131851.png" new file mode 100644 index 00000000..0fd593a1 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713131851.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713132107.png" "b/_assets/\345\205\254\345\274\217_20180713132107.png" new file mode 100644 index 00000000..d383ecf7 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713132107.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713132400.png" "b/_assets/\345\205\254\345\274\217_20180713132400.png" new file mode 100644 index 00000000..4aff60a2 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713132400.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713132655.png" "b/_assets/\345\205\254\345\274\217_20180713132655.png" new file mode 100644 index 00000000..cc82b296 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713132655.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713133045.png" "b/_assets/\345\205\254\345\274\217_20180713133045.png" new file mode 100644 index 00000000..9da828a8 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713133045.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713133601.png" "b/_assets/\345\205\254\345\274\217_20180713133601.png" new file mode 100644 index 00000000..faff82d1 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713133601.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713134322.png" "b/_assets/\345\205\254\345\274\217_20180713134322.png" new file mode 100644 index 00000000..3ac2833c Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713134322.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713134514.png" "b/_assets/\345\205\254\345\274\217_20180713134514.png" new file mode 100644 index 00000000..818b4a34 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713134514.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713141712.png" "b/_assets/\345\205\254\345\274\217_20180713141712.png" new file mode 100644 index 00000000..39283730 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713141712.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713141808.png" "b/_assets/\345\205\254\345\274\217_20180713141808.png" new file mode 100644 index 00000000..5a72fa47 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713141808.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713141843.png" "b/_assets/\345\205\254\345\274\217_20180713141843.png" new file mode 100644 index 00000000..1bf32f3e Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713141843.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713142639.png" "b/_assets/\345\205\254\345\274\217_20180713142639.png" new file mode 100644 index 00000000..67509222 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713142639.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713142726.png" "b/_assets/\345\205\254\345\274\217_20180713142726.png" new file mode 100644 index 00000000..b8f7a19b Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713142726.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713143545.png" "b/_assets/\345\205\254\345\274\217_20180713143545.png" new file mode 100644 index 00000000..ae63ef63 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713143545.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713143622.png" "b/_assets/\345\205\254\345\274\217_20180713143622.png" new file mode 100644 index 00000000..5b009344 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713143622.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713143756.png" "b/_assets/\345\205\254\345\274\217_20180713143756.png" new file mode 100644 index 00000000..dada0d3f Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713143756.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713151730.png" "b/_assets/\345\205\254\345\274\217_20180713151730.png" new file mode 100644 index 00000000..90ca8fb9 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713151730.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713152150.png" "b/_assets/\345\205\254\345\274\217_20180713152150.png" new file mode 100644 index 00000000..6fa21807 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713152150.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713152255.png" "b/_assets/\345\205\254\345\274\217_20180713152255.png" new file mode 100644 index 00000000..cda78bc9 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713152255.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713192430.png" "b/_assets/\345\205\254\345\274\217_20180713192430.png" new file mode 100644 index 00000000..edd3689a Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713192430.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713193142.png" "b/_assets/\345\205\254\345\274\217_20180713193142.png" new file mode 100644 index 00000000..0415b209 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713193142.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713193328.png" "b/_assets/\345\205\254\345\274\217_20180713193328.png" new file mode 100644 index 00000000..becd11fe Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713193328.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713194600.png" "b/_assets/\345\205\254\345\274\217_20180713194600.png" new file mode 100644 index 00000000..e1e9b8b3 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713194600.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713194803.png" "b/_assets/\345\205\254\345\274\217_20180713194803.png" new file mode 100644 index 00000000..7ada1a2c Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713194803.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713195055.png" "b/_assets/\345\205\254\345\274\217_20180713195055.png" new file mode 100644 index 00000000..04888037 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713195055.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713195135.png" "b/_assets/\345\205\254\345\274\217_20180713195135.png" new file mode 100644 index 00000000..5ad8fe6b Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713195135.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713200326.png" "b/_assets/\345\205\254\345\274\217_20180713200326.png" new file mode 100644 index 00000000..b0e1b4f7 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713200326.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713200405.png" "b/_assets/\345\205\254\345\274\217_20180713200405.png" new file mode 100644 index 00000000..72e3c1ee Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713200405.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713200734.png" "b/_assets/\345\205\254\345\274\217_20180713200734.png" new file mode 100644 index 00000000..4c7a4248 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713200734.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713200756.png" "b/_assets/\345\205\254\345\274\217_20180713200756.png" new file mode 100644 index 00000000..0350488f Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713200756.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713200840.png" "b/_assets/\345\205\254\345\274\217_20180713200840.png" new file mode 100644 index 00000000..c77bc5c1 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713200840.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713202223.png" "b/_assets/\345\205\254\345\274\217_20180713202223.png" new file mode 100644 index 00000000..68091cfa Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713202223.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713202306.png" "b/_assets/\345\205\254\345\274\217_20180713202306.png" new file mode 100644 index 00000000..84dfc6e3 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713202306.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180713203827.png" "b/_assets/\345\205\254\345\274\217_20180713203827.png" new file mode 100644 index 00000000..7e26c6f1 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180713203827.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180715132127.png" "b/_assets/\345\205\254\345\274\217_20180715132127.png" new file mode 100644 index 00000000..edb08f8f Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180715132127.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180715132515.png" "b/_assets/\345\205\254\345\274\217_20180715132515.png" new file mode 100644 index 00000000..1889fef2 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180715132515.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180715132840.png" "b/_assets/\345\205\254\345\274\217_20180715132840.png" new file mode 100644 index 00000000..e0a8167c Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180715132840.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180715132906.png" "b/_assets/\345\205\254\345\274\217_20180715132906.png" new file mode 100644 index 00000000..f03ad2b4 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180715132906.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180715133122.png" "b/_assets/\345\205\254\345\274\217_20180715133122.png" new file mode 100644 index 00000000..319a8af7 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180715133122.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180715133256.png" "b/_assets/\345\205\254\345\274\217_20180715133256.png" new file mode 100644 index 00000000..953222e5 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180715133256.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180715134037.png" "b/_assets/\345\205\254\345\274\217_20180715134037.png" new file mode 100644 index 00000000..9a7502c9 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180715134037.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180715134916.png" "b/_assets/\345\205\254\345\274\217_20180715134916.png" new file mode 100644 index 00000000..88b8cba9 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180715134916.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180715135300.png" "b/_assets/\345\205\254\345\274\217_20180715135300.png" new file mode 100644 index 00000000..3b52a81d Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180715135300.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180715135456.png" "b/_assets/\345\205\254\345\274\217_20180715135456.png" new file mode 100644 index 00000000..15ab4896 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180715135456.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180715135945.png" "b/_assets/\345\205\254\345\274\217_20180715135945.png" new file mode 100644 index 00000000..c7b5d9ad Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180715135945.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180715140300.png" "b/_assets/\345\205\254\345\274\217_20180715140300.png" new file mode 100644 index 00000000..09a0a64e Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180715140300.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180715140328.png" "b/_assets/\345\205\254\345\274\217_20180715140328.png" new file mode 100644 index 00000000..0067aec9 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180715140328.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180715141210.png" "b/_assets/\345\205\254\345\274\217_20180715141210.png" new file mode 100644 index 00000000..2a682faf Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180715141210.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180715142042.png" "b/_assets/\345\205\254\345\274\217_20180715142042.png" new file mode 100644 index 00000000..c45717d8 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180715142042.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180715212915.png" "b/_assets/\345\205\254\345\274\217_20180715212915.png" new file mode 100644 index 00000000..a4d3f03c Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180715212915.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180715214352.png" "b/_assets/\345\205\254\345\274\217_20180715214352.png" new file mode 100644 index 00000000..30d372a4 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180715214352.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180715230941.png" "b/_assets/\345\205\254\345\274\217_20180715230941.png" new file mode 100644 index 00000000..119a5c66 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180715230941.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180716142855.png" "b/_assets/\345\205\254\345\274\217_20180716142855.png" new file mode 100644 index 00000000..13ae1996 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180716142855.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180716143232.png" "b/_assets/\345\205\254\345\274\217_20180716143232.png" new file mode 100644 index 00000000..e0a0f838 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180716143232.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180716143509.png" "b/_assets/\345\205\254\345\274\217_20180716143509.png" new file mode 100644 index 00000000..c87f4caf Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180716143509.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180717101425.png" "b/_assets/\345\205\254\345\274\217_20180717101425.png" new file mode 100644 index 00000000..63d773d3 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180717101425.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180717110557.png" "b/_assets/\345\205\254\345\274\217_20180717110557.png" new file mode 100644 index 00000000..5bb847c5 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180717110557.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180717112341.png" "b/_assets/\345\205\254\345\274\217_20180717112341.png" new file mode 100644 index 00000000..1284be5e Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180717112341.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180717114628.png" "b/_assets/\345\205\254\345\274\217_20180717114628.png" new file mode 100644 index 00000000..e1b3d03e Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180717114628.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180717115204.png" "b/_assets/\345\205\254\345\274\217_20180717115204.png" new file mode 100644 index 00000000..c5231887 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180717115204.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180717115402.png" "b/_assets/\345\205\254\345\274\217_20180717115402.png" new file mode 100644 index 00000000..bef7240a Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180717115402.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180717115616.png" "b/_assets/\345\205\254\345\274\217_20180717115616.png" new file mode 100644 index 00000000..5bb847c5 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180717115616.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180717144338.png" "b/_assets/\345\205\254\345\274\217_20180717144338.png" new file mode 100644 index 00000000..f7497f5f Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180717144338.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180717144904.png" "b/_assets/\345\205\254\345\274\217_20180717144904.png" new file mode 100644 index 00000000..d64fc64a Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180717144904.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180717145223.png" "b/_assets/\345\205\254\345\274\217_20180717145223.png" new file mode 100644 index 00000000..e310dda7 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180717145223.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180717145448.png" "b/_assets/\345\205\254\345\274\217_20180717145448.png" new file mode 100644 index 00000000..e3a67261 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180717145448.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180717145625.png" "b/_assets/\345\205\254\345\274\217_20180717145625.png" new file mode 100644 index 00000000..b8c8a09b Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180717145625.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180717145841.png" "b/_assets/\345\205\254\345\274\217_20180717145841.png" new file mode 100644 index 00000000..e88f564c Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180717145841.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180717212747.png" "b/_assets/\345\205\254\345\274\217_20180717212747.png" new file mode 100644 index 00000000..d8b768ef Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180717212747.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180717214302.png" "b/_assets/\345\205\254\345\274\217_20180717214302.png" new file mode 100644 index 00000000..62040d0b Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180717214302.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180717223137.png" "b/_assets/\345\205\254\345\274\217_20180717223137.png" new file mode 100644 index 00000000..af4a1491 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180717223137.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180718103749.png" "b/_assets/\345\205\254\345\274\217_20180718103749.png" new file mode 100644 index 00000000..02e55b17 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180718103749.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180718104559.png" "b/_assets/\345\205\254\345\274\217_20180718104559.png" new file mode 100644 index 00000000..c35e4362 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180718104559.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180718105408.png" "b/_assets/\345\205\254\345\274\217_20180718105408.png" new file mode 100644 index 00000000..6b1e1f02 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180718105408.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180718105727.png" "b/_assets/\345\205\254\345\274\217_20180718105727.png" new file mode 100644 index 00000000..1614eb86 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180718105727.png" differ diff --git "a/_assets/\345\205\254\345\274\217_2018071891909.png" "b/_assets/\345\205\254\345\274\217_2018071891909.png" new file mode 100644 index 00000000..0386ab35 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_2018071891909.png" differ diff --git "a/_assets/\345\205\254\345\274\217_2018071893503.png" "b/_assets/\345\205\254\345\274\217_2018071893503.png" new file mode 100644 index 00000000..b23ff60d Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_2018071893503.png" differ diff --git "a/_assets/\345\205\254\345\274\217_2018071895945.png" "b/_assets/\345\205\254\345\274\217_2018071895945.png" new file mode 100644 index 00000000..b05136bd Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_2018071895945.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180719142639.png" "b/_assets/\345\205\254\345\274\217_20180719142639.png" new file mode 100644 index 00000000..12adf8bb Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180719142639.png" differ diff --git "a/_assets/\345\205\254\345\274\217_201807191434562.png" "b/_assets/\345\205\254\345\274\217_201807191434562.png" new file mode 100644 index 00000000..70c094bb Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_201807191434562.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180719144515.png" "b/_assets/\345\205\254\345\274\217_20180719144515.png" new file mode 100644 index 00000000..b692dc03 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180719144515.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180720110804.png" "b/_assets/\345\205\254\345\274\217_20180720110804.png" new file mode 100644 index 00000000..2c2404b5 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180720110804.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180720113735.png" "b/_assets/\345\205\254\345\274\217_20180720113735.png" new file mode 100644 index 00000000..02fa7820 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180720113735.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180720114917.png" "b/_assets/\345\205\254\345\274\217_20180720114917.png" new file mode 100644 index 00000000..6f574cfd Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180720114917.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180720120400.png" "b/_assets/\345\205\254\345\274\217_20180720120400.png" new file mode 100644 index 00000000..ae9759ba Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180720120400.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180720172427.png" "b/_assets/\345\205\254\345\274\217_20180720172427.png" new file mode 100644 index 00000000..157f2ad7 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180720172427.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180723153506.png" "b/_assets/\345\205\254\345\274\217_20180723153506.png" new file mode 100644 index 00000000..76c09a96 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180723153506.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180728195601.png" "b/_assets/\345\205\254\345\274\217_20180728195601.png" new file mode 100644 index 00000000..6d68b37a Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180728195601.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180728201614.png" "b/_assets/\345\205\254\345\274\217_20180728201614.png" new file mode 100644 index 00000000..e4a36075 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180728201614.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180728202629.png" "b/_assets/\345\205\254\345\274\217_20180728202629.png" new file mode 100644 index 00000000..880dff8d Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180728202629.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180728205525.png" "b/_assets/\345\205\254\345\274\217_20180728205525.png" new file mode 100644 index 00000000..521957f7 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180728205525.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180805203945.png" "b/_assets/\345\205\254\345\274\217_20180805203945.png" new file mode 100644 index 00000000..c1ebce90 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180805203945.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180805204149.png" "b/_assets/\345\205\254\345\274\217_20180805204149.png" new file mode 100644 index 00000000..c9a18b2d Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180805204149.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180805204435.png" "b/_assets/\345\205\254\345\274\217_20180805204435.png" new file mode 100644 index 00000000..6d20e157 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180805204435.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180805210533.png" "b/_assets/\345\205\254\345\274\217_20180805210533.png" new file mode 100644 index 00000000..5526aad2 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180805210533.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180805211457.png" "b/_assets/\345\205\254\345\274\217_20180805211457.png" new file mode 100644 index 00000000..e45b2e6b Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180805211457.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180805211530.png" "b/_assets/\345\205\254\345\274\217_20180805211530.png" new file mode 100644 index 00000000..a3ad2a4b Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180805211530.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180805211644.png" "b/_assets/\345\205\254\345\274\217_20180805211644.png" new file mode 100644 index 00000000..b519b58c Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180805211644.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180805211936.png" "b/_assets/\345\205\254\345\274\217_20180805211936.png" new file mode 100644 index 00000000..c8cc6e0a Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180805211936.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180805212222.png" "b/_assets/\345\205\254\345\274\217_20180805212222.png" new file mode 100644 index 00000000..f50af9a6 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180805212222.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180805231532.png" "b/_assets/\345\205\254\345\274\217_20180805231532.png" new file mode 100644 index 00000000..e8fcec55 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180805231532.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180805231849.png" "b/_assets/\345\205\254\345\274\217_20180805231849.png" new file mode 100644 index 00000000..0e633eed Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180805231849.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180805235203.png" "b/_assets/\345\205\254\345\274\217_20180805235203.png" new file mode 100644 index 00000000..2f8501dd Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180805235203.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180805235444.png" "b/_assets/\345\205\254\345\274\217_20180805235444.png" new file mode 100644 index 00000000..3dc60bb5 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180805235444.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806100848.png" "b/_assets/\345\205\254\345\274\217_20180806100848.png" new file mode 100644 index 00000000..95a85544 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806100848.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806100950.png" "b/_assets/\345\205\254\345\274\217_20180806100950.png" new file mode 100644 index 00000000..a7ea70f0 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806100950.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806102047.png" "b/_assets/\345\205\254\345\274\217_20180806102047.png" new file mode 100644 index 00000000..d0ee9581 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806102047.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806115306.png" "b/_assets/\345\205\254\345\274\217_20180806115306.png" new file mode 100644 index 00000000..8f052fe4 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806115306.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806115533.png" "b/_assets/\345\205\254\345\274\217_20180806115533.png" new file mode 100644 index 00000000..5da9365d Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806115533.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806115820.png" "b/_assets/\345\205\254\345\274\217_20180806115820.png" new file mode 100644 index 00000000..8dca10b2 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806115820.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806120054.png" "b/_assets/\345\205\254\345\274\217_20180806120054.png" new file mode 100644 index 00000000..8f16086c Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806120054.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806120247.png" "b/_assets/\345\205\254\345\274\217_20180806120247.png" new file mode 100644 index 00000000..a15a31de Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806120247.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806145103.png" "b/_assets/\345\205\254\345\274\217_20180806145103.png" new file mode 100644 index 00000000..f6fda90e Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806145103.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806145459.png" "b/_assets/\345\205\254\345\274\217_20180806145459.png" new file mode 100644 index 00000000..91f7d33e Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806145459.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806145813.png" "b/_assets/\345\205\254\345\274\217_20180806145813.png" new file mode 100644 index 00000000..3769f787 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806145813.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806150512.png" "b/_assets/\345\205\254\345\274\217_20180806150512.png" new file mode 100644 index 00000000..2b5291a1 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806150512.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806150727.png" "b/_assets/\345\205\254\345\274\217_20180806150727.png" new file mode 100644 index 00000000..4682907f Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806150727.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806150816.png" "b/_assets/\345\205\254\345\274\217_20180806150816.png" new file mode 100644 index 00000000..1a46220e Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806150816.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806162119.png" "b/_assets/\345\205\254\345\274\217_20180806162119.png" new file mode 100644 index 00000000..6ef5eb93 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806162119.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806165417.png" "b/_assets/\345\205\254\345\274\217_20180806165417.png" new file mode 100644 index 00000000..a5da2b98 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806165417.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806170304.png" "b/_assets/\345\205\254\345\274\217_20180806170304.png" new file mode 100644 index 00000000..3b67ddd6 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806170304.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806170618.png" "b/_assets/\345\205\254\345\274\217_20180806170618.png" new file mode 100644 index 00000000..e704d8b7 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806170618.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806172840.png" "b/_assets/\345\205\254\345\274\217_20180806172840.png" new file mode 100644 index 00000000..d9a303da Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806172840.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806173111.png" "b/_assets/\345\205\254\345\274\217_20180806173111.png" new file mode 100644 index 00000000..be43c1cb Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806173111.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806175337.png" "b/_assets/\345\205\254\345\274\217_20180806175337.png" new file mode 100644 index 00000000..7e863850 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806175337.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806193111.png" "b/_assets/\345\205\254\345\274\217_20180806193111.png" new file mode 100644 index 00000000..a200db1a Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806193111.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180806200601.png" "b/_assets/\345\205\254\345\274\217_20180806200601.png" new file mode 100644 index 00000000..a6638b08 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180806200601.png" differ diff --git "a/_assets/\345\205\254\345\274\217_2018080694809.png" "b/_assets/\345\205\254\345\274\217_2018080694809.png" new file mode 100644 index 00000000..2f67e960 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_2018080694809.png" differ diff --git "a/_assets/\345\205\254\345\274\217_2018080695721.png" "b/_assets/\345\205\254\345\274\217_2018080695721.png" new file mode 100644 index 00000000..3a6f1fea Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_2018080695721.png" differ diff --git "a/_assets/\345\205\254\345\274\217_2018080695819.png" "b/_assets/\345\205\254\345\274\217_2018080695819.png" new file mode 100644 index 00000000..53da008d Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_2018080695819.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180807150747.png" "b/_assets/\345\205\254\345\274\217_20180807150747.png" new file mode 100644 index 00000000..d78a8d90 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180807150747.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180807151738.png" "b/_assets/\345\205\254\345\274\217_20180807151738.png" new file mode 100644 index 00000000..7659ae85 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180807151738.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180808110452.png" "b/_assets/\345\205\254\345\274\217_20180808110452.png" new file mode 100644 index 00000000..108995cc Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180808110452.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180808110741.png" "b/_assets/\345\205\254\345\274\217_20180808110741.png" new file mode 100644 index 00000000..bfe866b6 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180808110741.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180808114308.png" "b/_assets/\345\205\254\345\274\217_20180808114308.png" new file mode 100644 index 00000000..f346ce10 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180808114308.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180808201548.png" "b/_assets/\345\205\254\345\274\217_20180808201548.png" new file mode 100644 index 00000000..6de6cdb2 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180808201548.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180808201943.png" "b/_assets/\345\205\254\345\274\217_20180808201943.png" new file mode 100644 index 00000000..b8f7a19b Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180808201943.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180810211954.png" "b/_assets/\345\205\254\345\274\217_20180810211954.png" new file mode 100644 index 00000000..1eca5585 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180810211954.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180811211940.png" "b/_assets/\345\205\254\345\274\217_20180811211940.png" new file mode 100644 index 00000000..8645ff03 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180811211940.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180811215806.png" "b/_assets/\345\205\254\345\274\217_20180811215806.png" new file mode 100644 index 00000000..7984ee45 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180811215806.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180817210106.png" "b/_assets/\345\205\254\345\274\217_20180817210106.png" new file mode 100644 index 00000000..c90faa83 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180817210106.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180817210758.png" "b/_assets/\345\205\254\345\274\217_20180817210758.png" new file mode 100644 index 00000000..c085444e Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180817210758.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180817211749.png" "b/_assets/\345\205\254\345\274\217_20180817211749.png" new file mode 100644 index 00000000..b43814f5 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180817211749.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180817211903.png" "b/_assets/\345\205\254\345\274\217_20180817211903.png" new file mode 100644 index 00000000..ea04c117 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180817211903.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180817212111.png" "b/_assets/\345\205\254\345\274\217_20180817212111.png" new file mode 100644 index 00000000..0f953fb7 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180817212111.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180817220004.png" "b/_assets/\345\205\254\345\274\217_20180817220004.png" new file mode 100644 index 00000000..6fc4d6fa Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180817220004.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180817223923.png" "b/_assets/\345\205\254\345\274\217_20180817223923.png" new file mode 100644 index 00000000..b200bcb1 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180817223923.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180817230314.png" "b/_assets/\345\205\254\345\274\217_20180817230314.png" new file mode 100644 index 00000000..67b31b05 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180817230314.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180819113203.png" "b/_assets/\345\205\254\345\274\217_20180819113203.png" new file mode 100644 index 00000000..a3d59e02 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180819113203.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180819203034.png" "b/_assets/\345\205\254\345\274\217_20180819203034.png" new file mode 100644 index 00000000..a23ed27a Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180819203034.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180819204052.png" "b/_assets/\345\205\254\345\274\217_20180819204052.png" new file mode 100644 index 00000000..f711208c Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180819204052.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180819204219.png" "b/_assets/\345\205\254\345\274\217_20180819204219.png" new file mode 100644 index 00000000..9383a9d0 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180819204219.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180819204612.png" "b/_assets/\345\205\254\345\274\217_20180819204612.png" new file mode 100644 index 00000000..5dd277c3 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180819204612.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180820104712.png" "b/_assets/\345\205\254\345\274\217_20180820104712.png" new file mode 100644 index 00000000..7668dfd0 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180820104712.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180820230435.png" "b/_assets/\345\205\254\345\274\217_20180820230435.png" new file mode 100644 index 00000000..2dd1e962 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180820230435.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180820232647.png" "b/_assets/\345\205\254\345\274\217_20180820232647.png" new file mode 100644 index 00000000..e3fb0552 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180820232647.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180821162706.png" "b/_assets/\345\205\254\345\274\217_20180821162706.png" new file mode 100644 index 00000000..07f49be4 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180821162706.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180823231624.png" "b/_assets/\345\205\254\345\274\217_20180823231624.png" new file mode 100644 index 00000000..e1707365 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180823231624.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180824102139.png" "b/_assets/\345\205\254\345\274\217_20180824102139.png" new file mode 100644 index 00000000..a006b32a Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180824102139.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180824130328.png" "b/_assets/\345\205\254\345\274\217_20180824130328.png" new file mode 100644 index 00000000..88999741 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180824130328.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180824140303.png" "b/_assets/\345\205\254\345\274\217_20180824140303.png" new file mode 100644 index 00000000..a3deb4ec Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180824140303.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180824141327.png" "b/_assets/\345\205\254\345\274\217_20180824141327.png" new file mode 100644 index 00000000..47054988 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180824141327.png" differ diff --git "a/_assets/\345\205\254\345\274\217_2018082492318.png" "b/_assets/\345\205\254\345\274\217_2018082492318.png" new file mode 100644 index 00000000..2a3c5080 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_2018082492318.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180826160514.png" "b/_assets/\345\205\254\345\274\217_20180826160514.png" new file mode 100644 index 00000000..5029da9c Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180826160514.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180826161333.png" "b/_assets/\345\205\254\345\274\217_20180826161333.png" new file mode 100644 index 00000000..9df1c151 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180826161333.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180826203915.png" "b/_assets/\345\205\254\345\274\217_20180826203915.png" new file mode 100644 index 00000000..624cd068 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180826203915.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180828112616.png" "b/_assets/\345\205\254\345\274\217_20180828112616.png" new file mode 100644 index 00000000..cf64224d Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180828112616.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180828174727.png" "b/_assets/\345\205\254\345\274\217_20180828174727.png" new file mode 100644 index 00000000..d05f0621 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180828174727.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180828175013.png" "b/_assets/\345\205\254\345\274\217_20180828175013.png" new file mode 100644 index 00000000..871a8a01 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180828175013.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180828175518.png" "b/_assets/\345\205\254\345\274\217_20180828175518.png" new file mode 100644 index 00000000..7c201feb Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180828175518.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180828175623.png" "b/_assets/\345\205\254\345\274\217_20180828175623.png" new file mode 100644 index 00000000..4ebb8d5a Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180828175623.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180828195828.png" "b/_assets/\345\205\254\345\274\217_20180828195828.png" new file mode 100644 index 00000000..adacba49 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180828195828.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180828200432.png" "b/_assets/\345\205\254\345\274\217_20180828200432.png" new file mode 100644 index 00000000..59253805 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180828200432.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180828200604.png" "b/_assets/\345\205\254\345\274\217_20180828200604.png" new file mode 100644 index 00000000..08db2a0a Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180828200604.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180828215204.png" "b/_assets/\345\205\254\345\274\217_20180828215204.png" new file mode 100644 index 00000000..d34391e8 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180828215204.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180828225607.png" "b/_assets/\345\205\254\345\274\217_20180828225607.png" new file mode 100644 index 00000000..b5965052 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180828225607.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180829104207.png" "b/_assets/\345\205\254\345\274\217_20180829104207.png" new file mode 100644 index 00000000..ad30e0ae Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180829104207.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180829114415.png" "b/_assets/\345\205\254\345\274\217_20180829114415.png" new file mode 100644 index 00000000..2cd62a01 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180829114415.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180829114459.png" "b/_assets/\345\205\254\345\274\217_20180829114459.png" new file mode 100644 index 00000000..34c08bb0 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180829114459.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180829145336.png" "b/_assets/\345\205\254\345\274\217_20180829145336.png" new file mode 100644 index 00000000..9fcfc6f0 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180829145336.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180829145525.png" "b/_assets/\345\205\254\345\274\217_20180829145525.png" new file mode 100644 index 00000000..ae7cb3e6 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180829145525.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180829150659.png" "b/_assets/\345\205\254\345\274\217_20180829150659.png" new file mode 100644 index 00000000..f77b8208 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180829150659.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180831165516.png" "b/_assets/\345\205\254\345\274\217_20180831165516.png" new file mode 100644 index 00000000..908c30a6 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180831165516.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180831165546.png" "b/_assets/\345\205\254\345\274\217_20180831165546.png" new file mode 100644 index 00000000..33d6c45e Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180831165546.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180902220459.png" "b/_assets/\345\205\254\345\274\217_20180902220459.png" new file mode 100644 index 00000000..4d3919d6 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180902220459.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180903203229.png" "b/_assets/\345\205\254\345\274\217_20180903203229.png" new file mode 100644 index 00000000..fd6b2cbf Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180903203229.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180903210625.png" "b/_assets/\345\205\254\345\274\217_20180903210625.png" new file mode 100644 index 00000000..d4c4dea0 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180903210625.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180903212935.png" "b/_assets/\345\205\254\345\274\217_20180903212935.png" new file mode 100644 index 00000000..902984e9 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180903212935.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180903213109.png" "b/_assets/\345\205\254\345\274\217_20180903213109.png" new file mode 100644 index 00000000..f58d520e Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180903213109.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180903213410.png" "b/_assets/\345\205\254\345\274\217_20180903213410.png" new file mode 100644 index 00000000..30b9f50c Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180903213410.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180903220828.png" "b/_assets/\345\205\254\345\274\217_20180903220828.png" new file mode 100644 index 00000000..02baec21 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180903220828.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180903223427.png" "b/_assets/\345\205\254\345\274\217_20180903223427.png" new file mode 100644 index 00000000..e0d669b8 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180903223427.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180903224323.png" "b/_assets/\345\205\254\345\274\217_20180903224323.png" new file mode 100644 index 00000000..91adaf42 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180903224323.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180903224557.png" "b/_assets/\345\205\254\345\274\217_20180903224557.png" new file mode 100644 index 00000000..33e76bc4 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180903224557.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180904113355.png" "b/_assets/\345\205\254\345\274\217_20180904113355.png" new file mode 100644 index 00000000..b19f73c1 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180904113355.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180905213158.png" "b/_assets/\345\205\254\345\274\217_20180905213158.png" new file mode 100644 index 00000000..a0778187 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180905213158.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180905213258.png" "b/_assets/\345\205\254\345\274\217_20180905213258.png" new file mode 100644 index 00000000..c4c8121f Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180905213258.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180905213323.png" "b/_assets/\345\205\254\345\274\217_20180905213323.png" new file mode 100644 index 00000000..73fcf407 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180905213323.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180905213339.png" "b/_assets/\345\205\254\345\274\217_20180905213339.png" new file mode 100644 index 00000000..2d02f732 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180905213339.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180911214858.png" "b/_assets/\345\205\254\345\274\217_20180911214858.png" new file mode 100644 index 00000000..b0477dfd Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180911214858.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180911215014.png" "b/_assets/\345\205\254\345\274\217_20180911215014.png" new file mode 100644 index 00000000..612b60c9 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180911215014.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180911224527.png" "b/_assets/\345\205\254\345\274\217_20180911224527.png" new file mode 100644 index 00000000..5987b140 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180911224527.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180911225643.png" "b/_assets/\345\205\254\345\274\217_20180911225643.png" new file mode 100644 index 00000000..2c9e953a Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180911225643.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180911232200.png" "b/_assets/\345\205\254\345\274\217_20180911232200.png" new file mode 100644 index 00000000..129245ee Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180911232200.png" differ diff --git "a/_assets/\345\205\254\345\274\217_2018091402442.png" "b/_assets/\345\205\254\345\274\217_2018091402442.png" new file mode 100644 index 00000000..98deb26c Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_2018091402442.png" differ diff --git "a/_assets/\345\205\254\345\274\217_2018091402658.png" "b/_assets/\345\205\254\345\274\217_2018091402658.png" new file mode 100644 index 00000000..5ad181f0 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_2018091402658.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180914232209.png" "b/_assets/\345\205\254\345\274\217_20180914232209.png" new file mode 100644 index 00000000..b253067c Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180914232209.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180914232558.png" "b/_assets/\345\205\254\345\274\217_20180914232558.png" new file mode 100644 index 00000000..e1731742 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180914232558.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180915135024.png" "b/_assets/\345\205\254\345\274\217_20180915135024.png" new file mode 100644 index 00000000..0abff1aa Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180915135024.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180925142404.png" "b/_assets/\345\205\254\345\274\217_20180925142404.png" new file mode 100644 index 00000000..12817ef5 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180925142404.png" differ diff --git "a/_assets/\345\205\254\345\274\217_20180925143950.png" "b/_assets/\345\205\254\345\274\217_20180925143950.png" new file mode 100644 index 00000000..7dcd1fc5 Binary files /dev/null and "b/_assets/\345\205\254\345\274\217_20180925143950.png" differ diff --git "a/_assets/\345\205\261\347\216\260\347\237\251\351\230\265.png" "b/_assets/\345\205\261\347\216\260\347\237\251\351\230\265.png" new file mode 100644 index 00000000..253717da Binary files /dev/null and "b/_assets/\345\205\261\347\216\260\347\237\251\351\230\265.png" differ diff --git "a/_assets/\346\225\260\346\215\256\346\270\205\346\264\227\344\270\216\347\211\271\345\276\201\345\244\204\347\220\206.jpg" "b/_assets/\346\225\260\346\215\256\346\270\205\346\264\227\344\270\216\347\211\271\345\276\201\345\244\204\347\220\206.jpg" new file mode 100644 index 00000000..dfc1f03f Binary files /dev/null and "b/_assets/\346\225\260\346\215\256\346\270\205\346\264\227\344\270\216\347\211\271\345\276\201\345\244\204\347\220\206.jpg" differ diff --git "a/_assets/\346\231\256\351\200\232\345\215\267\347\247\257\344\270\216\350\206\250\350\203\200\345\215\267\347\247\257.png" "b/_assets/\346\231\256\351\200\232\345\215\267\347\247\257\344\270\216\350\206\250\350\203\200\345\215\267\347\247\257.png" new file mode 100644 index 00000000..b24d2c61 Binary files /dev/null and "b/_assets/\346\231\256\351\200\232\345\215\267\347\247\257\344\270\216\350\206\250\350\203\200\345\215\267\347\247\257.png" differ diff --git "a/_assets/\351\227\250\345\215\267\347\247\257.jpg" "b/_assets/\351\227\250\345\215\267\347\247\257.jpg" new file mode 100644 index 00000000..ee8ba3bc Binary files /dev/null and "b/_assets/\351\227\250\345\215\267\347\247\257.jpg" differ diff --git "a/_assets/\351\227\250\345\215\267\347\247\257.png" "b/_assets/\351\227\250\345\215\267\347\247\257.png" new file mode 100644 index 00000000..0a3b58c8 Binary files /dev/null and "b/_assets/\351\227\250\345\215\267\347\247\257.png" differ diff --git a/_codes/README.md b/_codes/README.md new file mode 100644 index 00000000..e69de29b diff --git "a/_codes/cpp/\351\235\242\345\220\221\345\257\271\350\261\241-\344\270\215\351\200\232\350\277\207\347\273\247\346\211\277\345\256\236\347\216\260\345\244\232\346\200\201-\345\212\250\346\200\201\345\272\217\345\210\227.cpp" "b/_codes/cpp/\351\235\242\345\220\221\345\257\271\350\261\241-\344\270\215\351\200\232\350\277\207\347\273\247\346\211\277\345\256\236\347\216\260\345\244\232\346\200\201-\345\212\250\346\200\201\345\272\217\345\210\227.cpp" new file mode 100644 index 00000000..7396bd4f --- /dev/null +++ "b/_codes/cpp/\351\235\242\345\220\221\345\257\271\350\261\241-\344\270\215\351\200\232\350\277\207\347\273\247\346\211\277\345\256\236\347\216\260\345\244\232\346\200\201-\345\212\250\346\200\201\345\272\217\345\210\227.cpp" @@ -0,0 +1,69 @@ +#include +using namespace std; + +class num_sequence { +public: + // PtrType 是一个指针,指向 num_sequence 的成员函数, + // 该成员函数必须只接受一个 int 型参数,以及返回类型为 void + typedef void (num_sequence::*PtrType)(int); + + enum { cnt_seq = 2 }; // 预定义了两种序列 + enum ns_type { + ns_fibonacci, ns_square + }; + + // 构造函数:默认指向斐波那契数列 + num_sequence(): _pmf(func_tbl[ns_fibonacci]) { } + + // 调整指针指向 + void set_sequence(ns_type nst) { + switch (nst) { + case ns_fibonacci: case ns_square: + _pmf = func_tbl[nst]; + break; + default: + cerr << "invalid sequence type\n"; + } + } + + void print(int n) { + (this->*_pmf)(n); // 通过指针选择需要调用的函数 + } + + // _pmf 可以指向以下任何一个函数 + void fibonacci(int n) { + int f = 1; + int g = 1; + for (int i = 2; i <= n; i++) + g = g + f, f = g - f; + cout << f << endl; + } + + void square(int n) { + cout << n * n << endl; + } + +private: + PtrType _pmf; + static PtrType func_tbl[cnt_seq]; // 保存所有序列函数的指针 +}; + +// static 成员变量初始化 +num_sequence::PtrType +num_sequence::func_tbl[cnt_seq] = { + &num_sequence::fibonacci, + &num_sequence::square, +}; + +int main() { + + auto ns = num_sequence(); + ns.print(5); // 5 + ns.set_sequence(num_sequence::ns_square); // 调整函数指针以获得多态的效果 + ns.print(5); // 25 + + cout << endl; + system("PAUSE"); + return 0; +} + diff --git a/_codes/machine_learning/KMeans/data.txt b/_codes/machine_learning/KMeans/data.txt new file mode 100644 index 00000000..0fae57b7 --- /dev/null +++ b/_codes/machine_learning/KMeans/data.txt @@ -0,0 +1,80 @@ +1.658985 4.285136 +-3.453687 3.424321 +4.838138 -1.151539 +-5.379713 -3.362104 +0.972564 2.924086 +-3.567919 1.531611 +0.450614 -3.302219 +-3.487105 -1.724432 +2.668759 1.594842 +-3.156485 3.191137 +3.165506 -3.999838 +-2.786837 -3.099354 +4.208187 2.984927 +-2.123337 2.943366 +0.704199 -0.479481 +-0.392370 -3.963704 +2.831667 1.574018 +-0.790153 3.343144 +2.943496 -3.357075 +-3.195883 -2.283926 +2.336445 2.875106 +-1.786345 2.554248 +2.190101 -1.906020 +-3.403367 -2.778288 +1.778124 3.880832 +-1.688346 2.230267 +2.592976 -2.054368 +-4.007257 -3.207066 +2.257734 3.387564 +-2.679011 0.785119 +0.939512 -4.023563 +-3.674424 -2.261084 +2.046259 2.735279 +-3.189470 1.780269 +4.372646 -0.822248 +-2.579316 -3.497576 +1.889034 5.190400 +-0.798747 2.185588 +2.836520 -2.658556 +-3.837877 -3.253815 +2.096701 3.886007 +-2.709034 2.923887 +3.367037 -3.184789 +-2.121479 -4.232586 +2.329546 3.179764 +-3.284816 3.273099 +3.091414 -3.815232 +-3.762093 -2.432191 +3.542056 2.778832 +-1.736822 4.241041 +2.127073 -2.983680 +-4.323818 -3.938116 +3.792121 5.135768 +-4.786473 3.358547 +2.624081 -3.260715 +-4.009299 -2.978115 +2.493525 1.963710 +-2.513661 2.642162 +1.864375 -3.176309 +-3.171184 -3.572452 +2.894220 2.489128 +-2.562539 2.884438 +3.491078 -3.947487 +-2.565729 -2.012114 +3.332948 3.983102 +-1.616805 3.573188 +2.280615 -2.559444 +-2.651229 -3.103198 +2.321395 3.154987 +-1.685703 2.939697 +3.031012 -3.620252 +-4.599622 -2.185829 +4.196223 1.126677 +-2.133863 3.093686 +4.668892 -2.562705 +-2.793241 -2.149706 +2.884105 3.043438 +-2.967647 2.848696 +4.479332 -1.764772 +-4.905566 -2.911070 diff --git a/_codes/machine_learning/KMeans/kmeans.py b/_codes/machine_learning/KMeans/kmeans.py new file mode 100644 index 00000000..93ec327a --- /dev/null +++ b/_codes/machine_learning/KMeans/kmeans.py @@ -0,0 +1,110 @@ +""" +K-Means +""" +import logging as log +import numpy as np +import random + +log.basicConfig(format="%(message)s", level=log.INFO) + + +def load_data(file_path): + """加载数据 + 源数据格式为多行,每行为两个浮点数,分别表示 (x,y) + """ + data = [] + with open(file_path, 'r', encoding='utf-8') as fr: + for line in fr.read().splitlines(): + line_float = list(map(float, line.split('\t'))) + data.append(line_float) + data = np.array(data) + return data + + +def score_euclidean(a, b): + """计算两个点之间的欧式距离""" + s = np.sqrt(np.sum(np.power(a - b, 2))) + return s + + +def rand_center(data, k): + """随机采样 k 个样本作为聚类中心""" + centers = np.array(random.sample(list(data), k)) + return centers + + +def k_means(data, k, max_iter=100, score=score_euclidean, e=1e-6): + """ + K-Means 算法 + + 一般 K-Mean 算法的终止条件有如下几个: + 1. 所有样本的类别不再改变 + 2. 达到最大迭代次数 + 3. 精度达到要求(?) + + 返回聚类中心及聚类结果 + """ + # 样本数 + n = len(data) + + # 保存结果 + # 每个结果为一个二元组 [label, score] 分别保存每个样本所在的簇及距离质心的距离 + ret = np.array([[-1, np.inf]] * n) + + # 选取聚类中心 + centers = rand_center(data, k) + + changed = True # 标记样本类别是否改变 + n_iter = 0 # 记录迭代次数 + while changed and n_iter < max_iter: + changed = False + n_iter += 1 + + for i in range(n): # 对每个数据 + i_score = np.inf + i_label = -1 + for j in range(k): # 与每个质心比较 + s_ij = score(data[i], centers[j]) + if s_ij < i_score: + i_score = s_ij + i_label = j + + if ret[i, 0] != i_label: # 样本的类别发生了改变 + changed = True + + ret[i, :] = i_label, i_score + + # 更新聚类中心 + log.info(centers) + for i in range(k): + data_i = data[ret[:, 0] == i] # 标签为 i 的样本 + centers[i, :] = np.mean(data_i, axis=0) # 按类别过滤样本 + + log.info(n_iter) # 迭代次数 + return centers, ret + + +def _test(): + """""" + file_path = r"./data.txt" + + data = load_data(file_path) + print(data) + print(np.shape(data)[1]) + + s = score_euclidean(data[0], data[1]) + print(s) + + centers = rand_center(data, 3) + print(centers) + + +if __name__ == '__main__': + """""" + # _test() + + file_path = "./data.txt" + data = load_data(file_path) + + centers, ret = k_means(data, 3) + # print(ret) diff --git "a/_codes/model/\345\200\222\346\216\222\347\264\242\345\274\225/data/a.txt" "b/_codes/model/\345\200\222\346\216\222\347\264\242\345\274\225/data/a.txt" new file mode 100644 index 00000000..ec1128d9 --- /dev/null +++ "b/_codes/model/\345\200\222\346\216\222\347\264\242\345\274\225/data/a.txt" @@ -0,0 +1 @@ +a b cb,f h f ds \ No newline at end of file diff --git "a/_codes/model/\345\200\222\346\216\222\347\264\242\345\274\225/data/b.txt" "b/_codes/model/\345\200\222\346\216\222\347\264\242\345\274\225/data/b.txt" new file mode 100644 index 00000000..7bec66d6 --- /dev/null +++ "b/_codes/model/\345\200\222\346\216\222\347\264\242\345\274\225/data/b.txt" @@ -0,0 +1 @@ +html a b c \ No newline at end of file diff --git "a/_codes/model/\345\200\222\346\216\222\347\264\242\345\274\225/data/c.txt" "b/_codes/model/\345\200\222\346\216\222\347\264\242\345\274\225/data/c.txt" new file mode 100644 index 00000000..bfbc6193 --- /dev/null +++ "b/_codes/model/\345\200\222\346\216\222\347\264\242\345\274\225/data/c.txt" @@ -0,0 +1 @@ +abc h html \ No newline at end of file diff --git "a/_codes/model/\345\200\222\346\216\222\347\264\242\345\274\225/inverse_index.py" "b/_codes/model/\345\200\222\346\216\222\347\264\242\345\274\225/inverse_index.py" new file mode 100644 index 00000000..41829875 --- /dev/null +++ "b/_codes/model/\345\200\222\346\216\222\347\264\242\345\274\225/inverse_index.py" @@ -0,0 +1,156 @@ +""" +倒排索引 + +没有任何优化,只是展示一下什么是“倒排索引” +""" + +import os + +from itertools import combinations + +from nltk.tokenize import RegexpTokenizer +from nltk import PorterStemmer + +tokenizer = RegexpTokenizer(r'\w+') +""" 分词器:匹配所有连续的字母串 +比如 "abc!@#def$%^gh, ijk" -> 'abc' 'def' 'gh' 'ijk' +""" +stemmer = PorterStemmer() +""" 词干提取器 +""" + + +def word_clean(word): + """单词清洗""" + word = word.lower() # 小写化 + word = stemmer.stem(word) # 提取词干 + return word + + +def create_inverse_index(files_list): + """对给定文件列表构建倒排索引""" + + ''' 倒排索引字典 ''' + index = dict() + ''' 频率字典:统计每个词在多少篇文档中出现 ''' + word_freq = dict() + + # 读取文件 + for f in files_list: + txt = open(f).read() + # words = word_tokenize(txt) + words = tokenizer.tokenize(txt) + # creating inverted index data structure + for word in words: + word = word_clean(word) # 单词清洗 + if word not in index: + index[word] = {f} + else: + index[word].add(f) + + for word in index.keys(): + word_freq[word] = len(index[word]) + + # print(index) + # print(word_freq) + return index, word_freq + + +# def get_all_subset(tokens: list): +# """获取 tokens 的所有非空子集""" +# tokens = set(tokens) +# +# ret = [] +# for i in range(1, len(tokens) + 1): +# ret.extend(list(combinations(tokens, i))) +# +# return ret + + +def search_tokens(tokens, inverse_index, word_freq=None): + """""" + ret = dict() + for term in tokens: + if term in inverse_index: + ret[frozenset([term])] = inverse_index[term] + else: + ret[frozenset([term])] = set() + return ret + + +def search(txt, inverse_index, word_freq=None): + """ + + Args: + txt(str): + inverse_index(dict): + word_freq(dict): + + Returns: + dict + """ + tokens = tokenizer.tokenize(txt) + tokens = [word_clean(token) for token in tokens] + + ret = search_tokens(tokens, inverse_index, word_freq) + + for i in range(2, len(tokens) + 1): + for ts in combinations(tokens, i): + ret[frozenset(ts)] = set.intersection(*[ret[frozenset([t])] for t in ts]) + + # tokens_list = get_all_subset(tokens) + # for tokens in tokens_list: + # ret[tokens] = search_tokens(tokens, inverse_index, word_freq) + + return ret + + +def printy(ret): + """ + + Args: + ret(dict): + + Returns: + + """ + for k, v in ret.items(): + tokens = list(k) + print(tokens) + if v: + for t in v: + print('\t', t) + else: + print('\t', '---') + + +if __name__ == '__main__': + dir_path = './data' + files = ['a.txt', 'b.txt', 'c.txt'] + files = [os.path.join(dir_path, file) for file in files] + + inverse_index, word_freq = create_inverse_index(files) + # print(inverse_index) + + search_txt = 'html a z' + ret = search(search_txt, inverse_index, word_freq) + # print(ret) + printy(ret) + r""" + ['html'] + ./data\b.txt + ./data\c.txt + ['a'] + ./data\b.txt + ./data\a.txt + ['z'] + - + ['a', 'html'] + ./data\b.txt + ['html', 'z'] + - + ['a', 'z'] + - + ['a', 'html', 'z'] + - + """ diff --git a/_codes/my_nlp/data/sentences.txt b/_codes/my_nlp/data/sentences.txt new file mode 100644 index 00000000..be3f42fd --- /dev/null +++ b/_codes/my_nlp/data/sentences.txt @@ -0,0 +1,2 @@ +this is an example sentence +this is another sentence that is longer \ No newline at end of file diff --git a/_codes/my_nlp/data/vocab.txt b/_codes/my_nlp/data/vocab.txt new file mode 100644 index 00000000..ee72afd3 --- /dev/null +++ b/_codes/my_nlp/data/vocab.txt @@ -0,0 +1,8 @@ +this 2 +is 3 +an 1 +example 1 +sentence 2 +another 1 +that 1 +longer 1 diff --git a/_codes/my_nlp/data/wv.txt b/_codes/my_nlp/data/wv.txt new file mode 100644 index 00000000..93f59a6a --- /dev/null +++ b/_codes/my_nlp/data/wv.txt @@ -0,0 +1,8 @@ +UNK 0.47473686064391796 0.2551379454015914 -0.09549180462608997 0.3525185101533437 -0.38921288549115596 0.09504760365238507 -0.0036589541792683145 -0.48320105411602277 0.03106995976609661 -0.12896052420266613this -0.26089280501684253 0.33709184364200495 -0.31317720987724795 0.4884969525943772 -0.375775757500155 -0.4408907057677858 0.1626928604750396 0.34127112099202794 0.24273974054518532 -0.1258628397124486 +is -0.036770529433394095 -0.3307659689898752 -0.15123693125492044 -0.4003801872340075 -0.49890851999804264 0.3462680261104789 0.06281867512346573 0.19124725844794555 -0.09333687821977321 -0.28290864230709956 +an -0.13831471295467157 -0.08229734738869166 0.42485934855453766 -0.1568549181775879 -0.37150568386870375 -0.4254058716743848 -0.045217314588190005 -0.16636653725389572 0.42166693833437774 -0.1912929379787459 +example -0.3970175796857577 0.3774091379331148 -0.07936074607671506 0.26018095825827847 -0.11033571909158335 -0.2812467504912525 0.365432690458945 -0.34027180100996446 -0.08291609732944538 -0.1957961319484557 +sentence -0.16769793036513336 -0.49778222033306474 0.0805611572307452 0.01395208889744215 -0.3405489883140319 0.08562891588552923 -0.33000114404463576 0.1561567542704626 -0.4629981947574374 -0.49907718588464633 +another 0.32992576543032615 -0.15876817076828786 0.08734737477239307 0.43666224851650315 -0.442407947831607 0.1032785293548546 -0.25330078020862745 -0.21341307622232775 0.07736936338956757 0.054750112318045385 +that 0.13087730284414556 0.39576931112666924 -0.22831040521133195 -0.47731855903716114 -0.2163400426893014 0.01342051694410229 0.1776704980749526 0.015963248029155497 -0.4389772156249837 0.43103963719421834 +longer -0.13943768241588395 -0.3680059365859867 -0.2429361146512169 0.35858588107327294 0.15384243506273243 -0.28324404684118987 0.2695076804036787 0.4978468313066393 -0.42255126846059987 0.3172934883201699 diff --git a/_codes/my_nlp/src/__init__.py b/_codes/my_nlp/src/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/_codes/my_nlp/src/sentence2vec/__init__.py b/_codes/my_nlp/src/sentence2vec/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/_codes/my_nlp/src/sentence2vec/sif.py b/_codes/my_nlp/src/sentence2vec/sif.py new file mode 100644 index 00000000..1fb6abd9 --- /dev/null +++ b/_codes/my_nlp/src/sentence2vec/sif.py @@ -0,0 +1,8 @@ +""" +Smooth Inverse Frequency(SIF) + +References: + A Simple but Tough-to-Beat Baseline for Sentence Embeddings, 2016 + https://github.com/PrincetonML/SIF +""" + diff --git a/_codes/my_nlp/src/utils/__init__.py b/_codes/my_nlp/src/utils/__init__.py new file mode 100644 index 00000000..45e70e76 --- /dev/null +++ b/_codes/my_nlp/src/utils/__init__.py @@ -0,0 +1,7 @@ +""" + +""" + + +def get_w2v(file_path): + """提取词向量""" diff --git a/_codes/my_nlp/src/utils/vocab.py b/_codes/my_nlp/src/utils/vocab.py new file mode 100644 index 00000000..5d20df59 --- /dev/null +++ b/_codes/my_nlp/src/utils/vocab.py @@ -0,0 +1,13 @@ +"""""" + + +def set_vocab(sentences, save_path=None): + """""" + + if save_path is not None: + with open(save_path, 'w', encoding='utf8') as f: + """""" + + +def get_vocab(file_path): + """""" diff --git a/_codes/my_tensorflow/README.md b/_codes/my_tensorflow/README.md new file mode 100644 index 00000000..f918efc3 --- /dev/null +++ b/_codes/my_tensorflow/README.md @@ -0,0 +1,11 @@ +my_tensorflow +=== + +Requirements +--- +- Tensorflow 1.0+ (1.8) + +Reference +--- +- Keras +- TensorLayer \ No newline at end of file diff --git a/_codes/my_tensorflow/src/__init__.py b/_codes/my_tensorflow/src/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/_codes/my_tensorflow/src/activations/__init__.py b/_codes/my_tensorflow/src/activations/__init__.py new file mode 100644 index 00000000..21f05d2f --- /dev/null +++ b/_codes/my_tensorflow/src/activations/__init__.py @@ -0,0 +1,75 @@ +"""激活函数""" + +from .relu import * + + +def linear(x): + """""" + return x + + +def identity(x): + """""" + return tf.identity(x) + + +def sigmoid(x): + """""" + return tf.nn.sigmoid(x) + + +def hard_sigmoid(x): + """ + x = 0. x < -2.5 + = 1. x > 2.5 + = 0.2 * x + 0.5 otherwise + """ + x = (0.2 * x) + 0.5 + x = tf.clip_by_value(x, 0., 1.) + return x + + +def tanh(x): + """""" + return tf.nn.tanh(x) + + +def softplus(x): + """""" + return tf.nn.softplus(x) + + +def softsign(x): + """ + o = x / (1 + abs(x)) + """ + return tf.nn.softsign(x) + + +def softmax(x, axis=-1): + """ + Examples: + n_dim = x.get_shape().ndims + assert n_dim >= 2 + + if n_dim == 2: + return tf.nn.softmax(x) + else: + e = tf.exp(x - tf.reduce_max(x, axis=axis, keepdims=True)) + s = tf.reduce_sum(e, axis=axis, keepdims=True) + return e / s + """ + return tf.nn.softmax(x, axis=axis) + + +def elu(x): + """指数线性单元""" + return tf.nn.elu(x) + + +def selu(x): + """缩放型指数线性单元""" + alpha = 1.6732632423543772848170429916717 + scale = 1.0507009873554804934193349852946 + o = tf.nn.elu(x) + return scale * tf.where(x > 0, o, alpha * o) diff --git a/_codes/my_tensorflow/src/activations/relu.py b/_codes/my_tensorflow/src/activations/relu.py new file mode 100644 index 00000000..e9dc662d --- /dev/null +++ b/_codes/my_tensorflow/src/activations/relu.py @@ -0,0 +1,60 @@ +"""ReLU 系列激活函数""" +import tensorflow as tf + +from ..utils import get_w, get_shape +from ..initializers import constant + + +def relu(x): + """ReLU + `o = max(0., x)` + """ + return tf.nn.relu(x) + + +def relu6(x): + """ + `o = min(max(x, 0), 6)` + """ + return tf.nn.relu6(x) + + +def crelu(x, axis=-1): + """Concatenated ReLU + """ + return tf.nn.crelu(x, axis=axis) + + +def leaky_relu(x, alpha=0.1): + """渗透 ReLU + `o = max(alpha * x, x)` + """ + return tf.nn.leaky_relu(x, alpha) + + +def clip_relu(x, max_value): + """截断 ReLU + `o = min(max(0., x), max_value)` + """ + o = tf.nn.relu(x) + o = tf.minimum(o, max_value) + return o + + +def parametric_relu(x, channel_shared=False, alpha_init=constant(0.), name="parametric_relu", reuse=None): + """参数化 ReLU + + References: + tflearn.prelu + """ + if channel_shared: + alpha_shape = get_shape(x)[-1:] + else: + alpha_shape = [1] + + with tf.variable_scope(name, reuse=reuse): + alpha = get_w(alpha_shape, w_initializer=alpha_init, name="alpha") + # o = relu(x) + 0.5 * tf.multiply(alpha, x - tf.abs(x)) # TFLearn + o = leaky_relu(x, alpha) # TensorLayer / + + return o diff --git a/_codes/my_tensorflow/src/initializers/__init__.py b/_codes/my_tensorflow/src/initializers/__init__.py new file mode 100644 index 00000000..f452ddd3 --- /dev/null +++ b/_codes/my_tensorflow/src/initializers/__init__.py @@ -0,0 +1,17 @@ +"""变量初始化器""" + +import tensorflow as tf + + +'''常用的 Tensorflow 基本初始化器''' +# 零 +zeros = tf.initializers.zeros +# 常量 +constant = tf.initializers.constant +# 标准正太分布 +normal = tf.initializers.random_normal +# 截断正态分布 +truncated_normal = tf.initializers.truncated_normal +# 均匀分布 +uniform = tf.initializers.random_uniform +# diff --git a/_codes/my_tensorflow/src/layers/__init__.py b/_codes/my_tensorflow/src/layers/__init__.py new file mode 100644 index 00000000..754b3327 --- /dev/null +++ b/_codes/my_tensorflow/src/layers/__init__.py @@ -0,0 +1,9 @@ +"""常见层与常用层的写法 +""" + +from .dense import * +from .cnn import * +from .highway import * +from .attention import * +from .match import * + diff --git a/_codes/my_tensorflow/src/layers/attention.py b/_codes/my_tensorflow/src/layers/attention.py new file mode 100644 index 00000000..3971acc2 --- /dev/null +++ b/_codes/my_tensorflow/src/layers/attention.py @@ -0,0 +1,118 @@ +"""Attention 层 + +作用与 highway 类似 +经过 Attention 层后维度应该保持一致 + +References: + https://github.com/philipperemy/keras-attention-mechanism +""" + +import tensorflow as tf + +from ..utils import get_wb, permute +from ..activations import softmax + + +def attention_for_dense(x, name=None, reuse=None): + """ + Input shape: [batch_size, n_input] + Output shape: [batch_size, n_input] + + 公式 + `o = x * softmax(Wx + b)` + + 一般用法 + ``` + x = attention1d(x) + o = dense(x, n_unit) + ``` + """ + n_input = int(x.get_shape()[-1]) + + with tf.variable_scope(name or "attention_for_dense", reuse=reuse): + W, b = get_wb([n_input, n_input]) + + a = softmax(tf.matmul(x, W) + b) # attention + o = tf.multiply(x, a) + + return o + + +def attention_for_rnn(x, n_step=None, name=None, reuse=None, use_mean_attention=False): + """ + Input shape: [batch_size, n_step, n_input] + Output shape: [batch_size, n_step, n_input] + + Examples: + 以下示例使用了 TensorLayer 库来快速构建模型;本来想用 Keras,但是无法完整获取中间输出的 shape + + Use attention **after** lstm: + ``` + tf.reset_default_graph() + + # Input shape: [128, 5, 32] + x = tf.constant(np.arange(10240, dtype=np.float32).reshape([128, 16, 5])) + x = InputLayer(x) + + # Use attention after lstm + x = RNNLayer(x, tf.nn.rnn_cell.LSTMCell, n_hidden=32) + x = attention_for_rnn(x.outputs) + + x = InputLayer(x) + x = FlattenLayer(x) + x = DenseLayer(x, n_units=1, act=tf.nn.sigmoid) + o = x.outputs + + with tf.Session() as sess: + tf.global_variables_initializer().run() + o_val = o.eval() + ``` + + Use attention **before** lstm: + ``` + tf.reset_default_graph() + + # Input shape: [128, 5, 32] + x = tf.constant(np.arange(10240, dtype=np.float32).reshape([128, 16, 5])) + + # Use attention before lstm + x = attention_for_rnn(x) + x = InputLayer(x) + x = RNNLayer(x, tf.nn.rnn_cell.LSTMCell, n_hidden=32, return_last=True) + + # x = FlattenLayer(x) + x = DenseLayer(x, n_units=1, act=tf.nn.sigmoid) + o = x.outputs + + with tf.Session() as sess: + tf.global_variables_initializer().run() + o_val = o.eval() + ``` + """ + n_input = int(x.get_shape()[-1]) + if n_step is None: + n_step = int(x.get_shape()[-2]) # 这种写法,不兼容 keras.layers.LSTM,此时需要手工传入 n_step + + with tf.variable_scope(name or "attention_for_rnn", reuse=reuse): + a = permute(x, [0, 2, 1]) # [batch_size, n_input, n_step] + a = tf.reshape(a, [-1, n_step]) # [batch_size*n_input, n_step] + + W, b = get_wb([n_step, n_step]) + a = softmax(tf.matmul(a, W) + b) + a = tf.reshape(a, [-1, n_input, n_step]) # [batch_size, n_input, n_step] + + if use_mean_attention: + a = tf.reduce_mean(a, axis=1) # [batch_size, n_step] + a = tf.expand_dims(a, axis=1) # [batch_size, 1, n_step] + a = tf.tile(a, [1, n_input, 1]) # [batch_size, n_input, n_step] + + a = permute(a, [0, 2, 1]) # [batch_size, n_step, n_input] + o = tf.multiply(x, a) # # [batch_size, n_step, n_input] + + return o + + + + + + diff --git a/_codes/my_tensorflow/src/layers/cnn.py b/_codes/my_tensorflow/src/layers/cnn.py new file mode 100644 index 00000000..5168b29f --- /dev/null +++ b/_codes/my_tensorflow/src/layers/cnn.py @@ -0,0 +1,57 @@ +"""卷积层 + +References: + tensorlayer.layers.Conv2dLayer +""" +import tensorflow as tf + +from ..activations import relu +from ..utils import get_wb, get_shape + + +# TODO(huay) +def conv1d(): + """""" + + +def conv2d(x, kernel_size, out_channels, + act_fn=relu, + strides=1, + padding="SAME", + name=None, + reuse=None): + """2-D 卷积层 + Input shape: [batch_size, in_h, in_w, in_channels] + Output shape: [batch_size, out_h, out_w, out_channels] + + Args: + x(tf.Tensor): + kernel_size(int or list of int): + out_channels(int): + act_fn(function): + strides(int or list of int): + padding(str): + name(str): + reuse(bool): + + Returns: + + """ + if isinstance(kernel_size, int): + kernel_size = [kernel_size] * 2 + if isinstance(strides, int): + strides = [strides] * 4 + + assert len(kernel_size) == 2 + assert len(strides) == 4 + + in_channels = get_shape(x)[-1] + kernel_shape = list(kernel_size) + [in_channels, out_channels] # [kernel_h, kernel_w, in_channels, out_channels] + + with tf.variable_scope(name or "conv2d", reuse=reuse): + W, b = get_wb(kernel_shape) + + o = tf.nn.conv2d(x, W, strides=strides, padding=padding) + b + o = act_fn(o) + + return o diff --git a/_codes/my_tensorflow/src/layers/dense.py b/_codes/my_tensorflow/src/layers/dense.py new file mode 100644 index 00000000..c03fada4 --- /dev/null +++ b/_codes/my_tensorflow/src/layers/dense.py @@ -0,0 +1,98 @@ +""" +全连接层 + +References: + tensorlayer.layers.DenseLayer + keras.layers.Dense +""" +import tensorflow as tf + +from ..utils import get_wb +from ..activations import relu +from ..activations import linear + + +def dense(x, n_unit, act_fn=relu, name=None, reuse=None): + """全连接层 + Input shape: [batch_size, n_input] + Output shape: [batch_size, n_unit] + + 如果需要 reuse 推荐使用类实现的 `Dense` + + Args: + x(tf.Tensor): + n_unit(int): + act_fn: + name(str): + reuse(bool): + """ + # n_input = tf.shape(x)[-1] # err: need int but tensor + n_input = int(x.get_shape()[-1]) + with tf.variable_scope(name or "dense", reuse=reuse): + W, b = get_wb([n_input, n_unit]) + o = act_fn(tf.matmul(x, W) + b) + return o + + +def multi_dense(x, n_unit_ls, act_fn=relu, name=None): + """多层全连接 + Input shape: [batch_size, n_input] + Output shape: [batch_size, n_unit_list[-1]] + + Args: + x(tf.Tensor): + n_unit_ls(list of int): + act_fn: + name(str): + """ + # n_layer = len(n_unit_list) + name = name or "dense" + for i, n_unit in enumerate(n_unit_ls): + x = dense(x, n_unit, act_fn=act_fn, name="{}-{}".format(name, i)) + + return x + + +def linear_dense(x, n_unit, name=None, reuse=None): + """线性全连接层 + Input shape: [batch_size, n_input] + Output shape: [batch_size, n_unit] + + Args: + x(tf.Tensor): + n_unit(int): + name(str): + reuse(bool) + """ + return dense(x, n_unit, act_fn=linear, name=(name or "linear_dense"), reuse=reuse) + + +class Dense(object): + """全连接层的类实现,方便 reuse""" + + def __init__(self, n_unit, act_fn=relu, name=None): + """""" + self.n_unit = n_unit + self.act_fn = act_fn + self.name = name + + self._built = False + + def _build(self, n_input): + """""" + with tf.variable_scope(self.name or "dense"): + self.W, self.b = get_wb([n_input, self.n_unit]) + + def _call(self, x): + """""" + o = self.act_fn(tf.matmul(x, self.W) + self.b) + return o + + def __call__(self, x): + """""" + n_input = int(x.get_shape()[-1]) + if not self._built: + self._build(n_input) + self._built = True + + return self._call(x) diff --git a/_codes/my_tensorflow/src/layers/embedding/__init__.py b/_codes/my_tensorflow/src/layers/embedding/__init__.py new file mode 100644 index 00000000..7d04b58e --- /dev/null +++ b/_codes/my_tensorflow/src/layers/embedding/__init__.py @@ -0,0 +1,3 @@ +"""""" + +from .char_cnn import * \ No newline at end of file diff --git a/_codes/my_tensorflow/src/layers/embedding/char_cnn.py b/_codes/my_tensorflow/src/layers/embedding/char_cnn.py new file mode 100644 index 00000000..f95da1aa --- /dev/null +++ b/_codes/my_tensorflow/src/layers/embedding/char_cnn.py @@ -0,0 +1,42 @@ +"""CharCNN Embedding Layer + +References: + [1509.01626] Character-level Convolutional Networks for Text Classification https://arxiv.org/abs/1509.01626 +""" + +import tensorflow as tf + +from ...utils import get_shape, get_w + + +# TODO(huay): char_cnn_embedding +def char_cnn_embedding(x, c_embed_size=8, share_cnn_weights=True, name="char_cnn_embedding", reuse=None): + """ + In: [N, max_n_word, max_n_char] + Out: [N, max_n_word, c_embed_size] + + max_n_word: 句子的最大长度 + max_n_char: 单词的最大长度 + + Args: + x: + c_embed_size: + share_cnn_weights: + name: + reuse: + + Returns: + + """ + max_sentence_len, max_word_len, char_vocab_size = get_shape(x)[1:] + + with tf.variable_scope(name, reuse=reuse): + char_embed_mat = get_w([char_vocab_size, c_embed_size], name="char_embed_matrix") + + + + + + + + diff --git a/_codes/my_tensorflow/src/layers/highway.py b/_codes/my_tensorflow/src/layers/highway.py new file mode 100644 index 00000000..63cc59e9 --- /dev/null +++ b/_codes/my_tensorflow/src/layers/highway.py @@ -0,0 +1,105 @@ +"""高速网络 Highway Network + +注意 x 经过 Highway 之后维度应该保持不变 + +References: + https://github.com/fomorians/highway-fcn + https://github.com/fomorians/highway-cnn +""" +import tensorflow as tf + +from ..utils import get_wb +from ..activations import relu, sigmoid + + +def highway_dense(x, act_fn=relu, carry_bias=-1.0, name=None): + """用于全连接层的 highway + Input shape: [batch_size, n_input] + Output shape: [batch_size, n_input] + + 公式 + `o = H(x, W)T(x, W) + x(1 - T(x, W))` + 其中 + H, T = dense + """ + n_input = int(x.get_shape()[-1]) + with tf.variable_scope(name or "highway_dense"): + W, b = get_wb([n_input, n_input]) + + with tf.variable_scope("transform"): + W_T, b_T = get_wb([n_input, n_input], b_initializer=tf.initializers.constant(carry_bias)) + + H = act_fn(tf.matmul(x, W) + b) + T = sigmoid(tf.matmul(x, W_T) + b_T) + o = tf.multiply(H, T) + tf.multiply(x, (1. - T)) + + return o + + +def multi_highway_dense(x, n_layer, act_fn=relu, carry_bias=-1.0, name=None): + """多层 highway_dense + Input shape: [batch_size, n_input] + Output shape: [batch_size, n_input] + """ + name = name or "highway_dense" + for i in range(n_layer): + x = highway_dense(x, act_fn=act_fn, carry_bias=carry_bias, name="{}-{}".format(name, i)) + + return x + + +def highway_conv2d(x, kernel_size, + act_fn=relu, + strides=1, + padding="SAME", + carry_bias=-1.0, + name=None): + """用于 conv2d 的 highway + Input shape: [batch_size, in_h, in_w, in_channels] + Output shape: [batch_size, in_h, in_w, in_channels] + + 公式 + `o = H(x, W)T(x, W) + x(1 - T(x, W))` + 其中 + H, T = conv2d + """ + if isinstance(kernel_size, int): + kernel_size = [kernel_size] * 2 + if isinstance(strides, int): + strides = [strides] * 4 + + assert len(kernel_size) == 2, "len(kernel_size) == 2" + assert len(strides) == 4, "len(strides) == 4" + + in_channels = int(x.get_shape()[-1]) + kernel_shape = list(kernel_size) + [in_channels, in_channels] + + with tf.variable_scope(name or "highway_conv2d"): + W, b = get_wb(kernel_shape, b_initializer=tf.initializers.constant(carry_bias)) + + with tf.variable_scope("transform"): + W_T, b_T = get_wb(kernel_shape) + + H = act_fn(tf.nn.conv2d(x, W, strides=strides, padding=padding) + b) + T = sigmoid(tf.nn.conv2d(x, W_T, strides=strides, padding=padding) + b_T) + o = tf.multiply(H, T) + tf.multiply(x, (1. - T)) + return o + + +def multi_highway_conv2d(x, kernel_size, n_layer, + act_fn=relu, + strides=1, + padding="SAME", + carry_bias=-1.0, + name=None): + """多层 highway_conv2d""" + if isinstance(kernel_size, int): + kernel_size = [kernel_size] * n_layer + + assert len(kernel_size) == n_layer, "len(kernel_size) == n_layer" + + name = name or "highway_conv2d" + for i, kz in enumerate(kernel_size): + x = highway_conv2d(x, kz, act_fn, strides, padding, carry_bias, name="{}-{}".format(name, i)) + + return x diff --git a/_codes/my_tensorflow/src/layers/match/__init__.py b/_codes/my_tensorflow/src/layers/match/__init__.py new file mode 100644 index 00000000..e2408f48 --- /dev/null +++ b/_codes/my_tensorflow/src/layers/match/__init__.py @@ -0,0 +1,3 @@ +"""匹配层""" + +from .attention_flow import * diff --git a/_codes/my_tensorflow/src/layers/match/attention_flow.py b/_codes/my_tensorflow/src/layers/match/attention_flow.py new file mode 100644 index 00000000..5df55ff7 --- /dev/null +++ b/_codes/my_tensorflow/src/layers/match/attention_flow.py @@ -0,0 +1,167 @@ +"""Attention Flow 匹配层 + +Attention flow layer is responsible for linking and fusing information from the context and the query words. + +References: + [paper] Bidirectional Attention Flow for Machine Comprehension (https://arxiv.org/abs/1611.01603) + [github/DuReader] https://github.com/baidu/DuReader/blob/master/tensorflow/layers/match_layer.py + [github/BiDAF(PyTorch)] https://github.com/jojonki/BiDAF/blob/master/layers/bidaf.py +""" + +import tensorflow as tf + +from ...activations import softmax +from ...utils import get_w + + +def attention_flow_self(h, u, T=None, J=None, d=None, name=None, reuse=None): + """Attention Flow Self Match Layer + Input shape: + h: [N, T, d] # 原文中的 shape 为 [N, T, 2d], 因为经过了 bi-LSTM, 维度扩了一倍 + u: [N, J, d] + Output shape: + [N, T, 4d] + + 这里用到的变量名严格按照论文中的定义 + + 后缀 self 的意思是直接使用 h 和 u 的 cosine 相似度作为相似度矩阵,因此该层没有新的参数—— DuReader的实现方式 + 原文使用了可训练的相似度矩阵,带参数的版本参考 `attention_flow()` + + 这里实际上没有用到 J 和 d 这个参数,保留是为了与 `attention_flow()` 的参数兼容 + + Args: + h: context encoding shape: [N, T, d] + u: question encoding shape: [N, J, d] + T(int): context length + J(int): question length + d(int): features size + name(str): + reuse(bool): + """ + T = T or int(h.get_shape()[-2]) + + with tf.variable_scope(name or "attention_flow_self", reuse=reuse): + # similarity matrix + S = tf.matmul(h, u, transpose_b=True) # [N, T, J] + + # u_tilde(u~): context to question attended query vectors + u_tilde = tf.matmul(softmax(S), u) # [N, T, d] + + # h_tilde(h~): question to context attended query vectors + b = tf.reduce_max(S, axis=2) # [N, T] + b = softmax(b, axis=-1) # [N, T] + b = tf.expand_dims(b, axis=1) # [N, 1, T] + h_tilde = tf.matmul(b, h) # [N, 1, d] + h_tilde = tf.tile(h_tilde, [1, T, 1]) # [N, T, d] + + g = tf.concat([h, u_tilde, h * u_tilde, h * h_tilde], axis=-1) # [N, T, 4d] + + return g + + +def attention_flow(h, u, T=None, J=None, d=None, name=None, reuse=None): + """Attention Flow Match Layer + Input shape: + h: [N, T, d] # 原文中的 shape 为 [N, T, 2d], 因为经过了 bi-LSTM, 维度扩了一倍 + u: [N, J, d] + Output shape: + [N, T, 4d] + + Args: + h: context encoding shape: [N, T, d] + u: question encoding shape: [N, J, d] + T(int): context length + J(int): question length + d(int): features size + name(str): + reuse(bool): + + Returns: + + """ + d = d or int(h.get_shape()[-1]) + T = T or int(h.get_shape()[-2]) + J = J or int(u.get_shape()[-2]) + + with tf.variable_scope(name or "attention_flow", reuse=reuse): + h_expand = tf.tile(tf.expand_dims(h, axis=2), [1, 1, J, 1]) # [N, T, J, d] + u_expand = tf.tile(tf.expand_dims(u, axis=1), [1, T, 1, 1]) # [N, T, J, d] + hu = tf.multiply(h_expand, u_expand) # [N, T, J, d] + h_u_hu = tf.concat([h_expand, u_expand, hu], axis=-1) # [N, T, J, 3d] + W_s = get_w([3 * d, 1]) # [3d, 1] + + # similarity matrix + S = tf.reshape(tf.einsum("ntjd,do->ntjo", h_u_hu, W_s), [-1, T, J]) # [N, T, J] + # 以上操作等价于 + # S = tf.reshape(tf.matmul(tf.reshape(h_u_hu, [-1, 3*d]), W_s), [-1, T, J]) + + # 得到 S 后,下面的操作就与 `attention_flow_self` 一样了 + + # u_tilde(u~): context to question attended query vectors + u_tilde = tf.matmul(softmax(S), u) # [N, T, d] + + # h_tilde(h~): question to context attended query vectors + b = tf.reduce_max(S, axis=2) # [N, T] + b = softmax(b, axis=-1) # [N, T] + b = tf.expand_dims(b, axis=1) # [N, 1, T] + h_tilde = tf.matmul(b, h) # [N, 1, d] + h_tilde = tf.tile(h_tilde, [1, T, 1]) # [N, T, d] + + g = tf.concat([h, u_tilde, h * u_tilde, h * h_tilde], axis=-1) # [N, T, 4d] + + return g + + +# # Test +# def attention_flow_2(h, u, T=None, J=None, d=None, name=None, reuse=None): +# """Attention Flow Match Layer +# Input shape: +# h: [N, T, d] # 原文中的 shape 为 [N, T, 2d], 因为经过了 bi-LSTM, 维度扩了一倍 +# u: [N, J, d] +# Output shape: +# [N, T, 4d] +# +# Args: +# h: context encoding shape: [N, T, d] +# u: question encoding shape: [N, J, d] +# T(int): context length +# J(int): question length +# d(int): features size +# name(str): +# reuse(bool): +# +# Returns: +# +# """ +# print("Test") +# d = d or int(h.get_shape()[-1]) +# T = T or int(h.get_shape()[-2]) +# J = J or int(u.get_shape()[-2]) +# +# with tf.variable_scope(name or "attention_flow", reuse=reuse): +# h_expand = tf.tile(tf.expand_dims(h, axis=2), [1, 1, J, 1]) +# u_expand = tf.tile(tf.expand_dims(u, axis=1), [1, T, 1, 1]) +# hu = tf.multiply(h_expand, u_expand) # [N, T, J, d] +# h_u_hu = tf.concat([h_expand, u_expand, hu], axis=-1) # [N, T, J, 3d] +# W_s = get_w([3 * d, 1]) # [3d, 1] +# +# # similarity matrix +# # S = tf.reshape(tf.einsum("ntjd,do->ntjo", h_u_hu, W_s), [-1, T, J]) +# # 以上操作等价于 +# S = tf.reshape(tf.matmul(tf.reshape(h_u_hu, [-1, 3 * d]), W_s), [-1, T, J]) +# +# # 得到 S 后,下面的操作就与 `attention_flow_self` 一样了 +# +# # u_tilde(u~): context to question attended query vectors +# u_tilde = tf.matmul(softmax(S), u) # [N, T, d] +# +# # h_tilde(h~): question to context attended query vectors +# b = tf.reduce_max(S, axis=2) # [N, T] +# b = softmax(b, axis=-1) # [N, T] +# b = tf.expand_dims(b, axis=1) # [N, 1, T] +# h_tilde = tf.matmul(b, h) # [N, 1, d] +# h_tilde = tf.tile(h_tilde, [1, T, 1]) # [N, T, d] +# +# g = tf.concat([h, u_tilde, h * u_tilde, h * h_tilde], axis=-1) # [N, T, 4d] +# +# return g diff --git a/_codes/my_tensorflow/src/regularizers/L1L2.py b/_codes/my_tensorflow/src/regularizers/L1L2.py new file mode 100644 index 00000000..faaf9fd2 --- /dev/null +++ b/_codes/my_tensorflow/src/regularizers/L1L2.py @@ -0,0 +1,48 @@ +""" +L1 和 L2 正则化 + +References: + keras.regularizers +""" +import tensorflow as tf +import numpy as np + + +class L1L2Regularizer(object): + """L1 L2 正则化 + + Examples: + l2_regularizer = l2(0.01) + tf.get_variable(..., regularizer=l2_regularizer, ...) + """ + + def __init__(self, l1=0., l2=0.): + """ + Args: + l1(float): L1 正则化的系数 + l2(float): L2 正则化的系数 + """ + self.l1 = np.asarray(l1, dtype=np.float32) + self.l2 = np.asarray(l2, dtype=np.float32) + + def __call__(self, x): + """ + Args: + x: 注意 x.dtype == float32 + """ + # x = tf.cast(x, dtype=tf.float32) # 交给外部处理 + loss_regularization = 0. + if self.l1: + loss_regularization += tf.reduce_sum(self.l1 * tf.abs(x)) + if self.l2: + loss_regularization += tf.reduce_sum(self.l2 * tf.square(x)) + return loss_regularization + + +"""预定义好的正则化器 +""" +l1_regularizer = L1L2Regularizer(l1=0.01) + +l2_regularizer = L1L2Regularizer(l2=0.01) + +l1_l2_regularizer = L1L2Regularizer(l1=0.01, l2=0.01) diff --git a/_codes/my_tensorflow/src/regularizers/__init__.py b/_codes/my_tensorflow/src/regularizers/__init__.py new file mode 100644 index 00000000..fca6b4ab --- /dev/null +++ b/_codes/my_tensorflow/src/regularizers/__init__.py @@ -0,0 +1,9 @@ +""" +正则化函数 + `Tensor -> Tensor or None` + +Examples: + l2_regularizer = l2(0.01) + tf.get_variable(..., regularizer=l2_regularizer, ...) +""" +from .L1L2 import * diff --git a/_codes/my_tensorflow/src/utils/__init__.py b/_codes/my_tensorflow/src/utils/__init__.py new file mode 100644 index 00000000..a17b7e3c --- /dev/null +++ b/_codes/my_tensorflow/src/utils/__init__.py @@ -0,0 +1,96 @@ +""" +工具函数 +""" +from pprint import pprint +from functools import reduce +from operator import mul + +from .array_op import * +from .math_op import * +from ..regularizers import l2_regularizer + +import numpy as np +import tensorflow as tf + +tf_float = tf.float32 +zeros = tf.initializers.zeros +truncated_normal = tf.initializers.truncated_normal + +SPLIT_LINE = "-----------" + + +def foo(): + print("foo") + + +def get_shape(x): + """ + References: + tflearn.utils.get_incoming_shape + """ + if isinstance(x, (tf.Tensor, tf.SparseTensor)): + return x.get_shape().as_list() + elif type(x) in [np.array, np.ndarray, list, tuple]: + return list(np.shape(x)) + else: + raise Exception("Invalid `x`.") + + +def get_wb(shape, + w_initializer=truncated_normal, + b_initializer=zeros, + w_regularizer=l2_regularizer, + b_regularizer=None, # 一般不对偏置做权重惩罚,可能会导致欠拟合 + name=None): + """""" + name = "" if name is None else name + '_' + W = tf.get_variable(name + 'W', shape=shape, + dtype=tf_float, initializer=w_initializer, regularizer=w_regularizer) + b = tf.get_variable(name + 'b', shape=shape[-1:], + dtype=tf_float, initializer=b_initializer, regularizer=b_regularizer) + return W, b + + +def get_w(shape, + w_initializer=truncated_normal, + w_regularizer=l2_regularizer, + name=None): + name = name or 'W' + W = tf.get_variable(name, shape, dtype=tf_float, initializer=w_initializer, + regularizer=w_regularizer) + return W + + +def get_params_dict(): + """以字典形式获取所有 trainable 参数""" + param_dict = dict() + for var in tf.trainable_variables(): + param_dict[var.name] = {"shape": list(map(int, var.shape)), + "number": int(reduce(mul, var.shape, 1))} + return param_dict + + +def print_params_dict(): + """""" + print(SPLIT_LINE) + print("params_dict") + param_dict = get_params_dict() + # pprint(param_dict, indent=2) + for k, v in param_dict.items(): + print(' ', k, '\t', end='') + pprint(v, indent=2) + # for vk, vv in v.items(): + # print(vk, '-', vv, '\t', end='') + # print() + + +def get_params_number(): + """获取参数总量""" + param_dict = get_params_dict() + return sum((item["number"] for item in param_dict.values())) + + +def print_params_number(): + """""" + print(SPLIT_LINE) + print("params_number:", get_params_number()) diff --git a/_codes/my_tensorflow/src/utils/array_op.py b/_codes/my_tensorflow/src/utils/array_op.py new file mode 100644 index 00000000..e44c8949 --- /dev/null +++ b/_codes/my_tensorflow/src/utils/array_op.py @@ -0,0 +1,43 @@ +"""Tensor Transformations + +References: + https://www.tensorflow.org/api_guides/python/array_ops + keras.backend +""" + +import tensorflow as tf +import keras.backend as K + + +def permute(x, perm): + """ + Examples: + x.shape == [128, 32, 1] + x = permute(x, [0, 2, 1]) + x.shape == [128, 1, 32] + + y.shape == [128, 64, 32] + y = permute(x, [2, 1, 0]) + y.shape == [32, 64, 128] + + References: + K.permute_dimensions() + """ + return tf.transpose(x, perm) + + +def repeat(x, n): + """ + Examples: + x.shape == [batch_size, n_input] + x = repeat(x, n_step) + x.shape == [batch_size, n_step, n_input] + + References: + K.repeat() + tf.tile() + """ + assert x.get_shape().ndims == 2 + x = tf.expand_dims(x, axis=1) # -> [batch_size, 1, n_input] + return tf.tile(x, [1, n, 1]) # -> [batch_size, n, n_input] + diff --git a/_codes/my_tensorflow/src/utils/math_op.py b/_codes/my_tensorflow/src/utils/math_op.py new file mode 100644 index 00000000..37c5b0da --- /dev/null +++ b/_codes/my_tensorflow/src/utils/math_op.py @@ -0,0 +1,26 @@ +"""Math + +References: + https://www.tensorflow.org/api_guides/python/math_ops +""" + +import tensorflow as tf + + +def dot(x, y): + """Multiplies 2 tensors (and/or variables) and returns a *tensor*. + """ + x_shape = list(x.get_shape()) + y_shape = list(y.get_shape()) + x_ndim = len(x_shape) + y_ndim = len(y_shape) + + if x_ndim is not None and (x_ndim > 2 or y_ndim > 2): + y_permute_dim = list(range(y_ndim)) + y_permute_dim = [y_permute_dim.pop(-2)] + y_permute_dim + xt = tf.reshape(x, [-1, x_shape[-1]]) + yt = tf.reshape(tf.transpose(y, perm=y_permute_dim), [y_shape[-2], -1]) + return tf.reshape(tf.matmul(xt, yt), + x_shape[:-1] + y_shape[:-2] + y_shape[-1:]) + else: + return tf.matmul(x, y) diff --git a/_codes/my_tensorflow/test_layer.ipynb b/_codes/my_tensorflow/test_layer.ipynb new file mode 100644 index 00000000..e055ec20 --- /dev/null +++ b/_codes/my_tensorflow/test_layer.ipynb @@ -0,0 +1,949 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "foo\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "import tensorflow as tf\n", + "from src.utils import get_wb, foo\n", + "from src.utils import print_params_dict, print_params_number\n", + "\n", + "from tensorlayer.layers import DenseLayer, RNNLayer, InputLayer, FlattenLayer\n", + "\n", + "from src.layers import dense, multi_dense, linear_dense, Dense\n", + "from src.layers import highway_dense, multi_highway_dense\n", + "from src.layers import conv2d\n", + "from src.layers import highway_conv2d, multi_highway_conv2d\n", + "from src.layers import attention_for_rnn, attention_for_dense\n", + "from src.layers import attention_flow_self, attention_flow\n", + "\n", + "foo()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## get_wb" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(8, 16)\n", + "[-1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1.]\n", + "W:0 \t{'number': 32, 'shape': [2, 16]}\n", + "b:0 \t{'number': 16, 'shape': [16]}\n" + ] + } + ], + "source": [ + "# get_wb\n", + "tf.reset_default_graph()\n", + "\n", + "# Input shape: [8, 2]\n", + "x = tf.constant(np.arange(16, dtype=np.float32).reshape([8,2]))\n", + "# Output shape: [8, 16]\n", + "w, b = get_wb([2, 16], b_initializer=tf.initializers.constant(-1.0))\n", + "\n", + "with tf.Session() as sess:\n", + " tf.global_variables_initializer().run()\n", + " o_val = tf.matmul(x, w) + b\n", + " b_val = tf.identity(b).eval()\n", + " \n", + "print(o_val.shape)\n", + "print(b_val)\n", + "print_params_dict()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dense" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(8, 16)\n", + "D/W:0 \t{'number': 32, 'shape': [2, 16]}\n", + "D/b:0 \t{'number': 16, 'shape': [16]}\n" + ] + } + ], + "source": [ + "# Dense\n", + "tf.reset_default_graph()\n", + "\n", + "# Input shape: [8, 2]\n", + "x = tf.constant(np.arange(16, dtype=np.float32).reshape([8,2]))\n", + "# Output shape: [8, 16]\n", + "o = dense(x, 16, name=\"D\")\n", + "\n", + "with tf.Session() as sess:\n", + " tf.global_variables_initializer().run()\n", + " o_val = o.eval()\n", + " \n", + "print(o_val.shape)\n", + "print_params_dict()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dense reuse" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n", + "D/W:0 \t{'number': 32, 'shape': [2, 16]}\n", + "D/b:0 \t{'number': 16, 'shape': [16]}\n" + ] + } + ], + "source": [ + "# Dense reuse\n", + "tf.reset_default_graph()\n", + "\n", + "# Input shape: [8, 2]\n", + "x = tf.constant(np.arange(16, dtype=np.float32).reshape([8,2]))\n", + "# Output shape: [8, 16]\n", + "o = dense(x, 16, name=\"D\")\n", + "o2 = dense(x, 16, name=\"D\", reuse=True)\n", + "\n", + "with tf.Session() as sess:\n", + " tf.global_variables_initializer().run()\n", + " o_val = o.eval()\n", + " o2_val = o2.eval()\n", + " \n", + "print((o_val == o2_val).all())\n", + "print_params_dict()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dense reuse with class" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n", + "DD/W:0 \t{'number': 32, 'shape': [2, 16]}\n", + "DD/b:0 \t{'number': 16, 'shape': [16]}\n" + ] + } + ], + "source": [ + "# Dense class\n", + "tf.reset_default_graph()\n", + "\n", + "# Input shape: [8, 2]\n", + "x = tf.constant(np.arange(16, dtype=np.float32).reshape([8,2]))\n", + "# Output shape: [8, 16]\n", + "dense_reuse = Dense(16, name=\"DD\")\n", + "o = dense_reuse(x)\n", + "o2 = dense_reuse(x)\n", + "\n", + "with tf.Session() as sess:\n", + " tf.global_variables_initializer().run()\n", + " o_val = o.eval()\n", + " o2_val = o2.eval()\n", + " \n", + "print((o_val == o2_val).all())\n", + "print_params_dict()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Multi Dense" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(8, 5)\n", + "D-0/W:0 \t{'number': 6, 'shape': [2, 3]}\n", + "D-0/b:0 \t{'number': 3, 'shape': [3]}\n", + "D-1/W:0 \t{'number': 12, 'shape': [3, 4]}\n", + "D-1/b:0 \t{'number': 4, 'shape': [4]}\n", + "D-2/W:0 \t{'number': 20, 'shape': [4, 5]}\n", + "D-2/b:0 \t{'number': 5, 'shape': [5]}\n" + ] + } + ], + "source": [ + "# Multi Dense\n", + "tf.reset_default_graph()\n", + "\n", + "# Input shape: [8, 2]\n", + "x = tf.constant(np.arange(16, dtype=np.float32).reshape([8,2]))\n", + "# Output shape: [8, 5]\n", + "o = multi_dense(x, [3, 4, 5], name=\"D\")\n", + "\n", + "with tf.Session() as sess:\n", + " tf.global_variables_initializer().run()\n", + " o_val = o.eval()\n", + " \n", + "print(o_val.shape)\n", + "print_params_dict()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Highway Dense" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(8, 2)\n", + "H/W:0 \t{'number': 4, 'shape': [2, 2]}\n", + "H/b:0 \t{'number': 2, 'shape': [2]}\n", + "H/transform/W:0 \t{'number': 4, 'shape': [2, 2]}\n", + "H/transform/b:0 \t{'number': 2, 'shape': [2]}\n" + ] + } + ], + "source": [ + "# Highway Dense\n", + "tf.reset_default_graph()\n", + "\n", + "# Input shape: [8, 2]\n", + "x = tf.constant(np.arange(16, dtype=np.float32).reshape([8,2]))\n", + "# Output shape: [8, 2]\n", + "o = highway_dense(x, name=\"H\")\n", + "\n", + "with tf.Session() as sess:\n", + " tf.global_variables_initializer().run()\n", + " o_val = o.eval()\n", + " \n", + "print(o_val.shape)\n", + "print_params_dict()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Multi Highway Dense" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(8, 2)\n", + "H1-0/W:0 \t{'number': 4, 'shape': [2, 2]}\n", + "H1-0/b:0 \t{'number': 2, 'shape': [2]}\n", + "H1-0/transform/W:0 \t{'number': 4, 'shape': [2, 2]}\n", + "H1-0/transform/b:0 \t{'number': 2, 'shape': [2]}\n", + "H1-1/W:0 \t{'number': 4, 'shape': [2, 2]}\n", + "H1-1/b:0 \t{'number': 2, 'shape': [2]}\n", + "H1-1/transform/W:0 \t{'number': 4, 'shape': [2, 2]}\n", + "H1-1/transform/b:0 \t{'number': 2, 'shape': [2]}\n", + "H1-2/W:0 \t{'number': 4, 'shape': [2, 2]}\n", + "H1-2/b:0 \t{'number': 2, 'shape': [2]}\n", + "H1-2/transform/W:0 \t{'number': 4, 'shape': [2, 2]}\n", + "H1-2/transform/b:0 \t{'number': 2, 'shape': [2]}\n" + ] + } + ], + "source": [ + "# Multi Highway Dense\n", + "tf.reset_default_graph()\n", + "\n", + "# Input shape: [8, 2]\n", + "x = tf.constant(np.arange(16, dtype=np.float32).reshape([8,2]))\n", + "# Output shape: [8, 2]\n", + "o = multi_highway_dense(x, 3, name=\"H1\")\n", + "\n", + "with tf.Session() as sess:\n", + " tf.global_variables_initializer().run()\n", + " o_val = o.eval()\n", + " \n", + "print(o_val.shape)\n", + "print_params_dict()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Conv2d" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(4, 20, 20, 16)\n", + "conv2d/W:0 \t{'number': 144, 'shape': [3, 3, 1, 16]}\n", + "conv2d/b:0 \t{'number': 16, 'shape': [16]}\n" + ] + } + ], + "source": [ + "# Conv2d\n", + "tf.reset_default_graph()\n", + "\n", + "# Input shape: [4, 20, 20, 1]\n", + "x = tf.constant(np.arange(1600, dtype=np.float32).reshape([4, 20, 20, 1]))\n", + "# Output shape: [4, 20, 20, 16]\n", + "o = conv2d(x, 3, 16)\n", + "\n", + "with tf.Session() as sess:\n", + " tf.global_variables_initializer().run()\n", + " o_val = o.eval()\n", + " \n", + "print(o_val.shape)\n", + "print_params_dict()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Highway Conv2d" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(4, 20, 20, 1)\n", + "highway_conv2d/W:0 \t{'number': 9, 'shape': [3, 3, 1, 1]}\n", + "highway_conv2d/b:0 \t{'number': 1, 'shape': [1]}\n", + "highway_conv2d/transform/W:0 \t{'number': 9, 'shape': [3, 3, 1, 1]}\n", + "highway_conv2d/transform/b:0 \t{'number': 1, 'shape': [1]}\n" + ] + } + ], + "source": [ + "# Highway Conv2d\n", + "tf.reset_default_graph()\n", + "\n", + "# Input shape: [4, 20, 20, 1]\n", + "x = tf.constant(np.arange(1600, dtype=np.float32).reshape([4, 20, 20, 1]))\n", + "# Output shape: [4, 20, 20, 16]\n", + "o = highway_conv2d(x, 3)\n", + "\n", + "with tf.Session() as sess:\n", + " tf.global_variables_initializer().run()\n", + " o_val = o.eval()\n", + " \n", + "print(o_val.shape)\n", + "print_params_dict()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Multi Highway Conv2d" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(4, 20, 20, 1)\n", + "HCnn1/W:0 \t{'number': 9, 'shape': [3, 3, 1, 1]}\n", + "HCnn1/b:0 \t{'number': 1, 'shape': [1]}\n", + "HCnn1/transform/W:0 \t{'number': 9, 'shape': [3, 3, 1, 1]}\n", + "HCnn1/transform/b:0 \t{'number': 1, 'shape': [1]}\n", + "HCnn2/W:0 \t{'number': 16, 'shape': [4, 4, 1, 1]}\n", + "HCnn2/b:0 \t{'number': 1, 'shape': [1]}\n", + "HCnn2/transform/W:0 \t{'number': 16, 'shape': [4, 4, 1, 1]}\n", + "HCnn2/transform/b:0 \t{'number': 1, 'shape': [1]}\n", + "HCnn3/W:0 \t{'number': 25, 'shape': [5, 5, 1, 1]}\n", + "HCnn3/b:0 \t{'number': 1, 'shape': [1]}\n", + "HCnn3/transform/W:0 \t{'number': 25, 'shape': [5, 5, 1, 1]}\n", + "HCnn3/transform/b:0 \t{'number': 1, 'shape': [1]}\n", + "106\n" + ] + } + ], + "source": [ + "# Multi Highway Conv2d\n", + "tf.reset_default_graph()\n", + "\n", + "# Input shape: [4, 20, 20, 1]\n", + "x = tf.constant(np.arange(1600, dtype=np.float32).reshape([-1, 20, 20, 1]))\n", + "# Output shape: [4, 20, 20, 1]\n", + "x = highway_conv2d(x, 3, name=\"HCnn1\")\n", + "x = highway_conv2d(x, 4, name=\"HCnn2\")\n", + "o = highway_conv2d(x, 5, name=\"HCnn3\")\n", + "\n", + "with tf.Session() as sess:\n", + " tf.global_variables_initializer().run()\n", + " o_val = o.eval()\n", + " \n", + "print(o_val.shape)\n", + "print_params_dict()\n", + "print_params_number()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Multi Highway Conv2d with function" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(4, 20, 20, 1)\n", + "(4, 20, 20, 1)\n", + "highway_conv2d-0/W:0 \t{'number': 9, 'shape': [3, 3, 1, 1]}\n", + "highway_conv2d-0/b:0 \t{'number': 1, 'shape': [1]}\n", + "highway_conv2d-0/transform/W:0 \t{'number': 9, 'shape': [3, 3, 1, 1]}\n", + "highway_conv2d-0/transform/b:0 \t{'number': 1, 'shape': [1]}\n", + "highway_conv2d-1/W:0 \t{'number': 9, 'shape': [3, 3, 1, 1]}\n", + "highway_conv2d-1/b:0 \t{'number': 1, 'shape': [1]}\n", + "highway_conv2d-1/transform/W:0 \t{'number': 9, 'shape': [3, 3, 1, 1]}\n", + "highway_conv2d-1/transform/b:0 \t{'number': 1, 'shape': [1]}\n", + "HC-0/W:0 \t{'number': 9, 'shape': [3, 3, 1, 1]}\n", + "HC-0/b:0 \t{'number': 1, 'shape': [1]}\n", + "HC-0/transform/W:0 \t{'number': 9, 'shape': [3, 3, 1, 1]}\n", + "HC-0/transform/b:0 \t{'number': 1, 'shape': [1]}\n", + "HC-1/W:0 \t{'number': 16, 'shape': [4, 4, 1, 1]}\n", + "HC-1/b:0 \t{'number': 1, 'shape': [1]}\n", + "HC-1/transform/W:0 \t{'number': 16, 'shape': [4, 4, 1, 1]}\n", + "HC-1/transform/b:0 \t{'number': 1, 'shape': [1]}\n", + "HC-2/W:0 \t{'number': 25, 'shape': [5, 5, 1, 1]}\n", + "HC-2/b:0 \t{'number': 1, 'shape': [1]}\n", + "HC-2/transform/W:0 \t{'number': 25, 'shape': [5, 5, 1, 1]}\n", + "HC-2/transform/b:0 \t{'number': 1, 'shape': [1]}\n", + "146\n" + ] + } + ], + "source": [ + "# Multi Highway Conv2d\n", + "tf.reset_default_graph()\n", + "\n", + "# Input shape: [4, 20, 20, 1]\n", + "x = tf.constant(np.arange(1600, dtype=np.float32).reshape([-1, 20, 20, 1]))\n", + "# Output shape: [4, 20, 20, 1]\n", + "o = multi_highway_conv2d(x, 3, 2)\n", + "o2 = multi_highway_conv2d(x, [3,4,5], 3, name=\"HC\")\n", + "\n", + "\n", + "with tf.Session() as sess:\n", + " tf.global_variables_initializer().run()\n", + " o_val = o.eval()\n", + " o2_val = o2.eval()\n", + " \n", + "print(o_val.shape)\n", + "print(o2_val.shape)\n", + "print_params_dict()\n", + "print_params_number()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Attention for Dense" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(4, 8)\n", + "attention_for_dense/W:0 \t{'number': 64, 'shape': [8, 8]}\n", + "attention_for_dense/b:0 \t{'number': 8, 'shape': [8]}\n", + "72\n" + ] + } + ], + "source": [ + "# Attention for Dense\n", + "tf.reset_default_graph()\n", + "\n", + "# Input shape: [4, 8]\n", + "x = tf.constant(np.arange(32, dtype=np.float32).reshape([-1, 8]))\n", + "# Output shape: [4, 8]\n", + "o = attention_for_dense(x)\n", + "\n", + "with tf.Session() as sess:\n", + " tf.global_variables_initializer().run()\n", + " o_val = o.eval()\n", + " \n", + "print(o_val.shape)\n", + "print_params_dict()\n", + "print_params_number()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Attention for RNN (use_mean_attention=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(128, 5, 32)\n", + "attention_for_rnn/W:0 \t{'number': 25, 'shape': [5, 5]}\n", + "attention_for_rnn/b:0 \t{'number': 5, 'shape': [5]}\n", + "30\n" + ] + } + ], + "source": [ + "# Attention for RNN\n", + "tf.reset_default_graph()\n", + "\n", + "# Input shape: [128, 5, 32]\n", + "x = tf.constant(np.arange(20480, dtype=np.float32).reshape([128, 5, 32]))\n", + "# Output shape: [128, 5, 32]\n", + "o = attention_for_rnn(x)\n", + "\n", + "with tf.Session() as sess:\n", + " tf.global_variables_initializer().run()\n", + " o_val = o.eval()\n", + " \n", + "print(o_val.shape)\n", + "print_params_dict()\n", + "print_params_number()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Attention for RNN (use_mean_attention=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(128, 5, 32)\n", + "attention_for_rnn/W:0 \t{'number': 25, 'shape': [5, 5]}\n", + "attention_for_rnn/b:0 \t{'number': 5, 'shape': [5]}\n", + "30\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "import tensorflow as tf\n", + "from src.utils import get_wb, foo\n", + "from src.utils import print_params_dict, print_params_number\n", + "\n", + "from src.layers import attention_for_rnn\n", + "\n", + "# Attention for RNN\n", + "tf.reset_default_graph()\n", + "\n", + "# Input shape: [128, 5, 32]\n", + "x = tf.constant(np.arange(20480, dtype=np.float32).reshape([128, 5, 32]))\n", + "# Output shape: [128, 5, 32]\n", + "o = attention_for_rnn(x, n_step=5, use_mean_attention=True)\n", + "\n", + "with tf.Session() as sess:\n", + " tf.global_variables_initializer().run()\n", + " o_val = o.eval()\n", + " \n", + "print(o_val.shape)\n", + "print_params_dict()\n", + "print_params_number()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Use attention after lstm (use tensorlayer)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[TL] InputLayer input: (128, 16, 5)\n", + "[TL] RNNLayer rnn: n_hidden: 32 n_steps: 5 in_dim: 3 in_shape: (128, 16, 5) cell_fn: LSTMCell \n", + "[TL] RNN batch_size (concurrent processes): 128\n", + "[TL] n_params : 2\n", + "[TL] InputLayer input: (128, 5, 32)\n", + "[TL] FlattenLayer flatten: 160\n", + "[TL] DenseLayer dense: 1 sigmoid\n", + "(128, 1)\n", + "rnn/lstm_cell/kernel:0 \t{'number': 4736, 'shape': [37, 128]}\n", + "rnn/lstm_cell/bias:0 \t{'number': 128, 'shape': [128]}\n", + "attention_for_rnn/W:0 \t{'number': 25, 'shape': [5, 5]}\n", + "attention_for_rnn/b:0 \t{'number': 5, 'shape': [5]}\n", + "dense/W:0 \t{'number': 160, 'shape': [160, 1]}\n", + "dense/b:0 \t{'number': 1, 'shape': [1]}\n", + "5055\n" + ] + } + ], + "source": [ + "# Attention for RNN\n", + "tf.reset_default_graph()\n", + "\n", + "# Input shape: [128, 16, 5]\n", + "x = tf.constant(np.arange(10240, dtype=np.float32).reshape([128, 16, 5]))\n", + "x = InputLayer(x)\n", + "\n", + "# Use attention after lstm\n", + "x = RNNLayer(x, tf.nn.rnn_cell.LSTMCell, n_hidden=32)\n", + "x = attention_for_rnn(x.outputs)\n", + "\n", + "x = InputLayer(x)\n", + "x = FlattenLayer(x)\n", + "x = DenseLayer(x, n_units=1, act=tf.nn.sigmoid)\n", + "o = x.outputs\n", + "\n", + "with tf.Session() as sess:\n", + " tf.global_variables_initializer().run()\n", + " o_val = o.eval()\n", + "\n", + "print(o_val.shape) # [128, 1]\n", + "print_params_dict()\n", + "print_params_number()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Use attention before lstm (use tensorlayer)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[TL] InputLayer input: (128, 16, 5)\n", + "[TL] RNNLayer rnn: n_hidden: 32 n_steps: 5 in_dim: 3 in_shape: (128, 16, 5) cell_fn: LSTMCell \n", + "[TL] RNN batch_size (concurrent processes): 128\n", + "[TL] n_params : 2\n", + "[TL] DenseLayer dense: 1 sigmoid\n", + "(128, 1)\n", + "attention_for_rnn/W:0 \t{'number': 256, 'shape': [16, 16]}\n", + "attention_for_rnn/b:0 \t{'number': 16, 'shape': [16]}\n", + "rnn/lstm_cell/kernel:0 \t{'number': 4736, 'shape': [37, 128]}\n", + "rnn/lstm_cell/bias:0 \t{'number': 128, 'shape': [128]}\n", + "dense/W:0 \t{'number': 32, 'shape': [32, 1]}\n", + "dense/b:0 \t{'number': 1, 'shape': [1]}\n", + "5169\n" + ] + } + ], + "source": [ + "# Attention for RNN\n", + "tf.reset_default_graph()\n", + "\n", + "# Input shape: [128, 16, 5]\n", + "x = tf.constant(np.arange(10240, dtype=np.float32).reshape([128, 16, 5]))\n", + "\n", + "# Use attention before lstm\n", + "x = attention_for_rnn(x)\n", + "x = InputLayer(x)\n", + "x = RNNLayer(x, tf.nn.rnn_cell.LSTMCell, n_hidden=32, return_last=True)\n", + "\n", + "# x = FlattenLayer(x)\n", + "x = DenseLayer(x, n_units=1, act=tf.nn.sigmoid)\n", + "o = x.outputs\n", + "\n", + "with tf.Session() as sess:\n", + " tf.global_variables_initializer().run()\n", + " o_val = o.eval()\n", + "\n", + "print(o_val.shape) # [128, 1]\n", + "print_params_dict()\n", + "print_params_number()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Attention Flow Self Match Layer" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(128, 10, 128)\n", + "0\n" + ] + } + ], + "source": [ + "#from src.__ref import AttentionFlowMatchLayer\n", + "\n", + "# Attention Flow Self Match Layer\n", + "tf.reset_default_graph()\n", + "\n", + "# Input shape\n", + "c = tf.constant(np.arange(40960, dtype=np.float32).reshape([128, 10, 32]))\n", + "q = tf.constant(np.arange(20480, dtype=np.float32).reshape([128, 5, 32]))\n", + "\n", + "o = attention_flow_self(c, q)\n", + "# o2 = AttentionFlowMatchLayer(0).match(c, q, _, _)[0]\n", + "\n", + "with tf.Session() as sess:\n", + " tf.global_variables_initializer().run()\n", + " o_val = o.eval()\n", + " #o2_val = o2.eval()\n", + "\n", + "print(o_val.shape) # [128, 1]\n", + "#print((o_val==o2_val).all())\n", + "print_params_dict()\n", + "print_params_number()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Usage of `tf.einsum`" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(10, 3, 4, 1)\n", + "True\n", + "W:0 \t{'number': 30, 'shape': [30, 1]}\n", + "b:0 \t{'number': 1, 'shape': [1]}\n", + "31\n" + ] + } + ], + "source": [ + "tf.reset_default_graph()\n", + "T, J, d = 3, 4, 10\n", + "h = tf.constant(np.arange(3600, dtype=np.float32).reshape([10, T, J, 3*d]))\n", + "\n", + "w, b = get_wb([3*d, 1])\n", + "\n", + "# tf.matmul(w, h)\n", + "o = tf.einsum(\"ntjd,do->ntjo\", h, w)\n", + "o2 = tf.matmul(tf.reshape(h, [-1, 3*d]), w)\n", + "o2 = tf.reshape(o2,[-1, 3, 4, 1])\n", + "\n", + "with tf.Session() as sess:\n", + " tf.global_variables_initializer().run()\n", + " o_val = o.eval()\n", + " o2_val = o2.eval()\n", + "\n", + "print(o_val.shape) # [128, 1]\n", + "print((o_val==o2_val).all())\n", + "print_params_dict()\n", + "print_params_number()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Attention Flow Match Layer" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(128, 10, 128)\n", + "-----------\n", + "params_dict\n", + " af/W:0 \t{'number': 96, 'shape': [96, 1]}\n", + "-----------\n", + "params_number: 96\n" + ] + } + ], + "source": [ + "#from src.layers import attention_flow_2\n", + "\n", + "# Attention Flow Match Layer\n", + "tf.reset_default_graph()\n", + "\n", + "# Input shape\n", + "c = tf.constant(np.arange(40960, dtype=np.float32).reshape([128, 10, 32]))\n", + "q = tf.constant(np.arange(20480, dtype=np.float32).reshape([128, 5, 32]))\n", + "\n", + "o = attention_flow(c, q, name=\"af\")\n", + "#o2 = attention_flow_2(c, q, name=\"af\", reuse=True)\n", + "\n", + "with tf.Session() as sess:\n", + " tf.global_variables_initializer().run()\n", + " o_val = o.eval()\n", + " #o2_val = o2.eval()\n", + "\n", + "print(o_val.shape) # [128, 1]\n", + "#print((o_val==o2_val).all())\n", + "print_params_dict()\n", + "print_params_number()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git "a/_codes/\345\267\245\345\205\267\345\272\223/gensim/FastText.py" "b/_codes/\345\267\245\345\205\267\345\272\223/gensim/FastText.py" new file mode 100644 index 00000000..028e4f3e --- /dev/null +++ "b/_codes/\345\267\245\345\205\267\345\272\223/gensim/FastText.py" @@ -0,0 +1,109 @@ +""" +`gensim.models.FastText` 使用示例 +""" +# gensim 示例 +import gensim +import numpy as np +from gensim.test.utils import common_texts +from gensim.models.keyedvectors import FastTextKeyedVectors +from gensim.models._utils_any2vec import compute_ngrams, ft_hash +from gensim.models import FastText + +# 构建 FastText 模型 +sentences = [["Hello", "World", "!"], ["I", "am", "huay", "."]] +min_ngrams, max_ngrams = 2, 4 # ngrams 范围 +model = FastText(sentences, size=5, min_count=1, min_n=min_ngrams, max_n=max_ngrams) + +# 可以通过相同的方式获取每个单词以及任一个 n-gram 的向量 +print(model.wv['hello']) +print(model.wv['', '', ''] +['', '', ''] +['', ''] +['', ''] +['', '', ''] +['', '', ''] +['<.', '.>', '<.>'] +""" +assert sum_ngrams == len(model.wv.vectors_ngrams) +print(sum_ngrams) # 57 +print() + +# 因为 "a", "aa", "aaa" 中都只含有 " + print(model.wv['z']) + File "D:\program\work\Python\Anaconda3\envs\tf\lib\site-packages\gensim\models\keyedvectors.py", line 336, in __getitem__ + return self.get_vector(entities) + File "D:\program\work\Python\Anaconda3\envs\tf\lib\site-packages\gensim\models\keyedvectors.py", line 454, in get_vector + return self.word_vec(word) + File "D:\program\work\Python\Anaconda3\envs\tf\lib\site-packages\gensim\models\keyedvectors.py", line 1989, in word_vec + raise KeyError('all ngrams for word %s absent from model' % word) +KeyError: 'all ngrams for word z absent from model' +""" + diff --git a/_notes/Frontiers of Natural Language Processing.pdf b/_notes/Frontiers of Natural Language Processing.pdf new file mode 100644 index 00000000..b75a3b55 Binary files /dev/null and b/_notes/Frontiers of Natural Language Processing.pdf differ diff --git a/_notes/probability.pdf b/_notes/probability.pdf new file mode 100644 index 00000000..90c1b781 Binary files /dev/null and b/_notes/probability.pdf differ diff --git "a/_notes/\344\270\223\351\242\230-Bash\345\221\275\344\273\244.md" "b/_notes/\344\270\223\351\242\230-Bash\345\221\275\344\273\244.md" new file mode 100644 index 00000000..5e39e8c2 --- /dev/null +++ "b/_notes/\344\270\223\351\242\230-Bash\345\221\275\344\273\244.md" @@ -0,0 +1,29 @@ +专题-Bash命令 +=== + +Index +--- + + +- [Bash 必背命令](#bash-必背命令) + - [wc/sed/awk/grep/sort/uniq/paste/cat/head/tail](#wcsedawkgrepsortuniqpastecatheadtail) + - [---](#---) + - [wc](#wc) +- [使用场景](#使用场景) + - [统计文件中出现次数最多的前 10 个词](#统计文件中出现次数最多的前-10-个词) + + + +# Bash 必背命令 + +## wc/sed/awk/grep/sort/uniq/paste/cat/head/tail + +## --- + +## wc + +# 使用场景 +## 统计文件中出现次数最多的前 10 个词 +```bash + +``` \ No newline at end of file diff --git "a/_notes/\345\233\276\347\244\272.pptx" "b/_notes/\345\233\276\347\244\272.pptx" new file mode 100644 index 00000000..bf90481c Binary files /dev/null and "b/_notes/\345\233\276\347\244\272.pptx" differ diff --git "a/_notes/\345\244\207\345\277\230-Markdown\345\260\217\346\212\200\345\267\247.md" "b/_notes/\345\244\207\345\277\230-Markdown\345\260\217\346\212\200\345\267\247.md" new file mode 100644 index 00000000..c9b98ddb --- /dev/null +++ "b/_notes/\345\244\207\345\277\230-Markdown\345\260\217\346\212\200\345\267\247.md" @@ -0,0 +1,100 @@ +备忘-Markdown小技巧 +=== + +Index +--- + + +- [自动更新目录](#自动更新目录) +- [图片居中](#图片居中) +- [隐藏代码块](#隐藏代码块) +- [HTML 表格](#html-表格) +- [Latex 公式](#latex-公式) + + + +## 自动更新目录 +- VSCode 插件 [`Markdown TOC`](https://marketplace.visualstudio.com/items?itemName=AlanWalk.markdown-toc) + +## 图片居中 +- 不带链接 + ``` +
+ ``` +- 带链接 + ``` +
+ ``` +- `height=""`用于控制图片的大小 + +## 隐藏代码块 +``` +
示例:动态序列(点击展开) + +// 代码块,注意上下都要保留空行 + +
+``` + +## HTML 表格 +``` + + + + + + + + + + + + + + + + + + + + + + + +
No padding, no stridesArbitrary padding, no stridesHalf padding, no stridesFull padding, no strides
No padding, stridesPadding, stridesPadding, strides (odd)
+``` + +## Latex 公式 +> 在线 LaTeX 公式编辑器 http://www.codecogs.com/latex/eqneditor.php + +**斜体加粗** +``` +\boldsymbol{x} +``` +**期望** +``` +\mathbb{E} +``` +**矩阵对齐** +``` +\begin{array}{ll} + & \\ + & \\ +\end{array} +``` +**转置** +``` +^\mathsf{T} +``` +**省略号** +``` +水平方向 \cdots +竖直方向 \vdots +对角线方向 \ddots +``` +**按元素相乘** +``` +\circ +或 +\odot +``` \ No newline at end of file diff --git "a/_notes/\345\244\207\345\277\230-Python\347\233\270\345\205\263\345\267\245\345\205\267.md" "b/_notes/\345\244\207\345\277\230-Python\347\233\270\345\205\263\345\267\245\345\205\267.md" new file mode 100644 index 00000000..42e67b59 --- /dev/null +++ "b/_notes/\345\244\207\345\277\230-Python\347\233\270\345\205\263\345\267\245\345\205\267.md" @@ -0,0 +1,82 @@ +备忘-IPython小技巧 +=== + +Index +--- + + +- [IPython](#ipython) + - [自动重新加载模块](#自动重新加载模块) +- [Anaconda](#anaconda) + - [虚拟环境相关](#虚拟环境相关) +- [PyCharm](#pycharm) + - [Python Console](#python-console) +- [常用库安装](#常用库安装) + - [PyTorch 安装](#pytorch-安装) + + + +## IPython + +### 自动重新加载模块 +``` +%load_ext autoreload +%autoreload 2 +``` +- 这个有时候也不太好用 +- 需要反复测试的,建议使用 Jupyter Notebook + + +## Anaconda + +### 虚拟环境相关 +- 创建虚拟环境 + ``` + conda create -n env_name anaconda python=3 + ``` +- 复制虚拟环境 + ``` + conda create --name dst_name --clone src_name + ``` +- 删除虚拟环境 + ``` + conda remove --name nev_name --all + ``` + + +## PyCharm + +### Python Console +- 默认 + ``` + import sys; print('Python %s on %s' % (sys.version, sys.platform)) + sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS]) + ``` +- 修改为,避免每次重新导入 + ``` + %load_ext autoreload + %autoreload 2 + + import sys; print('Python %s on %s' % (sys.version, sys.platform)) + sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS]) + + import numpy as np + import tensorflow as tf + ``` + +## 常用库安装 + +### PyTorch 安装 +- Windows Python3.6 CPU + - conda 提供的源很慢,不建议 + - 官网提供的 pip 安装方法,在安装 `torchvision` 时会报错 + - 正确安装方法(2018年8月17日15:09:40) + ``` + pip3 install http://download.pytorch.org/whl/cpu/torch-0.4.1-cp36-cp36m-win_amd64.whl + pip3 install --no-deps torchvision + ``` + > 相比官方,只添加了 `--no-deps` 参数 + - 推荐先下载在安装 + ``` + pip3 install torch-0.4.1-cp36-cp36m-win_amd64.whl + ``` diff --git "a/_notes/\345\244\207\345\277\230-\345\267\245\345\205\267\345\272\223.md" "b/_notes/\345\244\207\345\277\230-\345\267\245\345\205\267\345\272\223.md" new file mode 100644 index 00000000..fd9f9b96 --- /dev/null +++ "b/_notes/\345\244\207\345\277\230-\345\267\245\345\205\267\345\272\223.md" @@ -0,0 +1,47 @@ +专题-工具库 +=== + +Index +--- + + +- [工具列表](#工具列表) +- [jieba 分词](#jieba-分词) + - [分词](#分词) + + + +# 工具列表 +- Stanford Core NLP + - 语义分析 +- NLTK + - 分词(西文)、分句、读取语义树 + - 词干提取 +- jieba + - 中文分词、词性标注 + +# jieba 分词 +> fxsjy/[jieba: 结巴中文分词](https://github.com/fxsjy/jieba) + +## 分词 +**代码示例** +```Python +import jieba + +# 全模式 +seg_list = jieba.cut("我来到北京清华大学", cut_all=True) +print("【全模式】: " + "/ ".join(seg_list)) + +# 精确模式(默认) +seg_list = jieba.cut("我来到北京清华大学", cut_all=False) +# seg_list = jieba.cut("他来到了网易杭研大厦") +print("【精确模式】: " + "/ ".join(seg_list)) +print(", ".join(seg_list)) + +# 新词识别 + + +# 搜索引擎模式 +seg_list = jieba.cut_for_search("小明硕士毕业于中国科学院计算所,后在日本京都大学深造") +print(", ".join(seg_list)) +``` \ No newline at end of file diff --git "a/_notes/\345\244\207\345\277\230-\345\270\270\347\224\250\345\255\220\345\207\275\346\225\260.md" "b/_notes/\345\244\207\345\277\230-\345\270\270\347\224\250\345\255\220\345\207\275\346\225\260.md" new file mode 100644 index 00000000..ce99f710 --- /dev/null +++ "b/_notes/\345\244\207\345\277\230-\345\270\270\347\224\250\345\255\220\345\207\275\346\225\260.md" @@ -0,0 +1,129 @@ +**RoadMap** +--- + + +- [暴力搜索](#暴力搜索) + - [遍历所有子集](#遍历所有子集) + - [遍历所有正因子](#遍历所有正因子) +- [二分查找](#二分查找) + - [离散模板](#离散模板) + - [连续模板](#连续模板) +- [位运算](#位运算) + - [计算二进制表示中`1`的个数](#计算二进制表示中1的个数) + - [判断第`i`位是否为 1](#判断第i位是否为-1) + - [置第`i`位为 1 且不影响其他位](#置第i位为-1-且不影响其他位) + - [判断奇偶](#判断奇偶) + - [简单快速幂](#简单快速幂) + + + +# 暴力搜索 + +## 遍历所有子集 +- 代码 + - **imhuay**/**Algorithm_for_Interview**/_utils工具函数/[遍历所有子集.hpp](https://github.com/imhuay/Algorithm_for_Interview-Chinese/blob/master/Algorithm_for_Interview/_utils工具函数/遍历所有子集.hpp) +- 相关问题 + +## 遍历所有正因子 +- 代码 + - **imhuay**/**Algorithm_for_Interview**/_utils工具函数/[遍历所有正因子.hpp](https://github.com/imhuay/Algorithm_for_Interview-Chinese/blob/master/Algorithm_for_Interview/_utils工具函数/遍历所有正因子.hpp) +- 相关问题 + +# 二分查找 + +## 离散模板 + +## 连续模板 + +# 位运算 +- 代码 + - **imhuay**/**Algorithm_for_Interview**/_utils工具函数/[位运算.hpp](https://github.com/imhuay/Algorithm_for_Interview-Chinese/blob/master/Algorithm_for_Interview/_utils工具函数/位运算.hpp) + +## 计算二进制表示中`1`的个数 +- **法 1**:O(m) - m 为 1 的个数 + ```C + int numberOfOne(int n) { + int cnt = 0; + while (n > 0) { + cnt++; + } + return cnt; + } + /* 说明: + n-1 后会翻转右数第一个 1 及之后的每一位; + 然后 n&(n-1) 会把该位置的 1 及之后的每一位置零; + 因此每次循环恰好消去一个 1 直到变为 0 + n = 100,100 + n-1 = 100,011 + n = n&(n-1) = 100,000 cnt++ + n-1 = 011,111 + n = n&(n-1) = 000,000 cnt++ + */ + ``` +- **法2**:O(n) - n 为二进制的长度 + ```C + int numberOfOne(int n) { + int cnt = 0; + while (n > 0) { + if (n & 1) + cnt++; + n >>= 1; + } + return cnt; + } + /* 说明: + 每次移动一位,判断是否为 1 + */ + ``` + +## 判断第`i`位是否为 1 +- 构造一个第`i`位是`1`的数与原数做**与运算** + ```C + int i = 4; // 因为 i 从 0 开始,所以这实际上是右起第 5 位 + int n = 16; // 10000 + // 法 1:移动 1 + if (n & (1 << i)) + cout << "true" << endl; + // 法 2:移动 n + if ((n >> i) & 1) + cout << "true" << endl; + ``` + +## 置第`i`位为 1 且不影响其他位 +- 类似[判断第`i`位是否为 1](#判断第i位是否为-1)的思路 +``` +int ret = 3; // 011 +int i = 2; +ret ^= 1 << i; // 111 +``` + +## 判断奇偶 +- 判断奇偶,实际上就是判断二进制表示的最后一位是否为 1 + ```C + n = 5; + if (n & 1) // 只适用于正整数,但是效率更高 + cout << n << " 是奇数" << endl; + + // 常规方法 + if (n % 2 != 0) // 适用于正、负数 + cout << n << " 是奇数" << endl; + ``` + +## 简单快速幂 +```C +double quickPow(double base, int p) { + double ret = 1.0; + int q = abs(p); + while (q > 0) { + if (q & 1) + ret *= base; + base *= base; + q >>= 1; + } + return p > 0 ? ret : 1 / ret; +} +``` +- 用一个例子来说明这段代码更直观 + - 求 `3^20 = 9^10 = 81^5 (= 81*81^4) = 81*6561^2 = 81*43046721` + - 循环次数 = `bin(20)`的位数 = `len(10100)` = 5 + diff --git "a/images/TIM\346\210\252\345\233\27620180608171710.png" "b/images/TIM\346\210\252\345\233\27620180608171710.png" deleted file mode 100644 index 82cac162..00000000 Binary files "a/images/TIM\346\210\252\345\233\27620180608171710.png" and /dev/null differ diff --git "a/images/TIM\346\210\252\345\233\27620180608172312.png" "b/images/TIM\346\210\252\345\233\27620180608172312.png" deleted file mode 100644 index 5cb69e7e..00000000 Binary files "a/images/TIM\346\210\252\345\233\27620180608172312.png" and /dev/null differ diff --git "a/images/TIM\346\210\252\345\233\27620180608195851.png" "b/images/TIM\346\210\252\345\233\27620180608195851.png" deleted file mode 100644 index 09902f06..00000000 Binary files "a/images/TIM\346\210\252\345\233\27620180608195851.png" and /dev/null differ diff --git "a/images/TIM\346\210\252\345\233\27620180608204913.png" "b/images/TIM\346\210\252\345\233\27620180608204913.png" deleted file mode 100644 index 210cb997..00000000 Binary files "a/images/TIM\346\210\252\345\233\27620180608204913.png" and /dev/null differ diff --git "a/images/TIM\346\210\252\345\233\27620180608212808.png" "b/images/TIM\346\210\252\345\233\27620180608212808.png" deleted file mode 100644 index 55c9572b..00000000 Binary files "a/images/TIM\346\210\252\345\233\27620180608212808.png" and /dev/null differ diff --git "a/\346\234\272\345\231\250\345\255\246\344\271\240/QA.md" "b/\346\234\272\345\231\250\345\255\246\344\271\240/QA.md" deleted file mode 100644 index 3cd60a38..00000000 --- "a/\346\234\272\345\231\250\345\255\246\344\271\240/QA.md" +++ /dev/null @@ -1,5 +0,0 @@ -## [问题与答案](./QA.md) - -## 问题列表 - -- #### \ No newline at end of file diff --git "a/\346\234\272\345\231\250\345\255\246\344\271\240/README.md" "b/\346\234\272\345\231\250\345\255\246\344\271\240/README.md" deleted file mode 100644 index a2d4937a..00000000 --- "a/\346\234\272\345\231\250\345\255\246\344\271\240/README.md" +++ /dev/null @@ -1,5 +0,0 @@ -## - -## 问题列表 - -- #### \ No newline at end of file diff --git "a/\346\267\261\345\272\246\345\255\246\344\271\240/QA.md" "b/\346\267\261\345\272\246\345\255\246\344\271\240/QA.md" deleted file mode 100644 index 43c81a54..00000000 --- "a/\346\267\261\345\272\246\345\255\246\344\271\240/QA.md" +++ /dev/null @@ -1,444 +0,0 @@ -## 问题与答案 - -### 1. 如何设置网络的初始值? - -一般总是使用服从(截断)高斯或均匀分布的随机值,具体是高斯还是均匀分布影响不大,但是也没有详细的研究。 - -但是,**初始值的大小**会对优化结果和网络的泛化能力产生较大的影响:更大的初始值有助于避免冗余的单元和梯度消失;但如果初始值太大,又会造成梯度爆炸。 - -> 《深度学习》 ch8.4 - 参数初始化策略 - -一些启发式初始化策略通常是根据输入与输出的单元数来决定初始权重的大小,比如 Glorot and Bengio (2010) 中建议建议使用的标准初始化,其中 m 为输入数,n 为输出数 - - - -还有一些方法推荐使用随机正交矩阵来初始化权重 (Saxe et al., 2013)。 - -> 常用的初始化策略可以参考 Keras 中文文档:[初始化方法Initializers](http://keras-cn.readthedocs.io/en/latest/other/initializations/) - - -### 2. 梯度爆炸的解决办法 - -1. **梯度截断**/裁剪——如果梯度超过某个阈值,就对其进行限制 - - 下面是 Tensorflow 提供的几种方法: - - - `tf.clip_by_value(t, clip_value_min, clip_value_max)` - - `tf.clip_by_norm(t, clip_norm)` - - `tf.clip_by_average_norm(t, clip_norm)` - - `tf.clip_by_global_norm(t_list, clip_norm)` - - 这里以`tf.clip_by_global_norm`为例: - - ``` - To perform the clipping, the values `t_list[i]` are set to: - - t_list[i] * clip_norm / max(global_norm, clip_norm) - - where: - - global_norm = sqrt(sum([l2norm(t)**2 for t in t_list])) - ``` - - 用法: - - ``` - train_op = tf.train.AdamOptimizer() - params = tf.trainable_variables() - gradients = tf.gradients(loss, params) - - clip_norm = 100 - clipped_gradients, global_norm = tf.clip_by_global_norm(gradients, clip_norm) - - optimizer_op = train_op.apply_gradients(zip(clipped_gradients, params)) - ``` - - > clip_norm 的设置视 loss 的大小而定,如果比较大,那么可以设为 100 或以上,如果比较小,可以设为 10 或以下。 - -2. 良好的参数初始化策略也能缓解梯度爆炸问题(权重正则化) - - > [如何设置网络的初始值?](#1.-如何设置网络的初始值?) - -3. 使用线性整流激活函数,如 ReLU 等 - - -### 3. MLP 的万能近似定理 - -一个前馈神经网络如果具有至少一个非线性输出层,那么只要给予网络足够数量的隐藏单元,它就可以以任意的精度来近似任何从一个有限维空间到另一个有限维空间的函数。 - -> 《深度学习》 ch6.4.1 - 万能近似性质和深度 - - -### 4. 在 MLP 中,深度与宽度的关系,及其表示能力的差异 - -隐藏层的数量称为模型的**深度**,隐藏层的维数(单元数)称为该层的**宽度**。 - -**万能近似定理**表明一个单层的网络就足以表达任意函数,但是该层的维数可能非常大,且几乎没有泛化能力;此时,使用更深的模型能够减少所需的单元数,同时增强泛化能力(减少泛化误差)。参数数量相同的情况下,浅层网络比深层网络更容易过拟合。 - -> 《深度学习》 ch6.4 - 架构设计;这一节的内容比较分散,想要更好的回答这个问题,需要理解深度学习的本质——学习多层次组合(ch1.2),这才是现代深度学习的基本原理。 - - -### 5. 稀疏表示,低维表示,独立表示 - -无监督学习任务的目的是找到数据的“最佳”表示。“最佳”可以有不同的表示,但是一般来说,是指该表示在比本身表示的信息更简单的情况下,尽可能地保存关于 x 更多的信息。 - -低维表示、稀疏表示和独立表示是最常见的三种“简单”表示:1)低维表示尝试将 x 中的信息尽可能压缩在一个较小的表示中;2)稀疏表示将数据集嵌入到输入项大多数为零的表示中;3)独立表示试图分开数据分布中变化的来源,使得表示的维度是统计独立的。 - -这三种表示不是互斥的,比如主成分分析(PCA)就试图同时学习低维表示和独立表示。 - -表示的概念是深度学习的核心主题之一。 - -> 《深度学习》 ch5.8 - 无监督学习算法 - - -### 6. 局部不变性(平滑先验)及其局限性 - -局部不变性:函数在局部小区域内不会发生较大的变化。 - -为了更好地**泛化**,机器学习算法需要由一些先验来引导应该学习什么类型的函数。 - -其中最广泛使用的“隐式先验”是平滑先验(smoothness prior),也称局部不变性先验(local constancy prior)。许多简单算法完全依赖于此先验达到良好的(局部)泛化,一个极端例子是 k-最近邻系列的学习算法。 - -但是仅依靠平滑先验**不足以**应对人工智能级别的任务。简单来说,区分输入空间中 O(k) 个区间,需要 O(k) 个样本,通常也会有 O(k) 个参数。最近邻算法中,每个训练样本至多用于定义一个区间。类似的,决策树也有平滑学习的局限性。 - -以上问题可以总结为:是否可以有效地表示复杂的函数,以及所估计的函数是否可以很好地泛化到新的输入。该问题的一个关键观点是,只要我们通过额外假设生成数据的分布来建立区域间的依赖关系,那么 O(k) 个样本足以描述多如 O(2^k) 的大量区间。通过这种方式,能够做到**非局部的泛化**。 - -> 《深度学习》 ch5.11.2 - 局部不变性与平滑正则化 -> -> 一些其他的机器学习方法往往会提出更强的,针对特定问题的假设,例如周期性。通常,神经网络不会包含这些很强的针对性假设——深度学习的核心思想是假设数据由因素或特征组合产生,这些因素或特征可能来自一个层次结构的多个层级。许多其他类似的通用假设进一步提高了深度学习算法。这些很温和的假设允许了样本数目和可区分区间数目之间的**指数增益**。深度的分布式表示带来的指数增益有效地解决了维数灾难带来的挑战 ->> 指数增益:《深度学习》 ch6.4.1、ch15.4、ch15.5 - - -### 7. 为什么交叉熵损失相比均方误差损失能提高以 sigmoid 和 softmax 作为激活函数的层的性能? - -《深度学习》 ch6.6 - 小结中提到了这个结论,但是没有给出具体原因(可能在前文)。 - -简单来说,就是使用均方误差(MSE)作为损失函数时,会导致大部分情况下**梯度偏小**,其结果就是权重的更新很慢,且容易造成“梯度消失”现象。而交叉熵损失克服了这个缺点,当误差大的时候,权重更新就快,当误差小的时候,权重的更新才慢。 - -具体推导过程如下: - -> https://blog.csdn.net/guoyunfei20/article/details/78247263 - CSDN 博客 -> -> 这里给出了一个具体的[例子](https://blog.csdn.net/shmily_skx/article/details/53053870) - - -### 8. 分段线性单元(如 ReLU)代替 sigmoid 的利弊 - -- 当神经网络比较小时,sigmoid 表现更好; -- 在深度学习早期,人们认为应该避免具有不可导点的激活函数,而 ReLU 不是全程可导/可微的 -- sigmoid 和 tanh 的输出是有界的,适合作为下一层的输入,以及整个网络的输出。实际上,目前大多数网络的输出层依然使用的 sigmoid(单输出) 或 softmax(多输出)。 - - > 为什么 ReLU 不是全程可微也能用于基于梯度的学习?——虽然 ReLU 在 0 点不可导,但是它依然存在左导数和右导数,只是它们不相等(相等的话就可导了),于是在实现时通常会返回左导数或右导数的其中一个,而不是报告一个导数不存在的错误。 - >> 一阶函数:可微==可导 - -- 对于小数据集,使用整流非线性甚至比学习隐藏层的权重值更加重要 (Jarrett et al., 2009b) -- 当数据增多时,在深度整流网络中的学习比在激活函数具有曲率或两侧**饱和**的深度网络中的学习更容易 (Glorot et al., 2011a):传统的 sigmoid 函数,由于两端饱和,在传播过程中容易丢弃信息 -- ReLU 的过程更接近生物神经元的作用过程 - - > 饱和(saturate)现象:在函数图像上表现为变得很平,对输入的微小改变会变得不敏感。 - -> 《深度学习》 ch6.6 - 小结 -> -> https://blog.csdn.net/code_lr/article/details/51836153 - CSDN博客 ->> 答案总结自该知乎问题:https://www.zhihu.com/question/29021768 - - -### 9. 在做正则化过程中,为什么只对权重做正则惩罚,而不对偏置做权重惩罚 - -在神经网络中,参数包括每一层仿射变换的**权重**和**偏置**,我们通常只对权重做惩罚而不对偏置做正则惩罚。 - -精确拟合偏置所需的数据通常比拟合权重少得多。每个权重会指定两个变量如何相互作用。我们需要在各种条件下观察这两个变量才能良好地拟合权重。而每个偏置仅控制一个单变量。这意味着,我们不对其进行正则化也不会导致太大的方差。另外,正则化偏置参数可能会导致明显的欠拟合。 - -> 《深度学习》 ch7.1 - 参数范数惩罚 - - -### 10. 列举常见的一些范数及其应用场景,如 L0, L1, L2, L∞, Frobenius 范数 - -L0: 向量中非零元素的个数 - -L1: 向量中所有元素的绝对值之和 - - - -L2: 向量中所有元素平方和的开放 - - - -其中 L1 和 L2 范数分别是 Lp (p>=1) 范数的特例: - - - -L∞: 向量中最大元素的绝对值,也称最大范数 - - - -Frobenius 范数:作用于矩阵的 L2 范数 - - - -> 《深度学习》 ch2.5 - 范数(介绍),ch - -范数最主要的应用:正则化——权重衰减/参数范数惩罚 - -#### 权重衰减的目的: - -限制模型的学习能力,通过限制参数 θ 的规模(主要是权重 w 的规模,偏置 b 不参与惩罚),使模型偏好于权值较小的目标函数,防止过拟合。 - -> 《深度学习》 ch7.1 - 参数范数惩罚 - - -### 11. L1 和 L2 范数的异同 - -#### 相同点 -- 限制模型的学习能力,通过限制参数的规模,使模型偏好于权值较小的目标函数,防止过拟合。 - -#### 不同点 -- L1 正则化可以产生稀疏权值矩阵,即产生一个稀疏模型,可以用于特征选择;一定程度上防止过拟合 -- L2 正则化主要用于防止模型过拟合 -- L1 适用于特征之间有关联的情况;L2 适用于特征之间没有关联的情况 - -> 《深度学习》 ch7.1.1 - L2参数正则化 & ch7.1.2 - L1参数正则化 -> -> [机器学习中正则化项L1和L2的直观理解](https://blog.csdn.net/jinping_shi/article/details/52433975) - CSDN博客 - - -### 12. 为什么 L1 正则化可以产生稀疏权值,L2 正则化可以防止过拟合? - -#### 为什么 L1 正则化可以产生稀疏权值? - -添加 L1 正则化,相当于在 L1范数的约束下求目标函数 J 的最小值,下图展示了二维的情况: - -![](../images/TIM截图20180608171710.png) - -图中 J 与 L 首次相交的点就是最优解。L1 在和每个坐标轴相交的地方都会有“角”出现(多维的情况下,这些角会更多),在角的位置就会产生稀疏的解。而 J 与这些“角”相交的机会远大于其他点,因此 L1 正则化会产生稀疏的权值。 - -#### 为什么 L2 正则化不会产生稀疏的解? - -类似的,可以得到带有 L2正则化的目标函数在二维平面上的图形,如下: - -![](../images/TIM截图20180608172312.png) - -相比 L1,L2 不会产生“角”,因此 J 与 L2 相交的点具有稀疏性的概率就会变得非常小。 - -> [机器学习中正则化项L1和L2的直观理解](https://blog.csdn.net/jinping_shi/article/details/52433975) - CSDN博客 - -#### 为什么 L1 和 L2 正则化可以防止过拟合? - -L1 & L2 正则化会使模型偏好于更小的权值。 - -简单来说,更小的权值意味着更低的模型复杂度,也就是对训练数据的拟合刚刚好(奥卡姆剃刀),不会过分拟合训练数据(比如异常点,噪声),以提高模型的泛化能力。 - -此外,添加正则化相当于为模型添加了某种**先验**(限制),规定了参数的分布,从而降低了模型的复杂度。模型的复杂度降低,意味着模型对于噪声与异常点的抗干扰性的能力增强,从而提高模型的泛化能力。 - -> [机器学习中防止过拟合的处理方法](https://blog.csdn.net/heyongluoyao8/article/details/49429629) - CSDN博客 - - -### 13. 简单介绍常用的激活函数,如 sigmoid, relu, softplus, tanh, RBF 及其应用场景 - -#### 整流线性单元(ReLU) - - - -![](../images/TIM截图20180608212808.png) - -整流线性单元(ReLU)通常是激活函数较好的默认选择。 - -整流线性单元易于优化,因为它们和线性单元非常类似。线性单元和整流线性单元的唯一区别在于整流线性单元在其一半的定义域上输出为零。这使得只要整流线性单元处于激活状态,它的导数都能保持较大。它的梯度不仅大而且一致。整流操作的二阶导数几乎处处为 0,并且在整流线性单元处于激活状态时,它的一阶导数处处为 1。这意味着相比于引入二阶效应的激活函数来说,它的梯度方向对于学习来说更加有用。 - -**ReLU 的拓展** - -ReLU 的三种拓展都是基于以下变型: - - - -ReLU 及其扩展都是基于一个原则,那就是如果它们的行为更接近线性,那么模型更容易优化。 - -- 绝对值整流(absolute value rectification) - - 固定 α == -1,此时整流函数即一个绝对值函数 - - - - 绝对值整流被用于图像中的对象识别 (Jarrett et al., 2009a),其中寻找在输入照明极性反转下不变的特征是有意义的。 - -- 渗漏整流线性单元(Leaky ReLU, Maas et al., 2013) - - 固定 α 为一个类似于 0.01 的小值 - -- 参数化整流线性单元(parametric ReLU, PReLU, He et al., 2015) - - 将 α 作为一个参数学习 - -- maxout 单元 (Goodfellow et al., 2013a) - - maxout 单元 进一步扩展了 ReLU,它是一个可学习的多达 k 段的分段函数 - - 关于 maxout 网络的分析可以参考论文或网上的众多分析,下面是 Keras 中的实现: - ``` - # input shape: [n, input_dim] - # output shape: [n, output_dim] - W = init(shape=[k, input_dim, output_dim]) - b = zeros(shape=[k, output_dim]) - output = K.max(K.dot(x, W) + b, axis=1) - ``` - > [深度学习(二十三)Maxout网络学习](https://blog.csdn.net/hjimce/article/details/50414467) - CSDN博客 - -#### sigmoid 与 tanh(双曲正切函数) - -在引入 ReLU 之前,大多数神经网络使用 sigmoid 激活函数: - - - -![](../images/TIM截图20180608195851.png) - -或者 tanh(双曲正切函数): - - - -tanh 的图像类似于 sigmoid,区别在其值域为 (-1, 1). - -这两个函数有如下关系: - - - -**sigmoid 函数要点**: -- sigmoid 常作为输出单元用来预测二值型变量取值为 1 的概率 - > 换言之,sigmoid 函数可以用来产生**伯努利分布**中的参数 ϕ,因为它的值域为 (0, 1). -- sigmoid 函数在输入取绝对值非常大的正值或负值时会出现**饱和**(saturate)现象,在图像上表现为开始变得很平,此时函数会对输入的微小改变会变得不敏感。仅当输入接近 0 时才会变得敏感。 - > 饱和现象会导致基于梯度的学习变得困难,并在传播过程中丢失信息。——[为什么用ReLU代替sigmoid?](#8.-分段线性单元(如-ReLU)代替-sigmoid-的利弊) -- 如果要使用 sigmoid 作为激活函数时(浅层网络),tanh 通常要比 sigmoid 函数表现更好。 - > tanh 在 0 附近与单位函数类似,这使得训练 tanh 网络更容易些。 - -#### 其他激活函数(隐藏单元) - -很多未发布的非线性激活函数也能表现的很好,但没有比流行的激活函数表现的更好。比如使用 cos 也能在 MNIST 任务上得到小于 1% 的误差。通常新的隐藏单元类型只有在被明确证明能够提供显著改进时才会被发布。 - -**线性激活函数**: - -如果神经网络的每一层都都由线性变换组成,那么网络作为一个整体也将是线性的,这会导致失去万能近似的性质。但是,仅**部分层是纯线性**是可以接受的。这可以帮助**减少网络中的参数**。 - -**softmax**: - -softmax 单元常作为网络的输出层,它很自然地表示了具有 k 个可能值的离散型随机变量的概率分布。 - -**径向基函数(radial basis function, RBF)**: - - - -在神经网络中很少使用 RBF 作为激活函数,因为它对大部分 x 都饱和到 0,所以很难优化。 - -**softplus**: - - - -![](../images/TIM截图20180608204913.png) - -softplus 是 ReLU 的平滑版本。通常不鼓励使用 softplus 函数,大家可能希望它具有优于整流线性单元的点,但根据经验来看,它并没有。 -> (Glorot et al., 2011a) 比较了这两者,发现 ReLU 的结果更好。 - -**硬双曲正切函数(hard tanh)**: - - - -它的形状和 tanh 以及整流线性单元类似,但是不同于后者,它是有界的。 -> Collobert, 2004 - -#### sigmoid 和 softplus 的一些性质 - -![](../images/TIM截图20180608205223.png) - -> 《深度学习》 ch3.10 - 常用函数的有用性质 - - -### 14. Jacobian 和 Hessian 矩阵及其在深度学习中的重要性 - -> 《深度学习》 ch4.3.1 - 梯度之上:Jacobian 和 Hessian 矩阵 - - -### 15. 信息论、KL 散度(相对熵)与交叉熵 - -信息论的基本想法是一个不太可能的事件居然发生了,要比一个非常可能的事件发生,能提供更多的信息。 - -该想法可描述为以下性质: -1. 非常可能发生的事件信息量要比较少,并且极端情况下,确保能够发生的事件应该没有信息量。 -2. 比较不可能发生的事件具有更高的信息量。 -3. 独立事件应具有增量的信息。例如,投掷的硬币两次正面朝上传递的信息量,应该是投掷一次硬币正面朝上的信息量的两倍。 - -#### 自信息(self-information) - -自信息是一种量化以上性质的函数,定义一个事件 x 的自信息为: - - - -> 当该对数的底数为 e 时,单位为奈特(nats,本书标准);当以 2 为底数时,单位为比特(bit)或香农(shannons) - -#### 信息熵(Information-entropy) - -自信息只处理单个的输出。此时,用信息熵来对整个概率分布中的不确定性总量进行量化: - - - -> 信息熵也称香农熵(Shannon entropy) - -#### 相对熵/KL 散度(Kullback-Leibler divergence) - -P 对 Q 的相对熵: - - - -**KL 散度在信息论中度量的是那个直观量**: - -在离散型变量的情况下, KL 散度衡量的是,当我们使用一种被设计成能够使得概率分布 Q 产生的消息的长度最小的编码,发送包含由概率分布 P 产生的符号的消息时,所需要的额外信息量。 - -**KL 散度的性质**: -- 非负;KL 散度为 0 当且仅当P 和 Q 在离散型变量的情况下是相同的分布,或者在连续型变量的情况下是“几乎处处”相同的 -- 不对称;D_p(q) != D_q(p) - -#### 交叉熵(cross-entropy) - - - -**交叉熵与 KL 散度的关系**: - - - -针对 Q 最小化交叉熵等价于最小化 KL 散度,因为 Q 并不参与被省略的那一项。 - -> 信息论中,记 `0log0 = 0` - -> 《深度学习》 ch3.13 - 信息论 -> -> [信息量,信息熵,交叉熵,KL散度和互信息(信息增益)](https://blog.csdn.net/haolexiao/article/details/70142571) - CSDN博客 - - -### 16. 如何避免数值计算中的上溢和下溢问题,以 softmax 为例 - -- **上溢**:一个很大的数被近似为 ∞ 或 -∞; -- **下溢**:一个很小的数被近似为 0 - -必须对上溢和下溢进行**数值稳定**的一个例子是 **softmax 函数**: - - - -因为 softmax 解析上的函数值不会因为从输入向量减去或加上**标量**而改变, -于是一个简单的解决办法是对 x: - - - -减去 `max(x_i)` 导致 `exp` 的最大参数为 `0`,这排除了上溢的可能性。同样地,分母中至少有一个值为 `1=exp(0)` 的项,这就排除了因分母下溢而导致被零除的可能性。 - -**注意**:虽然解决了分母中的上溢与下溢问题,但是分子中的下溢仍可以导致整体表达式被计算为零。此时如果计算 log softmax(x) 时,依然要注意可能造成的上溢或下溢问题,处理方法同上。 - -当然,大多数情况下,这是底层库开发人员才需要注意的问题。 - -> 《深度学习》 ch4.1 - 上溢与下溢 - - -### 17. 训练误差、泛化误差;过拟合、欠拟合;模型容量,表示容量,有效容量,最优容量的概念; 奥卡姆剃刀原则 - -> 《深度学习》 ch5.2 - 容量、过拟合和欠拟合 - - -### 18. 高斯分布的广泛应用的原因 - -> 《深度学习》 ch3.9.3 - 高斯分布 diff --git "a/\346\267\261\345\272\246\345\255\246\344\271\240/README.md" "b/\346\267\261\345\272\246\345\255\246\344\271\240/README.md" deleted file mode 100644 index 8062657a..00000000 --- "a/\346\267\261\345\272\246\345\255\246\344\271\240/README.md" +++ /dev/null @@ -1,43 +0,0 @@ -## [问题与答案](./QA.md) - -部分问题不能很好的总结答案,只列出了在书中的参考页码 - -## 问题列表 - -#### 1. 如何设置网络的初始值? - -### 2. 梯度爆炸的解决办法 - -#### 3. MLP 的万能近似定理 - -### 4. 在 MLP 中,深度与宽度的关系,及其表示能力的差异 - -#### 5. 稀疏表示,低维表示,独立表示 - -#### 6. 局部不变性(平滑先验)及其局限性 - -### 7. 为什么交叉熵损失相比均方误差损失能提高以 sigmoid 和 softmax 作为激活函数的层的性能? - -### 8. 分段线性单元(如 ReLU)代替 sigmoid 的利弊 - -#### 9. 在做正则化过程中,为什么只对权重做正则惩罚,而不对偏置做权重惩罚 - -### 10. 列举常见的一些范数及其应用场景,如 L0, L1, L2, L∞, Frobenius 范数 - -### 11. L1 和 L2 范数的异同 - -#### 12. 为什么 L1 正则化可以产生稀疏权值,而 L2 正则化可以防止过拟合? - -### 13. 简单介绍常用的激活函数,如 sigmoid, relu, softplus, tanh, RBF 及其应用场景 - -##### 14. Jacobian 和 Hessian 矩阵及其在深度学习中的重要性 - -#### 15. 信息论、KL 散度(相对熵)与交叉熵 - -#### 16. 如何避免数值计算中的上溢和下溢问题,以 softmax 为例 - -##### 17. 训练误差、泛化误差;过拟合、欠拟合;模型容量,表示容量,有效容量,最优容量的概念; 奥卡姆剃刀原则 - -### 18. 高斯分布的广泛应用的原因 - -