📈

GNN使ってみる話Part2

2024/11/24に公開

前回の続きだよ

(1)グラフを作成してみる

1.Homebrewを使ってインストール

ターミナルで以下を実行する

brew install gnuplot

→ちなみにGNUplotっていうのは、C++コードからグラフを描画できる軽量なツール

2.インストールの確認

gnuplot --version

→バージョンが表示されればOK

3.前回のC++コードを修正

#include <iostream>
#include <fstream>
#include <vector>
#include <cmath>

double sigmoid(double x) {
    return 1.0 / (1.0 + exp(-x));
}

int main() {
    // トレーニングデータ
    std::vector<std::pair<double, double>> inputs = {
        {0, 0}, {0, 1}, {1, 0}, {1, 1}
    };
    std::vector<double> targets = {0, 1, 1, 1};

    // 学習後の重みとバイアス
    double w1 = 3.30955, w2 = 3.31161, b = -1.34464;

    // 結果を保存するためのファイル
    std::ofstream output("output.dat");
    for (size_t i = 0; i < inputs.size(); ++i) {
        double x1 = inputs[i].first;
        double x2 = inputs[i].second;
        double y = sigmoid(w1 * x1 + w2 * x2 + b);
        output << x1 << " " << x2 << " " << y << " " << targets[i] << std::endl;
    }
    output.close();

    // GNUplotスクリプトを生成
    std::ofstream gnuplot_script("plot.gp");
    gnuplot_script << "set terminal pngcairo size 800,600\n";
    gnuplot_script << "set output 'plot.png'\n";
    gnuplot_script << "set xlabel 'Input X1'\n";
    gnuplot_script << "set ylabel 'Input X2'\n";
    gnuplot_script << "set zlabel 'Output Y'\n";
    gnuplot_script << "splot 'output.dat' using 1:2:3 with points pointtype 7 title 'Model Output', '' using 1:2:4 with points pointtype 6 title 'Target'\n";
    gnuplot_script.close();

    // GNUplot を呼び出してグラフ生成
    system("gnuplot plot.gp");

    std::cout << "Plot saved as 'plot.png'." << std::endl;
    return 0;
}

前回同様保存する

コードを修正する方法は以下に

1.修正したいファイルを開く

nano ファイル名(今回はperceptron.cpp)

2.修正して保存

4.実行する

./perceptron

→ここでまたもやエラーが発生
グラフを描く機能を追加していなかったみたい
再度修正

#include <iostream>
#include <cmath>
#include <vector>
#include <fstream> // ファイル操作のためのヘッダー

// シグモイド関数
double sigmoid(double x) {
    return 1.0 / (1.0 + exp(-x));
}

// シグモイドの微分
double sigmoid_derivative(double x) {
    double sig = sigmoid(x);
    return sig * (1 - sig);
}

// トレーニングデータ
struct Data {
    double x1;
    double x2;
    double y; // ターゲット値
};

// 学習パラメータを最適化する関数
void train(std::vector<Data> &dataset, double &w1, double &w2, double &b, double learning_rate, int epochs) {
    for (int epoch = 0; epoch < epochs; ++epoch) {
        double total_loss = 0.0;

        for (const auto &data : dataset) {
            // フォワード計算
            double z = w1 * data.x1 + w2 * data.x2 + b;
            double y_pred = sigmoid(z);

            // 誤差
            double error = y_pred - data.y;

            // コスト関数(二乗誤差)
            total_loss += 0.5 * error * error;

            // 勾配の計算
            double grad_y_pred = error;
            double grad_z = grad_y_pred * sigmoid_derivative(z);
            double grad_w1 = grad_z * data.x1;
            double grad_w2 = grad_z * data.x2;
            double grad_b = grad_z;

            // パラメータの更新
            w1 -= learning_rate * grad_w1;
            w2 -= learning_rate * grad_w2;
            b -= learning_rate * grad_b;
        }

        // エポックごとの損失を表示
        std::cout << "Epoch " << epoch + 1 << ": Loss = " << total_loss << std::endl;
    }
}

int main() {
    // トレーニングデータセット
    std::vector<Data> dataset = {
        {0, 0, 0},
        {0, 1, 1},
        {1, 0, 1},
        {1, 1, 1},
    };

    // 初期パラメータ
    double w1 = 0.0, w2 = 0.0, b = 0.0;
    double learning_rate = 0.1;
    int epochs = 1000;

    // 学習
    train(dataset, w1, w2, b, learning_rate, epochs);

    // 結果の表示
    std::cout << "Trained Parameters:" << std::endl;
    std::cout << "w1 = " << w1 << ", w2 = " << w2 << ", b = " << b << std::endl;

    // 学習データと予測結果を保存
    std::ofstream output("output.dat");
    for (const auto &data : dataset) {
        double y_pred = sigmoid(w1 * data.x1 + w2 * data.x2 + b);
        output << data.x1 << " " << data.x2 << " " << y_pred << " " << data.y << std::endl;
    }
    output.close();

    // GNUplotスクリプトを生成
    std::ofstream gnuplot_script("plot.gp");
    gnuplot_script << "set terminal pngcairo size 800,600\n";
    gnuplot_script << "set output 'plot.png'\n";
    gnuplot_script << "set xlabel 'Input X1'\n";
    gnuplot_script << "set ylabel 'Input X2'\n";
    gnuplot_script << "set zlabel 'Output Y'\n";
    gnuplot_script << "splot 'output.dat' using 1:2:3 with points pointtype 7 title 'Model Output', '' using 1:2:4 with points pointtype 6 title 'Target'\n";
    gnuplot_script.close();

    // GNUplotを実行してプロットを生成
    system("gnuplot plot.gp");

    std::cout << "Plot saved as 'plot.png'." << std::endl;

    return 0;
}

5.コンパイル

g++ -std=c++11 perceptron.cpp -o perceptron

6.実行する

./perceptron

ただこれだと、最終的な結果のみがグラフ化されているから過程もグラフ化したい

(2)数値を求める過程をすべてグラフ化

7.コードの修正

#include <iostream>
#include <fstream> // ファイル操作用
#include <vector>
#include <cmath>

// (中略)

// 学習パラメータを最適化する関数
void train(std::vector<Data> &dataset, double &w1, double &w2, double &b, double learning_rate, int epochs) {
    std::ofstream loss_file("loss_history.dat"); // 損失値を保存
    std::ofstream params_file("params_history.dat"); // パラメータを保存

    for (int epoch = 0; epoch < epochs; ++epoch) {
        double total_loss = 0.0;

        for (const auto &data : dataset) {
            // フォワード計算
            double z = w1 * data.x1 + w2 * data.x2 + b;
            double y_pred = sigmoid(z);

            // 誤差
            double error = y_pred - data.y;

            // コスト関数(二乗誤差)
            total_loss += 0.5 * error * error;

            // 勾配の計算
            double grad_y_pred = error;
            double grad_z = grad_y_pred * sigmoid_derivative(z);
            double grad_w1 = grad_z * data.x1;
            double grad_w2 = grad_z * data.x2;
            double grad_b = grad_z;

            // パラメータの更新
            w1 -= learning_rate * grad_w1;
            w2 -= learning_rate * grad_w2;
            b -= learning_rate * grad_b;
        }

        // エポックごとの損失値とパラメータを保存
        loss_file << epoch + 1 << " " << total_loss << std::endl;
        params_file << epoch + 1 << " " << w1 << " " << w2 << " " << b << std::endl;

        // エポックごとの損失を表示
        std::cout << "Epoch " << epoch + 1 << ": Loss = " << total_loss << std::endl;
    }

    loss_file.close();
    params_file.close();
}

8.GNUplotスクリプトの変更

1.スクリプトを作成

nano loss_plot.gp

2.スクリプト内容を入力

set terminal pngcairo size 800,600
set output 'loss_plot.png'
set title 'Loss Over Epochs'
set xlabel 'Epoch'
set ylabel 'Loss'
plot 'loss_history.dat' using 1:2 with lines title 'Loss'

そしたらねこんなエラーが出た

"loss_plot.gp" line 6: warning: Cannot find or open file "loss_history.dat"
"loss_plot.gp" line 6: No data in plot

→loss_history.dat ファイルが存在しないか、スクリプトの中で指定されたファイルパスが間違っているみたい
定義したつもりだったけど、できてないみたいだから再度やり直し

3.C++のコードの修正

#include <iostream>
#include <cmath>
#include <vector>
#include <fstream> // ファイル操作のためのヘッダー

// シグモイド関数
double sigmoid(double x) {
    return 1.0 / (1.0 + exp(-x));
}

// シグモイドの微分
double sigmoid_derivative(double x) {
    double sig = sigmoid(x);
    return sig * (1 - sig);
}

// トレーニングデータ
struct Data {
    double x1;
    double x2;
    double y; // ターゲット値
};

// 学習パラメータを最適化する関数
void train(std::vector<Data> &dataset, double &w1, double &w2, double &b, double learning_rate, int epochs) {
    std::ofstream loss_file("loss_history.dat"); // 損失値を保存
    std::ofstream params_file("params_history.dat"); // パラメータを保存

    if (!loss_file.is_open() || !params_file.is_open()) {
        std::cerr << "Error: Could not open file for writing." << std::endl;
        return;
    }

    for (int epoch = 0; epoch < epochs; ++epoch) {
        double total_loss = 0.0;

        for (const auto &data : dataset) {
            // フォワード計算
            double z = w1 * data.x1 + w2 * data.x2 + b;
            double y_pred = sigmoid(z);

            // 誤差
            double error = y_pred - data.y;

            // コスト関数(二乗誤差)
            total_loss += 0.5 * error * error;

            // 勾配の計算
            double grad_y_pred = error;
            double grad_z = grad_y_pred * sigmoid_derivative(z);
            double grad_w1 = grad_z * data.x1;
            double grad_w2 = grad_z * data.x2;
            double grad_b = grad_z;

            // パラメータの更新
            w1 -= learning_rate * grad_w1;
            w2 -= learning_rate * grad_w2;
            b -= learning_rate * grad_b;
        }

        // エポックごとの損失を表示
        std::cout << "Epoch " << epoch + 1 << ": Loss = " << total_loss << std::endl;

        // 損失値をファイルに保存
        loss_file << epoch + 1 << " " << total_loss << std::endl;

        // パラメータをファイルに保存
        params_file << epoch + 1 << " " << w1 << " " << w2 << " " << b << std::endl;
    }

    loss_file.close();
    params_file.close();
}

int main() {
    // トレーニングデータセット
    std::vector<Data> dataset = {
        {0, 0, 0},
        {0, 1, 1},
        {1, 0, 1},
        {1, 1, 1},
    };

    // 初期パラメータ
    double w1 = 0.0, w2 = 0.0, b = 0.0;
    double learning_rate = 0.1;
    int epochs = 1000;

    // 学習
    train(dataset, w1, w2, b, learning_rate, epochs);

    // 結果の表示
    std::cout << "Trained Parameters:" << std::endl;
    std::cout << "w1 = " << w1 << ", w2 = " << w2 << ", b = " << b << std::endl;

    return 0;
}

4.スクリプトを作成

nano params_plot.gp

2.スクリプト内容を入力

set terminal pngcairo size 800,600
set output 'params_plot.png'    # 出力先ファイル名
set title 'Parameter Changes Over Epochs'
set xlabel 'Epoch'
set ylabel 'Parameter Values'
plot 'params_history.dat' using 1:2 with lines title 'w1', \
     'params_history.dat' using 1:3 with lines title 'w2', \
     'params_history.dat' using 1:4 with lines title 'b'

4.実行

./perceptron


こんな感じ。
できたけど実はよくわかってない。
1から学び直し。これが初心者。てへぺろ。

Discussion