ทำไม TensorFlow 2 ช้ากว่า TensorFlow 1 มาก


137

มันถูกอ้างถึงโดยผู้ใช้หลายคนว่าเป็นเหตุผลของการเปลี่ยนไปใช้ Pytorch แต่ฉันยังไม่พบเหตุผล / คำอธิบายสำหรับการเสียสละคุณภาพความเร็วที่สำคัญที่สุดสำหรับการปฏิบัติจริง

ด้านล่างเป็นรหัสประสิทธิภาพการเปรียบเทียบกับ TF1 TF2 - TF1 กับการทำงานที่ใดก็ได้จาก47% ถึง 276% เร็ว

คำถามของฉันคืออะไรที่กราฟหรือระดับฮาร์ดแวร์ที่ทำให้เกิดการชะลอตัวที่สำคัญ?


กำลังมองหาคำตอบโดยละเอียด - ฉันคุ้นเคยกับแนวคิดที่กว้างขวางแล้ว Git ที่เกี่ยวข้อง

ข้อมูลจำเพาะ : CUDA 10.0.130, cuDNN 7.4.2, Python 3.7.4, Windows 10, GTX 1070


ผลการเกณฑ์มาตรฐาน :


อัปเดต : การปิดใช้งานการเรียกใช้ Eager ตามรหัสด้านล่างไม่ได้ช่วยอะไร อย่างไรก็ตามพฤติกรรมนั้นไม่สอดคล้องกัน: บางครั้งการทำงานในโหมดกราฟช่วยได้มากบางครั้งก็ทำงานช้าลงเมื่อเทียบกับ Eager

เนื่องจากผู้ที่ชื่นชอบ TF ไม่ปรากฏที่ใด ๆ ฉันจะตรวจสอบเรื่องนี้ด้วยตัวเอง - สามารถติดตามความคืบหน้าในปัญหา Github ที่เชื่อมโยงได้

อัปเดต 2 : ตันของผลการทดลองที่จะแบ่งปันพร้อมคำอธิบาย; ควรทำวันนี้


รหัสมาตรฐาน :

# use tensorflow.keras... to benchmark tf.keras; used GPU for all above benchmarks
from keras.layers import Input, Dense, LSTM, Bidirectional, Conv1D
from keras.layers import Flatten, Dropout
from keras.models import Model
from keras.optimizers import Adam
import keras.backend as K
import numpy as np
from time import time

batch_shape = (32, 400, 16)
X, y = make_data(batch_shape)

model_small = make_small_model(batch_shape)
model_small.train_on_batch(X, y)  # skip first iteration which builds graph
timeit(model_small.train_on_batch, 200, X, y)

K.clear_session()  # in my testing, kernel was restarted instead

model_medium = make_medium_model(batch_shape)
model_medium.train_on_batch(X, y)  # skip first iteration which builds graph
timeit(model_medium.train_on_batch, 10, X, y)

ฟังก์ชั่นที่ใช้ :

def timeit(func, iterations, *args):
    t0 = time()
    for _ in range(iterations):
        func(*args)
    print("Time/iter: %.4f sec" % ((time() - t0) / iterations))

def make_small_model(batch_shape):
    ipt   = Input(batch_shape=batch_shape)
    x     = Conv1D(128, 400, strides=4, padding='same')(ipt)
    x     = Flatten()(x)
    x     = Dropout(0.5)(x)
    x     = Dense(64, activation='relu')(x)
    out   = Dense(1,  activation='sigmoid')(x)
    model = Model(ipt, out)
    model.compile(Adam(lr=1e-4), 'binary_crossentropy')
    return model

def make_medium_model(batch_shape):
    ipt   = Input(batch_shape=batch_shape)
    x     = Bidirectional(LSTM(512, activation='relu', return_sequences=True))(ipt)
    x     = LSTM(512, activation='relu', return_sequences=True)(x)
    x     = Conv1D(128, 400, strides=4, padding='same')(x)
    x     = Flatten()(x)
    x     = Dense(256, activation='relu')(x)
    x     = Dropout(0.5)(x)
    x     = Dense(128, activation='relu')(x)
    x     = Dense(64,  activation='relu')(x)
    out   = Dense(1,   activation='sigmoid')(x)
    model = Model(ipt, out)
    model.compile(Adam(lr=1e-4), 'binary_crossentropy')
    return model

def make_data(batch_shape):
    return np.random.randn(*batch_shape), np.random.randint(0, 2, (batch_shape[0], 1))

คุณเคยใช้เครื่องมือชนิด cProfile เพื่อวิเคราะห์ส่วนใดที่ทำให้แตกต่างกันบ้าง?
zihaozhihao

@zihaozhihao ฉันมีแต่ไม่ใช่สำหรับสิ่งนี้โดยเฉพาะ; ตามลิงก์ก่อนหน้าและเขียนเครื่องมือเพิ่มประสิทธิภาพที่กำหนดเองฉันคุ้นเคยกับความแตกต่างในการโทรแล้ว แต่ไม่เข้าใจว่าทำไมคนเราถึงช้ากว่าคนอื่น - และผู้เชี่ยวชาญที่ไม่ใช่ TF ไม่สามารถเข้าใจได้จากแหล่งที่มา ยุ่งเหยิงยุ่งเหยิงไม่ได้บันทึกการแสดงที่เกี่ยวข้อง กราฟ / ฮาร์ดแวร์ระดับ Intel จำเป็นต้องใช้ซึ่ง profilers จะไม่ให้ (เท่าที่ฉันสามารถใช้งานได้)
OverLordGoldDragon

รุ่น numpy เป็นเหมือนกันในการทดสอบทั้งสอง?
chabir

อุ๊ย .... ถ้า Keras เก่าอย่างเดียวช้ากว่า PyTorch อย่างมากลองจินตนาการดู
Daniel Möller

ขนาดของปัญหามีขนาดเท่าไร คุณเคยลองใช้เกณฑ์มาตรฐานเดียวกันกับ OS อื่นหรือไม่
okawo

คำตอบ:


76

UPDATE 2/18/2020 : ฉันได้ benched 2.1 และ 2.1 nightly; ผลลัพธ์จะผสมกัน ทั้งหมดยกเว้นหนึ่งการกำหนดค่า (รุ่นและขนาดข้อมูล) นั้นเร็วเท่าหรือเร็วกว่า TF2 และ TF1 ที่ดีที่สุด อันที่ช้ากว่าและช้าลงอย่างมากคือใหญ่ - ใหญ่ ในการประมวลผลกราฟ ( ช้าลง 1.6x ถึง 2.5x )

นอกจากนี้ยังมีความแตกต่างในการทำซ้ำอย่างมากระหว่าง Graph กับ Eager สำหรับโมเดลขนาดใหญ่ที่ฉันทดสอบซึ่งไม่สามารถอธิบายได้ด้วยการสุ่ม / การคำนวณแบบขนาน ขณะนี้ฉันไม่สามารถแสดงรหัสที่สามารถทำซ้ำได้สำหรับข้อเรียกร้องเหล่านี้ต่อข้อ จำกัด ด้านเวลาดังนั้นฉันขอแนะนำให้ทดสอบสิ่งนี้สำหรับรุ่นของคุณเอง

ยังไม่ได้เปิดปัญหา Git กับสิ่งเหล่านี้ แต่ฉันได้แสดงความคิดเห็นเกี่ยวกับต้นฉบับ - ยังไม่มีการตอบสนอง ฉันจะอัปเดตคำตอบเมื่อมีความคืบหน้า


คำตัดสิน : ไม่ใช่ถ้าคุณรู้ว่าคุณกำลังทำอะไรอยู่ แต่ถ้าคุณทำไม่ได้มันอาจทำให้คุณเสียค่าใช้จ่ายมากมาย - โดยการอัพเกรด GPU โดยเฉลี่ยเพียงเล็กน้อยและโดย GPU ที่เลวร้ายที่สุด


คำตอบนี้ : มีวัตถุประสงค์เพื่อให้คำอธิบายในระดับสูงของปัญหาเช่นเดียวกับแนวทางในการตัดสินใจเกี่ยวกับการกำหนดค่าการฝึกอบรมที่ตรงกับความต้องการของคุณ สำหรับคำอธิบายโดยละเอียดในระดับต่ำซึ่งรวมถึงผลลัพธ์การเปรียบเทียบทั้งหมด + รหัสที่ใช้ดูคำตอบอื่น ๆ ของฉัน

ฉันจะอัปเดตคำตอบของฉันด้วยข้อมูลเพิ่มเติมหากฉันเรียนรู้ใด ๆ - สามารถคั่นหน้า / "ติดดาว" คำถามนี้เพื่อการอ้างอิง


สรุปประเด็นปัญหา : ตามที่ได้รับการยืนยันโดยผู้พัฒนา TensorFlow Q. Scott Zhu, TF2 มุ่งเน้นการพัฒนาเกี่ยวกับการประมวลผลที่กระตือรือร้นและการผนวกรวมที่แน่นหนาด้วย Keras ซึ่งเกี่ยวข้องกับการเปลี่ยนแปลงในแหล่ง TF - รวมถึงที่ระดับกราฟ ประโยชน์ที่ได้รับ: ความสามารถในการประมวลผลการกระจายการดีบักและการปรับใช้ที่เพิ่มขึ้นอย่างมาก อย่างไรก็ตามค่าใช้จ่ายบางอย่างนั้นก็คือความเร็ว

อย่างไรก็ตามเรื่องนี้ค่อนข้างซับซ้อนกว่า มันไม่ได้เป็นเพียง TF1 เทียบกับ TF2 - ปัจจัยที่ให้ความแตกต่างอย่างมีนัยสำคัญในความเร็วของรถไฟ ได้แก่ :

  1. TF2 กับ TF1
  2. กระตือรือร้นกับโหมดกราฟ
  3. keras เมื่อเทียบกับ tf.keras
  4. numpyเทียบtf.data.Datasetกับ ...
  5. train_on_batch() เมื่อเทียบกับ fit()
  6. GPU กับ CPU
  7. model(x)เทียบmodel.predict(x)กับ ...

น่าเสียดายที่แทบไม่มีสิ่งใดในข้างต้นที่เป็นอิสระจากสิ่งอื่นและอย่างน้อยก็สามารถดำเนินการได้สองครั้งเมื่อเทียบกับอีกคนหนึ่ง โชคดีที่คุณสามารถกำหนดสิ่งที่จะทำงานได้ดีที่สุดอย่างเป็นระบบและมีทางลัดสองสามอย่าง - ตามที่ฉันจะแสดง


ฉันควรทำอย่างไรดี? ปัจจุบันวิธีเดียวคือ - ทดสอบรุ่นข้อมูลและฮาร์ดแวร์เฉพาะของคุณ ไม่มีการกำหนดค่าเดียวมักจะทำงานได้ดีที่สุด - แต่มีจะทำและไม่ได้เป็นเพื่อให้ง่ายต่อการค้นหาของคุณ:

>> ทำ:

  • train_on_batch()+ numpy+ tf.keras+ TF1 + กระตือรือร้น / กราฟ
  • train_on_batch()+ numpy+ tf.keras+ TF2 + กราฟ
  • fit()+ numpy+ tf.keras+ TF1 / TF2 + กราฟ + โมเดลและข้อมูลขนาดใหญ่

>> อย่า:

  • fit()+ numpy+ kerasสำหรับโมเดลและข้อมูลขนาดเล็กและขนาดกลาง
  • fit()+ numpy+ tf.keras+ TF1 / TF2 + กระตือรือร้น
  • train_on_batch()+ numpy+ keras+ TF1 + กระตือรือร้น

  • [เมเจอร์] tf.python.keras ; มันสามารถรันได้ช้ากว่า 10-100x และมีข้อบกพร่องมากมาย; ข้อมูลเพิ่มเติม

    • ซึ่งรวมถึงการlayers, models, optimizersและที่เกี่ยวข้องกับ "ออกจากกล่อง" การนำเข้าการใช้งาน; ops, utils, และการนำเข้า 'ส่วนบุคคล' ที่เกี่ยวข้องนั้นใช้ได้ - แต่ต้องแน่ใจว่าตรวจสอบ alts และดูว่าพวกเขากำลังใช้อยู่หรือไม่tf.keras

อ้างถึงรหัสที่ด้านล่างของคำตอบอื่น ๆ ของฉันสำหรับตัวอย่างการตั้งค่าการเปรียบเทียบ รายการด้านบนนี้ยึดตามตาราง "BENCHMARKS" เป็นหลักในคำตอบอื่น ๆ


ข้อ จำกัดของสิ่งที่ควรทำและไม่ควรทำ:

  • คำถามนี้มีชื่อว่า "ทำไม TF2 ถึงช้ากว่า TF1 มาก" และในขณะที่ร่างกายของมันเกี่ยวข้องกับการฝึกอบรมอย่างชัดเจนเรื่องไม่ได้ จำกัด อยู่ที่มัน การอนุมานก็ขึ้นอยู่กับความแตกต่างของความเร็วที่สำคัญแม้ใน TF รุ่นเดียวกันนำเข้ารูปแบบข้อมูล ฯลฯ - ดูคำตอบนี้
  • RNN มีแนวโน้มที่จะเปลี่ยนตารางข้อมูลในคำตอบอื่น ๆ โดยเฉพาะเนื่องจากพวกเขาได้รับการปรับปรุงใน TF2
  • รุ่นที่ใช้เป็นหลักConv1DและDense- ไม่มี RNNs, ข้อมูล / เป้าหมายที่กระจัดกระจาย, อินพุต 4 / 5D, และการกำหนดค่าอื่น ๆ
  • ข้อมูลอินพุต จำกัดnumpyและtf.data.Datasetในขณะที่มีรูปแบบอื่น ๆ ดูคำตอบอื่น ๆ
  • ใช้ GPU ผลลัพธ์จะแตกต่างกันบน CPU ในความเป็นจริงเมื่อฉันถามคำถาม CUDA ของฉันไม่ได้รับการกำหนดค่าอย่างเหมาะสมและผลลัพธ์บางอย่างเป็นแบบใช้ CPU

ทำไม TF2 ถึงเสียสละคุณภาพความเร็วในการใช้งานจริงสำหรับการดำเนินการอย่างกระตือรือร้น? มันไม่ชัดเจน - กราฟยังคงมีอยู่ แต่ถ้าคำถามคือ "ทำไมกระตือรือร้นเลย":

  • การดีบักที่เหนือกว่า : คุณน่าจะเจอคำถามมากมายที่ถามว่า "ฉันจะได้รับเลเยอร์ผลลัพธ์" หรือ "ฉันจะตรวจสอบน้ำหนักได้อย่างไร"; มีความกระตือรือร้นที่มันเป็น (เกือบ) .__dict__เป็นง่ายๆเป็น ในทางกลับกันกราฟต้องการความคุ้นเคยกับฟังก์ชั่นแบ็กเอนด์พิเศษซึ่งทำให้กระบวนการทั้งหมดของการดีบั๊ก & วิปัสสนาซับซ้อนยิ่งขึ้น
  • การสร้างต้นแบบที่เร็วขึ้น : ต่อความคิดที่คล้ายกับด้านบน เข้าใจเร็วขึ้น = เหลือเวลาอีกมากสำหรับ DL ที่แท้จริง

วิธีเปิดใช้งาน / ปิดใช้งานนกอินทรี

tf.enable_eager_execution()  # TF1; must be done before any model/tensor creation
tf.compat.v1.disable_eager_execution() # TF2; above holds

ข้อมูลเพิ่มเติม :

  • ระวังด้วย_on_batch()วิธีการใน TF2; ตาม TF dev พวกเขายังคงใช้การดำเนินการช้าลง แต่ไม่ได้ตั้งใจ - นั่นคือจะได้รับการแก้ไข ดูคำตอบอื่น ๆ สำหรับรายละเอียด

คำขอให้ใช้ TENSORFLOW DEVS :

  1. โปรดแก้ไขtrain_on_batch()และประสิทธิภาพการทำงานของการโทรfit()ซ้ำ ๆ ลูปรถไฟแบบกำหนดเองมีความสำคัญต่อคนจำนวนมากโดยเฉพาะกับฉัน
  2. เพิ่มเอกสาร / docstring พูดถึงความแตกต่างของประสิทธิภาพเหล่านี้สำหรับความรู้ของผู้ใช้
  3. ปรับปรุงความเร็วในการใช้งานทั่วไปเพื่อไม่ให้ peeps ข้ามไปที่ Pytorch

ACKNOWLEDGMENTS : ขอบคุณ


อัปเดต :

  • 11/14/19 - พบรุ่น (ในแอปพลิเคชันจริงของฉัน) ที่ทำงานช้าลงบน TF2 สำหรับการกำหนดค่าทั้งหมด * ที่มีข้อมูลอินพุต Numpy ความแตกต่างอยู่ระหว่าง 13-19% โดยเฉลี่ย 17% อย่างไรก็ตามความแตกต่างระหว่างkerasและtf.kerasมีความน่าสนใจมากขึ้น: 18-40% , เฉลี่ย 32% (ทั้ง TF1 และ 2) (* - ยกเว้น Eager ซึ่ง TF2 OOM จะใช้)

  • 11/17/19 - devs การปรับปรุงon_batch()วิธีการในเร็ว ๆ นี้กระทำที่ระบุจะมีความเร็วที่ดีขึ้น - ที่จะออกใน TF 2.1 tf-nightlyหรือที่มีอยู่ในขณะนี้เป็น ในขณะที่ฉันไม่สามารถวิ่งได้ในภายหลังฉันจะชะลอการ benching จนถึง 2.1

  • 2/20/20 - ประสิทธิภาพการทำนายก็คุ้มค่าเช่นกัน ใน TF2 เช่นเวลาการคาดการณ์ของ CPU สามารถเกี่ยวข้องกับหนามแหลมได้เป็นระยะ

3
เกี่ยวกับfit_generatorอะไร ... ฉันไม่เคยต้องการtrain_on_batchและจัดการลูปการฝึกอบรมของตัวเองข้ามแบตช์เป็นรูปแบบการต่อต้านที่ยิ่งใหญ่ที่ต้องหลีกเลี่ยงแม้ในราคาที่คุ้มค่า
ely

@ โดยจะยังคงได้รับการทดสอบตามที่ระบุไว้ในคำตอบอื่น ๆ ของฉัน - แต่หากมีสิ่งใดที่ฉันคาดว่าจะเป็นfitค่าใช้จ่ายในการประมวลผลข้อมูลเพิ่มเติมเล็กน้อย สำหรับลูปรถไฟฉันเขียนของฉันเองที่กลายเป็น API ในที่สุด fit_generatorขาดวิปัสสนาปรับแต่งได้และบันทึก / โหลด - ดังนั้นไม่แน่นอนสำหรับฉัน ในที่สุดฉันจะเผยแพร่ลูปการฝึกอบรมของฉันใน Github
OverLordGoldDragon

การขาดวิปัสสนาและการปรับแต่งเองเป็นคุณสมบัติสำหรับฉันไม่ใช่ข้อผิดพลาด IDK หมายถึงอะไรความคิดเห็นบันทึก / โหลดหมายถึงอะไร การบันทึก / การโหลดระดับกลางระหว่างลูปที่ไม่ได้ควบคุมโดยตัวสร้างข้อมูล (ฉันเองก็มีความสุขที่ต้องพึ่งการติดต่อกลับเพื่อสิ่งนั้นและจะเห็นความจำเป็นในการปรับแต่งเพิ่มเติมใด ๆ ต่อไปเนื่องจากกลิ่นรหัสที่ห่วงการฝึกของฉันออกแบบมาผิด)
ely

@ely ไม่ใช่เรื่องง่าย แต่จำเป็นสำหรับการฝึกอบรมที่มีท่อข้อมูลอินพุตที่ซับซ้อนฟังก์ชันวัตถุประสงค์และการกำหนดค่าโมเดลที่ไม่ใช่ API (เช่น ensembles) วิปัสสนาเป็นสิ่งจำเป็นสำหรับการแก้จุดบกพร่องและคุณสมบัติทางวิศวกรรมหลายอย่าง ขาดการบันทึก / โหลดจากภายนอกและฝึกความเสถียรและความสามารถในการรีลูปสำหรับโมเดลที่คำนวณราคาแพงซึ่งเป็นฝันร้าย ไม่ว่าท้ายที่สุดแล้วขึ้นอยู่กับความต้องการเฉพาะของคุณและการปิดหัวข้อ วิธีที่ดีที่สุดในการทดสอบประสิทธิภาพที่มีfit_generatorสำหรับแอปพลิเคชันของคุณคือทดสอบทดสอบ
OverLordGoldDragon

47

คำตอบนี้ : มีวัตถุประสงค์เพื่อให้คำอธิบายรายละเอียดกราฟ / ระดับฮาร์ดแวร์ของปัญหา - รวมถึงลูปรถไฟ TF2 กับ TF1 ตัวประมวลผลข้อมูลอินพุตและการดำเนินการโหมดกระตือรือร้นกับกราฟ สำหรับสรุปปัญหาและแนวทางแก้ไขปัญหาให้ดูคำตอบอื่นของฉัน


การตัดสินประสิทธิภาพ : บางครั้งหนึ่งเร็วขึ้นบางครั้งขึ้นอยู่กับการกำหนดค่า เท่าที่ TF2 กับ TF1 ดำเนินไปพวกเขาโดยเฉลี่ยแล้วมีความแตกต่างอย่างมีนัยสำคัญ แต่ก็มีอยู่จริงและ TF1 สำคัญกว่า TF2 มากกว่า TF2 บ่อยกว่าในทางกลับกัน ดู "การทำเครื่องหมาย" ที่ด้านล่าง


EAGER VS กราฟ : เนื้อของคำตอบทั้งหมดนี้สำหรับบางคน: ความกระตือรือร้นของ TF2 ช้ากว่าของ TF1 ตามการทดสอบของฉัน รายละเอียดเพิ่มเติมลงไป

ความแตกต่างพื้นฐานระหว่างทั้งสองคือกราฟตั้งค่าเครือข่ายการคำนวณเชิงรุกและดำเนินการเมื่อ 'บอกกับ' - ในขณะที่กระตือรือร้นดำเนินการทุกอย่างเมื่อสร้าง แต่เรื่องราวเริ่มต้นที่นี่เท่านั้น:

  • ความกระตือรือร้นไม่ใช่กราฟและในความเป็นจริงอาจเป็นกราฟส่วนใหญ่ตรงกันข้ามกับความคาดหมาย ส่วนใหญ่มันคือการดำเนินการกราฟ - ซึ่งรวมถึงน้ำหนักของโมเดลและเครื่องมือเพิ่มประสิทธิภาพซึ่งประกอบด้วยส่วนที่ดีของกราฟ

  • สร้างใหม่กระตือรือร้นที่เป็นส่วนหนึ่งของกราฟเองที่ดำเนินการ ; ผลที่ตามมาโดยตรงของกราฟที่ไม่ได้ถูกสร้างขึ้นอย่างสมบูรณ์ - ดูผลลัพธ์ของผู้สร้างโปรไฟล์ นี่คือค่าใช้จ่ายในการคำนวณ

  • กระตือรือร้นที่จะช้าลงด้วยอินพุต Numpy ; ตามความคิดเห็นและรหัสGit นี้อินพุต Numpy ใน Eager รวมถึงค่าใช้จ่ายในการคัดลอกเทนเซอร์จากซีพียูไปยัง GPU การก้าวผ่านซอร์สโค้ดความแตกต่างของการจัดการข้อมูลนั้นชัดเจน กระตือรือร้นที่จะผ่าน Numpy โดยตรงในขณะที่กราฟผ่านเทนเซอร์ซึ่งประเมินค่าเป็น Numpy ความไม่แน่นอนของกระบวนการที่แน่นอน แต่อย่างหลังควรเกี่ยวข้องกับการเพิ่มประสิทธิภาพระดับ GPU

  • TF2 Eager ช้ากว่า TF1 Eager - นี่คือ ... ไม่คาดคิด ดูผลลัพธ์การเปรียบเทียบด้านล่าง ความแตกต่างมีตั้งแต่เล็กน้อยไปจนถึงมีนัยสำคัญ แต่มีความสอดคล้องกัน ไม่แน่ใจว่าทำไมมันถึงเป็นเช่นนั้น - หาก Dev dev ของ TF ชี้แจงจะอัพเดตคำตอบ


TF2 กับ TF1 : อ้างถึงส่วนที่เกี่ยวข้องของ TF dev, Q. Scott Zhu's, การตอบสนอง - w / bit จากการเน้นของฉัน & การอ้างอิงใหม่:

ในความกระตือรือร้นรันไทม์จำเป็นต้องเรียกใช้ ops และส่งกลับค่าตัวเลขสำหรับทุกบรรทัดของรหัสไพ ธ อน ลักษณะของการดำเนินการขั้นตอนเดียวที่ทำให้มันจะช้า

ใน TF2 Keras ใช้ประโยชน์จาก tf.function เพื่อสร้างกราฟสำหรับการฝึกอบรมการประเมินผลและการทำนาย เราเรียกพวกเขาว่า "ฟังก์ชันการประมวลผล" สำหรับโมเดล ใน TF1 "ฟังก์ชั่นการทำงาน" เป็น FuncGraph ซึ่งใช้องค์ประกอบทั่วไปร่วมกันเป็นฟังก์ชั่น TF แต่มีการใช้งานที่แตกต่างกัน

ในระหว่างกระบวนการเราก็ทิ้งการดำเนินการไม่ถูกต้องสำหรับ train_on_batch () test_on_batch () และ predict_on_batch () พวกเขายังคงถูกต้องเป็นตัวเลขแต่ฟังก์ชั่นการดำเนินการสำหรับ x_on_batch เป็นฟังก์ชั่นหลามบริสุทธิ์มากกว่าฟังก์ชั่นห่องูเหลือม tf.function สิ่งนี้จะทำให้เกิดความเชื่องช้า

ใน TF2 เราแปลงข้อมูลอินพุตทั้งหมดเป็น tf.data.Dataset ซึ่งเราสามารถรวมฟังก์ชันการดำเนินการของเราเพื่อจัดการอินพุตชนิดเดียว อาจมีค่าใช้จ่ายบางส่วนในการแปลงชุดข้อมูลและฉันคิดว่านี่เป็นค่าใช้จ่ายเพียงครั้งเดียวเท่านั้นแทนที่จะเป็นต้นทุนต่อชุด

ด้วยประโยคสุดท้ายของย่อหน้าสุดท้ายข้างต้นและประโยคสุดท้ายของย่อหน้าด้านล่าง:

เพื่อเอาชนะความเชื่องช้าในโหมดกระตือรือร้นเรามี @ tf.function ซึ่งจะเปลี่ยนฟังก์ชั่นของไพ ธ อนให้เป็นกราฟ เมื่อป้อนค่าตัวเลขเช่นอาร์เรย์ np เนื้อความของ tf.function จะถูกแปลงเป็นกราฟคงที่การปรับให้เหมาะสมและส่งคืนค่าสุดท้ายซึ่งรวดเร็วและควรมีประสิทธิภาพเช่นเดียวกับโหมดกราฟ TF1

ฉันไม่เห็นด้วย - ต่อผลลัพธ์การทำโปรไฟล์ของฉันซึ่งแสดงให้เห็นว่าการประมวลผลข้อมูลเข้าของ Eager นั้นช้ากว่ากราฟอย่างมาก นอกจากนี้ไม่แน่ใจtf.data.Datasetโดยเฉพาะเกี่ยวกับEager แต่ซ้ำ ๆ เรียกหลายวิธีการแปลงข้อมูลเดียวกัน - ดู profiler

สุดท้ายความคลาดเคลื่อนของการเชื่อมโยงการกระทำ: จำนวนของการเปลี่ยนแปลงอย่างมีนัยสำคัญที่จะสนับสนุนการ Keras ลูป


Train Loops : ขึ้นอยู่กับ (1) กระตือรือร้นกับกราฟ; (2) รูปแบบข้อมูลอินพุตการฝึกอบรมในจะดำเนินการต่อด้วยขบวนรถไฟที่แตกต่างกัน - ใน TF2 _select_training_loop(),, training.py , หนึ่งใน:

training_v2.Loop()
training_distributed.DistributionMultiWorkerTrainingLoop(
              training_v2.Loop()) # multi-worker mode
# Case 1: distribution strategy
training_distributed.DistributionMultiWorkerTrainingLoop(
            training_distributed.DistributionSingleWorkerTrainingLoop())
# Case 2: generator-like. Input is Python generator, or Sequence object,
# or a non-distributed Dataset or iterator in eager execution.
training_generator.GeneratorOrSequenceTrainingLoop()
training_generator.EagerDatasetOrIteratorTrainingLoop()
# Case 3: Symbolic tensors or Numpy array-like. This includes Datasets and iterators 
# in graph mode (since they generate symbolic tensors).
training_generator.GeneratorLikeTrainingLoop() # Eager
training_arrays.ArrayLikeTrainingLoop() # Graph

แต่ละจัดการการจัดสรรทรัพยากรแตกต่างกันและมีผลต่อประสิทธิภาพและความสามารถ


Train Loops: fitvs train_on_batch, kerasvs.tf.keras : แต่ละสี่คนใช้ลูปรถไฟที่แตกต่างกันถึงแม้ว่าอาจจะไม่ได้อยู่ในชุดค่าผสมที่เป็นไปได้ทั้งหมด keras' fitยกตัวอย่างเช่นการใช้รูปแบบของfit_loope กรัมtraining_arrays.fit_loop()และมันอาจจะใช้train_on_batch มีลำดับชั้นที่ซับซ้อนยิ่งขึ้นซึ่งอธิบายไว้ในส่วนหนึ่งของส่วนก่อนหน้าK.function()tf.keras


Train Loops: เอกสารประกอบ - docstring แหล่งข้อมูลที่เกี่ยวข้องในวิธีการดำเนินการที่แตกต่างกัน:

ซึ่งแตกต่างจากการดำเนินงานของ TensorFlow อื่น ๆ เราไม่แปลงอินพุตตัวเลขหลามเป็นเมตริกซ์ ยิ่งไปกว่านั้นกราฟใหม่ถูกสร้างขึ้นสำหรับค่าตัวเลขไพ ธ อนแต่ละค่า

function instantiates กราฟแยกต่างหากสำหรับทุกชุดที่เป็นเอกลักษณ์ของรูปทรงเข้าและประเภทข้อมูล

วัตถุ tf.function เดียวอาจจำเป็นต้องแมปไปยังกราฟการคำนวณหลายอันภายใต้ประทุน สิ่งนี้ควรมองเห็นได้ก็ต่อเมื่อมีประสิทธิภาพ (กราฟการติดตามมีการคำนวณที่ไม่ใช่ศูนย์และต้นทุนหน่วยความจำ )


ตัวประมวลผลข้อมูลขาเข้า : คล้ายกับด้านบนโปรเซสเซอร์จะถูกเลือกเป็นกรณี ๆ ไปขึ้นอยู่กับการตั้งค่าสถานะภายในตามการกำหนดค่ารันไทม์ (โหมดการดำเนินการรูปแบบข้อมูลกลยุทธ์การกระจาย) กรณีที่ง่ายที่สุดกับ Eager ซึ่งทำงานโดยตรงกับอาร์เรย์ Numpy สำหรับตัวอย่างเฉพาะดูคำตอบนี้


MODEL SIZE ขนาด DATA:

  • เด็ดขาด; ไม่มีการกำหนดค่าเดียวครองตำแหน่งตัวเองอยู่เหนือโมเดลและขนาดข้อมูลทั้งหมด
  • ขนาดข้อมูลที่สัมพันธ์กับขนาดของโมเดลมีความสำคัญ สำหรับข้อมูลและรุ่นขนาดเล็กการถ่ายโอนข้อมูล (เช่น CPU ไปยัง GPU) เหนือศีรษะสามารถครองได้ ตัวประมวลผลค่าโสหุ้ยขนาดเล็กสามารถทำงานได้ช้าลงในข้อมูลขนาดใหญ่ต่อการแปลงข้อมูลที่มีอิทธิพลเหนือเวลา (ดู convert_to_tensorใน "PROFILER")
  • ความแตกต่างของความเร็วต่อลูปของขบวนรถไฟและตัวประมวลผลข้อมูลที่ต่างกันของการจัดการทรัพยากร

BENCHMARKS : เนื้อบด - เอกสาร Word - Excel Spreadsheet


คำศัพท์ :

  • ตัวเลข% -less เป็นวินาทีทั้งหมด
  • % คำนวณเป็น(1 - longer_time / shorter_time)*100; เหตุผล: เราสนใจว่าปัจจัยใดเร็วกว่าอีกปัจจัย shorter / longerจริงๆแล้วเป็นความสัมพันธ์ที่ไม่ใช่เชิงเส้นซึ่งไม่มีประโยชน์สำหรับการเปรียบเทียบโดยตรง
  • การกำหนดเครื่องหมาย%:
    • TF2 กับ TF1: +ถ้า TF2 เร็วกว่า
    • GvE (กราฟกับความกระตือรือร้น): +หากกราฟเร็วขึ้น
  • TF2 = TensorFlow 2.0.0 + Keras 2.3.1; TF1 = TensorFlow 1.14.0 + Keras 2.2.5

โปรไฟล์ :


PROFILER - คำอธิบาย : Spyder 3.3.6 IDE profiler

  • ฟังก์ชั่นบางอย่างซ้ำแล้วซ้ำอีกในรังของคนอื่น ๆ ; ดังนั้นจึงเป็นเรื่องยากที่จะติดตามการแยกที่แน่นอนระหว่างฟังก์ชั่น "การประมวลผลข้อมูล" และ "การฝึกอบรม" ดังนั้นจะมีการทับซ้อนกัน - ดังที่ปรากฏในผลลัพธ์สุดท้าย

  • ตัวเลขที่คำนวณได้จาก wrt runtime ลบด้วยเวลาสร้าง

  • Build time ที่คำนวณได้จากการรวม runtimes ทั้งหมด
  • เวลารถไฟคำนวณโดยการรวมรันไทม์ทั้งหมด (ที่ไม่ซ้ำกัน) ซึ่งถูกเรียกว่า # ครั้งเดียวกับจำนวนครั้งของการทำซ้ำและบางช่วงเวลาของรังของพวกเขา
  • ฟังก์ชั่นจะถูกทำโปรไฟล์ตามชื่อดั้งเดิมของพวกเขาโชคไม่ดี (เช่น_func = funcจะทำโปรไฟล์เป็นfunc) ซึ่งผสมในเวลาสร้าง - ดังนั้นจึงจำเป็นต้องแยกออก

สภาพแวดล้อมการทดสอบ :

  • รันรหัสที่ด้านล่าง w / งานพื้นหลังที่น้อยที่สุดทำงาน
  • GPU ได้รับการ "อุ่นเครื่อง" ด้วยการทำซ้ำสองสามครั้งก่อนกำหนดเวลาซ้ำตามที่แนะนำในโพสต์นี้
  • CUDA 10.0.130, cuDNN 7.6.0, TensorFlow 1.14.0, & TensorFlow 2.0.0 ที่สร้างจากแหล่งรวมถึง Anaconda
  • Python 3.7.4, Spyder 3.3.6 IDE
  • GTX 1070, Windows 10, 24GB DDR4 RAM 2.4-MHz, i7-7700HQ CPU 2.8-GHz

วิธีการ :

  • เกณฑ์มาตรฐาน 'เล็ก', 'กลาง', & 'ใหญ่' โมเดลและขนาดข้อมูล
  • แก้ไข # ของพารามิเตอร์สำหรับขนาดแต่ละรุ่นโดยไม่ขึ้นกับขนาดข้อมูลเข้า
  • โมเดล "ใหญ่ขึ้น" มีพารามิเตอร์และเลเยอร์มากกว่า
  • ข้อมูล "ใหญ่ขึ้น" มีลำดับที่ยาวกว่า แต่เหมือนกันbatch_sizeและnum_channels
  • รุ่นเพียงใช้Conv1D, Denseชั้น 'learnable'; หลีกเลี่ยง RNN ต่อแต่ละเวอร์ชัน TF ใช้ ความแตกต่าง
  • วิ่งรถไฟหนึ่งขบวนพอดีกับวงรอบการเปรียบเทียบเพื่อละเว้นการสร้างกราฟของโมเดลและเครื่องมือเพิ่มประสิทธิภาพ
  • ไม่ใช้ข้อมูลเบาบาง (เช่นlayers.Embedding()) หรือเป้าหมายกระจัดกระจาย (เช่นSparseCategoricalCrossEntropy()

ข้อ จำกัด : คำตอบ "สมบูรณ์" จะอธิบายทุก ๆ วงรถไฟ & ตัววนซ้ำที่เป็นไปได้ แต่นั่นเป็นเรื่องที่เกินความสามารถด้านเวลาของฉัน paycheck ที่ไม่มีอยู่หรือความจำเป็นทั่วไป ผลลัพธ์นั้นดีพอ ๆ กับวิธีการ - ตีความด้วยใจที่เปิดกว้าง


รหัส :

import numpy as np
import tensorflow as tf
import random
from termcolor import cprint
from time import time

from tensorflow.keras.layers import Input, Dense, Conv1D
from tensorflow.keras.layers import Dropout, GlobalAveragePooling1D
from tensorflow.keras.models import Model
from tensorflow.keras.optimizers import Adam
import tensorflow.keras.backend as K
#from keras.layers import Input, Dense, Conv1D
#from keras.layers import Dropout, GlobalAveragePooling1D
#from keras.models import Model 
#from keras.optimizers import Adam
#import keras.backend as K

#tf.compat.v1.disable_eager_execution()
#tf.enable_eager_execution()

def reset_seeds(reset_graph_with_backend=None, verbose=1):
    if reset_graph_with_backend is not None:
        K = reset_graph_with_backend
        K.clear_session()
        tf.compat.v1.reset_default_graph()
        if verbose:
            print("KERAS AND TENSORFLOW GRAPHS RESET")

    np.random.seed(1)
    random.seed(2)
    if tf.__version__[0] == '2':
        tf.random.set_seed(3)
    else:
        tf.set_random_seed(3)
    if verbose:
        print("RANDOM SEEDS RESET")

print("TF version: {}".format(tf.__version__))
reset_seeds()

def timeit(func, iterations, *args, _verbose=0, **kwargs):
    t0 = time()
    for _ in range(iterations):
        func(*args, **kwargs)
        print(end='.'*int(_verbose))
    print("Time/iter: %.4f sec" % ((time() - t0) / iterations))

def make_model_small(batch_shape):
    ipt   = Input(batch_shape=batch_shape)
    x     = Conv1D(128, 40, strides=4, padding='same')(ipt)
    x     = GlobalAveragePooling1D()(x)
    x     = Dropout(0.5)(x)
    x     = Dense(64, activation='relu')(x)
    out   = Dense(1,  activation='sigmoid')(x)
    model = Model(ipt, out)
    model.compile(Adam(lr=1e-4), 'binary_crossentropy')
    return model

def make_model_medium(batch_shape):
    ipt = Input(batch_shape=batch_shape)
    x = ipt
    for filters in [64, 128, 256, 256, 128, 64]:
        x  = Conv1D(filters, 20, strides=1, padding='valid')(x)
    x     = GlobalAveragePooling1D()(x)
    x     = Dense(256, activation='relu')(x)
    x     = Dropout(0.5)(x)
    x     = Dense(128, activation='relu')(x)
    x     = Dense(64,  activation='relu')(x)
    out   = Dense(1,   activation='sigmoid')(x)
    model = Model(ipt, out)
    model.compile(Adam(lr=1e-4), 'binary_crossentropy')
    return model

def make_model_large(batch_shape):
    ipt   = Input(batch_shape=batch_shape)
    x     = Conv1D(64,  400, strides=4, padding='valid')(ipt)
    x     = Conv1D(128, 200, strides=1, padding='valid')(x)
    for _ in range(40):
        x = Conv1D(256,  12, strides=1, padding='same')(x)
    x     = Conv1D(512,  20, strides=2, padding='valid')(x)
    x     = Conv1D(1028, 10, strides=2, padding='valid')(x)
    x     = Conv1D(256,   1, strides=1, padding='valid')(x)
    x     = GlobalAveragePooling1D()(x)
    x     = Dense(256, activation='relu')(x)
    x     = Dropout(0.5)(x)
    x     = Dense(128, activation='relu')(x)
    x     = Dense(64,  activation='relu')(x)    
    out   = Dense(1,   activation='sigmoid')(x)
    model = Model(ipt, out)
    model.compile(Adam(lr=1e-4), 'binary_crossentropy')
    return model

def make_data(batch_shape):
    return np.random.randn(*batch_shape), \
           np.random.randint(0, 2, (batch_shape[0], 1))

def make_data_tf(batch_shape, n_batches, iters):
    data = np.random.randn(n_batches, *batch_shape),
    trgt = np.random.randint(0, 2, (n_batches, batch_shape[0], 1))
    return tf.data.Dataset.from_tensor_slices((data, trgt))#.repeat(iters)

batch_shape_small  = (32, 140,   30)
batch_shape_medium = (32, 1400,  30)
batch_shape_large  = (32, 14000, 30)

batch_shapes = batch_shape_small, batch_shape_medium, batch_shape_large
make_model_fns = make_model_small, make_model_medium, make_model_large
iterations = [200, 100, 50]
shape_names = ["Small data",  "Medium data",  "Large data"]
model_names = ["Small model", "Medium model", "Large model"]

def test_all(fit=False, tf_dataset=False):
    for model_fn, model_name, iters in zip(make_model_fns, model_names, iterations):
        for batch_shape, shape_name in zip(batch_shapes, shape_names):
            if (model_fn is make_model_large) and (batch_shape is batch_shape_small):
                continue
            reset_seeds(reset_graph_with_backend=K)
            if tf_dataset:
                data = make_data_tf(batch_shape, iters, iters)
            else:
                data = make_data(batch_shape)
            model = model_fn(batch_shape)

            if fit:
                if tf_dataset:
                    model.train_on_batch(data.take(1))
                    t0 = time()
                    model.fit(data, steps_per_epoch=iters)
                    print("Time/iter: %.4f sec" % ((time() - t0) / iters))
                else:
                    model.train_on_batch(*data)
                    timeit(model.fit, iters, *data, _verbose=1, verbose=0)
            else:
                model.train_on_batch(*data)
                timeit(model.train_on_batch, iters, *data, _verbose=1)
            cprint(">> {}, {} done <<\n".format(model_name, shape_name), 'blue')
            del model

test_all(fit=True, tf_dataset=False)

ฉันไม่แน่ใจว่ารหัสของคุณถูกต้องหรือไม่ ฉันคิดว่าแบบจำลองของคุณจะทำงานในโหมดกราฟตลอดเวลาเนื่องจากคุณโทรmodel.compileโดยไม่มีการrun_eagerly=Trueโต้แย้ง tf.functionหากอยู่ในโหมดความกระตือรือร้นที่คุณสามารถเรียกใช้เป็นส่วนหนึ่งของรหัสของคุณในโหมดกราฟใช้ ดังนั้นฉันคิดว่าการใช้งานเริ่มต้นของcompileคือการสร้างกราฟการคำนวณแทนการรันมันอย่างกระตือรือร้นด้วยเหตุผลด้านประสิทธิภาพ นอกจากนี้โปรดทราบว่าหากแบบจำลองของคุณเป็นแบบ Convolutional คุณจะไม่เห็นการเร่งความเร็วในโหมดกราฟเนื่องจากการทำงานแบบหลามนั้นน้อยมาก หากคุณทำคณิตศาสตร์หลายอย่างเกินกว่าจะสร้างความแตกต่างได้มาก
user2781994

@OverLordGoldDragon แต่ใน TF 2 โหมด eager เป็นค่าเริ่มต้น แต่model.compileไม่มีrun_eagerly=Trueโหมดกราฟแน่ใจหรือไม่?
2781994

@OverLordGoldDragon ฉันยอมรับว่าไม่ใช่วิธีการที่นำเข้าทั้งหมดที่ทำงานในโหมดกราฟ แต่ฉันคิดว่าอย่างใดอย่างหนึ่งmodel.compileหรือmodel.fitต้องให้แน่ใจว่าการฝึกอบรมทำงานในโหมดกราฟภายใน
user2781994

@OverLordGoldDragon TRUE - "tf.keras.Model.compile ใช้เวลาสามข้อโต้แย้งที่สำคัญ: ... นอกจากนี้เพื่อให้แน่ใจว่าแบบจำลองรถไฟและประเมินผลอย่างกระตือรือร้นคุณสามารถตรวจสอบให้แน่ใจว่าผ่านrun_eagerly=Trueเป็นพารามิเตอร์ในการรวบรวม" (แหล่งtenorflow.org/guide/keras/overview ) ดังนั้นฉันถ้าคุณไม่ผ่านrun_eagerly=Trueแบบจำลองสามารถทำงานในโหมดกราฟ ฉันไม่แน่ใจว่าปัจจัยการตัดสินใจคืออะไร แต่ทำไมมันไม่ทำงานในโหมดกราฟหากมันมีประสิทธิภาพมากกว่าความกระตือรือร้น
user2781994

คุณต้องการหลักฐานเพิ่มเติมหรือไม่ :) "โดยค่าเริ่มต้นเราจะพยายามรวบรวมโมเดลของคุณเป็นกราฟคงที่เพื่อให้ได้ประสิทธิภาพในการดำเนินการที่ดีที่สุด" ( github.com/tensorflow/tensorflow/blob/r2.0/tensorflow/python/… )
2781994
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.