• Nenhum resultado encontrado

MONOGRAFIA Implementacao de uma rede neural artificial de camada simples em plataforma GPU utilizando linguagem CUDA

N/A
N/A
Protected

Academic year: 2021

Share "MONOGRAFIA Implementacao de uma rede neural artificial de camada simples em plataforma GPU utilizando linguagem CUDA"

Copied!
100
0
0

Texto

(1)

IMPLEMENTAÇÃO DE UMA REDE NEURAL

ARTIFICIAL DE CAMADA SIMPLES EM

PLATAFORMA GPU UTILIZANDO LINGUAGEM

CUDA

LAVRAS - MG 2011

(2)

CAMADA SIMPLES EM PLATAFORMA GPU UTILIZANDO LINGUAGEM CUDA

Monografia apresentada ao Colegiado do Curso de Ciência da Computação, para a obtenção do título de Bacharel em Ciên-cia da Computação.

Orientador

Prof. Dr. Wilian Soares Lacerda

LAVRAS - MG 2011

(3)
(4)
(5)

ram.

Agradeço ao meu irmão, Tiago, pela força nos momentos mais difí-ceis.

Agradeço ao meu amigo da república, Ronaldo pela amizade.

Agradeço aos meus amigos do curso, pelo companheirismo e amizade durante toda a faculdade.

Agradeço aos meus amigos, que mesmo estando longe, me apoiaram, me deram forças e conselhos.

Agradeço aos professores, pelos conhecimentos repassados. Agradeço à professora, Marluce, pela orientação neste trabalho.

(6)

Palavras-chave: Rede naural Artificial, Paralelismo, GPU, CUDA, OpenCL

(7)

improve performance in artificial neural networks. Two different techniques were implemented to run from the GPU, but have been unsuccessful against the CPU. A complete analysis of the speedup of the used techniques shows which is the more efficient is displayed at the end of the study.

Keywords: Artificial Neural Network, Parallelism, Artificial Inteligence, GPU, CUDA, OpenCL

(8)

Figura 6 Hierarquia de threads, bloco e memória(Fonte: NVIDIA

Corporation). . . 21

Figura 7 Hierarquia de Grids e memória(Fonte: NVIDIA Corporation). . . 22

Figura 8 Comportamento de um programa CUDA (Fonte: NVIDIA Corporation). . . 23

Figura 9 kernel GPU. . . 24

Figura 10 Inicialização das variáveis. . . 25

Figura 11 Cópias de memória e chamada do kernel da GPU. . . 26

Figura 12 Impressão de resultados e liberação de espaço alocado na GPU. . . . 27

Figura 13 Comparativo as terminologias para implementação(Fonte: AMD Developer Central). . . 28

Figura 14 Comparação entre comando CUDA x OpenCL (Fonte: AMD Developer Central). . . 28

Figura 15 Leitura do programa GPU-Z da GPU GTX285 . . . 33

Figura 16 Representação gráfica da rede neural implementada. . . 34

Figura 17 Representação gráfica do dígito do número um. . . 34

Figura 18 Algoritmo do código. . . 36

Figura 19 Relatório gprof. . . 37

Figura 20 Função AdjustWeights CPU. . . 37

Figura 21 Sequencial GPU (1 thread). . . 38

Figura 22 Sequencial GPU (N thread). . . 39

Figura 23 Componentes de uma thread. . . 39

Figura 24 Algoritmo do método do neurônio por thread. . . 40

Figura 25 Treinamento 11 neurônios. . . 42

Figura 26 Treinamento 22 neurônios. . . 43

Figura 27 Treinamento 34 neurônios. . . 44

Figura 28 Treinamento 11 neurônios. . . 45

Figura 29 Treinamento 22 neurônios. . . 46

Figura 30 Treinamento 34 neurônios. . . 47

Figura 31 Tempo do Crivo. . . 57 7

(9)
(10)

2.3 Computação Paralela. . . 18

2.4 Programação em GPGPU. . . 19

2.5 CUDA. . . 20

2.6 ATI Stream. . . 26

2.7 Trabalhos Realizados nos Últimos Anos. . . 28

3 Materiais e Métodos. . . 32

3.1 Tipo de Pesquisa. . . 32

3.2 Materiais. . . 32

3.3 Procedimentos Metodológicos. . . 32

3.3.1 Método da menor granularidade. . . 35

3.3.2 Método do Neurônio por thread. . . 38

4 Resultados . . . 41

4.1 Resultados Obtidos com o Método da Menor Granularidade. . . 41

4.2 Resultados Obtidos com o Método do Neurônio por Thread. . . 45

5 Conclusões. . . 49

5.1 Propostas de Continuidade. . . 49

6 Referencia Bibliográfica. . . 51

7 ANEXO Crivo de Erastóstenes. . . 56

8 ANEXO Código Crivo de Eratóstenes. . . 58

9 ANEXO Códigos de RNA. . . 64

(11)

1.1 Contextualização e Motivação

O homem em sua busca pela perfeição e velocidade sempre se inspirou na natureza para solucionar problemas. Não foi diferente no mundo da computação, onde inspirados no cérebro humano, os pesquisadores McCulloch e Pitts (1943), Hebb (1949) e Rosenblatt (1961) tiveram as mais importantes publicações que introduziram o primeiro modelo de redes neurais artificiais.

Redes neurais são utilizadas em uma grande variedade de aplicações, tais como reconhecimento de voz (Scanzio et al., 2010), reconhecimento de padrões (Jang et al., 2008), reconhecimento de face (Poli et al., 2008), avaliação de crédito (Khashman, 2010), robótica (Shi et al., 2010), entre outras.

Redes neurais podem ser implementadas em hardware (Misra and Saha, 2010). Um exemplo de implementação em hardware é o mapeamento da rede neu-ral em uma arquitetura de hardware de alto desempenho, como uma plataforma Field-Programmable Gate Array(FPGA). Ly e Chown (2010) propuseram um fra-meworkmodular de uma máquina de Boltzmann restrita (RBM) buscando reduzir a complexidade de tempo das computações. RBMs grandes foram particionadas em componentes menores e distribuídos entre múltiplo recursos de FPGA e assim conseguiram obter desempenho computacional.

A execução de grande quantidade de dados utilizando uma rede neural poderá ter um tempo de execução alto, o que motiva a utilização de técnicas que acelerem o processamento de uma rede neural.

Como o nosso cérebro não funciona de modo sequencial, evoluiu-se o conceito de programação, onde as tarefas computacionais são executadas de forma sequencial ou linear, muitas vezes deixando o hardware ocioso, surgindo o conceito

(12)

anos, com arquiteturas altamente paralelas e com alto poder de processamento a um custo financeiro relativamente baixo. Empresas como a IBM com o proces-sador CELL (Gschwind, 2005), a NVIDIA com as placas de vídeo com múltiplas unidades de processamento (CUDA, 2009) e a ATI com a tecnologia Stream Pro-cessor (ATI, 2009) têm mostrado um novo caminho para os programadores que enfrentavam dificuldades em implementar soluções de alto desempenho.

Com esta nova tecnologia de hardware surgiu também a motivação para explorar o paralelismo de redes neurais artificiais, visando reduzir o tempo de execução e a obtenção mais rápida de resultados.

1.2 Objetivos

Este trabalho tem como objetivo geral o estudo de diferentes técnicas de programação paralela para se implementar redes neurais artificiais em GPU (Gra-phic Processing Units) utilizando CUDA(Compute Unified Device Architecture).

Como objetivo específico serão propostas diferentes implementações para-lelas para rede neural artificial para serem executadas em GPU utilizando CUDA, visando obter alto desempenho.

1.3 Estrutura

Os capítulos seguintes desta monografia estão organizados da seguinte forma: O capítulo 2 aborda os principais conceitos referentes às redes neurais artificiais, programação paralela, programação em placas de vídeo e trabalhos

(13)

rea-capítulo 4 apresenta os resultados e os discute. O rea-capítulo 5 conclui o trabalho e sugere trabalhos futuros. Os capítulos de anexo trazem os códigos utilizados para a realização de todo este trabalho e um algoritmo com ganho de desempenho da GPU frente a CPU com o Crivo de Erastóstenes.

(14)

Uma Rede Neural Artificial (RNA) é um modelo baseado na natureza, mais especificamente no cérebro humano (Barreto, 2002). O cérebro humano é composto por cerca de 10 bilhões neurônios. Os neurônios se conectam através de sinapses, e juntos formam uma grande rede chamada de rede neural (Tatibana e Kaetsu, 2009). A Figura1apresenta uma ilustração de um neurônio biológico genérico e suas partes constituintes.

Uma rede neural artificial (RNA) é um sistema composto por vários neu-rônios. Alguns neurônios recebem excitações do exterior e são chamados neurô-nios de entrada. Outros têm suas respostas usadas para alterar, de alguma forma, o mundo exterior e são chamados neurônios de saída. Os neurônios que não são nem entrada nem saída são conhecidos como neurônios internos. Estes neurônios inter-nos à rede têm grande importância e são conhecidos na literatura saxônica como “hidden”, traduzindo para o português, “escondido” ( Barreto, 2002). A figura2

apresenta um neurônio artificial desenvolvido por McCulloch em 1943. Neste neu-rônio, as entradas são combinadas utilizando uma função F visando produzir um estado de ativação do neurônio.

A Figura 3mostra uma RNA com 2 neurônios na camada de entrada, 8 neurônios na camada escondida e 1 neurônio na camada de saída.

As Redes Neurais Artificiais ao longo do tempo foram evoluindo tanto em complexidade quanto em poder de aprendizado. Primeiramente surgiu o

(15)

Per-Figura 1: Neurônio Genérico (Fonte: Tatibana e Kaetsu, 2009)

Figura 2: Neurônio Artificial desenvolvido por McCulloch (Fonte: Tatibana e Kaetsu, 2009)

ceptron de Rosenblatt (1961), onde os neurônios eram organizados em camada de entrada e saída, onde os pesos das conexões eram adaptados a fim de se atingir a eficiência sináptica.

(16)

Figura 3: Exemplo de uma RNA com 4 camadas (Fonte: Tatibana e Kaetsu, 2009)

Surgiu a rede ADALINE (ADAptative LInear Network) e posteriormente o MADALINE(Many ADALINE) perceptron, proposto por Widrow e Hoff. Estas redes neurais artificiais utilizavam-se de saídas analógicas em uma arquitetura de três camadas. Rumelhart, Hinton e Williams introduziram o método Backpropa-gation. Era o modelo que apresentava a camada oculta, que poderiam ser várias, e um algoritmo onde a retro-propagação do erro sobre as camada ocultas para o reajustes dos pesos sugeriu seu nome.

As redes neurais artificiais podem ser aplicadas em diversos problemas. Atualmente, já vêm sendo aplicadas em (Tatibana e Kaetsu, 2009):

• análise e processamento de sinais;

• controle de processos;

(17)

• reconhecimento de padrões em linhas de montagem;

• filtros contra ruídos eletrônicos;

• análise de imagens;

• análise de voz;

• avaliação de crédito.

2.2 Perceptron

Criado por Rosenblatt(1961), o perceptron é um clássico modelo de neu-rônio que permite saídas diretas muito usado para tarefas de classificação. O pro-cesso de classificação se baseia em encontrar padrões em comum que possam ser linearmente separadas em classes. A figura4mostra o modelo de um Perceptron com várias entradas, para cada entrada um peso. É usada uma função somatória com um valor de entrada (threshold) e uma função de ativação, no caso da figura, sigmoidal.

A regra Delta usada para o treinamento do Perceptron ajusta os pesos so-mente quando um padrão é classificado incorretaso-mente. Na figura5perceber que dentro da estrutura de repetição na linha 3 é feito o calculo para a possível saida. Na estrutura da linha 4, é feita a regra delta para se obter o valor da saída. Na linha 8 é verificada se a saída calculada está correta e o erro é calculado. Na linha 9 é feita a atualização do peso.

(18)

Figura 4: Perceptron (Fonte: Tatibana e Kaetsu, 2009)

(19)

A transição entre a computação sequencial e a paralela é uma forma en-contrada para utilizar de forma mais consciente os recursos computacionais dis-poníveis. Assim, a computação paralela vem minimizar o tempo de computação, racionalizando melhor o uso do hardware e economizando recursos (Marowka, 2008).

Computador paralelo é um computador ou vários deles equipados com processadores capazes de trabalhar de forma cooperativa para resolver um pro-blema computacional. Como exemplo temos os super computadores com cente-nas ou milhares de processadores, redes de estação de trabalho, múltiplas estações de trabalho e sistemas incorporados. Computadores paralelos são interessantes porque oferecem uma possibilidade de compartilhar os recursos computacionais, sejam processadores, memória, entrada e saída de dados, largura de banda para resolver inúmeros problemas computacionais (Foster, 1995).

Dividir o problema em problemas menores, distribuir esses problemas me-nores entre os processadores ou computadores, implementar um mecanismo de co-municações e sincronia entre os mesmos são apenas alguns dos passos que devem ser seguidos para que um problema seja paralelizado.

As redes neurais são paralelas por natureza, logo as redes neurais artificiais podem ter seu algoritmo paralelizado. O quanto o algoritmo pode ser paralelizado irá determinar a sua granularidade. Em granularidades mais finas, cálculos básicos do algoritmo são executados em paralelos, já a granularidade mais grossa toda uma estrutura do algoritmo é executada em paralelo.

(20)

lares e em tudo que tenha algum display que exiba imagens mais elaboradas. Com o interesse por exibição gráfica crescendo, com a alta resolução e o alto desempenho, começaram a ser criadas GPUs com processamento 2D em chip único, anos depois GPUs com processamento 3D. Com isso as GPUs receberam a capacidade de processar mais cálculos de ponto flutuante, capacidade de processar figuras geométricas, tornando-se tão flexíveis quanto uma CPU (Mocelin et al., 2009).

Logo que surgiram as primeiras ferramentas para se utilizar a GPU além de sua função básica, surgiu o conceito de GPGPU (General-Purpose computation on Graphics Processing Unitsou Unidade de processador Gráfico de Propósito Geral) com suporte para interfaces de programação e linguagens padrão da indústria, tais como C. Assim as aplicações para GPU possam ser executadas em menor tempo do que as aplicações para CPU usando o que as GPUs tem de melhor, o paralelismo (GPGPU.org, 2009).

A NVIDIA desenvolveu uma arquitetura de computação paralela chamada CUDA (Compute Unified Device Architecture), que tira proveito do mecanismo de computação paralela das GPUs. Também foi disponibilizado todo um kit de desenvolvimento baseado em linguagem de alto nível C com um compilador pro-prietário para o desenvolvimento de softwares de alta performance compatíveis com CUDA. Com o nome de ATI Stream SDK, a sua concorrente ATI também disponibiliza tecnologia semelhante.

(21)

Em Novembro de 2006 a NVIDIA introduziu uma arquitetura de compu-tação paralela de propósito geral, com um novo modelo de programação paralela que aproveita o mecanismo de computação paralela em GPUs para resolver muitos problemas computacionais complexos em uma maneira mais eficiente do que em um CPU. O CUDA (Computer Unified Device Architecture) esconde a complexi-dade da GPU através de sua API (Application Programming Interface ou Interface de Programação de Aplicações), permitindo assim que os programadores não se preocupem com detalhes complexos de hardware durante a programação.

O modelo de programação paralela CUDA é projetado para programa-dores familiarizados com o padrão da linguagem C. Em sua essência, as abstra-ções adicionadas são uma hierarquia de threads, memórias compartilhadas e uma sincronização para as threads.

A linguagem C para o CUDA foi estendida permitindo que o programador defina funções chamadas de kernel.A palavra kernel é utilizada para determinar que certo trecho do código será utilizada pela GPU. O código que será executado na GPU é separado do restante pois possui prefixos especiais e chamadas reserva-das, identificando o que será processado pela CPU e pela GPU.

O modelo de programação para a GPU segue uma hierarquia de threads e de memória. Cada thread possui uma memória local, e um conjunto de threads é organizado em blocos, e cada bloco de thread possui uma memória compartilhada para todas as threads, como é mostrado figura 6.

Cada bloco de threads por sua vez agrupa se em Grid e as Grids compar-tilham de uma mesmo espaço de memória, chamado memória global como ilustra a figura 7.

(22)

Figura 6: Hierarquia de threads, bloco e memória(Fonte: NVIDIA Corporation)

Toda essa hierarquia de threads, blocos e grids com suas respectivas memó-rias devem ser respeitadas para cada kernel executado.

Uma visão geral de como se comporta um programa CUDA é demonstrado na figura 8. O programa seqüencial é inicializado pela CPU, o CUDA reconhece a CPU como Host. Durante a execução desse programa, existem chamadas para um kernel paralelo, este kernel ativa um Device, que é como a GPU é reconhe-cida. Quando ativada, a GPU passa a executar um trecho do programa de forma paralela, enquanto isso a CPU continua seu trabalho de forma seqüencial. Ao fim do processo paralelo a GPU retorna com o resultado de seu processamento para a CPU.

No programa exemplo da figura 9estão presentes os principais coman-dos e estruturas de um programa CUDA. Pode-se observar que a primeira palavra reservada

(23)

Figura 7: Hierarquia de Grids e memória(Fonte: NVIDIA Corporation)

é usada para determinar o que será executado pela GPU, a palavra reservada ante-cipa a função. Estão presentes três palavras reservadas para controle e identifica-ção das threads

blockIdx.x

blockDim.x

e

threadIdx.x

que são respectivamente identificação do bloco, dimensão do bloco e por último a identificação da thread.

(24)

Figura 8: Comportamento de um programa CUDA (Fonte: NVIDIA Corporation)

A parte do código que executará seqüencialmente na CPU apresenta ape-nas os seguintes passos: alocação de memória, cópia das variáveis da CPU para

(25)

Figura 9: kernel GPU

GPU, chamada do kernel da GPU e por último a cópia da variável da GPU para CPU.

Na figura 10 podemos observar a declaração das variáveis do tipo pon-teiro, determinação de seu tamanho estaticamente e um comando reservado CUDA,

cudaMalloc()

, que é a versão do comando de alocação de memória

malloc()

(em linguagem C) mas para alocação do espaço em memória na GPU.

Na figura 11, pode-se observar os três principais passos para a programa-ção CUDA, a cópia da variável para a GPU, a chamada do kernel da GPU para execução paralela do código e por último a cópia da variável para a CPU. A cópia é feita através do comando reservado

(26)

Figura 10: Inicialização das variáveis

cudaMemcpy()

seguido da seguinte estrutura: variável de destino, variável de origem, tamanho e sentido da cópia. O sentido da cópia é dado por outra palavra reservada

(27)

cudaMemcpyDeviceToHost

que copia as informações respectivamente no sentido da CPU para GPU ou GPU para CPU.

A chamada para o kernel na GPU é dado como uma função em C, mas com a adição de um parâmetro

<<< , >>>

que determina o número de blocos e threads ou grids e blocos que serão executados nesta chamada. No exemplo da figura 11são executados 1 bloco com 256 threads.

Figura 11: Cópias de memória e chamada do kernel da GPU

Na figura 12pode-se observar o comando de impressão das variáveis em tela, com seus respectivos valores e por último um comando reservado

cudafree()

usado para liberar o espaço estaticamente alocado para a variável na GPU.

2.6 ATI Stream

A tecnologia ATI Stream é uma tecnologia que permite que o programa-dor tenha acesso aos núcleos da GPU possibilitando que trabalhe em conjunto

(28)

Figura 12: Impressão de resultados e liberação de espaço alocado na GPU

com a CPU, para acelerar as aplicações. Assim como a NVIDIA desenvolveu o CUDA, a ATI disponibilizou uma plataforma de desenvolvimento completa, a Stream Software Development Kit (SDK), criada pela AMD para permitir o de-senvolvimento de aplicações para a tecnologia ATI Stream. O SDK permite que se desenvolva aplicativos em uma linguagem de alto nível utilizando o OpenCL (Open Computing Language).

OpenCL foi inicialmente desenvolvida pela Apple, que tem direitos sobre a marca, mas hoje tem o padrão administrado pelo Khronos Group, que também gerencia OpenGL e tem como contribuintes de desenvolvimento a AMD, Apple, Intel e NVIDIA.

O funcionamento tanto do gerenciamento de threads quanto de memória para o OpenCL é semelhante ao CUDA. Na figura 13há uma terminologia dife-rente para alguns termos na implementação com CUDA e com OpenCL, mas com a mesma funcionalidade.

Esta semelhança possibilita que os códigos em CUDA sejam portados com bastante facilidade para o OpenCL. A figura 14apresenta os principais comandos na plataforma CUDA e sua respectiva terminologia no OpenCL.

(29)

Figura 13: Comparativo as terminologias para implementação(Fonte: AMD De-veloper Central)

Figura 14: Comparação entre comando CUDA x OpenCL (Fonte: AMD Develo-per Central)

2.7 Trabalhos Realizados nos Últimos Anos

Esta seção apresenta trabalhos relacionados à utilização de programação para GPUs e implementação de redes neurais artificiais paralelas, especialmente aquelas utilizando CUDA. Nestes trabalhos foram utilizadas diferentes redes neu-rais artificiais: Spyking, Neocognitron, celular e perceptron multi-camadas.

(30)

e saída e gerenciar a transferência de dados entre CPU e GPU; Um módulo de co-processamento paralelo entre CPU-GPU. Com esta estrutura montada conse-guiram significantes speedups sobre a execução em uma CPU.

Pamplona et al (2010) apresentaram um trabalho de análise de desem-penho entre a linguagem de programação C e o CUDA para implementações do problema das N-Rainhas. O problema N-Rainhas é definido quando em um tabu-leiro de xadrez NxN, são posicionadas as N rainhas de maneira que não se ata-quem. Dentro deste panorama foram implementados os códigos para CPU e GPU e comparados os tempos de execução de soluções para cada uma destas arquite-turas. Mesmo com maior número de processadores e da exclusividade da GPU tiveram como resultado a CPU com desempenho superior.

Jang et al (2008) apresentou uma série de algoritmos de redes neurais artificiais usando OpenMP e CUDA para o processamento de imagem em GPU para busca de computação mais rápida. Implementaram redes neurais artificiais baseadas em detecção de texto usando a proposta de cada arquitetura, OpenMP e CUDA. O código OpenMP demonstrou ser 10 vezes mais rápido que o código sequencial em CPU e 5 vezes mais rápido do que o código para GPU.

Nageswaran et al (2008) apresentaram uma eficiente simulação de uma rede neural utilizando o simulador Spyking Neural Network e uma GPU. Demons-traram um eficiente neurônio baseado numa rede neural Spyking de grande escala. Este tipo de rede neural é geralmente simulado em grandes clusters, supercomputa-dores ou hardwares de arquitetura dedicada. Os resultados obtidos foram 26 vezes

(31)

simulada uma rede com 100 mil neurônios e 50 milhões de conexões sinápticas. Para um modelo de 10 milhões de conexões sinápticas foi somente 1,5 vezes mais lenta do que seria na estrutura biológica.

Poli et al (2008) apresentaram soluções de alto desempenho para o recon-hecimento de face com uma implementação de rede neural artificial Neocognitron junto a arquitetura CUDA. Neocognitron é uma rede neural artificial proposta por Fukushima, constituída de várias camadas hierárquicas de neurônios organizadas em matrizes bidimensionais chamadas planos celulares. Utilizando da arquite-tura CUDA, o balanceamento de carga foi organizado através do uso de conexões celulares com as threads se organizando em blocos, seguindo a filosofia do desen-volvimento da arquitetura CUDA. Os resultados mostraram a flexibilidade desse tipo de dispositivo como uma ferramenta de processamento paralelo em massa, quanto menor a granularidade e a dependência dos dados de processamento para-lelo, melhor foi o desempenho. Conclu-se que a implementação para a arquitetura CUDA foi mais eficiente que a implementação sequencial para a CPU.

Fernandez et al (2008) simularam uma rede neural celular (CNN) em uma GPU com a utilização do CUDA. Os resultados mostraram uma grande eficiência devido ao alto grau de paralelismo proporcionado pela GPU. Para isso foi simulada uma rede neural celular padrão desenvolvida por Chuan-Yang, que foi otimizada para a CPU. Foi também implementada uma versão paralela para a GPU. Utili-zaram esta rede neural para fazer detecção de bordas binárias, resolver a equação diferencial parcial de Laplace e propagação de onda. Os resultados demonstraram que o programa desenvolvido para utilizar o paralelismo da GPU foi mais rápido que na CPU.

(32)

CPU com múltiplas threads em múltiplos núcleos. Na comparação entre os resul-tados obtidos com a implementação múltiplas threads em múltiplos núcleos e a implementação para GPU demonstrou que a GPU teve melhores resultados.

(33)

todos de implementação utilizados e a descrição de como serão realizados os ex-perimentos e da entrada para a rede neural implementada.

3.1 Tipo de Pesquisa

Trata-se de uma pesquisa exploratória, pois visa o aprimoramento de idéias já existentes e a descoberta de novas informações. Quanto aos procedimentos pode ser classificada como pesquisa experimental, pois requer a manipulação de variáveis para a coleta de dados sobre o fenômeno de interesse, visando obter os melhores resultados.

3.2 Materiais

O equipamento utilizado para realizar os experimentos foi um computador equipado com processador Intel Core 2 Duo E8400, 3.0 GHz, FSB 1333Mhz, cache L2 6MB, soquete 775 com 4GB de memória DDR2 667Mhz e uma placa gráfica GeForce GTX 285, PCI-e 16x, barramento 512-bits, 1024MB GDDR3, core 670MHz, memória 2500MHz (Figura 15). O sistema operacional utilizado foi o Ubuntu 9.04.

3.3 Procedimentos Metodológicos

Nesta seção são apresentados os métodos utilizados para a paralelização da rede neural artificial. Para a primeira técnica de paralelização, método da me-nor granularidade, a rede neural artificial usada para análise de performance é uma versão modificada de uma RNA desenvolvida por Karsten Kutza(1996), pois

(34)

ape-Figura 15: Leitura do programa GPU-Z da GPU GTX285

nas uma pequena parte do algoritmo foi implementado para a GPU. A página na qual foi retirado o código do Karsten Kutza possui um conjunto de códigos de rede neural fonte para entusiastas e pesquisadores (Japan Singapore AI Centre), servido de base para estudos. Para a segunda técnica de paralelização, método do neurô-nio por thread, foi criada uma nova rede neural artificial, que faça o mesmo da desenvolvida por Karsten Kutza, mas que todo o treinamento fosse implementado para GPU. A rede neural artificial é de camada simples usada para classificador de padrões, como mostrada na figura 16.

(35)

Figura 16: Representação gráfica da rede neural implementada

A rede neural deve ser capaz de reconhecer as classes de representação de dígitos alfa numéricos com dimensões 5x7 como o exemplo do número um na figura 17, também pode ser visto no anexo códigos de RNA.

Figura 17: Representação gráfica do dígito do número um

Todos os códigos usados na monografia, incluido o do anexo Crivo de Erastóstenes, foram desenvolvidos primeiramente sobre o emulation mode, ou modo de emulação, disponível pelo CUDA até a versão 3.0. O modo de

(36)

emu-printf()

que estejam na kernel da GPU, opção que não existe caso o código esteja rodando direto na GPU.

A maior desvantagem do modo de emulação está no número total de threadsque suporta, apenas 512. Independente do número de grids e blocos cria-dos.

O modo de emulação pode ser facilmente usado adicionando a flag

emu=1

após o comando

make

para compilação do algoritmo.

3.3.1 Método da menor granularidade

No método da menor granularidade somente uma pequena fração do có-digo é paralelizada. Este pequeno trecho geralmente corresponde a parte que pos-sui grande repetição e tempo de computação. A rede neural artificial foi imple-mentada de forma seqüencial para ser executada na CPU. O algoritmo seqüencial é apresentado na figura 18.

Foi identificada a função que mais tempo gasta ao ser executada e esta foi colocada para ser executada na GPU. Para isso, foi utilizada a ferramenta gprof

(37)

Figura 18: Algoritmo do código

(Fenlason e Stallman, 2010), que informa o percentual de tempo gasto em cada função.

No código foi identificada a função que mais exige tempo de processa-mento. A função de nome AdjustWeights está dentro da simulação da rede neural. A figura 19exibe um relatório completo do programa gprof onde pode-se identi-ficar a função anteriormente mencionada gasta 45,83 por cento do tempo total de execução. Esta função é chamada várias vezes em diferentes trechos do programa. A figura 20apresenta o algoritmo da função AdjustWeights que será exe-cutado de forma seqüencial na CPU.

Utilizando das mesmas técnicas de programação, a função AdjustWeights foi adaptada para ser executada na GPU. A figura 21 mostra como ficou o al-goritmo adaptado para GPU. Pode-se observar que há uma parte do alal-goritmo executada na CPU e outra na GPU (Kernel GPU)

Na análise de complexidade da função AdjustWeights percebe-se que pos-sui uma complexidade de tempo de O2devido ao for encadeado para ajuste de

(38)

pe-Figura 19: Relatório gprof

Figura 20: Função AdjustWeights CPU

sos. Utilizando de técnicas de programação específicas e da forma como o CUDA estrutura as threads para serem executadas dentro da GPU é possível reduzir esta complexidade para constante, pois o número de threads utilizadas é igual ao ta-manho do vetor de pesos. A figura 22mostra o algoritmo com a utilização desta técnica. Pode-se observar que na linha 5 a chamada da função Kernel é realizada considerando que serão criadas N threads, onde N é o número de elementos do

(39)

Figura 21: Sequencial GPU (1 thread)

vetor de tamanho ixj domo da variável wheight[i][j]. O algoritmo do método da menor granularidade pode ser visto no anexo códigos de RNA.

3.3.2 Método do Neurônio por thread

A rede neural teve todo o seu treinamento implementado na GPU. Cara thread na GPU apresentará apenas a entrada para o treinamento de um neurônio, como é ilustrado na figura 19.

Como forma de diminuir o número de trocas de informação entre a CPU e GPU foi feito uma análise das variáveis necessárias a todo código e somente ao treinamento. Variáveis importantes somente para o treinamento foram implemen-tadas somente na GPU, livrando a CPU o tempo de criação e a GPU o tempo de cópia da mesma para sua memória.

(40)

Figura 22: Sequencial GPU (N thread)

Figura 23: Componentes de uma thread

Com este novo método diminuir drasticamente a troca de informação entre CPU e GPU, reduzindo a apenas 2 momentos, o envio e o retorno do Peso. O algoritmo do método de um neurônio por thread pode ser visto no anexo intitulado "‘códigos de RNA"’.

(41)
(42)

de 6 os resultados entre os tamanhos de treinamento ficaram muito próximos. A comparação de desempenho entre arquiteturas diferentes (CPU e GPU) não é justa, pois temos velocidades de clock, quantidade de memória, número de núcleos entre outras variáveis diferentes. Por isso, buscou-se comparar a execução com 1 e com N threads na mesma arquitetura (GPU).

Para cada tamanho de problema foram realizados 3 experimentos: exe-cução do código seqüencialmente na CPU, seqüencialmente na GPU utilizando somente uma thread e paralelizado na GPU utilizando N threads. Cada código foi executado 10 vezes e calculada a média aritmética do tempo gasto. No primeiro tamanho de problema é feito o treinamento da rede neural com 11 neurônios, no segundo são 22 neurônios e no terceiro 34 neurônios.

4.1 Resultados Obtidos com o Método da Menor Granularidade Para este método, o valor de N é igual a da quantidade de pesos da rede neural artificial. Na figura 25 são apresentados os tempos em microssegundos gastos para a execução dos experimentos com 11 elementos.

Podemos observar na figura 25 que o tempo de execução seqüencial do código na CPU foi menor, aproximadamente 121,9 vezes menor que o mesmo có-digo executado seqüencialmente na GPU e aproximadamente 69.5 menor que o

(43)

Figura 25: Treinamento 11 neurônios

código usando N threads na GPU, isso comparando estruturas de hardware dife-rentes (CPUxGPU).

Comparando o tempo de execução do código na GPU observa-se que quando se utiliza N threads obtém-se um tempo de execução em torno de 1,7 vezes menor que o mesmo código executado sequencialmente em uma thread na GPU.

Na figura 26são apresentados os tempos gastos para a execução dos ex-perimentos com 22 elementos.

Podemos observar na figura 26 que o tempo de execução seqüencial do código na CPU foi menor, aproximadamente 112,5 vezes menor que o mesmo có-digo executado seqüencialmente na GPU e aproximadamente 33.5 menor que o código usando N threads na GPU, isso comparando estruturas de hardware dife-rentes (CPUxGPU).

(44)

Figura 26: Treinamento 22 neurônios

Comparando o tempo de execução do código na GPU observa-se que quando se utiliza N threads obtém-se um tempo de execução em torno de 3,3 vezes menor que o mesmo código executado sequencialmente em uma thread na GPU.

Na figura 27são apresentados os tempos gastos para a execução dos ex-perimentos com 34 elementos.

Podemos observar na figura 27 que o tempo de execução seqüencial do código na CPU foi menor, aproximadamente 112,6 vezes menor que o mesmo có-digo executado seqüencialmente na GPU e aproximadamente 17,8 menor que o código usando N threads na GPU, isso comparando estruturas de hardware dife-rentes (CPUxGPU).

Comparando o tempo de execução do código na GPU observa-se que quando se utiliza N threads obtém-se um tempo de execução em torno de 6,7

(45)

Figura 27: Treinamento 34 neurônios

vezes menor que o mesmo código executado sequencialmente em uma thread na GPU.

Observando o aumento do problema e o comportamento entre estruturas de hardware e técnicas de programação podemos perceber que o código seqüencial na CPU mesmo sendo mais rápido até então apresentou aumento de tempo de 8,4 vezes, enquanto o aumento do tempo seqüencial na GPU foi de 7,3 vezes e o aumento de tempo utilizando N threads na GPU foi de 2,0 vezes.

Dentro de uma mesma arquitetura de hardware, podemos notar que a dife-rença de técnicas de programação faz com que a GPU seja mais bem aproveitada. A diferença de tempo de execução vai se alargando entre as duas implementações apresentadas à medida que o tamanho do problema aumenta.

(46)

Figura 28: Treinamento 11 neurônios

Podemos observar na figura 28 que o tempo de execução seqüencial do código na CPU foi menor, aproximadamente 523,7 vezes menor que o mesmo có-digo executado seqüencialmente na GPU e aproximadamente 68,7 menor que o código usando N threads na GPU, isso comparando estruturas de hardware dife-rentes (CPUxGPU).

Comparando o tempo de execução do código na GPU observa-se que quando se utiliza N threads obtém-se um tempo de execução em torno de 7,5 vezes menor que o mesmo código executado sequencialmente em uma thread na GPU.

(47)

perimentos com 22 elementos.

Figura 29: Treinamento 22 neurônios

Podemos observar na figura 29 que o tempo de execução seqüencial do código na CPU foi menor, aproximadamente 577,5 vezes menor que o mesmo có-digo executado seqüencialmente na GPU e aproximadamente 34,3 menor que o código usando N threads na GPU, isso comparando estruturas de hardware dife-rentes (CPUxGPU).

Comparando o tempo de execução do código na GPU observa-se que quando se utiliza N threads obtém-se um tempo de execução em torno de 17,0 vezes menor que o mesmo código executado sequencialmente em uma thread na GPU.

Na figura 30são apresentados os tempos gastos para a execução dos ex-perimentos com 34 elementos.

(48)

Figura 30: Treinamento 34 neurônios

Podemos observar na figura 30 que o tempo de execução seqüencial do código na CPU foi menor, aproximadamente 538 vezes menor que o mesmo có-digo executado seqüencialmente na GPU e aproximadamente 21,2 menor que o código usando N threads na GPU, isso comparando estruturas de hardware dife-rentes (CPUxGPU).

Comparando o tempo de execução do código na GPU observa-se que quando se utiliza N threads obtém-se um tempo de execução em torno de 25,2 vezes menor que o mesmo código executado sequencialmente em uma thread na GPU.

Observando o aumento do problema, pouco mais de 3 vezes, e o comporta-mento entre estruturas de hardware e técnicas de programação podemos perceber que o código seqüencial na CPU apresentou aumento de tempo em torno de 8,9 vezes. Foi de 2,8 vezes da GPU utilizando threads enquanto o aumento do tempo seqüencial na GPU foi de 10,1 vezes.

(49)

rença de técnicas de programação faz com que a GPU seja mais bem aproveitada. A diferença de tempo de execução vai se alargando entre as duas implementações apresentadas à medida que o tamanho do problema aumenta.

No anexo Crivo de Erastóstenes será discutido o que foi apontado como gargalo nos métodos de implementação das redes neurais junto a um algoritmo com ganho real de desempenho da GPU sobre a CPU.

(50)

técnicas implementadas na GPGPU.

A implementação do método da menor granularidade teve como desvan-tagem o número alto de trocas de informação entre a CPU e GPU, que segundo o ’NVIDIA CUDA C Best Practices Guide’ é algo que deve ser evitado. A im-plementação do método de um neurônio por thread teve como objetivo evitar ao máximo a troca de informação entre a CPU e GPU, mas apresentou como desvan-tagem a baixa quantidade de threads utilizadas, ou seja, tendo em vista que a GPU é um dispositivo de paralelismo em massa, esta foi subutilizada para esta tarefa.

Para melhorar a eficiência, o ideal seria aumentar a quantidade de threads no método de um neurônio por thread. Os resultados obtidos mostraram que para aproveitar melhor os recursos computacionais de uma GPGPU é necessário utilizar técnicas específicas de programação, designadas exclusivamente para esse tipo de hardware.

5.1 Propostas de Continuidade Para os trabalhos futuros, sugere-se:

• estudar e implementar novos métodos de implementação de redes neurais artificiais utilizando o CUDA;

(51)

• testar a rede neural implementada com novos dados de treinamento.

• reconhecimento de padrões em linhas de montagem;

(52)

ARM Ltd and ARM Germany GmbH, 2011. Sieve of Eratosthenes. Disponível em http://www.keil.com/benchmarks/sieve.asp. Acessado em 17/05/2011.

ATI Stream Technology. GPU Technology for Accelerated Computing. Dis-ponível em http://www.amd.com/US/PRODUCTS/TECHNOLOGIES/STREAM-T ECHNOLOGY/Pages/stream-technology.aspx. Acessado em 10/09/2009.

Barreto, J. B. Introdução às Redes Neurais Artificiais. 2002. Disponível em http://www.inf.ufsc.br/ barreto/tutoriais/Survey.pdf. Acessado em 10/09/2009.

Fang, W., Lau, K. K., Lu, M., Xiao, X., Lam, C. K., Yang, P. Y., He, B., Luo, Q., Sander, P. V. and Yang, K. Parallel Data Mining on Graphics Processors. Disponível em http://gpuminer.googlecode.com/files/gpuminer.pdf. Acessado em 10/09/2009.

Fenlason J, Stallman R. GNU gprof: The GNU Profiler. Disponível em http://ww w.cs.utah.edu/dept/old/texinfo/as/gprof_toc.html. Acessado em 10/09/2009.

Fernandez, A., Martin, R. S., Farguell, E. and Pazienza, G. E. Cellular Neural Networks Simulation on a Parallel Graphics Processing Unit. 11th

(53)

Interna-Compostela, Spain, 14-16 July 2008.

Foster, I. Designing and Building Parallel Programs. Disponível em http://ww w.mcs.anl.gov/ itf/dbpp. Acessado em 10/09/2009.

GPGPU.org. General-Purpose Computation on Graphics Hardware. Disponí-vel em http://gpgpu.org/about. Acessado em 10/09/2009.

Gschwind, M., Hofstee, P., Flachs, B., Hopkins, M., Watanabe, Y. and Yamazaki, T. Broadband Processor Architecture. A novel SIMD architecture for the Cell heterogeneous chip-multiprocessor. Disponível em http://www.hotchips.org/arc hives/hc17/2 Mon/HC17.S1/HC17.S1T1.pdf. Acessado em 10/09/2009.

Hebb, D. O. The Organization of Behavior: A Neuropsychological Theory. John Wiley & Sons Inc., 1949.

Jang, H., Park, A., Jung, K. Neural Network Implementation using CUDA and OpenMP.- Proceedings of the 2008 Digital Image Computing: Techniques and Applications. Disponível em http://portal.acm.org/citation.cfm?id=1470322. Aces-sado em 10/09/2009.

Japan Singapore AI Centre. Neural Networks Source Code. Disponível em http://tralvex.com/pub/nap/nn-src. Acessado em 29/09/2010.

(54)

Ly, D. L. and Chow, P. High-Performance Reconfigurable Hardware Architec-ture for Restricted Boltzmann Machines. IEEE Transactions on Neural Net-works, 2010.

McCulloch, W. and Pitts, W. A logical calculus of the ideas immanent in ner-vous activity. Bulletin of Mathematical Biophysics, vol. 5, num. 4, pages 115-133, 1943.

Marowka, A. Think Parallel: Teaching Parallel Programming Today, IEEE Distributed Systems Online, vol. 9, no. 8, 2008, art. no. 0808-o8002. Disponível em http://www.computer.org/portal/web/csdl/abs/html/mags/ds/2008/08/mds2008 080001.htm. Acessado em 10/09/2009.

Misra, J. and Saha, I. Artificial neural networks in hardware: A survey of two decades of progress. Neurocomputing, Article in Press, 2010.

Mocelin, C. L., Silva, E. F. B., Tobaldini, G. I. GPU: Aspectos Gerais. Disponível em http://www.uri.com.br/ adario/disciplinas/AC2/files/Artigo%20GPU.pdf. Aces-sado em 10/09/2009.

(55)

cient Simulation of Large-Scale Spiking Neural Networks Using CUDA Gra-phics Processors. Disponível em http://www.ics.uci.edu/ jmoorkan/pub/gpusnn-ijcnn.pdf. Acessado em 10/09/2009.

NVIDIA Corporation. CUDA C Best Practices Guide. Disponível em http://dev eloper.download.nvidia.com/compute/cuda/3_0/toolkit/docs/NVIDIA_CUDA_Pro grammingGuide.pdf. Acessado em 10/09/2009.

NVIDIA Corporation. Programing Guide 3.0. Disponível em http://developer.d ownload.nvidia.com/compute/cuda/3_0/toolkit/docs/NVIDIA_CUDA_Programm ingGuide.pdf. Acessado em 10/09/2009.

NVIDIA Corporation. What is Cuda?. Disponível em http://www.nvidia.com/ object/cuda_what_is.html. Acessado em 10/09/2009.

Pamplona, Vitor. Análise de Performance: C vs CUDA. Disponível em http://vit orpamplona.com/wiki/An%C3%A1lise%20de%20Performance:%20C%20vs%20 Cuda. Acessado em 10/09/2009.

Poli, G., Saito, J. H., Mari, J. F. and Zorzan, M. R. Processing Neocognitron of Face Recognition on High Performance Environment Based on GPU with CUDA Architecture. 20th International Symposium on Computer Architecture and High Performance Computing, 2008.

(56)

mentation of Artificial Neural Network training for speech recognition. Pat-tern Recognition Letters. Volume 31, Issue 11, 1 August 2010, Pages 1302-1309.

Shi, M., Pan, W., Garis, H. and Chen, K. Approach to Controlling Robot by Artificial Brain Based on Parallel Evolutionary Neural Network. 2nd Interna-tional Conference on Industrial Mechatronics and Automation, 30-31 May, China, 2010.

Tatibana, C. Y. , Kaetsu, D. Aplicações de Redes Neurais Artificiais 2009 Dis-ponível em http://www.din.uem.br/ia/neurais. Acessado em 10/09/2009.

(57)

para determinar a velocidade relativa de diferentes computadores, ou, neste caso, a eficiência do código gerado para a CPU e GPU. O algoritmo foi desenvolvido peneira na Grécia antiga e é um dos vários métodos usados para encontrar números primos.

A fim de testar o real poder de processamento paralelo da GPU, foram implementados 2 algoritmos do Crivo de Eratóstenes, um seqüencial para CPU e um paralelo para GPU.

O processo é:

2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Começando após 2, eliminar todos os múltiplos de 2.

2 3 5 7 9 11 13 15 17 19 21 23 25

Começando após 3, eliminar todos os múltiplos de 3. 2 3 5 7 11 13 17 19 23 25

Começando após 5, eliminar todos os múltiplos de 5. 2 3 5 7 11 13 17 19 23

Continue até o próximo número ainda é maior do que a raiz quadrada do maior número na série original. Neste caso, o próximo número, 7, é maior do que a raiz quadrada de 25, portanto, o processo pára. Os números restantes são primos.

2 3 5 7 11 13 17 19 23

Mas para esta medição de performance, afim de aumentar o número de processamento nas plataformas, a verificação para o próximo número primo conti-nuará até a metade do maior número da série.

Para o algoritmo do Crivo de Eratóstenes que será executado junto a GPU, foram seguidos da melhor maneira possível as informações contidas no NVIDIA

(58)

Figura 31: Tempo do Crivo

Observando a figura 31, podemos concluir que com o aumento da série para números próximos a 106o Crivo de Erastóstenes na GPU foi 16,5 vezes mais rápido que na CPU.

(59)

#include <cuda.h> #include <cutil.h> #include<stdio.h> #include <cuda_runtime.h> /////////////////////////////////////////////////////////////////////////////////// //GPU KERNEL __shared__ int k;

__global__ void crivo_array(int *a)

{

int idx = blockIdx.x*blockDim.x+threadIdx.x; a[idx]= idx;

k=2;

while(k*2<=idx){

(60)

while(a[k]<0){ k++; } } } /////////////////////////////////////////////////////////////////////////////////// //CPU int main(void) {

int *a_h, *a_d, threads, blocks;

const int N=1000000;

if (N < 256) {

(61)

} else { threads = 256; if ((N % threads) == 0) blocks = N/threads; else blocks = (N/threads) + 1; }

size_t size = N*sizeof(int);

a_h=(int*)malloc(size);

cudaMalloc((void**)&a_d,size);

unsigned int timer = 0;

CUT_SAFE_CALL(cutCreateTimer(&timer)); CUT_SAFE_CALL(cutStartTimer(timer));

cudaMemcpy(a_d,a_h,size,cudaMemcpyHostToDevice); crivo_array<<<blocks,threads>>>(a_d);

cudaMemcpy(a_h,a_d,size,cudaMemcpyDeviceToHost);

(62)

for(int i=0; i<N; i++) if (a_h[i] > 0) printf("%d\n",a_h[i]); free(a_h); cudaFree(a_d); }

Código Crivo de Eratóstenes para CPU com a série de 106elementos.

#include <cuda.h> #include <cutil.h> #include<stdio.h> #include <cuda_runtime.h> int main(void) { int *a_h, i, j; j=0;

(63)

const int N=1000000;

size_t size = N*sizeof(int);

a_h=(int*)malloc(size);

for( i=0; i<N; i++) a_h[i]= i + 2;

int k = a_h[0];

unsigned int timer = 0;

CUT_SAFE_CALL(cutCreateTimer(&timer)); CUT_SAFE_CALL(cutStartTimer(timer));

while (j*2 <= (N-2)) {

j++;

for(i=j; i<N; i++){

if (((a_h[i] % k) == (0))&&(a_h[i] > 0)) a_h[i] = a_h[i] * -1;

}

(64)

}

k = a_h[j]; }

printf("\nTempo de computacao: %f (ms) \n\n", cutGetTimerValue(timer)); CUT_SAFE_CALL(cutDeleteTimer(timer));

printf("São primos os numeros entre 2 e %d\n", N); for(i=0; i<(N-2); i++)

if (a_h[i] > 0)

printf("%d\n",a_h[i]);

(65)

threads.

#include <stdlib.h> #include <stdio.h>

#include <cuda_runtime.h> #include <cutil.h>

typedef int BOOL; typedef char CHAR;

typedef int INT;

typedef float REAL; //typedef double REAL;

#define FALSE 0

#define TRUE 1

#define NOT !

#define AND &&

#define OR ||

#define MIN(x,y) ((x)<(y) ? (x) : (y)) #define MAX(x,y) ((x)>(y) ? (x) : (y))

#define LO -1

(66)

//variavel global REAL *vetorA ; INT *vetorB ; REAL *matrizAB; REAL *d_vetorA ; INT *d_vetorB; REAL *d_matrizAB ;

REAL *SUPERmatrizAB ;/// Net->OutputLayer->Weight[i][j] size_t size = sizeof(float) * 11 * 36;

typedef struct { /* A LAYER OF A NET: */

INT Units; /* - number of units in this layer */ REAL* Activation; /* - activation of ith unit */

INT* Output; /* - output of ith unit */

REAL* Error; /* - error term of ith unit */

REAL** Weight; /* - connection weights to ith unit */ } LAYER;

typedef struct { /* A NET: */

(67)

REAL Eta; /* - learning rate */

REAL Error; /* - total net error */

REAL Epsilon; /* - net error to terminate training */ } NET; /****************************************************************************** R A N D O M S D R A W N F R O M D I S T R I B U T I O N S ******************************************************************************/ void InitializeRandoms() { srand(4711); }

INT RandomEqualINT(INT Low, INT High) {

return rand() % (High-Low+1) + Low; }

REAL RandomEqualREAL(REAL Low, REAL High) {

(68)

/****************************************************************************** A P P L I C A T I O N - S P E C I F I C C O D E ******************************************************************************/ #define NUM_DATA 11 #define X 5 #define Y 7 #define N (X * Y) #define M 11 //Pattern[NUM_DATA][X][Y]

CHAR Pattern[NUM_DATA][7][6] = { { " OOO ", "O O", "O O", "O O", "O O", "O O", " OOO " }, { " O ",

(69)

"O O ", " O ", " O ", " O ", " O " }, { " OOO ", "O O", " O", " O ", " O ", " O ", "OOOOO" }, { " OOO ", "O O", " O", " OOO ", " O", "O O", " OOO " }, { " O ", " OO ", " O O ",

(70)

{ "OOOOO", "O ", "O ", "OOOO ", " O", "O O", " OOO " }, { " OOO ", "O O", "O ", "OOOO ", "O O", "O O", " OOO " }, { "OOOOO", " O", " O", " O ", " O ",

(71)

"O " }, { " OOO ", "O O", "O O", " OOO ", "O O", "O O", " OOO " }, { " OOO ", "O O", "O O", " OOOO", " O", "O O", " OOO " }, { " O ", " O O ", "O O", "O O", "OOOOO", "O O", "O O" } };

(72)

{ {HI, LO, LO, LO, LO, LO, LO, LO, LO, LO, LO}, {LO, HI, LO, LO, LO, LO, LO, LO, LO, LO, LO}, {LO, LO, HI, LO, LO, LO, LO, LO, LO, LO, LO}, {LO, LO, LO, HI, LO, LO, LO, LO, LO, LO, LO}, {LO, LO, LO, LO, HI, LO, LO, LO, LO, LO, LO}, {LO, LO, LO, LO, LO, HI, LO, LO, LO, LO, LO}, {LO, LO, LO, LO, LO, LO, HI, LO, LO, LO, LO}, {LO, LO, LO, LO, LO, LO, LO, HI, LO, LO, LO}, {LO, LO, LO, LO, LO, LO, LO, LO, HI, LO, LO}, {LO, LO, LO, LO, LO, LO, LO, LO, LO, HI, LO}, {LO, LO, LO, LO, LO, LO, LO, LO, LO, LO, HI} };

FILE* f;

void InitializeApplication(NET* Net) {

INT n,i,j;

Net->Eta = 0.001; Net->Epsilon = 0.0001;

(73)

for (i=0; i<Y; i++) { for (j=0; j<X; j++) {

Input[n][i*X+j] = (Pattern[n][i][j] == ’O’) ? HI : LO; }

} }

f = fopen("SAIDA.txt", "w"); }

void WriteInput(NET* Net, INT* Input) {

INT i;

for (i=0; i<N; i++) { if (i%X == 0) {

fprintf(f, "\n"); }

fprintf(f, "%c", (Input[i] == HI) ? ’O’ : ’ ’); }

fprintf(f, " -> "); }

(74)

Count = 0;

for (i=0; i<M; i++) { if (Output[i] == HI) { Count++; Index = i; } } if (Count == 1)

fprintf(f, "%i\n", Index); else

fprintf(f, "%s\n", "invalid"); }

void FinalizeApplication(NET* Net) {

fclose(f); }

/****************************************************************************** I N I T I A L I Z A T I O N

(75)

void GenerateNetwork(NET* Net) {

INT i;

Net->InputLayer = (LAYER*) malloc(sizeof(LAYER)); Net->OutputLayer = (LAYER*) malloc(sizeof(LAYER));

Net->InputLayer->Units = N;

Net->InputLayer->Output = (INT*) calloc(N+1, sizeof(INT)); Net->InputLayer->Output[0] = BIAS;

Net->OutputLayer->Units = M;

Net->OutputLayer->Activation = (REAL*) calloc(M+1, sizeof(REAL)); Net->OutputLayer->Output = (INT*) calloc(M+1, sizeof(INT)); Net->OutputLayer->Error = (REAL*) calloc(M+1, sizeof(REAL)); Net->OutputLayer->Weight = (REAL**) calloc(M+1, sizeof(REAL*));

for (i=1; i<=M; i++) {

Net->OutputLayer->Weight[i] = (REAL*) calloc(N+1, sizeof(REAL)); }

Net->Eta = 0.1; Net->Epsilon = 0.01;

(76)

{

INT i,j,k;

k=0;

for (i=1; i<=Net->OutputLayer->Units; i++) { for (j=0; j<=Net->InputLayer->Units; j++) { SUPERmatrizAB[k] = 0; k++; } } }

void SetInput(NET* Net, INT* Input, BOOL Protocoling) {

INT i;

for (i=1; i<=Net->InputLayer->Units; i++) { Net->InputLayer->Output[i] = Input[i-1]; vetorB[i] = Input[i-1];

}

(77)

} }

void GetOutput(NET* Net, INT* Output, BOOL Protocoling) {

INT i;

for (i=1; i<=Net->OutputLayer->Units; i++) { Output[i-1] = Net->OutputLayer->Output[i]; } if (Protocoling) { WriteOutput(Net, Output); } } /****************************************************************************** P R O P A G A T I N G S I G N A L S ******************************************************************************/

void PropagateNet(NET* Net) {

(78)

Sum = 0;

for (j=0; j<=Net->InputLayer->Units; j++) {

Sum += SUPERmatrizAB[k] * Net->InputLayer->Output[j]; k++; } Net->OutputLayer->Activation[i] = Sum; if (Sum >= 0) Net->OutputLayer->Output[i] = HI; else Net->OutputLayer->Output[i] = LO; } } /****************************************************************************** A D J U S T I N G W E I G H T S ******************************************************************************/

void ComputeOutputError(NET* Net, INT* Target) {

(79)

Net->Error = 0;

for (i=1; i<=Net->OutputLayer->Units; i++) {

Err = Target[i-1] - Net->OutputLayer->Output[i]; Net->OutputLayer->Error[i] = Err;

vetorA[i-1] = Err; // NEW Net->Error += 0.5 * sqr(Err); }

}

///////////////////////////////////////////////////////////////////// //Kernel GPU

__global__ void multi_vetor( REAL *A, INT *B, REAL *AB, INT col, REAL net )

{

int idx = threadIdx.x ;

AB[idx] += A[idx - (idx - ( idx/col ))] * B[idx - (col * ( idx/col ))] * net;

}

(80)

int col;

// Codigo CUDA

cudaMemcpy(d_vetorA, vetorA, size, cudaMemcpyHostToDevice); cudaMemcpy(d_vetorB, vetorB, size, cudaMemcpyHostToDevice);

cudaMemcpy(d_matrizAB, SUPERmatrizAB, size, cudaMemcpyHostToDevice);

net = Net->Eta; col = 36;

multi_vetor<<<1, 396>>>(d_vetorA, d_vetorB, d_matrizAB, col, net);

cudaMemcpy(SUPERmatrizAB, d_matrizAB, size, cudaMemcpyDeviceToHost);

}

///////////////////////////////////////////////////////////////////////////// /******************************************************************************

S I M U L A T I N G T H E N E T

******************************************************************************/

(81)

{

INT Output[M];

SetInput(Net, Input, Protocoling); PropagateNet(Net);

GetOutput(Net, Output, Protocoling);

ComputeOutputError(Net, Target); if (Training) AdjustWeights(Net); } /****************************************************************************** M A I N ******************************************************************************/ int main() { NET Net; REAL Error; BOOL Stop; INT n,m;

(82)

vetorA= (REAL*) malloc(size); vetorB= (int*) malloc(size); matrizAB= (REAL*) malloc(size); SUPERmatrizAB= (REAL*) malloc(size);

cudaMalloc((void**)&d_vetorA, size); cudaMalloc((void**)&d_vetorB, size); cudaMalloc((void**)&d_matrizAB, size); InitializeRandoms(); GenerateNetwork(&Net); RandomWeights(&Net); InitializeApplication(&Net); do { Error = 0;

(83)

for (n=0; n<NUM_DATA; n++) {

SimulateNet(&Net, Input[n], Output[n], FALSE, FALSE); Error = MAX(Error, Net.Error);

Stop = Stop AND (Net.Error < Net.Epsilon); }

Error = MAX(Error, Net.Epsilon); if (NOT Stop) {

for (m=0; m<10*NUM_DATA; m++) { n = RandomEqualINT(0, NUM_DATA-1);

SimulateNet(&Net, Input[n], Output[n], TRUE, FALSE); }

}

} while (NOT Stop);

for (n=0; n<NUM_DATA; n++) {

SimulateNet(&Net, Input[n], Output[n], FALSE, TRUE); }

printf("\n Tempo de computacao: %f (ms) \n", cutGetTimerValue(timer)); cutDeleteTimer(timer);

FinalizeApplication(&Net);

cudaFree(d_vetorA); cudaFree(d_vetorB);

(84)

Código do método do neurônio por thread com apenas 11 neurônios e cada neurônio em uma thread.

#include <stdlib.h> #include <stdio.h> #include <cuda_runtime.h> #include <cutil.h> FILE* f; //////////////////////////// //CUDA// ////////////////////////////

__global__ void treinamento(float *Peso){

int idx = blockIdx.x*blockDim.x+threadIdx.x;

float NetError, Soma, Ativa[11], ERRO[11], Xin[36]; int n, i, j, conta, Parada, YSaida[11];

(85)

float tolerancia = 0.0001; int XQuantidade = 35; int YinQuantidade = 11; Xin[0] = 1; float Entrada [11][35] = {{-1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, -1 }, {-1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1 }, {-1, 1, 1, 1, -1,

(86)

1, -1, -1, -1, -1, -1, 1, 1, 1, 1 }, {1, 1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, 1, -1 }, {1, -1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1 }, {-1, 1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1,

(87)

-1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, 1, -1 }, {-1, -1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, -1 }, {1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1 }, {-1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, -1, 1, -1, -1, -1, 1,

(88)

1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, 1, -1 }, {-1, -1, 1, -1, -1, //A -1, 1, -1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1 }}; float YDesejado[11][11] = {{1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {-1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {-1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1}, {-1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1}, {-1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1},

(89)

{-1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1}, {-1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1}, {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1}};

n=idx;

for (i=1; i<=XQuantidade; i++) { Xin[i] = Entrada[n][i-1]; } Parada = 0; while (Parada != 1) { Parada = 1; conta= 396 * idx;

for (i=1; i<=YinQuantidade; i++) { Soma = 0;

for (j=0; j<=XQuantidade; j++) {

Soma += __fmul_rn(Peso[conta], Xin[j]); conta++;

}

(90)

}

NetError = 0;

for (i=1; i<=YinQuantidade; i++) { ERRO[i] = YDesejado[n][i-1] - YSaida[i]; NetError += __fmul_rn(ERRO[i], ERRO[i]); }

conta=396 * idx;

for (i=1; i<=YinQuantidade; i++) { for (j=0; j<=XQuantidade; j++) {

Peso[conta] += __fmul_rn(__fmul_rn(Alfa, ERRO[i]), Xin[j]); conta++;

} }

if (NetError > tolerancia) Parada = 0;

(91)

}

}

//////////////////////////// Fim CUDA ////////////////////////////

//////////////////////////// RANDOM ////////////////////////////

float RandomFloat(float Low, float High) {

return ((float) rand() / RAND_MAX) * (High-Low) + Low; }

//////////////////////////// Fim RANDOM ////////////////////////////

(92)

int YDesejado2[11]; int i;

for (i=1; i<=XQuantidade; i++) { Xin[i] = Entrada[i-1];

}

for (i=0; i<35; i++) { if (i%5 == 0) {

fprintf(f, "\n"); }

fprintf(f, "%c", (Entrada[i] == 1) ? ’O’ : ’ ’); }

fprintf(f, " -> ");

int j, conta; float Soma;

(93)

Soma = 0;

for (j=0; j<=XQuantidade; j++) { Soma += Peso[conta] * Xin[j]; conta++; } Ativa[i] = Soma; if (Soma >= 0) YSaida[i] = 1; else YSaida[i] = 0; }

for (i=1; i<=YinQuantidade; i++) { YDesejado2[i-1] = YSaida[i];

//printf("YSaida[%d] %d\n", i, YSaida[i]); }

int Count, Index;

Count = 0;

for (i=0; i<11; i++) { if (YDesejado2[i] == 1) { Count++;

(94)

fprintf(f, "%i\n", Index); else fprintf(f, "%s\n", "invalid"); } //////////////////////////// //MAIN// //////////////////////////// int main() {

float Ativa[11], ERRO[11];

int n, conta, YSaida[11], Xin[36];

int XQuantidade = 35; int YinQuantidade = 11;

(95)

srand(4711);

size_t size = 4356*sizeof(float); float* Peso, *_Peso;

Peso =(float*)malloc(size);

cudaMalloc((void**)&_Peso,size);

for (conta=0; conta<=4356; conta++) { Peso[conta]= 0; } int Entrada [11][35] = {{-1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, -1 },

(96)

-1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1 }, {-1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1 }, {1, 1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, 1, -1 }, {1, -1, -1, -1, 1, 1, -1, -1, -1, 1,

(97)

-1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1 }, {-1, 1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, 1, -1 }, {-1, -1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, -1 }, {1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, -1, -1,

(98)

{-1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, -1 }, {-1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, 1, -1 }, {-1, -1, 1, -1, -1, //A -1, 1, -1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 1,

(99)

int YDesejado[11][11] = {{1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {-1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {-1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1}, {-1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1}, {-1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1}, {-1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1}, {-1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1}, {1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}; f = fopen("SAIDA.txt", "w");

cudaMemcpy(_Peso, Peso, size, cudaMemcpyHostToDevice);

unsigned int timer = 0;

CUT_SAFE_CALL(cutCreateTimer(&timer)); CUT_SAFE_CALL(cutStartTimer(timer));

(100)

cudaMemcpy(Peso, _Peso, size, cudaMemcpyDeviceToHost);

for (n=0; n<11; n++) {

yperceptron(ERRO, Peso, Ativa, Xin, YSaida, XQuantidade, YinQuantidade, Entrada[n], YDesejado[n], n);

}

fclose(f);

Referências

Documentos relacionados

a) “Para analisar o processo universal de desencantamento, que se cumpre na história das grandes religiões e que, a seu juízo, satisfaz as condições internas necessárias para

fundamentada no referencial teórico, objetivando compreender as percepções de ensino de gêneros textuais/discursivos e/ou textos relatadas pelos professores, durante

17.º, n.º 4 - Se a abertura de um processo de insolvência for recusada por tribunal de um Estado-membro da União Europeia em virtude de a competência caber aos tribunais

Segundo o mesmo autor, a animação sociocultural, na faixa etária dos adultos, apresenta linhas de intervenção que não se esgotam no tempo livre, devendo-se estender,

O personagem Negreiro evidencia a facilidade em burlar a lei de 1831, que previa que todo o africano que desembarcasse nas costas brasileiras seria livre: “Há por aí

O predomínio na sub-bacia de uso com pastagens degradadas, fases de relevo principalmente fortemente onduladas e onduladas, e manejo incorreto do solo, são as

O mercado de Food Service necessita excelência em higienização, devido não só aos microrganismos patogênicos existentes como também aos emergentes e, para isso, os produtos 3M são

Costa, Capelo, Neto, Espírito-Santo &amp; Lousã 1997 (Ericenion umbellatae, Ericion umbellatae, Ulicetalia minoris, Calluno-Ulicetea);.. 16)