컴파일 후 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 있지만 성능 차이가 그렇게 극적이어서는 안됩니다. fitcompile
OverLordGoldDragon

답변:


22

업데이트 1/15/2020 : 작은 배치 크기에 대한 현재 모범 사례는 입력을 모델에 직접 공급하는 것입니다. 즉 preds = model(x), 트레인 / 추론에서 레이어가 다르게 동작하는 경우 model(x, training=False)입니다. 최근 커밋 당 문서화되었습니다 .

나는 이것들을 벤치마킹하지는 않았지만 Git 토론 에 따라 predict_on_batch()특히 TF 2.1의 개선으로 시도해 볼 가치가 있습니다.


궁극의 범인 : 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. 특별한 kwargs가 predictand로 전달되지 않으면 compile다른 모든 플래그는 다음과 같습니다.
    • (A) FLAG==True ->func = training_v2.Loop()
    • (B) FLAG==False ->func = training_arrays.ArrayLikeTrainingLoop()
  4. 에서 소스 코드를 참조 문 , (A)는 , 크게 의존 그래프 더 분배 전략을 사용하며,이 생성 및 OPS (DO) "할 수있다"그래프 소자 성능에 영향을 파괴되기 쉽다.

진정한 범인 : 런타임의 81 %를_process_inputs() 차지 합니다 . 주요 구성 요소? _create_graph_function(), 런타임의 72 % . 이 방법은도하지 않는 존재 에 대한 (B) . 그러나 중간 크기 모델을 사용하면 런타임의 1 % 미만이_process_inputs 구성 됩니다 . 하단의 코드와 프로파일 링 결과가 이어집니다.


데이터 프로세서 :

(A) : <class 'tensorflow.python.keras.engine.data_adapter.TensorLikeDataAdapter'>에서 사용됩니다 _process_inputs(). 관련 소스 코드

(B) : numpy.ndarray에 의해 반환됩니다 convert_eager_tensors_to_numpy. 관련 소스 코드여기


모델 실행 기능 (예 : 예측)

(A) : 분포 함수여기

(B) : 분포 함수 (상이)여기


PROFILER : 다른 답변 "tiny model"및이 답변 "medium model"의 코드 결과 :

작은 모델 : 1000 회 반복compile()

작은 모델 : 1000 회 반복, 아니오 compile()

중간 모델 : 10 회 반복


영향에 대한 문서 (간접적) compile(): source

다른 TensorFlow 작업과 달리 Python 숫자 입력을 텐서로 변환하지 않습니다. 또한 각각의 고유 한 파이썬 숫자 값 에 대해 새 그래프가 생성됩니다 ( 예 : 호출 g(2)g(3)두 개의 새 그래프 생성).

function 모든 고유 한 입력 모양 및 데이터 유형 집합에 대해 별도의 그래프를 인스턴스화합니다 . 예를 들어, 다음 코드 스 니펫은 각 입력의 모양이 다르기 때문에 3 개의 고유 한 그래프가 추적됩니다.

단일 tf.function 객체는 후드 아래의 여러 계산 그래프에 매핑해야 할 수도 있습니다. 이것은 성능으로 만 볼 수 있어야하며 (추적 그래프에는 계산 및 메모리 비용이 0 이 아님) 프로그램의 정확성에 영향을 미치지 않아야합니다.


카운터 예 :

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

나는 전체를 읽었지만 코드를 디버깅 한 사람이 아니기 때문에 이해하기가 어렵습니다. 따라서 디버깅 단계에서 찾은 중간 변수를 포함하지 않는 결론을 내릴 필요가 있습니다. 예 : "모델이 작 으면 컴파일을 사용하지 마십시오. 모델이 중간 크기 인 경우 컴파일을 사용할 수 있습니다
.`

1
@ off99555 충분히 공정한; 업데이트되었습니다. 새 섹션은 상당히 상식적이지만 즉시 실현되지는 않습니다.
OverLordGoldDragon

1
@ off99555 테스트 한 것은 아니지만 매우 큰 모델 (ResNet 등)은 특히 빠른 컴파일 속도로 실행될 수 있습니다. 많은 장치에 분산 된 경우 - (A) 가 그래프와 분포가 더 무거 우므로. 가장 확실한 테스트는 답과 같은 테스트입니다. TF 라이트에 익숙하지 않지만 별도의 질문입니다
OverLordGoldDragon

15

업데이트 : 실제 답변은 별도의 답변으로 게시되어 있습니다. 이 게시물에는 보충 정보가 포함되어 있습니다


.compile() 손실, 메트릭스, 그라디언트, 부분적으로 옵티 마이저 및 가중치를 포함하여 대부분의 TF / Keras 그래프를 설정하여 현저한 속도 저하를 보장합니다.

무엇 이며 예기치 않은 것은 경기 침체의 정도입니다 - 내 자신의 실험에 10 배, 그리고에 대한 predict()어떤 가중치를 갱신하지 않는다. TF2의 소스 코드를 살펴보면 그래프 요소가 밀접하게 얽혀있는 것처럼 보이며 리소스가 반드시 "공정하게"할당되지는 않습니다.

predict모델이 일반적으로 컴파일되어 사용되기 때문에 컴파일되지 않은 모델의 성능에 대한 개발자의 간과 가능성 은 있지만 실제로 는 용납 할 수없는 차이입니다. 간단한 해결 방법이 있기 때문에 "필요한 악"일 수도 있습니다 (아래 참조).

이것은 완전한 대답이 아니며 누군가가 여기에 제공 할 수 있기를 바랍니다. 그렇지 않은 경우 TensorFlow에서 Github 문제를 여는 것이 좋습니다. (OP는; 여기 )


해결 방법 : 모델을 학습하고 가중치를 저장하고 컴파일하지 않고 모델을 다시 빌드하고 가중치를로드하십시오. 마십시오 하지 전체 모델 (예를 저장 model.save()하는 대신 사용 -이 컴파일되면로드 겠지만,) model.save_weights()model.load_weights().

해결 방법 2 : 위와 같지만 load_model(path, compile=False); 제안 신용 : D. Möller


UPDATE : 명확하게는, 최적화가되어 있지 완전히 인스턴스화 compile의 포함 weightsupdates텐서 -이 작업이 완료되는 피팅 함수에 대한 첫 번째 호출 (이루어질 때 fit, train_on_batch등)를 통해 model._make_train_function().

따라서 관찰 된 동작은 훨씬 더 이상합니다. 더 나쁜 것은 옵티 마이저를 구축해 도 더 이상 속도 저하 가 발생하지 않습니다 (아래 참조). 여기서 "그래프 크기"는 주요 설명이 아닙니다.


편집 : 일부 모델에서는 30 배 속도가 느려 집니다. 텐서 플로우, 무엇을 했습니까? 아래 예 :

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.