Resolvendo a equação da regressão linear simples

O artigo discute várias maneiras de determinar a equação matemática de uma linha de regressão simples (emparelhado).

Todos os métodos de resolução da equação discutida aqui são baseados no método dos mínimos quadrados. Vamos denotar os métodos da seguinte forma:

  • Solução analítica
  • Gradiente descendente
  • Descida gradiente estocástica

Para cada método de resolução da equação de uma reta, o artigo fornece diversas funções, que se dividem principalmente naquelas que são escritas sem o uso da biblioteca NumPy e aqueles que usam para cálculos NumPy. Acredita-se que o uso habilidoso NumPy reduzirá os custos de computação.

Todo o código fornecido no artigo está escrito na linguagem python-2.7 usando Caderno Jupyter. O código-fonte e o arquivo com dados de amostra estão publicados em GitHub

O artigo é mais voltado tanto para iniciantes quanto para aqueles que já começaram gradativamente a dominar o estudo de uma seção muito ampla da inteligência artificial - o aprendizado de máquina.

Para ilustrar o material, usamos um exemplo muito simples.

Condições de exemplo

Temos cinco valores que caracterizam a dependência Y de X (Tabela nº 1):

Tabela No. 1 “Exemplo de condições”

Resolvendo a equação da regressão linear simples

Vamos supor que os valores Resolvendo a equação da regressão linear simples é o mês do ano e Resolvendo a equação da regressão linear simples – receita este mês. Em outras palavras, a receita depende do mês do ano, e Resolvendo a equação da regressão linear simples - o único sinal do qual depende a receita.

O exemplo é razoável, tanto do ponto de vista da dependência condicional da receita do mês do ano, como do ponto de vista do número de valores - são muito poucos. Porém, tal simplificação permitirá, como dizem, explicar, nem sempre com facilidade, o material que os iniciantes assimilam. E também a simplicidade dos números permitirá a quem quiser resolver o exemplo no papel sem custos de mão-de-obra significativos.

Suponhamos que a dependência dada no exemplo possa ser muito bem aproximada pela equação matemática de uma linha de regressão simples (emparelhado) da forma:

Resolvendo a equação da regressão linear simples

onde Resolvendo a equação da regressão linear simples é o mês em que a receita foi recebida, Resolvendo a equação da regressão linear simples — receitas correspondentes ao mês, Resolvendo a equação da regressão linear simples и Resolvendo a equação da regressão linear simples são os coeficientes de regressão da reta estimada.

Observe que o coeficiente Resolvendo a equação da regressão linear simples frequentemente chamada de inclinação ou gradiente da linha estimada; representa o valor pelo qual o Resolvendo a equação da regressão linear simples quando isso muda Resolvendo a equação da regressão linear simples.

Obviamente, nossa tarefa no exemplo é selecionar tais coeficientes na equação Resolvendo a equação da regressão linear simples и Resolvendo a equação da regressão linear simples, em que os desvios de nossos valores de receita calculados por mês em relação às respostas verdadeiras, ou seja, os valores apresentados na amostra serão mínimos.

Método dos mínimos quadrados

De acordo com o método dos mínimos quadrados, o desvio deve ser calculado elevando-o ao quadrado. Esta técnica permite evitar o cancelamento mútuo de desvios caso tenham sinais opostos. Por exemplo, se num caso o desvio for +5 (mais cinco), e no outro -5 (menos cinco), então a soma dos desvios se anulará e totalizará 0 (zero). Você não precisa elevar o desvio ao quadrado, mas use a propriedade do módulo e então todos os desvios serão positivos e se acumularão. Não nos deteremos neste ponto em detalhes, mas simplesmente indicaremos que, para comodidade dos cálculos, costuma-se quadrar o desvio.

Esta é a aparência da fórmula com a qual determinaremos a menor soma dos desvios quadrados (erros):

Resolvendo a equação da regressão linear simples

onde Resolvendo a equação da regressão linear simples é uma função da aproximação das respostas verdadeiras (ou seja, a receita que calculamos),

Resolvendo a equação da regressão linear simples são as respostas verdadeiras (receitas fornecidas na amostra),

Resolvendo a equação da regressão linear simples é o índice amostral (número do mês em que o desvio é determinado)

Vamos diferenciar a função, definir as equações diferenciais parciais e estar prontos para passar à solução analítica. Mas primeiro, vamos fazer uma breve excursão sobre o que é diferenciação e lembrar o significado geométrico da derivada.

Diferenciação

A diferenciação é a operação de encontrar a derivada de uma função.

Para que é usada a derivada? A derivada de uma função caracteriza a taxa de variação da função e nos diz sua direção. Se a derivada em um determinado ponto for positiva, então a função aumenta; caso contrário, a função diminui. E quanto maior o valor da derivada absoluta, maior será a taxa de variação dos valores da função, bem como mais acentuada será a inclinação do gráfico da função.

Por exemplo, nas condições de um sistema de coordenadas cartesianas, o valor da derivada no ponto M(0,0) é igual a +25 significa que em um determinado ponto, quando o valor é deslocado Resolvendo a equação da regressão linear simples à direita por uma unidade convencional, valor Resolvendo a equação da regressão linear simples aumenta em 25 unidades convencionais. No gráfico parece um aumento bastante acentuado nos valores Resolvendo a equação da regressão linear simples a partir de um determinado ponto.

Outro exemplo. O valor da derivada é igual -0,1 significa que quando deslocado Resolvendo a equação da regressão linear simples por unidade convencional, valor Resolvendo a equação da regressão linear simples diminui em apenas 0,1 unidade convencional. Ao mesmo tempo, no gráfico da função, podemos observar uma inclinação descendente quase imperceptível. Fazendo uma analogia com uma montanha, é como se descêssemos muito lentamente uma encosta suave de uma montanha, ao contrário do exemplo anterior, onde tivemos que subir picos muito íngremes :)

Assim, depois de derivar a função Resolvendo a equação da regressão linear simples por probabilidades Resolvendo a equação da regressão linear simples и Resolvendo a equação da regressão linear simples, definimos equações diferenciais parciais de 1ª ordem. Após determinar as equações, receberemos um sistema de duas equações, resolvendo as quais poderemos selecionar tais valores dos coeficientes Resolvendo a equação da regressão linear simples и Resolvendo a equação da regressão linear simples, no qual os valores das derivadas correspondentes em determinados pontos mudam em uma quantidade muito, muito pequena e, no caso de uma solução analítica, não mudam em nada. Ou seja, a função erro nos coeficientes encontrados atingirá um mínimo, pois os valores das derivadas parciais nesses pontos serão iguais a zero.

Assim, de acordo com as regras de diferenciação, a equação da derivada parcial de 1ª ordem em relação ao coeficiente Resolvendo a equação da regressão linear simples assumirá a forma:

Resolvendo a equação da regressão linear simples

Equação derivada parcial de 1ª ordem em relação a Resolvendo a equação da regressão linear simples assumirá a forma:

Resolvendo a equação da regressão linear simples

Como resultado, obtivemos um sistema de equações que possui uma solução analítica bastante simples:

começar{equação*}
começar{casos}
na + bsumlimits_{i=1}^nx_i — sumlimits_{i=1}^ny_i = 0

somalimits_{i=1}^nx_i(a +bsumlimits_{i=1}^nx_i — somalimits_{i=1}^ny_i) = 0
fim{casos}
fim{equação*}

Antes de resolver a equação, vamos pré-carregar, verificar se o carregamento está correto e formatar os dados.

Carregando e formatando dados

Ressalta-se que devido ao fato de que para a solução analítica, e posteriormente para gradiente e descida gradiente estocástica, utilizaremos o código em duas variações: utilizando a biblioteca NumPy e sem usá-lo, precisaremos de formatação de dados apropriada (ver código).

Código de carregamento e processamento de dados

# импортируем все нужные нам библиотеки
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import math
import pylab as pl
import random

# графики отобразим в Jupyter
%matplotlib inline

# укажем размер графиков
from pylab import rcParams
rcParams['figure.figsize'] = 12, 6

# отключим предупреждения Anaconda
import warnings
warnings.simplefilter('ignore')

# загрузим значения
table_zero = pd.read_csv('data_example.txt', header=0, sep='t')

# посмотрим информацию о таблице и на саму таблицу
print table_zero.info()
print '********************************************'
print table_zero
print '********************************************'

# подготовим данные без использования NumPy

x_us = []
[x_us.append(float(i)) for i in table_zero['x']]
print x_us
print type(x_us)
print '********************************************'

y_us = []
[y_us.append(float(i)) for i in table_zero['y']]
print y_us
print type(y_us)
print '********************************************'

# подготовим данные с использованием NumPy

x_np = table_zero[['x']].values
print x_np
print type(x_np)
print x_np.shape
print '********************************************'

y_np = table_zero[['y']].values
print y_np
print type(y_np)
print y_np.shape
print '********************************************'

Visualização

Agora, depois de termos, em primeiro lugar, carregado os dados, em segundo lugar, verificado a exatidão do carregamento e finalmente formatado os dados, faremos a primeira visualização. O método frequentemente usado para isso é gráfico de pares Biblioteca marinho. No nosso exemplo, devido ao número limitado, não faz sentido usar a biblioteca marinho. Usaremos a biblioteca normal matplotlib e basta olhar para o gráfico de dispersão.

Código do gráfico de dispersão

print 'График №1 "Зависимость выручки от месяца года"'

plt.plot(x_us,y_us,'o',color='green',markersize=16)
plt.xlabel('$Months$', size=16)
plt.ylabel('$Sales$', size=16)
plt.show()

Gráfico nº 1 “Dependência da receita do mês do ano”

Resolvendo a equação da regressão linear simples

Solução analítica

Vamos usar as ferramentas mais comuns em python e resolva o sistema de equações:

começar{equação*}
começar{casos}
na + bsumlimits_{i=1}^nx_i — sumlimits_{i=1}^ny_i = 0

somalimits_{i=1}^nx_i(a +bsumlimits_{i=1}^nx_i — somalimits_{i=1}^ny_i) = 0
fim{casos}
fim{equação*}

De acordo com a regra de Cramer encontraremos o determinante geral, bem como os determinantes por Resolvendo a equação da regressão linear simples e Resolvendo a equação da regressão linear simples, após o que, dividindo o determinante por Resolvendo a equação da regressão linear simples para o determinante geral - encontre o coeficiente Resolvendo a equação da regressão linear simples, da mesma forma encontramos o coeficiente Resolvendo a equação da regressão linear simples.

Código da solução analítica

# определим функцию для расчета коэффициентов a и b по правилу Крамера
def Kramer_method (x,y):
        # сумма значений (все месяца)
    sx = sum(x)
        # сумма истинных ответов (выручка за весь период)
    sy = sum(y)
        # сумма произведения значений на истинные ответы
    list_xy = []
    [list_xy.append(x[i]*y[i]) for i in range(len(x))]
    sxy = sum(list_xy)
        # сумма квадратов значений
    list_x_sq = []
    [list_x_sq.append(x[i]**2) for i in range(len(x))]
    sx_sq = sum(list_x_sq)
        # количество значений
    n = len(x)
        # общий определитель
    det = sx_sq*n - sx*sx
        # определитель по a
    det_a = sx_sq*sy - sx*sxy
        # искомый параметр a
    a = (det_a / det)
        # определитель по b
    det_b = sxy*n - sy*sx
        # искомый параметр b
    b = (det_b / det)
        # контрольные значения (прооверка)
    check1 = (n*b + a*sx - sy)
    check2 = (b*sx + a*sx_sq - sxy)
    return [round(a,4), round(b,4)]

# запустим функцию и запишем правильные ответы
ab_us = Kramer_method(x_us,y_us)
a_us = ab_us[0]
b_us = ab_us[1]
print ' 33[1m' + ' 33[4m' + "Оптимальные значения коэффициентов a и b:"  + ' 33[0m' 
print 'a =', a_us
print 'b =', b_us
print

# определим функцию для подсчета суммы квадратов ошибок
def errors_sq_Kramer_method(answers,x,y):
    list_errors_sq = []
    for i in range(len(x)):
        err = (answers[0] + answers[1]*x[i] - y[i])**2
        list_errors_sq.append(err)
    return sum(list_errors_sq)

# запустим функцию и запишем значение ошибки
error_sq = errors_sq_Kramer_method(ab_us,x_us,y_us)
print ' 33[1m' + ' 33[4m' + "Сумма квадратов отклонений" + ' 33[0m'
print error_sq
print

# замерим время расчета
# print ' 33[1m' + ' 33[4m' + "Время выполнения расчета суммы квадратов отклонений:" + ' 33[0m'
# % timeit error_sq = errors_sq_Kramer_method(ab,x_us,y_us)

Aqui está o que temos:

Resolvendo a equação da regressão linear simples

Assim, encontrados os valores dos coeficientes, foi estabelecida a soma dos quadrados dos desvios. Desenhe uma linha reta no histograma de dispersão de acordo com os coeficientes encontrados.

Código de linha de regressão

# определим функцию для формирования массива рассчетных значений выручки
def sales_count(ab,x,y):
    line_answers = []
    [line_answers.append(ab[0]+ab[1]*x[i]) for i in range(len(x))]
    return line_answers

# построим графики
print 'Грфик№2 "Правильные и расчетные ответы"'
plt.plot(x_us,y_us,'o',color='green',markersize=16, label = '$True$ $answers$')
plt.plot(x_us, sales_count(ab_us,x_us,y_us), color='red',lw=4,
         label='$Function: a + bx,$ $where$ $a='+str(round(ab_us[0],2))+',$ $b='+str(round(ab_us[1],2))+'$')
plt.xlabel('$Months$', size=16)
plt.ylabel('$Sales$', size=16)
plt.legend(loc=1, prop={'size': 16})
plt.show()

Gráfico nº 2 “Respostas corretas e calculadas”

Resolvendo a equação da regressão linear simples

Você pode ver o gráfico de desvio para cada mês. No nosso caso, não derivaremos dela nenhum valor prático significativo, mas satisfaremos a nossa curiosidade sobre quão bem a equação de regressão linear simples caracteriza a dependência da receita do mês do ano.

Código do gráfico de desvio

# определим функцию для формирования массива отклонений в процентах
def error_per_month(ab,x,y):
    sales_c = sales_count(ab,x,y)
    errors_percent = []
    for i in range(len(x)):
        errors_percent.append(100*(sales_c[i]-y[i])/y[i])
    return errors_percent

# построим график
print 'График№3 "Отклонения по-месячно, %"'
plt.gca().bar(x_us, error_per_month(ab_us,x_us,y_us), color='brown')
plt.xlabel('Months', size=16)
plt.ylabel('Calculation error, %', size=16)
plt.show()

Gráfico nº 3 “Desvios,%”

Resolvendo a equação da regressão linear simples

Não é perfeito, mas concluímos nossa tarefa.

Vamos escrever uma função que, para determinar os coeficientes Resolvendo a equação da regressão linear simples и Resolvendo a equação da regressão linear simples usa a biblioteca NumPy, mais precisamente, escreveremos duas funções: uma usando uma matriz pseudoinversa (não recomendada na prática, pois o processo é computacionalmente complexo e instável), a outra usando uma equação matricial.

Código da solução analítica (NumPy)

# для начала добавим столбец с не изменяющимся значением в 1. 
# Данный столбец нужен для того, чтобы не обрабатывать отдельно коэффицент a
vector_1 = np.ones((x_np.shape[0],1))
x_np = table_zero[['x']].values # на всякий случай приведем в первичный формат вектор x_np
x_np = np.hstack((vector_1,x_np))

# проверим то, что все сделали правильно
print vector_1[0:3]
print x_np[0:3]
print '***************************************'
print

# напишем функцию, которая определяет значения коэффициентов a и b с использованием псевдообратной матрицы
def pseudoinverse_matrix(X, y):
    # задаем явный формат матрицы признаков
    X = np.matrix(X)
    # определяем транспонированную матрицу
    XT = X.T
    # определяем квадратную матрицу
    XTX = XT*X
    # определяем псевдообратную матрицу
    inv = np.linalg.pinv(XTX)
    # задаем явный формат матрицы ответов
    y = np.matrix(y)
    # находим вектор весов
    return (inv*XT)*y

# запустим функцию
ab_np = pseudoinverse_matrix(x_np, y_np)
print ab_np
print '***************************************'
print

# напишем функцию, которая использует для решения матричное уравнение
def matrix_equation(X,y):
    a = np.dot(X.T, X)
    b = np.dot(X.T, y)
    return np.linalg.solve(a, b)

# запустим функцию
ab_np = matrix_equation(x_np,y_np)
print ab_np

Vamos comparar o tempo gasto na determinação dos coeficientes Resolvendo a equação da regressão linear simples и Resolvendo a equação da regressão linear simples, de acordo com os 3 métodos apresentados.

Código para calcular o tempo de cálculo

print ' 33[1m' + ' 33[4m' + "Время выполнения расчета коэффициентов без использования библиотеки NumPy:" + ' 33[0m'
% timeit ab_us = Kramer_method(x_us,y_us)
print '***************************************'
print
print ' 33[1m' + ' 33[4m' + "Время выполнения расчета коэффициентов с использованием псевдообратной матрицы:" + ' 33[0m'
%timeit ab_np = pseudoinverse_matrix(x_np, y_np)
print '***************************************'
print
print ' 33[1m' + ' 33[4m' + "Время выполнения расчета коэффициентов с использованием матричного уравнения:" + ' 33[0m'
%timeit ab_np = matrix_equation(x_np, y_np)

Resolvendo a equação da regressão linear simples

Com uma pequena quantidade de dados, sai na frente uma função “autoescrita”, que encontra os coeficientes usando o método de Cramer.

Agora você pode passar para outras maneiras de encontrar coeficientes Resolvendo a equação da regressão linear simples и Resolvendo a equação da regressão linear simples.

Gradiente descendente

Primeiro, vamos definir o que é um gradiente. Simplificando, o gradiente é um segmento que indica a direção de crescimento máximo de uma função. Por analogia com a escalada de uma montanha, onde o gradiente enfrenta é onde está a subida mais íngreme até o topo da montanha. Desenvolvendo o exemplo da montanha, lembramos que na verdade precisamos da descida mais íngreme para chegar o mais rápido possível à baixada, ou seja, o mínimo - o local onde a função não aumenta nem diminui. Neste ponto a derivada será igual a zero. Portanto, não precisamos de um gradiente, mas sim de um antigradiente. Para encontrar o antigradiente basta multiplicar o gradiente por -1 (menos um).

Prestemos atenção ao fato de que uma função pode ter vários mínimos, e tendo descido a um deles utilizando o algoritmo proposto a seguir, não conseguiremos encontrar outro mínimo, que pode ser inferior ao encontrado. Vamos relaxar, isso não é uma ameaça para nós! No nosso caso estamos lidando com um único mínimo, já que nossa função Resolvendo a equação da regressão linear simples no gráfico é uma parábola regular. E como todos deveríamos saber muito bem no nosso curso escolar de matemática, uma parábola tem apenas um mínimo.

Depois descobrimos porque precisávamos de um gradiente, e também que o gradiente é um segmento, ou seja, um vetor com determinadas coordenadas, que são precisamente os mesmos coeficientes Resolvendo a equação da regressão linear simples и Resolvendo a equação da regressão linear simples podemos implementar a descida gradiente.

Antes de começar, sugiro ler apenas algumas frases sobre o algoritmo de descida:

  • Determinamos de forma pseudo-aleatória as coordenadas dos coeficientes Resolvendo a equação da regressão linear simples и Resolvendo a equação da regressão linear simples. Em nosso exemplo, determinaremos coeficientes próximos de zero. Esta é uma prática comum, mas cada caso pode ter sua própria prática.
  • Da coordenada Resolvendo a equação da regressão linear simples subtraia o valor da derivada parcial de 1ª ordem no ponto Resolvendo a equação da regressão linear simples. Portanto, se a derivada for positiva, a função aumenta. Portanto, ao subtrair o valor da derivada, avançaremos na direção oposta ao crescimento, ou seja, na direção de descida. Se a derivada for negativa, então a função neste ponto diminui e subtraindo o valor da derivada nos movemos na direção de descida.
  • Realizamos uma operação semelhante com a coordenada Resolvendo a equação da regressão linear simples: subtraia o valor da derivada parcial no ponto Resolvendo a equação da regressão linear simples.
  • Para não pular o mínimo e voar para o espaço profundo, é necessário definir o tamanho do passo na direção da descida. Em geral, você poderia escrever um artigo inteiro sobre como definir o degrau corretamente e como alterá-lo durante o processo de descida para reduzir custos computacionais. Mas agora temos uma tarefa um pouco diferente pela frente, e estabeleceremos o tamanho do passo usando o método científico de “cutucar” ou, como dizem na linguagem comum, empiricamente.
  • Assim que estivermos nas coordenadas fornecidas Resolvendo a equação da regressão linear simples и Resolvendo a equação da regressão linear simples subtrair os valores das derivadas, obtemos novas coordenadas Resolvendo a equação da regressão linear simples и Resolvendo a equação da regressão linear simples. Damos o próximo passo (subtração), já a partir das coordenadas calculadas. E assim o ciclo recomeça continuamente, até que a convergência necessária seja alcançada.

Todos! Agora estamos prontos para partir em busca do desfiladeiro mais profundo da Fossa das Marianas. Vamos começar.

Código para descida gradiente

# напишем функцию градиентного спуска без использования библиотеки NumPy. 
# Функция на вход принимает диапазоны значений x,y, длину шага (по умолчанию=0,1), допустимую погрешность(tolerance)
def gradient_descent_usual(x_us,y_us,l=0.1,tolerance=0.000000000001):
    # сумма значений (все месяца)
    sx = sum(x_us)
    # сумма истинных ответов (выручка за весь период)
    sy = sum(y_us)
    # сумма произведения значений на истинные ответы
    list_xy = []
    [list_xy.append(x_us[i]*y_us[i]) for i in range(len(x_us))]
    sxy = sum(list_xy)
    # сумма квадратов значений
    list_x_sq = []
    [list_x_sq.append(x_us[i]**2) for i in range(len(x_us))]
    sx_sq = sum(list_x_sq)
    # количество значений
    num = len(x_us)
    # начальные значения коэффициентов, определенные псевдослучайным образом
    a = float(random.uniform(-0.5, 0.5))
    b = float(random.uniform(-0.5, 0.5))
    # создаем массив с ошибками, для старта используем значения 1 и 0
    # после завершения спуска стартовые значения удалим
    errors = [1,0]
    # запускаем цикл спуска
    # цикл работает до тех пор, пока отклонение последней ошибки суммы квадратов от предыдущей, не будет меньше tolerance
    while abs(errors[-1]-errors[-2]) > tolerance:
        a_step = a - l*(num*a + b*sx - sy)/num
        b_step = b - l*(a*sx + b*sx_sq - sxy)/num
        a = a_step
        b = b_step
        ab = [a,b]
        errors.append(errors_sq_Kramer_method(ab,x_us,y_us))
    return (ab),(errors[2:])

# запишем массив значений 
list_parametres_gradient_descence = gradient_descent_usual(x_us,y_us,l=0.1,tolerance=0.000000000001)


print ' 33[1m' + ' 33[4m' + "Значения коэффициентов a и b:" + ' 33[0m'
print 'a =', round(list_parametres_gradient_descence[0][0],3)
print 'b =', round(list_parametres_gradient_descence[0][1],3)
print


print ' 33[1m' + ' 33[4m' + "Сумма квадратов отклонений:" + ' 33[0m'
print round(list_parametres_gradient_descence[1][-1],3)
print



print ' 33[1m' + ' 33[4m' + "Количество итераций в градиентном спуске:" + ' 33[0m'
print len(list_parametres_gradient_descence[1])
print

Resolvendo a equação da regressão linear simples

Mergulhamos até o fundo da Fossa das Marianas e lá encontramos todos os mesmos valores de coeficiente Resolvendo a equação da regressão linear simples и Resolvendo a equação da regressão linear simples, que é exatamente o que era de se esperar.

Vamos dar mais um mergulho, só que desta vez o nosso veículo de alto mar estará repleto de outras tecnologias, nomeadamente uma biblioteca NumPy.

Código para descida gradiente (NumPy)

# перед тем определить функцию для градиентного спуска с использованием библиотеки NumPy, 
# напишем функцию определения суммы квадратов отклонений также с использованием NumPy
def error_square_numpy(ab,x_np,y_np):
    y_pred = np.dot(x_np,ab)
    error = y_pred - y_np
    return sum((error)**2)

# напишем функцию градиентного спуска с использованием библиотеки NumPy. 
# Функция на вход принимает диапазоны значений x,y, длину шага (по умолчанию=0,1), допустимую погрешность(tolerance)
def gradient_descent_numpy(x_np,y_np,l=0.1,tolerance=0.000000000001):
    # сумма значений (все месяца)
    sx = float(sum(x_np[:,1]))
    # сумма истинных ответов (выручка за весь период)
    sy = float(sum(y_np))
    # сумма произведения значений на истинные ответы
    sxy = x_np*y_np
    sxy = float(sum(sxy[:,1]))
    # сумма квадратов значений
    sx_sq = float(sum(x_np[:,1]**2))
    # количество значений
    num = float(x_np.shape[0])
    # начальные значения коэффициентов, определенные псевдослучайным образом
    a = float(random.uniform(-0.5, 0.5))
    b = float(random.uniform(-0.5, 0.5))
    # создаем массив с ошибками, для старта используем значения 1 и 0
    # после завершения спуска стартовые значения удалим
    errors = [1,0]
    # запускаем цикл спуска
    # цикл работает до тех пор, пока отклонение последней ошибки суммы квадратов от предыдущей, не будет меньше tolerance
    while abs(errors[-1]-errors[-2]) > tolerance:
        a_step = a - l*(num*a + b*sx - sy)/num
        b_step = b - l*(a*sx + b*sx_sq - sxy)/num
        a = a_step
        b = b_step
        ab = np.array([[a],[b]])
        errors.append(error_square_numpy(ab,x_np,y_np))
    return (ab),(errors[2:])

# запишем массив значений 
list_parametres_gradient_descence = gradient_descent_numpy(x_np,y_np,l=0.1,tolerance=0.000000000001)

print ' 33[1m' + ' 33[4m' + "Значения коэффициентов a и b:" + ' 33[0m'
print 'a =', round(list_parametres_gradient_descence[0][0],3)
print 'b =', round(list_parametres_gradient_descence[0][1],3)
print


print ' 33[1m' + ' 33[4m' + "Сумма квадратов отклонений:" + ' 33[0m'
print round(list_parametres_gradient_descence[1][-1],3)
print

print ' 33[1m' + ' 33[4m' + "Количество итераций в градиентном спуске:" + ' 33[0m'
print len(list_parametres_gradient_descence[1])
print

Resolvendo a equação da regressão linear simples
Valores de coeficiente Resolvendo a equação da regressão linear simples и Resolvendo a equação da regressão linear simples imutável.

Vejamos como o erro mudou durante a descida do gradiente, ou seja, como a soma dos desvios quadrados mudou a cada passo.

Código para traçar somas de desvios quadrados

print 'График№4 "Сумма квадратов отклонений по-шагово"'
plt.plot(range(len(list_parametres_gradient_descence[1])), list_parametres_gradient_descence[1], color='red', lw=3)
plt.xlabel('Steps (Iteration)', size=16)
plt.ylabel('Sum of squared deviations', size=16)
plt.show()

Gráfico nº 4 “Soma dos desvios quadrados durante a descida do gradiente”

Resolvendo a equação da regressão linear simples

No gráfico vemos que a cada passo o erro diminui, e após um certo número de iterações observamos uma linha quase horizontal.

Finalmente, vamos estimar a diferença no tempo de execução do código:

Código para determinar o tempo de cálculo da descida do gradiente

print ' 33[1m' + ' 33[4m' + "Время выполнения градиентного спуска без использования библиотеки NumPy:" + ' 33[0m'
%timeit list_parametres_gradient_descence = gradient_descent_usual(x_us,y_us,l=0.1,tolerance=0.000000000001)
print '***************************************'
print

print ' 33[1m' + ' 33[4m' + "Время выполнения градиентного спуска с использованием библиотеки NumPy:" + ' 33[0m'
%timeit list_parametres_gradient_descence = gradient_descent_numpy(x_np,y_np,l=0.1,tolerance=0.000000000001)

Resolvendo a equação da regressão linear simples

Talvez estejamos fazendo algo errado, mas novamente é uma função simples “escrita em casa” que não usa a biblioteca NumPy supera o tempo de cálculo de uma função usando a biblioteca NumPy.

Mas não estamos parados, mas sim avançando no estudo de outra maneira interessante de resolver a equação de regressão linear simples. Encontrar!

Descida gradiente estocástica

Para compreender rapidamente o princípio de operação da descida gradiente estocástica, é melhor determinar suas diferenças em relação à descida gradiente comum. Nós, no caso do gradiente descendente, nas equações das derivadas de Resolvendo a equação da regressão linear simples и Resolvendo a equação da regressão linear simples usou as somas dos valores de todos os recursos e respostas verdadeiras disponíveis na amostra (ou seja, as somas de todos Resolvendo a equação da regressão linear simples и Resolvendo a equação da regressão linear simples). Na descida gradiente estocástica, não usaremos todos os valores presentes na amostra, mas em vez disso, selecionaremos pseudo-aleatoriamente o chamado índice de amostra e usaremos seus valores.

Por exemplo, se o índice for determinado como o número 3 (três), então tomamos os valores Resolvendo a equação da regressão linear simples и Resolvendo a equação da regressão linear simples, então substituímos os valores nas equações derivadas e determinamos novas coordenadas. Então, tendo determinado as coordenadas, determinamos novamente pseudo-aleatoriamente o índice da amostra, substituímos os valores correspondentes ao índice nas equações diferenciais parciais e determinamos as coordenadas de uma nova maneira Resolvendo a equação da regressão linear simples и Resolvendo a equação da regressão linear simples etc. até que a convergência fique verde. À primeira vista, pode não parecer que isso funcione, mas funciona. É verdade que vale ressaltar que o erro não diminui a cada passo, mas certamente há uma tendência.

Quais são as vantagens da descida gradiente estocástica em relação à convencional? Se o tamanho da nossa amostra for muito grande e medido em dezenas de milhares de valores, será muito mais fácil processar, digamos, milhares deles aleatoriamente, em vez de toda a amostra. É aqui que entra em jogo a descida gradiente estocástica. No nosso caso, é claro, não notaremos muita diferença.

Vejamos o código.

Código para descida gradiente estocástica

# определим функцию стох.град.шага
def stoch_grad_step_usual(vector_init, x_us, ind, y_us, l):
#     выбираем значение икс, которое соответствует случайному значению параметра ind 
# (см.ф-цию stoch_grad_descent_usual)
    x = x_us[ind]
#     рассчитывыаем значение y (выручку), которая соответствует выбранному значению x
    y_pred = vector_init[0] + vector_init[1]*x_us[ind]
#     вычисляем ошибку расчетной выручки относительно представленной в выборке
    error = y_pred - y_us[ind]
#     определяем первую координату градиента ab
    grad_a = error
#     определяем вторую координату ab
    grad_b = x_us[ind]*error
#     вычисляем новый вектор коэффициентов
    vector_new = [vector_init[0]-l*grad_a, vector_init[1]-l*grad_b]
    return vector_new


# определим функцию стох.град.спуска
def stoch_grad_descent_usual(x_us, y_us, l=0.1, steps = 800):
#     для самого начала работы функции зададим начальные значения коэффициентов
    vector_init = [float(random.uniform(-0.5, 0.5)), float(random.uniform(-0.5, 0.5))]
    errors = []
#     запустим цикл спуска
# цикл расчитан на определенное количество шагов (steps)
    for i in range(steps):
        ind = random.choice(range(len(x_us)))
        new_vector = stoch_grad_step_usual(vector_init, x_us, ind, y_us, l)
        vector_init = new_vector
        errors.append(errors_sq_Kramer_method(vector_init,x_us,y_us))
    return (vector_init),(errors)


# запишем массив значений 
list_parametres_stoch_gradient_descence = stoch_grad_descent_usual(x_us, y_us, l=0.1, steps = 800)

print ' 33[1m' + ' 33[4m' + "Значения коэффициентов a и b:" + ' 33[0m'
print 'a =', round(list_parametres_stoch_gradient_descence[0][0],3)
print 'b =', round(list_parametres_stoch_gradient_descence[0][1],3)
print


print ' 33[1m' + ' 33[4m' + "Сумма квадратов отклонений:" + ' 33[0m'
print round(list_parametres_stoch_gradient_descence[1][-1],3)
print

print ' 33[1m' + ' 33[4m' + "Количество итераций в стохастическом градиентном спуске:" + ' 33[0m'
print len(list_parametres_stoch_gradient_descence[1])

Resolvendo a equação da regressão linear simples

Observamos cuidadosamente os coeficientes e nos surpreendemos perguntando “Como pode ser isso?” Temos outros valores de coeficiente Resolvendo a equação da regressão linear simples и Resolvendo a equação da regressão linear simples. Talvez a descida gradiente estocástica tenha encontrado parâmetros mais ideais para a equação? Infelizmente não. Basta olhar a soma dos desvios quadrados e ver que com novos valores dos coeficientes o erro é maior. Não temos pressa em nos desesperar. Vamos construir um gráfico da mudança de erro.

Código para traçar a soma dos desvios quadrados na descida gradiente estocástica

print 'График №5 "Сумма квадратов отклонений по-шагово"'
plt.plot(range(len(list_parametres_stoch_gradient_descence[1])), list_parametres_stoch_gradient_descence[1], color='red', lw=2)
plt.xlabel('Steps (Iteration)', size=16)
plt.ylabel('Sum of squared deviations', size=16)
plt.show()

Gráfico nº 5 “Soma dos desvios quadrados durante a descida gradiente estocástica”

Resolvendo a equação da regressão linear simples

Olhando o cronograma, tudo se encaixa e agora vamos consertar tudo.

Então o que aconteceu? Aconteceu o seguinte. Quando selecionamos um mês aleatoriamente, então é para o mês selecionado que nosso algoritmo busca reduzir o erro no cálculo da receita. Depois selecionamos outro mês e repetimos o cálculo, mas reduzimos o erro para o segundo mês selecionado. Agora lembre-se que os primeiros dois meses se desviam significativamente da linha da equação de regressão linear simples. Isso significa que quando qualquer um desses dois meses é selecionado, ao reduzir o erro de cada um deles, nosso algoritmo aumenta seriamente o erro de toda a amostra. Então o que fazer? A resposta é simples: você precisa reduzir o degrau de descida. Afinal, ao reduzir o degrau de descida, o erro também deixará de “saltar” para cima e para baixo. Ou melhor, o erro de “salto” não vai parar, mas não vai parar tão rápido :) Vamos verificar.

Código para executar SGD com incrementos menores

# запустим функцию, уменьшив шаг в 100 раз и увеличив количество шагов соответсвующе 
list_parametres_stoch_gradient_descence = stoch_grad_descent_usual(x_us, y_us, l=0.001, steps = 80000)

print ' 33[1m' + ' 33[4m' + "Значения коэффициентов a и b:" + ' 33[0m'
print 'a =', round(list_parametres_stoch_gradient_descence[0][0],3)
print 'b =', round(list_parametres_stoch_gradient_descence[0][1],3)
print


print ' 33[1m' + ' 33[4m' + "Сумма квадратов отклонений:" + ' 33[0m'
print round(list_parametres_stoch_gradient_descence[1][-1],3)
print



print ' 33[1m' + ' 33[4m' + "Количество итераций в стохастическом градиентном спуске:" + ' 33[0m'
print len(list_parametres_stoch_gradient_descence[1])

print 'График №6 "Сумма квадратов отклонений по-шагово"'
plt.plot(range(len(list_parametres_stoch_gradient_descence[1])), list_parametres_stoch_gradient_descence[1], color='red', lw=2)
plt.xlabel('Steps (Iteration)', size=16)
plt.ylabel('Sum of squared deviations', size=16)
plt.show()

Resolvendo a equação da regressão linear simples

Gráfico nº 6 “Soma dos desvios quadrados durante a descida gradiente estocástica (80 mil passos)”

Resolvendo a equação da regressão linear simples

Os coeficientes melhoraram, mas ainda não são os ideais. Hipoteticamente, isso pode ser corrigido desta forma. Selecionamos, por exemplo, nas últimas 1000 iterações os valores dos coeficientes com os quais foi cometido o erro mínimo. É verdade que para isso também teremos que anotar os valores dos próprios coeficientes. Não faremos isso, mas sim ficaremos atentos ao cronograma. Parece suave e o erro parece diminuir uniformemente. Na verdade isso não é verdade. Vejamos as primeiras 1000 iterações e compará-las com as últimas.

Código para gráfico SGD (primeiros 1000 passos)

print 'График №7 "Сумма квадратов отклонений по-шагово. Первые 1000 итераций"'
plt.plot(range(len(list_parametres_stoch_gradient_descence[1][:1000])), 
         list_parametres_stoch_gradient_descence[1][:1000], color='red', lw=2)
plt.xlabel('Steps (Iteration)', size=16)
plt.ylabel('Sum of squared deviations', size=16)
plt.show()

print 'График №7 "Сумма квадратов отклонений по-шагово. Последние 1000 итераций"'
plt.plot(range(len(list_parametres_stoch_gradient_descence[1][-1000:])), 
         list_parametres_stoch_gradient_descence[1][-1000:], color='red', lw=2)
plt.xlabel('Steps (Iteration)', size=16)
plt.ylabel('Sum of squared deviations', size=16)
plt.show()

Gráfico nº 7 “Soma dos desvios quadrados SGD (primeiros 1000 passos)”

Resolvendo a equação da regressão linear simples

Gráfico nº 8 “Soma dos desvios quadrados SGD (últimos 1000 passos)”

Resolvendo a equação da regressão linear simples

Logo no início da descida, observamos uma diminuição do erro bastante uniforme e acentuada. Nas últimas iterações, vemos que o erro gira e volta do valor de 1,475 e em alguns momentos até se iguala a esse valor ótimo, mas depois ainda sobe... Repito, você pode anotar os valores do coeficientes Resolvendo a equação da regressão linear simples и Resolvendo a equação da regressão linear simplese selecione aqueles para os quais o erro é mínimo. Porém, tivemos um problema mais sério: tivemos que dar 80 mil passos (ver código) para chegar a valores próximos do ideal. E isso já contradiz a ideia de economizar tempo de cálculo com descida gradiente estocástica em relação à descida gradiente. O que pode ser corrigido e melhorado? Não é difícil perceber que nas primeiras iterações estamos descendo com confiança e, portanto, devemos deixar um grande passo nas primeiras iterações e reduzir o passo à medida que avançamos. Não faremos isso neste artigo - já é muito longo. Quem quiser pode pensar por si mesmo como fazer isso, não é difícil :)

Agora vamos realizar a descida gradiente estocástica usando a biblioteca NumPy (e não vamos tropeçar nas pedras que identificamos anteriormente)

Código para descida gradiente estocástica (NumPy)

# для начала напишем функцию градиентного шага
def stoch_grad_step_numpy(vector_init, X, ind, y, l):
    x = X[ind]
    y_pred = np.dot(x,vector_init)
    err = y_pred - y[ind]
    grad_a = err
    grad_b = x[1]*err
    return vector_init - l*np.array([grad_a, grad_b])

# определим функцию стохастического градиентного спуска
def stoch_grad_descent_numpy(X, y, l=0.1, steps = 800):
    vector_init = np.array([[np.random.randint(X.shape[0])], [np.random.randint(X.shape[0])]])
    errors = []
    for i in range(steps):
        ind = np.random.randint(X.shape[0])
        new_vector = stoch_grad_step_numpy(vector_init, X, ind, y, l)
        vector_init = new_vector
        errors.append(error_square_numpy(vector_init,X,y))
    return (vector_init), (errors)

# запишем массив значений 
list_parametres_stoch_gradient_descence = stoch_grad_descent_numpy(x_np, y_np, l=0.001, steps = 80000)

print ' 33[1m' + ' 33[4m' + "Значения коэффициентов a и b:" + ' 33[0m'
print 'a =', round(list_parametres_stoch_gradient_descence[0][0],3)
print 'b =', round(list_parametres_stoch_gradient_descence[0][1],3)
print


print ' 33[1m' + ' 33[4m' + "Сумма квадратов отклонений:" + ' 33[0m'
print round(list_parametres_stoch_gradient_descence[1][-1],3)
print



print ' 33[1m' + ' 33[4m' + "Количество итераций в стохастическом градиентном спуске:" + ' 33[0m'
print len(list_parametres_stoch_gradient_descence[1])
print

Resolvendo a equação da regressão linear simples

Os valores acabaram sendo quase os mesmos de descer sem usar NumPy. No entanto, isso é lógico.

Vamos descobrir quanto tempo levaram as descidas gradientes estocásticas.

Código para determinação do tempo de cálculo do SGD (80 mil passos)

print ' 33[1m' + ' 33[4m' +
"Время выполнения стохастического градиентного спуска без использования библиотеки NumPy:"
+ ' 33[0m'
%timeit list_parametres_stoch_gradient_descence = stoch_grad_descent_usual(x_us, y_us, l=0.001, steps = 80000)
print '***************************************'
print

print ' 33[1m' + ' 33[4m' +
"Время выполнения стохастического градиентного спуска с использованием библиотеки NumPy:"
+ ' 33[0m'
%timeit list_parametres_stoch_gradient_descence = stoch_grad_descent_numpy(x_np, y_np, l=0.001, steps = 80000)

Resolvendo a equação da regressão linear simples

Quanto mais dentro da floresta, mais escuras são as nuvens: mais uma vez, a fórmula “auto-escrita” mostra o melhor resultado. Tudo isso sugere que deve haver maneiras ainda mais sutis de usar a biblioteca NumPy, o que realmente acelera as operações de computação. Neste artigo não aprenderemos sobre eles. Haverá algo em que pensar no seu tempo livre :)

Vamos resumir

Antes de resumir, gostaria de responder a uma pergunta que muito provavelmente surgiu do nosso caro leitor. Por que, de fato, tanta “tortura” com descidas, por que precisamos subir e descer a montanha (principalmente descer) para encontrar a preciosa planície, se temos em mãos um dispositivo tão poderoso e simples, no forma de solução analítica, que nos teletransporta instantaneamente para o lugar certo?

A resposta a esta pergunta está na superfície. Agora vimos um exemplo muito simples, no qual a verdadeira resposta é Resolvendo a equação da regressão linear simples depende de um sinal Resolvendo a equação da regressão linear simples. Você não vê isso com frequência na vida, então vamos imaginar que temos 2, 30, 50 ou mais signos. Vamos adicionar a isso milhares, ou mesmo dezenas de milhares de valores para cada atributo. Neste caso, a solução analítica pode não resistir ao teste e falhar. Por sua vez, a descida do gradiente e suas variações nos aproximarão lenta mas seguramente do objetivo - o mínimo da função. E não se preocupe com a velocidade - provavelmente veremos maneiras que nos permitirão definir e regular o comprimento do passo (ou seja, a velocidade).

E agora o breve resumo real.

Em primeiro lugar, espero que o material apresentado no artigo ajude os “cientistas de dados” iniciantes a entender como resolver equações de regressão linear simples (e não apenas).

Em segundo lugar, examinamos várias maneiras de resolver a equação. Agora, dependendo da situação, podemos escolher aquele que melhor se adapta para resolver o problema.

Terceiro, vimos o poder das configurações adicionais, ou seja, o comprimento do passo de descida gradiente. Este parâmetro não pode ser negligenciado. Conforme observado acima, para reduzir o custo dos cálculos, o comprimento do passo deve ser alterado durante a descida.

Em quarto lugar, no nosso caso, as funções “escritas em casa” mostraram os melhores resultados de tempo para cálculos. Isto provavelmente se deve ao uso não muito profissional dos recursos da biblioteca NumPy. Mas seja como for, a seguinte conclusão surge. Por um lado, às vezes vale a pena questionar opiniões estabelecidas e, por outro lado, nem sempre vale a pena complicar tudo - pelo contrário, às vezes uma forma mais simples de resolver um problema é mais eficaz. E como nosso objetivo era analisar três abordagens para resolver uma equação de regressão linear simples, o uso de funções “auto-escritas” foi suficiente para nós.

Literatura (ou algo parecido)

1. Regressão linear

http://statistica.ru/theory/osnovy-lineynoy-regressii/

2. Método dos mínimos quadrados

mathprofi.ru/metod_naimenshih_kvadratov.html

3. Derivada

www.mathprofi.ru/chastnye_proizvodnye_primery.html

4. Gradiente

mathprofi.ru/proizvodnaja_po_napravleniju_i_gradient.html

5. Descida gradiente

habr.com/en/post/471458

habr.com/en/post/307312

artemarakcheev.com//2017-12-31/linear_regression

6. Biblioteca NumPy

docs.scipy.org/doc/numpy-1.10.1/reference/generated/numpy.linalg.solve.html

docs.scipy.org/doc/numpy-1.10.0/reference/generated/numpy.linalg.pinv.html

pythonworld.ru/numpy/2.html

Fonte: habr.com

Adicionar um comentário