
C++编译器 当我们定义了一个类的时候, C++编译器在默认的情况下会为我们添加默认的构造方法, 拷贝构造方法, 析构函数和=运算符 在第一次创建对象的语句中如: MyString myString = "hello, world!";中, 如果我们定义的构造函数为如下, 则就是隐式调用构造方法, 如果构造方法使用了explicit修饰则会报错, 总之在第一次创建对象的语句中, 就算出现了=, 只能调用构造方法, 而不是=方法, 因为我们是要构造对象, =真正起作用是在这个对象创建完毕之后 MyString(const char *str="") { this->len = strlen(str); this->values = new char[this->len + 1]; strcpy(this->values, str); } C++中的对象在内存中的分布 没有继承的情况下 就是一个结构体的分布 单继承的情况下(方法使用virtual修饰) A为基类, B为派生类, 在创建B的实例的时候, 在内存中B的内存中会有一个虚指针(virt_pa)指向一个虚函数表, 注意是一个虚指针(virt_pa), 默认先拷贝A中的虚函数表, 如果B中有新的虚函数则注册到这个表中, 若B中重载了A中的虚函数, 则将虚函数表中的那个对应的虚函数改成B的虚函数(这就是实现多态的关键) 在多继承的情况下(方法都是用virtual修饰) A和B为C的基类 和单继承类似, 只不过多出了一个虚指针(virt_pb), 这个虚指针对应的虚函数表在C对象创建的时候会自动拷贝B的虚函数表, 而另外一个虚指针(virt_pa)指向的虚函数表则拷贝A的虚函数表, 接着在看C类中定义的函数, 如果C中出现了新的虚函数, 则将这个虚函数放到C第一个继承的基类在C对象中对应的虚指针指向的虚函数表中, 这里指的就是A, 也就是所B中多出来的foo函数的地址会放到virt_pa指向的虚函数表中, 如果C重载了函数, 则判断是重载了哪个函数, 这个函数从哪个基类中继承过来的, 知道了是从哪个基类中继承过来的, 就可以通过对应的虚指针找到被重载的函数在虚函数表中的位置, 将其替换成C中重载了的函数, 比如, A中有一个foo函数, C中也有一个foo函数, 则编译器会通过virt_pa指针在对应的表中将C::foo的地址替换到A::foo所在的位置 多重单继承 A -> B -> C 和单继承非常的类似, 也是只有一个指针, 这个指针为virt_pa, 可以知道是超级基类的, 在创建C对象的时候, 将A和B中的虚函数表拷贝到C中的virt_pa对应的虚函数表中, 在考虑重载 虚继承 A -> B 虚继承与上面的继承最大的不同就是在创建了B对象的时候, 会有两个虚指针, 一个是virt_pa, 另外一个是virt_pb, 多出来一个与B相关的指针; 除此之外, virt_pb指向的虚函数表只保存B类中定义的所有的虚函数, 如果B重载了A中的某一个方法, 则就会将B中那个函数的指针拷贝到virp_pa指向的虚函数表中的被重载函数的位置
数据降维 分类 PCA(主成分分析降维) 相关系数降维 PCA 降维(不常用) 实现思路 对数据进行标准化 计算出数据的相关系数矩阵(是方阵, 维度是nxn, n是特征的数量) 计算出相关系数矩阵的特征值和特征向量(虽然这里说的是向量, 但是是矩阵, 这个矩阵的每一列都是特征值或者特征向量, 是nxn), 特征值是每一个特征的特征值的集合, 但是在特征向量是每一个特征的特征向量的集合, 前者我们提到的特征值和特征向量是集合 多特征值进行降序排序 根据已经得到的特征值计算出贡献率和累计贡献率(主要看累计贡献率, 单单一个贡献率指的是一个主成分保存的原始特征的信息, 累计贡献率是总共保存的原始特征信息) 设置信息阈值T, 一般设置为0.9, 如果大于T, 则记录下来当前的位置k(k也就是我们选择的主成分的个数, 主成分就是特征, 也就是一列) 根据k选择主成分对应的特征向量 将标准化之后的数据(矩阵)右乘在上一步中选择出来的特征向量(在这一步得到的矩阵就是m x new_n维度的了), 得到的就是主成分的分数, 也就是降维之后的数据集合 伪代码 X = load('data.xlsx', 'B1:I11'); m = size(X, 1); % m 表示样本的数量 n = size(X, 2); % n 表示特征的数量 % 数据标准化 for i = 1:m SX(:, i) = (X(:, i) - mean(X(:, i))) / std(X(:, i)); end % 计算相关系数 CM = corrcoef(SX); % V 是特征向量, D 是特征值 [V D] = eig(CM); % 对D特征值进行降序排序, 将结果保存到DS的第一列 for i = 1:n DS(:, 1) = D(n + 1 - i, n + 1 - i); end % 计算贡献率和累计贡献率 for i = 1:n % 第二列为当前单个, 每一个, 主成分的贡献率 DS(:, 2) = D(i, 1) / sum(D(:, 1)); % 第三列为到当前主成分的累计贡献率 DS(:, 3) = sum(D(1:i, 1)) / sum(D(:, 1)); end % 选择主成分 T = 0.9; for i = 1:n if DS(:, i) > T k = i; break; end end % 获取主成分对应的特征向量 for i = 1:n PV(:, i) = DS(:, n + 1 - i); end % 获取新的特征样本 X_new = SX * PV; 相关系数降维 公式: \[r=\sum_{j=1}^{m}{{(x_{j}-\overline{x_{j}})({y_{j}-\overline{y_{j}}})}\over{std(x_{j})std(y_{j})}}\] 如果|r|在[0.7, 1]时表示强线性关系, 说明x和y有很紧密的线性关系 如果|r|在[0.5, 0.7]时表示中线性关系 如果|r|在[0.2, 0.5]时表示低线性关系 如果|r|在[0, 0.2]时表示没有关系 r > 0表示正相关, r < 0表示负关系
机器学习中的常用操作 输入节点到隐藏节点,特征数量n可能会变化,这个取决于我们定义的隐藏层的节点个数,但是样本数量m是不变的,从隐藏层出来还是m 在预测的时候,我们需要不断的迭代输入的特征 提高精度 增加样本数量 -> 解决high variance 减少特征 -> 解决high variance 增加特征 -> 解决high bias 根据现有的特征生成多项式(从\(x_1\), \(x_2\)扩展到\(x_1 + x_2 + x_1^{2} + x_2^{2} + x_1{x_2}\)) 寻找新的特征 增加正则化参数\(\lambda\) -> 解决high variance 减小正则化参数\(\lambda\) -> 解决high bias 对数据的划分 将原来的训练样本按照6:2:2的比例划分成Train, Cross Validation, Test三个集合 如果不考虑Cross Validation的话, 则将训练样本划分成7:3的比例 -> Train(7), Test(3) 关于Cross Validation 如果我们对同一个机器学习问题, 假设了多个不同的模型(表现形式不同, 如\(kx+b\)和\(x^2+b\), 而不是\(k_1x+b_1\)和\(kx+b\), 因为k和b是我们的参数, 是我们要求的, 他们不应该考虑进去), 我们需要选择最好的模型(需要引进额外的参数d, 表示那个模型), 这个时候就要通过Cross Validation中的数据计算每一个模型测试的\(J_{cv}(\theta)\)来判断, \(J_{cv}(\theta)\)在后面会提到 误差 一旦对数据集合进行了划分,那么我们的损失值就从原来的\(J(\theta)\)变成了\(J_{train}(\theta)\), \(J_{cv}(\theta)\), \(J_{test}(\theta)\), 其中\(J_{train}(\theta)\)的功能就是在没有进行数据集合划分的\(J(\theta)\)的功能, 而\(J_{test}(\theta)\)是在我们已经拟合了假设函数, 使用Test集合中的数据进行测试所产生的损失, \(J_{cv}(\theta)\)在上面已经提到过了, 其实在CV数据集中的进行的就是对模型的测试而已, 和我们要在Test数据集中是一样的, 只是目的不同, 在CV数据集中, 我们目的是找出最好的模型, 因为这个时候模型太多了, 而在Test数据集中的时候, 在之前我们已经通过交叉验证获取了最好的模型, 现在是来测试一下, 这个模型对Test中的数据拟合的情况 \(J_{train}(\theta)\), \(J_{cv}(\theta)\), \(J_{test}(\theta)\)的公式和原始的\(J(\theta)\)一样, 为\(J_{train}(\theta)={{{1}\over{2m}}\sum_{i=1}^{m}(h(x^{(i)})-y^{(i)})^{2}}\), 注意, m表示训练样本的数量, x和y也都是在训练样本中的, 以此类推到\(J_{cv}(\theta)\), \(J_{test}(\theta)\) 高偏差(high bias)和高方差(high variance) 高偏差: 欠拟合 增加样本数量是徒劳 高方差: 过拟合 增加样本数量会提高精度 常见的\(J_{train}\)和\(J_{cv}\)关系 随着样本逐渐增加 \(J_{train}\uparrow\), 因为在样本很少的时候是很好拟合的, 随着样本的增加想要拟合所有的点就非常的困难 \(J_{cv}{\downarrow}\), 但是交叉验证的结果越来越小, 我们主要看的就是这个 随着正则化参数\(\lambda\)逐渐增加 \(J_{train}\uparrow\), \(\lambda\)越大, 则表示我们对\(\theta\)的惩罚力度在不断的增大, 模型会朝着过拟合的反方向发展, 我们知道过拟合的\(j_{train}\)很小, 所以现在这个情况下\(J_{train}\)应该增大 \(J_{cv}{\downarrow}{\uparrow}\), \(J_{cv}\)先下降后上升, \(\lambda\)太小或者太大都不好 随着阶数逐渐增加 \(J_{train}\downarrow\) \(J_{cv}{\downarrow}{\uparrow}\) 从上面我们发现, \(J_{cv}\)要么是下降的, 要么是先下降再上升的
单词列表 eradicate: 根除 epidemic: 流行 cumulative: 积累 feasibility: 可行性 discrepancy: 差异 critical: 批评的;决定性的 corpse: 尸体 intervention: 介入 infectious: 传染的 resistance: 阻力 transmission: 传动装置 incubation: 潜伏期 decay: 衰变 estimation: 估计 obtain: 获取 compatible: 兼容的 onset: 开始着手 ratio: 比率 rodent: 啮齿类 fatal: 致命的 fatality: 死亡;致命性 sensitivity: 敏感 widget: 装饰品 notation: 符号 WHO(World Health Organization) transmit: 传送 decease: 死亡 chaos: 混乱 reproduction: 繁殖 outbreak: 爆发 virus: 病毒 drama: 戏剧 strain: 张力 coordinate: 坐标 suspect: 可疑 contaminate: 污染 validity: 有效性 infectious: 传染的 schematic: 示意图 dotted curve: 点图 hospitalized individual: 就医的人 illustrate: 说明 transition: 过渡 denotes: 表示 radius: 半径 least square curve: 最小二乘曲线 does: 剂量 efficacy: 功效 句子 the most recent one: 最近的一个 the current situation can be seen clearly by this graph released by WHO the data includes the numbers of total cases we need to first set the values for some of the parameters to carry on our calculation the average duration values of other parameters be listed in table 1 apply these parameters into the equation verify the validity of our model cumulative number of individuals from the figure above therefore, the model can serve as a good tool for it we have to take other factors into consideration So here the traditional model should be modified and the improved model can be written as ... where variable s is ..., t is ... it will reduce significantly this equation is improved from that model and it is our ultimate model. the transition A to B shows how basic model can be greatly improved based on other factors R denotes the radius of a matrix A the parameters are borrowed from the existing reference we assume their relationship to be \(y=\theta{b}\) where y is assumed to be 3.2 based on the experience the start day of the stimulation is defined as Day \(1^{st}\) the parameter values are ... a large speed of spread of the virus poses great potential threat
神经网络案例 摘要 在Compute Vision(计算机视觉)中,我们输入的是一张一张的图片,但是在计算机看来,每一张图片都是由一个一个像素点组成的,那么,什么是我们的输入样本X,什么又是我们的标签y?在图像识别中,一张图片所有像素点就是一个样本,也就是矩阵X中的一行,y就是对这个图片判断的结果。可想而知,就算是一张50x50的图片,它的特征也有2500之多,如果他是RGB图片,那就是7500个特征,那么特征的值是什么?就是图片的亮度(intensity),它的是在0-255之间。在图像识别中,我们会将像素矩阵unrolled成为一个向量,将这个向量作为一个训练集。如果要可视化图片,需要将其在转换为矩阵。 activation function(激活函数),其实它就是在机器学习领域中的sigmoid(logistic)函数,这是在神经网络中换了一个名字罢了。 在深度学习中,我们会遇到比较复杂的\(J(\theta)\)目标函数(cost function), 一般情况下,使用梯度下降的方法来计算\(J(\theta)\)目标函数的最小值,有下面这个梯度下降公式\[\theta := \theta - {{\partial}\over{\partial{\theta}}}J(\theta)\]其实\({{\partial}\over{\partial{\theta}}}J(\theta)\)是梯度,梯度下降公式的关键点就是计算出梯度,在线性回归和逻辑回归这些简单的模型中的目标函数的梯度是好求的,可以直接带入偏导的公式,但是在深度学习中比较复杂,求梯度需要回归到最原始的求法,使用定义法进行求导,不过这样的计算量非常的大,于是后来就诞生了反向传播算法(backpropagation algorithm),这个算法大量地使用了链式求导法则。大致上是下面的公式(如果实现了反向传播算法,要通过梯度检测,之后再投入到训练中,因为反向传播算法比较的复杂,实现起来可以会有bug) 假设我们现在有4个layer,输出层是一个节点(这是一个单分类的问题) \(\delta^{(4)}=(a^{(4)}-y)\) \(\delta^{(3)}=({\theta^{(3)}})^{T}{\delta}^{(4)} \times g(z^{(3)})\), 其中\(\delta^{(3)}\), \(z^{(3)}\)等都是向量或者矩阵,建议在数学公式推导的时候使用实数,在推导结束时候,放到matlab等应用的时候将其转为向量或者矩阵表示。 \(\delta^{(2)} = ({\theta^{(2)}})^{T}{\delta}^{(3)} \times g(z^{(2)})\) 第一层是不需要计算误差的,因为它是我们原始的输入层。 上述式子中的\(g(z^{(3)})\)等于sigmoid(\(z^{(3)}\))(sigmoid(\(z^{(3)} - 1\))) 梯度检测(gradient checking) 在上面我们已经知道了,在使用了反向传播算法计算出误差值(error),为了防止使用反向传播高级算法出现bug,需要使用正规的求导的方法来检测反向传播算法是否出现了bug,只要gradApprox \(\approx\) DVec则表示没有bug,对于如何实现梯度检测,只需要构建一个小型的神经网络,接着生成一些数据X和标签y分别输入到反向传播函数和一般计算梯度的函数中,最后将结果进行比较即可 在神经网络中会有非常多的参数,为这些参数赋予初始值是非常重要的,于是就产生了随机初始化的方法(random initialize), 一个参数表示为\(\theta_{ji}^{(l)}\),随机初始化的目的就是将\(\epsilon\)的初始值在\(-\epsilon\)到\(\delta\)之间 在进行反向传播的时候,计算出来的error item \(\delta\) 的个数与参数的个数一样的,因为error item与每一个与之对应的参数密切相关,我们要通过error item计算出参数的梯度theta_grad,当所有的训练样本都输入进去并参与到了error item的运算中去的时候,得到的theta_grad与参数是同维度的,由此可以推断出,error item 也是与参数同维度的,这里将error item \(\approx {{\partial}\over{\partial{\theta}}}J(\theta)\)$,这样就计算出了梯度,接下来就可以更新参数了 我们知道在神经网络中我们的参数\(\theta\)成为了多个矩阵,返回的梯度也成为多个矩阵,这与我们之前学习的线性回归和逻辑回归不同,他们的是一个列向量,上面提到的是在一次迭代中,因此为了统一和方便编程,将几个矩阵全部unrolled成为一个列向量 规定 L: 表示layer的个数 \(a_{i}^{(j)}\): 表示第j层layer的第i个单元(unit) \(s_{j}\): 表示第j层layer的单元的个数 \(\Theta^{(j)}\): 表示第j层layer的权重矩阵 代码 costFunction % 将y标签的值转为[0 0 0 0 1 0 0 0 0 0 ...]的形式 % 使用for循环迭代每一个样本 % 注意,显示的情况就是参数都已经有了,在一个for循环中,输入的是一个样本 tmp = (1:num_labels)'; for i = 1:m % 将其中的一个样本的标签转化为[0 0 0 0 0 1 ... 0 0]形式 % 神经网络的前向传播 y_new = y(i) == tmp; x = [1 X(i, :)]; z_2 = Theta1 * x'; a_2 = sigmoid(z_2); % 25x1 demensions z_3 = Theta2 * [1, a_2']'; a_3 = sigmoid(z_3); % 计算损失函数 J = J + sum(-y_new .* log(a_3) - (1 - y_new) .* log(1 - a_3)); % 神经网络的后向传播 delta_3 = a_3 - y_new; delta_2 = Theta2' * delta_3 .* [1; sigmoidGradient(z_2)]; delta_2 = delta_2(2:end); Theta1_grad = Theta1_grad + delta_2 * x; Theta2_grad = Theta2_grad + delta_3 * [1 a_2']; end J = 1 / m * J; J = J + lambda / (2 * m) * (sum(sum(Theta1 .^ 2)) + sum(sum(Theta2 .^ 2))); % ========================================================================= Theta1_grad = Theta1_grad / m + lambda / m * Theta1; Theta1_grad(:, 1) = Theta1_grad(:, 1) - lambda /m * Theta1(:, 1); Theta2_grad = Theta2_grad / m + lambda / m * Theta2; Theta2_grad(:, 1) = Theta2_grad(:, 1) - lambda / m * Theta2(:, 2); % Unroll gradients grad = [Theta1_grad(:) ; Theta2_grad(:)]; end randomInitWeights % You need to return the following variables correctly W = zeros(L_out, 1 + L_in); % ====================== YOUR CODE HERE ====================== % Instructions: Initialize W randomly so that we break the symmetry while % training the neural network. % % Note: The first column of W corresponds to the parameters for the bias unit % epsilon_init = 0.12; W = rand(L_out, L_in + 1) * 2 * epsilon_init - epsilon_init; % =========================================================================
机器学习摘要 matlab 损失函数 对应一个已经确定了参数的cost function,尽管输入的参数是向量或者是矩阵,但是返回的\(J(\theta)\)一定是一个实数 \(J(\theta)\)是将所有训练样本都输入到模型中计算,返回一个实数 更新假设函数的参数是在输入了所有的训练样本到模型中并且计算出了一个\(J(\theta)\),才进行的 假设函数的值 在matlab编程中,\(h(x)\)假设函数返回的维度和标签是一样的。 要和公式对的上,就按照样本数量遍历训练样本,而不是直接使用矩阵的方法,在实现神经网络的时候会哭的。 神经网络 在神经网络的\(J(\Theta)\)损失函数中,出现了从k=1到k=K的求和,其实这个就是对y在[0 0 0 1 ... 0 0]形式下,对这个向量中的每一个元素的操作。K的大小就是y的长度
使用one-vs-all初始手写字母识别 数据特点 每一个图片都是20 x 20的像素矩阵,但是在输入的样本中是一个1 x 400的向量,标签y在{0, 1, 2, ..., 9}之间取值 共有5000个训练样本 可视化数据 从5000个样本中随机的挑选出100个训练样本进行可视化 得到的100个样本中,每一个样本都是一个向量,要想对其可视化,需要将其从向量还原为原始的矩阵 首先确定矩阵的高和宽(单位是像素pixel) 创建一个displayArray矩阵,用来保存100个样本转换为矩阵的像素数据,形象地讲,就是将100个图片放到一个大的面板上,这样才能做到可视化数据 初始完毕displayArray矩阵之后,使用matlib中的imagesc函数将其显示出来 代码如下: myDisplayData.m function [h, displayArray] = myDisplayData(X) % 获取一张图片的高和宽 exampleHeight = round(sqrt(size(X(1, :), 2))); exampleWidth = round(size(X(1, :), 2) / exampleHeight); % 计算整个面板的高和宽(但是是图片的个数) [m, n] = size(X); displayRows = round(sqrt(m)); displayCols = round(m / displayRows); % 先创建出displayRows * exampleHeight, displayCols * exampleWidth的面板 displayArray = ones(displayRows * exampleHeight, displayCols * exampleWidth); % 将图片放到面板对应的位置上 % 下面的式子就和数学有一些关系,如何确定现在填充的矩阵在displayArray中的位置 currExample = 1; for i = 1:displayRows for j = 1:displayCols displayArray(... (i - 1) * exampleWidth + 1:exampleWidth + (i - 1) * exampleWidth, ... (j - 1) * exampleHeight + 1:exampleHeight + (j - 1) * exampleHeight ... ) = ... reshape(X(currExample, :), ... exampleHeight, exampleWidth); currExample = currExample + 1; end end colormap(gray); h = imagesc(display_array); axis image off; end 数据预处理 为X添加bias(偏移量): X = [ones(m, 1), X]; % m表示样本的数量 m: 训练样本的数量 n: 特征的数量,不包括bias(偏移)特征 y: 标签,{0, 1, 2, ..., 9} numLabel: y可以取的值的个数,这里为10 确定假设函数 由题目可知,这是一个典型的Multi-Class Logistic Regression问题,因此使用逻辑回归模型 模型函数: \[h(\theta)=g(\theta^{T}x)={{1}\over{1+e^{-\theta^{T}x}}}\] 因为这个一个10分类的问题,所以需要拟合出10个假设函数才行,也就是要最小化出10个\(\theta\)向量,我们调用oneVsAll函数返回的参数应该是一个矩阵,每一个行向量是一个类别的假设函数的参数 计算损失函数(cost function)和梯度 损失函数公式: \[J(\theta)={{1}\over{m}}\sum_{i=1}^m(-y^{(i)}log(h_{\theta}(x^{(i)})) - (1-y^{(i)})log(1-h_{\theta}(x^{(i)})))+{{\lambda}\over{2m}}\sum_{j=1}^m{\theta_{j}^2}\]其中\(\theta\), \(y^{(i)}\), \(x^{(i)}\)为向量或者矩阵,后一项是对非偏差项的正则化 梯度公式: j >= 1 \[{{\partial}\over{\partial}\theta_{j}}J(\theta)={{1}\over{m}}\sum_{i=1}^m{(h_{\theta}(x^{(i)})-y^{(i)})x^{(i)}} + {{\lambda}\over{m}}\theta_{j}\] j = 0 \[{{\partial}\over{\partial}\theta_{0}}J(\theta)={{1}\over{m}}\sum_{i=1}^m{(h_{\theta}(x^{(i)})-y^{(i)})x^{(i)}}\] 注意,上面的\(h_{\theta}(x^{(i)})\)是sigmoid函数,自己在实现的时候总是将其写成线性回归函数 在oneVsAll.m文件中实现\(minimize_{\theta}J(\theta)\) 每一个类都进行梯度下降,计算出这一类的参数,也就是写一个循环,在每一个循环中都有可以得到最终的这个类别对应的参数,循环的次数为numLabels 核心代码 for index = 1:numLabels initialTheta = zeros(n + 1, 1); options = optimset('GradObj', 'on', 'MaxIter', 50); % fmincg会自动选择最优的学习率alpha allTheta(index, :) = fmincg(@(t)(lrCostFunction(t, X, (y == index), lambda)), ... initialTheta, options); end 预测 输入的一个样本,需要为其添加bias值,在将样本分别输入到10个假设函数中,计算出最大的值,那个值对应的就是类别。 核心代码 tmp = zeros(m, num_labels); for i = 1:num_labels tmp(:, i) = sigmoid(X * allTheta(i, :)'); end [val, p] = max(tmp, [], 2);
数字逻辑VHDL signal是全局的,在整个结构体中都有效,它的赋值是在进程结束。 variable是局部的,它的赋值是立即生效的。 process之间是并行的,但是在内部是按照顺序执行的。 标准头 LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.STD_LOGIC_UNSIGNED.ALL; 定义ENTITY的注意点 ENTITY comp IS PORT( A1: IN STD_LOGIC; B1: IN STD_LOGIC; A0: IN STD_LOGIC; B0: IN STD_LOGIC; aleb: OUT STD_LOGIC; agtb: OUT STD_LOGIC; aeqb: OUT STD_LOGIC -- 没有分号! ); END comp;
Docker 安装 macOS或者windows 下载boot2docker工具 CentOS yum install docker-io -y systemctl start docker docker摘要 docker虚拟化 只虚拟User space 一台机器可以运行20-50个container 启动速度快 对内核有要求(不能像VMware一样可以安装mac,windows和linux) 用于大数据,分布式和集群 container实现 cgroup(内核中对资源的限制机制,通过控制进程(一个container实例)来限制) namespace(每一个容器都是有一个自己网络进程的独立的虚拟环境,实现容器间的隔离 chroot(文件系统的隔离,有一个真实的物理文件系统(物理机上),其他的都是虚拟的文件系统(虚拟的文件系统在内存中)) 以上,cgroup,namespace,chroot都是在linux中,也就是直接调用linux,后来为了跨平台,将上面的封装成了libcontainer库,让docer依赖于它 pid,容器有自己独立的进程表和1号进程 net,容器有自己的network info ipc,container之间的通信 mnt,每个容器都有自己唯一的目录挂载,是真实的挂载目录的子目录 utc,有独立的hostname和domain 层次 server -> hostOS -> docker engine -> app1 使用aufs实现分层的文件系统管理 Image是一种文件系统,里面必须有操作系统或者软件,docker中的系统Image很小,因为只包含很好的最核心的部分,每一个那么多的外围 只读为Image,可写为container,image是java中的class,container是对象 Image类似一个单链表系统,每一个image包含一个指向parent image的指针 每一个parent image的image是base image 层次: debain image(base image) -> apache image -> container 一般base image是操作系统镜像 建议:一个container干一件事情 dockerfile(类似于makefile,是命令的集合) 描述一个Image的层次,就提描述了一个依赖次序,centos -> apache -> container 有了它可以完美的重现出开发环境,方便了测试人员 docker hub(镜像仓库,类似于github) C/S运行 client request -> server -> http server -> router -> job -> driver 使用 docker pull {image name} docker images --no-trunc docker run [OPTIONS] IMAGE[:TAG] [CMD][ARG...] -name -i: iteract -t: tag -d: detach docker ps 不加上-a选项显示的正在运行的容器 加上-a显示所有的容器,包括停止的 docker inspect [ID] docker logs docker build docker rm/kill/stop id, 关闭容器 批量操作: docker rm/kill/stop $(docker ps -a -q) docker run -t -i centos /bin/bash, -t表示是伪tty(就是给一个终端,如果每一个则容器一start就stop了),-i表示iteract(在有了终端这个前提之下,如果attach到容器中,可以执行交互式的命令,如果没有则什么都干不了),-d就是在后台运行,不会在执行了上面这个命令之后直接进入到容器中 进入到了交互之界面,使用exit退出,但是也会stop容器,如果要在一次进入到该容器,需要先start容器,则attach容器,因为-i会前台运行,使用-d则是相反的 docker cp id:path hostpath 自己的摘要 docker在某些方面和git是一样的,不如说对于image镜像都是通过版本控制的,我们从dockeruhb中pull下来镜像文件,如果我们将其启动为一个容器,如果我们在这个容器中进行了修改,docker会通过文件将其修改记录下来,我们可将当当前的容器commit成一个新的image,这就类似于git中的一个commit点 在确定一个容器的时候是使用container id 在确定一个image的时候,是通过name:tag,如果只有name则会选中所有的tag,tag表示一个版本 生成一个image(提交点)的方法有两种,docker commit 和 docker build,在docker build中就是在一个image中执行一个命令的集合在提交成一个新的image
Unix与Windows的思想 Unix中的哲学是“一切皆文件”,这里的一切皆文件是一个广泛的概念,有一些特殊的设备文件,在/dev目录下 物理设备在Unix中就对应一个特殊的设备文件,比如打印机就是/dev/lp0,这个设备文件直接与物理设备的串行端口连接,只要向这个设备文件中传入数据,就可以调用打印机。 而没有与物理设备直接连接的特殊的设备文件,称之为伪设备文件。伪设备文件一般都是成对出现的,就像是打电话一样,通过这种逻辑关系就可以实现与物理设备一样的功能,比如在伪设备的一端发送一串字符串,在另一端就会就收到这个字符串,另一端可能的操作就是将这个字符串原样返回,或者对其进行加密再返回,在或者将其传送到/dev/lp0设备文件中调用打印机打印文件。 在Unix中命令行中的字符都将会被转换成为文件序列(文件流),这样才能够实现“一切皆文件”的思想,任何东西都可以被抽象成文件流。 在Unix中,要使用命令行,先启动一个终端,接着启动一个shell,shell和终端通过pty伪终端设备通信,也就是在mac下,我们鼠标点击terminal这个终端应用程序,在进入到终端的程序之后,会默认启动一个shell,但是在windows中却是用户只能启动命令行程序,而不能启动终端,在Windows中我们没有看见过一个terminal程序,只要我们启动了一个shell,如cmd或者powershell,windows会自动为我们启动一个一个的控制台,将他们依附在一起。 windows中的哲学是“一切皆对象“ 在早期的windows中,其实也是参考着Unix的哲学思想,创建一些特殊的设备文件,比如要打印文件,就将文件输出到DLT设备文件中就行了,windows中的特殊文件与文件系统是分离的,他们是直接在内核中实现了,所有特殊文件的作用是是全局的,不管是在C:还是在E:中,都是可以直接使用PRN名称的,而不像是其他文件一样,需要提供相对路径或者绝对路径。 后来随着面向对象思想的发展,windows开始采用面向对象的思想进行设计,一提到面向对象,就应该联想到API,所有windows的”一切皆对象“就是”面向API“编程。但是windows有为了兼容老的版本,还是保留了之前”一切皆文件“的特殊设备文件。所以现在的windows就有了一个bug,命名的文件去掉扩展名之后不能使windows为了兼容而保留的特殊设备文件的文件名,比如aux.txt, aux.h, con.txt等等,其他他们在windows中已经用的不多了,在windows都是采用API实现这些了。 在命令行程序中键入的字符,虽然我们看到的是和在Unix shell中见到的一样,都是字符串,但是在回车之后就完全不听了,在Unix中会将内容转换为文件流,而Windows会将其转换为消息进行传输,也就是调用等效的API,为Console API,也就是说,如果我们现在想要实现一个Powershell,那么一定会疯狂的调用Console API。虽然在Unix中写bash也是调用api的进行系统调用的,但是两者在内部的实现是不同的,在windows中的会将其封装成对象,接着在调用对象的API,而在Unix中则是直接操作的,没有加上对象这一层。为了能够变出跨平台的程序(以桌面程序为例),比如qt等,他们提供了统一的接口,在内部实现上调用了两个操作系统的API。调用Windows的API一般需要获取句柄,接着通过这个句柄调用对应的函数,而在Unix中就是函数,windows的API数量是Unix的好几倍。
下载zsh,并安装oh-my-zsh dnf install zsh -y 到github的oh-my-zsh上按照教程安装oh-my-zsh,配置主题为minimal,这个是个人比较喜欢的主题,因为比较简洁 下载pyenv和virtualenv 到github的pyenv上安装pyenv 到github的pyenv-virtualenv上安装virtualenv 配置pip源 安装vim yum update vim-minimal yum install vim 配置rpmfusion源 链接https://rpmfusion.org/Configuration sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm 浏览器 下载chrome 数学建模 dnf install octave 配置源 到fedora中文社区配置源 docker sudo yum install docker-io -y 执行某些命令报错 setenforce() failed是因为权限不够 其他软件 jdk nodejs 输入法 默认是安装中文输入法的,需要去开启 如果想要安装sogoupinyin,需要配置中文社区源,再dnf install sogoupinyin,但是不建议安装,可以会有问题还浪费时间,fedora有智能拼音
逻辑回归案例 小细节 逻辑回归(logistic regression)虽然被称之为逻辑回归,但是它本质上其实是一种分类算法(classification algorithm),逻辑回归名字的由来是有历史原因的。 sigmoid函数在逻辑回归中站着重要的位置,sigmoid function也被称为logistic function,称之为逻辑函数就做到了见名知意了,说明\({{1}\over{1 + e^{-x}}}\)与逻辑回归密切相关。 在线性回归中得知,假设函数\(h(x) = \theta_0 + \theta_1x\),逻辑回归处理的是分类的问题,那么我们的算法要么输出的是概率(返回在0到1之间),要么是输出的就是0或者1等等,在线性回归中建立的假设函数\(h(x) = \theta_0 + \theta_1x\)显然不会在0到1之间或者输出的值为0或者1,这个时候就需要sigmoid函数了;sigmoid函数可以将实数范围内的值转换为0-1之间的值,而这个值恰巧就是概率所在的范围,进一步的,得到了概率的值,只要我们设定了阈值(threshold)就可以将其转换为0或者1等等。 逻辑回归是在线性回归的基础上发展而来的,它是依赖于线性回归的,为什么?因为在逻辑回归中,定义的假设函数时\(h_{\theta}(x) = g(\theta^{T}x)\),其中\(\theta^{T}x\)就是在线性回归中假设函数的矩阵形式,在逻辑回归中通过g函数将其封装到sigmoid函数中,\(g(x) = {{1} \over {1 + e^{-x}}}\),只有这样,才能将\(\theta^{T}x\)这个输出在R上的值映射到0-1之间,所有逻辑回归中的假设函数为\(h_{\theta}(x) = g(\theta^{T}x) = {{1} \over {1 + e^{-\theta^{T}x}}}\)。 逻辑回归假设函数的概率表现形式: \(h(x) = g(\theta^{T}x) = P(y = 1|x; \theta)\),在提醒一下,这里的\(\theta\)是一个列向量,在MATLAB/Octave中出现公式的地方,十有八九都是使用矩阵方程表达的,输入和输出也大部分是列向量或者矩阵。 关于阈值(threshold),在上面几点中已经提到的,这里提一下如何将一个0-1的概率值转换为一个0或者1等等的分类结果,首先根据通过sigmoid函数,我们的线性回归的结果会被锁定到0-1之间,这个时候如果假设函数\(h_{\theta}(x) = g(\theta^{T}x) = {{1} \over {1 + e^{-\theta^{T}x}}}\)的结果为0.7,表示 \(y = 1\)的概率为0.7,对立的,\(y = 0\)的概率为0.3,如果规定threshold为0.5,则表示如果假设函数\(h()\)的输出大于等于0.5则\(y = 1\),如果小于0.5则\(y = 0\),换一个角度来说,sigmoid中封装的线性回归函数在大于等于0的时候,\(y = 1\),在小于0的时候,\(y = 0\)。 sigmoid function(logistic function) 定义 \({{1} \over {1 + e^{-x}}}\) 图像 根据学生的两次考试成绩来判断是否能够被大学录取 案例概要 第一列为第一次考试成绩列向量 第二列为第二次考试成绩列向量 第三列为是否被录取(0 or 1) 案例分析 定义假设函数\(h(x) = g(\theta^{T}x) = {{1} \over {1 + e^{-\theta^{T}x}}}\)。 数据变量 m: the number of training examples,样本的数量。 n: the number of features,特征的数量,这里不包括第0个特征,所以为2。 x的上标:样本的行数。 x的下标:表示第几个特征。 输入X,注意:这里的X已经添加上了默认的第0个特征,这个列向量中的值都为1\[ \begin{bmatrix} 1 & x^{1}_{1} & x^{1}_{2} \\ 1 & x^{2}_{1} & x^{2}_{2} \\ \vdots & \vdots & \vdots \\ 1 & x^{m}_{1} & x^{m}_{2} \end{bmatrix} \] 目标函数(\(J(\theta)\)) 与线性回归中一样\[J(\theta) = {{1} \over {2m}}\sum_{i=1}^{m}(h_{\theta}(x^{(i)}) - y^{(i)})^{2} = {{1} \over {2m}}\sum_{i=1}^{m}({{1} \over {1 + e^{-\theta{x^{(i)}}}}} - y^{(i)})^{2}\]这里的\(\theta\)为列向量。 思考,如何计算\(minimize_{\theta}J(\theta)\) 在线性回归中我们使用梯度下降的方法可以很好的收敛,因为线性回归中的最小化方程是一个凸函数,没有局部最优点,只有一个全局最优点,但是在逻辑回归中,因为我们将线性回归函数封装到了sigmoid函数中,导致目标函数\(J(\theta)\)与\(\theta\)构成的函数图像是弯弯曲曲的,有多个局部最优点,导致了无法使用梯度下降的方法求出最优解。 此时应该对目标函数进行等价替换(注意:等价替换值得是效果一样,但是数值可能不同) 首先\[J(\theta) = {{1} \over {2m}}\sum_{i=1}^{m}(h_{\theta}(x^{(i)}) - y^{(i)})^{2} = {{1} \over {m}}\sum_{i=1}^{m}(h_{\theta}(x^{(i)}) - y^{(i)})^{2}\] 接着定义Cost函数\[Cost(h_{\theta}({x^{(i)}}), y^{(i)}) = (h_{\theta}(x^{(i)}) - y^{(i)})^{2}\],为什么?因为我们接下来要将\[(h_{\theta}(x^{(i)}) - y^{(i)})^{2}\]等价替换成一个分段函数,在机器学习的数学公式表示中,如果遇到一个分段函数,则在原来函数中使用一个新的逻辑函数(为什么说是抽象函数?因为在这里这个新的函数还没有函数的实体)将其替换掉,再对这个新的变量进行定义。 其次将Cost函数等价替换为\[ Cost(h_{\theta}({x^{(i)}}), y^{(i)}) = \begin{cases} -log(h_{\theta}({x^{(i)}})) & if & y = 1 \\ -log(1 - h_{\theta}({x^{(i)}})) & if & y = 0 \end{cases} \] 上面的等价替换就是将返回概率值的假设函数\(h_{\theta}(x^{(i)})\)转为取log之后的结果。 现在目标函数\(J(\theta)\)为\[ J(\theta) = {{1}\over{m}}\sum_{i=1}^{m}Cost(h_{\theta}({x^{(i)}}), y^{(i)}) \\ Cost(h_{\theta}({x^{(i)}}), y^{(i)}) = \begin{cases} -log(h_{\theta}({x^{(i)}})) & if & y = 1 \\ -log(1 - h_{\theta}({x^{(i)}})) & if & y = 0 \end{cases} \] 通过数学方法可以将上面的两个式子合并为一个\[ J(\theta) = -{{1}\over{m}}[\sum_{i=0}^{m}y^{(i)}log(h_{\theta}({x^{(i)}})) + (1 - y^{(i)})log(1 - h_{\theta}({x^{(i)}}))] \] 梯度下降 梯度(也就是偏导) \(grad = {{\partial}\over{\partial{\theta}}}J(\theta)\) 梯度下降 \(\theta_{j} := \theta_{j}- \alpha{{\partial}\over{\theta_{j}}}J(\theta)\),对所有的特征都进行梯度下降 展开来就是和线性回归一样的式子\[\theta_{j} := \theta_{j}- \alpha{\sum_{i=1}^{m}(h_{\theta}}(x^{(i)}) - y^{(i)})x_{j}^{(i)}\] 不断地更新参数即可 完成了拟合,投入到预测的时候是使用等价替换之前的假设函数,值得一提的是,前面的等价替换是对目标函数J的操作,和假设函数无关,在机器学习中,大部分的分析的时间除了在构建假设函数之外,就是合理的处理目标函数 如果y的值为0, 1, 2, 3,如果使用拟合出来的函数进行预测 思路和对一个函数求偏导是一样,当我们讨论y=0的情况时,就将其他的1,2,3情况都归为一类,一次类推,我们可以得出y=0,1,2,3的概率,只要\(max(h(x^{(i)}))\)即可,这种情况时需要定义三个h(x)的,在预测的时候进行比较,这种方法叫做one-vs-all。 根据学生的两次考试成绩来判断是否能够被大学录取-正则化(regularize),也可以是惩罚 regularize与normalize区别 normalize是将特征的值样本值锁定在0-1之间 regularize的目的是将我们最终得到的假设函数的高阶自变量的参数尽可以的小,这样就可以减弱高阶对预测的影响,防止过拟合,反之高阶越多,权重越大,则过拟合的可能性越大。要想实现正则化,我们不需要修改假设函数,而是修改目标函数J\[ J(\theta) = {{1}\over{2m}}[\sum_{i=1}^{m}(h_{\theta}(x^{(i)}) - y^{(i)})^2 + {\lambda}\sum_{i=0}^{n}{\theta}^2] \] 上面的式子是虽有的\(\theta\)都进行正则化(惩罚) 正则化(惩罚)参数与\(\theta\)的关系 大致上成反比的关系,如果\(\lambda\)很大,则\(\theta\)就会很小,可以可能会导致拟合出来的目标函数是一条水平直线,反之亦然。 永远不会惩罚\(\theta_0\) 梯度 变为 for j = 1 \[{{{1}\over{m}}\sum_{i=1}^{m}}(h_{\theta}(x^{(i)}) - y^{(i)})x_{j}^{(i)}\] for j >= 1\[({{{1}\over{m}}\sum_{i=1}^{m}}(h_{\theta}(x^{(i)}) - y^{(i)})x_{j}^{(i)}) + {{\lambda}\over{m}}\theta_{j}\] 梯度下降公式为 \[\theta := \theta - \alpha{{{\partial}\over{\partial}\theta}}J(\theta)\] 其中的\[{{{\partial}\over{\partial}\theta}}J(\theta)\]用上面的两个式子替换即可。 其他 在机器学习中,对于分类问题,我们已经拟合出了假设函数,在画决策边界的时候,不是根据假设函数绘画的,而是根据sigmoid函数中封装的线性回归函数绘制的,只要把它画出来即可,如果边界是直线,则确定两个点即可,如果是闭合曲线,则使用等高线。 要想画出复杂的曲线,那个这个函数一定是高阶的。
环境 macOS Python 版本要求 3.5+ 2.7+ Python 环境 pyenv 安装 配置阿里云镜像(就算配置镜像下载都很慢,不配置还得了,好像阿里云没有对TensorFlow做镜像) pip3 install "tensorflow>=1.7.1",否则可能找不到TensorFlow包 pip3 会自动解决包之间的依赖关系 注意 上面提到了下载很慢,这里提供了清华的tensorflow镜像,下载很快tensorflow
matlab 线性回归实战 统一 输入时列向量 输出也是列向量 中间的过程可以出现行向量或者列向量,但是不能影响输入和输出为列向量 参数运算的输入都不会只是一个实数,要么是列向量,要么是一个矩阵 对于矩阵,取数据也是一列一列的去,也就是\(X(:1)\),\(X(:2)\)等 命令的时候为向量和矩阵加后缀, 如X_norm, x_new
关于反对幂三指 指的是哪个留下来 在隐函数中求导数\({{dy}\over{dy}}\) 不是众生平等,而是将y看成是x的方程 对隐函数求微分 众生平等,加法两侧都看成一个单元,对自己的函数,求微分,遇到复合也一样 微分公式为\({{\partial{y}}\over{\partial{x}}}dy\) 微分的近似\[ dy \approx f^{'}(x_0)\Delta{x} \] \[ dy = f(x + x_0) - f(x_0) \] \[ f(x + x_0) \approx f(x_0) + f^{'}(x_0)\Delta{x} \] \[ f(x) \approx f(x_0) + f^{'}(x_0)(x - x_0) \] 以此类推 \[ f(x) \approx f(x_0) + f^{'}(x_0)(x - x_0) + {f^{''}(x_0)(x - x_0)^{2}\over{2!}} + \cdots + {f^{(n)}(x_0)(x - x_0)^{n}\over{n!}} \] 上式已经非常接近泰勒公式了,添加上一个拉格朗日余项即可\[ f(x) \approx f(x_0) + f^{'}(x_0)(x - x_0) + {f^{''}(x_0)(x - x_0)^{2}\over{2!}} + \cdots + {f^{(n)}(x_0)(x - x_0)^{n}\over{n!}} + R_n(x) \] 当\(x_0 = 0\)的时候就是麦克劳林公式 无法估计可能用到的等式 \(tanx \approx x\) \(sinx \approx x\) \({(1 + x)}^{\alpha} \approx 1 + \alpha{x}\) \(e^x \approx 1 + x\) \(ln(1 + x) \approx x\) 洛必达公式 洛必达是关于求极限的方法 \(0\over0\)或者\(\infty\over\infty\)
数学期望 X 为随机变量,它不会出现在函数的具体表示中,而是在抽象的表示中,也就是说会出现在\(E(X)\),这个X不会出现在$E(X) = $的右侧,在右侧中X要对应的使用x来替代。在P,E中放的一定是随机变量,是大写的字母,这才符合概率论。 密度函数对R的积分为1。 离散的情况不会使用到积分,但是在连续的情况一定会使用到积分,所有如果我们有了一个密度函数,则暗示着是连续的,如果没有则是离散的;这也为我们记忆一些公式提供了方便,我们首先考虑记忆密度函数,如果这个分布是离散的,如泊松分布,则记忆分布函数。 连续性数学期望\(E(X) = \int_{-\infty}^{\infty}{xf(x)}\),数学期望为随机变量乘以密度函数,在右侧X转为x。 泊松分布是离散的,因为泊松分布表示的是事件发生的次数,而次数是离散的,所有在推\(E(X)\)的时候我们使用离散的数学期望公式,分布函数为\(P(X) = {\lambda^{k}\over{k!}}\lambda^{k}\) 指数分布是两件事情发生的平均间隔时间,时间是连续变量,所以指数分布是一种连续随机变量的分布,正态分布也是连续的。 均匀分布,不要参考书上的,均匀分布的概率密度函数就是\[ f(x) = \begin{cases} {1 \over S_D} & a < x < b \\ 0 & others \end{cases} \]
mathjax公式 \(\delta\): \delta \(\Delta\): \Delta \(\int\): \int \(\iint\): \iint \(\approx\): \approx \(\theta\): \theta \(\alpha\): \alpha \(\cdots\): \cdots \(\prod\): \prod \(\times\): \times \(a\over{b}\): a\over{b} \(\partial\): \partial \(\vec{a}\): \vec{a} \(1 \choose n\): 1 \choose n \(\omega\): \omega \(\Omega\): \Omega 矩阵\[ \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ \vdots & \vdots & \vdots \\ \end{bmatrix} \] \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ \vdots & \vdots & \vdots \\ \end{bmatrix} 分段函数 \[ f(x) = \begin{cases} x + 1 & x \gt 1 \\ 2x & others \end{cases} \] f(x) = \begin{cases} x + 1 & x \gt 1 \\ 2x & others \end{cases}
在macOS上安装gitbook 安装nodejs, 在macOS上, 只要安装了Xcode, nodejs就会被默认安装 node -V, 显示当前系统的node版本号 npm install -g npm, 更新当前系统的npm版本 npm install -g gitbook-cli, 安装gitbook命令行工具 gitbook -V, 查看当前的gitbook-cli和gitbook版本, 这个时候会自动安装gitbook, gitbook-cli和gitbook是两个软件, 使用gitbook-cli中的gitbook命令来管理我们的gitbook brew cask install gitbook-editor, 安装gitbook编辑器 为了生成电子书, 安装ebook-convert, 下载 Calibre application, 接着sudo ln -sv /Applications/calibre.app/Contents/MacOS/ebook-convert /usr/local/bin, 如果要生成pdf的话, 则使用gitbook pdf . ./mybook.pdf gitbook命令 gitbook ls-remote: 列出远程gitbook可用的版本 gitbook ls: 列出本地gitbook可用的版本 gitbook init: 初始化目录为gitbook的根目录 gitbook build: 编译成静态的html gitbook serve: 启动服务到localhost:4000 gitbook uninstall 2.2.2: 卸载对应的版本的gitbook gitbook update: 更新gitbook 创建电子书步骤 mkdir book cd book gitbook init gitbook build gitbook serve 配置 通过book.json文件修改 gitbook editor 使用注意点 gitbook editor 对中文的支持不太好, 所以最好在终端中创建 gitbook editor 存放的图片都放在asset中, 所以不需要担心URL
监督学习案例 规范 假设函数: 使用h(hypothesis, 假设)表示 输入(input value) 向量或者实数: 使用小写字母x等 矩阵: 使用大写字母X等 输出(output value) 向量或者实数: 使用小写字母y等 矩阵: 使用大写字母Y等 参数(Parameters): \(\theta\) 样本的数量(列数): m 样本中特征(feature)的个数: n \(x_0^{(1)}\): 0表示第0个特征(为我们给出的样本中的特征是从1开始的,这里的0是我们自己添加上去的,所有的\(x_0^{(m)}\)都为1,其中1表示第几行数据, 它的一般式就是\(x_j^{(i)}\), 以后j表示第几个特征,i表示第几个样本 房价的Size和Price的预测 X 为 房子的 Size 建立一个线性模型: \(h_\theta(x) = \theta_0 + \theta_1x\) 要让我们fit的model与y差异最小, 称之为最小化(minimize) 在这个案例中使用\[J(\theta_0, \theta_1) = {1\over2m}\sum_{i = 0}^m(h\theta(x^{(i)}) - y^{(i)})^2\] 上面的就是我们的代价函数(cost function), 因为我们有让得到的和除以了2m, 所以我们的到函数也称之为平均误差函数(squared error function), 注意: cost function的自变量时theta_0和theta_1, 不在是我们熟悉的x了 \[{minimize_{\theta_0\theta_1}} J(\theta_0, \theta_1)\]表示求出一个\(\theta_0\)和\(\theta_1\)使得\(J(\theta_0, \theta_1)\)的值最小, 我们称之为最小化的过程, 上面的这个表达式就是我们的优化目标(optimization objective), 也就是我们的目标函数 对于线性回归模型来说, 它的\(J(\theta_0, \theta_1)\)目标函数是一个凸函数(没有局部最优点, 只有一个全局的最优点), 在二维上是抛物线, 在三维上是一个碗状, 对于三维的(J有两个theta参数), 一般使用等高线图来替代三维凸函数 使用gradient regression梯度降维求出最优解, 梯度降维的公式为\(\theta_0 := \theta_0 - \alpha \times {\partial\over\partial\theta_0}J(\theta_0, \theta_1)\), 对于另一个\(\theta_1\)也是一样的, \(\theta_1 := \theta_1 - \alpha \times {\partial\over\partial\theta_1}J(\theta_0, \theta_1)\), 上面的是公式, 在实际更新我们的参数\(\theta_0, \theta_1\)的时候, 应该保证\(\theta_0, \theta_1\)同步更新, 所以应该这样子\[tmp0 := \theta_0 - \alpha \times {\partial\over\partial\theta_0}J(\theta_0, \theta_1)\] \[tmp1 := \theta_1 - \alpha \times {\partial\over\partial\theta_1}J(\theta_0, \theta_1)\] \[\theta_0 := tmp0\] \[\theta_1 := tmp1\], 在最后同步更新\(\theta_0\)和\(\theta_1\)的值 关于梯度下降公式的细节 公式中, \(\alpha\)表示学习率, \({\partial\over\partial\theta_0}J(\theta_0, \theta_1)\)表示梯度下降的方向, 所以\(\alpha \times {\partial\over\partial\theta_0}J(\theta_0, \theta_1)\)表示\(\theta_0\)要更新多少的值, 形象一点就是说, 一个人在一个山顶上, 他步子的大小为\(\alpha\), 他希望尽快下山的方向为\({\partial\over\partial\theta_0}J(\theta_0, \theta_1)\), 这样我们就可以更新\(\theta_0\)的值了 虽然我们在公式中规定了\(\alpha\)学习率, 但是并不代表我们走的每一步就是不变的, 因为导数是在变化的, 为最低点的时候为0, 在其他地方有时别的值 要应用梯度下降法, 我们需要为\(\theta_0\)和\(\theta_1\)进行初始化, 一般来说都初始化为0, 但是也要视情况而定 什么时候停止梯度下降? 我们可以规定一个阈值, 当我们的\(\alpha \times {\partial\over\partial\theta_0}J(\theta_0, \theta_1)\)小于这个阈值的时候停止, 这个是通过计算机自动停止迭代的, 但是不推荐使用 另外一种方式就是通过画出\(minimize_{\theta_0\theta_1}J(\theta_0, \theta_1)\)与迭代次数的图像来判断应该迭代多少次, 如果画出来的是一个抛物线则表示我们设置的\(\alpha\)学习率太大了, 应该减小\(\alpha\)的值 其他 对于这个房价的模型, 我们除了使用梯度下降的方法求出我们的目标函数之外还可以使用matrix的方法来求, 这个更加的简单 我们每求一次\(J(\theta_0, \theta_1)\)的值就要遍历一遍所有的数据, 因为the definition of the \(J(\theta_0, \theta_1)\) is \[\sum_{i=1}^{m}{1\over2m}{(h(x^{(i)}) - y^{(i)})^2})\], 这种方式我们称之为Batch梯度下降 房价的Size和Price的预测-其他解决方案 在上一节中我们已经得到了一个线性模型\(h_\theta(x) = \theta_0 + \theta_1x\),根据这个假设函数我们定义出了一个目标函数(也称之为损失函数)\[J(\theta_0, \theta_1) = {1 \over 2m}\sum_{i = 0}^{m}(h(x^{(i)}) - y^{(i)})^2\]接着通过梯度下降的方法计算出\(minimize_{\theta_0\theta_1}J(\theta_0, \theta_1)\), 我们知道这种方法需要对进行迭代,比较麻烦,那有没有更加简单的方法呢?能不能一次迭代就可以求出我们的\(\theta_0\)和\(\theta_1\)呢?答案就是正规方程 正规方程: \(\theta = (A^{T}A)^{-1}A^{T}y\) 其中\(\theta\)为一个列向量, 表示我们所有的参数\[ \begin{bmatrix} \theta_0 \\ \theta_1 \end{bmatrix} \] A 表示输入的 x 组成的矩阵, 这里就是\[ \begin{bmatrix} 1 & x_1^{(1)} \\ 1 & x_1^{(2)} \\ \vdots & \vdots \\ 1 & x_1^{(m)} \end{bmatrix} \] 上面的矩阵的原型就是\[ \begin{bmatrix} x_0^{(1)} & x_1^{(1)} \\ x_0^{(2)} & x_1^{(2)} \\ \vdots & \vdots \\ x_0^{(m)} & x_1^{(m)} \end{bmatrix} \] 从上面我们可以知道\(x_0^{(n)}\)是值为1,这里的 \[ \begin{bmatrix} x_0^{(1)} \\ x_0^{(2)} \\ x_0^{(3)} \\ \vdots \\ x_0^{(m)} \end{bmatrix} \] 就是我们自己添加上去的新的特征值,这个特征比较特殊,因为它所有的值都为0,为什么要这样做?通过观察假设函数\(h(x^{(i)}) = \theta_0 \times 1 + \theta_1x^{(i)}\)我们发现,之后\(\theta_0\)没有自变量,为了数学上的统一,于是我们对其进行修改\(h(x^{(i)}) = \theta_0x_0^{(i)} + \theta_1x_1^{(i)}\),其中\(x_0^{(i)}\)为1。 其中y为一个列向量,它的值为\[ \begin{bmatrix} y^{(1)} \\ y^{(2)} \\ y^{(3)} \\ \vdots \\ y^{(m)} \end{bmatrix} \] 是\(x_j^{(i)}\)对象的标签值 如果构建 A ? 首先,在书写假设函数的时候应该安装阶数的升序书序,如\(h(x^{(i)}) = \theta_0 + \theta_1x_1^{(i)} + \theta_2{(x_1^{(i)})}^2\)或者\(h(x^{(i)}) = \theta_0 + \theta_1x_1^{(i)} + \theta_2x_2^{(i)}\)分别从方便的式子中构建 A 第一个是\[ \begin{bmatrix} 1 & x_1^{(1)} & {(x_1^{(1)})}^2 \\ 1 & x_1^{(2)} & {(x_1^{(2)})}^2 \\ \vdots & \vdots & \vdots \\ 1 & x_1^{(m)} & {(x_1^{(m)})}^2 \\ \end{bmatrix} \] 第二个是\[ \begin{bmatrix} 1 & x_1^{(1)} & x_2^{(1)} \\ 1 & x_1^{(2)} & x_2^{(2)} \\ \vdots & \vdots & \vdots \\ 1 & x_1^{(m)} & x_2^{(m)} \\ \end{bmatrix} \] 如果已经构建出了矩阵 A,接下来的y的构建是非常简单的,就是安装y给出的顺序构建一个列向量即可 带入公式就可以直接求出 \(\theta\) 正规方程的优点 不需要像梯度下降法一样麻烦,需要换出\(minimize_{\theta_0\theta_1}J(\theta_0, \theta_1)\)和迭代次数的图像来确定迭代的次数,除此之外还要确定出\(\alpha\)学习率的大小 正规方程的缺点 在公式中发现这个A矩阵包含了所有的输入,如果输入量非常的大,则A矩阵就会非常大,在计算\({(A^{T}A)}^{-1}\)矩阵的维度就会增大,并且求矩阵的逆在大数据的情况下是非常消耗计算机的性能的 使用的范围小,只适用于线性回归,而梯度下降使用的范围要广很多 房价的Size和Price的预测-多变量 在上两节中我们只考虑到了房子的大小和房子的关系,当然这个是不包括我们计自己添加上去的第0个特征,它们的值都为1。下面我们再添加一些特征,下面就添加 Age。 此时我们的假设函数为\(h(x^{(i)}) = \theta_0 + \theta_1x_1^{(i)} + \theta_2x_2^{(i)}\),其中\(x_1^{(i)}\)为size输入,\(x_2^{(i)}\)为age的输入,构建目标函数(cost function)\[J(\theta_0, \theta_1, \theta_2) = {1 \over 2m}\sum_{i = 0}^{m}(h(x^{(i)}) - y^{(i)})\],为了方便起见,通常我们会将向量输入到函数中而不是一个实数,因为在编程中我们就是输入向量或者矩阵的\[J(\theta) = {1 \over 2m}\sum_{i = 0}^{m}(h(x^{(i)}) - y^{(i)})\]其中,\(\theta\)是一个向量,它的值为\[ \begin{bmatrix} \theta_0 \\ \theta_1 \\ \theta_2 \end{bmatrix} \] 接下来就是我们熟悉的步骤了通过画出\(minimize_{\theta_0, \theta_1, \theta_2}J(\theta_0, \theta_1, \theta_2)\)和迭代次数的图求出应该迭代几次,关于\(minimize_{\theta_0, \theta_1, \theta_2}J(\theta_0, \theta_1, \theta_2)\)$的值已经在前几节讲过了,使用梯度下降的方法 \[ tmp0 := \theta_0 - \alpha \times {\partial\over\partial\theta_0}J(\theta) \]\[ tmp1 := \theta_1 - \alpha \times{\partial\over\partial\theta_1}J(\theta) \]\[ tmp2 := \theta_2 - \alpha \times{\partial\over\partial\theta_2}J(\theta) \]\[ \theta_0 := tmp0 \theta_1 := tmp1 \theta_2 := tmp2 \] 在最后同步更新\(\theta_0\), \(\theta_1\)和\(\theta_2\)的值,道理都是一样的 另外的思考-房子的宽(width)和长(length)对房价进行预测 根据标题知道,题目给出了两个特征width和length,我们当然可以和上一节那样构建假设函数,上一节的假设函数绝对是首选,但是如果拟合的不好呢?这就意味着我们需要重新构建假设函数,现在我们构建一个新的假设函数\(h(x^{(i)}) = \theta_0 + \theta_1{x_1^{(i)}} + \theta_2{(x^{(i)})}^{2}\),我们发现这与我们之前的假设函数不一样的地方在于有2阶,这个地方就有技巧了,将\({x_1^{(i)}}\)看成一个新的特征\(xnew_1^{(i)}\) 和 \({(x_2^{(i)})}^{2}\)看成另外一个新的特征\(xnew_2^{(i)}\),这样子我们的假设函数就又成为了一阶了的,操作步骤就和之前的一样了 对数据的处理 在获取到需要训练的数据的时候,我们需要对数据进行去均值化 什么是去均值化 去均值化就是将我们的一列数据的范围固定到\(-1 \to 1\)$这个范围之间,但是这个范围不是硬性要求,可以在这个区间内波动 公式:\[ v = {{x - avg} \over {max - min}} \] v 为去均值之后的数据样本列向量 x 为待去均值的数据样本列向量 avg 为 x 的均值 max 为 x 的最大值 min 为 x 的最小值 还是要提的是x是一个列向量,就是我们数据表中的一列,就是\(x_j^{(i)}\)中的第j个特征的所有的值 举例: 假设我们的房价中size的范围是在(0, 3000)区间内,可想而知有10,有200, 有3000,这样数据之间的差异太大了,对我们的拟合时候影响的,如果我们使用梯度下降发进行最小化,因为数据差异大,会导致最小化的速度变慢 使用公式\[ v = {{x - avg} \over {max - min}} \] 得到的v,它所有元素的大小都在\(-1 \to 1\)区间内 为什么要去均值 很简单,为了在使用梯度下降的方法进行最小化的时候加快速度 执行去均值之后,我们应该保存mean和std,因为我们使用的是去均值之后的数据拟合的模型,在使用这个模型的时候,我们也要对输入进行去均值,这样拟合出来的数据才是我们需要的。 所有在之前的案例中,我们应该加上去均值这一步
Machine learning Preface Definition T: Task E: Experience P: Performance Sequence: T -> E -> P Supervised learning Definition Give the right answer to each example of the data set(called training data). Type Regression: get the continuous values Classification: get the discrete values like 0, 1, 2, 3 and so on application scenarios Regression: predict the price of the house based on the square, location of the house house price Classification: Tumor prediction Spam filter Unsupervised learning Type Cluster algorithm application scenarios Google news: get lots of related news in the Internet and put them in one set of URL. Social network: find the common friends. Market segmentation: We all know the data, but we don't know the what kinds of market segmentation, so let unsupervised learning to deal with it. Extract human voice from records: you know, there are some noise in these records, we need to get the human voice, so we let cluster algorithm to deal with. Others Recommender system
<?php $username = $_POST["username"]; $passcode = $_POST["passcode"]; # 一定要mysqli_connect $con = mysqli_connect("jh.bux.org", "jh", "jhisjh", "mytest"); # 一定要加引号 $sql = "insert into userinfo (username, password) values ('$username', '$passcode');"; mysqli_query($con, $sql); mysql_close($con); ?>
打开Active Monitor, 找到coreaudiod进程, 将其quit掉即可
定义 公式为:\[ l_k(x):= \prod_{i=0, i \neq k}^{j}{{x - x_i}\over{x_k - x_i}} \] 从上面的公式中我们可以了解到, i从0递增到j, 但是在k不会等于i, 因为如果k=i了, 则分母就成为了0, 这个式子就没有意义了, 在给定了k之后, 则i会从0开始递增到j的, 这个\(l_k(x)\)就是一个拉格朗日插值函数 每一个y值有一个特定的拉格朗日插值函数, 我们最终得到的一个多项式就是让每一个y乘以一个它特定的拉格朗日插值函数 我们得到的最终的多项式, 如果为x取一个值, 则在我们获取到的拉格朗日插值多项式中是这样表现出来的 --> \(L_(xj)\sum_{i=0}^k y_il_i(x_j)= 0 + 0 + 0 + ... + y_j + 0 + 0\), 其他的地方都是0, 之后我们需要的那个值的插值函数\(l_j({x_i})\)为1, 这就是它的实质 优点 当我们添加了一个新的样本点的时候, 我们不需要进行重新的计算 插值法与拟合的差别 拟合出来的曲线不一定要经过所有的我们给出的实际的样本点 插值法求出来的多项式的曲线一定是经过了我们给他的样本点 在matlab中实现一个拉格朗日插值求多项式 function p = lagrange(x, y) % p = lagrange(x, y) % 其中x和y是向量,p是返回的多项式向量 % m获取x的个数 m = length(x); for k = 1:1:m % 表示一个插值函数的起始值 V = 1; for i = 1:1:m if k ~= i % 注意: 以后凡是在遇到一个在数学上是 x + 1 等包含着变量的表达式, 在matlab中同时使用多项式来表示, 也就是表示成 [1, 1], 当然这个是在只有一个变量的情况下] % x(k) - x(i), 为分母, 分子 conv(V, [1, x(i)])是一个多项式, 最后将得到的多项式赋给V, 这个就是我们在上面写到的拉格朗日的插值函数的使用累乘符号表达式在matlab中的表达而已 V = conv(V, [1, x(i)])/ (x(k) - x(i)); end end % 当k为k的时候得到的一组拉格朗日插值函数保存起来 l(k, :) = V; end % 得出最终的多项式 p = y * l;
一个知乎账号, 分析了很多的数学问题: https://www.zhihu.com/people/matongxue/activities关于三阶样条的解析: https://blog.csdn.net/flyingleo1981/article/details/53008931
如果x轴上的点不是按照升序排列的, 那么应该让其按照升序排序, 否则画出来的图是错误的,还会非常的混乱
在WndProc函数中 最好不要出现WM_SYSCOMMAND消息, 如果有了这个消息, 可能我们对创建出来的窗口就什么都管不了了, 因为我们阻碍了DefWndProc函数去处理它 不在.rc文件中添加弹出式菜单, 而是在程序中动态的生成弹出式菜单, 那么在AppendMenu或者InsertMenu的时候应该传入MF_POPUP, 否则即使我们成功的添加上去了一个菜单项但是点击它是什么都不会弹出来的
JavaScript快速入门 唯一判断是否为NaN的方式就是isNaN(obj) 整数和浮点类型都是数字类型 比较表达式不建议使用==, 而是使用=== null和undefined, 其实两者没有什么大的区别, 大多数我们使用null, 表示空值, 不同于java中的null, ''和0都不是null, 而undefined仅仅用来判断函数参数是否传递了, 我们注意到undefined == null返回的是true, 而undefined === null返回的是false, 如果数组下标越界则返回undefined(未定义的) 通过json创建一个对象var person = {name: 'mega', age: 20};返回的就是一个json, 属性名是字符串 定义一个变量的时候使用var进行声明, 如果不适用var的话则默认声明为一个全局变量, 这个是在没有'use strict'的情况下, 如果使用了'use strict'则必须使用var ``包含的字符串和python中的''' '''是一样的 string对象 length toUpperCase() toLowerCase() indexOf() substring() 运行js的环境除了浏览器, 就是node.js了
python 在windows下监听键盘按键 使用到的库 ctypes(通过ctypes来调用Win32API, 主要就是调用钩子函数) 使用的Win32API SetWindowsHookEx(), 将用户定义的钩子函数添加到钩子链中, 也就是我们的注册钩子函数 UnhookWindowsHookEx(), 卸载钩子函数 CallNextHookEx()在我们的钩子函数中必须调用, 这样才能让程序的传递消息 在没有钩子函数的情况下windows程序运行机制 键盘输入 --> 系统消息队列 --> 对应应用程序的消息队列 --> 将消息发送到对应的窗口中 在有了钩子函数的情况下windows程序运行机制 键盘输入 --> 系统消息队列 --> 对应应用程序消息队列 --> 将消息发送到钩子链中 --> 消息一一调用完毕所有的钩子函数(需要调用CallNextHookEx函数才能将消息传递下去) --> 将消息发送到对应的窗口中 示例程序 注意: 在程序中, 我们通过CFUNCTYPE返回一个类对象, 通过该类对象可以实例化出我们需要的c类型的函数, 但是如果不将他放在全局的话则会失去效果, 因为在C语言中函数是全局的 # -*- coding: utf-8 -*- import os import sys from ctypes import * from ctypes.wintypes import * """ define constants """ WH_KEYBOARD = 13 WM_KEYDOWN = 0x0100 CTRL_CODE = 162 class JHKeyLogger(object): def __init__(self, user32, kernel32): """ Description: Init the keylogger object, the property 'hook_' is the handle to control our hook function Args: @(dll)user32: just put windll.user32 here @(dll)kernel32: just put windll.kernel32 here Returns: None """ self.user32_ = user32 self.kernel32_ = kernel32 self.hook_ = None def install_hookproc(self, hookproc): """ Description: install hookproc function into message chain Args: @(c type function)hookproc: hookproc is the hook function to call Returns: @(bool): if SetWindowHookExA() function works successfully, return True else return False """ self.hook_ = self.user32_.SetWindowsHookExA( WH_KEYBOARD, hookproc, self.kernel32_.GetModuleHandleW(None), 0) if not self.hook_: return False return True def uninstall_hookproc(self): """ Description: uninstall the hookproc function which means pick the hookproc pointer off the message chain Args: None Returns: None """ if not self.hook_: return self.user32_.UnhookWindowsHookEx(self.hook_) self.hook_ = None def start(self): """ Description: start logging, just get the message, the current thread will blocked by the GetMessageA() function Args: None Returns: None """ msg = MSG() self.user32_.GetMessageA(msg, 0, 0, 0) def stop(self): self.uninstall_hookproc() def hookproc(nCode, wParam, lParam): """ Description: An user-defined hook function Attention: here we use the global variable named 'g_keylogger' """ if wParam != WM_KEYDOWN: return g_keylogger.user32_.CallNextHookEx(g_keylogger.hook_, nCode, wParam, lParam) pressed_key = chr(lParam[0]) print pressed_key, # hit ctrl key to stop logging if CTRL_CODE == lParam[0]: g_keylogger.stop() sys.exit(-1) return g_keylogger.user32_.CallNextHookEx(g_keylogger.hook_, nCode, wParam, lParam) # Attention: pointer must be defined as a global variable cfunctype = CFUNCTYPE(c_int, c_int, c_int, POINTER(c_void_p)) pointer = cfunctype(hookproc) g_keylogger = JHKeyLogger(windll.user32, windll.kernel32) def main(): if g_keylogger.install_hookproc(pointer): print 'install keylogger successfully!' g_keylogger.start() print 'hit ctrl to stop' if __name__ == '__main__': main()
探索eleme用到的库 xml re库 通过regex = re.compile(pattern)返回一个pattern对象, 通过该对象匹配正则表达式的字符串, 最好在模式中使用r'some'原始字符串 regex有很多的方法, 最常用的就是findall(), 因为这个方法返回的是一个字符串列表, 而其他的返回的是Match对象 match(), 返回一个Match对象, 要获取其中维护的值, 需要调用group()方法 search(), 返回一个Match对象, 要获取其中维护的值, 需要调用group()方法 requests库(对urllib库的封装) response = requests.get(url, data=data, headers=headers), 发送get请求 response = requests.post(url, data=data, headers=headers), 发送post请求 response对象的属性 status_code url headers text: 文本 content: 二进制 encode: 默认为iso, 如果text中有中文会乱码, 如果修改了encode='utf-8', 则response对象在response.text会自己处理编码 phantomjs工具(一开始有尝试了一下, 运行起来是真的慢, 其实在项目中并没有使用这个库, 但还是mark一下) 一个为界面的浏览器, 是一个浏览器内核, 可以渲染js 一般需要通过selenium库将phantomjs和python对接起来 selenium web.py(一个轻量级的web框架) 注意(坑): web.ctx.env如果直接在终端输入会提示没有env属性的, 这个属性只有 ```python app = web.application(urls, globals()) app.run() ``` 才会被添加, 其中ctx是context的缩写 web.input()获取get请求的数据 web.data()返回post的data部分
登录 lftp username:password@ip:port 设置字符集 set ftp:charset 'gbk' set ftp:charset 'utf-8' 下载文件 mget *.txt get test.txt mirror dir 上传文件 put mput mirror -R
JMeter(主要用于发包, Fiddler虽然也可以但是发包的功能没有它强) 需要安装Java8+版本 在bin目录下双击jmeter.bat即可运行 在运行之前建议处理编码问题, 在jmeter.properties文件中, 找到ISO, 修改编码为utf-8 JMeter 使用 jmeter的操作都是基于线程组的, 所以我们需要创建线程组
Cookie的作用 主要作用就是为了用户认证 保留用户的一些其他信息 注意: SESSIONID就可能是Cookie中的一个字段的值 Cookie的种类 会话Cookie: 只是临时的cookie, 当用户关闭了浏览器的时候该cookie就会从浏览器的内存中移除掉 持久Cookie: 存储在磁盘上, 但是持久的Cookie也是有过期时间的 Cookie的属性 Expires: 表示过期时间 Path: 那个URI可以访问这个Cookie HttpOnly: 一般与用户登录有关的Cookie都需要设置HttpOnly, 为了安全方面的考虑 用户登录过程中cookie的变化 在登录页面时, 用户输入用户名和密码, 发送一个登录请求给服务器端 服务器端发送过来的response中包含了Set-Cookie字段, 里面包含着用户登录的SESSIONID, 用于维持用户登录的状态 在登录进入之后, 用户在需要登录才能访问的页面中发送过来的请求中会包含一个Cookie, 该Cookie中有一个关于SESSIONID的字段, 服务器就是通过这个来判断是否和本地的SESSIONID一致是否该用户已经登录 扩展 所谓的自动登录就是将维持登录信息的Cookie保存到了磁盘中
打开fiddler script editor 在fiddler中Rules -> Customize Rules打开 在editor中点击open, 打开CustomRules.js文件, 对其进行编辑即可 修改CustomRules.js文件 函数调用的几个阶段 OnBeforeRequest OnBeforeResponse 可以用到的内部对象 oSession 属性: hostname oRequest 方法: HostNameIs uriContains utilReplaceInRequest oRequest 获取方式: oSession.oRequest 属性: headers 表示的就是在http请求消息中的首部 Remove方法删除指定的字段 Add添加字段 通过[]访问 实战案例 将对A站点的请求全部都转移到B站点 修改hostname即可 修改response中的body元素的值 oSession.utilReplaceInResponse("megachen", "megachen123");
如何判断缓存新鲜度 If-Modified-Since告诉服务器, 在服务器中的响应报文中有一个Last-Modified字段, 如果两者一直则表示在浏览器中缓存的文件是最新的, 可以直接使用浏览器缓存中的副本 If-None-Match字段告诉服务器, 它的值为ETag的id号, 如果服务器端的Etag的id号与这个字段一直则使用缓存中的数据, 返回304 在request中有关cache的字段 Cache-Control: no-cache 不使用缓存 If-Modified-Since If-None-Match Cache-Control: max-age, 缓存文件在浏览器端缓存的时间 response中有关cache的字段 Cache-Control: public, 公有缓存 Cache-Control: private, 私有缓存 Cache-Control: no-store, 禁止使用缓存 Date: 响应发送的时间 Expires: 缓存失效的时间 Last-Modified: 服务器端文件的最后修改时间 ETag: "67adsfaf908", 和request中的If-None-Match进行比较, 如果一样则让浏览器直接使用放在缓存中的数据 注意 对于缓存有效的判断, 先根据Cache-Control中的max-age判断, 如果没有这个key-value则通过Expires进行判断 304响应过程 浏览器发送request给服务器 -> 浏览器先检查在本地缓存中是否已经有了这个资源 -> 如果有了则获取其中的If-Modified-Since, 构成响应报文, 发送给服务器端 -> 服务器端通过If-Modified-Since和响应的Last-Modified的值进行比较, 如果一致则表示在浏览器中缓存的数据就是最新的数据, 于是服务器发送304状态码的响应 -> 浏览器从本地缓存中获取资源
Accept: 客户端支持的文件类型, 如果为/表示任何类型 Accept-Encoding: 客户端浏览器支持的文件压缩格式 Accept-Language: 客户端支持的语言 User-Agent: 客户端告诉服务器的浏览器的信息 Referer: 让服务器判断当前正在访问的URL是从哪个页面URL跳转过来的, 如果不是在本域中, 则认为是非法访问资源, 用来防止盗链 Connection: 在HTTP/1.1中默认是Keep-Alive Host: 方位的主机名
http method HEAD: 只返回相应的header POST: 一般用于提交表单 PUT: 向Web服务器上传文件 GET: 查 DELET: 删除 status code 1xx与2xx: 返回提示信息 3xx: 重定向 4xx: 客户端错误 5xx: 服务器端错误 具体 200: OK 204: No Content, 请求的资源没有body 206: Partial Content, 部分内容, 使用206状态码实现断点续传, 在206的response中有一个Range字段, 他的值是一个数据的范围 301: Move permanently, 永久重定向, 在301中有Location字段, 它的值为新的URL让浏览器自动去访问, 原来的资源是永远无效的 302: Found, 临时重定向, 在当前资源可以访问, 但是需要先访问在302中字段名为Location的URL才行, 典型的就是我们提前访问了需要用户登录才能访问的URL 304: Not Modified, 告诉客户端当前访问的资源没有更新, 可以使用缓存在内存中的页面资源 403: Forbidden, 被Web服务器拒绝了 404: URL找不到 500: Internal Server Error, 服务器错误 503: Server Unavailable, 服务器当前服务访问
python 在windows下监听键盘按键 使用到的库 ctypes(通过ctypes来调用Win32API, 主要就是调用钩子函数) 使用的Win32API SetWindowsHookEx(), 将用户定义的钩子函数添加到钩子链中, 也就是我们的注册钩子函数 UnhookWindowsHookEx(), 卸载钩子函数 CallNextHookEx()在我们的钩子函数中必须调用, 这样才能让程序的传递消息 在没有钩子函数的情况下windows程序运行机制 键盘输入 --> 系统消息队列 --> 对应应用程序的消息队列 --> 将消息发送到对应的窗口中 在有了钩子函数的情况下windows程序运行机制 键盘输入 --> 系统消息队列 --> 对应应用程序消息队列 --> 将消息发送到钩子链中 --> 消息一一调用完毕所有的钩子函数(需要调用CallNextHookEx函数才能将消息传递下去) --> 将消息发送到对应的窗口中 示例程序 注意: 在程序中, 我们通过CFUNCTYPE返回一个类对象, 通过该类对象可以实例化出我们需要的c类型的函数, 但是如果不将他放在全局的话则会失去效果, 因为在C语言中函数是全局的 # -*- coding: utf-8 -*- import os import sys from ctypes import * from ctypes.wintypes import * """ define constants """ WH_KEYBOARD = 13 WM_KEYDOWN = 0x0100 CTRL_CODE = 162 class JHKeyLogger(object): def __init__(self, user32, kernel32): """ Description: Init the keylogger object, the property 'hook_' is the handle to control our hook function Args: @(dll)user32: just put windll.user32 here @(dll)kernel32: just put windll.kernel32 here Returns: None """ self.user32_ = user32 self.kernel32_ = kernel32 self.hook_ = None def install_hookproc(self, hookproc): """ Description: install hookproc function into message chain Args: @(c type function)hookproc: hookproc is the hook function to call Returns: @(bool): if SetWindowHookExA() function works successfully, return True else return False """ self.hook_ = self.user32_.SetWindowsHookExA( WH_KEYBOARD, hookproc, self.kernel32_.GetModuleHandleW(None), 0) if not self.hook_: return False return True def uninstall_hookproc(self): """ Description: uninstall the hookproc function which means pick the hookproc pointer off the message chain Args: None Returns: None """ if not self.hook_: return self.user32_.UnhookWindowsHookEx(self.hook_) self.hook_ = None def start(self): """ Description: start logging, just get the message, the current thread will blocked by the GetMessageA() function Args: None Returns: None """ msg = MSG() self.user32_.GetMessageA(msg, 0, 0, 0) def stop(self): self.uninstall_hookproc() def hookproc(nCode, wParam, lParam): """ Description: An user-defined hook function Attention: here we use the global variable named 'g_keylogger' """ if wParam != WM_KEYDOWN: return g_keylogger.user32_.CallNextHookEx(g_keylogger.hook_, nCode, wParam, lParam) pressed_key = chr(lParam[0]) print pressed_key, # hit ctrl key to stop logging if CTRL_CODE == lParam[0]: g_keylogger.stop() sys.exit(-1) return g_keylogger.user32_.CallNextHookEx(g_keylogger.hook_, nCode, wParam, lParam) # Attention: pointer must be defined as a global variable cfunctype = CFUNCTYPE(c_int, c_int, c_int, POINTER(c_void_p)) pointer = cfunctype(hookproc) g_keylogger = JHKeyLogger(windll.user32, windll.kernel32) def main(): if g_keylogger.install_hookproc(pointer): print 'install keylogger successfully!' g_keylogger.start() print 'hit ctrl to stop' if __name__ == '__main__': main()
XSS(Cross-Site Scripting) Hacker PC -- upload XSS script to Web Server --> User PC Request for this Web Server --> Web Server response to the User PC with XSS script --> Run XSS script on User PC --> User PC send cookie to Hacker PC(这一步就获取了用户的个人信息) CSRF(Cross Site Request Forgery) 和XSS攻击的步骤一样, 只是在User PC端运行的脚本是不断地向Web Server发送请求 网络钓鱼 Hack PC 发送一份邮件给User PC --> User PC点击该邮件中的链接地址 --> 打开之后显示的是Hack PC伪造的网站 域欺骗 Hack PC Hack DNS Server --> 修改DNS的A记录 --> 用户访问原来的URL的时候看到的是Hack PC伪装的网站 SQL注入 在HTML的表单中, User PC在input标签中输入特殊的SQL语句获取从Web Server中返回的数据, 接着进行分析 Web Shell Hack PC将用于远程操作Web Server的Web Shell Code脚本Upload到Web Server上, 接着通过浏览器Call Web Shell File By URL执行该Web Shell File Code, 从而获取root权限操纵Web Server Packet Sniffer(包嗅探) 在同一个局域网中, 一台PC向另一台PC传送数据, 会将数据广播到同一网段中的所有PC。所接收到数据报文的PC将自身的MAC地址和数据报文中的MAC地址进行比较, 如果一致则接受, 否则就丢弃它 会话劫持 HTTP会话劫持和TCP会话劫持 HTTP是获取Cookie和Session ID进行攻击 TCP是数据包信息 DoS攻击 通过一致发送TCP第一次握手的SYN包, 让Web Server不断的为请求分配缓冲区, 知道缓冲区崩溃 DDoS攻击 单单一个DoS攻击往往无法通过几台计算机让服务器崩溃 DDoS采用的就是分布式拒绝服务攻击, 将恶意代码扩散到网络中, 其他的UserPC在获取了这段代码之后安装到本地, 接着Hack PC远程控制这些User PC(成为了僵尸PC, 他们组成的网络为Botnet(僵尸网络)), 发送攻击命令 TCP TCP主要由ip, port, sequence number三个元素组成, 其中port是TCP的标识符 所谓的对端口进行扫描就是对UDP和TCP的各个端口进行扫描
matlab中的向量与矩阵 如果定义一个A = [1, 2, 3]; 则A为一个行向量 但是A(:)返回的是一个列向量 关于函数的返回值 在function [a, b, c] = fit_quadratic(x, y)中 如果我们不适用任何变量来接受这个返回值, 则默认返回a 如果我们使用 [a, b] = fit_quadratic(x, y);则返回a和b
git使用摘记 git冲突的问题主要是在修改的部分而不是添加的部分, 如果merge的文件在同一个位置有不同的信息则git会报错 git push origin中的origin表示的是远程的仓库名为origin, 一般我们只有一个远程仓库, 它默认的名称就是origin git pull命令会自动merge, 可能就会产生合并冲突 git diff命令不仅可以比较文件还可以比较分支, 这个对为程序打补丁有用, 使用git diff master > mypat, 在git commit -a -m "msg"之后返回到master, 会发现居然会有mypat文件, 按理来说是没有的, 因为mypat的文件格式是git可以识别将会特殊对待的补丁文件, 在另一个分支上使用git apply mypat打上补丁即可, 接着删除mypat git的merge默认就是移动了分支指针 HEAD指针 -> 分支指针 -> 提交点 常用子命令 git tag git checkout git branch git remote git config git log --pretty=oneline --abbrev-commit git reflog --pretty=oneline --abbrev-commit git clone git push origin brh:brh git push --tags git pull git status git add git commit git stash(使用的前提是将修改add到stage中) git stash list git stash pop 一些概念 版本库: .git目录 工作区: 包含着.git目录的目录 所谓的HEAD指针的值就是保存在.git目录下的一个HEAD文件中的 注意点 使用ssh密钥连接github的话, 那么remote的url就要是ssh的, 否则无法push, 会提Permission denied
数字电路逻辑设计摘要 BCD码 Binary Coded Decimal, 使用二进制码(4位)的形式来表示(一位)十进制 有权BCD码: 8421, 2421等 无权BCD码: 余3码等 一个逻辑表达式的功能表达方式 通过真值表显示 通过真值表我们已经知道了该表达式的逻辑功能, 现在我们根据真值表画出卡诺图得出最小项 \(\to\) 我们的表达式 最小项的重要性 现在给出一个逻辑表达式式: AB + BC, 现在要我们通过设计一个电路实现这个表达式 该表达式中有ABC三个变量, 我们需要有三个输入, 在观察AB发现, 这个使用C的值我们似乎(其实是可以确定的)无法确定, 现在就是最小项发挥作用的时候了, 通过将原来的表达式转换为一个最小项表达式, 式子中所有的单元都是有这3个变量组成的而没有缺省项, 这样我们在设计电路的时候就会清楚很多 最小项表达式是画出卡诺图的前提, 而我们画出卡诺图的目的就是为了化简, 因此可以说最小项表达式可以方便我们化简电路 求解一个函数(F)的最小项表达式 下面的几个的前提都是先求出F的最小项表达式 求解F非的最小项表达式: 就是F的最小项表达式的剩余项 求解F*的最小项表达式: F的最小项表达式中的每一个项与F*的最小项表达式中的每一个项的和为\(2^n - 1\) 实际电路设计中 一般需要将得到的表达式转为与非表达式 译码器 二-十进制译码器: 3线-8线译码器 全加器与半加器 区别: 全加器考虑到了进位, 而半加器不考虑进位 对于1位(几位指的有几个信号组成的)的全加器画出真值表, 对于多为的全加器好像不好画(那就不画了)
异常和中断 保留现场: 通过push指令将寄存器中的值都压入到栈中 恢复现场: 通过pop指令将栈中的值赋值给寄存器中 进程间切换 保留现场: 通过创建并初始化一个结构体(struct pt_regs该结构体中的属性就是用来保存各个寄存器中的数据的), 将该结构体中的所有数据直接memcpy到栈中, 提高了效率 恢复现场: 也是通过pop指令将栈中的数据重新还原到各个寄存器中 在应用层的代码通过系统调用访问内核空间 CPU在执行应用层的代码的时候, 遇到了系统调用, 那么就需要切换栈空间到内核的栈空间, 这里涉及到了特权的转换, 具体的执行流程如下 当前esp, ss, eip等寄存器的值被保存到了CPU内部 CPU通过读取TSS结构, 从中提取出内核栈空间的栈段选择子和栈基地址, 将这个值更新到esp, ss中, 这个时候就是在了内核空间了, 因为我们的已经指向了内核的栈空间了 将第一步保存的应用层代码的esp, ss等值压栈 将调用者(应用层)的栈空间的参数复制到内核的栈中 通过门描述符获取系统调用的入口地址, 将其更新到eip, cs寄存器中 CPU在内核空间执行程序 执行完毕, 从系统调用中返回, 进行现场的恢复
// 进程控制结构体(PCB) --> 用来管理进程 struct tack_struct { struct List list; // 双向链表, 用于连接各个进程控制结构体, 在Linux中这样的链表创建方式比较常见 volatile long state; // 表示进程的状态: 运行态, 停止态, 可中断态等 unsigned long flags; // 进程标志, 是进程还是线程, 也许这就是Linux中的线程被称为轻量级的进程的原因 struct mm_struct *mm; // 记录内存页表和程序段信息, 说白了就是管理内存中的程序(data, code, rodata, bss), 应用程序的栈顶地址 struct thread_struct *thread; // 用于保存进程切换时的数据 unsigned long addr_limit; // 进程地址空间范围 long pid; long counter; // 进程占用的时间片 long signal; // 进程的信号 long priority; // 进程的优先级 }; struct mm_struct { // pgd的值是从cr3寄存器中获取的, 就是页表的地址 pml4t_5 *pgd; // 页表指针 unsigned long start_code, end_code; unsigned long start_data, end_data; unsigned long start_rodata, end_rodata; unsigned long start_brk, end_brk; // 应用程序的栈顶地址 unsigned long start_stack; }; // 用于保留现场 struct thread_struct { unsigned long rsp0; // 内核层栈基地址 unsigned long rip; // 内核层代码指针 unsigned long rsp; // 内核层当前栈指针 unsigned long fs; // 存储fs段寄存器的值 unsigned long gs; // gs段寄存器的值 unsigned long cr2; // cr2控制寄存器的值 unsigned long trap_nr; // 产成异常的异常号 unsigned long error_code; // 异常的错误码 }; /* * 在Linux内核中, 将task_truct结构体和进程的内核层栈空间融为一体, 低地址存放task_struct结构体, 余下的存放进程的内核层栈空间使用 * */ // 通过该联合体创建出来的是Linux下第一个进程, 注意: 这个进程不是我们提到的init进程, init进程是Linux第二个进程 union task_union { struct task_struct task; unsigned long stack[STACK_SIZE / sizeof(unsigned long)]; }; #define INIT_TASK(tsk) \ {\ .state = TASK_UNINTERRUPTIBLE, \ .flags = PF_KTHREAD,\ .mm = &init_mm,\ .thread = &init_thread, \ .addr_limit = 0xffff800000000000, \ .pid = 0, \ .counter = 1, \ .signal = 0, \ .priority = 0\ } // 1, 2, 3都是初始化第一个进程 // 1 union task_union init_task_union __attribute__((__section__(".data.init_task"))) = {INIT_TASK(init_task_union.task)}; // 2 struct task_struct *init_task[NP_CPUS] = {&init_task_union.task, 0}; struct mm_struct init_mm = {0}; // 3 struct thread_struct init_thread = { .rsp0 = (unsigned long)(init_task_union.stack + STACK_SIZE / sizeof(unsigned long)), .rsp = (unsigned long)(init_task_union.stack + STACK_SIZE / sizeof(unsigned long)), .fs = KERNEL_DS, .gs = KERNEL_DS, .cr2 = 0, .trap_nr = 0, .error_code = 0 }; struct tss_struct { unsigned int reserved0; unsigned long rsp0; unsigned long rsp1; unsigned long rsp2; unsigned long reserved1; unsigned long ist1; unsigned long ist2; unsigned long ist3; unsigned long ist4; unsigned long ist5; unsigned long ist6; unsigned long ist7; unsigned long reserved2; unsigned short reserved3; unsigned short iomapbaseaddr; }__attirbute__((packed)); #define INIT_TSS \ {\ .reserved = 0, ... \ .ist1 = 0xffff800000007c00,\ ...\ } struct tss_struct init_tss[NR_CPUS] = { [0 ... NR_CPUS - 1] = INIT_TSS }; /* * 在之前我们恢复异常和中断的现场的时候就发现要对大量的寄存器中的数据压栈保存, 有在task_struct的thread在进程切换的时候保存数据的方式的启发, 我们可以定义一个结构体来保存保留 * 现场的寄存器的数据, 然后直接将这个结构体中的数据直接拷贝到内核栈中, 而不是一个一个地压入到内核栈中, 这样效率高 */ /* * 这个进程的现场保留的数据就交给了这个pt_regs结构体了, 他和异常以及中断时将大部分寄存器中的数据压栈是一样的 */ /* * 我们知道一个操作系统会管理很多个进程, 我们需要有一个函数来获取当前的task_struct */ inline struct task_struct *get_current() { struct task_struct *current = NULL; __asm__ __volatile__("andq %%rsp, %0":"=r"(current):"0"(~3276UL)); return current; } #define current get_current() #define GET_CURRENT \ "movq %rsp, %rbx \n\t" \ "andq $ - 32768, %rbx \n\t" 进程内的切换是在内核空间中的, 如果将这个机制搬运到应用程序中则实现了线程间的切换工作 进程间的切换主要涉及到页目录的切换和各个寄存器值的保存和恢复 进程间切换需要在一块公共区域内进行, 这个区域就是内核空间(注意: 作为的在内核空间运行就是指我们当前的堆栈指针指向的是内核的堆栈) 对于操作系统的第一个PCB我们进行特殊的创建, 就是直接手动的创建, 目前来看这个就是我们的current的进程控制结构体了, 接着我们要生成一个新的进程, 这个进程就是我们熟悉的init进程, 这个进程和剩余的其他进程都是通过一个kernel_thread函数创建的, 而在kernel_thread函数中核心的创建子函数就是do_fork函数, 这个函数会拷贝当前的current的内存数据到一个新的区域, 很熟悉吧, 这个就是所谓的子进程从父进程中的一个复制, 下面就是来介绍这个kernel_thread函数 kernel_thread: 先声明一点, 所谓创建一个新的进程和一个老的进程恢复现场都是一个样的, 所以我们在创建一个新的进程的时候采用伪造恢复现场的方式来创建 因此在kernel_thread函数中, 我们先创建一个用来存储恢复现场的数据结构体, 也就是pt_regs struct结构体, 这个结构体的属性几乎就是所有的寄存器(因为保留现场我们需要将几乎所有的寄存器的值都保存起来), 这个pt_regs结构体在前面有提到过, 这里还有一点需要注意: 我们在这里还要提到一个kernel_thread_func函数指针(在这个函数中先进行现场恢复, 再调用传入的进程的入口函数地址使用call指令执行该进程代码, 接着返回, 调用call do_exit退出进程程序, 如果不在内核层的话, 则调用的是另外一个ret_from_syscall, 因为do_fork是一个系统调用), 这个函数就是用来在执行我们的进程的function的使用先执行的一段恢复现场的代码, 这里我们又提到了一个function, 其实这个就是一个程序的入口地址而已, 我们所以的进程不就是一个动态的程序吗, 就是让cpu去执行, 那么就需要知道他的入口地址 初始化完毕pt_regs结构体之后, 使用的do_fork函数去fork父进程创建出子进程 do_fork 函数 + 在该函数中我们需要调用分页的alloc_pages函数去一个物理页存放task truct(也就是进程控制结构体) + 接着讲task所在的物理页的数据清0 + 这个语句 task = current就是核心了, 就是所谓的子进程从父进程复制数据出来(这里没有使用先进的CoW技术) + 将这个task结构体添加到进程控制列表中 + 递增pid, 原来pid只这样出来的 :) + thread结构体就创建在task之后, thread结构体是用来存储一些进程的状态的, 用于现场保留什么的 + 判断task是在内核态还是在应用层 + 设置task的state为TASK_RUNNING 调用了kernel_thread函数之后, 我们只是完成了一个子进程的创建, 现在我们要去运行该进程了 使用switch_to函数进行进程切换即可 在switch_to函数中, 会执行current进程的现场保留 修改rip等寄存器的值, 让其指向新的子进程的func函数指针指向的地址, 在上面我们已经知道了func会执行现场恢复, 执行进程函数, 退出进程
/* define SAVE_ALL "cld; \n\t" "pushq %rax; \n\t" "pushq %rax; \n\t" "pushq %es, %rax; \n\t" "pushq %rax; \n\t" "pushq" ... 上面的这段汇编代码的宏定义就是在执行中断程序的时候调用的现场保留的操作, 和异常的现场保留是类似的 不同的是, 在异常中是处理器产生的任务暂停, 而在中断中是外部设备产生的任务暂停 也和异常一样, 在执行中断处理函数之前就需要执行现场保留的代码 */ /* 执行的流程, 通过处理函数的入口函数, 先执行现场保留的代码,接着下来就和异常处理程序有一点不一样了, 在中断处理中, 调用一个do_IRQ函数, do_IRQ函数寄存器中的参数调用 对应的处理函数, 在异常中, 我们通过处理函数的入口函数执行了现场保留的代码之后, 在调用明确的异常处理函数, 当然程序的返回地址都是要记录的, 这样才能返回我们原来的程序 */ /* 上面讲到的是在遇到了异常的时候CPU是怎么处理的, 前提是我们现在为中断提供了入口函数 --> 注意: 入口函数和程序处理函数不是完全一样的, 入口函数中包含有程序处理函数, 在入口函数中调用程序处理函数, 在异常和中断中都是这样的, 只不过正如上面提到的那样, 在中断中的程序处理函数都是交给了一个do_IRQ函数通过参数判断要调用对应的中断处理函数 我们现在就是要使用set_intr_gate函数将中断的入口函数们一一注册好, 注意: 这里的set_intr_gate函数虽然接受的参数是我们入口函数的地址, 但是他在中断向量表对应的index 写入的数据是一个门描述符, 该门描述符不仅仅是简单的是入口函数的地址在Linux中, 有一个函数指针数组, 在该数组中保存这的就是所有的中断处理函数的入口函数的地址, 也就是 函数指针, 需要注意的是, 我们使用for循环进行注册的时候, 起始的i是应该为32, 因为前32个中断向量号已经被异常时候, 但是有不是32个异常, 用到的是20个异常, 剩下的12个异常中断 向量号为Intel所保留 */ /* 注册好了之后, 我们需要初始化好ICW和OCW寄存器中的数据 */ /* 下面是一个do_IRQ的demo示例 功能: 在屏幕上打印出时钟中断的中断向量号 */ void do_IRQ(unsigned long regs, unsigned long nr) { color_printk(RED, BLACK, "do_IRQ:%#08x\t", nr); io_out(0x20, 0x20); } /* 通过阅读建议的键盘驱动, 发现所以的驱动编写就是在编写一个中断处理函数, 我们已经知道了, CPU根据中断向量表中的index找到键盘中断的入口函数, 在该入口函数中调用do_IRQ函数, 在 do_IRQ函数中调用驱动程序, 就是这样而已, 所以编写驱动程序仅仅是内核中的冰山一角 */
下载webmin的rpm包 yum install webmin-rpm systemctl start webmin 即可
yum groupinstall "Development Tools" yum install php-mysqli php-mbstring php-mcrypt yum install mariadb mariadb-server yum install httpd httpd-devel 编译安装php, 将php作为httpd的模块编译进入httpd --> 另一篇博客: https://www.cnblogs.com/megachen/p/9807361.html 下载phpMyAdmin的tar.gz包 tar -xf phpMyAdmin-4.0.10.20-all-languages -C /var/www/html ln -sv phpMyAdmin-4.0.10.20-all-languages pma cd pma cp config.sample-.nc.php config.inc.php 修改配置文件, 一般人都懂得, 我就不讲了 配置一下mariadb即可
wget http://hk1.php.net/distributions/php-5.6.31.tar.gz yum groupinstall "Development Tools" yum install zlib openssl perl yum install httpd httpd-devel tar -xf php.tar.gz -C /usr/src cd /usr/src/php yum install mariadb mariadb-server php-mysql 配置 ./configure --prefix /usr/local/php56 --with-apxs2=/usr/local/httpd24/bin/apxs --with-config-file-path=/etc/ --with-config-file-scan-dir=/etc/php.d --with-libxml-dir --with-openssl --with-zlib --enable-mbstring --with-mysql 其中php与httpd的联系就是apxs, 与mysql的联系就是with-mysql make && make install cp php.ini-development /etc/php.ini mkdir /etc/php.d/ 修改httpd.conf
初级内存管理单元 关于内存的分页 以往的物理页是按照4KB进行分配和管理的, 而在Linux之后流行的就是2MB大小的物理页的分配和管理, 整个物理内存管理单元也是2MB物理页管理的 先获取基本的物理地址空间信息 在bootloader程序中, 已经调用了BIOS的int 15h中断将物理内存地址的结构体放置到了1MB之下的物理地址0x7e00处, 我们需要将其提取出来 每一条物理空间信息BIOS加载到内存时20B, 因此我们要获取该数据, 也需要定义一个结构体也占用20B的物理内存大小, 获取0x7e00地址上的数据, 但是在IA-32e模式下, 我们能够使用的是线性地址, 不能直接访问物理地址, 访问物理地址需要通过我们已经在head.S程序中定义好的页表进行页的映射, 在head.S中我们只进行了10MB的映射, 这对于我们目前来说已经足够了, 不过我们还是要知道物理地址0对应的线性地址时多少, 这样我们才能进行编码 物理地址空间的大小为32个, 我们一开始先打印出这些信息, 输出物理内存哪些是可用的(type == 1), 如果多了脏数据(type > 4)证明这个地方的数据经不明数据污染了, 也就是说有数据将BIOS写到0x7e00地址上的数据覆盖了, 那我们就不要在读取了, 因为已经是错误的了, 没有意义了, 打印出来我们总共可用的内存大小 在分配可用物理页之前先获取可用物理页的页数 在这个部分, 我们需要对复制BIOS的全部物理地址空间信息到一个memory_management_struct中, 这个结构体就是我们内存管理的核心, 它保存着所有的内存页的信息, 包括不可用的 接下来我们计算出可用的物理内存页数, 获取一个可用的物理内存段之后, 我们对其起始地址尽心2MB的物理页的对齐, 返回的就是对齐后的物理地址, 接着计算出这个物理内存段的结束地址end, (end - start) >> page_2m_shift 计算出在这个物理段中有几个物理页, 也就一个分隔游戏, start就是对齐之后的物理地址, 在操作系统中一般有两个可用的物理地址段, 即为A和B, 他们一般不在一起, 第一个就是从我们的物理地址0开始的物理内存段, 显然我们的bootloader和内核代码都在这里, 这里肯定是可用的物理内存, 其实地址就是0, 对齐后的地址也是0, 因为2MB对齐就是将我们所有的内存分成一个一个的2MB, 但是另外一个可用的物理地址段它的start地址就不一定就是在一个矩形的2MB的起始位置了, 也就是说地址不对齐了, 这个时候我们就需要进行物理地址的对齐, 一般来说经过运算后, 我们原来的物理地址start的物理地址会增加一点到一个2MB的起始地址, 虽然这样会浪费一小段可用的物理地址空间, 但是我们完成了2MB的分隔, 更加方便我们的管理 分配可用物理内存页 需要用到的逻辑上的结构体 struct page --> 代表的就是我们说的2MB的物理页, 但是它本身不是2MB, 而是他管理的物理内存时2MB的, 这里所谓的管理就是通过属性保存地址, phyaddr就是管理的物理页的物理起始地址 struct zone --> 区域空间结构体, 它与page联系紧密, 它标志一个可用物理地址段, 就是我们在上面讲到了两个可用的物理地址段, 他代表着一个段, 怎么代表的呢, 也是通过属性startaddr和endaddr, startaddr保存着一个可用物理内存段的起始地址, endaddr保存着一个可用物理内存段的结束地址, 我们已经知道另一个一个物理段是很大的, 有多个2MB的物理页, 因此这里的endaddr - startadd的值也大于2MB, 所以会有多个page结构体引用着一个zone, 用来在逻辑层面上模拟分配一个页 上面已经提到过的memory_management_struct(在后面简称为mm), 他包含了从BIOS获取的物理地址信息, bitsmap(就是一个整数, 和ext3文件系统一样一样的)用来方便索引空闲的物理页page, 一个page结构体数组(在逻辑上属于zone), 一个zone结构体数组, 内核代码结束位置, 自己的结束位置,注意page和zone结构体分配在了内核代码之后, zone在page之后 开始分配可用物理内存页(类似与Python中的__new__()魔法方法的功能, 但是与Java中的new关键字的功能不同, 这里只是为page和zone结构体分配了内存, 这里到处了实质: 可用物理页的分配就是在为结构体创建内存空间, 但是不进行初始化) 初始化bitmap mm.bits_size属性赋值为我们之前计算出来的可用物理内存页的个数, 这样才能确定bitmap的大小 将bitmap的值置为0, 虽然在这个时候我们的内核代码已经在内存中了, 我们理应将对应的bitmap中的一个位置位, 但是我们现在的内存管理的数据结构体还不完善, 所以我们将这个往后推 初始化page struct结构体数组 mm.pages_size等都记录下来, page结构体采用的4K物理页的对齐方式, 反正这些元数据结构体会比较特殊 分配内存空间, 将所有的值初始化为0 初始化zone 结构体数组 分配内存空间, 将所有的值初始化为0 第二次初始化(此时数据结构体的内存大致已经分配好了, 就是属性需要进行赋值, 类似于Python中的__init__()方法) 先为zone结构体进行属性的初始化, 从mm中的bios的物理地址空间信息中读取 type == 1的地址, 对齐, 赋值该zone的start, end和上面的一样计算, 这样一个物理段就初始化完毕了, 此时在这个物理段zone的基础上初始上page的属性(但是这里的zone和page, 并不是所有的属性都被初始化了, 有一些需要在函数page_init中进行初始化), 我们知道page都是属于zone的, 通过循环将zone所代表的物理段分成多个2MB大小的物理页, 当然是使用page结构体的phyaddr和length来表示了, 接着这样page指向该zone, 表示page的所属是谁 现在我们也为page和zone的属性都赋值了, 现在我们就要通过一个page_init函数来初始化内核代码所在的内存, 还记得上面提到了在初始化bitmap的时候, 我说过的要将这个事情往后推吗! 在初始化内存的时候, page的属性refcount++, page指向的zone的freepages--, pageusing++, 并且置位bitmap表示已用 内存管理需要的东西完成了, 下面就是通过一个函数接口来通过访问这里的内存管理机制分配到内存了 通过alloc_pages函数返回一个struct page数组内核层和应用层使用 判断向内存中的哪个区域要物理页 通过bitmap找到指定连续数量的未被使用的位, 通过该位计算得出这个page数组的首地址, 将连续的page数据返回, 同时标志bitmap对应的位已用
初入阿里云 基本设施(参考博客) ECS (Elastic Compute Service):以虚拟机的方式将一台物理机分成多台云服务器,提供可伸缩的计算服务。 SLB (Server Load Balance):基于LVS和Tengine实现的4层和7层负载均衡,有动态扩容,session保持等特点。 RDS (Relational Database Service);:通过云服务的方式让关系型数据库管理、操作和扩展变得更加简单。 OCS (Open Cache Service):基于内部Tair,增加一层Proxy,支持海量小数据的高速访问。 OTS(Open Table Service):海量(结构化)数据存储和实时查询服务。 OSS(Open Store Service):对任意大小数据对象提供高可用,高可靠的海量存储服务。 CDN(Content Delevery Network):通过覆盖全网的缓存服务以及负载均衡等技术将用户请求定向到最合适的区域,提高用户服务的响应速度及网站服务能力。 OAS (Open Archive Service):离线归档,冷数据备份,类似Amazon Glacier。 ODPS(Open Data Processing Service):海量数据处理和分析平台。 SLS (Simple Log Service):解决异构、分布式系统中日志实时收集、存储与查询的基础服务。 搭建ftp 在Linux的基本上就是不说了, 这里就提一下在阿里云的安全组中的坑 如果只在Linux主机上搭建了一个vsftpd是不可以直接访问的, 我们还需要修改安全组, 在安全组中的入网中添加22/22以及1024/65563
环境 32位操作系统 通过结构体的内存字节对齐了解操作系统的内存对齐 在32位操作系统中, CPU默认读和写数据是按照4字节的方式 在一个结构体中, 在编译的时候, 编译器会根据结构体中的成员变量使其内存对齐, 让他们都是符合让CPU一次读取的数据而不用再读取一次数据, 减少了读取的次数 下面通过案例讲解 1. struct s { char c; // 占用1个字节 int i; // 占用4个字节 short s; // 占用2个字节 }stu; 如果不对齐的话, 则c与i的前3个字节在同一个4字节内, s和i的后1个字节在另外一个4字节内, 当使用stu.i访问的时候, CPU要读取第一个4字节获取i的前三个字节的数据, 在读取后4个字节获取i的后一个字节的数据, 接受处理数据拿出i的值 如果对齐的话, 在编译器会自动添加一些无关的变量对数据进行填充, 使得c一个4字节, i一个4字节, s一个4字节, 这样获取stu.i就是一个内存访问了, 该结构体占了12个字节 2. struct s { char c; short t; int i; char ch; }; 编译器的对齐方式中, c和t是在同一个4字节中的, 因为CPU一次性就可以读取出来一个4字节, CPU在过滤一下就可以取出c的值了, 这样节省了内存