移動平均を計算するための効率的なアルゴリズム/データ構造


9

現在、ヒートポンプシステムの温度、流量、電圧、電力、エネルギーを表示するグラフィックLCDシステムを開発しています。グラフィックLCDを使用するということは、私のSRAMの半分と私のフラッシュの約75%がスクリーンバッファーと文字列によって使用されていることを意味します。

現在、エネルギーの最小値/最大値/平均値を表示しています。真夜中に毎日の数値がリセットされると、システムはその日の消費量が前の最小値または最大値を上回っているか下回っているかをチェックし、値を保存します。平均は、累積エネルギー消費量を日数で割って計算されます。

先週と先月(簡単にするために4週間)の日平均、つまり移動平均を表示したいと思います。現在、これには過去28日間の値の配列を維持し、毎月の配列全体と毎週の最後の7日間の平均を計算することが含まれます。

最初は、フロートの配列を使用してこれを実行していました(エネルギーは「12.12kWh」の形式であるため)。これは、28 * 4バイト= 112バイト(SRAMの5.4%)を使用していました。小数点以下1桁でも構わないので、uint16_tを使用して図に100を掛けることに変更しました。これは、12.12が1212として表され、表示のために100で割ることを意味します。

配列のサイズは56バイトに下がりました(はるかに良いです!)。

図をuint8_tに減らす簡単な方法はありません。小数点以下の桁落ちは許容できますが(「12.12kWh」ではなく「12.1kWh」)、消費量は25.5kWh(255は8ビットの符号なし整数で表される最高値)よりも高いことがよくあります。消費量が10.0kWh未満または35.0kWhを超えたことは一度もないので、格納された数値から10を差し引くこともできますが、ある日、これらの制限を超えることがわかっています。

次に、9ビットの値を配列にパックするコードをテストしました。これは0-51.2kWhの範囲を与え、合計で32バイトを使用します。ただし、このような配列へのアクセスは、特に平均を計算するためにすべての値を反復処理する必要がある場合は、かなり低速です。

だから私の質問は-寿命、28日、7日の3つのウィンドウで移動平均を計算するより効率的な方法はありますか?効率とは、SRAMの使用量の点で小さいことを意味しますが、巨大なコードのペナルティはありません。すべての値の保存を回避できますか?


特定のウィンドウの移動平均を計算しますか、それとも平均の推定/近似で計算しますか?
asheeshr 2014年

7日と28日のウィンドウの移動平均が必要です。
Cyber​​gibbons 2014年

0.2kWh(係数5で除算して乗算)の解像度を使用しても、8ビットで0〜51.2kWhの範囲が得られます
ラチェットフリーク

文字列やその他の定数を外部RAMまたは外部フラッシュに配置することになるかもしれません。「フラッシュメモリまたはSRAMが不足した場合はどうすればよいですか?」を参照してください
David Cary

回答:


2

データの標準偏差が低い場合、1つの方法は、ウィンドウ全体で値を合計し、合計から平均を差し引いて、新しい値を追加することです。

これは、外れ値ない場合にうまく機能し、それにより、時間の経過とともに全体的なエラーがゼロになる傾向があります。

//Pseudocode

count=0
while new_reading and count<7:
    sum += new_reading        //Calculate the sum of first 7 values
    count++

while new_reading:            //Loop till new readings available
    avg = sum / 7             //Calculate average
    sum -= avg                //Subtract average from sum
    sum += new_reading        //Add next reading to sum
    print avg

2

別の方法を使用でき、現在の平均を維持してから

average = (weight1*average+weight2*new_value)/(weight1+weight2);

真のローリング平均ではなく、セマンティクスも異なりますが、それでもニーズに合う場合があります

値ごとの9ビットソリューションのより効率的な計算方法として、値の上位8ビットを配列に保持し、最下位ビットを分離することができます。

uint8_t[28] highbits;
uint32_t lowbits;

分割する必要がある値を設定するには

void getvalue(uint8_t index, uint16_t value){
    highbits[index] = value>>1;
    uint32_t flag = (value & 1)<<index;
    highbits|=flag;
    highbits&=~flag;
}

結果として2はANDとORをシフトし、notをシフトします

平均を計算するには、さまざまなビットトリックを使用してスピードを上げることができます。

uint16_t getAverage(){
    uint16_t sum=0;
    for(uint8_t i=0;i<28;i++){
        sum+=highbits[i];
    }
    sum<<=1;//multiply by 2 after the loop
    sum+=bitcount(lowbits);
    return sum/28;
}

あなたはのための効率的な並列ビットカウントを使用することができますbitcount()


1
これにより、7日と28日の平均をどのように計算できるかについて、さらに説明していただけますか?
Cyber​​gibbons 2014年

私は以前、このアプローチを使用してノイズの多いアナログ値を平滑化しましたが、確かにかなり効果的でした。ただし、結果の値は非常に粗い量子化器で処理されていたため、あまり精度を上げる必要はありませんでした。履歴平均も必要ありませんでした。
Peter Bloomfield

これにより、特定のウィンドウの平均を計算できません。
asheeshr 2014年

@Cyber​​gibbons異なる重みを使用してウィンドウを概算できるため、古い値が前後に重要でなくなるか、7日間のウィンドウで7日間、28日間の平均でこの移動平均を維持できます
ラチェットフリーク

1

以前の値との差だけを保存してみませんか?電子工学には、DA / ADコンバーターに使用されるデルタシグマコンバーターと呼ばれる同様の概念があります。以前の測定値が現在の測定値にかなり近いという事実に依存しています。


別の興味深いアイデア。残念ながら、これはヒートポンプシステムであり、1日は30kWh、次の10kWhかかるため、エネルギー消費が常にこのようになるかどうかはわかりません。私は本当にデータを集めて見る必要があります。
Cyber​​gibbons 2014年

0

取得したらすぐに値を加算できないのはなぜですか。つまり、1日目の値を取得し、それを1で除算して、どこかに保存します。次に、1に値を掛けて次の値に加算し、両方を2で割ります。

この方法を実行すると、2つまたは3つの変数を持つローリング平均が作成されます。私はいくつかのコードを記述しますが、stackexchangeは初めてなので、ご容赦ください。


これが7日間と28日間のウィンドウをどのように処理するのかわかりませんか?
Cyber​​gibbons 2014年

前の値と次の値を追跡し、移動平均からそれらを加算および減算し続けます...
Aditya Somani 14

1
それで、私は27日間の歴史を思い出す必要がある状態に戻っていますか?
Cyber​​gibbons 2014年

私は考えてきました、そしてあなたは正しいです。したがって、技術的には私の答えが正しくありません。私はそれにもう少し時間と忍耐を費やしています。多分箱から出して何か。何か思いついたらお知らせします。私は職場でこのようなことをよくしています。質問させてください。混乱してすみません。
Aditya Somani 14年

0

... 28日と7日で移動平均を計算するより効率的な方法はありますか?... 27日間の履歴を覚える必要があります...?

次のように、28個の値ではなく11個の値を格納するのに十分近くなる可能性があります。

// untested code
// static variables
uint16_t daily_energy[7]; // perhaps in units of 0.01 kWh ?
uint16_t weekly_energy[4]; // perhaps in units of 0.1 kWh ?

void print_week_status(){
    Serial.print( F("last week's total energy :") );
    Serial.println( weekly_energy[0] );
    int sum = 0;
    for( int i=0; i<4; i++ ){
        sum += weekly_energy[i];
    };
    Serial.print( F("Total energy over last 4 complete weeks :") );
    Serial.println( sum );
    int average_weekly_energy = sum/4;
    int average_daily_energy = average_weekly_energy/7;
    Serial.print( F("Average daily energy over last 4 weeks :") );
    Serial.println( average_daily_energy );
}
void print_day_status(){
    Serial.print( F("Yesterday's energy :") );
    Serial.println( daily_energy[0] );
    Serial.print( F("average daily energy over the last 7 complete days: ") );
    int sum = 0;
    for( int i=0; i<7; i++ ){
        sum += daily_energy[i];
    };
    int average = sum/7;
    Serial.println( average );
}

つまり、過去27日間の毎日のすべての詳細を保存するのではなく、(a)過去7日間程度の詳細な毎日の情報の値を7個程度保存し、(b)「要約」を4個程度保存します過去4週間ほどの各週の合計または平均情報の値。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.