【学习机器学习】实验——模型评估与选择

【学习机器学习】模型评估与选择

这学期的课很多都要实验啊,不过机器学习真的可以算其中比较离谱的了,不说当堂上交,就这个任务量也属实有点多。其实要到4月3号才是我们班做,提前找其他班同学要到了先用python写写,到时候可能还要用matlab重新实现。。。

0、实验总览

Ⅰ、编程实现训练数据集与测试数据集

Ⅱ、编程实现性能度量

Ⅲ、编程实现假设检验(不太会)

一、任务一

1.1 留出法

留出法原理很直观,就是按照设定的比例对原始数据随机划分成两个互斥的集合,注意训练/测试集划分要尽可能保持数据分布的一致性(其实只要是随机分的多数可以保证)
那代码实现,如果调用sklearn真的是一句话就完事儿了,例如:

 count = 0
 while count < 100:
        data_train, data_test = train_test_split(data, train_size=0.80, random_state=None, shuffle=True)
        np.savetxt('./留出法数据/train{no}.csv'.format(no=count+1), data_train, fmt='%f', delimiter=',')
        np.savetxt('./留出法数据/test{no}.csv'.format(no=count+1), data_test, fmt='%f', delimiter=',')
        count = count + 1

所以我决定对这个train_test_split的源码进行一下研究,看看具体怎么实现的(防止老师禁止调用现成函数)

train_test_split源码如下:

def train_test_split(*arrays, **options):
    n_arrays = len(arrays)  # 一共有几个数据集
    if n_arrays == 0:       # 不能为空数据集
        raise ValueError("At least one array required as input")
    test_size = options.pop('test_size', None)  # test_size即测试集占比,0~1之间的浮点数
    train_size = options.pop('train_size', None)    # train_size即训练集的占比,0~1之间的浮点数
    random_state = options.pop('random_state', None)    # 可以是一个整数,指定随机数生成器的种子
    stratify = options.pop('stratify', None)    # stratify可以是一个数据或者None。如果它不是None,则原始数据会分层采样,采样的标记数据由该参数指定。
    shuffle = options.pop('shuffle', True)  # shuffle主要决定每次执行结果是否相同

    if options: # 无效的选项
        raise TypeError("Invalid parameters passed: %s" % str(options))

    arrays = indexable(*arrays)

    n_samples = _num_samples(arrays[0])
    n_train, n_test = _validate_shuffle_split(n_samples, test_size, train_size, 
    default_test_size=0.25)   # 检验参数选项是否合理,得到训练集和测试集大小

    if shuffle is False:
        if stratify is not None:
            raise ValueError(
                "Stratified train/test split is not implemented for "
                "shuffle=False")

        train = np.arange(n_train)
        test = np.arange(n_train, n_train + n_test)

    else:
        if stratify is not None:
            CVClass = StratifiedShuffleSplit
        else:
            CVClass = ShuffleSplit

        cv = CVClass(test_size=n_test,
                     train_size=n_train,
                     random_state=random_state)

        train, test = next(cv.split(X=arrays[0], y=stratify))

    return list(chain.from_iterable((safe_indexing(a, train),
                                     safe_indexing(a, test)) for a in arrays))  
    #返回值:一个列表,依次给出一个或者多个数据集的划分的结果。每个数据集都划分为两部分:训练集和测试集。

# Tell nose that train_test_split is not a test.
# (Needed for external libraries that may use nose.)
train_test_split.__test__ = False

一些参数的意义我已经写了注释,不过注释并不全面完整,因为这些参数可以取浮点值可以取整数值,还可以有布尔值的表示,我只列出了这次实验要用的最常用的。另外最后实际生效用来做划分的ShuffleSplit对象,以及调用split方法,大家可以点进链接看看比较专业的解答。

1.2 p次k折交叉验证法

交叉验证的原理也很好理解,把原本的数据集近似均分(大小相似即可)的划分为k个子集。每次用k-1个子集的并集作为训练集,剩下的那1个作为测试集,这样一来k折就会有k次训练、测试结果,而p次k折一共可以做出k*p次结果,却只需要p次划分,相当方便。
sklearn里面直接就有一个RepeatedKFold对象,完美实现:

    count = 0
    kf = RepeatedKFold(n_splits=10, n_repeats=5, random_state=0)
    for train, test in kf.split(data):
        # print("%s""\n""%s" % (train, test))
        np.savetxt("./交叉验证法数据/train{no}.csv".format(no=count+1), train, fmt="%f", delimiter=",")
        np.savetxt("./交叉验证法数据/test{no}.csv".format(no=count+1), test, fmt="%f", delimiter=",")
        count = count + 1

这个的源码就不看了,因为连随机都用不到,直接等分即可。另外k折可以衍生出一种留一法,即假设数据有m条,那么就m折(每条数据算一个子集),这样即使数据不多也可以大幅提高测试量,结果一般会更准(但不一定),缺点是太费时。

1.3 自助法

自助法我喜欢叫抽签法,因为和概率论里那些抽来抽去的好像。基本原理就是对有m个数据的数据集进行m次有放回的抽取,把抽到的作为训练集D’,D-D’作为测试集。
从原理上讲,有大概35%的数据永远不会被抽到,因此一般只用于小量数据或者难以有效划分训练/测试集时才会用。
python实现:我只做了一些修改,原本见代码出处

bootstrapping = []
    for i in range(len(data)):
        bootstrapping.append(np.floor(np.random.random() * len(data)))
    # 通过序号获得原始数据集中的数据
    train_set = []
    test_set = []
    for i in range(len(data)):
        train_set.append(data[int(bootstrapping[i])])
    np.savetxt("./自助法数据/train.csv", train_set, fmt="%f", delimiter=",")
    for i in range(len(data)):
        if i not in bootstrapping:
            test_set.append(data[int(bootstrapping[i])])
    np.savetxt("./自助法数据/test.csv", test_set, fmt="%f", delimiter=",")

二、任务二

接下来求取一系列性能度量就开始让我有些疑惑了,主要是数据应该是什么格式呢?以下的大部分代码我都是假设有已知list集合:

D=[[x1,y1],[x2,y2],[x3,y3],…,[xm.ym]]

其中[xi,yi],xi表示预测器得到的结果,yi表示标记值

2.1 均方误差MSE

均方误差的公式为: E ( f ; D ) = 1 m i = 1 m ( f ( x i ) y i ) 2 E(f;D)=\cfrac{1}{m}\sum_{i=1}^m (f(xi)-yi)^2

def calculateMSE(D): 
    in_bracket = []
    for i in range(len(D)):
        num = D[i][0] - D[i][1]
        num = pow(num, 2)
        in_bracket.append(num)

    all_sum = sum(in_bracket)
    MSE = all_sum / len(D)

    return MSE

2.2 错误率与精度

从2.2错误率与精度开始,一直到2.5绘制ROC曲线,都是针对二分类问题。既然针对二分类问题,那么就需要设置一个阈值用以判断正例与反例,我这里以0.8作为阈值。

def calculate_ErrorRate_and_Accuracy(D, threshold):
    error = 0
    accurate = 0
    for i in range(len(D)):
        if D[i][0] <= threshold && D[i][1] == 0:
            accurate = accurate + 1
        elif D[i][0] > threshold and D[i][1] == 1:
            accurate = accurate + 1
        else:
            error = error + 1
            
    return error / len(D), accurate / len(D)

2.3 查准率与查全率

有了上面错误率与精度,实际上我们已经可以做出如下的分类结果混淆矩阵了:
分类结果混淆矩阵
按照上图中对结果的分类,我们就可以有查准率与查全率的定义了。
查准率(Precision): T P T P + F P \cfrac{TP}{TP+FP}
查全率(Recall): T P T P + F N \cfrac{TP}{TP+FN}
用大白话来解释就是,查准率表示:在所有我认为的正例中,真正的正例占比;查全率表示:在所有真正的正例中,我查出了多少正例。

2.4 F1指数

F1度量是从P-R图(Precision-Recall)的平衡点引申出来的,不过我不太想写关于P-R图的内容了,因为这个图还是比较好理解的。计算公式如下:
F 1 = 2 P R P + R = 2 T P m + T P T N F1=\cfrac{2*P*R}{P+R}=\cfrac{2*TP}{m+TP-TN}

2.5 ROC曲线,计算AUC

这个ROC曲线我在课上没有听明白,还是自己查了些资料理解的,尤其是绘图的那个公式,强烈建议看一看这位答主关于ROC图的解答。实际上就是将阈值从最大的预测值逐一减小到最小的预测值,每次减小会增加一个你认为的真正例或者假正例,所以要+1。
那么具体的绘图我们还是使用sklearn自带的ROC计算,并用matplotlib绘制:代码出自,也感谢这位博主的讲解

def draw_ROC(data, target):
    # 为数据增加噪声
    random_state = np.random.RandomState(0)
    n_samples, n_features = data.shape
    data = np.c_[data, random_state.randn(n_samples, 200 * n_features)]  # 按行连接数据矩阵和随机数值矩阵

    # 用留出法分出训练集和测试集
    data_train, data_test, target_train, target_test = train_test_split(data, target, test_size=0.2, random_state=0)

    # Learn to predict each class against the other
    clf = svm.SVC(kernel='linear', probability=True, random_state=random_state)

    # 通过decision_function()计算得到的y_score的值,用在roc_curve()函数中
    target_score = clf.fit(data_train, target_train).decision_function(data_test)

    # Compute ROC curve and ROC area for each class
    fpr, tpr, threshold = roc_curve(target_test, target_score)  # 计算真正率和假正率,threshold阈值
    roc_auc = auc(fpr, tpr)  # 计算auc的值

    plt.figure()
    lw = 2  # 设置行距
    plt.figure(figsize=(10, 10))
    plt.plot(fpr, tpr, color='darkorange',
             lw=lw, label='ROC curve (area = %0.2f)' % roc_auc)  # 假正率为横坐标,真正率为纵坐标做曲线
    plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--')
    plt.xlim([0.0, 1.0])
    plt.ylim([0.0, 1.05])
    plt.xlabel('False Positive Rate')
    plt.ylabel('True Positive Rate')
    plt.title('Receiver operating characteristic example')
    plt.legend(loc="lower right")
    plt.show()
    print(roc_auc)

结果大概如图,iris数据集样本量不是很大,锯齿感不明显

ROC图

三、任务三

不是很会,老师也说不做了。

参考文献

1、自助法:https://blog.csdn.net/weixin_43216017/article/details/86707415
2、ROC曲线:https://blog.csdn.net/xyz1584172808/article/details/81839230

原创文章 8 获赞 13 访问量 2342

猜你喜欢

转载自blog.csdn.net/weixin_44151650/article/details/105196583
今日推荐