代码管理的git--应对百变的需求

01 Mar 2019 Category: 工具

需求变更主要涉及到时间安排的变化,上线内容的变化,版本回退等。

分支

git 可以使用多个分支同时进行开发。一般情况,在开发团队中会有开发版本分支(dev),发布版本分支(release)。再日常项目中,我一般还会添加一个预发布分支(pre-release)。作为预发布代码的测试。

开发分支用于做日常开发。发布分支用于发布线上代码,预发布分支用于预发布测试。正常开发流程,在开发分支完成测试环境测试之后,会合并到预发布分支上,在预发布环境进行测试。预发布环境测试完之后,再合并到发布分支上。

如果在新版本开发的过程中,线上代码有个bug需要修复,可以从预发布分支上检出一份代码,修复bug之后再合并到预发布分支上,在预发布环境测试。测试没问题再发布到线上分支。

整个过程流程图如下:

git 开发 版本 流程图

以下是实际开发的一个截图

git dev branch

git fix bug

分支相关命令如下:

## 创建新分支
git branch newbranchname 

## 切换到新分支
git checkout newbranchname

## 创建并切换到新分支
git branch -b newbranchname

## 将新分支推送到远程服务器(先进入到分支中)
git push --set-upstream origin newbranchname

## 删除分支(本地分支)
git branch newbranchname

## 强制删除(本地分支)
git branch -D newbranchname

## 删除远程分支(不会删除本地分支)
git push origin --delete newbranchname

## 查看本地分支
git branch 

## 查看所有分支(会把远程分支列出来)
git barnch -a

## 查看本地分支与远程分支关联关系
git branch -vv

分支解决了开发环境,测试环境,预发布环境代码问题。有了分支之后,开发人员可以在不同的情况下切换到不同分支进行开发。

合并指定内容

一般的版本开发都需要一个周期。但是有的时候,前期评估周期是一个月,但是在刚开发到半个月的时候就在想能否部分功能先上线。开发过程提倡的是频繁提交,因此一些功能散落在多次提交当中,并且每次提交都是相对的功能专一。这种情况下,比较适合单独合并指定commit。

git中cherry-pick就可以实现这个功能。cherry-pick会把commit的变更内容”复制”过来,同时在当前分支生成一个新的commit。commit hash与原来的不再相同。如果没有冲突,则commit msg 是一样的。如果有冲突,则需要自己手动解决冲突,编辑提交信息

## 合并指定的commitid到本分支
git cherry-pick commit-hash

## 合并连续的多个commit
git cherry-pick commit-start..commit-end

在测试分支上的version1的提交

dev version1

在预发布分支上,使用cherry-pick version1

cherry-pick version1

使用cherry-pick之后,预发布版本上的内容有了version1的提交。同时创建了一个新的commit。

找回历史的一个版本

需求变更是常事,经常做了一圈之后发现还是第一个版本好,这个时候想要找到以一个版本的内容。

恢复指定文件到指定版本。首要任务是先找到指定版本。可以使用git log查找指定版本。

## 查看提交记录
## (包含提交信息,时间,提交人)
## 只会显示当前分支记录
git log

## 查看所有分支记录
git log -g

## 按commit msg 查找
git log -g --grep="查找关键字"

## 按时间查找
git log --after="2019-02-27 00:00" --before="2019-02-27 12:00"

## 显示指定文件的历史记录
## (会显示文件变更内容)
git log -p filename

## 按文件内容变化查找
git log --all -S "文件内容关键字" filename

使用git log 查找到需要恢复的commitid。一般情况,由于版本开发过程,文件之间相互引用,不会直接替换成旧版本,而只是把一部分功能恢复成旧版本。

可以使用checkout将旧版本和当前版本合并。

## checkout 文件 
## -p参数显示commit变更内容
## 然后根据提示合并内容
git checkout commit-id filename -p

也可以使用git show将旧版本输出到一个文件,然后手动合并内容

git show commit-id:filename > old_version_savepath

以下是演示截图

checkout show

对于线上版本,如果真的不幸,上线之后又bug无法修复,那只能是进行版本回滚。回滚到上一个发布版本的commit-id。

## 将版本回退到指定commit
git checkout commit-id

以上内容就是在需求变更使用频繁的命令。主要设计到分支操作,git log的操作,以及checkout 历史的操作。

开发过程中除了上述命令之外,还有一些其他操作,可以很好的帮助开发人员提高代码管理的效率。比如整理commit,使得发布版本变得干净。更换,添加远程地址,修改提交信息,放弃提交内容等。避免篇幅过长,放在下一篇文章写。

查看更多

SVM支持向量机推导过程

28 Feb 2019 Category: 算法

SVM是一种二类分类模型,目的是找到一个超平面,使得样本点之间的间隔尽可能的远。样本中距离超平面最近的点的集合叫做支持向量。分类平面仅由这些支持向量决定。当两类样本的最近距离最大,分类器的泛化能力才越强。

推导svm算法,涉及到下面几点知识:

  • 点到平面的距离

对于点$x_i$ 到平面$w^Tx+b$的距离为: $d=\frac{w^Tx_i+b} {\parallel w \parallel}$

  • 不等式约束极値问题求解(KKT条件)

对于极値问题

可以使用KKT条件求解

如果$x ^{*} $ 是极值点,则一定有$\lambda ^{*} $

以上三个条件叫做KKT条件,更多内容参考链接

回到SVM当中,目标是最大间隔$max\ margin(w,b)$。间隔由两个分类最近的样本决定。假设存在一个平面$w^Tx_i+b$ 划分两个样本,则两个样本的间隔可以表示为最近样本到平面的距离。

正样本$y_i=1$样本,距离边界点的距离为$ d=\frac{w^Tx_i+b} {\parallel w \parallel}\geq 0 $,负样本$y_i=-1$ 距离边界点的距离$ d=\frac{w^Tx_i+b} {\parallel w \parallel}\leq 0 $ (距离是有方向的) ,对上述距离进行转换为无方向的距离

$r = y_i \frac{w^Tx_i+b} {\parallel w \parallel} $ ,$r$叫做函数距离。

查看更多

代码管理的git--非常常用命令

27 Feb 2019 Category: 工具

这里不说很多git是什么之类的,只说工作中会使用到的场景。

创建新项目

服务器端创建项目

服务端使用 git init –bare sample.git

客户端就可以通过git clone git@127.0.0.1:sample.git 克隆仓库

客户端创建项目

项目的创建也可以是在客户端创建(前提是当前用户有创建权限)

假设需要将本地sample文件夹创建为项目仓库,进入sample文件夹,执行

## 初始化
git init  
## 添加远程地址
git remote add origin git@127.0.0.1:sample.git  

## 添加文件
git add README.md 

## 添加第一个commit	
git commit -m 'init' 

## push到远程
#第一次push,使用push -u origin master

git push -u origin master 

查看更多

ML算法评估标准

26 Feb 2019 Category: 算法

机器学习中,模型评价标准太多了,根本分不清谁是谁,统一整理放在一篇文章当中,方便以后查阅

实际类别 预测正 预测负
TP FN
FP TN

错误率

错误率表示分类错误的比例(包括正样本分类为负样本FN,负样本分类为正样本FP)

精度,准确率(ACC)

分类正确的比例(TP+TN) = 1-错误率

精确率,查准率(P)

查全率表示有多少正样本被正确找出(TP/(TP+TP) ),衡量查询结果信噪比,表征查找的准确性

查全率,召回率(R)

查准率表示查找出来的正样本占实际正样本的比例(TP/(TP+FN)),表征查找的全面性

ROC 曲线

ROC曲线的横坐标为false positive rate(FPR):FP/(FP+TN)

纵坐标为true positive rate(TPR):TP/(TP+FN)

(0,1)点,即FPR=0,TPR=1,这是一个完美的分类器,它将所有的样本都正确分类。

(1,0)点,即FPR=1,TPR=0,这是一个最糟糕的分类器,因为它成功避开了所有的正确答案。

(0,0)点,即FPR=TPR=0,即FP(false positive)=TP(true positive)=0,全都没被检测到,即全部选0

(1,1)点,分类器实际上预测所有的样本都为1 。

分析可只:ROC曲线越接近左上角,该分类器的性能越好。如果ROC曲线完全被另一个分类器包裹,则任务另一个分类器的性能优于当前分类器

ROC曲线有个很好的特性:当测试集中的正负样本的分布变化的时候,ROC曲线能够保持不变。

AUC

AUC(Area Under Curve)被定义为ROC曲线下的面积,完全随机的二分类器的AUC为0.5

AUC反应的是分类器对样本的排序能力

F1

F1=2*(P*R)/(P+R)

P是查准率,R是查全率

F1兼顾了分类模型的准确率和召回率,可以看作是模型准确率和召回率的调和平均数,最大值是1,最小值是0。F1越大越好

查看更多

ML概率相关的几个名词

25 Feb 2019 Category: 算法

概率中的各种概率名词,统一整理。

先验概率: 事件X发生的概率P(X)叫做先验概率。一般通过统计获得

条件概率: 事件X在条件Y的情况下发生的概率P(X Y)叫做条件Y下X的条件概率,又叫似然概率,一般通过统计获得
后验概率: 事件X发生的条件下Y发生的概率P(Y X)叫做X的后验概率。事件发生后求的反向条件概率

贝叶斯公式:

P(X Y) = P(X,Y)/P(Y)
P(Y X) = P(X,Y)/P(X)
P(Y X) = P(X Y)P(Y)/P(X)
贝叶斯决策: 若Y是Y样本X的分类标签,P(Y X)最大的类别作为判别结果。P(Y X)=P(X Y)P(Y)/P(X)
一般的,先验概率可以通过统计得到。条件概率P(X Y)由于条件组合多,一般无法使用统计获取。解决的办法就是,把估计完全未知的条件概率分布转化为估计参数。这里就将概率密度估计问题转化为参数估计问题,极大似然估计就是一种参数估计方法。

最大似然估计的目的就是:利用已知的样本结果,反推最有可能(最大概率)导致这样结果的参数值。

对于函数:P(x θ)

输入有两个:x表示某一个具体的数据;θ表示模型的参数。

如果θ是已知确定的,x是变量,这个函数叫做概率函数(probability function),它描述对于不同的样本点x,其出现概率是多少。

如果xx是已知确定的,θ是变量,这个函数叫做似然函数(likelihood function), 它描述对于不同的模型参数,出现x这个样本点的概率是多少。

最大似然估计(MLE)

给定一堆数据,假如我们知道它是从某一种分布中随机取出来的,可是我们并不知道这个分布具体的参,即“模型已定,参数未知”。例如,我们知道这个分布是正态分布,但是不知道均值和方差;MLE的目标是找出一组参数,使得模型产生出观测数据的概率最大:

其中$p(X;\mu)$叫做似然函数,表示在参数$\mu$下出现观测数据的概率.

为了计算方便,一般取对数似然:

对对数似然函数求导,使得导数为0,求出参数$\mu$

最大后验概率估计(MAP)

最大似然估计是求参数$\mu$, 使似然函数$p(X;\mu)$最大。最大后验概率估计则是想求$\mu$使$ p(X \mu)p(\mu) $最大。求得的$\mu$不单单让似然函数大,$\mu$自己出现的先验概率也得大。
实际上,最大后验概率是求$p(\mu X)=\frac{p(X \mu)p(\mu)} {p(X)} $最大,由于$p(x)$是常数,因此最大后验概率估计在于求$ p(X \mu)p(\mu) $最大。

随着我们观测到越来越多的数据,MAP估计逐步逼近MLE。当我们观测到的数据越来越多的时候,我们从数据中获取的信息的置信度是越高的。

贝叶斯参数估计

在估计参数之前对参数已经有了了解称为参数的先验知识。贝叶斯估计即在估计过程中将先验知识也考虑了进去。先验知识可以是一个具体的值,也可以是取值范围(函数)。实际应用中,通常会将参数的先验知识视作一个分布。

贝叶斯估计的目的是结合参数的先验知识,使得估计出来的参数能令贝叶斯风险达到最小。简单说就是最小化贝叶斯风险。

贝叶斯风险是风险函数在θ上的期望

查看更多

ML生成模型与判别模型

24 Feb 2019 Category: 算法

监督学习的任务是对给定的输入预测相应的输出。模型的形式一般是一个判别函数或者一个条件概率分布。

监督学习模型可分为生成模型与判别模型。

判别方法:由数据直接学习决策函数Y=f(X)或者条件概率分布P(Y X)作为预测的模型,即判别模型。基本思想是有限样本条件下建立判别函数,不考虑样本的产生模型,直接研究预测模型。
生成方法:由数据学习联合概率密度分布P(X,Y),然后求出条件概率分布P(Y X)作为预测的模型,即生成模型:P(Y X)= P(X,Y)/ P(X)。基本思想是首先建立样本的联合概率概率密度模型P(X,Y),然后再得到后验概率P(Y X),再利用它进行分类。

区别和联系

判别模型 直接学习样本的条件概率分布或者判别函数。相当于找到样本之间的最优分隔平面。

生成模型则是学习样本之间的联合分布。得到联合分布之后再利用条件概率进行分类。

生成模型可以得到判别模型,因为判别模型的依据是条件概率分布。

特点

生成方法目的是画的样本的联合分布P(X,Y),找到样本数据的真实分布,生成方法不关心样本的分类边界。生成方法的学习收敛速度更快,当存在隐变量时,仍可以用生成方法学习。

判别方法的特点:判别方法直接学习的是决策函数Y=f(X)或者条件概率分布P(Y X)。不能反映训练数据本身的特性。但它寻找不同类别之间的最优分类面,反映的是异类数据之间的差异。直接面对预测,往往学习的准确率更高。

生成算法尝试刻画数据的真实分布,然后在分类。判别模型尝试区分数据之间的差异进行划分。

常见模型

判别模型,K 近邻、感知机(神经网络)、决策树、逻辑斯蒂回归、最大熵模型、SVM、提升方法、条件随机场

生成模型,朴素贝叶斯、隐马尔可夫模型、混合高斯模型、贝叶斯网络、马尔可夫随机场

查看更多

ML的方差与偏差

23 Feb 2019 Category: 算法

偏差-方差分解可以解释学习器的泛化性能。

偏差与方差分别是用于衡量一个模型泛化误差的两个方面;

  • 模型的偏差,指的是模型预测的期望值与真实值之间的差;

  • 模型的方差,指的是模型预测的期望值与预测值之间的差平方和;

在监督学习中,模型的泛化误差可分解为偏差、方差与噪声之和。

偏差用于描述模型的拟合能力方差用于描述模型的稳定性

导致偏差和方差的原因

当模型错误,特征不足,或者训练样本少的情况下,模型输出与实际输出有很大的偏差。此时方差比较小(因为不管怎么选择样本,输出结果都很大的偏离正确结果)

当特征选取太多,模型复杂度过度的时候,偏差较小,方差比较大。不同的测试数据,测试结果会有很大的波动。

如何权衡方差偏差,找到合适的模型

一般情况,偏差方差是相互冲突的。偏差和方差的关系和模型容量(模型复杂度)相关,不同的时候导致欠拟合,过拟合的结果。

可以通过绘制学习曲线,找到方差和偏差相对平衡的模型训练程度。

在使用cross-validation的时候,如果K值选取比较高,则会使得模型过度拟合测试集,bias比较低,但是var比较高。

Bagging与Boosting在方差偏差上的区别

Bagging:多个分类器,对原样本进行有放回的抽样训练,组成集成分类器,然后使用投票等算法得到最终结果。其代表算法为随机森林。

Boosting:使用一系列分类器,对每个子分类器输出的错误值增加权重,使得直到结束。其代表算法为AdaBoost、GBDT、XGBoost。

对于Bagging而言,使用了多个不同的分类器,降低整个集成分类器的方差。对于基分类器而言,其目的更多是降低偏差,会偏向于更加复杂的分类器。

对于Boosting而言,每次都会对错误结构增加权重,因此降低了整个集成分类器的偏差。对于基分类器而言,其目的是降低方差。因此会尽可能选择简单分类器。

因此,一般情况,随机森林的树的深度往往大于GBDT的树的深度。

查看更多

PHP strpos,strstr,strpbrk这几个函数有什么区别

22 Feb 2019 Category: php

确定一个字符串是否在另一个字符串中,在PHP中有很多方法实现。strpos,strstr,strpbrk这几个函数都可以实现。那么这几个函数有什么不同呢?

strstr

strstr — 查找字符串的首次出现,别名strchar

strstr ( string $haystack , mixed $needle [, bool $before_needle = FALSE ] ) : string

返回 haystack 字符串从 needle 第一次出现的位置开始到 haystack 结尾的字符串。

如果$before_needle=true则返回第一次出现的位置前面的字符。如果字符不存在,则返回false。

如果needle不是一个字符串,那么它将被转化为整型并且作为字符的序号来使用。


if (Z_TYPE_P(needle) == IS_STRING) {
	if (!Z_STRLEN_P(needle)) {
		php_error_docref(NULL, E_WARNING, "Empty needle");
		RETURN_FALSE;
	}

	found = (char*)php_memnstr(ZSTR_VAL(haystack), Z_STRVAL_P(needle), Z_STRLEN_P(needle), ZSTR_VAL(haystack) + ZSTR_LEN(haystack));
} else {
	if (php_needle_char(needle, needle_char) != SUCCESS) {
		RETURN_FALSE;
	}
	needle_char[1] = 0;

	found = (char*)php_memnstr(ZSTR_VAL(haystack), needle_char, 1, ZSTR_VAL(haystack) + ZSTR_LEN(haystack));
}

if (found) {
	found_offset = found - ZSTR_VAL(haystack);
	if (part) {
		RETURN_STRINGL(ZSTR_VAL(haystack), found_offset);
	} else {
		RETURN_STRINGL(found, ZSTR_LEN(haystack) - found_offset);
	}
}

strpos

查找字符串首次出现的位置。


strpos ( string $haystack , mixed $needle [, int $offset = 0 ] ) : int

返回 needle 在 haystack 中首次出现的数字位置。查询从offset开始。offset不影响输出的数值。只用于跳过不查询的字符串。

官方文档的Note中:

如果你仅仅想确定 needle 是否存在于 haystack 中,请使用速度更快、耗费内存更少的 strpos() 函数。

以下是strpos 的源码


if (Z_TYPE_P(needle) == IS_STRING) {
	if (!Z_STRLEN_P(needle)) {
		php_error_docref(NULL, E_WARNING, "Empty needle");
		RETURN_FALSE;
	}

	found = (char*)php_memnstr(ZSTR_VAL(haystack) + offset,
		                Z_STRVAL_P(needle),
		                Z_STRLEN_P(needle),
		                ZSTR_VAL(haystack) + ZSTR_LEN(haystack));
} else {
	if (php_needle_char(needle, needle_char) != SUCCESS) {
		RETURN_FALSE;
	}
	needle_char[1] = 0;

	found = (char*)php_memnstr(ZSTR_VAL(haystack) + offset,
						needle_char,
						1,
	                    ZSTR_VAL(haystack) + ZSTR_LEN(haystack));
}
if (found) {
	RETURN_LONG(found - ZSTR_VAL(haystack));
} else {
	RETURN_FALSE;
}

对比两个函数的内部实现,除了offset之外,其实际差别在于strstr最后返回了字符串,strpos返回的是一个数。由于字符串返回的时候涉及到字符串复制的过程,因此会有速度和内存上的损耗。在性能上,strpos 会比strstr好一点点。

可以看一下网上的测试效果,测试效果地址

strpbrk

strpbrk — 在字符串中查找一组字符的任何一个字符。返回一个以找到的字符开始的子字符串。如果没有找到,则返回 FALSE。


strpbrk ( string $haystack , string $char_list ) : string

strpbrk() 函数在 haystack 字符串中查找 char_list 中的字符。


for (haystack_ptr = ZSTR_VAL(haystack); haystack_ptr < (ZSTR_VAL(haystack) + ZSTR_LEN(haystack)); ++haystack_ptr) {
	for (cl_ptr = ZSTR_VAL(char_list); cl_ptr < (ZSTR_VAL(char_list) + ZSTR_LEN(char_list)); ++cl_ptr) {
		if (*cl_ptr == *haystack_ptr) {
			RETURN_STRINGL(haystack_ptr, (ZSTR_VAL(haystack) + ZSTR_LEN(haystack) - haystack_ptr));
		}
	}
}

相对于上面两个函数,strpbrk相对粗暴些,直接两个循环,实现字符的查找。在性能上,应该是这三个函数垫底的了。

总结

以字符串ABCGCAC为例。

strpos 返回的是完整匹配查询字符串的第一次出现位置。strpos(‘ABCGCAC’,’CA’)返回结果是4。

strpbrk 返回的是字符列表中匹配的任意一个字符第一次出现之后的字符串。 strpbrk(‘ABCGCAC’,’CA’) 返回的内容是ABCGCAC,如果传入整数,会转成字符类型strpbrk(‘ABC13G2CAC’,’123’) 返回13G2CAC

strstr 返回的是完整匹配查询字符串第一次后出现后的字符串,strstr(‘ABCGCAC’,’CA’) 返回结果CAC

道路千万条,性能优化第一条,一点点的提升也是提升,只需要选择函数的时候合理选择。

字符串处理函数中,以下函数传入整数是当做ascii值去查找

1、strpos,stripos,strrpos,strripos 2、strstr,stristr,strrstr,strchar,strrchar

查看更多