Hướng dẫn viết lệnh gọi lại TensorFlow / Keras tùy chỉnh

Giới thiệu

Giả sử bạn muốn mô hình Keras của mình có một số hành vi cụ thể trong quá trình đào tạo, đánh giá hoặc dự đoán. Chẳng hạn, bạn có thể muốn lưu mô hình của mình ở mọi giai đoạn đào tạo. Một cách để làm điều này là sử dụng Callbacks.

Nói chung, Gọi lại là các hàm được gọi khi một số sự kiện xảy ra và được chuyển dưới dạng đối số cho các hàm khác. Trong trường hợp của Keras, chúng là một công cụ để tùy chỉnh hành vi của mô hình của bạn – có thể là trong quá trình đào tạo, đánh giá hoặc suy luận. Một số ứng dụng đang ghi nhật ký, tính bền vững của mô hình, dừng sớm hoặc thay đổi tốc độ học tập. Điều này được thực hiện bằng cách chuyển một danh sách các Gọi lại làm đối số cho keras.Model.fit(),keras.Model.evaluate() or keras.Model.predict().

Một số trường hợp sử dụng phổ biến cho lệnh gọi lại là sửa đổi tốc độ học, ghi nhật ký, giám sát và dừng đào tạo sớm. Keras có một số cuộc gọi lại tích hợp, chi tiết
trong tài liệu
.

Tuy nhiên, một số ứng dụng cụ thể hơn có thể yêu cầu gọi lại tùy chỉnh. Ví dụ, triển khai khởi động Tốc độ học tập với Phân rã Cosine sau một thời gian nắm giữ hiện không được tích hợp sẵn, nhưng được sử dụng rộng rãi và được chấp nhận làm công cụ lập lịch trình.

Lớp gọi lại và các phương thức của nó

Keras có một lớp gọi lại cụ thể, keras.callbacks.Callback, với các phương thức có thể được gọi trong quá trình đào tạo, thử nghiệm và suy luận ở cấp độ toàn cầu, lô hoặc kỷ nguyên. Để tạo cuộc gọi lại tùy chỉnh, chúng ta cần tạo một lớp con và ghi đè các phương thức này.

Sản phẩm keras.callbacks.Callback lớp có ba loại phương thức:

  • các phương thức toàn cầu: được gọi ở đầu hoặc ở cuối fit(), evaluate()predict().
  • các phương thức cấp lô: được gọi khi bắt đầu hoặc khi kết thúc quá trình xử lý một lô.
  • các phương pháp cấp độ kỷ nguyên: được gọi khi bắt đầu hoặc khi kết thúc đợt huấn luyện.

Lưu ý: Mỗi phương pháp có quyền truy cập vào một dict được gọi là logs. Các khóa và giá trị của logs là theo ngữ cảnh - chúng phụ thuộc vào sự kiện gọi phương thức. Hơn nữa, chúng ta có quyền truy cập vào mô hình bên trong mỗi phương thức thông qua self.model thuộc tính.

Chúng ta hãy xem ba ví dụ về cuộc gọi lại tùy chỉnh – một để đào tạo, một để đánh giá và một để dự đoán. Mỗi cái sẽ in ở mỗi giai đoạn mô hình của chúng tôi đang làm gì và chúng tôi có quyền truy cập vào nhật ký nào. Điều này hữu ích để hiểu những gì có thể thực hiện với lệnh gọi lại tùy chỉnh ở mỗi giai đoạn.

Hãy bắt đầu bằng cách xác định một mô hình đồ chơi:

import tensorflow as tf
from tensorflow import keras
import numpy as np

model = keras.Sequential()
model.add(keras.layers.Dense(10, input_dim = 1, activation='relu'))
model.add(keras.layers.Dense(10, activation='relu'))
model.add(keras.layers.Dense(1))
model.compile(
    optimizer=keras.optimizers.RMSprop(learning_rate=0.1),
    loss = "mean_squared_error",
    metrics = ["mean_absolute_error"]
)

x = np.random.uniform(low = 0, high = 10, size = 1000)
y = x**2
x_train, x_test = (x[:900],x[900:])
y_train, y_test = (y[:900],y[900:])

Gọi lại đào tạo tùy chỉnh

Cuộc gọi lại đầu tiên của chúng tôi sẽ được gọi trong quá trình đào tạo. Hãy phân lớp các Callback lớp học:

class TrainingCallback(keras.callbacks.Callback):
    def __init__(self):
        self.tabulation = {"train":"", 'batch': " "*8, 'epoch':" "*4}
    def on_train_begin(self, logs=None):
        tab = self.tabulation['train']
        print(f"{tab}Training!")
        print(f"{tab}available logs: {logs}")

    def on_train_batch_begin(self, batch, logs=None):
        tab = self.tabulation['batch']
        print(f"{tab}Batch {batch}")
        print(f"{tab}available logs: {logs}")

    def on_train_batch_end(self, batch, logs=None):
        tab = self.tabulation['batch']
        print(f"{tab}End of Batch {batch}")
        print(f"{tab}available logs: {logs}")

    def on_epoch_begin(self, epoch, logs=None):
        tab = self.tabulation['epoch']
        print(f"{tab}Epoch {epoch} of training")
        print(f"{tab}available logs: {logs}")

    def on_epoch_end(self, epoch, logs=None):
        tab = self.tabulation['epoch']
        print(f"{tab}End of Epoch {epoch} of training")
        print(f"{tab}available logs: {logs}")

    def on_train_end(self, logs=None):
        tab = self.tabulation['train']
        print(f"{tab}Finishing training!")
        print(f"{tab}available logs: {logs}")

Nếu bất kỳ phương pháp nào trong số này không bị ghi đè - hành vi mặc định sẽ tiếp tục như trước đây. Trong ví dụ của chúng tôi - chúng tôi chỉ cần in ra các nhật ký có sẵn và cấp độ áp dụng gọi lại, với thụt đầu dòng thích hợp.

Chúng ta hãy xem các kết quả đầu ra:

model.fit(
    x_train,
    y_train,
    batch_size=500,
    epochs=2,
    verbose=0,
    callbacks=[TrainingCallback()],
)
Training!
available logs: {}
    Epoch 0 of training
    available logs: {}
        Batch 0
        available logs: {}
        End of Batch 0
        available logs: {'loss': 2172.373291015625, 'mean_absolute_error': 34.79669952392578}
        Batch 1
        available logs: {}
        End of Batch 1
        available logs: {'loss': 2030.1309814453125, 'mean_absolute_error': 33.30256271362305}
    End of Epoch 0 of training
    available logs: {'loss': 2030.1309814453125, 'mean_absolute_error': 33.30256271362305}
    Epoch 1 of training
    available logs: {}
        Batch 0
        available logs: {}
        End of Batch 0
        available logs: {'loss': 1746.2772216796875, 'mean_absolute_error': 30.268001556396484}
        Batch 1
        available logs: {}
        End of Batch 1
        available logs: {'loss': 1467.36376953125, 'mean_absolute_error': 27.10252571105957}
    End of Epoch 1 of training
    available logs: {'loss': 1467.36376953125, 'mean_absolute_error': 27.10252571105957}
Finishing training!
available logs: {'loss': 1467.36376953125, 'mean_absolute_error': 27.10252571105957}


Lưu ý rằng chúng ta có thể theo dõi ở từng bước xem mô hình đang làm gì và chúng ta có quyền truy cập vào số liệu nào. Vào cuối mỗi đợt và kỷ nguyên, chúng tôi có quyền truy cập vào hàm mất mát trong mẫu và các số liệu của mô hình của chúng tôi.

Gọi lại đánh giá tùy chỉnh

Bây giờ, hãy gọi Model.evaluate() phương pháp. Chúng ta có thể thấy rằng vào cuối đợt, chúng ta có quyền truy cập vào hàm tổn thất và các chỉ số tại thời điểm đó, đồng thời khi kết thúc đánh giá, chúng ta có quyền truy cập vào tổng tổn thất và các chỉ số:

class TestingCallback(keras.callbacks.Callback):
    def __init__(self):
          self.tabulation = {"test":"", 'batch': " "*8}
      
    def on_test_begin(self, logs=None):
        tab = self.tabulation['test']
        print(f'{tab}Evaluating!')
        print(f'{tab}available logs: {logs}')

    def on_test_end(self, logs=None):
        tab = self.tabulation['test']
        print(f'{tab}Finishing evaluation!')
        print(f'{tab}available logs: {logs}')

    def on_test_batch_begin(self, batch, logs=None):
        tab = self.tabulation['batch']
        print(f"{tab}Batch {batch}")
        print(f"{tab}available logs: {logs}")

    def on_test_batch_end(self, batch, logs=None):
        tab = self.tabulation['batch']
        print(f"{tab}End of batch {batch}")
        print(f"{tab}available logs: {logs}")
res = model.evaluate(
    x_test, y_test, batch_size=100, verbose=0, callbacks=[TestingCallback()]
)
Evaluating!
available logs: {}
        Batch 0
        available logs: {}
        End of batch 0
        available logs: {'loss': 382.2723083496094, 'mean_absolute_error': 14.069927215576172}
Finishing evaluation!
available logs: {'loss': 382.2723083496094, 'mean_absolute_error': 14.069927215576172}

Gọi lại dự đoán tùy chỉnh

Cuối cùng, hãy gọi Model.predict() phương pháp. Lưu ý rằng vào cuối mỗi đợt, chúng tôi có quyền truy cập vào các kết quả đầu ra được dự đoán của mô hình của chúng tôi:

class PredictionCallback(keras.callbacks.Callback):
    def __init__(self):
        self.tabulation = {"prediction":"", 'batch': " "*8}

    def on_predict_begin(self, logs=None):
        tab = self.tabulation['prediction']
        print(f"{tab}Predicting!")
        print(f"{tab}available logs: {logs}")

    def on_predict_end(self, logs=None):
        tab = self.tabulation['prediction']
        print(f"{tab}End of Prediction!")
        print(f"{tab}available logs: {logs}")

    def on_predict_batch_begin(self, batch, logs=None):
        tab = self.tabulation['batch']
        print(f"{tab}batch {batch}")
        print(f"{tab}available logs: {logs}")

    def on_predict_batch_end(self, batch, logs=None):
        tab = self.tabulation['batch']
        print(f"{tab}End of batch {batch}")
        print(f"{tab}available logs:n {logs}")
res = model.predict(x_test[:10],
                    verbose = 0, 
                    callbacks=[PredictionCallback()])

Xem hướng dẫn thực hành, thực tế của chúng tôi để học Git, với các phương pháp hay nhất, các tiêu chuẩn được ngành công nghiệp chấp nhận và bảng lừa đảo đi kèm. Dừng lệnh Googling Git và thực sự học nó!

Predicting!
available logs: {}
        batch 0
        available logs: {}
        End of batch 0
        available logs:
 {'outputs': array([[ 7.743822],
       [27.748264],
       [33.082104],
       [26.530678],
       [27.939169],
       [18.414223],
       [42.610645],
       [36.69335 ],
       [13.096557],
       [37.120853]], dtype=float32)}
End of Prediction!
available logs: {}

Với những thứ này – bạn có thể tùy chỉnh hành vi, thiết lập giám sát hoặc thay đổi các quy trình đào tạo, đánh giá hoặc suy luận. Một giải pháp thay thế cho sublcassing là sử dụng LambdaCallback.

Sử dụng LambaCallback

Một trong những cuộc gọi lại tích hợp trong Keras là LambdaCallback lớp. Cuộc gọi lại này chấp nhận một chức năng xác định cách nó hoạt động và những gì nó làm! Theo một nghĩa nào đó, nó cho phép bạn sử dụng bất kỳ chức năng tùy ý nào làm hàm gọi lại, do đó cho phép bạn tạo các hàm gọi lại tùy chỉnh.

Lớp có các tham số tùy chọn:
on_epoch_begin

  • on_epoch_end
  • on_batch_begin
  • on_batch_end
  • on_train_begin
  • on_train_end

Mỗi tham số chấp nhận một chức năng được gọi trong sự kiện mô hình tương ứng. Ví dụ: hãy gọi lại để gửi email khi mô hình kết thúc đào tạo:

import smtplib
from email.message import EmailMessage

def send_email(logs): 
    msg = EmailMessage()
    content = f"""The model has finished training."""
    for key, value in logs.items():
      content = content + f"n{key}:{value:.2f}"
    msg.set_content(content)
    msg['Subject'] = f'Training report'
    msg['From'] = '[email protected]'
    msg['To'] = 'receiver-email'

    s = smtplib.SMTP('smtp.gmail.com', 587)
    s.starttls()
    s.login("[email protected]", "your-gmail-app-password")
    s.send_message(msg)
    s.quit()

lambda_send_email = lambda logs : send_email(logs)

email_callback = keras.callbacks.LambdaCallback(on_train_end = lambda_send_email)

model.fit(
    x_train,
    y_train,
    batch_size=100,
    epochs=1,
    verbose=0,
    callbacks=[email_callback],
)

Để thực hiện cuộc gọi lại tùy chỉnh của chúng tôi bằng cách sử dụng LambdaCallback, chúng ta chỉ cần triển khai chức năng mà chúng ta muốn được gọi, bọc nó dưới dạng lambda chức năng và chuyển nó đến
LambdaCallback lớp như một tham số.

Gọi lại để trực quan hóa đào tạo mô hình

Trong phần này, chúng tôi sẽ đưa ra một ví dụ về gọi lại tùy chỉnh giúp tạo hoạt ảnh về hiệu suất của mô hình được cải thiện trong quá trình đào tạo. Để làm điều này, chúng tôi lưu trữ các giá trị của nhật ký ở cuối mỗi đợt. Sau đó, khi kết thúc vòng lặp đào tạo, chúng tôi tạo hoạt ảnh bằng cách sử dụng matplotlib.

Để tăng cường khả năng trực quan hóa, tổn thất và số liệu sẽ được vẽ theo thang logarit:

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
from IPython import display

class TrainingAnimationCallback(keras.callbacks.Callback):
    def __init__(self, duration = 40, fps = 1000/25):
        self.duration = duration
        self.fps = fps
        self.logs_history = []

    def set_plot(self):   
        self.figure = plt.figure()
        
        plt.xticks(
            range(0,self.params['steps']*self.params['epochs'], self.params['steps']),
            range(0,self.params['epochs']))
        plt.xlabel('Epoch')
        plt.ylabel('Loss & Metrics ($Log_{10}$ scale)')

        self.plot = {}
        for metric in self.model.metrics_names:
          self.plot[metric], = plt.plot([],[], label = metric)
          
        max_y = [max(log.values()) for log in self.logs_history]
        
        self.title = plt.title(f'batches:0')
        plt.xlim(0,len(self.logs_history)) 
        plt.ylim(0,max(max_y))

           
        plt.legend(loc='upper right')
  
    def animation_function(self,frame):
        batch = frame % self.params['steps']
        self.title.set_text(f'batch:{batch}')
        x = list(range(frame))
        
        for metric in self.model.metrics_names:
            y = [log[metric] for log in self.logs_history[:frame]]
            self.plot[metric].set_data(x,y)
        
    def on_train_batch_end(self, batch, logs=None):
        logarithm_transform = lambda item: (item[0], np.log(item[1]))
        logs = dict(map(logarithm_transform,logs.items()))
        self.logs_history.append(logs)
       
    def on_train_end(self, logs=None):
        self.set_plot()
        num_frames = int(self.duration*self.fps)
        num_batches = self.params['steps']*self.params['epochs']
        selected_batches = range(0, num_batches , num_batches//num_frames )
        interval = 1000*(1/self.fps)
        anim_created = FuncAnimation(self.figure, 
                                     self.animation_function,
                                     frames=selected_batches,
                                     interval=interval)
        video = anim_created.to_html5_video()
        
        html = display.HTML(video)
        display.display(html)
        plt.close()

Chúng tôi sẽ sử dụng cùng một mô hình như trước đây, nhưng với nhiều mẫu đào tạo hơn:

import tensorflow as tf
from tensorflow import keras
import numpy as np

model = keras.Sequential()
model.add(keras.layers.Dense(10, input_dim = 1, activation='relu'))
model.add(keras.layers.Dense(10, activation='relu'))
model.add(keras.layers.Dense(1))
model.compile(
    optimizer=keras.optimizers.RMSprop(learning_rate=0.1),
    loss = "mean_squared_error",
    metrics = ["mean_absolute_error"]
)

def create_sample(sample_size, train_test_proportion = 0.9):
    x = np.random.uniform(low = 0, high = 10, size = sample_size)
    y = x**2
    train_test_split = int(sample_size*train_test_proportion)
    x_train, x_test = (x[:train_test_split],x[train_test_split:])
    y_train, y_test = (y[:train_test_split],y[train_test_split:])
    return (x_train,x_test,y_train,y_test)

x_train,x_test,y_train,y_test = create_sample(35200)


model.fit(
    x_train,
    y_train,
    batch_size=32,
    epochs=2,
    verbose=0,
    callbacks=[TrainingAnimationCallback()],
)

Đầu ra của chúng tôi là hình ảnh động của các chỉ số và hàm mất mát khi chúng thay đổi trong quá trình đào tạo:

Trình duyệt của bạn không hỗ trợ video HTML.

Kết luận

Trong hướng dẫn này, chúng tôi đã xem xét việc triển khai các cuộc gọi lại tùy chỉnh trong Keras.
Có hai tùy chọn để triển khai các cuộc gọi lại tùy chỉnh – thông qua phân lớp con keras.callbacks.Callback lớp, hoặc bằng cách sử dụng keras.callbacks.LambdaCallback lớp học.

Chúng tôi đã thấy một ví dụ thực tế sử dụng LambdaCallbackđể gửi email ở cuối vòng đào tạo và một ví dụ về phân lớp con Callback lớp tạo hoạt ảnh của vòng lặp đào tạo.

Althoug Keras có nhiều lệnh gọi lại tích hợp sẵn, biết cách triển khai lệnh gọi lại tùy chỉnh có thể hữu ích cho các ứng dụng cụ thể hơn.

Dấu thời gian:

Thêm từ xếp chồng lên nhau