赞
踩
从这篇博客开始,我将比较系统的讲解机器学习的相关知识,博客内容来自本人看过的书籍,视频教程(占大多数),其它博客,以及自己在实际项目过程中的思考和总结。写博客的初衷是与大家一起交流技术,共同进步。因为本人水平有限,可能部分理解会有错误,还请读者发现之后可以及时告知,另外机器学习是一个比较大的领域,因此本系列博客会不断更新,欢迎参考阅读。
输入大量的学习资料(数据) 到机器学习算法中,产生一个模型,模型可以对输入的样例进行预测,得到预测的结果。图示如下:
数学 | 语文 | 英语 | 级别 |
---|---|---|---|
100 | 95 | 100 | 学神 |
90 | 90 | 90 | 学霸 |
85 | 85 | 85 | 优秀 |
80 | 75 | 70 | 普通 |
60 | 60 | 60 | 学渣 |
例如判断一张图像是猫还是狗,手写数字图像上的数字是几,一封邮件是否是垃圾邮件等
在机器学习算法方面,可以将来机器学习分为四类:监督学习,非监督学习,半监督学习,增强学习
给机器的训练数据拥有标记或者答案。例如手写数字图片,猫狗识别等。
给机器的训练数据没有标记信息。
一部分数据有标记或者答案,另一部分数据没有。这种情况更加常见
根据周围环境的情况,采取行动,根据采取行动的结果,学习行动方式。
增强学习非常适合于机器人,无人驾驶等领域,是比较前沿的研究方向。
预先确定好最终模型的形式,然后对参数进行不断的学习。例如预测房价,房屋面积为 x 1 x_1 x1,房间数量为 x 2 x_2 x2,房间距离地铁站为 x 3 x_3 x3,我们假设最终房价为 y = a x 1 + b x 2 + c x 3 y=ax_1+bx_2+cx_3 y=ax1+bx2+cx3。后期的任务就是根据训练任务确定参数 a,b,c。
特点:一旦学到了参数,就不在需要原有的数据集。
不对模型进行过多假设。
非参数不是没有参数,只是不把问题理解为找到预先确定模型的最佳参数的过程。
以一个股票预测的任务为例,早期的方法示意如下:
这种做法将数据集中的全部数据进行训练,训练得到的模型直接在真实环境中使用。
这会有一些问题:
(1) 首先,模型可能很差,导致预测的结果错误率比较高,在股市预测的例子中可能造成严重经济损失
(2)真实的label 有时候很难获得(例如银行评估贷款给某一用户的风险级别,要想评估风险,可能需要对用户在一定时间内的信用情况统计,需要很多时间)
改进之后的模式如下所示:
与之前不同的是,在训练的时候, 不再是将全部的数据都用于训练,而是采用将数据集分为训练用的部分和测试用的部分。这样做的好处是可以使用测试数据对模型的好坏进行判断,在模型进入真是的环境之前可以改进模型。这也是现在主流的方法。
训练用的部分叫做训练集,测试用的部分叫做测试集,一般而言,70-80%的数据用于训练,剩下的用于测试。 需要特别注意的是,在从数据集中分离训练集和测试集的时候,不能简单的前 70% 是训练集,其它是测试集。训练集和测试集中数据的类别应该包括所有类别,并且类别的分布具有随机性。我们还以猫狗识别为例,假设有1000张猫和狗的图像,测试集和训练集中都应该包括猫和狗的图片,不能全部都是猫或者狗,否则模型训练出来效果会不好;也不能出现下面的情况:训练集或者测试集前半部分都是猫的图像,后半部分都是狗。这样的后果是模型可能会学习到隐含的数据集中类别的顺序特征,而我们不希望模型的预测结果与样本在数据集中的位置有关,所以,类别的分布要具有随机性。
基于上面的问题,下面是将来数据集分为训练集和测试集的代码实现:
def train_test_split(x,y,test_size=0.2,random_seed=None): """ 进行训练集和数据集的分离 :param x: 要分离的x数据 np.array :param y: 要分离的y数据 np.array :param test_size: 测试集占用数据集的比重,范围:[0.0,1.0] :param random_seed: 【可选项】 随机数的种子。用于实现实验的可重复性 :return: 返回拆分之后的数据集。 """ assert x.shape[0]==y.shape[0],\ "the size of x must be equal to the y" assert 0.0<=test_size<=1.0,\ "the test_size must be in the range of [0.0,1.0]" if random_seed: np.random.seed(random_seed) # 洗牌功能,让样本的 label类别分布具有随机性 shuffer_indexs=np.random.permutation(len(x)) test_num=int(test_size*len(x)) # 取前test_num 的数据作为测试集 x_test=x[shuffer_indexs[:test_num]] x_train=x[shuffer_indexs[test_num:]] y_test=y[shuffer_indexs[:test_num]] y_train=y[shuffer_indexs[test_num:]] return x_train,x_test,y_train,y_test
计算方式如下:
a
c
c
u
r
a
c
y
=
分
类
正
确
的
样
本
数
量
样
本
的
总
数
accuracy=\frac{分类正确的样本数量}{样本的总数}
accuracy=样本的总数分类正确的样本数量
代码实现如下(python):
import numpy as np
def accuracy_score(y_true,y_predict):
"""
检查最终的准确度
:param y_true: 正确的标签数组
:param y_predict: 预测出来的标签结果数组
:return: 返回预测成功的准确度
"""
assert y_true.shape[0]==y_predict.shape[0],\
"the size of the y_true must be equal to the y_predict"
return np.sum(y_true==y_predict)/len(y_predict)
对于简单的线性回归任务,主要有 MAE,MSE,RMSE, R 2 R^2 R2 等评估方法
以上四种评估方式的实现代码如下:
def mean_squared_error(y_true,y_predict): """ 根据准确值和预测值,计算均方误差并返回 :param y_true: 准确值 :param y_predict: 模型的预测结果 :return: 返回的均方误差 """ assert y_true.shape[0]==y_predict.shape[0],\ "the size of the y_true and the y_predict must be equal" return np.sum((y_true-y_predict)**2) / len(y_true) def root_mean_sqiared_error(y_true,y_predict): """ 根据准确值和预测值,计算均方根误差并返回 :param y_true: 准确值 :param y_predict: 模型的预测值 :return: 返回均方根误差 """ assert y_true.shape[0] == y_predict.shape[0],\ "the size of the y_true and the y_predict must be equal" return sqrt(np.sum((y_true-y_predict)**2) / len(y_true)) def mean_absolute_error(y_true,y_predict): """ 根据准确值和预测值返回平均绝对误差 :param y_true: 准确值 :param y_predict: 预测值 :return: 返回平均绝对误差 """ assert y_true.shape[0] == y_predict.shape[0], \ "the size of the y_true and the y_predict must be equal" return np.sum(np.absolute(y_true-y_predict)) /len(y_true) def r2_score(y_true,y_predict): """ 计算 预测值和准确值 的 R2值。 :param y_true: 准确值 :param y_predict: 预测值 :return: 返回预测值和准确值之间的 R2 值。 """ assert y_true.shape[0]==y_predict.shape[0],\ "the size of the y_true and the y_predict must be equal" return 1-mean_squared_error(y_true,y_predict) / np.var(y_true)
分为模型参数和超参数
模型参数:算法运行过程中需要学习的参数。
超参数:在算法运行前需要人为指定的参数。
平时听到的机器学习算法工程师所说的调参,实际上就是调整的超参数。
需要注意的是,超参数很重要,选取不当可能会给最终的模型造成很大的影响!
例如梯度下降法(本机器学习系列课程会详细介绍,请到相应专栏学习参考),学习率和训练次数就是超参数,假如学习率太大,无法下降到最低点,可能造成模型无法达到最优,学习率太小可能陷入局部最优解。对于训练次数,如果太少,可能还没有得到最优解程序就运行结束了,训练次数太多会使得训练耗时增加。
机器学习领域中,寻找好的超参数,也是非常重要的内容,常用的方法有:领域知识,经验数值,实验搜索等。
对于领域知识,比较专业了,这里不做介绍。对于经验数值,常见的例如卷积神经网络 的卷积核大小设置为 3, KNN算法中的K设置为 3 等。对于实验搜索,也是一种比较普遍的方法,使用循环遍历超参数取值范围的方式,找到最好的超参数。
sklearn 机器学习库中有一个很好的用于超参数搜索的类:GridSearchCV ,之后的系列教程中会演示使用方法。
在运行机器学习算法之前,一个很重要的步骤就是数据归一化过程。数据归一化可以将所有的数据映射到统一尺度,避免造成某一特征主导最终预测结果的现象。
举例说明如下:
肿瘤大小(厘米) | 肿瘤发现时间(天) | |
---|---|---|
样本1 | 1 | 200 |
样本2 | 5 | 100 |
对于以上表格,在计算样本特征空间距离的时候,发现时间起到了主导作用,肿瘤大小即使变化了五倍,对于最终的距离影响也很小,这样肿瘤大小特征相当于没有起到什么作用。
常见的特征归一化是最值归一化,这种归一化可以把所有数据映射到 0 - 1 之间,计算方法如下:
x
s
c
a
l
e
=
x
−
x
m
i
n
x
m
a
x
−
x
m
i
n
x_{scale}=\frac{x-x_{min}}{x_{max}-x_{min}}
xscale=xmax−xminx−xmin
这种方法适用于分布有明显边界的情况,例如学生的成绩(0-100),图像的像素点(0-255)。显著的缺点是受 outlier 的影响交大。例如 收入的分布,绝大部分人月工资在 8000左右,但是马云爸爸可能是好几亿,马云爸爸就是一个outlier,再使用最值归一化,导致的结果是分布不均匀,绝大部分人都在 0.000008这样的范围内。
另外一种归一化方法是均值方差归一化,适用于数据分布没有明显边界,有可能存在极端数据值(例如收入分布中的马云爸爸) 的情况。这种方法把所有的数据归一化到均值为0 ,方差为 1 的分布中。推荐这种方法,因为它同样也适用于分布有明显边界的情况。计算方法如下:
x
s
c
a
l
e
=
x
−
x
m
e
a
n
s
t
d
x_{scale}=\frac{x-x_{mean}}{std}
xscale=stdx−xmean
即样本与均值的差比上标准差。
使用Python numpy 模块实现两种归一化代码如下:
最值归一化:
import numpy as np
import matplotlib.pyplot as plt
# 模拟成绩数据,范围0-100,两个科目。
X=np.random.randint(0,100,(20,2))
X1=np.asarray(X,dtype=float)
X2=np.asarray(X,dtype=float)
# 最值归一化
X1[:,0]=(X1[:,0]-np.min(X1[:,0]) ) / ( np.max(X1[:,0])-np.min(X1[:,0]) )
X1[:,1]=(X1[:,1]-np.min(X1[:,1]) ) / ( np.max(X1[:,1])-np.min(X1[:,1]) )
# 绘制结果图像
plt.scatter(X1[:,0],X1[:,1])
plt.show()
绘制的散点图如下:
均值方差归一化
import numpy as np import matplotlib.pyplot as plt # 模拟成绩数据,范围0-100,两个科目。 X=np.random.randint(0,100,(20,2)) X1=np.asarray(X,dtype=float) # 均值方差归一化 X1[:,0]=(X1[:,0]-np.mean(X1[:,0]) ) / np.std(X1[:,0]) X1[:,1]=(X1[:,1]-np.mean(X1[:,1]) ) / np.std(X1[:,1]) print(np.mean(X1[:,0])) print(np.mean(X1[:,1])) print(np.std(X1[:,0])) print(np.std(X1[:,1])) # 绘制结果图像 plt.scatter(X1[:,0],X1[:,1]) plt.show() 输出结果为: -4.718447854656915e-17 -6.938893903907228e-17 1.0 1.0
绘制的散点图如下:
特别注意:以上进行归一化的过程是在训练集上进行的,那对于测试集,要怎么进行归一化呢?
有人说只需要对测试集做同样的操作就可以了,实际上这是错误的做法,产生这种想法的根本原因是没有理解测试集的概念:测试集模拟的是真实的环境, 根本无从得知有多少样本,进而不可能得到均值和方差。所以正确的做法是均值和方差都使用训练集得到的值。即对于测试集,均值方差归一化的公式为:
x
s
c
a
l
e
=
x
t
e
s
t
−
m
e
a
n
_
x
t
r
a
i
n
s
t
d
(
t
r
a
i
n
)
x_{scale}=\frac{x_{test}-mean\_x_{train}} {std_{(train)}}
xscale=std(train)xtest−mean_xtrain
封装一个均值方差归一化的工具类:
import numpy as np class StandardScaler: """ 均值方差归一化方法简单实现 """ def __init__(self): """ 初始化函数,创建一个 Scaler """ self.mean_=None self.scale_=None def fit(self,x): """ 对数据的每一列进行方差和标准差的运算。 :param x: 训练数据 np.array (注意,这里只能是训练数据) :return: 返回scaler本身 """ assert x.ndim==2,"the dimension of x must be 2" self.mean_=np.array([np.mean(x[:,i]) for i in range(x.shape[1])]) self.scale_=np.array([np.std(x[:,i]) for i in range(x.shape[1])]) return self def transform(self,x): """ 对数据进行归一化过程,并且返回归一化之后的数据集 :param x: 可能是测试数据,也可能是训练数据,训练数据和测试数据必须使用同一种归一化的方法。 :return: """ assert x.ndim==2 ,"the dimension of x must be 2" assert self.mean_ is not None and self.scale_ is not None,\ "you must fit before transform" assert x.shape[1]==len(self.mean_),\ "the feature number of the x must be equal to mean_ and std_" res=np.empty(shape=x.shape,dtype=float) for col in range(x.shape[1]): res[:,col]=(x[:,col]-self.mean_[col]) / self.scale_[col] return res
使用工具类对数据进行归一化操作,数据集还是选择 sklearn 中的 鸢尾花数据集
from sklearn import datasets import matplotlib.pyplot as plt import numpy as np iris=datasets.load_iris() x=iris.data y=iris.target # 训练集和测试集分离 from sklearn.model_selection import train_test_split x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.2,random_state=666) stand=StandardScaler() stand.fit(x_train) # 查看均值: print(stand.mean_) # 查看方差: print(stand.scale_) # 对原数据进行归一化操作 x_train=stand.transform(x_train) x_test=stand.transform(x_test) 输出结果: [5.83416667 3.08666667 3.70833333 1.17 ] [0.81019502 0.44327067 1.76401924 0.75317107]
实际上,sklearn 中封装了StandardScaler工具类,直接导入即可使用,方法和上面自己实现的一致。
from sklearn.preprocessing import StandardScaler
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。