Reconhecimento de voz: "Sim" ou "Não"?

33

Tarefa

Implemente um programa em bytes mínimos de código-fonte ou código binário que faça o reconhecimento de voz de uma amostra de voz (eu dizendo "sim", "sim" ou "não" na voz ou no sussurro, clara ou subtilmente) com base em amostras de treinamento com a máxima precisão .

O programa deve ler train/yes0.wav, train/no0.wav, train/yes1.wave assim por diante (há 400 sins e 400 nãos no conjunto de dados de treinamento), em seguida, começar a ler inputs/0.wav, inputs/1.wavaté que ele não consegue encontrar o arquivo, analisá-lo e produzir "sim" ou "não" (ou outra palavra para resposta intermediária).

Se desejar, você pode pré-treinar o programa em vez de ler train/, mas a tabela de dados resultante conta para a pontuação (e cuidado com o ajuste excessivo nas amostras de treinamento - elas não se sobrepõem às de exame). Melhor incluir o programa usado para produzir a tabela de dados como um adendo neste caso.

Todos os arquivos de amostra são pequenos arquivos WAV estéreo endian de 16 bits, apenas do microfone de laptop, sem filtragem / redução de ruído.

Limites

Recursos proibidos:

  • Usando rede;
  • Tentando alcançar o arquivo de respostas inputs/key;
  • Subvertendo o runnerprograma que calcula a precisão;
  • Usando bibliotecas de reconhecimento existentes. Não é permitido vincular à implementação da FFT: são permitidas apenas funções matemáticas externas com quantidade constante de informações (como sinou atan2); Se você deseja o FFT, basta adicionar sua implementação ao código-fonte do programa (pode ser multilíngue, se necessário).

Limites de recursos:

  • O programa não deve levar mais de 30 minutos de tempo de CPU no meu laptop i5. Se for preciso mais, apenas a produção produzida nos primeiros 30 minutos é contada e todo o resto é assumido como meia partida;
  • Limite de memória: 1 GB (incluindo arquivos temporários);

Ferramentas

O tools/runnerprograma executa automaticamente sua solução e calcula a precisão.

$ tools/runner solutions/example train train/key 
Accuracy: 548 ‰

Ele pode examinar o programa usando dados de treinamento ou dados reais do exame. Vou tentar respostas enviadas no conjunto de dados de exame e publicar resultados (porcentagem de precisão) até tornar o conjunto de dados público.

Pontuação

Existem 5 classes de solução, dependendo da precisão:

  • Todas as amostras foram adivinhadas corretamente: Classe 0;
  • Precisão 950-999: Classe 1;
  • Precisão 835-950: Classe 2;
  • Precisão 720-834: Classe 3;
  • Precisão 615-719: Classe 4;

Dentro de cada classe, a pontuação é o número de bytes que a solução leva.

Resposta aceita: a menor solução da melhor classe não vazia.

Ligações

Todas as amostras devem ser consideradas CC-0 (domínio público), scripts e programas devem ser considerados MIT.

Solução de exemplo

Ele fornece uma qualidade de reconhecimento muito ruim, apenas mostra como ler arquivos e gerar respostas

#define _BSD_SOURCE
#include <stdio.h>
#include <assert.h>
#include <endian.h>


#define Nvols 30

#define BASS_WINDOW 60
#define MID_WINDOW 4

struct training_info {
    double bass_volumes[Nvols];
    double mid_volumes[Nvols];
    double treble_volumes[Nvols];
    int n;
};


struct training_info yes;
struct training_info no;

static int __attribute__((const)) mod(int n, int d) {
    int m = n % d;
    if (m < 0) m+=d;
    return m;
}

// harccoded to 2 channel s16le
int get_file_info(const char* name, struct training_info *inf) {
    FILE* in = fopen(name, "rb");

    if (!in) return -1;

    setvbuf(in, NULL, _IOFBF, 65536);

    inf->n = 1;

    fseek(in, 0, SEEK_END);
    long filesize = ftell(in);
    fseek(in, 128, SEEK_SET);
    filesize -= 128; // exclude header and some initial samples

    int nsamples = filesize / 4; 

    double bass_a=0, mid_a=0;
    const int HISTSIZE  = 101;
    double xhistory[HISTSIZE];
    int histpointer=0;
    int histsize = 0;

    //FILE* out = fopen("debug.raw", "wb");

    int i;
    for (i=0; i<Nvols; ++i) {
        int j;

        double total_vol = 0;
        double bass_vol = 0;
        double mid_vol = 0;
        double treble_vol = 0;

        for (j=0; j<nsamples / Nvols; ++j) {
            signed short int l, r; // a sample
            if(fread(&l, 2, 1, in)!=1) break;
            if(fread(&r, 2, 1, in)!=1) break;
            double x = 1/65536.0 * ( le16toh(l) + le16toh(r) );


            bass_a += x;
            mid_a  += x;


            if (histsize == HISTSIZE-1) bass_a   -= xhistory[mod(histpointer-BASS_WINDOW,HISTSIZE)];
            if (histsize == HISTSIZE-1) mid_a    -= xhistory[mod(histpointer-MID_WINDOW ,HISTSIZE)];

            double bass = bass_a / BASS_WINDOW;
            double mid = mid_a / MID_WINDOW - bass;
            double treble = x - mid_a/MID_WINDOW;

            xhistory[histpointer++] = x;
            if(histpointer>=HISTSIZE) histpointer=0;
            if(histsize < HISTSIZE-1) ++histsize;

            total_vol  += bass*bass + mid*mid + treble*treble;
            bass_vol   += bass*bass;
            mid_vol    += mid*mid;
            treble_vol += treble*treble;


            /*
            signed short int y;
            y = 65536 * bass;

            y = htole16(y);
            fwrite(&y, 2, 1, out);
            fwrite(&y, 2, 1, out);
            */
        }

        inf->bass_volumes[i] = bass_vol / total_vol;
        inf->mid_volumes[i] = mid_vol / total_vol;
        inf->treble_volumes[i] = treble_vol / total_vol;

        //fprintf(stderr, "%lf %lf %lf    %s\n", inf->bass_volumes[i], inf->mid_volumes[i], inf->treble_volumes[i], name);
    }
    fclose(in);

    return 0;
}

static void zerotrdata(struct training_info *inf) {
    int i;
    inf->n = 0;
    for (i=0; i<Nvols; ++i) {
        inf->bass_volumes[i] = 0;
        inf->mid_volumes[i] = 0;
        inf->treble_volumes[i] = 0;
    }
}

static void train1(const char* prefix, struct training_info *inf) 
{
    char buf[50];

    int i;

    for(i=0;; ++i) {
        sprintf(buf, "%s%d.wav", prefix, i);
        struct training_info ti;
        if(get_file_info(buf, &ti)) break;

        ++inf->n;

        int j;
        for (j=0; j<Nvols; ++j) {
            inf->bass_volumes[j]   += ti.bass_volumes[j];
            inf->mid_volumes[j]    += ti.mid_volumes[j];
            inf->treble_volumes[j] += ti.treble_volumes[j];
        }
    }

    int j;
    for (j=0; j<Nvols; ++j) {
        inf->bass_volumes[j]   /= inf->n;
        inf->mid_volumes[j]    /= inf->n;
        inf->treble_volumes[j] /= inf->n;
    }
}

static void print_part(struct training_info *inf, FILE* f) {
    fprintf(f, "%d\n", inf->n);
    int j;
    for (j=0; j<Nvols; ++j) {
        fprintf(f, "%lf %lf %lf\n", inf->bass_volumes[j], inf->mid_volumes[j], inf->treble_volumes[j]);
    }
}

static void train() {
    zerotrdata(&yes);
    zerotrdata(&no);

    fprintf(stderr, "Training...\n");

    train1("train/yes", &yes);
    train1("train/no", &no);

    fprintf(stderr, "Training completed.\n");

    //print_part(&yes, stderr);
    //print_part(&no, stderr);

    int j;
    for (j=0; j<Nvols; ++j) {
        if (yes.bass_volumes[j]   > no.bass_volumes[j]) {   yes.bass_volumes[j] = 1;   no.bass_volumes[j] = 0; }
        if (yes.mid_volumes[j]    > no.mid_volumes[j]) {    yes.mid_volumes[j] = 1;    no.mid_volumes[j] = 0; }
        if (yes.treble_volumes[j] > no.treble_volumes[j]) { yes.treble_volumes[j] = 1; no.treble_volumes[j] = 0; }
    }
}


double delta(struct training_info *t1, struct training_info *t2) {
    int j;
    double d = 0;
    for (j=0; j<Nvols; ++j) {
        double rb = t1->bass_volumes[j] - t2->bass_volumes[j];
        double rm = t1->mid_volumes[j] - t2->mid_volumes[j];
        double rt = t1->treble_volumes[j] - t2->treble_volumes[j];
        d += rb*rb + rm*rm + rt*rt;
    }
    return d;
}

int main(int argc, char* argv[])
{
    (void)argc; (void)argv;

    train();


    int i;

    int yes_count = 0;
    int no_count = 0;

    for (i=0;; ++i) {
        char buf[60];
        sprintf(buf, "inputs/%d.wav", i);

        struct training_info ti;

        if(get_file_info(buf, &ti)) break;

        double dyes = delta(&yes, &ti);
        double dno = delta(&no, &ti);

        //printf("%lf %lf %s ", dyes, dno, buf);

        if (dyes > dno) {
            printf("no\n");
            ++no_count;
        } else  {
            printf("yes\n");
            ++yes_count;
        }
    }

    fprintf(stderr, "yeses: %d noes: %d\n", yes_count, no_count);

}
Vi.
fonte
5
sem bibliotecas fft? Por quê?
John Dvorak
11
E as funções internas da FFT? O que exatamente conta como externo? Além disso, o que conta como uma função de biblioteca matemática? Podemos usar sumou precisamos usar foldl (+) 0(foldl não é específico da matemática e +não é variável)?
John Dvorak
11
ainda ... você está efetivamente proibindo sum. Eu acho que essa não é a sua intenção?
John Dvorak
11
Qual é a definição exata de funções matemáticas? Aqueles que são especializados para operar com números? E uma função genérica de "soma" que usa adição para números, mas concatenação para seqüências de caracteres? Essa soma agora é permitida?
John Dvorak
11
E as operações de vetores de J? Eles são proibidos?
John Dvorak

Respostas:

27

C ++ 11 (gcc; 1639 1625 1635 bytes, Classe 1, pontuação = 983, 960)

Vamos começar. Provavelmente é o código mais longo que já reduzi ...

#include <bits/stdc++.h>
#define $ complex<double>
#define C vector<$>
#define I int
#define D double
#define P pair<D,I>
#define Q pair<D,D>
#define E vector<D>
#define V vector<P>
#define W vector<Q>
#define S char*
#define Z(x)if(fread(&x,2,1,y)!=1)break;
#define B push_back
#define F(i,f,t)for(I i=f;i<t;i++)
#define _ return
#define J first
#define L second
const I K=75,O=16384;using namespace std;C R(C&i,I s,I o=0,I d=1){if(!s)_ C(1,i[o]);C l=R(i,s/2,o,d*2),h=R(i,s/2,o+d,d*2);C r(s*2);$ b=exp($(0,-M_PI/s)),f=1;F(k,0,s){r[k]=l[k]+f*h[k];r[k+s]=l[k]-f*h[k];f*=b;}_ r;}C T(C&i){_ R(i,i.size()/2);}char b[O];E U(S m){FILE*y;if(!(y=fopen(m,"r")))_ E();setvbuf(y,b,0,O);fseek(y,0,2);I z=ftell(y)/4-32;fseek(y,128,0);C p;F(i,0,z){short l,r;Z(l);Z(r);if(i&1)p.B($(0.5/O*le16toh(l),0));}p.resize(O);E h(O),n(O);p=T(p);F(j,0,O)h[j]=norm(p[j])/O;F(i,1,O-1)n[i]=(h[i-1]+h[i+1]+h[i]*8)/10;fclose(y);_ n;}W G(E&d){V p;F(i,3,O/2-3)if(d[i]==*max_element(d.begin()+i-3,d.begin()+i+4))p.B({d[i],i});sort(p.begin(),p.end(),greater<P>());W r;F(i,3,K+3)r.B({D(p[i].L)/O*22050,log(p[i].J)+10});D s=0;F(i,0,K)s+=r[i].L;F(i,0,K)r[i].L/=s;_ r;}char m[O];I X(S p,W&r){E t(O),h(t);I f=0;while(1){sprintf(m,"%s%d.wav",p,f++);h=U(m);if(!h.size())break;F(j,0,O)t[j]+=h[j];}F(j,0,O)t[j]/=f;r=G(t);}D Y(W&a,W&b){D r=0;F(i,0,K){D d=b[i].L;F(j,0,K)if(abs((b[i].J-a[j].J)/b[i].J)<0.015)d=min(d,abs(b[i].L-a[j].L));r+=d;}_ r;}I H(S p,W&y,W&n){I f=0;while(1){sprintf(m,"%s%d.wav",p,f++);E h=U(m);if(!h.size())break;W p=G(h);D q=Y(p,y),r=Y(p,n);printf(abs(q-r)<=0.01?"?\n":q<r?"yes\n":"no\n");}}I main(){W y,n;X("train/yes",y);X("train/no",n);H("inputs/",y,n);}

"Ungolfed" (embora seja difícil chamar um código-fonte com mais de 1,5K de golfe):

#include <iostream>
#include <stdio.h>
#include <string>
#include <algorithm>
#include <vector>
#include <math.h>
#include <complex>
#include <endian.h>
#include <functional>

using namespace std;

typedef complex<double> CD;

vector<CD> run_fft(vector<CD>& input, int offset, int size, int dist){
    if(size == 1){
        return vector<CD>(1, input[offset]);
    }
    vector<CD> partLow = run_fft(input, offset, size/2, dist*2),
               partHi  = run_fft(input, offset+dist, size/2, dist*2);

    vector<CD> result(size);
    CD factorBase = exp(CD(0, (inv?2:-2)*M_PI/size)), factor = 1;

    for(int k = 0; k < size/2; k++){
        result[k] = partLow[k] + factor*partHi[k];
        result[k+size/2] = partLow[k] - factor*partHi[k];
        factor *= factorBase;
    }
    return result;
}

vector<CD> fft(vector<CD>& input){
    int N = input.size();
    return run_fft(input, 0, N, 1);
}



const int MAX_BUF = 65536;
const int PWR_TWO = 16384;
const int NUM_CHECK = 75;
int sampling;

char buf[MAX_BUF];
vector<double> read_data(char* filenam){
    FILE* fp = fopen(filenam, "r");
    if(!fp)
        return vector<double>();
    setvbuf(fp, buf, _IOFBF, MAX_BUF);

    fseek(fp, 0, SEEK_END);
    int filesiz = ftell(fp);
    fseek(fp, 128, SEEK_SET);
    filesiz -= 128;

    int insamp = filesiz / 4;
    int freqsamp = 2,
        act_mod = 0;
    sampling = 44100 / freqsamp;
    int inputSize;

    vector<CD> input;

    for(int i = 0; i < insamp; i++){
        signed short int l, r;
        if(fread(&l, 2, 1, fp) != 1) break;
        if(fread(&r, 2, 1, fp) != 1) break;

        double act = 1/32768.0 * (le16toh(l));

        if((++act_mod) == freqsamp){
            inputSize++;
            input.push_back(CD(act,0));
            act_mod = 0;
        }
    }
    inputSize = input.size();

    //printf("%s\n", filenam);
    int numParts = (inputSize+PWR_TWO-1)/PWR_TWO;
    double partDelta = (double)inputSize / numParts, actDelta = 0;
    vector<CD> ndata(PWR_TWO);
    for(int i = 0; i < numParts; i++){
        vector<CD> partInput(PWR_TWO);
        int from = floor(actDelta),
            to = floor(actDelta)+PWR_TWO;

        for(int j = from; j < to; j++)
            partInput[j-from] = input[j];

        vector<CD> partData = fft(partInput);
        for(int j = 0; j < PWR_TWO; j++)
            ndata[j] += partData[j]*(1.0/numParts);
    }


    vector<double> height(PWR_TWO);
    for(int i = 0; i < PWR_TWO; i++)
        height[i] = norm(ndata[i])/PWR_TWO;

    vector<double> nheight(height);
    nheight[0] = (height[0]*0.8 + height[1]*0.1)/0.9;
    nheight[PWR_TWO-1] = (height[PWR_TWO]*0.8 + height[PWR_TWO-1]*0.1)/0.9;
    for(int i = 1; i < PWR_TWO-1; i++)
        nheight[i] = height[i-1]*0.1 + height[i]*0.8 + height[i+1]*0.1;

    fclose(fp);

    return nheight;
}


vector< pair<double,double> > get_highest_peaks(vector<double>& freqData){
    vector< pair<double,int> > peaks;

    for(int i = 3; i < PWR_TWO/2-3; i++){
        if(freqData[i] == *max_element(freqData.begin()+i-3, freqData.begin()+i+4)){
            peaks.push_back(make_pair(freqData[i], i));
        }
    }

    sort(peaks.begin(), peaks.end(), greater< pair<double,int> >());

    vector< pair<double,double> > res;
    for(int i = 3; i < NUM_CHECK+3; i++){
        res.push_back(make_pair((double)(peaks[i].second)/PWR_TWO*sampling, log(peaks[i].first)+10));
    }

    double sum_res = 0;
    for(int i = 0; i < NUM_CHECK; i++)
        sum_res += res[i].second;
    for(int i = 0; i < NUM_CHECK; i++)
        res[i].second /= sum_res;

    /*for(int i = 0; i < NUM_CHECK; i++)
        printf("%12lf %12lf\n", res[i].first, res[i].second);
    printf("\n");*/

    return res;
}


void train(char* dir, const char* type, vector< pair<double,double> >& res){
    vector<double> result(PWR_TWO), height(PWR_TWO);

    int numFile = 0;
    while(true){
        char filenam[256];
        snprintf(filenam, 255, "%s/%s%d.wav", dir, type, numFile);
        height = read_data(filenam);

        if(height.size() == 0)
            break;

        for(int j = 0; j < PWR_TWO; j++)
            result[j] += height[j];

        numFile++;
    }
    fprintf(stderr, "trained %s on %d files\n", type, numFile);

    for(int j = 0; j < PWR_TWO; j++)
        result[j] /= numFile;

    res = get_highest_peaks(result);
}


double dist_ab(vector< pair<double,double> >& A, vector< pair<double,double> >& B){
    double result = 0;
    for(int i = 0; i < NUM_CHECK; i++){
        double add = B[i].second;

        for(int j = 0; j < NUM_CHECK; j++){
            double dist = (B[i].first-A[j].first)/B[i].first;
            if(abs(dist) < 0.015)
                add = min(add, abs(B[i].second - A[j].second));
        }
        result += add;
    }
    return result;
}


void trial(char* dir, const char* pref, vector< pair<double,double> >& yes,
                                        vector< pair<double,double> >& no){
    int numFile = 0;
    int numYes = 0, numDunno = 0, numNo = 0;
    while(true){
        char filenam[256];
        snprintf(filenam, 255, "%s/%s%d.wav", dir, pref, numFile);

        vector<double> height = read_data(filenam);
        if(height.size() == 0)
            break;

        vector< pair<double,double> > peaks = get_highest_peaks(height);


        double distYes = dist_ab(peaks, yes),
               distNo = dist_ab(peaks, no);

        if(abs(distYes-distNo) <= 0.01){
            printf("dunno\n");
            numDunno++;
        } else if(distYes < distNo){
            printf("yes\n");
            numYes++;
        } else {
            printf("no\n");
            numNo++;
        }
        //printf(" (%lf %lf)\n", distYes, distNo);

        numFile++;
    }
}


int main(int argc, char** argv){
    vector< pair<double,double> > yes, no;


    train("train", "yes", yes);
    train("train", "no", no);

    trial("inputs", "", yes, no);
}

Não tenho a menor idéia se funcionará corretamente no conjunto de dados real (aposto que não, mas preciso tentar).

Como funciona:

  1. Colete N = 2 14 amostras do canal esquerdo, cada uma no mesmo período de tempo. Normalize-os para que o valor mínimo = 0 e o valor máximo = 1.
  2. Processe-os usando a FFT. Agora passamos do domínio do tempo para o domínio da frequência. Podemos dizer que a célula 0 de resultar matriz é 0Hz equivalente e 2 13 células -1º é 22050Hz equivalente (que é porque eu levei todos os outros amostra do canal L, por isso a minha amostragem é 22050Hz em vez de frequência de WAV, 44100Hz).
  3. Encontre a média de todos esses sinais - chame-a de "distribuição de frequência média". Encontre K os picos mais altos nessa distribuição (aqui K = 75), omitindo os primeiros (provavelmente o ruído) e encontre sua força. Usei log(mean distribution)+10e normalizei para que a soma dos maiores picos fosse 1.
  4. Temos duas "distribuições de pico" - uma para Sim e depois para Não. Se tivermos um WAV para testar, nós o transformamos da mesma maneira que antes (etapas 1, 2, 3) e obtemos a distribuição D. Então temos que verifique com qual distribuição (S / N) D é mais semelhante. Utilizei a seguinte abordagem: para cada pico em Y / N, tente encontrá-lo em D. Se o encontrarmos (aproximadamente), a pontuação desse pico é a diferença absoluta entre a força de Y / N e D; no caso oposto, é a força de Y / N (assumimos que é sempre positivo). Melhor (menor) pontuação ganha. Se os resultados estiverem muito próximos (usei diferença absoluta de 0,01), emita dunno.

Como eu disse, provavelmente nos testes finais será classificado como "ainda pior do que aleatório". Claro, espero que não: D

Editar: bug corrigido (esqueci de fechar os arquivos).

mnbvmar
fonte
11
Você está com sorte se executar worse than random. Você literalmente precisa alterar apenas um byte - distYes > distNo, e isso funcionará better than random. Ou, em outras palavras, seria bastante surpreendente se você pudesse adivinhar o resultado de uma moeda girar incorretamente 100 vezes seguidas! E não são inéditos os algoritmos simples que superam os mais complexos, portanto, +1 e desejo-lhe boa sorte.
blutorange 02/09
Testando ... Ele termina prematuramente devido a EMFILE (Too many open files)... Tentando corrigir ...
Vi.
Contador máximo máximo de arquivos abertos, agora funciona. Resultados: conjunto de dados de treinamento: Accuracy: 983 ‰; Time: 0m27.570s;; dataset exame: Accuracy: 960 ‰; Time: 0m32.957s. Bom trabalho.
Vi.
Ok, eu consertei isso. 10 bytes a mais. :)
mnbvmar
Incrível uso de #defines: P
qwr