В этом репозитории было проведено несколько игрушечных экспериментов на основе Tensorflow (популярной среды машинного обучения), чтобы представить некоторые концепции глубокого обучения, которые используются для распознавания изображений и языкового моделирования. Я суммировал их на три части:

  • Сверточная нейронная сеть для распознавания рукописных цифр
  • Последовательность уровня символов на основе LSTM для генерации последовательности
  • Классификация пар вопросов с помощью RNN

Сверточные нейронные сети

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

Извлечение данных

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

Приведенный выше код используется для извлечения данных MNIST, они уже были сохранены как встроенная функция в Tensorflow.

Строительство сети

def conv2d(x, W):
  return tf.nn.conv2d(input=x, filter=W, strides=[1, 1, 1, 1], padding='SAME')
def max_pool_2x2(x):
  return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')

Сверточный 2D-слой и слой максимального объединения - два наиболее распространенных слоя с точки зрения CNN для распознавания 2D-изображений. Приведенный выше код определяет двумерный сверточный слой (см. Функцию conv2d ()) и максимальный уровень объединения (см. Функцию max_pool_2x2 ()).

Для функции tf.nn.conv2d () необходимо указать следующие параметры:

  1. первый - это входные данные, которые должны быть тензором формы [batch, in_height, in_width, in_channels].
  2. Второй - фильтр, который должен иметь тот же тип, что и вход, с формой [filter_height, filter_width, in_channels, out_channels].
  3. Третий - шаг, одномерный тензор длины 4, который описывает скользящее окно для каждого измерения ввода.
  4. Четвертый - это заполнение, для которого нужно указать два режима, а именно «ЖЕ» и «ДЕЙСТВИТЕЛЬНЫЙ».
  5. Для «ЖЕЛАЯ» размер вывода будет вычисляться как: out_height = ceil (float (in_height) / float (strides [1])), out_width = ceil (float (in_width) / float (strides [2])).
  6. Для «VALID» выходной размер будет вычисляться как: out_height = ceil (float (in_height - filter_height + 1) / float (strides [1])), out_width = ceil (float (in_width - filter_width + 1) / float ( шагов [2])).
  7. Здесь автор использует «SAME» для функции conv2d, чтобы сохранить размер вывода таким же, как размер ввода, и просто уменьшить пространственный размер во время операции max_pool_2x2.

Для функции tf.nn.max_pool () установка параметров и их использование такие же, как и для функции tf.nn.conv2d ().

Здесь это код для построения простой сверточной сети:

tf.reset_default_graph() 
sess = tf.InteractiveSession()
x = tf.placeholder("float", shape = [None, 28,28,1]) #shape in CNNs is always None x height x width x color channels
y_ = tf.placeholder("float", shape = [None, 10]) #shape is always None x number of classes
#First Conv and Pool Layers
W_conv1 = tf.Variable(tf.truncated_normal([5, 5, 1, 32], stddev=0.1))
b_conv1 = tf.Variable(tf.constant(.1, shape = [32])) #shape of the bias just has to match output channels of the filter
h_conv1 = tf.nn.conv2d(input=x, filter=W_conv1, strides=[1, 1, 1, 1], padding='SAME') + b_conv1
h_conv1 = tf.nn.relu(h_conv1)
h_pool1 = tf.nn.max_pool(h_conv1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
#Second Conv and Pool Layers
W_conv2 = tf.Variable(tf.truncated_normal([5, 5, 32, 64], stddev=0.1))
b_conv2 = tf.Variable(tf.constant(.1, shape = [64]))
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)
#First Fully Connected Layer
W_fc1 = tf.Variable(tf.truncated_normal([7 * 7 * 64, 1024], stddev=0.1))
b_fc1 = tf.Variable(tf.constant(.1, shape = [1024]))
h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
#Dropout Layer
keep_prob = tf.placeholder("float")
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
#Second Fully Connected Layer
W_fc2 = tf.Variable(tf.truncated_normal([1024, 10], stddev=0.1))
b_fc2 = tf.Variable(tf.constant(.1, shape = [10]))
#Final Layer
y = tf.matmul(h_fc1_drop, W_fc2) + b_fc2

Следует отметить два момента: первая - это функция tf.placeholder (), это просто переменная, которой мы назначим данные позже (во время обучения). Это позволяет нам создавать наши операции и строить наш вычислительный граф, не нуждаясь в реальных данных, и граф будет заполнен действительными числами во время оптимизации. Таким образом, мы можем определить серию вычислений, не зная фактических данных. Второй - tf.Variable (), в основном это просто тензор, который является переменной, поэтому его также можно обучить во время оптимизации, и значения в этих матрицах могут изменяться (думая о SGD обновления).

Для tf.placeholder () необходимо указать несколько параметров: первый - dtype, который описывает тип элементов. в тензоре; второй - shape, который описывает форму тензора; третье - имя, и используется для наименования переменной (необязательный параметр), которая помогает программистам во время отладки и записи при построении графа сложной модели, кроме того, с помощью механизма области видимости имен он также позволяет вы можете повторно использовать переменную, что делает ее чрезвычайно полезной для обработки некоторых определенных задач (например, машинный перевод на основе RNN, генеративные состязательные модели и т. д.).

Для tf.Variable () первым параметром должно быть начальное значение, которое может быть тензором, и это начальное значение переменной. Обратите внимание, что начальное значение должно иметь указанную форму. Вторым может быть настраиваемое имя переменной (если вы хотите поделиться переменной позже), но это необязательно.

Оптимизация

Вот код для обучения:

crossEntropyLoss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels = y_, logits = y))
trainStep = tf.train.AdamOptimizer().minimize(crossEntropyLoss)
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
sess.run(tf.global_variables_initializer())
batchSize = 50
for i in range(1000):
    batch = mnist.train.next_batch(batchSize)
    trainingInputs = batch[0].reshape([batchSize,28,28,1])
    trainingLabels = batch[1]
    if i%10 == 0:
        summary = sess.run(merged, {x: trainingInputs, y_: trainingLabels, keep_prob: 1.0})
        writer.add_summary(summary, i)
    if i%100 == 0:
        trainAccuracy = accuracy.eval(session=sess, feed_dict={x:trainingInputs, y_: trainingLabels, keep_prob: 1.0})
        print "step %d, training accuracy %g"%(i, trainAccuracy)
    trainStep.run(session=sess, feed_dict={x: trainingInputs, y_: trainingLabels, keep_prob: 0.5})

Также стоит отметить два момента: сначала график должен быть запущен с помощью функции sess.run (), которая преобразует конструкцию символьного уровня в вычисление реального значения; второй - tf.global_variables_initializer (), он должен быть запущен перед обучением, чтобы инициализировать все переменные, объявленные ранее.

Char-RNN

В этом руководстве автор сначала выполнил некоторую предварительную обработку текста, чтобы очистить текстовые данные до уровня символов (удалил знаки препинания, круглые скобки, вопросительные знаки и т. Д. И оставил только буквенно-цифровые символы), прежде чем приступить к построению сети и запуску. обучение. Здесь я просто расскажу, как использовать Tensorflow для построения модели Char-RNN, но не буду вдаваться в подробности о предварительной обработке данных, см. Следующий код:

#prepare the dataset of input to output pairs encoded as integers
dataX = []
dataY = []
for i in range(0, nChars - seqLength, 1):
    seq_in = allText[i:i + seqLength]
    seq_out = allText[i + seqLength]
    dataX.append([charToInt[char] for char in seq_in])
    dataY.append(charToInt[seq_out])
# reshape X to be [samples, time steps, features]
X = np.reshape(dataX, (nExamples, seqLength, 1))
# normalize
X = X / float(nVocab)
# one hot encode the output variable
y = np.zeros([nExamples, nVocab])
for i, example in enumerate(dataY):
    lis = np.zeros(nVocab)
    lis[example] = 1
    y[i] = lis

До сих пор мы видим, что X можно рассматривать как системные входные данные размера [nExamples, SeqLength, DimFeatures], а y можно рассматривать как входную цель системы размера [nExamples, VocabSize] (одноразовое кодирование), они будут введены в заполнитель во время обучения.

Определите модель Char-RNN:

batchSize = 24
lstmUnits = 48
iterations = 100000
numDimensions = 1
numClasses = nVocab
tf.reset_default_graph()
labels = tf.placeholder(tf.float32, [None, numClasses])
input_data = tf.placeholder(tf.float32, [None, seqLength, numDimensions])
lstmCell = tf.contrib.rnn.BasicLSTMCell(lstmUnits)
lstmCell = tf.contrib.rnn.DropoutWrapper(cell=lstmCell, output_keep_prob=0.85)
value, _ = tf.nn.dynamic_rnn(lstmCell, input_data, dtype=tf.float32)
weight = tf.Variable(tf.truncated_normal([lstmUnits, numClasses]))
bias = tf.Variable(tf.constant(0.1, shape=[numClasses]))
value = tf.transpose(value, [1, 0, 2])
last = tf.gather(value, int(value.get_shape()[0]) - 1)
prediction = (tf.matmul(last, weight) + bias)

Здесь автор использует tf.contrib.rnn.BasicLSTMCell () для создания базовой ячейки LSTM (без двунаправленной передачи), а затем использует tf.nn.dynamic_rnn () для вывода выходных данных LSTM для каждого временного шага и последнего скрытого состояния.

Для tf.contrib.rnn.BasicLSTMCell (), необходимо указать некоторые параметры, например: num_units - количество единиц в ячейке LSTM. повторное использование - следует ли повторно использовать переменные в существующей области. По умолчанию установлено значение «none».

Для tf.nn.dynamic_rnn (), есть также некоторые параметры, которые нам необходимо установить:

  1. первый - это экземпляр RNN Cell, здесь он был определен как lstmCell (см. код выше)
  2. второй - это вход RNN, который должен иметь форму [batch_size, time_step, numDims].
  3. Возвращаемое значение tf.nn.dynamic_rnn () дает пару кортежей (выходы, состояние), где выход - это тензор формы [batch_size, time_step, lstm_output_dim], а состояние - это последнее скрытое состояние формы [batch_size, lstm_state_dim].

Как мы знаем, выходные данные имеют форму [batch_size, time_step, lstm_output_dim], поэтому, если вы хотите получить последний вывод time_step, его нужно сначала транспонировать, а затем принять последний time_step следующим образом:

value = tf.transpose(value, [1, 0, 2])
last = tf.gather(value, int(value.get_shape()[0]) - 1)


И тогда это будет проблема классификации нескольких классов, как и в части оптимизации первого раздела.

Классификация вопросов с помощью Bi-LSTM

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

is_duplicate = 0 означает, что это разные вопросы, is_duplicate = 1 означает, что они имеют одинаковое значение.

Автор дает указанную выше структуру данных для объединения пары вопросов в один тензор. В этом случае, если мы посмотрим на первое предложение «Как я могу быть хорошим геологом?», Оно состоит из 8 символов. Каждый может быть представлен как 50-мерный вектор признаков (автор использовал GLOVE для представления word2vec с 50-мерным вектором признаков для каждого слова), так что это тензор 8x50. Что касается второго вопроса «Что мне делать, чтобы стать великим геологом», то его можно представить как тензор 10x50, поэтому после слияния это тензор 18x50. Затем некоторые текстовые данные снова очищаются, например:

def cleanSentences(string):
    if (isinstance(string, basestring) == False):
        return " " 
    string = string.lower()
    string = re.sub('([.,!?()])', r' \1 ', string) # Separates punctuation from the word
    return string

Вышеупомянутая функция отделяет знаки препинания от слова.

Построение данных

Для экономии памяти вход и цель должны быть построены следующим образом:

numClasses = 2
X = np.zeros((numTrainExamples + numTestExamples, maxSeqLength), dtype='int64')
Y = np.zeros((numTrainExamples + numTestExamples, numClasses), dtype='int32')

Для экономии памяти X должен иметь форму (nExamples, maxSeqLength), а не (nExamples, maxSeqlength, word2vecDim). Y должно быть (nExamples, 2), например, 10 означает is_duplicate = 1, 01 означает is_duplicate = 0 (проблема бинарной логистической классификации). Следующий код используется для построения X и Y:

exampleCounter = 0
for index, row in df.iterrows():
    firstQuestion = cleanSentences(row['question1'])
    secondQuestion = cleanSentences(row['question2'])
    firstQuestionSplit = firstQuestion.split()
    secondQuestionSplit = secondQuestion.split()
    indexCounter = 0
    for word in firstQuestionSplit:
        try:
            X[exampleCounter][indexCounter] = wordsList.index(word)
        except ValueError:
            X[exampleCounter][indexCounter] = 399999 #Vector for unkown words
        indexCounter = indexCounter + 1
    for word in secondQuestionSplit:
        try:
            X[exampleCounter][indexCounter] = wordsList.index(word)
        except ValueError:
            X[exampleCounter][indexCounter] = 399999 #Vector for unkown words
        indexCounter = indexCounter + 1
    if (row['is_duplicate'] == 1):
        Y[exampleCounter] = [0,1]
    else:
        Y[exampleCounter] = [1,0]
    exampleCounter = exampleCounter + 1
np.save('Data/xMatrix.npy', X)
np.save('Data/yMatrix.npy', Y)

Из приведенного выше кода мы видим, что каждая строка X дает индекс слов для пар вопросов, затем мы можем использовать таблицу поиска для индексации вектора реальных слов в качестве входных данных для Bi-LSTM. система. См. Следующую конструкцию модели Bi-LSTM:

tf.reset_default_graph()
labels = tf.placeholder(tf.float32, [batchSize, numClasses])
input_data = tf.placeholder(tf.int32, [batchSize, maxSeqLength])
keep_prob = tf.placeholder(tf.float32)
seq_len = tf.placeholder(tf.int32, [None])
data = tf.Variable(tf.zeros([batchSize, maxSeqLength, numDimensions]),dtype=tf.float32)
data = tf.nn.embedding_lookup(wordVectors,input_data)
lstm_fw_cell = tf.contrib.rnn.BasicLSTMCell(lstmUnits, forget_bias=1.0, state_is_tuple=True)
lstm_bw_cell = tf.contrib.rnn.BasicLSTMCell(lstmUnits, forget_bias=1.0, state_is_tuple=True)
lstm_fw_cell = tf.contrib.rnn.DropoutWrapper(lstm_fw_cell, keep_prob)
lstm_bw_cell = tf.contrib.rnn.DropoutWrapper(lstm_bw_cell, keep_prob)
value, states = tf.nn.bidirectional_dynamic_rnn(cell_fw=lstm_fw_cell, cell_bw=lstm_bw_cell, inputs=data,sequence_length=seq_len, dtype=tf.float32)
value = tf.concat(value, 2)
hiddenUnits = 32
weight = tf.Variable(tf.truncated_normal([2*lstmUnits, hiddenUnits]))
bias = tf.Variable(tf.constant(0.1, shape=[hiddenUnits]))
value = tf.transpose(value, [1, 0, 2])
last = tf.gather(value, int(value.get_shape()[0]) - 1)
fc1 = (tf.matmul(last, weight) + bias)
weight2 = tf.Variable(tf.truncated_normal([hiddenUnits, numClasses]))
bias2 = tf.Variable(tf.constant(0.1, shape=[numClasses]))
prediction = (tf.matmul(fc1, weight2) + bias2)
correctPred = tf.equal(tf.argmax(prediction,1), tf.argmax(labels,1))
accuracy = tf.reduce_mean(tf.cast(correctPred, tf.float32))
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=prediction, labels=labels))
optimizer = tf.train.AdamOptimizer().minimize(loss)
sess = tf.InteractiveSession()
saver = tf.train.Saver()
sess.run(tf.global_variables_initializer())
for i in range(iterations):
    #Next Batch of reviews
    nextBatch, nextBatchLabels = getBatch();
    train_seq_len = np.ones(batchSize) * maxSeqLength
    sess.run(optimizer, {input_data: nextBatch, labels: nextBatchLabels, keep_prob: 0.7, seq_len: train_seq_len})
    
    if (i % 100 == 0):
        trainingAccuracy = sess.run(accuracy, {input_data: nextBatch, labels: nextBatchLabels, keep_prob: 1.0, seq_len: train_seq_len})
        print 'The training loss at iteration', i, 'is', trainingAccuracy
writer.close()

После слоя tf.nn.embedding_lookup (wordVectors, input) входные данные shape [batch_size, maxSeqlength] станут тензором формы [batch_size, maxSeqlength, wordDim], теперь его можно преобразовать в качестве входных данных в систему.

Самая интересная часть здесь - это функция tf.nn.bidirectional_dynamic_rnn (), в которую вы можете поместить forward_lstm_cell и backward_lstm_cell.

  1. Первый параметр - это экземпляр ячейки RNN, который будет использоваться для прямого направления.
  2. Второй параметр также является экземпляром ячейки RNN, которую можно использовать для обратного направления.
  3. Третий параметр - это входные данные с формой [batch_size, time_step, wordDim].
  4. Четвертый параметр - seq_length, который представляет собой вектор с формой [batch_size], он содержит фактическую длину для каждой последовательности в пакете, обычно мы заполняем этот вектор max_seq_length.
  5. Последний параметр - dtype, это необязательный выбор.

tf.nn.bidirectional_dynamic_rnn () вернет кортеж (output, output_state), где сами выходные данные являются кортежем (output_fw, output_bw), output_fw - это тензор формы [batch_size, max_seq_len, cell_fw. output_size] и output_bw - это тензор формы [batch_size, max_seq_len, cell_bw.output_size]. output_state также является кортежем (output_state_fw, ouput_state_bw), который дает прямое и обратное последнее скрытое состояние Bi-LSTM.

Поскольку возвращаемый результат является кортежем, ему все равно необходимо объединить их с tf.concat (output, 2) (объединить их по третьему dim) для дальнейших вычислений. Процедура оптимизации такая же, как и раньше (задача мультиклассовой классификации).

Заключение

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

Автор: Шон Ян | Редактор: Циньтонг У | Продюсер: Чейн Чжан