Implementação da descida estocástica de gradiente em Python


7

Eu estou tentando implementar um algoritmo básico de descida de gradiente estocástico para uma regressão linear 2D em python. Recebi um código padrão para o GD baunilha e tentei convertê-lo para trabalhar para o SGD.

Especificamente - estou um pouco inseguro quanto à implementação correta da função de perda e derivadas parciais, pois sou novo em regressões em geral.

Eu vejo que os erros tendem a "zig zag" conforme o esperado. O que se segue parece uma implementação correta ou cometi algum erro?

#sample data
data  = [(1,1),(2,3),(4,3),(3,2),(5,5)]


def compute_error_for_line_given_points(b, m, points):
    totalError = 0
    x = points[0]
    y = points[1]
    return float(totalError + (y - (m * x + b)) ** 2)

def step_gradient(b_current, m_current, points, learningRate):
    N = float(1)
    for i in range(0, 1):
        x = points[0]
        y = points[1]
        b_gradient = -(2/N) * (y - ((m_current * x) + b_current)) #this is the part I am unsure
        m_gradient = -(2/N) * x * (y - ((m_current * x) + b_current)) #here as well
    new_b = b_current - (learningRate * b_gradient)
    new_m = m_current - (learningRate * m_gradient)
    return [new_b, new_m]

err_log = []
coef_log = []
b = 0 #initial intercept
m = 0 #initial slope

iterations = 4
for i in range(iterations): #epochs
    for point in data: #one point at a time for SGD
        err = compute_error_for_line_given_points(b,m, point)
        err_log.append(err)
        b,m = step_gradient(b,m,point,.01)
        coef_log.append((b,m))

Respostas:


4

Há apenas uma pequena diferença entre a descida do gradiente e a descida do gradiente estocástico. A descida do gradiente calcula o gradiente com base na função de perda calculada em todas as instâncias de treinamento, enquanto a descida do gradiente estocástico calcula o gradiente com base na perda em lotes. Ambas as técnicas são usadas para encontrar parâmetros ideais para um modelo.

Vamos tentar implementar o SGD neste conjunto de dados 2D.

insira a descrição da imagem aqui

O algoritmo

O conjunto de dados possui 2 recursos, no entanto, queremos adicionar um termo de viés para anexar uma coluna deles ao final da matriz de dados.

shape = x.shape 
x = np.insert(x, 0, 1, axis=1)

Depois inicializamos nossos pesos, existem muitas estratégias para fazer isso. Para simplificar, definirei todos eles como 1, no entanto, definir os pesos iniciais aleatoriamente provavelmente é melhor para poder usar várias reinicializações.

w = np.ones((shape[1]+1,))

Nossa linha inicial é assim

insira a descrição da imagem aqui

Agora, atualizaremos iterativamente os pesos do modelo se ele classificar um exemplo por engano.

for ix, i in enumerate(x):
   pred = np.dot(i,w)
   if pred > 0: pred = 1
   elif pred < 0: pred = -1
   if pred != y[ix]:
      w = w - learning_rate * pred * i

Esta linha é a atualização de peso w = w - learning_rate * pred * i.

Podemos ver que fazer esse processo continuamente levará à convergência.

Após 10 épocas

insira a descrição da imagem aqui

Após 20 épocas

insira a descrição da imagem aqui

Após 50 épocas

insira a descrição da imagem aqui

Após 100 épocas

insira a descrição da imagem aqui

E finalmente,

insira a descrição da imagem aqui


O código

O conjunto de dados para este código pode ser encontrado aqui .

A função que treinará os pesos leva na matriz de feixes e os alvosxy. Retorna os pesos treinadosW e uma lista de pesos históricos encontrados ao longo do processo de treinamento.

%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt

def get_weights(x, y, verbose = 0):
    shape = x.shape
    x = np.insert(x, 0, 1, axis=1)
    w = np.ones((shape[1]+1,))
    weights = []

    learning_rate = 10
    iteration = 0
    loss = None
    while iteration <= 1000 and loss != 0:
        for ix, i in enumerate(x):
            pred = np.dot(i,w)
            if pred > 0: pred = 1
            elif pred < 0: pred = -1
            if pred != y[ix]:
                w = w - learning_rate * pred * i
            weights.append(w)    
            if verbose == 1:
                print('X_i = ', i, '    y = ', y[ix])
                print('Pred: ', pred )
                print('Weights', w)
                print('------------------------------------------')


        loss = np.dot(x, w)
        loss[loss<0] = -1
        loss[loss>0] = 1
        loss = np.sum(loss - y )

        if verbose == 1:
            print('------------------------------------------')
            print(np.sum(loss - y ))
            print('------------------------------------------')
        if iteration%10 == 0: learning_rate = learning_rate / 2
        iteration += 1    
    print('Weights: ', w)
    print('Loss: ', loss)
    return w, weights

Aplicaremos esse SGD aos nossos dados em perceptron.csv .

df = np.loadtxt("perceptron.csv", delimiter = ',')
x = df[:,0:-1]
y = df[:,-1]

print('Dataset')
print(df, '\n')

w, all_weights = get_weights(x, y)
x = np.insert(x, 0, 1, axis=1)

pred = np.dot(x, w)
pred[pred > 0] =  1
pred[pred < 0] = -1
print('Predictions', pred)

Vamos traçar o limite de decisão

x1 = np.linspace(np.amin(x[:,1]),np.amax(x[:,2]),2)
x2 = np.zeros((2,))
for ix, i in enumerate(x1):
    x2[ix] = (-w[0] - w[1]*i) / w[2]

plt.scatter(x[y>0][:,1], x[y>0][:,2], marker = 'x')
plt.scatter(x[y<0][:,1], x[y<0][:,2], marker = 'o')
plt.plot(x1,x2)
plt.title('Perceptron Seperator', fontsize=20)
plt.xlabel('Feature 1 ($x_1$)', fontsize=16)
plt.ylabel('Feature 2 ($x_2$)', fontsize=16)
plt.show()

Para ver o processo de treinamento, você pode imprimir os pesos à medida que eles mudavam nas épocas.

for ix, w in enumerate(all_weights):
    if ix % 10 == 0:
        print('Weights:', w)
        x1 = np.linspace(np.amin(x[:,1]),np.amax(x[:,2]),2)
        x2 = np.zeros((2,))
        for ix, i in enumerate(x1):
            x2[ix] = (-w[0] - w[1]*i) / w[2]
        print('$0 = ' + str(-w[0]) + ' - ' + str(w[1]) + 'x_1'+ ' - ' + str(w[2]) + 'x_2$')

        plt.scatter(x[y>0][:,1], x[y>0][:,2], marker = 'x')
        plt.scatter(x[y<0][:,1], x[y<0][:,2], marker = 'o')
        plt.plot(x1,x2)
        plt.title('Perceptron Seperator', fontsize=20)
        plt.xlabel('Feature 1 ($x_1$)', fontsize=16)
        plt.ylabel('Feature 2 ($x_2$)', fontsize=16)
        plt.show()

Use a opção detalhada se desejar uma leitura completa para ver o que o algoritmo está fazendo ao longo das iterações.
JahKnows
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.