Чому модель кераса прогнозує повільніше після компіляції?


23

прогнозування швидкості кери

Теоретично прогнозування має бути постійним, оскільки ваги мають фіксований розмір. Як повернути свою швидкість після компіляції (без необхідності видаляти оптимізатор)?

Дивіться пов'язаний експеримент: https://nbviewer.jupyter.org/github/off99555/TensorFlowExperiment/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 - 1/15/2020 : поточна краща практика для невеликих обсягів партій повинні годувати входи моделі безпосередньо - тобто preds = model(x), і якщо шари поводяться по- різному на потяги / виводу, model(x, training=False). За останнім зобов'язанням, це зараз задокументовано .

Я цього не оцінював, але в рамках дискусії Git також варто спробувати, predict_on_batch()особливо з покращенням TF 2.1.


ULTIMATE Винуватець : self._experimental_run_tf_function = True. Це експериментально . Але насправді це не погано.

Для будь-якого читання розробників TensorFlow: очистіть свій код . Це безлад. І це порушує важливі практики кодування, такі як одна функція виконує одне ; _process_inputsробить набагато більше, ніж "процеси вводу", те ж саме для _standardize_user_data. «Я не заплатив досить» , - але ви робите оплату, в додатковий час , витраченого розуміння свого власного матеріалу, а також користувачі , що заповнюють сторінку Проблеми , пов'язані з помилками простіше вирішити з більш ясним кодом.


ПІДСУМОК : це лише трохи повільніше compile().

compile()встановлює внутрішній прапор, який присвоює іншу функцію передбачення predict. Ця функція будує новий графік при кожному виклику, уповільнюючи його відносно некомпільованого. Однак різниця виражена лише тоді, коли час поїздів значно коротший, ніж час обробки даних . Якщо збільшити розмір моделі принаймні середнього розміру, вони стають рівними. Дивіться код внизу.

Це незначне збільшення часу на обробку даних більш ніж компенсується розширеною здатністю графіка. Оскільки ефективніше зберігати лише один модельний графік, попередня компіляція відкидається. Тим не менш : якщо ваша модель невелика по відношенню до даних, вам краще не compile()робити висновок про модель. Дивіться мою іншу відповідь для вирішення.


ЩО Я ПОВИНЕН ЗРОБИТИ?

Порівняйте продуктивність моделі, складену проти некомпільованої, як у мене в коді внизу.

  • Компілюється швидше : запустіть predictза складеною моделлю.
  • Компілюється повільніше : працювати predictза некомпільованою моделлю.

Так, можливі обидва , і це залежатиме від (1) розміру даних; (2) розмір моделі; (3) обладнання. Код внизу насправді показує, що складена модель є швидшою, але 10 ітерацій - це невеликий зразок. Дивіться "обхідні шляхи" в іншій моїй відповіді на "як".


ДЕТАЛІ :

Це зайняло час, щоб налагодити, але було весело. Нижче я опису головних винуватців, яких я виявив, наводжу деяку відповідну документацію та показую результати профілерів, що призвели до остаточного вузького місця.

( FLAG == self.experimental_run_tf_functionдля стислості)

  1. Modelза замовчуванням екземпляри до FLAG=False. compile()встановлює його True.
  2. predict() передбачає придбання функції прогнозування, func = self._select_training_loop(x)
  3. Без будь-яких спеціальних кваргів, переданих до predictта compile, всі інші прапори такі:
    • (А) FLAG==True ->func = training_v2.Loop()
    • (В) FLAG==False ->func = training_arrays.ArrayLikeTrainingLoop()
  4. З вихідного коду рядка документації , (А) в значній мірі залежить граф-, використовує більше стратегії розподілу і ОПС схильні до створення і руйнувати елементи графа, які «можуть» (DO) впливає на продуктивність.

Справжній винуватець : _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. Відповідний вихідний код , і тут


ФУНКЦІЯ ВИКОНАННЯ МОДЕЛІ (наприклад, прогнозування)

(A) : функція розподілу , і тут

(B) : функція розподілу (різні) , і тут


ПРОФІЛЕР : результати для коду в іншій моїй відповіді, "крихітна модель", і в цій відповіді "середня модель":

Крихітна модель : 1000 ітерацій,compile()

Крихітна модель : 1000 ітерацій, ні compile()

Середня модель : 10 ітерацій


ДОКУМЕНТАЦІЯ (опосередковано) на вплив compile(): джерела

На відміну від інших операцій TensorFlow, ми не перетворюємо числові входи python в тензори. Більше того, для кожного окремого числового значення пітона формується новий графік , наприклад виклик g(2)і g(3)генерує два нові графіки

function створює окремий графік для кожного унікального набору вхідних форм і типів даних . Наприклад, наступний фрагмент коду призведе до відстеження трьох різних графіків, оскільки кожен вхід має різну форму

Одному об'єкту tf.function може знадобитися зіставити декілька обчислювальних графіків під кришкою. Це повинно бути видно лише як продуктивність (графіки відстеження мають ненульову обчислювальну та пам’ятну вартість ), але не повинна впливати на правильність програми


КОНТРОЛЬНИЙ ПРИКЛАД :

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

ОНОВЛЕННЯ : див. Фактичну відповідь, розміщену як окрему відповідь; ця публікація містить додаткову інформацію


.compile() встановлює більшість графіків TF / Keras, включаючи втрати, метрики, градієнти та частково оптимізатор та його ваги - що гарантує помітне уповільнення.

Що є несподіваним є ступінь уповільнення - 10 раз на моєму власному досвіді, і predict(), що ні оновлює ніяких ваг. Дивлячись на вихідний код TF2, елементи графіків виглядають щільно переплетеними, при цьому ресурси не обов'язково розподіляються "справедливо".

Можливий огляд розробниками на predictпродуктивність некомпільованої моделі, оскільки зазвичай використовуються моделі, складені - але на практиці це неприйнятна різниця. Можливо також, що це "необхідне зло", оскільки існує просте рішення (див. Нижче).

Це не повна відповідь, і я сподіваюся, що хтось може надати її тут - якщо ні, я б запропонував відкрити випуск Github на TensorFlow. (ОП має; тут )


Обхід : тренуйте модель, зберігайте її ваги , відновлюйте модель, не складаючи, завантажуйте ваги. Ви НЕ зберегти всю модель (наприклад model.save()), так як вона буде завантажуйте скомпільована - замість того, щоб використовувати model.save_weights()і model.load_weights().

Обхід 2 : вище, але використовувати load_model(path, compile=False); кредит пропозиції: Д. Мьоллер


ОНОВЛЕННЯ : уточнити, оптимізатор НЕ в повній мірі екземпляри з compile, в тому числі її weightsта updatesтензорів - це зроблено , коли перший виклик функції фитинга проводиться ( fit, train_on_batchі т.д.), з допомогою model._make_train_function().

Таким чином, поведінка, що спостерігається, ще більш дивна. Що ще гірше, побудова оптимізатора не спричиняє подальших уповільнень (див. Нижче) - припущення "розмір графіка" не є головним поясненням тут.


EDIT : у деяких моделей 30-кратне уповільнення . 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()
Даніель

1
У минулому я міг помітити значну різницю швидкості між Keras і PyTorch (будучи PyTorch швидше).
Даніель

1
Я відкрив тут проблему: github.com/tensorflow/tensorflow/isissue/33340
off99555

2
Так. Це неправильний вибір дизайну, що ви ставите код, пов’язаний з навчанням, всередині прогнозування. Оскільки користувачі використовуватимуть цю функцію передбачення послідовно кілька разів у виробництві. Він повинен працювати найшвидше, щоб викликати найменший сюрприз. Порівнюючи з numpy реалізацією, вам потрібно лише помножити матрицю, додати зміщення, активувати, і це все для щільного шару. Не потрібно стосуватися жодної функції втрат.
off99555

1
Підказка, ви можете використовувати load_model(name, compile=False), це простіше, ніж збереження / завантаження ваг і відтворення моделі.
Даніель
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.