diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index ff7c1671..222046dd 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -109,7 +109,7 @@ flowchart TD
START(["๐ ็จๅบๅฏๅจ"]) --> PARSE["่งฃๆๅฝไปค่กๅๆฐ
--epochs, --batch-size,
--lr, --optimizer"]
PARSE --> LOAD_DATA["ๅ ่ฝฝ CSV ๆฐๆฎ
MNIST 784 ็ปดๅ็ด "]
LOAD_DATA --> SPLIT["ๅๅฒ่ฎญ็ป้/ๆต่ฏ้"]
- SPLIT --> BUILD["ๆๅปบ็ฝ็ปๆจกๅ
Linear(784โ64)โReLU
ร 3 ๅฑ
โ Linear(64โ10)"]
+ SPLIT --> BUILD["ๆๅปบ็ฝ็ปๆจกๅ
Linear(784โ512)โLayerNormโGeLU
โ Linear(512โ256)โLayerNormโGeLU
โ Linear(256โ128)โLayerNormโGeLU
โ Linear(128โ64)โLayerNormโGeLU
โ Linear(64โ10)"]
BUILD --> LOAD_PREV{"ๆ้ข่ฎญ็ปๆจกๅ?"}
LOAD_PREV -->|"ๆฏ"| LOAD_M["ๅ ่ฝฝ้ข่ฎญ็ปๆ้"]
LOAD_PREV -->|"ๅฆ"| INIT["Xavier ๅๅงๅๆ้"]
@@ -117,7 +117,7 @@ flowchart TD
INIT --> TRAIN_LOOP
subgraph TRAIN_LOOP["๐ ่ฎญ็ปๅพช็ฏ (per epoch)"]
- BATCH["ๅไธไธช batch
(้ป่ฎค 32 ๆ ทๆฌ)"]
+ BATCH["ๅไธไธช batch
(้ป่ฎค 64 ๆ ทๆฌ)"]
BATCH --> FORWARD["โก ๅๅไผ ๆญ
model.forward(X)"]
FORWARD --> LOSS["๐ ่ฎก็ฎๆๅคฑ
CrossEntropyLoss"]
LOSS --> BACKWARD["๐ ๅๅไผ ๆญ
model.backward(grad)"]
@@ -217,8 +217,37 @@ classDiagram
+backward(grad_output) Matrix
}
+ class GeLU {
+ -Matrix input_cache_
+ -Matrix sigmoid_cache_
+ -constexpr double BETA = 1.702
+ +forward(input) Matrix
+ +backward(grad_output) Matrix
+ }
+
+ class LayerNorm {
+ -size_t normalized_shape_
+ -double epsilon_
+ -Matrix gamma_
+ -Matrix beta_
+ -Matrix grad_gamma_
+ -Matrix grad_beta_
+ +forward(input) Matrix
+ +backward(grad_output) Matrix
+ }
+
+ class PositionalEncoding {
+ -size_t max_len_
+ -size_t d_model_
+ -Matrix pe_
+ +forward(input) Matrix
+ }
+
Layer <|-- Linear
Layer <|-- ReLU
+ Layer <|-- GeLU
+ Layer <|-- LayerNorm
+ Layer <|-- PositionalEncoding
```
### Model โ ็ฝ็ปๅฎนๅจ
@@ -311,13 +340,15 @@ flowchart LR
end
```
-ๅๆฐๅๅ
ฅ้กบๅบ๏ผไปฅ 3 ๅฑ้่ๅฑไธบไพ๏ผ๏ผ
-1. Wโ (64ร784), bโ (64ร1)
-2. Wโ (64ร64), bโ (64ร1)
-3. Wโ (64ร64), bโ (64ร1)
-4. Wโ (10ร64), bโ (10ร1)
+ๅๆฐๅๅ
ฅ้กบๅบ๏ผ้ป่ฎค 4 ๅฑ้่ๅฑๆถๆ๏ผ๏ผ
+1. Wโ (512ร784), bโ (512ร1)
+2. Wโ (256ร512), bโ (256ร1)
+3. Wโ (128ร256), bโ (128ร1)
+4. Wโ (64ร128), bโ (64ร1)
+5. Wโ
(10ร64), bโ
(10ร1)
+6. ฮณโ~ฮณโ, ฮฒโ~ฮฒโ (LayerNorm ๅๆฐ)
-ๅ
ฑ **8 ไธช็ฉ้ตๅๆฐ**ใ
+ๅ
ฑ **10 ไธช Linear ๅๆฐ็ฉ้ต + 8 ไธช LayerNorm ๅๆฐ็ฉ้ต**ใ
---
@@ -350,12 +381,9 @@ flowchart TD
```mermaid
flowchart TD
- CMAKE["CMakeLists.txt"] --> OPT1["benchmark_naive
ๆด็ด ็ฉ้ตไนๆณๅบๅ"]
- CMAKE --> OPT2["benchmark_b32
32 ็บฟ็จ็ฉ้ตไนๆณๅบๅ"]
- CMAKE --> OPT3["benchmark_b64
64 ็บฟ็จ็ฉ้ตไนๆณๅบๅ"]
- CMAKE --> TARGET1["mnist_train
่ฎญ็ปๅฏๆง่กๆไปถ"]
+ CMAKE["CMakeLists.txt
C++26, Clang++"] --> TARGET1["mnist_train
่ฎญ็ปๅฏๆง่กๆไปถ"]
CMAKE --> TARGET2["mnist_infer
ๆจ็ๅฏๆง่กๆไปถ"]
- CMAKE --> DEPS["ไพ่ต: OpenMP (ๅฏ้)
C++26 ๆ ๅ"]
+ CMAKE --> DEPS["็ผ่ฏ้้กน: -O3 -ffast-math
-fexperimental-library
-Wall -Wextra -Wpedantic -Werror"]
```
---
@@ -388,23 +416,29 @@ flowchart LR
## ๐ฏ ็ฝ็ป็ปๆๆป็ป
+้ป่ฎค MNIST ๆถๆ๏ผๅฎไนๅจ `mnist_common.hpp` ็ `MNIST_LAYER_DIMS`๏ผ๏ผ
+
```
-โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-โ MNIST ๆๅๆฐๅญ่ฏๅซ็ฝ็ป โ
-โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
-โ Input: 784 (28ร28 ๅ็ด ๅฑๅนณ) โ
-โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
-โ Layer 1: Linear(784 โ 64) + ReLU โ
-โ Layer 2: Linear(64 โ 64) + ReLU โ
-โ Layer 3: Linear(64 โ 64) + ReLU โ
-โ Layer 4: Linear(64 โ 10) โ
-โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
-โ Loss: CrossEntropy (ๅซ Softmax) โ
-โ Optimizer: Adam / SGD / SGD+Momentum โ
-โ ่พๅบ: 10 ็ปดๅ้ โ argmax โ ๆฐๅญ 0-9 โ
-โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+โ MNIST ๆๅๆฐๅญ่ฏๅซ็ฝ็ป โ
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
+โ Input: 784 (28ร28 ๅ็ด ๅฑๅนณ) โ
+โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
+โ Layer 1: Linear(784 โ 512) + LayerNorm + GeLU โ
+โ Layer 2: Linear(512 โ 256) + LayerNorm + GeLU โ
+โ Layer 3: Linear(256 โ 128) + LayerNorm + GeLU โ
+โ Layer 4: Linear(128 โ 64) + LayerNorm + GeLU โ
+โ Layer 5: Linear(64 โ 10) โ
+โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
+โ Loss: CrossEntropy (ๅซๆฐๅผ็จณๅฎ Softmax) โ
+โ Optimizer: Adam / SGD / SGD+Momentum โ
+โ ่พๅบ: 10 ็ปดๅ้ โ argmax โ ๆฐๅญ 0-9 โ
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
```
+> ๐ ็ฝ็ปๆถๆ็ฑ `MNIST_LAYER_DIMS = {784, 512, 256, 128, 64, 10}` ้ฉฑๅจ๏ผ
+> ่ชๅจๅจ้่ๅฑไน้ดๆๅ
ฅ LayerNorm + GeLU ๆฟๆดปใ
+
---
*Generated by GitHub Copilot โ MiMo V2.5*
diff --git a/DEVELOPMENT_STANDARDS.md b/DEVELOPMENT_STANDARDS.md
index 6f78b4fe..88f0205a 100644
--- a/DEVELOPMENT_STANDARDS.md
+++ b/DEVELOPMENT_STANDARDS.md
@@ -100,7 +100,22 @@ void process(std::span data);
void process(const double* data, std::size_t size);
```
-#### 2.4 ้ขๅ้
็ผๅฒๅบๆจกๅผ
+#### 2.4 ไฝฟ็จ std::span ่ฎฟ้ฎ็ฉ้ตๆฐๆฎ
+```cpp
+// โ
ๆจ่๏ผไฝฟ็จ span() ่ฎฟ้ฎ็ฉ้ตๆฐๆฎ๏ผC++20 ้ถๅผ้ๆฝ่ฑก๏ผ
+auto data = matrix.span(); // std::span
+auto cdata = const_matrix.span(); // std::span
+
+// ๅจ lambda ไธญๆ่ท span๏ผ็ฑปๅๅฎๅ
จ๏ผ็ผ่ฏๆๅคงๅฐๆฃๆฅ๏ผ
+auto func = [data](std::size_t i) noexcept {
+ return data[i] * 2.0;
+};
+
+// โ ้ฟๅ
๏ผไฝฟ็จๅทฒๅบๅผ็ data_ptr()
+double* ptr = matrix.data_ptr(); // [[deprecated]]
+```
+
+#### 2.5 ้ขๅ้
็ผๅฒๅบๆจกๅผ
```cpp
// โ
ๆจ่๏ผ้ขๅ้
ๅนถๅจๅฐฑๅฐๆไฝ
void multiply_to(Matrix& result, const Matrix& other) const {
@@ -123,7 +138,7 @@ void multiply_to(Matrix& result, const Matrix& other) const {
- [ ] ๆฏๅฆๆ `new` / `delete` ๆไฝ๏ผ
- [ ] ๆฏๅฆไฝฟ็จ `std::vector` ๆฟไปฃๅจๆๆฐ็ป๏ผ
- [ ] ๆฏๅฆไฝฟ็จ `std::unique_ptr` ็ฎก็ๅคๆๅฏน่ฑก๏ผ
-- [ ] ๆฏๅฆไฝฟ็จ `std::span` ไผ ้ๅช่ฏป่ฟ็ปญๆฐๆฎ๏ผ
+- [ ] ๆฏๅฆไฝฟ็จ `std::span` ๆฟไปฃ่ฃธๆ้่ฎฟ้ฎ่ฟ็ปญๆฐๆฎ๏ผ
- [ ] ็ญ่ทฏๅพไธญๆฏๅฆ้ขๅ้
็ผๅฒๅบ๏ผ
---
@@ -378,32 +393,17 @@ set(CMAKE_CXX_STANDARD 26)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
```
-### 2. ๅฟ
้กปไฝฟ็จ็ C++26 ็นๆง
-
-| ็นๆง | ็จ้ | ็คบไพ |
-|------|------|------|
-| `std::generator` | ๅ็จ็ๆๅจ | ๆฐๆฎๅ ่ฝฝๅจ |
-| `std::print` / `std::println` | ๆ ผๅผๅ่พๅบ | ่ฐ่ฏไฟกๆฏ |
-| `std::expected` | ้่ฏฏๅค็ | ่ฟๅๅผ็ฑปๅ |
-| `std::flat_map` | ้ซๆง่ฝๆ ๅฐ | ้
็ฝฎ็ฎก็ |
-| `std::mdspan` | ๅค็ปดๆฐ็ป่งๅพ | ็ฉ้ตๆไฝ |
-| `std::stacktrace` | ่ฐ่ฏๅ ๆ ่ท่ธช | ้่ฏฏๆฅๅ |
-
-### 3. ็งฏๆไฝฟ็จ็ C++23/20 ็นๆง
+### 2. ็งฏๆไฝฟ็จ็ C++20/23 ็นๆง
| ็นๆง | ๆ ๅ | ็จ้ |
|------|------|------|
-| `std::ranges` | C++20 | ๆฐๆฎๅค็็ฎก้ |
-| `std::views` | C++20 | ๅปถ่ฟๆฑๅผ |
-| `std::format` | C++20 | ๅญ็ฌฆไธฒๆ ผๅผๅ |
-| `std::span` | C++20 | ้ๆฅๆ่งๅพ |
-| `std::jthread` | C++20 | ่ชๅจ่ฟๆฅ็บฟ็จ |
-| `std::source_location` | C++20 | ้่ฏฏๅฎไฝ |
-| `std::print` | C++23 | ๆ ผๅผๅ่พๅบ |
-| `std::generator` | C++23 | ๅ็จ็ๆๅจ |
-| `std::mdspan` | C++23 | ๅค็ปดๆฐ็ป |
-
-### 4. ๆ ๅ่ท่ฟๆฃๆฅๆธ
ๅ
+| `std::span` | C++20 | ้ๆฅๆๅ่ฟ็ปญๅ
ๅญ่งๅพ๏ผๆฟไปฃ่ฃธๆ้ |
+| `std::ranges` | C++20 | ๆฐๆฎๅค็็ฎก้๏ผๅฆ `ranges::generate` Xavier ๅๅงๅ๏ผ |
+| `std::views::iota` | C++20 | ๅปถ่ฟๆดๆฐๅบๅ๏ผๅนถ่ก็ดขๅผ็ๆ๏ผ |
+| `std::views::zip` | C++20 | ๅค่ๅดๅนถ่ก่ฟญไปฃ๏ผไผๅๅจๅๆฐๆดๆฐ๏ผ |
+| `std::execution::par_unseq` | C++17 | ๅนถ่กๆง่ก็ญ็ฅ๏ผ็ฉ้ต่ฟ็ฎ๏ผ |
+
+### 3. ๆ ๅ่ท่ฟๆฃๆฅๆธ
ๅ
- [ ] ๆฏๅฆไฝฟ็จๆๆฐ็ C++ ๆ ๅ๏ผC++26๏ผ๏ผ
- [ ] ๆฏๅฆ็งฏๆไฝฟ็จ C++20/23/26 ๆฐ็นๆง๏ผ
diff --git a/README.md b/README.md
index c7f18bc0..c7f54ca0 100644
--- a/README.md
+++ b/README.md
@@ -8,28 +8,40 @@
neuralnet.cpp/
โโโ .gitignore
โโโ CMakeLists.txt
+โโโ ARCHITECTURE.md โ ๆถๆๆๆกฃ
+โโโ DEVELOPMENT_STANDARDS.md โ ๅผๅ่ง่
โโโ README.md
-โโโ gui.py โ ๅพๅฝขๅๆไฝ็้ข
-โโโ csv_png.py
-โโโ extract_digits.py
-โโโ save_dataset.py
-โโโ build/
-โ โโโ mnist_infer.exe
+โโโ gui.py โ ๅพๅฝขๅๆไฝ็้ข (Tkinter)
+โโโ csv_png.py โ CSV ่ฝฌ PNG ๅพๅ
+โโโ extract_digits.py โ ไป MNIST CSV ๆๅๅไธชๆฐๅญ
+โโโ save_dataset.py โ ไธ่ฝฝ MNIST ๆฐๆฎ้
+โโโ build/ โ CMake ๆๅปบ่พๅบ
โ โโโ mnist_train.exe
-โ โโโ mnist_data/
-โ โโโ test.csv
-โ โโโ train.csv
+โ โโโ mnist_infer.exe
โโโ include/
โ โโโ neuralnet.cpp/
-โ โโโ layer.hpp
-โ โโโ loss.hpp
-โ โโโ matrix.hpp
-โ โโโ optimizer.hpp
-โโโ src/
- โโโ infer.cpp
- โโโ train.cpp
+โ โโโ nn.hpp โ ็ปไธๅ
ฅๅฃๅคดๆไปถ
+โ โโโ nn_config.hpp โ SmartPolicyใBLOCK_SIZE ็ญ้
็ฝฎ
+โ โโโ thread_pool.hpp โ ๅ
จๅฑ็บฟ็จๆฑ
+โ โโโ matrix.hpp โ Matrix ็ฑป๏ผๅไธปๅบ๏ผ
+โ โโโ layer.hpp โ Layer ๅบ็ฑป + Linear/ReLU/GeLU/LayerNorm/PositionalEncoding
+โ โโโ loss.hpp โ Loss ๅบ็ฑป + MSELoss/CrossEntropyLoss
+โ โโโ optimizer.hpp โ SGD / SGD_w_Momentum / Adam
+โ โโโ model.hpp โ Model ๅฎนๅจ๏ผ้พๅผ add<>๏ผ
+โ โโโ model_io.hpp โ ไบ่ฟๅถๆจกๅๅบๅๅ
+โ โโโ mnist_common.hpp โ MNIST ๅธธ้ไธ build_mnist_model()
+โโโ src/
+โ โโโ train.cpp
+โ โโโ infer.cpp
+โโโ datasets/
+โ โโโ mnist_data/ โ MNIST CSV ๆฐๆฎ
+โ โโโ test/ โ ๆๆฐๅญๅ็ฑป็ๆต่ฏๅพ็
+โโโ pretrained/
+ โโโ mnist_model.bin โ ้ข่ฎญ็ปๆจกๅ
```
+[](https://deepwiki.com/EthanPeng-2048/neuralnet.cpp)
+
## ไพ่ต
- **็ผ่ฏๅจ**: LLVM Clang++ 22.1+๏ผ`C:/Program Files/LLVM/bin/clang++.exe`๏ผ
@@ -56,14 +68,17 @@ python save_dataset.py
### ่ฟ่ก่ฎญ็ป
```bash
-# ไปๅคดๅผๅง่ฎญ็ป
+# ไปๅคดๅผๅง่ฎญ็ป๏ผ้ป่ฎค: 10่ฝฎ, ๅญฆไน ็0.001, ๆนๅคงๅฐ64, Adamไผๅๅจ๏ผ
./build/mnist_train
# ไปๅทฒๆๆจกๅๆขๅค่ฎญ็ป
-./build/mnist_train --load mnist_model.bin
+./build/mnist_train --resume pretrained/mnist_model.bin
+
+# ่ชๅฎไนๅๆฐ
+./build/mnist_train --epochs 20 --lr 0.001 --batch-size 32 --optimizer adam --save my_model.bin
-# ๆๅฎไฟๅญ่ทฏๅพ
-./build/mnist_train --save my_model.bin
+# ๆฏๆ็ไผๅๅจ: sgd / sgd_w_momentum / adam
+./build/mnist_train --optimizer sgd_w_momentum
```
### ๐ฅ๏ธ ๅพๅฝขๅ็้ข (GUI)
@@ -87,21 +102,37 @@ python gui.py
## ็ฝ็ป็ปๆ
+้ป่ฎค MNIST ๆถๆ๏ผๅฎไนๅจ `mnist_common.hpp`๏ผ๏ผ
+
```
-่พๅ
ฅ (784) โ Linear(64) โ ReLU โ Linear(64) โ ReLU โ Linear(64) โ ReLU โ Linear(10)
+่พๅ
ฅ (784)
+โ Linear(784 โ 512) + LayerNorm + GeLU
+โ Linear(512 โ 256) + LayerNorm + GeLU
+โ Linear(256 โ 128) + LayerNorm + GeLU
+โ Linear(128 โ 64) + LayerNorm + GeLU
+โ Linear(64 โ 10)
+โ CrossEntropy Loss (ๅซ Softmax)
```
## ๆไพ็็ปไปถ
| ็ปไปถ | ่ฏดๆ |
|------|------|
-| `nn::Matrix` | ๅไธปๅบ็ฉ้ต๏ผๆฏๆๅนถ่กๅ /ๅ/ไน/่ฝฌ็ฝฎ |
-| `nn::Linear` | ๅ
จ่ฟๆฅๅฑ๏ผๅซ Xavier ๅๅงๅ๏ผ |
+| `nn::Matrix` | ๅไธปๅบ็ฉ้ต๏ผๆฏๆๅนถ่กๅ /ๅ/ไน/่ฝฌ็ฝฎใ้ขๅ้
็ผๅฒๅบ |
+| `nn::Model` | ็ฝ็ปๅฎนๅจ๏ผๆฏๆ้พๅผ `add()` ๆๅปบ |
+| `nn::Linear` | ๅ
จ่ฟๆฅๅฑ๏ผๅซ Xavier ๅๅๅๅงๅใ่ๅ bias ่ฎก็ฎ๏ผ |
| `nn::ReLU` | ReLU ๆฟๆดปๅฝๆฐ |
+| `nn::GeLU` | QuickGeLU ๆฟๆดปๅฝๆฐ๏ผ`x ยท ฯ(1.702x)`๏ผ |
+| `nn::LayerNorm` | ๅฑๅฝไธๅ๏ผๅซๅฏๅญฆไน ฮณ/ฮฒ ๅๆฐ๏ผ |
+| `nn::PositionalEncoding` | ๆญฃๅผฆ/ไฝๅผฆไฝ็ฝฎ็ผ็ ๏ผTransformer ็จ๏ผ |
| `nn::MSELoss` | ๅๆน่ฏฏๅทฎๆๅคฑ |
-| `CrossEntropyLoss` | ไบคๅ็ตๆๅคฑ๏ผๅซๆฐๅผ็จณๅฎ Softmax๏ผๅจ `train.cpp` ไธญๅฎไน๏ผ |
+| `nn::CrossEntropyLoss` | ไบคๅ็ตๆๅคฑ๏ผๅซๆฐๅผ็จณๅฎ Softmax๏ผ`loss.hpp` ไธญๅฎไน๏ผ |
| `nn::SGD` | ้ๆบๆขฏๅบฆไธ้ไผๅๅจ |
-| `save_model` / `load_model` | ไบ่ฟๅถๆจกๅๅบๅๅ |
+| `nn::SGD_w_Momentum` | ๅจ้ SGD ไผๅๅจ |
+| `nn::Adam` | Adam ไผๅๅจ๏ผไธ้ถ/ไบ้ถ็ฉไผฐ่ฎก๏ผ |
+| `nn::SmartPolicy` | ่ช้ๅบๅนถ่ก็ญ็ฅ๏ผๅฐ็ฉ้ตไธฒ่ก๏ผๅคง็ฉ้ต็บฟ็จๆฑ ๅนถ่ก |
+| `nn::ThreadPool` | ๅ
จๅฑๅไพ็บฟ็จๆฑ ๏ผๆๅๅงๅ๏ผ |
+| `nn::save_model` / `nn::load_model` | ไบ่ฟๅถๆจกๅๅบๅๅ |
## ๐ ๅผๅ่ง่
diff --git a/gui.py b/gui.py
index 27e85d53..1edb2ccb 100644
--- a/gui.py
+++ b/gui.py
@@ -144,6 +144,12 @@ def _build_train_tab(self):
opt_combo = ttk.Combobox(param_frame, textvariable=self.train_opt_var, width=14, state="readonly",
values=["sgd", "sgd_w_momentum", "adam"])
opt_combo.grid(row=1, column=1, sticky="w", pady=(6, 0))
+
+ ttk.Label(param_frame, text="ๆจกๅ็ฑปๅ:").grid(row=1, column=2, sticky="w", pady=(6, 0))
+ self.train_model_type_var = tk.StringVar(value="mlp")
+ model_type_combo = ttk.Combobox(param_frame, textvariable=self.train_model_type_var, width=14, state="readonly",
+ values=["mlp", "transformer"])
+ model_type_combo.grid(row=1, column=3, sticky="w", pady=(6, 0))
row += 1
# ๆ้ฎ
@@ -201,7 +207,8 @@ def _start_training(self):
"--epochs", str(self.train_epochs_var.get()),
"--lr", str(self.train_lr_var.get()),
"--batch-size", str(self.train_batch_var.get()),
- "--optimizer", self.train_opt_var.get()]
+ "--optimizer", self.train_opt_var.get(),
+ "--model-type", self.train_model_type_var.get()]
if self.train_resume_var.get():
cmd += ["--resume", self.train_resume_path_var.get()]
@@ -249,7 +256,7 @@ def _build_infer_tab(self):
ttk.Button(f, text="ๆต่งโฆ", command=self._browse_input).grid(row=row, column=2)
row += 1
- # Top-K
+ # Top-K + ๆจกๅ็ฑปๅ
param_frame = ttk.Frame(f)
param_frame.grid(row=row, column=0, columnspan=3, sticky="w", pady=4)
row += 1
@@ -257,12 +264,54 @@ def _build_infer_tab(self):
self.infer_topk_var = tk.IntVar(value=3)
ttk.Spinbox(param_frame, from_=1, to=10, width=4,
textvariable=self.infer_topk_var).pack(side="left", padx=4)
+ ttk.Label(param_frame, text=" ๆจกๅ็ฑปๅ:").pack(side="left")
+ self.infer_model_type_var = tk.StringVar(value="mlp")
+ infer_model_type_combo = ttk.Combobox(param_frame, textvariable=self.infer_model_type_var,
+ width=12, state="readonly",
+ values=["mlp", "transformer"])
+ infer_model_type_combo.pack(side="left", padx=4)
row += 1
# ๆ้ฎ
ttk.Button(f, text="โถ ๅผๅงๆจ็", command=self._start_infer).grid(row=row, column=0, columnspan=3, pady=4)
row += 1
+ # โโ ๆๅๆฟ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ self._handwriting_infer = False
+ DRAW_SCALE = 10
+ DRAW_SIZE = IMG_DIM * DRAW_SCALE # 280
+ self._draw_size = DRAW_SIZE
+ self._draw_scale = DRAW_SCALE
+ self._brush_radius = 14
+ self._draw_pixels = [0.0] * (DRAW_SIZE * DRAW_SIZE)
+ self._drawing = False
+
+ draw_frame = ttk.LabelFrame(f, text="โ๏ธ ๆๅ่ฏๅซ", padding=6)
+ draw_frame.grid(row=row, column=0, columnspan=3, sticky="ew", pady=4)
+
+ draw_left = ttk.Frame(draw_frame)
+ draw_left.pack(side="left", padx=(0, 12))
+
+ self._draw_canvas = tk.Canvas(draw_left, width=DRAW_SIZE, height=DRAW_SIZE,
+ bg="white", cursor="pencil", relief="sunken", bd=2)
+ self._draw_canvas.pack()
+ self._draw_canvas.bind("", self._on_draw_start)
+ self._draw_canvas.bind("", self._on_draw_move)
+ self._draw_canvas.bind("", self._on_draw_end)
+
+ btn_row = ttk.Frame(draw_left)
+ btn_row.pack(fill="x", pady=4)
+ ttk.Button(btn_row, text="๐๏ธ ๆธ
้ค", command=self._clear_drawing).pack(side="left", padx=4)
+ ttk.Button(btn_row, text="๐ ่ฏๅซๆๅๆฐๅญ", command=self._recognize_drawing).pack(side="left", padx=4)
+
+ # ๅณไพง๏ผ28ร28 ้ข่ง
+ draw_right = ttk.Frame(draw_frame)
+ draw_right.pack(side="left", fill="both", expand=True)
+ ttk.Label(draw_right, text="28ร28 ้ข่ง:", font=("Segoe UI", 9)).pack(anchor="w")
+ preview_size = IMG_DIM * PIXEL_SIZE
+ self._draw_preview = tk.Canvas(draw_right, width=preview_size, height=preview_size, bg="black")
+ self._draw_preview.pack(pady=4)
+ row += 1
# ็ปๆๅบๅ
result_frame = ttk.Frame(f)
result_frame.grid(row=row, column=0, columnspan=3, sticky="ew")
@@ -306,7 +355,114 @@ def _browse_input(self):
if path:
self.infer_input_var.set(path)
+ # ---- ๆๅๆฟไบไปถ ----
+ def _on_draw_start(self, event):
+ self._drawing = True
+ self._paint_at(event.x, event.y)
+
+ def _on_draw_move(self, event):
+ if self._drawing:
+ self._paint_at(event.x, event.y)
+
+ def _on_draw_end(self, event):
+ self._drawing = False
+
+ def _paint_at(self, cx, cy):
+ """ๅจ็ปๆฟ (cx, cy) ๅค็ปๅถ็ฌ่งฆ"""
+ r = self._brush_radius
+ for dy in range(-r, r + 1):
+ for dx in range(-r, r + 1):
+ dist_sq = dx * dx + dy * dy
+ if dist_sq > r * r:
+ continue
+ px, py = cx + dx, cy + dy
+ if 0 <= px < self._draw_size and 0 <= py < self._draw_size:
+ dist = math.sqrt(dist_sq)
+ sigma = r / 2.0
+ intensity = math.exp(-dist_sq / (2 * sigma * sigma))
+ idx = py * self._draw_size + px
+ self._draw_pixels[idx] = max(self._draw_pixels[idx], intensity)
+ self._draw_canvas.create_oval(cx - r, cy - r, cx + r, cy + r,
+ fill="black", outline="")
+
+ def _clear_drawing(self):
+ """ๆธ
้คๆๅๆฟ"""
+ self._draw_pixels = [0.0] * (self._draw_size * self._draw_size)
+ self._draw_canvas.delete("all")
+ self._draw_preview.delete("all")
+
+ def _downsample_drawing(self) -> list[float]:
+ """ๅฐ 280ร280 ็ปๆฟไธ้ๆ ทไธบ 28ร28 ๅ็ด ๅผ"""
+ scale = self._draw_scale
+ pixels = []
+ for r in range(IMG_DIM):
+ for c in range(IMG_DIM):
+ total = 0.0
+ for dy in range(scale):
+ for dx in range(scale):
+ y = r * scale + dy
+ x = c * scale + dx
+ total += self._draw_pixels[y * self._draw_size + x]
+ avg = total / (scale * scale)
+ pixels.append(avg)
+ return pixels
+
+ def _recognize_drawing(self):
+ """่ฏๅซๆๅๆฟไธ็ๆฐๅญ"""
+ exe = str(INFER_EXE)
+ if not Path(exe).exists():
+ messagebox.showerror("้่ฏฏ", f"ๆพไธๅฐๆจ็ๅฏๆง่กๆไปถ:\n{exe}\n่ฏทๅ
ๆๅปบ้กน็ฎ")
+ return
+
+ # ๆฃๆฅๆฏๅฆๆๅ
ๅฎน
+ if max(self._draw_pixels) < 0.01:
+ messagebox.showwarning("ๆ็คบ", "่ฏทๅ
ๅจๆๅๆฟไธๅไธไธชๆฐๅญ")
+ return
+
+ pixels = self._downsample_drawing()
+
+ # ๆพ็คบ 28ร28 ้ข่ง
+ draw_digit(self._draw_preview, pixels)
+ draw_digit(self.infer_canvas, pixels)
+ self.infer_filename_label.config(text="ๆๅ่พๅ
ฅ")
+
+ # ๅๅ
ฅไธดๆถ CSV
+ import tempfile
+ tmp = tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False, newline="")
+ tmp.write(",".join(f"{v:.6f}" for v in pixels))
+ tmp.close()
+
+ cmd = [exe, tmp.name,
+ "--model", self.infer_model_var.get(),
+ "--model-type", self.infer_model_type_var.get(),
+ "--topk", str(self.infer_topk_var.get())]
+
+ self._log_infer(f"[ๆๅ่ฏๅซ] $ {' '.join(cmd)}\n")
+ self._handwriting_infer = True
+
+ def _run():
+ try:
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
+ self._log_infer(result.stdout)
+ if result.stderr:
+ self._log_infer(result.stderr)
+ self._parse_and_display_infer(result.stdout)
+ except FileNotFoundError:
+ self._log_infer("้่ฏฏ: ๅฏๆง่กๆไปถๆชๆพๅฐ\n")
+ except subprocess.TimeoutExpired:
+ self._log_infer("้่ฏฏ: ๆจ็่ถ
ๆถ\n")
+ except Exception as e:
+ self._log_infer(f"้่ฏฏ: {e}\n")
+ finally:
+ try:
+ os.unlink(tmp.name)
+ except OSError:
+ pass
+
+ threading.Thread(target=_run, daemon=True).start()
+
def _start_infer(self):
+ self._handwriting_infer = False
exe = str(INFER_EXE)
if not Path(exe).exists():
messagebox.showerror("้่ฏฏ", f"ๆพไธๅฐๆจ็ๅฏๆง่กๆไปถ:\n{exe}\n่ฏทๅ
ๆๅปบ้กน็ฎ:\n cmake -B build -G Ninja && ninja -C build")
@@ -319,6 +475,7 @@ def _start_infer(self):
cmd = [exe, input_path,
"--model", self.infer_model_var.get(),
+ "--model-type", self.infer_model_type_var.get(),
"--topk", str(self.infer_topk_var.get())]
self._log_infer(f"$ {' '.join(cmd)}\n")
@@ -350,7 +507,10 @@ def _parse_and_display_infer(self, output: str):
predictions_str = parts[1].strip()
# ๆพ็คบๆไปถๅ
- self.after(0, lambda f=filename: self.infer_filename_label.config(text=f))
+ if self._handwriting_infer:
+ self.after(0, lambda: self.infer_filename_label.config(text="ๆๅ่พๅ
ฅ"))
+ else:
+ self.after(0, lambda f=filename: self.infer_filename_label.config(text=f))
# ่งฃๆ้ขๆต็ปๆ
predictions = []
@@ -361,14 +521,15 @@ def _parse_and_display_infer(self, output: str):
# ๆพ็คบ็ปๆๆกๅฝขๅพ
self.after(0, lambda p=predictions: self._show_prediction_bars(p))
- # ๅฐ่ฏๆพ็คบๅพ็
- input_path = self.infer_input_var.get().strip()
- if os.path.isfile(input_path):
- self.after(0, lambda p=input_path: self._display_csv_image(p))
- elif os.path.isdir(input_path):
- img_path = os.path.join(input_path, filename)
- if os.path.isfile(img_path):
- self.after(0, lambda p=img_path: self._display_csv_image(p))
+ # ๅฐ่ฏๆพ็คบๅพ็๏ผๆๅๆจกๅผ่ทณ่ฟ๏ผๅทฒๆ้ข่ง๏ผ
+ if not self._handwriting_infer:
+ input_path = self.infer_input_var.get().strip()
+ if os.path.isfile(input_path):
+ self.after(0, lambda p=input_path: self._display_csv_image(p))
+ elif os.path.isdir(input_path):
+ img_path = os.path.join(input_path, filename)
+ if os.path.isfile(img_path):
+ self.after(0, lambda p=img_path: self._display_csv_image(p))
break
def _show_prediction_bars(self, predictions: list[tuple[int, float]]):
diff --git a/include/neuralnet.cpp/layer.hpp b/include/neuralnet.cpp/layer.hpp
index 2ee4b4a8..5bc2eb92 100644
--- a/include/neuralnet.cpp/layer.hpp
+++ b/include/neuralnet.cpp/layer.hpp
@@ -3,18 +3,20 @@
#include
#include
+#include
#include
#include
#include
#include
#include
#include
+#include
#include
#include
#include
-#include "matrix.hpp"
-#include
+#include "matrix.hpp"
+#include "nn_config.hpp"
namespace nn
{
@@ -26,10 +28,6 @@ namespace nn
virtual Matrix backward(const Matrix &grad_output) = 0;
virtual std::vector> parameters() { return {}; }
virtual std::vector> param_gradients() { return {}; }
-
- // ๆทปๅ ๅๆฐๆดๆฐ่พ
ๅฉๆนๆณ๏ผ้ฟๅ
่ๅฝๆฐ่ฐ็จๅผ้
- virtual void update_params(double /*lr*/) noexcept {}
- virtual void zero_grad() noexcept {}
};
class Linear final : public Layer
@@ -91,16 +89,16 @@ namespace nn
// result = product + bias๏ผ่ฟๅๆฐ็ฉ้ต๏ผNRVO ไผๅ๏ผ
Matrix result(out_feat, batch);
- const double *prod_ptr = product_buf_.data_ptr();
- const double *bias_ptr = b_.data_ptr();
- double *res_ptr = result.data_ptr();
+ const auto prod_span = product_buf_.span();
+ const auto bias_span = b_.span();
+ auto res_span = result.span();
const auto total = static_cast(out_feat * batch);
auto indices = std::views::iota(std::size_t{0}, total);
SmartPolicy::for_each(indices.begin(), indices.end(),
- [prod_ptr, bias_ptr, res_ptr, batch](std::size_t idx) noexcept
+ [prod_span, bias_span, res_span, batch](std::size_t idx) noexcept
{
- res_ptr[idx] = prod_ptr[idx] + bias_ptr[idx / batch];
+ res_span[idx] = prod_span[idx] + bias_span[idx / batch];
});
return result;
@@ -125,31 +123,33 @@ namespace nn
// grad_W += grad_output * input_cache_^T๏ผ้ๅ
็ด ็ดฏๅ ๏ผ้ฟๅ
ไธดๆถ็ฉ้ต๏ผ
// ๆๅจ่ฎก็ฎ๏ผgrad_W[i][j] += sum_k(grad_output[i][k] * input_cache_[j][k])
{
- const double *go_ptr = grad_output.data_ptr();
- const double *ic_ptr = input_cache_.data_ptr();
- double *gw_ptr = grad_W_.data_ptr();
- for (std::size_t of = 0; of < out_feat; ++of)
- {
- for (std::size_t inf = 0; inf < in_feat; ++inf)
+ const auto go_span = grad_output.span();
+ const auto ic_span = input_cache_.span();
+ auto gw_span = grad_W_.span();
+ const std::size_t gw_size = out_feat * in_feat;
+ auto gw_indices = std::views::iota(std::size_t{0}, gw_size);
+ SmartPolicy::for_each(gw_indices.begin(), gw_indices.end(),
+ [go_span, ic_span, gw_span, in_feat, batch](std::size_t idx) noexcept
{
+ const std::size_t of = idx / in_feat;
+ const std::size_t inf = idx % in_feat;
double sum = 0.0;
for (std::size_t b = 0; b < batch; ++b)
- sum += go_ptr[of * batch + b] * ic_ptr[inf * batch + b];
- gw_ptr[of * in_feat + inf] += sum;
- }
- }
+ sum += go_span[of * batch + b] * ic_span[inf * batch + b];
+ gw_span[idx] += sum;
+ });
}
// grad_b += sum(grad_output, dim=batch)
{
- const double *go_ptr = grad_output.data_ptr();
- double *gb_ptr = grad_b_.data_ptr();
+ const auto go_span = grad_output.span();
+ auto gb_span = grad_b_.span();
for (std::size_t of = 0; of < out_feat; ++of)
{
double sum = 0.0;
for (std::size_t b = 0; b < batch; ++b)
- sum += go_ptr[of * batch + b];
- gb_ptr[of] += sum;
+ sum += go_span[of * batch + b];
+ gb_span[of] += sum;
}
}
@@ -169,20 +169,20 @@ namespace nn
{
input_cache_ = input;
- const double *in_ptr = input.data_ptr();
- const auto n = static_cast(input.size());
+ const auto in_span = input.span();
+ const auto n = input.size();
Matrix result(input.rows(), input.cols());
- double *out_ptr = result.data_ptr();
+ auto out_span = result.span();
if (n >= SmartPolicy::PARALLEL_THRESHOLD) {
- auto indices = std::views::iota(0LL, n);
+ auto indices = std::views::iota(std::size_t{0}, n);
SmartPolicy::for_each(indices.begin(), indices.end(),
- [in_ptr, out_ptr](long long i) noexcept
- { out_ptr[i] = in_ptr[i] > 0.0 ? in_ptr[i] : 0.0; });
+ [in_span, out_span](std::size_t i) noexcept
+ { out_span[i] = in_span[i] > 0.0 ? in_span[i] : 0.0; });
} else {
- for (long long i = 0; i < n; ++i)
- out_ptr[i] = in_ptr[i] > 0.0 ? in_ptr[i] : 0.0;
+ for (std::size_t i = 0; i < n; ++i)
+ out_span[i] = in_span[i] > 0.0 ? in_span[i] : 0.0;
}
return result;
}
@@ -192,21 +192,1172 @@ namespace nn
if (input_cache_.rows() != grad_output.rows() || input_cache_.cols() != grad_output.cols())
throw std::invalid_argument("relu backward shape mismatch");
- const double *in_ptr = input_cache_.data_ptr();
- const double *go_ptr = grad_output.data_ptr();
- const auto n = static_cast(grad_output.size());
+ const auto in_span = input_cache_.span();
+ const auto go_span = grad_output.span();
+ const auto n = grad_output.size();
+
+ Matrix grad_input(grad_output.rows(), grad_output.cols());
+ auto out_span = grad_input.span();
+
+ if (n >= SmartPolicy::PARALLEL_THRESHOLD) {
+ auto indices = std::views::iota(std::size_t{0}, n);
+ SmartPolicy::for_each(indices.begin(), indices.end(),
+ [in_span, go_span, out_span](std::size_t i) noexcept
+ { out_span[i] = in_span[i] > 0.0 ? go_span[i] : 0.0; });
+ } else {
+ for (std::size_t i = 0; i < n; ++i)
+ out_span[i] = in_span[i] > 0.0 ? go_span[i] : 0.0;
+ }
+ return grad_input;
+ }
+ };
+
+ class GeLU final : public Layer
+ {
+ private:
+ Matrix input_cache_;
+ Matrix sigmoid_cache_; // ็ผๅญ sigmoid(1.702 * x) ็จไบๅๅไผ ๆญ
+ static constexpr double BETA = 1.702;
+
+ public:
+ GeLU() = default;
+
+ // QuickGeLU: x * sigmoid(1.702 * x)
+ Matrix forward(const Matrix &input) override
+ {
+ const auto n = input.size();
+ input_cache_ = input;
+ sigmoid_cache_.resize(input.rows(), input.cols());
+
+ const auto in_span = input.span();
+ auto sig_span = sigmoid_cache_.span();
+
+ Matrix result(input.rows(), input.cols());
+ auto out_span = result.span();
+
+ if (n >= SmartPolicy::PARALLEL_THRESHOLD) {
+ auto indices = std::views::iota(std::size_t{0}, n);
+ SmartPolicy::for_each(indices.begin(), indices.end(),
+ [in_span, out_span, sig_span](std::size_t i) noexcept
+ {
+ double sigmoid_input = BETA * in_span[i];
+ double sigmoid_val = 1.0 / (1.0 + std::exp(-sigmoid_input));
+ sig_span[i] = sigmoid_val;
+ out_span[i] = in_span[i] * sigmoid_val;
+ });
+ } else {
+ for (std::size_t i = 0; i < n; ++i) {
+ double sigmoid_input = BETA * in_span[i];
+ double sigmoid_val = 1.0 / (1.0 + std::exp(-sigmoid_input));
+ sig_span[i] = sigmoid_val;
+ out_span[i] = in_span[i] * sigmoid_val;
+ }
+ }
+ return result;
+ }
+
+ // d/dx [x * sigmoid(ฮฒx)] = sigmoid(ฮฒx) + x * ฮฒ * sigmoid(ฮฒx) * (1 - sigmoid(ฮฒx))
+ // = sigmoid(ฮฒx) * [1 + ฮฒx * (1 - sigmoid(ฮฒx))]
+ Matrix backward(const Matrix &grad_output) override
+ {
+ if (input_cache_.rows() != grad_output.rows() || input_cache_.cols() != grad_output.cols())
+ throw std::invalid_argument("gelu backward shape mismatch");
+
+ const auto in_span = input_cache_.span();
+ const auto sig_span = sigmoid_cache_.span();
+ const auto go_span = grad_output.span();
+ const auto n = grad_output.size();
Matrix grad_input(grad_output.rows(), grad_output.cols());
- double *out_ptr = grad_input.data_ptr();
+ auto out_span = grad_input.span();
if (n >= SmartPolicy::PARALLEL_THRESHOLD) {
- auto indices = std::views::iota(0LL, n);
+ auto indices = std::views::iota(std::size_t{0}, n);
SmartPolicy::for_each(indices.begin(), indices.end(),
- [in_ptr, go_ptr, out_ptr](long long i) noexcept
- { out_ptr[i] = in_ptr[i] > 0.0 ? go_ptr[i] : 0.0; });
+ [in_span, sig_span, go_span, out_span](std::size_t i) noexcept
+ {
+ double s = sig_span[i];
+ out_span[i] = go_span[i] * s * (1.0 + BETA * in_span[i] * (1.0 - s));
+ });
+ } else {
+ for (std::size_t i = 0; i < n; ++i) {
+ double s = sig_span[i];
+ out_span[i] = go_span[i] * s * (1.0 + BETA * in_span[i] * (1.0 - s));
+ }
+ }
+ return grad_input;
+ }
+ };
+
+ class LayerNorm final : public Layer
+ {
+ private:
+ std::size_t normalized_shape_;
+ double epsilon_;
+
+ // ๅฏๅญฆไน ๅๆฐ
+ Matrix gamma_; // ็ผฉๆพๅๆฐ (normalized_shape_, 1)
+ Matrix beta_; // ๅ็งปๅๆฐ (normalized_shape_, 1)
+ Matrix grad_gamma_; // gamma ๆขฏๅบฆ
+ Matrix grad_beta_; // beta ๆขฏๅบฆ
+
+ // ็ผๅญ็จไบๅๅไผ ๆญ
+ Matrix input_cache_;
+ Matrix normalized_cache_; // ๅฝไธๅๅ็ๅผ
+ Matrix std_cache_; // ๆ ๅๅทฎๅๆฐ (1/sqrt(ฯยฒ + ฮต))
+ Matrix mean_cache_; // ๅๅผ
+
+ // ๆฐๅผ็จณๅฎๆง
+ static constexpr double EPSILON = 1e-5;
+
+ public:
+ explicit LayerNorm(std::size_t normalized_shape, double epsilon = EPSILON)
+ : normalized_shape_(normalized_shape),
+ epsilon_(epsilon),
+ gamma_(normalized_shape, 1, 1.0), // ๅๅงๅไธบ1
+ beta_(normalized_shape, 1, 0.0), // ๅๅงๅไธบ0
+ grad_gamma_(normalized_shape, 1),
+ grad_beta_(normalized_shape, 1)
+ {
+ // ๅๆฐๅๅงๅๅทฒๅจๆ้ ๅฝๆฐไธญๅฎๆ
+ }
+
+ std::vector> parameters() override
+ {
+ return {std::ref(gamma_), std::ref(beta_)};
+ }
+
+ std::vector> param_gradients() override
+ {
+ return {std::ref(grad_gamma_), std::ref(grad_beta_)};
+ }
+
+ Matrix forward(const Matrix &input) override
+ {
+ // ่พๅ
ฅๅฝข็ถ: (normalized_shape_, batch_size)
+ if (input.rows() != normalized_shape_)
+ throw std::invalid_argument("layer_norm forward input shape mismatch");
+
+ input_cache_ = input;
+ const std::size_t batch_size = input.cols();
+ const std::size_t features = input.rows();
+
+ // ็ผๅญไธญ้ด็ปๆ๏ผๅค็จๅทฒๆๅ
ๅญ๏ผ
+ mean_cache_.resize(1, batch_size);
+ std_cache_.resize(1, batch_size);
+ normalized_cache_.resize(features, batch_size);
+
+ Matrix result(features, batch_size);
+
+ const auto in_span = input.span();
+ const auto gamma_span = gamma_.span();
+ const auto beta_span = beta_.span();
+ auto mean_span = mean_cache_.span();
+ auto std_span = std_cache_.span();
+ auto norm_span = normalized_cache_.span();
+ auto res_span = result.span();
+
+ // ๅฏนๆฏไธชๆ ทๆฌ๏ผๅ๏ผ็ฌ็ซๅค็
+ auto batch_indices = std::views::iota(std::size_t{0}, batch_size);
+
+ if (batch_size >= SmartPolicy::PARALLEL_THRESHOLD) {
+ SmartPolicy::for_each(batch_indices.begin(), batch_indices.end(),
+ [in_span, gamma_span, beta_span, mean_span, std_span, norm_span, res_span,
+ features, batch_size, epsilon = epsilon_](std::size_t b) noexcept
+ {
+ // ่ฎก็ฎๅๅผ
+ double sum = 0.0;
+ for (std::size_t f = 0; f < features; ++f)
+ sum += in_span[f * batch_size + b];
+ double mean = sum / static_cast(features);
+ mean_span[b] = mean;
+
+ // ่ฎก็ฎๆนๅทฎ
+ double var_sum = 0.0;
+ for (std::size_t f = 0; f < features; ++f) {
+ double diff = in_span[f * batch_size + b] - mean;
+ var_sum += diff * diff;
+ }
+ double variance = var_sum / static_cast(features);
+ double std_inv = 1.0 / std::sqrt(variance + epsilon);
+ std_span[b] = std_inv;
+
+ // ๅฝไธๅๅไปฟๅฐๅๆข
+ for (std::size_t f = 0; f < features; ++f) {
+ double normalized = (in_span[f * batch_size + b] - mean) * std_inv;
+ norm_span[f * batch_size + b] = normalized;
+ res_span[f * batch_size + b] = gamma_span[f] * normalized + beta_span[f];
+ }
+ });
+ } else {
+ for (std::size_t b = 0; b < batch_size; ++b) {
+ // ่ฎก็ฎๅๅผ
+ double sum = 0.0;
+ for (std::size_t f = 0; f < features; ++f)
+ sum += in_span[f * batch_size + b];
+ double mean = sum / static_cast(features);
+ mean_span[b] = mean;
+
+ // ่ฎก็ฎๆนๅทฎ
+ double var_sum = 0.0;
+ for (std::size_t f = 0; f < features; ++f) {
+ double diff = in_span[f * batch_size + b] - mean;
+ var_sum += diff * diff;
+ }
+ double variance = var_sum / static_cast(features);
+ double std_inv = 1.0 / std::sqrt(variance + epsilon_);
+ std_span[b] = std_inv;
+
+ // ๅฝไธๅๅไปฟๅฐๅๆข
+ for (std::size_t f = 0; f < features; ++f) {
+ double normalized = (in_span[f * batch_size + b] - mean) * std_inv;
+ norm_span[f * batch_size + b] = normalized;
+ res_span[f * batch_size + b] = gamma_span[f] * normalized + beta_span[f];
+ }
+ }
+ }
+
+ return result;
+ }
+
+ Matrix backward(const Matrix &grad_output) override
+ {
+ if (grad_output.rows() != normalized_shape_)
+ throw std::invalid_argument("layer_norm backward shape mismatch");
+
+ const std::size_t features = normalized_shape_;
+ const std::size_t batch_size = grad_output.cols();
+
+ Matrix grad_input(features, batch_size);
+
+ const auto go_span = grad_output.span();
+ const auto norm_span = normalized_cache_.span();
+ const auto std_span = std_cache_.span();
+ const auto gamma_span = gamma_.span();
+ auto gi_span = grad_input.span();
+ auto gg_span = grad_gamma_.span();
+ auto gb_span = grad_beta_.span();
+
+ // ่ฎก็ฎๆขฏๅบฆ
+ auto batch_indices = std::views::iota(std::size_t{0}, batch_size);
+
+ if (batch_size >= SmartPolicy::PARALLEL_THRESHOLD) {
+ // ๅ
ๅนถ่ก่ฎก็ฎๆฏไธชๆ ทๆฌ็ dL/dx๏ผๆ ็ซไบ๏ผ๏ผ
+ // ๅไธฒ่ก็ดฏๅ dL/dฮณ ๅ dL/dฮฒ๏ผ้ฟๅ
ๆฐๆฎ็ซไบ๏ผ
+ SmartPolicy::for_each(batch_indices.begin(), batch_indices.end(),
+ [go_span, norm_span, std_span, gamma_span, gi_span,
+ features, batch_size](std::size_t b) noexcept
+ {
+ double std_inv = std_span[b];
+ double sum_grad = 0.0;
+ double sum_grad_norm = 0.0;
+ for (std::size_t f = 0; f < features; ++f) {
+ double g = go_span[f * batch_size + b] * gamma_span[f];
+ sum_grad += g;
+ sum_grad_norm += g * norm_span[f * batch_size + b];
+ }
+ const double inv_features = 1.0 / static_cast(features);
+ for (std::size_t f = 0; f < features; ++f) {
+ double grad_out = go_span[f * batch_size + b];
+ double g = grad_out * gamma_span[f];
+ gi_span[f * batch_size + b] = (g - sum_grad * inv_features -
+ norm_span[f * batch_size + b] * sum_grad_norm * inv_features) * std_inv;
+ }
+ });
+ // ไธฒ่ก็ดฏๅ dL/dฮณ ๅ dL/dฮฒ
+ for (std::size_t b = 0; b < batch_size; ++b) {
+ for (std::size_t f = 0; f < features; ++f) {
+ double grad_out = go_span[f * batch_size + b];
+ gg_span[f] += grad_out * norm_span[f * batch_size + b];
+ gb_span[f] += grad_out;
+ }
+ }
} else {
- for (long long i = 0; i < n; ++i)
- out_ptr[i] = in_ptr[i] > 0.0 ? go_ptr[i] : 0.0;
+ for (std::size_t b = 0; b < batch_size; ++b) {
+ double std_inv = std_span[b];
+
+ // ้ข่ฎก็ฎ็ป่ฎก้๏ผO(N) ่้ O(Nยฒ)
+ double sum_grad = 0.0;
+ double sum_grad_norm = 0.0;
+ for (std::size_t f = 0; f < features; ++f) {
+ double g = go_span[f * batch_size + b] * gamma_span[f];
+ sum_grad += g;
+ sum_grad_norm += g * norm_span[f * batch_size + b];
+ }
+
+ // ๅๆฌก้ๅ๏ผๅๆถ่ฎก็ฎ dL/dฮณใdL/dฮฒใdL/dx
+ const double inv_features = 1.0 / static_cast(features);
+ for (std::size_t f = 0; f < features; ++f) {
+ double grad_out = go_span[f * batch_size + b];
+ double g = grad_out * gamma_span[f];
+ gg_span[f] += grad_out * norm_span[f * batch_size + b];
+ gb_span[f] += grad_out;
+ gi_span[f * batch_size + b] = (g - sum_grad * inv_features -
+ norm_span[f * batch_size + b] * sum_grad_norm * inv_features) * std_inv;
+ }
+ }
+ }
+
+ return grad_input;
+ }
+
+ };
+
+ // โโ Softmax ๆฟๆดปๅฑ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ // ๅฏนๆฏไธ่ก็ฌ็ซๅบ็จ softmax๏ผout[i][j] = exp(in[i][j]) / ฮฃ_k exp(in[i][k])
+ // ่พๅ
ฅๅฝข็ถ: (rows, cols)๏ผ่พๅบๅฝข็ถ็ธๅใ
+ // ไฝฟ็จๆๅคงๅผๅๆณไฟ่ฏๆฐๅผ็จณๅฎๆงใ
+ // ๅๅไผ ๆญ: grad[i][j] = out[i][j] * (grad_out[i][j] - ฮฃ_k out[i][k] * grad_out[i][k])
+ // โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ class Softmax final : public Layer
+ {
+ private:
+ Matrix output_cache_;
+
+ public:
+ Softmax() = default;
+
+ Matrix forward(const Matrix &input) override
+ {
+ const std::size_t rows = input.rows();
+ const std::size_t cols = input.cols();
+ output_cache_.resize(rows, cols);
+
+ Matrix result(rows, cols);
+ const auto in_span = input.span();
+ auto out_span = result.span();
+ auto cache_span = output_cache_.span();
+
+ auto row_indices = std::views::iota(std::size_t{0}, rows);
+ const std::size_t total = rows * cols;
+
+ auto process_row = [in_span, out_span, cache_span, cols](std::size_t r) noexcept
+ {
+ const std::size_t offset = r * cols;
+ const auto row_in = in_span.subspan(offset, cols);
+ auto row_out = out_span.subspan(offset, cols);
+ auto row_cache = cache_span.subspan(offset, cols);
+
+ // ๆฐๅผ็จณๅฎ๏ผๅๅป่กๅ
ๆๅคงๅผ
+ double max_val = row_in[0];
+ for (std::size_t c = 1; c < cols; ++c)
+ max_val = std::max(max_val, row_in[c]);
+
+ // ่ฎก็ฎ exp ๅๆฑๅ
+ double sum = 0.0;
+ for (std::size_t c = 0; c < cols; ++c)
+ {
+ double e = std::exp(row_in[c] - max_val);
+ row_out[c] = e;
+ row_cache[c] = e;
+ sum += e;
+ }
+
+ // ๅฝไธๅ
+ const double inv_sum = 1.0 / sum;
+ for (std::size_t c = 0; c < cols; ++c)
+ {
+ row_out[c] *= inv_sum;
+ row_cache[c] *= inv_sum;
+ }
+ };
+
+ if (total >= SmartPolicy::PARALLEL_THRESHOLD)
+ SmartPolicy::for_each(row_indices.begin(), row_indices.end(), process_row);
+ else
+ for (std::size_t r = 0; r < rows; ++r)
+ process_row(r);
+
+ return result;
+ }
+
+ Matrix backward(const Matrix &grad_output) override
+ {
+ const std::size_t rows = output_cache_.rows();
+ const std::size_t cols = output_cache_.cols();
+
+ if (grad_output.rows() != rows || grad_output.cols() != cols)
+ throw std::invalid_argument("softmax backward shape mismatch");
+
+ Matrix grad_input(rows, cols);
+
+ const auto go_span = grad_output.span();
+ const auto out_span = output_cache_.span();
+ auto gi_span = grad_input.span();
+
+ auto row_indices = std::views::iota(std::size_t{0}, rows);
+ const std::size_t total = rows * cols;
+
+ auto process_row = [go_span, out_span, gi_span, cols](std::size_t r) noexcept
+ {
+ const std::size_t offset = r * cols;
+ const auto row_go = go_span.subspan(offset, cols);
+ const auto row_out = out_span.subspan(offset, cols);
+ auto row_gi = gi_span.subspan(offset, cols);
+
+ // dot = ฮฃ_k out[k] * grad_out[k]
+ double dot = 0.0;
+ for (std::size_t c = 0; c < cols; ++c)
+ dot += row_out[c] * row_go[c];
+
+ // grad = out * (grad_out - dot)
+ for (std::size_t c = 0; c < cols; ++c)
+ row_gi[c] = row_out[c] * (row_go[c] - dot);
+ };
+
+ if (total >= SmartPolicy::PARALLEL_THRESHOLD)
+ SmartPolicy::for_each(row_indices.begin(), row_indices.end(), process_row);
+ else
+ for (std::size_t r = 0; r < rows; ++r)
+ process_row(r);
+
+ return grad_input;
+ }
+ };
+
+ // โโ ๅคๅคดๆณจๆๅๅฑ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ // ๆฅๆบ: "Attention Is All You Need" (Vaswani et al., 2017)
+ //
+ // MultiHead(Q, K, V) = Concat(head_1, ..., head_h) W^O
+ // head_i = Attention(QW_i^Q, KW_i^K, VW_i^V)
+ // Attention(Q, K, V) = softmax(QK^T / โd_k) V
+ //
+ // ่พๅ
ฅๅฝข็ถ: (d_model, seq_len)๏ผ่พๅบๅฝข็ถ: (d_model, seq_len)
+ // โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ class MultiHeadAttention final : public Layer
+ {
+ private:
+ std::size_t d_model_;
+ std::size_t num_heads_;
+ std::size_t d_k_;
+ double scale_; // 1.0 / sqrt(d_k)
+
+ // ๆๅฝฑๅฑ
+ Linear W_q_;
+ Linear W_k_;
+ Linear W_v_;
+ Linear W_o_;
+
+ // ๅๅไผ ๆญ็ผๅญ
+ Matrix Q_cache_; // (d_model, seq_len)
+ Matrix K_cache_; // (d_model, seq_len)
+ Matrix V_cache_; // (d_model, seq_len)
+ std::vector attn_; // num_heads_ ร (seq_len, seq_len) โ softmax ่พๅบ
+
+ // Per-head ๅ็็ผๅญ๏ผforward ๆๅ๏ผbackward ไฝฟ็จ๏ผ
+ std::vector Q_heads_; // num_heads_ ร (d_k_, seq_len)
+ std::vector K_heads_;
+ std::vector V_heads_;
+ std::vector O_heads_; // num_heads_ ร (d_k_, seq_len) โ ๅๅ่พๅบ็ผๅญ
+
+ // ่พ
ๅฉ็ผๅฒๅบ๏ผ้ฟๅ
ๅพช็ฏๅ
้ๅคๅ้
๏ผ
+ Matrix grad_scores_buf_; // (seq_len, seq_len)
+ Matrix grad_A_buf_; // (seq_len, seq_len)
+ Matrix grad_O_h_buf_; // (d_k_, seq_len) โ backward ไธญๅค็จ
+ Softmax softmax_; // ๅค็จ๏ผ้ฟๅ
ๆฏๅคดๆ้ /ๆๆ
+
+ // โโ ไป็ฉ้ตไธญๆๅ่กๅ็ๅฐ dst โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ static void extract_rows(const Matrix &src, std::size_t row_start,
+ std::size_t row_count, Matrix &dst)
+ {
+ const std::size_t cols = src.cols();
+ dst.resize(row_count, cols);
+ const auto src_span = src.span();
+ auto dst_span = dst.span();
+ for (std::size_t r = 0; r < row_count; ++r)
+ std::copy_n(src_span.begin() + (row_start + r) * cols, cols, dst_span.begin() + r * cols);
+ }
+
+ // โโ ๅฐ src ๅๅ
ฅ dst ็ๆๅฎ่ก่ๅด โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ static void insert_rows(Matrix &dst, std::size_t row_start,
+ const Matrix &src)
+ {
+ const std::size_t row_count = src.rows();
+ const std::size_t cols = src.cols();
+ auto dst_span = dst.span();
+ const auto src_span = src.span();
+ for (std::size_t r = 0; r < row_count; ++r)
+ std::copy_n(src_span.begin() + r * cols, cols, dst_span.begin() + (row_start + r) * cols);
+ }
+
+ // โโ ้ๅ
็ด ็ผฉๆพ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ static void scale_inplace(Matrix &m, double s)
+ {
+ auto m_span = m.span();
+ const auto n = m.size();
+ if (n >= SmartPolicy::PARALLEL_THRESHOLD)
+ {
+ auto indices = std::views::iota(std::size_t{0}, n);
+ SmartPolicy::for_each(indices.begin(), indices.end(),
+ [m_span, s](std::size_t i) noexcept { m_span[i] *= s; });
+ }
+ else
+ {
+ for (std::size_t i = 0; i < n; ++i)
+ m_span[i] *= s;
+ }
+ }
+
+ public:
+ MultiHeadAttention(std::size_t d_model, std::size_t num_heads)
+ : d_model_(d_model),
+ num_heads_(num_heads),
+ d_k_(d_model / num_heads),
+ scale_(1.0 / std::sqrt(static_cast(d_model / num_heads))),
+ W_q_(d_model, d_model),
+ W_k_(d_model, d_model),
+ W_v_(d_model, d_model),
+ W_o_(d_model, d_model)
+ {
+ if (d_model % num_heads != 0)
+ throw std::invalid_argument("MultiHeadAttention: d_model must be divisible by num_heads");
+ }
+
+ std::vector> parameters() override
+ {
+ auto params = W_q_.parameters();
+ auto wk = W_k_.parameters();
+ auto wv = W_v_.parameters();
+ auto wo = W_o_.parameters();
+ params.insert(params.end(), wk.begin(), wk.end());
+ params.insert(params.end(), wv.begin(), wv.end());
+ params.insert(params.end(), wo.begin(), wo.end());
+ return params;
+ }
+
+ std::vector> param_gradients() override
+ {
+ auto grads = W_q_.param_gradients();
+ auto gk = W_k_.param_gradients();
+ auto gv = W_v_.param_gradients();
+ auto go = W_o_.param_gradients();
+ grads.insert(grads.end(), gk.begin(), gk.end());
+ grads.insert(grads.end(), gv.begin(), gv.end());
+ grads.insert(grads.end(), go.begin(), go.end());
+ return grads;
+ }
+
+ Matrix forward(const Matrix &input) override
+ {
+ if (input.rows() != d_model_)
+ throw std::invalid_argument("MultiHeadAttention forward input shape mismatch");
+
+ const std::size_t seq_len = input.cols();
+
+ // โโ 1. ็บฟๆงๆๅฝฑ Q, K, V โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ Q_cache_ = W_q_.forward(input);
+ K_cache_ = W_k_.forward(input);
+ V_cache_ = W_v_.forward(input);
+
+ // โโ 2. ๅ้
per-head ็ผๅฒๅบ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ Q_heads_.resize(num_heads_);
+ K_heads_.resize(num_heads_);
+ V_heads_.resize(num_heads_);
+ O_heads_.resize(num_heads_);
+ attn_.resize(num_heads_);
+
+ // โโ 3. ้ๅคด่ฎก็ฎ็ผฉๆพ็น็งฏๆณจๆๅ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ for (std::size_t h = 0; h < num_heads_; ++h)
+ {
+ const std::size_t row_start = h * d_k_;
+
+ // ๆๅ Q_h, K_h, V_h โ (d_k_, seq_len)
+ extract_rows(Q_cache_, row_start, d_k_, Q_heads_[h]);
+ extract_rows(K_cache_, row_start, d_k_, K_heads_[h]);
+ extract_rows(V_cache_, row_start, d_k_, V_heads_[h]);
+
+ // S_h = Q_h^T @ K_h โ (seq_len, seq_len)
+ Matrix QhT = Q_heads_[h].transpose();
+ attn_[h].resize(seq_len, seq_len);
+ QhT.multiply_to(attn_[h], K_heads_[h]);
+
+ // S_h *= 1/โd_k
+ scale_inplace(attn_[h], scale_);
+
+ // A_h = softmax(S_h) โ ๆ่ก softmax๏ผๅค็จๆๅ softmax_๏ผ
+ attn_[h] = softmax_.forward(attn_[h]);
+
+ // O_h = V_h @ A_h โ (d_k_, seq_len)๏ผ็ผๅญๅฐ O_heads_
+ O_heads_[h].resize(d_k_, seq_len);
+ V_heads_[h].multiply_to(O_heads_[h], attn_[h]);
+ }
+
+ // โโ 4. ๆผๆฅๆๆๅคด็่พๅบๅนถๆๅฝฑ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ Matrix output(d_model_, seq_len);
+ for (std::size_t h = 0; h < num_heads_; ++h)
+ insert_rows(output, h * d_k_, O_heads_[h]);
+
+ return W_o_.forward(output);
+ }
+
+ Matrix backward(const Matrix &grad_output) override
+ {
+ const std::size_t seq_len = grad_output.cols();
+
+ // โโ 1. ่พๅบๆๅฝฑๅๅ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ Matrix grad_concat = W_o_.backward(grad_output); // (d_model, seq_len)
+
+ // โโ 2. ๅๅงๅๅๅคดๆขฏๅบฆ็ดฏๅ ็ฉ้ต โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ Matrix grad_Q_all(d_model_, seq_len);
+ Matrix grad_K_all(d_model_, seq_len);
+ Matrix grad_V_all(d_model_, seq_len);
+ {
+ grad_Q_all.zero();
+ grad_K_all.zero();
+ grad_V_all.zero();
+ }
+
+ // โโ 3. ้ๅคด่ฎก็ฎๆณจๆๅๅๅ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ for (std::size_t h = 0; h < num_heads_; ++h)
+ {
+ const std::size_t row_start = h * d_k_;
+ const Matrix &Q_h = Q_heads_[h]; // (d_k_, seq_len)
+ const Matrix &K_h = K_heads_[h]; // (d_k_, seq_len)
+ const Matrix &V_h = V_heads_[h]; // (d_k_, seq_len)
+ const Matrix &A_h = attn_[h]; // (seq_len, seq_len)
+
+ // ๆๅ grad_O_h๏ผๅค็จๆๅ็ผๅฒๅบ๏ผ
+ extract_rows(grad_concat, row_start, d_k_, grad_O_h_buf_);
+
+ // grad_V_h = grad_O_h @ A_h^T โ (d_k_, seq_len)
+ Matrix AhT = A_h.transpose();
+ Matrix grad_V_h(d_k_, seq_len);
+ grad_O_h_buf_.multiply_to(grad_V_h, AhT);
+
+ // grad_A_h = V_h^T @ grad_O_h โ (seq_len, seq_len)
+ Matrix VhT = V_h.transpose();
+ grad_A_buf_.resize(seq_len, seq_len);
+ VhT.multiply_to(grad_A_buf_, grad_O_h_buf_);
+
+ // grad_S_h = A_h โ (grad_A_h - row_sum(A_h โ grad_A_h))
+ grad_scores_buf_.resize(seq_len, seq_len);
+ {
+ const auto a_span = A_h.span();
+ const auto ga_span = grad_A_buf_.span();
+ auto gs_span = grad_scores_buf_.span();
+
+ for (std::size_t i = 0; i < seq_len; ++i)
+ {
+ double dot = 0.0;
+ for (std::size_t j = 0; j < seq_len; ++j)
+ dot += a_span[i * seq_len + j] * ga_span[i * seq_len + j];
+ for (std::size_t j = 0; j < seq_len; ++j)
+ gs_span[i * seq_len + j] =
+ a_span[i * seq_len + j] * (ga_span[i * seq_len + j] - dot);
+ }
+ }
+
+ // grad_Q_h = K_h @ grad_S_h^T * scale โ (d_k_, seq_len)
+ Matrix gsT = grad_scores_buf_.transpose();
+ Matrix grad_Q_h(d_k_, seq_len);
+ K_h.multiply_to(grad_Q_h, gsT);
+ scale_inplace(grad_Q_h, scale_);
+
+ // grad_K_h = Q_h @ grad_S_h * scale โ (d_k_, seq_len)
+ Matrix grad_K_h(d_k_, seq_len);
+ Q_h.multiply_to(grad_K_h, grad_scores_buf_);
+ scale_inplace(grad_K_h, scale_);
+
+ // ็ดฏๅ ๅฐๅ
จๅฑๆขฏๅบฆ๏ผๆฏไธชๅคดๅๅ
ฅไธๅ่ก๏ผๆ ้ๅ ๏ผ
+ insert_rows(grad_Q_all, row_start, grad_Q_h);
+ insert_rows(grad_K_all, row_start, grad_K_h);
+ insert_rows(grad_V_all, row_start, grad_V_h);
+ }
+
+ // โโ 4. ๆๅฝฑๅฑๅๅ๏ผ็ดฏๅ ่พๅ
ฅๆขฏๅบฆ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ Matrix grad_input = W_q_.backward(grad_Q_all);
+ grad_input = grad_input + W_k_.backward(grad_K_all);
+ grad_input = grad_input + W_v_.backward(grad_V_all);
+
+ return grad_input;
+ }
+ };
+
+ // โโ ๆญฃๅผฆๆณขๅบๅฎไฝ็ฝฎ็ผ็ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ // ๆฅๆบ: "Attention Is All You Need" (Vaswani et al., 2017)
+ //
+ // PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
+ // PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
+ //
+ // ่พๅ
ฅๅฝข็ถ: (d_model, seq_len)๏ผ่พๅบๅฝข็ถ็ธๅใ
+ // ็ผ็ ็ฉ้ตๅจๆ้ ๆถไธๆฌกๆง้ข่ฎก็ฎ๏ผๅๅไผ ๆญไป
ๅ้ๅ
็ด ๅ ๆณใ
+ // ๅๅไผ ๆญ็ดๆฅ็ฉฟ้๏ผ็ผ็ ไธๅฏๅญฆไน ๏ผใ
+ // โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ class PositionalEncoding final : public Layer
+ {
+ private:
+ std::size_t d_model_;
+ std::size_t max_len_;
+ Matrix encoding_; // (d_model, max_len) โ ้ข่ฎก็ฎ็ๆญฃๅผฆๆณข็ผ็
+
+ public:
+ PositionalEncoding(std::size_t d_model, std::size_t max_len = 5000)
+ : d_model_(d_model),
+ max_len_(max_len),
+ encoding_(d_model, max_len)
+ {
+ // โโ ไธๆฌกๆง้ข่ฎก็ฎ้ข็ไธ็ผ็ โโ
+ // ๅ
่ฎก็ฎๆฏไธช็นๅพๅฏน็่ง้ข็๏ผ้ฟๅ
ๅจ position ๅพช็ฏไธญ้ๅค pow๏ผ
+ const std::size_t half = d_model / 2;
+ std::vector freqs(half);
+ for (std::size_t i = 0; i < half; ++i)
+ freqs[i] = 1.0 / std::pow(10000.0, static_cast(2 * i) / d_model);
+
+ auto e_span = encoding_.span();
+ for (std::size_t pos = 0; pos < max_len; ++pos)
+ {
+ const double pos_d = static_cast(pos);
+ for (std::size_t i = 0; i < half; ++i)
+ {
+ const double angle = pos_d * freqs[i];
+ e_span[(2 * i) * max_len + pos] = std::sin(angle);
+ e_span[(2 * i + 1) * max_len + pos] = std::cos(angle);
+ }
+ // ๅฅๆฐ็ปดๅบฆ๏ผๆๅไธไธช็นๅพไป
ไฝฟ็จ sin
+ if (d_model % 2 == 1)
+ {
+ const double freq_last = 1.0 / std::pow(10000.0, static_cast(2 * half) / d_model);
+ const double angle = pos_d * freq_last;
+ e_span[(d_model - 1) * max_len + pos] = std::sin(angle);
+ }
+ }
+ }
+
+ std::vector> parameters() override { return {}; }
+ std::vector> param_gradients() override { return {}; }
+
+ Matrix forward(const Matrix &input) override
+ {
+ if (input.rows() != d_model_)
+ throw std::invalid_argument("positional encoding forward: d_model mismatch");
+
+ const std::size_t seq_len = input.cols();
+
+ if (seq_len > max_len_)
+ throw std::invalid_argument("positional encoding forward: sequence length exceeds max_len");
+
+ // result = input + encoding[:, 0:seq_len]
+ Matrix result(d_model_, seq_len);
+ const auto in_span = input.span();
+ const auto e_span = encoding_.span();
+ auto out_span = result.span();
+
+ const auto total = static_cast(d_model_ * seq_len);
+ const std::size_t max_len = max_len_;
+ auto indices = std::views::iota(std::size_t{0}, total);
+ SmartPolicy::for_each(indices.begin(), indices.end(),
+ [in_span, e_span, out_span, seq_len, max_len](std::size_t idx) noexcept
+ {
+ // idx = row * seq_len + col
+ const std::size_t row = idx / seq_len;
+ const std::size_t col = idx % seq_len;
+ out_span[idx] = in_span[idx] + e_span[row * max_len + col];
+ });
+
+ return result;
+ }
+
+ // ไฝ็ฝฎ็ผ็ ไธบๅบๅฎๅผ๏ผๆขฏๅบฆ็ดๆฅ็ฉฟ้
+ Matrix backward(const Matrix &grad_output) override { return grad_output; }
+ };
+
+ // โโ ๅ้ฆ็ฝ็ป (Feed-Forward Network) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ // FFN(x) = Linear(d_ff โ d_model)(GeLU(Linear(d_model โ d_ff)(x)))
+ // ่พๅ
ฅ/่พๅบๅฝข็ถ: (d_model, seq_len)
+ class FeedForward final : public Layer
+ {
+ private:
+ Linear fc1_; // (d_ff, d_model)
+ Linear fc2_; // (d_model, d_ff)
+ GeLU activation_;
+ Matrix input_cache_;
+
+ public:
+ FeedForward(std::size_t d_model, std::size_t d_ff)
+ : fc1_(d_model, d_ff), fc2_(d_ff, d_model)
+ {}
+
+ std::vector> parameters() override
+ {
+ auto params = fc1_.parameters();
+ auto p2 = fc2_.parameters();
+ params.insert(params.end(), p2.begin(), p2.end());
+ return params;
+ }
+
+ std::vector> param_gradients() override
+ {
+ auto grads = fc1_.param_gradients();
+ auto g2 = fc2_.param_gradients();
+ grads.insert(grads.end(), g2.begin(), g2.end());
+ return grads;
+ }
+
+ Matrix forward(const Matrix &input) override
+ {
+ input_cache_ = input;
+ auto x = activation_.forward(fc1_.forward(input));
+ return fc2_.forward(x);
+ }
+
+ Matrix backward(const Matrix &grad_output) override
+ {
+ auto grad = fc2_.backward(grad_output);
+ grad = activation_.backward(grad);
+ return fc1_.backward(grad);
+ }
+ };
+
+ // โโ Transformer ็ผ็ ๅจๅฑ (Pre-Norm ๆถๆ) โโโโโโโโโโโโโโโโโโโโโโโโโโ
+ // x = x + SelfAttn(LayerNormโ(x)) โ ๆฎๅทฎ่ฟๆฅ
+ // x = x + FFN(LayerNormโ(x)) โ ๆฎๅทฎ่ฟๆฅ
+ // ่พๅ
ฅ/่พๅบๅฝข็ถ: (d_model, seq_len)
+ class TransformerEncoderLayer final : public Layer
+ {
+ private:
+ MultiHeadAttention self_attn_;
+ LayerNorm norm1_;
+ FeedForward ff_;
+ LayerNorm norm2_;
+
+ // โโ ๅๅไผ ๆญ็ผๅญ โโ
+ Matrix residual1_cache_; // ็ฌฌไธไธชๆฎๅทฎ่ฟๆฅๅ็ๅๅง่พๅ
ฅ
+ Matrix residual2_cache_; // ็ฌฌไบไธชๆฎๅทฎ่ฟๆฅๅ็่พๅ
ฅ (= residual1)
+
+ public:
+ TransformerEncoderLayer(std::size_t d_model, std::size_t num_heads, std::size_t d_ff)
+ : self_attn_(d_model, num_heads),
+ norm1_(d_model),
+ ff_(d_model, d_ff),
+ norm2_(d_model)
+ {}
+
+ std::vector> parameters() override
+ {
+ auto params = self_attn_.parameters();
+ auto n1 = norm1_.parameters();
+ auto f = ff_.parameters();
+ auto n2 = norm2_.parameters();
+ params.insert(params.end(), n1.begin(), n1.end());
+ params.insert(params.end(), f.begin(), f.end());
+ params.insert(params.end(), n2.begin(), n2.end());
+ return params;
+ }
+
+ std::vector> param_gradients() override
+ {
+ auto grads = self_attn_.param_gradients();
+ auto gn1 = norm1_.param_gradients();
+ auto gf = ff_.param_gradients();
+ auto gn2 = norm2_.param_gradients();
+ grads.insert(grads.end(), gn1.begin(), gn1.end());
+ grads.insert(grads.end(), gf.begin(), gf.end());
+ grads.insert(grads.end(), gn2.begin(), gn2.end());
+ return grads;
+ }
+
+ Matrix forward(const Matrix &input) override
+ {
+ // ๅญๅฑ1: Self-Attention + ๆฎๅทฎ
+ residual1_cache_ = input;
+ auto norm1_out = norm1_.forward(input);
+ auto attn_out = self_attn_.forward(norm1_out);
+ residual2_cache_ = input + attn_out; // ๆฎๅทฎ่ฟๆฅ
+
+ // ๅญๅฑ2: FFN + ๆฎๅทฎ
+ auto norm2_out = norm2_.forward(residual2_cache_);
+ auto ff_out = ff_.forward(norm2_out);
+ return residual2_cache_ + ff_out;
+ }
+
+ Matrix backward(const Matrix &grad_output) override
+ {
+ // โโ ๅๅไผ ๆญ็ฌฌ2ไธชๆฎๅทฎ่ฟๆฅ: grad_r2 = grad_output (ๅๆตๅฐไธคๆก่ทฏๅพ) โโ
+ // ่ทฏๅพA: ็ดๆฅๆตๅ residual2 (= residual1)
+ // ่ทฏๅพB: ๆตๅ FFN
+ Matrix grad_residual1 = grad_output; // ่ทฏๅพA
+ Matrix grad_ff_out = grad_output; // ่ทฏๅพB
+
+ // ๅๅ FFN โ LayerNormโ
+ auto grad_norm2 = ff_.backward(grad_ff_out);
+ grad_residual1 = grad_residual1 + norm2_.backward(grad_norm2);
+
+ // โโ ๅๅไผ ๆญ็ฌฌ1ไธชๆฎๅทฎ่ฟๆฅ โโ
+ // ่ทฏๅพA: ็ดๆฅๆตๅ่พๅ
ฅ x
+ // ่ทฏๅพB: ๆตๅ Self-Attention
+ Matrix grad_input = grad_residual1; // ่ทฏๅพA
+ Matrix grad_attn_out = grad_residual1; // ่ทฏๅพB
+
+ // ๅๅ Self-Attention โ LayerNormโ
+ auto grad_norm1 = self_attn_.backward(grad_attn_out);
+ grad_input = grad_input + norm1_.backward(grad_norm1);
+
+ return grad_input;
+ }
+ };
+
+ // โโ Transformer ็ผ็ ๅจ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ // ๅ ๅ N ไธช TransformerEncoderLayer๏ผๅ
ๅซไฝ็ฝฎ็ผ็ + ๅ
จๅฑๅนณๅๆฑ ๅ
+ // ่พๅ
ฅ: (d_model ร num_patches, batch_size) โ PatchEmbedding ็่พๅบ
+ // ่พๅบ: (d_model, batch_size) โ ๆฑ ๅๅ็ๅบๅ่กจ็คบ
+ //
+ // ๅๅไผ ๆญ็ญ็ฅ: ๅฏนๆฏไธชๆ ทๆฌ re-forward ไปฅ้ๅปบๅ
้จ็ผๅญ (checkpointing)
+ // โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ class TransformerEncoder final : public Layer
+ {
+ private:
+ std::size_t d_model_;
+ std::size_t num_patches_;
+
+ std::vector layers_;
+ PositionalEncoding pos_encoding_;
+
+ // โโ ๅๅไผ ๆญ็ผๅญ: ๆฏไธชๆ ทๆฌๅจไฝ็ฝฎ็ผ็ ๅ็่พๅ
ฅ๏ผไพ re-forward ้ๅปบ๏ผ โโ
+ std::vector stored_inputs_; // [sample] โ ๆฏไธชๆ ทๆฌ PE ๅ็่พๅ
ฅ
+ std::size_t batch_size_{0};
+
+ public:
+ TransformerEncoder(std::size_t d_model, std::size_t num_heads,
+ std::size_t d_ff, std::size_t num_layers,
+ std::size_t num_patches)
+ : d_model_(d_model), num_patches_(num_patches),
+ pos_encoding_(d_model, num_patches)
+ {
+ for (std::size_t i = 0; i < num_layers; ++i)
+ layers_.emplace_back(d_model, num_heads, d_ff);
+ }
+
+ std::vector> parameters() override
+ {
+ std::vector> params;
+ for (auto &layer : layers_)
+ {
+ auto lp = layer.parameters();
+ params.insert(params.end(), lp.begin(), lp.end());
+ }
+ return params;
+ }
+
+ std::vector> param_gradients() override
+ {
+ std::vector> grads;
+ for (auto &layer : layers_)
+ {
+ auto lg = layer.param_gradients();
+ grads.insert(grads.end(), lg.begin(), lg.end());
+ }
+ return grads;
+ }
+
+ // โโ ๅๅไผ ๆญ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ Matrix forward(const Matrix &input) override
+ {
+ batch_size_ = input.cols();
+ stored_inputs_.resize(batch_size_);
+
+ Matrix output(d_model_, batch_size_);
+
+ for (std::size_t b = 0; b < batch_size_; ++b)
+ {
+ // ไปๅฑๅนณๅ้ไธญๆๅๆ ทๆฌ b โ (d_model, num_patches)
+ Matrix x(d_model_, num_patches_);
+ for (std::size_t r = 0; r < d_model_; ++r)
+ for (std::size_t c = 0; c < num_patches_; ++c)
+ x.set_value_unchecked(r, c,
+ input.at_unchecked(r * num_patches_ + c, b));
+
+ // ๆทปๅ ไฝ็ฝฎ็ผ็
+ x = pos_encoding_.forward(x);
+
+ // ็ผๅญ PE ๅ็่พๅ
ฅ๏ผไพ backward ไธญ re-forward ้ๅปบ็ผๅญ
+ stored_inputs_[b] = x;
+
+ // ไพๆฌก้่ฟ็ผ็ ๅจๅฑ
+ for (std::size_t l = 0; l < layers_.size(); ++l)
+ x = layers_[l].forward(x);
+
+ // ๅ
จๅฑๅนณๅๆฑ ๅ: (d_model, num_patches) โ (d_model,)
+ const double inv_n = 1.0 / static_cast(num_patches_);
+ for (std::size_t r = 0; r < d_model_; ++r)
+ {
+ double sum = 0.0;
+ for (std::size_t c = 0; c < num_patches_; ++c)
+ sum += x.at_unchecked(r, c);
+ output.set_value_unchecked(r, b, sum * inv_n);
+ }
+ }
+ return output;
+ }
+
+ // โโ ๅๅไผ ๆญ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ // ๅฏนๆฏไธชๆ ทๆฌ: re-forward ้ๅปบ็ผๅญ โ ๅๅไผ ๆญ โ ็ดฏๅ ๅๆฐๆขฏๅบฆ
+ Matrix backward(const Matrix &grad_output) override
+ {
+ Matrix grad_input(d_model_ * num_patches_, batch_size_);
+ grad_input.zero();
+
+ for (std::size_t b = 0; b < batch_size_; ++b)
+ {
+ // โโ Re-forward: ้ๅปบ่ฏฅๆ ทๆฌ็ๅ
จ้จ็ผๅญ โโ
+ // stored_inputs_[b] ๅทฒๅซไฝ็ฝฎ็ผ็ ๏ผๆ ้้ๅคๆฝๅ
+ Matrix x = stored_inputs_[b];
+ for (std::size_t l = 0; l < layers_.size(); ++l)
+ x = layers_[l].forward(x); // ้ๅปบๆฏๅฑ็ผๅญ
+
+ // โโ ๅ
จๅฑๅนณๅๆฑ ๅๆขฏๅบฆ: ๅฑๅผ โโ
+ const double inv_n = 1.0 / static_cast(num_patches_);
+ Matrix grad(d_model_, num_patches_);
+ for (std::size_t r = 0; r < d_model_; ++r)
+ {
+ double g = grad_output.at_unchecked(r, b) * inv_n;
+ for (std::size_t c = 0; c < num_patches_; ++c)
+ grad.set_value_unchecked(r, c, g);
+ }
+
+ // โโ ๅๅไผ ๆญ็ผ็ ๅจๅฑ โโ
+ for (int l = static_cast(layers_.size()) - 1; l >= 0; --l)
+ grad = layers_[l].backward(grad);
+
+ // โโ ๅๅ
ฅ grad_input โโ
+ for (std::size_t r = 0; r < d_model_; ++r)
+ for (std::size_t c = 0; c < num_patches_; ++c)
+ grad_input.set_value_unchecked(r * num_patches_ + c, b,
+ grad.at_unchecked(r, c));
+ }
+ return grad_input;
+ }
+ };
+
+ // โโ Patch ๅตๅ
ฅๅฑ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ // ๅฐๅฑๅนณ็ๅพๅๅๅฒไธบไธ้ๅ ็ patch ๅนถๆๅฝฑๅฐ d_model ็ปด็ฉบ้ด
+ // ่พๅ
ฅ: (img_sizeยฒ, batch_size) โ ๅฑๅนณ็ 28ร28 ๅพๅ
+ // ่พๅบ: (d_model ร num_patches, batch_size) โ ๅฑๅนณ็ patch ๅบๅ
+ // โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ class PatchEmbedding final : public Layer
+ {
+ private:
+ std::size_t img_size_;
+ std::size_t patch_size_;
+ std::size_t grid_size_; // img_size / patch_size
+ std::size_t num_patches_; // grid_sizeยฒ
+ std::size_t patch_dim_; // patch_sizeยฒ
+ std::size_t d_model_;
+ Linear projection_; // (patch_dim, d_model)
+ Matrix input_cache_;
+
+ public:
+ PatchEmbedding(std::size_t img_size, std::size_t patch_size, std::size_t d_model)
+ : img_size_(img_size), patch_size_(patch_size),
+ grid_size_(img_size / patch_size),
+ num_patches_((img_size / patch_size) * (img_size / patch_size)),
+ patch_dim_(patch_size * patch_size),
+ d_model_(d_model),
+ projection_(patch_dim_, d_model)
+ {
+ if (img_size % patch_size != 0)
+ throw std::invalid_argument(
+ "PatchEmbedding: img_size must be divisible by patch_size");
+ }
+
+ [[nodiscard]] std::size_t num_patches() const noexcept { return num_patches_; }
+ [[nodiscard]] std::size_t d_model() const noexcept { return d_model_; }
+
+ std::vector> parameters() override
+ {
+ return projection_.parameters();
+ }
+
+ std::vector> param_gradients() override
+ {
+ return projection_.param_gradients();
+ }
+
+ // โโ ๅๅไผ ๆญ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ // ๆน้ๆๅ patch ๅนถไธๆฌกๆงๆๅฝฑ
+ Matrix forward(const Matrix &input) override
+ {
+ input_cache_ = input;
+ const std::size_t batch = input.cols();
+
+ // Step 1: ๆๅๆๆ patch โ (patch_dim, num_patches ร batch)
+ // ๅธๅฑ: patch p ็ sample b ไฝไบๅ p ร batch + b
+ Matrix all_patches(patch_dim_, num_patches_ * batch);
+
+ for (std::size_t b = 0; b < batch; ++b)
+ {
+ for (std::size_t p = 0; p < num_patches_; ++p)
+ {
+ const std::size_t gr = (p / grid_size_) * patch_size_;
+ const std::size_t gc = (p % grid_size_) * patch_size_;
+ const std::size_t col_idx = p * batch + b;
+
+ for (std::size_t pr = 0; pr < patch_size_; ++pr)
+ {
+ for (std::size_t pc = 0; pc < patch_size_; ++pc)
+ {
+ const std::size_t flat = pr * patch_size_ + pc;
+ const std::size_t pix = (gr + pr) * img_size_ + (gc + pc);
+ all_patches.set_value_unchecked(flat, col_idx,
+ input.at_unchecked(pix, b));
+ }
+ }
+ }
+ }
+
+ // Step 2: ๆๅฝฑ โ (d_model, num_patches ร batch)
+ Matrix projected = projection_.forward(all_patches);
+
+ // Step 3: ้ๆไธบ (d_model ร num_patches, batch)
+ Matrix output(d_model_ * num_patches_, batch);
+ for (std::size_t r = 0; r < d_model_; ++r)
+ for (std::size_t p = 0; p < num_patches_; ++p)
+ for (std::size_t b = 0; b < batch; ++b)
+ output.set_value_unchecked(r * num_patches_ + p, b,
+ projected.at_unchecked(r, p * batch + b));
+
+ return output;
+ }
+
+ // โโ ๅๅไผ ๆญ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ Matrix backward(const Matrix &grad_output) override
+ {
+ const std::size_t batch = grad_output.cols();
+
+ // Step 1: ้ๆๆขฏๅบฆ โ (d_model, num_patches ร batch)
+ Matrix grad_projected(d_model_, num_patches_ * batch);
+ for (std::size_t r = 0; r < d_model_; ++r)
+ for (std::size_t p = 0; p < num_patches_; ++p)
+ for (std::size_t b = 0; b < batch; ++b)
+ grad_projected.set_value_unchecked(r, p * batch + b,
+ grad_output.at_unchecked(r * num_patches_ + p, b));
+
+ // Step 2: ๆๅฝฑๅฑๅๅ โ (patch_dim, num_patches ร batch)
+ Matrix grad_patches = projection_.backward(grad_projected);
+
+ // Step 3: ๆฃๅฐๆขฏๅบฆๅ่พๅ
ฅ โ (img_sizeยฒ, batch)
+ Matrix grad_input(img_size_ * img_size_, batch);
+ grad_input.zero();
+
+ for (std::size_t b = 0; b < batch; ++b)
+ {
+ for (std::size_t p = 0; p < num_patches_; ++p)
+ {
+ const std::size_t gr = (p / grid_size_) * patch_size_;
+ const std::size_t gc = (p % grid_size_) * patch_size_;
+ const std::size_t col_idx = p * batch + b;
+
+ for (std::size_t pr = 0; pr < patch_size_; ++pr)
+ {
+ for (std::size_t pc = 0; pc < patch_size_; ++pc)
+ {
+ const std::size_t flat = pr * patch_size_ + pc;
+ const std::size_t pix = (gr + pr) * img_size_ + (gc + pc);
+ const double val = grad_input.at_unchecked(pix, b)
+ + grad_patches.at_unchecked(flat, col_idx);
+ grad_input.set_value_unchecked(pix, b, val);
+ }
+ }
+ }
}
return grad_input;
}
diff --git a/include/neuralnet.cpp/loss.hpp b/include/neuralnet.cpp/loss.hpp
index dba3fdd3..bd31bc09 100644
--- a/include/neuralnet.cpp/loss.hpp
+++ b/include/neuralnet.cpp/loss.hpp
@@ -2,12 +2,14 @@
#define LOSS_HPP
#include
+#include
#include
+#include
#include
#include
-#include
-#include
+#include "nn_config.hpp"
+#include "matrix.hpp"
namespace nn
{
@@ -99,7 +101,13 @@ namespace nn
// ่ถ
่ฟ 128 ็ฑปๆถๆน็จ vector๏ผ้็ญ่ทฏๅพ๏ผๅผ้ๅฏๆฅๅ๏ผ
std::array exp_vals_fixed{};
std::vector exp_vals_heap;
- double* exp_vals = (classes <= 128) ? exp_vals_fixed.data() : (exp_vals_heap.resize(classes), exp_vals_heap.data());
+ std::span exp_vals;
+ if (classes <= 128) {
+ exp_vals = exp_vals_fixed;
+ } else {
+ exp_vals_heap.resize(classes);
+ exp_vals = exp_vals_heap;
+ }
double sum_exp = 0.0;
for (std::size_t c = 0; c < classes; ++c)
diff --git a/include/neuralnet.cpp/matrix.hpp b/include/neuralnet.cpp/matrix.hpp
index 7abfbad8..ff47cd44 100644
--- a/include/neuralnet.cpp/matrix.hpp
+++ b/include/neuralnet.cpp/matrix.hpp
@@ -10,11 +10,12 @@
#include
#include
#include
+#include
#include
#include
#include
-#include
+#include "nn_config.hpp"
namespace nn
{
@@ -71,8 +72,15 @@ namespace nn
data_.resize(rows * cols);
}
- // โโ ๅๅงๆ้่ฎฟ้ฎ๏ผไพ SIMD / ๅ
ๆ ธไฝฟ็จ๏ผ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ // โโ std::span ่ฎฟ้ฎ๏ผC++20 ็ฐไปฃๆฅๅฃ๏ผๆจ่ไฝฟ็จ๏ผ โโโโโโโโโโโโโโโโโโโโ
+ // ้ถๅผ้ๆฝ่ฑก๏ผ็ผ่ฏๅ็ญไปทไบ่ฃธๆ้ + ๅคงๅฐ๏ผๅฏๆฟไปฃๆๆ data_ptr() ๅบๆฏ
+ [[nodiscard]] std::span span() const noexcept { return {data_.data(), data_.size()}; }
+ [[nodiscard]] std::span span() noexcept { return {data_.data(), data_.size()}; }
+
+ // โโ ๅๅงๆ้่ฎฟ้ฎ๏ผไป
ไพ้่ฆๆ้็ฎๆฏ็ๅๅ็ฎๆณๅ
้จไฝฟ็จ๏ผ โโโโโโโโโโโโ
+ [[deprecated("use span() instead for safer access")]]
[[nodiscard]] const double *data_ptr() const noexcept { return data_.data(); }
+ [[deprecated("use span() instead for safer access")]]
[[nodiscard]] double *data_ptr() noexcept { return data_.data(); }
// ่ฎฟ้ฎๅจ
@@ -160,29 +168,42 @@ namespace nn
result.resize(cols_, rows_);
if (rows_ == 0 || cols_ == 0) return;
- const auto *src = data_ptr();
- auto *dst = result.data_ptr();
+ const auto src = span();
+ auto dst = result.span();
const std::size_t R = rows_;
const std::size_t C = cols_;
const std::size_t i_blocks = (R + BLOCK_SIZE - 1) / BLOCK_SIZE;
const std::size_t j_blocks = (C + BLOCK_SIZE - 1) / BLOCK_SIZE;
- auto block_indices = std::views::iota(std::size_t{0}, i_blocks * j_blocks);
-
- SmartPolicy::for_each(block_indices.begin(), block_indices.end(),
- [src, dst, R, C, j_blocks](std::size_t block_idx) noexcept
- {
- const std::size_t ib = block_idx / j_blocks;
- const std::size_t jb = block_idx % j_blocks;
- const std::size_t i0 = ib * BLOCK_SIZE;
- const std::size_t j0 = jb * BLOCK_SIZE;
- const std::size_t i1 = std::min(i0 + BLOCK_SIZE, R);
- const std::size_t j1 = std::min(j0 + BLOCK_SIZE, C);
- for (std::size_t i = i0; i < i1; ++i)
- for (std::size_t j = j0; j < j1; ++j)
- dst[j * R + i] = src[i * C + j];
- });
+ // ็ป่ฟ SmartPolicy๏ผๅๅๆฐๅพๅฐ๏ผ1~ๅ ๅ๏ผ๏ผไฝๅ
ถๅ
ๅพช็ฏ่ฎก็ฎ้ๅคงใ
+ // SmartPolicy ๆๅ
็ด ๆฐๅคๆญไผ้่ฏฏๅฐๅฐๅๅ็บงๅซ็ๅนถ่ก้ๅไธบไธฒ่กใ
+ // ่ฟ้็ดๆฅ่ฐ็จ็บฟ็จๆฑ ๏ผ>1 ๅๆถๅนถ่ก๏ผ๏ผ้ถๅ้
latch ่ฎพ่ฎกๅผ้ๆไฝใ
+ const auto n_blocks = i_blocks * j_blocks;
+ if (n_blocks <= 1)
+ {
+ for (std::size_t i = 0; i < R; ++i)
+ for (std::size_t j = 0; j < C; ++j)
+ dst[j * R + i] = src[i * C + j];
+ }
+ else
+ {
+ auto block_indices = std::views::iota(std::size_t{0}, n_blocks);
+ global_thread_pool().parallel_for_blocks(
+ block_indices.begin(), block_indices.end(),
+ [src, dst, R, C, j_blocks](std::size_t block_idx) noexcept
+ {
+ const std::size_t ib = block_idx / j_blocks;
+ const std::size_t jb = block_idx % j_blocks;
+ const std::size_t i0 = ib * BLOCK_SIZE;
+ const std::size_t j0 = jb * BLOCK_SIZE;
+ const std::size_t i1 = std::min(i0 + BLOCK_SIZE, R);
+ const std::size_t j1 = std::min(j0 + BLOCK_SIZE, C);
+ for (std::size_t i = i0; i < i1; ++i)
+ for (std::size_t j = j0; j < j1; ++j)
+ dst[j * R + i] = src[i * C + j];
+ });
+ }
}
[[nodiscard]] Matrix operator+(const Matrix &other) const
@@ -227,7 +248,7 @@ namespace nn
}
// โโ ็ฉ้ตไนๆณๅฐ้ขๅ้
็ผๅฒๅบ๏ผ้ถๅ้
็ญ่ทฏๅพ๏ผ โโโโโโโโโโโโโโโโโโโโโโโโโ
- // ไฝฟ็จๅๅงๆ้ + restrict ๆ็คบ๏ผๅธฎๅฉ็ผ่ฏๅจ่ชๅจๅ้ๅ
+ // ไฝฟ็จ std::span๏ผC++20๏ผๆไพ็ฑปๅๅฎๅ
จ็้ๆฅๆ่งๅพ
void multiply_to(Matrix &result, const Matrix &other) const
{
assert(&result != this && "multiply_to: self-referencing not supported");
@@ -237,55 +258,90 @@ namespace nn
result.resize(M, N);
if (M == 0 || N == 0 || K == 0) return;
- // ๆธ
้ถ็ปๆ็ฉ้ต
- auto *r_ptr = result.data_ptr();
- std::fill(r_ptr, r_ptr + M * N, 0.0);
+ // ๆธ
้ถ็ปๆ็ฉ้ต๏ผไฝฟ็จ RAII ๅฐ่ฃ
็ๆนๆณ๏ผ
+ result.zero();
- const double *a_ptr = data_ptr();
- const double *b_ptr = other.data_ptr();
+ // std::span ้ๆฅๆ่งๅพ๏ผไป std::vector ๅๅบ๏ผ็ๅฝๅจๆ็ฑ่ฐ็จๆ ไฟ่ฏ
+ const auto a = span();
+ const auto b = other.span();
+ auto r = result.span();
const std::size_t i_blocks = (M + BLOCK_SIZE - 1) / BLOCK_SIZE;
const std::size_t j_blocks = (N + BLOCK_SIZE - 1) / BLOCK_SIZE;
- auto block_indices = std::views::iota(std::size_t{0}, i_blocks * j_blocks);
- SmartPolicy::for_each(block_indices.begin(), block_indices.end(),
- [a_ptr, b_ptr, r_ptr, M, N, K, j_blocks](std::size_t block_idx) noexcept
- {
- const std::size_t i_block = block_idx / j_blocks;
- const std::size_t j_block = block_idx % j_blocks;
- const std::size_t i_start = i_block * BLOCK_SIZE;
- const std::size_t i_end = std::min(i_start + BLOCK_SIZE, M);
- const std::size_t j_start = j_block * BLOCK_SIZE;
- const std::size_t j_end = std::min(j_start + BLOCK_SIZE, N);
-
- for (std::size_t k_start = 0; k_start < K; k_start += BLOCK_SIZE)
- {
- const std::size_t k_end = std::min(k_start + BLOCK_SIZE, K);
- const std::size_t k_len = k_end - k_start;
- const std::size_t j_len = j_end - j_start;
-
- // ๅ ่ฝฝ B ็ๅญๅๅฐๆ ๆฐ็ปๅนถ่ฝฌ็ฝฎ๏ผๆนๅ่ฎฟ้ฎๅฑ้จๆง
- std::array b_block{};
- for (std::size_t jj = 0; jj < j_len; ++jj)
- for (std::size_t kk = 0; kk < k_len; ++kk)
- b_block[jj * k_len + kk] = b_ptr[(k_start + kk) * N + (j_start + jj)];
-
- // ็ดฏๅ ๅฝๅ K ๅๅฏน C ๅ็่ดก็ฎ
- for (std::size_t i = i_start; i < i_end; ++i)
- {
- const double *a_row = a_ptr + i * K + k_start;
- double *r_row = r_ptr + i * N + j_start;
- for (std::size_t j = 0; j < j_len; ++j)
- {
- const double *b_col = b_block.data() + j * k_len;
- double sum = 0.0;
- for (std::size_t kk = 0; kk < k_len; ++kk)
- sum += a_row[kk] * b_col[kk];
- r_row[j] += sum;
- }
- }
- }
- });
+ // ็ป่ฟ SmartPolicy๏ผๅๅๆฐๅฏ่ฝๅพๅฐไฝๅ
ถๅ
ๅพช็ฏ่ฎก็ฎ้ๅทจๅคง๏ผไธๅฑๅพช็ฏ O(nยณ)๏ผใ
+ // ็ดๆฅ่ฐ็จ็บฟ็จๆฑ ๏ผ้ถๅ้
latch ๅผ้ๆไฝใ
+ const auto n_blocks = i_blocks * j_blocks;
+ if (n_blocks <= 1)
+ {
+ // ๅๅไธฒ่กโโไธไธๆน lambda ้ป่พ็ธๅ๏ผไฝ้ฟๅ
ไธๅฟ
่ฆ็็บฟ็จๆฑ ่ฐ็จ
+ const std::size_t i_start = 0, i_end = M;
+ const std::size_t j_start = 0, j_end = N;
+ for (std::size_t k_start = 0; k_start < K; k_start += BLOCK_SIZE)
+ {
+ const std::size_t k_end = std::min(k_start + BLOCK_SIZE, K);
+ const std::size_t k_len = k_end - k_start;
+ const std::size_t j_len = j_end - j_start;
+ std::array b_block{};
+ for (std::size_t jj = 0; jj < j_len; ++jj)
+ for (std::size_t kk = 0; kk < k_len; ++kk)
+ b_block[jj * k_len + kk] = b[(k_start + kk) * N + (j_start + jj)];
+ for (std::size_t i = i_start; i < i_end; ++i)
+ {
+ const auto a_row = a.subspan(i * K + k_start);
+ auto r_row = r.subspan(i * N + j_start);
+ for (std::size_t j = 0; j < j_len; ++j)
+ {
+ const double *b_col = b_block.data() + j * k_len;
+ double sum = 0.0;
+ for (std::size_t kk = 0; kk < k_len; ++kk)
+ sum += a_row[kk] * b_col[kk];
+ r_row[j] += sum;
+ }
+ }
+ }
+ }
+ else
+ {
+ auto block_indices = std::views::iota(std::size_t{0}, n_blocks);
+ global_thread_pool().parallel_for_blocks(
+ block_indices.begin(), block_indices.end(),
+ [a, b, r, M, N, K, j_blocks](std::size_t block_idx) noexcept
+ {
+ const std::size_t i_block = block_idx / j_blocks;
+ const std::size_t j_block = block_idx % j_blocks;
+ const std::size_t i_start = i_block * BLOCK_SIZE;
+ const std::size_t i_end = std::min(i_start + BLOCK_SIZE, M);
+ const std::size_t j_start = j_block * BLOCK_SIZE;
+ const std::size_t j_end = std::min(j_start + BLOCK_SIZE, N);
+
+ for (std::size_t k_start = 0; k_start < K; k_start += BLOCK_SIZE)
+ {
+ const std::size_t k_end = std::min(k_start + BLOCK_SIZE, K);
+ const std::size_t k_len = k_end - k_start;
+ const std::size_t j_len = j_end - j_start;
+
+ std::array b_block{};
+ for (std::size_t jj = 0; jj < j_len; ++jj)
+ for (std::size_t kk = 0; kk < k_len; ++kk)
+ b_block[jj * k_len + kk] = b[(k_start + kk) * N + (j_start + jj)];
+
+ for (std::size_t i = i_start; i < i_end; ++i)
+ {
+ const auto a_row = a.subspan(i * K + k_start);
+ auto r_row = r.subspan(i * N + j_start);
+ for (std::size_t j = 0; j < j_len; ++j)
+ {
+ const double *b_col = b_block.data() + j * k_len;
+ double sum = 0.0;
+ for (std::size_t kk = 0; kk < k_len; ++kk)
+ sum += a_row[kk] * b_col[kk];
+ r_row[j] += sum;
+ }
+ }
+ }
+ });
+ }
}
void scale_inplace(double scalar) noexcept
diff --git a/include/neuralnet.cpp/mnist_common.hpp b/include/neuralnet.cpp/mnist_common.hpp
new file mode 100644
index 00000000..50b06c57
--- /dev/null
+++ b/include/neuralnet.cpp/mnist_common.hpp
@@ -0,0 +1,96 @@
+#ifndef MNIST_COMMON_HPP
+#define MNIST_COMMON_HPP
+
+#include
+#include
+#include
+#include
+
+#include "model.hpp"
+
+namespace nn {
+
+// โโ MNIST ๅธธ้ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+inline constexpr std::size_t MNIST_INPUT_DIM = 784;
+inline constexpr std::size_t MNIST_NUM_CLASSES = 10;
+
+// ้ป่ฎค MLP ็ฝ็ปๆถๆ๏ผ่พๅ
ฅๅฑ -> ้่ๅฑ1 -> ้่ๅฑ2 -> ้่ๅฑ3 -> ่พๅบๅฑ
+inline const std::vector MNIST_LAYER_DIMS = {
+ MNIST_INPUT_DIM, 512, 256, 128, 64, MNIST_NUM_CLASSES
+};
+
+// ้ป่ฎค Transformer ่ถ
ๅๆฐ
+inline constexpr std::size_t TRANSFORMER_D_MODEL = 64;
+inline constexpr std::size_t TRANSFORMER_NUM_HEADS = 4;
+inline constexpr std::size_t TRANSFORMER_D_FF = 128;
+inline constexpr std::size_t TRANSFORMER_NUM_LAYERS = 2;
+inline constexpr std::size_t TRANSFORMER_PATCH_SIZE = 7; // 28 / 7 = 4 โ 4ร4 = 16 patches
+
+// โโ ๆๅปบ MLP ๆจกๅ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+// ๆ นๆฎ MNIST_LAYER_DIMS ่ชๅจๆๅปบ็ฝ็ป (Linear + LayerNorm + GeLU)
+[[nodiscard]] inline Model build_mnist_mlp_model()
+{
+ Model model;
+ for (std::size_t i = 0; i < MNIST_LAYER_DIMS.size() - 1; ++i)
+ {
+ std::size_t in_dim = MNIST_LAYER_DIMS[i];
+ std::size_t out_dim = MNIST_LAYER_DIMS[i + 1];
+
+ model.add(in_dim, out_dim);
+
+ if (i < MNIST_LAYER_DIMS.size() - 2)
+ {
+ model.add(out_dim)
+ .add();
+ }
+ }
+ return model;
+}
+
+// โโ ๆๅปบ Transformer ๆจกๅ (ViT-like) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+// ๆถๆ: PatchEmbedding โ TransformerEncoder โ Linear(ๅ็ฑปๅคด)
+//
+// ่พๅ
ฅ: (784, batch) โ ๅฑๅนณ็ 28ร28 ๅพๅ
+// PatchEmbedding: 28ร28 โ 16 ไธช 7ร7 patch โ ๆๅฝฑๅฐ d_model
+// TransformerEncoder: N ๅฑ Self-Attention + FFN (Pre-Norm) + ไฝ็ฝฎ็ผ็ + ๆฑ ๅ
+// ๅ็ฑปๅคด: Linear(d_model โ 10)
+//
+// ไผ ๆญๆต็จ:
+// (784, batch)
+// โ PatchEmbedding: (d_modelร16, batch)
+// โ TransformerEncoder: (d_model, batch)
+// โ Linear: (10, batch)
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+[[nodiscard]] inline Model build_mnist_transformer_model(
+ std::size_t d_model = TRANSFORMER_D_MODEL,
+ std::size_t num_heads = TRANSFORMER_NUM_HEADS,
+ std::size_t d_ff = TRANSFORMER_D_FF,
+ std::size_t num_layers = TRANSFORMER_NUM_LAYERS,
+ std::size_t patch_size = TRANSFORMER_PATCH_SIZE)
+{
+ constexpr std::size_t img_size = 28;
+ const std::size_t num_patches = (img_size / patch_size) * (img_size / patch_size);
+
+ Model model;
+ model.add(img_size, patch_size, d_model)
+ .add(d_model, num_heads, d_ff, num_layers, num_patches)
+ .add(d_model, MNIST_NUM_CLASSES);
+ return model;
+}
+
+// โโ ็ปไธๆๅปบๅ
ฅๅฃ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+// model_type: "mlp" ๆ "transformer"
+[[nodiscard]] inline Model build_mnist_model(const std::string &model_type = "mlp")
+{
+ if (model_type == "mlp")
+ return build_mnist_mlp_model();
+ else if (model_type == "transformer")
+ return build_mnist_transformer_model();
+ else
+ throw std::invalid_argument("Unknown model type: " + model_type
+ + " (available: mlp, transformer)");
+}
+
+} // namespace nn
+
+#endif // MNIST_COMMON_HPP
\ No newline at end of file
diff --git a/include/neuralnet.cpp/model.hpp b/include/neuralnet.cpp/model.hpp
index 0663aff9..b66a3abe 100644
--- a/include/neuralnet.cpp/model.hpp
+++ b/include/neuralnet.cpp/model.hpp
@@ -6,8 +6,8 @@
#include
#include
-#include
-#include
+#include "nn_config.hpp"
+#include "layer.hpp"
namespace nn
{
diff --git a/include/neuralnet.cpp/model_io.hpp b/include/neuralnet.cpp/model_io.hpp
index 7f6338d1..4aa5ec27 100644
--- a/include/neuralnet.cpp/model_io.hpp
+++ b/include/neuralnet.cpp/model_io.hpp
@@ -7,9 +7,9 @@
#include
#include
-#include
-#include
-#include
+#include "nn_config.hpp"
+#include "layer.hpp"
+#include "model.hpp"
namespace nn
{
diff --git a/include/neuralnet.cpp/nn.hpp b/include/neuralnet.cpp/nn.hpp
index b68938e0..38f284d3 100644
--- a/include/neuralnet.cpp/nn.hpp
+++ b/include/neuralnet.cpp/nn.hpp
@@ -5,11 +5,12 @@
#include
#include
-#include
-#include
-#include
-#include
-#include
+#include "nn_config.hpp"
+#include "layer.hpp"
+#include "loss.hpp"
+#include "model.hpp"
+#include "optimizer.hpp"
+#include "mnist_common.hpp"
namespace nn
{
diff --git a/include/neuralnet.cpp/nn_config.hpp b/include/neuralnet.cpp/nn_config.hpp
index 116f1f2a..d464b70c 100644
--- a/include/neuralnet.cpp/nn_config.hpp
+++ b/include/neuralnet.cpp/nn_config.hpp
@@ -26,15 +26,19 @@ namespace nn
inline constexpr double EPSILON = 1e-8;
// โโ ๆบ่ฝๆง่ก็ญ็ฅ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- // ๆ นๆฎๅ
็ด ๆฐ้่ชๅจ้ๆฉไธฒ่กๆไฝฟ็จๅ
จๅฑ็บฟ็จๆฑ ๅนถ่ก๏ผ้ฟๅ
ๅฐ็ฉ้ต็็บฟ็จๆฑ ่ฐๅบฆๅผ้
- // ็บฟ็จๆฑ ๅจ้ฆๆฌกไฝฟ็จๆถๆๅๅงๅ๏ผ่ฎญ็ปๆ้ดๅธธ้ฉป๏ผ้ฟๅ
ๅๅคๅๅปบ/้ๆฏ
+ // ๆ นๆฎๅ
็ด ๆฐ้่ชๅจ้ๆฉไธฒ่กๆไฝฟ็จๅ
จๅฑ็บฟ็จๆฑ ๅนถ่กใ
+ // ็บฟ็จๆฑ ้็จ latch + ่ฐ็จ่
ๅไธ่ฎพ่ฎก๏ผไธๆฌกๆน้ๅ
ฅ้ + ๅๅญ่ฎกๆฐๅจ๏ผ
+ // ๆถ้คไบๆง็ Nรsubmit + Nรfuture ็ๅ ๅ้
ๅๅๆญฅๅผ้ใ
struct SmartPolicy {
- static constexpr long long PARALLEL_THRESHOLD = 100000; // 100Kๅ
็ด ๏ผๅฎๆตๆไผ๏ผ
+ // ้ๅผ = ๆฏๆ ธๆๅฐๅๅ ร 2๏ผ็กฎไฟ่ณๅฐๆ 2 ไธชๅๅๅฏๅนถ่กใ
+ // chunk_count() ๅทฒ่ชๅจๆๆ ธๅฟๆฐ็ผฉๆพ๏ผmin(cores, total/4096)๏ผ๏ผ
+ // ้ๅผๅช้่ฎพๆไฝ้จๆง๏ผๆ ธๅฟๅฉ็จ้ๆฐๆฎ้่ช็ถๅข้ฟใ
+ inline static const std::size_t PARALLEL_THRESHOLD = 4096 * 2; // 8192
// for_each ็ๆฌ
template
static void for_each(Iterator first, Iterator last, Func&& func) {
- const auto n = static_cast(std::distance(first, last));
+ const auto n = static_cast(std::distance(first, last));
if (n >= PARALLEL_THRESHOLD) {
global_thread_pool().parallel_for_each(first, last, std::forward(func));
} else {
@@ -46,7 +50,7 @@ namespace nn
// transform ็ๆฌ๏ผไธๅ
๏ผ
template
static void transform(InputIt first, InputIt last, OutputIt d_first, UnaryOp&& op) {
- const auto n = static_cast(std::distance(first, last));
+ const auto n = static_cast(std::distance(first, last));
if (n >= PARALLEL_THRESHOLD) {
global_thread_pool().parallel_transform(first, last, d_first, std::forward(op));
} else {
@@ -57,7 +61,7 @@ namespace nn
// transform ็ๆฌ๏ผไบๅ
๏ผ
template
static void transform(InputIt1 first1, InputIt1 last1, InputIt2 first2, OutputIt d_first, BinaryOp&& op) {
- const auto n = static_cast(std::distance(first1, last1));
+ const auto n = static_cast(std::distance(first1, last1));
if (n >= PARALLEL_THRESHOLD) {
global_thread_pool().parallel_transform(first1, last1, first2, d_first, std::forward(op));
} else {
@@ -68,10 +72,10 @@ namespace nn
// transform_reduce ็ๆฌ๏ผไธๅ
transform๏ผ
template
static T transform_reduce(InputIt first, InputIt last, T init, BinaryOp&& reduce_op, UnaryOp&& transform_op) {
- const auto n = static_cast(std::distance(first, last));
+ const auto n = static_cast(std::distance(first, last));
if (n >= PARALLEL_THRESHOLD) {
return global_thread_pool().parallel_transform_reduce(
- first, last, first, init,
+ first, last, init,
std::forward(reduce_op), std::forward(transform_op));
} else {
return std::transform_reduce(std::execution::seq, first, last, init,
@@ -82,7 +86,7 @@ namespace nn
// transform_reduce ็ๆฌ๏ผไบๅ
transform๏ผไธคไธช่พๅ
ฅ่ๅด๏ผ
template
static T transform_reduce(InputIt1 first1, InputIt1 last1, InputIt2 first2, T init, BinaryOp&& reduce_op, UnaryOp&& transform_op) {
- const auto n = static_cast(std::distance(first1, last1));
+ const auto n = static_cast(std::distance(first1, last1));
if (n >= PARALLEL_THRESHOLD) {
return global_thread_pool().parallel_transform_reduce(first1, last1, first2, init,
std::forward(reduce_op),
diff --git a/include/neuralnet.cpp/optimizer.hpp b/include/neuralnet.cpp/optimizer.hpp
index 1e8e9b38..f5021a3b 100644
--- a/include/neuralnet.cpp/optimizer.hpp
+++ b/include/neuralnet.cpp/optimizer.hpp
@@ -5,7 +5,7 @@
#include
#include
-#include
+#include "matrix.hpp"
namespace nn
{
diff --git a/include/neuralnet.cpp/thread_pool.hpp b/include/neuralnet.cpp/thread_pool.hpp
index c6aa515b..9b330382 100644
--- a/include/neuralnet.cpp/thread_pool.hpp
+++ b/include/neuralnet.cpp/thread_pool.hpp
@@ -9,15 +9,18 @@
#include
#include
#include
-#include // for std::distance
+#include // for std::transform (serial fallback)
+#include // for std::transform_reduce (serial fallback)
+#include // for std::distance
#include
#include
namespace nn
{
- // โโ ็ฎๆ็บฟ็จๆฑ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- // ่ฎญ็ปๅผๅงๆถๅๅปบๅบๅฎๆฐ้็บฟ็จ๏ผ้ฟๅ
ๅๅคๅๅปบ/้ๆฏ็่ฐๅบฆๅผ้
- // ๆฏๆๆไบคไปปๆๅฏ่ฐ็จๅฏน่ฑก๏ผ่ฟๅ std::future ไปฅ่ทๅ็ปๆ
+ // โโ ็ฎๆ็บฟ็จๆฑ ๏ผlatch ้ถๅ้
่ฎพ่ฎก๏ผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ // ๆ ธๅฟๆน่ฟ๏ผๅฐๅๆฅ็ "N ๆฌก submit + N ไธช future + N ๆฌกๅ ้" ๆฟๆขไธบ
+ // "1 ๆฌกๆน้ๅ
ฅ้ + 1 ไธชๅๅญ่ฎกๆฐๅจ + ่ฐ็จ่
ๅไธๅค็"
+ // ๆถ้คๆฏๅๅไธๆฌก shared_ptr ๅ ๅ้
ๅ future ๅๆญฅๅผ้
class ThreadPool
{
private:
@@ -29,7 +32,6 @@ namespace nn
std::atomic stop_{false};
public:
- // ๆ้ ๏ผๅฏๅจๆๅฎๆฐ้็ๅทฅไฝ็บฟ็จ
explicit ThreadPool(std::size_t num_threads = std::thread::hardware_concurrency())
{
if (num_threads == 0) num_threads = 1;
@@ -58,7 +60,7 @@ namespace nn
}
}
- // ๆไบคไปปๅก๏ผ่ฟๅ future
+ // โโ ้็จๅไปปๅกๆไบค๏ผไฟ็็ป้ๆง่ฝๆๆ่ทฏๅพ๏ผ โโโโโโโโโโโโโโโโโโโโโโ
template
[[nodiscard]] auto submit(F&& f, Args&&... args)
-> std::future>
@@ -74,102 +76,224 @@ namespace nn
std::unique_lock lock(queue_mutex_);
if (stop_.load(std::memory_order_acquire))
throw std::runtime_error("submit on stopped ThreadPool");
- tasks_.emplace([task]()
- {
- (*task)();
- });
+ tasks_.emplace([task]() { (*task)(); });
}
condition_.notify_one();
return result;
}
- // ๅนถ่ก for_each๏ผๅฐ [first, last) ๅๅๅๅ๏ผๆฏๅๆไบคไธไธชไปปๅก
+ private:
+ // โโ ๅๅ่พ
ๅฉ๏ผ่ฎก็ฎๅ็็ๅๅๆฐ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ // ๆฏๅๅ่ณๅฐ 4096 ไธช double๏ผ32 KB๏ผ๏ผๅฏ่ฃ
ๅ
ฅ L1 ็ผๅญใ
+ // 256โ4096๏ผๆงๅผๅฏผ่ด 8192 ๅ
็ด ่ขซๅๆ 16 ไธชๅพฎๅ๏ผ่ฐๅบฆๅผ้่ฟ่ถ
่ฎก็ฎใ
+ [[nodiscard]] std::size_t chunk_count(std::size_t total) const noexcept
+ {
+ constexpr std::size_t MIN_CHUNK = 4096;
+ const auto nw = workers_.size();
+ if (nw <= 1 || total < MIN_CHUNK * 2)
+ return 1;
+ auto n = std::min(nw, total / MIN_CHUNK);
+ return n < 1 ? 1 : n;
+ }
+
+ // โโ work-stealing ็ญๅพ
๏ผ่ฐ็จ่
ไธ็ฉบ่ฝฌ๏ผๅธฎๅฟๅค็้ๅไปปๅก โโโโโโโโโ
+ void wait_for_latch(std::atomic& latch)
+ {
+ while (latch.load(std::memory_order_acquire) > 0)
+ {
+ std::function task;
+ {
+ std::lock_guard lock(queue_mutex_);
+ if (!tasks_.empty())
+ {
+ task = std::move(tasks_.front());
+ tasks_.pop();
+ }
+ }
+ if (task)
+ task();
+ else
+ std::this_thread::yield();
+ }
+ }
+
+ public:
+ // โโ ๅนถ่ก for_each๏ผlatch + ่ฐ็จ่
ๅไธ๏ผ โโโโโโโโโโโโโโโโโโโโโโโโโ
+ // ไธๆง็็ธๆฏ๏ผ้ถ future ๅ้
ใไธๆฌกๅ ้ๅ
ฅ้ใ่ฐ็จ่
ไธ็ฉบ็ญ
template
void parallel_for_each(Iterator first, Iterator last, Func&& func)
{
const auto total = static_cast(std::distance(first, last));
if (total == 0) return;
- const auto num_threads = workers_.size();
- if (num_threads <= 1 || total < num_threads)
+ const auto n_chunks = chunk_count(total);
+ if (n_chunks <= 1)
{
- // ไธฒ่กๅ้
for (auto it = first; it != last; ++it)
func(*it);
return;
}
- const std::size_t chunk_size = total / num_threads;
- std::vector> futures;
- futures.reserve(num_threads);
+ const std::size_t base = total / n_chunks;
+ const std::size_t rem = total % n_chunks;
+
+ // ๅๅญ่ฎกๆฐๅจ๏ผๅๅงๅผ = n_chunks
+ std::atomic latch{static_cast(n_chunks)};
+
+ // ๅฐๅ n_chunks-1 ไธชๅๅๆน้ๅ
ฅ้๏ผไป
ไธๆฌกๅ ้๏ผ
+ {
+ std::lock_guard lock(queue_mutex_);
+ std::size_t off = 0;
+ for (std::size_t c = 0; c < n_chunks - 1; ++c)
+ {
+ const std::size_t len = base + (c < rem ? 1 : 0);
+ auto beg = first;
+ std::advance(beg, static_cast(off));
+ auto end = beg;
+ std::advance(end, static_cast(len));
+ off += len;
+
+ tasks_.emplace([beg, end, &func, &latch]()
+ {
+ for (auto it = beg; it != end; ++it)
+ func(*it);
+ latch.fetch_sub(1, std::memory_order_release);
+ });
+ }
+ }
+ condition_.notify_all();
+
+ // ่ฐ็จ่
ๅค็ๆๅไธไธชๅๅ๏ผไธ็ป่ฟ้ๅ๏ผ้ถๅ้
๏ผ
+ {
+ const std::size_t c = n_chunks - 1;
+ const std::size_t len = base + (c < rem ? 1 : 0);
+ const std::size_t off = total - len;
+ auto beg = first;
+ std::advance(beg, static_cast(off));
+ for (auto it = beg; it != last; ++it)
+ func(*it);
+ latch.fetch_sub(1, std::memory_order_release);
+ }
- for (std::size_t t = 0; t < num_threads; ++t)
+ // work-stealing ็ญๅพ
๏ผ่ฐ็จ่
ๅธฎๅฟๅค็้ๅไปปๅก่้็ฉบ่ฝฌ
+ wait_for_latch(latch);
+ }
+
+ // โโ ๅ็บงๅนถ่ก for_each๏ผไพ็ฉ้ตไนๆณ/่ฝฌ็ฝฎ็ๅๅๅพช็ฏไฝฟ็จ๏ผโโโโโโโโโ
+ // ไธๆฎ้ parallel_for_each ไธๅ๏ผๆฏไธช"ๅ
็ด "ๆฌ่บซๅฐฑๆฏไธๆฌก้้็บง่ฎก็ฎ
+ // ๏ผๅฆ 64ร64รK ็็ฉ้ตไนๆณๅๅ๏ผ๏ผๅ ๆญคไธ้็จ MIN_CHUNK ไฟๆคใ
+ // ็ดๆฅๆๅๆฐๅ็ปๅ็บฟ็จ๏ผ1 ๅ = 1 ๅ็ใ
+ template
+ void parallel_for_blocks(Iterator first, Iterator last, Func&& func)
+ {
+ const auto total = static_cast(std::distance(first, last));
+ if (total <= 1)
{
- auto chunk_start = first;
- std::advance(chunk_start, static_cast(t * chunk_size));
- auto chunk_end = (t == num_threads - 1) ? last : chunk_start;
- if (t < num_threads - 1)
- std::advance(chunk_end, static_cast(chunk_size));
+ for (auto it = first; it != last; ++it)
+ func(*it);
+ return;
+ }
+
+ const auto n_chunks = std::min(workers_.size(), total);
+ const std::size_t base = total / n_chunks;
+ const std::size_t rem = total % n_chunks;
+ std::atomic latch{static_cast(n_chunks)};
- futures.push_back(submit([&func, chunk_start, chunk_end]()
+ {
+ std::lock_guard lock(queue_mutex_);
+ std::size_t off = 0;
+ for (std::size_t c = 0; c < n_chunks - 1; ++c)
{
- for (auto it = chunk_start; it != chunk_end; ++it)
- func(*it);
- }));
+ const std::size_t len = base + (c < rem ? 1 : 0);
+ auto beg = first;
+ std::advance(beg, static_cast(off));
+ auto end = beg;
+ std::advance(end, static_cast(len));
+ off += len;
+
+ tasks_.emplace([beg, end, &func, &latch]()
+ {
+ for (auto it = beg; it != end; ++it)
+ func(*it);
+ latch.fetch_sub(1, std::memory_order_release);
+ });
+ }
}
+ condition_.notify_all();
- // ็ญๅพ
ๆๆๅๅฎๆ
- for (auto& fut : futures)
- fut.get();
+ {
+ const std::size_t c = n_chunks - 1;
+ const std::size_t off = total - (base + (c < rem ? 1 : 0));
+ auto beg = first;
+ std::advance(beg, static_cast(off));
+ for (auto it = beg; it != last; ++it)
+ func(*it);
+ latch.fetch_sub(1, std::memory_order_release);
+ }
+
+ wait_for_latch(latch);
}
- // ๅนถ่ก transform๏ผๅฐ [first1, last1) ๅๅๅๅ
+ // โโ ๅนถ่ก transform๏ผไธๅ
๏ผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
template
void parallel_transform(InputIt first, InputIt last, OutputIt d_first, UnaryOp&& op)
{
const auto total = static_cast(std::distance(first, last));
if (total == 0) return;
- const auto num_threads = workers_.size();
- if (num_threads <= 1 || total < num_threads)
+ const auto n_chunks = chunk_count(total);
+ if (n_chunks <= 1)
{
std::transform(first, last, d_first, std::forward(op));
return;
}
- const std::size_t chunk_size = total / num_threads;
- std::vector> futures;
- futures.reserve(num_threads);
+ const std::size_t base = total / n_chunks;
+ const std::size_t rem = total % n_chunks;
+ std::atomic latch{static_cast(n_chunks)};
- for (std::size_t t = 0; t < num_threads; ++t)
{
- auto chunk_start = first;
- auto out_start = d_first;
- std::advance(chunk_start, static_cast(t * chunk_size));
- std::advance(out_start, static_cast(t * chunk_size));
-
- auto chunk_end = (t == num_threads - 1) ? last : chunk_start;
- auto out_end = (t == num_threads - 1) ? d_first : out_start;
- if (t < num_threads - 1)
+ std::lock_guard lock(queue_mutex_);
+ std::size_t off = 0;
+ for (std::size_t c = 0; c < n_chunks - 1; ++c)
{
- std::advance(chunk_end, static_cast(chunk_size));
- std::advance(out_end, static_cast(chunk_size));
+ const std::size_t len = base + (c < rem ? 1 : 0);
+ auto in_beg = first;
+ auto out_beg = d_first;
+ std::advance(in_beg, static_cast(off));
+ std::advance(out_beg, static_cast(off));
+ auto in_end = in_beg;
+ std::advance(in_end, static_cast(len));
+ off += len;
+
+ tasks_.emplace([in_beg, in_end, out_beg, &op, &latch]()
+ {
+ auto in = in_beg;
+ auto out = out_beg;
+ for (; in != in_end; ++in, ++out)
+ *out = op(*in);
+ latch.fetch_sub(1, std::memory_order_release);
+ });
}
+ }
+ condition_.notify_all();
- futures.push_back(submit([&op, chunk_start, chunk_end, out_start]()
- {
- auto in_it = chunk_start;
- auto out_it = out_start;
- for (; in_it != chunk_end; ++in_it, ++out_it)
- *out_it = op(*in_it);
- }));
+ {
+ const std::size_t c = n_chunks - 1;
+ const std::size_t off = total - (base + (c < rem ? 1 : 0));
+ auto in_beg = first;
+ auto out_beg = d_first;
+ std::advance(in_beg, static_cast(off));
+ std::advance(out_beg, static_cast(off));
+ for (; in_beg != last; ++in_beg, ++out_beg)
+ *out_beg = op(*in_beg);
+ latch.fetch_sub(1, std::memory_order_release);
}
- for (auto& fut : futures)
- fut.get();
+ wait_for_latch(latch);
}
- // ๅนถ่ก transform๏ผไบๅ
็ๆฌ๏ผ
+ // โโ ๅนถ่ก transform๏ผไบๅ
๏ผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
template
void parallel_transform(InputIt1 first1, InputIt1 last1, InputIt2 first2,
OutputIt d_first, BinaryOp&& op)
@@ -177,49 +301,131 @@ namespace nn
const auto total = static_cast(std::distance(first1, last1));
if (total == 0) return;
- const auto num_threads = workers_.size();
- if (num_threads <= 1 || total < num_threads)
+ const auto n_chunks = chunk_count(total);
+ if (n_chunks <= 1)
{
std::transform(first1, last1, first2, d_first, std::forward(op));
return;
}
- const std::size_t chunk_size = total / num_threads;
- std::vector> futures;
- futures.reserve(num_threads);
+ const std::size_t base = total / n_chunks;
+ const std::size_t rem = total % n_chunks;
+ std::atomic latch{static_cast(n_chunks)};
- for (std::size_t t = 0; t < num_threads; ++t)
{
- auto in1_start = first1;
- auto in2_start = first2;
- auto out_start = d_first;
- std::advance(in1_start, static_cast(t * chunk_size));
- std::advance(in2_start, static_cast(t * chunk_size));
- std::advance(out_start, static_cast(t * chunk_size));
-
- auto in1_end = (t == num_threads - 1) ? last1 : in1_start;
- auto out_end = (t == num_threads - 1) ? d_first : out_start;
- if (t < num_threads - 1)
+ std::lock_guard lock(queue_mutex_);
+ std::size_t off = 0;
+ for (std::size_t c = 0; c < n_chunks - 1; ++c)
{
- std::advance(in1_end, static_cast(chunk_size));
- std::advance(out_end, static_cast(chunk_size));
+ const std::size_t len = base + (c < rem ? 1 : 0);
+ auto i1 = first1;
+ auto i2 = first2;
+ auto o = d_first;
+ std::advance(i1, static_cast(off));
+ std::advance(i2, static_cast(off));
+ std::advance(o, static_cast(off));
+ auto i1_end = i1;
+ std::advance(i1_end, static_cast(len));
+ off += len;
+
+ tasks_.emplace([i1, i1_end, i2, o, &op, &latch]()
+ {
+ auto it1 = i1;
+ auto it2 = i2;
+ auto out = o;
+ for (; it1 != i1_end; ++it1, ++it2, ++out)
+ *out = op(*it1, *it2);
+ latch.fetch_sub(1, std::memory_order_release);
+ });
}
+ }
+ condition_.notify_all();
+
+ {
+ const std::size_t c = n_chunks - 1;
+ const std::size_t off = total - (base + (c < rem ? 1 : 0));
+ auto i1 = first1;
+ auto i2 = first2;
+ auto o = d_first;
+ std::advance(i1, static_cast(off));
+ std::advance(i2, static_cast(off));
+ std::advance(o, static_cast(off));
+ for (; i1 != last1; ++i1, ++i2, ++o)
+ *o = op(*i1, *i2);
+ latch.fetch_sub(1, std::memory_order_release);
+ }
+
+ wait_for_latch(latch);
+ }
- futures.push_back(submit([&op, in1_start, in1_end, in2_start, out_start]()
+ // โโ ๅนถ่ก transform_reduce๏ผไธๅ
transform๏ผโโโโโโโโโโโโโโโโโโโโโโ
+ template
+ T parallel_transform_reduce(InputIt first, InputIt last, T init,
+ BinaryOp&& reduce_op, UnaryOp&& transform_op)
+ {
+ const auto total = static_cast(std::distance(first, last));
+ if (total == 0) return init;
+
+ const auto n_chunks = chunk_count(total);
+ if (n_chunks <= 1)
+ {
+ return std::transform_reduce(first, last, init,
+ std::forward(reduce_op),
+ std::forward(transform_op));
+ }
+
+ const std::size_t base = total / n_chunks;
+ const std::size_t rem = total % n_chunks;
+ std::atomic latch{static_cast(n_chunks)};
+
+ // ้ขๅ้
็ปๆๆฐ็ป๏ผๆ ไธๅฐ vector๏ผ๏ผ้ฟๅ
future ๅ ๅ้
+ std::vector partials(n_chunks);
+
+ {
+ std::lock_guard lock(queue_mutex_);
+ std::size_t off = 0;
+ for (std::size_t c = 0; c < n_chunks - 1; ++c)
{
- auto it1 = in1_start;
- auto it2 = in2_start;
- auto out_it = out_start;
- for (; it1 != in1_end; ++it1, ++it2, ++out_it)
- *out_it = op(*it1, *it2);
- }));
+ const std::size_t len = base + (c < rem ? 1 : 0);
+ auto beg = first;
+ std::advance(beg, static_cast(off));
+ auto end = beg;
+ std::advance(end, static_cast(len));
+ off += len;
+
+ tasks_.emplace([beg, end, &reduce_op, &transform_op, &partials, &latch, c]()
+ {
+ T local{}; // ่ฆๆฑ T{} ๆฏ reduce_op ็ๅไฝๅ
+ for (auto it = beg; it != end; ++it)
+ local = reduce_op(local, transform_op(*it));
+ partials[c] = std::move(local);
+ latch.fetch_sub(1, std::memory_order_release);
+ });
+ }
+ }
+ condition_.notify_all();
+
+ {
+ const std::size_t c = n_chunks - 1;
+ const std::size_t off = total - (base + (c < rem ? 1 : 0));
+ auto beg = first;
+ std::advance(beg, static_cast(off));
+ T local{};
+ for (auto it = beg; it != last; ++it)
+ local = reduce_op(local, transform_op(*it));
+ partials[c] = std::move(local);
+ latch.fetch_sub(1, std::memory_order_release);
}
- for (auto& fut : futures)
- fut.get();
+ wait_for_latch(latch);
+
+ T result = init;
+ for (auto& p : partials)
+ result = reduce_op(result, p);
+ return result;
}
- // ๅนถ่ก transform_reduce๏ผไบๅ
่พๅ
ฅ่ๅด๏ผ
+ // โโ ๅนถ่ก transform_reduce๏ผไบๅ
่พๅ
ฅ่ๅด๏ผโโโโโโโโโโโโโโโโโโโโโโโโ
template
T parallel_transform_reduce(InputIt1 first1, InputIt1 last1, InputIt2 first2,
T init, BinaryOp&& reduce_op, UnaryOp&& transform_op)
@@ -227,48 +433,70 @@ namespace nn
const auto total = static_cast(std::distance(first1, last1));
if (total == 0) return init;
- const auto num_threads = workers_.size();
- if (num_threads <= 1 || total < num_threads)
+ const auto n_chunks = chunk_count(total);
+ if (n_chunks <= 1)
{
return std::transform_reduce(first1, last1, first2, init,
std::forward(reduce_op),
std::forward(transform_op));
}
- const std::size_t chunk_size = total / num_threads;
- std::vector> futures;
- futures.reserve(num_threads);
+ const std::size_t base = total / n_chunks;
+ const std::size_t rem = total % n_chunks;
+ std::atomic latch{static_cast(n_chunks)};
+ std::vector partials(n_chunks);
- for (std::size_t t = 0; t < num_threads; ++t)
{
- auto in1_start = first1;
- auto in2_start = first2;
- std::advance(in1_start, static_cast(t * chunk_size));
- std::advance(in2_start, static_cast(t * chunk_size));
-
- auto in1_end = (t == num_threads - 1) ? last1 : in1_start;
- if (t < num_threads - 1)
- std::advance(in1_end, static_cast(chunk_size));
-
- futures.push_back(submit([&reduce_op, &transform_op, in1_start, in1_end, in2_start]()
+ std::lock_guard lock(queue_mutex_);
+ std::size_t off = 0;
+ for (std::size_t c = 0; c < n_chunks - 1; ++c)
{
- T local_sum{};
- auto it1 = in1_start;
- auto it2 = in2_start;
- for (; it1 != in1_end; ++it1, ++it2)
- local_sum = reduce_op(local_sum, transform_op(*it1, *it2));
- return local_sum;
- }));
+ const std::size_t len = base + (c < rem ? 1 : 0);
+ auto i1 = first1;
+ auto i2 = first2;
+ std::advance(i1, static_cast(off));
+ std::advance(i2, static_cast(off));
+ auto i1_end = i1;
+ std::advance(i1_end, static_cast(len));
+ off += len;
+
+ tasks_.emplace([i1, i1_end, i2, &reduce_op, &transform_op, &partials, &latch, c]()
+ {
+ T local{};
+ auto it1 = i1;
+ auto it2 = i2;
+ for (; it1 != i1_end; ++it1, ++it2)
+ local = reduce_op(local, transform_op(*it1, *it2));
+ partials[c] = std::move(local);
+ latch.fetch_sub(1, std::memory_order_release);
+ });
+ }
}
+ condition_.notify_all();
+
+ {
+ const std::size_t c = n_chunks - 1;
+ const std::size_t off = total - (base + (c < rem ? 1 : 0));
+ auto i1 = first1;
+ auto i2 = first2;
+ std::advance(i1, static_cast(off));
+ std::advance(i2, static_cast(off));
+ T local{};
+ for (; i1 != last1; ++i1, ++i2)
+ local = reduce_op(local, transform_op(*i1, *i2));
+ partials[c] = std::move(local);
+ latch.fetch_sub(1, std::memory_order_release);
+ }
+
+ wait_for_latch(latch);
- // ๆถ้ๅๅ็ปๆ
T result = init;
- for (auto& fut : futures)
- result = reduce_op(result, fut.get());
+ for (auto& p : partials)
+ result = reduce_op(result, p);
return result;
}
- // ๆๆ๏ผ็ญๅพ
ๆๆไปปๅกๅฎๆๅๅ
ณ้ญ็บฟ็จ
+ // โโ ๆๆ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
~ThreadPool()
{
stop_.store(true, std::memory_order_release);
@@ -280,7 +508,6 @@ namespace nn
}
}
- // ็ฆๆญขๆท่ด/็งปๅจ
ThreadPool(const ThreadPool&) = delete;
ThreadPool& operator=(const ThreadPool&) = delete;
ThreadPool(ThreadPool&&) = delete;
@@ -289,8 +516,7 @@ namespace nn
[[nodiscard]] std::size_t size() const noexcept { return workers_.size(); }
};
- // โโ ๅ
จๅฑ็บฟ็จๆฑ ๅไพ๏ผๆๅๅงๅ๏ผ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- // ่ฎญ็ปๅผๅงๆถ่ชๅจๅๅปบ๏ผ็จๅบ็ปๆๆถ่ชๅจ้ๆฏ๏ผ้ฟๅ
ๅๅคๅๅปบ/้ๆฏ
+ // โโ ๅ
จๅฑ็บฟ็จๆฑ ๅไพ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
inline ThreadPool& global_thread_pool()
{
static ThreadPool pool{std::thread::hardware_concurrency()};
diff --git a/mnist_model.bin b/mnist_model.bin
new file mode 100644
index 00000000..0262ec14
Binary files /dev/null and b/mnist_model.bin differ
diff --git a/src/infer.cpp b/src/infer.cpp
index fd7b05e1..41b6bbf5 100644
--- a/src/infer.cpp
+++ b/src/infer.cpp
@@ -1,5 +1,6 @@
#include
#include
+#include
#include
#include
#include
@@ -14,10 +15,7 @@
namespace fs = std::filesystem;
// ==================== ๅธธ้ ====================
-static constexpr std::size_t INPUT_DIM = 784;
-static constexpr std::size_t HIDDEN_DIM = 64;
-static constexpr std::size_t NUM_CLASSES = 10;
-static constexpr std::size_t NUM_HIDDEN_LAYERS = 3;
+// ็ฐๅจไฝฟ็จ nn::MNIST_INPUT_DIM, nn::MNIST_NUM_CLASSES, nn::MNIST_LAYER_DIMS
// ==================== ๅธฎๅฉไฟกๆฏ ====================
void print_usage(const char *prog)
@@ -28,16 +26,18 @@ void print_usage(const char *prog)
<< " " << prog << " [้้กน] ๆจ็ๅๅผ ๅพ็\n"
<< " " << prog << " <็ฎๅฝ> [้้กน] ๆน้ๆจ็็ฎๅฝไธๆๆ CSV\n\n"
<< "้้กน:\n"
- << " --model ๆจกๅๆไปถ่ทฏๅพ (้ป่ฎค: pretrained/model.bin)\n"
- << " --topk ๆพ็คบๅ n ไธช้ขๆต็ปๆ (้ป่ฎค: 3)\n"
- << " --show-pixels ๆพ็คบๅ็ด ็ฉ้ต (่ฐ่ฏ็จ)\n"
- << " --help ๆพ็คบๆญคๅธฎๅฉไฟกๆฏ\n";
+ << " --model ๆจกๅๆไปถ่ทฏๅพ (้ป่ฎค: pretrained/model.bin)\n"
+ << " --model-type ๆจกๅ็ฑปๅ: mlp/transformer (้ป่ฎค: mlp)\n"
+ << " --topk ๆพ็คบๅ n ไธช้ขๆต็ปๆ (้ป่ฎค: 3)\n"
+ << " --show-pixels ๆพ็คบๅ็ด ็ฉ้ต (่ฐ่ฏ็จ)\n"
+ << " --help ๆพ็คบๆญคๅธฎๅฉไฟกๆฏ\n";
}
// ==================== ๅฝไปค่กๅๆฐ ====================
struct InferConfig
{
std::string model_path = "pretrained/model.bin";
+ std::string model_type = "mlp";
std::string input_path;
int topk = 3;
bool show_pixels = false;
@@ -60,6 +60,16 @@ InferConfig parse_args(int argc, char *argv[])
{
cfg.model_path = argv[++i];
}
+ else if (arg == "--model-type" && i + 1 < argc)
+ {
+ cfg.model_type = argv[++i];
+ if (cfg.model_type != "mlp" && cfg.model_type != "transformer")
+ {
+ std::cerr << "ๆช็ฅๆจกๅ็ฑปๅ: " << cfg.model_type
+ << "๏ผๅฏ้: mlp, transformer\n";
+ std::exit(1);
+ }
+ }
else if (arg == "--topk" && i + 1 < argc)
{
cfg.topk = std::stoi(argv[++i]);
@@ -90,19 +100,7 @@ InferConfig parse_args(int argc, char *argv[])
}
// ==================== ๆๅปบ็ฝ็ป ====================
-nn::Model build_model()
-{
- nn::Model model;
- model.add(INPUT_DIM, HIDDEN_DIM)
- .add();
- for (std::size_t i = 0; i < NUM_HIDDEN_LAYERS - 1; ++i)
- {
- model.add(HIDDEN_DIM, HIDDEN_DIM)
- .add();
- }
- model.add(HIDDEN_DIM, NUM_CLASSES);
- return model;
-}
+// build_model ๅฝๆฐๅทฒ็งป่ณ neuralnet.cpp/mnist_common.hpp ไธญ็ nn::build_mnist_model()
// ==================== ๆฐๆฎ่ฏปๅ ====================
nn::Matrix load_image_from_csv(const std::string &csv_line)
@@ -114,11 +112,11 @@ nn::Matrix load_image_from_csv(const std::string &csv_line)
{
pixels.push_back(std::stod(token));
}
- if (pixels.size() != INPUT_DIM)
- throw std::runtime_error("CSV ๅฟ
้กปๅ
ๅซๆฐๅฅฝ " + std::to_string(INPUT_DIM) + " ไธชๅผ๏ผๅฎ้
: " + std::to_string(pixels.size()));
+ if (pixels.size() != nn::MNIST_INPUT_DIM)
+ throw std::runtime_error("CSV ๅฟ
้กปๅ
ๅซๆฐๅฅฝ " + std::to_string(nn::MNIST_INPUT_DIM) + " ไธชๅผ๏ผๅฎ้
: " + std::to_string(pixels.size()));
- nn::Matrix img(INPUT_DIM, 1);
- for (std::size_t i = 0; i < INPUT_DIM; ++i)
+ nn::Matrix img(nn::MNIST_INPUT_DIM, 1);
+ for (std::size_t i = 0; i < nn::MNIST_INPUT_DIM; ++i)
img.set_value_unchecked(i, 0, pixels[i]);
return img;
}
@@ -136,12 +134,12 @@ std::vector predict_with_confidence(nn::Model &model, const nn::Matr
// Softmax ่ฎก็ฎๆฆ็
double max_val = logits.at_unchecked(0, 0);
- for (int c = 1; c < static_cast(NUM_CLASSES); ++c)
+ for (int c = 1; c < static_cast(nn::MNIST_NUM_CLASSES); ++c)
max_val = std::max(max_val, logits.at_unchecked(c, 0));
double sum_exp = 0.0;
- std::vector probs(NUM_CLASSES);
- for (std::size_t c = 0; c < NUM_CLASSES; ++c)
+ std::vector probs(nn::MNIST_NUM_CLASSES);
+ for (std::size_t c = 0; c < nn::MNIST_NUM_CLASSES; ++c)
{
probs[c] = std::exp(logits.at_unchecked(c, 0) - max_val);
sum_exp += probs[c];
@@ -150,7 +148,7 @@ std::vector predict_with_confidence(nn::Model &model, const nn::Matr
p /= sum_exp;
// ๆๆฆ็ๆๅบๅ top-k
- std::vector indices(NUM_CLASSES);
+ std::vector