Keras - LSTM Автоэнкодер Weird Performance - PullRequest
0 голосов
/ 04 февраля 2019

Мой набор данных - это одномерный временной ряд со многими пропущенными значениями.Есть 120 строк, в каждой строке 211 временных отметок (в днях).Я тренирую автоэнкодер LSTM для вменения значимых значений.Исходный набор данных находится в диапазоне 0-10.Я все раз с 0,1, чтобы нормализовать данные в диапазоне [0,1].Я использовал Учебное пособие по автоэнкодеру и Учебное пособие по автоэнкодеру , чтобы построить сеть.Поскольку нет реальной метки для пропущенного значения, я сначала использую линейную интерполяцию для вменения значений, притворяюсь, что это правда, и устанавливаю пропорцию пропущенных значений, чтобы проверить производительность авто-кодировщика.Я добавил все пропущенные значения с константой (в данном случае среднее).Я оцениваю только среднюю абсолютную ошибку по наблюдаемым значениям (не учитываю пропущенное значение) для потери обучения.Для ошибки тестирования я оцениваю среднюю абсолютную ошибку только для пропущенных значений.

Гиперпараметры:

  1. recurrent_weight = 0.5 # для каждой эпохи, насколько я хочу сбрасывать со счетов предыдущий прогноз (новое значение = 0,5 * предыдущее прогнозирование + 0,5 * текущее прогнозирование
  2. один скрытый слой с latent_dim = 32
  3. оптимизатор Adam с lr = 0,01
  4. batch_size = 30
  5. эпоха = 20
  6. фракция = 0,5 (доля значений, установленных как пропущенные)

Я пробовал различные значения для этих параметров, результат в значительной степени одинаков.что-нибудь еще, чего я здесь упускаю, что приводило к тому, что тренировочные и проверочные потери не менялись? Почему тренировочные потери настолько малы?

настраиваемая функция обучения и проверки потерь:

def make_reconstruction_loss(n_features):
'''customized training loss function: only MSE for observed values (values not marked 1 in missing_mask)'''
    def reconstruction_loss(input_and_mask, y_pred):
        X_values = input_and_mask[:, :n_features,:] 
        missing_mask = input_and_mask[:, n_features:,:] 
        observed_mask = 1 - missing_mask
        X_values_observed = X_values * observed_mask
        y_pred = y_pred[:, :n_features,:]
        pred_observed = y_pred * observed_mask
        return mean_absolute_error(y_true=X_values_observed, y_pred=pred_observed)
    return reconstruction_loss

def masked_mae(X_true, X_pred, mask): #opposite of reconstruction_loss, MAE of only missing values
    masked_diff = X_true[mask] - X_pred[mask]
    return np.mean(np.abs(masked_diff))

Класс автоэнкодера:

class Autoencoder:

def __init__(self, data,
             recurrent_weight=0.5,
             optimizer= Adam(lr=0.01),
            latent_dim = 32,
            n_features = 1):
    self.data = data.copy()
    self.recurrent_weight = recurrent_weight
    self.optimizer = optimizer
    self.latent_dim = latent_dim
    self.n_features = n_features

def _create_model(self):
    timesteps =  self.data.shape[1]*2 #211*2 == n_dims
    inputs = Input(shape=(timesteps, self.n_features)) #number of days, pain level is the only feature so n_features == 1
    encoded = LSTM(self.latent_dim, return_sequences=False, name="encoder")(inputs)

    encoder = Model(inputs, encoded)
    print(encoder.summary())

    decoded = RepeatVector(timesteps)(encoded) ##Repeats the provided 2D input multiple times to create a 3D output.
    decoded = LSTM(self.n_features, return_sequences=True, name='decoder')(decoded)

    autoencoder = Model(inputs, decoded)
    loss_function = make_reconstruction_loss(n_features =self.data.shape[1]) # n_features = 211
    autoencoder.compile(optimizer=self.optimizer, loss=loss_function) 
    return autoencoder

def _create_missing_mask(self, fraction): #a matrix of binary value, 1= missing, 0 = observed
    missing_m = actual_missing_mask.values
    idx = np.flatnonzero(missing_m) # get the nonzero indices
    totalnum_missing = np.count_nonzero(missing_m!=0)
    print(totalnum_missing)
    N = int(np.count_nonzero(missing_m!=0)*(1-fraction))
    np.put(missing_m,np.random.choice(idx,size=N,replace=False),0) #replace (1-frac)% of to be zero(not missing)
    newnum_missing = np.count_nonzero(missing_m!=0)
    print('percentage of missing value',newnum_missing/totalnum_missing)
    return missing_m

def fill(self, missing_mask):
    self.data[missing_mask] = overall_mean # fill NA with overall_mean

def _train_epoch(self, model, missing_mask, batch_size):
    input_with_mask = np.hstack([self.data, missing_mask]) # append two matrix side by side 
    n_samples = len(input_with_mask)
    n_batches = int(np.ceil(n_samples / batch_size))
    timesteps = self.data.shape[1]*2
    X_reshaped = input_with_mask.reshape((input_with_mask.shape[0],timesteps,self.n_features)) #shape (112, 422, 1)
    for batch_idx in range(n_batches):
        batch_start = batch_idx * batch_size
        batch_end = (batch_idx + 1) * batch_size
        batch_data = X_reshaped[batch_start:batch_end, :,:] 
        loss = model.train_on_batch(batch_data, batch_data) #all missing values == mean now
    print('train loss =', loss*10) #normalize back
    return model.predict(X_reshaped)

def train(self, train_epochs = 10, batch_size=256):
    missing_mask = self._create_missing_mask(fraction = 0.5) #! fraction # a binary matrix of (missing = 1, else 0)
    missing_mask = missing_mask + earlydeath_mask #! don't account early death into loss
    self.fill(missing_mask = missing_mask)
    self.data = self.data*0.1 #normalize
    self.model = self._create_model()

    for epoch in range(train_epochs):
        print('epoch', epoch)
        X_pred = self._train_epoch(self.model, missing_mask, batch_size) #* error
        X_pred = X_pred.reshape(X_pred.shape[0], X_pred.shape[1]) #reshape for mased_mae dimension match, from 3D back to 2D
        X_pred = X_pred[:, :211] #remove the 2nd half binary matrix, left with original data

        missing_mae = masked_mae(X_true=myinput.values,
                                X_pred=X_pred*10,
                                mask=missing_mask-earlydeath_mask) #all numpy array, shape=(112, 211)

        print("missing mae:", missing_mae)
        #all missing values which is the overall_mean now divide by half + half of prediction
        old_weight = (1.0 - self.recurrent_weight)
        self.data[missing_mask] *= old_weight 
        pred_missing = X_pred[missing_mask]
        self.data[missing_mask] += self.recurrent_weight * pred_missing
    self.data = self.data * 10 #normalize back
    return self.data.copy(), missing_mask

Поезд:

np.random.seed(1337)  # for reproducibility
imputer = Autoencoder(myinput.values)
output, missing_mask = imputer.train(train_epochs=20, batch_size=30)

Training Result enter image description here

...