Содержание страницы
Описание класса автокодировщиков в коде
Здравствуйте и вновь добро пожаловать на занятия по теме «Глубокое обучение без учителя на языке Python».
Настало время нам с вами написать код для класса автокодировщиков.
Если вы не хотите писать его сами, вы можете перейти по адресу https://github.com/lazyprogrammer/machine_learning_examples и зайти в папку unsupervised_class2. Нужный для этой лекции файл называется autoencoder.py
Итак, мы начинаем с импорта библиотек.
import numpy as np
import theano
import theano.tensor as T
import matplotlib.pyplot as plt
from sklearn.utils import shuffle
from util import relu, error_rate, getKaggleMNIST, init_weights
Теперь определим класс автокодировщиков. Мы создадим своеобразный «конструктор» для них, поэтому вводятся произвольный параметр M, который не зависит от данных, а также параметр an_id, который мы используем для определения имён переменных Theano.
class AutoEncoder(object):
def __init__(self, M, an_id):
self.M = M
self.id = an_id
Далее определим функцию fit. В качестве аргумента она принимает только X, но не Y, ведь это обучение без учителя. В ней мы определяем форму нашего X, количество пакетов для пакетного градиентного спуска и инициируем весовые коэффициенты с использованием переменных Theano.
def fit(self, X, learning_rate=0.5, mu=0.99, epochs=1, batch_sz=100, show_fig=False):
N, D = X.shape
n_batches = N / batch_sz
W0 = init_weights((D, self.M))
self.W = theano.shared(W0, ‘W_%s’ % self.id)
self.bh = theano.shared(np.zeros(self.M), ‘bh_%s’ % self.id)
self.bo = theano.shared(np.zeros(D), ‘bo_%s’ % self.id)
При этом нам нужно отслеживать параметры в массиве, поскольку они понадобятся нам для градиентного спуска, а также отслеживать, какие из параметров являются первыми, поскольку они понадобятся при построении глубокой нейронной сети.
self.params = [self.W, self.bh, self.bo]
self.forward_params = [self.W, self.bh]
Теперь определим изменение каждой переменной, поскольку мы будем использовать импульс.
self.dW = theano.shared(np.zeros(W0.shape), ‘dW_%s’ % self.id)
self.dbh = theano.shared(np.zeros(self.M), ‘dbh_%s’ % self.id)
self.dbo = theano.shared(np.zeros(D), ‘dbo_%s’ % self.id)
self.dparams = [self.dW, self.dbh, self.dbo]
self.forward_dparams = [self.dW, self.dbh]
Далее определим тензорный вход X_in. Это будет матрица. Также определим переменную X_hat, которая будет восстановленными данными, хотя саму функцию определим чуть позже.
X_in = T.matrix(‘X_%s’ % self.id)
X_hat = self.forward_output(X_in)
Теперь определим операцию скрытого слоя в качестве функции Theano; это связано с тем, что она нам понадобится и при написании класса глубокой нейронной сети.
H = T.nnet.sigmoid(X_in.dot(self.W) + self.bh)
self.hidden_op = theano.function(
inputs=[X_in],
outputs=H,
)
Далее определим функцию затрат. Это можно сделать двумя способами – или как квадрат ошибок, и тогда запишем
cost = ((X_in – X_hat) * (X_in – X_hat)).sum() / N
Или использовать (что мы и сделаем) кросс-энтропийную функцию ошибок. Поэтому запишем сразу вместе с функцией Theano для расчёта:
cost = -(X_in * T.log(X_hat) + (1 – X_in) * T.log(1 – X_hat)).sum() / N
cost_op = theano.function(
inputs=[X_in],
outputs=cost,
)
Теперь определим наши обновления весовых коэффициентов с помощью градиентного спуска.
updates = [
(p, p + mu*dp – learning_rate*T.grad(cost, p)) for p, dp in zip(self.params, self.dparams)
] + [
(dp, mu*dp – learning_rate*T.grad(cost, p)) for for p, dp in zip(self.params, self.dparams)
]
Теперь функция обучения.
train_op = theano.function(
inputs=[X_in],
updates=updates,
)
И переходим к циклу.
costs = []
print(“training autoencoder: %s” % self.id)
for i in xrange(epochs):
print(“epoch:”, i)
X = shuffle(X)
for j in xrange(n_batches):
batch = X[j*batch_sz:(j*batch_sz + batch_sz)]
train_op(batch)
the_cost = cost_op(X)
print(“j / n_batches:”, j, “/”, n_batches, “cost:”, the_cost)
costs.append(the_cost)
if show_fig:
plt.plot(costs)
plt.show()
И теперь определим функцию forward_hidden для перехода к скрытым исходящим данным:
def forward_hidden(self, X):
Z = T.nnet.sigmoid(X.dot(self.W) + self.bh)
return Z
Здесь использована сигмоида, но я настоятельно предлагаю попробовать также и гиперболический тангенс, и relu.
И ещё одна функция forward_output:
def forward_output(self, X):
Z = self.forward_hidden(X)
Y = T.nnet.sigmoid(Z.dot(self.W.T) + self.bo)
return Y
Это, собственно, и всё, что касается класса автокодировщиков.
Описание класса глубокой нейронной сети в коде
Конкретно сейчас мы напишем код для класса глубокой нейронной сети. В дальнейшем мы его дополним, чтобы иметь возможность использовать этот код и для ограниченных машин Больцмана.
class DNN(object):
def __init__(self, hidden_layer_sizes, UnsupervisedModel=AutoEncoder):
self.hidden_layers = []
count = 0
for M in hidden_layer_sizes:
ae = UnsupervisedModel(M, count)
self.hidden_layers.append(ae)
count += 1
Далее напишем функцию fit. Это уже сфера обучения с учителем, поэтому в ней есть и X, и Y. Кроме того, я хочу отобразить функцию затрат для проверочного набора, поэтому добавлены также Xtest и Ytest, хотя в «настоящем» коде делать это совершенно необязательно.
def fit(self, X, Y, Xtest, Ytest, pretrain=True, learning_rate=0.01, mu=0.99, reg=0.1, epochs=1, batch_sz=100):
pretrain_epochs = 1
if not pretrain:
pretrain_epochs = 0
Приступаем к «жадному» послойному предварительному обучению:
current_input = X
for ae in self.hidden_layers:
ae.fit(current_input, epochs=pretrain_epochs)
current_input = ae.hidden_op(current_input)
Теперь инициируем слой логистической регрессии:
N = len(Y)
K = len(set(Y))
W0 = init_weights((self.hidden_layers[-1].M, K))
self.W = theano.shared(W0, “W_logreg”)
self.b = theano.shared(np.zeros(K), “b_logreg”)
self.params = [self.W, self.b]
for ae in self.hidden_layers:
self.params += ae.forward_params
self.dW = theano.shared(np.zeros(W0.shape), “dW_logreg”)
self.db = theano.shared(np.zeros(K), “db_logreg”)
self.dparams = [self.dW, self.db]
for ae in self.hidden_layers:
self.dparams += ae.forward_dparams
Теперь определим входные и исходящие переменные. Они являются тензорами.
X_in = T.matrix(‘X_in’)
targets = T.ivector(‘Targets’)
pY = self.forward(X_in)
squared_magnitude = [(p*p) for p in self.params]
reg_cost = -T.sum(squared_magnitude)
cost = -T.mean( T.log(pY[T.arange(pY.shape[0]), targets])) + reg_cost
prediction = self.predict(X_in)
cost_predict_op = theano.function(
inputs=[X_in, targets],
outputs=[cost, prediction],
)
Теперь определим наши обновления. Этот код идентичен тому, который был написан на предыдущей лекции.
updates = [
(p, p + mu*dp – learning_rate*T.grad(cost, p)) for p, dp in zip(self.params, self.dparams)
] + [
(dp, mu*dp – learning_rate*T.grad(cost, p)) for for p, dp in zip(self.params, self.dparams)
]
train_op = theano.function(
inputs=[X_in, targets],
updates=updates,
)
n_batches = N / batch_sz
costs = []
print(“supervised training…”)
for i in xrange(epochs):
print(“epoch:”, i)
X, Y = shuffle(X, Y)
for j in xrange(n_batches):
Xbatch = X[j*batch_sz:(j*batch_sz + batch_sz)]
Ybatch = Y[j*batch_sz:(j*batch_sz + batch_sz)]
train_op(Xbatch, Ybatch)
the_cost, the_prediction = cost_predict_op(Xtest, Ytest)
error = error_rate(the_prediction, Ytest)
print(“j / n_batches:”, j, “/”, n_batches, “cost:”, the_cost, “error:”, error)
costs.append(the_cost)
plt.plot(costs)
plt.show()
На этом с функцией fit покончено. Теперь определим другие необходимые функции predict и forward.
def predict(self, X):
return T.argmax(self.forward(X), axis=1)
def forward(self, X):
current_input = X
for ae in self.hidden_layers:
Z = ae.forward_hidden(current_input)
current_input = Z
Y = T.nnet.softmax(T.dot(current_input, self.W) + self.b)
return Y
Вот и всё, что касается класса глубокой нейронной сети.
Проверка. Обучение “жадного” послойного автокодировщика и обычный метод обратного распространения
Итак, последний этап – написание функции main.
Установим для нашей нейронной сети 1000 узлов для первого скрытого слоя, 750 для второго и 500 для третьего.
def main():
Xtrain, Ytrain, Xtest, Ytest = getKaggleMNIST()
dnn = DNN([1000, 750, 500])
dnn.fit(Xtrain, Ytrain, Xtest, Ytest, epochs=3)
Вначале мы используем функцию квадрата ошибок, то есть для переменной cost используем выражение
cost = ((X_in – X_hat) * (X_in – X_hat)).sum() / N
И возвращаемся к функции main.
if __name__ == ‘__main__’:
main()
Запустим наш код.
Как можно убедиться, он работает весьма неплохо.
Попробуем теперь использовать кросс-энтропийную функцию ошибок, то есть запишем
cost = -(X_in * T.log(X_hat) + (1 – X_in) * T.log(1 – X_hat)).sum() / N
И запустим код ещё раз.
Как мы видим, и в этом случае код хорошо работает.
А теперь попробуем ещё раз, но не используя предварительного обучения.
def main():
Xtrain, Ytrain, Xtest, Ytest = getKaggleMNIST()
dnn = DNN([1000, 750, 500])
# dnn.fit(Xtrain, Ytrain, Xtest, Ytest, epochs=3)
# vs
dnn.fit(Xtrain, Ytrain, Xtest, Ytest, pretrain=False, epochs=10)
Попробуем.
Можем убедиться, что в этом случае нейронной сети требуется намного больше времени для уменьшения ошибки, а значит, предварительное обучение без учителя является весьма полезным.