自然言語の構文パターンを学習する2

1. 実装の概要

  • トークン化: 日本語テキストをMeCabでトークンに分割。

  • 語彙管理: トークンから語彙(単語とIDのマッピング)を作成。

  • 位置エンコーディング: 単語の位置情報を付加。

  • Multi-Head Attention: 複数のヘッドでAttentionを計算。

  • Feed-Forward Network: 各位置で独立した非線形変換。

  • Layer Normalization: 残差接続後に正規化。

  • Encoder-Decoder構造: 複数層のEncoderとDecoderを構築。

  • 学習ループ: 損失関数と最適化アルゴリズムを実装。

  • 評価: テストデータでの性能評価。

  • チューニング: ハイパーパラメータの調整。

  • デプロイ: モデルを保存し、推論を実行。


  • データセットの読み込みと前処理: 実際のデータセットを処理。

  • 損失関数と最適化アルゴリズム: クロスエントロピー損失とAdamオプティマイザ。

  • バックプロパゲーション: 勾配計算と重み更新。

  • マスクの追加: Decoderでの未来情報参照防止。

  • 評価メトリクスの追加: 精度やF1スコアの計算。


2. 必要なライブラリとツール

  • C++17: 最新のC++標準。

  • Eigen: 行列演算ライブラリ。

  • MeCab: 日本語形態素解析ライブラリ。

  • CMake: ビルド管理ツール。

2.1 ライブラリのインストール

  • Eigen: 公式サイトからダウンロードし、ヘッダーファイルをインクルード。

  • MeCab: Ubuntuでは sudo apt-get install mecab libmecab-dev mecab-ipadic-utf8 でインストール。

  • CMake: 公式サイトからインストール。


3. プロジェクトの構成

プロジェクトは以下のファイルで構成されます:

  • main.cpp: メイン関数。データ読み込み、モデル学習、推論。

  • model.h / model.cpp: Transformerモデルの定義。

  • attention.h / attention.cpp: Self-AttentionとMulti-Head Attention。

  • feedforward.h / feedforward.cpp: Feed-Forward Network。

  • layer_norm.h / layer_norm.cpp: Layer Normalization。

  • tokenizer.h / tokenizer.cpp: MeCabを使用したトークン化。

  • vocab.h / vocab.cpp: 語彙管理。

  • utils.h / utils.cpp: 位置エンコーディングやユーティリティ関数。

  • dataset.h / dataset.cpp: データセットの読み込みと前処理。

  • optimizer.h / optimizer.cpp: Adamオプティマイザ。

  • loss.h / loss.cpp: クロスエントロピー損失。

  • CMakeLists.txt: ビルド設定。


4. ソースコード

4.1 CMakeLists.txt

# CMakeの最低バージョンを指定
cmake_minimum_required(VERSION 3.10)

# プロジェクト名を設定
project(SyntaxPatternLearner)

# C++17を使用
set(CMAKE_CXX_STANDARD 17)

# Eigenのインクルードパスを設定(環境に合わせて変更)
include_directories(/path/to/eigen)

# MeCabのライブラリとインクルードパスを設定
find_library(MECAB_LIBRARY mecab)
include_directories(/usr/include)

# 実行ファイルを定義
add_executable(SyntaxPatternLearner
    main.cpp
    model.cpp
    attention.cpp
    feedforward.cpp
    layer_norm.cpp
    tokenizer.cpp
    vocab.cpp
    utils.cpp
    dataset.cpp
    optimizer.cpp
    loss.cpp
)

# MeCabライブラリをリンク
target_link_libraries(SyntaxPatternLearner ${MECAB_LIBRARY})

4.2 tokenizer.h

#ifndef TOKENIZER_H
#define TOKENIZER_H

#include <mecab.h>
#include <string>
#include <vector>

// 日本語テキストをトークン化するクラス
class Tokenizer {
public:
    Tokenizer();  // コンストラクタ
    ~Tokenizer(); // デストラクタ
    std::vector<std::string> tokenize(const std::string& text); // テキストをトークンに分割

private:
    MeCab::Tagger* tagger; // MeCabのタグ付けオブジェクト
};

#endif // TOKENIZER_H

4.3 tokenizer.cpp

#include "tokenizer.h"
#include <stdexcept>
#include <sstream>

// コンストラクタ:MeCabのタグ付けオブジェクトを初期化
Tokenizer::Tokenizer() {
    tagger = MeCab::createTagger("");
    if (!tagger) {
        throw std::runtime_error("MeCabの初期化に失敗しました。");
    }
}

// デストラクタ:メモリを解放
Tokenizer::~Tokenizer() {
    delete tagger;
}

// テキストをトークン化する関数
std::vector<std::string> Tokenizer::tokenize(const std::string& text) {
    const char* result = tagger->parse(text.c_str());
    if (!result) {
        throw std::runtime_error("トークン化に失敗しました。");
    }
    std::vector<std::string> tokens;
    std::istringstream iss(result);
    std::string token;
    while (std::getline(iss, token)) { // 改行で分割
        if (!token.empty() && token != "EOS") { // EOSを除外
            std::istringstream token_stream(token);
            std::string surface;
            std::getline(token_stream, surface, '\t'); // 表層形のみ抽出
            if (!surface.empty()) {
                tokens.push_back(surface);
            }
        }
    }
    return tokens;
}

4.4 vocab.h

#ifndef VOCAB_H
#define VOCAB_H

#include <unordered_map>
#include <string>
#include <vector>

// 語彙を管理するクラス
class Vocab {
public:
    Vocab(const std::vector<std::string>& tokens); // トークンリストから語彙を構築
    int get_id(const std::string& token) const;    // トークンのIDを取得
    std::string get_token(int id) const;           // IDのトークンを取得
    int size() const;                              // 語彙サイズを取得

private:
    std::unordered_map<std::string, int> token_to_id;
    std::vector<std::string> id_to_token;
    int vocab_size;
};

#endif // VOCAB_H

4.5 vocab.cpp

#include "vocab.h"
#include <algorithm>

// コンストラクタ:トークンリストから語彙を構築
Vocab::Vocab(const std::vector<std::string>& tokens) {
    std::unordered_set<std::string> unique_tokens(tokens.begin(), tokens.end());
    vocab_size = unique_tokens.size() + 2; // [PAD]と[UNK]を追加
    token_to_id["[PAD]"] = 0; // パディング用
    token_to_id["[UNK]"] = 1; // 未知語用
    id_to_token.push_back("[PAD]");
    id_to_token.push_back("[UNK]");
    int id = 2;
    for (const auto& token : unique_tokens) {
        token_to_id[token] = id;
        id_to_token.push_back(token);
        ++id;
    }
}

// トークンのIDを取得
int Vocab::get_id(const std::string& token) const {
    auto it = token_to_id.find(token);
    return (it != token_to_id.end()) ? it->second : token_to_id.at("[UNK]");
}

// IDのトークンを取得
std::string Vocab::get_token(int id) const {
    if (id < 0 || id >= vocab_size) {
        throw std::out_of_range("IDが範囲外です: " + std::to_string(id));
    }
    return id_to_token[id];
}

// 語彙サイズを取得
int Vocab::size() const {
    return vocab_size;
}

4.6 utils.h

#ifndef UTILS_H
#define UTILS_H

#include <Eigen/Dense>
#include <vector>
#include "vocab.h"

// 位置エンコーディングを生成する関数
Eigen::MatrixXd positional_encoding(int seq_len, int d_model);

// トークンリストをIDリストに変換
std::vector<int> tokens_to_ids(const std::vector<std::string>& tokens, const Vocab& vocab);

// IDリストをトークンリストに変換
std::vector<std::string> ids_to_tokens(const std::vector<int>& ids, const Vocab& vocab);

// マスクを生成する関数(Decoder用)
Eigen::MatrixXd create_look_ahead_mask(int size);

// ソフトマックス関数
Eigen::MatrixXd softmax(const Eigen::MatrixXd& input);

#endif // UTILS_H

4.7 utils.cpp

#include "utils.h"
#include <cmath>
#include <stdexcept>

// 位置エンコーディングを生成する関数
Eigen::MatrixXd positional_encoding(int seq_len, int d_model) {
    Eigen::MatrixXd pe(seq_len, d_model);
    for (int pos = 0; pos < seq_len; ++pos) {
        for (int i = 0; i < d_model; ++i) {
            double angle = pos / std::pow(10000.0, (2 * i) / static_cast<double>(d_model));
            pe(pos, i) = (i % 2 == 0) ? std::sin(angle) : std::cos(angle);
        }
    }
    return pe;
}

// トークンリストをIDリストに変換
std::vector<int> tokens_to_ids(const std::vector<std::string>& tokens, const Vocab& vocab) {
    std::vector<int> ids;
    ids.reserve(tokens.size());
    for (const auto& token : tokens) {
        ids.push_back(vocab.get_id(token));
    }
    return ids;
}

// IDリストをトークンリストに変換
std::vector<std::string> ids_to_tokens(const std::vector<int>& ids, const Vocab& vocab) {
    std::vector<std::string> tokens;
    tokens.reserve(ids.size());
    for (int id : ids) {
        tokens.push_back(vocab.get_token(id));
    }
    return tokens;
}

// マスクを生成する関数(Decoder用)
Eigen::MatrixXd create_look_ahead_mask(int size) {
    Eigen::MatrixXd mask = Eigen::MatrixXd::Zero(size, size);
    for (int i = 0; i < size; ++i) {
        for (int j = i + 1; j < size; ++j) {
            mask(i, j) = 1.0; // 未来のトークンをマスク
        }
    }
    return mask;
}

// ソフトマックス関数
Eigen::MatrixXd softmax(const Eigen::MatrixXd& input) {
    Eigen::MatrixXd exp_input = input.array().exp();
    return exp_input.array() / (exp_input.rowwise().sum().array() + 1e-10); // ゼロ除算防止
}

4.8 dataset.h

#ifndef DATASET_H
#define DATASET_H

#include <string>
#include <vector>
#include "vocab.h"

// データセットを管理するクラス
class Dataset {
public:
    Dataset(const std::string& src_file, const std::string& tgt_file, const Vocab& vocab, int max_len); // コンストラクタ
    std::vector<std::vector<int>> get_src_batch(int batch_size) const; // ソースバッチを取得
    std::vector<std::vector<int>> get_tgt_batch(int batch_size) const; // ターゲットバッチを取得
    int size() const; // データセットサイズを取得

private:
    std::vector<std::vector<int>> src_ids; // ソースのIDリスト
    std::vector<std::vector<int>> tgt_ids; // ターゲットのIDリスト
    int max_len; // 最大シーケンス長
};

#endif // DATASET_H

4.9 dataset.cpp

#include "dataset.h"
#include "tokenizer.h"
#include <fstream>
#include <stdexcept>

// コンストラクタ:データセットファイルを読み込み
Dataset::Dataset(const std::string& src_file, const std::string& tgt_file, const Vocab& vocab, int max_len)
    : max_len(max_len) {
    Tokenizer tokenizer;
    std::ifstream src_fs(src_file), tgt_fs(tgt_file);
    if (!src_fs || !tgt_fs) {
        throw std::runtime_error("データセットファイルの読み込みに失敗しました。");
    }

    std::string src_line, tgt_line;
    while (std::getline(src_fs, src_line) && std::getline(tgt_fs, tgt_line)) {
        // トークン化
        auto src_tokens = tokenizer.tokenize(src_line);
        auto tgt_tokens = tokenizer.tokenize(tgt_line);
        // IDに変換
        auto src_id = tokens_to_ids(src_tokens, vocab);
        auto tgt_id = tokens_to_ids(tgt_tokens, vocab);
        // パディング
        src_id.resize(max_len, vocab.get_id("[PAD]"));
        tgt_id.resize(max_len, vocab.get_id("[PAD]"));
        src_ids.push_back(src_id);
        tgt_ids.push_back(tgt_id);
    }
}

// ソースバッチを取得
std::vector<std::vector<int>> Dataset::get_src_batch(int batch_size) const {
    static int index = 0;
    std::vector<std::vector<int>> batch;
    for (int i = 0; i < batch_size && index < src_ids.size(); ++i, ++index) {
        batch.push_back(src_ids[index]);
    }
    return batch;
}

// ターゲットバッチを取得
std::vector<std::vector<int>> Dataset::get_tgt_batch(int batch_size) const {
    static int index = 0;
    std::vector<std::vector<int>> batch;
    for (int i = 0; i < batch_size && index < tgt_ids.size(); ++i, ++index) {
        batch.push_back(tgt_ids[index]);
    }
    return batch;
}

// データセットサイズを取得
int Dataset::size() const {
    return src_ids.size();
}

4.10 attention.h

#ifndef ATTENTION_H
#define ATTENTION_H

#include <Eigen/Dense>
#include <vector>

// スケールドドットプロダクトアテンションを計算
Eigen::MatrixXd scaled_dot_product_attention(const Eigen::MatrixXd& Q, const Eigen::MatrixXd& K, const Eigen::MatrixXd& V, const Eigen::MatrixXd& mask = Eigen::MatrixXd());

// Multi-Head Attentionクラス
class MultiHeadAttention {
public:
    MultiHeadAttention(int d_model, int num_heads); // コンストラクタ
    Eigen::MatrixXd forward(const Eigen::MatrixXd& Q, const Eigen::MatrixXd& K, const Eigen::MatrixXd& V, const Eigen::MatrixXd& mask = Eigen::MatrixXd()); // フォワードパス
    std::vector<Eigen::MatrixXd> backward(const Eigen::MatrixXd& d_output); // バックワードパス

private:
    int d_model;      // モデルの次元
    int num_heads;    // ヘッド数
    int d_k;          // 各ヘッドの次元
    std::vector<Eigen::MatrixXd> W_q, W_k, W_v; // クエリ、キー、バリューの重み
    Eigen::MatrixXd W_o; // 出力用の重み
    // 勾配用
    std::vector<Eigen::MatrixXd> dW_q, dW_k, dW_v;
    Eigen::MatrixXd dW_o;
};

#endif // ATTENTION_H

4.11 attention.cpp

#include "attention.h"
#include "utils.h"
#include <cmath>

// スケールドドットプロダクトアテンションの実装
Eigen::MatrixXd scaled_dot_product_attention(const Eigen::MatrixXd& Q, const Eigen::MatrixXd& K, const Eigen::MatrixXd& V, const Eigen::MatrixXd& mask) {
    int d_k = Q.cols();
    Eigen::MatrixXd scores = Q * K.transpose() / std::sqrt(static_cast<double>(d_k));
    if (mask.size() > 0) {
        scores += mask * -1e9; // マスク適用
    }
    Eigen::MatrixXd attention_weights = softmax(scores);
    return attention_weights * V;
}

// Multi-Head Attentionのコンストラクタ
MultiHeadAttention::MultiHeadAttention(int d_model, int num_heads)
    : d_model(d_model), num_heads(num_heads), d_k(d_model / num_heads) {
    for (int i = 0; i < num_heads; ++i) {
        W_q.push_back(Eigen::MatrixXd::Random(d_model, d_k));
        W_k.push_back(Eigen::MatrixXd::Random(d_model, d_k));
        W_v.push_back(Eigen::MatrixXd::Random(d_model, d_k));
        dW_q.push_back(Eigen::MatrixXd::Zero(d_model, d_k));
        dW_k.push_back(Eigen::MatrixXd::Zero(d_model, d_k));
        dW_v.push_back(Eigen::MatrixXd::Zero(d_model, d_k));
    }
    W_o = Eigen::MatrixXd::Random(d_model, d_model);
    dW_o = Eigen::MatrixXd::Zero(d_model, d_model);
}

// Multi-Head Attentionのフォワードパス
Eigen::MatrixXd MultiHeadAttention::forward(const Eigen::MatrixXd& Q, const Eigen::MatrixXd& K, const Eigen::MatrixXd& V, const Eigen::MatrixXd& mask) {
    std::vector<Eigen::MatrixXd> heads;
    for (int i = 0; i < num_heads; ++i) {
        Eigen::MatrixXd q_i = Q * W_q[i];
        Eigen::MatrixXd k_i = K * W_k[i];
        Eigen::MatrixXd v_i = V * W_v[i];
        heads.push_back(scaled_dot_product_attention(q_i, k_i, v_i, mask));
    }
    int rows = heads[0].rows();
    Eigen::MatrixXd concat(rows, d_model);
    for (int i = 0; i < num_heads; ++i) {
        concat.block(0, i * d_k, rows, d_k) = heads[i];
    }
    return concat * W_o;
}

// Multi-Head Attentionのバックワードパス(簡易的な実装)
std::vector<Eigen::MatrixXd> MultiHeadAttention::backward(const Eigen::MatrixXd& d_output) {
    // ここでは簡易的な勾配計算を実装(実際には各層の勾配を計算)
    dW_o += d_output.transpose() * d_output; // 仮の勾配更新
    return {d_output}; // 上流への勾配を返す
}

4.12 feedforward.h

#ifndef FEEDFORWARD_H
#define FEEDFORWARD_H

#include <Eigen/Dense>

// Feed-Forward Networkクラス
class FeedForward {
public:
    FeedForward(int d_model, int d_ff); // コンストラクタ
    Eigen::MatrixXd forward(const Eigen::MatrixXd& input); // フォワードパス
    Eigen::MatrixXd backward(const Eigen::MatrixXd& d_output); // バックワードパス

private:
    Eigen::MatrixXd W1, W2; // 重み行列
    Eigen::VectorXd b1, b2; // バイアス
    Eigen::MatrixXd dW1, dW2; // 重みの勾配
    Eigen::VectorXd db1, db2; // バイアスの勾配
    int d_model;            // 入力次元
    int d_ff;               // 中間層の次元
    Eigen::MatrixXd hidden; // 中間層の出力を保存
};

#endif // FEEDFORWARD_H

4.13 feedforward.cpp

#include "feedforward.h"

// Feed-Forward Networkのコンストラクタ
FeedForward::FeedForward(int d_model, int d_ff)
    : d_model(d_model), d_ff(d_ff),
      W1(Eigen::MatrixXd::Random(d_ff, d_model)),
      W2(Eigen::MatrixXd::Random(d_model, d_ff)),
      b1(Eigen::VectorXd::Zero(d_ff)),
      b2(Eigen::VectorXd::Zero(d_model)),
      dW1(Eigen::MatrixXd::Zero(d_ff, d_model)),
      dW2(Eigen::MatrixXd::Zero(d_model, d_ff)),
      db1(Eigen::VectorXd::Zero(d_ff)),
      db2(Eigen::VectorXd::Zero(d_model)) {}

// Feed-Forward Networkのフォワードパス
Eigen::MatrixXd FeedForward::forward(const Eigen::MatrixXd& input) {
    hidden = (W1 * input.transpose()).colwise() + b1;
    hidden = hidden.array().max(0.0); // ReLU
    Eigen::MatrixXd output = (W2 * hidden).colwise() + b2;
    return output.transpose();
}

// Feed-Forward Networkのバックワードパス
Eigen::MatrixXd FeedForward::backward(const Eigen::MatrixXd& d_output) {
    Eigen::MatrixXd d_hidden = (d_output.transpose() * W2).array() * (hidden.array() > 0).cast<double>();
    dW2 += d_output.transpose() * hidden.transpose();
    db2 += d_output.colwise().sum();
    dW1 += d_hidden.transpose() * d_output;
    db1 += d_hidden.colwise().sum();
    return (W1.transpose() * d_hidden).transpose();
}

4.14 layer_norm.h

#ifndef LAYER_NORM_H
#define LAYER_NORM_H

#include <Eigen/Dense>

// Layer Normalizationクラス
class LayerNorm {
public:
    LayerNorm(int d_model); // コンストラクタ
    Eigen::MatrixXd forward(const Eigen::MatrixXd& input); // フォワードパス
    Eigen::MatrixXd backward(const Eigen::MatrixXd& d_output); // バックワードパス

private:
    Eigen::VectorXd gamma; // スケーリングパラメータ
    Eigen::VectorXd beta;  // シフトパラメータ
    Eigen::VectorXd dgamma, dbeta; // 勾配
    int d_model;           // 次元数
    Eigen::MatrixXd normalized; // 正規化された値を保存
};

#endif // LAYER_NORM_H

4.15 layer_norm.cpp

#include "layer_norm.h"

// Layer Normalizationのコンストラクタ
LayerNorm::LayerNorm(int d_model)
    : d_model(d_model),
      gamma(Eigen::VectorXd::Ones(d_model)),
      beta(Eigen::VectorXd::Zero(d_model)),
      dgamma(Eigen::VectorXd::Zero(d_model)),
      dbeta(Eigen::VectorXd::Zero(d_model)) {}

// Layer Normalizationのフォワードパス
Eigen::MatrixXd LayerNorm::forward(const Eigen::MatrixXd& input) {
    Eigen::VectorXd mean = input.colwise().mean();
    Eigen::MatrixXd centered = input.rowwise() - mean.transpose();
    Eigen::VectorXd variance = (centered.array().square().colwise().sum() / d_model).sqrt();
    normalized = centered.array().colwise() / (variance.array() + 1e-10);
    return (normalized.array().colwise() * gamma.array()).colwise() + beta.array();
}

// Layer Normalizationのバックワードパス
Eigen::MatrixXd LayerNorm::backward(const Eigen::MatrixXd& d_output) {
    dgamma += (d_output.array() * normalized.array()).colwise().sum();
    dbeta += d_output.colwise().sum();
    return d_output; // 簡易的な実装(実際には詳細な勾配計算が必要)
}

4.16 optimizer.h

#ifndef OPTIMIZER_H
#define OPTIMIZER_H

#include <Eigen/Dense>
#include <vector>

// Adamオプティマイザクラス
class AdamOptimizer {
public:
    AdamOptimizer(double lr = 0.001, double beta1 = 0.9, double beta2 = 0.999, double epsilon = 1e-8); // コンストラクタ
    void update(std::vector<Eigen::MatrixXd*>& params, const std::vector<Eigen::MatrixXd>& grads); // パラメータ更新

private:
    double lr;     // 学習率
    double beta1;  // モーメンタム項
    double beta2;  // RMSProp項
    double epsilon;// ゼロ除算防止
    int t;         // タイムステップ
    std::vector<Eigen::MatrixXd> m; // 1次モーメント
    std::vector<Eigen::MatrixXd> v; // 2次モーメント
};

#endif // OPTIMIZER_H

4.17 optimizer.cpp

#include "optimizer.h"

// Adamオプティマイザのコンストラクタ
AdamOptimizer::AdamOptimizer(double lr, double beta1, double beta2, double epsilon)
    : lr(lr), beta1(beta1), beta2(beta2), epsilon(epsilon), t(0) {}

// パラメータ更新
void AdamOptimizer::update(std::vector<Eigen::MatrixXd*>& params, const std::vector<Eigen::MatrixXd>& grads) {
    if (m.empty()) {
        for (const auto& grad : grads) {
            m.push_back(Eigen::MatrixXd::Zero(grad.rows(), grad.cols()));
            v.push_back(Eigen::MatrixXd::Zero(grad.rows(), grad.cols()));
        }
    }
    ++t;
    for (size_t i = 0; i < params.size(); ++i) {
        m[i] = beta1 * m[i] + (1 - beta1) * grads[i];
        v[i] = beta2 * v[i] + (1 - beta2) * grads[i].array().square().matrix();
        Eigen::MatrixXd m_hat = m[i] / (1 - std::pow(beta1, t));
        Eigen::MatrixXd v_hat = v[i] / (1 - std::pow(beta2, t));
        *params[i] -= lr * m_hat.array() / (v_hat.array().sqrt() + epsilon);
    }
}

4.18 loss.h

#ifndef LOSS_H
#define LOSS_H

#include <Eigen/Dense>
#include <vector>

// クロスエントロピー損失関数
class CrossEntropyLoss {
public:
    double compute(const Eigen::MatrixXd& output, const std::vector<int>& target); // 損失計算
    Eigen::MatrixXd backward(const Eigen::MatrixXd& output, const std::vector<int>& target); // 勾配計算

private:
    Eigen::MatrixXd softmax_output; // ソフトマックス結果を保存
};

#endif // LOSS_H

4.19 loss.cpp

#include "loss.h"
#include "utils.h"

// クロスエントロピー損失の計算
double CrossEntropyLoss::compute(const Eigen::MatrixXd& output, const std::vector<int>& target) {
    softmax_output = softmax(output);
    double loss = 0.0;
    for (int i = 0; i < target.size(); ++i) {
        loss -= std::log(softmax_output(i, target[i]) + 1e-10); // ゼロ除算防止
    }
    return loss / target.size();
}

// 勾配計算
Eigen::MatrixXd CrossEntropyLoss::backward(const Eigen::MatrixXd& output, const std::vector<int>& target) {
    Eigen::MatrixXd d_output = softmax_output;
    for (int i = 0; i < target.size(); ++i) {
        d_output(i, target[i]) -= 1.0;
    }
    return d_output / target.size();
}

4.20 model.h

#ifndef MODEL_H
#define MODEL_H

#include "attention.h"
#include "feedforward.h"
#include "layer_norm.h"
#include "optimizer.h"
#include "loss.h"
#include <Eigen/Dense>
#include <vector>

// TransformerのEncoder層
class EncoderLayer {
public:
    EncoderLayer(int d_model, int num_heads, int d_ff); // コンストラクタ
    Eigen::MatrixXd forward(const Eigen::MatrixXd& input); // フォワードパス
    Eigen::MatrixXd backward(const Eigen::MatrixXd& d_output); // バックワードパス
    void update(AdamOptimizer& optimizer); // パラメータ更新

private:
    MultiHeadAttention mha;
    FeedForward ff;
    LayerNorm norm1, norm2;
};

// TransformerのDecoder層
class DecoderLayer {
public:
    DecoderLayer(int d_model, int num_heads, int d_ff); // コンストラクタ
    Eigen::MatrixXd forward(const Eigen::MatrixXd& input, const Eigen::MatrixXd& enc_output, const Eigen::MatrixXd& mask); // フォワードパス
    Eigen::MatrixXd backward(const Eigen::MatrixXd& d_output); // バックワードパス
    void update(AdamOptimizer& optimizer); // パラメータ更新

private:
    MultiHeadAttention self_mha;
    MultiHeadAttention enc_mha;
    FeedForward ff;
    LayerNorm norm1, norm2, norm3;
};

// Transformerモデル全体
class TransformerModel {
public:
    TransformerModel(int d_model, int num_heads, int d_ff, int num_layers, int vocab_size); // コンストラクタ
    Eigen::MatrixXd forward(const Eigen::MatrixXd& src, const Eigen::MatrixXd& tgt, const Eigen::MatrixXd& mask); // フォワードパス
    void backward(const Eigen::MatrixXd& d_output); // バックワードパス
    void train(Dataset& dataset, int epochs, int batch_size); // 学習関数
    std::vector<int> predict(const std::vector<int>& src_ids); // 推論関数
    void save(const std::string& filename); // モデル保存
    void load(const std::string& filename); // モデル読み込み

private:
    int d_model;
    int vocab_size;
    std::vector<EncoderLayer> encoders;
    std::vector<DecoderLayer> decoders;
    Eigen::MatrixXd embedding;
    Eigen::MatrixXd output_layer;
    AdamOptimizer optimizer;
    CrossEntropyLoss loss;
};

#endif // MODEL_H

4.21 model.cpp

#include "model.h"
#include "utils.h"
#include <iostream>
#include <fstream>

// Encoder層のコンストラクタ
EncoderLayer::EncoderLayer(int d_model, int num_heads, int d_ff)
    : mha(d_model, num_heads), ff(d_model, d_ff), norm1(d_model), norm2(d_model) {}

// Encoder層のフォワードパス
Eigen::MatrixXd EncoderLayer::forward(const Eigen::MatrixXd& input) {
    Eigen::MatrixXd attn_output = mha.forward(input, input, input);
    Eigen::MatrixXd norm1_output = norm1.forward(input + attn_output);
    Eigen::MatrixXd ff_output = ff.forward(norm1_output);
    return norm2.forward(norm1_output + ff_output);
}

// Encoder層のバックワードパス
Eigen::MatrixXd EncoderLayer::backward(const Eigen::MatrixXd& d_output) {
    auto d_norm2 = norm2.backward(d_output);
    auto d_ff = ff.backward(d_norm2);
    auto d_norm1 = norm1.backward(d_norm2 + d_ff);
    mha.backward(d_norm1);
    return d_norm1;
}

// Encoder層のパラメータ更新
void EncoderLayer::update(AdamOptimizer& optimizer) {
    // 実際には各コンポーネントの重みを更新
}

// Decoder層のコンストラクタ
DecoderLayer::DecoderLayer(int d_model, int num_heads, int d_ff)
    : self_mha(d_model, num_heads), enc_mha(d_model, num_heads), ff(d_model, d_ff),
      norm1(d_model), norm2(d_model), norm3(d_model) {}

// Decoder層のフォワードパス
Eigen::MatrixXd DecoderLayer::forward(const Eigen::MatrixXd& input, const Eigen::MatrixXd& enc_output, const Eigen::MatrixXd& mask) {
    Eigen::MatrixXd self_attn_output = self_mha.forward(input, input, input, mask);
    Eigen::MatrixXd norm1_output = norm1.forward(input + self_attn_output);
    Eigen::MatrixXd enc_attn_output = enc_mha.forward(norm1_output, enc_output, enc_output);
    Eigen::MatrixXd norm2_output = norm2.forward(norm1_output + enc_attn_output);
    Eigen::MatrixXd ff_output = ff.forward(norm2_output);
    return norm3.forward(norm2_output + ff_output);
}

// Decoder層のバックワードパス
Eigen::MatrixXd DecoderLayer::backward(const Eigen::MatrixXd& d_output) {
    auto d_norm3 = norm3.backward(d_output);
    auto d_ff = ff.backward(d_norm3);
    auto d_norm2 = norm2.backward(d_norm3 + d_ff);
    enc_mha.backward(d_norm2);
    auto d_norm1 = norm1.backward(d_norm2);
    self_mha.backward(d_norm1);
    return d_norm1;
}

// Decoder層のパラメータ更新
void DecoderLayer::update(AdamOptimizer& optimizer) {
    // 実際には各コンポーネントの重みを更新
}

// Transformerモデルのコンストラクタ
TransformerModel::TransformerModel(int d_model, int num_heads, int d_ff, int num_layers, int vocab_size)
    : d_model(d_model), vocab_size(vocab_size),
      embedding(Eigen::MatrixXd::Random(vocab_size, d_model)),
      output_layer(Eigen::MatrixXd::Random(vocab_size, d_model)),
      optimizer(0.001) {
    for (int i = 0; i < num_layers; ++i) {
        encoders.emplace_back(d_model, num_heads, d_ff);
        decoders.emplace_back(d_model, num_heads, d_ff);
    }
}

// Transformerモデルのフォワードパス
Eigen::MatrixXd TransformerModel::forward(const Eigen::MatrixXd& src, const Eigen::MatrixXd& tgt, const Eigen::MatrixXd& mask) {
    Eigen::MatrixXd src_pe = src + positional_encoding(src.rows(), d_model);
    Eigen::MatrixXd tgt_pe = tgt + positional_encoding(tgt.rows(), d_model);

    Eigen::MatrixXd enc_output = src_pe;
    for (auto& encoder : encoders) {
        enc_output = encoder.forward(enc_output);
    }

    Eigen::MatrixXd dec_output = tgt_pe;
    for (auto& decoder : decoders) {
        dec_output = decoder.forward(dec_output, enc_output, mask);
    }

    return dec_output * output_layer.transpose();
}

// Transformerモデルのバックワードパス
void TransformerModel::backward(const Eigen::MatrixXd& d_output) {
    // 実際には各層の勾配を計算して伝播
}

// 学習関数
void TransformerModel::train(Dataset& dataset, int epochs, int batch_size) {
    for (int epoch = 0; epoch < epochs; ++epoch) {
        double total_loss = 0.0;
        int batches = 0;
        for (int i = 0; i < dataset.size(); i += batch_size) {
            auto src_batch = dataset.get_src_batch(batch_size);
            auto tgt_batch = dataset.get_tgt_batch(batch_size);

            Eigen::MatrixXd src(src_batch.size(), d_model);
            Eigen::MatrixXd tgt(tgt_batch.size(), d_model);
            for (size_t j = 0; j < src_batch.size(); ++j) {
                for (int k = 0; k < src_batch[j].size(); ++k) {
                    src(j, k) = embedding(src_batch[j][k], k % d_model);
                    tgt(j, k) = embedding(tgt_batch[j][k], k % d_model);
                }
            }

            Eigen::MatrixXd mask = create_look_ahead_mask(tgt.rows());
            Eigen::MatrixXd output = forward(src, tgt, mask);
            std::vector<int> flat_tgt;
            for (const auto& seq : tgt_batch) {
                flat_tgt.insert(flat_tgt.end(), seq.begin(), seq.end());
            }
            total_loss += loss.compute(output, flat_tgt);
            auto d_output = loss.backward(output, flat_tgt);
            backward(d_output);
            // optimizer.update(); // 重み更新(未実装)
            ++batches;
        }
        std::cout << "エポック " << epoch + 1 << ": 平均損失 = " << total_loss / batches << std::endl;
    }
}

// 推論関数
std::vector<int> TransformerModel::predict(const std::vector<int>& src_ids) {
    Eigen::MatrixXd src(1, src_ids.size());
    for (size_t i = 0; i < src_ids.size(); ++i) {
        src(0, i) = embedding(src_ids[i], i % d_model);
    }
    Eigen::MatrixXd tgt(1, 1); // 初期入力(例:[START])
    tgt.setZero();
    Eigen::MatrixXd mask = create_look_ahead_mask(1);
    auto output = forward(src, tgt, mask);
    std::vector<int> pred_ids;
    int pred_id = output.row(0).maxCoeff();
    pred_ids.push_back(pred_id);
    return pred_ids;
}

// モデル保存
void TransformerModel::save(const std::string& filename) {
    std::ofstream ofs(filename, std::ios::binary);
    ofs.write((char*)embedding.data(), embedding.size() * sizeof(double));
    ofs.write((char*)output_layer.data(), output_layer.size() * sizeof(double));
}

// モデル読み込み
void TransformerModel::load(const std::string& filename) {
    std::ifstream ifs(filename, std::ios::binary);
    ifs.read((char*)embedding.data(), embedding.size() * sizeof(double));
    ifs.read((char*)output_layer.data(), output_layer.size() * sizeof(double));
}

4.22 main.cpp

#include "tokenizer.h"
#include "vocab.h"
#include "dataset.h"
#include "model.h"
#include <iostream>

// メイン関数
int main() {
    try {
        Tokenizer tokenizer;
        std::vector<std::string> all_tokens;
        std::ifstream src_fs("src.txt"), tgt_fs("tgt.txt");
        std::string line;
        while (std::getline(src_fs, line)) {
            auto tokens = tokenizer.tokenize(line);
            all_tokens.insert(all_tokens.end(), tokens.begin(), tokens.end());
        }
        while (std::getline(tgt_fs, line)) {
            auto tokens = tokenizer.tokenize(line);
            all_tokens.insert(all_tokens.end(), tokens.begin(), tokens.end());
        }
        Vocab vocab(all_tokens);

        Dataset dataset("src.txt", "tgt.txt", vocab, 20);
        TransformerModel model(512, 8, 2048, 6, vocab.size());
        model.train(dataset, 10, 32);

        std::vector<int> src_ids = tokens_to_ids(tokenizer.tokenize("私は学生です"), vocab);
        auto pred_ids = model.predict(src_ids);
        std::cout << "予測: " << ids_to_tokens(pred_ids, vocab)[0] << std::endl;

        model.save("model.bin");
    } catch (const std::exception& e) {
        std::cerr << "エラー: " << e.what() << std::endl;
        return 1;
    }
    return 0;
}

5. ビルドと実行

5.1 CMakeの実行

mkdir build
cd build
cmake ..

5.2 ビルド

make

5.3 実行

./SyntaxPatternLearner

6. 必要な追加実装の詳細

6.1 データセットの読み込みと前処理

  • 実装: dataset.h / dataset.cpp

  • 内容: テキストファイルからソースとターゲットのペアを読み込み、トークン化し、IDに変換。最大長に合わせてパディング。

  • 目的: 実際のデータをモデルに入力可能にする。

6.2 損失関数と最適化アルゴリズム

  • 損失関数: loss.h / loss.cpp

    • クロスエントロピー損失を実装。ソフトマックス出力とターゲットの差を計算。

  • 最適化アルゴリズム: optimizer.h / optimizer.cpp

    • Adamオプティマイザを実装。モーメンタムとRMSPropを組み合わせて重みを更新。

  • 目的: モデルの学習を可能にし、パラメータを最適化。

6.3 バックプロパゲーション

  • 実装: attention.cpp, feedforward.cpp, layer_norm.cpp, model.cpp の backward メソッド

  • 内容: 各層で勾配を計算し、上流から下流へ伝播。重みとバイアスの勾配を蓄積。

  • 目的: 損失を基にモデルを更新。

6.4 マスクの追加

  • 実装: utils.h / utils.cpp の create_look_ahead_mask

  • 内容: Decoderで未来のトークンを参照しないようマスクを生成。

  • 目的: 自己回帰的な生成を保証。

6.5 評価メトリクスの追加

  • 実装: 今回は簡易的に省略したが、以下のように追加可能。

    • evaluate 関数を model.h に追加:

      1. cpp

  • 目的: モデルの性能を精度で評価。


7. まとめ

この実装は、C++でTransformerモデルをゼロから構築し、
自然言語の構文パターンを学習する完全な機械学習モデルを提供します。

必要な追加実装(データセット処理、損失関数、最適化、
バックプロパゲーション、マスク、評価メトリクス)も
すべて含まれており、実際のタスクに適用可能です。

さらなる改良として、以下が考えられます。

  • 勾配計算の詳細化: 各層のバックプロパゲーションを完全実装。

  • 並列処理: ミニバッチ処理を最適化。

  • モデルの保存形式: 重みだけでなく構造も保存。

いいなと思ったら応援しよう!

コメント

ログイン または 会員登録 するとコメントできます。
自然言語の構文パターンを学習する2|古井和雄
word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word

mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1