0%

正则化相关问题


1.实现参数的稀疏有什么好处吗?

1
2
1.可以简化模型,避免过拟合。因为一个模型中真正重要的参数可能并不多,如果考虑所有的参数起作用,那么可以对训练数据可以预测的很好,但是对测试数据就只能呵呵了。
2.参数变少可以使整个模型获得更好的可解释性。

2.参数值越小代表模型越简单吗?

1
是的。这是因为越复杂的模型,越是会尝试对所有的样本进行拟合,甚至包括一些异常样本点,这就容易造成在较小的区间里预测值产生较大的波动,这种较大的波动也反映了在这个区间里的导数很大,而只有较大的参数值才能产生较大的导数。因此复杂的模型,其参数值会比较大。

3.模型简单包括什么?

1
2
1.参数少
2.参数值小

4.从贝叶斯角度看L1和L2正则化分贝数与什么分布?

对于频率学派,认为要将参数θ作为未知的定值,而样本X是随机的,其着眼点在样本空间,参数θ虽然我们不知道是什么,但是他是固定的,我们需要通过随机产生的样本去估计这个参数,所以才有了最大似然估计这些方法。

对于贝叶斯学派,把参数θ也视为满足某一个分布的随机变量,而X是固定的,其着眼点在参数空间,固定的操作模式是通过参数的先验分布结合样本信息得到参数的后验分布,核心是

L1正则化相当于先验分布是拉普拉斯分布,L2正则化相当于先验概率是正态分布。拉普拉斯分布的计算公式:

正态分布概率密度分布公式:

正则化


机器学习中几乎都可以看到损失函数后面会添加一个额外项,常用的额外项一般有两种,一般英文称作L1-norm和L2-norm,中文称作L1正则化和L2正则化,或者L1范数和L2范数。

对于线性回归模型,使用L1正则化的模型建叫做Lasso回归,使用L2正则化的模型叫做Ridge回归(岭回归)

概念

L1正则化是指权值向量绝对值之和,通常表示为||w||1

L2正则化是指全职向量w中各个元素的平方和让后再求平方根,通常表示为||w||2

下图是Python中Lasso回归的损失函数,式中加号后面一项α||w||1即为L1正则化项。

img

下图是Python中Ridge回归的损失函数,式中加号后面一项α||w||22 即为L2正则化项

注:
1.上面的两个函数前半部分可以为任意的线性函数的损失函数,组合成的函数都可以成为Lasso回归会Ridge回归
2.上面两个式子中的α为正则化系数,后续通过交叉验证确定

注:上面两个式子中的α为正则化系数,后续通过交叉验证确定)

L1正则化与L2正则化的作用:

L1正则化可产生稀疏权值矩阵,即产生一个稀疏模型,可用用于特征选择

L2正则化主要用于防止过拟合



L1正则化


L1正则化的标准形式:

​ 其中J0是原始的损失函数,加好后面是L1正则化项。机器学习的最终目就是找出损失函数的最小值,当我们在原本的损失函数后面加上L1正则化后,相当于对J0做了一个约束,另L1正则化项等于L,则 J=J0+L,任务转化为在L1的约束下求J0最小值的解
​ 考虑二维情况,即只有两个权值w1和w2,此时L=|w1|+|w2|,对于梯度下降算法,求解j0的过程中画出等值线,同时将L1正则化的函数L也在w1、w2空间化出来,二者图像首次相交处即为最优解,获得下图:

​ 从图中可看出j0与L相交于L的一个顶点处,这个顶点即为最优解。注意这个顶点的值为(w1,w2)=(0,w),可以想象,在更多维的情况下,L将会有很多突出的角,而J与这些叫接触的几率将远大于与L其他部位接触的概率,而这些角上将会有许多权值为0,从而产生系数矩阵,进而用于特征选择

1
2
3
4
5
6
7
8
9
10
11
from sklearn.linear_model import Lasso
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_bostonboston=load_boston()

scaler=StandardScaler()
X=scaler.fit_transform(boston["data"])
Y=boston["target"]
names=boston["feature_names"]
lasso=Lasso(alpha=.3)
lasso.fit(X,Y)
print"Lasso model: ",pretty_print_linear(lasso.coef_,names,sort=True)

L2正则化


L2正则化的标准形式

​ 和L1正则化相同,任务转化为在L2的约束下求J0最小值的解。考虑二维情况,即只有两个权值w1和w2,此时L=|w1|+|w2|,对于梯度下降算法,求解j0的过程中画出等值线,同时将L1正则化的函数L也在w1、w2空间化出来,二者图像首次相交处即为最优解,获得下图:

机器学习过程中权值尽可能小的原因:

试想对于一个模型,当参数很大时,只要数据偏移一点点,就会对结果造成很大的影响,如果参数较小,则数据偏移的多一点,也不会对结果产生多大的影响,抗扰动能力强

为什么L2正则化可以使权值尽可能的小?

对于损失函数不带L2正则化项的梯度下降时参数更新公式为:

加入L2正则化项,参数更新公式为:

根据两个公式之间的差别,我们可以明显的看到,加入正则化以后的梯度下降在进行参数更新时,要先将原有的参数值乘以一个小于1的值,因此权值也会变得比不带的参数小

1.SVM模型的超参数

​ SVM模型主要包括Cgamma两个超参数。

C是惩罚系数,也就是对误差的宽容度,C越大代表越不能容忍出现误差,越容易出现过拟合;

gamma是选择RBF核时,RBF核自带的一个参数,隐含的决定数据映射到新空间的后的分布,gamma越大,支持向量越少。

支持向量的个数影响训练和预测的个数

gamma的物理意义,大家提到很多的RBF的幅宽,它会影响每个支持向量对应的高斯的作用范围,从而影响泛化性能。我的理解:如果gamma设的太大,σ会很小,σ很小的高斯分布长得又高又瘦, 会造成只会作用于支持向量样本附近,对于未知样本分类效果很差,存在训练准确率可以很高,无穷小,则理论上,高斯核的SVM可以拟合任何非线性数据,但容易过拟合)而测试准确率不高的可能,就是通常说的过训练;而如果设的过小,则会造成平滑效应太大,无法在训练集上得到特别高的准确率,也会影响测试集的准确率

2.

在实际流量解析过程中一般使用

1.url编码解码-urllib

​ python使用urllib包来进行url编码和解码,对于python3:

1
2
3
4
5
6
7
8
9
10
11
12
import urllib

rawurl="xxc=B&z0=GB2312&z1=%E4%B8%AD%E5%9B%BD"

#python2
url = url.unquote(rawurl)

#python3
url=urllib.parse.unquote(rawurl)

output:
'xxc=B&z0=GB2312&z1=中国'

2.字符串转十六进制

​ 字符串转十六进制可以分为两种:1.对于已经是十六进制格式,但是已经被转为字符串,例如:””

1
2
3
import binascii

#python3

3.原始字节串和十六进制字节串之间的转化—binascii

1
2
3
4
5
6
7
8
9
10
import binascii

data_bytes = b"cfb5cdb3d5d2b2bbb5bdd6b8b6a8b5c4c2b7beb6a1a3"
data_hex = b'\xcf\xb5\xcd\xb3\xd5\xd2\xb2\xbb\xb5\xbd\xd6\xb8\xb6\xa8\xb5\xc4\xc2\xb7\xbe\xb6\xa1\xa3'

#原始字节串==>十六进制字节串
binascii.hexlify(data_bytes)

#十六进制字节串==>原始字节串
binascii.unhexlify(data_bytes)

1.匹配一个指定字符串,指定字符串前后不能有任何字母和数字内容

1
2
#以c99关键字为例
[]

​ 逻辑回归通过拟合曲线实现分类,决策树通过寻找最佳划分特征进而学习样本路径实现分类,支持向量机通过寻找分类超平面进而最大化类间间隔实现分类,而朴素贝叶斯通过

朴素贝叶斯思想


​ 朴素贝叶斯是一种最简单的概率图模型,通过根据训练样本统计出样本的概率分布,基于贝叶斯定理条件独立假设来进行建模预测的模型。

朴素贝叶斯概率图

贝叶斯定理

1
2
3
4
5
6
7
8
p(AB)=P(A/B)P(B)
=P(B/A)P(A)

在贝叶斯模型中用到的是下面的形式:
P(Ci/W) = P(W|Ci)*P(Ci)/P(W)
其中,W为向量,有的多个值组成,Ci为标签,也就是上式可以写成下面的形式
P(Ci/w0,w1,..,w) = P(w0,w1,...,wn/Ci)*P(Ci)/P(W)
里面的P(Ci/w0,w1,..,w)就是机器学习建模最终的目标,在一定条件下是某一类的概率

条件独立假设

1
2
3
4
5
	条件独立假设认为:每个事件的发生都相互独立,互相之间没有影响。由于这个假设,上面的式子可以改为:

P(Ci/w0,w1,..,w) = P(w0,w1,...,wn/Ci)/P(Ci) = P(w0/Ci)P(w1/Ci)...P(wn/Ci)*P(Ci)/p(W)

到这里,我们可以知道,要求的最终的结果,只需要在训练集中求得P(Ci)以及在P(w0/Ci)...P(wn/Ci)即可

模型训练

因此在NB算法训练时,只需要在训练集样本中到下面三个概率分布:

​ 1.P(Ci),在训练集中标签1出现的概率(二分类只需要统计一个,n分类就需要n-1个)

​ 2.P(wj/Ci),在训练集中属于各个标签的条件下第n个特征是i的概率

注意:这里不需要统计P(W)的概率,因为最终属于各个类型的概率都需要除以相同的P(W),因此约掉

训练代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def trainNB(dataSetList,labels):
dataSetVec = np.array(dataSetList)

#计算Pc
sampleNums = len(dataSetVec)
pc = np.sum(datasetVec)/sampleNums

#计算p(wj/Ci),这里是二分类
p0Nums = 0
p1Nums = 0

#这里涉及到初始化问题
p0Vecs = np.ones(len(dataSetVec[0]))
p1Vecs = np.ones(len(dataSetVec[0]))

for i in range(len(labels)):
if labels[i]==0:
p0Vecs += dataSetVec[0]
p0Nums += 1
else:
p1Vecs += dataSetVec[0]
p1Nums += 1

p0Vecs = p0Vecs/p0Nums
p1Vecs = p1Vecs/p1Nums

return pc,p0Vecs,p1Vecs

初始化问题

​ 再利用贝叶斯分类器进行分类时,要计算多个概率等乘积以计算文档属于某个分类的概率,即计算:

​ P(w0|c=1)P(w1|c=1)….P(wn|c=1)

​ 如果其中任意一项为0,那么最终的成绩也将等于0。为了降低这种情况造成的影响,可以将所有词初始化为1.

预测过程

​ NB模型的预测过程就是使用上面统计得到的概率分布与输入数据进行关联后,计算出新的样本属于各个类型的概率,然后选择其中概率最大的类型作为模型预测类型的过程。预测过程中需要关注的一个关键问题需要重点关注,那就是python的下溢出问题

下溢出问题:在python中当多个很小的数相乘时会产生下溢出问题(最后四舍五入得到0)

解决办法:取自然对数。因为自然对数和原来的数怎增减性相同,极值点也相同

​ 使用自然对数后,上面的式可以转换成:

​ P(Ci/w0,w1,..,w) = P(w0/Ci)P(w1/Ci)…P(wn/Ci)/P(Ci) —>P(Ci/w0,w1,..,w) = log(P(w0/Ci))+…+log(P(wn/Ci))+P(Ci)

预测代码

​ 预测过程中将已知的概率分布与输入数据进行关联的方式:

​ log(P(w0/Ci))+…+log(P(wn/Ci))+P(Ci) ——>log(P(w0/Ci))x0+…+log(P(wn/Ci))xn+log(P(Ci)

​ 这里的input_data*np.log(p0Vecs)代表将每个出现的词和其出现在该类中出现该词的概率关联起来.

1
2
3
4
5
6
7
8
9
def classfyNB(input_data,pc,p0Vecs,p1Vecs):
#这里的input_data*np.log(p0Vecs)代表将每个出现的词和其出现在该类中出现该词的概率关联起来
#这里之所以没有除以pw,是因为对每个类型的pw是一致的,就没有必要所有都除了
p0 = sum(input_data*np.log(p0Vecs))+math.log(pc)
p1 = sum(input_data*np.log(p1Vecs))+math.log(1-pc)
if p0>p1:
return 0
else:
return 1


github相关知识

​ github项目使用终端进行管理时分为三个区域:工作目录、暂存区(本地库)、github上的远程项目,当我们要更新以及操作一个项目时,要遵循以下的格式:

1.先从github上面pull下远程的项目分支

2.本地的项目文件夹中的文件进行更新(更新工作目录中的文件)

3.使用add将更新的索引添加到本地库

4.使用commit工作目录中的文件提交到暂存区(本地库)

5.将文件push到远程分支或merge到远程分支

基本操作

git clone “ssh项目地址” 克隆远程项目

git pull origin master 取回远程主机或本地的某个分支的更新,再与本地分支进行合并(这种写法是origin主机的master分支与本地当前分支进行合并)

git push origin master 将本地的当前分支push到origin主机的master分支上

git add “文件名” 将指定文件提交到本地库

git commit -m “描述信息” 将本地的全部文件都提交到本地库

git log 打印该项目的版本操作信息

git status 查看到那个钱仓库状态

更新github

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#将项目从github上面拉下来(本地已经有的可以跳过,已有则直接进入该文件夹)
git clone github链接

#查看项目状态
git status

output:
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

#创建或者导入新文件到工作区
touch "文件1"

#将文件工作目录的文件提交到暂存区
git add "文件1" #提交指定文件
git add -A #一次提交工作目录中的全部文件

#查看项目状态
git status


#第一次提交描述时需要设置账户信息
git config --global user.name "John Doe"
git config --global user.email johndoe@example.com

#添加描述
git commit -m "此次添加的描述信息"

#查看项目状态
git status
output:
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)

#将修改从暂存区提交到远程分支
git push origin master

删除已经提交到github上面的文件

1
2
3
4
5
6
7
8
9
#在本地拉取远程分支
git pull original master

#在本地删除对应的文件
git rm filename

#添加描述上传到远程分支
git commit -m "删除文件filename"
git push original master

已经提交到github上的文件进行版本回退

1
2
3
4
5
6
7
8
9
10
#先通过git log获取版本号
git log

#然后使用git revert 版本号来回退到指定版本
git retvert 版本号

#然后:x保存退出就可以了撤回到指定的版本了

#最后再将本地分支push到github上
git push origin master

分支切换

1
2
3
4
5
6

git checkout ... #创建分支

git checkout -b ... #创建并且换到分支

git checkout ... #切换到分支
1
2
3
4
5
git branch #查看本地分支

git branch -a #查看本地和远程的全部分支

git push origin --delete dev2 #删除远程分支

​ pyspark的ML软件包主要用于针对spark dataframe的建模(MLlib主要还是针对RDD,准备废弃),ML包主要包含了转化器Transformer评估器Estimater管道Pipline三个部分。

1.转化器

​ 转换器通常通过将一个新列附加到DataFrame来转化数据,每个转化器都必须实现.transform()方法。

使用在预处理截断

.transorm()方法常用的参数:

​ 1.dataframe 这是唯一一个强制性参数(也可以不理解为参数)

​ 2.inputCol 输入列名

​ 3.outputCol 输出列名

​ 要使用转化器首先需要引入宝feature

1
from pyspark.ml.feature import ...

(1)Binarizer

​ 根据指定阈值将连续变量进行二值化

注:这里需要输入的那一列的数据类型为DoubleType,InterType和FloatType都不支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
df = spark.createDataFrame([[2.0,'a'],[1.0,'b'],[4.0,'b'],[9.0,'b'],[4.3,'c']],schema=schema)
binarizer = Binarizer(threshold=4.0,inputCol='id',outputCol='binarizer_resulit')
binarizer.transform(df).show()

output:
+---+---+-----------------+
| id|age|binarizer_resulit|
+---+---+-----------------+
|2.0| a| 0.0|
|1.0| b| 0.0|
|4.0| b| 0.0| #当值与阈值相同的时候向下取
|9.0| b| 1.0|
|4.3| c| 1.0|
+---+---+-----------------+

(2)Bucketizer

​ 根据阈值列表将连续变量值离散化

注:splits一定要能包含该列所有值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
df = spark.createDataFrame([[2.0,'a'],[1.0,'b'],[4.0,'b'],[9.0,'b'],[4.3,'c']],schema=schema)
bucketizer = Bucketizer(splits=[0,2,4,10],inputCol='id',outputCol='bucketizer_result')
bucketizer.setHandleInvalid("keep").transform(df).show()

output:
+---+---+-----------------+
| id|age|bucketizer_result|
+---+---+-----------------+
|2.0| a| 1.0|
|1.0| b| 0.0|
|4.0| b| 2.0|
|9.0| b| 2.0|
|4.3| c| 2.0|
+---+---+-----------------+

(3)QuantileDiscretizer

​ 根据数据的近似分位数来将离散变量转化来进行离散化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
df = spark.createDataFrame([[2.0,'a'],[1.0,'b'],[4.0,'b'],[9.0,'b'],[4.3,'c']],schema=schema)
quantile_discretizer = QuantileDiscretizer(numBuckets=3,inputCol='id',outputCol='quantile_discretizer_result')
bucketizer.setHandleInvalid("keep").transform(df).show()

output:
+---+---+-----------------+
| id|age|bucketizer_result|
+---+---+-----------------+
|2.0| a| 1.0|
|1.0| b| 0.0|
|4.0| b| 2.0|
|9.0| b| 2.0|
|4.3| c| 2.0|
+---+---+-----------------+

(4)Ngram

​ 将一个字符串列表转换为ngram列表,以空格分割两个词,一般要先使用算法来先分词,然后再进行n-gram操作。

注:1.空值将被忽略,返回一个空列表

​ 2.输入的列必须为一个ArrayType(StringType())

1
2
3
4
5
6
7
df = spark.createDataFrame([
[['a','b','c','d','e']],
[['s','d','u','y']]
],['word'])

ngram = NGram(n=2,inputCol="word",outputCol="ngram_result")
ngram.transform(df).show()

(5)RegexTokener

​ 正则表达式分词器,用于将一个字符串根据指定的正则表达式来进行分词。

参数包括:

​ pattern:用于指定分词正则表达式,默认为遇到任何空白字符则分词

​ minTokenLength: 最小分词长度过滤,小于这个长度则过滤掉

1
2


(6)VectorIndexer

​ VectorIndexer是对数据集特征向量中的类别(离散值)特征进行编号。它能够自动判断那些特征是离散值型的特征,并对他们进行编号,具体做法是通过设置一个maxCategories,特征向量中某一个特征不重复取值个数小于maxCategories,则被重新编号为0~K(K<=maxCategories-1)。某一个特征不重复取值个数大于maxCategories,则该特征视为连续值,不会重新编号

主要作用:提升决策树、随机森林等ML算法的效果

参数:

​ 1.MaxCategories 是否被判为离散类型的标准

​ 2.inputCol 输入列名

​ 3.outputCol 输出列名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+-------------------------+-------------------------+
|features |indexedFeatures |
+-------------------------+-------------------------+
|(3,[0,1,2],[2.0,5.0,7.0])|(3,[0,1,2],[2.0,1.0,1.0])|
|(3,[0,1,2],[3.0,5.0,9.0])|(3,[0,1,2],[3.0,1.0,2.0])|
|(3,[0,1,2],[4.0,7.0,9.0])|(3,[0,1,2],[4.0,3.0,2.0])|
|(3,[0,1,2],[2.0,4.0,9.0])|(3,[0,1,2],[2.0,0.0,2.0])|
|(3,[0,1,2],[9.0,5.0,7.0])|(3,[0,1,2],[9.0,1.0,1.0])|
|(3,[0,1,2],[2.0,5.0,9.0])|(3,[0,1,2],[2.0,1.0,2.0])|
|(3,[0,1,2],[3.0,4.0,9.0])|(3,[0,1,2],[3.0,0.0,2.0])|
|(3,[0,1,2],[8.0,4.0,9.0])|(3,[0,1,2],[8.0,0.0,2.0])|
|(3,[0,1,2],[3.0,6.0,2.0])|(3,[0,1,2],[3.0,2.0,0.0])|
|(3,[0,1,2],[5.0,9.0,2.0])|(3,[0,1,2],[5.0,4.0,0.0])|
+-------------------------+-------------------------+
结果分析:特征向量包含3个特征,即特征0,特征1,特征2。如Row=1,对应的特征分别是2.0,5.0,7.0.被转换为2.0,1.0,1.0
我们发现只有特征1,特征2被转换了,特征0没有被转换。这是因为特征06中取值(234589),多于前面的设置setMaxCategories(5)
,因此被视为连续值了,不会被转换。
特征1中,(4,5,6,7,9)-->(0,1,2,3,4,5)
特征2中, (2,7,9)-->(0,1,2)

(7)StringIndexer

​ 将label标签进行重新设置,出现的最多的标签被设置为0,最少的设置最大。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
按label出现的频次,转换成0~num numOfLabels-1(分类个数),频次最高的转换为0,以此类推:
label=3,出现次数最多,出现了4次,转换(编号)为0
其次是label=2,出现了3次,编号为1,以此类推
+-----+------------+
|label|indexedLabel|
+-----+------------+
|3.0 |0.0 |
|4.0 |3.0 |
|1.0 |2.0 |
|3.0 |0.0 |
|2.0 |1.0 |
|3.0 |0.0 |
|2.0 |1.0 |
|3.0 |0.0 |
|2.0 |1.0 |
|1.0 |2.0 |
+-----+------------+

(8)StringToIndex

​ 功能与StringIndexer完全相反,用于使用StringIndexer后的标签进行训练后,再将标签对应会原来的标签

作用:恢复StringIndexer之前的标签

参数:

​ 1.inputCol 输入列名

​ 2.outputCol 输出列名

1
2
3
4
5
6
7
8
9
10
11
12
|label|prediction|convetedPrediction|
+-----+----------+------------------+
|3.0 |0.0 |3.0 |
|4.0 |1.0 |2.0 |
|1.0 |2.0 |1.0 |
|3.0 |0.0 |3.0 |
|2.0 |1.0 |2.0 |
|3.0 |0.0 |3.0 |
|2.0 |1.0 |2.0 |
|3.0 |0.0 |3.0 |
|2.0 |1.0 |2.0 |
|1.0 |2.0 |1.0 |

2.评估器

​ 评估器就是机器学习模型,通过统计数据从而进行预测工作,每个评估器都必须实现.fit主要分为分类和回归两大类,这里只针对分类评估器进行介绍。

​ Pyspark的分类评估器包含以下七种:

1.LogisticRegression

​ 逻辑回归模型

2.DecisionTreeClassifier

​ 决策树模型

3.GBTClassifier

​ 梯度提升决策树

4.RandomForestClassifier

​ 随机森林

5.MultilayerPerceptronClassifier

​ 多层感知机分类器

1.select

​ select用于列选择,选择指定列,也可以用于和udf函数结合新增列

列选择

1
2
data_set.select("*").show()		#选择全部列
data_set.select("file_name","webshell").show() #选择file_name,webshell列

与udf函数组合新增列

1
2
3
4
5
6
from pysaprk.sql.function import udf
def sum(col1,col2):
return col1+col2
udf_sun = udf(sum,IntergerType())

data_set.select("*",udf_sum("a1","a2")).show() #新增一列a1和a2列加和后的列

2.filter

​ filter用于行选择,相当于使用前一半写好的sql语句进行查询。

​ 查询某个列等于固定值

1
data_set.filter("file_name=='wp-links-opml.php'").show()

​ 查询符合某个正则表达式所有行:​

1
data_set.filter("file_name regexp '\.php$')		#选择所有.php结尾文件

3.join

​ pyspark的dataframe横向连接只能使用join进行连接,需要有一列为两个dataframe的共有列

1
df_join = df1.join(df,how="left",on='id').show() 	#id为二者的共有列

4.agg

​ 使用agg来进行不分组聚合操作,获得某些统计项

1
2
3
4
5
6
7
#获取数据中样本列最大的数值
#方式一:
df.agg({"id":"max"}).show()

#方式二:
import pyspark.sql.functions as fn
df.agg(F.min("id")).show()

5.groupby

​ 使用groupby可以用来进行分组聚合。

1
df.groupby(")

6.printSchema

​ 以数的形式显示dataframe的概要

1
2
3
4
5
6
df.printSchema()

output:
root
|-- id: integer (nullable = true)
|-- age: integer (nullable = true)

7.subtract

​ 找到那些在这个dataframe但是不在另一个dataframe中的行,返回一个dataframe

1
2
3
4
df = spark.createDataFrame([[2.0,'a'],[1.0,'b'],[4.0,'b'],[9.0,'b'],[4.3,'c']],schema=schema)
df1 = spark.createDataFrame([[2.0,'a'],[4.3,'c']],schema=schema)

df.subtract(df1).show() #找到在df中但是不在df1中的行

8.cast

​ 指定某一列的类型

1
2


9.sort

​ 按照某列来进行排序

参数:

​ columns: 可以是一列,也可以是几列的列表

​ ascending:升序,默认是True

1
data.sort(['count'],ascending=False).show()

​ 这一节主要讲的是spark在机器学习处理过程中常用的一列操作,包括获得各种预处理。

1.将多列转化成一列

​ pyspark可以直接使用VectorAssembler来将多列数据直接转化成vector类型的一列数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from pyspark.ml.feature import VectorAssembler

discretization_feature_names = [
'discretization_tag_nums',
'discretization_in_link_nums',
'discretization_out_link_nums',
'discretization_style_nums',
'discretization_local_img_nums',
'discretization_out_img_nums',
'discretization_local_script_nums',
'discretization_in_script_nums',
'discretization_out_script_nums'
]

vecAssembler = VectorAssembler(inputCols=discretization_feature_names, outputCol="feature_vec_new")

data_set = vecAssembler.transform(dataset) #会返回一个在原有的dataframe上面多出来一列的新dataframe

data_set.printscheama()

output:
root
|-- discretization_tag_nums: double (nullable = true)
|-- discretization_in_link_nums: double (nullable = true)
|-- discretization_out_link_nums: double (nullable = true)
|-- discretization_style_nums: double (nullable = true)
|-- discretization_local_img_nums: double (nullable = true)
|-- discretization_out_img_nums: double (nullable = true)
|-- discretization_local_script_nums: double (nullable = true)
|-- discretization_in_script_nums: double (nullable = true)
|-- discretization_out_script_nums: double (nullable = true)
|-- feature_vec_new: vector (nullable = true) #多出来的

2.连续数据离散化

​ pyspark中提供QuantileDiscretizer来根据分位点来进行离散化的操作,可以根据数据整体情况来对某一列进行离散化。

常用参数:

​ numBuckets:将整个空间分为几份,在对应的分为点处将数据进行切分

​ relativeError:

​ handleInvalid:

1
2
3
4
5
6
7
8
from pyspark.ml.feature import QuantileDiscretizer


qds = QuantileDiscretizer(numBuckets=3,inputCol=inputCol, outputCol=outputCol, relativeError=0.01, handleInvalid="error")

#这里的setHandleInvalid是代表对缺失值如何进行处理
#keep表示保留缺失值
dataframe = qds.setHandleInvalid("keep").fit(dataframe).transform(dataframe)

3.增加递增的id列

​ monotonically_increasing_id() 方法给每一条记录提供了一个唯一并且递增的ID。

1
2
3
from pyspark.sql.functions import monotonically_increasing_id

df.select("*",monotonically_increasing_id().alias("id")).show()

4.指定读取或创建dataframe各列的类型

​ pyspark可以支持使用schema创建StructType来指定各列的读取或者创建时的类型,一个StructType里面包含多个StructField来进行分别执行列名、类型、是否为空。

1
2
3
4
5
6
7
8
9
10
11
12
13
from pyspark.sql.types import *
schema = StructType([
StructField("id",IntegerType(),True),
StructField("name",StringType(),True)
])

df = spark.createDataFrame([[2,'a'],[1,'b']],schema)
df.printSchema()

output:
root
|-- id: integer (nullable = true)
|-- name: string (nullable = true)

5.查看各类缺失值情况

1
2
3
import pyspark.sql.functions as fn

data_set.agg(*[(1-(fn.count(i)/fn.count('*'))).alias(i+"_missing") for i in data_set.columns]).show()

​ 注:在其中agg()里面的 ”\“代表将该列表处理为一组独立的参数传递给函数

6.使用时间窗口来进行分组聚合

这也是pyspark比pandas多出来的一个时间窗口聚合的使用

1
2
src_ip_feature = feature_data.groupby("srcIp",F.window("time", "60 seconds")).agg(
F.count("distIp").alias("request_count"),

7.过滤各种空值

(1)过滤字符串类型的列中的某列为null行

​ 这里要借助function中的isnull函数来进行

1
2
3
4
5
import pyspark.sql.function as F

df.filter(F.isnull(df["response_body"])).show() #只留下response_body列为null的

df.filter(~F.isnull(df["response_body"])).show() #只留下response_body列不为null的

8.列名重命名

​ 借助selectExpr可以实现在select的基础上使用sql表达式来进行进一步的操作这一特性,将列名进行修改

1
2
#将count列重命名为no_detection_nums,webshelll_names列名不变
df = df.selectExpr("webshell_names","count as no_detection_nums")

HMM

一、HMM五元素

​ 其中:

​ N:隐藏状态数 hidden states

​ M:观测状态数 observed states

​ A: 状态转移矩阵 transition matrix

​ B:发射矩阵 emission matrix

​ pi:初始隐状态向量 initial state vector

HMM全称隐马尔科夫链,常用与异常检测,在大量正常的模式中找出异常的模式。

​ 隐马尔科夫链模型相关的问题主要分为三类:

1.已知隐含状态数量、隐含状态的转换矩阵、根据可见的状态链,求出隐藏的状态链

2.已知隐含状态数量、隐含状态的转换矩阵、根据可见的状态链,求得出这个可见状态链的概率

3.已知隐含状态数量、可以观察到多个可见状态链,求因此状态的转移矩阵和发射概率

1.求隐藏状态链问题

​ 该问题是在:已知隐含状态数量、隐含状态的转换矩阵、根据可见的状态链,求出隐藏的状态链(也就是最大概率的转移序列)

应用场景:语音识别解码问题

方法Viterbi algorithm

时间复杂度:O(m*n^2)

m为时间序列的长度,n为每个时间点可能对应的状态数

​ 举例来说,我知道我有三个骰子,六面骰,四面骰,八面骰。我也知道我掷了十次的结果(1 6 3 5 2 7 3 5 2 4),我不知道每次用了那种骰子,我想知道最有可能的骰子序列。

​ 首先,如果我们只掷一次骰子:看到结果为1.对应的最大概率骰子序列就是D4,因为D4产生1的概率是1/4,高于1/6和1/8.

​ 把这个情况拓展,我们掷两次骰子:结果为1,6.这时问题变得复杂起来,我们要计算三个值,分别是第二个骰子是D6,D4,D8的最大概率。显然,要取到最大概率,第一个骰子必须为D4。这时,第二个骰子取到D6的最大概率是
img
img

​ 同样的,我们可以计算第二个骰子是D4或D8时的最大概率。我们发现,第二个骰子取到D6的概率最大。而使这个概率最大时,第一个骰子为D4。所以最大概率骰子序列就是D4 D6。
​ 继续拓展,我们掷三次骰子:同样,我们计算第三个骰子分别是D6,D4,D8的最大概率。我们再次发现,要取到最大概率,第二个骰子必须为D6。这时,第三个骰子取到D4的最大概率是img
img
​ 同上,我们可以计算第三个骰子是D6或D8时的最大概率。我们发现,第三个骰子取到D4的概率最大。而使这个概率最大时,第二个骰子为D6,第一个骰子为D4。所以最大概率骰子序列就是D4 D6 D4。

写到这里,大家应该看出点规律了。既然掷骰子一二三次可以算,掷多少次都可以以此类推。

​ 我们发现,我们要求最大概率骰子序列时要做这么几件事情。首先,不管序列多长,要从序列长度为1算起,算序列长度为1时取到每个骰子的最大概率。然后,逐渐增加长度,每增加一次长度,重新算一遍在这个长度下最后一个位置取到每个骰子的最大概率。因为上一个长度下的取到每个骰子的最大概率都算过了,重新计算的话其实不难。当我们算到最后一位时,就知道最后一位是哪个骰子的概率最大了。然后,我们要把对应这个最大概率的序列从后往前推出来,这就是Viterbi算法。

2.求得出某个可见状态链的概率

​ 该问题是在:已知隐含状态数量、隐含状态的转换矩阵、根据可见的状态链,求得出这个可见状态链的概率

应用场景:检测观察到的结果与我们已知的模型是否吻合,即异常检测

方法:前向算法(forward algorithm)

要算用正常的三个骰子掷出这个结果的概率,其实就是将所有可能情况的概率进行加和计算(即在当前的HMM下可能出啊先找个状态链的概率)。同样,简单而暴力的方法就是把穷举所有的骰子序列,还是计算每个骰子序列对应的概率,但是这回,我们不挑最大值了,而是把所有算出来的概率相加,得到的总概率就是我们要求的结果。这个方法依然不能应用于太长的骰子序列(马尔可夫链)。
​ 我们会应用一个和前一个问题类似的解法,只不过前一个问题关心的是概率最大值,这个问题关心的是概率之和。解决这个问题的算法叫做前向算法(forward algorithm)
​ 首先,如果我们只掷一次骰子,看到结果为1.产生这个结果的总概率可以按照如下计算,总概率为0.18:

​ 把这个情况拓展,我们掷两次骰子,看到结果为1,6,总概率为0.05:

​ 继续拓展,我们掷三次骰子,看到结果为1,6,3,计算总概率为0.03:

​ 同样的,我们一步一步的算,有多长算多长,再长的马尔可夫链总能算出来的。用同样的方法,也可以算出不正常的六面骰和另外两个正常骰子掷出这段序列的概率,然后我们比较一下这两个概率大小,就能知道你的骰子是不是被人换了。

3. 求状态转移矩阵和发射概率(训练过程)

​ 该问题是在: 已知隐含状态数量、可以观察到多个可见状态链

应用场景:有大量该问题的已知观测序列,想训练一个HMM模型

方法:Baum-Welch算法

面试常见问题

1.HMM的两个不合理假设

1.当前时刻的状态只与上一时刻的状态有关

2.当前表现只与当前状态有关

2.MEMM(最大熵马尔科夫模型)对HMM做了哪些改进还存在哪些问题?

虽然MEMM解决了HMM输出独立性假设的问题,但是只解决了观察值独立的问题,状态之间的假设则是标注偏置问题产生的根源,CRF则解决了标注偏置问题,是HMM模型的进一步优化。

缺陷:标注偏置问题

HMM:

MEMM

2.CRF和HMM和MEMM的不同点

整体来说就是解决了MEMM的标注偏置的问题、去除了HMM中两个不合理的假设

1.HMM是生成式模型,CRF是一种判别式模型。HMM对状态矩阵和发射矩阵进行直接建模,统计共同出现的概率,因此是一种生成式模型;

2.HMM是有向图模型,CRF是无向图模型。

3.CRF是全局最优解,结局了MEMM的标注偏置问题。MEMM是对转移概率和表现概率建立联合概率,统计时统计的是条件概率,由于其只在局部做归一化,所以容易陷入局部最优。

​ CRF是在全局范围内统计归一化的概率,而不像是MEMM在局部统计归一化概率。是全局最优的解。解决了MEMM中标注偏置的问题。

4.但是CRF的训练代价大,是复杂度高