1. 程式人生 > >python機器學習——十次交叉驗證訓練的資料準備演算法

python機器學習——十次交叉驗證訓練的資料準備演算法

攝於 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