Глубокие нейронные сети на Python


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

Мои вопросы:

  • Как я могу увеличить производительность? На данный момент требуется несколько дней, чтобы обучить сеть

Код выполняется на одном ядре. Но так как каждый цикл по партии работать самостоятельно, он может быть распараллелен.

  • Как я могу распараллелить цикл по партии?

Я нашел некоторые учебники на параллельных циклов в Python, но я не могу применить его к моей проблеме.

Вот мой проверенный код при некоторой тренировке псевдо данные:

from numpy import random, zeros, array, dot
from scipy.special import expit
import time 

def sigma(x):
    return expit(x)

def sigma_prime(x):
    u = expit(x)
    return  u-u*u 

def SGD(I, L, batch_size, eta):

    images = len(L)

    # Pre-activation
    z = [zeros((layer_size[l],1)) for l in range(1,nn_size)]

    # Activations
    a = [zeros((layer_size[l],1)) for l in range(nn_size)]

    # Ground truth      
    y = zeros((images, layer_size[-1]))
    for i in range(images):
        y[i,L[i]] = 1.0

    while (1):

        t0 = time.time()

        # Create random batch
        batch = random.randint(0,images,batch_size)

        dW = [zeros((layer_size[l+1], layer_size[l])) for l in range(nn_size-1)]
        db = [zeros((layer_size[l],1)) for l in range(1, nn_size)]

        for i in batch:        
            # Feedforward
            a[0] = array([I[i]]).T
            for l in range(nn_size-1):
                z[l] = dot(W[l], a[l]) + b[l]
                a[l+1] = sigma(z[l])

            # Backpropagation
            delta = (a[nn_size-1]-array([y[i]]).T) * sigma_prime(z[nn_size-2])
            dW[nn_size-2] += dot(delta, a[nn_size-2].T)
            dW[nn_size-2] += delta.dot(a[nn_size-2].T)
            db[nn_size-2] += delta
            for l in reversed(range(nn_size-2)):
                delta = dot(W[l+1].T, delta) * sigma_prime(z[l])
                dW[l] += dot(delta, a[l].T)
                db[l] += delta

        # Update Weights and Biases
        for l in range(nn_size-1):
            W[l] += - eta * dW[l] / batch_size
            b[l] += - eta * db[l] / batch_size

        print(time.time() - t0)

input_size = 1000
output_size = 10

layer_size = [input_size, 30**2, 30**2, 30**2, output_size]

nn_size = len(layer_size)
layer_size = layer_size

# Weights
W = [random.randn(layer_size[l+1],layer_size[l]) for l in range(nn_size-1)]

# Bias
b = [random.randn(layer_size[l],1) for l in range(1,nn_size)]

# Some random training data with label
size_training_data = 1000
# Random data I of size "size_training_data" x "input_size"
I = random.rand(size_training_data, input_size)
# Label for all training data
L = random.randint(0,10, input_size)

batch_size = 100
eta = 0.1
SGD(I, L, batch_size, eta)

Вывод показывает время, необходимое для приготовления одной порции размер batch_size.



351
6
задан 23 марта 2018 в 08:03 Источник Поделиться
Комментарии
1 ответ

Причина, почему ваш эпох так медленно, потому что вы не перебираете каждый пример в пакете и вычисления градиентов в цикле for. Ключ для ускорения, понимая, что вы выполняете одни и те же операции над каждым примером в пакете, и поэтому вы можете сложить ваши примеры в матрицу и вычислить градиенты для всех примеров в единую матрицу.

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

Это 900 скрытых узлов для первого примера.

Однако, поскольку мы выполняем те же операции для каждого примера в пакете, мы можем стек 100 примеров, чтобы сформировать матрицу размера (100,1000) вместо (1,1000) затем берем скалярное произведение этой матрицы с помощью транспонированной матрицы вес, (1000,900) для результирующей матрицы (100,900). Добавить уклоном (1,900), который будет автоматически транслироваться в numpy для матрицы размера (100,900) (это тот же вектора смещения укладываются в 100 раз) и применить нелинейное преобразование для окончательной матрица размера (100,900). Это 900 скрытых узлов в каждом по 100 примеров.

Это может быть применено в каждом скрытом слое сети.

Факторинг исходный код:

for i in batch:        
# Feedforward
a[0] = array([I[i]]).T
for l in range(nn_size-1):
z[l] = dot(W[l], a[l]) + b[l]
a[l+1] = sigma(z[l])

# Backpropagation
delta = (a[nn_size-1]-array([y[i]]).T) * sigma_prime(z[nn_size-2])
dW[nn_size-2] += dot(delta, a[nn_size-2].T)
dW[nn_size-2] += delta.dot(a[nn_size-2].T)
db[nn_size-2] += delta
for l in reversed(range(nn_size-2)):
delta = dot(W[l+1].T, delta) * sigma_prime(z[l])
dW[l] += dot(delta, a[l].T)
db[l] += delta

в матричной математической форме:

a[0] = I[batch]
for l in range(nn_size-1):
z[l] = a[l].dot(W[l].T) + b[l].T
a[l+1] = sigma(z[l])

delta = (a[nn_size-1] - y[batch]) * sigma_prime(z[nn_size-2])
dW[nn_size-2] += 2*delta.T.dot(a[nn_size-2])
db[nn_size-2] += np.sum(delta.T, axis=1, keepdims=True)

for l in reversed(range(nn_size-2)):
delta = delta.dot(W[l+1]) * sigma_prime(z[l])
dW[l] += a[l].T.dot(delta).T
db[l] += np.sum(delta.T, axis=1, keepdims=True)

При расчете градиентов над партиями, сложения или средних градиентов по поводу примеров, оба работают, но Эндрю Нг предлагает использовать в среднем за партию как разъяснил в своем курсе и здесь: https://stats.stackexchange.com/questions/183840/sum-or-average-of-gradients-in-mini-batch-gradient-decent

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

С оригинальной для цикла реализации более 10 эпох, каждая эпоха занимает где-то между 1.75 - 2.25 S С в среднем 1.91 с каждой эпохой.

С матрицей реализации более 100 эпох, каждая эпоха занимает от 0.06 - 0.25 S С в среднем 0,08 С в эпоху.

Распараллеливание Синг тоже несколько нетривиальных как SGD является последовательный алгоритм. Например, представьте, что вы стоите на вершине холма и пытаются найти самый короткий путь (не зная его). Единственный способ сделать свой путь вниз по склону, на самом деле предпринимает шаги вниз и повторной оценке ваших вариантов на каждом шаге. Даже если там было несколько клонов, все ваши клоны будут стоять на том же месте, на холме, и поэтому прирост информации будет ограничен. Тем не менее, существуют методы для параллельных SGD и поэтому, если вы решились на проведение этого, я хотел бы предложить вам прочитать несколько статей по теме:

Ченг 2017: взвешенные параллельных СГД для распределенных несбалансированной нагрузкой система обучения https://arxiv.org/abs/1708.04801

Зинкевич 2010: распараллелен стохастического градиентного спуска http://martin.zinkevich.org/publications/nips2010.pdf

Или на более высокий обзор на уровне:
http://blog.smola.org/post/977927287/parallel-stochastic-gradient-descent

6
ответ дан 29 марта 2018 в 01:03 Источник Поделиться