现如今,机器学习、人工智能等技术在各行各业中的运用越来越广泛,有越来越多的人想要去学习并抓住这个风口。那么到底什么是机器学习,有哪些常用的机器学习模型,我们又该如何运用这些模型去解决现实问题呢?
本文将从宏观的角度,带大家纵览机器学习的全貌,给想要入门机器学习的同学一个参考。至于文中提到的各种模型,会在以后的文章中对其中比较常用的模型进行深入的探讨。
1. 什么是机器学习?
机器学习的正式概念是由 Arthur Samuel 在1959年提出的。他说
(Machine Learning is the) field of study that gives computers the ability to learn without being explicitly programmed.
Tom Mitchell (1997) 提出了一个更具体更全面的机器学习定义,这个定义也是众多机器学习课程和资料中经常提及的。
A computer program is said to learn from experience E with respect to some task T and some performance measure P, if its performance on T, as measured by P, improves with experience E.
上述的定义点到了机器学习过程中的几个关键因素。
- 首先,我们需要有一个目标 T。 这个目标是从现实的问题出发,基于我们需要解决的问题而转化成的数学问题。
- 其次,我们需要有一个衡量指标 P。它用于衡量我们对于目标 T 的实现成果如何,用于评估模型效果以及寻找提高模型精度的方法。
- 此外,我们需要有经验数据 E。它是我们训练机器学习模型的基础,可以理解为是模型所学习的教材。
- 同时,我们还需要一个学习方法。根据经验数据类型的不同、现实目标的不同等条件,可能需要采用不同的学习方法,也就是我们所说的机器学习算法。
我个人是这么理解的。机器学习,顾名思义,就是让机器去学习。
- 跟谁学?—— 跟已有的数据学 (input data)
- 学什么?—— 学习数据的特点(比如聚类、降维问题)、学习数据之间的关系(比如回归、分类问题)
- 通过什么方法学?—— 各种不同的机器学习算法(比如 Logistic Regression, SVM, Decision Tree 等)
2. 为什么使用机器学习?
机器学习在以下几种情况下,可以表现出它的强大优势。
当传统方法涉及很多手动调整以及需要制定多种规则时,机器学习通常可以轻而易举地通过简单的代码实现目标,而且表现更好。以“标注垃圾邮件”这个典型的机器学习项目为例。手动制定垃圾邮件的规则需要我们列出很多可能的(代表垃圾邮件的)条件,而且人工列出的规则还有可能不全面。而通过机器学习的方法,几段简洁的代码就可以让模型自主学习并找到垃圾邮件的典型特征,从而提高标注精度。
当传统的方法无法解决我们所面临的复杂问题时,机器学习通常可以提供一个有效解。以语音识别为例。如何有效地在成千上万个使用不同语言、具有不同口音的人的语音中区分出“一” 和 “二”。传统方法显然难以规模化地实现。而机器学习在语音识别和自然语言处理领域具有很大的优势。
当外界环境在不断的改变,模型需要不断地调整以适应和学习新的数据时,机器学习模型可以自动化地进行调整,从而省时省力。
最后,机器学习也可以帮助人类学习。如上述的标注垃圾邮件例子。当机器学习模型训练完之后,它可以告诉我们哪些词语或者短语能够有效地鉴别出垃圾邮件,而这些词语和短语可能是一开始我们并没有注意到的。通过使用机器学习的方法研究大量的数据从而找到数据内部隐含的趋势和关系,也就是我们常说的“数据挖掘”。
3. 机器学习有哪些类型?
这里必须要祭出 scikit-learn 网站上的一张图了。
上图提到了分类 (classification)、回归 (regression)、聚类 (clustering)、降维 (dimension reduction) 等等,这些都分别属于什么机器学习的哪一类呢?下面我们来具体说说。
根据分类标准的不同,可以将机器学习方法划分为不同的类型。
3.1 标准一:原始数据是否有标签
根据所学习的数据是否有标签(标签可以是类别型或者是数值型数据),可以将机器学习分为以下四种类型。
(1) 有监督学习 Supervised Learning
如果训练集的数据是有标签的,即有我们最终想要得到的结果,那么这类机器学习方法叫做有监督学习。
这里的标签可以是类别变量(比如垃圾邮件的识别,它的标签是类别型变量,0代表不是垃圾邮件,1代表是垃圾邮件);也可以是数值变量(比如房价的预测,它的标签是房屋的价格,是一个数值)。
从而根据标签的类型不同,又可以进一步分为
分类问题 Classification:
常见算法有KNN, SVM, Tree Based Method 等回归问题 Regression:
常见算法有 Linear Regression, Regression Tree, Neural Net 等
(2) 无监督学习 Unsupervised Learning
如果训练集的数据是没有标签的,即不包含我们最终想要得到的结果,那么这类机器学习方法叫做无监督学习。
根据最终目标的不同,无监督学习又可以进一步分为:
聚类问题 Clustering:
常见算法有 K-means, Hierarchical Clustering 等异常检测 Anomaly Detection:
常见算法有 Isolation Forest, One-class SVM等降维 Dimension Reduction:
常见算法有 PCA, Kernel PCA, t-SNE 等
(3) 半监督学习 Semi-supervised Learning
当机器学习使用的训练集数据部分有标注时(通常是大部分无标注,而只有小部分有标注),我们称之为半监督学习。
大多数半监督学习的算法是无监督学习和有监督学习算法的结合。比如 Deep belief networks (DBNs) 等。
(4) 强化学习 Reinforcement Learning
强化学习强调通过观察周围的环境及其改变,采取一系列的行动以最大化未来的收益 (或者最小化未来的惩罚)。
3.2 标准二:是否可以从不断进入的数据流中持续学习
根据机器学习系统是否能循序渐进地学习不断进入的新数据,可以将机器学习方法分为以下两种类型。
(1) 批量学习 Batch Learning
批量学习的系统并不能循序渐进的学习,而是一次性利用所有的数据,可以理解为是“填鸭式”的学习。先训练模型,然后将模型投入使用,而并不考虑新的数据。
如果你希望你的批量学习系统可以学习到一些新的数据,那么你需要从头开始利用这个更大的数据集来重新训练模型,在其完成训练之后替代原来的模型投入使用。
总体来说,这类方法需要花费更多地时间和算力,所以通常是线下训练的。
(2) 在线学习 Online Learning
在线学习的系统持续不断地将数据流输入进来,模型循序渐进地得到训练和改善。每一步训练过程都是很快的,所以系统可以在新数据进入的过程中不断的得到调整。
当你的数据集很大从而无法全部存储在计算机中的时候,使用在线学习不失为一个好方法。
在线学习涉及到一个重要的参数 —— 学习率 (learning rate)。它表示在线学习的系统会按怎样的速度来调整以适应新的数据。如果学习率设的过大,那么模型会很快的学习新的数据特点,但是也会很快的忘记旧的数据 (有些时候这并不是一件好事); 相反,如果学习率设的过小,那么模型学习新数据特点会很慢,但同时,它对于新数据中的噪音和异常值也会不那么敏感。所以如何设定一个合适地学习率,也是模型训练中的一个技巧。
4. 机器学习过程中有哪些挑战?
机器学习的过程就是用合适的模型来学习数据,由此可见,这个过程主要面临来自两个方面的挑战 —— 数据和模型。
4.1 来自数据的挑战
训练集不够大。机器学习通常涉及大量的数据,过小的训练集会大大降低机器学习模型的效果。然而数据是昂贵的(尤其是带标签的数据 labelled data),目前有学者通过数据增强的方法来扩充数据集。
训练集不具有代表性。为了提高模型的泛化能力,训练集必须能够代表未来可能遇到的新的样本。但是通常这也很难满足。样本量过小所带来的 sampling noise,或者采样方法不当所带来的 sampling bias,都会导致训练样本不具有代表性。
训练集质量低。如果由于测量偏误等原因导致训练样本存在很多的误差、极端值、噪音等,那么显然机器学习模型很难学习和检测到数据背后真实的趋势。所以,花时间清洗数据是训练机器学习模型非常必要的一步,比如,如何处理异常点、缺失值等。
无关特征。Garbage in, garbage out. 只有当训练集包含足够多的相关特征时,模型才可以得到有效地训练。所以机器学习过程中关键一步就是得到有效的特征集,这一过程被称为特征工程 (Feature Engineering)。它包括特征选择 (从已有的特征中选择最有效的特征集),特征构建 (在已有特征的基础上构建新的有效特征)等。
4.2 来自模型的挑战
模型过拟合 (overfitting)。通常当模型相对于训练集的样本量和样本噪音而言过于复杂时,模型可能会将数据的噪音也学习进来,从而造成过拟合的问题。过拟合一个典型的表现就是,模型在训练集上表现很好,而在测试集上表现很差。这时我们可以考虑采取简化模型、加入正则项(regularization, 降低模型复杂度的一种方式),增加数据量或者降低数据噪音等方法来缓解。
模型低拟合 (underfitting)。通常当模型过于简单时,它无法充分地学习到数据背后的结构,从而造成模型拟合效果差(在训练集上的拟合效果就很差)。这时我们可以通过使用有更多参数的更强的模型、使用更有效的特征,降低模型的正则化限制等方式来解决这类模型问题。
模型选择。通常,我们需要将原始的数据集分成训练集 (training set) 和测试集 (test set)。训练集用于训练模型,而测试集用于评价模型的最终效果。你需要记住一条准则,就是测试集必须是独立的,在训练模型的过程中绝不能被模型所看见的。所以如果我们需要选择最优的模型或者超参的值,那么仅凭训练集和测试集两部分是不够的。这时我们要采取留出验证 (holdout validation) 或者交叉验证 (cross validation) 的方法。
5. 一个小例子
完成一个机器学习项目通常会经历以下几个步骤
- 明确问题和目标,将现实问题转化为建模问题。
- 获取数据
- 通过观察数据 (数据可视化) 获取灵感,寻找关系
- 数据清洗和特称工程
- 选择并训练合适的机器学习模型
- 微调模型 (fine-tune)
- 展示结果
- 上线、监控、维护这个模型系统
下面以一个简单的例子带领大家从头到尾完成一个机器学习模型的训练。
例子来源于《Hands-On Machine Learning with Scikit-Learn, Keras and Tensorflow》
Step 1: 明确问题
我们希望构建一个模型,利用加州的人口普查数据 (包括加州每个区的人口、人均收入、人均房价等),预测其他区的人均房价。
根据上文的,我们知道,这是一个有监督学习 (因为训练集数据包含我们想要估计的房价)中的回归问题(因为房价是数值而不是类型)。也可以进一步分类为单变量 (只估计一个变量就是房价)的多元回归 (利用多个特征进行估计)。
Step 2:选择合适的性能指标
选择一个合适性能指标,衡量模型的精度。
对于回归问题来说,我们常用的均方误差 (Mean Squared Error)
: 训练集的样本数量
: 第个样本的特征向量 (包含多个特征)
: 表示一个模型,输入,输出估计的标签值
: 第个样本的真实标签值
除此之外,还有均绝对值误差 (Mean Absolute Error) 等用于回归问题的衡量指标。而用于分类问题的衡量指标也有很多,比如精度 (precision)、召回 (recall)等。
Step 3: 获取数据
原始数据可以从网站上下载
读取下载的数据
import pandas as pd
housing = pd.read_cvs('housing.csv')
housing.head()
housing.info()
上面的结果可以给我们一些关于此数据集的基本信息,比如样本量,每个特征的类型,是否存在缺失值等等。
此外,还可以通过housing.describe()
以及housing.hist()
得到数值特征的一些信息,比如均值、方差、分布等情况。
Step 4: 划分训练集和测试集
sklearn
提供了随机划分测试机和训练集的函数。可以利用如下的代码,将完整的数据集按照 8:2 的比例随机划分为训练集和测试集。
from sklearn.model_selection import train_test_split
train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)
但是在这里我们使用的不是上述的划分方法,而是分层抽样 (stratified sampling) 的方法,这样划分可以使得训练集样本更具有代表性(尤其是当样本量不够大时)。
具体说来,如果已知地区的人均收入与该地区的房价高度相关,那么我们应该尽量保证训练集和测试集中的样本能够包含和代表原始数据集中各种人均收入类型。
首先,在原始数据集中,我们按照地区人均收入的值,其划分成不同的人均收入类别:人均收入在 0 - 1.5, 1.5 - 3,3 - 4.5, 4.5 - 6,> 6, 分别被分入类别1、2、3、4、5。
import numpy as np
housing['income_cat'] = pd.cut(housing['median_income'], \
bins = [0., 1.5, 3.0, 4.5, 6., np.inf], \
labels = [1, 2, 3, 4, 5])
然后按照各个人均收入类型中样本在全部样本中所占的比例,做分层抽样,划分出训练集和测试集,以保证在人均收入这个重要特征上,训练集和测试集能够具有代表性。
from sklearn.model_selection import StratifiedShuffleSplit
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)
这样我们就得到了训练集 strat_train_set
和测试集strat_test_set
。
Step 5: 观察数据,寻找相关关系
在这一步,我们通过数据可视化等方法,观察数据以及不同特征之间的关系,从而试图发现数据背后的一些结构,并为最终预测房价提供一些参考。
需要注意的是,在这一步,我们只能观察和操作训练集,不能对测试集做任何的处理 (假装我们手里并没有测试集)。
比如,我们通过画图看一下longitude
和latitude
这两个地理位置特征和房价的关系。
import matplotlib.pyplot as plt
# make a copy of train set to manipulate
housing_copy = strat_train_set.copy()
housing_copy.plot(kind='scatter', x='longitude', y='latitude', alpha=0.4, \
s = housing_copy['population']/100, label='population', \
figsize=(10,7), c='median_house_value', cmap=plt.get_cmap('jet'), \
colorbar=True)
plt.legend()
上图中,颜色代表房价,圆圈的大小代表人口的多少。由此可见,加州靠海的湾区人口密度更大,房价也更高。
除此之外,我们还可以通过计算各个特征之间的相关系数,寻找特征之间的关系,尤其是与房价之间的相关关系大小。
corr_matrix = housing_copy.corr()
corr_matrix['median_house_value'].sort_values(ascending=False)
我们也可以构建一些新的特征,并检查这些特征与房价之间的相关性,从中找出有效的新特征。
housing_copy['rooms_per_household'] = housing_copy['total_rooms']/housing_copy['households']
housing_copy['bedrooms_per_room'] = housing_copy['total_bedrooms']/housing_copy['total_rooms']
housing_copy['population_per_household'] = housing_copy['population']/housing_copy['households']
corr_matrix = housing_copy.corr()
corr_matrix['median_house_value'].sort_values(ascending=False)
Step 6: 数据清洗和特征工程
首先,我们将训练集中的特征和目标值分开,因为后序的数据清洗和特征构建只需要在用于预测的特征上操作。
housing = strat_train_set.drop('median_house_value', axis = 1)
housing_labels = strat_train_set['median_house_value'].copy()
处理缺失值。对缺失值的处理可以采用删除或补齐(平均值填充、热卡填充、回归等)等方法。这里,我们采用比较简单的均值填充的办法。但是需要注意,均值填充一般只适用于数值型特征。ocean_proximity
是类别型特征,需要剔出来单独处理。
from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy='median')
housing_num = housing.drop('ocean_proximity', axis = 1)
imputer.fit(housing_num)
X = imputer.transform(housing_num)
housing_tr = pd.DataFrame(X, columns = housing_num.columns)
处理类别变量和文字变量。有些机器学习算法可以直接处理类别变量,但是大多数机器学习算法都更倾向于处理数值型变量。所以在这里,我们先把类别型变量或者文字变量转化成数值变量。常用的处理类别变量的方法有Ordinal Encoder 和 One-hot Encoder,两者的区别在于两个不同值之间的距离是否有实际意义。这里我们用One-hot Encoder 来处理类别变量ocean-proximity
from sklearn.preprocessing import OneHotEncoder
housing_cat = housing[['ocean_proximity']]
cat_encoder = OneHotEncoder()
housing_cat_1hot = cat_encoder.fit_transform(housing_cat)
构建新特征。根据前面的分析,我们可以构建一些新的对于地区房价预测有明显效果的特征,比如该地区平均每户的房间数,平均每户的卧室数,平均每户的人口等。
from sklearn.base import BaseEstimator, TransformerMixin
rooms_ix, bedrooms_ix, population_ix, households_ix = 3,4,5,6
class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
def __init__(self, add_bedrooms_per_room = True):
self.add_bedrooms_per_room = add_bedrooms_per_room
def fit(self, X, y=None):
return self
def transform(self, X, y=None):
rooms_per_household = X[:,rooms_ix] / X[:, households_ix]
population_per_household = X[:, population_ix] / X[:, households_ix]
if self.add_bedrooms_per_room:
bedrooms_per_room = X[:, bedrooms_ix] / X[:, rooms_ix]
return np.c_[X, rooms_per_household, population_per_household, bedrooms_per_room]
else:
return np.c_[X, rooms_per_household, population_per_household]
attr_adder = CombinedAttributesAdder(add_bedrooms_per_room=False)
housing_extra_attribs = attr_adder.transform(housing.values)
特征标准化处理。当输入的特征数量级相差特别大时,机器学习算法通常的表现不会很好。所以,我们会对特征进行标准化处理,常用的方法有 min-max scaling 和 standardization. 这里我们采 standardization 的方法,将每个特征处理为均值为0方查为1.
上面所有的处理,我们可以写成一个 pipeline, 有助于重复使用。
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.compose import ColumnTransformer
num_pipeline = Pipeline([
('imputer', SimpleImputer(strategy='median')),
('attribs_adder', CombinedAttributesAdder()),
('std_scaler', StandardScaler()),
])
housing_num_tr = num_pipeline.fit_transform(housing_num)
num_attribs = list(housing_num) # column names
cat_attribs = ['ocean_proximity']
full_pipeline = ColumnTransformer([
('num', num_pipeline, num_attribs),
('cat', OneHotEncoder(), cat_attribs),
])
housing_prepared = full_pipeline.fit_transform(housing)
Step 7: 训练模型
这里我们尝试一个简单的线性回归模型。
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(housing_prepared, housing_labels)
用这个模型,来预测训练集中的某些点,看看效果如何。
some_data = housing.iloc[:5]
some_labels = housing_labels.iloc[:5]
some_data_prepared = full_pipeline.transform(some_data)
print('Predictions:', lin_reg.predict(some_data_prepared))
print('Labels', list(some_labels))
该模型在整个训练集上的均方误差可以通过下面的代码得到。
from sklearn.metrics import mean_squared_error
housing_predictions = lin_reg.predict(housing_prepared)
lin_mse = mean_squared_error(housing_labels, housing_predictions)
该模型在测试集上的表现如何呢?
X_test = strat_test_set.drop('median_house_value', axis = 1)
y_test = strat_test_set['median_house_value'].copy()
X_test_prepared = full_pipeline.transform(X_test)
test_predictions = lin_reg.predict(X_test_prepared)
test_mse = mean_squared_error(y_test, test_predictions)
通过比较模型在训练集和测试集上的表现,可以得出模型是否存在过拟合的问题。
此外,我们还可以尝试其他不同的模型,比较模型的优劣,选出合适的模型以及超参,并逐步的优化调整。
到这一步为止,我们可以算初步完成了一个机器学习模型的训练。当然,这只是一个简化的过程,在实际操作中,还会遇到很多其他问题需要我们逐步的解决和优化。