机器学习
一、基本概念
1.机器学习概念
机器学习是一门能够让编程计算机从数据中学习的计算机科学和艺术。
2.为什么要使用机器学习
1.有的任务只能由机器学习完成,例如:垃圾邮件检测
2.提高工作效率,例如:车牌识别、人脸识别
3.机器学习分类
(1)从是否有人类监督的角度划分
- 1.监督学习:用于学习的数据是带标签的数据。
常见任务:
①回归(Regression)
②分类(Classification)
- 2.无监督学习:用于学习的数据是不带任何标签的数据。
常见任务:
①聚类(Clustering)
②降维(Dimensionality Reduction)
- 3.半监督学习:用于学习的数据部分带标签。
- 4.强化学习(Reinforcement Learning,RL):考虑的是一系列动作之后收益最大化。
(2)从系统是否可以传入数据流进行增量学习的角度划分
- 1.批量学习(Batch Learning):必须使用所有可用的数据进行训练,通常都是离线完成的。先完成系统的训练,再投入到生产环境中。如果需要更新系统,则需要重新训练,重新部署。(训练时间长,耗费资源多)
- 2.在线学习(Online Learning):可以循序渐进地给系统提供训练数据,逐步积累学习成果。(遗忘率高,可以通过学习率参数调节)
注意:虽然名字叫在线学习,但实际训练过程同样是离线完成。
(3)从泛化的角度划分
1.基于实例(样本)的学习:系统先完全记住学习的实例,然后通过某种相似度度量方式将其泛化到新的实例。
2.基于模型的学习:从一组实例中,构建这些实例的模型,然后用这个模型进行预测。
4.目前机器学习主要存在的挑战
- 1.训练数据不足
- 2.训练数据不具代表性
- 3.训练数据质量差
- 4.训练数据存在无关特征
- 5.过拟合(Overfitting):模型拟合过度严格,导致泛化能力降低。
解决方案:
①简化模型。
②收集更多数据。
③减少训练数据中的噪音。
- 6.欠拟合(Underfitting)
解决方案:
①选择拥有更多参数、更强大的模型。
②给学习算法提供更好的数据集。
③减少模型中的约束(例如:降低正则化超参数)。注释:
正则化:简化模型
超参数:模型的预设参数(能够控制参数的参数)
5.测试与验证
- 1.一般会将数据集分为训练集(80%)和测试集(20%),用训练集的数据取训练模型,用测试集数据去测试模型的泛化能力。
- 2.验证集(Validation Set):用来训练模型的超参数(在需要验证集时通常以50%/25%/25%划分数据集)。
- 3.通常用80%的数据集作为训练集,20%的数据集作为测试集。
- 4.如果训练误差很低,泛化误差很高,代表模型过拟合。
泛化误差:将模型应用于新场景的误差,通常用来对模型进行评估。
- 5.K-折交叉验证:将训练集分为K个互补的子集,模型通过这个子集的不同组合进行训练,用剩余的子集进行验证。
10-折交叉验证.jpg
二、机器学习基本步骤
以房价预测为例
1.安装Scikit-Learn
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ sklearn
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ scikit-learn
2.确定模型并选择算法
监督式、回归算法 -> 线性回归算法
3.选择算法的性能指标
(1)一般用成本函数(cost function)来表示算法的性能。
回归算法的典型性能指标:均方根误差(RMSE)
RMSE.jpg
h(xi):第i个元素的预测值
yi:原始值
m:样本数量
R(X,h):表示假设h上的成本函数
(2)有时也会用平均绝对误差(MAE)来表示成本。
MAE.jpg(3)范数
范数.png范数的指数越高,则越关注较大的值。
4.读取数据
(1)读取CSV文件
import pandas as pd
def load_data(filename):
return pd.read_csv('./{}.csv'.format(filename))
data = load_data("文件名")
示例:
import pandas as pd
def load_housing_data():
return pd.read_csv('./housing.csv')
housing = load_housing_data()
(2)获取数据的简单描述
housing.info()
(3)查看属性的取值范围及每个取值的数量统计
housing['列名'].value_counts()
(4)了解数据的基本情况
housing.describe()
(5)绘制每个属性的直方图来快速了解数据
%matplotlib inline
import matplotlib.pyplot as plt
housing.hist(bins=50, figsize=(20, 15))
plt.show()
5.切分数据集
(1)手动切分
import numpy as np
def split_train_test(data, test_ratio):
"""
切分训练集和测试集
:param data: 数据集
:param test_ratio: 测试集比率
:return: 训练集、测试集
"""
np.random.seed(42)
# 随机生成不重复的下标
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:]
return data.iloc[train_indices], data.iloc[test_indices]
train_set, test_set = split_train_test(housing, 0.2)
(2)高级切分算法
import hashlib
def test_set_check(identifier, test_ratio, hash):
"""
求数据选取与否的判定掩码
:param identifier: 索引
:param test_ratio: 测试集比率
:param hash: 哈希算法
:return: 判定掩码
"""
return hash(np.int64(identifier)).digest()[-1] < (256 * test_ratio)
def split_train_test_by_id(data, test_ratio, id_column, hash=hashlib.md5):
"""
切分训练集和测试集
:param data: 数据集
:param test_ratio: 测试集比率
:param id_column: ID列索引名
:param hash: 哈希算法
:return: 训练集、测试集
"""
ids = data[id_column]
in_test_set = ids.apply(lambda id_: test_set_check(id_, test_ratio, hash))
return data.loc[~in_test_set], data.loc[in_test_set]
# 为数据添加index列
housing_width_id = housing.reset_index()
train_set, test_set = split_train_test_by_id(housing_width_id, 0.2, 'index')
(3)Scikit-Learn内置切分方法
from sklearn.model_selection import train_test_split
train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)
6.特征提取
(1)创建辅助列
# 创建收入类别辅助列
housing['income_category'] = np.ceil(housing['median_income'] / 1.5)
housing['income_category'].hist(bins=5)
(2)分层随机抽取数据集
from sklearn.model_selection import StratifiedShuffleSplit
split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)
result = split.split(housing, housing['income_category'])
for train_index, test_index in result:
start_train_set = housing.loc[train_index]
start_test_set = housing.loc[test_index]
(3)探索数据
将地理数据可视化
housing = start_train_set.copy()
housing.plot(kind='scatter', x='longitude', y='latitude', alpha=0.3,
s=housing['population'] / 100,
label='population', c='median_house_value',
cmap=plt.get_cmap('jet'), colorbar=True)
plt.legend()
plt.show()
(4)寻找特征之间的关系
- 计算相关系数矩阵
corr_matrix = housing.corr()
corr_matrix['median_house_value'].sort_values(ascending=False)
- 使用Pandas的scatter_matrix函数绘制每个属性相对于其他属性的相关性
from pandas.plotting import scatter_matrix
attributes = ['median_house_value', 'median_income', 'total_rooms', 'housing_median_age']
scatter_matrix(housing[attributes], figsize=(12, 8))
- 添加辅助列
housing['rooms_per_household'] = housing['total_rooms'] / housing['households']
housing['bedrooms_per_room'] = housing['total_bedrooms'] / housing['total_rooms']
housing['population_per_household'] = housing['population'] / housing['households']
corr_matrix = housing.corr()
corr_matrix['median_house_value'].sort_values(ascending=False)
7.数据准备
(1)获取训练数据
# 获取训练数据特征值
housing = start_train_set.drop('median_house_value', axis=1)
# 获取训练数据标签
housing_labels = start_train_set['median_house_value'].copy()
(2)缺失值处理
缺失值处理方式
1.放弃含缺失值的样本
2.放弃含缺失值的属性
3.填补缺失值
# 放弃含缺失值的样本
housing.dropna(['total_bedrooms'])
# 放弃含缺失值的属性
housing.drop('total_bedrooms', axis=1)
# 用平均值填补缺失值
median = housing['total_bedrooms'].mean()
housing['total_bedrooms'].fillna(median)
- Scikit-Learn内置缺失值处理方法:imputer
from sklearn.preprocessing import Imputer
# 删除文本类型属性ocean_proximity
housing_num = housing.drop('ocean_proximity', axis=1)
imputer = Imputer(strategy='median')
# 将填充策略应用于所有属性上
imputer.fit(housing_num)
# 执行填充,返回NumPy数组
X = imputer.transform(housing_num)
# 将NumPy数组转换回DataFrame格式
housing_str = pd.DataFrame(X, columns=housing_num.columns)
- SimpleImputer估算器
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
num_pipeline = Pipeline([
# 用中位数填充缺失值
('impuer', SimpleImputer(strategy='median')),
])
(3)处理文本和分类属性
- 将类别文本转换为数字形式
from sklearn.preprocessing import LabelEncoder
encoder = LabelEncoder()
housing_category = housing['ocean_proximity']
housing_category_encoded = encoder.fit_transform(housing_category)
- 查看文本数值映射关系
encoder.classes_
- 将数字形式编码转换为独热编码
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder()
housing_category_1hot = encoder.fit_transform(housing_category_encoded.reshape(-1, 1))
# 返回结果是一个稀疏矩阵,可以用toarray()方法将其转换为NumPy数组
housing_category_1hot.toarray()
LabelBinarizer转换器:可以将文本一次性转换为独热编码
from sklearn.preprocessing import LabelBinarizer
encoder = LabelBinarizer()
housing_category_1hot = encoder.fit_transform(housing_category)
(4)添加组合属性列
- 自定义转换器
from sklearn.base import BaseEstimator, TransformerMixin
rooms_ix, bedrooms_ix, population_ix, household_ix = 3, 4, 5, 6
class CombineAttributesAdder(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[:, household_ix]
population_per_household = X[:, population_ix] / X[:, household_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 = CombineAttributesAdder(add_bedrooms_per_room=False)
housing_extra_attributes = attr_adder.transform(housing.values)
- 在流水线中使用自定义转换器
from sklearn.pipeline import Pipeline
num_pipeline = Pipeline([
# 新增组合属性列
('attribs_addr', CombineAttributesAdder()),
])
(5)特征缩放
常见缩放方式
- 1.最小最大缩放(归一化):将值重新缩放使其最终范围归到0到1之间。
①最大最小归一化(Min-Max Normalization)
x' = (x - X_min) / (X_max - X_min)
②平均归一化
x' = (x - μ) / (MaxValue - MinValue)
- 2.标准化:将值首先减去平均值(代表标准化后的数据平均值为0),然后除以标准差(使结果的分布具有单位标准差)。
x' = (x - μ)/σ
- 标准化与归一化相比的优势:标准化不将值绑定到特定范围,受异常值影响更小。
- Scikit-Learn提供了两个归一化标准化转换器:MinMaxScaler、StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
num_pipeline = Pipeline([
# 数据标准化
('std_scaler', StandardScaler()),
])
(6)数据准备完整流水线
FeatureUnion类:将多个流水线合并
from sklearn.pipeline import Pipeline
from sklearn.pipeline import FeatureUnion
full_pipeline = FeatureUnion(transformer_list=[
("流水线名1", 流水线名1),
("流水线名2", 流水线名2),
.........
])
- 完整数据预处理流水线
from sklearn.pipeline import Pipeline
from sklearn.pipeline import FeatureUnion
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, LabelBinarizer
from sklearn.base import BaseEstimator, TransformerMixin
class DataFrameSelector(BaseEstimator, TransformerMixin):
def __init__(self, attribute_name):
self.attribute_name = attribute_name
def fit(self, X, Y=None):
return self
def transform(self, X):
return X[self.attribute_name].values
rooms_ix, bedrooms_ix, population_ix, household_ix = 3, 4, 5, 6
class CombineAttributesAdder(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[:, household_ix]
population_per_household = X[:, population_ix] / X[:, household_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]
class MyLabelBinarizer(BaseEstimator, TransformerMixin):
def __init__(self, encoder):
self.encoder = encoder
def fit(self, X, Y=None):
self.encoder.fit(X)
return self
def transform(self, X, Y=None):
return self.encoder.transform(X)
num_attribs = list(housing_num.columns)
cat_attribs = ['ocean_proximity']
label_encoder = LabelBinarizer()
# 数值处理流水线
num_pipeline = Pipeline([
# 将DataFrame转换为NumPy数组
('selector', DataFrameSelector(num_attribs)),
# 用中位数填充缺失值
('imputer', SimpleImputer(strategy='median')),
# 新增组合属性列
('attribs_addr', CombineAttributesAdder()),
# 数据标准化
('std_scaler', StandardScaler())
])
# 文本处理流水线
cat_pipeline = Pipeline([
# 将DataFrame转换为NumPy数组
('selector', DataFrameSelector(cat_attribs)),
# 将类别文本转换为独热编码
('label_binarizer', MyLabelBinarizer(label_encoder)),
])
# 合并流水线
full_pipeline = FeatureUnion(transformer_list=[
("num_pipeline", num_pipeline),
("cat_pipeline", cat_pipeline)
])
# 运行整条流水线
housing_prepared = full_pipeline.fit_transform(housing)
housing_prepared
8.训练模型
(1)训练模型
- 线性回归模型
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(housing_prepared, housing_labels)
- 决策树回归模型
from sklearn.tree import DecisionTreeRegressor
tree_reg = DecisionTreeRegressor()
tree_reg.fit(housing_prepared, housing_labels)
- 随机森林回归模型
from sklearn.ensemble import RandomForestRegressor
forest_reg = RandomForestRegressor(n_estimators=20)
forest_reg.fit(housing_prepared, housing_labels)
(2)保存模型
- 1.安装joblib
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ joblib
- 2.保存模型
import joblib
joblib.dump(模型名, '文件名.pkl')
(3)导入模型
模型名= joblib.load('文件名.pkl')
10.测试模型
(1)均方根误差(RMSE)
- 使用mean_squared_error测量模型均方误差MSE
from sklearn.metrics import mean_squared_error
housing_predictions = lin_reg.predict(housing_prepared)
lin_mse = mean_squared_error(housing_labels, housing_predictions)
lin_rmse = np.sqrt(lin_mse)
print(lin_rmse)
housing_predictions = tree_reg.predict(housing_prepared)
tree_mse = mean_squared_error(housing_labels, housing_predictions)
tree_rmse = np.sqrt(tree_mse)
print(tree_rmse)
(2)泛化性能
- 使用交叉验证测试泛化效果
# 10-折交叉验证
from sklearn.model_selection import cross_val_score
# Scikit-Learn的交叉验证倾向使用效用函数,而非成本函数。因此这里采用负的MSE
scores = cross_val_score(tree_reg, housing_prepared, housing_labels, scoring='neg_mean_squared_error', cv=10)
rmse_scores = np.sqrt(-scores)
def display_scores(scores):
print('Scores:', scores)
print('Mean:', scores.mean())
print('Std:', scores.std())
display_scores(rmse_scores)
# 测试线性回归模型的泛化效果
lin_scores = cross_val_score(lin_reg, housing_prepared, housing_labels, scoring='neg_mean_squared_error', cv=10)
lin_rmse_scores = np.sqrt(-lin_scores)
display_scores(lin_rmse_scores)
由于决策树模型过拟合,因此在此处线性回归模型性能都要优于决策树。
# 测量随机森林回归模型的泛化效果
forest_scores = cross_val_score(forest_reg, housing_prepared, housing_labels, scoring='neg_mean_squared_error', cv=10)
forest_rmse_scores = np.sqrt(-forest_scores)
display_scores(forest_rmse_scores)
11.模型调优
(1)网格搜索
- 网格搜索随机森林回归模型超参数
from sklearn.model_selection import GridSearchCV
param_grid = [
{"n_estimators": [3, 10, 30], "max_features": [2, 4, 6, 8]},
{"bootstrap":[False], "n_estimators": [3, 30], "max_features":[2, 3, 4, 5]}
]
forest_reg = RandomForestRegressor()
grid_search = GridSearchCV(forest_reg, param_grid, cv=5, scoring="neg_mean_squared_error")
grid_search.fit(housing_prepared, housing_labels)
- 获取搜索结果
# 最好的一组参数
grid_search.best_params_
# 最好的模型
grid_search.best_estimator_
# 获取评估分数
cvres = grid_search.cv_results_
for mean_score, params in zip(cvres['mean_test_score'], cvres['params']):
print(np.sqrt(-mean_score), params)
(2)随机搜索
RandomizedSearchCV类:用法与GridSearchCV完全一样,但是不会尝试所有的组合,而是在每次迭代中为每个超参数随机选择一个值,然后对一定数量的随机组合进行评估。
from sklearn.model_selection import RandomizedSearchCV
三、Scikit-Learn框架
Scikit-Learn API设计具有较高的一致性
一般来说Scikit-Learn由以下模块组成:
- 1.估算器:能够根据数据集对某些参数进行估算的任意对象(例如:Imputer)。
- 估算器由fit()方法执行,需要一个数据集作为参数。
- 对于监督学习,需要两个参数,第二个参数是包含标签的数据集。
- 创建估算器需要指定估算策略(例如:strategy='mean')。
- 2.转换器:可以用来转换数据集的估算器。
- API一般包括:transform():该方法传入一个待转换的数据集,返回一个转换后的数据集。
- 所有转换器都有一个方法:fit_transform():相当于先调用fit()方法,再调用transform()方法。而且这个方法经过了优化,会更快。
- 3.预测器:能够基于给定数据集进行预测的估算器。
- predict()方法:接受一个新的实例数据集,返回一个包含相应预测的数据集。
- score()方法:衡量给定测试集的预测质量。
1.估算器
2.转换器
自定义转换器
- 1.转换器需要实现三个方法:fit()(返回自身)、transform()、fit_transform()。
- 2.如果将TransformMixin作为基类,可以直接调用fit_transform()。
- 3.如果将BaseEstimator作为基类(在构造方法中,args、kwargs参数不适用),可以获得get_params()和set_params()方法(用来调整超参数)。
3.流水线
Scikit-Learn提供了Pipeline(流水线),用来完成按步骤的数据转换。
Pipeline(流水线)的构造函数是一系列估算器、转换器定义的步骤序列。除了最后一个可以是估算器以外,其余的都必须是转换器。
流水线名 = Pipeline([
('转换器名1', 转换器1(参数)),
('转换器名2', 转换器2(参数)),
.........
('转换器名n', 估算器/转换器n(参数))
])
- FeatureUnion类:将多个流水线合并
流水线名 = FeatureUnion(transformer_list=[
("流水线名1", 流水线名1),
("流水线名2", 流水线名2),
.........
])
网友评论