preview
Rede neural na prática: Mínimos Quadrados

Rede neural na prática: Mínimos Quadrados

MetaTrader 5Aprendizado de máquina | 25 abril 2024, 14:18
135 0
Daniel Jose
Daniel Jose

Introdução

Olá pessoal, e sejam bem-vindos a mais um artigo sobre Rede Neural.

No artigo anterior Rede neural na prática: Reta Secante , começamos a ver a questão da matemática sendo aplicada na prática. Porém aquilo foi apenas uma leve e breve introdução ao assunto. Vimos que a principal operação matemática a ser utilizada é uma função trigonométrica. E diferente do que muitos imaginam, não é a função tangente. Mas sim a função secante. Apesar de tudo aquilo parecer bastante confuso, neste primeiro momento. Você em breve verá, que é bem mais simples do que parece. E diferente do que muitos fazem, que é justamente gerar uma baita de uma confusão, a respeito da parte matemática. A coisa toda se desenha de maneira bastante natural.


Algo estranho que não entendi.

Porém existe uma pequena falha, se é que posso dizer assim, na qual eu não consegui entende. Já que a mesma não faz sentido. Pelo menos não para mim. No entanto, como pode servir de alerta para outras pessoas, que por ventura vierem a tentar fazer a mesma coisa. Deixei as coisas daquela forma. Mas aqui vamos corrigir o problema. Isto por que se não o corrigirmos teremos muitos outros problemas na parte matemática da coisa.

No artigo anterior, temos o seguinte fragmento, mostrado abaixo:

19. //+------------------------------------------------------------------+
20. void Func_01(void)
21. {
22.     int A[] {
23.                 -100,  150,
24.                  -80,   50,
25.                   30,  -80,
26.                  100, -120
27.             };
28. 
29.     canvas.LineVertical(global.x, global.y - _SizeLine, global.y + _SizeLine, ColorToARGB(clrRoyalBlue, 255));
30.     canvas.LineHorizontal(global.x - _SizeLine, global.x + _SizeLine, global.y, ColorToARGB(clrRoyalBlue, 255));
31. 
32.     for (uint c0 = 0, c1 = 0; c1 < A.Size(); c0++)
33.         canvas.FillCircle(global.x + A[c1++], global.y + A[c1++], 5, ColorToARGB(clrRed, 255));
34. }
35. //+------------------------------------------------------------------+

Este fragmento tem como objetivo, plotar no gráfico, pequenos círculos, cuja posição está no array A, presente na linha 22. Até aqui nada de errado. A ideia é justamente esta. Porém ao plotar os pontos, você verá a imagem abaixo.


Estes pontos estão localizados nas posições erradas. Eles estão invertidos com relação ao X e Y. Mas por que ?!?! Bem, sinceramente não sei explicar. Isto por que na linha 33 no fragmento, capturamos os valores presentes no array, e logo depois somamos uma posição. Fazendo assim, o compilador deveria entender que estamos apontando para o próximo valor no array. De fato, isto acontece, caso não ocorresse teríamos a geração de um erro de acesso não autorizado a memória. O que seria visto como um erro de range, e a aplicação encerraria.

Porém o valor que está sendo somado a global.x não são os valores de índex par, mas sim os de índex ímpar. E os valores somados a global.y são os valores de índex par, sendo que o esperado seria de índex ímpar.

Só percebi isto, ao começar a escrever a parte matemática para este artigo. Não compreendendo o motivo para tal fato. Já que os cálculos não estava batendo com o resultado apresentado pela aplicação. Assim decidi, checar o motivo. Você pode fazer a mesma coisa modificando ligeiramente o fragmento como mostrado abaixo.

19. //+------------------------------------------------------------------+
20. void Func_01(void)
21. {
22.     int A[]={
23.                 -100,  150,
24.                  -80,   50,
25.                   30,  -80,
26.                  100, -120
27.             };
28. 
29.     int vx, vy;
30.     string s = "";
31. 
32.     canvas.LineVertical(global.x, global.y - _SizeLine, global.y + _SizeLine, ColorToARGB(clrRoyalBlue, 255));
33.     canvas.LineHorizontal(global.x - _SizeLine, global.x + _SizeLine, global.y, ColorToARGB(clrRoyalBlue, 255));
34. 
35.     for (uint c0 = 0, c1 = 0; c1 < A.Size(); c0++)
36.     {
37.         canvas.FillCircle(global.x + (vx = A[c1++]), global.y + (vy = A[c1++]), 5, ColorToARGB(clrRed, 255));
38.         s += StringFormat("[ %d <> %d ] ", vx, vy);
39.     }
40.     canvas.TextOut(global.x - 200, global.y + _SizeLine + 70, s, ColorToARGB(clrBlack));
41. }
42. //+------------------------------------------------------------------+

Esta mudança nos permite enxergar o que está acontecendo. O resultado é visto na imagem abaixo:


Observe os valores dentro dos colchetes. Eles são o resultado da captura feita na linha 37. Ou seja, estamos capturando os valores que são usados para posicionar os círculos no gráfico. Porém note que eles estão invertidos frente ao que está declarado no array. O que é bem estranho. Bem devido a este fato, precisamos modificar o código como mostrado no próximo fragmento.

19. //+------------------------------------------------------------------+
20. void Func_01(void)
21. {
22.     int A[]={
23.                 -100,  150,
24.                  -80,   50,
25.                   30,  -80,
26.                  100, -120
27.             };
28. 
29.     int vx, vy;
30.     string s = "";
31. 
32.     canvas.LineVertical(global.x, global.y - _SizeLine, global.y + _SizeLine, ColorToARGB(clrRoyalBlue, 255));
33.     canvas.LineHorizontal(global.x - _SizeLine, global.x + _SizeLine, global.y, ColorToARGB(clrRoyalBlue, 255));
34. 
35.     for (uint c0 = 0, c1 = 0; c1 < A.Size(); c0++)
36.     {
37.         vx = A[c1++];
38.         vy = A[c1++];
39.         canvas.FillCircle(global.x + vx, global.y + vy, 5, ColorToARGB(clrRed, 255));
40.         s += StringFormat("[ %d <> %d ] ", vx, vy);
41.     }
42.     canvas.TextOut(global.x - 200, global.y + _SizeLine + 70, s, ColorToARGB(clrBlack));
43. }
44. //+------------------------------------------------------------------+

Bem, o fato de usamos agora este fragmento, faz com que o resultado obtido seja o que é visto na imagem logo abaixo.


Agora você pode notar que os valores nos colchetes, correspondem ao que está declarado no array. Peço desculpa a todos por não ter notado tal falha antes. Mas que isto sirva de alerta a todos, que desejarem fazer as coisas de uma determinada maneira. Como não entendi por que os índex estavam sendo invertidos, não sei como explicar por que a falha acontece. Bem, feita esta correção, que de fato é necessária podemos continuar para a próxima coisa a ser implementada e mostrada.


Preparando para fazer contas e mais contas.

Muito bem, o que faremos a partir deste momento, pode ser bastante confuso, se eu simplesmente viesse a jogar o código na cara de vocês. Como quero que você, meu caro leitor, compreenda exatamente o que e por que estaremos fazendo certas coisas. Vou explicar a coisa bem devagar. Então leia com calma o que será explicado. Modifique o código conforme for mostrando. E o experimente e procure entender o que está acontecendo. Quando entender, passe para a próxima etapa. Não tente ir direto aos finalmente, pois se fizer isto, pode ser que você não consiga entender absolutamente nada do que estará ocorrendo.

A primeira coisa que faremos é modificar ligeiramente o código como mostrado no fragmento abaixo.

19. //+------------------------------------------------------------------+
20. void Func_01(void)
21. {
22.     int A[]={
23.                 -100,  150,
24.                  -80,   50,
25.                   30,  -80,
26.                  100, -120
27.             };
28. 
29.     int vx, vy;
30.     string s = "";
31.     double ly;
32. 
33.     canvas.LineVertical(global.x, global.y - _SizeLine, global.y + _SizeLine, ColorToARGB(clrRoyalBlue, 255));
34.     canvas.LineHorizontal(global.x - _SizeLine, global.x + _SizeLine, global.y, ColorToARGB(clrRoyalBlue, 255));
35. 
36.     for (uint c0 = 0, c1 = 0; c1 < A.Size(); c0++)
37.     {
38.         vx = A[c1++];
39.         vy = A[c1++];
40.         canvas.FillCircle(global.x + vx, global.y + vy, 5, ColorToARGB(clrRed, 255));
41.         ly = (vx * MathTan(_ToRadians(global.Angle))) - vy;
42.         canvas.LineVertical(global.x + vx, global.y + vy, global.y + (int)(ly + vy), ColorToARGB(clrPurple));
43.         s += StringFormat("sy%d = %.4f  | ", c0, ly);
44.     }
45.     canvas.TextOut(global.x - 200, global.y + _SizeLine + 50, s, ColorToARGB(clrBlack));
46. }
47. //+------------------------------------------------------------------+
48. void NewAngle(const char direct, const double step = 0.2)
49. {
50.     canvas.Erase(ColorToARGB(clrWhite, 255));
51. 
52.     global.Angle = (MathAbs(global.Angle + (step * direct)) < 90 ? global.Angle + (step * direct) : global.Angle);
53.     canvas.TextOut(global.x + _SizeLine + 50, global.y, StringFormat("%.2f", MathAbs(global.Angle)), ColorToARGB(clrBlack));
54.     canvas.TextOut(global.x, global.y + _SizeLine + 20, StringFormat("f(x) = %.8fx", -MathTan(_ToRadians(global.Angle))), ColorToARGB(clrBlack));
55.     canvas.Line(
56.                 global.x - (int)(_SizeLine * cos(_ToRadians(global.Angle))), 
57.                 global.y - (int)(_SizeLine * sin(_ToRadians(global.Angle))), 
58.                 global.x + (int)(_SizeLine * cos(_ToRadians(global.Angle))), 
59.                 global.y + (int)(_SizeLine * sin(_ToRadians(global.Angle))), 
60.                 ColorToARGB(clrForestGreen)
61.             );
62.        
63.     Func_01();
64. 
65.     canvas.Update(true);
66. }
67. //+------------------------------------------------------------------+

Apesar das coisas não estarem sendo feitas da melhor maneira. O resultado visto na animação abaixo, apresenta exatamente o que desejamos visualizar.


O que estamos fazendo é simplesmente criando uma linha purpura. Está nos diz a distância entre a reta e o ponto. Preste atenção a isto. O comprimento da linha é conseguido na linha 41 do fragmento. Apesar de o cálculo não está exatamente como deveria ser, ele funciona. Então vamos com calma, pois precisaremos mudar este cálculo depois. Na linha 43, criamos uma string que nos permite saber qual o comprimento desta linha purpura. Vendo a animação você percebe, que existem comprimentos negativos, o que não faz sentido. Mas não se preocupe com isto por hora. Quero primeiro que você entenda o que estamos fazendo.

Agora quero que você pense um pouco, meu caro leitor. A função da reta, é calculada sobre o coeficiente de inclinação. Este coeficiente é obtido pelo ângulo que estamos modificando, ao pressionar a seta para direita ou para esquerda. E o passo com que esta inclinação muda, é dado pelo valor de step, presente na linha 52.

Ok, então observando o comprimento de cada uma das linhas purpuras. Você pode com base em um pouco de paciência, conseguir com que a inclinação da reta, seja a melhor possível, fazendo com que o tamanho de cada uma das linhas purpuras pare de subir. Ele pode diminuir, porém não deve crescer. Quando você conseguir fazer com que nenhum dos comprimentos cresça, ok, você terá o coeficiente de inclinação ideal.

Mas perceba o seguinte: No caso precisamos olhar quatro elementos. O que é relativamente simples. Porém acontecer de termos centenas ou milhares de pontos no array. Isto tornaria a tarefa, de ajustar os valores para que eles não cresçam mais, algo extremante complicado de ser feito. Mas podemos usar uma artimanha matemática. Que é o seguinte: Se somarmos os valores, do comprimento de cada uma das linhas purpuras. Tudo que precisaríamos fazer, seria olha apenas um único valor. Se ele começasse a crescer teríamos passado do ponto ideal do coeficiente de inclinação. Simples não é mesmo. Então modificamos mais uma vez o fragmento para o que é visto abaixo.

19. //+------------------------------------------------------------------+
20. void Func_01(void)
21. {
22.     int A[]={
23.                 -100,  150,
24.                  -80,   50,
25.                   30,  -80,
26.                  100, -120
27.             };
28. 
29.     int vx, vy;
30.     double ly, err;
31. 
32.     canvas.LineVertical(global.x, global.y - _SizeLine, global.y + _SizeLine, ColorToARGB(clrRoyalBlue, 255));
33.     canvas.LineHorizontal(global.x - _SizeLine, global.x + _SizeLine, global.y, ColorToARGB(clrRoyalBlue, 255));
34. 
35.     err = 0;
36.     for (uint c0 = 0, c1 = 0; c1 < A.Size(); c0++)
37.     {
38.         vx = A[c1++];
39.         vy = A[c1++];
40.         canvas.FillCircle(global.x + vx, global.y + vy, 5, ColorToARGB(clrRed, 255));
41.         ly = (vx * MathTan(_ToRadians(global.Angle))) - vy;
42.         canvas.LineVertical(global.x + vx, global.y + vy, global.y + (int)(ly + vy), ColorToARGB(clrPurple));
43.         err += MathAbs(ly);
44.     }
45.     canvas.TextOut(global.x - 200, global.y + _SizeLine + 20, StringFormat("Error: %.8f", err), ColorToARGB(clrRed));
46. }
47. //+------------------------------------------------------------------+

O resultado pode ser visto na animação abaixo. E note que é bem mais simples de ajustar as coisas.


Ajustando o quadrante.

Antes de realmente colocarmos nossos pés na parte matemática da coisa. Precisamos fazer uma pequena modificação. Se bem que ela não irá de fato influenciar o que iremos fazer. Mas irá influenciar as coisas caso você venha a tentar usar o que será explicado em outros programas. Ou mesmo tente fazer algumas experiências na mão. E sim, muitas das vezes, primeiro começamos a fazer as coisas experimentando as mesmas manualmente. Fazendo as contas no lápis mesmo. Isto evita que ao programarmos as coisas venhamos a cometer o erro capital de acreditar nas contas feitas. Mesmo quando elas estão completamente erradas.

Não é raro, muitos programadores iniciantes cometerem tal pecado. Isto muitas das vezes pode custar bastante caro. Já que você acaba ficando focado em tentar resolver as coisas, da forma errada. Todo bom programador, também é uma pessoa extremamente desconfiada, dos cálculos que estão sendo feitos. Ele sempre procura testar as coisas de diversas formas diferentes, antes de bater o martelo e dizer: Pronto, agora eu posso confiar no resultado que me será apresentado.

Estou dizendo isto, pois apesar de muitos ignorarem, e eu mesmo muitas das vezes faço isto. Quando criamos uma apresentação gráfica, estaremos sempre trabalhando no quarto quadrante do plano cartesiano. E isto influencia tudo. Não para os cálculos a serem feitos. Mas sim para aqueles que desejam efetuar os mesmos cálculos na mão, ou em algum outro programa. Tipo MatLab, SCILab ou mesmo o bom e velho Excel. Já que no Excel, temos a possibilidade de criar uma apresentação gráfica dos valores e fórmulas usadas aqui.

Muito bem, mas o que tudo isto tem haver, com o que iremos fazer ?!?! Bem, meu caro leitor, se você olhar a matriz de dados, que está no programa, irá notar que ela é um pouco estranha frente ao que está sendo apresentado no gráfico. Se você plotar os mesmos valores em um outro programa, de plotagem de gráficos, verá que o eixo Y está girado. Mas como assim ?!?! Não entendi. Calma, veja as imagens abaixo para entender.


Esta imagem acima é vista quando plotamos no MetaTrader 5. Porém ao plotarmos os mesmos valores no Excel por exemplo o gráfico apresentado será o visto logo abaixo:


Este tipo de coisa torna bastante confusa, grande parte dos testes que alguém possa vir a desejar fazer. Isto para comprovar que de fato os cálculos estejam sendo feitos da forma correta.

Como corrigimos a questão de apresentar os dados de forma adequada no tópico anterior. Precisamos agora corrigir este pequeno problema. Que é justamente o fato de que o eixo Y está girado, ou espelhado, como queira dizer.

E por que esta falha na apresentação acontece ?!?! O motivo é simples e falei dele no começo do tópico. A tela em que plotamos os dados está no quarto quadrante. Isto por que a origem dela, ou seja, os pontos ( 0, 0 ) estão no canto superior esquerdo. Agora preste atenção. Mesmo que você pegue e transfira este ponto de origem para o centro da tela, esta transferência acontece de forma virtual. Isto por que de fato você não muda o ponto de origem. Você apenas move a apresentação de parte dos cálculos para um outro ponto de origem. Por isto que no programa estamos somando o valor do ponto da matriz, a um outro conjunto de pontos. Assim conseguimos virtualmente transferir a origem do canto superior esquerdo para qualquer outra localização na tela.

Mas como você pode ter notado na imagem acima, existe um problema, na forma como estou fazendo a virtualização. Pois o eixo Y está girado, ou espelhado. Para corrigir isto precisaremos mudar só um pouco, algumas pequenas coisas no código. Algo simples, mas que tornará mais claro as coisas.

Então no código fazemos as seguintes mudanças vista no fragmento abaixo:

19. //+------------------------------------------------------------------+
20. void Func_01(void)
21. {
22.     int A[]={
23.                 -100, -150,
24.                  -80,  -50,
25.                   30,   80,
26.                  100,  120
27.             };
28. 
29.     int vx, vy;
30.     double ly, err;
31. 
32.     canvas.LineVertical(global.x, global.y - _SizeLine, global.y + _SizeLine, ColorToARGB(clrRoyalBlue, 255));
33.     canvas.LineHorizontal(global.x - _SizeLine, global.x + _SizeLine, global.y, ColorToARGB(clrRoyalBlue, 255));
34. 
35.     err = 0;
36.     for (uint c0 = 0, c1 = 0; c1 < A.Size(); c0++)
37.     {
38.         vx = A[c1++];
39.         vy = A[c1++];
40.         canvas.FillCircle(global.x + vx, global.y - vy, 5, ColorToARGB(clrRed, 255));
41.         ly = vy - (vx * -MathTan(_ToRadians(global.Angle)));
42.         canvas.LineVertical(global.x + vx, global.y - vy, global.y - (int)(ly + vy), ColorToARGB(clrPurple));
43.         err += MathAbs(ly);
44.     }
45.     canvas.TextOut(global.x - 200, global.y + _SizeLine + 20, StringFormat("Error: %.8f", err), ColorToARGB(clrRed));
46. }
47. //+------------------------------------------------------------------+

Note como a coisa é extremamente sútil. Primeiro modificamos os valores na matriz. E depois modificamos na linha 40 o cálculo para posicionar os pontos. Mas também foi preciso modificar na linha 41 e 42, que é a parte onde o cálculo para que as linhas em purpura, viessem a se ligar corretamente aos pontos, fosse efetuado. Este tipo de coisa, apesar de ser basicamente apenas estética. Traz um alívio ao coração, daqueles que pretendem de alguma forma, efetuar um estudo mais a fundo sobre o assunto, do qual estamos tratando. Isto por que eles poderão comparar os gráficos gerados, sem correr o risco, de vir a imaginar que algo está sendo calculado de forma equivocada. Seja em um programa seja em outro.

Note que agora temos uma tarefa, realmente bem mais simples de ser cumprida. Já que se o valor de erro aumentar significa que estamos indo na direção errada. Se ele diminuir estamos indo na direção correta.

Legal, mas agora vem a questão, e deste momento em diante começaremos a entrar no que rege o assunto de redes neurais. Ajustar a inclinação da reta tangente, ou coeficiente de inclinação é bem simples, isto quando observamos o valor de erro. Mas será que existe uma forma de ajustar ele de maneira automática. Ou melhor dizendo, criar algum mecanismo que permita que a máquina procure o melhor valor para o coeficiente. Lembrando que no momento estamos usando apenas uma única variável no sistema. Já que a reta tangente passa exatamente pelo ponto (0, 0) que é a origem dos planos cartesianos. Porém nem sempre é assim. Mas vamos resolver primeiro esta questão mais simples. Que é justamente onde temos apenas uma única variável para ajustar.

Se você entendeu o artigo anterior deve ter notado que falei como calcular a reta secante, para que ela se torne a reta tangente. Bem, mas apesar de aquilo funcionar. Vamos pensar em uma outra hipótese um pouco mais simples. Onde não precisaremos fazer um loop para encontra o valor do coeficiente angular. Vamos tentar encontrar uma fórmula para que o coeficiente seja calculado de maneira mais rápida e simples.

Para fazer isto precisamos usar algumas artimanhas matemáticas. E para separar melhor as coisas, vamos ver isto em um novo tópico.


Procurando a menor área possível.

Se você, meu caro leitor, procurou se iterar sobre o assunto de inteligência artificial. Deve ter notado, algo bem recorrente sendo dito. Mas não confunda inteligência artificial com redes neurais. Ambas coisas apesar de estarem correlacionadas não são trabalhadas da mesma forma. Isto no ponto de vista da programação. Existem algumas pequenas diferenças. Mas aqui, o que pretendendo fazer, é criar uma rede neural que consiga encontrar a equação que represente da melhor maneira possível, os valores no banco de dados. Este tipo de cálculo é feita por uma rede neural. A inteligência artificial pesquisa no banco. A rede neural cria o banco.

No caso poderíamos já começar a usar neurônios para isto. Mas, ao meu ver, isto ainda é um tanto cedo. Podemos, nos mesmos criamos um cálculo, para tal coisa. O que pouparia um imenso tempo de processamento.

Para podermos calcular, ou gerar um cálculo que encontre o coeficiente angular, vamos usar derivadas. Poderíamos fazer de outra forma, mas vamos usar derivadas aqui. Isto por que é o caminho mais rápido. Mas para entender o que será feito. Vou assumir que você entendeu o que foi feito no tópico anterior. Então agora vamos partir para a parte matemática da coisa.

Para calcularmos o erro, estamos fazendo o seguinte cálculo matemático, visto abaixo.


O valor da constante < a >, que é visto na fórmula, é justamente o coeficiente angular da reta. Ou seja, é o valor da tangente da reta, que está sendo criada. Muito bem, mas esta fórmula não está compacta o suficiente. Podemos simplificar ela, tornando assim a fórmula um pouco mais compacta, como mostrado abaixo.


Apesar de parecer fórmulas diferentes. Elas representam a mesma coisa. Apenas a notação foi mudada para uma mais compacta. Por ser mais compacta, tornará mais simples de escrever as fórmulas passaremos a desenvolver. Muito bem, note que a cada passo, temos a soma do valor que é o comprimento da linha purpura no gráfico mostrado no tópico anterior. Este tipo de coisa não nos permite fazer uso de derivadas. Já que se tentarmos derivar tal formulação o resultado seria a constante zero. Porém queremos justamente encontrar o seguinte resultado, mostrado na fórmula abaixo.


Ou seja, queremos derivar o erro em relação ao coeficiente angular da reta. E conforme este valor tender a se tornar igual a zero. Passamos a ter o coeficiente angular, cada vez mais próximo do valor perfeito. Beleza, esta é a ideia. Porém dificilmente este valor encontrado será zero. Ele pode se aproximar de zero, quanto mais próximo melhor. No entanto, é como eu disse, a pouco. Não temos como derivar a função que gera o comprimento da linha purpura. Mas, porém, toda via e entretanto, existe uma forma. E esta é tornar a linha purpura em uma figura quadrada. Ao fazermos isto, iremos passar a não mais procurar, o comprimento mínimo da linha. E sim, passaremos a procurar a menor área possível do quadrado gerado, por cada uma das linhas. Com isto, passamos a ter uma nova formulação que é vista abaixo.


Observe que agora, apesar de não aparentar, estamos calculando o mesmo coeficiente. Só que desta fez, usando a área do quadrado, que está sendo gerado por cada uma das linhas purpuras, no gráfico. Ok, isto começou a ficar interessante. Se desenvolvemos esta equação, chegaremos na equação mostrada logo abaixo.


Você talvez não esteja percebendo o que acabamos de fazer. Mas está equação, que surgiu pelo simples fato de termos mudando o cálculo de comprimento de uma linha para a área de um quadrado. É de fato uma equação muito, mas muito interessante. Você consegue enxergar isto, meu caro leitor ?!?! Sabe me dizer que equação é esta ?!?! Sim ?!?! Não ?!? Talvez ?!?! Ou quem sabe ?!?!

Bem, se você não consegue enxergar que equação é esta. Tudo bem, não precisa ficar chateado ou deprimido. Esta equação, nada mais é do que a equação quadrática. Isto mesmo, é a mesma equação que gera uma parábola. E sendo assim é uma equação que nos permite criar uma derivada de primeira ordem. Lembre-se que queremos derivar em relação ao coeficiente angular. Mas antes de fazermos isto. Vou adicionar um passo extra, que normalmente não fazemos. Mas quero mostrar a você por que a fórmula final tem uma determinada aparência. Este passo extra, se dá na questão de agrupar os termos e os separar de forma adequada, como mostrado abaixo.


O que estou fazendo aqui, meu caro leitor, é tornar a equação mais simples de você entender ao fazermos a derivação. Este passo extra, normalmente não é feito, partimos direto para a fórmula final, que é vista logo abaixo.


Virgem Maria. Que DEUS tenha misericórdia de todos nos. Agora complicou de vez, pois não estou entendendo absolutamente nada. Como assim. Que cálculo doido é este ?!?! Como eu vou conseguir criar isto em termos de código ?!?! Calma meu caro leitor. Calma. Normalmente não gosto de mostrar fórmulas, justamente para não deixar você pensando que a vida é complicada. Quando na verdade as coisas são bem simples. Este cálculo é justamente a derivada que precisamos calcular. Ao fazermos isto, teremos um valor aproximado do que precisaremos usar no coeficiente angular da reta.

Muito bem, então vamos colocar isto em formato de código para ver o que obteremos. Isto é visto no fragmento logo abaixo.

19. //+------------------------------------------------------------------+
20. void Func_01(void)
21. {
22.     int A[]={
23.                 -100, -150,
24.                  -80,  -50,
25.                   30,   80,
26.                  100,  120
27.             };
28. 
29.     int vx, vy;
30.     double ly, err, d0, d1;
31. 
32.     canvas.LineVertical(global.x, global.y - _SizeLine, global.y + _SizeLine, ColorToARGB(clrRoyalBlue, 255));
33.     canvas.LineHorizontal(global.x - _SizeLine, global.x + _SizeLine, global.y, ColorToARGB(clrRoyalBlue, 255));
34. 
35.     err = d0 = d1 = 0;
36.     for (uint c0 = 0, c1 = 0; c1 < A.Size(); c0++)
37.     {
38.         vx = A[c1++];
39.         vy = A[c1++];
40.         d0 += (vx * vy);
41.         d1 += MathPow(vx, 2);
42.         canvas.FillCircle(global.x + vx, global.y - vy, 5, ColorToARGB(clrRed, 255));
43.         ly = vy - (vx * -MathTan(_ToRadians(global.Angle)));
44.         canvas.LineVertical(global.x + vx, global.y - vy, global.y - (int)(ly + vy), ColorToARGB(clrPurple));
45.         err += MathAbs(ly);
46.     }
47.     canvas.TextOut(global.x - 200, global.y + _SizeLine + 20, StringFormat("Error: %.8f", err), ColorToARGB(clrRed));
48.     canvas.TextOut(global.x - 200, global.y + _SizeLine + 40, StringFormat("(de/da) : %.8f", d0 / d1), ColorToARGB(clrForestGreen));
49. }
50. //+------------------------------------------------------------------+

Note que diferente do que poderia ser imaginado, ao olhar todas aquelas fórmulas. Fazer o cálculo em si, isto no código, é algo bastante simples e direto. Na linha 40 calculamos a somatória dos pontos. Já na linha 41, calculamos o quadrado dos pontos em X. Simples assim. Na linha 48, imprimimos o valor do gráfico. Agora preste bastante atenção, ao que irei dizer: Este valor que encontramos é o ideal quando tomamos apenas uma única variável na equação da reta. Para que você possa de fato entender, o que estou querendo explicar. Veja a formula da equação da reta. Ela pode ser vista logo abaixo.


Nesta equação o valor de < a > é justamente o coeficiente angular. Já o valor de < b > indica o ponto onde temos a raiz da equação. Para quem não sabe, a raiz de uma equação, é justamente os pontos onde a curva ou reta, no caso, tocam o eixo Y, quando o valor de X é igual a zero. Então por conta que o valor < b >, nesta solução encontrada é igual a zero. O valor retornado como ideal, apenas mostra qual deveria ser o coeficiente angular mais próximo do ideal. Isto não quer dizer que ele seja de fato o correto. Já que estamos dizendo que a raiz da equação é exatamente a origem dos planos. Ou seja, X e Y iguais a zero. E isto raramente acontecerá na realidade. Tanto que ao rodar esta aplicação, teremos como resultado a animação que é vista logo abaixo.


Note que nesta animação temos uma linha no fundo. Ela está ali apenas para mostrar onde estaria o valor ideal calculado. Porém fique olhando o valor de erro, e note que o coeficiente na equação é ligeiramente diferente do que é mostrado em verde.

Este valor em verde é justamente o valor que calculamos com base na equação desenvolvida. Você pode perceber claramente apenas calcular o valor do coeficiente angular não é o bastante. Precisamos de fato que a equação da reta seja criada, e para isto, além do valor de < a >, precisamos também do valor de < b >. Isto para que possamos ter uma solução mais genérica, e não fiquemos presos ao ponto (0, 0).

Este ponto (0, 0) que na verdade não faz parte do banco de dados. Gera uma influência implícita nos resultados dos cálculos sobre o coeficiente angular. Porém para remover este ponto adequadamente, precisamos modificar algumas coisas na aplicação. De forma que você, meu caro leitor, consiga sair deste ponto de origem, que é justamente o (0, 0).


Deslocando a raiz da função de reta.

Como precisamos preparar a aplicação para o que faremos a seguir, e que será visto no próximo artigo. Vamos criar um mecanismo bem curioso no restante deste artigo. Daí no próximo, poderemos focar na parte matemática.

Adicionar o tal mecanismos, precisaremos mudar muito pouco no código. Porém os efeitos serão bem grandes. Já que nos permitirá procurar uma equação da reta, onde teremos a menor área possível em qualquer situação. Criando assim um mecanismo genérico para encontrar o valor das constantes mais adequadas possíveis. Isto para que a regressão linear que estará sendo expressa no gráfico, possa representar de maneira mais adequada o banco de dados.

Fazer tais mudanças, é algo muito simples. Tudo que precisaremos fazer é deslocar a raiz da função de reta. Parece algo complicado, ao se ouvir tais palavras. Mas quando colocamos isto no código, a coisa se torna bem simples como pode ser visualizado abaixo.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. #property indicator_chart_window
004. #property indicator_plots 0
005. //+------------------------------------------------------------------+
006. #include <Canvas\Canvas.mqh>
007. //+------------------------------------------------------------------+
008. #define _ToRadians(A) (A * (M_PI / 180.0))
009. #define _SizeLine 200
010. //+------------------------------------------------------------------+
011. CCanvas canvas;
012. //+------------------------------------------------------------------+
013. struct st_00
014. {
015.     int     x,
016.             y;
017.     double  Angle,
018.             Const_B;
019. }global;
020. //+------------------------------------------------------------------+
021. void PlotText(const uchar line, const string sz0)
022. {
023.     uint w, h;
024. 
025.     TextGetSize(sz0, w, h);
026.     canvas.TextOut(global.x - (w / 2), global.y + _SizeLine + (line * h) + 5, sz0, ColorToARGB(clrBlack));   
027. }
028. //+------------------------------------------------------------------+
029. void Func_01(void)
030. {
031.     int A[]={
032.                 -100, -150,
033.                  -80,  -50,
034.                   30,   80,
035.                  100,  120
036.             };
037. 
038.     int vx, vy;
039.     double ly, err;
040.     string s0 = "";
041. 
042.     canvas.LineVertical(global.x, global.y - _SizeLine, global.y + _SizeLine, ColorToARGB(clrRoyalBlue, 255));
043.     canvas.LineHorizontal(global.x - _SizeLine, global.x + _SizeLine, global.y, ColorToARGB(clrRoyalBlue, 255));
044. 
045.     err = 0;
046.     for (uint c0 = 0, c1 = 0; c1 < A.Size(); c0++)
047.     {
048.         vx = A[c1++];
049.         vy = A[c1++];
050.         canvas.FillCircle(global.x + vx, global.y - vy, 5, ColorToARGB(clrRed, 255));
051.         ly = vy - (vx * -MathTan(_ToRadians(global.Angle))) - global.Const_B;
052.         s0 += StringFormat("%.4f || ", MathAbs(ly));
053.         canvas.LineVertical(global.x + vx, global.y - vy, global.y + (int)(ly - vy), ColorToARGB(clrPurple));
054.         err += MathPow(ly, 2);
055.     }
056.     PlotText(3, StringFormat("Error: %.8f", err));
057.     PlotText(4, s0);
058. }
059. //+------------------------------------------------------------------+
060. void NewAngle(const char direct, const char updow, const double step = 0.1)
061. {
062.     canvas.Erase(ColorToARGB(clrWhite, 255));
063. 
064.     global.Angle = (MathAbs(global.Angle + (step * direct)) < 90 ? global.Angle + (step * direct) : global.Angle);
065.     global.Const_B += (step * updow);  
066.     PlotText(1, StringFormat("Angle in graus => %.2f", MathAbs(global.Angle)));
067.     PlotText(2, StringFormat("f(x) = %.4fx %c %.4f", -MathTan(_ToRadians(global.Angle)), (global.Const_B < 0 ? '-' : '+'), MathAbs(global.Const_B)));
068.     canvas.LineAA(
069.                 global.x - (int)(_SizeLine * cos(_ToRadians(global.Angle))), 
070.                 (global.y - (int)global.Const_B) - (int)(_SizeLine * sin(_ToRadians(global.Angle))), 
071.                 global.x + (int)(_SizeLine * cos(_ToRadians(global.Angle))), 
072.                 (global.y - (int)global.Const_B) + (int)(_SizeLine * sin(_ToRadians(global.Angle))), 
073.                 ColorToARGB(clrForestGreen)
074.             );
075.        
076.     Func_01();
077. 
078.     canvas.Update(true);
079. }
080. //+------------------------------------------------------------------+
081. int OnInit()
082. {    
083.     global.Angle = 0;
084.     global.x = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
085.     global.y = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
086. 
087.     canvas.CreateBitmapLabel("BL", 0, 0, global.x, global.y, COLOR_FORMAT_ARGB_NORMALIZE);
088.     global.x /= 2;
089.     global.y /= 2;
090.         
091.     NewAngle(0, 0);
092. 
093.     canvas.Update(true);
094.     
095.     return INIT_SUCCEEDED;
096. }
097. //+------------------------------------------------------------------+
098. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
099. {
100.     return rates_total;
101. }
102. //+------------------------------------------------------------------+
103. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
104. {
105.     switch (id)
106.     {
107.         case CHARTEVENT_KEYDOWN:
108.             if (TerminalInfoInteger(TERMINAL_KEYSTATE_LEFT))
109.                 NewAngle(-1, 0);
110.             if (TerminalInfoInteger(TERMINAL_KEYSTATE_RIGHT))
111.                 NewAngle(1, 0);
112.             if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP))
113.                 NewAngle(0, 1);
114.             if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN))
115.                 NewAngle(0, -1);
116.             break;
117.     }
118. }
119. //+------------------------------------------------------------------+
120. void OnDeinit(const int reason)
121. {
122.     canvas.Destroy();
123. }
124. //+------------------------------------------------------------------+

O resultado é visto na animação abaixo:


Basicamente tudo que foi preciso fazer, para deslocar a raiz é adicionar a variável da linha 18. E usando as setas para cima e para baixo, mudamos o valor desta variável na linha 65. Todo restante é bastante simples. Já que apenas precisamos corrigir o valor do comprimento da linha purpura, baseado no valor desta nova variável presente no código. Tenho total confiança de que não será preciso comentar como isto está sendo feito. Já que é algo muito simples.


Considerações finais.

Aqui neste artigo, mostrei como muitas vezes fórmulas matemáticas parecem muito mais complicadas, quando a olhamos, do que quando a implementamos em termos de código. Muita gente acha difícil fazer tais coisas. Mas aqui ficou provado que é bem mais simples do que parece. Porém, não fizemos todo o trabalho. Apenas fizemos parte dele. Precisamos agora encontrar uma maneira de criar a equação da reta de forma bem mais adequada. E isto usando o último código visto neste artigo. Diferente do que você meu caro leitor, poderia estar imaginando no começo deste assunto. Encontrar a função da reta por meio de um loop não é algo tão simples como parecia. E isto por que apenas adicionamos mais uma variável a ser procurada. Pense só no trabalho que daria para procurar a equação, se tivéssemos um milhão de variáveis. Fazer via força bruta, seria completamente impraticável. Porém usando a matemática de forma adequada, encontrar a equação é algo bem mais simples.

Um último detalhe: Antes de ver o próximo artigo, use o código visto no final, que gera a última animação, para tentar encontrar a equação. Você vai ver que é uma tarefa bem desafiadora. Então nos vemos no próximo artigo.



Arquivos anexados |
Anexo.mq5 (4.53 KB)
Aprendendo MQL5 do iniciante ao profissional (Parte I): Comecemos a programar Aprendendo MQL5 do iniciante ao profissional (Parte I): Comecemos a programar
Este artigo é uma introdução a uma série completa de artigos sobre programação. Aqui supomos que o leitor nunca teve contato com programação antes. Por isso, começo pelo básico, com nível de conhecimento de programação: iniciante absoluto.
Validação cruzada e noções básicas de inferência causal em modelos CatBoost, exportação para o formato ONNX Validação cruzada e noções básicas de inferência causal em modelos CatBoost, exportação para o formato ONNX
Este artigo propõe um método autoral para a criação de robôs usando aprendizado de máquina.
Validação cruzada combinatoriamente simétrica no MQL5 Validação cruzada combinatoriamente simétrica no MQL5
Neste artigo veremos como implementar a verificação cruzada combinatoriamente simétrica no MQL5 puro para medir o grau de ajuste após a otimização de uma estratégia usando o algoritmo completo e lento do testador de estratégias.
Redes neurais de maneira fácil (Parte 64): Método de clonagem de comportamento ponderada conservadora (CWBC) Redes neurais de maneira fácil (Parte 64): Método de clonagem de comportamento ponderada conservadora (CWBC)
Pelo resultado dos testes realizados em artigos anteriores, concluímos que a qualidade da estratégia treinada depende muito da amostra de treinamento utilizada. Neste artigo, apresento a vocês um método simples e eficaz para selecionar trajetórias com o objetivo de treinar modelos.