一、K-近邻算法简介
1.概念:就是通过你的"邻居"来判断你属于哪个类别。
2.算法思想:一个样本与数据集中的k个样本最相似,如果这k个样本中的大多数属于某一个类别,则认为该样本也属于此类别。
3.实现流程:
- 计算已知类别数据集中的点与当前点之间的距离
- 按距离递增次序排序
- 选取与当前点距离最小的k个点
- 统计前k个点所在的类别出现的频率
- 返回前k个点出现频率最高的类别作为当前点的预测分类
二、K-近邻算法api介绍
1.Scikit-learn工具
- 包含分类、聚类、回归等算法
- 特征工程
- 模型选择、调优
安装:pip install scikit-learn
注:安装scikit-learn需要Numpy, Scipy等库
2.K-近邻算法api简单使用
# 1.导入模块
from sklearn.neighbors import KNeighborsClassifier
# 2.构造数据集
x = [[0], [1], [2], [3]]
y = [0, 0, 1, 1]
# 3.机器学习 -- 模型训练
# 3.1实例化API
estimator = KNeighborsClassifier(n_neighbors=2)
# 3.2使用fit方法进行训练
estimator.fit(x, y)
# 3.3预测结果
estimator.predict([[1]])
三、距离度量
3.1 欧式距离(Euclidean Distance)
欧氏距离是最容易直观理解的距离度量方法,两个点在空间中的距离一般都是指欧氏距离。通过距离平方进行计算
二
维
平
面
上
点
a
(
x
1
,
y
1
)
与
b
(
x
2
,
y
2
)
间
的
欧
氏
距
离
:
d
12
=
(
x
1
−
x
2
)
2
+
(
y
1
−
y
2
)
2
{二维平面上点a(x_1,y_1)与b(x_2,y_2)间的欧氏距离:} d_{12} = \sqrt{(x_1-x_2)^2+(y_1-y_2)^2 }
二维平面上点a(x1,y1)与b(x2,y2)间的欧氏距离:d12=(x1−x2)2+(y1−y2)2
三
维
点
a
(
x
1
,
y
1
,
z
1
)
与
b
(
x
2
,
y
2
,
z
2
)
欧
氏
距
离
d
12
=
(
x
1
−
x
2
)
2
+
(
y
1
−
y
2
)
2
+
(
z
1
−
z
2
)
2
{三维点a(x_1,y_1,z_1)与b(x_2,y_2,z_2)欧氏距离} d_{12} = \sqrt{(x_1-x_2)^2+(y_1-y_2)^2 +(z_1-z_2)^2}
三维点a(x1,y1,z1)与b(x2,y2,z2)欧氏距离d12=(x1−x2)2+(y1−y2)2+(z1−z2)2
n
维
点
a
(
x
11
,
x
12
,
.
.
.
,
x
1
n
)
与
b
(
x
21
,
x
22
,
.
.
.
,
x
2
n
)
欧
氏
距
离
d
12
=
∑
k
=
1
n
(
x
1
k
−
x
2
k
)
2
{n维点a(x_{11},x_{12},...,x_{1n})与b(x_{21},x_{22},...,x_{2n})欧氏距离} d_{12} = \sqrt{\sum_{k=1}^n(x_{1k}-x_{2k})^2}
n维点a(x11,x12,...,x1n)与b(x21,x22,...,x2n)欧氏距离d12=k=1∑n(x1k−x2k)2
举例:
X=[[1,1],[2,2],[3,3],[4,4]];
经计算得:
d = 1.4142 2.8284 4.2426 1.4142 2.8284 1.4142
3.2 曼哈顿距离(Manhattan Distance)
曼哈顿距离也称为“城市街区距离”(City Block distance)。通过距离的绝对值进行计算
二
维
平
面
上
点
a
(
x
1
,
y
1
)
与
b
(
x
2
,
y
2
)
间
的
曼
哈
顿
距
离
:
d
12
=
∣
x
1
−
x
2
∣
+
∣
y
1
−
y
2
∣
{二维平面上点a(x_1,y_1)与b(x_2,y_2)间的曼哈顿距离:} d_{12} =|x_1-x_2|+|y_1-y_2|
二维平面上点a(x1,y1)与b(x2,y2)间的曼哈顿距离:d12=∣x1−x2∣+∣y1−y2∣
n
维
空
间
点
a
(
x
11
,
x
12
,
.
.
.
,
x
1
n
)
与
b
(
x
21
,
x
22
,
.
.
.
,
x
2
n
)
曼
哈
顿
距
离
d
12
=
∑
k
=
1
n
(
x
1
k
−
x
2
k
)
{n维空间点a(x_{11},x_{12},...,x_{1n})与b(x_{21},x_{22},...,x_{2n})曼哈顿距离} d_{12} = \sum_{k=1}^n(x_{1k}-x_{2k})
n维空间点a(x11,x12,...,x1n)与b(x21,x22,...,x2n)曼哈顿距离d12=k=1∑n(x1k−x2k)
举例:
X=[[1,1],[2,2],[3,3],[4,4]];
经计算得:
d = 2 4 6 2 4 2
3.3 切比雪夫距离 (Chebyshev Distance)
通过维度的最大值进行计算
二
维
平
面
上
点
a
(
x
1
,
y
1
)
与
b
(
x
2
,
y
2
)
间
的
切
比
雪
夫
距
离
:
d
12
=
max
(
∣
x
1
−
x
2
∣
,
∣
y
1
−
y
2
∣
)
{二维平面上点a(x_1,y_1)与b(x_2,y_2)间的切比雪夫距离:} d_{12} =\max(|x_1-x_2|,|y_1-y_2|)
二维平面上点a(x1,y1)与b(x2,y2)间的切比雪夫距离:d12=max(∣x1−x2∣,∣y1−y2∣)
n
维
空
间
点
a
(
x
11
,
x
12
,
.
.
.
,
x
1
n
)
与
b
(
x
21
,
x
22
,
.
.
.
,
x
2
n
)
切
比
雪
夫
距
离
d
12
=
max
(
∣
x
1
i
−
x
2
i
∣
)
{n维空间点a(x_{11},x_{12},...,x_{1n})与b(x_{21},x_{22},...,x_{2n})切比雪夫距离} d_{12} = \max(|x_{1i}-x_{2i}|)
n维空间点a(x11,x12,...,x1n)与b(x21,x22,...,x2n)切比雪夫距离d12=max(∣x1i−x2i∣)
举例:
X=[[1,1],[2,2],[3,3],[4,4]];
经计算得:
d = 1 2 3 1 2 1
3.4 闵可夫斯基距离(Minkowski Distance)
闵氏距离不是一种距离,而是一组距离的定义,是对多个距离度量公式的概括性的表述。
n
维
空
间
点
a
(
x
11
,
x
12
,
.
.
.
,
x
1
n
)
与
b
(
x
21
,
x
22
,
.
.
.
,
x
2
n
)
切
比
雪
夫
距
离
d
12
=
∑
k
=
1
n
(
x
1
k
−
x
2
k
)
p
p
{n维空间点a(x_{11},x_{12},...,x_{1n})与b(x_{21},x_{22},...,x_{2n})切比雪夫距离} d_{12} = \sqrt[p]{\sum_{k=1}^n(x_{1k}-x_{2k})^p}
n维空间点a(x11,x12,...,x1n)与b(x21,x22,...,x2n)切比雪夫距离d12=pk=1∑n(x1k−x2k)p
其中p是一个变参数:
当p=1时,就是曼哈顿距离;
当p=2时,就是欧氏距离;
当p→∞时,就是切比雪夫距离。
- 注:前面四个距离公式都是把单位相同看待了,所以计算过程不是很科学
3.5 标准化欧氏距离 (Standardized Euclidean Distance)
在计算过程中添加了标准差,对不同维度的量刚进行处理
标
准
化
欧
氏
距
离
公
式
:
d
12
=
∑
k
=
1
n
(
x
1
k
−
x
2
k
S
k
)
2
,
S
表
示
标
准
差
{标准化欧氏距离公式:} d_{12} = \sqrt{\sum_{k=1}^n(\frac{x_{1k}-x_{2k}}{S_k})^2} { \qquad, S表示标准差}
标准化欧氏距离公式:d12=k=1∑n(Skx1k−x2k)2,S表示标准差
3.6 余弦距离(Cosine Distance)
几何中,夹角余弦可用来衡量两个向量方向的差异;机器学习中,借用这一概念来衡量样本向量之间的差异。
二
维
向
量
A
(
x
1
,
y
1
)
与
向
量
B
(
x
2
,
y
2
)
的
夹
角
余
弦
公
式
:
cos
θ
=
x
1
x
2
+
y
1
y
2
x
1
2
+
y
1
2
x
2
2
+
y
2
2
{二维向量A(x_1,y_1)与向量B(x_2,y_2)的夹角余弦公式:} \cos\theta = \frac{x_1x_2+y_1y_2}{\sqrt{x_1^2+y_1^2}\sqrt{x_2^2+y_2^2}}
二维向量A(x1,y1)与向量B(x2,y2)的夹角余弦公式:cosθ=x12+y12x22+y22x1x2+y1y2
n
维
向
量
A
(
x
11
,
x
12
,
.
.
.
,
x
1
n
)
与
B
(
x
21
,
x
22
,
.
.
.
,
x
2
n
)
:
cos
θ
=
∑
k
=
1
n
x
1
k
x
2
k
∑
k
=
1
n
x
1
k
2
∑
k
=
1
n
x
2
k
2
{n维向量A(x_{11},x_{12},...,x_{1n})与B(x_{21},x_{22},...,x_{2n}):} \cos\theta = \frac{\sum_{k=1}^{n}x_{1k}x_{2k}}{\sqrt{\sum_{k=1}^{n}x_{1k}^2}\sqrt{\sum_{k=1}^{n}x_{2k}^2}}
n维向量A(x11,x12,...,x1n)与B(x21,x22,...,x2n):cosθ=∑k=1nx1k2∑k=1nx2k2∑k=1nx1kx2k
夹角余弦取值范围为[-1,1]。余弦越大表示两个向量的夹角越小,余弦越小表示两向量的夹角越大。当两个向量的方向重合时余弦取最大值1,当两个向量的方向完全相反余弦取最小值-1。
举例:
X=[[1,1],[1,2],[2,5],[1,-4]]
经计算得:
d = 0.9487 0.9191 -0.5145 0.9965 -0.7593 -0.8107
四、k值的选择
-
K值过小:
- 容易受到异常点的影响
- 近似误差会减小,估计误差会增大
- K值的减小就意味着整体模型变得复杂,容易发生过拟合
-
k值过大:
- 受到样本均衡的问题
- 近似误差会增大,估计误差会减小
- K值的增大就意味着整体的模型变得简单,容易发生欠拟合
-
采用交叉验证法(简单来说,就是把训练数据再分成两组:训练集和验证集)来选择最优的K值
近似误差:对现有训练集的训练误差,关注训练集,在训练集上表现好,测试集表现不好。模型本身不是最接近最佳模型。
估计误差:可以理解为对测试集的测试误差,关注测试集,估计误差小说明对未知数据的预测能力好,模型本身最接近最佳模型。
五、kd树
5.1 kd树简介
1. 什么是kd树
根据KNN每次需要预测一个点时,我们都需要计算训练数据集里每个点到这个点的距离,然后选出距离最近的k个点进行投票。当数据集很大时,计算成本非常高,针对N个样本,D个特征的数据集,其算法复杂度为O(DN^2)。
kd树:为了避免每次都重新计算一遍距离,算法会把距离信息保存在一棵树里,这样在计算之前从树里查询距离信息,尽量避免重新计算。其基本原理是,如果A和B距离很远,B和C距离很近,那么A和C的距离也很远。有了这个信息,就可以在合适的时候跳过距离远的点。
这样优化后的算法复杂度可降低到O(DNlog(N))
2. kd树原理
- 树的构建
- 最近邻域搜索(Nearest-Neighbor Lookup)
kd树(K-dimension tree)是一种对k维空间中的实例点进行存储以便对其进行快速检索的树形数据结构。kd树是一种二叉树,表示对k维空间的一个划分,构造kd树相当于不断地用垂直于坐标轴的超平面将K维空间切分,构成一系列的K维超矩形区域。kd树的每个结点对应于一个k维超矩形区域。利用kd树可以省去对大部分数据点的搜索,从而减少搜索的计算量。
5.2 kd树的构建
(1)构造根结点,使根结点对应于K维空间中包含所有实例点的超矩形区域;
(2)通过递归的方法,不断地对k维空间进行划分,生成子结点;
(3)上述过程直到子区域内没有实例时终止(终止时的结点为叶结点);
(4)通常,循环的选择坐标轴对空间切分,选择训练实例点在坐标轴上的中位数为切分点,这样得到的kd树是平衡的.
好的维度划分方法是在数据比较分散的那一维进行划分(分散的程度可以根据方差来衡量)。好的数据划分方法可以使构建的树比较平衡,可以每次选择中位数来进行划分
示例: 给定一个二维空间数据集:T={(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)},构造一个平衡kd树。
根结点对应包含数据集T的矩形,选择x(1)轴,6个数据点的x(1)坐标中位数是6,这里选最接近的(7,2)点为根节点,以平面x(1)=7将空间分为左、右两个子矩形(子结点);接着左矩形以x(2)=4分为两个子矩形(左矩形中{(2,3),(5,4),(4,7)}点的x(2)坐标中位数正好为4),右矩形以x(2)=6分为两个子矩形,如此递归,最后得到如下图所示的特征空间划分和kd树。
5.3 最近邻域搜索
首先通过二叉树搜索(比较待查询节点和分裂节点的分裂维的值,小于等于就进入左子树分支,大于就进入右子树分支直到叶子结点),顺着“搜索路径”很快能找到最近邻的近似点,也就是与待查询点处于同一个子空间的叶子结点;
然后再回溯搜索路径,并判断搜索路径上的结点的其他子结点空间中是否可能有距离查询点更近的数据点,如果有可能,则需要跳到其他子结点空间中去搜索(将其他子结点加入到搜索路径)。
重复这个过程直到搜索路径为空。
示例: 查找点(2.1,3.1)
在(7,2)点测试到达(5,4),在(5,4)点测试到达(2,3),然后search_path中的结点为<(7,2),(5,4), (2,3)>,从search_path中取出(2,3)作为当前最佳结点nearest, dist为0.141;
然后回溯至(5,4),以(2.1,3.1)为圆心,以dist=0.141为半径画一个圆,并不和超平面y=4相交,如上图,所以不必跳到结点(5,4)的右子空间去搜索,因为右子空间中不可能有更近样本点了。
于是再回溯至(7,2),同理,以(2.1,3.1)为圆心,以dist=0.141为半径画一个圆并不和超平面x=7相交,所以也不用跳到结点(7,2)的右子空间去搜索。
至此,search_path为空,结束整个搜索,返回nearest(2,3)作为(2.1,3.1)的最近邻点,最近距离为0.141。
六、特征工程
6.1 特征预处理
1. 概念
通过一些转换函数将特征数据转换成更加适合算法模型的特征数据过程。
2. 归一化/标准化
特征的单位或者大小相差较大,或者某特征的方差相比其他的特征要大出几个数量级,容易影响(支配)目标结果,使得一些算法无法学习到其它的特征。
需要用到一些方法进行无量纲化,使不同规格的数据转换到同一规格
3. 特征预处理API
sklearn.preprocessing
6.2 归一化
1. 定义
通过对原始数据进行变换把数据映射到(默认为[0,1])之间
2. 公式
X
′
=
x
−
min
max
−
min
X
′
′
=
X
′
∗
(
m
x
−
m
i
)
+
m
i
X^\prime = \frac{x-\min}{\max-\min}{\qquad X{^\prime}{^\prime}=X^\prime\;*\;(mx-mi)\;+\;mi}
X′=max−minx−minX′′=X′∗(mx−mi)+mi
作用于每一列,max为一列的最大值,min为一列的最小值,
X''为最终结果,mx,mi分别为指定区间值默认mx为1,mi为0
3. API
sklearn.preprocessing.MinMaxScaler (feature_range=(0,1)… )
- MinMaxScalar.fit_transform(X)
- X:ndarray格式的数据
- 返回值:转换后的形状相同的array
4. 示例
# 归一化演示
data = pd.read_csv("./data/dating.txt")
print(data)
# 1、实例化一个转换器类
transfer = MinMaxScaler(feature_range=(2, 3))
# 2、调用fit_transform
data = transfer.fit_transform(data[['milage','Liters','Consumtime']])
print("最小值最大值归一化处理的结果:\n", data)
结果:
milage Liters Consumtime target
0 40920 8.326976 0.953952 3
1 14488 7.153469 1.673904 2
2 26052 1.441871 0.805124 1
3 75136 13.147394 0.428964 1
4 38344 1.669788 0.134296 1
最小值最大值归一化处理的结果:
[[2.43582641 2.58819286 2.53237967]
[2. 2.48794044 3. ]
[2.19067405 2. 2.43571351]
[3. 3. 2.19139157]
[2.3933518 2.01947089 2. ]]
5. 归一化总结
注意最大值最小值是变化的,另外,最大值与最小值非常容易受异常点影响,所以这种方法健壮性较差,只适合传统精确小数据场景
6.3 标准化
1. 定义
通过对原始数据进行变换把数据变换到均值为0,标准差为1范围内
2. 公式
X
′
=
x
−
min
S
作用于每一列,mean为平均值,S为标准差
X^\prime = \frac{x-\min}{S} \text{\qquad作用于每一列,mean为平均值,S为标准差}
X′=Sx−min作用于每一列,mean为平均值,S为标准差
如果出现异常点,由于具有一定数据量,少量的异常点对于平均值的影响并不大,从而方差改变较小。
3. API
sklearn.preprocessing.StandardScaler( )
- StandardScaler.fit_transform(X)
- X:ndarray格式的数据
- 返回值:转换后的形状相同的array
4. 示例
import pandas as pd
from sklearn.preprocessing import StandardScaler
# 标准化演示
data = pd.read_csv("./data/dating.txt")
print(data)
# 1、实例化一个转换器类
transfer = StandardScaler()
# 2、调用fit_transform
data = transfer.fit_transform(data[['milage','Liters','Consumtime']])
print("标准化的结果:\n", data)
print("每一列特征的平均值:\n", transfer.mean_)
print("每一列特征的方差:\n", transfer.var_)
结果:
milage Liters Consumtime target
0 40920 8.326976 0.953952 3
1 14488 7.153469 1.673904 2
2 26052 1.441871 0.805124 1
3 75136 13.147394 0.428964 1
4 38344 1.669788 0.134296 1
标准化的结果:
[[ 0.0947602 0.44990013 0.29573441]
[-1.20166916 0.18312874 1.67200507]
[-0.63448132 -1.11527928 0.01123265]
[ 1.77297701 1.54571769 -0.70784025]
[-0.03158673 -1.06346729 -1.27113187]]
每一列特征的平均值:
[3.8988000e+04 6.3478996e+00 7.9924800e-01]
每一列特征的方差:
[4.15683072e+08 1.93505309e+01 2.73652475e-01]
5. 标准化总结
在已有样本足够多的情况下比较稳定,适合现代嘈杂大数据场景。
七、案例:鸢尾花种类预测
""" 机器学习工作流程
1. 获取数据
2. 数据基本处理
3. 特征工程
4. 机器学习(模型训练)
5. 模型评估
"""
# 案例 鸢尾花数据案例
import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
# 1. 获取数据(鸢尾花数据)
iris = load_iris()
# 2. 数据基本处理,因为是内置数据,不用处理缺失值异常值问题
# 2.1 数据分割
# 得到 特征值训练集,特征值测试集,目标值训练集,目标值测试集
x_train, x_test, y_train, y_test = train_test_split(iris.data, iris.target, random_state=22, test_size=0.2)
# 3. 特征工程
# 3.1 实例化一个转换器 (标准化)
transfer = StandardScaler()
# 3.2 调用fit_transform()方法处理特征值
x_train = transfer.fit_transform(x_train)
x_test = transfer.fit_transform(x_test)
# 4. 机器学习(模型训练)
# 4.1 实例化一个模型 默认k为5
moder = KNeighborsClassifier()
# 4.2 模型训练 传入特征值和目标值的训练集数据
moder.fit(x_train, y_train)
# 5. 模型评估
# 5.1 输出预测值
y_pre = moder.predict(x_test)
num_true = np.sum(y_pre == y_test)
print("预测对的结果数量为:", num_true)
print("预测错的结果数量为:", y_test.shape[0] - num_true)
# 5.2 评估指标
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, \
cohen_kappa_score, classification_report
print("预测数据的准确率为:{:.4}%".format(accuracy_score(y_test, y_pre) * 100))
print("预测数据的精确率为:{:.4}%".format(precision_score(y_test, y_pre, average='macro') * 100))
print("预测数据的召回率为:{:.4}%".format(recall_score(y_test, y_pre, average='macro') * 100))
print("预测数据的F1-score为:{:.4}".format(f1_score(y_test, y_pre, average='macro')))
print("预测数据的Cohen's Kappa系数为:{:.4}".format(cohen_kappa_score(y_test, y_pre)))
print("预测数据的分类报告为:\n", classification_report(y_test, y_pre))
结果为:
预测对的结果数量为: 23
预测错的结果数量为: 7
预测数据的准确率为:76.67%
预测数据的精确率为:86.27%
预测数据的召回率为:83.33%
预测数据的F1-score为:0.8025
预测数据的Cohen's Kappa系数为:0.6477
预测数据的分类报告为:
precision recall f1-score support
0 1.00 1.00 1.00 6
1 0.59 1.00 0.74 10
2 1.00 0.50 0.67 14
accuracy 0.77 30
macro avg 0.86 0.83 0.80 30
weighted avg 0.86 0.77 0.76 30
八、交叉验证,网格搜索
8.1 交叉验证(Cross Validation)
1. 概念
交叉验证:将训练集数据,再划分为训练和验证集。然后经过n次(组)的测试,每次都更换不同的验证集。即得到n组模型的结果,取平均值作为最终结果。又称n折交叉验证。
2. 交叉验证目的
- 为了让被评估的模型更加准确可信
注意:交叉验证并不能提高模型的准确度
8.2 网格搜索(Grid Search)
1. 概念和目的
通常情况下,有很多参数是需要手动指定的(如K-近邻算法中的k值),这种叫超参数。但是手动过程繁杂,所以需要对模型预设几种超参数组合。每组超参数都采用交叉验证来进行评估。最后选出最优参数组合建立模型。
2. 交叉验证,网格搜索(模型选择与调优)API
sklearn.model_selection.GridSearchCV(estimator, param_grid=None,cv=None)
- estimator:估计器对象(模型对象)
- param_grid:估计器参数(dict),{“n_neighbors”:[1,3,5]}
- cv:指定n折交叉验证
结果分析:
best_score_:在交叉验证中验证的最好结果
best_estimator_:最好的参数模型
cv_results_:每次交叉验证后的验证集准确率结果和训练集准确率结果
九、模型参数调优
对上述案例进行模型参数调优,这里只展示调优部分的代码,其他代码不变
# 4.2 模型训练与调优——网格搜索和交叉验证
# 准备KNN要调的超参数
param_dict = {
# k值
"n_neighbors": [1, 3, 5, 7, 9],
# 算法
"algorithm": ['auto', 'ball_tree', 'kd_tree', 'brute'],
# 距离度量
"metric": ['euclidean', 'manhattan', 'chebyshev', 'minkowski'],
# 权重
"weights": ['distance', 'uniform']
}
estimator = GridSearchCV(moder, param_grid=param_dict, cv=3)
estimator.fit(x_train, y_train)
# 查看网格搜索结果
print("在交叉验证中验证的最好结果:\n", estimator.best_score_)
print("最好的参数模型:\n", estimator.best_estimator_)
# print("每次交叉验证后的准确率结果:\n", estimator.cv_results_)
# 5. 模型评估,使用最好的参数模型
best_moder = estimator.best_estimator_
# 5.1 输出预测值
y_pre = best_moder.predict(x_test)
结果:
在交叉验证中验证的最好结果:
0.975
最好的参数模型:
KNeighborsClassifier(metric='euclidean', weights='distance')
预测对的结果数量为: 24
预测错的结果数量为: 6
预测数据的准确率为:80.0%
预测数据的精确率为:87.5%
预测数据的召回率为:85.71%
预测数据的F1-score为:0.8322
预测数据的Cohen's Kappa系数为:0.6959
预测数据的分类报告为:
precision recall f1-score support
0 1.00 1.00 1.00 6
1 0.62 1.00 0.77 10
2 1.00 0.57 0.73 14
accuracy 0.80 30
macro avg 0.88 0.86 0.83 30
weighted avg 0.88 0.80 0.80 30