机器学习的开发实战推进
目录
1.机器学习理论知识
机器学习是英文名称Machine
Learning(简称ML)的直译。机器学习涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多门学科。专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。它是人工智能的核心,是使计算机具有智能的根本途径,其应用遍及人工智能的各个领域,它主要使用归纳、综合而不是演绎。
相对于传统的计算机工作,我们给它一串指令,然后它遵照这个指令一步步执行下去即可。机器学习根本不接受你输入的指令,相反,它只接受你输入的数据!也就是说它某种意义上具有了我们人处理事情的能力。
机器学习分为:监督学习,无监督学习,半监督学习,强化学习
1.2.1 机器学习方法-监督学习
监督学习:从给定的训练数据集中学习出一个函数(模型参数),当新的数据到来时,可以根据这个函数预测结果。监督学习的训练集要求包括输入输出,也可以说是特征和目标。训练集中的目标是由人标注的。监督学习就是最常见的分类(注意和聚类区分)问题,通过已有的训练样本(即已知数据及其对应的输出)去训练得到一个最优模型(这个模型属于某个函数的集合,最优表示某个评价准则下是最佳的),再利用这个模型将所有的输入映射为相应的输出,对输出进行简单的判断从而实现分类的目的。也就具有了对未知数据分类的能力。监督学习的目标往往是让计算机去学习我们已经创建好的分类系统(模型)。
监督学习是训练神经网络和决策树的常见技术。这两种技术高度依赖事先确定的分类系统给出的信息,对于神经网络,分类系统利用信息判断网络的错误,然后不断调整网络参数。对于决策树,分类系统用它来判断哪些属性提供了最多的信息。
在监督式学习下,输入数据被称为“训练数据”,每组训练数据有一个明确的标识或结果,如对防垃圾邮件系统中“垃圾邮件”“非垃圾邮件”,对手写数字识别中的“1“,”2“,”3“,”4“等。在建立预测模型的时候,监督式学习建立一个学习过程,将预测结果与“训练数据”的实际结果进行比较,不断的调整预测模型,直到模型的预测结果达到一个预期的准确率。
监督式学习的常见应用场景如分类问题和回归问题。常见算法有逻辑回归(Logistic Regression)和反向传递神经网络(Back Propagation Neural Network)
(1) regression:Y是实数vector。回归问题,就是拟合(X,Y)的一条曲线,使得下式cost function L最小。
(2) classification:Y是一个finite number,可以看做类标号。分类问题需要首先给定有label的数据训练分类器,故属于有监督学习过程。分类问题中,cost function L(X,Y)是X属于类Y的概率的负对数,其中fi(X)=P(Y=i | X);
属于监督式学习的算法有:回归模型,决策树,随机森林,K邻近算法,逻辑回归等。
1.2.2 机器学习方法-无监督学习
无监督学习:在非监督式学习中,数据并不被特别标识,学习模型是为了推断出数据的一些内在结构。常见的应用场景包括关联规则的学习以及聚类等。常见算法包括Apriori算法以及k-Means算法。
无监督学习的目的是学习一个function
f,使它可以描述给定数据的位置分布P(Z)。包括两种:density estimation & clustering.
density estimation就是密度估计,估计该数据在任意位置的分布密度
clustering就是聚类,将Z聚集几类(如K-Means),或者给出一个样本属于每一类的概率。由于不需要事先根据训练数据去train聚类器,故属于无监督学习。
属于无监督式学习的算法有:关联规则,K-means聚类算法等。
解释1:输入数据没有被标记,也没有确定的结果。样本数据类别未知,需要根据样本间的相似性对样本集进行分类(聚类,clustering)试图使类内差距最小化,类间差距最大化。通俗点将就是实际应用中,不少情况下无法预先知道样本的标签,也就是说没有训练样本对应的类别,因而只能从原先没有样本标签的样本集开始学习分类器设计。
解释2:非监督学习目标不是告诉计算机怎么做,而是让它(计算机)自己去学习怎样做事情。非监督学习有两种思路。第一种思路是在指导Agent时不为其指定明确分类,而是在成功时,采用某种形式的激励制度。需要注意的是,这类训练通常会置于决策问题的框架里,因为它的目标不是为了产生一个分类系统,而是做出最大回报的决定,这种思路很好的概括了现实世界,agent可以对正确的行为做出激励,而对错误行为做出惩罚。
无监督学习的方法分为两大类:
(1) 一类为基于概率密度函数估计的直接方法:指设法找到各类别在特征空间的分布参数,再进行分类。
(2) 另一类是称为基于样本间相似性度量的简洁聚类方法:其原理是设法定出不同类别的核心或初始内核,然后依据样本与核心之间的相似性度量将样本聚集成不同的类别。
利用聚类结果,可以提取数据集中隐藏信息,对未来数据进行分类和预测。应用于数据挖掘,模式识别,图像处理等。
属于无监督式学习的算法有:关联规则,K-means聚类算法等。
PCA和很多deep learning算法都属于无监督学习。
半监督学习:在此学习方式下,输入数据部分被标识,部分没有被标识,这种学习模型可以用来进行预测,但是模型首先需要学习数据的内在结构以便合理的组织数据来进行预测。
应用场景包括分类和回归,算法包括一些对常用监督式学习算法的延伸,这些算法首先试图对未标识数据进行建模,在此基础上再对标识的数据进行预测。如图论推理算法(Graph Inference)或者拉普拉斯支持向量机(Laplacian SVM.)等。
强化学习:在这种学习模式下,输入数据作为对模型的反馈,不像监督模型那样,输入数据仅仅是作为一个检查模型对错的方式,在强化学习下,输入数据直接反馈到模型,模型必须对此立刻作出调整。常见的应用场景包括动态系统以及机器人控制等。常见算法包括Q-Learning以及时间差学习(Temporal difference learning)
回归算法是试图采用对误差的衡量来探索变量之间的关系的一类算法。回归算法是统计机器学习的利器。
常见的回归算法包括:最小二乘法(Ordinary Least Square),逻辑回归(Logistic Regression),逐步式回归(Stepwise Regression),多元自适应回归样条(Multivariate Adaptive Regression Splines)以及本地散点平滑估计(LocallyEstimated Scatterplot Smoothing)
基于实例的算法常常用来对决策问题建立模型,这样的模型常常先选取一批样本数据,然后根据某些近似性把新数据与样本数据进行比较。通过这种方式来寻找最佳的匹配。因此,基于实例的算法常常也被称为“赢家通吃”学习或者“基于记忆的学习”。常见的算法包括 k-Nearest Neighbor(KNN), 学习矢量量化(LearningVector Quantization, LVQ),以及自组织映射算法(Self-Organizing Map , SOM)深度学习的概念源于人工神经网络的研究。含多隐层的多层感知器就是一种深度学习结构。深度学习通过组合低层特征形成更加抽象的高层表示属性类别或特征,以发现数据的分布式特征表示
决策树算法根据数据的属性采用树状结构建立决策模型,决策树模型常常用来解决分类和回归问题。常见的算法包括:分类及回归树(Classification And Regression Tree, CART), ID3(Iterative Dichotomiser 3), C4.5, Chi-squared Automatic Interaction Detection(CHAID), Decision Stump,随机森林(Random Forest),多元自适应回归样条(MARS)以及梯度推进机(Gradient Boosting Machine, GBM)
贝叶斯方法算法是基于贝叶斯定理的一类算法,主要用来解决分类和回归问题。常见算法包括:朴素贝叶斯算法,平均单依赖估计(Averaged One-Dependence Estimators, AODE),以及BayesianBelief Network(BBN)。
基于核的算法中最著名的莫过于支持向量机(SVM)了。基于核的算法把输入数据映射到一个高阶的向量空间,在这些高阶向量空间里,有些分类或者回归问题能够更容易的解决。常见的基于核的算法包括:支持向量机(Support Vector Machine, SVM),径向基函数(Radial Basis Function ,RBF),以及线性判别分析(Linear Discriminate Analysis ,LDA)等。
聚类,就像回归一样,有时候人们描述的是一类问题,有时候描述的是一类算法。聚类算法通常按照中心点或者分层的方式对输入数据进行归并。所以的聚类算法都试图找到数据的内在结构,以便按照最大的共同点将数据进行归类。常见的聚类算法包括 k-Means算法以及期望最大化算法(Expectation Maximization, EM)。EM算法,指的是最大期望算法(ExpectationMaximization Algorithm,又译期望最大化算法),是一种迭代算法,在统计学中被用于寻找,依赖于不可观察的隐性变量的概率模型中,参数的最大似然估计。
EM算法的思想是:
1,给θ自主规定个初值(既然我不知道想实现“两个碟子平均分配锅里的菜”的话每个碟子需要有多少菜,那我就先估计个值);
2,根据给定观测数据和当前的参数θ,求未观测数据z的条件概率分布的期望(在上一步中,已经根据手感将菜倒进了两个碟子,然后这一步根据“两个碟子里都有菜”和“当前两个碟子都有多少菜”来判断自己倒菜的手感);
3,上一步中z已经求出来了,于是根据极大似然估计求最优的θ’(手感已经有了,那就根据手感判断下盘子里应该有多少菜,然后把菜匀匀);
4,因为第二步和第三步的结果可能不是最优的,所以重复第二步和第三步,直到收敛(重复多次匀匀的过程,直到两个碟子中菜的量大致一样)。
而上面的第二步被称作E步(求期望),第三步被称作M步(求极大化),于是EM算法就在不停的EM、EM、EM….,所以被叫做EM算法,你看,多形象(摊手)。
像聚类算法一样,降低维度算法试图分析数据的内在结构,不过降低维度算法是以非监督学习的方式试图利用较少的信息来归纳或者解释数据。这类算法可以用于高维数据的可视化或者用来简化数据以便监督式学习使用。常见的算法包括:主成份分析(Principle Component Analysis, PCA),偏最小二乘回归(PartialLeast Square Regression,PLS), Sammon映射,多维尺度(Multi-Dimensional Scaling, MDS), 投影追踪(ProjectionPursuit)等。
关联规则学习通过寻找最能够解释数据变量之间关系的规则,来找出大量多元数据集中有用的关联规则。常见算法包括 Apriori算法和Eclat算法等。
集成算法用一些相对较弱的学习模型独立地就同样的样本进行训练,然后把结果整合起来进行整体预测。集成算法的主要难点在于究竟集成哪些独立的较弱的学习模型以及如何把学习结果整合起来。这是一类非常强大的算法,同时也非常流行。常见的算法包括:Boosting, Bootstrapped Aggregation(Bagging), AdaBoost,堆叠泛化(StackedGeneralization, Blending),梯度推进机(Gradient Boosting Machine, GBM),随机森林(Random Forest)。
人工神经网络算法模拟生物神经网络,是一类模式匹配算法。通常用于解决分类和回归问题。人工神经网络是机器学习的一个庞大的分支,有几百种不同的算法。(其中深度学习就是其中的一类算法,我们会单独讨论),重要的人工神经网络算法包括:感知器神经网络(Perceptron Neural Network), 反向传递(BackPropagation), Hopfield网络,自组织映射(Self-Organizing Map, SOM)。学习矢量量化(LearningVector Quantization, LVQ)
均方根误差RSEM
平均绝对误差MAE
MSE 和 MAE 都是测量预测值和目标值两个向量距离的方法。有多种测量距离的方法,或范数,更一般的K 阶闵氏范数写成。p=0时,ℓ0(汉明范数)只显示了这个向量的基数(即,非零元素的个数),p趋于无穷时,ℓ∞(切比雪夫范数)是向量中最大的绝对值。
两种拆分方式:二八拆分和分层采样。
上述已经对机器学习基本知识进行了陈述,下面从实战角度对机器学习进行论述,我们以以下数据为例,通过使用两种拆分方式的实例来更深入的了解机器学习:
上图为使用housing.csv 训练数据。
实例代码:
import numpy as np
def split_train_test(data, test_ratio):
shuffled_indices =np.random.permutation(len(data))
test_set_size =int(len(data) * test_ratio) #拆分比例
test_indices =shuffled_indices[:test_set_size]
train_indices =shuffled_indices[test_set_size:]
returndata.iloc[train_indices], data.iloc[test_indices]
train_set, test_set = split_train_test(housing, 0.2) # housing数据二八拆分
经验证这个方法可行,但是并不完美:如果再次运行程序,就会产生一个不同的测试集。多次运行之后,你(或你的机器学习算法)就会得到整个数据集,这是需要避免的。
一个通常的解决办法是使用每个实例的识别码,以判定是否这个实例是否应该放入测试集(假设实例有单一且不变的识别码)。例如,你可以计算出每个实例识别码的哈希值,只保留其最后一个字节,如果值小于等于 51(约为 256 的 20%),就将其放入测试集。这样可以保证在多次运行中,测试集保持不变,即使更新了数据集。新的测试集会包含新实例中的 20%,但不会有之前位于训练集的实例。
下面是一种可用的方法:
import hashlib
# 参数identifier为单一且不变的识别码,可以为索引id
# hash(np.int64(identifier)).digest()[-1]返回识别码的哈希摘要值的最后一个字节
def test_set_check(identifier, test_ratio, hash):
returnhash(np.int64(identifier)).digest()[-1] < 256 * test_ratio #记录满足条件的索引
def split_train_test_by_id(data, test_ratio, id_column,hash=hashlib.md5):
ids = data[id_column]
in_test_set =ids.apply(lambda id_: test_set_check(id_, test_ratio, hash))
returndata.loc[~in_test_set], data.loc[in_test_set]
但房产数据集没有识别码这一列。最简单的方法是使用行索引作为 ID:
housing_with_id = housing.reset_index() #增加一个索引列,放在数据的第一列
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2,"index")
如果使用行索引作为唯一识别码,你需要保证新数据放到现有数据的尾部,且没有行被深处。如果做不到,则可以用最稳定的特征来创建唯一识别码。例如,一个区的维度和经度在几百万年之内是不变的,所以可以将两者结合成一个 ID。
将人群分成均匀的子分组,称为分层,从每个分层去除合适数量的实例,以保证测试集对总人数有代表性。例如,美国人口的 51.3% 是女性,48.7% 是男性。所以在美国,严谨的调查需要保证样本也是这个比例:513 名女性,487 名男性作为数据样本。
数据集中的每个分层都要有足够的实例位于你的数据中,这点很重要。否则,对分层重要性的评估就会有偏差。这意味着,你不能有过多的分层,且每个分层都要足够大。后面的代码通过将收入中位数除以 1.5(以限制收入分类的数量),创建了一个收入类别属性,用ceil对值舍入(以产生离散的分类),然后将所有大于 5的分类归入到分类5 :
# 预处理,创建"income_cat"属性
# 凡是会对原数组作出修改并返回一个新数组的,往往都有一个 inplace可选参数
# inplace=True,原数组名对应的内存值直接改变;inplace=False,原数组名对应的内存值并不改变,新的结果赋给一个新的数组.
housing["income_cat"]= np.ceil(housing["median_income"] / 1.5)
housing["income_cat"].where(housing["income_cat"]< 5, 5.0, inplace=True)
# 现在,就可以根据收入分类,进行分层采样。你可以使用 Scikit-Learn 的StratifiedShuffleSplit类
fromsklearn.model_selection import StratifiedShuffleSplit
# random_state为随机种子生成器,可以得到相同的随机结果
# n_splits是将训练数据分成train/test对的组数,这里汇总成一组数据
split =StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)
for train_index,test_index in split.split(housing, housing["income_cat"]):
strat_train_set = housing.loc[train_index]
strat_test_set = housing.loc[test_index]
# 现在,你需要删除income_cat属性,使数据回到初始状态:
for set in(strat_train_set, strat_test_set):
set.drop(["income_cat"], axis=1,inplace=True)
可视化数据寻找规律:
housing.plot(kind="scatter",x="longitude", y="latitude", alpha=0.4,
s=housing["population"]/100,label="population",
c="median_house_value",cmap=plt.get_cmap("jet"), colorbar=True,
)
plt.legend()
每个圈的半径表示街区的人口(选项s),颜色代表价格(选项c)。我们用预先定义的名为jet的颜色图(选项cmap),它的范围是从蓝色(低价)到红色(高价):
这张图说明房价和位置(比如,靠海)和人口密度联系密切。你还可以很容易地使用corr()方法计算出每对属性间的标准相关系数(也称作皮尔逊相关系数):
corr_matrix =housing.corr()
现在来看下每个属性和房价中位数的关联度:
>>>corr_matrix["median_house_value"].sort_values(ascending=False)
median_house_value 1.000000
median_income 0.687170
total_rooms 0.135231
housing_median_age 0.114220
households 0.064702
total_bedrooms 0.047865
population -0.026699
longitude -0.047279
latitude -0.142826
Name:median_house_value, dtype: float64
相关系数的范围是 -1 到 1。当接近 1 时,意味强正相关;例如,当收入中位数增加时,房价中位数也会增加。当相关系数接近 -1 时,意味强负相关;你可以看到,纬度和房价中位数有轻微的负相关性(即,越往北,房价越可能降低)。最后,相关系数接近 0,意味没有线性相关性。(没有直接的线性关系,不是没有关系)
另一种检测属性间相关系数的方法是使用Pandas 的scatter_matrix函数,它能画出每个数值属性对每个其它数值属性的图。因为现在共有 11 个数值属性,你可以得到11 ** 2 = 121张图。
frompandas.tools.plotting import scatter_matrix
attributes =["median_house_value", "median_income","total_rooms",
"housing_median_age"]
scatter_matrix(housing[attributes],figsize=(12, 8))
得到两个属性的散点图:
函数可以让你在任何数据集上(比如,你下一次获取的是一个新的数据集)方便地进行重复数据转换。
你能慢慢建立一个转换函数库,可以在未来的项目中复用。
在将数据传给算法之前,你可以在实时系统中使用这些函数。
这可以让你方便地尝试多种数据转换,查看那些转换方法结合起来效果最好。
大多机器学习算法不能处理特征丢失,因此先创建一些函数来处理特征丢失的问题。前面,你应该注意到了属性total_bedrooms有一些缺失值。有三个解决选项:
去掉对应的街区;
去掉整个属性;
进行赋值(0、平均值、中位数等等)。
用DataFrame的dropna(),drop(),和fillna()方法,可以方便地实现:
housing.dropna(subset=["total_bedrooms"]) #选项1
housing.drop("total_bedrooms",axis=1) #选项2 axis=0对行操作,axis=1对列操作
median =housing["total_bedrooms"].median()
housing["total_bedrooms"].fillna(median) #选项3
如果选择选项 3,你需要计算训练集的中位数,用中位数填充训练集的缺失值,不要忘记保存该中位数。后面用测试集评估系统时,需要替换测试集中的缺失值,也可以用来实时替换新数据中的缺失值。
Scikit-Learn 提供了一个方便的类来处理缺失值:Imputer。下面是其使用方法:首先,需要创建一个Imputer实例,指定用该属性的中位数替换它的每个缺失值:
from sklearn.preprocessingimport Imputer
imputer =Imputer(strategy="median") #进行中位数赋值
因为只有数值属性才能算出中位数,我们需要创建一份不包括文本属性ocean_proximity的数据副本:
housing_num =
housing.drop("ocean_proximity", axis=1) # 去除ocean_proximity不为数值属性的特征
现在,就可以用fit()方法将imputer实例拟合到训练数据:
imputer.fit(housing_num)
imputer计算出了每个属性的中位数,并将结果保存在了实例变量statistics_中。只有属性total_bedrooms有缺失值,但是我们确保一旦系统运行起来,新的数据中没有缺失值,所以安全的做法是将imputer应用到每个数值:
>>> imputer.statistics_ #实例变量statistics_和housing_num数值数据得到的中位数是一样的
array([ -118.51, 34.26 , 29. , 2119. , 433. , 1164. , 408. , 3.5414])
>>>housing_num.median().values
array([ -118.51
, 34.26 , 29. , 2119. , 433. , 1164. , 408. , 3.5414])
现在,你就可以使用这个“训练过的”imputer来对训练集进行转换,通过将缺失值替换为中位数:
X = imputer.transform(housing_num)
结果是一个普通的 Numpy 数组,包含有转换后的特征。如果你想将其放回到 PandasDataFrame中,也很简单:
housing_tr =
pd.DataFrame(X, columns=housing_num.columns) # 得到处理缺失值后的DF数据
前面,我们丢弃了类别属性ocean_proximity,因为它是一个文本属性,不能计算出中位数。大多数机器学习算法跟喜欢和数字打交道,所以让我们把这些文本标签转换为数字。Scikit-Learn 为这个任务提供了一个转换器LabelEncoder:
# 简单来说 LabelEncoder 是对不连续的数字或者文本进行编号
# le.fit([1,5,67,100])
#le.transform([1,1,100,67,5])
# 输出:array([0,0,3,2,1])
>>> fromsklearn.preprocessing import LabelEncoder
>>> encoder =LabelEncoder()
>>> housing_cat =housing["ocean_proximity"]
>>>housing_cat_encoded = encoder.fit_transform(housing_cat)
>>>housing_cat_encoded
array([1, 1, 4, ..., 1, 0,3])
处理离散特征这还不够,Scikit-Learn提供了一个编码器OneHotEncoder,用于将整书分类值转变为独热向量。注意fit_transform()用于 2D 数组,而housing_cat_encoded是一个 1D 数组,所以需要将其变形:
>>> fromsklearn.preprocessing import OneHotEncoder
>>> encoder =OneHotEncoder()
>>>housing_cat_1hot = encoder.fit_transform(housing_cat_encoded.reshape(-1,1))
>>>housing_cat_1hot
<16513x5 sparse matrixof type ''
with 16513 stored elements in CompressedSparse Row format>
注意输出结果是一个 SciPy 稀疏矩阵,而不是NumPy 数组。当类别属性有数千个分类时,这样非常有用。经过独热编码,我们得到了一个有数千列的矩阵,这个矩阵每行只有一个 1,其余都是 0。使用大量内存来存储这些 0 非常浪费,所以稀疏矩阵只存储非零元素的位置。你可以像一个 2D 数据那样进行使用,但是如果你真的想将其转变成一个(密集的)NumPy 数组,只需调用toarray()方法:
>>>housing_cat_1hot.toarray()
array([[ 0., 1., 0., 0., 0.],
[ 0., 1., 0., 0., 0.],
[ 0., 0., 0., 0., 1.],
...,
[ 0., 1., 0., 0., 0.],
[ 1., 0., 0., 0., 0.],
[ 0., 0., 0., 1., 0.]])
使用类LabelBinarizer,我们可以用一步执行这两个转换(从文本分类到整数分类,再从整数分类到独热向量):
>>> fromsklearn.preprocessing import LabelBinarizer
>>> encoder =LabelBinarizer()
>>>housing_cat_1hot = encoder.fit_transform(housing_cat)
>>>housing_cat_1hot
array([[0, 1, 0, 0, 0],
[0, 1, 0, 0, 0],
[0, 0, 0, 0, 1],
...,
[0, 1, 0, 0, 0],
[1, 0, 0, 0, 0],
[0, 0, 0, 1, 0]])
注意默认返回的结果是一个密集 NumPy 数组。向构造器LabelBinarizer传递sparse_output=True,就可以得到一个稀疏矩阵。
经过以上自己对机器学习实战开发演练,使初学者对机器学习的概念会有更深入的认识,对机器学习设计模式及开发逻辑更加了解,希望对机器学习的爱好者在实战开发中有一定的助力。