Также проверьте LSTM
Это чистая реализация генерации слов с использованием RNN
Мы собираемся научить нашу сеть предсказывать следующие слова в данном абзаце. Это потребует повторяющейся архитектуры, поскольку сеть должна будет запоминать последовательность символов. Порядок имеет значение. 1000 итераций, и у нас будет произносимый английский. Чем больше время тренировки, тем лучше. Вы можете кормить его любой текстовой последовательностью (слова, Python, HTML и т. Д.)
Что такое рекуррентная сеть?
Сети прямого распространения отлично подходят для изучения закономерностей между набором входов и выходов.
- температура и местоположение
- высота вес
- скорость и марка машины
Но что, если порядок данных имеет значение?
Алфавит, текст песни. Они хранятся с использованием условной памяти. Вы можете получить доступ к элементу, только если у вас есть доступ к предыдущим элементам (например, к связанному списку).
Войдите в повторяющиеся сети
Мы передаем скрытое состояние из предыдущего временного шага обратно в сеть на следующем временном шаге.
Итак, вместо того, чтобы операция потока данных происходила так
ввод - ›скрытый -› вывод
бывает вот так
(input + prev_hidden) - ›hidden -› output
ждать. Почему не это?
(ввод + пред_ввод) - ›скрытый -› вывод
Скрытое повторение изучает, что нужно помнить, тогда как повторение ввода жестко запрограммировано, чтобы просто помнить непосредственно предыдущую точку данных
Формула RNN
Он в основном говорит, что текущее скрытое состояние h (t) является функцией f предыдущего скрытого состояния h (t-1) и текущего входа x (t). Тета - это параметры функции f. Сеть обычно учится использовать h (t) как своего рода сводку с потерями релевантных для задачи аспектов прошлой последовательности входных данных до t.
Функция потерь
Общие потери для данной последовательности значений x в паре с последовательностью значений y будут тогда просто суммой потерь за все временные шаги. Например, если L (t) - отрицательная логарифмическая вероятность y (t) при x (1),. . . , x (t), затем суммируйте их, вы получите потерю для последовательности
Наши шаги
- Инициализировать веса случайным образом
- Дайте модели пару символов (входной символ и целевой символ. Целевой символ - это символ, который сеть должна угадать, это следующий символ в нашей последовательности)
- Прямой проход (мы рассчитываем вероятность для каждого возможного следующего символа в соответствии с состоянием модели, используя параметры)
- Ошибка измерения (расстояние между предыдущей вероятностью и целевым символом)
- Мы рассчитываем градиенты для каждого из наших параметров, чтобы увидеть их влияние на потери (обратное распространение во времени).
- обновлять все параметры по направлению с помощью градиентов, которые помогают минимизировать потери
- Повторить! Пока наша потеря невелика AF
Какие варианты использования?
- Прогнозирование временных рядов (прогноз погоды, цены на акции, объем трафика и т. Д.)
- Последовательная генерация данных (музыка, видео, аудио и т. Д.)
Другие примеры
-Https: //github.com/anujdutt9/RecurrentNeuralNetwork (двоичное сложение)
Что дальше?
1 Сети LSTM 2 Двунаправленные сети 3 рекурсивные сети
Код состоит из 4 частей
- Загрузите данные обучения
- кодировать символы в векторы
- Определите рекуррентную сеть
- Определите функцию потерь
- Вперед пас
- Потеря
- Обратный проход
- Определите функцию для создания предложений из модели
- Обучите сеть
- Накормите сеть
- Рассчитать градиент и обновить параметры модели
- Выведите текст, чтобы увидеть, как идет обучение
Загрузите обучающие данные
Сеть нуждается в большом текстовом файле в качестве входных данных.
Содержимое файла будет использовано для обучения сети.
Я использую Methamorphosis от Kafka (Public Domain). Потому что Кафка был странным чуваком. Мне нравится.
In [3]:
data = open('kafka.txt', 'r').read() chars = list(set(data)) data_size, vocab_size = len(data), len(chars) print 'data has %d chars, %d unique' % (data_size, vocab_size) data has 137629 chars, 81 unique
Кодировать / декодировать символ / вектор
Нейронные сети работают с векторами (вектор - это массив с плавающей запятой). Итак, нам нужен способ кодирования и декодирования символа как вектора.
Мы посчитаем количество уникальных символов (vocab_size). Это будет размер вектора. Вектор содержит только ноль, за исключением положения символа, значение которого равно 1.
Итак, сначала давайте вычислим vocab_size:
In [5]:
char_to_ix = { ch:i for i,ch in enumerate(chars)} ix_to_char = { i:ch for i, ch in enumerate(chars)} print char_to_ix print ix_to_char {'\n': 0, 'C': 31, '!': 3, ' ': 4, '"': 5, '%': 6, '$': 7, "'": 8, ')': 9, '(': 10, '*': 11, '-': 12, ',': 13, '/': 2, '.': 15, '1': 16, '0': 17, '3': 18, '2': 19, '5': 20, '4': 21, '7': 22, '6': 23, '9': 24, '8': 25, ';': 26, ':': 27, '?': 28, 'A': 29, '@': 30, '\xc3': 1, 'B': 32, 'E': 33, 'D': 34, 'G': 35, 'F': 36, 'I': 37, 'H': 38, 'K': 39, 'J': 40, 'M': 41, 'L': 42, 'O': 43, 'N': 44, 'Q': 45, 'P': 46, 'S': 47, 'R': 48, 'U': 49, 'T': 50, 'W': 51, 'V': 52, 'Y': 53, 'X': 54, 'd': 59, 'a': 55, 'c': 56, 'b': 57, 'e': 58, '\xa7': 14, 'g': 60, 'f': 61, 'i': 62, 'h': 63, 'k': 64, 'j': 65, 'm': 66, 'l': 67, 'o': 68, 'n': 69, 'q': 70, 'p': 71, 's': 72, 'r': 73, 'u': 74, 't': 75, 'w': 76, 'v': 77, 'y': 78, 'x': 79, 'z': 80} --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-5-83f18c934ed8> in <module>() 2 ix_to_char = { i:ch for i, ch in enumerate(chars)} 3 print char_to_ix ----> 4 print char_to_char NameError: name 'char_to_char' is not defined
Затем мы создаем 2 словаря для кодирования и декодирования char в int
In [ ]:
Наконец, мы создаем вектор из символа следующим образом:
Словарь, определенный выше, позволяет нам создать вектор размером 61 вместо 256.
Здесь и пример char 'a'
Вектор содержит только нули, за исключением позиции char_to_ix ['a'], где ставим 1.
In [7]:
import numpy as np vector_for_char_a = np.zeros((vocab_size, 1)) vector_for_char_a[char_to_ix['a']] = 1 print vector_for_char_a.ravel() [ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
Определение сети
Нейронная сеть состоит из 3-х слоев:
- входной слой
- скрытый слой
- выходной слой
Все слои полностью связаны со следующим: каждый узел слоя связан со всеми узлами следующего слоя. Скрытый слой связан с выходом и сам с собой: значения из итерации используются для следующей.
Чтобы централизовать значения, которые имеют значение для обучения (гиперпараметры), мы также определяем длину последовательности и скорость обучения
In [9]:
#hyperparameters File "<ipython-input-9-695b49af656c>", line 5 learning_rate 1e-1 ^ SyntaxError: invalid syntax
In [12]:
#model parameters hidden_size = 100 seq_length = 25 learning_rate = 1e-1 Wxh = np.random.randn(hidden_size, vocab_size) * 0.01 #input to hidden Whh = np.random.randn(hidden_size, hidden_size) * 0.01 #input to hidden Why = np.random.randn(vocab_size, hidden_size) * 0.01 #input to hidden bh = np.zeros((hidden_size, 1)) by = np.zeros((vocab_size, 1))
Параметры модели настраиваются в процессе обучения.
- Wxh - это параметры для соединения вектора, который содержит один вход, со скрытым слоем.
- Какие параметры для подключения скрытого слоя к самому себе. Это Ключ Rnn: Рекурсия выполняется путем внедрения предыдущих значений из вывода скрытого состояния в себя на следующей итерации.
- Почему - это параметры для подключения скрытого слоя к выходу
- bh содержит скрытую предвзятость
- by содержит смещение вывода
В следующем разделе вы увидите, как эти параметры используются для создания предложения.
Определите функцию потерь
Потеря является ключевым понятием при обучении всех нейронных сетей. Это значение, которое описывает, насколько хороша наша модель.
Чем меньше потеря, тем лучше наша модель.
(Хорошая модель - это модель, в которой прогнозируемый результат близок к результату обучения)
На этапе обучения мы хотим минимизировать потери.
Функция потерь вычисляет потери, но также и градиенты (см. Обратный проход):
- Он выполняет прямой проход: вычисляет следующий символ с учетом символа из обучающего набора.
- Он вычисляет потери, сравнивая предсказанный char с целевым char. (Целевой символ - это ввод, следующий за символом в наборе передачи)
- Он вычисляет обратный проход для вычисления градиентов
Эта функция принимает в качестве входных данных:
- список входных символов
- список целевых символов
- и предыдущее скрытое состояние
Эта функция выводит:
- потеря
- градиент для каждого параметра между слоями
- последнее скрытое состояние
Вперед пас
Прямой проход использует параметры модели (Wxh, Whh, Why, bh, by) для вычисления следующего символа с учетом символа из обучающего набора.
xs [t] - это вектор, который кодирует символ в позиции t. ps [t] - это вероятности для следующего символа
hs[t] = np.tanh(np.dot(Wxh, xs[t]) + np.dot(Whh, hs[t-1]) + bh) # hidden state ys[t] = np.dot(Why, hs[t]) + by # unnormalized log probabilities for next chars ps[t] = np.exp(ys[t]) / np.sum(np.exp(ys[t])) # probabilities for next chars
или грязный псевдокод для каждого символа
hs = input*Wxh + last_value_of_hidden_state*Whh + bh ys = hs*Why + by ps = normalized(ys)
Обратный проход
Наивный способ рассчитать все градиенты - это пересчитать потери для небольших вариаций для каждого параметра. Это возможно, но потребует много времени. Существует методика расчета всех градиентов для всех параметров одновременно: распространение фона.
Градиенты рассчитываются в обратном порядке прямого прохода с использованием простых методов.
цель состоит в том, чтобы рассчитать градиенты для прямой формулы:
hs = input*Wxh + last_value_of_hidden_state*Whh + bh ys = hs*Why + by
Потеря одной точки данных
Каким образом вычисленные оценки внутри f должны изменяться t, чтобы уменьшить потери? Чтобы понять это, нам нужно получить градиент.
Поскольку все единицы вывода вносят вклад в ошибку каждой скрытой единицы, мы суммируем все градиенты, вычисленные на каждом временном шаге в последовательности, и используем это для обновления параметров. Таким образом, наши градиенты параметров становятся:
Наш первый градиент нашей потери. Мы будем распространять это через цепное правило
Цепное правило - это метод нахождения производной составных функций или функций, созданных путем объединения одной или нескольких функций.
In [14]:
def lossFun(inputs, targets, hprev): """ inputs,targets are both list of integers. hprev is Hx1 array of initial hidden state returns the loss, gradients on model parameters, and last hidden state """ #store our inputs, hidden states, outputs, and probability values xs, hs, ys, ps, = {}, {}, {}, {} #Empty dicts # Each of these are going to be SEQ_LENGTH(Here 25) long dicts i.e. 1 vector per time(seq) step # xs will store 1 hot encoded input characters for each of 25 time steps (26, 25 times) # hs will store hidden state outputs for 25 time steps (100, 25 times)) plus a -1 indexed initial state # to calculate the hidden state at t = 0 # ys will store targets i.e. expected outputs for 25 times (26, 25 times), unnormalized probabs # ps will take the ys and convert them to normalized probab for chars # We could have used lists BUT we need an entry with -1 to calc the 0th hidden layer # -1 as a list index would wrap around to the final element xs, hs, ys, ps = {}, {}, {}, {} #init with previous hidden state # Using "=" would create a reference, this creates a whole separate copy # We don't want hs[-1] to automatically change if hprev is changed hs[-1] = np.copy(hprev) #init loss as 0 loss = 0 # forward pass for t in xrange(len(inputs)): xs[t] = np.zeros((vocab_size,1)) # encode in 1-of-k representation (we place a 0 vector as the t-th input) xs[t][inputs[t]] = 1 # Inside that t-th input we use the integer in "inputs" list to set the correct hs[t] = np.tanh(np.dot(Wxh, xs[t]) + np.dot(Whh, hs[t-1]) + bh) # hidden state ys[t] = np.dot(Why, hs[t]) + by # unnormalized log probabilities for next chars ps[t] = np.exp(ys[t]) / np.sum(np.exp(ys[t])) # probabilities for next chars loss += -np.log(ps[t][targets[t],0]) # softmax (cross-entropy loss) # backward pass: compute gradients going backwards #initalize vectors for gradient values for each set of weights dWxh, dWhh, dWhy = np.zeros_like(Wxh), np.zeros_like(Whh), np.zeros_like(Why) dbh, dby = np.zeros_like(bh), np.zeros_like(by) dhnext = np.zeros_like(hs[0]) for t in reversed(xrange(len(inputs))): #output probabilities dy = np.copy(ps[t]) #derive our first gradient dy[targets[t]] -= 1 # backprop into y #compute output gradient - output times hidden states transpose #When we apply the transpose weight matrix, #we can think intuitively of this as moving the error backward #through the network, giving us some sort of measure of the error #at the output of the lth layer. #output gradient dWhy += np.dot(dy, hs[t].T) #derivative of output bias dby += dy #backpropagate! dh = np.dot(Why.T, dy) + dhnext # backprop into h dhraw = (1 - hs[t] * hs[t]) * dh # backprop through tanh nonlinearity dbh += dhraw #derivative of hidden bias dWxh += np.dot(dhraw, xs[t].T) #derivative of input to hidden layer weight dWhh += np.dot(dhraw, hs[t-1].T) #derivative of hidden layer to hidden layer weight dhnext = np.dot(Whh.T, dhraw) for dparam in [dWxh, dWhh, dWhy, dbh, dby]: np.clip(dparam, -5, 5, out=dparam) # clip to mitigate exploding gradients return loss, dWxh, dWhh, dWhy, dbh, dby, hs[len(inputs)-1]
Создайте предложение из модели
In [17]:
#prediction, one full forward pass def sample(h, seed_ix, n): """ sample a sequence of integers from the model h is memory state, seed_ix is seed letter for first time step n is how many characters to predict """ #create vector x = np.zeros((vocab_size, 1)) #customize it for our seed char x[seed_ix] = 1 #list to store generated chars ixes = [] #for as many characters as we want to generate for t in xrange(n): #a hidden state at a given time step is a function #of the input at the same time step modified by a weight matrix #added to the hidden state of the previous time step #multiplied by its own hidden state to hidden state matrix. h = np.tanh(np.dot(Wxh, x) + np.dot(Whh, h) + bh) #compute output (unnormalised) y = np.dot(Why, h) + by ## probabilities for next chars p = np.exp(y) / np.sum(np.exp(y)) #pick one with the highest probability ix = np.random.choice(range(vocab_size), p=p.ravel()) #create a vector x = np.zeros((vocab_size, 1)) #customize it for the predicted char x[ix] = 1 #add it to the list ixes.append(ix) txt = ''.join(ix_to_char[ix] for ix in ixes) print '----\n %s \n----' % (txt, ) hprev = np.zeros((hidden_size,1)) # reset RNN memory #predict the 200 next characters given 'a' sample(hprev,char_to_ix['a'],200) ---- HD')JsuLNgDw!4ejckLaA6zAt*(d'�5O;p5DoglJ!u-RN'v/:XaByA1Q"BWcO*Tip:AO/O%Wl/o)�Q1(DC.�3ztLPh/9RJ' ?W"p hpXoCIQgN3!j0-@49z9Hit5Kc$mn'L8 :T.T;2.bKd(GS$7 ID8@?iitb2"OSubD7wJyh3@(:5hEmKX4T?i;;Ceq,BmY2yoPHx ----
Обучение
Эта последняя часть кода является основным циклом обучения:
- Подайте в сеть часть файла. Размер блока seq_lengh
- Используйте функцию потерь, чтобы:
- Выполните прямой проход, чтобы вычислить все параметры модели для заданных пар ввода / вывода
- Сделайте обратный проход, чтобы вычислить все градиенты
- Распечатайте предложение из случайного числа, используя параметры сети
- Обновите модель, используя технику адаптивного градиента Adagrad
Наполните функцию потерь входными данными и целями
Мы создаем два массива char из файла данных, один целевой сдвигается по сравнению с входным.
Для каждого символа во входном массиве целевой массив дает следующий символ.
In [15]:
p=0 inputs = [char_to_ix[ch] for ch in data[p:p+seq_length]] print "inputs", inputs targets = [char_to_ix[ch] for ch in data[p+1:p+seq_length+1]] print "targets", targets inputs [43, 69, 58, 4, 66, 68, 73, 69, 62, 69, 60, 13, 4, 76, 63, 58, 69, 4, 35, 73, 58, 60, 68, 73, 4] targets [69, 58, 4, 66, 68, 73, 69, 62, 69, 60, 13, 4, 76, 63, 58, 69, 4, 35, 73, 58, 60, 68, 73, 4, 47]
Adagrad для обновления параметров
Это тип стратегии градиентного спуска.
размер шага = скорость обучения
Самый простой способ обновить параметры модели:
param += dparam * step_size
Adagrad - более эффективный метод, в котором step_size становится меньше во время обучения.
Он использует переменную памяти, которая со временем увеличивается:
mem += dparam * dparam
и используйте его для вычисления step_size:
step_size = 1./np.sqrt(mem + 1e-8)
Суммируя:
mem += dparam * dparam param += -learning_rate * dparam / np.sqrt(mem + 1e-8) # adagrad update
Smooth_loss
Smooth_loss не играет никакой роли в обучении. Это просто версия потери, отфильтрованная нижними частотами:
smooth_loss = smooth_loss * 0.999 + loss * 0.001
Это способ усреднить потери за последние итерации, чтобы лучше отслеживать прогресс.
Итак, наконец
Вот код основного цикла, который время от времени выполняет обучение и генерирует текст:
In [18]:
n, p = 0, 0 mWxh, mWhh, mWhy = np.zeros_like(Wxh), np.zeros_like(Whh), np.zeros_like(Why) mbh, mby = np.zeros_like(bh), np.zeros_like(by) # memory variables for Adagrad smooth_loss = -np.log(1.0/vocab_size)*seq_length # loss at iteration 0 while n<=1000*100: # prepare inputs (we're sweeping from left to right in steps seq_length long) # check "How to feed the loss function to see how this part works if p+seq_length+1 >= len(data) or n == 0: hprev = np.zeros((hidden_size,1)) # reset RNN memory p = 0 # go from start of data inputs = [char_to_ix[ch] for ch in data[p:p+seq_length]] targets = [char_to_ix[ch] for ch in data[p+1:p+seq_length+1]] # forward seq_length characters through the net and fetch gradient loss, dWxh, dWhh, dWhy, dbh, dby, hprev = lossFun(inputs, targets, hprev) smooth_loss = smooth_loss * 0.999 + loss * 0.001 # sample from the model now and then if n % 1000 == 0: print 'iter %d, loss: %f' % (n, smooth_loss) # print progress sample(hprev, inputs[0], 200) # perform parameter update with Adagrad for param, dparam, mem in zip([Wxh, Whh, Why, bh, by], [dWxh, dWhh, dWhy, dbh, dby], [mWxh, mWhh, mWhy, mbh, mby]): mem += dparam * dparam param += -learning_rate * dparam / np.sqrt(mem + 1e-8) # adagrad update p += seq_length # move data pointer n += 1 # iteration counter iter 0, loss: 109.861223 ---- /gQv8H'-656q@7o 0A r2,GWqf: l1 dPSqT18X!7g"*,M�-Gw�rM9e(M(KLW)Yt;C?3N*jWJx@i*BMdoU4K5MoeJOI/�4�-l MA.*)HgUd,o4bW;'DK/')NE $:z9b6xkaqA$PnVRTjRLtG�Cf gbv�DH�:os;ByVL$F(fxsSo(v86W.kt,8i miEOr6"y0o(5Mx �/ ---- iter 1000, loss: 86.766971 ---- hi k cehi. kposod w pit se oc hebint gi d bis ,he nn ne to- t mir ildtec- aovi s tof lica . tha bilt Ig mclinlf, t orir!ee peld wafrecathe fm doathioame mhass fd al w, eagoy s woyigs miievonr, iee W ---- iter 2000, loss: 69.478687 ---- he ate leth hebuelyhe wGthremilkel hore cke inse mo highe Is hodont mlathem wise ceuG here le sime socleelldk nuSind d aS there'cecichind hither whe angave he ut soure herle tathes be nos, athlle sos ---- iter 3000, loss: 60.740861 ---- ld oulile hisisced abdan shimttey ir ad hay leri gousse way ynas,rs simos himonac the way in ag lnere wouis sfat wad boon ooutilit; whare forey ruro ud tuuly he furny arghly lus bpers;nupdey hered bom ---- iter 4000, loss: 56.398800 ---- her thonilgme heprailytinwok hle thit tootwis acks in aas fom songain towith waen him the waninty on the beinl win thitghed ning wspa nhat tweaw. bus hire moror'od hre in ang whithenghaw whoraliwiit b ---- iter 5000, loss: 61.150802 ---- erinfpmeacho cuthath7rPUiitey tor Glfer lewd entin7 bo asche toOinLlen Groa ciwinreIed yoin atho1le p1sthMyin clqot 1kee athmtina7froit acros.in kertho inm, at7iveeiGpBoelem mo erwor ast.ot oach mche ---- iter 6000, loss: 62.854070 ---- asidyade sy uld Eafere whey cist om not cad hay cally' robd ffot sa bustepcomed. Beger chawgesmey coull thouns,e wouls ir co thomered it hit 1o rad Inat dof hit nf, are bl oo hew of woundall c0earacle ---- iter 7000, loss: 57.474113 ---- ems bound ht hacteld has ar teis the his oume co Boom hinde piud tor, sis a ding nod ut, saprisdi ang es oyoustacullaghs n6 fet havat thof was waabs angimy an atC ace rumtome thovi hef wad fafattenin ---- iter 8000, loss: 53.622344 ---- aed the nised wat sthed anr the woulch not sued On in the eat qurecantt avem shipke faed il reln t, at have the hime hlecr hid coobd, beghem aly en andand sidf and lers ceeve ild on in ad Gred oipnea ---- --------------------------------------------------------------------------- KeyboardInterrupt Traceback (most recent call last) <ipython-input-18-800feb8589cf> in <module>() 13 14 # forward seq_length characters through the net and fetch gradient ---> 15 loss, dWxh, dWhh, dWhy, dbh, dby, hprev = lossFun(inputs, targets, hprev) 16 smooth_loss = smooth_loss * 0.999 + loss * 0.001 17 <ipython-input-14-0bac30c55025> in lossFun(inputs, targets, hprev) 28 xs[t][inputs[t]] = 1 # Inside that t-th input we use the integer in "inputs" list to set the correct 29 hs[t] = np.tanh(np.dot(Wxh, xs[t]) + np.dot(Whh, hs[t-1]) + bh) # hidden state ---> 30 ys[t] = np.dot(Why, hs[t]) + by # unnormalized log probabilities for next chars 31 ps[t] = np.exp(ys[t]) / np.sum(np.exp(ys[t])) # probabilities for next chars 32 loss += -np.log(ps[t][targets[t],0]) # softmax (cross-entropy loss) KeyboardInterrupt:
Заключительные слова:
Следуйте за мной, когда я пишу о машинном обучении, науке о данных и блокчейне. Вы можете следить за мной и моим кодом на Github
Хорошего дня !!!