คำตอบนี้ : มีวัตถุประสงค์เพื่อให้คำอธิบายรายละเอียดกราฟ / ระดับฮาร์ดแวร์ของปัญหา - รวมถึงลูปรถไฟ 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: fit
vs train_on_batch
, keras
vs.tf.keras
: แต่ละสี่คนใช้ลูปรถไฟที่แตกต่างกันถึงแม้ว่าอาจจะไม่ได้อยู่ในชุดค่าผสมที่เป็นไปได้ทั้งหมด keras
' fit
ยกตัวอย่างเช่นการใช้รูปแบบของfit_loop
e กรัม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)