Этот пост является следующей частью сообщения в блоге Medium (Часть 1) и блокнота в Блокноте 1 части. Пожалуйста, убедитесь, что вы прошли их через них, так как я буду использовать некоторые концепции оттуда.

В этой части я больше сосредоточусь на встраиваниях, поскольку эта концепция кажется неясной для новичков.

В последней части мы увидели, что можем использовать вложения, чтобы изучить представление для ввода. Эта концепция станет более ясной в этой части.

Настройка:
В этой части наша цель - выяснить, содержит ли данная последовательность чисел «4» или нет. Это проблема классификации. Вы можете провести параллели с задачей, в которой ваша цель - выяснить, содержит ли предложение слово «не» (очень простая форма анализа настроений).

Давайте начнем

In:

# Import modules 
import numpy as np
from keras.models import Model
from keras.layers import Input, SimpleRNN, Activation
from keras.layers import Embedding
from keras.optimizers import Adam
Using TensorFlow backend.

In :

# Data and model parameters
seq_len = 3   #Length of each sequence 
rnn_size = 1  #Output shape of RNN
input_size = 10000 #Numbers of instances
all_feat = np.random.randint(low=0, high=10, size=(input_size,3))
all_label = np.apply_along_axis(func1d=lambda x: int(np.any(x==4)), axis=1, arr=all_feat)
print('\nInput Sample:\n', all_feat[:5])
print('\nOutput Sample:\n',all_label[:5])

Вне:

Input Sample:
 [[8 8 6]
 [8 5 5]
 [7 1 4]
 [7 1 0]
 [7 8 5]]
Output Sample:
 [0 0 1 0 0]

В этой модели мы снова собираемся выбрать размер встраивания = 10, так как мы хотим использовать вложения как замену однократному кодированию.

Но мы не можем напрямую использовать предыдущую модель (которая решает проблему регрессии). На этот раз мы хотим предсказать вероятность. Итак, после нашей RNN мы будем использовать сигмовидную активацию.

Как вы можете видеть, сигмовидные сквоши выводятся до числа от 0 до 1, так что чем больше число, тем оно ближе к 1. Аналогично, чем меньше число, тем ближе оно к 0.

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

Классификация

In:

input_1 = Input(shape=(3,), name='Input_Layer')
x = Embedding(input_dim=10, output_dim=10, name='Embedding_Layer')(input_1)
x = SimpleRNN(rnn_size, activation='linear', name='RNN_Layer')(x)
y = Activation('sigmoid', name='Activation_Layer')(x)
model = Model(inputs=input_1, outputs=y)
print(model.summary())

Вне:

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
Input_Layer (InputLayer)     (None, 3)                 0         
_________________________________________________________________
Embedding_Layer (Embedding)  (None, 3, 10)             100       
_________________________________________________________________
RNN_Layer (SimpleRNN)        (None, 1)                 12        
_________________________________________________________________
Activation_Layer (Activation (None, 1)                 0         
=================================================================
Total params: 112.0
Trainable params: 112.0
Non-trainable params: 0.0
_________________________________________________________________

In :

model.compile(optimizer=Adam(0.02), loss='binary_crossentropy', metrics=['acc'])
history = model.fit(x=all_feat, y=all_label, batch_size=8, epochs=4, validation_split=0.2, verbose=1)

Вне:

Train on 8000 samples, validate on 2000 samples
Epoch 1/4
8000/8000 [==============================] - 4s - loss: 0.0106 - acc: 0.9956 - val_loss: 6.5674e-05 - val_acc: 1.0000
Epoch 2/4
8000/8000 [==============================] - 4s - loss: 3.2502e-05 - acc: 1.0000 - val_loss: 1.6959e-05 - val_acc: 1.0000
Epoch 3/4
8000/8000 [==============================] - 4s - loss: 1.0502e-05 - acc: 1.0000 - val_loss: 6.8796e-06 - val_acc: 1.0000
Epoch 4/4
8000/8000 [==============================] - 4s - loss: 4.6197e-06 - acc: 1.0000 - val_loss: 3.3005e-06 - val_acc: 1.0000

In :

print('\nInput features: \n', all_feat[-10:,:])
print('\nLabels: \n', all_label[-10:])
print('\nPredictions: \n', model.predict(all_feat[-10:,:]))

Вне:

Input features: 
 [[5 6 9]
 [9 9 6]
 [5 8 4]
 [5 8 1]
 [6 8 1]
 [9 2 7]
 [6 0 0]
 [9 7 8]
 [8 7 9]
 [0 2 7]]
Labels: 
 [0 0 1 0 0 0 0 0 0 0]
Predictions: 
 [[  2.12621512e-06]
 [  2.11419456e-06]
 [  9.99977827e-01]
 [  1.95994267e-06]
 [  2.24871815e-06]
 [  2.05333026e-06]
 [  2.08695269e-06]
 [  1.90066737e-06]
 [  1.92356879e-06]
 [  1.98162775e-06]]

In :

embd_layer = model.get_layer('Embedding_Layer')
embd_mats = embd_layer.get_weights()
wgt_layer = model.get_layer('RNN_Layer')
wgts_mats = wgt_layer.get_weights()

Проверьте, получили ли мы ожидаемые формы:

In :

print('Embedding W shape: ', embd_mats[0].shape)
print('W shape: ', wgts_mats[0].shape)
print('U shape: ', wgts_mats[1].shape)
print('b shape: ', wgts_mats[2].shape)

Вне:

Embedding W shape:  (10, 10)
W shape:  (10, 1)
U shape:  (1, 1)
b shape:  (1,)

Прежде чем смотреть на веса, давайте посмотрим, чего мы от них ждем:

Напоминание: f линейный.

Мы ожидаем, что комбинация матрицы внедрения и W будет иметь высокое значение для позиции, соответствующей input = 4. U должен просто пересылать вывод в следующую ячейку.

Таким образом, если модель где-нибудь увидит 4, производительность станет высокой. U поможет перенести этот результат в следующую ячейку.

Вложение матрицы весов:

In:

embd_mats

Вне:

[array([[-0.14044982,  0.21658441, -0.22791751,  0.21502978, -0.19579233,
          0.18623219,  0.2239477 , -0.14737135,  0.1834836 ,  0.17848013],
        [-0.16119297,  0.15270022, -0.22112088,  0.1870423 , -0.21034855,
          0.22094215,  0.22617932, -0.19690502,  0.16201828,  0.16872635],
        [-0.20279713,  0.17129959, -0.18022029,  0.17888239, -0.19878508,
          0.18757217,  0.18948197, -0.15959248,  0.21192279,  0.1793922 ],
        [-0.16744758,  0.18689834, -0.20116815,  0.16811925, -0.21271777,
          0.16176091,  0.23074993, -0.22508025,  0.17736089,  0.19697021],
        [ 0.87358165, -0.92919093,  1.27148986, -0.97707129,  1.06918406,
         -1.25646746, -1.07512939,  1.01008677, -1.22653592, -1.07279408],
        [-0.20252103,  0.13714807, -0.17961763,  0.20893431, -0.21239041,
          0.19352351,  0.20141117, -0.19705266,  0.16560002,  0.20322703],
        [-0.20628117,  0.14427678, -0.21260698,  0.15537232, -0.14697219,
          0.18460469,  0.15442781, -0.18720369,  0.2582643 ,  0.21449965],
        [-0.15330791,  0.21459399, -0.22178707,  0.13402544, -0.13827942,
          0.2318393 ,  0.21597138, -0.20058507,  0.23698436,  0.19769941],
        [-0.15754659,  0.1512261 , -0.20155624,  0.22907215, -0.17636189,
          0.18587922,  0.17176367, -0.19894339,  0.22978529,  0.18345623],
        [-0.13863607,  0.22530088, -0.15114433,  0.19402944, -0.21190034,
          0.1904562 ,  0.22017194, -0.18479121,  0.2103454 ,  0.18715787]], dtype=float32)]

Не очень четкую картину, правда?
Давайте проверим веса RNN

In :

wgts_mats

Вне:

[array([[ 2.06557703],
        [-1.83462012],
        [ 1.95859563],
        [-2.21554518],
        [ 1.99453723],
        [-2.04525447],
        [-1.56783688],
        [ 1.71975875],
        [-1.72644722],
        [-1.72969031]], dtype=float32),
 array([[ 1.21534526]], dtype=float32),
 array([ 0.02591785], dtype=float32)]

U в порядке. W выглядит несколько ровно. Давайте проверим нашу обычную конструкцию:

In :

print('\n W_embd * W + b: \n', np.matmul(embd_mats[0], wgts_mats[0]) + wgts_mats[2])
print('\nU: \n', wgts_mats[1])

Вне:

W_embd * W + b: 
 [[ -3.58580279]
 [ -3.57090545]
 [ -3.48436666]
 [ -3.58020806]
 [ 20.28836632]
 [ -3.57022905]
 [ -3.47717571]
 [ -3.60041976]
 [ -3.53663325]
 [ -3.56173849]]
U: 
 [[ 1.21534526]]

При индексе 4 мы получаем большое положительное значение, а другие - отрицательное. Таким образом, если 4 введены последовательно, модель создает высокий положительный результат, в противном случае - отрицательное значение.

U немного выше 1, поэтому предыдущая оценка с усилением (т.е. положительный ввод даст более высокое положительное значение, отрицательный ввод даст более отрицательное значение)

Имеет смысл, правда?

Меньший размер вложения

Вложения должны изучать представления, верно? Попробуем более низкое встраивание.

In:

del model

input_1 = Input(shape=(3,), name='Input_Layer')
x = Embedding(input_dim=10, output_dim=4, name='Embedding_Layer')(input_1)
x = SimpleRNN(rnn_size, activation='linear', name='RNN_Layer')(x)
y = Activation('sigmoid', name='Activation_Layer')(x)
model = Model(inputs=input_1, outputs=y)
model.compile(optimizer=Adam(0.02), loss='binary_crossentropy', metrics=['acc'])
history = model.fit(x=all_feat, y=all_label, batch_size=8, epochs=4, validation_split=0.2, verbose=1)

Вне:

Train on 8000 samples, validate on 2000 samples
Epoch 1/4
8000/8000 [==============================] - 4s - loss: 0.0180 - acc: 0.9916 - val_loss: 1.4316e-04 - val_acc: 1.0000
Epoch 2/4
8000/8000 [==============================] - 4s - loss: 6.9616e-05 - acc: 1.0000 - val_loss: 3.6003e-05 - val_acc: 1.0000
Epoch 3/4
8000/8000 [==============================] - 4s - loss: 2.2050e-05 - acc: 1.0000 - val_loss: 1.4364e-05 - val_acc: 1.0000
Epoch 4/4
8000/8000 [==============================] - 4s - loss: 9.6034e-06 - acc: 1.0000 - val_loss: 6.8176e-06 - val_acc: 1.0000

In:

print('\nInput features: \n', all_feat[-10:,:])
print('\nLabels: \n', all_label[-10:])
print('\nPredictions: \n', model.predict(all_feat[-10:,:]))

Вне:

Input features: 
 [[5 6 9]
 [9 9 6]
 [5 8 4]
 [5 8 1]
 [6 8 1]
 [9 2 7]
 [6 0 0]
 [9 7 8]
 [8 7 9]
 [0 2 7]]
Labels: 
 [0 0 1 0 0 0 0 0 0 0]
Predictions: 
 [[  4.59608827e-06]
 [  4.54348674e-06]
 [  9.99956727e-01]
 [  4.26225279e-06]
 [  4.60571482e-06]
 [  4.42924829e-06]
 [  4.36698019e-06]
 [  4.22405401e-06]
 [  4.26433053e-06]
 [  4.23319989e-06]]

In :

embd_layer = model.get_layer('Embedding_Layer')
embd_mats = embd_layer.get_weights()
wgt_layer = model.get_layer('RNN_Layer')
wgts_mats = wgt_layer.get_weights()

In :

embd_mats

Вне:

[array([[ 0.28512904,  0.25177249, -0.27367339,  0.30789083],
        [ 0.25492424,  0.26012757, -0.29675287,  0.30629158],
        [ 0.28270748,  0.28677672, -0.26648894,  0.25294301],
        [ 0.29766184,  0.26947719, -0.26931292,  0.28334641],
        [-1.50977004, -1.4418962 ,  1.47527599, -1.76327348],
        [ 0.33177933,  0.24144135, -0.26087469,  0.27504072],
        [ 0.25945908,  0.24145941, -0.25259978,  0.34160569],
        [ 0.29079974,  0.26891741, -0.2553148 ,  0.30429697],
        [ 0.29276261,  0.23101816, -0.28032303,  0.29925823],
        [ 0.27864024,  0.23305847, -0.24671097,  0.35506517]], dtype=float32)]

На этот раз вложения кажутся более интуитивными. В индексе 4 мы видим большие и отрицательные числа.

In:

print('\n W_embd * W + b: \n', np.matmul(embd_mats[0], wgts_mats[0]) + wgts_mats[2])
print('\nU: \n', wgts_mats[1])

Вне:

W_embd * W + b: 
 [[ -3.31143618]
 [ -3.31706262]
 [ -3.2351234 ]
 [ -3.3211081 ]
 [ 19.09644699]
 [ -3.28206587]
 [ -3.23133922]
 [ -3.31450295]
 [ -3.26382184]
 [ -3.28180361]]
U: 
 [[ 1.23604953]]

Это похоже на предыдущую модель. Итак, нижнее вложение работает!

Минимальное вложение

Давайте станем более интуитивно понятными. Это проблема классификации. Так зачем нам 10 или 4 вложения. Мы должны выучить только 1 число: высокое значение для 4, низкое значение для других.

In :

del model
input_1 = Input(shape=(3,), name='Input_Layer')
x = Embedding(input_dim=10, output_dim=1, name='Embedding_Layer')(input_1)
x = SimpleRNN(rnn_size, activation='linear', name='RNN_Layer')(x)
y = Activation('sigmoid', name='Activation_Layer')(x)
model = Model(inputs=input_1, outputs=y)
print(model.summary())
model.compile(optimizer=Adam(0.02), loss='binary_crossentropy', metrics=['acc'])
history = model.fit(x=all_feat, y=all_label, batch_size=8, epochs=4, validation_split=0.2, verbose=1)

Вне:

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
Input_Layer (InputLayer)     (None, 3)                 0         
_________________________________________________________________
Embedding_Layer (Embedding)  (None, 3, 1)              10        
_________________________________________________________________
RNN_Layer (SimpleRNN)        (None, 1)                 3         
_________________________________________________________________
Activation_Layer (Activation (None, 1)                 0         
=================================================================
Total params: 13.0
Trainable params: 13.0
Non-trainable params: 0.0
_________________________________________________________________
None
Train on 8000 samples, validate on 2000 samples
Epoch 1/4
8000/8000 [==============================] - 4s - loss: 0.0248 - acc: 0.9902 - val_loss: 4.7527e-04 - val_acc: 1.0000
Epoch 2/4
8000/8000 [==============================] - 4s - loss: 2.2767e-04 - acc: 1.0000 - val_loss: 1.1516e-04 - val_acc: 1.0000
Epoch 3/4
8000/8000 [==============================] - 4s - loss: 6.9932e-05 - acc: 1.0000 - val_loss: 4.4882e-05 - val_acc: 1.0000
Epoch 4/4
8000/8000 [==============================] - 4s - loss: 2.9992e-05 - acc: 1.0000 - val_loss: 2.1167e-05 - val_acc: 1.0000

In:

embd_layer = model.get_layer('Embedding_Layer')
embd_mats = embd_layer.get_weights()
wgt_layer = model.get_layer('RNN_Layer')
wgts_mats = wgt_layer.get_weights()
print('\nEmbedding weights: \n',embd_mats[0])
print('\nRNN weights: \n', wgts_mats[0], wgts_mats[1], wgts_mats[2] )
print('\n W_embd * W + b: \n', np.matmul(embd_mats[0], wgts_mats[0]) + wgts_mats[2])
print('\nU: \n', wgts_mats[1])

Вне:

Embedding weights: 
 [[-0.43760461]
 [-0.43051854]
 [-0.43027946]
 [-0.44076917]
 [ 2.7104342 ]
 [-0.44030327]
 [-0.42853352]
 [-0.43567708]
 [-0.43628559]
 [-0.43209222]]
RNN weights: 
 [[ 6.39243031]] [[ 1.37941635]] [ 0.15867162]
 W_embd * W + b: 
 [[ -2.63868523]
 [ -2.59338808]
 [ -2.59185982]
 [ -2.65891457]
 [ 17.48493385]
 [ -2.65593624]
 [ -2.58069897]
 [ -2.62636375]
 [ -2.63025355]
 [ -2.60344768]]
U: 
 [[ 1.37941635]]

На этот раз мы можем заметить, что и внедрение, и веса RNN выглядят разумными. Трансформированные веса по-прежнему аналогичны предыдущим моделям.

Урок:

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

Приближаемся к реальным моделям

Мы постоянно используем линейную активацию, т. Е. Без изменений ввода, чтобы упростить понимание. Этот подход не работает для более длинных последовательностей. Почему?

Запомните значение U; оно было несколько выше 1. Таким образом, большое значение, полученное в начале последовательности, будет продолжать умножаться на U, вызывая взрыв. Низкое значение в начале исчезнет.
Если значение U ‹1, произойдет обратное.

Рассмотрите различные сложные варианты ввода в более длинных последовательностях, и вы получите представление о проблемах.

Чтобы справиться с этим, мы будем использовать неактивацию. Обычно это tanh, но поскольку мне нужна моя вероятность, я буду использовать сигмоид ( tanh выводит число в диапазоне (-1,1)).

In :

del model
input_1 = Input(shape=(3,), name='Input_Layer')
x = Embedding(input_dim=10, output_dim=1, name='Embedding_Layer')(input_1)
y = SimpleRNN(rnn_size, activation='sigmoid', name='RNN_Layer')(x)
model = Model(inputs=input_1, outputs=y)
print(model.summary())
model.compile(optimizer=Adam(0.03), loss='binary_crossentropy', metrics=['acc'])
history = model.fit(x=all_feat, y=all_label, batch_size=8, epochs=4, validation_split=0.2, verbose=1)

Вне:

________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
Input_Layer (InputLayer)     (None, 3)                 0         
_________________________________________________________________
Embedding_Layer (Embedding)  (None, 3, 1)              10        
_________________________________________________________________
RNN_Layer (SimpleRNN)        (None, 1)                 3         
=================================================================
Total params: 13.0
Trainable params: 13.0
Non-trainable params: 0.0
_________________________________________________________________
None
Train on 8000 samples, validate on 2000 samples
Epoch 1/4
8000/8000 [==============================] - 5s - loss: 0.1316 - acc: 0.9599 - val_loss: 0.0094 - val_acc: 1.0000
Epoch 2/4
8000/8000 [==============================] - 4s - loss: 0.0054 - acc: 1.0000 - val_loss: 0.0029 - val_acc: 1.0000
Epoch 3/4
8000/8000 [==============================] - 4s - loss: 0.0020 - acc: 1.0000 - val_loss: 0.0013 - val_acc: 1.0000
Epoch 4/4
8000/8000 [==============================] - 4s - loss: 9.8492e-04 - acc: 1.0000 - val_loss: 6.7974e-04 - val_acc: 1.0000

In:

print('\nInput features: \n', all_feat[-10:,:])
print('\nLabels: \n', all_label[-10:])
print('\nPredictions: \n', model.predict(all_feat[-10:,:]))

Вне:

Input features: 
 [[5 6 9]
 [9 9 6]
 [5 8 4]
 [5 8 1]
 [6 8 1]
 [9 2 7]
 [6 0 0]
 [9 7 8]
 [8 7 9]
 [0 2 7]]
Labels: 
 [0 0 1 0 0 0 0 0 0 0]
Predictions: 
 [[  4.83995711e-04]
 [  5.38199791e-04]
 [  9.99984384e-01]
 [  5.32271166e-04]
 [  5.32272446e-04]
 [  5.16529719e-04]
 [  4.35839931e-04]
 [  5.51217643e-04]
 [  4.83845797e-04]
 [  5.16526750e-04]]

In:

embd_layer = model.get_layer('Embedding_Layer')
embd_mats = embd_layer.get_weights()
wgt_layer = model.get_layer('RNN_Layer')
wgts_mats = wgt_layer.get_weights()
print('\nEmbedding weights: \n',embd_mats[0])
print('\nRNN weights: \n', wgts_mats[0], wgts_mats[1], wgts_mats[2] )
print('\n W_embd * W + b: \n', np.matmul(embd_mats[0], wgts_mats[0]) + wgts_mats[2])
print('\nU: \n', wgts_mats[1])

Вне:

Embedding weights: 
 [[ 1.22580421]
 [ 1.1842196 ]
 [ 1.15448904]
 [ 1.20168781]
 [-2.71523046]
 [ 1.19090605]
 [ 1.18169999]
 [ 1.19069457]
 [ 1.17678034]
 [ 1.20412242]]
RNN weights: 
 [[-4.77058172]] [[ 13.87013912]] [-1.89605367]
 W_embd * W + b: 
 [[ -7.74385309]
 [ -7.54547024]
 [ -7.40363789]
 [ -7.62880373]
 [ 11.05717564]
 [ -7.57736826]
 [ -7.53345013]
 [ -7.57635975]
 [ -7.50998068]
 [ -7.64041805]]
U: 
 [[ 13.87013912]]

Вес по-прежнему имеет смысл. Мы можем использовать нелинейные активации.

Ниже приводится ссылка на блокнот для этой части: