最近在看Aurélien Geron的第二版《Hands-on Machine Learning with Scikit-Learn, Keras and TensorFlow》,于是想做一系列的笔记,分享给大家的同时也帮助自己消化。
本篇笔记先讲机器学习概览,同时简单实现一个的机器学习案例-预测塞浦路斯的生活满意度。
1. 机器学习基础
什么是机器学习?
Tom Mitchell 的定义是:一个计算机利用经验E来学习任务T,性能是P,如果针对任务T的性能P随着经验E不断增长,则称为机器学习。
机器学习的例子有什么?
例如垃圾邮件过滤器 就是比较经典的一个机器学习应用。使用传统判断垃圾邮件方法一般是检测某些字段,比如使用固定词语进行匹配,写一个检测算法,检测到某些固定词汇就标记为垃圾邮件。但是这样是不够完善的,垃圾邮件可以不断更新一些词汇,这样需要编写的检测程序会变得冗长和不宜维护。
机器学习就很适合处理这个问题,通过不断学习新旧垃圾邮件的特点,不断更新垃圾软件的解决方案,能够始终保证一个可靠的垃圾邮件屏蔽能力。
机器学习的适合范围
机器学习比较适合:
- 有解决方案的问题,机器学习可以简化代码,同时有更好的性能
- 传统方法难以解决的复杂问题
- 环境有波动或数据量大,机器学习可以不断适应新数据
应用示例:
- 图像分类:使用卷积神经网络(CNN)
- 发现脑部肿瘤:语义分割,使用卷积神经网络(CNN)
- 新闻处理:自然语言处理(NLP)
- 预测下年度公司收入:回归问题
- 语音识别:使用循环神经网络(RNN),卷积神经网络(CNN),Transformer
- 等等
2. 机器学习的类型
先了解一下机器学习的类型,之后如果时间充足,本人也会尽可能多分享一下提到的技术
2.1 有监督学习、无监督学习、半监督学习和强化学习
- 有监督学习:通过大量的实例和标记的所属进行训练。如垃圾邮件的分类,回归问题的预测。
- 无监督学习:系统在没有“老师"的情况下自主学习,主要有如下算法:
- 聚类算法
- k-均值
- DBSCAN
- 分层聚类分析(HCA)
- 异常检测和新颖性检测
- 单类SVM
- 孤立森林
- 可视化和降维
- 主成分分析(PCA)
- 核主成分分析
- 局部线性嵌入(LLE)
- t-分布随机近邻嵌入(t-SNE)
- 关联规则学习
- Apriori
- Eclat
- 聚类算法
- 半监督学习:有大部分未标记的数据和少量已标记的数据。
- 强化学习:其学习系统能够观察环境,做出选择,执行动作,并获得回报(或者以负面回报的形式获得惩罚)
2.2 批量学习和在线学习
- 批量学习:即使用所有可用数据进行训练,这需要大量时间和计算资源,所以通常是离线完成的。先训练系统,然后投入生产环境,这是学习过程停止。需要不断更新数据,来训练系统新版本。
- 在线学习:循序渐进的给系统提供训练数据,逐步积累学习成果。能够使用小批量的小数据,快速且便宜的训练,这样系统可以飞快的使用新数据学习。对于超大的数据集——超过一台计算机主存储器的数据,算法每次只加载部分数据,并针对这部分数据训练,然后不断重复这个过程,直至完成所有数据训练,这也称为核外学习。
2.3 基于实例的学习与基于模型的学习
-
基于实例的学习:通过简单的死记硬背,使用相似度度量比较新实例和已经学习的实例,从而泛化新实例。
-
基于模型的学习:构建出学习的实例模型,通过模型进行预测
-
对于一些线性模型,可以定义成本函数来衡量线性模型的预测与训练实例之间的差距,目的是尽量使得差距最小化。
3. 机器学习的主要挑战
- 训练数据的数量不足,根据2001年发表的一篇论文,微软的研究员Michele Banko和Eric Brill表明在给定足够的数据,截然不同的算法,在自然语言歧义消除这个复杂问题上,表现几乎完全一致。因此对于复杂问题,数据比算法更重要。
- 训练数据不具代表性,不具备代表性的数据集训练的数据不可能做出准确的预估。
- 低质量的数据,满是错误,异常值和噪声,系统将难以检测到底层模式,表现不够良好。
- 无关特征,一个成功的项目,需要提取一组适合训练的特征集,否则就是垃圾入,垃圾出。一般提取过程叫特征工程,包含以下部分:
- 特征选择:从现有特征中选择最有用的特征训练
- 特征提取:将现有的特征进行整合,产生更有用的特征。
- 收集新数据创建新特征
- 过拟合训练数据,过拟合是指模型在训练数据上表现良好,但是泛化时却不尽如人意。当训练数据的数量和噪度都过于复杂时,会发生过拟合,可能的解决方案有:
- 简化模型:选择较少参数的模型,减少模型的属性,增加模型约束
- 收集更多的训练数据
- 减少数据噪声,尽可能修复数据集中含有的错误
约束模型使其更简单,并降低拟合的风险,这个过程称为正则化。应用正则化的程度可以通过一个超参数来控制,超参数是学习算法的参数,不是模型的参数。
- 欠拟合训练数据,对于底层结构,当前的训练模型过于简单,则预测不够准确。解决方案主要是:
- 选择带有更多参数、更强大的模型
- 给学习算法提供更好的特征集
- 减少模型的约束,减少正则化超参数
4. 测试与验证
最后要想知道训练模型对于新场景的泛化能力,就需要进行测试和验证。一般会将数据分割成训练集和测试集。使用训练集来训练模型,用测试机来评估泛化误差。
一般可以把80%数据用于训练,20%数据用于测试。当然主要还是看数据集大小,数据足够大时,比如有1000万,那么即使只有1%的测试集,也能够很好的估计泛化误差
当我们想要调整一些超参数和选择更好的模型时,我们可以对某个测试集进行多次测试,最终找到最优超参数和最好模型时,这恰恰也会出现新问题,也就是对某个测试集过拟合,这样对于新数据的泛化效果也不尽人意。
解决此类问题常见方法是保持验证:取训练集的一部分,叫验证集(也称开发集dev set),评估几种候选模型和选择最佳模型。即在训练集中减去验证集部分,用于训练具有具有各种超参数的多个模型,在验证集上与选择最优模型。
没有免费的午餐
模型是观察的简化版。通过丢弃一部分的参数和细节,让统计和分析变得更加容易。但是在决定要丢弃那些数据和保留哪些数据,一定要做好假设。
1996年David Wolpert在一篇著名论文中表明,如果你对数据绝对没有任何假设,那么就没有理由更偏好于某个模型,这被称作没有免费的午餐(No Free Lunch, NFL)。对于不同的数据,就会有不同的最适合的模型。想要对知道是哪个模型最好就需要对所有的模型进行评估,但是这是基本不可能的,因此需要对某些数据进行一定的假设,选择出合适的几个模型进行评估。
4.1 常见问题:数据不匹配
当我们想要训练某个模型时,这时需要大量的数据,于是你用网上下载数以百万计的数据,但是真正符合却可能只有一小部分时,这是如果训练完后生成的模型,在生产环境中效果可能不太好。因此一般可以将符合的与符合的混合,取一半放于测试机,取一般放于验证机,在额外有一个完全符合要求的train-dev(测试开发)集,同时验证。如果此时模型都良好,则说明不是过拟合,如果在验证集不良好,则说明数据不匹配。如果train-dev不匹配则是过拟合。
5. 一个简单的线性回归模型
最后再简单实现一个预测居民生活幸福指数是否与金钱有关系的Demo,本篇只是简单展示一下机器学习的基础和应用。
以基于模型的学习算法,我们想要预测一下塞浦路斯的生活满意度,先从经合组织(OECD)网站上下载“幸福指数”的数据,再从国际货币基金组织(IMF)上找到人均GDP的统计数据。通过绘制出的趋势图,可以很容易得出一个线性模型公式:
使用Scikit-Learn训练好这个模型,再通过这个模型我们可以简单预测一下塞浦路斯的生活满意度,详细代码可以看 01_the_machine_learning_landscape.ipynb
# 预处理数据代码,将CSV读取的oecd和gdp数据进行筛选、合并和重组
def prepare_country_stats(oecd_bli, gdp_per_capita):
oecd_bli = oecd_bli[oecd_bli["INEQUALITY"] == "TOT"]
oecd_bli = oecd_bli.pivot(index="Country", columns="Indicator", values="Value")
gdp_per_capita.rename(columns={"2015": "GDP per capita"}, inplace=True)
gdp_per_capita.set_index("Country", inplace=True)
full_country_stats = pd.merge(left=oecd_bli, right=gdp_per_capita,
left_index=True, right_index=True)
full_country_stats.sort_values(by="GDP per capita", inplace=True)
remove_indices = [0, 1, 6, 8, 33, 34, 35]
keep_indices = list(set(range(36)) - set(remove_indices))
return full_country_stats[["GDP per capita", 'Life satisfaction']].iloc[keep_indices]
# 数据集地址
datapath = os.path.join("datasets", "lifesat", "")
# 加载数据
oecd_bli = pd.read_csv(datapath + "oecd_bli_2015.csv", thousands=',')
gdp_per_capita = pd.read_csv(datapath + "gdp_per_capita.csv", thousands=',', delimiter='\t',
encoding='latin1', na_values="n/a")
# 预处理
country_stats = prepare_country_stats(oecd_bli, gdp_per_capita)
X = np.c_[country_stats["GDP per capita"]]
y = np.c_[country_stats["Life satisfaction"]]
# 数据可视化
country_stats.plot(kind='scatter', x="GDP per capita", y='Life satisfaction')
plt.show()
# 选择线性模型
model = sklearn.linear_model.LinearRegression()
# 训练数据
model.fit(X, y)
# 根据人均GDP预测塞浦路斯的生活满意度
X_new = [[22587]]
print(model.predict(X_new))
最后数据趋势图如下:
输出的数据:
# 最终的数据还是比较符合预期
[[5.96242338]]
如果以基于实例的学习算法,去一个类似人均GDP的国家斯洛文尼亚,满意度为5.7,也可以预测塞浦路斯为5.7。当然如果想要更加精确的,可以再找几个类似的国家,最后求一个平均数,这个便是用到了k-近邻回归算法。只需要修改如下代码:
from sklearn.neighbors import KNeighborsRegressor
# 本例k可以取3,表示有3个类似的对象可以对比
# 将原本的model = sklearn.linear_model.LinearRegression() 代码修改成如下代码
model = KNeighborsRegressor(3)
# 输出[[5.76666667]]
最后再了解一下所用的几个API函数,后面笔记将会有更加详细的解释
# 当DataFrame的格式不是我们所需要时,便需要重组
# pivot会根据填写index和columns对DataFrame进行重组
df.pivot(index="Country", columns="Indicator", values="Value")
# 当需要合并二维数组时
# 使用merge的inner连接方式和依据左右Index合并两个DataFrame
pd.merge(left=oecd_bli, right=gdp_per_capita,left_index=True,right_index=True)