场景
之前看一些歌手或者演员选取节目的时候,上面不是一个评委,少则三五个,多则几十个,当做重要决定时,大家可能都会考虑吸取多个专家而不只是一个人的意见。机器学习也是一样的,机器学习中分为两种,投票选举和再学习,其中投票选举类似于Redis集群中选举Master,在机器学习中投票选举最流行的就是 : 随机森林(random forest)。
随机森林
随机森林的船队组成:
随机森林,这是一支由许多树组成的强大舰队,每棵树都是通过观察星辰——数据的一部分,来学习并作出自己的预测。当我们需要做出最终决定时,这些树会聚在一起,通过多数投票的方式来确定最佳的行动路径。
树的数量(n_estimators):想象你有一支由许多船只组成的舰队,每艘船都可以独立探索并报告它们发现的宝藏。在随机森林中,这些船只就是决策树,而你的舰队就越大,就越有可能找到最珍贵的宝藏。
最大特征数(max_features):每艘船只通过望远镜观察大海,但为了保证探索的多样性,每次只能选择一定数量的星星来引导航向。在随机森林中,这意味着每棵树在分裂时只考虑一个随机子集的特征,这帮助提高了整个舰队的导航能力(模型的泛化能力)。
树的最大深度(max_depth):这表示你的船只能下潜多深。有时,较浅的潜水足以发现埋藏在海底的宝藏,而过深的潜水可能导致困在特定区域。在随机森林中,限制树的深度可以帮助防止模型过于复杂和过拟合。
样本的随机选择(bootstrap samples):在出航前,每艘船通过抽签决定其船员名单,即从所有海盗中随机选择一部分,有时还会重复选择某些海盗。这样做确保了每次探险的独特性和舰队的多样性。
DEMO
我们以预测泰坦尼克号乘客的生存情况为例,详细探讨随机森林算法,并逐一说明每个参数的作用及其大小对模型性能的影响。我们将从数据加载、预处理,到模型训练、参数调优,最后是模型评估和保存,贯穿整个机器学习流程。
数据加载和预处理
我们使用泰坦尼克号乘客数据集,这个数据集包括乘客的年龄、性别、票价等特征,以及他们是否生存的信息。
加载数据
# 加载数据
data = pd.read_csv('titanic.csv')
现在要选择特征标签
# 选择特征和标签
X = data.drop('Survived', axis=1)
y = data['Survived']
数据预处理
这里数据有两种,一种是数值,一种是类别,我们分开处理
# 预处理
numeric_features = ['Age', 'Fare']
categorical_features = ['Sex', 'Pclass', 'Embarked']
对于数值的数据,我们对于缺省的部分,我们使用SimpleImputer来处理,策略是取中值,然后每个特征去中心化并缩放到单位方差。代码应该是:
numeric_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())])
对于类型的数据,我们使用独热编码转换器
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
('onehot', OneHotEncoder(handle_unknown='ignore'))])
组合起来处理
preprocessor = ColumnTransformer(
transformers=[
('num', numeric_transformer, numeric_features),
('cat', categorical_transformer, categorical_features)])
X_preprocessed = preprocessor.fit_transform(X)
OneHotEncoder的作用
将类别特征转换为数值特征:机器学习模型通常无法直接处理文本或类别数据,OneHotEncoder通过将每个类别映射到一个独热向量来解决这个问题。独热向量长度等于类别的数量,对于每个样本,其对应类别的位置为1,其余位置为0。
避免数值类别的误导:如果简单地将类别编码为数值(比如1, 2, 3等),模型可能会误解这些数字之间存在数学上的顺序或比例关系。独热编码通过将每个类别表示为一个独立的特征,避免了这种误解。
OneHotEncoder的主要参数
handle_unknown:这个参数决定了当遇到训练集中未见过的类别时OneHotEncoder应该如何处理。如果设置为’ignore’,则在转换时忽略未知类别,如果设置为’error’(默认值),则遇到未知类别时抛出错误。
drop:可以设置为None(默认,不删除任何类别)、‘first’(删除每个特征的第一个类别,用于减少由于独热编码引入的多重共线性问题)、或者是一个数组指定哪些类别应该被删除。
Pipeline
Pipeline是scikit-learn一个类,用于封装机器学习工作流中的一系列预处理步骤和最终的估计器。在机器学习项目中,通常需要执行多个数据预处理步骤,然后再进行模型训练和预测。Pipeline允许你将这些步骤组织成一个序列,这样在训练和预测时,只需调用Pipeline的方法即可自动按顺序执行所有步骤。使用Pipeline可以有效防止数据泄露,特别是在交叉验证和网格搜索时。数据泄露通常发生在预处理步骤使用了测试集信息的情况下,这会导致过于乐观的性能估计。Pipeline确保每次交叉验证的迭代中,预处理步骤仅使用来自当前训练集的信息。Pipeline可以与GridSearchCV或RandomizedSearchCV等工具结合使用,以便在执行模型选择和超参数调优时自动应用相同的预处理步骤。这使得寻找最佳模型参数的过程既高效又一致。
HOW TO USE
#创建Pipeline时,你需要提供一个步骤列表,每个步骤由一个元组组成,元组的第一个元素是步骤的名称,第二个元素是执行该步骤的估计器或转换器对象。
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
pipeline = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler()),
('classifier', LogisticRegression())
])
训练模型:调用fit方法进行训练。
pipeline.fit(X_train, y_train)
进行预测:使用predict方法进行预测。
y_pred = pipeline.predict(X_test)
评估模型:可以使用score方法或其他评估函数。
score = pipeline.score(X_test, y_test)
划分测评集
测试集大小:原始代码中使用test_size=0.2表示测试集占原始数据集的20%。
分割验证集:从训练集中分割出验证集时,如果我们希望验证集的大小相对于原始数据集是固定的比例,那么我们需要进行一些计算。因为在第一次分割后,训练集占了原始数据的80%,我们再从中分割出25%作为验证集,实际上验证集占原始数据集的20%(因为0.25 * 0.8 = 0.2)。这样,训练集、验证集和测试集的比例将会是64%(训练集)、16%(验证集)、20%(测试集)相对于原始数据集。
random_state参数:这个参数确保每次分割都能得到相同的结果,这对实验的可重复性很重要。
# 假设 X_preprocessed 和 y 已经是预处理后的数据
# 首先分割出训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X_preprocessed, y, test_size=0.2, random_state=42)
# 然后从训练集中分割出验证集
X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=0.25, random_state=42)
模型训练
我们使用随机森林模型训练模型
model = RandomForestClassifier(n_estimators=100, max_depth=None, random_state=42)
model.fit(X_train, y_train)
参数解释和调优
n_estimators(树的数量):
作用:随机森林中决策树的数量。更多的树增加了模型的稳定性和准确性,但同时也增加了计算成本。
太大:可能导致计算时间长,但通常不会导致过拟合。
太小:可能导致模型没有充分学习数据,影响准确性。
max_depth(树的最大深度):
作用:决定树可以生长的深度。限制深度有助于防止过拟合。
太大:树可能会变得复杂,捕捉过多训练数据的噪声,导致过拟合。
太小:可能导致树没有足够的分割来理解数据,导致欠拟合。
max_features(最大特征数):
作用:决定每次分割时考虑的最大特征数量。它控制了树的随机性。
太大:树之间的差异减小,可能导致模型过于依赖某些特征,减少模型的泛化能力。
太小:增加单个树的多样性,但也
可能导致每棵树没有充分利用有用信息。
使用GridSearchCV进行参数调优
param_grid = {
'n_estimators': [100, 200],
'max_depth': [10, 20, None],
'max_features': ['sqrt', 'log2', None]
}
grid_search = GridSearchCV(RandomForestClassifier(random_state=42), param_grid, cv=5)
grid_search.fit(X_train, y_train)
print("最佳参数:", grid_search.best_params_)
这个之前已经聊过了
性能测试和保存
和之前操作几乎一致了
# 使用找到的最佳参数创建新的SVM模型
best_model = grid.best_estimator_
# 首先在验证集上进行评估,验证集用于模型选择和调参,已经通过交叉验证间接使用
y_val_pred = best_model.predict(X_val)
print("性能评估(验证集):")
print(classification_report(y_val, y_val_pred))
# 现在使用测试集来评估最终模型的性能
y_test_pred = best_model.predict(X_test)
print("性能评估(测试集):")
print(classification_report(y_test, y_test_pred))
# 保存模型
model_filename = 'best_model.joblib'
dump(best_model, model_filename)
结束
随机森林是一种集成学习算法,它的核心思想是构建多个决策树并将它们的预测结果汇总得到最终结果。随机森林通过引入随机性来提升模型的准确性和鲁棒性,具体原理如下:
-
集成学习
随机森林属于集成学习方法中的Bagging(自助聚合)策略。集成学习的核心思想是将多个弱学习器(模型表现稍好于随机猜测的模型)组合起来,形成一个强学习器(性能较好的模型)。在随机森林中,这些弱学习器就是决策树。 -
构建多个决策树
随机森林通过在训练过程中构建多个决策树来工作。每棵树的训练数据都是通过从原始数据集中进行有放回抽样(也称为自助采样)得到的。这意味着,每棵树的训练数据集可能包含重复的样本,而有些原始数据则可能被省略。 -
引入随机性
随机森林中的“随机”体现在两个方面:
随机样本:如上所述,每棵树训练的数据是通过从原始数据集中有放回抽样得到的,使得每棵树的训练数据都有所不同。
随机特征:在每个决策树的分裂过程中,算法随机选择一部分特征而不是所有特征,并在这些随机选出的特征中选择最佳分裂特征。这进一步增加了模型的多样性。 -
汇总预测结果
随机森林的最终预测是通过汇总其所有决策树的预测结果得到的:
分类任务:采用多数投票机制,即随机森林输出所有树中得票最多的类别作为最终预测结果。
回归任务:计算所有树的预测结果的平均值作为最终预测结果。 -
优点
准确性高:通过集成多个决策树,随机森林通常能提供高准确率的预测。
防止过拟合:相较于单一决策树,随机森林引入的随机性帮助降低了模型过拟合的风险。
灵活性:能够处理分类和回归任务,且能够处理特征数量大于样本数量的情况,处理缺失值,并提供特征的重要性评估。 -
缺点
模型复杂度:随机森林模型包含多个决策树,可能导致模型较大,占用更多内存,预测过程较慢。
解释性:相比于单一决策树,随机森林模型的解释性较差,因为你需要考虑多棵树的决策过程。