Vous êtes sur la page 1sur 44

FUNDAO ESCOLA TCNICA LIBERATO SALZANO VIEIRA DA CUNHA CURSO TCNICO EM ELETRNICA

VOX SISTEMA DE RECONHECIMENTO DE VOZ BASEADO EM REDES NEURAIS

FRANCISCO SOCAL LEANDRO MOTTA BARROS RAFAEL DE FIGUEIREDO PROFESSOR ORIENTADOR: DANIEL HART

Novo Hamburgo, outubro de 1998.

SUMRIO

INTRODUO ................................................................................................ 4 1.PROJETO DE TRABALHO .......................................................................... 5 1.1.Objetivo................................................................................................... 5 1.2.Justificativa.............................................................................................. 5 1.3.Metodologia............................................................................................. 6 1.4.Recursos .................................................................................................. 6 1.4.1.Humanos........................................................................................... 6 1.4.2.Materiais........................................................................................... 7 1.5.Cronograma ............................................................................................. 7 2.PROCESSAMENTO DA VOZ ...................................................................... 8 2.1.Caractersticas da voz .............................................................................. 8 2.1.1.A produo da voz ............................................................................ 8 2.1.2.A composio da voz ........................................................................ 9 2.2.Parametrizao da voz ............................................................................. 9 2.2.1.Anlise espectral ............................................................................. 10 2.2.2.Medida de energia........................................................................... 11 3.RECONHECIMENTO ................................................................................. 12 3.1.Inteligncia artificial .............................................................................. 12

3.2.Redes neurais......................................................................................... 13 3.2.1.O neurnio ...................................................................................... 13 3.2.2.Redes feedforward .......................................................................... 15 3.2.3.Treinamento.................................................................................... 16 3.2.4.Projetando uma rede neural ............................................................. 17 4.IMPLEMENTAO.................................................................................... 19 4.1.Aquisio do sinal de voz ...................................................................... 19 4.1.1.As funes de baixo nvel para udio em forma de onda.................. 20 4.1.2.A biblioteca de classes para a aquisio de dados............................ 20 4.2.Processamento dos sinais ....................................................................... 21 4.2.1.Parametrizao................................................................................ 21 4.2.2.Deteco dos limites das palavras.................................................... 22 4.2.3.Normalizao das amplitudes.......................................................... 22 4.2.4.Levantamento de dados para o reconhecimento............................... 22 4.3.Redes neurais......................................................................................... 22 4.4.Programa de teste................................................................................... 23 5.RESULTADOS............................................................................................ 24 CONCLUSO ................................................................................................ 25 REFERNCIAS BIBLIOGRFICAS ............................................................. 27 ANEXO 1 Listagem dos principais arquivos................................................. 29 ANEXO 2 Tela do programa de teste............................................................ 44

INTRODUO

O desenvolvimento tecnolgico que o mundo vive atualmente notvel. A cada dia novas tecnologias so desenvolvidas em laboratrios de pesquisa e logo incorporadas ao cotidiano. Muitas pessoas, porm, no conseguem acompanhar este desenvolvimento frentico: a quantidade de novidades que surgem to grande que elas no so capazes de se adaptar. Certamente esta situao seria diferente se as formas de interagir com toda esta tecnologia fossem mais simples. Neste sentido, a possibilidade de comandar mquinas atravs da voz representa um grande avano. Propomo-nos a desenvolver um mtodo de reconhecimento de voz simples, mas que levante novos questionamentos e apresente novas possibilidades para esta rea. Porm, este um assunto complexo, com muitas variveis a serem analisadas e otimizadas. Para facilitar o desenvolvimento do mtodo ele foi dividido em duas etapas. A primeira consiste em extrair da voz os parmetros que sejam mais significativos, que a representem da forma mais eficiente possvel. A segunda etapa responsvel pelo reconhecimento propriamente dito. Este trabalho, que busca relatar o desenvolvimento e implementao do mtodo criado, est dividido em quatro captulos. O primeiro o projeto de trabalho, que representa nossas expectativas iniciais, antes do incio da pesquisa propriamente dita. O segundo e o terceiro captulos apresentam uma abordagem terica das duas etapas do trabalho. O captulo final busca mostrar a implementao e os resultados prticos obtidos.

1. PROJETO DE TRABALHO

1.1.

Objetivo

Nosso objetivo com este trabalho pesquisar e desenvolver um mtodo que permita a um computador reconhecer certas palavras quando faladas em um microfone conectado placa de som. Mais precisamente, desejamos fazer com que determinadas aes que normalmente so executas com o mouse ou teclado, possam ser ativadas atravs da voz. Estamos, de fato, mais interessados nos mtodos utilizados para fazer isto do que os resultados efetivamente conseguidos, pois sabemos que estamos trabalhando com tpicos bastante complexos durante um perodo relativamente curto. 1.2. Justificativa

Dois argumentos justificam a realizao de uma pesquisa nesta rea. O primeiro o grande nmero de aplicaes para o reconhecimento de fala. Elas vo desde equipamentos voltados para deficientes fsicos at sistemas de controle para situaes em que as mos no podem ou no devem ser utilizadas, como em um rdio de carro. H ainda as aplicaes voltadas meramente ao conforto, como em um controle-remoto. O segundo argumento a possibilidade de preencher uma lacuna que existe em termos de reconhecimento de fala: so rarssimos os sistemas capazes de oferecer uma

boa qualidade de reconhecimento sem necessitar de hardware que vai alm das possibilidades dos usurios domsticos. 1.3. Metodologia

Definido o escopo do projeto, o primeiro passo a realizao de uma detalhada pesquisa, a fim de avaliar cada uma das partes em que o projeto divido. A parte inicial a aquisio dos sinais sonoros atravs da placa de som do computador. Na seguinte so realizadas transformaes matemticas, com o objetivo de representar o sinal de uma maneira mais adequada, transformando-o em algo que denominaremos de padro. Feita a representao, cabe a outra etapa fazer o reconhecimento propriamente dito do padro. ainda nesta etapa em que define-se quais padres sero reconhecidos pelo sistema. A quarta parte o gerenciamento dos padres aprendidos, mantendo-os armazenados e possibilitando o acesso de maneira eficiente. A ltima e bvia diviso a interface grfica que permite o controle de todos elementos do sistema. Com as partes definidas e implementadas nos concentraremos em junt-las e fazer o sistema funcionar. Faremos ento os ajustes e calibraes necessrios, e acreditamos que neste ponto o projeto esteja no nvel objetivado inicialmente. Por se tratar basicamente de uma pesquisa em que visamos desenvolver um mtodo e no um produto, possivelmente nos veremos obrigados a alterar o rumo da pesquisa, em funo de alguma suposio feita inicialmente que no corresponda corretamente s nossas expectativas. 1.4. Recursos

1.4.1. Humanos Para a realizao deste trabalho contaremos com o auxlio de alguns professores da Fundao Liberato. Alm do professor Daniel Hart, que nos orienta, sabemos que

alguns outros docentes desta escola podero ajudar na realizao deste trabalho. A professora Regina Ungaretti presta-nos auxlio no que diz respeito a relatrios e apresentaes. Temos ainda a possibilidade de consultar, atravs da Internet, pessoas com experincia em diversos assuntos com os quais nos depararemos. 1.4.2. Materiais Uma vez que se trata de um projeto baseado em software, necessitaremos basicamente de computadores para o desenvolvimento dos diversos programas. Como precisamos ser capazes de gravar sons, estes computadores devero possuir recursos de multimdia. 1.5. Cronograma

Setembro

Outubro

Agosto

Junho

Julho

Maio

Pesquisa inicial; diviso do trabalho em partes Pesquisa aprofundada; incio do desenvolvimento de cada parte Concluso de cada parte e sua unio Ensaios e ajustes finais Confeco do relatrio e preparao da apresentao

2. PROCESSAMENTO DA VOZ

2.1.

Caractersticas da voz

A voz humana, sendo analisada como um som qualquer, consiste na variao da presso do ar ao longo do tempo. A partir de impulsos eltricos enviados pelo crebro humano, o aparelho fonador produz uma seqncia de sons que caracteriza a voz, contendo diversas informaes, entre elas a mensagem sendo transmitida. Esta mensagem o objeto de estudo de um sistema de reconhecimento de voz, porm importante que tal sistema retire as demais informaes, como o timbre e o estado emocional do locutor. 2.1.1. A produo da voz A voz produzida pela passagem do ar vindo dos pulmes atravs da laringe, onde se encontram as cordas vocais. Ao passar pelas cordas, o ar faz com que elas vibrem, deixando escapar lufadas de ar que atingem as demais partes do aparelho fonador, onde a vibrao original modificada. No trato vocal, que compreende a regio entre as cordas vocais e lbios, incluindo as cavidades nasal e oral, so feitas alteraes na forma da onda gerada pelas cordas, dando origem aos diferentes fonemas. H, porm, outros sons que compem a fala: os no vozeados. So caracterizados por no serem produzidos pela vibrao das cordas vocais, mas sim pela liberao repentina de ar. Como exemplo, tem-se os fonemas /t/ e /s/ da palavra teste.

2.1.2. A composio da voz Uma anlise acstica da voz mostra claramente que ela no pode ser considerada uma onda estacionria, entretanto, suas caractersticas permanecem quase constantes nos diversos segmentos que a compem. Cada segmento representa um fonema, apresentando caractersticas prprias bem definidas, diferenciando-o dos demais. Fonemas vozeados apresentam uma estrutura harmnica, onde distingue-se claramente a freqncia fundamental, que praticamente constante para cada pessoa, alm claro de suas freqncias harmnicas. Busca-se ento identificar a composio freqencial do fonema, uma vez que a informao desejada est nela contida. Contudo, a grande variabilidade destes parmetros em funo, no s do locutor, mas de inmeros fatores, tem constitudo o grande desafio de um sistema de reconhecimento de voz: eliminar as variaes e chegar a poucos dados que representem claramente um fonema, independentemente do locutor, do rudo presente e de outros agravantes. Por outro lado, os fonemas no-vozeados no apresentam esta estrutura harmnica, pois no so formadas pela vibrao das cordas vocais. Apresentam de fato componentes freqenciais de baixa amplitude distribudas quase que aleatoriamente ao longo do espectro, como pode-se perceber na figura 2.1.

Figura 2.1 Espectrograma para a palavra /teste/.

2.2.

Parametrizao da voz

Para um sistema de reconhecimento de voz, a representao ao longo do tempo da voz, como obtida atravs da digitalizao, tem pouco sentido. A variao temporal

10

da amplitude afetada diretamente por variaes no ambiente e no locutor, como percebido na figura 2.2. Ao ser pronunciado em diferentes situaes, o mesmo fonema /a/ apresenta formas de onda sensivelmente diferentes.

Figura 2.2 O fonema /a/ sendo pronunciado em situaes diferentes.

Esta variabilidade, juntamente com o grande volume de dados, inviabiliza a utilizao direta da forma de onda no reconhecimento, tornando-se necessrio uma correta parametrizao da voz. A parametrizao visa basicamente extrair os dados que caracterizem cada fonema, remover redundncias, rudos e distores do sinal. 2.2.1. Anlise espectral A anlise em espectro tem como objetivo identificar as freqncias que compem o sinal. A base matemtica para esta anlise transformada de Fourier. Para sinais discretos, representados atravs de um vetor, utiliza-se a DFT (Discrete Fourier Transform, Transformada Discreta de Fourier), dada por: H [k ] = h[n]e j 2kn / N
n=0 N 1

Onde h representa o vetor com os dados temporais, enquanto o vetor resultante H contm os dados freqenciais, sendo N o nmero de amostras. Existe ainda a FFT (Fast Fourier Transform, Transformada Rpida de Fourier) que faz uso de mtodos computacionais para acelerar a transformao.

11

Como j foi dito anteriormente, a voz caracteriza-se por ser no estacionria, variando suas caractersticas freqenciais ao longo do tempo. Porm em um intervalo suficientemente curto pode ser considerada como tal. Desta maneira, aplica-se a FFT sobre as janelas temporais (que podem variar de 10 a 50 milissegundos) e agrupa-se os vetores freqenciais ao longo do tempo formando um espectrograma. Um espectrograma, como a figura 2.1, indica a variao da amplitude em funo das freqncias e ao longo do tempo. Outra maneira de se desenhar um espectrograma est exemplificado na figura 2.3. Neste caso, a amplitude, originalmente indicada pelo eixo vertical, passa a ser identificada pela tonalidade presente.

Figura 2.3 Espectrograma para as palavras /abrir/, /fechar/ e /documento/ ditas pausadamente.

Apesar da grande quantidade de dados envolvidos, o espectrograma um bom parmetro para ser reconhecido, uma vez que pode-se identificar nele os fonemas ao longo do tempo. 2.2.2. Medida de energia A medida de energia uma das maneiras mais simples de representar um sinal de voz, porm no fornece informaes suficientes para caracterizar corretamente uma palavra. Seu uso est ligado deteco dos limites da palavras. O clculo da energia feito a partir do valor mdio quadrtico, dado pela equao: 1 N

E=

h
n =0

N 1

[ n]

Onde h representa o vetor contendo N amostras correspondente janela temporal aplicada sobre o sinal de voz.

3. RECONHECIMENTO

As tcnicas discutidas at aqui nos permitem extrair de uma palavra falada uma srie de informaes que a caracterizam. A primeira idia que poderia ser pensada para fazer o reconhecimento propriamente dito seria simples: comparar as informaes extradas de uma palavra falada com as de um banco de dados que contenha as informaes das palavras que devero ser reconhecidas pelo sistema. Na prtica, porm, esta soluo apresenta-se invivel, pois, como uma palavra nunca pronunciada da mesma forma, a palavra falada jamais seria encontrada no banco de dados. Problemas como este exigem uma soluo mais verstil, capaz de adaptarse a todas as variaes possveis na pronncia. Algoritmos voltados inteligncia artificial visam exatamente este tipo de problema. 3.1. Inteligncia artificial

Os processadores utilizados atualmente so muito diferentes do crebro humano. Eles podem pode ser excelentes para a resoluo de problemas lgicos ou matemticos, mas deixam muito a desejar quando o problema envolve conceitos abstratos. Os estudos de inteligncia artificial buscam dar s mquinas a capacidade de trabalhar de uma forma mais semelhante ao crebro humano. Neste sentido, duas tcnicas ganharam grande destaque nas duas ltimas dcadas: lgica fuzzy e redes neurais. Ambas buscam inspirao no crebro humano; a pri-

13

meira procura imitar a forma inexata com que ele percebe as informaes enquanto a segunda, busca inspirao na sua construo fsica. Segundo a literatura consultada, redes neurais tm sido utilizadas com grande sucesso para problemas envolvendo classificao e/ou reconhecimento de padres. Como o reconhecimento de voz pode ser considerado como tal, optamos pela utilizao de redes neurais para fazer o reconhecimento. 3.2. Redes neurais

Quando o crebro humano comeou a ser desvendado, descobriu-se que as clulas que o formam, os neurnios, so elementos muito simples, incapazes de realizar tarefas complexas. Logo percebeu-se que o crebro no um nico, grande e poderoso processador, mas sim um conjunto de bilhes de processadores muito simples trabalhando simultaneamente. Pesquisadores das reas de informtica e eletrnica perceberam que poderiam utilizar uma estrutura semelhante para criar sistemas com algumas das caractersticas do crebro. Desta forma, iniciaram-se pesquisas mais detalhadas sobre os neurnios e de formas de represent-lo matematicamente. 3.2.1. O neurnio Como j foi comentado, uma rede neural busca inspirao na estrutura do crebro. A unidade bsica de nosso crebro, o neurnio, apresenta uma regio onde informaes so processadas (o soma), algumas entradas (os dentritos) e uma sada (o axnio). Os impulsos eltricos recebidos nos dentritos so processados pelo soma e o resultado deste processamento colocado no axnio. O modelo de neurnio no qual se baseiam as redes neurais possui uma estrutura idntica. Basicamente, a ativao (sada) de um neurnio artificial uma funo da soma ponderada de suas entradas: S = f ( E1 * P1 + E2 * P2 + E3 * P3 ) , onde S a sada, Ex as entradas e Px os pesos
das somas.

14

Figura 3.1 Esquema de um neurnio artificial

A funo f, utilizada para obter a sada do neurnio, chamada de funo de ativao. As funes de ativao mais utilizadas so funes do tipo sigmoidal (com forma de S). A mais utilizada de todas a funo logstica: f ( x) = 1 . 1 + ex

Figura 3.2 A funo logstica

A maior vantagem desta funo sua derivada, facilmente encontrada: f ' ( x ) = f ( x ).(1 f ( x)) A derivada da funo de ativao ser necessria no processo de treinamento da rede neural, discutido adiante. interessante observar que um nico neurnio no capaz de resolver nenhum problema prtico. Porm, muitos neurnios adequadamente conectados e com os pesos das conexes devidamente ajustados so capazes de resolver complexos problemas nodeterminsticos. Quanto maior a complexidade do problema a ser resolvido, maior ser o nmero de neurnios utilizados; para se ter uma idia, o crebro humano formado

15

por cerca de 100 bilhes de neurnios e o nmero de conexes entre estes neurnios est na casa das dezenas de trilhes. 3.2.2. Redes feedforward possvel conectar os neurnios de uma rede neural de modos variados, dando origem a diversas topologias. A topologia mais utilizada atualmente em problemas prticos a feedforward, que pode ser implementada em processadores comuns e, comparando-se com outras topologias, no exige muita memria. Uma rede deste tipo est representada na figura 3.3.

Figura 3.3 Rede neural feedforward

Uma rede neural feedforward composta de algumas camadas. Cada neurnio de uma camada est conectado a todos os neurnios das camadas adjacentes. importante destacar que a camada de entrada, na verdade, no formada por neurnios reais, pois eles no realizam nenhum processamento; simplesmente distribuem os valores das entradas da rede para os neurnios da primeira camada oculta. Uma rede neural deste tipo, depois de pronta, capaz de associar uma srie de valores que so colocados em suas entradas a uma determinada sada. Ela no se trata, porm, simplesmente de uma memria, pois tem a capacidade da generalizao; ela pode encontrar respostas corretas mesmo quando os dados disponveis para as entradas esto incompletos ou danificados ou mesmo quando a relao entre entrada e sada no concreta. Sabe-se, por exemplo, que h empresas utilizando redes neurais para previso financeira: nas entradas so colocados dados sobre diversos indicadores econmicos

16

e na sada obtm-se informaes como a tendncia das bolsas valores para o prximo dia. O grande problema para a utilizao de redes neurais tm sido encontrar regras que permitam determinar o valor que os pesos das conexes devem ter para que a rede neural realize a funo desejada. O processo pelo qual os pesos de uma rede neural so determinados conhecido por treinamento. 3.2.3. Treinamento O treinamento de redes feedforward do tipo supervisionado. Neste tipo de treinamento preciso possuir um conjunto de dados para treinamento, ou seja, uma srie de pares de entradas e sadas desejadas. As entradas so apresentadas rede e seus pesos so alterados de modo que a sada se aproxime da sada desejada. Pode-se dizer que a rede neural aprende a fazer seu trabalho observando uma srie de exemplos que lhe so exibidos. Para alterar os pesos de forma adequada necessria uma regra. A regra de treinamento mais utilizada para o treinamento de redes neurais feedforward a Error Backpropagation (retropropagao de erros). A idia deste algoritmo atualizar os pesos utilizando as derivadas dos erros em relao aos pesos. O estudo destas derivadas foi publicado por Rumelhart e McClelland em 1986 e seus resultados esto descritos a seguir. Para uma conexo do neurnio j da camada de sada ao neurnio i da camada oculta anterior, as seguintes equaes so vlidas:

j = f ' ( sp j ).(d j o j )

E = oi . j Pji

Onde spj a soma ponderada que chega ao neurnio j da camada de sada, dj a sada desejada para o este mesmo neurnio j, oj a sada ali obtida e oi a sada do neurnio i da camada que antecede a camada de sada.

17

Para pesos que chegam s camadas ocultas o clculo um pouco mais complexo, pois envolve os deltas da prxima camada. Considerando spj a soma ponderada chegando ao neurnio j da camada oculta em questo, k os deltas da prxima camada e Pkj o peso do neurnio k da camada anterior ao neurnio j da camada em questo:

j = f ' ( sp j ). ( k .Pkj )
k

E = oi . j Pji

O processo de treinamento iterativo. Cada vez que um par de entrada / sada desejada apresentado rede neural, as derivadas so recalculadas e os pesos so modificados no sentido inverso desta derivada, de modo a reduzir o peso. Isto repetido para todos os exemplos de treinamento, tantas vezes quantas forem necessrias para que o erro fique dentro de limites aceitveis. A figura 3.4 mostra a curva tpica da reduo do erro durante o treinamento de uma rede neural feedforward. Ela foi obtida a partir do treinamento de uma rede neural simples.

Figura 3.4 Treinamento de uma rede neural

3.2.4. Projetando uma rede neural Criar uma rede neural para a resoluo de um problema uma tarefa que exige ateno quanto a alguns detalhes. O primeiro deles a definio da sua forma, quantas camadas ela deve possuir e quais devem ser seus tamanhos. Teoricamente, qualquer problema pode ser resolvido por uma rede neural feedforward com duas camadas ocul-

18

tas. Na prtica, o mais comum utilizar apenas uma camada oculta, que suficiente na absoluta maioria dos casos. A determinao do tamanho das camadas de entrada e de sada no problemtica, j que eles tm uma relao direta com o formato dos dados que utilizaremos nas entradas e os que desejamos obter nas sadas. Determinar o tamanho da camada oculta , segundo diversos autores, um processo de tentativa e erro. Sabe-se que se ela for muito pequena no ter poder de processamento suficiente para resolver o problema; por outro lado, se for muito grande, perder sua capacidade de generalizao e atuar como uma memria. Tambm preciso ter em mente que o conjunto de exemplos utilizados no treinamento crtico. Ele deve conter exemplos que representem o maior nmero de casos possveis, para que o sistema seja capaz de aprender a resolver o problema nas mais diversas situaes.

4. IMPLEMENTAO

O sistema de reconhecimento de voz implementado teve como base a utilizao de microcomputador PC, sendo que a aquisio dos dados foi realizada atravs de uma placa de som convencional instalada e configurada para o sistema operacional Windows. Para o desenvolvimento do software, foram utilizados os programas Borland C++ 4.52 e Borland C++ Builder. 4.1. Aquisio do sinal de voz

O sistema operacional Windows incorpora em sua API (Application Programming Interface, interface de programao de aplicativos) funes para a utilizao dos recursos multimdia de um PC. Basicamente oferece trs opes para a gravao de sons: o uso da MCI (Media Control Interface, interface de controle de mdia) atravs de mensagens; o uso da MCI atravs de strings; e os servios de baixo nvel para audio em forma de onda. Cada opo oferece suas vantagens e desvantagens, mas interessante destacar a facilidade e simplicidade de uso das duas primeiras opes, porm retornam os dados j padronizados na forma de um arquivo WAVE, deixando o processo de aquisio extremamente lento. A terceira opo, como o prprio nome j diz, possui a desvantagem de ser constituda por funes de baixo nvel, dificultando a programao, mas por outro lado, oferecendo a rapidez desejada e os dados gravados diretamente na memria, deixando-os na forma original.

20

Optou-se ento pelas funes de baixo nvel, tendo em vista a necessidade de velocidade e dos dados agrupados em pores de memria. 4.1.1. As funes de baixo nvel para udio em forma de onda A API do Windows fornece uma srie de funes para a entrada de udio em forma de onda, entre elas: waveInOpen, waveInClose, waveInStart, waveInStop e waveInReset responsveis pelo controle do dispositivo, e as funes waveInPrepareBuffer, waveInUnprepareBuffer e waveInAddBuffer, respons-

veis pela manipulao dos blocos de memria a ser preenchidos com os dados (buffers). Um detalhamento melhor destas pode ser encontrado no Help Online de referncia da API do Windows. 4.1.2. A biblioteca de classes para a aquisio de dados A utilizao destas funes requer um cuidado especial, no que diz respeito manipulao da memria. A memria est constantemente sendo atualizada com os valores adquiridos, fazendo com que o programa tenha um controle dinmico sobre a memria. Optando pela linguagem de programao C++, foi possvel desenvolver uma biblioteca de classes para realizar o encapsulamento das funes da API. A biblioteca desenvolvida consiste basicamente em duas classes: WaveIn e Recorder. A primeira responsvel por uma interface mais intuitiva com as funes da API, alm de fornecer um tratamento de erro adequado. A segunda se encarrega de manipular os buffers que so utilizados para gravar os dados, alm de realizar as devidas configuraes do dispositivo de entrada. A classe WaveIn se encarrega-se de oferecer uma interface orientada objeto para a utilizao das funes de baixo nvel, tendo como variveis membro um handle para o dispositivo de entrada e o status do dispositivo. A funo Open deve ser utilizada para abrir um dispositivo de entrada de udio, fornecendo os parmetros quanto ao formato desejado dos dados e a janela que receber as mensagens do dispositivo. J a funo Close deve ser utilizada para fechar o dispositivo, enquanto as funes Start e
Stop so utilizadas no controle de gravao dos dados.

21

Apesar desta funcionalidade, esta classe no deve ser utilizada diretamente, tendo seu uso voltado para uma varivel membro da classe Recorder, responsvel por uma gama maior de funes. A classe Recorder possui como membro um objeto do tipo WaveIn, alm de variveis que contm o formato do som a ser gravado, como o nmero de amostras por buffer, de amostras por segundo, de canais e de bits por amostra. Seu uso se d atravs das funes Start e Stop, que controlam o andamento da gravao propriamente dita, alm da funo FillVector, que preenche um vetor com o ltimo buffer recebido do dispositivo. Um aplicativo que deseja ento adquirir udio em forma de onda deve ento, antes de mais nada, criar um objeto Recorder, especificando os parmetros desejados para a digitalizao. Para o iniciar o processo, deve acionar a funo Start, especificando um handle de uma janela que recebe os dados. Esta janela recebe a mensagem
MM_WIM_DATA, indicando que um buffer est disponvel, sendo que obtido atravs da

funo FillVector, fornecida pelo objeto Recorder. A biblioteca est listada em anexo nos arquivos WAVEIN.H e WAVEIN.CPP, cabealho e cdigo fonte respectivamente. 4.2. Processamento dos sinais

4.2.1. Parametrizao O parmetro escolhido para o reconhecimento foi a criao de espectrogramas. Para isso criou-se uma classe para realizar a FFT. No construtor destas classe, so realizados os clculos iniciais envolvendo os dados necessrios para a utilizao do algoritmo. Alm do construtor, a classe conta tambm com duas funes Transform sobrecarregadas: uma que aceita como parmetro um vetor de valores do tipo complex, e outra com valores do tipo double. Esta diferenciao deve-se ao fato do algoritmo da FFT transformar apenas vetores de nmeros complexos. O arquivo FFT.H, listado em anexo, contm as definies da classe Fft, enquanto o arquivo FFT.CPP contm a classe em si.

22

4.2.2. Deteco dos limites das palavras Com o intuito de implementar um sistema em tempo real, um algoritmo que detecte os limites das palavras fez-se necessrio. O mtodo escolhido foi baseado no clculo da potncia do sinal sendo captado. Foi definido assim um limiar de energia, a partir do qual considerou-se uma palavra sendo dita. 4.2.3. Normalizao das amplitudes O sistema apresentou melhora significativa no desempenho quando as amplitudes foram normalizadas, antes da aplicao das FFTs. Sem esta normalizao, o reconhecimento mostrava-se extremamente dependente de fatores como o volume da voz ou a posio do microfone. 4.2.4. Levantamento de dados para o reconhecimento Como j foi dito, os dados escolhidos para realizar o reconhecimento foram os espectrogramas. Estes foram produzidos atravs da aplicao da FFT em janelas de 23 ms (equivalente a 256 amostras). Os dados foram amostrados a uma freqncia de 11025 kHz e a uma resoluo de 8 bits. A fim de diminuir o volume de dados a ser analisado, limitou-se as freqncias do espectrograma em aproximadamente 2 kHz, o que no implica na perda de dados significativos. Fixou-se tambm um tamanho de 40 vetores (aproximadamente um segundo) para os espectrogramas analisados. Pelo fato dos dados espectrais conterem muito rudo (proveniente da digitalizao e dos resduos da transformao), buscou-se dar prioridade aos sinais de maior amplitude. Para isso elevou-se os dados dos vetores espectrais diversas potncias. A potncia 1,5 foi a que melhor se adequou. 4.3. Redes neurais

A implementao das redes neurais feedforward e do algoritmo de treinamento backpropagation tambm baseou-se na programao orientada a objetos. Foi criada uma classe, FeedforwardInputLayer, para representar a camada de entrada e outra,

23

FeedforwardLayer, para representar as camadas de sada e oculta. Estas classes encar-

regam-se de armazenar valores para entradas, sadas, pesos, de calcular os valores das suas sadas e inicializar os pesos de forma aleatria. Uma terceira classe, FeedforwardNetwork, representa uma rede neural feedforward de trs camadas. Ela possui como membros uma FeedforwardInputLayer e duas FeedforwardInputLayer, alm de funes para obter o valor das sadas para uma entrada fornecida como argumento, para inicializar todos os pesos aleatoriamente e para gravar e ler os pesos em arquivos em disco. Para o treinamento uma outra classe, Backprop, utilizada. Esta classe exige como argumento em seu construtor um ponteiro para a FeedforwardNetwork a ser treinada. Dentre suas funes membro destacamos as funes para incluso de novos dados para treinamento, e para a gravao e leitura destes dados em um arquivo. A funo para o treinamento propriamente dita permite escolher o nmero mximo de iteraes, e um limite mnimo de erro que, quando atingido, pra o treinamento. A funo para treinamento retorna o nmero de iteraes efetivamente feitas e preenche um vetor com o erro da rede a cada iterao. As listagens dos arquivos Feedforward.h e Backprop.h, onde estas classes esto implementadas esto includas como anexo deste trabalho. 4.4. Programa de teste

O programa para teste do sistema foi desenvolvido utilizando o compilador Borland C++ Builder, visando sua utilizao em sistemas operacionais windows de 32 bits, como o Windows 95. Todo o processamento realizado pelas classes j descritas; o programa simplesmente uma interface para o usurio utiliz-las. Uma imagem do programa foi includa como anexo. A camada de entrada da rede neural utilizada tem 1600 neurnios, correspondentes a cada um dos pontos que formam o espectrograma. A camada de sada possui quatro neurnios, um para cada uma das palavras que pode ser reconhecida. Para a camada oculta, o valor que mostrou-se mais adequado foi o de 80 neurnios.

5. RESULTADOS

Os resultados aqui descritos foram obtidos atravs da utilizao do programa criado para testar o sistema de reconhecimento de voz. Inicialmente o programa foi treinado para reconhecer as palavras /um/, /dois/, /trs/ e /quatro/. Foram utilizadas 13 amostras de cada palavra, todas ditas pelo mesmo locutor. O ndice de acertos oscilou entre 80% e 85%, porm, notvel a melhora dos resultados quando o locutor acostuma-se a utilizar o programa e passa a pronunciar as palavras da maneira mais adequada. Foi realizado um segundo teste com dados de treinamento compostos pelas palavras /norte/, /sul/, /leste/ e /oeste/ pronunciadas por outro locutor. Nesta situao os acertos corresponderam a mais de 90% dos casos. A razo desta sensvel melhora foi a utilizao de palavras mais extensas, aumentando assim a diferena entre elas. Um terceiro experimento foi feito treinando a rede neural com trs locutores. As palavras utilizadas foram /abrir/, /fechar/, /editar/ e /inserir/, pronunciadas 5 vezes por cada um. Neste caso percebeu-se um decaimento da eficincia, abaixando a taxa de reconhecimento para prximo de 60%, variando at 70% em funo do locutor e da palavra em questo.

CONCLUSO

O reconhecimento de voz um assunto complexo. Existem inmeras variveis a serem otimizadas, decises a serem tomadas e detalhes a serem analisados. O desenvolvimento de uma pesquisa nesta rea exige muito trabalho. Acreditamos os resultados obtidos foram muito bons, considerando a complexidade da proposta e o tempo dedicado a ela. O sistema que desenvolvemos mostrou-se capaz de reconhecer com considervel preciso palavras isoladas de um vocabulrio bastante limitado. Certamente tal sistema no o ideal para ser utilizado com interface entre o homem e a mquina. , porm, um excelente ponto de partida a partir do qual um sistema mais complexo possa ser desenvolvido. Acreditamos que a maior contribuio desta pesquisa no a criao de um novo mtodo para reconhecimento de voz, mas sim a avaliao sobre a possibilidade da utilizao de novas tcnicas. Redes neurais, por exemplo, tm sido pouco exploradas nesta rea. Segundo alguns especialistas este o futuro do reconhecimento de voz. Pelos testes que fizemos no temos como negar esta afirmao, pois mesmo uma rede neural simples como a que foi utilizada mostrou-se eficiente. Tambm foi possvel averiguar se os espectrogramas seriam uma forma adequada de representar a voz para o reconhecimento. Verificamos que um espectrograma tal e

26

qual criado a partir de um som em forma de onda no o ideal. Foi necessrio alterar o espectrograma para que o sistema apresentasse melhor desempenho. Ficou claro que um espectrograma possui todas as informaes necessrias para identificar palavras e fonemas, mas a parametrizao precisa ser levada adiante. possvel melhorar as duas etapas principais do sistema a fim de melhorar seu desempenho. Seria possvel, por exemplo, fazer uma anlise matemtica das harmnicas dos fonemas para identific-los mais precisamente. preciso tambm buscar formas de diferenciar fonemas no-vozeados, que mostraram-se os de mais difcil identificao. Quanto etapa de reconhecimento, seria possvel utilizar uma rede neural que se adaptasse ao locutor, de modo que a eficincia do sistema fosse sendo incrementada automaticamente durante seu uso. Redes neurais com esta caracterstica so mais complexas, mas perfeitamente possveis. Sabemos tambm que o sistema carece de recursos que permitam ignorar determinados fatores. O tipo de microfone utilizado, por exemplo, influencia nos resultados de forma relevante. possvel que no futuro o comando de mquinas pela voz faa com que mais pessoas tenham acesso s tecnologias que so criadas para melhorar a nossa vida. Esperamos com este trabalho estar contribuindo para que isto se torne realidade.

REFERNCIAS BIBLIOGRFICAS

1.

CONNOR, F. R. Tpicos de Introduo Electrnica e s Telecomunicaes Sinais. Lisboa, Intercincia, 1978. 93 p.

2.

JAIN, Anil K. Fundamentals of Digital Image Processing. Engelwood Hills, Prentice Hall, 1989. 569 p.

3.

KOVCS, Zsolt. Redes Neurais Artificiais. Teoria e Aplicao. So Paulo, collegium cognitio, 1996. 139 p.

4.

LIM, Jae S. Two-Dimensional Signal and Image Processing. Engelwood Hills, Prentice Hall, 1990. 694 p.

5.

LUFT, Joel. Reconhecimento Automtico de Voz para Palavras Isoladas e Independente do Locutor. Dissertao de mestrado, PPGEMM, Universidade Federal do Rio Grande do Sul, 1994.

6.

MARKOWITZ, Judith A. Using Speech Recognition. New Jersey, Prentice Hall, 1996. 292 p.

7.

MASTERS, Timothy. Practical Neural Networks Recipes in C++. San Diego, Academic Press, 1993. 493 p.

8.

Microsoft Multimedia Programmers Reference. Microsoft Corporation, 1996.

28

9.

MILANO, John; CABANSKY, Tom & HOWE, Harold. Borland C++ Builder How To. Corte Madera, Waite Group Press, 1997. 822 p.-

10. NORTON, Peter & YAO, Paul. Programando em Borland C++ para Windows. So Paulo, Ed. Berkley, 1992. 584 p. 11. OKANO, Emico; CALDAS, Iber L. & CHOW, Ceci. Fsica Para Cincias Biomdicas. So Paulo, Harbra, 1982. 612 p. 12. ORTH, A. Reconhecimento Automtico de Peas. Revista Saber Eletrnica. Ano 34. No 308. So Paulo, Outubro 1998. 13. OHSMANN, M. Introduction to Digital Signal Processing. Elektor Electronics. No 262. Janeiro de 1998. 14. RAO, Valluru B. & RAO, Hayagriva V. C++ Neural Networks & Fuzzy Logic. New York, MIS:Press, 1995. 551 p. 15. Reliable Software Web Site. www.relisoft.com

ANEXO 1 LISTAGEM DOS PRINCIPAIS ARQUIVOS

Wavein.h
#ifndef _WAVEIN_H #define _WAVEIN_H #include <windows.h> #include "svector.h" #pragma warn -sig class WaveHeader : public WAVEHDR { public: bool isDone() const { return dwFlags & WHDR_DONE ; } ; } ; class WaveFormat : public WAVEFORMATEX { public: WaveFormat( DWORD rate, WORD chan, WORD bits ) { wFormatTag = WAVE_FORMAT_PCM ; nChannels = chan ; nSamplesPerSec = rate ; nAvgBytesPerSec = chan * rate * bits / 8 ; nBlockAlign = chan * bits / 8 ; wBitsPerSample = bits ; cbSize = 0 ; } ; } ;

class WaveIn { public: WaveIn() : status( MMSYSERR_BADDEVICEID ) {} ; ~WaveIn() { if( ok() ) { Stop(); Reset() ; Close() ; } ; } ; bool Open( HWND, UINT, WaveFormat& ) ; bool Close() ; void Reset() { if( ok() ) waveInReset( hWave ) ; } ; void Start() { waveInStart( hWave ) ; } ;

30

void Stop() { waveInStop( hWave ) ; } ; void Prepare( WaveHeader* ) ; void UnPrepare( WaveHeader* ) ; void Send( WaveHeader* ) ; LPSTR queryError() ; LPCSTR queryTitle() { return "WaveAudio Input Engine" ; } ; bool ok() { return status == 0 ; } ; bool isInUse() { return status == MMSYSERR_ALLOCATED ; } ; private: HWAVEIN hWave ; UINT status ; char errorText[164] ; } ; class Recorder { enum { NUM_BUF = 8 } ; public: Recorder( WORD cSamples, DWORD cSamplesPerSec, WORD nChannels, WORD bits ) ; ~Recorder() ; bool Start( HWND hwnd ) ; void Stop() ; bool isBufferDone() const { return _header[_iBuf].isDone() ; } ; bool BufferDone() ; bool FillVector( svector<double>& ) ; WORD SampleCount() const { return _cSamples ; } ; DWORD SamplesPerSec() const { return _cSamplesPerSec ; } ; WORD Bits() const { return _bits ; } ; WORD Channels() const { return _nChannels ; } ; protected: WaveIn _wave ; int _iBuf ; WORD _cSamples ; DWORD _cSamplesPerSec ; WORD _nChannels ; WORD _bits ; WORD _cbBuf ; WaveHeader _header [ NUM_BUF ] ; LPSTR _dataPool ; svector<int> avoid_errors ; } ; inline bool WaveIn::Open( HWND hWnd, UINT id, WaveFormat& fmt ) { status = waveInOpen( &hWave, id, &fmt, (DWORD)hWnd, NULL, CALLBACK_WINDOW ) ; return ok() ; } ; inline bool WaveIn::Close() { if( waveInClose( hWave ) == 0 && ok() ) { status = MMSYSERR_BADDEVICEID ; return true ; } ; return false ; } ; inline void WaveIn::Prepare( WaveHeader* phdr ) {

31

waveInPrepareHeader( hWave, phdr, sizeof(WAVEHDR) ) ; } ; inline void WaveIn::UnPrepare( WaveHeader* phdr ) { waveInUnprepareHeader( hWave, phdr, sizeof(WAVEHDR) ) ; } ; inline void WaveIn::Send( WaveHeader* phdr ) { waveInAddBuffer( hWave, phdr, sizeof(WAVEHDR) ) ; } ; inline LPSTR WaveIn::queryError() { waveInGetErrorText( status, errorText, sizeof(errorText) ) ; return errorText ; } ; #endif

Wavein.cpp
#ifndef wavein_cpp #define wavein_cpp #pragma warn -sig #include "wavein.h" Recorder::Recorder( WORD cSamples, DWORD cSamplesPerSec, WORD nChannels, WORD bits ) : _cSamples( cSamples ), _cSamplesPerSec( cSamplesPerSec ), _nChannels( nChannels ), _bits( bits ), _iBuf(0), _cbBuf( cSamples * nChannels * bits / 8 ) { _dataPool = new char[ _cbBuf*NUM_BUF ] ; assert( _dataPool != NULL ) ; } ; Recorder::~Recorder() { Stop() ; delete [] _dataPool ; } ; bool Recorder::Start( HWND hwnd ) { LPSTR errorText ; WaveFormat format( _cSamplesPerSec, _nChannels, _bits ) ; _wave.Open( hwnd, 0, format ) ; if( !_wave.ok() ) { if( _wave.isInUse() ) errorText = "Outro aplicativo est utilizando o dispositivo de entrada." ; else errorText = _wave.queryError() ; MessageBox( hwnd, errorText, _wave.queryTitle(), MB_ICONSTOP ) ; return false ; } ; for( int i=0; i<NUM_BUF - 1; i++ ) { _header[i].lpData = &_dataPool[ i*_cbBuf ] ; _header[i].dwBufferLength = _cbBuf ;

32

_header[i].dwFlags = 0 ; _header[i].dwLoops = 0 ; _wave.Prepare( &_header[i] ) ; _wave.Send( &_header[i] ) ; } ; _iBuf = 0 ; _wave.Start() ; return true ; } ; void Recorder::Stop() { _wave.Reset() ; _wave.Close() ; } ; bool Recorder::BufferDone() { if( !_wave.ok() ) return false ; assert( isBufferDone() ) ; _wave.UnPrepare( &_header[ _iBuf ] ) ; int prevBuf = _iBuf - 1 ; if( prevBuf < 0 ) prevBuf = NUM_BUF - 1 ; _iBuf++ ; if( _iBuf == NUM_BUF ) _iBuf = 0 ; _header[prevBuf].lpData = &_dataPool[ prevBuf*_cbBuf ] ; _header[prevBuf].dwBufferLength = _cbBuf ; _header[prevBuf].dwFlags = 0 ; _header[prevBuf].dwLoops = 0 ; _wave.Prepare( &_header[prevBuf] ) ; _wave.Send( &_header[prevBuf] ) ; return true ; } ; bool Recorder::FillVector( svector<double>& vc ) { #pragma warn -csu if( !_wave.ok() ) return false ; assert( isBufferDone() ) ; int i ; WaveHeader* hdr = &_header[_iBuf] ; LPSTR data = hdr->lpData ; vc.resize( hdr->dwBytesRecorded / (_nChannels*_bits/8) ) ; if( _bits == 8 && _nChannels == 1 ) { for( i=0 ; i<vc.limit() ; i++ ) vc[i] = (((unsigned char)data[i]-128)) ; } else if( _bits == 16 && _nChannels == 1 ) { for( i=0 ; i<vc.limit() ; i++ ) vc[i] = (((short*)data)[i]) / 64 ; } else return false ; return true ; #pragma warn +csu } ; #endif

33

Fft.h
#ifndef _FFT_H #define _FFT_H #include <stdlib.h> #include <complex.h> #include "svector.h" class WrongNumberOfPoints{} ; inline double abs( double vl ) { return fabs( vl ) ; } ; inline double round ( double vl ) { return (vl-floor(vl)) < 0.5 ? floor(vl) : ceil(vl) ; } ; class Fft { public: Fft( int Points ) ; ~Fft() {} ; int Points () const { return _Points ; } ; svector<complex>& Transform( svector<complex>& ) ; svector<double>& Transform( svector<double>& ) ; protected: void _Transform() ; int _Points ; const double pi ; private : int _logPoints ; double _sqrtPoints ; svector<int> _aBitRev ; svector< svector< complex > > _W ; svector<complex> x ; svector<double> vec_char ; // Devido a um bug do compilador ao trabalhar } ; // com Templates de classes sem nenhum objeto criado #endif

Fft.cpp
#ifndef fft_cpp #define fft_cpp #pragma warn -sig #pragma warn -csu #include "fft.h" #include <math.h> Fft::Fft( int Points ) : _Points( Points ), _aBitRev( _Points ), pi( M_PI ), x( _Points ) { _sqrtPoints = sqrt( (double) _Points ) ; _logPoints = 0 ; Points-- ;

34

while( Points != 0 ) { Points >>= 1 ; _logPoints++ ; } ; _W.resize( _logPoints + 1 ) ; int _2_l = 2 ; double re, im ; for( int l=1 ; l < _W.limit() ; l++ ) { _W[l].resize( _Points ) ; for( int i=0; { re = cos( im = -sin( _W[l][i] = } ; _2_l <<= 1 ; } ; i < _W[l].limit() ; i++ ) 2.0 * pi * i / _2_l ) ; 2.0 * pi * i / _2_l ) ; complex( re, im ) ;

int rev = 0 ; int halfPoints = _Points >> 1 ; int mask ; for( int j=0 ; j < _Points - 1 ; j++ ) { x[j] = complex( 0, 0 ) ; _aBitRev[j] = rev ; mask = halfPoints ; while( rev >= mask ) { rev -= mask ; mask >>= 1 ; } ; rev += mask ; } ; _aBitRev[ _Points-1 ] = _Points - 1 ; } ; svector<complex>& Fft::Transform( svector<complex>& vc ) { int i ; if( vc.limit() != _Points ) throw WrongNumberOfPoints() ; for( i=0; i<_Points ; i++ ) x[ _aBitRev[i] ] = vc[i] ; _Transform() ; vc = x ; return vc ; } ; svector<double>& Fft::Transform( svector<double>& vc ) { int i ; if( vc.limit() != _Points ) throw WrongNumberOfPoints() ; for( i=0; i<_Points ; i++ ) x[ _aBitRev[i] ] = complex( vc[i], 0 ) ;

35

_Transform() ; vc.resize( _Points / 2 ) ; for( i=0; i<vc.limit() ; i++ ) vc[i] = abs( x[1+i] ) / _Points ; return vc ; } ; void Fft::_Transform() { int step = 1 ; int increm ; int i, j ; complex u, t ;

for( int level=1; level < _W.limit(); level++ ) { increm = step*2 ; for( j=0; j<step; j++ ) { u = _W [level] [j] ; for( i=j; i<_Points ; i += increm ) { t = u ; t *= x [i+step] ; x [i+step] = x[i] ; x [i+step] -= t ; x [i] += t ; } ; } ; step <<= 1 ; } ; } ; #endif

Feedforward.h
#ifndef Feedforward_h #define Feedforward_h #include <math.h> inline float RandomWeight() { float Num; Num = rand() % 1000; return 2 * (Num / 1000) 1; } float DotProd (int sz, float *vet1, float *vet2) { int k, m, p = 0; float sum = 0; k = sz / 4; m = sz % 4; while { sum sum sum sum } (k--) += += += += vet1[p] vet1[p] vet1[p] vet1[p] * * * * vet2[p++]; vet2[p++]; vet2[p++]; vet2[p++]; // // // // Fazendo desta forma, de 4 em 4, o nmero de loops reduzido. Isto deve acelerar bastante o processo, especialmente se o processador utilizado usar pipelining.

36

while (m--) sum += vet1[p] * vet2[p++]; return sum; } #define F_TABLE_LENGTH 100 #define F_TABLE_MAX 10.0 static float f_factor, f_f[F_TABLE_LENGTH], f_d[F_TABLE_LENGTH]; void InitActFunc() { f_factor = (float)(F_TABLE_LENGTH - 1) / (float)F_TABLE_MAX; for (int c = 0 ; c < F_TABLE_LENGTH ; c++) { f_f[c] = 1.0 / (1.0 + exp (-((float)c) / f_factor)); if (c) f_d[c-1] = f_f[c] - f_f[c-1]; } } float ActFunc (float x) { int i; float xd; // distancia no eixo x if (x >= 0) { xd = x * f_factor; i = (int)xd; if (i >= (F_TABLE_LENGTH - 1)) return f_f[F_TABLE_LENGTH - 1]; return f_f[i] + f_d[i] * (xd - i); } else { xd = -x * f_factor; // x negativo, logo, xd positivo (f_factor positivo) i = (int)xd; if (i >= (F_TABLE_LENGTH - 1)) return 1.0 - f_f[F_TABLE_LENGTH - 1]; return 1.0 - f_f[i] + f_d[i] * (xd - i); } } float ActDeriv(float f) // O parmetro f(x), no simplesmente x { return f * (1.0 - f); } // ============================================================================ // = FeedforwardInputLayer = // ============================================================================ class FeedforwardInputLayer { public: FeedforwardInputLayer(int sz); ~FeedforwardInputLayer(); float *Outputs; int NumNrns; }; // - Construtor --------------------------------------------------------------FeedforwardInputLayer::FeedforwardInputLayer(int sz) { Outputs = new float[sz]; NumNrns = sz; } // - Destrutor ---------------------------------------------------------------FeedforwardInputLayer::~FeedforwardInputLayer() { delete[] Outputs; }

37

// ============================================================================ // = FeedforwardLayer = // ============================================================================ class FeedforwardLayer { public: FeedforwardLayer(int szThis, int szPrev); ~FeedforwardLayer(); void ForwardPropagate(); void RandomizeWeights(); float *Inputs; float *Outputs; float *Weights; int NumNrns; int NumNrnsPrev; }; // - Construtor --------------------------------------------------------------FeedforwardLayer::FeedforwardLayer(int szThis, int szPrev) { Outputs = new float[szThis]; Weights = new float[szThis * szPrev]; NumNrns = szThis; NumNrnsPrev = szPrev; } // - Destrutor ---------------------------------------------------------------FeedforwardLayer::~FeedforwardLayer() { delete[] Outputs; delete[] Weights; } // - ForwardPropagate --------------------------------------------------------void FeedforwardLayer::ForwardPropagate() { float *vcWeights; vcWeights = new float[NumNrnsPrev]; for (int eON = 0 ; eON < NumNrns ; eON++) // Para cada neurnio da camada { for (int eIN = 0 ; eIN < NumNrnsPrev ; eIN++) vcWeights[eIN] = Weights[NumNrnsPrev * eON + eIN]; Outputs[eON] = ActFunc(DotProd(NumNrnsPrev, Inputs, vcWeights)); } delete[] vcWeights; } // - RandomizeWeights --------------------------------------------------------void FeedforwardLayer::RandomizeWeights() { for (int c = 0 ; c < NumNrns * NumNrnsPrev ; c++) Weights[c] = RandomWeight(); } typedef FeedforwardLayer FeedforwardOutputLayer; typedef FeedforwardLayer FeedforwardHiddenLayer; // ============================================================================ // = FeedforwardNetwork = // ============================================================================ class FeedforwardNetwork { friend class Backprop; public: FeedforwardNetwork(int szInp, int szHid, int szOut);

38

~FeedforwardNetwork(); float *GetOutputs(float *Inputs); void RandomizeWeights(); void WriteWeights (char *FileName); bool ReadWeights (char *FileName); // retorna true se OK FeedforwardInputLayer *InputLayer; FeedforwardHiddenLayer *HiddenLayer; FeedforwardOutputLayer *OutputLayer; }; // - Construtor --------------------------------------------------------------FeedforwardNetwork::FeedforwardNetwork(int szInp, int szHid, int szOut) { InputLayer = new FeedforwardInputLayer(szInp); HiddenLayer = new FeedforwardHiddenLayer(szHid, szInp); OutputLayer = new FeedforwardOutputLayer(szOut, szHid); HiddenLayer->Inputs = InputLayer->Outputs; OutputLayer->Inputs = HiddenLayer->Outputs; InitActFunc(); } // - Destrutor ---------------------------------------------------------------FeedforwardNetwork::~FeedforwardNetwork() { delete InputLayer; delete HiddenLayer; delete OutputLayer; } // - GetOutputs --------------------------------------------------------------float *FeedforwardNetwork::GetOutputs(float *Inputs) { for (int c = 0 ; c < InputLayer->NumNrns ; c++) InputLayer->Outputs[c] = Inputs[c]; HiddenLayer->ForwardPropagate(); OutputLayer->ForwardPropagate(); return OutputLayer->Outputs; } // - RandomizeWeights --------------------------------------------------------void FeedforwardNetwork::RandomizeWeights() { HiddenLayer->RandomizeWeights(); OutputLayer->RandomizeWeights(); } // - WriteWeights -----------------------------------------------------------void FeedforwardNetwork::WriteWeights (char* FileName) { TFileStream *File; unsigned short int *iBuf; float *fBuf; unsigned long int Pos = 0; File = new TFileStream (FileName, fmCreate); iBuf = new unsigned iBuf[0] = (unsigned iBuf[1] = (unsigned iBuf[2] = (unsigned short short short short int [3]; int)InputLayer->NumNrns; int)HiddenLayer->NumNrns; int)OutputLayer->NumNrns;

File->Write((void*)iBuf, 6); fBuf = new float [ (iBuf[0] * iBuf[1]) + (iBuf[1] * iBuf[2]) ]; for (int eHN = 0 ; eHN < HiddenLayer->NumNrns ; eHN++) for (int eIN = 0 ; eIN < InputLayer->NumNrns ; eIN++) fBuf[Pos++] = HiddenLayer->Weights[InputLayer->NumNrns * eHN + eIN]; for (int eON = 0 ; eON < OutputLayer->NumNrns ; eON++) for (int eHN = 0 ; eHN < HiddenLayer->NumNrns ; eHN++) fBuf[Pos++] = OutputLayer->Weights[HiddenLayer->NumNrns * eON + eHN];

39

File->Write( (void*)fBuf, ( (iBuf[0] * iBuf[1]) + (iBuf[1] * iBuf[2])) * 4); delete[] fBuf; delete[] iBuf; delete File; } // - ReadWeights ------------------------------------------------------------bool FeedforwardNetwork::ReadWeights (char* FileName) { TFileStream *File; unsigned short int *iBuf; float *fBuf; unsigned long int Pos = 0; File = new TFileStream (FileName, fmOpenRead); iBuf = new unsigned short int [3]; File->Read((void*)iBuf, 6); // So 3 unsigned short int's -> 6 bytes if ( iBuf[0] != InputLayer->NumNrns || iBuf[1] != HiddenLayer->NumNrns || iBuf[2] != OutputLayer->NumNrns ) { delete[] iBuf; return false; } fBuf = new float [ (iBuf[0] * iBuf[1]) + (iBuf[1] * iBuf[2]) ]; File->Read((void*)fBuf, ((iBuf[0] * iBuf[1]) + (iBuf[1] * iBuf[2])) * 4); for (int eHN = 0 ; eHN < iBuf[1] ; eHN++) for (int eIN = 0 ; eIN < iBuf[0] ; eIN++) HiddenLayer->Weights[iBuf[0] * eHN + eIN] = fBuf[Pos++]; for (int eON = 0 ; eON < iBuf[2] ; eON++) for (int eHN = 0 ; eHN < iBuf[1] ; eHN++) OutputLayer->Weights[iBuf[1] * eON + eHN] = fBuf[Pos++]; delete[] fBuf; delete[] iBuf; delete File; return true; }

#endif

Backprop.h
#ifndef Backprop_h #define Backprop_h #include <Feedforward.h> // ============================================================================ // = Backprop - Faz o treinamento propriamente dito = // ============================================================================ class Backprop { public: Backprop(FeedforwardNetwork *pNet); ~Backprop(); float GetInput(int Pair, int Nrn);

40

float GetOutput(int Pair, int Nrn); void AddPair(float *Input, float *Output); float GetError(float *Targets); // Retorna o erro mdio quadrtico (MSE) float GetEpochError(); // Retorna MSE para todos os pares void CalcGrads(int Pair); // Calcula gradientes para o par argumentado int Train(int MaxIter, float ErrTol, float LR, float *ErrVect); // Treina bool ReadData(char *FileName); // retorna true se OK void WriteData(char *FileName); FeedforwardNetwork *Net; // Ponteiro para a rede a ser treinada float *Data; // Dados (entrada/saida desejada) float *GradsH, *GradsO; // Gradientes da camada oculta / saida int NumPairs, InputSize, HiddenSize, OutputSize; bool HasAPair; }; // - Construtor --------------------------------------------------------------Backprop::Backprop(FeedforwardNetwork *pNet) { NumPairs = 0; HasAPair = false; Net = pNet; InputSize = Net->InputLayer->NumNrns; HiddenSize = Net->HiddenLayer->NumNrns; OutputSize = Net->OutputLayer->NumNrns; GradsH = new float[HiddenSize * InputSize]; GradsO = new float[OutputSize * HiddenSize]; } // - Destrutor ---------------------------------------------------------------Backprop::~Backprop() { if (HasAPair) delete[] Data; delete[] GradsH; delete[] GradsO; } // - GetInput ----------------------------------------------------------------float Backprop::GetInput(int Pair, int Nrn) { return Data[(InputSize+OutputSize) * Pair + Nrn]; } // - GetOutput ---------------------------------------------------------------float Backprop::GetOutput(int Pair, int Nrn) { return Data[(InputSize+OutputSize) * Pair + InputSize + Nrn]; } // - AddPair -----------------------------------------------------------------void Backprop::AddPair(float *Input, float *Output) { float *vcTmp; int Pos = 0; int PairSize = InputSize + OutputSize; vcTmp = new float[PairSize * NumPairs]; for (int c = 0 ; c < PairSize * NumPairs ; c++) vcTmp[c] = Data[c]; if (HasAPair) delete[] Data; Data = new float[PairSize * (NumPairs + 1)]; for (int c = 0 ; c < PairSize * NumPairs ; c++) Data[c] = vcTmp[c]; for (int c = PairSize * NumPairs ; c < PairSize * NumPairs + InputSize; c++) Data[c] = Input[Pos++]; Pos = 0; for (int c = PairSize * NumPairs + InputSize; c < PairSize * (NumPairs + 1) ; c++) Data[c] = Output[Pos++];

41

NumPairs++; HasAPair = true; delete[] vcTmp; } // - GetError ----------------------------------------------------------------float Backprop::GetError(float *Targets) { float Err, Sum = 0; for (int c = 0 ; c < OutputSize ; c++) { Err = Targets[c] - Net->OutputLayer->Outputs[c]; Sum += Err * Err; } return Sum / (float)OutputSize; } // - GetEpochError -----------------------------------------------------------float Backprop::GetEpochError() { float AccErr = 0, *Inp, *Trgt; Inp = new float[InputSize]; Trgt = new float[OutputSize]; for (int eP = 0 ; eP < NumPairs ; eP++) { for (int eI = 0 ; eI < InputSize ; eI++) Inp[eI] = GetInput(eP, eI); for (int eO = 0 ; eO < OutputSize ; eO++) Trgt[eO] = GetOutput(eP, eO); Net->GetOutputs(Inp); AccErr += GetError(Trgt); } AccErr /= (float)NumPairs; delete[] Inp; delete[] Trgt; return AccErr; } // - CalcGrads ---------------------------------------------------------------void Backprop::CalcGrads(int Pair) { float Out, Delta, Sum, *Inp, *Trgt, *Deltas; Deltas = new float[OutputSize]; Inp = new float[InputSize]; Trgt = new float[OutputSize]; for (int eI = 0 ; eI < InputSize ; eI++) Inp[eI] = GetInput(Pair, eI); for (int eO = 0 ; eO < OutputSize ; eO++) Trgt[eO] = GetOutput(Pair, eO); Net->GetOutputs(Inp); for (int eON = 0 ; eON < OutputSize ; eON++) { Out = Net->OutputLayer->Outputs[eON]; Delta = (Trgt[eON] - Out) * ActDeriv(Out); Deltas[eON] = Delta; for (int eHN = 0 ; eHN < HiddenSize ; eHN++)

42

GradsO[HiddenSize * eON + eHN] = Delta * Net->HiddenLayer->Outputs[eHN]; } for (int eHN = 0 ; eHN < HiddenSize ; eHN++) { Sum = 0; for (int eON = 0 ; eON < OutputSize ; eON++) Sum += Deltas[eON] * Net->OutputLayer->Weights[eON * HiddenSize + eHN]; Delta = Sum * ActDeriv(Net->HiddenLayer->Outputs[eHN]); for (int eIN = 0 ; eIN < InputSize ; eIN++) GradsH[InputSize * eHN + eIN] = Delta * Net->InputLayer->Outputs[eIN]; } delete[] Deltas; delete[] Inp; delete[] Trgt; } // - Train -------------------------------------------------------------------int Backprop::Train (int MaxIter, float ErrTol, float LR, float *ErrVect) { float Corr, Err; for (int Iter = 0 ; Iter < MaxIter ; Iter++) { Err = GetEpochError(); if (Err < ErrTol) { for (int c = Iter ; c < MaxIter ; c++) ErrVect[c] = Err; return Iter + 1; } ErrVect[Iter] = Err; for (int eP = 0 ; eP < NumPairs ; eP++) { CalcGrads(eP); for (int eOW = 0 ; eOW < OutputSize * HiddenSize ; eOW++) { Corr = LR * GradsO[eOW]; Net->OutputLayer->Weights[eOW] += Corr; } for (int eHW = 0 ; eHW < HiddenSize * InputSize ; eHW++) { Corr = LR * GradsH[eHW]; Net->HiddenLayer->Weights[eHW] += Corr; } } } return MaxIter; } // - WriteData ---------------------------------------------------------------void Backprop::WriteData(char *FileName) { TFileStream *File; unsigned short int *iBuf; float *fBuf; File = new TFileStream (FileName, fmCreate); iBuf = new unsigned short int [3]; iBuf[0] = (unsigned short int)NumPairs; iBuf[1] = (unsigned short int)InputSize; iBuf[2] = (unsigned short int)OutputSize; File->Write((void*)iBuf, 6);

43

fBuf = new float [(InputSize + OutputSize) * NumPairs]; for (unsigned short int ePair = 0 ; ePair < { for (unsigned short int eInp = 0 ; eInp < fBuf[(ePair * (InputSize + OutputSize)) for (unsigned short int eOut = 0 ; eOut < fBuf[(ePair * (InputSize + OutputSize)) } File->Write((void*)fBuf, (InputSize + OutputSize) * NumPairs * 4); delete[] fBuf; delete[] iBuf; delete File; } // -- ReadData ---------------------------------------------------------------bool Backprop::ReadData (char *FileName) { TFileStream *File; unsigned short int *iBuf; float *fBuf, *NewInput, *NewOutput; int PairSize, NewNumPairs; File = new TFileStream (FileName, fmOpenRead); iBuf = new unsigned short int [3]; File->Read((void*)iBuf, 6); if (iBuf[1] != InputSize && iBuf[2] != OutputSize) return false; NewInput = new float[InputSize]; NewOutput = new float[OutputSize]; NewNumPairs = iBuf[0]; PairSize = InputSize + OutputSize; if (HasAPair) delete[] Data; Data = new float[PairSize * NewNumPairs]; fBuf = new float [PairSize * NewNumPairs]; File->Read((void*)fBuf, PairSize * NewNumPairs * 4); for (int ePair = 0 ; ePair < NewNumPairs ; ePair++ ) { for (int eInp = 0 ; eInp < InputSize ; eInp++ ) NewInput[eInp] = fBuf[ePair * PairSize + eInp]; for (int eOut = 0 ; eOut < OutputSize ; eOut++ ) NewOutput[eOut] = fBuf[ePair * PairSize + InputSize + eOut]; AddPair(NewInput, NewOutput); } delete[] fBuf; delete[] iBuf; delete[] NewInput; delete[] NewOutput; delete File; return true; } #endif NumPairs ; ePair++ ) InputSize ; eInp++ ) + eInp] = GetInput(ePair, eInp); OutputSize ; eOut++ ) + InputSize + eOut] = GetOutput(ePair, eOut);

ANEXO 2 TELA DO PROGRAMA DE TESTE

Vous aimerez peut-être aussi