python机器学习——十次交叉验证训练的数据准备算法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/AlanConstantineLau/article/details/71773484

摄于 2017年421日 台湾垦丁船帆石海滩
摄于 2017年4月21日 台湾垦丁船帆石海滩

前言

python强大的机器学习包scikit-learn可以直接进行交叉分割,之所以写个相当于锻炼自己思维。

这两天本来打算开始写朴素贝叶斯分类器的算法的,由于上一篇博文python实现贝叶斯推断——垃圾邮件分类在实现时,在数据划分训练集和测试集的时候遇到两个问题,第一是数据量太少,只有50条数据,解决方法就是扩大数据量咯。第二个,也是今天写这篇博文的目的,就是在训练的时候,我先把数据文件进行随机乱序,然后抽取了乱序后前10个数据文件,这个目的实际上就是为了随机从中抽取10个数据文件作为测试集,剩下的40个数据文件作为训练集。这种方法在机器学习的数据准备过程中是非常常见的。但是,为了能够更好的测试模型,尽可能的排除外在因素的干扰,消除偏好,同时获得最好的精度,所以这里则引入交叉验证(Cross-Validation),而交叉验证的次数一般取10次,所以一般也叫十次交叉验证。从stackoverflow上找到一张图,一看即明,原图网站

算法思路

如果数据量为10的倍数,则分离数据是非常方便的,即直接均分十份,依次轮流存入test和train两个文件夹中。
但是如果不是10的倍数呢?
思路如下:
1.获取数据数量,余除10,获得余数,例如现在有24个数据文件,24%10=4
2.根据余数拆分数据,即将数据拆分成20和4两份。
3.将拆分数据后能够整除10的一份均分十份,即将数据量为20的数据量均分为十份,每份包含2个数据文件。
4.将余数拆分成一份一个数据,然后遍历每一份,依次一份分开加入到第3步已经均分好的数据中,直到余数部分的数据使用完。
没看懂?
甩图:

2.png
这样做的目的是尽可能达到数据均分的效果,让实验效果更加。

算法实现

前期准备

数据来源

700条neg电影数据+700条pos电影数据,共1400条数据。下一篇博客朴素贝叶斯分类器将会用到这批数据。完整数据可以在下方的github获得。

python包

1.fwalker
2.bfile
3.numpy
4.shutil
5.os
fwalker和bfile是不是很陌生?哈哈这是我自己写的包,传送门:python3文本读取与写入常用代码python中import自己写的.py
当一些代码(如写入写出文本,创建文件夹、统计词频)经常需要被使用到时,可以考虑下我这种方法,非常方便,可以大大缩短编写算法的周期和减少代码量。

代码实现

既然我们需要进行十次交叉验证,因此数据需要复制十份,则需要10个文件夹来进行存放,每个文件夹下又包含test和train数据。

代码(写一行代码打一行注释,良心啊~)

# -*- coding: utf-8 -*-
# @Date    : 2017-05-11 21:24:50
# @Author  : Alan Lau ([email protected])
# @Version : Python3.5

from fwalker import fun
from bfile import buildfile as bf
from random import shuffle
import numpy as np
import shutil
import os


def buildfile(output_path):
    for i in range(1, 10+1):
        # 循环新建编号1-10的文件夹,用于存放train和test文件夹
        file_num = bf('%s\\%d' % (output_path, i))
        # 在每个编号文件夹下新建train文件夹,用于存放90%的训练数据
        train_file = bf('%s\\train' % file_num)
        # 在每个编号文件夹下新建test文件夹,用于存放10%的训练数据
        test_file = bf('%s\\test' % file_num)
    print('Data storage has been bulit!')
    return output_path


def split_ten(files):
    file_len = len(files)  # 获取文件总数
    shuffle(files)  # 随机打乱文件路径列表的顺序,即使python的随机是伪随机
    data_storage = []  # 初始化一个列表,用来接收分划分好的文件路径
    remainder = file_len % 10  # 判断文件数量能否直接被10整除
    if remainder == 0:  # 如果可以整除,直接将数据切成10组
        np_files = np.array(files)  # 将文件路径列表转换成numpy
        data_storage = np_files.reshape(10, -1)  # 利用numpy的reshape来将文件路径切分为10组
        # 比如说现在有20个文件路径
        # reshape()后得到的结果为2、2、2、2、2、2、2、2、2、2,即共十份、每份包含2个文件路径。
        return data_storage
    else:  # 否则,则先切开余数部分的文件
        np_files = np.array(files[:-1*remainder])  # 切开余数部分的文件,使文件数量保证能够被10整除
        data_storage_ten = np_files.reshape(10, -1)  # 同样利用上面的方法使用numpy切分10组文件
        # 获取余数部分的文件列表,遍历列表,尽可能的将多余的文件分散在10组文件中,而不是直接加入到一个文件中
        remainder_files = (
            np.array(files[-1*remainder:])).reshape(remainder, -1)  # 使用reshape切分问一份一组
        for i in range(0, len(remainder_files)):
            ech_dst = data_storage_ten[i]
            ech_rf = remainder_files[i]
            # 将取出来的余数内的路径分别加入到已经均分好的10份的前remainder个数据当中,比如说现在有24份文件,
            # 将24拆份拆分成一个能被10整除的数和一个余数,即这里拆分成20和4,我们首先将拆出来的20份文件均分10份,
            # 即每份有2个文件路径,然后,再将剩下后面的4个文件路径,尽可能的放入到刚刚均分好的10份数据中。
            # 因此最终拆分的结果共有十份,每份数量分别为:3、3、3、3、2、2、2、2、2、2。
            data_storage.append(np.concatenate((ech_dst, ech_rf)))
        for j in range(remainder, len(data_storage_ten)):
            # 将将剩下的没有被余数部分加入的份加入到data_storage中
            data_storage.append(data_storage_ten[j])
        return np.array(data_storage)


def group_data(data_storage, output_path):
    for i in range(0, len(data_storage)):
        ech_path = '%s\\%d' % (output_path, i+1)  # 构造每一份需要写入的路径
        ech_train_path = '%s\\train' % ech_path
        ech_test_path = '%s\\test' % ech_path
        test_paths = data_storage[i]
        move_file(test_paths, ech_test_path)
        train_paths = np.concatenate(([data_storage[:i], data_storage[i+1:]]))
        # 将剩下的训练部分加入到train_paths中,并且降维
        train_paths = np.concatenate((train_paths))  # 再次降维,使其变成1维
        move_file(train_paths, ech_train_path)
        num = i+1
        print('No.%d is over!' % num)


def move_file(old_paths, new_path):
    for old_path in old_paths:
        shutil.copy2(old_path, new_path)
        flag_name = '_'.join(old_path.split('\\')[-2:])
        old_name = '%s\\%s' % (new_path, old_path.split('\\')[-1])
        new_name = '%s\\%s' % (new_path, flag_name)
        os.rename(old_name, new_name)


def main():
    file_path = r'..\data\data_of_movie'
    # file_path = r'..\data\test'
    output_path = r'..\data\tenTimesTraining'
    files = fun(file_path)
    output_path = buildfile(output_path)
    data_storage = split_ten(files)
    group_data(data_storage, output_path)


if __name__ == '__main__':
    main()

至于实验的结果,就是把1400条数据随打乱,轮流存入10个文件夹中,并且其中的10%轮流作为测试集存入test文件中,剩下的90%则存入了train。
由于数据量大,我这里截取一小部分数据展示。

对比没有划分数据前的数据名称会发现,我这里还把类别的标签加到了每条数据的名称中。

这个算法可以使用到大多数的机器学习需要划分的数据当中,前提只需要将不同分类的数据存在以分类命名的文件夹下即可。为了方便识别,算法会提取文件夹的名称作为标签名重命名每条数据。重命名格式为:标签_xxx.txt

所有数据以及完整代码GITHUB

猜你喜欢

转载自blog.csdn.net/AlanConstantineLau/article/details/71773484