ホーム » 2018 (ページ 9)

年別アーカイブ: 2018

2025年5月
 123
45678910
11121314151617
18192021222324
25262728293031

検索・リンク

様々な移動平均・レポート-No.3

移動平均のレポートでは、表計算ソフトを用いて、移動平均の範囲のとり方などを変えながら、平均をとった結果に、どう影響するのかを考える。

Excel で様々な移動平均の式を入力

表計算ソフトに、移動平均の式を入力する際には、以下の資料を参考にせよ。

上図のB4〜E4にできた移動平均の式は、B5以下にコピーすればいい。

レポート内容

以下のような移動平均を、Excel にて計算し、その結果の違いについて考察せよ。
移動平均で用いる点の数は、自分の出席番号の3の余りによって、条件を与える。

計算方法 出席%3=0 出席%3=1 出席%3=2
単純移動平均(狭) n=3 n=4 n=5
単純移動平均(広) n=6 n=8 n=10
過去の値による単純移動平均(狭) n=3 n=4 n=5
過去の値による単純移動平均(広) n=6 n=8 n=10
加重移動平均(狭) n=3 n=4 n=5
加重移動平均(広) n=6 n=8 n=10
指数移動平均(基本) α=1/2 α=1/2 α=1/2
指数移動平均(広) α=1/3 α=1/4 α=1/5

平均に用いる値は、以下のデータとする。

    • 2018-06-05-wave.csv 時刻tとx(t)のコンマ区切りファイル
    • 時間の遅延がわかるような波形を用い、考察してあることが望ましい

1枚のグラフの中に、元波形+8波形=9本のグラフを記載すると、グラフの内容が分かりにくいので、複数のグラフ結果で図示すること。

プログラミングが得意な人は、上記をExcelで処理するのではなく、C言語にて移動平均を計算し、結果をExcelに取り込んでグラフとして表示することにチャレンジしてほしい。

提出レポートに、全データの計算結果は不要です。動作の確認の意味で、先頭10点ほどの計算結果をつけてください。

様々な移動平均

波形処理をハードウェアで行うかソフトウェアで行うか

組込み用の小型コンピュータが普及する以前は、このような波形に対する処理を行う場合には、電子回路的に波形のフィルタリングを行っていた。しかし電子回路的な方法では、回路の特性が変化してうまく処理ができなくなった場合に、回路の組み換えが発生するかもしれない。ただし回路の変更は基板の作り直しが必要であったりすることから、コストがかかる

一方、A/D変換機能を内蔵した組込み用小型コンピュータも極めて安価になってきた。

こういったコンピュータの普及で、最近ではアナログ値をコンピュータに取り込んでデジタルの数値的な処理で余計な情報を取り除く。この方法であれば、プログラムを変更するだけなので、安価に変更が可能となる。ただし、アナログ値をA/D変換するのには時間がかかることから、周波数の高い信号には不向きである。

単純移動平均

前回説明を行った単純移動平均は、時刻tの平均を、その前後のデータで平均を求めた。この方式は、実際には与えられた波形のデータを全部記録した跡に、単純移動平均をとる場合に有効である。

しかし、時々刻々変化する測定値の平均をその都度使うことを考えると、上記の方法は、未来の測定値を使っていることから、現実的ではない。

#define NS 3
int x[ SIZE ] ; // 入力値
int y[ SIZE ] ; // 出力値
for( int t = NS ; t < SIZE-NS ; t++ ) {
   int s = 0 ;
   for( int i = -NS ; i <= +NS ; i++ ) // 2*NS+1回の繰り返し
      s += x[t+i] ;
   y[t] = s / (2*NS + 1) ;
}

過去の値だけを使った移動平均

そこで、過去の値だけで移動平均をとることも考えられる。

この、単純移動平均と、過去の値だけを使う単純移動平均を、適当な測定値に対して適用した場合のグラフの変化を Excel によってシミュレーションした結果を以下に示す。

しかし、このグラフを見ると、波形後半の部分に注目するとよく分かるが、過去の値だけを使った移動平均では、測定値が立ち上がったのを追いかけて値が増えていく。これでは移動平均は時間的な遅れとなってしまう。

for( int t = NS ; t < SIZE ; t++ ) {
   int s = 0 ;
   for( int i = 0 ; i <= NS ; i++ ) // NS+1回の繰り返し
      s += x[t-i] ;
   y[t] = s / (NS+1) ;
}

加重移動平均

過去の値を使った移動平均では遅れが発生する。でも、平均を取る際に、「n回前の値」と「現在の値」を考えた時、「その瞬間の平均値」は「現在の値」の方が近い値のはず。であれば、平均を取る時に、「n回前の値は少なめ」「現在の値は多め」に比重をかけて加算する方法がある。

for( int t = 3 ; t < SIZE ; t++ ) {
   int s = x[t]   * 3
         + x[t-1] * 2
         + x[t-2] * 1 ; // 数個の移動平均だし、
   y[t] = s / (3+2+1) ; //  ループを使わずに書いてみる。
}

この様に、過去に遡るにつれ、平均をとる比重を直線的に小さくしながら移動平均をとる方法は、加重移動平均と呼ばれる。以下にその変化をExcelでシミュレーションしたものを示す。

指数移動平均

ここまで説明してきた、単純移動平均や、加重移動平均は、平均をとる範囲の「過去の値」を記憶しておく必要がある。広い時間にわたる移動平均をとる場合は、それに応じてメモリも必要となる。これは、組み込み型の小型コンピュータであれば、メモリが足りず平均処理ができない場合もでてくる。

そこで、荷重移動平均の重みを、は、100%,は50%,は25%… というように、過去に遡るにつれ、半分にして平均をとる。

しかし、以降の項で、 を使うと以下のように書き換えることができる。

for( int t = 1 ; t < SIZE ; t++ ) {
   y[t] = ( x[t] + y[t-1] ) / 2 ;
}

この方法であれば、直前の平均値を記録しておくだけで良い。このような移動平均を、指数移動平均と呼ぶ。

ここで示した指数移動平均は、過去を遡るにつれとなっているが、これをさらに一般化した指数移動平均は、以下の式で示される。前述の移動平均は、とみなすことができる。

#define ALPHA 0.5
for( int t = 1 ; t < SIZE ; t++ ) {
    y[t] = ALPHA * x[t] + (1.0 - ALPHA) * y[t-1] ;
}

以下のプログラムは、うまく動かない。理由を説明せよ。

#define RVA 4
for( int t = 1 ; t < SIZE ; t++ ) {
   // 以下はy[t]は全部ゼロになる。
   y[t] = 1/RVA * x[t] + (1.0 - 1/RVA) * y[t-1] ;

   // 以下は、整数型演算だけで、正しく動くだろう。
   // y[t] = ( x[t] + (RVA-1) * y[t-1] ) / RVA ;
}

K-SEC セキュリティサマースクールin 岐阜高専

夏休みに開催される、高専学生対象のセキュリティに関するサマースクールのお知らせです。

  1. 期日 平成30年8月23日(木)・24日(金)
  2. 会場 岐阜工業高等専門学校 専攻科棟2 階 第1講義室・第2講義室
  3. 内容

    学生の情報セキュリティに関する知識とスキルの向上と、情報セキュリティ教育に関わる教員の研鑽を目的として、2日間で情報セキュリティ高度人材育成に関するサマースクールを実施する。

    1日目【8月23日(木)】
    09:30 – 10:00 受付
    10:10 – 10:20 挨拶
    10:30 – 12:00 講座A/講座B
    12:00 – 13:00 昼休み
    13:00 – 14:30 講座C/講座D
    14:40 – 16:10 講座E/講座F
    2日目【8月24日(金)】
    08:30 – 08:45 受付
    08:50 – 10:20 講座G/講座H
    10:30 – 12:00 講座I/講座J
    12:10 – 12:40 クロージング
  4. 参加対象学生

    各国公私立高等専門学校の本科学生および専攻科生(定員:40 名)

  5. 見学対象教職員

    全国各校の情報セキュリティ教育担当者、または情報セキュリティ教育にご興味のある方

福井高専の参加希望者は、6/29までに斉藤に連絡をください。

高専プロコン連携シンポジウム2018の開催について

高専プロコンを前に、現場のニーズや動向を学習する機会を提供することを目的としたシンポジウムがテレビ会議システムにて開催されます。電子情報工学科の皆様および高専プロコンに興味のある方は、ぜひ参加をお願いします。


  1. 概 要
    全国高専を結んだビデオ会議システムにより,講師から企業,高専生,プロコンの関係についての情報を提供頂きます。プロコンに興味を持っている教職員及び学生はもちろん,一般の学生にも興味の持てる内容となっております。
  2. 日 程
    平成30年6月22日(金)16:20~17:20
    福井高専合併教室(石川高専から配信)
  3. 講演者・講演タイトル
    16時20分~16時50分
    講 師:チームラボ株式会社 取締役 CTO 田村 哲也 様
    講演テーマ:会社が求める人材と高専プロコン
    16時50分~17時20分
    講 師:合同会社DMM.com 動画配信開発部 矢野完人 様
    講演テーマ:高専生がITベンチャーで働くって?
  4. 参加方法
    質疑応答は,Twitterを利用予定です(質問はハッシュタグ #procon29 でお寄せください)。
  5. その他
    追加情報等につきましては,プロコン公式サイト(http//www.procon.gr.jp/)及びメールにてご連絡いたします。

(2018-06-22)
プロコンシンポジウムでは、電子情報4年から、10名の学生さんが参加・聴講してくれました。
動画配信では、多くの利用者が集中したためか、上流の動画配信サーバからの配信で聞きづらい状態でした。その中でも、Twitter でいろいろな感想が飛び交っていました。

Googleスピードテスト

ブラウザを用いた Google によるインターネット速度テストの結果。

自宅、500Mbps超えてたのに比べたら…(x_x;

高専生の毒舌な妹bot より引用

移動平均のプログラム

移動平均のプログラム(ダサっ)

#include <stdio.h>
#define WIDTH 5
double xt[1000] ; // 元波形データ
double yt[1000] ; // 移動平均処理後のデータ
int main() {
    int    i ; 
    double t , x ;
    // 全データを読み込む(入力はコンマ区切りの2データ)
    for( i = 0 ; scanf( "%lf,%lf" , &t , &x ) == 2 ; i++ )
        xt[ i ] = x ;
    // データ xt[*] の移動平均を yt[*] に求める。
    for( i = WIDTH ; i < 1000 - WIDTH ; i++ ) {
        int j ;
        double s = 0.0 ; // 合計
        // 前後の値の合計を求める
        for( j = -WIDTH ; j <= WIDTH ; j++ )
            s = s + xt[ i + j ] ;
        // 合計をデータ数で割る
        yt[ i ] = s / (WIDTH * 2 + 1) ;
    }
    // 処理結果を出力する。
    for( i = 0 ; i < 1000 ; i++ )
        printf( "%d,%lf,%lf\n" , i , xt[i] , yt[i] ) ;
    return 0 ;
}

でも、このプログラムは、以下の点で問題がある。

  1. 範囲のデータを加算しているけど、加算の繰り返しが多い。
  2. 配列にデータを最初に全て読み込んでいるけど、長時間のデータならば大量のメモリが必要。
  3. 測定しながら移動平均を計算する場合、データはどうする?

移動平均のプログラム(ちょっと改良)

全部のデータを覚えるのはメモリの無駄なので、移動平均する直近のデータだけを覚えるように改良する。
しかし、データを保存する度に、配列をずらす処理も無駄なので、データを保存する場所(以下の例ではbp)を保存したら次の場所を示すように記述してみる。

#include <stdio.h>

#define WIDTH 10
double buff[ WIDTH ] ; // 直近のWIDTH個だけ保存
int    bp = 0 ;        // 最新データの場所
double bs = 0.0 ;      // 直近のWIDTH個の合計

int main() {
    int i ;
    double t , x ;
    for( i = 0 ; scanf( "%lf,%lf" , &t , &x ) == 2 ; i++ ) {
        // WIDTH個前のデータを捨てるために合計から引く
        bs = bs - buff[ bp ] ;

        buff[ bp ] = x ;      // 最新データを保存
        bs = bs + x ;         // 最新のデータで合計
        // 直近のデータを覚える場所を移動
        bp++ ;
        if ( bp >= WIDTH )
            bp = 0 ;
        // 移動平均を出力
        printf( "%d %lf\n" , i , bs / WIDTH ) ;
    }
    return 0 ;
}

仮想関数

仮想関数

前回の派生したプログラムで継承の説明をしたが、以下のようなプログラムでは、Student 型が混在した family[] の配列でも、Person へのポインタに「格下げ」されて保存されているため、
family[i]->print() では、Student 型でも Person型で表示処理が行われる。

class Student : public Person {
   :
   void print() {
      Person::print() ;                    // 名前と年齢を表示
      printf( " %s %d¥n" , dep , grade ) ; // 所属と学年を表示
   }
} ;
void main() {
   Person saitoh( "t-saitoh" , 53 ) ;
   saitoh.print() ; // t-saitoh 53

   Student mitsu( "mitsuki" , 18 , "E" , 4 ) ;
   Student ayuka( "ayuka" , 16 , "EI" , 2 ) ;
   mitsu.print() ; // mitsuki 18 / E 4   名前,年齢,所属,学年を表示
   ayuka.print() ; // ayuka 16 / EI 2    名前,年齢,所属,学年を表示

   Person* family[] = {
      &saitoh , &mitsu , &ayuka ,  // 配列の中に、Personへのポインタと
   } ;                             // Studentへのポインタが混在している
                                   // 派生クラスのポインタは、
                                   // 基底クラスのポインタとしても扱える
   for( int i = 0 ; i < 3 ; i++ )
      family[ i ]->print() ;       // t-saitoh 53/mitsuki 18/ayuka 16
}                                  //  が表示される。

しかし、Student型とPerson型の機能を活かせないだろうか?

仮想関数

このような場合、オブジェクト指向では、仮想関数の機能が便利である。

class Person {
   :
   virtual void print() {
      printf( "%s %d\n" , name , age ) ;
   }
} ;
class Student : public Person {
   :
   virtual void print() {
      Person::print() ;                    // 名前と年齢を表示
      printf( " %s %d¥n" , dep , grade ) ; // 所属と学年を表示
   }
} ;
void main() {
   Person saitoh( "t-saitoh" , 53 ) ;
   saitoh.print() ; // t-saitoh 53

   Student mitsu( "mitsuki" , 18 , "E" , 4 ) ;
   Student ayuka( "ayuka" , 16 , "EI" , 2 ) ;
   mitsu.print() ; // mitsuki 18 / E 4   名前,年齢,所属,学年を表示
   ayuka.print() ; // ayuka 16 / EI 2    名前,年齢,所属,学年を表示

   Person* family[] = {
      &saitoh , &mitsu , &ayuka ,
   } ;
   for( int i = 0 ; i < 3 ; i++ )
      family[ i ]->print() ;       // t-saitoh 53/mitsuki 18,E 4/ayuka 16,EI 2
}

仮想関数が宣言されると、基底クラスの中に「型情報(PersonなのかStudentなのか)」が自動的に埋め込まれる。仮想関数を呼び出すと、型情報を使って、Person::print()を呼び出すか、Student::print()を呼び出すか、を選んでくれる。

仮想関数が生まれた背景

仮想関数は、GUI のプログラム記述に向いていた。例えば、GUIシステムでは、画面にデータを表示するための基本型として、座標と幅,高さの情報を持つ Window 型がある。

また、画面のアイコンは、Window型に、表示する絵の画像を追加した派生クラス WindowIcon 型、画面のテキストは、Windows型に、表示するテキストやフォント情報を持った、WindowText といった型のようなものとする。そうなると仮想関数の概念が無いと、display() を呼び出すためには、派生型の種類が大量ならばデータの型に応じた if 文を大量に書き並べないといけないし、データ型を区別するための情報(下記の例ならばtype)に、型毎に異なるマジックナンバーを埋め込む処理を自分で書かないといけない。

class Window {  // 概念を説明するための例にすぎない
private:
    int x , y , w , h ;
    int type ; // 型を区別するための情報
public:
    void display() {
        // 表示する処理
    }
} ;
class WindowIcon : public Window {
private:
    Image  img ;
public:
    void display() {
        // 画像を表示する処理
    }
} ;
class WindowText : public Window {
private:
    Font  font ;
    char* text ;
public:
    void display() {
        // テキストを表示する処理
    }
} ;
void main() {
    WindowIcon wi( アイコンのコンストラクタ ) ;
    WindowText wt( テキストのコンストラクタ ) ;
    Window* wins[] = {
        &wi , &wt , ...
    } ;
    for( int i = 0 ; i < 配列すべて ; i++ ) {
        if ( wins[i]->type が アイコンならば )
            wins[i]->display() ; // アイコンを表示する
        else if ( wins[ i ]->type が テキストならば )
            wins[i]->display() ; // テキストを表示する。
        else if ....
        :
    }
}

関数ポインタ

では、仮想関数はどのようなテクニックを用いて実装されているのだろうか?

これには、関数ポインタが用いられる。

int add( int x , int y ) {
    return x + y ;
}
int mul( int x , int y ) {
    return x * y ;
}
int main() {
    int (*func)( int , int ) ;
    func = add ; // add() ではない
    printf( "%d¥n" , (*func)( 3 , 4 ) ) ; // 7
    func = mul ;
    printf( "%d¥n" , (*func)( 3 , 4 ) ) ; // 12
    return 0 ;
}

仮想関数を用いると、基底クラスにはクラス毎の仮想関数への「関数ポインタ」などの型情報を保存する場所が自動的に作られ、基底クラス・派生クラスが使われると、そのオブジェクト毎に型情報を初期化する処理が行われる。仮想関数が呼び出されると、関数ポインタにより、各型毎のメソッドが呼び出されるため、大量の if 文は不要となる。

純粋仮想基底クラス

// 純粋仮想基底クラス
class Object {
public:
   virtual void print() = 0 ; // 中身の無い純粋基底クラスを記述しない時の書き方。
} ;
// 整数データの派生クラス
class IntObject : public Object {
private:
   int data ;
public:
   IntObject( int x ) {
      data = x ;
   }
   virtual void print() {
      printf( "%d\n" , data ) ;
   }
} ;
// 文字列の派生クラス
class StringObject : public Object {
private:
   char data[ 100 ] ;
public:
   StringObject( const char* s ) {
      strcpy( data , s ) ;
   }
   virtual void print() {
      printf( "%s\n" , data ) ;
   }
} ;
// 実数の派生クラス
class DoubleObject : public Object {
private:
   double data ;
public:
   DoubleObject( double x ) {
      data = x ;
   }
   virtual void print() {
      printf( "%lf\n" , data ) ;
   }
} ;
// 動作確認
int main() {
   Object* data[3] = {
      new IntObject( 123 ) ,
      new StringObject( "abc" ) ,
      new DoubleObject( 1.23 ) ,
   } ;
   for( int i = 0 ; i < 3 ; i++ ) {
      data[i]->print() ;
   }
   return 0 ;
} ;

この書き方では、data[]には、整数、文字列、実数という異なるデータが入っているが、 Objectという純粋仮想基底クラスを通して、共通な型のように扱えるようになる。 そして、data[i]->print() では、各型の仮想関数が呼び出されるため、 「123 abc 1.23」 が表示される。

ここで、Object の様な一見すると中身が何もないクラスを宣言し、 このクラスから様々な派生クラスを用いるプログラムテクニックは、 広く利用され、Objectのような基底クラスは、純粋仮想基底クラスなどと呼ばれる。

移動平均の処理

前回の授業で説明したようなA/D変換した数値データを読み取った場合、どのようなことが発生するか考える。

例えば、以下に示すような測定値があったとする。

このデータの一部をグラフ化してみると、次のような波形であった。

この波形をみると、大きく見ればsinカーブだが、細かい点を見るとデータにブレがある。

誤差の原因

このような測定結果が得られた場合、本来コンピュータで処理したいデータは何であろうか?

原因は様々なものが考えられるが、
(1) 回路のノイズ対策が不十分で、外部の電気的な影響が混入してしまうこともある。
(2) 一方で、D/A 変換を行う場合には、量子化誤差もある。

青線:元波形、赤線:4段階で量子化

この例は、-1〜1の範囲を、4段階に量子化することで、本来の波形とは異なった値になっている。

例えば、最初の波形が、加速度センサーの値であったとして、船の上で揺れているために、大きな周期で加速度が変化しているかもしれない。一方で、船自体がエンジンによる揺れで加速度が変化しているかもしれない。

船の中で波の揺れと、エンジンの揺れが観測されている加速度センサーの情報で、船の揺れの大きさ・揺れの周期を知りたい場合、どうすればいいだろうか?単純なsinカーブの波形であれば、波形の最大値・最小値・0との交点の場所を探せば、振幅や周期が求めることができるが、このようなノイズが入った波形では、正しく振幅・周期が求まらない。

移動平均

このデータを見ると、10個のデータまでの間で、波形が上下に変動している。船の揺れとエンジンの揺れが原因であれば、10個ぐらいのデータのゆらぎが、エンジンによる揺れと考えられる。では、この10個ぐらいの範囲で値が上下の影響を減らしたければ、どうすればいいか?一番簡単な方法は、前後10個のデータで平均を取ればいいだろう。まずは、Excel で前後データの平均をとってみよう。

Excelで前後11点の平均を求める式をセルに入れる

青線:元波形データ(B列)、赤線:前後11点の平均(C列)

このように、データの前後の決められた範囲の平均を平均する処理は、移動平均(単純移動平均)と呼ぶ。

時間tにおけるデータをとした場合、前後5点の移動平均は、以下のような式で表せるだろう。

自宅学習の課題(レポート提出は不要)

表計算ソフトで、移動平均を計算させてみよう。  

  • 元波形
  • 前後5点で移動平均
  • 前後11点で移動平均
  • 前後51点で移動平均

をとるような表計算の式を書き込んで、その結果の波形がどんなグラフになるのか確認しておくこと。

移動平均の応用

例えば、以下のような心電図のデータがあったとする。心電図では、波の高さで心臓が正常か判断するが、この青のグラフでは、大きな周期の変動が含まれるため、波の高さを正確に測れない。この波形から診断するときに、移動平均が使えないだろうか?

このような場合には、測定値の波形の移動平均をとり心拍データの変動を取り除き、測定値から移動平均の値を引くことで、心拍データだけを取り出すことが可能となる。

mimetex.cgi は便利

学科のWebサーバには、mimetex.cgi というパッケージを入れてある。これを使えば、img タグの src 部分に、mimetex.cgi を記述し、パラメータのURL部分に、の数式を書くだけ。

(( mimetex.cgi のインストール ))
$ sudo aptitude install mimetex

なので、普通にテキストで書いたら、O(N log N) みたいな式でも、 なんて記述も、 で数式書くのに慣れてれば、楽々。

(( HTML の中で数式画像の img タグを書く ))
<p align="center">
  <img src="/cgi-bin/mimetex.cgi
     ?\int{\frac{1}{\sqrt{a^2-x^2}}dx}
      = \sin^{-1}\frac{x}{a}+C" /><p>

システム

最新の投稿(電子情報)

アーカイブ

カテゴリー