Forum: Digitale Signalverarbeitung / DSP / Machine Learning Festkommarealisierung LMS (Least Mean Square)


von Mario A. (itse_me_mario)



Lesenswert?

Hallo zusammen,

ich versuche gerade mein in Gleitkommaarithmetik entworfenes LMS-Filter 
in ein Festkomma LMS-Filter umzubauen. Leider habe ich im Internet noch 
keine Antwort auf mein Problem gefunden. Die Problemstellung ist in 
Zeichnung.png abgebildet. Vllt habt ihr auch noch Anmerkungen. Würdet 
ihr zus. eine Over-/Underflowerkennung einbauen? Würdet ihr long integer 
als Akkumulator verwenden?

Erst einmal habe ich CodeBlocks verwendet. Später soll das ganze noch 
auf einen DSP und Mikrocontroller sowie ein FPGA.

Das Gleitkomma-LMS-Filter ist im Anhang (siehe 
Referenzcode_LMS_Float_X1D1.txt). Die Funktion habe ich anhand von 
ERLE-Kurven überprüft - das passt. Zusätzlich habe ich das ganze auch in 
MATLAB implementiert.

Für ein Beta (Lernrate) von 0.01 habe ich die Ergebnisse für den 
Filterausgang y und den Fehler e angehängt (LMS_Data....txt). Damit man 
aber besser abschätzen kann, wo sich die Fehler im C-Programm befinden, 
habe ich die Signale x, d, e und y nochmal als .txt-Datei im Q15-Format 
angehängt.

Meine Daten d (DataD1Q15.txt) und x (DataX1Q15.txt) sind im Q1.15 
Datenformat. Dafür wurden die double Werte für d und x mit 2^15 
multipliziert. Die Ergebnisse sollen wieder als Q0.15 Zahlen (1 
Vorzeichen Bit, 15 Nachkommastellen) dargestellt werden.

Hier mein aktueller Stand, nach etlichen Änderungen... :P
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <stdint.h>
4
5
#define N  32             // Anzahl adaptiver FIR-Filterkoeffizienten
6
#define NUM_ITERS  32000  // Anzahl Durchläufe
7
8
int16_t s16_beta = 327;   // Lernrate 0.01*2^15 = 327.68
9
10
int32_t s32_w[N] = {0};       
11
int16_t s16_w[N] = {0};       
12
13
int16_t s16_x[N] = {0};      
14
15
int32_t i = 0;
16
int32_t n = 0;
17
18
int16_t s16_d = 0;
19
int16_t s16_y = 0;
20
int16_t s16_e = 0;
21
22
int16_t s16_prod1 = 0;
23
int16_t s16_prod2 = 0;
24
25
int32_t s32_acc = 0;
26
27
int main()
28
{
29
    //*************************************************************
30
    FILE *fp_x;
31
    FILE *fp_d;
32
    FILE *fp_y;
33
    FILE *fp_e;
34
    //*************************************************************
35
    fp_x = fopen("DataX1Q15.txt","r");
36
    fp_d = fopen("DataD1Q15.txt","r");
37
    fp_y = fopen("LMS_Data_Y_Q15_beta_0_01.txt", "w");
38
    fp_e = fopen("LMS_Data_E_Q15_beta_0_01.txt", "w");
39
    //*************************************************************
40
41
    if( (fp_x == NULL) || (fp_d == NULL) || (fp_y == NULL) || (fp_e == NULL))
42
    {
43
        printf("One of the Files could not be opened.\n");
44
    }
45
    else
46
    {
47
        for(n=0; n<NUM_ITERS; n++)
48
        {
49
            //******************Read Data from TXT-File*************
50
            fscanf(fp_x,"%d\n", &s16_x[0]);
51
            fscanf(fp_d,"%d\n", &s16_d);
52
            //******************************************************
53
54
            s32_acc = 0; 
55
56
            for (i = 0; i < N; i++)
57
            {
58
                //hier könnte Overflow auftreten
59
                s32_acc = s32_acc + (int32_t)s16_w[i] * (int32_t)s16_x[i];
60
            }
61
62
            s16_y = (int16_t)(s32_acc >> 15);
63
            s16_e = s16_d - s16_y;
64
65
            for (i = N-1; i >= 0; i--)
66
            {
67
                s16_prod1 = (int16_t)((s16_x[i]  * s16_e   )>>15);
68
                s16_prod2 = (int16_t)((s16_prod1 * s16_beta)>>15);
69
70
                //Achtung hier könnte Overflow auftreten
71
                s16_w[i] = s16_w[i] + s16_prod2; 
72
73
                if(i!=0)
74
                {
75
                    s16_x[i] = s16_x[i-1]; 
76
                }
77
            }
78
79
            //************print values*************
80
            printf("%d Values: x=%d\td=%d\t Write Values \ty=%d\te=%d \t s32_acc=%d\n\n", n+1, s16_x[0], s16_d, s16_y, s16_e, s32_acc);
81
            //*************************************
82
83
            //******Write Data to TXT-File*********
84
            fprintf(fp_y, "%d\n", s16_y);
85
            fprintf(fp_e, "%d\n", s16_e);
86
            //*************************************
87
        }
88
    }
89
    fclose(fp_x);
90
    fclose(fp_d);
91
    fclose(fp_y);
92
    fclose(fp_e);
93
94
    while(1){}
95
}

Das Problem ist, dass die Werte für y sehr stark von d abweichen. Der 
Fehler e = d-y ist dadurch sehr groß und wird nicht kleiner mit der 
Zeit. Der Sollverlauf des Fehlers ist in 
LMS_Data_X1_D1_E_Float_Order31_beta_0_01.txt zu sehen. Also werden wohl 
die Filterkoeffizienten w[n] falsch bestimmt. Leider sehe ich nicht 
woran es liegt. Vermutlich sind die Werte für das rechtsshiften >>15 zu 
klein und werden dadurch 0 bzw. zu klein. Weiß jemand wie man das am 
besten angeht?

Später soll auch noch das NLMS Filter als Festkommavariante 
implementiert werden.

Über eure Hilfe oder/und Tipps für gute Literatur würde ich mich sehr 
freuen! Vielen Dank im Voraus!

: Bearbeitet durch User
von Mario A. (itse_me_mario)


Angehängte Dateien:

Lesenswert?

Ich hänge mal noch den Code inkl. dem Schreiben der Filterkoeffizienten 
in ein txt-File an.

von foobar (Gast)


Lesenswert?

Hmm... scanf %d und ein int16_t?  Passt das?

Ansonsten: wenn du unerwartete Ergebnisse bekommst und schon vermutest, 
dass Overflows stattfinden, dann check das doch mal.  Ggfs den Accu 
erweitern oder saturiert arbeiten.

von foobar (Gast)


Lesenswert?

> fscanf(fp_d,"%d\n", &s16_x[0]);
> ...
> s16_prod1 = (int16_t)((s16_x[i]  * s16_e   )>>15);

Nur eine der beiden Zeile kann korrekt sein. Wenn ints 16 Bit sind, die 
erste, wenn 32 dann die zweite.

von Mario A. (itse_me_mario)


Angehängte Dateien:

Lesenswert?

Das könnte ich noch mit
fscanf(fp_x,"%16d\n", &s16_x[0]);
fscanf(fp_d,"%16d\n", &s16_d);
auf 16 Stellen begrenzen. Sollte aber auch ohne gehen oder?

Hmm wenn ich

s16_prod1 = (int16_t)((s16_x[i]  * s16_e   )>>15);

in

s32_Wert1 = s16_x[i]  * s16_e;
s16_prod1 = (int16_t)(s32_Wert1>>15);

ändere? Bringt das was? Ich probiers mal. Ich bin mir nicht ganz sicher 
was du meinst. Also über die Filepointer fp_x und fp_d kann ich die 
Zahlen als int16 einlesen.

Wenn ich eine Zahl im Q1.15 Datenformat mit einer anderen Zahl im Q1.15 
Datenformat multipliziere kommt eine Zahl im Q2.30 Datenformat raus. Die 
Zahl enthält dann 2 redundante Vorzeichenbits. Durch (int16_t)(Zahl>>15) 
wird daraus wieder eine Zahl im Q1.15 Datenformat, wenn ich mich nicht 
irre.
s16_x und s16_d sollten Zahlen im Q1.15 Datenformat sein.

Im Anhang mal ein Beispiel, was ich meine und die zugehörige 
Konsolenausgabe. Edit: Gerade gesehen, dass in der letzten Zeile nicht 
int32 von 0,5*0,5*0,5 sondern nur von 0,5*0,5 ausgegeben wird mit 
printf. Der Q-Wert stimmt aber.

: Bearbeitet durch User
von foobar (Gast)


Lesenswert?

> fscanf(fp_x,"%16d\n", &s16_x[0]);

Nein, %hd oder SCNd16:
  fscanf(fp_x, SCNd16"\n", &s16_x[0]);

Alternativ:
  int tmp;
  ...
  fscanf(fp_x,"%d\n", &tmp); s16_x[0] = (int16_t)tmp;


> s32_Wert1 = s16_x[i]  * s16_e;
> s16_prod1 = (int16_t)(s32_Wert1>>15);

Bei 16-Bit-ints ist die Multiplikation nur 16 Bit.  Sie muß 32-Bit sein, 
also mind einen Operand erweitern:
   (int32_t)s16_x[i] * s16_e

von Mario A. (itse_me_mario)



Lesenswert?

Ahh danke für den Tipp mit %hd! Sehr guter Tipp, danke!

>>Bei 16-Bit-ints ist die Multiplikation nur 16 Bit.
Ja das hab ich vorhin übersehen! Hatte ich schon ausgebessert aber danke 
für den Tipp!

Mein Code sieht jetzt so aus:
1
...
2
#define N  32             // number of filter coeffs/weights
3
#define NUM_ITERS  32000  // number of iterations
4
5
int16_t s16_beta = 327;         // learning rate 0.01*2^15 = 327.68
6
int16_t s16_w[N] = {0};       // adaptive filter weights
7
int16_t s16_x[N] = {0};       // adaptive filter delay line
8
9
int32_t i = 0;
10
int32_t n = 0;
11
12
int16_t s16_d = 0;
13
int16_t s16_y = 0;
14
int16_t s16_e = 0;
15
16
int16_t s16_prod1 = 0;
17
int16_t s16_prod2 = 0;
18
19
int32_t s32_acc = 0;
20
int32_t s32_Wert1 = 0;
21
int32_t s32_Wert2 = 0;
22
23
int main()
24
{
25
    ...
26
27
    else
28
    {
29
        for(n=0; n<NUM_ITERS; n++)
30
        {
31
            fscanf(fp_x,"%hd\n", &s16_x[0]); 
32
            fscanf(fp_d,"%hd\n", &s16_d);
33
34
            s32_acc = 0;     
35
36
            for (i = 0; i < N; i++)
37
            {
38
                //Achtung hier könnte Overflow auftreten
39
                s32_acc = s32_acc + (int32_t)s16_w[i] * (int32_t)s16_x[i]; 
40
            }
41
42
            s16_y = (int16_t)(s32_acc >> 15);
43
            s16_e = s16_d - s16_y;
44
45
            for (i = N-1; i >= 0; i--)
46
            {
47
                s32_Wert1 = s16_x[i]  * s16_e;
48
                s16_prod1 = (int16_t)(s32_Wert1>>15);
49
50
                s32_Wert2 = s16_prod1 * s16_beta;
51
                s16_prod2 = (int16_t)(s32_Wert2>>15);
52
53
                s16_w[i] = s16_w[i] + s16_prod2; //Achtung hier könnte Overflow auftreten
54
55
                if(i!=0)
56
                {
57
                    s16_x[i] = s16_x[i-1];  // shift data in delay line
58
                }
59
60
                if(i>0)
61
                {
62
                    fprintf(fp_w, "%d\n", s16_w[i]); //Filterkoeffizienten
63
                }
64
                else
65
                {
66
                    fprintf(fp_w, "%d\n", s16_w[i]);
67
                    fprintf(fp_w, "%dte Koeffizienten\n",n);
68
                }
69
            }
70
...

Ich hab das mal geplotted (siehe Anhang). Sieht jetzt schon mal viel 
besser aus. Im Vergleich zur Double-Implementierung ist noch Luft nach 
oben. Vllt muss ich mal eine höhere Lernrate testen. Ich hab mal 
gelesen, dass ein zu kleines beta bei Festkommarealisierungen sonst dazu 
führt, dass sich die Koeffizienten ab einem gewissen Punkt nicht mehr 
ändern.

Vllt sollte ich es auch mal mit 24 Bit versuchen (also Q0.23).

Vielen Dank foobar! Der Tipp mit %hd hat mir echt geholfen! :)


Edit: Habe mal einen Vergleich von Double und Q15 angehängt (beide mit 
beta=0,2).

: Bearbeitet durch User
von Mario A. (itse_me_mario)


Lesenswert?

Ich hätte jetzt mal versucht das ganze auf den NLMS Algorithmus 
anzuwenden, bevor ich den LMS weiter optimiere.

An sich kommt nur eine Schleife
1
for(j = 0; j < N; j++)
2
{
3
   s32_input_signal_power = s32_input_signal_power + ...
4
   (int32_t)s16_x[j] * (int32_t)s16_x[j];
5
}
6
s16_input_signal_power = (int16_t)(s32_input_signal_power>>15);

und eine zusätzliche Division durch den ermittelten Wert 
s16_input_signal_power dazu:
1
for(i = N-1; i >= 0; i--)
2
{ 
3
// w = w + (beta*e*x)/(x*x)
4
5
s32_Wert1 = s16_x[i]  * s16_e;
6
s16_prod1 = (int16_t)(s32_Wert1>>15);
7
8
s32_Wert2 = s16_prod1 * s16_beta;
9
s16_prod2 = (int16_t)(s32_Wert2>>15);
10
11
s16_div = s16_prod2 / s16_input_signal_power;
12
s16_w[i] = s16_w[i] + s16_div; //Achtung hier könnte Overflow auftreten
13
...
14
}
Der Rest bleibt gleich. So funktioniert das ganze leider noch nicht. Ich 
habe auch mal versucht, dass ich s16_prod2 vor der Division mit 
verschiedenen Werten multipliziere bzw. einen größeren Datentyp für den 
Zähler, aber das hat leider auch NOCH nichts gebracht. Ich schau mir das 
morgen nochmal an. Jetzt geht grad nichts mehr. ;)

: Bearbeitet durch User
von foobar (Gast)


Lesenswert?

Wenn du die Implementierungen vergleichen willst, solltest du beide auch 
mit gleichen Daten füttern.  Rechne mal die reduzierten int-Daten nach 
double zurück und fütter damit die Double-Version.  Dann weisst du, was 
mit den reduzierten Daten überhaupt noch erreichbar ist und was durch 
die int-Implementation zusätzlich verloren geht.

von Mario A. (itse_me_mario)



Lesenswert?

Ich hab jetzt mal den Zähler vorher multipliziert (<<16). Anfangs sehen 
die Fehler e ganz gut aus. Irgendwann weichen die Fehler zu sehr vom 
Ideal ab (vgl. Data_E... und Data_Y... der Gleitkommarealisierung).
1
else
2
    {
3
        for(n=0; n<NUM_ITERS; n++)
4
        {
5
            fscanf(fp_x,"%hd\n", &s16_x[0]); 
6
            fscanf(fp_d,"%hd\n", &s16_d);
7
8
            s32_acc = 0;                    // compute filter output
9
            s32_input_signal_power = 0;
10
11
            for(j = 0; j < N; j++)
12
            {
13
                s32_input_signal_power = s32_input_signal_power + (int32_t)s16_x[j] * (int32_t)s16_x[j];
14
            }
15
16
            s16_input_signal_power = (int16_t)(s32_input_signal_power>>15);
17
18
            for(i = 0; i < N; i++)
19
            {
20
                s32_acc = s32_acc + (int32_t)s16_w[i] * (int32_t)s16_x[i]; 
21
            }
22
23
            s16_y = (int16_t)(s32_acc >> 15);
24
            s16_e = s16_d - s16_y;
25
26
            for(i = N-1; i >= 0; i--)
27
            {
28
                s32_Wert1 = s16_x[i]  * s16_e;
29
                s16_prod1 = (int16_t)(s32_Wert1>>15);
30
31
                s32_Wert2 = s16_prod1 * s16_beta;
32
                s16_prod2 = (int16_t)(s32_Wert2>>15);
33
34
                s32_test1 = ((int32_t)(s16_prod2)<<15);
35
36
                s32_test2 = s32_test1 / (int32_t)s16_input_signal_power;
37
38
                s16_div = (int16_t)(s32_test2); // >> 16);
39
40
                s16_w[i] = s16_w[i] + s16_div; 
41
...
42
            }

von Mario A. (itse_me_mario)


Angehängte Dateien:

Lesenswert?

>>Wenn du die Implementierungen vergleichen willst, solltest du beide auch
mit gleichen Daten füttern.

Danke für den Tipp. Ich habe mal eben die verwendeten Q15 Werte für X 
und D in MATLAB als Ganzzahlen eingelesen und dann durch 2^15 geteilt. 
Dann habe ich diese wieder in ein txt-file geschrieben (als Double) und 
anschließend nochmal gelesen und in MATLAB mit den Original Double 
Werten von X und D verglichen.

Es scheint, dass das nicht das Problem ist.

von foobar (Gast)


Lesenswert?

> Es scheint, dass das nicht das Problem ist.

Das siehst du anhand des grafischen Vergleichs?  Da sind, wenn's hoch 
kommt, gerade mal die oberen 7-8 Bit zu erkennen und natürlich stimmen 
die überein - die Unterschiede sind in den abgeschnittenen unteren 32-48 
Bits.

von Mario A. (itse_me_mario)


Angehängte Dateien:

Lesenswert?

Nein ich hab mir auch die Abweichung plotten lassen und die Differenz in 
den Arrays überprüft. Aber so vom Prinzip her hast du es gemeint oder?

Ich hab jetzt mal zusätzlich noch
if(s16_input_signal_power == 0){ s16_input_signal_power = 1; }
eingebaut. Bin weiter am schauen woran´s liegt.

Bei der Division geht wohl viel verloren. Vllt sollte ich für den 
Quotienten 64 Bit verwenden. Ich versuch das mal.

: Bearbeitet durch User
von Mario A. (itse_me_mario)


Lesenswert?

Leider hab ich noch nicht die Lösung meines Problems gefunden.. Schau 
mir morgen mal die einzelnen Koeffizienten w[n] genauer an.

von Mario A. (itse_me_mario)


Angehängte Dateien:

Lesenswert?

Meine Koeffizienten w werden scheinbar irgendwann zu groß... Dadurch 
wird dann auch y zu groß. Ich weiß noch nicht wie ich das umgehe. Hab 
jetzt zus. noch eine Overflow/Underflowerkennung drin mit Sättigung.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.