Проблемы с пониманием настройки модели линейной регрессии в tf.keras - PullRequest
6 голосов
/ 19 июня 2020

Я работаю над Линейной регрессией с помощью Syntheti c Упражнение Data Colab , которое исследует линейную регрессию с игрушечным набором данных. Существует построенная и обученная модель линейной регрессии, и можно поиграть со скоростью обучения, эпохой и размером пакета. У меня проблемы с пониманием того, как именно выполняются итерации и как это связано с «эпохой» и «размером партии». Я в основном не понимаю, как обучается фактическая модель, как обрабатываются данные и выполняются итерации. Чтобы понять это, я хотел проследить это, вычисляя каждый шаг вручную. Поэтому я хотел иметь наклон и коэффициент пересечения для каждого шага. Чтобы я мог видеть, какие данные «компьютер» использует, помещает в модель, какие результаты модели получаются на каждой определенной c итерации и как выполняются итерации. Сначала я попытался получить наклон и точку пересечения для каждого отдельного шага, но безуспешно, потому что только в конце выводятся наклон и точка пересечения. Мой модифицированный код (оригинальный, только что добавленный :)

  print("Slope")
  print(trained_weight)
  print("Intercept")
  print(trained_bias)

код:

import pandas as pd
import tensorflow as tf
from matplotlib import pyplot as plt

#@title Define the functions that build and train a model
def build_model(my_learning_rate):
  """Create and compile a simple linear regression model."""
  # Most simple tf.keras models are sequential. 
  # A sequential model contains one or more layers.
  model = tf.keras.models.Sequential()

  # Describe the topography of the model.
  # The topography of a simple linear regression model
  # is a single node in a single layer. 
  model.add(tf.keras.layers.Dense(units=1, 
                                  input_shape=(1,)))

  # Compile the model topography into code that 
  # TensorFlow can efficiently execute. Configure 
  # training to minimize the model's mean squared error. 
  model.compile(optimizer=tf.keras.optimizers.RMSprop(lr=my_learning_rate),
                loss="mean_squared_error",
                metrics=[tf.keras.metrics.RootMeanSquaredError()])
 
  return model           


def train_model(model, feature, label, epochs, batch_size):
  """Train the model by feeding it data."""

  # Feed the feature values and the label values to the 
  # model. The model will train for the specified number 
  # of epochs, gradually learning how the feature values
  # relate to the label values. 
  history = model.fit(x=feature,
                      y=label,
                      batch_size=batch_size,
                      epochs=epochs)

  # Gather the trained model's weight and bias.
  trained_weight = model.get_weights()[0]
  trained_bias = model.get_weights()[1]
  print("Slope")
  print(trained_weight)
  print("Intercept")
  print(trained_bias)
  # The list of epochs is stored separately from the 
  # rest of history.
  epochs = history.epoch

  # Gather the history (a snapshot) of each epoch.
  hist = pd.DataFrame(history.history)

 # print(hist)
  # Specifically gather the model's root mean 
  #squared error at each epoch. 
  rmse = hist["root_mean_squared_error"]

  return trained_weight, trained_bias, epochs, rmse

print("Defined create_model and train_model")

#@title Define the plotting functions
def plot_the_model(trained_weight, trained_bias, feature, label):
  """Plot the trained model against the training feature and label."""

  # Label the axes.
  plt.xlabel("feature")
  plt.ylabel("label")

  # Plot the feature values vs. label values.
  plt.scatter(feature, label)

  # Create a red line representing the model. The red line starts
  # at coordinates (x0, y0) and ends at coordinates (x1, y1).
  x0 = 0
  y0 = trained_bias
  x1 = my_feature[-1]
  y1 = trained_bias + (trained_weight * x1)
  plt.plot([x0, x1], [y0, y1], c='r')

  # Render the scatter plot and the red line.
  plt.show()

def plot_the_loss_curve(epochs, rmse):
  """Plot the loss curve, which shows loss vs. epoch."""

  plt.figure()
  plt.xlabel("Epoch")
  plt.ylabel("Root Mean Squared Error")

  plt.plot(epochs, rmse, label="Loss")
  plt.legend()
  plt.ylim([rmse.min()*0.97, rmse.max()])
  plt.show()

print("Defined the plot_the_model and plot_the_loss_curve functions.")

my_feature = ([1.0, 2.0,  3.0,  4.0,  5.0,  6.0,  7.0,  8.0,  9.0, 10.0, 11.0, 12.0])
my_label   = ([5.0, 8.8,  9.6, 14.2, 18.8, 19.5, 21.4, 26.8, 28.9, 32.0, 33.8, 38.2])

learning_rate=0.05
epochs=1
my_batch_size=12

my_model = build_model(learning_rate)
trained_weight, trained_bias, epochs, rmse = train_model(my_model, my_feature, 
                                                         my_label, epochs,
                                                         my_batch_size)
plot_the_model(trained_weight, trained_bias, my_feature, my_label)
plot_the_loss_curve(epochs, rmse)

В моем случае c мой результат был:

ex1

Now I tried to replicate this in a simple excel sheet and calculated the rmse manually:

eso

However, I get 21.8 and not 23.1? Also my loss is not 535.48, but 476.82

My first question is therefore: Where is my mistake, how is the rmse calculated?

Second question(s): How can I get the rmse for each specific iteration? Let's consider epoch is 4 and batch size is 4.

экзамен

Это дает 4 эпохи и 3 пакета с каждыми 4 примерами (наблюдениями). Я не понимаю, как модель обучается с помощью этих итераций. Итак, как я могу получить коэффициенты каждой регрессионной модели и rmse? Не только для каждой эпохи (т. Е. 4), но и для каждой итерации. Я думаю, что у каждой эпохи по 3 итерации. Так в итоге получается 12 моделей линейной регрессии? Я хотел бы увидеть эти 12 моделей. Какие исходные значения используются в начальной точке, когда информация не указана, какой тип наклона и пересечения используется? Начало действительно с первой точки. Я этого не уточняю. Затем я хотел бы иметь возможность следить за тем, как наклон и пересечения адаптируются на каждом этапе. Я думаю, это будет из алгоритма градиентного спуска. Но это был бы супер плюс. Для меня важнее сначала понять, как выполняются эти итерации и как они связаны с эпохой и пакетом.

Обновление: я знаю, что начальные значения (для наклона и пересечения) выбираются случайным образом.

Ответы [ 3 ]

2 голосов
/ 25 июня 2020

Я попытался немного поиграть с ним, и я думаю, что он работает так:

  1. веса (обычно случайные, в зависимости от настроек) для каждой функции инициализируются. Также инициируется смещение, которое изначально равно 0,0.
  2. вычисляются потери и метрики для первого пакета и печатаются , а веса и смещение обновляются.
  3. шаг 2. повторяется. для всех партий в эпоху, однако, после потери последней партии и метрики не печатаются, поэтому на экране вы видите потери и метрики перед последним обновлением в эпоху .
  4. новая эпоха начальные и первые метрики и потери, которые вы видите напечатанными, на самом деле те, которые были вычислены на основе последних обновленных весов с предыдущей эпохи ... затем веса обновляются, что означает, что обновление весов является последней операцией в эпохе.

    Если ваша модель обучена с использованием одной эпохи и одного пакета, то то, что вы видите на экране, - это потери, вычисленные на основе начальных весов и смещения. Если вы хотите видеть потери и метрики после окончания каждой эпохи (с наибольшим «фактическим» весом), вы можете перейти к параметру validation_data=(X,y) методу fit. Это говорит алгоритму снова вычислить потери и метрики для этих данных проверки, когда эпоха закончится.

    Что касается начальных весов модели, вы можете попробовать это, когда вы вручную установите некоторые начальные веса для слоя (используя kernel_initializer параметр):

      model.add(tf.keras.layers.Dense(units=1,
                                      input_shape=(1,),
                                      kernel_initializer=tf.constant_initializer(.5)))
    

    Вот обновленная часть функции train_model, которая показывает, что я имел в виду:

      def train_model(model, feature, label, epochs, batch_size):
            """Train the model by feeding it data."""
    
            # Feed the feature values and the label values to the
            # model. The model will train for the specified number
            # of epochs, gradually learning how the feature values
            # relate to the label values.
            init_slope = model.get_weights()[0][0][0]
            init_bias = model.get_weights()[1][0]
            print('init slope is {}'.format(init_slope))
            print('init bias is {}'.format(init_bias))
    
            history = model.fit(x=feature,
                              y=label,
                              batch_size=batch_size,
                              epochs=epochs,
                              validation_data=(feature,label))
    
            # Gather the trained model's weight and bias.
            #print(model.get_weights())
            trained_weight = model.get_weights()[0]
            trained_bias = model.get_weights()[1]
            print("Slope")
            print(trained_weight)
            print("Intercept")
            print(trained_bias)
            # The list of epochs is stored separately from the
            # rest of history.
            prediction_manual = [trained_weight[0][0]*i + trained_bias[0] for i in feature]
    
            manual_loss = np.mean(((np.array(label)-np.array(prediction_manual))**2))
            print('manually computed loss after slope and bias update is {}'.format(manual_loss))
            print('manually computed rmse after slope and bias update is {}'.format(manual_loss**(1/2)))
    
            prediction_manual_init = [init_slope*i + init_bias for i in feature]
            manual_loss_init = np.mean(((np.array(label)-np.array(prediction_manual_init))**2))
            print('manually computed loss with init slope and bias is {}'.format(manual_loss_init))
            print('manually copmuted loss with init slope and bias is {}'.format(manual_loss_init**(1/2)))
    

    вывод:

    """
    init slope is 0.5
    init bias is 0.0
    1/1 [==============================] - 0s 117ms/step - loss: 402.9850 - root_mean_squared_error: 20.0745 - val_loss: 352.3351 - val_root_mean_squared_error: 18.7706
    Slope
    [[0.65811384]]
    Intercept
    [0.15811387]
    manually computed loss after slope and bias update is 352.3350379264957
    manually computed rmse after slope and bias update is 18.77058970641295
    manually computed loss with init slope and bias is 402.98499999999996
    manually copmuted loss with init slope and bias is 20.074486294797182
    """
    

    Обратите внимание, что вычисленные вручную потери и метрики после обновления наклона и смещения соответствуют потерям и метрикам проверки, а вычисленные вручную потери и метрики перед обновлением соответствуют потерям и метрикам начального наклона и смещения.

    Что касается второго вопроса, я думаю, что вы могли бы вручную разбить данные на пакеты, а затем перебирать каждый пакет и помещаться в него. Затем на каждой итерации модель печатает потери и метрики для данных проверки. Примерно так:

      init_slope = model.get_weights()[0][0][0]
      init_bias = model.get_weights()[1][0]
      print('init slope is {}'.format(init_slope))
      print('init bias is {}'.format(init_bias))
      batch_size = 3
    
      for idx in range(0,len(feature),batch_size):
          model.fit(x=feature[idx:idx+batch_size],
                    y=label[idx:idx+batch_size],
                    batch_size=1000,
                    epochs=epochs,
                    validation_data=(feature,label))
          print('slope: {}'.format(model.get_weights()[0][0][0]))
          print('intercept: {}'.format(model.get_weights()[1][0]))
          print('x data used: {}'.format(feature[idx:idx+batch_size]))
          print('y data used: {}'.format(label[idx:idx+batch_size]))
    

    вывод:

    init slope is 0.5
    init bias is 0.0
    1/1 [==============================] - 0s 117ms/step - loss: 48.9000 - root_mean_squared_error: 6.9929 - val_loss: 352.3351 - val_root_mean_squared_error: 18.7706
    slope: 0.6581138372421265
    intercept: 0.15811386704444885
    x data used: [1.0, 2.0, 3.0]
    y data used: [5.0, 8.8, 9.6]
    1/1 [==============================] - 0s 21ms/step - loss: 200.9296 - root_mean_squared_error: 14.1750 - val_loss: 306.3082 - val_root_mean_squared_error: 17.5017
    slope: 0.8132714033126831
    intercept: 0.3018075227737427
    x data used: [4.0, 5.0, 6.0]
    y data used: [14.2, 18.8, 19.5]
    1/1 [==============================] - 0s 22ms/step - loss: 363.2630 - root_mean_squared_error: 19.0595 - val_loss: 266.7119 - val_root_mean_squared_error: 16.3313
    slope: 0.9573485255241394
    intercept: 0.42669767141342163
    x data used: [7.0, 8.0, 9.0]
    y data used: [21.4, 26.8, 28.9]
    1/1 [==============================] - 0s 22ms/step - loss: 565.5593 - root_mean_squared_error: 23.7815 - val_loss: 232.1553 - val_root_mean_squared_error: 15.2366
    slope: 1.0924618244171143
    intercept: 0.5409283638000488
    x data used: [10.0, 11.0, 12.0]
    y data used: [32.0, 33.8, 38.2]
    
1 голос
/ 28 июня 2020

Foundation

Постановка проблемы

Давайте рассмотрим модель линейной регрессии для набора образцов X, где каждый образец представлен одной функцией x. В рамках обучения модели мы ищем строку w.x + b такую, что ((w.x+b) -y )^2 (квадрат потерь) минимален. Для набора точек данных мы берем средний квадрат потерь для каждой выборки и так называемую среднеквадратичную ошибку (MSE). w и b, обозначающие вес и смещение, вместе называются весами.

Установка линии / Обучение модели

  1. У нас есть решение в закрытой форме для решая задачу линейной регрессии и составляет (X^T.X)^-1.X^T.y
  2. . Мы также можем использовать метод градиентного приличия для поиска весов, которые минимизируют квадрат потерь. Фреймворки, такие как tenorflow, pytorch, используют градиентный приличный для поиска весов (называемый обучением).

Градиентный приличный

Градиентный приличный алгоритм для обучения регрессии выглядит как удар

w, b = some initial value
While model has not converged:
    y_hat = w.X + b
    error = MSE(y, y_hat) 
    back propagate (BPP) error and adjust weights

Каждый прогон приведенного выше l oop называется эпохой. Однако из-за ограничений ресурсов вычисление y_hat, error и BPP не выполняется для полного набора данных, вместо этого данные делятся на более мелкие пакеты, и указанные выше операции выполняются для одного пакета за раз. Также мы обычно фиксируем количество эпох и отслеживаем сходимость модели.

w, b = some initial value
for i in range(number_of_epochs)
    for X_batch,y_batch in get_next_batch(X, y)
        y_hat = w.X_batch + b
        error = MSE(y_batch, y_hat) 
    back propagate (BPP) error and adjust weights

Реализация пакетов Keras

Допустим, мы хотели бы добавить root среднеквадратичную ошибку для трассировки производительность модели во время ее обучения. Keras реализует следующий способ:

w, b = some initial value
for i in range(number_of_epochs)
    all_y_hats = []
    all_ys = []
    for X_batch,y_batch in get_next_batch(X, y)
        y_hat = w.X_batch + b
        error = MSE(y_batch, y_hat)

        all_y_hats.extend(y_hat) 
        all_ys.extend(y_batch)

        batch_rms_error = RMSE(all_ys, all_y_hats)

    back propagate (BPP) error and adjust weights

Как вы можете видеть выше, прогнозы накапливаются, и RMSE рассчитывается на основе накопленных прогнозов, а не на основе среднего значения RMSE всех предыдущих пакетов.

Реализация в keras

Теперь, когда наша основа ясна, давайте посмотрим, как мы можем реализовать то же самое в keras. У keras есть обратные вызовы, поэтому мы можем подключиться к обратному вызову on_batch_begin и накапливать all_y_hats и all_ys. На обратном вызове on_batch_end keras дает нам рассчитанное RMSE. Мы вручную вычислим RMSE, используя наши накопленные all_y_hats и all_ys, и проверим, совпадает ли оно с тем, что было рассчитано keras. Мы также сохраним веса, чтобы мы могли позже построить линию, которая изучается.

import numpy as np
from sklearn.metrics import mean_squared_error
import keras
import matplotlib.pyplot as plt

# Some training data
X = np.arange(16)
y = 0.5*X +0.2

batch_size = 8
all_y_hats = []
learned_weights = [] 

class CustomCallback(keras.callbacks.Callback):
  def on_batch_begin(self, batch, logs={}):    
    w = self.model.layers[0].weights[0].numpy()[0][0]
    b = self.model.layers[0].weights[1].numpy()[0]    
    s = batch*batch_size
    all_y_hats.extend(b + w*X[s:s+batch_size])    
    learned_weights.append([w,b])

  def on_batch_end(self, batch, logs={}):    
    calculated_error = np.sqrt(mean_squared_error(all_y_hats, y[:len(all_y_hats)]))
    print (f"\n Calculated: {calculated_error},  Actual: {logs['root_mean_squared_error']}")
    assert np.isclose(calculated_error, logs['root_mean_squared_error'])

  def on_epoch_end(self, batch, logs={}):
    del all_y_hats[:]    


model = keras.models.Sequential()
model.add(keras.layers.Dense(1, input_shape=(1,)))
model.compile(optimizer=keras.optimizers.RMSprop(lr=0.01), loss="mean_squared_error",  metrics=[keras.metrics.RootMeanSquaredError()])
# We should set shuffle=False so that we know how baches are divided
history = model.fit(X,y, epochs=100, callbacks=[CustomCallback()], batch_size=batch_size, shuffle=False) 

Вывод:

Epoch 1/100
 8/16 [==============>...............] - ETA: 0s - loss: 16.5132 - root_mean_squared_error: 4.0636
 Calculated: 4.063645694548688,  Actual: 4.063645839691162

 Calculated: 8.10112834945773,  Actual: 8.101128578186035
16/16 [==============================] - 0s 3ms/step - loss: 65.6283 - root_mean_squared_error: 8.1011
Epoch 2/100
 8/16 [==============>...............] - ETA: 0s - loss: 14.0454 - root_mean_squared_error: 3.7477
 Calculated: 3.7477213352845675,  Actual: 3.7477214336395264
-------------- truncated -----------------------

Ta-da! утверждение assert np.isclose(calculated_error, logs['root_mean_squared_error']) ни разу не сработало, поэтому наши расчеты / понимание верны.

Линия

Наконец, построим линию, которая корректируется алгоритмом BPP на основе среднеквадратичной потери ошибок . Мы можем использовать приведенный ниже код для создания png-изображения линии, изучаемой в каждом пакете, вместе с данными поезда.

for i, (w,b) in enumerate(learned_weights):
  plt.close()
  plt.axis([-1, 18, -1, 10])
  plt.scatter(X, y)
  plt.plot([-1,17], [-1*w+b, 17*w+b], color='green')
  plt.savefig(f'img{i+1}.png')

Ниже приведена анимация в формате gif для вышеуказанных изображений в порядке их изучения.

enter image description here

The hyperplane (line in this case) being learned when y = 0.5*X +5.2

введите описание изображения здесь

0 голосов
/ 26 июня 2020

Модель линейной регрессии

Модель линейной регрессии имеет только один нейрон с функцией линейной активации. Основы обучения модели в том, что мы используем градиентный спуск. Каждый раз, когда все данные проходят через модель и веса обновляются, это называется 1 эпоха . Однако концепция итерации и эпохи здесь ничем не отличается.

Basi c Шаги обучения :

Prepare data
Initialize the model and its parameters (weights and biases)
for each epoch:  #(both iteration and epoch same here)
    Forward Propagation
    Compute Cost
    Back Propagation
    Update Parameters

Gradient Descent имеет три варианта :

  • Пакетный градиентный спуск (BDG)
  • Сточасть c Градиентный спуск (SDG)
  • Мини-пакетный градиентный спуск (MDG)

Пакетный градиентный спуск - это то, о чем мы говорили ранее (передача всех данных). Обычно также называется градиентным спуском.

В Stochasti c Gradient Descent мы передаем 1 случайный пример за раз, и вес обновляется с каждым пройденным примером. Теперь в игру вступает итерация. По завершении обучения модели с 1 примером, 1 итерация завершена. Однако в наборе данных есть и другие примеры, которых модель еще не видела. Полное обучение всех этих примеров называется 1 эпоха . Поскольку за один раз передается 1 пример, SDG очень медленно работает с большим набором данных, поскольку теряет эффект векторизации.

Поэтому мы обычно используем Mini-Batch Gradient Descent . Здесь набор данных разделен на несколько блоков фиксированного размера. Размер каждого блока данных называется размером пакета и может быть где угодно от 1 до размера данных. В каждую эпоху эти пакеты данных используются для обучения модели.

1 итерация обрабатывает 1 пакет данных. 1 эпоха обрабатывает целые пакеты данных. 1 эпоха содержит 1 или более итераций.

Таким образом, если размер данных равен m, данные, загружаемые во время каждой итерации, будут:

  • BDG = m
  • SDG = 1
  • MDG = 1

Basi c Шаги обучения для MGD :

Prepare data
Initialize the model and its parameters (weights and biases)
for each epoch:  #(epoch)
    for each mini_batch: #(iteration)
        Forward Propagation
        Compute Cost
        Back Propagation
        Update Parameters

Это теоретическая концепция, лежащая в основе Gradient Descents, batch, epoch и Iteration.

Теперь перейдем к Keras и вашему коду:

Я запустил вам код Colab, и он работает отлично. В коде, который вы опубликовали, номер эпохи равен 1, что чрезвычайно мало для изучения модели, поскольку данных очень мало, а сама модель очень проста. Таким образом, вам нужно либо увеличить объем данных , либо создать более сложную модель или обучить для большего количества эпох от 400-500, пока я нашел из ноутбука . При правильной настройке скорости обучения номер эпохи может быть уменьшен как таковой

learning_rate=0.14
epochs=70
my_batch_size= 32 

my_model = build_model(learning_rate)
trained_weight, trained_bias, epochs, rmse = train_model(my_model, my_feature, 
                                                        my_label, epochs,
                                                        my_batch_size)
plot_the_model(trained_weight, trained_bias, my_feature, my_label)
plot_the_loss_curve(epochs, rmse)

Если скорость обучения очень мала, модель будет учиться медленно , поэтому для нее требуются более длительные циклы обучения (эпоха ), чтобы делать более точные прогнозы. С увеличением скорости обучения процесс обучения ускоряется, поэтому эпохи могут быть сокращены. Пожалуйста, сравните различные разделы кода в colab, чтобы найти подходящие примеры.

Что касается получения показателей для каждой итерации:

Keras - это высокоуровневый API TensorFlow. Пока я знаю (не учитывая настройку API). Во время обучения в Keras он вычисляет потери, ошибки и точность для обучающего набора в конце каждой итерации, а в конце каждой эпохи возвращает их соответствующее среднее значение. Таким образом, если существует n эпох, то будет n количество каждой из этих метрик независимо от того, сколько итераций происходит между ними.

Что касается наклона и точки пересечения :

Модель линейной регрессии использует функцию линейной активации на выходном слое, который равен y = mx + c. Для значений у нас есть

  • y - относится к выходу
  • x - относится к входам
  • m - относится к наклону (который необходимо отрегулировать )
  • c - относится к точке пересечения (которая также может быть скорректирована)

В нашей модели мы настраиваем эти m и c. Это вес и смещение нашей модели. Итак, наша функция выглядит как y = Wx + b, где b дает точку пересечения , а w дает наклон . Веса и смещения инициализируются случайным образом в начале.

Ссылка Colab для модели линейной регрессии с нуля

Измените значения по мере необходимости. Поскольку модель реализована с нуля, соберите или распечатайте любое значение, которое вы хотите отслеживать во время обучения. Вы также можете использовать свой собственный набор данных, но убедитесь, что он действителен или сгенерирован какой-либо библиотекой для проверки модели (sklearn).

https://colab.research.google.com/drive/1RfuRNMoVv-l6KyM_SegdJOHiXD_0xBHq?usp=sharing

PS Если вы находите что-то запутанным, пожалуйста, прокомментируйте. Буду рад ответить.

...