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

C++で自然言語の構文パターンを学習する
Transformerベースのモデルを独自に実装する。


1. 実装の概要

  • トークナイザ: 日本語テキストを単語に分割。

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

  • Self-Attention Mechanism: 入力シーケンス内の関係を学習。

  • Multi-Head Attention: 複数の視点から注目。

  • Feed-Forward Network: 各位置での非線形変換。

  • Layer Normalization: 安定した学習のために正規化。

  • Encoder-Decoder Structure: 構文パターンを学習する全体構造。

  • 学習ループ: モデルのトレーニング。


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

  • C++17: 最新のC++標準を使用。

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

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

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

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

  1. Eigen:

    • 公式サイト(Eigen)からダウンロード。

    • ヘッダーファイルをプロジェクトにインクルード。

  2. MeCab:

    • Ubuntuの場合:

      1. bash

    • Windowsでは、MeCabのバイナリをインストール。

  3. CMake:

    • 公式サイト(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を使用したトークン化。

  • utils.h / utils.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
    utils.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()) {
            tokens.push_back(token);
        }
    }
    return tokens;
}

4.4 utils.h

#ifndef UTILS_H
#define UTILS_H

#include <Eigen/Dense>

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

#endif // UTILS_H

4.5 utils.cpp

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

// 位置エンコーディングを生成する関数
// seq_len: シーケンス長, d_model: 埋め込み次元
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) {
            if (i % 2 == 0) {
                pe(pos, i) = std::sin(pos / std::pow(10000.0, i / static_cast<double>(d_model)));
            } else {
                pe(pos, i) = std::cos(pos / std::pow(10000.0, (i - 1) / static_cast<double>(d_model)));
            }
        }
    }
    return pe;
}

4.6 attention.h

#ifndef ATTENTION_H
#define ATTENTION_H

#include <Eigen/Dense>

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

// 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); // フォワードパス

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

#endif // ATTENTION_H

4.7 attention.cpp

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

// スケールドドットプロダクトアテンションの実装
Eigen::MatrixXd scaled_dot_product_attention(const Eigen::MatrixXd& Q, const Eigen::MatrixXd& K, const Eigen::MatrixXd& V) {
    int d_k = Q.cols();
    // スコアを計算:Q * K^T / sqrt(d_k)
    Eigen::MatrixXd scores = Q * K.transpose() / std::sqrt(static_cast<double>(d_k));
    // ソフトマックスを適用
    Eigen::MatrixXd attention_weights = scores.array().exp().matrix();
    attention_weights = attention_weights.array() / (attention_weights.array().rowwise().sum() + 1e-10); // ゼロ除算防止
    // バリューにアテンションを適用
    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));
    }
    W_o = Eigen::MatrixXd::Random(d_model, d_model);
}

// Multi-Head Attentionのフォワードパス
Eigen::MatrixXd MultiHeadAttention::forward(const Eigen::MatrixXd& Q, const Eigen::MatrixXd& K, const Eigen::MatrixXd& V) {
    std::vector<Eigen::MatrixXd> heads;
    for (int i = 0; i < num_heads; ++i) {
        // 各ヘッドでQ, K, Vを計算
        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));
    }
    // 全てのヘッドを結合
    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;
}

4.8 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); // フォワードパス

private:
    Eigen::MatrixXd W1, W2; // 重み行列
    Eigen::VectorXd b1, b2; // バイアス
    int d_model;            // 入力次元
    int d_ff;               // 中間層の次元
};

#endif // FEEDFORWARD_H

4.9 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)) {}

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

4.10 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); // フォワードパス

private:
    Eigen::VectorXd gamma; // スケーリングパラメータ
    Eigen::VectorXd beta;  // シフトパラメータ
    int d_model;           // 次元数
};

#endif // LAYER_NORM_H

4.11 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)) {}

// 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();
    // 正規化
    Eigen::MatrixXd normalized = centered.array().colwise() / (variance.array() + 1e-10); // ゼロ除算防止
    // スケーリングとシフト
    return (normalized.array().colwise() * gamma.array()).colwise() + beta.array();
}

4.12 model.h

#ifndef MODEL_H
#define MODEL_H

#include "attention.h"
#include "feedforward.h"
#include "layer_norm.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); // フォワードパス

private:
    MultiHeadAttention mha;     // Multi-Head Attention
    FeedForward ff;             // Feed-Forward Network
    LayerNorm norm1, norm2;     // 2つのLayer Normalization
};

// 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); // フォワードパス

private:
    MultiHeadAttention self_mha; // Self-Attention用
    MultiHeadAttention enc_mha;  // Encoder出力に対するAttention用
    FeedForward ff;              // Feed-Forward Network
    LayerNorm norm1, norm2, norm3; // 3つのLayer Normalization
};

// 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); // フォワードパス
    void train(const std::vector<std::string>& src_texts, const std::vector<std::string>& tgt_texts, int epochs); // 学習関数

private:
    int d_model;                          // モデルの次元
    int vocab_size;                       // 語彙サイズ
    std::vector<EncoderLayer> encoders;   // Encoder層のリスト
    std::vector<DecoderLayer> decoders;   // Decoder層のリスト
    Eigen::MatrixXd embedding;            // 埋め込み行列
    Eigen::MatrixXd output_layer;         // 出力層の重み
};

#endif // MODEL_H

4.13 model.cpp

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

// 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) {
    // Multi-Head Attentionと残差接続
    Eigen::MatrixXd attn_output = mha.forward(input, input, input);
    Eigen::MatrixXd norm1_output = norm1.forward(input + attn_output);
    // Feed-Forwardと残差接続
    Eigen::MatrixXd ff_output = ff.forward(norm1_output);
    return norm2.forward(norm1_output + ff_output);
}

// 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) {
    // Self-Attentionと残差接続
    Eigen::MatrixXd self_attn_output = self_mha.forward(input, input, input);
    Eigen::MatrixXd norm1_output = norm1.forward(input + self_attn_output);
    // Encoder-Decoder Attentionと残差接続
    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);
    // Feed-Forwardと残差接続
    Eigen::MatrixXd ff_output = ff.forward(norm2_output);
    return norm3.forward(norm2_output + ff_output);
}

// 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)) {
    // Encoder層を初期化
    for (int i = 0; i < num_layers; ++i) {
        encoders.emplace_back(d_model, num_heads, d_ff);
    }
    // Decoder層を初期化
    for (int i = 0; i < num_layers; ++i) {
        decoders.emplace_back(d_model, num_heads, d_ff);
    }
}

// Transformerモデルのフォワードパス
Eigen::MatrixXd TransformerModel::forward(const Eigen::MatrixXd& src, const Eigen::MatrixXd& tgt) {
    // 位置エンコーディングを追加
    Eigen::MatrixXd src_pe = src + positional_encoding(src.rows(), d_model);
    Eigen::MatrixXd tgt_pe = tgt + positional_encoding(tgt.rows(), d_model);

    // Encoderの処理
    Eigen::MatrixXd enc_output = src_pe;
    for (auto& encoder : encoders) {
        enc_output = encoder.forward(enc_output);
    }

    // Decoderの処理
    Eigen::MatrixXd dec_output = tgt_pe;
    for (auto& decoder : decoders) {
        dec_output = decoder.forward(dec_output, enc_output);
    }

    // 出力層
    return dec_output * output_layer.transpose();
}

// 学習関数(簡易的な実装)
void TransformerModel::train(const std::vector<std::string>& src_texts, const std::vector<std::string>& tgt_texts, int epochs) {
    // ダミーのトークンID変換(実際には語彙マッピングが必要)
    Eigen::MatrixXd src(src_texts.size(), d_model);
    Eigen::MatrixXd tgt(tgt_texts.size(), d_model);
    src.setRandom(); // 仮の入力データ
    tgt.setRandom(); // 仮のターゲットデータ

    for (int epoch = 0; epoch < epochs; ++epoch) {
        // フォワードパス
        Eigen::MatrixXd output = forward(src, tgt);
        // 損失計算(仮の実装)
        Eigen::MatrixXd loss = (output - tgt).array().square().mean();
        std::cout << "エポック " << epoch + 1 << ": 損失 = " << loss << std::endl;
        // 重みの更新(ここでは省略、実際には勾配降下法を実装)
    }
}

4.14 main.cpp

#include "tokenizer.h"
#include "model.h"
#include <iostream>
#include <vector>

// メイン関数
int main() {
    try {
        // トークナイザの初期化
        Tokenizer tokenizer;

        // サンプルテキスト
        std::vector<std::string> src_texts = {"私は学生です", "彼は教師です"};
        std::vector<std::string> tgt_texts = {"I am a student", "He is a teacher"};

        // トークン化のテスト
        for (const auto& text : src_texts) {
            std::vector<std::string> tokens = tokenizer.tokenize(text);
            std::cout << "トークン: ";
            for (const auto& token : tokens) {
                std::cout << token << " ";
            }
            std::cout << std::endl;
        }

        // モデルの初期化
        TransformerModel model(/*d_model=*/512, /*num_heads=*/8, /*d_ff=*/2048, /*num_layers=*/6, /*vocab_size=*/10000);

        // 学習の実行
        model.train(src_texts, tgt_texts, 10);

    } 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. 実装の詳細

  • トークン化: MeCabを使用して日本語テキストをトークンに分割。

  • 位置エンコーディング: 単語の位置情報を付加し、順序を考慮。

  • Multi-Head Attention: 複数のヘッドで異なる視点から関係性を学習。

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

  • Layer Normalization: 残差接続後に正規化を行い、学習を安定化。

  • Encoder-Decoder: 複数層のEncoderとDecoderで構成し、構文パターンを学習。

  • 学習ループ: 簡易的な損失計算を実装(実際には勾配計算と最適化が必要)。

実際の自然言語処理タスクに対応するには、以下のような拡張が必要です。

  • 語彙マッピング(トークンをIDに変換)。

  • 損失関数と勾配降下法の実装。

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

  • マスク付きアテンション(Decoderでの未来の情報参照防止)。

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

コメント

ログイン または 会員登録 するとコメントできます。
小ぶりなプログラムを試しに作っているんですが、 ここではその説明書きをしていこうと思います。 こういう機能をつけてみてほしいだとかいった要望があれば コメント欄に書いてみて下さい。 ひまをみて対応します。
自然言語の構文パターンを学習する1|古井和雄
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