ソフトウェア2 [第5回]dsk_saito/lecture/...(2020/12/17) 齋藤 大輔...

44
ソフトウェア2 [第5回] (2020/12/17) 齋藤 大輔

Transcript of ソフトウェア2 [第5回]dsk_saito/lecture/...(2020/12/17) 齋藤 大輔...

  • ソフトウェア2 [第5回]

    (2020/12/17)

    齋藤 大輔

  • 本日のメニュー!C言語入門

    !関数へのポインタ !分割コンパイル !Makefile

    !連続関数の最適化 !勾配法、最急降下法 !線形回帰

  • 本日のメニュー!C言語入門

    !関数へのポインタ !分割コンパイル !Makefile

    !連続関数の最適化 !勾配法、最急降下法 !線形回帰

  • 関数へのポインタ!関数のアドレス

    !関数の実行コードが存在するアドレス

    #include

    int add_one(int x) { return x + 1; }

    int main() {   printf("%p\n", add_one); } add_one 関数のアドレスが表示

    される

  • 関数へのポインタ!関数へのポインタ

    !関数を「もの」のように扱うことができる !ポインタ変数の定義の例

    fp is a pointer to a function(int) returning int fp は、返り値が int で、引数が int ひとつの関数へのポインタ

    int (*fp)(int);

    double (*fp)(double, double);

    fp is a pointer to function(double, double) returning double fp は、返り値が double で、引数が double ふたつの関数へのポインタ

  • 関数へのポインタ!関数ポインタを用いて関数を実行

    #include

    int add_one(int x) { return x + 1; }

    int main() { int (*fp)(int); fp = add_one;

    int x = 1; int y = (*fp)(x);

    printf("%d\n", y); }

    fp に、add_one 関数のアドレスを代入

    fp で指されている関数(add_one 関数) を呼び出す ※ int y = fp(x); でも良い

  • 使ってみる!使い方の例

    !関数に関数を渡して動作を変える

    #include

    int add(int x, int y) { return x + y; }

    int mul(int x, int y) { return x * y; }

    void apply(int v[], int n, int y, int (*fp)(int,int)) { for (int i = 0; i < n; i++) { v[i] = (*fp)(v[i],y); } }

    int main() { int v[10]; const int n = sizeof(v)/sizeof(int); for (int i = 0; i < n; i++) { v[i] = i; }

    apply(v, n, 2, add); apply(v, n, 3, mul);

    for (int i = 0; i < n; i++) { printf("v[%d] = %d\n”, i, v[i]); } }

  • クイックソート!qsort

    !標準ライブラリ関数のひとつ !#include

    !配列の要素を与えられた基準で並べ替える !クイックソートアルゴリズム → 計算量は O(n log n)

    void qsort(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *));

    配列の先頭アドレス 要素数 各要素の大きさ(バイト数)

    要素を比較する関数へのポインタ

  • 実習11. qsort.c を修正し、整数値を降順にソートする 2. qsort_struct.c の comp_name() 関数を完成させ、

    Student を名前の辞書順でソートする

  • 本日のメニュー!C言語入門

    !関数へのポインタ !分割コンパイル !Makefile

    !連続関数の最適化 !勾配法、最急降下法 !線形回帰

  • 分割コンパイル!中規模以上のプログラムにて

    !複数のソースファイルでプログラムを構成 !プログラムの保守性、再利用性が向上する

    main.c solve.c plot.c

    main.o solve.o plot.o

    コンパイル

    リンク

    a.out

    ソースファイル

    オブジェクトファイル

    実行ファイル

  • 今までのコンパイルの実際!qsort.c をコンパイル $ gcc -o qsort qsort.c

    !上記は以下の手順と同じ $ gcc -c qsort.c $ gcc -o qsort qsort.o

    !オブジェクトファイル !機械語バイナリとその配置情報が記録されている

    !リンク !オブジェクトファイルを結合して実行バイナリを生成

    オブジェクトファイルの生成リンク

  • 分割コンパイル!コンパイル(オブジェクトファイルの生成) $ gcc –c main.c $ gcc –c solve.c $ gcc –c plot.c

    !リンク $ gcc -o solver main.o solve.o plot.o

    → main.o, solve.o, plot.o というファイルが作成される

    → 実行ファイル solver が作成される

  • ヘッダファイル!構造体や関数のプロトタイプ宣言などを記述

    !関数の仕様と実際の実装を分割できるsolve.h 二重に include されるのを防止

      [プロジェクト名]_[ファイル名]_H などとすることが多い

    関数のプロトタイプ宣言   コンパイラに関数の情報(引数   返り値など)を与える

    main.c

    :

    solve.h をインクルード solve() 関数が main.c の中で使える

    #ifndef TSP_SOLVE_H #define TSP_SOLVE_H

    int solve(const int n, int route[]);

    #endif

    #include #include ”solve.h”

  • 実習2!qsort.cを、ヘッダファイルqsort.hとソースファイル

    main.cおよびqsort_lib.c に適切に分割し、分割コンパイルを実行する !main.c には main関数と#include文のみ !qsort.h には 関数のプロトタイプ宣言 !qsort_lib.c に実際の実装をかく !qsort.h で宣言される関数が使われる場合はincludeする

  • ライブラリ!ライブラリ

    !実は複数のオブジェクトファイルを一つに固めたもの !ソースコードの実装を意識せず関数を使用可能

    !関数の仕様はヘッダファイルに記述しておく !種類

    !静的ライブラリ: プログラム構築時に組み込まれる (lib*.a) "ライブラリ部分の再コンパイルは不要 "実行バイナリは大きくなりがち

    !共有ライブラリ: プログラム実行時にメモリに展開 (lib*.{so,dylib}) "ライブラリが更新されても本体プログラムの再コンパイルが不要 "実行バイナリは比較的小さい "ライブラリが置かれた情報を実行時に適切に知る必要がある

  • 静的ライブラリの作成!arコマンド

    !オブジェクトファイルを固めるのに使用する $ ar rsv libqsort.a qsort_lib.o ! r: 既存ファイルの更新 or 新規作成 ! s: 複数のオブジェクトファイルをランダムアクセス可能にする ! v: 詳細を表示

    !コンパイル時の使用法 !オブジェクトファイルと同様にする場合 $ gcc -o qsort main.c libqsort.a

    !lib????.a のリンクを -l???? 形式で指定する場合 $ gcc -o qsort main.c -L. -lqsort

  • ディレクトリ構造を整頓!ヘッダファイル、ソースコード、ライブラリ、実行コードを階層的に配置しておくとよい !src: ソースコードを格納する ! include: ヘッダファイルを格納(-I./include で参照) ! lib: ライブラリファイルを格納(-L./lib で参照) !bin: 実行バイナリを保存するディレクトリ

    !分割コンパイルを経て以下のようにビルドする $ gcc -I./include -o bin/qsort src/main.c -L./lib -lqsort

  • 実習3!qsort.h, main.c, qsort_lib.c をqsort.tar.gz を展開したディレクトリに適切に配置し、ライブラリlibqsort.a および 実行ファイルqsort を生成する

  • 本日のメニュー!C言語入門

    !関数へのポインタ !分割コンパイル !Makefile

    !連続関数の最適化 !勾配法、最急降下法 !線形回帰

  • Makefile!プログラムをコンパイルするのに必要な情報を記述したファイル !コンパイルに必要なファイル群 !ファイル同士の依存関係 !コンパイルオプション

    !make コマンド !Makefile を参照してプログラムをコンパイル

  • Makefile!ファイル生成のルールを並べたもの

    !ルール

    ! target: 生成したいファイル !prerequisites: target を生成するのに必要なファイル ! recipe: 実行するコマンド

    !個々の recipe の前には TAB が必要なことに注意

    target ... : prerequisites recipe ... ...

  • Makefile!ルールの例

    !ルールの実行 !Makefile が存在するディレクトリで

    $ make target とすると、ルールが実行され target が作成される

    !prerequisites のタイムスタンプがチェックされ、更新されている場合にのみ recipe が実行される → 必要なコマンドだけが実行される

    main.o: main.c gcc –c main.c

    main.o を作成するためには、main.c が必要で、作成のためのコマンドは  gcc –c main.c という意味

  • Makefile!Makefile の例

    この Makefile が存在するディレクトリで   $ make とすると、main.o, solve.o, plot.o, a.out が順々に作成される

    a.out: main.o solve.o plot.o gcc main.o solve.o plot.o

    main.o: main.c gcc –c main.c

    solve.o: solve.c gcc –c solve.c

    plot.o: plot.c gcc –c plot.c

    ※ target を省略した場合、最初のターゲットが指定されたとみなされる

  • Makefile!Phony target (偽のターゲット)

    !実は target は必ずしもファイルでなくともよい

    !clean というファイルが存在すると意図した挙動をしない

    clean: rm *.o *~ a.out

    make clean で a.out やオブジェクトファイル等をまとめて消せる

    オプションを変更して全部コンパイルしなおしたいときなどに $ make clean $ make とか

    .PHONY: clean clean: rm *.o *~ a.out

  • Makefile!変数: Makefile の先頭で変数を定義できる

    !マクロ: recipes で使える変数 !$@ : ターゲット !$< : prerequisitesの最初 !$^: 全てのprerequisites

    CC = gcc

    a.out: main.o solve.o plot.o $(CC) main.o solve.o plot.o

    main.o: main.c $(CC) –c main.c

    solve.o: solve.c $(CC) –c solve.c

    plot.o: plot.c $(CC) –c plot.c

  • Makefile!暗黙のルール(implicit rules)

    ! foo.o というファイルは foo.c というファイルから $(CC) $(CPPFLAGS) $(CFLAGS) -c  というコマンドで作成される

    !他にもいろいろ・・・

    CC = gcc

    a.out: main.o solve.o plot.o $(CC) main.o solve.o plot.o

  • Makefile!結局

    CC = gcc OBJS = main.o solve.o plot.o

    a.out: $(OBJS) $(CC) $(OBJS)

    a.out: main.o solve.o plot.o gcc main.o solve.o plot.o

    main.o: main.c gcc –c main.c

    solve.o: solve.c gcc –c solve.c

    plot.o: plot.c gcc –c plot.c

  • 実習4! tsp_solver および qsort のディレクトリに適切な

    Makefile を書いてみる !ライブラリの生成はar コマンドを使う

  • 本日のメニュー!C言語入門

    !関数へのポインタ !分割コンパイル !Makefile

    !連続関数の最適化 !勾配法、最急降下法 !線形回帰

  • 連続関数の最適化!多変数関数の最適化

    !入力はベクトル、出力はスカラー !関数の最小値およびそれを実現する入力を計算

    !勾配(gradient)がわかっている場合 !勾配法 !最急降下法

    f(x̂) x̂ = argminxf(x)

  • 1変数関数! f(x) が最小になる x を求めたい

    が代数的に解けない場合の素朴な数値解法• 適当に x を決める • f’(x) < 0 なら右に移動 • f’(x) > 0 なら左に移動

    繰り返す

    f(x) = (x� 3)2 + 1f 0(x) = 2(x� 3)

    f 0(x) = 0

  • 2変数関数の最適化! f(x, y) が最小になる (x, y) を求めたい

    gradient (勾配): 関数の値を最も大きく増加させる方向→ 適当な (x, y) から出発して gradient と反対方向に進んでいけばよい

    f(x, y) = (x� 3)2 + (y � 2)2

    rf =✓

    @f

    @x,@f

    @y

    =h2(x� 3), 2(y � 2)

    i

  • 多変数関数の最適化

    1. 適当に初期位置  を決める

    2.    を計算 ノルムが十分小さければ終了

    3.   として更新       

    4. 2に戻り繰り返す

    最急降下法(gradient descent)

    x

    rf(x)

    x x� ↵rf(x)

  • サンプルプログラム!展開 $ tar xvzf optimize.tar.gz $ cd optimize

    !コンパイル&実行 $ make $ ./optimizer

  • !Makefile

    CC = gcc CFLAGS = -Wall –g LDLIBS = -lm OBJS = main.o func.o optimize.o TARGET = optimizer

    $(TARGET): $(OBJS) $(CC) -o $@ $^ $(LDLIBS) .PHONY: tmpclean clean tmpclean: rm –f *~

    clean: tmpclean rm –f $(OBJS) $(TARGET)

    サンプルプログラム

    optimizerは main.o func.o optimize.o に依存

    optimizerの作り方(変数展開後)は、 gcc -o optimizer main.o func.o optimize.o -lm

    コンパイルするときのオプションは –Wall -g

    オブジェクトファイルや、余計なファイルを消したいときは make clean

  • 実習51. optimize.c を修正し、最適化の終了条件を厳しくする

    !make したときに optimize.c しかコンパイルされないことを確認

    2. 最適化の各ステップで関数の値 f(x) も表示するように改良

    !関数 optimize() に関数 f_value() のポインタを渡すようにする

  • 応用!最小二乗法による線形回帰

    データ

    二乗誤差の和

    これが最小になる a と b を求める

    {(x1, y1), (x2, y2), . . . , (xn, yn)}

    E =NX

    i=1

    (yi � (axi + b))2y = ax + b

    x

    y

  • 勾配

    勾配

    ※この問題に関しては、どちらも 0 となるよう連立方程式を   解いて a と b を直接求めることもできる

    二乗誤差の和

    E =NX

    i=1

    (yi � (axi + b))2

    @E

    @a= �2

    nX

    i=1

    (yi � axi � b)xi@E

    @b= �2

    nX

    i=1

    (yi � axi � b)

  • 大まかな手順1. a, b を適当な値で初期化 2. 与えられた(x, y) と a, bを用いて パラメータa, b に対する勾配を求める

    3. a, b の値を上記勾配に基づいて最急降下法で更新 4. 平均二乗誤差が十分小さければ終了 5. そうでない場合は2 - 4 を繰り返す

    今回は1次関数の線形回帰のため、実際には決定的に定まる 上記の手順はニューラルネットの最適化でも基本的な考えとなる

  • 標高と気温の関係を推定!長野県の都市の標高と7月の平均気温

    場所 標高 平均気温野沢温泉 576 22.3

    飯山 313 23.3

    長野 418.2 23.8

    大町 784 21.1

    菅平 1253 18.5

    軽井沢 999.1 19.5

    松本 610 23.6

    奈川 1068 19.7

    諏訪 760.1 22.7

    野辺山 1350 18.4

    伊那 633 22.7

    南木曽 560 22.3

    飯田 516.4 23.9

    南信濃 407 23.5

    出典:気象庁ホームページ (http://www.jma.go.jp/jma/index.html)

  • レポート課題(締切12/26)1.最小二乗法により標高と気温の関係を推定せよ

    ! 得られたパラメータから推測される富士山頂の7月の平均気温を示すこと ! “func1.c”の構造体定義 を参考にdata.csv のデータを読みとって利用する

    ! あわせて与えられたデータを標高に基づいてソートし、表示せよ ! プログラムおよびコンパイルするディレクトリを添付すること (添付方法は後述) !ディレクトリ名はmtfuji / main関数を含むファイル名は “mtfuji.c”

    2.[発展課題]より高度な回帰プログラムを実装し評価せよ ! モデルの改良の例

    !重回帰分析、非線形回帰、etc ! 最適化の手法を改良しても良い ! 分析対象(データ)を変更しても良い ! プログラム、データおよびコンパイルするディレクトリを添付すること

    !ディレクトリ名はadv / main関数を含むファイル名は“adv_regression.c”

  • 課題提出の注意! 基本課題のみの場合はSOFT-12-17-NNNNNNNN 以下にmtfuji というディレクトリ、

    発展課題も提出の場合は mtfuji および adv という2つのディレクトリを作成する

    ! 今回の課題ではコンパイル用のMakefileを作成し同梱せよ。

    それぞれのディレクトリに作成したMakefileを設置する

    ! make clean も実装すること

    ! 提出前にmake clean しておく(= バイナリは同梱しない)

    ! 追加のデータファイル、README等ある場合はそれも同梱せよ

  • 課題の提出方法(ITC-LMS)!形式: ファイルアップロード

    !全てのプログラム/ファイルをまとめ、zipやtar.gzで圧縮 !git archive コマンドやzipコマンド等を用いる !SOFT-12-17-NNNNNNNN.zip または

    SOFT-12-17-NNNNNNNN.tar.gz ! NNNNNNNN 部分は学籍番号(ハイフン除く) ! JについてはJ??????? のようにしてください。

    !課題について ! [基本課題] 毎回提出 ! [発展課題] 成績計算に全6回中上位3回分を採用する

    44