Spracherkennung: "Ja" oder "Nein"?

33

Aufgabe

Implementieren Sie ein Programm in minimalen Byte Quell- oder Binärcode, das die Spracherkennung eines Sprachmusters (wobei ich "Ja", "Ja" oder "Nein" in der Stimme oder im Flüsterton, einfach oder schrullig, sage) basierend auf Trainingsmustern mit maximaler Genauigkeit ausführt .

Das Programm sollte lesen train/yes0.wav, train/no0.wav, train/yes1.wavund so weiter (es gibt 400 Ja und 400 Neins in Trainingsdaten), dann anfangen zu lesen inputs/0.wav, inputs/1.wavbis es die Datei nicht finden kann, es zu analysieren und „ja“ oder „nein“ (oder ein anderes Wort ausgibt für Zwischenantwort).

Wenn Sie möchten, können Sie das Programm vorab trainieren, anstatt es zu lesen train/. Die resultierende Datentabelle wird jedoch zur Punktzahl hinzugerechnet (und achten Sie darauf, dass die Trainingsmuster nicht zu stark angepasst werden - sie überschneiden sich nicht mit den Prüfungsproben). In diesem Fall ist es besser, das zur Erstellung der Datentabelle verwendete Programm als Nachtrag aufzunehmen.

Alle Beispieldateien sind Little-Endian-16-Bit-Stereo-WAV-Dateien, nur vom Laptop-Mikrofon, ohne Filterung / Rauschunterdrückung.

Grenzen

Verbotene Funktionen:

  • Netzwerk verwenden;
  • Der Versuch, die Antwortdatei zu erreichen inputs/key;
  • Untergraben des runnerProgramms, das die Genauigkeit berechnet;
  • Vorhandene Erkennungsbibliotheken verwenden. Die Verknüpfung mit einer FFT-Implementierung ist nicht zulässig: Es sind nur externe mathematische Funktionen zulässig, die eine konstante Menge an Informationen (wie sinoder atan2) enthalten. Wenn Sie FFT möchten, fügen Sie die Implementierung einfach Ihrem Programmquellcode hinzu (sie kann bei Bedarf mehrsprachig sein).

Ressourcenlimits:

  • Das Programm sollte auf meinem i5-Laptop nicht länger als 30 Minuten CPU-Zeit beanspruchen. Wenn es länger dauert, werden nur die in den ersten 30 Minuten produzierten Ergebnisse gezählt, und für alle anderen wird eine halbe Übereinstimmung angenommen.
  • Speicherlimit: 1 GB (einschließlich temporärer Dateien);

Werkzeuge

Das tools/runnerProgramm führt Ihre Lösung automatisch aus und berechnet die Genauigkeit.

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

Es kann das Programm anhand von Trainingsdaten oder anhand von tatsächlichen Prüfungsdaten untersuchen. Ich werde eingereichte Antworten auf den Untersuchungsdatensatz versuchen und die Ergebnisse veröffentlichen (Genauigkeit in Prozent), bis ich den Datensatz öffentlich mache.

Wertung

Je nach Genauigkeit gibt es 5 Lösungsklassen:

  • Alle Proben richtig geraten: Klasse 0;
  • Genauigkeit 950-999: Klasse 1;
  • Genauigkeit 835-950: Klasse 2;
  • Genauigkeit 720-834: Klasse 3;
  • Genauigkeit 615-719: Klasse 4;

In jeder Klasse gibt die Bewertung die Anzahl der Bytes an, die die Lösung benötigt.

Akzeptierte Antwort: Kleinste Lösung in der besten nicht leeren Klasse.

Links

Alle Beispiele sollten als CC-0 (Public Domain) betrachtet werden, Skripte und Programme sollten als MIT betrachtet werden.

Beispiellösung

Es bietet eine sehr schlechte Erkennungsqualität und zeigt lediglich, wie Dateien gelesen und Antworten ausgegeben werden

#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.
quelle
5
Keine FFT-Bibliotheken? Warum?
John Dvorak
1
Was ist mit eingebauten FFT-Funktionen? Was genau zählt als extern? Was zählt auch als mathematische Bibliotheksfunktion? Dürfen wir verwenden sumoder müssen wir verwenden foldl (+) 0(foldl ist nicht mathematikspezifisch und +nicht variadisch)?
John Dvorak
1
immer noch ... du verbietest effektiv sum. Ich denke, das ist nicht deine Absicht?
John Dvorak
1
Was ist die genaue Definition von mathematischen Funktionen? Diejenigen, die auf Zahlen spezialisiert sind? Was ist mit einer generischen "Summen" -Funktion, die Addition für Zahlen, aber Verkettung für Zeichenfolgen verwendet? Ist diese Summe jetzt erlaubt?
John Dvorak
1
Was ist mit Js Vektoroperationen? Sind sie nicht erlaubt?
John Dvorak

Antworten:

27

C ++ 11 (gcc; 1639 1625 1635 Bytes, Klasse 1, Score = 983, 960)

Fangen wir an. Es ist wahrscheinlich der längste Code, den ich je gekürzt habe ...

#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" (obwohl es schwierig ist, einen über 1,5 KB großen Quellcode als Golf zu bezeichnen):

#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);
}

Ich habe keine Ahnung, ob es in einem echten Datensatz richtig funktioniert (ich wette, das wird es nicht, aber ich muss es versuchen).

Wie es funktioniert:

  1. Nehmen Sie N = 2 14 Proben aus dem linken Kanal, jeweils in gleicher Zeitspanne. Normalisieren Sie sie so, dass der Mindestwert = 0 und der Höchstwert = 1 ist.
  2. Verarbeiten Sie sie mit FFT. Jetzt haben wir vom Zeitbereich zum Frequenzbereich gewechselt. Man könnte sagen , dass 0 - ten Zelle Array von 0Hz resultierenden ist äquivalent und 2 13 -1 Zelle ist 22050Hz Äquivalent (das ist , weil ich jede andere Probe , die von L - Kanal hat, so dass meine Probenahme 22050Hz anstelle von WAV der Frequenz 44100 Hz).
  3. Ermitteln Sie den Mittelwert aller dieser Signale - nennen Sie ihn "mittlere Häufigkeitsverteilung". Finden Sie K höchste Peaks in einer solchen Verteilung (hier K = 75), lassen Sie die ersten paar (wahrscheinlich Rauschen) weg und finden Sie ihre Stärke. Ich habe verwendet log(mean distribution)+10und dann normalisiert, so dass die Summe der größten Peaks 1 war.
  4. Wir haben zwei "Spitzenverteilungen" - eine für Ja, die zweite für Nein. Wenn wir ein WAV zum Testen haben, transformieren wir es auf die gleiche Weise wie zuvor (Schritte 1, 2, 3) und erhalten die Verteilung D. Dann müssen wir Überprüfen Sie, welcher Verteilung (J / N) D ähnlicher ist. Ich habe den folgenden Ansatz gewählt: Versuchen Sie für jeden Peak in Y / N, ihn in D zu finden. Wenn wir ihn (ungefähr) finden, ist die Punktzahl für diesen Peak der absolute Unterschied zwischen der Stärke von Y / N und D; im gegenteiligen Fall ist es die Stärke von Y / N (wir gehen davon aus, dass sie immer positiv ist). Bessere (kleinere) Punktzahl gewinnt. Wenn die Ergebnisse sehr ähnlich sind (ich habe 0,01 absolute Differenz verwendet), wird ausgegeben dunno.

Wie ich bereits sagte, wird es wahrscheinlich bei den letzten Tests als "noch schlimmer als zufällig" eingestuft. Natürlich hoffe ich nicht: D

Bearbeiten: Fehler behoben (vergessen, die Dateien zu schließen).

mnbvmar
quelle
1
Sie haben Glück, wenn es funktioniert worse than random. Sie müssen buchstäblich nur ein Byte ändern - distYes > distNound das wird es auch tun better than random. Oder anders ausgedrückt, es wäre ziemlich erstaunlich, wenn Sie das Ergebnis eines Münzwurfs 100 Mal hintereinander falsch erraten könnten! Und es ist keine Seltenheit, dass diese einfachen Algorithmen besser abschneiden als komplexere. +1 und ich wünsche Ihnen viel Glück.
Blutorange
Testen ... Das Programm wird vorzeitig beendet, weil EMFILE (Too many open files)... versucht wird, das Problem zu beheben ...
Vi.
Maximaler Zähler für geöffnete Dateien überschritten, jetzt funktioniert es. Ergebnisse: Trainingsdatenmenge: Accuracy: 983 ‰; Time: 0m27.570s;; Prüfung Dataset: Accuracy: 960 ‰; Time: 0m32.957s. Gut gemacht.
Vi.
Okay, ich habe das behoben. 10 Bytes mehr. :)
mnbvmar
Unglaubliche Verwendung von #defines: P
qwr