【一起来啃西瓜书】——模型评估与选择

1.经验误差与过拟合

1)经验误差与泛化误差

a.错误率:测试样本中分类错误的样本数占总样本数的比例。
E = b m × 100 % E = \frac bm ×100 \% E=mb×100%
b.精度:测试样本中分类正确的样本数占总样本数的比例。
A c c = k m × 100 % = 1 − b m × 100 % Acc = \frac km ×100 \% = 1- \frac bm ×100 \% Acc=mk×100%=1mb×100%
错误率 + 精度 = 1

错误率与精度常用于评估分类模型的泛化能力

:假设我们有一个水果数据集 D ;其中, D 包括了12个苹果和8个西瓜一共20个水果;我们使用数据集 D 学习得到了一个水果分类模型 M ,如图所示:
在这里插入图片描述

可以发现,分类模型 M 把水果分成了两部分。

其中,左边的水果分类为苹果,右边的水果分类为西瓜。

然而水果分类模型却将其中一个西瓜错分为苹果,将其中两个苹果错分为西瓜。

根据上面的计算公式,可以得到:

​ m = 20
​ b = 3
​ a = 17
E = b m × 100 % = 3 20 × 100 % = 15 % E = \frac bm ×100 \% = \frac {3}{20} ×100 \% = 15 \% E=mb×100%=203×100%=15%
​ 𝐴𝑐𝑐=1−𝐸=85%

即水果分类模型的错误率是15%,精度是85%。

错误率与精度的代码实现

在这里插入图片描述

可以看出,水果分类模型对5、14、15号水果的分类结果是错误的。

#导入相关包
import numpy as np
#获取真实标签和预测标签   其中,0表示苹果,1表示西瓜
# 数据集的真实标签  
# x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 x13 x14 x15 x16 x17 x18 x19 x20
ground_true_label = np.array([0,0,0,0,1,0,0,0,0,0,0,1,1,0,0,1,1,1,1,1])                                                                 
# 分类模型对数据集的预测标签
# x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 x13 x14 x15 x16 x17 x18 x19 x20
classifier_pred = np.array([0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1])

#比较真实标签和预测标签
cmp = (ground_true_label == classifier_pred)
print('cmp        :',cmp)
cmp = cmp.astype(np.int)
print('cmp        :',cmp)

#获得样本数 m 和分类错误的样本数 b
m = len(ground_true_label)
b = m - np.sum(cmp)

#计算错误率(error rate)和精度(accuracy):
error_rate = (b/m)*100
print('error_rate=%i'%error_rate + '%')
accuracy = (1 - b/m)*100
print('accuracy=%i'%accuracy + '%')

error_rate=15%

accuracy=85%

c.误差:模型的实际预测输出与样本的真实输出之间的差异。
在这里插入图片描述

训练误差经验误差:模型在训练集 D 上的误差

泛化误差: 模型在新样本的数据集 T 上的误差

2)过拟合与欠拟合

a.过拟合:训练误差和泛化误差之间的差距太大。(模型在训练集上表现很好,但在测试集上却表现很差。)

b.欠拟合:经验误差和泛化误差都很大。(模型无论是在训练集中还是在新样本上,表现都很差。)

在这里插入图片描述

3)原因和解决措施

a.过拟合的原因和解决措施

<1>过拟合的原因

​ 训练数据集样本不足

​ 训练数据中噪声干扰过大

​ 模型过于复杂

<2>过拟合的解决措施(过拟合是无法彻底避免的,我们所能做的只是"缓解",或者说减小其风险。)

​ 数据集增强

​ 降低模型复杂度

​ Early stopping(提前终止)

img

​ 降低特征的数量

​ Dropout (在训练过程中每次按一定的概率随机地“删除”一部分神经元)

​ 使用正则化(在目标函数之后加上对于模型参数 W 的范数,在机器学习中一般使用 L2 正则化)

b.欠拟合的原因和解决措施

<1>欠拟合的原因:

通常是由于模型学习能力较弱,而数据复杂度较高,此时模型由于学习能力不足,无法学习到数据集中的“一般规律”。

<2>欠拟合的解决措施

​ 增加新特征(可以考虑加入新特征来增大假设空间)

​ 添加多项式特征(可以将线性模型通过添加二次项或者三次项使模型泛化能力更强)

​ 减少正则化参数(模型出现了欠拟合需要减少正则化参数)

​ 使用非线性模型(比如核SVM 、决策树、深度学习等模型)

​ 调整模型的容量(可以适当增加模型的容量)

2.评估方法

测试集应该尽可能与训练集互斥。

我们只有一个包含m个样例的数据集D,既要训练,又要测试,一般是通过对D进行适当的处理,从中产生训练集S和测试集T。

1)留出法

直接将数据集D划分为两个互斥的集合。

在这里插入图片描述

例如:以二分类任务为例,假定D包含1000个样本,将其划分为S包含700个样本,T包含300个样本,用S进行训练后,如果模型在T上有90个样本分类错误,那么其错误率为(90/300)×100%=30%,相应的,精度为1-30%=70%。

在使用留出法时,需要注意:

  • 要有足够的样本量,以保证训练模型的效果。

  • 在划分时注意保证数据分布的一致性(如:500个样本中正例和反例的比为2:3,则在训练集和测试集中正例和反例的比也要求为2:3),只需要采用随机分层抽样即可。

  • 为了减弱随机划分的影响,重复划分训练集和测试集,对得到的多次结果取平均作为最后的结果。

  • 一般训练集和测试集的比例在8:2或者7:3。

import numpy as np

# 加载数据集
def load_pts(): 
    '''
    return: 返回随机生成200个点的坐标
    '''
    dots = 4608  # 样本数
    dim = 2 #数据维度,分别表示苹果的大小和颜色
    X = np.random.uniform(0, 50, (dots,dim)) #建立数据集,shape(4608,2)
    # 建立样本X的类别
    y = np.zeros(dots, dtype='int')      
    for i in range(X.shape[0]):
        if X[i,0] > 18 and X[i,0] < 40 and X[i,1] > 25 and X[i,1] < 35: 
            y[i] = 1           
    return X, y

X, Y = load_pts()

# 请你补全以下代码,实现留出法
def train_test_split(X, Y, test_size, is_shuffle=True, random_state=2021):
    """
    计算错误率和精度
    :参数 X: 数据集样本
    :参数 Y: 数据集标签
    :参数 test_size: 测试集比例
    :is_shuffle: 是否随机打乱数据集
    :random_state: 随机种子
    :返回值: train_X: 训练集样本
            test_X: 训练集标签
            train_Y: 测试集样本
            test_Y: 测试集标签
    """
    if is_shuffle is True:
        shuffle_indexs=np.random.permutation(len(X))
    else:
        shuffle_indexs=np.arange(len(X))
    test_size=int(len(X)*test_size)
    test_indexs=shuffle_indexs[:test_size]
    train_indexs=shuffle_indexs[test_size:]
    train_X=X[train_indexs]
    train_Y=Y[train_indexs]
    test_X=X[test_indexs]
    test_Y=Y[test_indexs]
    return train_X, test_X, train_Y ,test_Y
    
train_X , test_X, train_Y ,test_Y = train_test_split(X, Y, 0.25, random_state=2021)

print("训练集样本train_X的shape:{}".format(train_X.shape))
print("训练集标签test_X的shape :{}".format(test_X.shape))
print("测试集样本train_Y的shape:{}".format(train_Y.shape))
print("测试集标签test_Y的shape :{}".format(test_Y.shape))

2)交叉验证法

先将数据集D划分为k个 大小相似的互斥子集
在这里插入图片描述
k最常用的取值是10,此时称为10折交叉验证。
在这里插入图片描述

留一法:若数据集 D 包含m个样本,先将数据集 D 划分为m个大小相似的子集,每个子集只有一个样本数据,则得到了交叉验证法的一个特例,留一法(Leave-One-Out,简称LOO)。

# 导入包
from sklearn.model_selection import KFold
import numpy as np

# 生成数据集,随机生成40个点
data = np.random.randn(40,2)

# 交叉验证法
kf = KFold(n_splits = 10,shuffle = False,random_state = None) 

for train, test in kf.split(data):
    print(train)
    print(test,'\n')

在这里插入图片描述

3)自助法

有放回抽样,给定包含m个样本的数据集 D ,我们对它进行采样产生数据集 D’ :

  • 每次随机从 D 中挑选一个样本;
  • 将该样本拷贝放入 D’,然后再将该样本放回初始数据集 D 中;
  • 重复执行m次该过程;
  • 最后得到包含m个样本数据集 D’。

初始数据集D中有一部分样本会在数据集 D’ 中多次出现,也有一部分样本不会在数据集 D’ 中出现。

样本在m次采样中始终不被采到的概率是:
在这里插入图片描述

对m取极限得到:
在这里插入图片描述

通过上式可知,初始数据集 D 中约有36.8%的样本未出现在采样数据集 D’ 中。

于是我们可以将 D’ 用作训练集,实际评估的模型与期望评估的模型都使用 m 个训练样本,我们仍有数据总量约1/3的,没在训练集中出现的样本作为测试集用于测试这样的测试结果,亦被称为”包外估计“(out-of-bag estimate)。

自助法产生数据集改变了初始数据集的分布会引入估计偏差,数据集D分为D1,D2,D3,D4数据集,假设知道自助采样的数据集结果,比较自助法采样产生的数据集分布和初始数据集的分布。
在这里插入图片描述

# 导入包
import numpy as np

#任意设置一个数据集
X = [1,4,3,23,4,6,7,8,9,45,67,89,34,54,76,98,43,52]

#通过产生的随机数获得抽取样本的序号 
bootstrapping = []
for i in range(len(X)):
    bootstrapping.append(np.random.randint(0,len(X),(1)))
    
#通过序号获得原始数据集中的数据
D_1 = []
for i in range(len(X)):
    D_1.append(X[int(bootstrapping[i])])
    
print(D_1)

在这里插入图片描述

4)评估方法比较

在这里插入图片描述

小样本数据集:建议采用自助法

大一点样本数据集:建议采用十折交叉验证法

超大样本数据集:建议采用留出法

5)调参与最终模型

机器学习算法工程师通常被称为“调参侠”,由此可见调参在机器学习应用中有举足轻重的地位。

在不少应用任务中,参数调的好不好往往对最终模型性能有关键性影响。

a.调参

大多数学习算法都有些参数(parameter) 需要设定,参数配置不同,学得模型的性能往往有显著差别。因此,在进行模型评估与选择时,除了要对适用学习算法进行选择,还需对算法参数进行设定,这就是通常所说的"参数调节"或 简称"调参" (parameter tuning)。

一般来说

对每种参数配置都训练出模型,然后把对应最好模型的参数作为结果。

但需注意

学习算法的很多参数是在实数范围内取值,因此,对每种参数配置都训练出模型来是不可行的。现实中常用的做法,是对每个参数选定一个范围和变化步长,例如在 [0 0.2] 范围内以 0.05 为步长,则实际要评估的候选参数值有5个,最终是从这5个候选值中产生选定值。

显然,这样选定的参数值往往不是"最佳"值,但这是在计算开销和性能估计之间进行折中的结果,通过这个折中,学习过程才变得可行。

调参的复杂性

假定算法有3个参数,每个参数仅考虑5个候选值,这样对每一组训练/测试集就有 5^3 = 125 个模型需考察;很多强大的学习算法有大量参数需设定,这将导致极大的调参工程量。

b.最终模型

用于对新数据进行预测的机器学习模型。

也就是说,给出新输入数据的例子,然后使用该模型预测输出的值。这可能是一个分类(分配标签)或回归(估实际值)模型。

例如,不管是判断猫还是狗的照片,还是明天的估计销售数量;机器学习项目的目标是获得最佳的最终模型,其中“最佳”由以下因素决定:

数据:可用的历史数据。

时间:在项目上花费的时间。

程序:数据准备步骤,一个或多个算法,以及算法配置的选择。

在整个项目中,收集数据,花费大量时间;要使用数据准备程序,要使用的算法以及如何对其进行配置。

最终的模型是这个过程的巅峰之作,最后你会发现实际上就是要做预测。

c.训练集VS验证集VS测试集

  • 训练集(train set) —— 用于模型拟合的数据样本。
  • 验证集(validation set)—— 是模型训练过程中单独留出的样本集,它可以用于调整模型的超参数和用于对模型的能力进行初步评估。 通常用来在模型迭代训练时,用以验证当前模型泛化能力(准确率,召回率等),以决定是否停止继续训练。
  • 测试集 —— 用来评估模最终模型的泛化能力。但不能作为调参、选择特征等算法相关的选择的依据。
    在这里插入图片描述

举栗子:

(1)训练集:学生的课本;学生根据课本里的内容来掌握知识

(2)验证集:作业;通过作业可以知道不同学生学习情况、进步的速度快慢

(3)测试集:考试;考的题是平常都没有见过,考察学生举一反三的能力

3.性能度量

1)性能度量的概念

对学习器的泛化性能进行评估,不仅需要有效可行的实验估计方法,还需要有衡量模型泛化能力的评价标准,这就是性能度量。

性能度量反映了任务需求,在对比不同模型的能力时,使用不同的性能度量往往会导致不同的评判结果。
这意味着模型的"好坏"是相对的,什么样的模型是好的? 这不仅取决于算法和数据,还决定于任务需求。

在这里插入图片描述

2)评估指数

a.均方误差MSE

回归任务最常用的性能度量是"均方误差"。
M S E = E ( f ; D ) = 1 m ∑ i = 1 m ( f ( x i ) − y i ) 2 MSE = E(f;D) = \frac 1m \sum_{i=1}^m (f(x_i)-y_i)^2 MSE=E(f;D)=m1i=1m(f(xi)yi)2

b.错误率与精度

错误率与精度是分类任务常用的性能度量

错误率:
E ( f ; D ) = 1 m ∑ i = 1 m P ( f ( x i ) ≠ y i ) E(f;D) = \frac 1m \sum_{i=1}^m P(f(x_i) \ne y_i) E(f;D)=m1i=1mP(f(xi)=yi)
精度:
a c c ( f ; D ) = 1 − E ( f ; D ) = 1 m ∑ i = 1 m P ( f ( x i ) = y i ) acc(f;D) = 1-E(f;D) =\frac 1m \sum_{i=1}^m P(f(x_i) 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是用Python实现的西瓜书id3决策树模型的代码: ```python import math import pandas as pd class Node: ''' 决策树节点类 ''' def __init__(self, col=-1, value=None, results=None, tb=None, fb=None): self.col = col # 待检验的判断条件所对应的列索引值 self.value = value # 为了使结果为True,当前列必须匹配的值 self.results = results # 存储叶节点上的结果,是一个字典形式,键为类别,值为次数 self.tb = tb # 左子树 self.fb = fb # 右子树 def load_data(): ''' 加载西瓜数据集,返回特征数据和标签 ''' data = pd.read_csv('watermelon.csv') return data.iloc[:, 1:-1], data.iloc[:, -1] def calc_entropy(labels): ''' 计算数据集的熵 ''' total = len(labels) counts = {} for label in labels: if label not in counts: counts[label] = 0 counts[label] += 1 entropy = 0.0 for key in counts: p = counts[key] / total entropy -= p * math.log2(p) return entropy def split_data(data, labels, col, value): ''' 根据给定特征划分数据集 ''' tb_rows, fb_rows = [], [] for i in range(len(data)): row = list(data.iloc[i]) if row[col] == value: tb_rows.append(row + [labels[i]]) else: fb_rows.append(row + [labels[i]]) return pd.DataFrame(tb_rows, columns=data.columns.tolist() + ['label']), pd.DataFrame(fb_rows, columns=data.columns.tolist() + ['label']) def build_tree(data, labels): ''' 构建决策树 ''' if len(labels) == 0: return Node() current_entropy = calc_entropy(labels) best_gain = 0.0 best_criteria = None best_sets = None feature_num = len(data.columns) for col in range(feature_num): column_values = set(data.iloc[:, col]) for value in column_values: tb_data, fb_data = split_data(data, labels, col, value) p = len(tb_data) / len(data) gain = current_entropy - p * calc_entropy(tb_data['label']) - (1 - p) * calc_entropy(fb_data['label']) if gain > best_gain and len(tb_data) > 0 and len(fb_data) > 0: best_gain = gain best_criteria = (col, value) best_sets = (tb_data, fb_data) if best_gain > 0: tb = build_tree(best_sets[0], best_sets[0]['label']) fb = build_tree(best_sets[1], best_sets[1]['label']) return Node(col=best_criteria[0], value=best_criteria[1], tb=tb, fb=fb) else: return Node(results={label: len([label for label in labels if label == '是']), '否': len([label for label in labels if label == '否'])}) def classify(sample, tree): ''' 使用决策树对单个样本进行分类 ''' if tree.results is not None: return tree.results else: v = sample[tree.col] branch = None if v == tree.value: branch = tree.tb else: branch = tree.fb return classify(sample, branch) def predict(data, tree): ''' 对数据集进行分类 ''' return [classify(list(data.iloc[i]), tree) for i in range(len(data))] data, labels = load_data() tree = build_tree(data, labels) print(predict(data, tree)) ``` 这里使用了pandas库来读取数据集,需要将数据集放在与代码文件相同的目录下,并命名为watermelon.csv。函数load_data返回的是特征数据和标签,分别是DataFrame类型和Series类型。函数build_tree实现了id3算法,返回构建好的决策树。函数classify用于对单个样本进行分类,函数predict用于对整个数据集进行分类
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值