ทำไมรูปแบบ keras ทำนายผลช้าลงหลังจากรวบรวม


23

keras ความเร็วในการทำนาย

ในทางทฤษฎีการทำนายควรจะคงที่เนื่องจากน้ำหนักมีขนาดคงที่ ฉันจะกลับมาใช้ความเร็วใหม่หลังจากคอมไพล์ได้อย่างไร (โดยไม่จำเป็นต้องลบเครื่องมือเพิ่มประสิทธิภาพ)

ดูการทดสอบที่เกี่ยวข้อง: https://nbviewer.jupyter.org/github/off99555/TensorFlowExperiments/blob/master/test-prediction-speed-after-compile.ipynb?flush_cache=true


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

@naive Fitting ไม่เกี่ยวข้องกับปัญหา หากคุณรู้ว่าเครือข่ายใช้งานได้จริงคุณจะสงสัยว่าทำไมการทำนายจึงช้าลง เมื่อทำการคาดการณ์จะใช้น้ำหนักเพียงอย่างเดียวสำหรับการคูณเมทริกซ์และน้ำหนักจะต้องได้รับการแก้ไขก่อนและหลังการคอมไพล์ดังนั้นเวลาการทำนายควรคงที่
off99555

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

3
@ naive ปัญหาเกี่ยวข้องกับการทำความเข้าใจรูปแบบการคอมไพล์เทียบกับไม่คอมไพล์โดยไม่เกี่ยวข้องกับความแม่นยำหรือการออกแบบโมเดล มันเป็นปัญหาที่ถูกกฎหมายที่จะทำให้ผู้ใช้ TF เสียค่าใช้จ่าย - สำหรับคนหนึ่งไม่มีเงื่อนงำเกี่ยวกับเรื่องนี้จนกระทั่งสะดุดกับคำถามนี้
OverLordGoldDragon

1
@naive คุณไม่สามารถทำได้fitโดยไม่ต้องcompile; เครื่องมือเพิ่มประสิทธิภาพไม่มีแม้แต่ที่จะอัปเดตตุ้มน้ำหนักใด ๆ predict สามารถใช้งานได้โดยไม่ต้องมีfitหรือcompileตามที่อธิบายไว้ในคำตอบของฉัน แต่ความแตกต่างของประสิทธิภาพไม่ควรเป็นไปได้อย่างน่าทึ่งซึ่งเป็นปัญหา
OverLordGoldDragon

คำตอบ:


22

UPDATE - 2020/01/15 : การปฏิบัติที่ดีที่สุดในปัจจุบันสำหรับขนาดชุดเล็กควรจะเป็นปัจจัยการผลิตฟีดกับรูปแบบโดยตรง - คือpreds = model(x)และถ้าชั้นทำงานแตกต่างกันที่รถไฟ / model(x, training=False)การอนุมาน ตามการกระทำล่าสุดสิ่งนี้ได้รับการบันทึกไว้แล้ว

ฉันไม่ได้เปรียบเทียบสิ่งเหล่านี้ แต่ตามการอภิปรายของ Gitมันก็คุ้มค่าที่จะลองpredict_on_batch()- โดยเฉพาะอย่างยิ่งกับการปรับปรุงใน TF 2.1


ULTIMATE ผู้ร้ายself._experimental_run_tf_function = True : มันเป็นเรื่องการทดลอง แต่มันก็ไม่ได้เลวร้ายจริงๆ

เพื่อ TensorFlow ใด devs อ่าน: ทำความสะอาดรหัสของคุณ มันเป็นระเบียบ และมีการละเมิดการปฏิบัติการเข้ารหัสที่สำคัญเช่นฟังก์ชันหนึ่งไม่สิ่งหนึ่ง ; _process_inputsไม่ได้มากขึ้นกว่า "ปัจจัยการผลิตกระบวนการ" _standardize_user_dataเหมือนกันสำหรับ "ฉันไม่ได้รับเงินมากพอ" - แต่คุณต้องจ่ายเงินเพิ่มในการใช้เวลาทำความเข้าใจกับสิ่งของของคุณเองและในผู้ใช้ที่กรอกหน้าปัญหาของคุณด้วยข้อบกพร่องที่แก้ไขได้ง่ายขึ้นด้วยรหัสที่ชัดเจนขึ้น


สรุป : เป็นเพียงเล็ก ๆ น้อย ๆcompile()ช้าลงด้วย

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

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


ฉันควรทำอย่างไรดี?

เปรียบเทียบประสิทธิภาพของรุ่นที่คอมไพล์แล้วเทียบกับคอมไพล์อย่างที่ฉันมีในโค้ดที่ด้านล่าง

  • รวบรวมได้เร็วขึ้น : ทำงานpredictในรูปแบบที่รวบรวม
  • การคอมไพล์ช้ากว่า : รันpredictบนโมเดลที่ไม่ได้คอมไพล์

ใช่ทั้งสองเป็นไปได้และมันจะขึ้นอยู่กับขนาดข้อมูล (1); (2) ขนาดรูปแบบ; (3) ฮาร์ดแวร์ โค้ดที่ด้านล่างแสดงแบบจำลองที่คอมไพล์ได้เร็วขึ้น แต่การทำซ้ำ 10 ครั้งเป็นตัวอย่างขนาดเล็ก ดู "วิธีแก้ไขปัญหา" ในคำตอบอื่น ๆ ของฉันสำหรับ "วิธีการ"


รายละเอียด :

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

( FLAG == self.experimental_run_tf_functionสำหรับความกะทัดรัด)

  1. ModelFLAG=Falseโดยเริ่มต้นด้วย instantiates ชุดมันไปcompile()True
  2. predict() เกี่ยวข้องกับการรับฟังก์ชั่นการทำนาย func = self._select_training_loop(x)
  3. หากไม่มี kwargs พิเศษใด ๆ ที่ส่งผ่านไปยังpredictและcompileแฟล็กอื่น ๆ ทั้งหมดจะเป็นเช่นนั้น:
    • (A) FLAG==True ->func = training_v2.Loop()
    • (B) FLAG==False ->func = training_arrays.ArrayLikeTrainingLoop()
  4. จากซอร์สโค้ด docstring , (A)เป็นกราฟที่พึ่งพาได้อย่างมากใช้กลยุทธ์การกระจายมากขึ้นและ ops มีแนวโน้มที่จะสร้างและทำลายองค์ประกอบกราฟซึ่ง "อาจ" (ทำ) ส่งผลกระทบต่อประสิทธิภาพ

ผู้กระทำผิดที่แท้จริง : _process_inputs()คิดเป็น81% ของรันไทม์ เป็นองค์ประกอบหลักหรือไม่ _create_graph_function(), 72% ของรันไทม์ วิธีการนี้ไม่ได้มีอยู่สำหรับ(B) โดยใช้แบบจำลองขนาดกลาง แต่_process_inputsประกอบด้วยน้อยกว่า 1% ของรันไทม์ โค้ดที่ด้านล่างและติดตามผลลัพธ์การทำโปรไฟล์


ตัวประมวลผลข้อมูล :

(A) : ใช้ในการ<class 'tensorflow.python.keras.engine.data_adapter.TensorLikeDataAdapter'> ซอร์สโค้ดที่เกี่ยวข้อง_process_inputs()

(B) : กลับโดยnumpy.ndarray ซอร์สโค้ดที่เกี่ยวข้องและที่นี่convert_eager_tensors_to_numpy


ฟังก์ชั่นการใช้งาน MODEL (เช่นทำนาย)

(A) : ฟังก์ชั่นการกระจายและที่นี่

(B) : ฟังก์ชั่นการกระจาย (แตกต่างกัน)และที่นี่


PROFILER : ผลลัพธ์สำหรับโค้ดในคำตอบอื่น ๆ ของฉัน "รุ่นจิ๋ว" และในคำตอบนี้ "โมเดลขนาดกลาง":

โมเดลจิ๋ว : 1,000 รอบcompile()

รุ่นจิ๋ว : 1,000 รอบซ้ำไม่ compile()

โมเดลขนาดกลาง : 10 ซ้ำ


เอกสาร (ทางอ้อม) เกี่ยวกับผลกระทบของcompile(): แหล่งที่มา

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

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

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


COUNTEREXAMPLE :

from tensorflow.keras.layers import Input, Dense, LSTM, Bidirectional, Conv1D
from tensorflow.keras.layers import Flatten, Dropout
from tensorflow.keras.models import Model
import numpy as np
from time import time

def timeit(func, arg, iterations):
    t0 = time()
    for _ in range(iterations):
        func(arg)
    print("%.4f sec" % (time() - t0))

batch_size = 32
batch_shape = (batch_size, 400, 16)
ipt   = Input(batch_shape=batch_shape)
x     = Bidirectional(LSTM(512, activation='relu', return_sequences=True))(ipt)
x     = LSTM(512, activation='relu', return_sequences=True)(ipt)
x     = Conv1D(128, 400, 1, 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)

X = np.random.randn(*batch_shape)
timeit(model.predict, X, 10)
model.compile('adam', loss='binary_crossentropy')
timeit(model.predict, X, 10)

เอาท์พุท :

34.8542 sec
34.7435 sec

1
ข้อสรุปเกี่ยวกับสิ่งที่เราควรทำเพื่อให้ได้ความเร็วในการทำนายที่เร็วที่สุดสำหรับทุกขนาดของโมเดล มันเป็นเพียงแค่ไม่ทำcompile()?
off99555

3
@ off99555 "สำหรับขนาดของโมเดล" - ไม่มีสิ่งนั้น อ่านคำตอบทั้งหมด - ถ้าฉันใช้เวลาหลายชั่วโมงเพื่อทำการดีบั๊กสักสองสามนาทีจากผู้ถามไม่ควรจะไม่มีเหตุผล
OverLordGoldDragon

ฉันอ่านทุกอย่าง แต่มันยากที่จะเข้าใจเพราะฉันไม่ใช่คนที่บั๊กโค้ด ดังนั้นคุณต้องให้ข้อสรุปที่ไม่เกี่ยวข้องกับตัวแปรกลางที่คุณพบในระหว่างขั้นตอนการตรวจแก้จุดบกพร่อง เช่น "ถ้าแบบจำลองของคุณมีขนาดเล็กอย่าใช้การคอมไพล์ถ้าแบบจำลองของคุณมีขนาดกลางคุณสามารถใช้การคอมไพล์ได้ 'บางอย่างเช่นนั้น
off99555

1
@ off99555 ยุติธรรมเพียงพอ; อัปเดต หัวข้อใหม่ค่อนข้างสามัญสำนึก แต่ฉันเห็นได้ว่ามันไม่ได้รับการตระหนักในทันที
OverLordGoldDragon

1
@ off99555 ไม่ใช่ว่าฉันทดสอบ แต่รุ่นที่มีขนาดใหญ่มาก (ResNet ฯลฯ ) อาจทำงานได้เร็วขึ้นโดยเฉพาะอย่างยิ่งการรวบรวม หากจำหน่ายในอุปกรณ์หลายชนิดเช่นเดียวกับ(A)จะมีกราฟและการกระจายมากขึ้น การทดสอบที่แน่นอนคือการทดสอบในคำตอบ ไม่คุ้นเคยกับ TF lite แต่เป็นคำถามแยกต่างหาก
OverLordGoldDragon

15

UPDATE : ดูคำตอบจริงที่โพสต์เป็นคำตอบแยกต่างหาก โพสต์นี้มีข้อมูลเพิ่มเติม


.compile() ตั้งค่ากราฟ TF / Keras ส่วนใหญ่ซึ่งรวมถึงการสูญเสียการวัดการไล่ระดับสีและเครื่องมือเพิ่มประสิทธิภาพและน้ำหนักบางส่วนซึ่งรับประกันการชะลอตัวที่น่าทึ่ง

สิ่งที่เป็นที่ไม่คาดคิดคือขอบเขตของการชะลอตัว - 10 เท่าในการทดสอบของฉันเองและpredict()ที่ไม่ได้อัปเดตน้ำหนักใด ๆ เมื่อพิจารณาถึงซอร์สโค้ดของ TF2 องค์ประกอบกราฟจะมีการเชื่อมโยงกันอย่างแน่นหนาโดยที่ทรัพยากรไม่จำเป็นต้องถูกจัดสรร "อย่างเป็นธรรม"

ความเป็นไปได้ที่นักพัฒนาอาจมองข้ามpredictสำหรับโมเดลที่ไม่ได้คอมไพล์เนื่องจากโมเดลมักใช้คอมไพล์ แต่ในทางปฏิบัตินี่เป็นความแตกต่างที่ยอมรับไม่ได้ อาจเป็นไปได้ว่าเป็น "ความชั่วร้ายที่จำเป็น" เนื่องจากมีวิธีแก้ไขง่ายๆ (ดูด้านล่าง)

นี่ไม่ใช่คำตอบที่สมบูรณ์และฉันหวังว่าบางคนสามารถให้ได้ที่นี่ - ถ้าไม่ใช่ฉันขอแนะนำให้เปิดปัญหา Github ใน TensorFlow (OP มี; ที่นี่ )


วิธีแก้ปัญหา : ฝึกอบรมโมเดลบันทึกน้ำหนักของมันสร้างโมเดลใหม่โดยไม่ต้องรวบรวมรวบรวมโหลดน้ำหนัก ไม่ได้บันทึกรูปแบบทั้งหมด (เช่นmodel.save()) มันจะโหลดเรียบเรียง - ใช้แทนและmodel.save_weights()model.load_weights()

วิธีแก้ปัญหา 2 : ข้างต้น แต่การใช้งานload_model(path, compile=False); เครดิตข้อเสนอแนะ: D. Möller


UPDATE : ชี้แจงเพิ่มประสิทธิภาพจะไม่ instantiated อย่างเต็มที่กับcompileรวมทั้งของตนweightsและupdatesเทนเซอร์ - นี้จะทำเมื่อสายแรกที่ฟังก์ชั่นที่เหมาะสมจะทำ ( fit, train_on_batchฯลฯ ) model._make_train_function()ผ่านทาง

พฤติกรรมที่สังเกตได้นั้นยิ่งแปลกไปกว่านี้ การสร้างเครื่องมือเพิ่มประสิทธิภาพจะไม่ทำให้เกิดการชะลอตัวใด ๆ เพิ่มเติม (ดูด้านล่าง) - การแนะนำ "ขนาดกราฟ" ไม่ใช่คำอธิบายหลักที่นี่


แก้ไข : ในบางรุ่นที่30x ชะลอตัว TensorFlow คุณทำอะไรไปแล้ว ตัวอย่างด้านล่าง:

from tensorflow.keras.layers import Input, Dense
from tensorflow.keras.models import Model
import numpy as np
from time import time

def timeit(func, arg, iterations):
    t0 = time()
    for _ in range(iterations):
        func(arg)
    print("%.4f sec" % (time() - t0))

ipt   = Input(shape=(4,))
x     = Dense(2, activation='relu')(ipt)
out   = Dense(1, activation='sigmoid')(x)
model = Model(ipt, out)

X = np.random.randn(32,4)

timeit(model.predict, X, 1000)
model.compile('adam', loss='binary_crossentropy')
timeit(model.predict, X, 1000)
model._make_train_function()  # build optimizer
timeit(model.predict, X, 1000)

เอาท์พุท :

0.9891 sec
29.785 sec
29.521 sec

1
นั่นดูน่าสนใจ. มันเป็นเวลาที่ฉันต้องการทดสอบการฝึกอบรมกับกราฟคงที่model.fit()เมื่อเทียบกับการวนซ้ำแบบไดนามิกที่มีความกระตือรือร้นในการดำเนินการเพื่อดูว่าการสูญเสียประสิทธิภาพมีขนาดใหญ่เกินไป ...
Daniel Möller

1
ในอดีตฉันสามารถสังเกตเห็นความแตกต่างความเร็วที่สำคัญระหว่าง Keras และ PyTorch (เป็นวิธี PyTorch เร็วขึ้น)
Daniel Möller

1
ฉันได้เปิดปัญหาที่นี่: github.com/tensorflow/tensorflow/issues/33340
off99555

2
ใช่. มันเป็นตัวเลือกการออกแบบที่ไม่ดีที่คุณใส่รหัสที่เกี่ยวข้องกับการฝึกอบรมในการทำนาย เนื่องจากผู้ใช้จะใช้ฟังก์ชันทำนายนี้เรียงตามลำดับหลายครั้งในการผลิต ควรทำงานเร็วที่สุดเพื่อทำให้เกิดความประหลาดใจน้อยที่สุด เมื่อเปรียบเทียบกับการใช้งานแบบ numpy คุณจะต้องคูณเมทริกซ์เพิ่มอคติเปิดใช้งานและนั่นก็เป็นเลเยอร์หนาแน่น ไม่จำเป็นต้องกังวลเกี่ยวกับฟังก์ชั่นการสูญเสียใด ๆ
off99555

1
คำแนะนำคุณสามารถใช้งานload_model(name, compile=False)ได้ง่ายกว่าการบันทึก / โหลดน้ำหนักและสร้างโมเดลใหม่
Daniel Möller
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.