Jump to content
henriqueborba

Resolvendo o problema de Inteligência Artificial vs PHP

Recommended Posts

Olá Devs!

 

Vim aqui compartilhar com vocês uma idéia que levou algum tempo para se concretizar e agora finalmente está se consolidando, gostaria de ouvir a opinião de vocês sobre o assunto e sobre a biblioteca em si, então vamos lá.

 

O problema: PHP vs Mercado crescente de Machine de Learning

 

Quando finalmente tive a oportunidade de me envolver com Big Data uma nova onda estava surgindo com muita intensidade: Inteligência Artificial. Não como aquelas complicadas e loucas que nós programadores sonhávamos em mexer como algo tão obscuro e complexo quanto o Assembly., mas algo prático, frameworks de inteligência artificial! Bibliotecas como Tensorflow, SKLearn e Keras estavam explodindo pela facilidade e pelos resultados alcançados. FINALMENTE! INTELIGÊNCIA ARTIFICIAL PRÁTICA E RÁPIDA DISPONÍVEL PARA QUALQUER UM.....que entendesse de python!

 

O que me incomodava profundamente, era que o Python não era uma maravilha em velocidade. Ele é interpretado por um interpretador em C assim como o PHP, e pra muitas coisas (depois do PHP 7) chega a ser mais lento. Então porque o PHP tinha que ficar num mundinho separado de API's e o Python seria o mestre das resoluções de problemas complexos? Simplesmente por permitir aritmética de matrizes? Não, isso me incomodava muito. Então decidi olhar o código dessas bibliotecas em Python e analisar de cabeça aberta o que estava acontecendo ali.

 

O Fator Comum

Depois de analisar o código, percebi que existia uma coisa muito em comum entre essas bibliotecas em Python, uma coisa obscura até então para mim, chamada Numpy! Decidi então verificar porque esse termo "Numpy" ou "import numpy as np" estava tão presente nessas bibliotecas em Python.

 

NumPy nada mais é que uma extensão para o Python escrita majoritariamente em C que utiliza um buffer em C e estruturas homogêneas em C para criar um novo tipo de Array para o python especializada em cálculos lineares e além disso permitir operações lineares especializadas (OpenBLAS, LAPACKE....).....pausa para respirar.

 

A minha conclusão no momento foi: "Eu acho que isso é possível numa extensão em C para o PHP"

 

O Desafio: C

Depois de 12 anos desenvolvendo em linguagens como PHP, Java, Python, Ruby e até mesmo Pascal, toda aquela preocupação de alocação e memória vai para o ralo. A gente esquece de muitos fatores que influenciam a computação de algo e nos mergulhamos em abstrações maravilhosas como se fosse um paraíso. Esse sonho foi um pesadelo quando finalmente decidi começar a desenvolver algo como o NumPy mas para o PHP, não existe nada minimamente aplicável em C que não dependa de um malloc ou realloc, meu desafio nesse momento foi reaprender C.

 

O que mais me surpreeendeu é que basicamente não existe documentação apropriada para extensões em PHP. Simplesmente você não vai encontrar nada atualizado e quando encontra é algo tão sintético que dificilmente vai te ajudar ou extremamente ultrapassado (PHP 5.6 e etc que não são mais compatíveis em C). A minha única idéia no momento foi entrar no GitHub e olhar o código das funções de array do próprio PHP 7 (array_sum, etc), elas estavam todas em um mesmo arquivo array.c e e então comecei a entender como as arrays eram tratadas internamente.

 

As arrays no PHP são Hash Maps, não vamos entrar em detalhes técnicos mas são ótimas quando você precisa de armazenar vários tipos de dados diferentes em uma mesma estrutura, por exemplo, você pode armazenar um inteiro na posição 0 e uma objeto (classe) na posição 1. Essa liberdade tem um custo alto, porque afinal de contas você nunca se "especializa" em tudo e vou mostrar mais a frente que esse custo é alto!

 

CArrays e o 7 andares do inferno

Agora falando com vocês, foram 7 meses de pesquisa e muitos problemas, cheguei a jogar 4 versões da biblioteca no lixo (que chegavam a ter 20 mil linhas programadas do zero) e começar novamente a partir dos erros anteriores. As primeiras versões continham tantos erros de memória e "seg faults" que era impossível produzir algo razoável. No fim tudo compensou e estou postando isso um dia depois dos testes finais para a release da extensão CArray e da biblioteca PHPSci. 

 

A Resolução

Consegui finalmente criar um novo tipo de array para o PHP utilizando uma extensão. Eu chamo de CArray e ela utiliza 50% menos memória em média que uma array do PHP e realiza cálculos lineares (ex: produto de matrizes) 800 vezes mais rápido que a mesma implementeção utilizando apenas estruturas PHP, o código da pre-release pode ser encontrado aqui (https://github.com/phpsci/phpsci-ext). 

 

As CArrays funcionam da seguinte forma, você cria uma a partir de uma array PHP ou utiliza um "initializer" (inicializador) para criar uma matriz, como no exemplo abaixo:

$a = [[1, 2], [3, 4]];
$b = CArray::fromArray($a);

$c = CArray::identity(100);

No exemplo, $b se torna uma CArray a partir de $a e $c se torna uma CArray a partir do "inicializador" identity, que nada mais é que uma matriz identidade (com 0 em todos os valores e 1 na diagonal)k, você também pode utilizar diversos inicializadores como "zeros", "ones", "one_like", "zeros_like", "eye" e etc.

 

Agora vamos dar um print_r na váriavel $b para vermos a matriz:

print_r($b);

O resultado será:

CArray Object
(
    [uuid] => 0
    [x] => 2
    [y] => 2
)

Você deve estar se perguntando, cadê minha array? O print_r do PHP só funciona com objetos nativamente em PHP, um objeto CArray é apenas um ponteiro para uma posição da memória contendo uma matriz em C. Esse ponteiro é o parâmetro "uuid" da classe acima, o 0 significa que nossa array [[1, 2], [3, 4]] está na posição 0 do buffer. O X e Y são apenas as dimensões da sua matriz, que facilitam o acesso do tamanho da sua array sem a necessidade de uma chamada de método ou recálculo do tamanho (que numa matriz seriam no mínimo dois FOR).

 

Para visualizar os valores da nossa CArray, vamos utilizar o ECHO ao invés do PRINT_R:

echo $b;
[
  [1, 2]
  [3, 4]
]

Dessa forma podemos ver que os valores são realmente armazenados, eles apenas não são mostrador pelo print_r porque o PHP não entende que existe um segundo buffer em C contendo esses valores. Você também pode utilizar o método estático toArray para converter sua array CArray para uma array PHP:

$d = CArray::toArray($b);

Pera, e a memória e o lixo desse buffer em C?

Esse foi um dos problemas! Como fazer o PHP limpar o buffer em C após rodar o Garbage Collection? Imagine se o buffer fosse alocado infinitamente e nunca liberado, a biblioteca se tornaria impraticável, alocando memória até inevitavelmente explodir. Graças ao método __destruct do PHP, as CArrays não utilizadas no buffer em C são automaticamente limpas durante o Garbage Collection do C. Isso resultou em uma alocação de array 50% em média menor que a mesma array em PHP.

 

E daí? Uma array em C vai me ajudar em que?

Acontece que a biblioteca em si não apenas aloca valores em uma array em C, ela possui mais de 45 métodos de resolução de funções lineares e matemáticas, otimizadas com algoritmos como OpenBLAS e LAPACKE, o que basicamente é aquilo que bibliotecas de IA precisam! Não existe como ter uma biblioteca de Inteligência Artificial razoável se os seus cálculos em arrays são extremamente lentos. Os métodos são compatíveis com o NumPy exatamente para facilitar a integração e o aprendizado:

 

$a = CArray::identity(1000);
$b = CArray::identity(1000);
$c = CArray::matmul($a, $b);

O código acima utiliza um dos 40 métodos da biblioteca (matmul) para computar o produto das matrizes $a e $b. O formato das duas matrizes é (1000 linhas, 1000 colunas), isso significa que o produto delas em PHP puro utilizando FOR levaria mais 1 bilhao de iterações. Esse método é um dos que chega a ser 800 vezes mais rápido, ao utilizar apenas arrays em C ele não precisa se quer de se comunicar com o PHP e assim pode utilizar computação paralela e algoritmos especializados.

 

O método matmul é apenas uma das diversas operações utilizadas em inteligência artificial, o CArray abre caminho para que isso seja aplicado de forma prática.

 

Resultados com Machine Learning

Agora estou reescrevendo o código do SKLearn do Python em PHP utilizando a extensão CArrray, vocês podem verificar essa biblioteca aqui: (https://github.com/phpsci/phpsci), o objetivo dela é oferecer aplicações práticas para a CArray, como por exemplo machine learning.

 

O algoritmo que implementei até agora é um simples Naive Bayes para classificação. Na primeira versão ele já é 13 vezes mais rápido que o Naive Bayes do projeto PHP-ML que não utiliza extensões e utiliza 50% menos memória. A idéia é continuar expandido tanto os métodos da extensão CArray como suas aplicabilidades na biblioteca PHPSci além claro, de melhorar sua performance.

 

Futuro: GPU

A boa notícia é que como utilizo o OpenBLAS nas operações em C, os métodos mais importantes podem ser implementados utilizando a biblioteca CuBLAS, basicamente seria a mesma biblioteca CArray porém executando operações em GPU. Dá pra imaginar PHP fazendo cálculo em GPU? Isso vai acontecer jaja, pelo menos se depender da minha determinação na lib.

 

É isso galera, volto aqui depois do lançamento e com a documentação pronta! Também vou mostrar como utilizar o PHPSci para aprendizado de máquina eficiente, sem ter que esperar horas mas poucos minutos ou segundos para um resultado.

 

O Presente:

Segue abaixo uma cópia do registro de métodos da CArray, dessa forma vocês conseguem enteder a abrangência da classe:

 // CARRAY ITERATOR
   PHP_ME(CArray, offsetUnset, arginfo_array_offsetGet, ZEND_ACC_PUBLIC)
   PHP_ME(CArray, offsetSet, arginfo_array_offsetSet, ZEND_ACC_PUBLIC)
   PHP_ME(CArray, offsetGet, arginfo_array_offsetGet, ZEND_ACC_PUBLIC)
   PHP_ME(CArray, offsetExists, arginfo_array_offsetGet, ZEND_ACC_PUBLIC)

   // PHP_ME(CArray, __construct, NULL, ZEND_ACC_PUBLIC)
   // RANGES SECTION
   PHP_ME(CArray, arange, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, linspace, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, logspace, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)

   // ARITHMETIC
   PHP_ME(CArray, add, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, subtract, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)

   // LOGARITHMS
   PHP_ME(CArray, log, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, log10, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, log2, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, log1p, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)

   // TRANSFORMATIONS SECTION
   PHP_ME(CArray, transpose, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, atleast_1d, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, atleast_2d, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, unique, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, flatten, NULL, ZEND_ACC_PUBLIC)

   // EIGENVALUES
   PHP_ME(CArray, eig, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, eigvals, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)

   // NORMS SECTION
   PHP_ME(CArray, norm, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)

   // OTHERS SECTION
   PHP_ME(CArray, det, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, cond, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)

   // PRODUCTS SECTION
   PHP_ME(CArray, matmul, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, inner, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, outer, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, inv, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, svd, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)

   // SET ROUTINES
   PHP_ME(CArray, in1d, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)

   // EQUATIONS SECTION
   PHP_ME(CArray, solve, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)

   // MAGIC PROPERTIES
   PHP_ME(CArray, __get, phpsci_get_args, ZEND_ACC_PUBLIC)

   // CARRAY MEMORY MANAGEMENT SECTION
   PHP_ME(CArray, __destruct, NULL, ZEND_ACC_PUBLIC)

   // TRIGONOMETRIC
   PHP_ME(CArray, tan, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, cos, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, sin, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, arctan, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, arccos, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, arcsin, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)

   // HYPERBOLIC
   PHP_ME(CArray, sinh, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, tanh, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, cosh, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)

   // INITIALIZERS SECTION
   PHP_ME(CArray, identity, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, zeros, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, zeros_like, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, ones, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, ones_like, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, eye, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, full, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, full_like, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   
   // BASIC OPERATIONS
   PHP_ME(CArray, sum, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, negative, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, multiply, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, divide, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, abs, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, absolute, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, square, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, all, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)

   // SEARCH
   PHP_ME(CArray, search_keys, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, argmax, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)

   // STATISTICS
   PHP_ME(CArray, amin, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, amax, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, mean, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, var, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)

   // EXPONENTIAL
   PHP_ME(CArray, exp, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   
   // CONVERT SECTION
   PHP_ME(CArray, toArray, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, fromArray, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, toDouble, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, fromDouble, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   
   // VISUALIZATION
   PHP_ME(CArray, print_r, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, __toString, NULL, ZEND_ACC_PUBLIC)

   // RANDOM SECTION
   PHP_ME(CArray, standard_normal, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, randn, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   PHP_ME(CArray, randint, NULL, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
   { NULL, NULL, NULL }

 

Limitações

  • As CArrays possuem até 2 dimensões, ou seja, você pode construir um double, um vetor e uma matriz. Matrizes com N-dimensões serão implementadas na próxima "major version"

 

Share this post


Link to post
Share on other sites

Parabéns pela iniciativa. O PHP realmente sofre o descaso de uma computação mais eficiente principalmente pelo fato da comunidade de suporte querer o PHP como uma linguagem mais pragmática, ou, nas palavras de Rudi Theunissen: "optimised for everything; optimised for nothing".

 

Falando no Rudi Theunissen, ele teve praticamente o mesmo problema que o seu (há alguns anos) e trabalhou em tornar os arrays no PHP mais eficientes, o qual você pode ver aqui (caso já não o conheça): https://medium.com/@rtheunissen/efficient-data-structures-for-php-7-9dda7af674cd

 

Entretanto, diferente da sua biblioteca, o objetivo dele era apenas com arrays, e a biblioteca não trabalha com matrizes ou cálculos matemáticos, apenas com a alocação de arrays lineares.

 

De qualquer forma, mantenha atualizado aqui no fórum, sugiro até entrar como um artigo do iMasters. Nesse caso, teria que entrar como submissão de projeto/trabalho.

Share this post


Link to post
Share on other sites
On 9/28/2018 at 8:40 AM, Gabriel Heming said:

Parabéns pela iniciativa. O PHP realmente sofre o descaso de uma computação mais eficiente principalmente pelo fato da comunidade de suporte querer o PHP como uma linguagem mais pragmática, ou, nas palavras de Rudi Theunissen: "optimised for everything; optimised for nothing".

 

Falando no Rudi Theunissen, ele teve praticamente o mesmo problema que o seu (há alguns anos) e trabalhou em tornar os arrays no PHP mais eficientes, o qual você pode ver aqui (caso já não o conheça): https://medium.com/@rtheunissen/efficient-data-structures-for-php-7-9dda7af674cd

 

Entretanto, diferente da sua biblioteca, o objetivo dele era apenas com arrays, e a biblioteca não trabalha com matrizes ou cálculos matemáticos, apenas com a alocação de arrays lineares.

 

De qualquer forma, mantenha atualizado aqui no fórum, sugiro até entrar como um artigo do iMasters. Nesse caso, teria que entrar como submissão de projeto/trabalho.

 

Fala Gabriel, tranquilo? Primeiramente muito obrigado por tirar seu tempo para ler a redação acima haha.

 

Sobre o post do medium, li o mesmo artigo antes de ter a ideia de ler o numpy, estava procurando saber se alguém estava resolvendo o problema ou se ja haviam resolvido. Quando falei "PHP arrays sao boas em tudo mas especializadas em nada" foi em referência a esse artigo, muito bom da sua parte ter notado.

 

Concordo plenamente, mas o que mais me incomoda é que extensões em C podem resolver praticamente todos esses problemas de perfomance. Acredito que a maior culpa é da própria PHP por negligenciar documentações apropriadas para isso.

 

Vou verificar o processo para a criação do artigo, acredito que as pessoas estejam mais interessadas na biblioteca de IA e por ai que devo "atacar". Ou você acha que as CArrays devem ter um artigo próprio? (Tenho receio de não haver o menor interesse sobre o assunto ainda)

 

Valeu Gabriel!

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×

Important Information

Ao usar o fórum, você concorda com nossos Terms of Use.