tensorflow多線程批量讀取文件

tensorflow多線程批量讀取數據

總所周知,在深度學習中,tensorflow是非常好用的一個框架,也是比較常用的一個框架,而我這篇博客主要是講述如何用tensorflow多線程批量讀取數據。

在我們使用算法來預測或者分類數據時,都會使用大量的數據來訓練模型,而這些數據往往都是使用文件來保存的。而我們在機器學習中往往會遇到數據量太大,讀取數據的速度太慢了,這種普通的讀取數據的方式會嚴重影響了我們訓練模型的效率。所以tensorflow就推出了多線程讀取數據的API,大大提高了我們訓練模型的效率。

多線程讀取csv文件數據

首先我們得知道這個多線程的流程是怎們樣的。

流程步驟

首先是把要讀取文件都放在一個列表當中,然後把文件列表存入隊列當中,然後再用閱讀器把隊列裏面的文件一個一個讀取出來,這裏讀取數據默認是一條一條數據讀取的,然後把讀取的數據進行解碼,解碼後的數據再放入一個新的隊列當中去,然後我們就可以從隊列裏面取出數據來訓練了。

這裏就實現了異步的過程,主線程是從隊列裏面讀取數據,子線程就是從文件裏面讀取數據然後存入隊列當中,兩個線程互不干擾。

把文件放入隊列當中

file_queue = tf.train.string_input_producer(["I:\\crack\\DATA\\submissions_metad\\train.csv"])

這裏我只放了一個文件,也可以放多個文件,注意要把文件放入列表中,這裏面還有幾個參數
string_input_producer(string_tensor,
num_epochs=None,
shuffle=True,
seed=None,
capacity=32,
shared_name=None,
name=None,
cancel_op=None)
num_epochs:一個整數(可選)。如果指定,string_input_producer在產生OutOfRange錯誤之前從string_tensor中產生num_epochs次字符串。如果未指定,則可以無限次循環遍歷字符串。

shuffle:布爾值。如果爲true,則在每個epoch內隨機打亂順序。

seed:一個整數(可選)。如果shuffle==True,則使用種子。

capacity:一個整數。設置隊列容量。

shared_name:(可選的)。如果設置,則此隊列將在多個會話的給定名稱下共享。對具有此隊列的設備打開的所有會話都可以通過shared_name訪問它。在分佈式設置中使用它意味着只有能夠訪問此操作的其中一個會話才能看到每個名稱。

name:此操作的名稱(可選)。

cancel_op:取消隊列的操作(可選)。

構造閱讀器,讀取數據

reader = tf.TextLineReader(skip_header_lines = 1)
key,value = reader.read(file_queue)

skip_header_lines參數是表示從第幾行開始讀取,因爲我們通常csv文件的第一行是字段名,所以我們就要從第二行開始讀取。

TextLineReader對像裏面有read的這個方法,read裏面傳遞的參數是文件的隊列,而用這個方法讀取的數據返回兩個對象,key代表文件名,value代表讀取的值,這裏默認只是讀取一行數據。

解碼數據

record = [[0.],[0.],[0.],[0.],[0.],[0.]]
values = tuple()
values = tf.decode_csv(value,record_defaults=record)

這裏的解碼數據有一個非常重要的參數record_defaults,這個參數就是指定你每列數據的數據類型,如果當你的數據爲空時就會自動補全。這裏我用record = [[0.],[0.],[0.],[0.],[0.],[0.]]來補全數據,這裏一定要是二維數組。

然後這裏返回的是解碼後的每個值,所以我用元組來接收,或者列表來接收都可以。

批量讀取數據

values_batch = tuple()
values_batch = tf.train.batch(values,batch_size = 100,num_threads=1,capacity = 100000)

batch_size代表數據到達多少個就讀取出來,num_threads代表線程數,capacity代表隊列長度。

這裏就是把一條條數據放入隊列當中,然後隊列的數量到達指定的數量,就讀取出來。

這裏一般都會出現重複值,因爲數據讀到最後的時候,如果隊列最後的數據到達不了指定的數據量,就會從隊列頭重新讀取數據,但是重複的數據對我們在訓練的時候是沒有多大的影響的。

開啓會話讀取數據

with tf.Session() as sess:
    #開啓線程協調器
    coord = tf.train.Coordinator()

    #開啓子線程
    threads = tf.train.start_queue_runners(sess,coord=coord,start=True)

    #打印數據,用循環不斷地讀取數據
    for i in range(100000):
        print(sess.run(values_batch))

    #回收資源
    coord.request_stop()
    coord.join(threads)

這裏面一定要開啓線程協調器調節子線程和回收資源。
這裏最主要的是開啓子線程
threads = tf.train.start_queue_runners(sess,coord=coord,start=True)
start是代表在開啓子線程的默認運行子線程,如果爲False就又得手動運行子線程。

在會話這裏可能有些疑惑,爲什麼前面有些op操作沒有在會話裏面運行,首先有關於隊列和線程都在開啓子線程的時候已經運行了,而其他那些操作也是屬於子線程裏面的操作,這個有點像python的函數調用,一環扣一環。

源碼

import tensorflow as tf
import numpy as np
from time import time

'''
多線程讀取csv數據的流程
1、首先把文件放入一個隊列當中
2、然後開始讀取數據,默認只讀取一列數據
3、解碼,因爲讀取的數據可以有編碼在裏面
4、把讀取的數據再放入一個新的隊列當中去,然後進行下面的處理
'''

#構造文件隊列,注意傳入的文件一定要是列表
file_queue = tf.train.string_input_producer(["I:\\crack\\DATA\\submissions_metad\\train.csv"])

#構造閱讀器,默認按行讀取,skip_header_lines指定從第幾行開始讀取
reader = tf.TextLineReader(skip_header_lines = 1)

#開始閱讀,用閱讀器閱讀,返回兩個值,一個是key就是文件名,另外那個是value是讀取的值
key,value = reader.read(file_queue)

#對內容進行解碼,用的方法是decode_csv,首先裏面有一個參數是record_defaults,這個是可以用來指定讀取的每一列的數據類型,和默認值,如果讀取的值是空值的話就會默認填入默認值,解碼返回的值是每一列一列的,所以也可以用元組對象來接收
record = [["None"],["None"],["None"],["None"],["None"],["None"]]
values = tuple()
values = tf.decode_csv(value,record_defaults=record)

#把數據放入一個隊列當中,用batch方法,裏面有batch_size就是一次性批處理多少個數據,num_threads子線程的數量,capacity隊列大小,同樣返回的是批量的數據,最好的方式用元組接收
values_batch = tuple
values_batch = tf.train.batch(values,batch_size = 100,num_threads=3,capacity = 100000)

start = time()
#先開啓會話讀取數據
with tf.Session() as sess:
    #開啓線程協調器
    coord = tf.train.Coordinator()

    #開啓子線程
    threads = tf.train.start_queue_runners(sess,coord=coord,start=True)

    #打印數據,用循環不斷地讀取數據
    for i in range(100000):
        print(sess.run(values_batch))

    #回收資源
    coord.request_stop()
    coord.join(threads)
end = time()
print(end - start)

批量讀取圖片文件

圖片也是有特徵值和目標值的,圖片的特徵值就是像素點,我們都知道像素點是又長和寬和通道數相關的,而我們如果要對圖片進行學習的話,就要保證特徵值的數量是一樣的,所以我們都要將圖片縮放成一樣大小

步驟

其實圖片批量讀取的步驟和csv文件是基本一樣的,只是它在每一塊的處理方式不一樣。
1、建立文件隊列
2、讀取數據,默認是按照一張一張圖片讀取的
3、解碼
4、批量讀取

建立文件隊列

用os來建立文件列表先

file_list = os.listdir(r"I:\crack\圖片讀取")
file_list = [os.path.join("I:\crack\圖片讀取",file) for file in file_list]

再建立文件隊列

file_queue = tf.train.string_input_producer([file_list])

構造閱讀器,閱讀文件

read = tf.WholeFileReader()
key, value = read.read(file_queue)

解碼

這裏解碼比csv文件解碼方式簡單地多

image = tf.image.decode_jpeg(value)

然後在將圖片進行統一縮放

image_resize = tf.image.resize_images(image,size=[1020,1500])

到這一步我們可以先打印縮放後的圖片對象,可以發現這裏的形狀是不確定的
Tensor(“resize/Squeeze:0”, shape=(1020, 1500, ?), dtype=float32)

所以我們要確定它的形狀是怎麼樣的,用靜態修改形狀也可以,動態也可以,因爲我們這裏的形狀是固定的,所以就用靜態的方式來修改形狀

image_resize.set_shape([1020,1500,3])

開啓會話運行

with tf.Session() as sess:
    #開啓線程協調器
    coord = tf.train.Coordinator()

    #開啓子線程
    threads = tf.train.start_queue_runners(sess,coord=coord,start=True)

    #打印數據,就是每個像素點的值
    # print(sess.run(images))
    print(sess.run(image_resize))

    #回收資源
    coord.request_stop()
    coord.join(threads)

源碼

import tensorflow as tf
import os

'''
圖片的特徵值就三個,長、寬、通道數,黑白圖片就有一個通道數,彩色圖片有三個通道數,
多線程讀取圖片和csv文件是一樣的,但是所用的API不同
'''

#構造圖片文件列表
file_list = os.listdir(r"I:\crack\圖片讀取")
file_list = [os.path.join("I:\crack\圖片讀取",file) for file in file_list]

#構造文件列表隊列
file_queue = tf.train.string_input_producer(file_list)

#構造閱讀器,默認是按照一張一張圖片讀取的
read = tf.WholeFileReader()
key, value = read.read(file_queue)

#對圖片進行解碼,這裏解碼就比csv文件解碼簡單地多
image = tf.image.decode_jpeg(value)

#因爲對圖片進行處理要求長和寬是一致的纔行,所以要對圖片進行縮放
image_resize = tf.image.resize_images(image,size=[1020,1500])

#因爲現在的形狀還是不固定的,通道數還是問號,所以要對形狀進行修改,修改成三個通道數,可以用前面的靜態修改或者動態修改都可以
image_resize.set_shape([1020,1500,3])

#然後就可以對圖片進行批處理了,這裏的圖片信息一定要用列表存儲
images = tf.train.batch([image_resize],batch_size=15,num_threads=1,capacity=1000)

#然後開始會話運行
with tf.Session() as sess:
    #開啓線程協調器
    coord = tf.train.Coordinator()

    #開啓子線程
    threads = tf.train.start_queue_runners(sess,coord=coord,start=True)

    #打印數據,就是每個像素點的值
    # print(sess.run(images))
    print(sess.run(image_resize))

    #回收資源
    coord.request_stop()
    coord.join(threads)

多線程讀取二進制文件

我們有些文件是以二進制文件的方式存儲的,比如圖片分類預測就是用二進制文件存儲的,下面我們就使用一個比賽數據來讀取,網址路徑如下:
(https://www.cs.toronto.edu/~kriz/cifar-10-binary.tar.gz)

建立文件隊列

file_list = os.listdir("I:\crack\cifar-10-batches-bin")
file_list = [os.path.join("I:\crack\cifar-10-batches-bin",file) for file in file_list if file[-3:] == "bin"]

#構造文件列表隊列
file_queue = tf.train.string_input_producer(file_list)

構造閱讀器

read = tf.FixedLengthRecordReader(3073)

key,value = read.read(file_queue)

在閱讀器裏面傳遞的參數,就是每個樣本的字節數,因爲我們的樣本圖片是[32,32,3]的三階數據,所以字節數就是三個相乘爲3072,因爲這個文件數據裏面還有分類的數據在裏面,所以就是3703個字節數

解碼

這裏的二進制文件解碼前,還有很多前奏,首先是轉換數據類型,一般圖片數據就是三階的數組,所以我們要把數據轉換爲uint8

image_label = tf.decode_raw(value,tf.uint8)

因爲數據裏面包含了特徵值和目標值,所以要把特徵值和目標值給切割出來

image = tf.slice(image_label,[1],[3072])
label = tf.slice(image_label,[0],[1])

順便把目標值轉換成int32

label = tf.cast(label,dtype=tf.int32)

因爲現在的特徵值是一階的數據,爲了方便圖片數據的運算,所以我們要把數據轉換成三階的,這裏要改變形狀階數,所以要用動態修改方式

image = tf.reshape(image,[32,32,3])

批處理

images,labels = tf.train.batch([image,label],batch_size=10,num_threads=1,capacity=1000)

開啓會話運行

with tf.Session() as sess:
    #開啓線程協調器
    coord = tf.train.Coordinator()

    #開啓子線程
    threads = tf.train.start_queue_runners(sess,coord=coord,start=True)

    #打印數據,就是每個像素點的值
    print(sess.run([images,labels]))

    #回收資源
    coord.request_stop()
    coord.join(threads)

源碼

import tensorflow as tf
import os

'''
二進制批量讀取的流程跟csv和圖片讀取的流程差不多相同,只是有些地方有差異
'''

#先建立文件列表
file_list = os.listdir("I:\crack\cifar-10-batches-bin")
file_list = [os.path.join("I:\crack\cifar-10-batches-bin",file) for file in file_list if file[-3:] == "bin"]

#構造文件列表隊列
file_queue = tf.train.string_input_producer(file_list)

#構造閱讀器,這裏要注意閱讀器默認是讀取每個文件的byte數目,所以我們要提前知道每個文件的的byte數量,這裏是圖片數目,所以像素數量就是byte數量,加上目標值的數目
read = tf.FixedLengthRecordReader(3073)

key,value = read.read(file_queue)

#解碼,這裏解碼要加上轉換成什麼類型數據,一般都是轉換成uint8類型,這裏返回的是一個一階張量數據,裏面包含目標值和特徵值
image_label = tf.decode_raw(value,tf.uint8)

#因爲這裏的數據包含了圖片的特徵值和目標值,所以要將這兩個數據給分割出來了,用tf.slice方法分割
image = tf.slice(image_label,[1],[3072])
label = tf.slice(image_label,[0],[1])
#順便把目標值的數據類型給轉換一下
label = tf.cast(label,dtype=tf.int32)

#因爲圖片數據一般都是三階的數據,所以要把形狀給改變一下
image = tf.reshape(image,[32,32,3])

#然後進行批處理
images,labels = tf.train.batch([image,label],batch_size=10,num_threads=1,capacity=1000)

#開啓會話進行讀取
with tf.Session() as sess:
    #開啓線程協調器
    coord = tf.train.Coordinator()

    #開啓子線程
    threads = tf.train.start_queue_runners(sess,coord=coord,start=True)

    #打印數據,就是每個像素點的值
    print(sess.run([images,labels]))

    #回收資源
    coord.request_stop()
    coord.join(threads)

tfrecords的存儲與讀取

tfrecords是tesorflow這個框架單獨開發的一種文件格式,這個文件是一個類字典的格式文件,這個文件比較小,也非常方便讀取和移動,但是存儲的時候就偏麻煩一點。

存儲步驟

1、讀取二進制文件數據
2、構造tfrecords存儲器
3、把每一個樣本的數據轉換成example協議塊
4、把每一個example協議快存儲進tfrecords存儲器中

讀取二進制文件

這裏的讀取方式跟上面的是一樣的

file_list = os.listdir("I:\crack\cifar-10-batches-bin")
file_list = [os.path.join("I:\crack\cifar-10-batches-bin",file) for file in file_list if file[-3:] == "bin"]
file_queue = tf.train.string_input_producer(file_list)
read = tf.FixedLengthRecordReader(3073)
key,value = read.read(file_queue)
image_label = tf.decode_raw(value,tf.uint8)
image = tf.slice(image_label,[1],[3072])
label = tf.slice(image_label,[0],[1])
label = tf.cast(label,dtype=tf.int32)
image = tf.reshape(image,[32,32,3])
images,labels = tf.train.batch([image,label],batch_size=50000,num_threads=10,capacity=100000)

構造tfrecords存儲器

write = tf.python_io.TFRecordWriter(r"I:\crack\tfrecords\cifar.tfrecords")

這裏面的參數要把文件路徑傳入進去,後綴名一定要是tfrecords

把數據轉換成example協議塊

    for i in range(10):
        image = images[i].eval().tostring() 
        label = labels[i].eval()[0]  
        example = tf.train.Example(features=tf.train.Features(feature={
            "image": tf.train.Feature(bytes_list=tf.train.BytesList(value=[image])),
            "label": tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
        }))

因爲我們已經知道了讀取多少個二進制文件,所以循環的次數也就知道了,如果我們不知道讀取了多少個二進制文件,就可以用形狀或者其他的方式來獲取數目。

這裏我們用image = images[i].eval().tostring()
label = labels[i].eval()[0] 來提取每個數據的特徵值和目標值,因爲特徵值在轉換的過程中要轉換成bytes類型,所以事先要先轉換成string類型,這個eval()獲取值的方式要在會話中才可以運行,所以這整個流程都要放在一個函數裏面,然後再會話裏面運行。

example = tf.train.Example(features=tf.train.Features(feature={
“image”: tf.train.Feature(bytes_list=tf.train.BytesList(value=[image])),
“label”: tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
}))這個轉換的過程大部分都是固定寫法的。
只有鍵值對裏面又不同的寫法,但是也只是又三種寫法
bytes_list = tf.train.BytesList(value = [])
int64_list = tf.train.Int64List(value = [])
float_list = tf.train_FloatList(value = [])

寫入和關閉存儲器

write.write(example.SerializeToString())
write.close()

因爲不能把協議直接寫入文件當中,所以要把example轉換成序列化格式纔可以

源碼

file_list = os.listdir("I:\crack\cifar-10-batches-bin")
file_list = [os.path.join("I:\crack\cifar-10-batches-bin",file) for file in file_list if file[-3:] == "bin"]
file_queue = tf.train.string_input_producer(file_list)
read = tf.FixedLengthRecordReader(3073)
key,value = read.read(file_queue)
image_label = tf.decode_raw(value,tf.uint8)
image = tf.slice(image_label,[1],[3072])
label = tf.slice(image_label,[0],[1])
label = tf.cast(label,dtype=tf.int32)
image = tf.reshape(image,[32,32,3])
images,labels = tf.train.batch([image,label],batch_size=50000,num_threads=10,capacity=100000)

#把二進制文件存入tfrecords
def writer(images,labels):

    #構造tfrecords存儲器,在裏面要寫入tfrecords的文件目錄
    write = tf.python_io.TFRecordWriter(r"I:\crack\tfrecords\cifar.tfrecords")
    # 將二進制文件每條數據轉換成example協議,這裏我們要用for循環寫入,這裏知道了是10張圖片,如果不知道,就得用形狀或者其他的方式來獲取數碼
    for i in range(10):
        # 把每條數據提取出來,用切片的方式獲取,我們是要獲取它的值而不是獲取它的對象,所以這一步驟要再會話裏面運行
        image = images[i].eval().tostring()  # 因爲在轉換的時候一般都是把特徵值轉換爲bytes類型數據,所以要把特徵值轉換成字符串
        label = labels[i].eval()[0]  # 這裏要把目標值提取出來

        '''
        轉換成example,下面大部分都是固定寫法。
        就鍵值對那裏又三種寫法,bytes_list = tf.train.BytesList(value = [])
        int64_list = tf.train.Int64List(value = [])
        float_list = tf.train_FloatList(value = [])
        '''
        example = tf.train.Example(features=tf.train.Features(feature={
            "image": tf.train.Feature(bytes_list=tf.train.BytesList(value=[image])),
            "label": tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
        }))

        # 寫入tfrecords存儲器中,這裏不能直接把example寫入存儲器,要將example轉換成序列化格式才能寫入
        write.write(example.SerializeToString())

    # 關閉存儲器
    write.close()

#這裏要注意,也要運行上面二進制文件的讀取
with tf.Session() as sess:
    coord = tf.train.Coordinator()
    # 開啓子線程
    threads = tf.train.start_queue_runners(sess, coord=coord, start=True)
    # 打印數據,就是每個像素點的值
    print(sess.run([images, labels]))

    writer(images,labels)
    # 回收資源
    coord.request_stop()
    coord.join(threads)

批量讀取tfrecords文件

在上面我們已經把tfrecords文件保存好了,所以我們就來讀取這個文件,步驟都差不多,但是多了一個解析的過程,因爲我們存儲的時候是example序列化格式的數據,所以需要把它解析出來。

構造文件隊列

file_queue = tf.train.input_producer(["I:\\crack\\tfrecords\\cifar.tfrecords"])

構造閱讀器

reader = tf.TFRecordReader()
key,value = reader.read(file_queue)

解析數據

feature = tf.parse_single_example(value,features={
    "image":tf.FixedLenFeature([],tf.string),
    "label":tf.FixedLenFeature([],tf.int64)
})

這裏要指定形狀,但是一般都不會指定形狀給它,這裏也要指定數據類型,指定的類型與我們存儲時寫入的類型一樣既可。

解析過後就可以直接用字典的方式提取出數據既可,這裏就是非常方便了。

解碼,批量讀取

這裏的解碼基本給二進制文件數據的解析方式差不多一樣

image = tf.decode_raw(feature["image"],tf.uint8)
label = feature["label"]
image = tf.reshape(image,[32,32,3])
images,labels = tf.train.batch([image,label],batch_size=10000,num_threads=1,capacity=100000)

源碼

file_queue = tf.train.input_producer(["I:\\crack\\tfrecords\\cifar.tfrecords"])

reader = tf.TFRecordReader()
key,value = reader.read(file_queue)

#解析序列化的example,這裏面都要指定形狀,但是一般都是不指定的
feature = tf.parse_single_example(value,features={
    "image":tf.FixedLenFeature([],tf.string),
    "label":tf.FixedLenFeature([],tf.int64)
})

#解碼,除了要把特徵值bytes解碼成uint8之外,所有的步驟都和二進制解碼步驟一樣
image = tf.decode_raw(feature["image"],tf.uint8)
label = feature["label"]
image = tf.reshape(image,[32,32,3])
images,labels = tf.train.batch([image,label],batch_size=10000,num_threads=1,capacity=100000)

with tf.Session() as sess:
    # 開啓線程協調器
    coord = tf.train.Coordinator()

    # 開啓子線程
    threads = tf.train.start_queue_runners(sess, coord=coord, start=True)

    # 打印數據,就是每個像素點的值
    print(sess.run([images, labels]))

    # 回收資源
    coord.request_stop()
    coord.join(threads)

這裏我們可以發現tfrecords文件的存儲時非常小的,所以非常方便移動,而且讀取的時候也比二進制的文件方便許多,但是存儲的時候就比較麻煩一點了。

如果有不懂的地方,歡迎添加我的QQ 1693490575,一起討論進步。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章