diff --git a/.editorconfig b/.editorconfig index 94b66ac2..be26136a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,7 +5,7 @@ root = true [*] charset = utf-8 -end_of_line = crlf +end_of_line = lf indent_style = space indent_size = 4 insert_final_newline = false diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..bd53aca3 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +CARGO_TERM_COLOR=always diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a0033ceb..426cdbba 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,8 +5,16 @@ updates: schedule: interval: monthly directories: - - /* - - /models/* + - . + - /concision + - /core + - /data + - /derive + - /ext + - /init + - /macros + - /params + - /traits - package-ecosystem: devcontainers directory: / schedule: diff --git a/.github/workflows/cargo-clippy.yml b/.github/workflows/cargo-clippy.yml index 244030d5..7ffd2685 100644 --- a/.github/workflows/cargo-clippy.yml +++ b/.github/workflows/cargo-clippy.yml @@ -7,7 +7,7 @@ concurrency: on: pull_request: branches: [main, master] - types: [edited, opened, ready_for_review, reopened] + types: [opened, ready_for_review, reopened] push: branches: [main, master] tags: [v*.*.*, "*-nightly"] @@ -27,7 +27,7 @@ jobs: statuses: write steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: diff --git a/.github/workflows/cargo-publish.yml b/.github/workflows/cargo-publish.yml index f956b7fb..25a28670 100644 --- a/.github/workflows/cargo-publish.yml +++ b/.github/workflows/cargo-publish.yml @@ -38,23 +38,15 @@ jobs: - concision-traits - concision-init - concision-params - - concision-utils - concision-core - concision-data - - concision-neural - concision-derive - concision-macros - concision - concision-ext - # non-sdk packages - - concision-kan - - concision-s4 - - concision-snn - - concision-transformer - - concision-models steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 ref: ${{ github.ref }} diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 419b8c3a..78f41832 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -7,7 +7,7 @@ concurrency: on: pull_request: branches: [main, master] - types: [edited, opened, ready_for_review, reopened] + types: [opened, ready_for_review, reopened] push: branches: [main, master] tags: [v*.*.*, "*-nightly"] @@ -25,13 +25,13 @@ jobs: continue-on-error: true runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 - - uses: cachix/install-nix-action@v31 + - name: Checkout + uses: actions/checkout@v6 + - name: Setup Nix + uses: cachix/install-nix-action@v31 with: github_access_token: ${{ secrets.GITHUB_TOKEN }} - name: Build - id: build run: nix build - name: Check the flake - id: check run: nix flake check diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7cb2b6f2..8741cfe1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,13 +34,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 ref: ${{ github.ref }} repository: ${{ github.repository }} - name: Publish to crates.io - uses: peter-evans/repository-dispatch@v3 + uses: peter-evans/repository-dispatch@v4 with: event-type: cargo-publish client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 0537e405..412ceb9d 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -12,6 +12,11 @@ on: pull_request: branches: [main, master] types: [edited, opened, ready_for_review, reopened, synchronize] + paths: + - '**.rs' + - 'Cargo.toml' + - 'Cargo.lock' + - '.github/workflows/rust.yml' push: branches: [main, master] tags: [v*.*.*, "*-nightly"] @@ -34,7 +39,7 @@ jobs: target: [x86_64-unknown-linux-gnu] steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: @@ -43,7 +48,7 @@ jobs: - name: Build the workspace run: cargo build --release --locked --workspace --features full --target ${{ matrix.target }} benchmark: - if: ${{ inputs.benchmark || github.event_name == 'push' }} + if: ${{ inputs.benchmark || github.event_name != 'pull_request' }} runs-on: ubuntu-latest outputs: digest: ${{ steps.artifacts.outputs.artifact-digest }} @@ -58,7 +63,7 @@ jobs: target: [x86_64-unknown-linux-gnu] steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: @@ -84,7 +89,7 @@ jobs: target: [x86_64-unknown-linux-gnu] steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: diff --git a/Cargo.lock b/Cargo.lock index 35a970d5..d6d6b333 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,7 +180,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "concision" -version = "0.2.8" +version = "0.2.9" dependencies = [ "anyhow", "approx", @@ -188,7 +188,6 @@ dependencies = [ "concision-data", "concision-derive", "concision-macros", - "concision-neural", "criterion", "lazy_static", "ndarray", @@ -200,18 +199,18 @@ dependencies = [ [[package]] name = "concision-core" -version = "0.2.8" +version = "0.2.9" dependencies = [ + "anyhow", "approx", "concision-init", "concision-params", "concision-traits", - "concision-utils", "getrandom", "lazy_static", "ndarray", - "num", "num-complex", + "num-integer", "num-traits", "paste", "rand 0.9.2", @@ -230,7 +229,7 @@ dependencies = [ [[package]] name = "concision-data" -version = "0.2.8" +version = "0.2.9" dependencies = [ "approx", "concision-core", @@ -249,7 +248,7 @@ dependencies = [ [[package]] name = "concision-derive" -version = "0.2.8" +version = "0.2.9" dependencies = [ "proc-macro2", "quote", @@ -258,7 +257,7 @@ dependencies = [ [[package]] name = "concision-ext" -version = "0.2.8" +version = "0.2.9" dependencies = [ "anyhow", "approx", @@ -280,7 +279,7 @@ dependencies = [ [[package]] name = "concision-init" -version = "0.2.8" +version = "0.2.9" dependencies = [ "approx", "getrandom", @@ -301,80 +300,18 @@ dependencies = [ "tracing", ] -[[package]] -name = "concision-kan" -version = "0.2.8" -dependencies = [ - "anyhow", - "approx", - "concision", - "ndarray", - "num-traits", - "tracing", -] - [[package]] name = "concision-macros" -version = "0.2.8" +version = "0.2.9" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "concision-models" -version = "0.2.8" -dependencies = [ - "anyhow", - "approx", - "concision", - "concision-ext", - "concision-kan", - "concision-s4", - "concision-transformer", - "lazy_static", - "ndarray", - "num-traits", - "tracing", - "tracing-subscriber", - "variants", -] - -[[package]] -name = "concision-neural" -version = "0.2.8" -dependencies = [ - "anyhow", - "approx", - "concision-core", - "concision-data", - "concision-params", - "either", - "getrandom", - "lazy_static", - "ndarray", - "num", - "num-complex", - "num-traits", - "paste", - "rand 0.9.2", - "rand_distr", - "rayon", - "rustfft", - "serde", - "serde_derive", - "serde_json", - "smart-default", - "strum", - "thiserror", - "tracing", - "variants", -] - [[package]] name = "concision-params" -version = "0.2.8" +version = "0.2.9" dependencies = [ "approx", "concision-init", @@ -394,42 +331,15 @@ dependencies = [ "variants", ] -[[package]] -name = "concision-s4" -version = "0.2.8" -dependencies = [ - "anyhow", - "approx", - "concision", - "ndarray", - "num-traits", - "tracing", -] - -[[package]] -name = "concision-snn" -version = "0.2.8" -dependencies = [ - "anyhow", - "approx", - "concision", - "ndarray", - "num-traits", - "serde", - "serde_derive", - "serde_json", - "tracing", -] - [[package]] name = "concision-traits" -version = "0.2.8" +version = "0.2.9" dependencies = [ "approx", "getrandom", - "lazy_static", "ndarray", "num-complex", + "num-integer", "num-traits", "paste", "rand 0.9.2", @@ -438,45 +348,6 @@ dependencies = [ "variants", ] -[[package]] -name = "concision-transformer" -version = "0.2.8" -dependencies = [ - "anyhow", - "approx", - "concision", - "ndarray", - "num-traits", - "tracing", -] - -[[package]] -name = "concision-utils" -version = "0.2.8" -dependencies = [ - "anyhow", - "approx", - "getrandom", - "lazy_static", - "ndarray", - "num", - "num-complex", - "num-traits", - "paste", - "rand 0.9.2", - "rand_distr", - "rayon", - "rustfft", - "serde", - "serde_derive", - "serde_json", - "smart-default", - "strum", - "thiserror", - "tracing", - "variants", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1845,9 +1716,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -1856,9 +1727,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" dependencies = [ "once_cell", ] diff --git a/Cargo.toml b/Cargo.toml index 0029123c..f71f5605 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,11 +8,8 @@ members = [ "ext", "init", "macros", - "neural", "params", "traits", - "utils", - "models/*", ] resolver = "3" @@ -27,31 +24,28 @@ license = "Apache-2.0" readme = "README.md" repository = "https://github.com/FL03/concision.git" rust-version = "1.85.0" -version = "0.2.8" +version = "0.2.9" [workspace.dependencies] -concision = { default-features = false, path = "concision", version = "0.2.8" } +concision = { default-features = false, path = "concision", version = "0.2.9" } -concision-core = { default-features = false, path = "core", version = "0.2.8" } -concision-data = { default-features = false, path = "data", version = "0.2.8" } -concision-derive = { default-features = false, path = "derive", version = "0.2.8" } -concision-init = { default-features = false, path = "init", version = "0.2.8" } -concision-macros = { default-features = false, path = "macros", version = "0.2.8" } -concision-neural = { default-features = false, path = "neural", version = "0.2.8" } -concision-params = { default-features = false, path = "params", version = "0.2.8" } -concision-traits = { default-features = false, path = "traits", version = "0.2.8" } -concision-utils = { default-features = false, path = "utils", version = "0.2.8" } +concision-core = { default-features = false, path = "core", version = "0.2.9" } +concision-data = { default-features = false, path = "data", version = "0.2.9" } +concision-derive = { default-features = false, path = "derive", version = "0.2.9" } +concision-init = { default-features = false, path = "init", version = "0.2.9" } +concision-macros = { default-features = false, path = "macros", version = "0.2.9" } +concision-neural = { default-features = false, path = "neural", version = "0.2.9" } +concision-params = { default-features = false, path = "params", version = "0.2.9" } +concision-traits = { default-features = false, path = "traits", version = "0.2.9" } # extras -concision-ext = { default-features = false, path = "ext", version = "0.2.8" } -# models -concision-kan = { default-features = false, path = "models/kan", version = "0.2.8" } -concision-models = { default-features = false, path = "models/models", version = "0.2.8" } -concision-s4 = { default-features = false, path = "models/s4", version = "0.2.8" } -concision-snn = { default-features = false, path = "models/snn", version = "0.2.8" } -concision-transformer = { default-features = false, path = "models/transformer", version = "0.2.8" } +concision-ext = { default-features = false, path = "ext", version = "0.2.9" } # custom variants = { default-features = false, features = ["derive"], version = "0.0.1" } +# tensors & arrays +ndarray = { default-features = false, version = "0.17" } +ndarray-linalg = { default-features = false, version = "0.18" } +ndarray-stats = "0.6" # benchmarking criterion = { version = "0.7" } # concurrency & parallelism @@ -63,11 +57,9 @@ serde_derive = { default-features = false, version = "1" } serde_json = { default-features = false, version = "1" } # math approx = { version = "0.5" } -ndarray = { default-features = false, version = "0.17" } -ndarray-linalg = { default-features = false, version = "0.18" } -ndarray-stats = "0.6" num = { default-features = false, version = "0.4" } num-complex = { default-features = false, version = "0.4" } +num-integer = { default-features = false, version = "0.1" } num-traits = { default-features = false, version = "0.2" } rustfft = { version = "6" } # random diff --git a/SECURITY.md b/SECURITY.md index c258463b..f78aac3a 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,12 +6,14 @@ Checkout the current and supported packages below | Version | Supported | |:------------------|:-------------------| -| 0.2.1 | :white_check_mark: | -| <=0.2.0,>=0.1.20 | :white_check_mark: | -| <=0.1.19 | :x: | +| 0.2.9 | :white_check_mark: | +| <=0.2.0,>0.2.9 | :white_check_mark: | +| <=0.2.0 | :x: | ## Reporting a Vulnerability +To report a security vulnerability, please: + - Open an issue on the [repositry](https://github.com/FL03/concision/issues) to report any vulnerabilities - Email our [support team](mailto:support@scsys.io) to communicate any security issues - Visit our [website](https://scsys.io/) for more information. diff --git a/concision/Cargo.toml b/concision/Cargo.toml index 6f053c2a..9c1b0497 100644 --- a/concision/Cargo.toml +++ b/concision/Cargo.toml @@ -1,206 +1,184 @@ [package] build = "build.rs" -name = "concision" - -authors.workspace = true -categories.workspace = true -description.workspace = true -edition.workspace = true -homepage.workspace = true -keywords.workspace = true -license.workspace = true -readme.workspace = true -repository.workspace = true +name = "concision" + +authors.workspace = true +categories.workspace = true +description.workspace = true +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true rust-version.workspace = true -version.workspace = true +version.workspace = true -[lib] -crate-type = ["cdylib", "rlib"] +[package.metadata.docs.rs] +all-features = false +doc-scrape-examples = true +features = ["full"] +rustc-args = ["--cfg", "docsrs"] +version = "v{{version}}" -bench = true -doc = true -doctest = true -test = true +[package.metadata.release] +no-dev-version = true +tag-name = "{{version}}" -# ************* [Benchmarks] ************* -[[bench]] -harness = false -name = "default" -path = "benches/default.rs" -required-features = ["std"] +[lib] +bench = true +crate-type = ["cdylib", "rlib"] +doc = true +doctest = true +name = "cnc" +path = "lib.rs" +test = true [[bench]] harness = false name = "params" path = "benches/params.rs" required-features = [ - "neural", "approx", "rand", "std", "tracing", ] -# ************* [Examples] ************* [[example]] -name = "basic" -required-features = [ - "approx", - "rand", - "std", - "tracing", -] +name = "params" +required-features = ["rand", "std", "tracing"] + +[[example]] +name = "simple" +required-features = ["rand", "std", "tracing"] -# ************* [Unit Tests] ************* [[test]] name = "default" [[test]] -name = "simple" -path = "tests/simple/main.rs" -required-features = ["approx", "default", "neural", "rand"] +name = "simple" +required-features = ["rand", "std"] [dependencies] -concision-core = { workspace = true } -concision-data = { optional = true, workspace = true } +concision-core = { workspace = true } +concision-data = { optional = true, workspace = true } concision-derive = { optional = true, workspace = true } concision-macros = { optional = true, workspace = true } -concision-neural = { optional = true, workspace = true } [dev-dependencies] -anyhow = { features = ["std"], workspace = true } -approx = { workspace = true } -criterion = { features = ["plotters"], workspace = true } -lazy_static = { workspace = true } -ndarray = { workspace = true } -num = { features = ["rand", "serde"], workspace = true } -serde = { features = ["std"], workspace = true } -tracing = { features = ["log", "std", "attributes"], workspace = true } +anyhow = { features = ["std"], workspace = true } +approx = { workspace = true } +criterion = { features = ["plotters"], workspace = true } +lazy_static = { workspace = true } +ndarray = { workspace = true } +num = { features = ["rand", "serde"], workspace = true } +serde = { features = ["std"], workspace = true } +tracing = { features = ["attributes", "log", "std"], workspace = true } tracing-subscriber = { workspace = true } [features] default = [ - "data", - "neural", + "rand", "std", - "utils", ] full = [ - "default", "approx", "data", + "default", "derive", "macros", "rand", "serde", - "tracing" + "tracing", ] nightly = [ "concision-core/nightly", "concision-data?/nightly", - "concision-neural?/nightly", ] # ************* [FF:Features] ************* derive = ["dep:concision-derive"] -macros = ["dep:concision-macros"] +macros = ["concision-core/macros", "dep:concision-macros"] data = ["dep:concision-data"] -init = ["concision-core/init"] - -neural = ["dep:concision-neural", "alloc"] - -utils = ["concision-core/utils"] +neural = ["alloc"] # ************* [FF:Environments] ************* std = [ "alloc", "concision-core/std", "concision-data?/std", - "concision-neural?/std", ] wasi = [ "concision-core/wasi", "concision-data?/wasi", - "concision-neural?/wasi" ] wasm = [ "concision-core/wasm", "concision-data?/wasm", - "concision-neural?/wasm" ] # ************* [FF:Dependencies] ************* alloc = [ "concision-core/alloc", "concision-data?/alloc", - "concision-neural?/alloc", ] approx = [ "concision-core/approx", "concision-data?/approx", - "concision-neural?/approx" ] complex = [ "concision-core/complex", "concision-data?/complex", - "concision-neural?/complex" ] json = [ "concision-core/json", "concision-data?/json", - "concision-neural?/json" ] rand = [ "concision-core/rand", "concision-data?/rand", - "concision-neural?/rand" ] rng = [ "concision-core/rng", "concision-data?/rng", - "concision-neural?/rng" ] rayon = [ "concision-core/rayon", "concision-data?/rayon", - "concision-neural?/rayon" ] rustfft = [ "concision-core/rustfft", - "concision-neural?/rustfft" ] serde = [ "concision-core/serde", "concision-data?/serde", - "concision-neural?/serde" ] tracing = [ "concision-core/tracing", "concision-data?/tracing", - "concision-neural?/tracing" ] # ********* [FF] Blas ********* blas = [ "concision-core/blas", "concision-data?/blas", - "concision-neural?/blas" ] intel-mkl-system = ["blas"] @@ -214,15 +192,3 @@ netlib-static = ["blas"] openblas-system = ["blas"] openblas-static = ["blas"] - -# ********* [Metadata] ********* -[package.metadata.docs.rs] -all-features = false -doc-scrape-examples = true -features = ["full"] -rustc-args = ["--cfg", "docsrs"] -version = "v{{version}}" - -[package.metadata.release] -no-dev-version = true -tag-name = "{{version}}" diff --git a/concision/benches/default.rs b/concision/benches/default.rs deleted file mode 100644 index e9b764e2..00000000 --- a/concision/benches/default.rs +++ /dev/null @@ -1,194 +0,0 @@ -/* - Appellation: default - Contrib: @FL03 -*/ -use core::hint::black_box; -use criterion::{BatchSize, BenchmarkId, Criterion, criterion_group, criterion_main}; -use lazy_static::lazy_static; -use std::time::Duration; - -const SAMPLES: usize = 50; -/// the default number of iterations to benchmark a method for -const N: usize = 20; -/// the default number of seconds a benchmark should complete in -const DEFAULT_DURATION_SECS: u64 = 10; - -lazy_static! { - /// a static reference to the duration of the benchmark - static ref DURATION: Duration = Duration::from_secs(DEFAULT_DURATION_SECS); -} - -fn bench_fib_func(c: &mut Criterion) { - c.bench_function("fibonacci", |b| b.iter(|| fib::fibonacci(black_box(N)))); -} - -fn bench_fib_recursive(c: &mut Criterion) { - c.bench_function("recursive_fibonacci", |b| { - b.iter(|| fib::recursive_fibonacci(black_box(N))) - }); -} - -fn bench_fib_iter(c: &mut Criterion) { - let measure_for = Duration::from_secs(DEFAULT_DURATION_SECS); - // create a benchmark group for the Fibonacci iterator - let mut group = c.benchmark_group("Fibonacci Iter"); - // set the measurement time for the group - group.measurement_time(measure_for); - //set the sample size - group.sample_size(SAMPLES); - - for &n in &[10, 50, 100, 500, 1000] { - group.bench_with_input(BenchmarkId::new("Fibonacci::compute", n), &n, |b, &x| { - b.iter_batched( - fib::Fibonacci::new, - |mut fib| { - black_box(fib.compute(x)); - }, - BatchSize::SmallInput, - ); - }); - } - - group.finish(); -} -// initialize the benchmark group -criterion_group! { - benches, - bench_fib_func, - bench_fib_iter, - bench_fib_recursive, -} -// This macro expands to a function named `benches`, which uses the given config -criterion_main!(benches); - -pub mod fib { - //! various implementations of the fibonacci sequence - //! - //! ##_Definition_: - //! - //! $F(0) = F(1) = 1 \text{ and } F(n+1) = F(n) + F(n-1) | \forall: n > 0$ - - /// a simple implementation of the fibonacci sequence for benchmarking purposes - /// **Warning:** This will overflow the 128-bit unsigned integer at n=186 - #[inline] - pub fn fibonacci(n: usize) -> u128 { - // Use a and b to store the previous two values in the sequence - let mut a = 0; - let mut b = 1; - for _ in 0..n { - // As we iterate through, move b's value into a and the new computed - // value into b. - let c = a + b; - a = b; - b = c; - } - b - } - /// a recursive implementation of the fibonacci sequence - pub const fn recursive_fibonacci(n: usize) -> u128 { - const fn _inner(n: usize, previous: u128, current: u128) -> u128 { - if n == 0 { - current - } else { - _inner(n - 1, current, current + previous) - } - } - // Call the actual tail recursive implementation, with the extra - // arguments set up. - _inner(n, 0, 1) - } - /// A structural implementation of the fibonacci sequence that leverages the - /// [`iter`](core::iter) as its backend - #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] - pub struct Fibonacci { - curr: u32, - next: u32, - } - - impl Fibonacci { - /// returns a new instance of the fibonacci sequence, with `curr` set to 0 and `next` - /// set to 1 - pub const fn new() -> Fibonacci { - Fibonacci { curr: 0, next: 1 } - } - /// returns a copy of the current value - pub const fn curr(&self) -> u32 { - self.curr - } - /// returns a mutable reference to the current value - pub const fn curr_mut(&mut self) -> &mut u32 { - &mut self.curr - } - /// returns a copy of the next value - pub const fn next(&self) -> u32 { - self.next - } - /// returns a mutable reference to the next value - pub const fn next_mut(&mut self) -> &mut u32 { - &mut self.next - } - /// computes the nth value of the fibonacci sequence - #[inline] - pub fn compute(&mut self, n: usize) -> u32 { - if let Some(res) = self.nth(n + 1) { - return res; - } - panic!("Unable to compute the nth value of the fibonacci sequence...") - } - /// reset the instance to its default state, with `curr` set to 0 and `next` set to 1 - pub const fn reset(&mut self) -> &mut Self { - self.set_curr(0).set_next(1) - } - /// compute the next value in the fibonacci sequence, using the current and next values - #[inline] - const fn compute_next(&self) -> u32 { - self.curr() + self.next() - } - /// [`replace`](core::mem::replace) the current value with the given value, returning the - /// previous value - const fn replace_curr(&mut self, curr: u32) -> u32 { - core::mem::replace(self.curr_mut(), curr) - } - /// [`replace`](core::mem::replace) the next value with the given value, returning the - /// previous value - const fn replace_next(&mut self, next: u32) -> u32 { - core::mem::replace(self.next_mut(), next) - } - /// update the current value and return a mutable reference to the instance - const fn set_curr(&mut self, curr: u32) -> &mut Self { - self.curr = curr; - self - } - /// update the next value and return a mutable reference to the instance - const fn set_next(&mut self, next: u32) -> &mut Self { - self.next = next; - self - } - /// replace the next value with the given, using the previous next as the new current - /// value, and returning the previous current value - const fn update(&mut self, next: u32) -> u32 { - let new = self.replace_next(next); - self.replace_curr(new) - } - } - - impl Default for Fibonacci { - fn default() -> Self { - Self::new() - } - } - - impl Iterator for Fibonacci { - type Item = u32; - - fn next(&mut self) -> Option { - // compute the new next value - let new_next = self.compute_next(); - // replaces the current next with the new value and replaces the current value with - // the previous next - let prev = self.update(new_next); - // return the previous current value - Some(prev) - } - } -} diff --git a/concision/benches/params.rs b/concision/benches/params.rs index ef271f27..06b51887 100644 --- a/concision/benches/params.rs +++ b/concision/benches/params.rs @@ -2,8 +2,7 @@ appellation: params authors: @FL03 */ -extern crate concision as cnc; -use cnc::init::Initialize; +use cnc::init::InitRand; use core::hint::black_box; use criterion::{BatchSize, BenchmarkId, Criterion}; diff --git a/concision/examples/basic.rs b/concision/examples/params.rs similarity index 93% rename from concision/examples/basic.rs rename to concision/examples/params.rs index 15e54089..a8d1c8fb 100644 --- a/concision/examples/basic.rs +++ b/concision/examples/params.rs @@ -1,10 +1,8 @@ /* - Appellation: basic + Appellation: params Contrib: FL03 */ -extern crate concision as cnc; - -use cnc::init::Initialize; +use cnc::init::InitRand; use cnc::params::Params; use ndarray::prelude::*; diff --git a/models/models/examples/simple.rs b/concision/examples/simple.rs similarity index 74% rename from models/models/examples/simple.rs rename to concision/examples/simple.rs index 6fdc3007..b399bdac 100644 --- a/models/models/examples/simple.rs +++ b/concision/examples/simple.rs @@ -1,11 +1,10 @@ /* - appellation: simple - authors: @FL03 + Appellation: linear + Created At: 2025.11.26:14:10:58 + Contrib: @FL03 */ -extern crate concision as cnc; - -use cnc::nn::{ModelFeatures, Predict, StandardModelConfig, Train}; -use concision_ext::simple::SimpleModel; +use cnc::models::ex::sample::TestModel; +use cnc::{ModelFeatures, Predict, StandardModelConfig, Train}; use ndarray::prelude::*; fn main() -> anyhow::Result<()> { @@ -27,11 +26,13 @@ fn main() -> anyhow::Result<()> { config.set_decay(0.0001); tracing::debug!("Model Config: {config:?}"); // initialize the model - let mut model = SimpleModel::::new(config, features).init(); + let mut model = TestModel::::new(config, features).init(); // initialize some input data let input = Array1::linspace(1.0, 9.0, model.features().input()); // propagate the input through the model - let output = model.predict(&input)?; + let output = model + .predict(&input) + .expect("Failed to forward the input through the model"); tracing::info!("output: {:?}", output); // verify the output shape assert_eq!(output.dim(), (model.features().output())); @@ -43,7 +44,9 @@ fn main() -> anyhow::Result<()> { model.train(&training_input, &expected_output)?; } // forward the input through the model - let output = model.predict(&input)?; + let output = model + .predict(&input) + .expect("Failed to forward the input through the model"); tracing::info!("output: {:?}", output); Ok(()) diff --git a/concision/src/lib.rs b/concision/lib.rs similarity index 90% rename from concision/src/lib.rs rename to concision/lib.rs index f8e21606..aa974afc 100644 --- a/concision/src/lib.rs +++ b/concision/lib.rs @@ -51,7 +51,6 @@ //! - **Visualization**: Utilities for visualizing model architectures and training progress //! - **WASM**: Native support for WebAssembly enabling models to be run in web browsers. //! -#![crate_name = "concision"] #![crate_type = "lib"] #![allow( clippy::missing_safety_doc, @@ -61,13 +60,6 @@ )] #![cfg_attr(not(feature = "std"), no_std)] -#[macro_use] -#[cfg(feature = "macros")] -pub(crate) mod macros { - #[macro_use] - pub mod config; -} - #[doc(inline)] pub use concision_core::*; #[doc(inline)] @@ -81,10 +73,6 @@ pub use concision_macros::*; #[doc(inline)] #[cfg(feature = "data")] pub use concision_data as data; -/// this module defines various neural network abstractions, layers, and training utilities -#[doc(inline)] -#[cfg(feature = "neural")] -pub use concision_neural as nn; #[doc(hidden)] pub mod prelude { @@ -99,7 +87,4 @@ pub mod prelude { #[doc(no_inline)] #[cfg(feature = "macros")] pub use concision_macros::*; - #[doc(no_inline)] - #[cfg(feature = "neural")] - pub use concision_neural::prelude::*; } diff --git a/concision/tests/simple/main.rs b/concision/tests/simple.rs similarity index 52% rename from concision/tests/simple/main.rs rename to concision/tests/simple.rs index cb5754f3..c088d3c3 100644 --- a/concision/tests/simple/main.rs +++ b/concision/tests/simple.rs @@ -2,34 +2,10 @@ appellation: simple authors: @FL03 */ -mod model; - -extern crate concision as cnc; - -use cnc::nn::{Model, ModelFeatures, StandardModelConfig}; +use cnc::models::ex::sample::TestModel; +use cnc::{Model, ModelFeatures, StandardModelConfig}; use ndarray::prelude::*; -use model::SimpleModel; - -#[test] -fn test_standard_model_config() { - // initialize a new model configuration with then given epochs and batch size - let mut config = StandardModelConfig::new() - .with_epochs(1000) - .with_batch_size(32); - // set various hyperparameters - config.set_learning_rate(0.01); - config.set_momentum(0.9); - config.set_decay(0.0001); - // verify the configuration - assert_eq!(config.batch_size(), 32); - assert_eq!(config.epochs(), 1000); - // validate the stored hyperparameters - assert_eq!(config.learning_rate(), Some(&0.01)); - assert_eq!(config.momentum(), Some(&0.9)); - assert_eq!(config.decay(), Some(&0.0001)); -} - #[test] fn test_simple_model() -> anyhow::Result<()> { let mut config = StandardModelConfig::new() @@ -41,7 +17,7 @@ fn test_simple_model() -> anyhow::Result<()> { // define the model features let features = ModelFeatures::deep(3, 9, 1, 8); // initialize the model with the given features and configuration - let model = SimpleModel::::new(config, features); + let model = TestModel::::new(config, features); // initialize some input data let input = Array1::linspace(1.0, 9.0, model.layout().input()); let expected = Array1::from_elem(model.layout().output(), 0.5); diff --git a/core/Cargo.toml b/core/Cargo.toml index f46649a4..0104084a 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -36,16 +36,18 @@ test = true concision-init = { workspace = true } concision-params = { workspace = true } concision-traits = { workspace = true } -concision-utils = { optional = true, workspace = true } # custom variants = { workspace = true } # concurrency & parallelism rayon = { optional = true, workspace = true } -# data & serialization +# data structures +ndarray = { workspace = true } +# serialization serde = { features = ["derive"], optional = true, workspace = true } serde_derive = { optional = true, workspace = true } serde_json = { optional = true, workspace = true } # error-handling +anyhow = { workspace = true } thiserror = { workspace = true } # logging tracing = { optional = true, workspace = true } @@ -55,9 +57,8 @@ smart-default = { workspace = true } strum = { workspace = true } # mathematics approx = { optional = true, workspace = true } -ndarray = { workspace = true } -num = { workspace = true } num-complex = { optional = true, workspace = true } +num-integer = { workspace = true } num-traits = { workspace = true } rustfft = { optional = true, workspace = true } # random @@ -69,13 +70,12 @@ rand_distr = { optional = true, workspace = true } lazy_static = { workspace = true } [features] -default = ["std", "utils"] +default = ["macros", "std"] full = [ "default", "approx", "complex", - "init", "json", "rand", "serde", @@ -87,35 +87,26 @@ nightly = [ "concision-init/nightly", "concision-params/nightly", "concision-traits/nightly", - "concision-utils?/nightly", ] # ************* [FF:Features] ************* -init = [ - "rand", -] +init = ["rand"] -json = ["alloc", "serde", "serde_json"] - -signal =[ - "complex", - "concision-utils?/signal", - "rustfft", -] +macros = [] -utils = ["concision_utils"] +json = ["alloc", "serde", "serde_json"] -concision_utils = ["dep:concision-utils"] +signal = ["complex", "rustfft"] # ************* [FF:Dependencies] ************* std = [ "alloc", + "anyhow/std", "concision-init/std", "concision-params/std", "concision-traits/std", - "concision-utils?/std", "ndarray/std", - "num/std", + "num-integer/std", "num-complex?/std", "num-traits/std", "rand?/std", @@ -131,7 +122,6 @@ wasi = [ "concision-init/wasi", "concision-params/wasi", "concision-traits/wasi", - "concision-utils?/wasi", ] wasm = [ @@ -139,15 +129,13 @@ wasm = [ "concision-init/wasm", "concision-params/wasm", "concision-traits/wasm", - "concision-utils?/wasm", ] + # ************* [FF:Dependencies] ************* alloc = [ "concision-init/alloc", "concision-params/alloc", "concision-traits/alloc", - "concision-utils?/alloc", - "num/alloc", "serde?/alloc", "serde_json?/alloc", "variants/alloc", @@ -157,14 +145,12 @@ approx = [ "dep:approx", "concision-init/approx", "concision-params/approx", - "concision-utils?/approx", "ndarray/approx", ] blas = [ "concision-init/blas", "concision-params/blas", - "concision-utils?/blas", "ndarray/blas", ] @@ -172,7 +158,6 @@ complex = [ "dep:num-complex", "concision-init/complex", "concision-params/complex", - "concision-utils?/complex", ] rand = [ @@ -181,8 +166,6 @@ rand = [ "concision-init/rand", "concision-params/rand", "concision-traits/rand", - "concision-utils?/rand", - "num/rand", "num-complex?/rand", "rng", ] @@ -190,7 +173,6 @@ rand = [ rayon = [ "dep:rayon", "concision-params/rayon", - "concision-utils?/rayon", "ndarray/rayon", ] @@ -199,7 +181,6 @@ rng = [ "concision-init/rng", "concision-params/rng", "concision-traits/rng", - "concision-utils?/rng", "rand?/small_rng", "rand?/thread_rng", ] @@ -211,9 +192,7 @@ serde = [ "dep:serde_derive", "concision-init/serde", "concision-params/serde", - "concision-utils?/serde", "ndarray/serde", - "num/serde", "num-complex?/serde", "rand?/serde", "rand_distr?/serde", @@ -223,18 +202,17 @@ serde_json = ["dep:serde_json"] tracing = [ "concision-init/tracing", - "concision-utils?/tracing", "dep:tracing", ] # ************* [Unit Tests] ************* -[[test]] -name = "default" - [[test]] name = "params" required-features = ["approx", "std"] [[test]] -name = "traits" -required-features = ["std"] +name = "fft" +required-features = ["alloc", "signal"] + +[[test]] +name = "utils" \ No newline at end of file diff --git a/core/src/activate/impls/impl_binary.rs b/core/src/activate/impls/impl_binary.rs index 16421cd8..46e77e28 100644 --- a/core/src/activate/impls/impl_binary.rs +++ b/core/src/activate/impls/impl_binary.rs @@ -2,7 +2,7 @@ Appellation: binary Contrib: FL03 */ -use crate::activate::{Heavyside, utils::heavyside}; +use crate::activate::{HeavysideActivation, utils::heavyside}; use ndarray::{Array, ArrayBase, Data, Dimension}; use num_traits::{One, Zero}; @@ -11,7 +11,7 @@ macro_rules! impl_heavyside { $(impl_heavyside!(@impl $ty);)* }; (@impl $ty:ty) => { - impl Heavyside for $ty { + impl HeavysideActivation for $ty { type Output = $ty; fn heavyside(self) -> Self::Output { @@ -33,36 +33,36 @@ impl_heavyside!( f32, f64, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, ); -impl Heavyside for ArrayBase +impl HeavysideActivation for ArrayBase where - A: Clone + Heavyside, + A: Clone + HeavysideActivation, D: Dimension, S: Data, { type Output = Array; fn heavyside(self) -> Self::Output { - self.mapv(Heavyside::heavyside) + self.mapv(HeavysideActivation::heavyside) } fn heavyside_derivative(self) -> Self::Output { - self.mapv(Heavyside::heavyside_derivative) + self.mapv(HeavysideActivation::heavyside_derivative) } } -impl Heavyside for &ArrayBase +impl HeavysideActivation for &ArrayBase where - A: Clone + Heavyside, + A: Clone + HeavysideActivation, D: Dimension, S: Data, { type Output = Array; fn heavyside(self) -> Self::Output { - self.mapv(Heavyside::heavyside) + self.mapv(HeavysideActivation::heavyside) } fn heavyside_derivative(self) -> Self::Output { - self.mapv(Heavyside::heavyside_derivative) + self.mapv(HeavysideActivation::heavyside_derivative) } } diff --git a/core/src/activate/impls/impl_nonlinear.rs b/core/src/activate/impls/impl_nonlinear.rs index a6e793c7..2272e635 100644 --- a/core/src/activate/impls/impl_nonlinear.rs +++ b/core/src/activate/impls/impl_nonlinear.rs @@ -2,12 +2,14 @@ Appellation: sigmoid Contrib: FL03 */ -use crate::activate::{ReLU, Sigmoid, Softmax, Tanh, utils::sigmoid_derivative}; +use crate::activate::{ + ReLUActivation, SigmoidActivation, SoftmaxActivation, TanhActivation, utils::sigmoid_derivative, +}; use ndarray::{Array, ArrayBase, Data, Dimension, ScalarOperand}; use num_traits::{Float, One, Zero}; -impl ReLU for ArrayBase +impl ReLUActivation for ArrayBase where A: Copy + PartialOrd + Zero + One, S: Data, @@ -24,7 +26,7 @@ where } } -impl Sigmoid for ArrayBase +impl SigmoidActivation for ArrayBase where A: ScalarOperand + Float, S: Data, @@ -44,7 +46,7 @@ where } } -impl Softmax for ArrayBase +impl SoftmaxActivation for ArrayBase where A: ScalarOperand + Float, S: Data, @@ -67,7 +69,7 @@ where } } -impl Tanh for ArrayBase +impl TanhActivation for ArrayBase where A: ScalarOperand + Float, S: Data, diff --git a/core/src/activate/traits/activate.rs b/core/src/activate/traits/activate.rs index 0092529a..8e671253 100644 --- a/core/src/activate/traits/activate.rs +++ b/core/src/activate/traits/activate.rs @@ -4,7 +4,7 @@ */ use super::unary::*; -use crate::Apply; +use concision_traits::Apply; #[cfg(feature = "complex")] use num_complex::ComplexFloat; use num_traits::One; @@ -31,56 +31,56 @@ pub trait Rho: Apply { fn heavyside(&self) -> Self::Cont where - U: Heavyside, + U: HeavysideActivation, { self.apply(|x| x.heavyside()) } fn heavyside_derivative(&self) -> Self::Cont where - U: Heavyside, + U: HeavysideActivation, { self.apply(|x| x.heavyside_derivative()) } fn relu(&self) -> Self::Cont where - U: ReLU, + U: ReLUActivation, { self.apply(|x| x.relu()) } fn relu_derivative(&self) -> Self::Cont where - U: ReLU, + U: ReLUActivation, { self.apply(|x| x.relu_derivative()) } fn sigmoid(&self) -> Self::Cont where - U: Sigmoid, + U: SigmoidActivation, { self.apply(|x| x.sigmoid()) } fn sigmoid_derivative(&self) -> Self::Cont where - U: Sigmoid, + U: SigmoidActivation, { self.apply(|x| x.sigmoid_derivative()) } fn tanh(&self) -> Self::Cont where - U: Tanh, + U: TanhActivation, { self.apply(|x| x.tanh()) } fn tanh_derivative(&self) -> Self::Cont where - U: Tanh, + U: TanhActivation, { self.apply(|x| x.tanh_derivative()) } diff --git a/core/src/activate/traits/unary.rs b/core/src/activate/traits/unary.rs index c299b2e4..cd482a3c 100644 --- a/core/src/activate/traits/unary.rs +++ b/core/src/activate/traits/unary.rs @@ -48,14 +48,14 @@ macro_rules! unary { } unary! { - Heavyside::heavyside(self), + HeavysideActivation::heavyside(self), LinearActivation::linear(self), - Sigmoid::sigmoid(self), - Softmax::softmax(&self), - ReLU::relu(&self), - Tanh::tanh(&self), + SigmoidActivation::sigmoid(self), + SoftmaxActivation::softmax(&self), + ReLUActivation::relu(&self), + TanhActivation::tanh(&self), } -pub trait SoftmaxAxis: Softmax { +pub trait SoftmaxAxis: SoftmaxActivation { fn softmax_axis(self, axis: usize) -> Self::Output; } diff --git a/core/src/activate/utils/non_linear.rs b/core/src/activate/utils/non_linear.rs index 3af4249c..61d218ac 100644 --- a/core/src/activate/utils/non_linear.rs +++ b/core/src/activate/utils/non_linear.rs @@ -82,14 +82,14 @@ where /// ``` pub fn tanh(args: T) -> T where - T: num::traits::Float, + T: Float, { args.tanh() } /// the derivative of the tanh function pub fn tanh_derivative(args: T) -> T where - T: num::traits::Float, + T: Float, { let t = tanh(args); T::one() - t * t diff --git a/core/src/activate/utils/simple.rs b/core/src/activate/utils/simple.rs index 5d680b27..fb5f04a2 100644 --- a/core/src/activate/utils/simple.rs +++ b/core/src/activate/utils/simple.rs @@ -16,7 +16,7 @@ pub fn linear_derivative() -> T where T: One, { - num::one() + ::one() } /// Heaviside activation function: diff --git a/core/src/config/mod.rs b/core/src/config/mod.rs new file mode 100644 index 00000000..035aa9a5 --- /dev/null +++ b/core/src/config/mod.rs @@ -0,0 +1,55 @@ +/* + appellation: config + authors: @FL03 +*/ +//! This module is dedicated to establishing common interfaces for valid configuration objects +//! while providing a standard implementation to quickly spin up a new model. +#[doc(inline)] +pub use self::{model_config::StandardModelConfig, traits::*, types::*}; + +pub mod model_config; + +mod traits { + #[doc(inline)] + pub use self::config::*; + + mod config; +} + +mod types { + #[doc(inline)] + pub use self::{hyper_params::*, key_value::*}; + + mod hyper_params; + mod key_value; +} + +pub(crate) mod prelude { + pub use super::model_config::*; + pub use super::traits::*; + pub use super::types::*; +} + +#[cfg(test)] +mod tests { + use super::StandardModelConfig; + + #[test] + fn test_standard_model_config() { + // initialize a new model configuration with then given epochs and batch size + let mut config = StandardModelConfig::new() + .with_epochs(1000) + .with_batch_size(32); + // set various hyperparameters + config.set_learning_rate(0.01); + config.set_momentum(0.9); + config.set_decay(0.0001); + // verify the configuration + assert_eq!(config.batch_size(), 32); + assert_eq!(config.epochs(), 1000); + // validate the stored hyperparameters + assert_eq!(config.learning_rate(), Some(&0.01)); + assert_eq!(config.momentum(), Some(&0.9)); + assert_eq!(config.decay(), Some(&0.0001)); + } +} diff --git a/neural/src/config/model_config.rs b/core/src/config/model_config.rs similarity index 97% rename from neural/src/config/model_config.rs rename to core/src/config/model_config.rs index 27adda41..1c4d4aa9 100644 --- a/neural/src/config/model_config.rs +++ b/core/src/config/model_config.rs @@ -3,7 +3,7 @@ Contrib: @FL03 */ use super::Hyperparameters::*; -use super::{NetworkConfig, RawConfig, TrainingConfiguration}; +use super::{ExtendedModelConfig, ModelConfiguration, RawConfig}; #[cfg(all(feature = "alloc", not(feature = "std")))] pub(crate) type ModelConfigMap = alloc::collections::BTreeMap; @@ -138,7 +138,7 @@ impl RawConfig for StandardModelConfig { type Ctx = T; } -impl NetworkConfig for StandardModelConfig { +impl ModelConfiguration for StandardModelConfig { fn get(&self, key: K) -> Option<&T> where K: AsRef, @@ -180,7 +180,7 @@ impl NetworkConfig for StandardModelConfig { } } -impl TrainingConfiguration for StandardModelConfig { +impl ExtendedModelConfig for StandardModelConfig { fn epochs(&self) -> usize { self.epochs } diff --git a/neural/src/config/traits/config.rs b/core/src/config/traits/config.rs similarity index 59% rename from neural/src/config/traits/config.rs rename to core/src/config/traits/config.rs index c8f2c136..bec53ffb 100644 --- a/neural/src/config/traits/config.rs +++ b/core/src/config/traits/config.rs @@ -1,79 +1,69 @@ -/* - Appellation: config - Contrib: @FL03 -*/ - -/// The [`RawConfig`] trait defines a basic interface for all _configurations_ used within the -/// framework for neural networks, their layers, and more. -pub trait RawConfig { - type Ctx; -} - -/// The [`NetworkConfig`] trait extends the [`RawConfig`] trait to provide a more robust -/// interface for neural network configurations. -pub trait NetworkConfig: RawConfig { - fn get(&self, key: K) -> Option<&T> - where - K: AsRef; - fn get_mut(&mut self, key: K) -> Option<&mut T> - where - K: AsRef; - - fn set(&mut self, key: K, value: T) -> Option - where - K: AsRef; - fn remove(&mut self, key: K) -> Option - where - K: AsRef; - fn contains(&self, key: K) -> bool - where - K: AsRef; - - fn keys(&self) -> Vec; -} - -macro_rules! hyperparam_method { - (@dyn $name:ident: $type:ty) => { - fn $name(&self) -> Option<&$type> where T: 'static { - self.get(stringify!($name)).map(|v| v.downcast_ref::<$type>()).flatten() - } - }; - (@impl $name:ident: $type:ty) => { - fn $name(&self) -> Option<&$type> { - self.get(stringify!($name)) - } - }; - (#[dyn] $($name:ident $type:ty),* $(,)?) => { - $( - hyperparam_method!(@dyn $name: $type); - )* - }; - ($($name:ident $type:ty),* $(,)?) => { - $( - hyperparam_method!(@impl $name: $type); - )* - }; -} - -pub trait TrainingConfiguration: NetworkConfig { - fn epochs(&self) -> usize; - - fn batch_size(&self) -> usize; - - hyperparam_method! { - learning_rate T, - momentum T, - weight_decay T, - dropout T, - decay T, - beta1 T, - beta2 T, - epsilon T, - gradient_clip T, - - } -} - -/* - ************* Implementations ************* -*/ +/* + Appellation: config + Contrib: @FL03 +*/ + +/// The [`RawConfig`] trait defines a basic interface for all _configurations_ used within the +/// framework for neural networks, their layers, and more. +pub trait RawConfig { + type Ctx; +} + +/// The [`ModelConfiguration`] trait extends the [`RawConfig`] trait to provide a more robust +/// interface for neural network configurations. +pub trait ModelConfiguration: RawConfig { + fn get(&self, key: K) -> Option<&T> + where + K: AsRef; + fn get_mut(&mut self, key: K) -> Option<&mut T> + where + K: AsRef; + + fn set(&mut self, key: K, value: T) -> Option + where + K: AsRef; + fn remove(&mut self, key: K) -> Option + where + K: AsRef; + fn contains(&self, key: K) -> bool + where + K: AsRef; + + fn keys(&self) -> Vec; +} + +macro_rules! hyperparam_method { + ($($(dyn)? $name:ident::<$type:ty>),* $(,)?) => { + $( + hyperparam_method!(@impl $name::<$type>); + )* + }; + (@impl dyn $name:ident::<$type:ty>) => { + fn $name(&self) -> Option<&$type> where T: 'static { + self.get(stringify!($name)).map(|v| v.downcast_ref::<$type>()).flatten() + } + }; + (@impl $name:ident::<$type:ty>) => { + fn $name(&self) -> Option<&$type> { + self.get(stringify!($name)) + } + }; +} + +pub trait ExtendedModelConfig: ModelConfiguration { + fn epochs(&self) -> usize; + + fn batch_size(&self) -> usize; + + hyperparam_method! { + learning_rate::, + epsilon::, + momentum::, + weight_decay::, + dropout::, + decay::, + beta::, + beta1::, + beta2::, + } +} diff --git a/neural/src/config/types/hyper_params.rs b/core/src/config/types/hyper_params.rs similarity index 64% rename from neural/src/config/types/hyper_params.rs rename to core/src/config/types/hyper_params.rs index f07b201f..b64dd3b4 100644 --- a/neural/src/config/types/hyper_params.rs +++ b/core/src/config/types/hyper_params.rs @@ -2,8 +2,12 @@ Appellation: hyperparameters Contrib: @FL03 */ -use crate::types::KeyValue; +use super::KeyValue; +#[cfg(feature = "alloc")] +use alloc::string::String; + +/// An enumeration of common hyperparameters used in neural network configurations. #[derive( Clone, Debug, @@ -48,19 +52,25 @@ pub enum HyperParams { Momentum(T), Temperature(T), WeightDecay(T), - Unknown(KeyValue), + Custom { key: String, value: T }, } -impl From> for HyperParams { - fn from(kv: KeyValue) -> Self { - match kv.key.as_str() { - "decay" => HyperParams::Decay(kv.value), - "dropout" => HyperParams::Dropout(kv.value), - "learning_rate" => HyperParams::LearningRate(kv.value), - "momentum" => HyperParams::Momentum(kv.value), - "temperature" => HyperParams::Temperature(kv.value), - "weight_decay" => HyperParams::WeightDecay(kv.value), - _ => HyperParams::Unknown(kv), +impl From> for HyperParams +where + K: AsRef, +{ + fn from(KeyValue { key, value }: KeyValue) -> Self { + match key.as_ref() { + "decay" => HyperParams::Decay(value), + "dropout" => HyperParams::Dropout(value), + "learning_rate" => HyperParams::LearningRate(value), + "momentum" => HyperParams::Momentum(value), + "temperature" => HyperParams::Temperature(value), + "weight_decay" => HyperParams::WeightDecay(value), + k => HyperParams::Custom { + key: String::from(k), + value, + }, } } } diff --git a/neural/src/types/key_value.rs b/core/src/config/types/key_value.rs similarity index 100% rename from neural/src/types/key_value.rs rename to core/src/config/types/key_value.rs diff --git a/core/src/error.rs b/core/src/error.rs index bd0fdde9..5388d629 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -4,24 +4,46 @@ */ //! This module implements the core [`Error`] type for the framework and provides a [`Result`] //! type alias for convenience. -#[cfg(feature = "alloc")] -use alloc::{ - boxed::Box, - string::{String, ToString}, -}; -#[allow(dead_code)] -/// a type alias for a [Result](core::result::Result) configured with an [`Error`] as its error -/// type. +/// a type alias for a [`Result`](core::result::Result) defined to use the custom [`Error`] as its error type. pub type Result = core::result::Result; /// The [`Error`] type enumerates various errors that can occur within the framework. #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum Error { - #[error("Invalid Shape")] - InvalidShape, + #[cfg(feature = "alloc")] + #[error(transparent)] + AllocError(#[from] alloc_err::AllocError), + #[error(transparent)] + ExtError(#[from] CommonError), + #[error("The model is not trained")] + NotTrained, + #[error("Invalid model configuration")] + InvalidModelConfig, + #[error("The model is not supported for the given input")] + IncompatibleInput, + #[error("An unsupported operation was attempted")] + UnsupportedOperation, + #[error("Invalid Batch Size")] + InvalidBatchSize, + #[error("Invalid Input Shape")] + InvalidInputShape, + #[error("Invalid Output Shape")] + InvalidOutputShape, + #[error("Uninitialized")] + Uninitialized, + #[error("Unsupported model {0}")] + UnsupportedModel(&'static str), + #[error("Parameter Error")] + ParameterError(&'static str), +} + +/// The [`CommonError`] type enumerates external errors handled by the framework +#[derive(Debug, thiserror::Error)] +pub enum CommonError { #[error(transparent)] - PadError(#[from] crate::ops::pad::error::PadError), + PadError(#[from] crate::utils::pad::PadError), #[error(transparent)] TraitError(#[from] concision_traits::Error), #[error(transparent)] @@ -29,11 +51,7 @@ pub enum Error { #[error(transparent)] InitError(#[from] concision_init::InitError), #[error(transparent)] - #[cfg(feature = "concision_utils")] - UtilityError(#[from] concision_utils::error::UtilityError), - #[cfg(feature = "alloc")] - #[error(transparent)] - BoxError(#[from] Box), + ShapeError(#[from] ndarray::ShapeError), #[cfg(feature = "serde")] #[error(transparent)] DeserializeError(#[from] serde::de::value::Error), @@ -46,43 +64,31 @@ pub enum Error { #[error(transparent)] IoError(#[from] std::io::Error), #[error(transparent)] - ShapeError(#[from] ndarray::ShapeError), - #[error(transparent)] #[cfg(feature = "rand")] UniformError(#[from] rand_distr::uniform::Error), - #[cfg(feature = "alloc")] - #[error("Unknown Error: {0}")] - Unknown(String), } #[cfg(feature = "alloc")] -impl Error { - /// create a new [`BoxError`](Error::BoxError) variant using the given error - pub fn box_error(error: E) -> Self - where - E: 'static + Send + Sync + core::error::Error, - { - Self::BoxError(Box::new(error)) - } - /// create a new [`Unknown`](Error::Unknown) variant using the given string - pub fn unknown(error: S) -> Self - where - S: ToString, - { - Self::Unknown(error.to_string()) +mod alloc_err { + use alloc::{boxed::Box, string::String}; + + #[derive(Debug, thiserror::Error)] + pub enum AllocError { + #[error(transparent)] + BoxError(#[from] Box), + #[error("Unknown Error: {0}")] + Unknown(String), } -} -#[cfg(feature = "alloc")] -impl From for Error { - fn from(value: String) -> Self { - Self::Unknown(value) + impl From for AllocError { + fn from(value: String) -> Self { + Self::Unknown(value) + } } -} -#[cfg(feature = "alloc")] -impl From<&str> for Error { - fn from(value: &str) -> Self { - Self::Unknown(String::from(value)) + impl From<&str> for AllocError { + fn from(value: &str) -> Self { + Self::Unknown(String::from(value)) + } } } diff --git a/neural/src/layers/layer.rs b/core/src/layers/layer.rs similarity index 98% rename from neural/src/layers/layer.rs rename to core/src/layers/layer.rs index af4f80c1..670112e0 100644 --- a/neural/src/layers/layer.rs +++ b/core/src/layers/layer.rs @@ -13,7 +13,8 @@ mod impl_layer_repr; mod impl_layer_deprecated; use super::Activator; -use cnc::{Forward, ParamsBase}; +use concision_params::ParamsBase; +use concision_traits::Forward; use ndarray::{DataOwned, Dimension, Ix2, RawData, RemoveAxis, ShapeBuilder}; /// The [`LayerBase`] struct is a base representation of a neural network layer, essentially diff --git a/neural/src/layers/layer/impl_layer.rs b/core/src/layers/layer/impl_layer.rs similarity index 96% rename from neural/src/layers/layer/impl_layer.rs rename to core/src/layers/layer/impl_layer.rs index bcff4504..0999f9c7 100644 --- a/neural/src/layers/layer/impl_layer.rs +++ b/core/src/layers/layer/impl_layer.rs @@ -5,7 +5,8 @@ use crate::layers::LayerBase; use crate::layers::{Activator, ActivatorGradient, Layer}; -use cnc::{Forward, ParamsBase}; +use concision_params::ParamsBase; +use concision_traits::Forward; use ndarray::{Data, Dimension, RawData}; impl core::ops::Deref for LayerBase diff --git a/neural/src/layers/layer/impl_layer_deprecated.rs b/core/src/layers/layer/impl_layer_deprecated.rs similarity index 100% rename from neural/src/layers/layer/impl_layer_deprecated.rs rename to core/src/layers/layer/impl_layer_deprecated.rs diff --git a/neural/src/layers/layer/impl_layer_repr.rs b/core/src/layers/layer/impl_layer_repr.rs similarity index 97% rename from neural/src/layers/layer/impl_layer_repr.rs rename to core/src/layers/layer/impl_layer_repr.rs index adc4c360..7a1be8f2 100644 --- a/neural/src/layers/layer/impl_layer_repr.rs +++ b/core/src/layers/layer/impl_layer_repr.rs @@ -5,7 +5,7 @@ use crate::layers::layer::LayerBase; use crate::layers::{Linear, ReLU, Sigmoid, Tanh}; -use cnc::ParamsBase; +use concision_params::ParamsBase; use ndarray::{Dimension, RawData}; impl LayerBase diff --git a/neural/src/layers/mod.rs b/core/src/layers/mod.rs similarity index 58% rename from neural/src/layers/mod.rs rename to core/src/layers/mod.rs index 894e779a..cdfb8cce 100644 --- a/neural/src/layers/mod.rs +++ b/core/src/layers/mod.rs @@ -11,34 +11,21 @@ pub mod sequential; pub(crate) mod traits { #[doc(inline)] - pub use self::prelude::*; + pub use self::{activate::*, layers::*}; mod activate; mod layers; - - mod prelude { - #[doc(inline)] - pub use super::activate::*; - #[doc(inline)] - pub use super::layers::*; - } + mod store; } pub(crate) mod types { #[doc(inline)] - pub use self::prelude::*; + pub use self::aliases::*; mod aliases; - - mod prelude { - #[doc(inline)] - pub use super::aliases::*; - } } pub(crate) mod prelude { - #[doc(inline)] pub use super::layer::*; - #[doc(inline)] pub use super::types::*; } diff --git a/neural/src/layers/sequential.rs b/core/src/layers/sequential.rs similarity index 87% rename from neural/src/layers/sequential.rs rename to core/src/layers/sequential.rs index 72109f26..98d5ce88 100644 --- a/neural/src/layers/sequential.rs +++ b/core/src/layers/sequential.rs @@ -2,7 +2,7 @@ appellation: sequential authors: @FL03 */ -use cnc::params::ParamsBase; +use concision_params::ParamsBase; use ndarray::{Dimension, RawData}; #[allow(dead_code)] diff --git a/neural/src/layers/traits/activate.rs b/core/src/layers/traits/activate.rs similarity index 91% rename from neural/src/layers/traits/activate.rs rename to core/src/layers/traits/activate.rs index df37a57f..414b0ae3 100644 --- a/neural/src/layers/traits/activate.rs +++ b/core/src/layers/traits/activate.rs @@ -113,8 +113,8 @@ macro_rules! activator { } activator! { - pub struct Linear::(linear); - pub struct ReLU::(relu); - pub struct Sigmoid::(sigmoid); - pub struct Tanh::(tanh); + pub struct Linear::(linear); + pub struct ReLU::(relu); + pub struct Sigmoid::(sigmoid); + pub struct Tanh::(tanh); } diff --git a/neural/src/layers/traits/layers.rs b/core/src/layers/traits/layers.rs similarity index 96% rename from neural/src/layers/traits/layers.rs rename to core/src/layers/traits/layers.rs index 17f73dba..f3e5b81f 100644 --- a/neural/src/layers/traits/layers.rs +++ b/core/src/layers/traits/layers.rs @@ -4,8 +4,8 @@ */ use super::{Activator, ActivatorGradient}; -use cnc::params::ParamsBase; -use cnc::{Backward, Forward}; +use concision_params::ParamsBase; +use concision_traits::{Backward, Forward}; use ndarray::{Data, Dimension, RawData}; /// A generic trait defining the composition of a _layer_ within a neural network. diff --git a/utils/src/signal/fft/dft.rs b/core/src/layers/traits/store.rs similarity index 100% rename from utils/src/signal/fft/dft.rs rename to core/src/layers/traits/store.rs diff --git a/neural/src/layers/types/aliases.rs b/core/src/layers/types/aliases.rs similarity index 100% rename from neural/src/layers/types/aliases.rs rename to core/src/layers/types/aliases.rs diff --git a/core/src/layout.rs b/core/src/layout.rs new file mode 100644 index 00000000..63139e74 --- /dev/null +++ b/core/src/layout.rs @@ -0,0 +1,277 @@ +/* + appellation: layout + authors: @FL03 +*/ +mod impl_model_features; +mod impl_model_format; + +pub trait IntoModelFeatures { + fn into_model_features(self) -> ModelFeatures; +} + +/// The [`RawModelLayout`] trait defines a minimal interface for objects capable of representing +/// the _layout_; i.e. the number of input, hidden, and output features of a neural network model +/// containing some number of hidden layers. +/// +/// **Note**: This trait is implemented for the 3- and 4-tuple consiting of usize elements as +/// well as for the `[usize; 3]` and `[usize; 4]` array types. In both these instances, the +/// elements are ordered as (input, hidden, output) for the 3-element versions, and +/// (input, hidden, output, layers) for the 4-element versions. +pub trait RawModelLayout { + /// returns the total number of input features defined for the model + fn input(&self) -> usize; + /// returns the number of hidden features for the model + fn hidden(&self) -> usize; + /// the number of output features for the model + fn output(&self) -> usize; + /// returns the number of hidden layers within the network + fn layers(&self) -> usize; +} + +pub trait ModelLayoutMut: RawModelLayout { + /// returns a mutable reference to number of the input features for the model + fn input_mut(&mut self) -> &mut usize; + /// returns a mutable reference to the number of hidden features for the model + fn hidden_mut(&mut self) -> &mut usize; + /// returns a mutable reference to the number of hidden layers for the model + fn layers_mut(&mut self) -> &mut usize; + /// returns a mutable reference to the output features for the model + fn output_mut(&mut self) -> &mut usize; +} + +/// The [`ModelLayout`] trait defines an interface for object capable of representing the +/// _layout_; i.e. the number of input, hidden, and output features of a neural network model +/// containing some number of hidden layers. +pub trait ModelLayout: RawModelLayout + ModelLayoutMut + Clone + core::fmt::Debug { + /// the dimension of the input layer; (input, hidden) + fn dim_input(&self) -> (usize, usize) { + (self.input(), self.hidden()) + } + /// the dimension of the hidden layers; (hidden, hidden) + fn dim_hidden(&self) -> (usize, usize) { + (self.hidden(), self.hidden()) + } + /// the dimension of the output layer; (hidden, output) + fn dim_output(&self) -> (usize, usize) { + (self.hidden(), self.output()) + } + /// the total number of parameters in the model + fn size(&self) -> usize { + self.size_input() + self.size_hidden() + self.size_output() + } + /// the total number of input parameters in the model + fn size_input(&self) -> usize { + self.input() * self.hidden() + } + /// the total number of hidden parameters in the model + fn size_hidden(&self) -> usize { + self.hidden() * self.hidden() * self.layers() + } + /// the total number of output parameters in the model + fn size_output(&self) -> usize { + self.hidden() * self.output() + } + #[inline] + /// update the number of input features for the model and return a mutable reference to the + /// current layout. + fn set_input(&mut self, input: usize) -> &mut Self { + *self.input_mut() = input; + self + } + #[inline] + /// update the number of hidden features for the model and return a mutable reference to + /// the current layout. + fn set_hidden(&mut self, hidden: usize) -> &mut Self { + *self.hidden_mut() = hidden; + self + } + #[inline] + /// update the number of hidden layers for the model and return a mutable reference to + /// the current layout. + fn set_layers(&mut self, layers: usize) -> &mut Self { + *self.layers_mut() = layers; + self + } + #[inline] + /// update the number of output features for the model and return a mutable reference to + /// the current layout. + fn set_output(&mut self, output: usize) -> &mut Self { + *self.output_mut() = output; + self + } +} + +/// The [`ModelFormat`] type enumerates the various formats a neural network may take, either +/// shallow or deep, providing a unified interface for accessing the number of hidden features +/// and layers in the model. This is done largely for simplicity, as it eliminates the need to +/// define a particular _type_ of network as its composition has little impact on the actual +/// requirements / algorithms used to train or evaluate the model (that is, outside of the +/// obvious need to account for additional hidden layers in deep configurations). In other +/// words, both shallow and deep networks are requried to implement the same traits and +/// fulfill the same requirements, so it makes sense to treat them as a single type with +/// different configurations. The differences between the networks are largely left to the +/// developer and their choice of activation functions, optimizers, and other considerations. +#[derive( + Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, strum::EnumCount, strum::EnumIs, +)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub enum ModelFormat { + Shallow { hidden: usize }, + Deep { hidden: usize, layers: usize }, +} + +/// The [`ModelFeatures`] provides a common way of defining the layout of a model. This is +/// used to define the number of input features, the number of hidden layers, the number of +/// hidden features, and the number of output features. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct ModelFeatures { + /// the number of input features + pub(crate) input: usize, + /// the features of the "inner" layers + pub(crate) inner: ModelFormat, + /// the number of output features + pub(crate) output: usize, +} + +/* + ************* Implementations ************* +*/ + +impl RawModelLayout for &T +where + T: RawModelLayout, +{ + fn input(&self) -> usize { + ::input(self) + } + fn hidden(&self) -> usize { + ::hidden(self) + } + fn layers(&self) -> usize { + ::layers(self) + } + fn output(&self) -> usize { + ::output(self) + } +} + +impl RawModelLayout for &mut T +where + T: RawModelLayout, +{ + fn input(&self) -> usize { + ::input(self) + } + fn hidden(&self) -> usize { + ::hidden(self) + } + fn layers(&self) -> usize { + ::layers(self) + } + fn output(&self) -> usize { + ::output(self) + } +} + +impl ModelLayout for T where T: ModelLayoutMut + Copy + core::fmt::Debug {} + +impl RawModelLayout for (usize, usize, usize) { + fn input(&self) -> usize { + self.0 + } + fn hidden(&self) -> usize { + self.1 + } + fn layers(&self) -> usize { + 1 + } + fn output(&self) -> usize { + self.2 + } +} + +impl RawModelLayout for (usize, usize, usize, usize) { + fn input(&self) -> usize { + self.0 + } + fn hidden(&self) -> usize { + self.1 + } + fn output(&self) -> usize { + self.2 + } + + fn layers(&self) -> usize { + self.3 + } +} + +impl ModelLayoutMut for (usize, usize, usize, usize) { + fn input_mut(&mut self) -> &mut usize { + &mut self.0 + } + + fn hidden_mut(&mut self) -> &mut usize { + &mut self.1 + } + + fn layers_mut(&mut self) -> &mut usize { + &mut self.2 + } + + fn output_mut(&mut self) -> &mut usize { + &mut self.3 + } +} + +impl RawModelLayout for [usize; 3] { + fn input(&self) -> usize { + self[0] + } + + fn hidden(&self) -> usize { + self[1] + } + + fn output(&self) -> usize { + self[2] + } + + fn layers(&self) -> usize { + 1 + } +} + +impl RawModelLayout for [usize; 4] { + fn input(&self) -> usize { + self[0] + } + + fn hidden(&self) -> usize { + self[1] + } + + fn output(&self) -> usize { + self[2] + } + + fn layers(&self) -> usize { + self[3] + } +} + +impl ModelLayoutMut for [usize; 4] { + fn input_mut(&mut self) -> &mut usize { + &mut self[0] + } + fn hidden_mut(&mut self) -> &mut usize { + &mut self[1] + } + fn layers_mut(&mut self) -> &mut usize { + &mut self[2] + } + fn output_mut(&mut self) -> &mut usize { + &mut self[3] + } +} diff --git a/neural/src/layout/features.rs b/core/src/layout/impl_model_features.rs similarity index 84% rename from neural/src/layout/features.rs rename to core/src/layout/impl_model_features.rs index dcc4d232..bf9db0dc 100644 --- a/neural/src/layout/features.rs +++ b/core/src/layout/impl_model_features.rs @@ -2,14 +2,14 @@ Appellation: layout Contrib: @FL03 */ -use super::{ModelFormat, ModelLayout}; +use super::{ModelFeatures, ModelFormat, ModelLayoutMut, RawModelLayout}; /// verify if the input and hidden dimensions are compatible by checking: /// /// 1. they have the same dimensionality /// 2. if the the number of dimensions is greater than one, the hidden layer should be square /// 3. the finaly dimension of the input is equal to one hidden dimension -pub fn _verify_input_and_hidden_shape(input: D, hidden: D) -> bool +fn _verify_input_and_hidden_shape(input: D, hidden: D) -> bool where D: ndarray::Dimension, { @@ -25,28 +25,16 @@ where valid } -/// The [`ModelFeatures`] provides a common way of defining the layout of a model. This is -/// used to define the number of input features, the number of hidden layers, the number of -/// hidden features, and the number of output features. -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct ModelFeatures { - /// the number of input features - pub(crate) input: usize, - /// the features of the "inner" layers - pub(crate) inner: ModelFormat, - /// the number of output features - pub(crate) output: usize, -} - impl ModelFeatures { /// creates a new instance of [`ModelFeatures`] for a neural network with `n` layers. If /// the number of layers is `<=1` then the [`ModelFormat`] is automatically /// configured as a _shallow_ neural network. pub const fn new(input: usize, hidden: usize, output: usize, layers: usize) -> Self { - match layers { - 0 | 1 => Self::shallow(input, hidden, output), - _ => Self::deep(input, hidden, output, layers), + let inner = ModelFormat::new(hidden, layers); + Self { + input, + output, + inner, } } /// creates a new instance of [`ModelFeatures`] for a deep neural network, using the given @@ -55,7 +43,7 @@ impl ModelFeatures { Self { input, output, - inner: ModelFormat::deep(hidden, layers), + inner: ModelFormat::Deep { hidden, layers }, } } /// returns a new instance of [`ModelFeatures`] for a shallow neural network, using the @@ -64,7 +52,17 @@ impl ModelFeatures { Self { input, output, - inner: ModelFormat::shallow(hidden), + inner: ModelFormat::Shallow { hidden }, + } + } + pub fn from_layout(layout: L) -> Self + where + L: RawModelLayout, + { + Self { + input: layout.input(), + inner: ModelFormat::new(layout.hidden(), layout.layers()), + output: layout.output(), } } /// returns a copy of the input features for the model @@ -186,34 +184,36 @@ impl ModelFeatures { } } -impl ModelLayout for ModelFeatures { +impl RawModelLayout for ModelFeatures { fn input(&self) -> usize { self.input() } - fn input_mut(&mut self) -> &mut usize { - self.input_mut() - } - fn hidden(&self) -> usize { self.hidden() } - fn hidden_mut(&mut self) -> &mut usize { - self.hidden_mut() - } - fn layers(&self) -> usize { self.layers() } - fn layers_mut(&mut self) -> &mut usize { - self.layers_mut() - } - fn output(&self) -> usize { self.output() } +} + +impl ModelLayoutMut for ModelFeatures { + fn input_mut(&mut self) -> &mut usize { + self.input_mut() + } + + fn hidden_mut(&mut self) -> &mut usize { + self.hidden_mut() + } + + fn layers_mut(&mut self) -> &mut usize { + self.layers_mut() + } fn output_mut(&mut self) -> &mut usize { self.output_mut() @@ -232,15 +232,15 @@ impl Default for ModelFeatures { } } } + impl core::fmt::Display for ModelFeatures { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!( - f, - "{{ input: {i}, hidden: {h}, output: {o}, layers: {l} }}", - i = self.input(), - h = self.hidden(), - l = self.layers(), - o = self.output() - ) + f.write_str(&format!( + "{{ input: {}, hidden: {}, layers: {}, output: {} }}", + self.input(), + self.hidden(), + self.layers(), + self.output() + )) } } diff --git a/neural/src/layout/format.rs b/core/src/layout/impl_model_format.rs similarity index 79% rename from neural/src/layout/format.rs rename to core/src/layout/impl_model_format.rs index ac77ec26..bf8ed4ca 100644 --- a/neural/src/layout/format.rs +++ b/core/src/layout/impl_model_format.rs @@ -2,27 +2,15 @@ appellation: format authors: @FL03 */ - -/// The [`ModelFormat`] type enumerates the various formats a neural network may take, either -/// shallow or deep, providing a unified interface for accessing the number of hidden features -/// and layers in the model. This is done largely for simplicity, as it eliminates the need to -/// define a particular _type_ of network as its composition has little impact on the actual -/// requirements / algorithms used to train or evaluate the model (that is, outside of the -/// obvious need to account for additional hidden layers in deep configurations). In other -/// words, both shallow and deep networks are requried to implement the same traits and -/// fulfill the same requirements, so it makes sense to treat them as a single type with -/// different configurations. The differences between the networks are largely left to the -/// developer and their choice of activation functions, optimizers, and other considerations. -#[derive( - Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, strum::EnumCount, strum::EnumIs, -)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub enum ModelFormat { - Shallow { hidden: usize }, - Deep { hidden: usize, layers: usize }, -} +use super::ModelFormat; impl ModelFormat { + pub const fn new(hidden: usize, layers: usize) -> Self { + match layers { + 0 | 1 => ModelFormat::Shallow { hidden }, + _ => ModelFormat::Deep { hidden, layers }, + } + } /// initialize a new [`Deep`](ModelFormat::Deep) variant for a deep neural network with the /// given number of hidden features and layers pub const fn deep(hidden: usize, layers: usize) -> Self { diff --git a/core/src/lib.rs b/core/src/lib.rs index a3e01201..cee6a451 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,7 +1,3 @@ -/* - Appellation: concision-core - Contrib: @FL03 -*/ //! This crate provides the core implementations for the `cnc` framework, defining various //! traits, types, and utilities essential for building neural networks. //! @@ -37,11 +33,10 @@ )] #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(feature = "nightly", feature(allocator_api))] -#![crate_type = "lib"] -#[cfg(not(all(feature = "std", feature = "alloc")))] +#[cfg(not(any(feature = "std", feature = "alloc")))] compiler_error! { - "At least one of the 'std' or 'alloc' features must be enabled." + "Either the \"std\" feature or the \"alloc\" feature must be enabled." } #[cfg(feature = "alloc")] @@ -54,76 +49,57 @@ pub use rand; #[doc(no_inline)] pub use rand_distr; -#[doc(inline)] -pub use concision_traits as traits; - /// this module establishes generic random initialization routines for models, params, and /// tensors. #[doc(inline)] pub use concision_init as init; -/// The [`params`] module works to provide a generic structure for handling weights and biases #[doc(inline)] pub use concision_params as params; -/// this module implements various utilities useful for developing machine learning models -#[doc(inline)] -#[cfg(feature = "concision_utils")] -pub use concision_utils as utils; - -#[cfg(feature = "concision_utils")] -pub use self::utils::prelude::*; #[doc(inline)] -pub use self::{ - activate::prelude::*, - error::*, - init::{Init, InitInplace, Initialize}, - loss::prelude::*, - ops::prelude::*, - params::prelude::*, - traits::prelude::*, -}; +pub use concision_init::prelude::*; +#[doc(inline)] +pub use concision_params::prelude::*; +#[doc(inline)] +pub use concision_traits::prelude::*; #[macro_use] pub(crate) mod macros { #[macro_use] pub mod seal; + #[macro_use] + pub mod config; } -/// this module is dedicated to activation function + pub mod activate; -/// this module provides the base [`Error`] type for the library +pub mod config; pub mod error; -/// this module focuses on the loss functions used in training neural networks. -pub mod loss; - -pub mod ops { - //! This module provides the core operations for tensors, including filling, padding, - //! reshaping, and tensor manipulation. - #[doc(inline)] - pub use self::prelude::*; - - pub mod mask; - pub mod pad; +pub mod layers; +pub mod layout; +pub mod models; +pub mod nn; +pub mod utils; - pub(crate) mod prelude { - #[doc(inline)] - pub use super::mask::*; - #[doc(inline)] - pub use super::pad::*; - } +pub mod types { + //! Core types supporting the `cnc` framework. } - +// re-exports +#[doc(inline)] +pub use self::{ + activate::prelude::*, config::prelude::*, error::*, layout::*, models::prelude::*, + utils::prelude::*, +}; +// prelude #[doc(hidden)] pub mod prelude { pub use concision_init::prelude::*; pub use concision_params::prelude::*; pub use concision_traits::prelude::*; - #[cfg(feature = "concision_utils")] - pub use concision_utils::prelude::*; - #[doc(no_inline)] pub use crate::activate::prelude::*; - #[doc(no_inline)] - pub use crate::loss::prelude::*; - #[doc(no_inline)] - pub use crate::ops::prelude::*; + pub use crate::layers::prelude::*; + pub use crate::layout::*; + pub use crate::models::prelude::*; + pub use crate::nn::prelude::*; + pub use crate::utils::prelude::*; } diff --git a/core/src/loss/mod.rs b/core/src/loss/mod.rs deleted file mode 100644 index 68667db2..00000000 --- a/core/src/loss/mod.rs +++ /dev/null @@ -1,37 +0,0 @@ -/* - Appellation: loss - Contrib: @FL03 -*/ -//! # Loss Functions -//! -//! Loss functions are used to measure the difference between the predicted output and the -//! actual output of a model. Over the years, several leading approaches have been researched -//! and developed, each with its own strengths and weaknesses. This module provides a -//! collection of traits and implementations for various loss functions, including -//! entropic loss, standard loss, and others. -#[doc(inline)] -pub use self::traits::*; - -mod traits { - //! this module implements the various traits of the loss module - #[doc(inline)] - pub use self::prelude::*; - - mod entropy; - mod loss; - mod standard; - - mod prelude { - #[doc(inline)] - pub use super::entropy::*; - #[doc(inline)] - pub use super::loss::*; - #[doc(inline)] - pub use super::standard::*; - } -} - -pub(crate) mod prelude { - #[doc(inline)] - pub use super::traits::*; -} diff --git a/core/src/loss/traits/entropy.rs b/core/src/loss/traits/entropy.rs deleted file mode 100644 index 0d40da4f..00000000 --- a/core/src/loss/traits/entropy.rs +++ /dev/null @@ -1,31 +0,0 @@ -/* - Appellation: loss - Contrib: @FL03 -*/ - -/// A trait for computing the cross-entropy loss of a tensor or array -pub trait CrossEntropy { - type Output; - - fn cross_entropy(&self) -> Self::Output; -} - -/* - ************* Implementations ************* -*/ - -use ndarray::{ArrayBase, Data, Dimension, ScalarOperand}; -use num_traits::{Float, FromPrimitive}; - -impl CrossEntropy for ArrayBase -where - A: Float + FromPrimitive + ScalarOperand, - D: Dimension, - S: Data, -{ - type Output = A; - - fn cross_entropy(&self) -> Self::Output { - self.mapv(|x| -x.ln()).mean().unwrap() - } -} diff --git a/core/src/loss/traits/standard.rs b/core/src/loss/traits/standard.rs deleted file mode 100644 index 0b922fe7..00000000 --- a/core/src/loss/traits/standard.rs +++ /dev/null @@ -1,60 +0,0 @@ -/* - Appellation: loss - Contrib: @FL03 -*/ - -/// Compute the mean absolute error (MAE) of the object; more formally, we define the MAE as -/// the average of the absolute differences between the predicted and actual values: -/// -/// ```math -/// Err = \frac{1}{n} \sum_{i=1}^{n} |y_i - \hat{y}_i| -/// ``` -pub trait MeanAbsoluteError { - type Output; - - fn mae(&self) -> Self::Output; -} -/// The [`MeanSquaredError`] (MSE) is the average of the squared differences between the -/// ($$\hat{y_{i}}$$) and actual values ($`y_{i}`$): -/// -/// ```math -/// Err = \frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2 -/// ``` -pub trait MeanSquaredError { - type Output; - - fn mse(&self) -> Self::Output; -} - -/* - ************* Implementations ************* -*/ - -use ndarray::{ArrayBase, Data, Dimension, ScalarOperand}; -use num_traits::{Float, FromPrimitive}; - -impl MeanAbsoluteError for ArrayBase -where - A: Float + FromPrimitive + ScalarOperand, - D: Dimension, - S: Data, -{ - type Output = A; - - fn mae(&self) -> Self::Output { - self.abs().mean().unwrap() - } -} - -impl MeanSquaredError for ArrayBase -where - A: Float + FromPrimitive + ScalarOperand, - D: Dimension, - S: Data, -{ - type Output = A; - - fn mse(&self) -> Self::Output { - self.pow2().mean().unwrap() - } -} diff --git a/concision/src/macros/config.rs b/core/src/macros/config.rs similarity index 98% rename from concision/src/macros/config.rs rename to core/src/macros/config.rs index 8e16e14a..2e6250d1 100644 --- a/concision/src/macros/config.rs +++ b/core/src/macros/config.rs @@ -2,6 +2,7 @@ appellation: config authors: @FL03 */ +#![cfg(feature = "macros")] #[doc(hidden)] #[macro_export] diff --git a/concision/tests/simple/model.rs b/core/src/models/ex/sample.rs similarity index 72% rename from concision/tests/simple/model.rs rename to core/src/models/ex/sample.rs index ace1d77d..437d0023 100644 --- a/concision/tests/simple/model.rs +++ b/core/src/models/ex/sample.rs @@ -2,27 +2,79 @@ appellation: model authors: @FL03 */ -use cnc::nn::{DeepModelParams, Model, ModelFeatures, NeuralError, StandardModelConfig, Train}; -use cnc::{Forward, Norm, Params, ReLU, Sigmoid}; +use crate::activate::{ReLUActivation, SigmoidActivation}; +use crate::{ + DeepModelParams, Error, Forward, Model, ModelFeatures, Norm, Params, StandardModelConfig, Train, +}; +#[cfg(feature = "rand")] +use concision_init::{ + InitRand, + rand_distr::{Distribution, StandardNormal}, +}; use ndarray::prelude::*; use ndarray::{Data, ScalarOperand}; -use num::traits::{Float, FromPrimitive, NumAssign}; +use num_traits::{Float, FromPrimitive, NumAssign, Zero}; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct SimpleModel { +pub struct TestModel { pub config: StandardModelConfig, pub features: ModelFeatures, pub params: DeepModelParams, } -impl SimpleModel -where - T: Float, -{ - pub fn new(config: StandardModelConfig, features: ModelFeatures) -> Self { +impl TestModel { + pub fn new(config: StandardModelConfig, features: ModelFeatures) -> Self + where + T: Clone + Zero, + { let params = DeepModelParams::zeros(features); - SimpleModel { + TestModel { + config, + features, + params, + } + } + /// returns an immutable reference to the model configuration + pub const fn config(&self) -> &StandardModelConfig { + &self.config + } + /// returns a reference to the model layout + pub const fn features(&self) -> ModelFeatures { + self.features + } + /// returns a reference to the model params + pub const fn params(&self) -> &DeepModelParams { + &self.params + } + /// returns a mutable reference to the model params + pub const fn params_mut(&mut self) -> &mut DeepModelParams { + &mut self.params + } + #[cfg(feature = "rand")] + /// consumes the current instance to initalize another with random parameters + pub fn init(self) -> Self + where + StandardNormal: Distribution, + T: Float, + { + let TestModel { + mut params, + config, + features, + } = self; + params.set_input(Params::::lecun_normal(( + features.input(), + features.hidden(), + ))); + for layer in params.hidden_mut() { + *layer = Params::::lecun_normal((features.hidden(), features.hidden())); + } + params.set_output(Params::::lecun_normal(( + features.hidden(), + features.output(), + ))); + TestModel { config, features, params, @@ -30,7 +82,7 @@ where } } -impl Model for SimpleModel { +impl Model for TestModel { type Config = StandardModelConfig; type Layout = ModelFeatures; @@ -43,8 +95,8 @@ impl Model for SimpleModel { &mut self.config } - fn layout(&self) -> ModelFeatures { - self.features + fn layout(&self) -> &ModelFeatures { + &self.features } fn params(&self) -> &DeepModelParams { @@ -56,7 +108,7 @@ impl Model for SimpleModel { } } -impl Forward> for SimpleModel +impl Forward> for TestModel where A: Float + FromPrimitive + ScalarOperand, D: Dimension, @@ -83,28 +135,25 @@ where } } -impl Train, ArrayBase> for SimpleModel +impl Train, ArrayBase> for TestModel where A: Float + FromPrimitive + NumAssign + ScalarOperand + core::fmt::Debug, S: Data, T: Data, { + type Error = Error; type Output = A; - #[cfg_attr( - feature = "tracing", - tracing::instrument(skip(self, input, target), level = "trace", target = "model",) - )] fn train( &mut self, input: &ArrayBase, target: &ArrayBase, - ) -> Result { + ) -> Result { if input.len() != self.layout().input() { - return Err(NeuralError::InvalidInputShape); + return Err(Error::InvalidInputShape); } if target.len() != self.layout().output() { - return Err(NeuralError::InvalidOutputShape); + return Err(Error::InvalidOutputShape); } // get the learning rate from the model's configuration let lr = self @@ -178,11 +227,8 @@ where .expect("Hidden failed training..."); } /* - Backpropagate to the input layer The delta for the input layer is computed using the weights of the first hidden layer and the derivative of the activation function of the first hidden layer. - - (h, h).dot(h) * derivative(h) = dim(h) where h is the number of features within a hidden layer */ delta = self.params().hidden()[0].weights().dot(&delta) * activations[1].relu_derivative(); delta /= delta.l2_norm(); // Normalize the delta to prevent exploding gradients @@ -195,37 +241,28 @@ where } } -impl Train, ArrayBase> for SimpleModel +impl Train, ArrayBase> for TestModel where A: Float + FromPrimitive + NumAssign + ScalarOperand + core::fmt::Debug, S: Data, T: Data, { + type Error = Error; type Output = A; - #[cfg_attr( - feature = "tracing", - tracing::instrument( - skip(self, input, target), - level = "trace", - name = "train", - target = "model", - fields(input_shape = ?input.shape(), target_shape = ?target.shape()) - ) - )] fn train( &mut self, input: &ArrayBase, target: &ArrayBase, - ) -> Result { + ) -> Result { if input.nrows() == 0 || target.nrows() == 0 { - return Err(NeuralError::InvalidBatchSize); + return Err(Error::InvalidBatchSize); } if input.ncols() != self.layout().input() { - return Err(NeuralError::InvalidInputShape); + return Err(Error::InvalidInputShape); } if target.ncols() != self.layout().output() || target.nrows() != input.nrows() { - return Err(NeuralError::InvalidOutputShape); + return Err(Error::InvalidOutputShape); } let batch_size = input.nrows(); let mut loss = A::zero(); diff --git a/neural/src/params/impls/impl_model_params.rs b/core/src/models/impls/impl_model_params.rs similarity index 97% rename from neural/src/params/impls/impl_model_params.rs rename to core/src/models/impls/impl_model_params.rs index 8347bf3e..0822c800 100644 --- a/neural/src/params/impls/impl_model_params.rs +++ b/core/src/models/impls/impl_model_params.rs @@ -2,10 +2,10 @@ appellation: impl_model_params authors: @FL03 */ -use crate::params::ModelParamsBase; +use crate::models::ModelParamsBase; use crate::{DeepModelRepr, RawHidden}; -use cnc::params::ParamsBase; +use concision_params::ParamsBase; use ndarray::{Data, Dimension, RawDataClone}; impl Clone for ModelParamsBase diff --git a/neural/src/params/impls/impl_model_params_rand.rs b/core/src/models/impls/impl_model_params_rand.rs similarity index 91% rename from neural/src/params/impls/impl_model_params_rand.rs rename to core/src/models/impls/impl_model_params_rand.rs index ca9fe26d..b76821a2 100644 --- a/neural/src/params/impls/impl_model_params_rand.rs +++ b/core/src/models/impls/impl_model_params_rand.rs @@ -3,11 +3,11 @@ authors: @FL03 */ -use crate::params::{DeepParamsBase, ShallowParamsBase}; +use crate::models::{DeepParamsBase, ShallowParamsBase}; use crate::ModelFeatures; -use cnc::init::{self, Initialize}; -use cnc::params::ParamsBase; +use concision_init::{InitRand, distr as init}; +use concision_params::ParamsBase; use ndarray::{DataOwned, Ix2}; use num_traits::{Float, FromPrimitive}; @@ -57,9 +57,7 @@ where A: Float + FromPrimitive, StandardNormal: Distribution, { - Self::init_rand(features, |(rows, cols)| { - cnc::init::XavierNormal::new(rows, cols) - }) + Self::init_rand(features, |(rows, cols)| init::XavierNormal::new(rows, cols)) } /// initialize the model parameters using a glorot uniform distribution pub fn glorot_uniform(features: ModelFeatures) -> Self @@ -101,9 +99,7 @@ where A: Float + FromPrimitive, StandardNormal: Distribution, { - Self::init_rand(features, |(rows, cols)| { - cnc::init::XavierNormal::new(rows, cols) - }) + Self::init_rand(features, |(rows, cols)| init::XavierNormal::new(rows, cols)) } /// initialize the model parameters using a glorot uniform distribution pub fn glorot_uniform(features: ModelFeatures) -> Self diff --git a/neural/src/params/impls/impl_model_params_serde.rs b/core/src/models/impls/impl_model_params_serde.rs similarity index 100% rename from neural/src/params/impls/impl_model_params_serde.rs rename to core/src/models/impls/impl_model_params_serde.rs diff --git a/neural/src/params/impls/impl_params_deep.rs b/core/src/models/impls/impl_params_deep.rs similarity index 94% rename from neural/src/params/impls/impl_params_deep.rs rename to core/src/models/impls/impl_params_deep.rs index 920720f5..3830841c 100644 --- a/neural/src/params/impls/impl_params_deep.rs +++ b/core/src/models/impls/impl_params_deep.rs @@ -5,8 +5,9 @@ use crate::{DeepParamsBase, ModelParamsBase}; use crate::ModelFeatures; -use crate::traits::DeepModelRepr; +use crate::models::traits::DeepModelRepr; use concision_params::ParamsBase; +use concision_traits::Forward; use ndarray::{Data, DataOwned, Dimension, Ix2, RawData}; use num_traits::{One, Zero}; @@ -34,11 +35,11 @@ where /// returns the total number parameters within the model, including the input and output layers #[inline] pub fn size(&self) -> usize { - let mut size = self.input().count_weight(); + let mut size = self.input().count_weights(); for layer in self.hidden() { - size += layer.count_weight(); + size += layer.count_weights(); } - size + self.output().count_weight() + size + self.output().count_weights() } /// set the layer at the specified index in the hidden layers of the model @@ -105,7 +106,7 @@ where where A: Clone, S: Data, - ParamsBase: cnc::Forward + cnc::Forward, + ParamsBase: Forward + Forward, { // forward the input through the input layer let mut output = self.input().forward(input)?; @@ -122,6 +123,7 @@ impl DeepParamsBase where S: RawData, { + #[allow(clippy::should_implement_trait)] /// create a new instance of the model; /// all parameters are initialized to their defaults (i.e., zero) pub fn default(features: ModelFeatures) -> Self diff --git a/neural/src/params/impls/impl_params_shallow.rs b/core/src/models/impls/impl_params_shallow.rs similarity index 90% rename from neural/src/params/impls/impl_params_shallow.rs rename to core/src/models/impls/impl_params_shallow.rs index 06cb1bf9..f4edf9d9 100644 --- a/neural/src/params/impls/impl_params_shallow.rs +++ b/core/src/models/impls/impl_params_shallow.rs @@ -2,11 +2,11 @@ Appellation: controller Contrib: @FL03 */ -use crate::params::{ModelParamsBase, ShallowParamsBase}; +use crate::models::{ModelParamsBase, ShallowParamsBase}; use crate::ModelFeatures; -use crate::traits::ShallowModelRepr; -use cnc::{ReLU, Sigmoid}; +use crate::activate::{ReLUActivation, SigmoidActivation}; +use crate::models::traits::ShallowModelRepr; use concision_params::ParamsBase; use ndarray::{ Array1, ArrayBase, Data, DataOwned, Dimension, Ix2, RawData, RemoveAxis, ScalarOperand, @@ -52,9 +52,9 @@ where /// returns the total number parameters within the model, including the input and output layers #[inline] pub fn size(&self) -> usize { - let mut size = self.input().count_weight(); - size += self.hidden().count_weight(); - size + self.output().count_weight() + let mut size = self.input().count_weights(); + size += self.hidden().count_weights(); + size + self.output().count_weights() } /// returns an immutable reference to the hidden weights pub const fn hidden_weights(&self) -> &ArrayBase { diff --git a/neural/src/params/mod.rs b/core/src/models/mod.rs similarity index 63% rename from neural/src/params/mod.rs rename to core/src/models/mod.rs index 839c12e0..c0f576dd 100644 --- a/neural/src/params/mod.rs +++ b/core/src/models/mod.rs @@ -6,16 +6,22 @@ //! implementation focuses on providing a generic container for the parameters of a neural //! network. #[doc(inline)] -pub use self::{model_params::*, types::*}; +pub use self::{model_params::*, traits::*, types::*}; -mod model_params; +pub mod model_params; + +#[doc(hidden)] +pub mod ex { + #[cfg(all(feature = "rand", feature = "std"))] + pub mod sample; +} mod impls { mod impl_model_params; mod impl_params_deep; mod impl_params_shallow; - #[cfg(feature = "init")] + #[cfg(feature = "rand")] mod impl_model_params_rand; #[cfg(feature = "serde")] mod impl_model_params_serde; @@ -23,19 +29,21 @@ mod impls { mod types { #[doc(inline)] - pub use self::prelude::*; + pub use self::aliases::*; mod aliases; +} - mod prelude { - #[doc(inline)] - pub use super::aliases::*; - } +mod traits { + #[doc(inline)] + pub use self::{hidden::*, model::*}; + + mod hidden; + mod model; } pub(crate) mod prelude { - #[doc(inline)] pub use super::model_params::*; - #[doc(inline)] + pub use super::traits::*; pub use super::types::*; } diff --git a/neural/src/params/model_params.rs b/core/src/models/model_params.rs similarity index 99% rename from neural/src/params/model_params.rs rename to core/src/models/model_params.rs index 641cd60c..88626a23 100644 --- a/neural/src/params/model_params.rs +++ b/core/src/models/model_params.rs @@ -3,7 +3,7 @@ Contrib: @FL03 */ -use cnc::params::ParamsBase; +use concision_params::ParamsBase; use ndarray::{ArrayBase, Dimension, RawData}; use crate::{DeepModelRepr, RawHidden}; diff --git a/neural/src/traits/models.rs b/core/src/models/traits.rs similarity index 97% rename from neural/src/traits/models.rs rename to core/src/models/traits.rs index 1e98487f..b9d6369a 100644 --- a/neural/src/traits/models.rs +++ b/core/src/models/traits.rs @@ -1,146 +1,146 @@ -/* - appellation: models - authors: @FL03 -*/ -use crate::config::NetworkConfig; -use crate::{DeepModelParams, ModelLayout}; -use crate::{Predict, Train}; -use concision_core::params::Params; -use concision_data::DatasetBase; - -/// The [`Model`] trait defines the core interface for all models; implementors will need to -/// provide the type of configuration used by the model, the type of layout used by the model, -/// and the type of parameters used by the model. The crate provides standard, or default, -/// definitions of both the configuration and layout types, however, for -pub trait Model { - /// The type of configuration used for the model - type Config: NetworkConfig; - /// The type of [`ModelLayout`] used by the model for this implementation. - type Layout: ModelLayout; - /// returns an immutable reference to the models configuration; this is typically used to - /// access the models hyperparameters (i.e. learning rate, momentum, etc.) and other - /// related control parameters. - fn config(&self) -> &Self::Config; - /// returns a mutable reference to the models configuration; useful for setting hyperparams - fn config_mut(&mut self) -> &mut Self::Config; - /// returns a copy of the model's current layout (features); a type providing the model - /// with a particular number of features for the various layers of a deep neural network. - /// - /// the layout is used in everything from creation and initialization routines to - /// validating the dimensionality of the model's inputs, outputs, training data, etc. - fn layout(&self) -> Self::Layout; - /// returns an immutable reference to the model parameters - fn params(&self) -> &DeepModelParams; - /// returns a mutable reference to the model's parameters - fn params_mut(&mut self) -> &mut DeepModelParams; - /// propagates the input through the model; each layer is applied in sequence meaning that - /// the output of each previous layer is the input to the next layer. This pattern - /// repeats until the output layer returns the final result. - /// - /// By default, the trait simply passes each output from one layer to the next, however, - /// custom models will likely override this method to inject activation methods and other - /// related logic - fn predict(&self, inputs: &U) -> Option - where - Self: Predict, - { - Predict::predict(self, inputs) - } - /// a convience method that trains the model using the provided dataset; this method - /// requires that the model implements the [`Train`] trait and that the dataset - fn train(&mut self, dataset: &DatasetBase) -> crate::NeuralResult - where - Self: Train, - { - Train::train(self, dataset.records(), dataset.targets()) - } -} - -pub trait ModelExt: Model { - /// [`replace`](core::mem::replace) the current configuration and returns the old one; - fn replace_config(&mut self, config: Self::Config) -> Self::Config { - core::mem::replace(self.config_mut(), config) - } - /// [`replace`](core::mem::replace) the current model parameters and returns the old one - fn replace_params(&mut self, params: DeepModelParams) -> DeepModelParams { - core::mem::replace(self.params_mut(), params) - } - /// overrides the current configuration and returns a mutable reference to the model - fn set_config(&mut self, config: Self::Config) -> &mut Self { - *self.config_mut() = config; - self - } - /// overrides the current model parameters and returns a mutable reference to the model - fn set_params(&mut self, params: DeepModelParams) -> &mut Self { - *self.params_mut() = params; - self - } - /// returns an immutable reference to the input layer; - #[inline] - fn input_layer(&self) -> &Params { - self.params().input() - } - /// returns a mutable reference to the input layer; - #[inline] - fn input_layer_mut(&mut self) -> &mut Params { - self.params_mut().input_mut() - } - /// returns an immutable reference to the hidden layer(s); - #[inline] - fn hidden_layers(&self) -> &Vec> { - self.params().hidden() - } - /// returns a mutable reference to the hidden layer(s); - #[inline] - fn hidden_layers_mut(&mut self) -> &mut Vec> { - self.params_mut().hidden_mut() - } - /// returns an immutable reference to the output layer; - #[inline] - fn output_layer(&self) -> &Params { - self.params().output() - } - /// returns a mutable reference to the output layer; - #[inline] - fn output_layer_mut(&mut self) -> &mut Params { - self.params_mut().output_mut() - } - #[inline] - fn set_input_layer(&mut self, layer: Params) -> &mut Self { - self.params_mut().set_input(layer); - self - } - #[inline] - fn set_hidden_layers(&mut self, layers: Vec>) -> &mut Self { - self.params_mut().set_hidden(layers); - self - } - #[inline] - fn set_output_layer(&mut self, layer: Params) -> &mut Self { - self.params_mut().set_output(layer); - self - } - /// returns a 2-tuple representing the dimensions of the input layer; (input, hidden) - fn input_dim(&self) -> (usize, usize) { - self.layout().dim_input() - } - /// returns a 2-tuple representing the dimensions of the hidden layers; (hidden, hidden) - fn hidden_dim(&self) -> (usize, usize) { - self.layout().dim_hidden() - } - /// returns the total number of hidden layers in the model; - fn hidden_layers_count(&self) -> usize { - self.layout().layers() - } - /// returns a 2-tuple representing the dimensions of the output layer; (hidden, output) - fn output_dim(&self) -> (usize, usize) { - self.layout().dim_output() - } -} - -impl ModelExt for M -where - M: Model, - M::Layout: ModelLayout, -{ -} +/* + appellation: models + authors: @FL03 +*/ +use crate::config::NetworkConfig; +use crate::{DeepModelParams, ModelLayout}; +use crate::{Predict, Train}; +use concision_core::params::Params; +use concision_data::DatasetBase; + +/// The [`Model`] trait defines the core interface for all models; implementors will need to +/// provide the type of configuration used by the model, the type of layout used by the model, +/// and the type of parameters used by the model. The crate provides standard, or default, +/// definitions of both the configuration and layout types, however, for +pub trait Model { + /// The type of configuration used for the model + type Config: NetworkConfig; + /// The type of [`ModelLayout`] used by the model for this implementation. + type Layout: ModelLayout; + /// returns an immutable reference to the models configuration; this is typically used to + /// access the models hyperparameters (i.e. learning rate, momentum, etc.) and other + /// related control parameters. + fn config(&self) -> &Self::Config; + /// returns a mutable reference to the models configuration; useful for setting hyperparams + fn config_mut(&mut self) -> &mut Self::Config; + /// returns a copy of the model's current layout (features); a type providing the model + /// with a particular number of features for the various layers of a deep neural network. + /// + /// the layout is used in everything from creation and initialization routines to + /// validating the dimensionality of the model's inputs, outputs, training data, etc. + fn layout(&self) -> Self::Layout; + /// returns an immutable reference to the model parameters + fn params(&self) -> &DeepModelParams; + /// returns a mutable reference to the model's parameters + fn params_mut(&mut self) -> &mut DeepModelParams; + /// propagates the input through the model; each layer is applied in sequence meaning that + /// the output of each previous layer is the input to the next layer. This pattern + /// repeats until the output layer returns the final result. + /// + /// By default, the trait simply passes each output from one layer to the next, however, + /// custom models will likely override this method to inject activation methods and other + /// related logic + fn predict(&self, inputs: &U) -> Option + where + Self: Predict, + { + Predict::predict(self, inputs) + } + /// a convience method that trains the model using the provided dataset; this method + /// requires that the model implements the [`Train`] trait and that the dataset + fn train(&mut self, dataset: &DatasetBase) -> crate::Result + where + Self: Train, + { + Train::train(self, dataset.records(), dataset.targets()) + } +} + +pub trait ModelExt: Model { + /// [`replace`](core::mem::replace) the current configuration and returns the old one; + fn replace_config(&mut self, config: Self::Config) -> Self::Config { + core::mem::replace(self.config_mut(), config) + } + /// [`replace`](core::mem::replace) the current model parameters and returns the old one + fn replace_params(&mut self, params: DeepModelParams) -> DeepModelParams { + core::mem::replace(self.params_mut(), params) + } + /// overrides the current configuration and returns a mutable reference to the model + fn set_config(&mut self, config: Self::Config) -> &mut Self { + *self.config_mut() = config; + self + } + /// overrides the current model parameters and returns a mutable reference to the model + fn set_params(&mut self, params: DeepModelParams) -> &mut Self { + *self.params_mut() = params; + self + } + /// returns an immutable reference to the input layer; + #[inline] + fn input_layer(&self) -> &Params { + self.params().input() + } + /// returns a mutable reference to the input layer; + #[inline] + fn input_layer_mut(&mut self) -> &mut Params { + self.params_mut().input_mut() + } + /// returns an immutable reference to the hidden layer(s); + #[inline] + fn hidden_layers(&self) -> &Vec> { + self.params().hidden() + } + /// returns a mutable reference to the hidden layer(s); + #[inline] + fn hidden_layers_mut(&mut self) -> &mut Vec> { + self.params_mut().hidden_mut() + } + /// returns an immutable reference to the output layer; + #[inline] + fn output_layer(&self) -> &Params { + self.params().output() + } + /// returns a mutable reference to the output layer; + #[inline] + fn output_layer_mut(&mut self) -> &mut Params { + self.params_mut().output_mut() + } + #[inline] + fn set_input_layer(&mut self, layer: Params) -> &mut Self { + self.params_mut().set_input(layer); + self + } + #[inline] + fn set_hidden_layers(&mut self, layers: Vec>) -> &mut Self { + self.params_mut().set_hidden(layers); + self + } + #[inline] + fn set_output_layer(&mut self, layer: Params) -> &mut Self { + self.params_mut().set_output(layer); + self + } + /// returns a 2-tuple representing the dimensions of the input layer; (input, hidden) + fn input_dim(&self) -> (usize, usize) { + self.layout().dim_input() + } + /// returns a 2-tuple representing the dimensions of the hidden layers; (hidden, hidden) + fn hidden_dim(&self) -> (usize, usize) { + self.layout().dim_hidden() + } + /// returns the total number of hidden layers in the model; + fn hidden_layers_count(&self) -> usize { + self.layout().layers() + } + /// returns a 2-tuple representing the dimensions of the output layer; (hidden, output) + fn output_dim(&self) -> (usize, usize) { + self.layout().dim_output() + } +} + +impl ModelExt for M +where + M: Model, + M::Layout: ModelLayout, +{ +} diff --git a/neural/src/traits/hidden.rs b/core/src/models/traits/hidden.rs similarity index 98% rename from neural/src/traits/hidden.rs rename to core/src/models/traits/hidden.rs index 2df774bf..7f8bd502 100644 --- a/neural/src/traits/hidden.rs +++ b/core/src/models/traits/hidden.rs @@ -2,7 +2,7 @@ appellation: hidden authors: @FL03 */ -use cnc::ParamsBase; +use concision_params::ParamsBase; use ndarray::{Data, Dimension, RawData}; /// The [`RawHidden`] trait for compatible representations of hidden layers diff --git a/core/src/models/traits/model.rs b/core/src/models/traits/model.rs new file mode 100644 index 00000000..ee238df6 --- /dev/null +++ b/core/src/models/traits/model.rs @@ -0,0 +1,137 @@ +/* + appellation: models + authors: @FL03 +*/ +use crate::config::ModelConfiguration; +use crate::{DeepModelParams, ModelLayout, RawModelLayout}; +use concision_params::Params; +use concision_traits::Predict; + +/// The [`Model`] trait defines the core interface for all models; implementors will need to +/// provide the type of configuration used by the model, the type of layout used by the model, +/// and the type of parameters used by the model. The crate provides standard, or default, +/// definitions of both the configuration and layout types, however, for +pub trait Model { + /// The type of configuration used for the model + type Config: ModelConfiguration; + /// The type of [`ModelLayout`] used by the model for this implementation. + type Layout: ModelLayout; + /// returns an immutable reference to the models configuration; this is typically used to + /// access the models hyperparameters (i.e. learning rate, momentum, etc.) and other + /// related control parameters. + fn config(&self) -> &Self::Config; + /// returns a mutable reference to the models configuration; useful for setting hyperparams + fn config_mut(&mut self) -> &mut Self::Config; + /// returns a copy of the model's current layout (features); a type providing the model + /// with a particular number of features for the various layers of a deep neural network. + /// + /// the layout is used in everything from creation and initialization routines to + /// validating the dimensionality of the model's inputs, outputs, training data, etc. + fn layout(&self) -> &Self::Layout; + /// returns an immutable reference to the model parameters + fn params(&self) -> &DeepModelParams; + /// returns a mutable reference to the model's parameters + fn params_mut(&mut self) -> &mut DeepModelParams; + /// propagates the input through the model; each layer is applied in sequence meaning that + /// the output of each previous layer is the input to the next layer. This pattern + /// repeats until the output layer returns the final result. + /// + /// By default, the trait simply passes each output from one layer to the next, however, + /// custom models will likely override this method to inject activation methods and other + /// related logic + fn predict(&self, inputs: &U) -> Option + where + Self: Predict, + { + Predict::predict(self, inputs) + } +} + +pub trait ModelExt: Model { + /// [`replace`](core::mem::replace) the current configuration and returns the old one; + fn replace_config(&mut self, config: Self::Config) -> Self::Config { + core::mem::replace(self.config_mut(), config) + } + /// [`replace`](core::mem::replace) the current model parameters and returns the old one + fn replace_params(&mut self, params: DeepModelParams) -> DeepModelParams { + core::mem::replace(self.params_mut(), params) + } + /// overrides the current configuration and returns a mutable reference to the model + fn set_config(&mut self, config: Self::Config) -> &mut Self { + *self.config_mut() = config; + self + } + /// overrides the current model parameters and returns a mutable reference to the model + fn set_params(&mut self, params: DeepModelParams) -> &mut Self { + *self.params_mut() = params; + self + } + /// returns an immutable reference to the input layer; + #[inline] + fn input_layer(&self) -> &Params { + self.params().input() + } + /// returns a mutable reference to the input layer; + #[inline] + fn input_layer_mut(&mut self) -> &mut Params { + self.params_mut().input_mut() + } + /// returns an immutable reference to the hidden layer(s); + #[inline] + fn hidden_layers(&self) -> &Vec> { + self.params().hidden() + } + /// returns a mutable reference to the hidden layer(s); + #[inline] + fn hidden_layers_mut(&mut self) -> &mut Vec> { + self.params_mut().hidden_mut() + } + /// returns an immutable reference to the output layer; + #[inline] + fn output_layer(&self) -> &Params { + self.params().output() + } + /// returns a mutable reference to the output layer; + #[inline] + fn output_layer_mut(&mut self) -> &mut Params { + self.params_mut().output_mut() + } + #[inline] + fn set_input_layer(&mut self, layer: Params) -> &mut Self { + self.params_mut().set_input(layer); + self + } + #[inline] + fn set_hidden_layers(&mut self, layers: Vec>) -> &mut Self { + self.params_mut().set_hidden(layers); + self + } + #[inline] + fn set_output_layer(&mut self, layer: Params) -> &mut Self { + self.params_mut().set_output(layer); + self + } + /// returns a 2-tuple representing the dimensions of the input layer; (input, hidden) + fn input_dim(&self) -> (usize, usize) { + self.layout().dim_input() + } + /// returns a 2-tuple representing the dimensions of the hidden layers; (hidden, hidden) + fn hidden_dim(&self) -> (usize, usize) { + self.layout().dim_hidden() + } + /// returns the total number of hidden layers in the model; + fn hidden_layers_count(&self) -> usize { + self.layout().layers() + } + /// returns a 2-tuple representing the dimensions of the output layer; (hidden, output) + fn output_dim(&self) -> (usize, usize) { + self.layout().dim_output() + } +} + +impl ModelExt for M +where + M: Model, + M::Layout: ModelLayout, +{ +} diff --git a/neural/src/params/types/aliases.rs b/core/src/models/types/aliases.rs similarity index 92% rename from neural/src/params/types/aliases.rs rename to core/src/models/types/aliases.rs index 94910628..aecbfc8d 100644 --- a/neural/src/params/types/aliases.rs +++ b/core/src/models/types/aliases.rs @@ -2,8 +2,8 @@ appellation: aliases authors: @FL03 */ -use crate::params::ModelParamsBase; -use cnc::params::ParamsBase; +use crate::models::ModelParamsBase; +use concision_params::ParamsBase; use ndarray::{Ix2, OwnedRepr}; /// A type alias for an owned representation of the [`ModelParamsBase`] generic of type `A` @@ -18,7 +18,7 @@ pub type DeepParamsBase = ModelParamsBase = ShallowParamsBase, D>; +pub type ShallowModelParams = ShallowParamsBase, D, A>; /// a type alias for a _shallow_ representation of the [`ModelParamsBase`] using a single /// [`ParamsBase`] instance as the hidden layer. pub type ShallowParamsBase = ModelParamsBase, A>; diff --git a/core/src/nn/mod.rs b/core/src/nn/mod.rs new file mode 100644 index 00000000..407c79a1 --- /dev/null +++ b/core/src/nn/mod.rs @@ -0,0 +1,27 @@ +/* + Appellation: nn + Created At: 2025.11.28:14:59:44 + Contrib: @FL03 +*/ +//! This module provides network specific implementations and traits supporting the development +//! of neural network models. +//! +#[doc(inline)] +pub use self::{neural_network::*, types::*}; + +mod neural_network; + +mod traits {} + +mod types { + #[doc(inline)] + pub use self::depth::*; + + mod depth; +} + +#[allow(unused)] +pub(crate) mod prelude { + pub use super::traits::*; + pub use super::types::*; +} diff --git a/core/src/nn/neural_network.rs b/core/src/nn/neural_network.rs new file mode 100644 index 00000000..7f38d443 --- /dev/null +++ b/core/src/nn/neural_network.rs @@ -0,0 +1,57 @@ +/* + Appellation: neural_network + Created At: 2025.11.28:15:01:28 + Contrib: @FL03 +*/ +use super::{Deep, NetworkDepth, Shallow}; +use crate::config::ModelConfiguration; +use ndarray::{Dimension, RawData}; + +/// The [`NeuralNetwork`] trait defines a generic interface for neural network models. +pub trait NeuralNetwork::Elem> +where + D: Dimension, + S: RawData, +{ + type Config: ModelConfiguration; + type Depth: NetworkDepth; + + /// returns a reference to the network configuration; + fn config(&self) -> &Self::Config; +} + +pub trait DeepNeuralNetwork::Elem>: + NeuralNetwork +where + D: Dimension, + S: RawData, +{ + private!(); +} + +pub trait ShallowNeuralNetwork::Elem>: + NeuralNetwork +where + D: Dimension, + S: RawData, +{ + private!(); +} + +impl DeepNeuralNetwork for N +where + D: Dimension, + S: RawData, + N: NeuralNetwork, +{ + seal!(); +} + +impl ShallowNeuralNetwork for N +where + D: Dimension, + S: RawData, + N: NeuralNetwork, +{ + seal!(); +} diff --git a/core/src/nn/types/depth.rs b/core/src/nn/types/depth.rs new file mode 100644 index 00000000..3bba63d6 --- /dev/null +++ b/core/src/nn/types/depth.rs @@ -0,0 +1,35 @@ +/* + Appellation: depth + Created At: 2025.11.28:15:03:02 + Contrib: @FL03 +*/ + +/// The [`NetworkDepth`] trait is used to define the depth/kind of a neural network model. +pub trait NetworkDepth { + private!(); +} + +macro_rules! network_format { + (#[$tgt:ident] $vis:vis enum {$($name:ident),* $(,)?}) => { + $( + network_format!(@impl #[$tgt] $vis $name); + )* + }; + (@impl #[$tgt:ident] $vis:vis $name:ident) => { + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] + $vis enum $name {} + + impl $tgt for $name { + seal!(); + } + }; +} + +network_format! { + #[NetworkDepth] + pub enum { + Deep, + Shallow, + } +} diff --git a/core/src/ops/mask.rs b/core/src/ops/mask.rs deleted file mode 100644 index 0bbd0bbe..00000000 --- a/core/src/ops/mask.rs +++ /dev/null @@ -1,18 +0,0 @@ -/* - appellation: mask - authors: @FL03 -*/ -//! this module implements various _masks_ often used in neural networks and other machine -//! learning applications to prevent overfitting or to control the flow of information -//! through the network. -#[doc(inline)] -pub use self::prelude::*; - -/// An implementation of the dropout regularization technique where randomly selected elements -/// within a tensor/layer are zeroed out during training to prevent overfitting. -mod dropout; - -mod prelude { - #[doc(inline)] - pub use super::dropout::*; -} diff --git a/core/src/ops/mask/dropout.rs b/core/src/ops/mask/dropout.rs deleted file mode 100644 index 62d3417a..00000000 --- a/core/src/ops/mask/dropout.rs +++ /dev/null @@ -1,28 +0,0 @@ -/// [Dropout] randomly zeroizes elements with a given probability (`p`). -pub trait DropOut { - type Output; - - fn dropout(&self, p: f64) -> Self::Output; -} - -#[cfg(feature = "init")] -impl DropOut for ndarray::ArrayBase -where - A: num_traits::Num + ndarray::ScalarOperand, - D: ndarray::Dimension, - S: ndarray::DataOwned, -{ - type Output = ndarray::Array; - - fn dropout(&self, p: f64) -> Self::Output { - pub use concision_init::Initialize; - use ndarray::Array; - let dim = self.dim(); - // Create a mask of the same shape as the input array - let mask: Array = Array::bernoulli(dim, p).expect("Failed to create mask"); - let mask = mask.mapv(|x| if x { A::zero() } else { A::one() }); - - // Element-wise multiplication to apply dropout - self.to_owned() * mask - } -} diff --git a/core/src/ops/pad.rs b/core/src/ops/pad.rs deleted file mode 100644 index 6564b9ab..00000000 --- a/core/src/ops/pad.rs +++ /dev/null @@ -1,46 +0,0 @@ -/* - Appellation: pad - Contrib: FL03 -*/ -#[doc(inline)] -pub use self::{error::*, mode::*, utils::*}; - -mod padding; - -pub(crate) mod error; -pub(crate) mod mode; -pub(crate) mod utils; - -/// The [`Pad`] trait defines a padding operation for tensors. -pub trait Pad { - type Output; - - fn pad(&self, mode: PadMode, pad: &[[usize; 2]]) -> Self::Output; -} - -pub struct Padding { - pub(crate) action: PadAction, - pub(crate) mode: PadMode, - pub(crate) pad: Vec<[usize; 2]>, - pub(crate) padding: usize, -} - -/* - ************* Implementations ************* -*/ - -use ndarray::{Array, ArrayBase, DataOwned, Dimension}; -use num::traits::{FromPrimitive, Num}; - -impl Pad for ArrayBase -where - A: Copy + FromPrimitive + Num, - D: Dimension, - S: DataOwned, -{ - type Output = Array; - - fn pad(&self, mode: PadMode, pad: &[[usize; 2]]) -> Self::Output { - utils::pad(self, pad, mode).unwrap() - } -} diff --git a/core/src/ops/pad/error.rs b/core/src/ops/pad/error.rs deleted file mode 100644 index 0cb7f36b..00000000 --- a/core/src/ops/pad/error.rs +++ /dev/null @@ -1,17 +0,0 @@ -/* - Appellation: error - Contrib: FL03 -*/ - -pub type PadResult = Result; - -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, thiserror::Error)] -#[cfg_attr( - feature = "serde", - derive(serde::Deserialize, serde::Serialize), - serde(rename_all = "snake_case") -)] -pub enum PadError { - #[error("Inconsistent Dimensions")] - InconsistentDimensions, -} diff --git a/core/src/ops/pad/utils.rs b/core/src/ops/pad/utils.rs deleted file mode 100644 index 75ad6234..00000000 --- a/core/src/ops/pad/utils.rs +++ /dev/null @@ -1,71 +0,0 @@ -/* - Appellation: utils - Contrib: FL03 -*/ -use super::{PadAction, PadError, PadMode}; -use crate::ArrayLike; -use ndarray::{Array, ArrayBase, AxisDescription, Data, DataOwned, Dimension, Slice}; -use num::{FromPrimitive, Num}; - -fn reader(nb_dim: usize, pad: &[[usize; 2]]) -> Result, PadError> { - if pad.len() == 1 && pad.len() < nb_dim { - // The user provided a single padding for all dimensions - Ok(vec![pad[0]; nb_dim]) - } else if pad.len() == nb_dim { - Ok(pad.to_vec()) - } else { - Err(PadError::InconsistentDimensions) - } -} - -pub fn pad( - data: &ArrayBase, - pad: &[[usize; 2]], - mode: PadMode, -) -> Result, PadError> -where - A: Copy + FromPrimitive + Num, - D: Dimension, - S: DataOwned, -{ - let pad = reader(data.ndim(), pad)?; - let mut dim = data.raw_dim(); - for (ax, (&ax_len, pad)) in data.shape().iter().zip(pad.iter()).enumerate() { - dim[ax] = ax_len + pad[0] + pad[1]; - } - - let mut padded = data.array_like(dim, mode.init()).to_owned(); - pad_to(data, &pad, mode, &mut padded)?; - Ok(padded) -} - -pub fn pad_to( - data: &ArrayBase, - pad: &[[usize; 2]], - mode: PadMode, - output: &mut Array, -) -> super::PadResult -where - A: Copy + FromPrimitive + Num, - D: Dimension, - S: Data, -{ - let pad = reader(data.ndim(), pad)?; - - // Select portion of padded array that needs to be copied from the original array. - output - .slice_each_axis_mut(|ad| { - let AxisDescription { axis, len, .. } = ad; - let pad = pad[axis.index()]; - Slice::from(pad[0]..len - pad[1]) - }) - .assign(data); - - match mode.into_pad_action() { - PadAction::StopAfterCopy => { - // Do nothing - Ok(()) - } - _ => unimplemented!(), - } -} diff --git a/core/src/utils.rs b/core/src/utils.rs new file mode 100644 index 00000000..5bb0b737 --- /dev/null +++ b/core/src/utils.rs @@ -0,0 +1,35 @@ +/* + Appellation: utils + Created At: 2025.11.26:13:20:12 + Contrib: @FL03 +*/ +//! Additional utilities for creating, manipulating, and managing tensors and models. +#[doc(inline)] +pub use self::prelude::*; + +#[cfg(feature = "signal")] +pub use self::fft::prelude::*; + +#[cfg(feature = "signal")] +pub mod fft; + +pub(crate) mod arith; +pub(crate) mod dropout; +pub(crate) mod gradient; +pub(crate) mod norm; +pub(crate) mod pad; +pub(crate) mod patterns; +pub(crate) mod tensor; + +pub(crate) mod prelude { + pub use super::arith::*; + pub use super::dropout::*; + pub use super::gradient::*; + pub use super::norm::*; + pub use super::pad::*; + pub use super::patterns::*; + pub use super::tensor::*; + + #[cfg(feature = "signal")] + pub use super::fft::prelude::*; +} diff --git a/utils/src/utils/arith.rs b/core/src/utils/arith.rs similarity index 100% rename from utils/src/utils/arith.rs rename to core/src/utils/arith.rs diff --git a/core/src/utils/dropout.rs b/core/src/utils/dropout.rs new file mode 100644 index 00000000..61d53f24 --- /dev/null +++ b/core/src/utils/dropout.rs @@ -0,0 +1,104 @@ +/* + Appellation: dropout + Created At: 2025.11.26:17:01:56 + Contrib: @FL03 +*/ + +/// [Dropout] randomly zeroizes elements with a given probability (`p`). +pub trait DropOut { + type Output; + + fn dropout(&self, p: f64) -> Self::Output; +} + +/// The [Dropout] layer is randomly zeroizes inputs with a given probability (`p`). +/// This regularization technique is often used to prevent overfitting. +/// +/// +/// ### Config +/// +/// - (p) Probability of dropping an element +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct Dropout { + pub(crate) p: f64, +} + +impl Dropout { + pub fn new(p: f64) -> Self { + Self { p } + } + + pub fn scale(&self) -> f64 { + (1f64 - self.p).recip() + } + + pub fn forward(&self, input: &U) -> Option<::Output> + where + U: DropOut, + { + Some(input.dropout(self.p)) + } +} + +impl Default for Dropout { + fn default() -> Self { + Self::new(0.5) + } +} + +#[cfg(feature = "rand")] +mod impl_rand { + use super::*; + use concision_init::InitRand; + use concision_traits::Forward; + use ndarray::{Array, ArrayBase, DataOwned, Dimension, ScalarOperand}; + use num_traits::Num; + + impl DropOut for ArrayBase + where + A: Num + ScalarOperand, + D: Dimension, + S: DataOwned, + { + type Output = Array; + + fn dropout(&self, p: f64) -> Self::Output { + let dim = self.dim(); + // Create a mask of the same shape as the input array + let mask: Array = Array::bernoulli(dim, p).expect("Failed to create mask"); + let mask = mask.mapv(|x| if x { A::zero() } else { A::one() }); + + // Element-wise multiplication to apply dropout + self.to_owned() * mask + } + } + + impl Forward for Dropout + where + U: DropOut, + { + type Output = ::Output; + + fn forward(&self, input: &U) -> Option { + Some(input.dropout(self.p)) + } + } +} + +#[cfg(all(test, feature = "rand"))] +mod tests { + use super::*; + use ndarray::Array2; + + #[test] + fn test_dropout() { + let shape = (512, 2048); + let arr = Array2::::ones(shape); + let dropout = Dropout::new(0.5); + let out = dropout.forward(&arr).expect("Dropout forward pass failed"); + + assert!(arr.iter().all(|&x| x == 1.0)); + assert!(out.iter().any(|x| x == &0f64)); + } +} diff --git a/utils/src/signal/fft/mod.rs b/core/src/utils/fft/mod.rs similarity index 89% rename from utils/src/signal/fft/mod.rs rename to core/src/utils/fft/mod.rs index 489d089f..2f2f236e 100644 --- a/utils/src/signal/fft/mod.rs +++ b/core/src/utils/fft/mod.rs @@ -1,13 +1,14 @@ /* - appellation: fft - authors: @FL03 + Appellation: fft + Created At: 2025.11.26:13:22:07 + Contrib: @FL03 */ //! this module implements the custom fast-fourier transform (FFT) algorithm +#![cfg(feature = "complex")] + #[doc(inline)] pub use self::{types::prelude::*, utils::*}; -#[doc(hidden)] -pub mod dft; /// this module implements the methods for the fast-fourier transform (FFT) module pub mod utils; @@ -25,7 +26,6 @@ pub mod types { } pub(crate) mod prelude { - #[doc(inline)] pub use super::utils::*; } diff --git a/utils/src/signal/fft/types/mode.rs b/core/src/utils/fft/types/mode.rs similarity index 100% rename from utils/src/signal/fft/types/mode.rs rename to core/src/utils/fft/types/mode.rs diff --git a/utils/src/signal/fft/types/plan.rs b/core/src/utils/fft/types/plan.rs similarity index 98% rename from utils/src/signal/fft/types/plan.rs rename to core/src/utils/fft/types/plan.rs index 997c3ed8..321a9222 100644 --- a/utils/src/signal/fft/types/plan.rs +++ b/core/src/utils/fft/types/plan.rs @@ -2,7 +2,7 @@ Appellation: plan Contrib: FL03 */ -use crate::signal::fft::fft_permutation; +use crate::utils::fft::fft_permutation; #[cfg(feature = "alloc")] use alloc::vec::{self, Vec}; diff --git a/utils/src/signal/fft/utils.rs b/core/src/utils/fft/utils.rs similarity index 97% rename from utils/src/signal/fft/utils.rs rename to core/src/utils/fft/utils.rs index 8bffd306..39616ee5 100644 --- a/utils/src/signal/fft/utils.rs +++ b/core/src/utils/fft/utils.rs @@ -3,9 +3,9 @@ Contrib: FL03 */ use super::FftPlan; -use crate::traits::AsComplex; -use num::complex::{Complex, ComplexFloat}; -use num::traits::{Float, FloatConst, NumAssignOps, NumCast, NumOps}; +use concision_traits::AsComplex; +use num_complex::{Complex, ComplexFloat}; +use num_traits::{Float, FloatConst, NumAssignOps, NumCast, NumOps}; pub(crate) fn fft_angle(n: usize) -> T where diff --git a/utils/src/utils/gradient.rs b/core/src/utils/gradient.rs similarity index 100% rename from utils/src/utils/gradient.rs rename to core/src/utils/gradient.rs diff --git a/utils/src/utils/norm.rs b/core/src/utils/norm.rs similarity index 100% rename from utils/src/utils/norm.rs rename to core/src/utils/norm.rs diff --git a/core/src/utils/pad.rs b/core/src/utils/pad.rs new file mode 100644 index 00000000..f4728949 --- /dev/null +++ b/core/src/utils/pad.rs @@ -0,0 +1,106 @@ +/* + Appellation: pad + Contrib: FL03 +*/ +mod impl_pad; +mod impl_pad_mode; +mod impl_padding; + +pub type PadResult = Result; + +/// The [`Pad`] trait defines a padding operation for tensors. +pub trait Pad { + type Output; + + fn pad(&self, mode: PadMode, pad: &[[usize; 2]]) -> Self::Output; +} + +pub struct Padding { + pub(crate) action: PadAction, + pub(crate) mode: PadMode, + pub(crate) pad: Vec<[usize; 2]>, + pub(crate) padding: usize, +} + +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + variants::VariantConstructors, + strum::AsRefStr, + strum::Display, + strum::EnumCount, + strum::EnumIs, + strum::EnumIter, + strum::EnumString, + strum::VariantArray, + strum::VariantNames, +)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(rename_all = "snake_case", untagged) +)] +#[strum(serialize_all = "snake_case")] +pub enum PadAction { + Clipping, + Lane, + Reflecting, + #[default] + StopAfterCopy, + Wrapping, +} + +#[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + strum::AsRefStr, + strum::Display, + strum::EnumCount, + strum::EnumIs, + strum::VariantNames, +)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(rename_all = "snake_case") +)] +#[strum(serialize_all = "snake_case")] +#[repr(C)] +#[derive(Default)] +pub enum PadMode { + Constant(T), + Edge, + Maximum, + Mean, + Median, + Minimum, + Mode, + Reflect, + Symmetric, + #[default] + Wrap, +} + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, thiserror::Error)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(rename_all = "snake_case") +)] +pub enum PadError { + #[error("Inconsistent Dimensions")] + InconsistentDimensions, +} diff --git a/core/src/utils/pad/impl_pad.rs b/core/src/utils/pad/impl_pad.rs new file mode 100644 index 00000000..1a37ad89 --- /dev/null +++ b/core/src/utils/pad/impl_pad.rs @@ -0,0 +1,82 @@ +/* + Appellation: impl_pad + Created At: 2025.11.26:16:12:28 + Contrib: @FL03 +*/ +use super::{Pad, PadAction, PadMode}; +use concision_traits::ArrayLike; +use ndarray::{Array, ArrayBase, AxisDescription, DataOwned, Dimension, Slice}; +use num_traits::{FromPrimitive, Num}; + +fn reader(ndim: usize, pad: &[[usize; 2]]) -> Option> { + debug_assert!(pad.len() == ndim, "Inconsistent dimensions for padding"); + if pad.len() != ndim { + return None; + } + Some(pad.to_vec()) +} + +fn apply_padding( + data: &ArrayBase, + pad: &[[usize; 2]], + mode: PadMode, + output: &mut Array, +) -> Option +where + A: Copy + FromPrimitive + Num, + D: Dimension, + S: DataOwned, +{ + let pad = reader(data.ndim(), pad)?; + + // Select portion of padded array that needs to be copied from the original array. + output + .slice_each_axis_mut(|ad| { + let AxisDescription { axis, len, .. } = ad; + let pad = pad[axis.index()]; + Slice::from(pad[0]..len - pad[1]) + }) + .assign(data); + + match mode.into_pad_action() { + PadAction::StopAfterCopy => { + // Do nothing + return Some(true); + } + _ => unimplemented!(), + } +} + +pub fn pad( + data: &ArrayBase, + padding: &[[usize; 2]], + mode: PadMode, +) -> Array +where + A: Copy + FromPrimitive + Num, + D: Dimension, + S: DataOwned, +{ + let pad = reader(data.ndim(), padding).expect("Inconsistent dimensions for padding"); + let mut dim = data.raw_dim(); + for (ax, (&ax_len, pad)) in data.shape().iter().zip(pad.iter()).enumerate() { + dim[ax] = ax_len + pad[0] + pad[1]; + } + + let mut padded = data.array_like(dim, mode.init()).to_owned(); + apply_padding(data, &pad, mode, &mut padded).expect("Failed to apply padding"); + padded +} + +impl Pad for ArrayBase +where + A: Copy + FromPrimitive + Num, + D: Dimension, + S: DataOwned, +{ + type Output = Array; + + fn pad(&self, mode: PadMode, padding: &[[usize; 2]]) -> Self::Output { + pad(self, padding, mode) + } +} diff --git a/core/src/ops/pad/mode.rs b/core/src/utils/pad/impl_pad_mode.rs similarity index 54% rename from core/src/ops/pad/mode.rs rename to core/src/utils/pad/impl_pad_mode.rs index 7dd96abd..cb84b786 100644 --- a/core/src/ops/pad/mode.rs +++ b/core/src/utils/pad/impl_pad_mode.rs @@ -1,80 +1,10 @@ /* - Appellation: mode - Contrib: FL03 + Appellation: impl_pad_mode + Created At: 2025.11.26:16:11:34 + Contrib: @FL03 */ -use num::Zero; - -#[derive( - Clone, - Copy, - Debug, - Default, - Eq, - Hash, - Ord, - PartialEq, - PartialOrd, - variants::VariantConstructors, - strum::AsRefStr, - strum::Display, - strum::EnumCount, - strum::EnumIs, - strum::EnumIter, - strum::EnumString, - strum::VariantArray, - strum::VariantNames, -)] -#[cfg_attr( - feature = "serde", - derive(serde::Deserialize, serde::Serialize), - serde(rename_all = "snake_case", untagged) -)] -#[strum(serialize_all = "snake_case")] -pub enum PadAction { - Clipping, - Lane, - Reflecting, - #[default] - StopAfterCopy, - Wrapping, -} - -#[derive( - Clone, - Copy, - Debug, - Eq, - Hash, - Ord, - PartialEq, - PartialOrd, - strum::AsRefStr, - strum::Display, - strum::EnumCount, - strum::EnumIs, - strum::VariantNames, -)] -#[cfg_attr( - feature = "serde", - derive(serde::Deserialize, serde::Serialize), - serde(rename_all = "snake_case") -)] -#[strum(serialize_all = "snake_case")] -#[repr(C)] -#[derive(Default)] -pub enum PadMode { - Constant(T), - Edge, - Maximum, - Mean, - Median, - Minimum, - Mode, - Reflect, - Symmetric, - #[default] - Wrap, -} +use super::{PadAction, PadMode}; +use num_traits::Zero; impl From for PadMode { fn from(value: T) -> Self { diff --git a/core/src/ops/pad/padding.rs b/core/src/utils/pad/impl_padding.rs similarity index 100% rename from core/src/ops/pad/padding.rs rename to core/src/utils/pad/impl_padding.rs diff --git a/utils/src/utils/patterns.rs b/core/src/utils/patterns.rs similarity index 100% rename from utils/src/utils/patterns.rs rename to core/src/utils/patterns.rs diff --git a/utils/src/utils/tensor.rs b/core/src/utils/tensor.rs similarity index 100% rename from utils/src/utils/tensor.rs rename to core/src/utils/tensor.rs diff --git a/utils/tests/fft.rs b/core/tests/fft.rs similarity index 96% rename from utils/tests/fft.rs rename to core/tests/fft.rs index aeda41b9..3a38521b 100644 --- a/utils/tests/fft.rs +++ b/core/tests/fft.rs @@ -2,14 +2,12 @@ Appellation: fft Contrib: FL03 */ -extern crate concision_utils as utils; - -use utils::signal::fft::*; +use concision_core::utils::fft::*; use approx::assert_abs_diff_eq; use lazy_static::lazy_static; -use num::complex::{Complex, ComplexFloat}; -use num::traits::Float; +use num_complex::{Complex, ComplexFloat}; +use num_traits::Float; const EPSILON: f64 = 1e-6; diff --git a/core/tests/traits.rs b/core/tests/traits.rs deleted file mode 100644 index 7404ccca..00000000 --- a/core/tests/traits.rs +++ /dev/null @@ -1,51 +0,0 @@ -/* - Appellation: traits - Contrib: FL03 -*/ -extern crate concision_core as cnc; - -use cnc::{Affine, Inverse, MaskFill, MatPow, Unsqueeze}; -use ndarray::{Array2, Ix2, array}; - -#[test] -fn test_affine() { - let x = array![[0.0, 1.0], [2.0, 3.0]]; - - let y = x.affine(4.0, -2.0); - assert_eq!(y, array![[-2.0, 2.0], [6.0, 10.0]]); -} - -#[test] -fn test_inverse() { - let a = array![[1.0, 2.0], [3.0, 4.0]]; - let b = array![[1.0, 2.0, 3.0,], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]; - let exp = array![[-2.0, 1.0], [1.5, -0.5]]; - assert_eq!(Some(exp), a.inverse()); - assert_eq!(None, b.inverse()); -} - -#[test] -fn test_masked_fill() { - let shape = (2, 2); - let mask = array![[true, false], [false, true]]; - let arr = cnc::linarr::(shape).unwrap(); - let a = arr.masked_fill(&mask, 0.0); - assert_eq!(a, array![[0.0, 1.0], [2.0, 0.0]]); -} - -#[test] -fn test_matrix_power() { - let x = array![[1.0, 2.0], [3.0, 4.0]]; - assert_eq!(x.matpow(0), Array2::::eye(2)); - assert_eq!(x.matpow(1), x); - assert_eq!(x.matpow(2), x.dot(&x)); -} - -#[test] -fn test_unsqueeze() { - let arr = array![1, 2, 3, 4]; - let a = arr.clone().unsqueeze(0); - assert_eq!(a.dim(), (1, 4)); - let b = arr.unsqueeze(1); - assert_eq!(b.dim(), (4, 1)); -} diff --git a/utils/tests/tensor.rs b/core/tests/utils.rs similarity index 73% rename from utils/tests/tensor.rs rename to core/tests/utils.rs index 7ac9b6b9..37fb7fc8 100644 --- a/utils/tests/tensor.rs +++ b/core/tests/utils.rs @@ -1,7 +1,10 @@ -extern crate concision_utils as utils; - +/* + Appellation: utils + Created At: 2025.11.26:12:41:08 + Contrib: @FL03 +*/ +use concision_core::{linarr, tril}; use ndarray::prelude::*; -use utils::linarr; #[test] fn test_linarr() { @@ -18,5 +21,5 @@ fn test_linarr() { fn test_tril() { let a = linarr::((3, 3)).unwrap(); let exp = array![[0.0, 0.0, 0.0], [3.0, 4.0, 0.0], [6.0, 7.0, 8.0,]]; - assert_eq!(exp, utils::tril(&a)); + assert_eq!(exp, tril(&a)); } diff --git a/neural/src/train/error.rs b/data/src/error.rs similarity index 67% rename from neural/src/train/error.rs rename to data/src/error.rs index 08c6ba27..fbff8524 100644 --- a/neural/src/train/error.rs +++ b/data/src/error.rs @@ -1,7 +1,14 @@ -#[allow(dead_code)] +/* + Appellation: error + Created At: 2025.11.26:17:48:01 + Contrib: @FL03 +*/ +//! The error module for external datasets and training; +//! + /// a type alias for a [`Result`](core::result::Result) with an error type of /// [`TrainingError`]. -pub(crate) type TrainingResult = Result; +pub type TrainingResult = Result; /// The [`TrainingError`] type enumerates the various errors that can occur during the /// training process. diff --git a/data/src/lib.rs b/data/src/lib.rs index 140e5524..225fcdc9 100644 --- a/data/src/lib.rs +++ b/data/src/lib.rs @@ -2,19 +2,31 @@ Appellation: concision-data Contrib: @FL03 */ -//! Datasets and data loaders for the Concision framework. -#![crate_name = "concision_data"] +//! This crate works to augment the training process by providing datasets and loaders for +//! common data formats. +#![allow( + clippy::missing_safety_doc, + clippy::module_inception, + clippy::needless_doctest_main, + clippy::upper_case_acronyms +)] #![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(feature = "nightly", feature(allocator_api))] +#![crate_type = "lib"] + +#[cfg(not(any(feature = "std", feature = "alloc")))] +compiler_error! { + "Either the \"std\" feature or the \"alloc\" feature must be enabled." +} #[cfg(feature = "alloc")] extern crate alloc; -#[doc(inline)] -pub use self::{dataset::DatasetBase, traits::prelude::*}; - pub mod dataset; +pub mod error; #[cfg(feature = "loader")] pub mod loader; +pub mod trainer; #[macro_use] pub(crate) mod macros { @@ -23,20 +35,20 @@ pub(crate) mod macros { } pub mod traits { + //! Additional traits and interfaces for working with datasets and data loaders. #[doc(inline)] - pub use self::prelude::*; - - pub mod convert; - pub mod records; + pub use self::{convert::*, records::*}; - pub(crate) mod prelude { - #[doc(inline)] - pub use super::convert::*; - #[doc(inline)] - pub use super::records::*; - } + mod convert; + mod records; } - +// re-exports +#[doc(inline)] +#[cfg(feature = "loader")] +pub use self::loader::*; +#[doc(inline)] +pub use self::{dataset::DatasetBase, error::*, trainer::*, traits::*}; +// prelude pub mod prelude { #[doc(no_inline)] pub use crate::dataset::*; @@ -44,5 +56,5 @@ pub mod prelude { #[doc(no_inline)] pub use crate::loader::prelude::*; #[doc(no_inline)] - pub use crate::traits::prelude::*; + pub use crate::traits::*; } diff --git a/data/src/trainer.rs b/data/src/trainer.rs new file mode 100644 index 00000000..bf63a543 --- /dev/null +++ b/data/src/trainer.rs @@ -0,0 +1,64 @@ +/* + Appellation: trainer + Contrib: @FL03 +*/ + +mod impl_trainer; + +use crate::Records; +use crate::dataset::DatasetBase; +use concision_core::Model; + +pub trait ModelTrainer { + type Model: Model; + /// returns a model trainer prepared to train the model; this is a convenience method + /// that creates a new trainer instance and returns it. Trainers are lazily evaluated + /// meaning that the training process won't begin until the user calls the `begin` method. + fn trainer<'a, U, V>( + &mut self, + dataset: DatasetBase, + model: &'a mut Self::Model, + ) -> Trainer<'a, Self::Model, T, DatasetBase> + where + Self: Sized, + T: Default, + for<'b> &'b mut Self::Model: Model, + { + Trainer::new(model, dataset) + } +} + +/// The [`Trainer`] is a generalized model trainer that works to provide a common interface for +/// training models over datasets. +pub struct Trainer<'a, M, T, R> +where + M: Model, + R: Records, +{ + /// the training dataset + pub(crate) dataset: DatasetBase, + pub(crate) model: &'a mut M, + /// the accumulated loss + pub(crate) loss: T, +} + +impl<'a, M, T, R> core::ops::Deref for Trainer<'a, M, T, R> +where + M: Model, + R: Records, +{ + type Target = M; + + fn deref(&self) -> &Self::Target { + self.model + } +} +impl<'a, M, T, R> core::ops::DerefMut for Trainer<'a, M, T, R> +where + M: Model, + R: Records, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + self.model + } +} diff --git a/neural/src/train/trainer.rs b/data/src/trainer/impl_trainer.rs similarity index 57% rename from neural/src/train/trainer.rs rename to data/src/trainer/impl_trainer.rs index 45b2e64b..85584358 100644 --- a/neural/src/train/trainer.rs +++ b/data/src/trainer/impl_trainer.rs @@ -1,22 +1,12 @@ /* - Appellation: trainer + Appellation: impl_trainer + Created At: 2025.11.28:13:12:11 Contrib: @FL03 */ - -use crate::Model; -use concision_data::{DatasetBase, IntoDataset, Records}; - -pub struct Trainer<'a, M, T, R> -where - M: Model, - R: Records, -{ - /// the training dataset - pub(crate) dataset: DatasetBase, - pub(crate) model: &'a mut M, - /// the accumulated loss - pub(crate) loss: T, -} +use super::Trainer; +use crate::dataset::DatasetBase; +use crate::{IntoDataset, Records}; +use concision_core::Model; impl<'a, M, T, R> Trainer<'a, M, T, R> where @@ -50,29 +40,8 @@ where pub fn dataset_mut(&mut self) -> &mut DatasetBase { &mut self.dataset } - #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))] + pub fn begin(&self) -> &Self { todo!("Define a generic training loop...") } } - -impl<'a, M, T, R> core::ops::Deref for Trainer<'a, M, T, R> -where - M: Model, - R: Records, -{ - type Target = M; - - fn deref(&self) -> &Self::Target { - self.model - } -} -impl<'a, M, T, R> core::ops::DerefMut for Trainer<'a, M, T, R> -where - M: Model, - R: Records, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - self.model - } -} diff --git a/data/src/traits/trainers.rs b/data/src/traits/trainers.rs new file mode 100644 index 00000000..c71bfde6 --- /dev/null +++ b/data/src/traits/trainers.rs @@ -0,0 +1,10 @@ +/* + appellation: trainers + authors: @FL03 +*/ +use crate::trainer::Trainer; + +use crate::dataset::DatasetBase; +use concision_core::Model; + + diff --git a/ext/Cargo.toml b/ext/Cargo.toml index 279468b0..d0cbc475 100644 --- a/ext/Cargo.toml +++ b/ext/Cargo.toml @@ -1,119 +1,143 @@ [package] -build = "build.rs" +build = "build.rs" description = "this crate implements additional models using the concision framework" -name = "concision-ext" - -authors.workspace = true -categories.workspace = true -edition.workspace = true -homepage.workspace = true -keywords.workspace = true -license.workspace = true -readme.workspace = true -repository.workspace = true +name = "concision-ext" + +authors.workspace = true +categories.workspace = true +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true rust-version.workspace = true -version.workspace = true +version.workspace = true [package.metadata.docs.rs] all-features = false -features = ["full"] -rustc-args = ["--cfg", "docsrs"] -version = "v{{version}}" +features = ["full"] +rustc-args = ["--cfg", "docsrs"] +version = "v{{version}}" [package.metadata.release] no-dev-version = true -tag-name = "{{version}}" +tag-name = "{{version}}" [lib] -bench = false +bench = false crate-type = ["cdylib", "rlib"] -doc = true -doctest = true -test = true +doc = true +doctest = true +test = true + +[[example]] +name = "attention" +required-features = ["attention", "rand", "std"] + +[[test]] +name = "attention" +required-features = ["approx", "attention", "rand", "std"] + +[[test]] +name = "snn" +required-features = ["approx", "snn", "std"] [dependencies] -# local concision = { features = ["neural"], workspace = true } # custom variants = { workspace = true } +# error handling +anyhow = { workspace = true } # concurrency & parallelism rayon = { optional = true, workspace = true } # data-structures ndarray = { workspace = true } # mathematics -approx = { optional = true, workspace = true } -num = { workspace = true } +approx = { optional = true, workspace = true } +num = { workspace = true } num-complex = { optional = true, workspace = true } -num-traits = { workspace = true } -rustfft = { optional = true, workspace = true } +num-traits = { workspace = true } +rustfft = { optional = true, workspace = true } # serialization -serde = { optional = true, workspace = true } +serde = { optional = true, workspace = true } serde_derive = { optional = true, workspace = true } -serde_json = { optional = true, workspace = true } +serde_json = { optional = true, workspace = true } # logging tracing = { optional = true, workspace = true } [dev-dependencies] -anyhow = { features = ["std"], workspace = true } -lazy_static = { workspace = true } +lazy_static = { workspace = true } tracing-subscriber = { features = ["std"], workspace = true } [features] default = ["attention", "std"] full = [ - "complex", - "default", - "json", - "rand", - "serde", - "tracing" + "complex", + "default", + "json", + "rand", + "serde", + "tracing", + "models", ] nightly = ["concision/nightly"] -# ************* [FF:Features] ************* -attention = [] +# ************* [FF:Toggles] ************* +json = ["alloc", "serde", "serde_json"] -signal = [ - "complex", "rustfft" -] +signal = ["complex", "rustfft"] -json = [ - "alloc", "serde", "serde_json" +# ********* [FF:Models] ********* +models = [ + "transformer", ] -init = [ - "concision/init", "rand" -] +attention = [] + +cnn = [] + +kan = [] + +rnn = [] + +s4 = [] + +snn = [] + +transformer = ["attention"] + # ************* [FF:Environments] ************* std = [ - "alloc", - "approx?/std", - "concision/std", - "ndarray/std", - "num-complex?/std", - "num-traits/std", - "num/std", - "serde?/std", - "serde_json?/std", - "tracing?/std", - "variants/std" + "alloc", + "anyhow/std", + "approx?/std", + "concision/std", + "ndarray/std", + "num-complex?/std", + "num-traits/std", + "num/std", + "serde?/std", + "serde_json?/std", + "tracing?/std", + "variants/std", ] wasi = ["concision/wasi"] wasm = [ - "concision/wasm", - "rayon?/web_spin_lock" + "concision/wasm", + "rayon?/web_spin_lock", ] # ************* [FF:Dependencies] ************* alloc = [ - "concision/alloc", - "num/alloc", - "serde?/alloc", - "serde_json?/alloc", - "variants/alloc" + "concision/alloc", + "num/alloc", + "serde?/alloc", + "serde_json?/alloc", + "variants/alloc", ] approx = ["concision/approx", "dep:approx", "ndarray/approx"] @@ -131,19 +155,14 @@ rng = ["concision/rng"] rustfft = ["dep:rustfft"] serde = [ - "concision/serde", + "concision/serde", "dep:serde", - "dep:serde_derive", - "ndarray/serde", - "num-complex?/serde", - "num/serde" + "dep:serde_derive", + "ndarray/serde", + "num-complex?/serde", + "num/serde", ] serde_json = ["dep:serde_json"] tracing = ["concision/tracing", "dep:tracing"] - -# ************* [Unit Tests] ************* -[[test]] -name = "attention" -required-features = ["approx", "attention", "default", "rand"] diff --git a/ext/examples/attention.rs b/ext/examples/attention.rs new file mode 100644 index 00000000..3d3fca29 --- /dev/null +++ b/ext/examples/attention.rs @@ -0,0 +1,23 @@ +/* + Appellation: attention + Created At: 2025.11.28:13:41:41 + Contrib: @FL03 +*/ +use concision_ext::attention::{Qkv, ScaledDotProductAttention}; + +fn main() -> anyhow::Result<()> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::TRACE) + .with_target(false) + .without_time() + .init(); + let (m, n) = (7, 10); + let qkv = Qkv::::ones((m, n)); + // initialize the scaled dot-product attention layer + let layer = ScaledDotProductAttention::::new(0.1, 1.0); + // compute the attention scores + let z_score = layer.attention(&qkv); + println!("z_score: {:?}", z_score); + + Ok(()) +} diff --git a/ext/src/attention/qkv.rs b/ext/src/attention/qkv.rs index 0d6a502b..19b48829 100644 --- a/ext/src/attention/qkv.rs +++ b/ext/src/attention/qkv.rs @@ -10,14 +10,14 @@ use num_traits::{One, Zero}; pub type Qkv = QkvParamsBase, D>; /// This object is designed to store the parameters of the QKV (Query, Key, Value) -pub struct QkvParamsBase +pub struct QkvParamsBase::Elem> where D: Dimension, - S: RawData, + S: RawData, { - pub(crate) query: ArrayBase, - pub(crate) key: ArrayBase, - pub(crate) value: ArrayBase, + pub(crate) query: ArrayBase, + pub(crate) key: ArrayBase, + pub(crate) value: ArrayBase, } impl QkvParamsBase @@ -25,7 +25,7 @@ where D: Dimension, S: RawData, { - pub fn new(query: ArrayBase, key: ArrayBase, value: ArrayBase) -> Self { + pub fn new(query: ArrayBase, key: ArrayBase, value: ArrayBase) -> Self { Self { query, key, value } } pub fn from_elem>(shape: Sh, elem: A) -> Self @@ -65,71 +65,70 @@ where Self::from_elem(shape, A::zero()) } /// returns an immutable reference to the key parameters - pub const fn key(&self) -> &ArrayBase { + pub const fn key(&self) -> &ArrayBase { &self.key } /// returns a mutable reference to the key parameters - pub fn key_mut(&mut self) -> &mut ArrayBase { + pub fn key_mut(&mut self) -> &mut ArrayBase { &mut self.key } /// returns an immutable reference to the query parameters - pub const fn query(&self) -> &ArrayBase { + pub const fn query(&self) -> &ArrayBase { &self.query } /// returns a mutable reference to the query parameters - pub fn query_mut(&mut self) -> &mut ArrayBase { + pub fn query_mut(&mut self) -> &mut ArrayBase { &mut self.query } /// returns an immutable reference to the value parameters - pub const fn value(&self) -> &ArrayBase { + pub const fn value(&self) -> &ArrayBase { &self.value } /// returns a mutable reference to the value parameters - pub fn value_mut(&mut self) -> &mut ArrayBase { + pub fn value_mut(&mut self) -> &mut ArrayBase { &mut self.value } - pub fn set_key(&mut self, key: ArrayBase) -> &mut Self { + pub fn set_key(&mut self, key: ArrayBase) -> &mut Self { *self.key_mut() = key; self } - pub fn set_query(&mut self, query: ArrayBase) -> &mut Self { + pub fn set_query(&mut self, query: ArrayBase) -> &mut Self { *self.query_mut() = query; self } - pub fn set_value(&mut self, value: ArrayBase) -> &mut Self { + pub fn set_value(&mut self, value: ArrayBase) -> &mut Self { *self.value_mut() = value; self } - pub fn with_key(self, key: ArrayBase) -> Self { + pub fn with_key(self, key: ArrayBase) -> Self { Self { key, ..self } } - pub fn with_query(self, query: ArrayBase) -> Self { + pub fn with_query(self, query: ArrayBase) -> Self { Self { query, ..self } } - pub fn with_value(self, value: ArrayBase) -> Self { + pub fn with_value(self, value: ArrayBase) -> Self { Self { value, ..self } } } -/// This trait is used to implement the forward pass for the QKV parameters. -impl Forward for QkvParamsBase +impl Forward for QkvParamsBase where A: Clone, D: Dimension, S: Data, - X: Dot, Output = Z>, - Z: core::ops::Add, - for<'a> Z: core::ops::Add<&'a Z, Output = Z>, + X: Dot, Output = Y>, + Y: core::ops::Add, + for<'a> Y: core::ops::Add<&'a Y, Output = Y>, { - type Output = Z; + type Output = Y; - fn forward(&self, input: &X) -> Option { + fn forward(&self, input: &X) -> Option { let query = input.dot(&self.query); let key = input.dot(&self.key); let value = input.dot(&self.value); diff --git a/models/kan/src/lib.rs b/ext/src/kan/mod.rs similarity index 52% rename from models/kan/src/lib.rs rename to ext/src/kan/mod.rs index a94e4319..53ea88b6 100644 --- a/models/kan/src/lib.rs +++ b/ext/src/kan/mod.rs @@ -1,9 +1,8 @@ /* - appellation: concision-kan - authors: @FL03 + Appellation: kan + Created At: 2025.11.26:13:58:50 + Contrib: @FL03 */ -//! # `concision-kan` -//! //! This library provides an implementation of the Kolmogorov–Arnold Networks (kan) model using //! the [`concision`](https://docs.rs/concision) framework. //! @@ -11,19 +10,11 @@ //! //! - [KAN: Kolmogorov–Arnold Networks](https://arxiv.org/html/2404.19756v1) //! -#![crate_name = "concision_kan"] -#![crate_type = "lib"] -#![cfg_attr(not(feature = "std"), no_std)] -#![allow(clippy::module_inception)] - -extern crate concision as cnc; - #[doc(inline)] pub use self::model::*; -pub mod model; +mod model; -pub mod prelude { - #[doc(inline)] +pub(crate) mod prelude { pub use super::model::*; } diff --git a/models/kan/src/model.rs b/ext/src/kan/model.rs similarity index 97% rename from models/kan/src/model.rs rename to ext/src/kan/model.rs index 6c0b73d8..ec21c8b1 100644 --- a/models/kan/src/model.rs +++ b/ext/src/kan/model.rs @@ -16,10 +16,7 @@ pub struct KanModel { pub params: DeepModelParams, } -impl KanModel -where - T: Float + FromPrimitive, -{ +impl KanModel { pub fn new(config: StandardModelConfig, features: ModelFeatures) -> Self where T: Clone + Default, @@ -31,14 +28,6 @@ where params, } } - #[cfg(feature = "rand")] - pub fn init(self) -> Self - where - rand_distr::StandardNormal: rand_distr::Distribution, - { - let params = DeepModelParams::glorot_normal(self.features()); - KanModel { params, ..self } - } /// returns a reference to the model configuration pub const fn config(&self) -> &StandardModelConfig { &self.config @@ -92,6 +81,21 @@ where } } +impl KanModel +where + T: 'static + Float + FromPrimitive, +{ + #[cfg(feature = "rand")] + pub fn init(self) -> Self + where + T: 'static + Float + FromPrimitive, + rand_distr::StandardNormal: rand_distr::Distribution, + { + let params = DeepModelParams::glorot_normal(self.features()); + KanModel { params, ..self } + } +} + impl Model for KanModel { type Config = StandardModelConfig; type Layout = ModelFeatures; diff --git a/ext/src/lib.rs b/ext/src/lib.rs index 3f6a8b7f..eb493376 100644 --- a/ext/src/lib.rs +++ b/ext/src/lib.rs @@ -17,17 +17,30 @@ #[cfg(feature = "alloc")] extern crate alloc; -extern crate concision as cnc; - -#[cfg(feature = "attention")] -pub use self::attention::prelude::*; - #[cfg(feature = "attention")] pub mod attention; +#[cfg(feature = "kan")] +pub mod kan; +#[cfg(feature = "s4")] +pub mod s4; +#[cfg(feature = "snn")] +pub mod snn; +#[cfg(feature = "transformer")] +pub mod transformer; -// pub mod simple; - +/// re-exports +#[cfg(feature = "attention")] +pub use self::attention::prelude::*; +#[doc(hidden)] pub mod prelude { #[cfg(feature = "attention")] pub use crate::attention::prelude::*; + #[cfg(feature = "kan")] + pub use crate::kan::prelude::*; + #[cfg(feature = "s4")] + pub use crate::s4::prelude::*; + #[cfg(feature = "snn")] + pub use crate::snn::prelude::*; + #[cfg(feature = "transformer")] + pub use crate::transformer::prelude::*; } diff --git a/models/s4/src/lib.rs b/ext/src/s4/mod.rs similarity index 60% rename from models/s4/src/lib.rs rename to ext/src/s4/mod.rs index 36a7354c..b02ac779 100644 --- a/models/s4/src/lib.rs +++ b/ext/src/s4/mod.rs @@ -1,9 +1,7 @@ /* - appellation: concision-s4 + appellation: s4 authors: @FL03 */ -//! # `concision-s4` -//! //! This library provides the sequential, structured state-space (S4) model implementation for the Concision framework. //! //! ## References @@ -11,19 +9,11 @@ //! - [Structured State Spaces for Sequence Modeling](https://arxiv.org/abs/2106.08084) //! - [Efficiently Modeling Long Sequences with Structured State Spaces](https://arxiv.org/abs/2111.00396) //! -#![crate_name = "concision_s4"] -#![crate_type = "lib"] -#![cfg_attr(not(feature = "std"), no_std)] -#![allow(clippy::module_inception)] - -extern crate concision as cnc; - #[doc(inline)] pub use self::model::*; -pub mod model; +mod model; -pub mod prelude { - #[doc(inline)] +pub(crate) mod prelude { pub use super::model::*; } diff --git a/models/s4/src/model.rs b/ext/src/s4/model.rs similarity index 95% rename from models/s4/src/model.rs rename to ext/src/s4/model.rs index e7c1584f..1144521e 100644 --- a/models/s4/src/model.rs +++ b/ext/src/s4/model.rs @@ -16,10 +16,7 @@ pub struct S4Model { pub params: DeepModelParams, } -impl S4Model -where - T: Float + FromPrimitive, -{ +impl S4Model { pub fn new(config: StandardModelConfig, features: ModelFeatures) -> Self where T: Clone + Default, @@ -31,15 +28,6 @@ where params, } } - #[cfg(feature = "rand")] - pub fn init(self) -> Self - where - T: Float + FromPrimitive, - rand_distr::StandardNormal: rand_distr::Distribution, - { - let params = DeepModelParams::glorot_normal(self.features()); - S4Model { params, ..self } - } /// returns a reference to the model configuration pub const fn config(&self) -> &StandardModelConfig { &self.config @@ -93,6 +81,21 @@ where } } +impl S4Model +where + T: 'static + Float + FromPrimitive, +{ + /// initialize the model parameters using Glorot normal initialization + #[cfg(feature = "rand")] + pub fn init(self) -> Self + where + T: 'static + Float + FromPrimitive, + rand_distr::StandardNormal: rand_distr::Distribution, + { + let params = DeepModelParams::glorot_normal(self.features()); + S4Model { params, ..self } + } +} impl Model for S4Model { type Config = StandardModelConfig; diff --git a/ext/src/simple.rs b/ext/src/simple.rs deleted file mode 100644 index 14559d18..00000000 --- a/ext/src/simple.rs +++ /dev/null @@ -1,305 +0,0 @@ -/* - Appellation: simple - Contrib: @FL03 -*/ -use cnc::nn::{DeepModelParams, Model, ModelFeatures, NeuralError, StandardModelConfig, Train}; -use cnc::{Forward, Norm, Params, ReLU, Sigmoid}; - -use ndarray::prelude::*; -use ndarray::{Data, ScalarOperand}; -use num_traits::{Float, FromPrimitive, NumAssign}; - -#[derive(Clone, Debug)] -pub struct SimpleModel { - pub config: StandardModelConfig, - pub features: ModelFeatures, - pub params: DeepModelParams, -} - -impl SimpleModel { - pub fn new(config: StandardModelConfig, features: ModelFeatures) -> Self - where - T: Clone + Default, - { - let params = DeepModelParams::default(features); - SimpleModel { - config, - features, - params, - } - } - /// returns a reference to the model configuration - pub const fn config(&self) -> &StandardModelConfig { - &self.config - } - /// returns a mutable reference to the model configuration - pub const fn config_mut(&mut self) -> &mut StandardModelConfig { - &mut self.config - } - /// returns the model features - pub const fn features(&self) -> ModelFeatures { - self.features - } - /// returns a mutable reference to the model features - pub const fn features_mut(&mut self) -> &mut ModelFeatures { - &mut self.features - } - /// returns a reference to the model parameters - pub const fn params(&self) -> &DeepModelParams { - &self.params - } - /// returns a mutable reference to the model parameters - pub const fn params_mut(&mut self) -> &mut DeepModelParams { - &mut self.params - } - /// set the current configuration and return a mutable reference to the model - pub fn set_config(&mut self, config: StandardModelConfig) -> &mut Self { - self.config = config; - self - } - /// set the current features and return a mutable reference to the model - pub fn set_features(&mut self, features: ModelFeatures) -> &mut Self { - self.features = features; - self - } - /// set the current parameters and return a mutable reference to the model - pub fn set_params(&mut self, params: DeepModelParams) -> &mut Self { - self.params = params; - self - } - /// consumes the current instance to create another with the given configuration - pub fn with_config(self, config: StandardModelConfig) -> Self { - Self { config, ..self } - } - /// consumes the current instance to create another with the given features - pub fn with_features(self, features: ModelFeatures) -> Self { - Self { features, ..self } - } - /// consumes the current instance to create another with the given parameters - pub fn with_params(self, params: DeepModelParams) -> Self { - Self { params, ..self } - } - /// initializes the model with Glorot normal distribution - #[cfg(feature = "rand")] - pub fn init(self) -> Self - where - T: Float + FromPrimitive, - cnc::rand_distr::StandardNormal: cnc::rand_distr::Distribution, - { - let params = DeepModelParams::glorot_normal(self.features()); - SimpleModel { params, ..self } - } -} - -impl Model for SimpleModel { - type Config = StandardModelConfig; - type Layout = ModelFeatures; - - fn config(&self) -> &StandardModelConfig { - &self.config - } - - fn config_mut(&mut self) -> &mut StandardModelConfig { - &mut self.config - } - - fn layout(&self) -> ModelFeatures { - self.features - } - - fn params(&self) -> &DeepModelParams { - &self.params - } - - fn params_mut(&mut self) -> &mut DeepModelParams { - &mut self.params - } -} - -impl Forward> for SimpleModel -where - A: Float + FromPrimitive + ScalarOperand, - D: Dimension, - S: Data, - Params: Forward, Output = Array>, -{ - type Output = Array; - - fn forward(&self, input: &ArrayBase) -> cnc::Result { - let mut output = self - .params() - .input() - .forward_then(&input.to_owned(), |y| y.relu())?; - - for layer in self.params().hidden() { - output = layer.forward_then(&output, |y| y.relu())?; - } - - let y = self - .params() - .output() - .forward_then(&output, |y| y.sigmoid())?; - Ok(y) - } -} - -impl Train, ArrayBase> for SimpleModel -where - A: Float + FromPrimitive + NumAssign + ScalarOperand + core::fmt::Debug, - S: Data, - T: Data, -{ - type Output = A; - - #[cfg_attr( - feature = "tracing", - tracing::instrument( - skip(self, input, target), - level = "trace", - name = "backward", - target = "model", - ) - )] - fn train( - &mut self, - input: &ArrayBase, - target: &ArrayBase, - ) -> Result { - if input.len() != self.features().input() { - return Err(NeuralError::InvalidInputShape); - } - if target.len() != self.features().output() { - return Err(NeuralError::InvalidOutputShape); - } - // get the learning rate from the model's configuration - let lr = self - .config() - .learning_rate() - .copied() - .unwrap_or(A::from_f32(0.01).unwrap()); - // Normalize the input and target - let input = input / input.l2_norm(); - let target_norm = target.l2_norm(); - let target = target / target_norm; - // self.prev_target_norm = Some(target_norm); - // Forward pass to collect activations - let mut activations = Vec::new(); - activations.push(input.to_owned()); - - let mut output = self.params().input().forward(&input)?.relu(); - activations.push(output.to_owned()); - // collect the activations of the hidden - for layer in self.params().hidden() { - output = layer.forward(&output)?.relu(); - activations.push(output.to_owned()); - } - - output = self.params().output().forward(&output)?.sigmoid(); - activations.push(output.to_owned()); - - // Calculate output layer error - let error = &target - &output; - let loss = error.pow2().mean().unwrap_or(A::zero()); - #[cfg(feature = "tracing")] - tracing::trace!("Training loss: {loss:?}"); - let mut delta = error * output.sigmoid_derivative(); - delta /= delta.l2_norm(); // Normalize the delta to prevent exploding gradients - - // Update output weights - self.params_mut() - .output_mut() - .backward(activations.last().unwrap(), &delta, lr)?; - - let num_hidden = self.features().layers(); - // Iterate through hidden layers in reverse order - for i in (0..num_hidden).rev() { - // Calculate error for this layer - delta = if i == num_hidden - 1 { - // use the output activations for the final hidden layer - self.params().output().weights().dot(&delta) * activations[i + 1].relu_derivative() - } else { - // else; backpropagate using the previous hidden layer - self.params().hidden()[i + 1].weights().t().dot(&delta) - * activations[i + 1].relu_derivative() - }; - // Normalize delta to prevent exploding gradients - delta /= delta.l2_norm(); - self.params_mut().hidden_mut()[i].backward(&activations[i + 1], &delta, lr)?; - } - /* - Backpropagate to the input layer - The delta for the input layer is computed using the weights of the first hidden layer - and the derivative of the activation function of the first hidden layer. - - (h, h).dot(h) * derivative(h) = dim(h) where h is the number of features within a hidden layer - */ - delta = self.params().hidden()[0].weights().dot(&delta) * activations[1].relu_derivative(); - delta /= delta.l2_norm(); // Normalize the delta to prevent exploding gradients - self.params_mut() - .input_mut() - .backward(&activations[1], &delta, lr)?; - - Ok(loss) - } -} - -impl Train, ArrayBase> for SimpleModel -where - A: Float + FromPrimitive + NumAssign + ScalarOperand + core::fmt::Debug, - S: Data, - T: Data, -{ - type Output = A; - - #[cfg_attr( - feature = "tracing", - tracing::instrument( - skip(self, input, target), - level = "trace", - name = "train", - target = "model", - fields(input_shape = ?input.shape(), target_shape = ?target.shape()) - ) - )] - fn train( - &mut self, - input: &ArrayBase, - target: &ArrayBase, - ) -> Result { - if input.nrows() == 0 || target.nrows() == 0 { - return Err(NeuralError::InvalidBatchSize); - } - if input.ncols() != self.features().input() { - return Err(NeuralError::InvalidInputShape); - } - if target.ncols() != self.features().output() || target.nrows() != input.nrows() { - return Err(NeuralError::InvalidOutputShape); - } - let mut loss = A::zero(); - - for (i, (x, e)) in input.rows().into_iter().zip(target.rows()).enumerate() { - loss += match Train::, ArrayView1>::train(self, &x, &e) { - Ok(l) => l, - Err(err) => { - #[cfg(feature = "tracing")] - tracing::error!( - "Training failed for batch {}/{}: {:?}", - i + 1, - input.nrows(), - err - ); - #[cfg(not(feature = "tracing"))] - eprintln!( - "Training failed for batch {}/{}: {:?}", - i + 1, - input.nrows(), - err - ); - return Err(err); - } - }; - } - - Ok(loss) - } -} diff --git a/ext/src/snn/mod.rs b/ext/src/snn/mod.rs new file mode 100644 index 00000000..84b78032 --- /dev/null +++ b/ext/src/snn/mod.rs @@ -0,0 +1,30 @@ +/* + appellation: snn + authors: @FL03 +*/ +//! Spiking neural networks (SNNs) for the [`concision`](https://crates.io/crates/concision) machine learning framework. +//! +//! ## References +//! +//! - [Deep Learning in Spiking Neural Networks](https://arxiv.org/abs/1804.08150) +//! +#[doc(inline)] +pub use self::{model::*, neuron::*, types::*}; + +mod model; +mod neuron; + +pub mod types { + //! Types for spiking neural networks + #[doc(inline)] + pub use self::{event::*, result::*}; + + mod event; + mod result; +} + +pub(crate) mod prelude { + pub use super::model::*; + pub use super::neuron::*; + pub use super::types::*; +} diff --git a/models/snn/src/model.rs b/ext/src/snn/model.rs similarity index 97% rename from models/snn/src/model.rs rename to ext/src/snn/model.rs index 4b03fc48..fc0d96dc 100644 --- a/models/snn/src/model.rs +++ b/ext/src/snn/model.rs @@ -16,10 +16,7 @@ pub struct SpikingNeuralNetwork { pub params: DeepModelParams, } -impl SpikingNeuralNetwork -where - T: Float + FromPrimitive, -{ +impl SpikingNeuralNetwork { pub fn new(config: StandardModelConfig, features: ModelFeatures) -> Self where T: Clone + Default, @@ -31,15 +28,6 @@ where params, } } - #[cfg(feature = "rand")] - pub fn init(self) -> Self - where - T: Float + FromPrimitive, - rand_distr::StandardNormal: rand_distr::Distribution, - { - let params = DeepModelParams::glorot_normal(self.features()); - SpikingNeuralNetwork { params, ..self } - } /// returns a reference to the model configuration pub const fn config(&self) -> &StandardModelConfig { &self.config @@ -93,6 +81,20 @@ where } } +impl SpikingNeuralNetwork +where + T: 'static + Float + FromPrimitive, +{ + #[cfg(feature = "rand")] + pub fn init(self) -> Self + where + rand_distr::StandardNormal: rand_distr::Distribution, + { + let params = DeepModelParams::glorot_normal(self.features()); + SpikingNeuralNetwork { params, ..self } + } +} + impl Model for SpikingNeuralNetwork { type Config = StandardModelConfig; diff --git a/models/snn/src/neuron.rs b/ext/src/snn/neuron.rs similarity index 89% rename from models/snn/src/neuron.rs rename to ext/src/snn/neuron.rs index 141a839a..3fdc5395 100644 --- a/models/snn/src/neuron.rs +++ b/ext/src/snn/neuron.rs @@ -5,13 +5,35 @@ */ //! Single spiking neuron (LIF + adaptation + exponential synapse) example in pure Rust. //! +//! ## Background +//! //! Model (forward-Euler integration; units are arbitrary but consistent): -//! tau_m * dv/dt = -(v - v_rest) + R*(I_ext + I_syn) - w -//! tau_w * dw/dt = -w -//! tau_s * ds/dt = -s (+ instantaneous increments when presynaptic spikes arrive) //! -//! Spike: when v >= v_thresh -> spike emitted, v <- v_reset, w += b -//! I_syn = s +//! ```math +//! \tau_m * \frac{dv}{dt} = -(v - v_{rest}) + R*(I_{ext} + I_{syn}) - \omega +//! ``` +//! +//! ```math +//! \tau_w * \frac{d\omega}{dt} = -\omega +//! ``` +//! +//! ```math +//! \tau_s * \frac{ds}{dt} = -s +//! ``` +//! +//! where: +//! - $`v`$: membrane potential +//! - $\omega$: adaptation variable +//! - $`s`$: synaptic variable representing total synaptic current +//! +//! If we allow the spike to be represented as $\delta$, then: +//! +//! ```math +//! v\geq{v_{thresh}}\rightarrow{\delta},v\leftarrow{v_{reset}},\omega\mathrel{+}=b +//! ``` +//! +//! and where `b` is the adaptation increment added on spike. +//! The synaptic current is given by: $I_{syn} = s$ //! //! The implementation is conservative with allocations and idiomatic Rust. use super::types::{StepResult, SynapticEvent}; @@ -56,11 +78,12 @@ pub struct SpikingNeuron { } impl SpikingNeuron { + #[allow(clippy::should_implement_trait)] /// Create a new neuron with common default parameters (units: ms and mV-like). /// /// Many fields are set to common neuroscience-like defaults but these are research parameters /// and should be tuned for your experiments. - pub fn new_default() -> Self { + pub fn default() -> Self { let tau_m = 20.0; // ms let resistance = 1.0; // arbitrary let v_rest = -65.0; // mV @@ -86,7 +109,7 @@ impl SpikingNeuron { } /// Create a neuron with explicit parameters and initial state. - pub fn new( + pub const fn new( tau_m: f64, resistance: f64, v_rest: f64, @@ -97,7 +120,11 @@ impl SpikingNeuron { tau_s: f64, initial_v: Option, ) -> Self { - let v0 = initial_v.unwrap_or(v_rest); + let v0 = if let Some(v_init) = initial_v { + v_init + } else { + v_rest + }; Self { tau_m, resistance, @@ -231,7 +258,7 @@ fn example() { let steps = (t_sim / dt) as usize; // Create neuron with defaults - let mut neuron = SpikingNeuron::new_default(); + let mut neuron = SpikingNeuron::default(); // Example external current (constant) let i_ext = 1.8; // tune to see spiking (units consistent with resistance & s) diff --git a/models/snn/src/types/event.rs b/ext/src/snn/types/event.rs similarity index 100% rename from models/snn/src/types/event.rs rename to ext/src/snn/types/event.rs diff --git a/models/snn/src/types/result.rs b/ext/src/snn/types/result.rs similarity index 100% rename from models/snn/src/types/result.rs rename to ext/src/snn/types/result.rs diff --git a/ext/src/transformer/mod.rs b/ext/src/transformer/mod.rs new file mode 100644 index 00000000..a2920c26 --- /dev/null +++ b/ext/src/transformer/mod.rs @@ -0,0 +1,15 @@ +/* + Appellation: transformers + Created At: 2025.11.26:13:59:39 + Contrib: @FL03 +*/ +//! A custom transformer model implementation for the Concision framework. +#[doc(inline)] +pub use self::model::*; + +mod model; + +pub(crate) mod prelude { + #[doc(inline)] + pub use super::model::*; +} diff --git a/models/transformer/src/model.rs b/ext/src/transformer/model.rs similarity index 83% rename from models/transformer/src/model.rs rename to ext/src/transformer/model.rs index 5612527e..a34df757 100644 --- a/models/transformer/src/model.rs +++ b/ext/src/transformer/model.rs @@ -2,12 +2,14 @@ Appellation: transformer Contrib: @FL03 */ - -use cnc::nn::{DeepModelParams, Model, ModelFeatures, NeuralError, StandardModelConfig, Train}; #[cfg(feature = "rand")] use cnc::rand_distr; -use cnc::{Forward, Norm, Params, ReLU, Sigmoid}; +use cnc::{ + DeepModelParams, Forward, Model, ModelFeatures, Norm, Params, ReLUActivation, + SigmoidActivation, StandardModelConfig, Train, +}; +use anyhow::Context; use ndarray::prelude::*; use ndarray::{Data, ScalarOperand}; use num_traits::{Float, FromPrimitive, NumAssign}; @@ -31,15 +33,6 @@ impl TransformerModel { params, } } - #[cfg(feature = "rand")] - pub fn init(self) -> Self - where - T: Float + FromPrimitive, - rand_distr::StandardNormal: rand_distr::Distribution, - { - let params = DeepModelParams::glorot_normal(self.features()); - TransformerModel { params, ..self } - } /// returns a reference to the model configuration pub const fn config(&self) -> &StandardModelConfig { &self.config @@ -93,6 +86,21 @@ impl TransformerModel { } } +impl TransformerModel +where + T: 'static + Float + FromPrimitive, +{ + #[cfg(feature = "rand")] + pub fn init(self) -> Self + where + T: 'static + Float + FromPrimitive, + rand_distr::StandardNormal: rand_distr::Distribution, + { + let params = DeepModelParams::glorot_normal(self.features()); + TransformerModel { params, ..self } + } +} + impl Model for TransformerModel { type Config = StandardModelConfig; type Layout = ModelFeatures; @@ -105,8 +113,8 @@ impl Model for TransformerModel { &mut self.config } - fn layout(&self) -> ModelFeatures { - self.features + fn layout(&self) -> &ModelFeatures { + &self.features } fn params(&self) -> &DeepModelParams { @@ -121,7 +129,7 @@ impl Model for TransformerModel { impl Forward for TransformerModel where A: Float + FromPrimitive + ScalarOperand, - V: ReLU + Sigmoid, + V: ReLUActivation + SigmoidActivation, Params: Forward + Forward, for<'a> &'a U: ndarray::linalg::Dot, Output = V> + core::ops::Add<&'a Array1>, V: for<'a> core::ops::Add<&'a Array1, Output = V>, @@ -149,6 +157,7 @@ where S: Data, T: Data, { + type Error = anyhow::Error; type Output = A; #[cfg_attr( @@ -164,12 +173,20 @@ where &mut self, input: &ArrayBase, target: &ArrayBase, - ) -> Result { + ) -> Result { if input.len() != self.features().input() { - return Err(NeuralError::InvalidInputShape); + anyhow::bail!( + "Invalid input shape: expected {}, got {}", + self.features().input(), + input.len() + ); } if target.len() != self.features().output() { - return Err(NeuralError::InvalidOutputShape); + anyhow::bail!( + "Invalid target shape: expected {}, got {}", + self.features().output(), + target.len() + ); } // get the learning rate from the model's configuration let lr = self @@ -190,14 +207,14 @@ where .params() .input() .forward(&input) - .expect("Output layer failed to forward propagate during training...") + .context("Output layer failed to forward propagate during training...")? .relu(); activations.push(output.to_owned()); // collect the activations of the hidden for layer in self.params().hidden() { output = layer .forward(&output) - .expect("Hidden layer failed to forward propagate during training...") + .context("Hidden layer failed to forward propagate during training...")? .relu(); activations.push(output.to_owned()); } @@ -206,7 +223,7 @@ where .params() .output() .forward(&output) - .expect("Input layer failed to forward propagate during training...") + .context("Input layer failed to forward propagate during training...")? .sigmoid(); activations.push(output.to_owned()); @@ -222,7 +239,7 @@ where self.params_mut() .output_mut() .backward(activations.last().unwrap(), &delta, lr) - .expect("Backward propagation failed..."); + .context("Backward propagation failed...")?; let num_hidden = self.features().layers(); // Iterate through hidden layers in reverse order @@ -240,7 +257,7 @@ where delta /= delta.l2_norm(); self.params_mut().hidden_mut()[i] .backward(&activations[i + 1], &delta, lr) - .expect("Backward propagation failed..."); + .context("Backward propagation failed...")?; } /* Backpropagate to the input layer @@ -254,7 +271,7 @@ where self.params_mut() .input_mut() .backward(&activations[1], &delta, lr) - .expect("Input layer backward pass failed"); + .context("Input layer backward pass failed")?; Ok(loss) } @@ -266,6 +283,7 @@ where S: Data, T: Data, { + type Error = anyhow::Error; type Output = A; #[cfg_attr( @@ -282,15 +300,25 @@ where &mut self, input: &ArrayBase, target: &ArrayBase, - ) -> Result { + ) -> Result { if input.nrows() == 0 || target.nrows() == 0 { - return Err(NeuralError::InvalidBatchSize); + anyhow::bail!("Input and target batches must have at least one sample each"); } if input.ncols() != self.features().input() { - return Err(NeuralError::InvalidInputShape); + anyhow::bail!( + "Invalid input shape: expected {}, got {}", + self.features().input(), + input.ncols() + ); } if target.ncols() != self.features().output() || target.nrows() != input.nrows() { - return Err(NeuralError::InvalidOutputShape); + anyhow::bail!( + "Invalid target shape: expected ({}, {}), got ({}, {})", + input.nrows(), + self.features().output(), + target.nrows(), + target.ncols() + ); } let mut loss = A::zero(); diff --git a/ext/tests/attention.rs b/ext/tests/attention.rs index e2919c5e..7b819932 100644 --- a/ext/tests/attention.rs +++ b/ext/tests/attention.rs @@ -4,8 +4,6 @@ */ use concision_ext::attention::{Qkv, ScaledDotProductAttention}; -use ndarray::prelude::*; - #[test] fn test_scaled_dot_product_attention() { let (m, n) = (7, 10); diff --git a/models/snn/tests/neurons.rs b/ext/tests/snn.rs similarity index 57% rename from models/snn/tests/neurons.rs rename to ext/tests/snn.rs index 8cce7427..620da0d6 100644 --- a/models/snn/tests/neurons.rs +++ b/ext/tests/snn.rs @@ -1,13 +1,14 @@ /* - Appellation: neurons - Created At: 2025.11.25:21:34:30 + Appellation: snn + Created At: 2025.11.26:15:42:45 Contrib: @FL03 */ -use concision_snn::SpikingNeuron; +use approx::assert_abs_diff_eq; +use concision_ext::snn::SpikingNeuron; #[test] fn test_snn_neuron_resting_no_input() { - let mut n = SpikingNeuron::new_default(); + let mut n = SpikingNeuron::default(); let dt = 1.0; // simulate 100 ms with no input -> should not spike and v near v_rest for _ in 0..100 { @@ -15,27 +16,19 @@ fn test_snn_neuron_resting_no_input() { assert!(!res.is_spiked()); } let v = n.membrane_potential(); - assert!((v - n.v_rest).abs() < 1e-6 || (v - n.v_rest).abs() < 1e-2); + assert_abs_diff_eq!(v, n.v_rest); } #[test] -fn test_receive_spike_increases_synaptic_state() { - let mut n = SpikingNeuron::new_default(); - let before = n.synaptic_state(); - n.receive_spike(2.5); - assert!(n.synaptic_state() > before); -} - -#[test] -#[ignore = "Need to fix"] -fn test_spiking_with_sufficient_input() { +// #[ignore = "Need to fix"] +fn test_snn_neuron_spikes() { // params - let dt: f64 = 0.1; - let i_ext: f64 = 5.0; // large i_ext to force spiking + let dt = 1f64; + let i_ext = 50f64; // large i_ext to force spiking // neuron - let mut n = SpikingNeuron::new_default(); + let mut n = SpikingNeuron::default(); let mut spiked = false; - let mut steps = 0_usize; + let mut steps = 0usize; // apply strong constant external current for a while while !spiked && steps < 1000 { spiked = n.step(dt, i_ext).is_spiked(); @@ -43,3 +36,11 @@ fn test_spiking_with_sufficient_input() { } assert!(spiked, "Neuron did not spike under strong current"); } + +#[test] +fn test_snn_neuron_synaptic_state_change() { + let mut n = SpikingNeuron::default(); + let before = n.synaptic_state(); + n.receive_spike(2.5); + assert!(n.synaptic_state() > before); +} diff --git a/flake.nix b/flake.nix index 58be2170..3985a471 100644 --- a/flake.nix +++ b/flake.nix @@ -15,7 +15,7 @@ { packages.default = rustPlatform.buildRustPackage { pname = "concision"; - version = "0.2.5"; + version = "0.2.9"; src = self; # "./."; # If Cargo.lock doesn't exist yet, remove or comment out this block: cargoLock = { diff --git a/init/src/distr/lecun.rs b/init/src/distr/lecun.rs index 5e3059b6..4667a2c2 100644 --- a/init/src/distr/lecun.rs +++ b/init/src/distr/lecun.rs @@ -26,7 +26,7 @@ impl LecunNormal { } /// Create a [truncated normal](TruncatedNormal) [distribution](Distribution) centered at 0; /// See [Self::std_dev] for the standard deviation calculations. - pub fn distr(&self) -> crate::Result> + pub fn distr(&self) -> crate::InitResult> where F: Float, StandardNormal: Distribution, diff --git a/init/src/distr/trunc.rs b/init/src/distr/trunc.rs index 682408f5..b48bcff7 100644 --- a/init/src/distr/trunc.rs +++ b/init/src/distr/trunc.rs @@ -29,7 +29,7 @@ where { /// create a new [`TruncatedNormal`] distribution with the given mean and standard /// deviation; both of which are type `T`. - pub const fn new(mean: T, std: T) -> crate::Result { + pub const fn new(mean: T, std: T) -> crate::InitResult { Ok(Self { mean, std }) } /// returns a copy of the mean for the distribution diff --git a/init/src/distr/xavier.rs b/init/src/distr/xavier.rs index b4b6bb80..1f51f133 100644 --- a/init/src/distr/xavier.rs +++ b/init/src/distr/xavier.rs @@ -68,7 +68,7 @@ mod impl_normal { } /// tries creating a new [`Normal`] distribution with a mean of 0 and the computed /// standard deviation ($\sigma$) based on the number of inputs and outputs. - pub fn distr(&self) -> crate::Result> { + pub fn distr(&self) -> crate::InitResult> { Normal::new(T::zero(), self.std_dev()).map_err(Into::into) } /// returns a reference to the standard deviation of the distribution @@ -111,7 +111,7 @@ mod impl_uniform { where T: SampleUniform, { - pub fn new(inputs: usize, outputs: usize) -> crate::Result + pub fn new(inputs: usize, outputs: usize) -> crate::InitResult where T: Float + FromPrimitive, { diff --git a/init/src/error.rs b/init/src/error.rs index c588be6a..2e045178 100644 --- a/init/src/error.rs +++ b/init/src/error.rs @@ -8,10 +8,10 @@ use alloc::string::String; use rand_distr::NormalError; use rand_distr::uniform::Error as UniformError; -#[allow(dead_code)] + /// a private type alias for a [`Result`](core::result::Result) type that is used throughout /// the library using an [`InitError`](InitError) as the error type. -pub(crate) type Result = core::result::Result; +pub type InitResult = core::result::Result; #[derive(Debug, thiserror::Error)] pub enum InitError { diff --git a/init/src/lib.rs b/init/src/lib.rs index 730622d0..129f3222 100644 --- a/init/src/lib.rs +++ b/init/src/lib.rs @@ -2,12 +2,15 @@ Appellation: concision-init Contrib: FL03 */ -//! # concision-init +//! One of the most important aspects of training neural networks and machine learning +//! lies within the _initialization_ of model parameters. Here, we work to provide additional +//! tools and utilities to facilitate effective initialization strategies including various +//! random distributions tailored directly to machine learning workloads such as: +//! Glorot (Xavier) initialization, LeCun initialization, etc. //! -//! This library provides various random distribution and initialization routines for the -//! `concision` framework. It includes implementations for different initialization strategies -//! optimized for neural networks, such as Glorot (Xavier) initialization, LeCun -//! initialization, etc. +//! Implementors of the [`Initialize`] trait can leverage the various initialization +//! distributions provided within this crate to initialize their model parameters in a +//! manner conducive to effective training and convergence. //! #![allow( clippy::missing_safety_doc, @@ -91,9 +94,11 @@ pub mod distr { #[doc(hidden)] pub mod prelude { + pub use crate::error::InitError; + pub use crate::traits::*; + #[cfg(feature = "rand")] - pub use super::distr::prelude::*; - pub use super::traits::*; + pub use crate::distr::prelude::*; #[cfg(feature = "rand")] - pub use super::utils::*; + pub use crate::utils::*; } diff --git a/init/src/traits/initialize.rs b/init/src/traits/initialize.rs index 9a7f3080..77055cd3 100644 --- a/init/src/traits/initialize.rs +++ b/init/src/traits/initialize.rs @@ -22,33 +22,41 @@ where (a, b) } +#[deprecated( + since = "0.2.9", + note = "Please use the `InitRand` trait instead which provides more comprehensive functionality." +)] +pub trait Initialize: InitRand +where + D: Dimension, + S: RawData, +{ +} /// This trait provides the base methods required for initializing tensors with random values. /// The trait is similar to the `RandomExt` trait provided by the `ndarray_rand` crate, /// however, it is designed to be more generic, extensible, and optimized for neural network -/// initialization routines. [Initialize] is implemented for [`ArrayBase`] as well as -/// [`ParamsBase`](crate::ParamsBase) allowing you to randomly initialize new tensors and -/// parameters. -pub trait Initialize: Sized +/// initialization routines. +pub trait InitRand::Elem>: Sized where D: Dimension, - S: RawData, + S: RawData, { fn rand(shape: Sh, distr: Ds) -> Self where - Ds: Distribution, + Ds: Distribution, Sh: ShapeBuilder, S: DataOwned; fn rand_with(shape: Sh, distr: Ds, rng: &mut R) -> Self where R: RngCore + ?Sized, - Ds: Distribution, + Ds: Distribution, Sh: ShapeBuilder, S: DataOwned; fn bernoulli(shape: Sh, p: f64) -> Result where - Bernoulli: Distribution, + Bernoulli: Distribution, S: DataOwned, Sh: ShapeBuilder, { @@ -58,9 +66,9 @@ where /// Initialize the object according to the Glorot Initialization scheme. fn glorot_normal>(shape: Sh) -> Self where - StandardNormal: Distribution, + StandardNormal: Distribution, S: DataOwned, - S::Elem: Float + FromPrimitive, + A: Float + FromPrimitive, { let shape = shape.into_shape_with_order(); let (inputs, outputs) = _extract_xy_from_shape(shape.raw_dim(), 0, 1); @@ -68,12 +76,12 @@ where Self::rand(shape, distr) } /// Initialize the object according to the Glorot Initialization scheme. - fn glorot_uniform(shape: Sh) -> crate::Result + fn glorot_uniform(shape: Sh) -> crate::InitResult where S: DataOwned, Sh: ShapeBuilder, - S::Elem: Float + FromPrimitive + SampleUniform, - ::Sampler: Clone, + A: Float + FromPrimitive + SampleUniform, + ::Sampler: Clone, { let shape = shape.into_shape_with_order(); let (inputs, outputs) = _extract_xy_from_shape(shape.raw_dim(), 0, 1); @@ -86,32 +94,32 @@ where /// square root of the reciprocal of the number of inputs. fn lecun_normal(shape: Sh) -> Self where - StandardNormal: Distribution, + StandardNormal: Distribution, S: DataOwned, Sh: ShapeBuilder, - S::Elem: Float, + A: Float, { let shape = shape.into_shape_with_order(); let distr = LecunNormal::new(shape.size()); Self::rand(shape, distr) } /// Given a shape, mean, and standard deviation generate a new object using the [Normal](rand_distr::Normal) distribution - fn normal(shape: Sh, mean: S::Elem, std: S::Elem) -> Result + fn normal(shape: Sh, mean: A, std: A) -> Result where - StandardNormal: Distribution, + StandardNormal: Distribution, S: DataOwned, Sh: ShapeBuilder, - S::Elem: Float, + A: Float, { let distr = Normal::new(mean, std)?; Ok(Self::rand(shape, distr)) } #[cfg(feature = "complex")] - fn randc(shape: Sh, re: S::Elem, im: S::Elem) -> Self + fn randc(shape: Sh, re: A, im: A) -> Self where S: DataOwned, Sh: ShapeBuilder, - num_complex::ComplexDistribution: Distribution, + num_complex::ComplexDistribution: Distribution, { let distr = num_complex::ComplexDistribution::new(re, im); Self::rand(shape, &distr) @@ -119,7 +127,7 @@ where /// Generate a random array using the [StandardNormal](rand_distr::StandardNormal) distribution fn stdnorm(shape: Sh) -> Self where - StandardNormal: Distribution, + StandardNormal: Distribution, S: DataOwned, Sh: ShapeBuilder, { @@ -128,46 +136,41 @@ where /// Generate a random array using the [`StandardNormal`] distribution with a given seed fn stdnorm_from_seed(shape: Sh, seed: u64) -> Self where - StandardNormal: Distribution, + StandardNormal: Distribution, S: DataOwned, Sh: ShapeBuilder, { Self::rand_with(shape, StandardNormal, &mut StdRng::seed_from_u64(seed)) } /// Initialize the object using the [`TruncatedNormal`] distribution - fn truncnorm(shape: Sh, mean: S::Elem, std: S::Elem) -> crate::Result + fn truncnorm(shape: Sh, mean: A, std: A) -> crate::InitResult where - StandardNormal: Distribution, + StandardNormal: Distribution, S: DataOwned, Sh: ShapeBuilder, - S::Elem: Float, + A: Float, { let distr = TruncatedNormal::new(mean, std)?; Ok(Self::rand(shape, distr)) } /// initialize the object using the [`Uniform`] distribution with values bounded by `+/- dk` - fn uniform(shape: Sh, dk: S::Elem) -> crate::Result + fn uniform(shape: Sh, dk: A) -> crate::InitResult where S: DataOwned, Sh: ShapeBuilder, - S::Elem: Clone + Neg + SampleUniform, - ::Sampler: Clone, + A: Clone + Neg + SampleUniform, + ::Sampler: Clone, { Self::uniform_between(shape, dk.clone().neg(), dk) } /// randomly initialize the object using the [`Uniform`] distribution with values between /// the `start` and `stop` params using some random seed. - fn uniform_from_seed( - shape: Sh, - start: S::Elem, - stop: S::Elem, - key: u64, - ) -> crate::Result + fn uniform_from_seed(shape: Sh, start: A, stop: A, key: u64) -> crate::InitResult where S: DataOwned, Sh: ShapeBuilder, - S::Elem: Clone + SampleUniform, - ::Sampler: Clone, + A: Clone + SampleUniform, + ::Sampler: Clone, { let distr = Uniform::new(start, stop)?; Ok(Self::rand_with( @@ -179,27 +182,27 @@ where /// initialize the object using the [`Uniform`] distribution with values bounded by the /// size of the specified axis. /// The values are bounded by `+/- dk` where `dk = 1 / size(axis)`. - fn uniform_along(shape: Sh, axis: usize) -> crate::Result + fn uniform_along(shape: Sh, axis: usize) -> crate::InitResult where Sh: ShapeBuilder, S: DataOwned, - S::Elem: Float + FromPrimitive + SampleUniform, - ::Sampler: Clone, + A: Float + FromPrimitive + SampleUniform, + ::Sampler: Clone, { // extract the shape let shape: Shape = shape.into_shape_with_order(); let dim: D = shape.raw_dim().clone(); - let dk = S::Elem::from_usize(dim[axis]).map(|i| i.recip()).unwrap(); + let dk = A::from_usize(dim[axis]).map(|i| i.recip()).unwrap(); Self::uniform(dim, dk) } /// initialize the object using the [`Uniform`] distribution with values between then given /// bounds, `a` and `b`. - fn uniform_between(shape: Sh, a: S::Elem, b: S::Elem) -> crate::Result + fn uniform_between(shape: Sh, a: A, b: A) -> crate::InitResult where Sh: ShapeBuilder, S: DataOwned, - S::Elem: Clone + SampleUniform, - ::Sampler: Clone, + A: Clone + SampleUniform, + ::Sampler: Clone, { let distr = Uniform::new(a, b)?; Ok(Self::rand(shape, distr)) @@ -209,14 +212,14 @@ where ************ Implementations ************ */ -impl Initialize for ArrayBase +impl InitRand for ArrayBase where D: Dimension, S: RawData, { fn rand(shape: Sh, distr: Ds) -> Self where - Ds: Distribution, + Ds: Distribution, Sh: ShapeBuilder, S: DataOwned, { @@ -226,7 +229,7 @@ where fn rand_with(shape: Sh, distr: Ds, rng: &mut R) -> Self where R: Rng + ?Sized, - Ds: Distribution, + Ds: Distribution, Sh: ShapeBuilder, S: DataOwned, { diff --git a/init/src/utils/rand_utils.rs b/init/src/utils/rand_utils.rs index ef34a974..04b868f7 100644 --- a/init/src/utils/rand_utils.rs +++ b/init/src/utils/rand_utils.rs @@ -2,7 +2,7 @@ Appellation: utils Contrib: FL03 */ -use crate::Initialize; +use crate::InitRand; use ndarray::{Array, ArrayBase, DataOwned, Dimension, IntoDimension, RawData, ShapeBuilder}; use num::Num; use num::complex::{Complex, ComplexDistribution}; @@ -54,7 +54,7 @@ pub fn uniform_from_seed( start: T, stop: T, shape: impl IntoDimension, -) -> crate::Result> +) -> crate::InitResult> where D: Dimension, T: SampleUniform, diff --git a/init/tests/init.rs b/init/tests/init.rs index e9a4fa46..cf2ffc8e 100644 --- a/init/tests/init.rs +++ b/init/tests/init.rs @@ -4,7 +4,7 @@ */ extern crate concision_init as cnc; -use cnc::Initialize; +use cnc::InitRand; use cnc::distr::LecunNormal; use ndarray::prelude::*; diff --git a/math.md b/math.md new file mode 100644 index 00000000..243cfeb2 --- /dev/null +++ b/math.md @@ -0,0 +1,15 @@ + +# Something + +$$ v \geq v_{thresh} \rightarrow{\delta}, v\leftarrow{v_{reset}}, \omega\mathrel{+}=b $$ + +$$ +\tau_m * \frac{dv}{dt} = -(v - v_{rest}) + R*(I_{ext} + I_{syn}) - \omega +$$ + +$$\tau_w * \frac{d\omega}{dt} = -\omega$$ +$$\tau_s * \frac{ds}{dt} = -s$$ + +where: + - $`v`$: membrane potential + - $\omega$: adaptation variable diff --git a/models/kan/Cargo.toml b/models/kan/Cargo.toml deleted file mode 100644 index 9793ea29..00000000 --- a/models/kan/Cargo.toml +++ /dev/null @@ -1,69 +0,0 @@ -[package] -build = "build.rs" -description = "this crate implements the kan model using the concision framework" -name = "concision-kan" - -authors.workspace = true -categories.workspace = true -edition.workspace = true -homepage.workspace = true -keywords.workspace = true -license.workspace = true -readme.workspace = true -repository.workspace = true -rust-version.workspace = true -version.workspace = true - -[package.metadata.docs.rs] -all-features = false -features = ["full"] -rustc-args = ["--cfg", "docsrs"] -version = "v{{version}}" - -[package.metadata.release] -no-dev-version = true -tag-name = "{{version}}" - -[lib] -bench = false -crate-type = ["cdylib", "rlib"] -doc = true -doctest = true -test = true - -[dependencies] -# sdk -concision = { features = ["neural"], workspace = true } -# error -anyhow = { workspace = true } -# mathematics -approx = { optional = true, workspace = true } -ndarray = { workspace = true } -num-traits = { workspace = true } -# logging -tracing = { optional = true, workspace = true } - -[features] -default = ["std"] - -full = ["default", "rand", "serde", "tracing"] - -# ************* [FF:Environments] ************* -std = ["concision/std", "ndarray/std", "num-traits/std", "tracing/std"] - -wasi = ["concision/wasi"] - -wasm = ["concision/wasm"] - -# ************* [FF:Dependencies] ************* -approx = ["concision/approx", "dep:approx", "ndarray/approx"] - -blas = ["concision/blas", "ndarray/blas"] - -rand = ["concision/rand"] - -rayon = ["concision/rayon", "ndarray/rayon"] - -serde = ["concision/serde"] - -tracing = ["concision/tracing", "dep:tracing"] diff --git a/models/kan/build.rs b/models/kan/build.rs deleted file mode 100644 index 940a4ce4..00000000 --- a/models/kan/build.rs +++ /dev/null @@ -1,8 +0,0 @@ -/* - Appellation: build - Contrib: FL03 -*/ - -fn main() { - println!("cargo::rustc-check-cfg=cfg(no_std)"); -} diff --git a/models/models/Cargo.toml b/models/models/Cargo.toml deleted file mode 100644 index 71eb27ea..00000000 --- a/models/models/Cargo.toml +++ /dev/null @@ -1,158 +0,0 @@ -[package] -build = "build.rs" -description = "this crate implements additional models using the concision framework" -name = "concision-models" - -authors.workspace = true -categories.workspace = true -edition.workspace = true -homepage.workspace = true -keywords.workspace = true -license.workspace = true -readme.workspace = true -repository.workspace = true -rust-version.workspace = true -version.workspace = true - -[lib] -crate-type = ["cdylib", "rlib"] -bench = false -doc = true -doctest = true -test = true - -# ************* [Examples] ************* -[[example]] -name = "simple" -required-features = ["approx", "rand", "tracing"] - -# ************* [Unit Tests] ************* -[[test]] -name = "simple" -required-features = ["approx", "rand"] - -[dependencies] -concision-kan = { optional = true, workspace = true } -concision-s4 = { optional = true, workspace = true } -concision-transformer = { optional = true, workspace = true } -# local -concision = { features = ["neural"], workspace = true } -concision-ext = { workspace = true } -# custom -variants = { workspace = true } -# mathematics -approx = { optional = true, workspace = true } -ndarray = { workspace = true } -num-traits = { workspace = true } -# logging -tracing = { optional = true, workspace = true } - -[dev-dependencies] -anyhow = { features = ["std"], workspace = true } -lazy_static ={ workspace = true } -tracing-subscriber = { features = ["std"], workspace = true } - -# ********* [Features] ********* -[features] -default = ["std"] - -full = [ - "default", - "models", - "rand", - "serde", - "tracing" -] - -# ************* [FF:Flags] ************* -models = [ - "simple", - "transformer" -] - -simple = [] - -kan = ["dep:concision-kan"] - -s4 = ["dep:concision-s4"] - -transformer = ["dep:concision-transformer"] - -# ************* [FF:Environments] ************* -std = [ - "concision/std", - "concision-ext/std", - "concision-kan?/std", - "concision-s4?/std", - "concision-transformer?/std", - "ndarray/std", - "num-traits/std", - "tracing?/std", -] - -# ************* [FF:Dependencies] ************* -approx = [ - "dep:approx", - "concision/approx", - "concision-ext/approx", - "concision-kan?/approx", - "concision-s4?/approx", - "concision-transformer?/approx", - "ndarray/approx", -] - -blas = [ - "concision/blas", - "concision-ext/blas", - "concision-kan?/blas", - "concision-s4?/blas", - "concision-transformer?/blas", - "ndarray/blas", -] - -rand = [ - "concision/rand", - "concision-ext/rand", - "concision-kan?/rand", - "concision-s4?/rand", - "concision-transformer?/rand", -] - -rayon = [ - "concision/rayon", - "concision-ext/rayon", - "concision-kan?/rayon", - "concision-s4?/rayon", - "concision-transformer?/rayon", - "ndarray/rayon", -] - -serde = [ - "concision/serde", - "concision-ext/rng", - "concision-kan?/serde", - "concision-s4?/serde", - "concision-transformer?/serde", - "ndarray/serde", -] - -tracing = [ - "dep:tracing", - "concision/tracing", - "concision-ext/tracing", - "concision-kan?/tracing", - "concision-s4?/tracing", - "concision-transformer?/tracing", -] - -# ********* [Metadata] ********* -[package.metadata.docs.rs] -all-features = false -doc-scrape-examples = true -features = ["full"] -rustc-args = ["--cfg", "docsrs"] -version = "v{{version}}" - -[package.metadata.release] -no-dev-version = true -tag-name = "{{version}}" \ No newline at end of file diff --git a/models/models/build.rs b/models/models/build.rs deleted file mode 100644 index 940a4ce4..00000000 --- a/models/models/build.rs +++ /dev/null @@ -1,8 +0,0 @@ -/* - Appellation: build - Contrib: FL03 -*/ - -fn main() { - println!("cargo::rustc-check-cfg=cfg(no_std)"); -} diff --git a/models/models/src/lib.rs b/models/models/src/lib.rs deleted file mode 100644 index fa048da6..00000000 --- a/models/models/src/lib.rs +++ /dev/null @@ -1,36 +0,0 @@ -/* - Appellation: concision-models - Contrib: @FL03 -*/ -//! # concision-ext -//! -//! This library uses the [`concision`](https://docs.rs/concision) framework to implement a -//! variety of additional machine learning models and layers. -//! -#![allow(clippy::module_inception, clippy::needless_doctest_main)] -#![cfg_attr(not(feature = "std"), no_std)] - -// #[cfg(feature = "alloc")] -// extern crate alloc; - -extern crate concision as cnc; - -#[cfg(feature = "simple")] -pub mod simple; -#[cfg(feature = "kan")] -pub use concision_kan as kan; -#[cfg(feature = "s4")] -pub use concision_s4 as s4; -#[cfg(feature = "transformer")] -pub use concision_transformer as transformer; - -pub mod prelude { - #[cfg(feature = "simple")] - pub use crate::simple::SimpleModel; - #[cfg(feature = "kan")] - pub use concision_kan::prelude::*; - #[cfg(feature = "s4")] - pub use concision_s4::prelude::*; - #[cfg(feature = "transformer")] - pub use concision_transformer::prelude::*; -} diff --git a/models/models/src/simple.rs b/models/models/src/simple.rs deleted file mode 100644 index ee9d005b..00000000 --- a/models/models/src/simple.rs +++ /dev/null @@ -1,317 +0,0 @@ -/* - Appellation: simple - Contrib: @FL03 -*/ -use cnc::nn::{DeepModelParams, Model, ModelFeatures, NeuralError, StandardModelConfig, Train}; -use cnc::{Forward, Norm, Params, ReLU, Sigmoid}; - -use ndarray::prelude::*; -use ndarray::{Data, ScalarOperand}; -use num_traits::{Float, FromPrimitive, NumAssign}; - -#[derive(Clone, Debug)] -pub struct SimpleModel { - pub config: StandardModelConfig, - pub features: ModelFeatures, - pub params: DeepModelParams, -} - -impl SimpleModel { - pub fn new(config: StandardModelConfig, features: ModelFeatures) -> Self - where - T: Clone + Default, - { - let params = DeepModelParams::default(features); - SimpleModel { - config, - features, - params, - } - } - /// returns a reference to the model configuration - pub const fn config(&self) -> &StandardModelConfig { - &self.config - } - /// returns a mutable reference to the model configuration - pub const fn config_mut(&mut self) -> &mut StandardModelConfig { - &mut self.config - } - /// returns the model features - pub const fn features(&self) -> ModelFeatures { - self.features - } - /// returns a mutable reference to the model features - pub const fn features_mut(&mut self) -> &mut ModelFeatures { - &mut self.features - } - /// returns a reference to the model parameters - pub const fn params(&self) -> &DeepModelParams { - &self.params - } - /// returns a mutable reference to the model parameters - pub const fn params_mut(&mut self) -> &mut DeepModelParams { - &mut self.params - } - /// set the current configuration and return a mutable reference to the model - pub fn set_config(&mut self, config: StandardModelConfig) -> &mut Self { - self.config = config; - self - } - /// set the current features and return a mutable reference to the model - pub fn set_features(&mut self, features: ModelFeatures) -> &mut Self { - self.features = features; - self - } - /// set the current parameters and return a mutable reference to the model - pub fn set_params(&mut self, params: DeepModelParams) -> &mut Self { - self.params = params; - self - } - /// consumes the current instance to create another with the given configuration - pub fn with_config(self, config: StandardModelConfig) -> Self { - Self { config, ..self } - } - /// consumes the current instance to create another with the given features - pub fn with_features(self, features: ModelFeatures) -> Self { - Self { features, ..self } - } - /// consumes the current instance to create another with the given parameters - pub fn with_params(self, params: DeepModelParams) -> Self { - Self { params, ..self } - } - /// initializes the model with Glorot normal distribution - #[cfg(feature = "rand")] - pub fn init(self) -> Self - where - T: Float + FromPrimitive, - cnc::rand_distr::StandardNormal: cnc::rand_distr::Distribution, - { - let params = DeepModelParams::glorot_normal(self.features()); - SimpleModel { params, ..self } - } -} - -impl Model for SimpleModel { - type Config = StandardModelConfig; - type Layout = ModelFeatures; - - fn config(&self) -> &StandardModelConfig { - &self.config - } - - fn config_mut(&mut self) -> &mut StandardModelConfig { - &mut self.config - } - - fn layout(&self) -> ModelFeatures { - self.features - } - - fn params(&self) -> &DeepModelParams { - &self.params - } - - fn params_mut(&mut self) -> &mut DeepModelParams { - &mut self.params - } -} - -impl Forward> for SimpleModel -where - A: Float + FromPrimitive + ScalarOperand, - D: Dimension, - S: Data, - Params: Forward, Output = Array>, -{ - type Output = Array; - - fn forward(&self, input: &ArrayBase) -> Option { - let mut output = self - .params() - .input() - .forward_then(&input.to_owned(), |y| y.relu())?; - - for layer in self.params().hidden() { - output = layer.forward_then(&output, |y| y.relu())?; - } - - let y = self - .params() - .output() - .forward_then(&output, |y| y.sigmoid())?; - Some(y) - } -} - -impl Train, ArrayBase> for SimpleModel -where - A: Float + FromPrimitive + NumAssign + ScalarOperand + core::fmt::Debug, - S: Data, - T: Data, -{ - type Output = A; - - #[cfg_attr( - feature = "tracing", - tracing::instrument(skip(self, input, target), level = "trace", target = "model",) - )] - fn train( - &mut self, - input: &ArrayBase, - target: &ArrayBase, - ) -> Result { - if input.len() != self.layout().input() { - return Err(NeuralError::InvalidInputShape); - } - if target.len() != self.layout().output() { - return Err(NeuralError::InvalidOutputShape); - } - // get the learning rate from the model's configuration - let lr = self - .config() - .learning_rate() - .copied() - .unwrap_or(A::from_f32(0.01).unwrap()); - // Normalize the input and target - let input = input / input.l2_norm(); - let target_norm = target.l2_norm(); - let target = target / target_norm; - // self.prev_target_norm = Some(target_norm); - // Forward pass to collect activations - let mut activations = Vec::new(); - activations.push(input.to_owned()); - - let mut output = self - .params() - .input() - .forward(&input) - .expect("Failed to complete the forward pass for the input layer") - .relu(); - activations.push(output.to_owned()); - // collect the activations of the hidden - for layer in self.params().hidden() { - output = layer - .forward(&output) - .expect("failed to complete the forward pass for the hidden layer") - .relu(); - activations.push(output.to_owned()); - } - - output = self - .params() - .output() - .forward(&output) - .expect("Output layer failed to forward propagate") - .sigmoid(); - activations.push(output.to_owned()); - - // Calculate output layer error - let error = &target - &output; - let loss = error.pow2().mean().unwrap_or(A::zero()); - #[cfg(feature = "tracing")] - tracing::trace!("Training loss: {loss:?}"); - let mut delta = error * output.sigmoid_derivative(); - delta /= delta.l2_norm(); // Normalize the delta to prevent exploding gradients - - // Update output weights - self.params_mut() - .output_mut() - .backward(activations.last().unwrap(), &delta, lr) - .expect("Output failed training..."); - - let num_hidden = self.layout().layers(); - // Iterate through hidden layers in reverse order - for i in (0..num_hidden).rev() { - // Calculate error for this layer - delta = if i == num_hidden - 1 { - // use the output activations for the final hidden layer - self.params().output().weights().dot(&delta) * activations[i + 1].relu_derivative() - } else { - // else; backpropagate using the previous hidden layer - self.params().hidden()[i + 1].weights().t().dot(&delta) - * activations[i + 1].relu_derivative() - }; - // Normalize delta to prevent exploding gradients - delta /= delta.l2_norm(); - self.params_mut().hidden_mut()[i] - .backward(&activations[i + 1], &delta, lr) - .expect("Hidden failed training..."); - } - /* - Backpropagate to the input layer - The delta for the input layer is computed using the weights of the first hidden layer - and the derivative of the activation function of the first hidden layer. - - (h, h).dot(h) * derivative(h) = dim(h) where h is the number of features within a hidden layer - */ - delta = self.params().hidden()[0].weights().dot(&delta) * activations[1].relu_derivative(); - delta /= delta.l2_norm(); // Normalize the delta to prevent exploding gradients - self.params_mut() - .input_mut() - .backward(&activations[1], &delta, lr) - .expect("failed to backpropagate input layer during training..."); - - Ok(loss) - } -} - -impl Train, ArrayBase> for SimpleModel -where - A: Float + FromPrimitive + NumAssign + ScalarOperand + core::fmt::Debug, - S: Data, - T: Data, -{ - type Output = A; - - #[cfg_attr( - feature = "tracing", - tracing::instrument( - skip(self, input, target), - level = "trace", - name = "train", - target = "model", - fields(input_shape = ?input.shape(), target_shape = ?target.shape()) - ) - )] - fn train( - &mut self, - input: &ArrayBase, - target: &ArrayBase, - ) -> Result { - if input.nrows() == 0 || target.nrows() == 0 { - return Err(NeuralError::InvalidBatchSize); - } - if input.ncols() != self.features().input() { - return Err(NeuralError::InvalidInputShape); - } - if target.ncols() != self.features().output() || target.nrows() != input.nrows() { - return Err(NeuralError::InvalidOutputShape); - } - let mut loss = A::zero(); - - for (i, (x, e)) in input.rows().into_iter().zip(target.rows()).enumerate() { - loss += match Train::, ArrayView1>::train(self, &x, &e) { - Ok(l) => l, - Err(err) => { - #[cfg(feature = "tracing")] - tracing::error!( - "Training failed for batch {}/{}: {:?}", - i + 1, - input.nrows(), - err - ); - #[cfg(not(feature = "tracing"))] - eprintln!( - "Training failed for batch {}/{}: {:?}", - i + 1, - input.nrows(), - err - ); - return Err(err); - } - }; - } - - Ok(loss) - } -} diff --git a/models/models/tests/default.rs b/models/models/tests/default.rs deleted file mode 100644 index 62e1f28a..00000000 --- a/models/models/tests/default.rs +++ /dev/null @@ -1,16 +0,0 @@ -/* - Appellation: default - Contrib: FL03 -*/ - -#[test] -fn lib_compiles() { - fn add(a: A, b: B) -> C - where - A: core::ops::Add, - { - a + b - } - assert_eq!(add(10, 10), 20); - assert_ne!(add(1, 1), 3); -} diff --git a/models/models/tests/simple.rs b/models/models/tests/simple.rs deleted file mode 100644 index 0729e15b..00000000 --- a/models/models/tests/simple.rs +++ /dev/null @@ -1,55 +0,0 @@ -/* - appellation: simple - authors: @FL03 -*/ -extern crate concision as cnc; - -use cnc::nn::{ModelFeatures, Predict, StandardModelConfig}; -use concision_ext::simple::SimpleModel; - -use ndarray::prelude::*; - -#[test] -fn test_standard_model_config() { - // initialize a new model configuration with then given epochs and batch size - let mut config = StandardModelConfig::new() - .with_epochs(1000) - .with_batch_size(32); - // set various hyperparameters - config.set_learning_rate(0.01); - config.set_momentum(0.9); - config.set_decay(0.0001); - // verify the configuration - assert_eq!(config.batch_size(), 32); - assert_eq!(config.epochs(), 1000); - // validate the stored hyperparameters - assert_eq!(config.learning_rate(), Some(&0.01)); - assert_eq!(config.momentum(), Some(&0.9)); - assert_eq!(config.decay(), Some(&0.0001)); -} - -#[test] -fn test_simple_model() -> anyhow::Result<()> { - let mut config = StandardModelConfig::::new() - .with_epochs(1000) - .with_batch_size(32); - config.set_learning_rate(0.01); - config.set_momentum(0.9); - config.set_decay(0.0001); - // define the model features - let features = ModelFeatures::deep(3, 9, 1, 7); - // initialize the model with the given features and configuration - let model = SimpleModel::::new(config, features); - // initialize some input data - let input = Array1::linspace(1.0, 9.0, model.features().input()); - // generate an array filled with expected values - let expected = Array1::from_elem(model.features().output(), 0.5); - // forward the input through the model - let y = model.predict(&input)?; - // verify the output shape - assert_eq!(y.shape(), &[features.output()]); - // compare the results to what we expected - assert_eq!(y, expected); - - Ok(()) -} diff --git a/models/s4/Cargo.toml b/models/s4/Cargo.toml deleted file mode 100644 index 40a42d3c..00000000 --- a/models/s4/Cargo.toml +++ /dev/null @@ -1,68 +0,0 @@ -[package] -build = "build.rs" -description = "this crate implements the s4 model using the concision framework" -name = "concision-s4" - -authors.workspace = true -categories.workspace = true -edition.workspace = true -homepage.workspace = true -keywords.workspace = true -license.workspace = true -readme.workspace = true -repository.workspace = true -rust-version.workspace = true -version.workspace = true - -[package.metadata.docs.rs] -all-features = false -features = ["full"] -rustc-args = ["--cfg", "docsrs"] -version = "v{{version}}" - -[package.metadata.release] -no-dev-version = true -tag-name = "{{version}}" -[lib] -bench = false -crate-type = ["cdylib", "rlib"] -doc = true -doctest = true -test = true - -[dependencies] -concision = { features = ["neural"], workspace = true } -# mathematics -approx = { optional = true, workspace = true } -ndarray = { workspace = true } -num-traits = { workspace = true } -# logging -tracing = { optional = true, workspace = true } - -[dev-dependencies] -anyhow = { features = ["std"], workspace = true } - -[features] -default = ["std"] - -full = ["default", "rand", "serde", "tracing"] - -# ************* [FF:Environments] ************* -std = ["concision/std", "ndarray/std", "num-traits/std", "tracing/std"] - -wasi = ["concision/wasi"] - -wasm = ["concision/wasm"] - -# ************* [FF:Dependencies] ************* -approx = ["concision/approx", "dep:approx", "ndarray/approx"] - -blas = ["concision/blas", "ndarray/blas"] - -rand = ["concision/rand"] - -rayon = ["concision/rayon", "ndarray/rayon"] - -serde = ["concision/serde"] - -tracing = ["concision/tracing", "dep:tracing"] diff --git a/models/s4/build.rs b/models/s4/build.rs deleted file mode 100644 index 940a4ce4..00000000 --- a/models/s4/build.rs +++ /dev/null @@ -1,8 +0,0 @@ -/* - Appellation: build - Contrib: FL03 -*/ - -fn main() { - println!("cargo::rustc-check-cfg=cfg(no_std)"); -} diff --git a/models/snn/Cargo.toml b/models/snn/Cargo.toml deleted file mode 100644 index f5d2d96e..00000000 --- a/models/snn/Cargo.toml +++ /dev/null @@ -1,111 +0,0 @@ -[package] -build = "build.rs" -description = "Synaptic Neural Networks for the Concision Machine Learning Framework" -name = "concision-snn" - -authors.workspace = true -categories.workspace = true -edition.workspace = true -homepage.workspace = true -keywords.workspace = true -license.workspace = true -readme.workspace = true -repository.workspace = true -rust-version.workspace = true -version.workspace = true - -[lib] -bench = false -crate-type = ["cdylib", "rlib"] -doc = true -doctest = true -test = true - -# ********* [Unit Tests] ********* -[[test]] -name = "neurons" -required-features = ["default"] - -[dependencies] -concision = { features = ["neural"], workspace = true } -# mathematics -approx = { optional = true, workspace = true } -ndarray = { workspace = true } -num-traits = { workspace = true } -# serialization -serde = { optional = true, workspace = true } -serde_derive = { optional = true, workspace = true } -serde_json = { optional = true, workspace = true } -# logging -tracing = { optional = true, workspace = true } - -[dev-dependencies] -anyhow = { features = ["std"], workspace = true } - -[features] -default = ["std"] - -full = [ - "default", - "json", - "rand", - "serde", - "tracing" -] - -json = ["alloc", "serde", "serde_json"] - -# ************* [FF:Environments] ************* -std = [ - "alloc", - "concision/std", - "ndarray/std", - "num-traits/std", - "serde?/std", - "serde_json?/std", - "tracing/std" - ] - -wasi = ["concision/wasi"] - -wasm = ["concision/wasm"] - -# ************* [FF:Dependencies] ************* -alloc = [ - "concision/alloc", - "serde?/alloc", - "serde_json?/alloc", -] - -approx = [ - "concision/approx", - "dep:approx", - "ndarray/approx" -] - -blas = ["concision/blas", "ndarray/blas"] - -rand = ["concision/rand"] - -rayon = ["concision/rayon", "ndarray/rayon"] - -serde = [ - "dep:serde", - "dep:serde_derive", - "concision/serde", -] - -serde_json = ["dep:serde_json"] - -tracing = ["concision/tracing", "dep:tracing"] - -# ********* [Metadata] ********* -[package.metadata.docs.rs] -all-features = false -features = ["full"] -rustc-args = ["--cfg", "docsrs"] -version = "v{{version}}" - -[package.metadata.release] -no-dev-version = true -tag-name = "{{version}}" diff --git a/models/snn/build.rs b/models/snn/build.rs deleted file mode 100644 index 940a4ce4..00000000 --- a/models/snn/build.rs +++ /dev/null @@ -1,8 +0,0 @@ -/* - Appellation: build - Contrib: FL03 -*/ - -fn main() { - println!("cargo::rustc-check-cfg=cfg(no_std)"); -} diff --git a/models/snn/src/lib.rs b/models/snn/src/lib.rs deleted file mode 100644 index e1449010..00000000 --- a/models/snn/src/lib.rs +++ /dev/null @@ -1,51 +0,0 @@ -/* - appellation: concision-snn - authors: @FL03 -*/ -//! Spiking neural networks (SNNs) for the [`concision`](https://crates.io/crates/concision) machine learning framework. -//! -//! ## References -//! -//! - [Deep Learning in Spiking Neural Networks](https://arxiv.org/abs/1804.08150) -//! -#![crate_type = "lib"] -#![cfg_attr(not(feature = "std"), no_std)] -#![allow(clippy::module_inception)] - -#[cfg(feature = "alloc")] -extern crate alloc; -extern crate concision as cnc; - -#[cfg(not(any(feature = "std", feature = "alloc")))] -compiler_error! { - "Either feature \"std\" or feature \"alloc\" must be enabled." -} - -#[doc(inline)] -pub use self::{model::*, neuron::*, types::*}; - -pub mod model; -pub mod neuron; - -pub mod types { - //! Types for spiking neural networks - #[doc(inline)] - pub use self::prelude::*; - - mod event; - mod result; - - pub(crate) mod prelude { - pub use super::event::*; - pub use super::result::*; - } -} - -pub mod prelude { - #[doc(inline)] - pub use crate::model::*; - #[doc(inline)] - pub use crate::neuron::*; - #[doc(inline)] - pub use crate::types::*; -} diff --git a/models/transformer/Cargo.toml b/models/transformer/Cargo.toml deleted file mode 100644 index cf7797cc..00000000 --- a/models/transformer/Cargo.toml +++ /dev/null @@ -1,69 +0,0 @@ -[package] -build = "build.rs" -description = "this crate implements the transformer model using the concision framework" -name = "concision-transformer" - -authors.workspace = true -categories.workspace = true -edition.workspace = true -homepage.workspace = true -keywords.workspace = true -license.workspace = true -readme.workspace = true -repository.workspace = true -rust-version.workspace = true -version.workspace = true - -[package.metadata.docs.rs] -all-features = false -features = ["full"] -rustc-args = ["--cfg", "docsrs"] -version = "v{{version}}" - -[package.metadata.release] -no-dev-version = true -tag-name = "{{version}}" - -[lib] -bench = false -crate-type = ["cdylib", "rlib"] -doc = true -doctest = true -test = true - -[dependencies] -concision = { features = ["neural"], workspace = true } -# mathematics -approx = { optional = true, workspace = true } -ndarray = { workspace = true } -num-traits = { workspace = true } -# logging -tracing = { optional = true, workspace = true } - -[dev-dependencies] -anyhow = { features = ["std"], workspace = true } - -[features] -default = ["std"] - -full = ["default", "rand", "serde", "tracing"] - -# ************* [FF:Environments] ************* -std = ["concision/std", "ndarray/std", "num-traits/std", "tracing/std"] - -wasi = ["concision/wasi"] - -wasm = ["concision/wasm"] - -# ************* [FF:Dependencies] ************* -approx = ["concision/approx", "dep:approx", "ndarray/approx"] - -blas = ["concision/blas", "ndarray/blas"] - -rand = ["concision/rand"] - -rayon = ["concision/rayon", "ndarray/rayon"] - -serde = ["concision/serde"] - -tracing = ["concision/tracing", "dep:tracing"] diff --git a/models/transformer/build.rs b/models/transformer/build.rs deleted file mode 100644 index 940a4ce4..00000000 --- a/models/transformer/build.rs +++ /dev/null @@ -1,8 +0,0 @@ -/* - Appellation: build - Contrib: FL03 -*/ - -fn main() { - println!("cargo::rustc-check-cfg=cfg(no_std)"); -} diff --git a/models/transformer/src/lib.rs b/models/transformer/src/lib.rs deleted file mode 100644 index 805c9bcd..00000000 --- a/models/transformer/src/lib.rs +++ /dev/null @@ -1,23 +0,0 @@ -/* - appellation: concision-transformer - authors: @FL03 -*/ -//! # `concision-transformer` -//! -//! `concision-transformer` is a library for building and training transformer models -#![crate_name = "concision_transformer"] -#![crate_type = "lib"] -#![cfg_attr(not(feature = "std"), no_std)] -#![allow(clippy::module_inception)] - -extern crate concision as cnc; - -#[doc(inline)] -pub use self::model::*; - -pub mod model; - -pub mod prelude { - #[doc(inline)] - pub use super::model::*; -} diff --git a/neural/Cargo.toml b/neural/Cargo.toml deleted file mode 100644 index deb577ce..00000000 --- a/neural/Cargo.toml +++ /dev/null @@ -1,214 +0,0 @@ -[package] -build = "build.rs" -description = "This library implements various abstractions for designing neural networks." -name = "concision-neural" - -authors.workspace = true -categories.workspace = true -edition.workspace = true -homepage.workspace = true -keywords.workspace = true -license.workspace = true -readme.workspace = true -repository.workspace = true -rust-version.workspace = true -version.workspace = true - -[lib] -crate-type = ["cdylib", "rlib"] -bench = false -doc = true -doctest = true -test = true - -[dependencies] -concision-core = { workspace = true } -concision-params = { workspace = true } -concision-data = { workspace = true } -# custom -variants = { workspace = true } -# concurrency & parallelism -rayon = { optional = true, workspace = true } -# data-structures -ndarray = { workspace = true } -# math -approx = { optional = true, workspace = true } -num = { workspace = true } -num-traits = { workspace = true } -num-complex = { optional = true, workspace = true } -rustfft = { optional = true, workspace = true } -# random -getrandom = { optional = true, workspace = true } -rand = { optional = true, workspace = true } -rand_distr = { optional = true, workspace = true } -# serialization -serde = { optional = true, workspace = true } -serde_derive = { optional = true, workspace = true } -serde_json = { optional = true, workspace = true } -# macros & utilities -either = { workspace = true } -paste = { workspace = true } -smart-default = { workspace = true } -strum = { workspace = true } -# error handling -thiserror = { workspace = true } -# logging -tracing = { optional = true, workspace = true } - -[dev-dependencies] -anyhow = { features = ["std"], workspace = true } -lazy_static = { workspace = true } - -[features] -default = ["std"] - -full = [ - "default", - "approx", - "complex", - "init", - "rand", - "rustfft", - "serde", - "tracing" -] - -nightly = [ - "concision-core/nightly", - "concision-data/nightly", - "concision-params/nightly", - "variants/nightly", -] - -# ************* [FF:Features] ************* -init = ["concision-core/init"] - -json = [ - "alloc", - "concision-core/json", - "concision-data/json", - "concision-params/json", - "serde_json", -] -# ************* [FF:Environments] ************* -std = [ - "concision-core/std", - "concision-data/std", - "concision-params/std", - "ndarray/std", - "num/std", - "num-traits/std", - "num-complex?/std", - "rand?/std", - "rand?/std_rng", - "serde?/std", - "serde_json?/std", - "strum/std", - "thiserror/std", - "tracing?/std", - "variants/std", -] - -wasi = [ - "concision-core/wasi", - "concision-data/wasi", - "concision-params/wasi", -] - -wasm = [ - "concision-core/wasm", - "concision-data/wasm", - "concision-params/wasm", - "getrandom?/wasm_js", - "rayon?/web_spin_lock", -] - -# ************* [FF:Dependencies] ************* -alloc = [ - "concision-core/alloc", - "concision-data/alloc", - "serde?/alloc", - "serde_json?/alloc", - "num/alloc", - "variants/alloc", -] - -approx = [ - "concision-core/approx", - "concision-data/approx", - "dep:approx", - "ndarray/approx", -] - -blas = [ - "concision-core/blas", - "concision-data/blas", - "ndarray/blas" -] - -complex = [ - "dep:num-complex", - "concision-core/complex", - "concision-data/complex", -] - -rand = [ - "dep:rand", - "dep:rand_distr", - "concision-core/rand", - "concision-data/rand", - "num/rand", - "rng", -] - -rng = [ - "concision-core/rng", - "concision-data/rng", -] - -rayon = [ - "concision-core/rayon", - "concision-data/rayon", - "dep:rayon", - "ndarray/rayon" -] - -serde = [ - "concision-core/serde", - "concision-data/serde", - "dep:serde", - "dep:serde_derive", - "ndarray/serde", - "num/serde", -] - -serde_json = ["dep:serde_json"] - -tracing = [ - "dep:tracing", - "concision-core/tracing", - "concision-data/tracing", -] - -# ********* [Metadata] ********* -[package.metadata.docs.rs] -all-features = false -features = ["full"] -rustc-args = ["--cfg", "docsrs"] -version = "v{{version}}" - -[package.metadata.release] -no-dev-version = true -tag-name = "{{version}}" - -# ************* [Unit Tests] ************* -[[test]] -name = "default" - -[[test]] -name = "layers" -required-features = ["alloc"] - -[[test]] -name = "masks" -required-features = ["alloc", "rand"] diff --git a/neural/build.rs b/neural/build.rs deleted file mode 100644 index 940a4ce4..00000000 --- a/neural/build.rs +++ /dev/null @@ -1,8 +0,0 @@ -/* - Appellation: build - Contrib: FL03 -*/ - -fn main() { - println!("cargo::rustc-check-cfg=cfg(no_std)"); -} diff --git a/neural/src/config/mod.rs b/neural/src/config/mod.rs deleted file mode 100644 index 9e8dc2df..00000000 --- a/neural/src/config/mod.rs +++ /dev/null @@ -1,43 +0,0 @@ -/* - appellation: config - authors: @FL03 -*/ -#[doc(inline)] -pub use self::{model_config::StandardModelConfig, traits::*, types::*}; - -pub mod model_config; - -mod traits { - #[doc(inline)] - pub use self::prelude::*; - - mod config; - - mod prelude { - #[doc(inline)] - pub use super::config::*; - } -} - -mod types { - //! this module defines various types in-support of the configuration model for the neural - //! library of the concision framework. - #[doc(inline)] - pub use self::prelude::*; - - mod hyper_params; - - mod prelude { - #[doc(inline)] - pub use super::hyper_params::*; - } -} - -pub(crate) mod prelude { - #[doc(inline)] - pub use super::model_config::*; - #[doc(inline)] - pub use super::traits::*; - #[doc(inline)] - pub use super::types::*; -} diff --git a/neural/src/error.rs b/neural/src/error.rs deleted file mode 100644 index 0906fd70..00000000 --- a/neural/src/error.rs +++ /dev/null @@ -1,92 +0,0 @@ -/* - Appellation: error - Contrib: @FL03 -*/ -//! this module defines the [`ModelError`] type, used to define the various errors encountered -//! by the different components of a neural network. Additionally, the [`ModelResult`] alias -//! is defined for convenience, allowing for a more ergonomic way to handle results that may -//! fail. - -#[cfg(feature = "alloc")] -use alloc::{boxed::Box, string::String}; - -#[allow(deprecated)] -#[deprecated(since = "0.2.8", note = "use `NeuralResult` instead")] -pub type ModelResult = core::result::Result; -#[deprecated(since = "0.2.8", note = "use `NeuralError` instead")] -pub type ModelError = NeuralError; - -/// a type alias for a [Result](core::result::Result) configured to use the [`ModelError`] -/// implementation as its error type. -pub type NeuralResult = core::result::Result; - -/// The [`ModelError`] type is used to define the various errors encountered by the different -/// components of a neural network. It is designed to be comprehensive, covering a wide range of -/// potential issues that may arise during the operation of neural network components, such as -/// invalid configurations, training failures, and other runtime errors. This error type is -/// intended to provide a clear and consistent way to handle errors across the neural network -/// components, making it easier to debug and resolve issues that may occur during the development -/// and execution of neural network models. -#[derive(Debug, variants::VariantConstructors, thiserror::Error)] -#[non_exhaustive] -pub enum NeuralError { - /// The model is not initialized - #[error("The model is not initialized")] - NotInitialized, - #[error("The model is not trained")] - /// The model is not trained - NotTrained, - #[error("Invalid model configuration")] - /// The model is not valid - InvalidModelConfig, - #[error("Unsupported model")] - /// The model is not supported - UnsupportedModel, - #[error("The model is not supported for the given input")] - /// The model is not compatible with the given input - IncompatibleInput, - #[error("An unsupported operation was attempted")] - UnsupportedOperation, - #[error("Invalid Batch Size")] - InvalidBatchSize, - #[error("Invalid Input Shape")] - InvalidInputShape, - #[error("Invalid Output Shape")] - InvalidOutputShape, - #[error(transparent)] - TrainingError(#[from] crate::train::TrainingError), - #[error(transparent)] - CoreError(#[from] concision_core::error::Error), - #[cfg(feature = "alloc")] - #[error("Parameter Error")] - ParameterError(String), -} - -impl From for concision_core::error::Error { - fn from(err: NeuralError) -> Self { - match err { - NeuralError::CoreError(e) => e, - _ => concision_core::error::Error::box_error(err), - } - } -} - -#[cfg(feature = "alloc")] -impl From> for NeuralError { - fn from(err: Box) -> Self { - cnc::Error::BoxError(err).into() - } -} -#[cfg(feature = "alloc")] -impl From for NeuralError { - fn from(err: String) -> Self { - cnc::Error::unknown(err).into() - } -} - -#[cfg(feature = "alloc")] -impl From<&str> for NeuralError { - fn from(err: &str) -> Self { - cnc::Error::unknown(err).into() - } -} diff --git a/neural/src/layers/traits/store.rs b/neural/src/layers/traits/store.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/neural/src/layout/mod.rs b/neural/src/layout/mod.rs deleted file mode 100644 index d4a47d7b..00000000 --- a/neural/src/layout/mod.rs +++ /dev/null @@ -1,30 +0,0 @@ -/* - appellation: layout - authors: @FL03 -*/ -#[doc(inline)] -pub use self::{features::ModelFeatures, format::ModelFormat, traits::*}; - -mod features; -mod format; - -mod traits { - #[doc(inline)] - pub use self::prelude::*; - - mod layout; - - mod prelude { - #[doc(inline)] - pub use super::layout::*; - } -} - -pub(crate) mod prelude { - #[doc(inline)] - pub use super::features::*; - #[doc(inline)] - pub use super::format::*; - #[doc(inline)] - pub use super::traits::*; -} diff --git a/neural/src/layout/traits/layout.rs b/neural/src/layout/traits/layout.rs deleted file mode 100644 index eb2c5638..00000000 --- a/neural/src/layout/traits/layout.rs +++ /dev/null @@ -1,82 +0,0 @@ -/* - appellation: layout - authors: @FL03 -*/ - -/// The [`ModelLayout`] trait defines an interface for object capable of representing the -/// _layout_; i.e. the number of input, hidden, and output features of a neural network model -/// containing some number of hidden layers. -pub trait ModelLayout: Copy + core::fmt::Debug { - /// returns a copy of the number of input features for the model - fn input(&self) -> usize; - /// returns a mutable reference to number of the input features for the model - fn input_mut(&mut self) -> &mut usize; - /// returns a copy of the number of hidden features for the model - fn hidden(&self) -> usize; - /// returns a mutable reference to the number of hidden features for the model - fn hidden_mut(&mut self) -> &mut usize; - /// returns a copy of the number of hidden layers for the model - fn layers(&self) -> usize; - /// returns a mutable reference to the number of hidden layers for the model - fn layers_mut(&mut self) -> &mut usize; - /// returns a copy of the output features for the model - fn output(&self) -> usize; - /// returns a mutable reference to the output features for the model - fn output_mut(&mut self) -> &mut usize; - #[inline] - /// update the number of input features for the model and return a mutable reference to the - /// current layout. - fn set_input(&mut self, input: usize) -> &mut Self { - *self.input_mut() = input; - self - } - #[inline] - /// update the number of hidden features for the model and return a mutable reference to - /// the current layout. - fn set_hidden(&mut self, hidden: usize) -> &mut Self { - *self.hidden_mut() = hidden; - self - } - #[inline] - /// update the number of hidden layers for the model and return a mutable reference to - /// the current layout. - fn set_layers(&mut self, layers: usize) -> &mut Self { - *self.layers_mut() = layers; - self - } - #[inline] - /// update the number of output features for the model and return a mutable reference to - /// the current layout. - fn set_output(&mut self, output: usize) -> &mut Self { - *self.output_mut() = output; - self - } - /// the dimension of the input layer; (input, hidden) - fn dim_input(&self) -> (usize, usize) { - (self.input(), self.hidden()) - } - /// the dimension of the hidden layers; (hidden, hidden) - fn dim_hidden(&self) -> (usize, usize) { - (self.hidden(), self.hidden()) - } - /// the dimension of the output layer; (hidden, output) - fn dim_output(&self) -> (usize, usize) { - (self.hidden(), self.output()) - } - /// the total number of parameters in the model - fn size(&self) -> usize { - self.size_input() + self.size_hidden() + self.size_output() - } - /// the total number of input parameters in the model - fn size_input(&self) -> usize { - self.input() * self.hidden() - } - /// the total number of hidden parameters in the model - fn size_hidden(&self) -> usize { - self.hidden() * self.hidden() * self.layers() - } - /// the total number of output parameters in the model - fn size_output(&self) -> usize { - self.hidden() * self.output() - } -} diff --git a/neural/src/lib.rs b/neural/src/lib.rs deleted file mode 100644 index b3170da2..00000000 --- a/neural/src/lib.rs +++ /dev/null @@ -1,137 +0,0 @@ -/* - Appellation: concision-neural - Contrib: @FL03 -*/ -//! Various components, implementations, and traits for creating neural networks. The crate -//! builds off of the [`concision_core`] crate, making extensive use of the [`ParamsBase`](cnc::ParamsBase) -//! type to define the parameters of layers within a network. -//! -//! ## Overview -//! -//! Neural networks are a fundamental part of machine learning, and this crate provides a -//! comprehensive set of tools to build, configure, and train neural network models. Listed -//! below are several key components of the crate: -//! -//! - [`Model`]: A trait for defining a neural network model. -//! - [`StandardModelConfig`]: A standard configuration for the models -//! - [`ModelFeatures`]: A default implementation of the [`ModelLayout`] trait that -//! sufficiently defines both shallow and deep neural networks. -//! -//! ### _Model Parameters_ -//! -//! Additionally, the crate defines a sequential -//! -//! **Note**: You should stick with the type aliases for the [`ModelParamsBase`] type, as they -//! drastically simplify the type-face of the model parameters. Attempting to generalize over -//! the hidden layers of the model might lead to excessive complexity. That being said, there -//! are provided methods and routines to convert from a shallow to deep model, and vice versa. -//! -//! - [`DeepModelParams`]: An owned representation of the [`ModelParamsBase`] for deep -//! neural networks. -//! - [`ShallowModelParams`]: An owned representation of the [`ModelParamsBase`] for shallow -//! neural networks. -//! -//! ### Traits -//! -//! This crate extends the [`Forward`](cnc::Forward) and [`Backward`](cnc::Backward) traits -//! from the [`core`](cnc) crate to provide additional functionality for neural networks. -//! -//! - [`Predict`]: A more robust implementation of the [`Forward`] trait -//! - [`Train`]: A trait for training a neural network model. -//! -#![cfg_attr(not(feature = "std"), no_std)] -#![allow( - clippy::missing_saftey_doc, - clippy::module_inception, - clippy::needless_doctest_main, - clippy::upper_case_acronyms -)] -// ensure that either `std` or `alloc` feature is enabled -#[cfg(not(any(feature = "std", feature = "alloc")))] -compile_error! { - "At least one of the 'std' or 'alloc' features must be enabled." -} - -extern crate concision_core as cnc; - -#[cfg(feature = "alloc")] -extern crate alloc; - -#[doc(inline)] -pub use self::{ - config::prelude::*, - error::*, - layers::{Layer, LayerBase}, - layout::prelude::*, - params::prelude::*, - train::prelude::*, - traits::*, - types::*, -}; - -#[macro_use] -pub(crate) mod macros { - #[macro_use] - pub mod seal; -} - -pub mod config; -pub mod error; -pub mod layers; -pub mod layout; -pub mod params; -pub mod train; - -pub(crate) mod traits { - #[doc(inline)] - pub use self::prelude::*; - - mod hidden; - mod models; - mod network; - mod predict; - - mod prelude { - #[doc(inline)] - pub use super::hidden::*; - #[doc(inline)] - pub use super::models::*; - #[doc(inline)] - pub use super::network::*; - #[doc(inline)] - pub use super::predict::*; - } -} - -pub(crate) mod types { - #[doc(inline)] - pub use self::prelude::*; - - mod dropout; - mod key_value; - - mod prelude { - #[doc(inline)] - pub use super::dropout::*; - #[doc(inline)] - pub use super::key_value::*; - } -} - -#[doc(hidden)] -pub mod prelude { - #[doc(no_inline)] - pub use super::config::prelude::*; - #[doc(hidden)] - pub use crate::layers::prelude::*; - #[doc(no_inline)] - pub use crate::layout::prelude::*; - #[doc(no_inline)] - pub use crate::params::prelude::*; - #[doc(no_inline)] - pub use crate::train::prelude::*; - #[doc(no_inline)] - pub use crate::traits::*; - #[doc(no_inline)] - pub use crate::types::*; -} diff --git a/neural/src/macros/seal.rs b/neural/src/macros/seal.rs deleted file mode 100644 index 05f2bbf6..00000000 --- a/neural/src/macros/seal.rs +++ /dev/null @@ -1,50 +0,0 @@ -/* - Appellation: seal - Contrib: FL03 -*/ -//! The public parts of this private module are used to create traits -//! that cannot be implemented outside of our own crate. This way we -//! can feel free to extend those traits without worrying about it -//! being a breaking change for other implementations. -//! -//! ## Usage -//! -//! To define a private trait, you can use the [`private!`] macro, which will define a hidden -//! method `__private__` that can only be implemented within the crate. - -/// If this type is pub but not publicly reachable, third parties -/// can't name it and can't implement traits using it. -#[allow(dead_code)] -pub struct Seal; -/// the [`private!`] macro is used to seal a particular trait, defining a hidden method that -/// may only be implemented within the bounds of the crate. -#[allow(unused_macros)] -macro_rules! private { - () => { - /// This trait is private to implement; this method exists to make it - /// impossible to implement outside the crate. - #[doc(hidden)] - fn __private__(&self) -> $crate::macros::seal::Seal; - }; -} -/// the [`seal!`] macro is used to implement a private method on a type, which is used to seal -/// the type so that it cannot be implemented outside of the crate. -#[allow(unused_macros)] -macro_rules! seal { - () => { - fn __private__(&self) -> $crate::macros::seal::Seal { - $crate::macros::seal::Seal - } - }; -} -/// this macros is used to implement a trait for a type, sealing it so that -/// it cannot be implemented outside of the crate. This is most usefuly for creating other -/// macros that can be used to implement some raw, sealed trait on the given _types_. -#[allow(unused_macros)] -macro_rules! sealed { - (impl$(<$($T:ident),*>)? $trait:ident for $name:ident$(<$($V:ident),*>)? $(where $($rest:tt)*)?) => { - impl$(<$($T),*>)? $trait for $name$(<$($V),*>)? $(where $($rest)*)? { - seal!(); - } - }; -} diff --git a/neural/src/train/mod.rs b/neural/src/train/mod.rs deleted file mode 100644 index d0e21206..00000000 --- a/neural/src/train/mod.rs +++ /dev/null @@ -1,38 +0,0 @@ -/* - appellation: train - authors: @FL03 -*/ -//! This module focuses on developing lazy trainers for neural networks. -#[doc(inline)] -pub use self::{error::*, trainer::Trainer, traits::*}; - -pub mod error; -/// this module provides the [`Trainer`] implementation, which is a generic, lazy trainer -/// for neural networks. It is designed to be flexible and extensible, allowing for the -/// implementation of various training algorithms and strategies. The trainer is built on top -/// of the [`Train`] trait, which defines the core training functionality. The trainer can be -/// used to train neural networks with different configurations and parameters, making it a -/// versatile tool for neural network training. -pub mod trainer; - -mod traits { - #[doc(inline)] - pub use self::prelude::*; - - mod train; - mod trainers; - - mod prelude { - #[doc(inline)] - pub use super::train::*; - #[doc(inline)] - pub use super::trainers::*; - } -} - -pub(crate) mod prelude { - #[doc(inline)] - pub use super::trainer::*; - #[doc(inline)] - pub use super::traits::*; -} diff --git a/neural/src/train/traits/train.rs b/neural/src/train/traits/train.rs deleted file mode 100644 index c7a5e58e..00000000 --- a/neural/src/train/traits/train.rs +++ /dev/null @@ -1,30 +0,0 @@ -/* - Appellation: train - Contrib: @FL03 -*/ -use crate::train::error::TrainingError; - -use crate::error::NeuralResult; - -/// This trait defines the training process for the network -pub trait Train { - type Output; - - fn train(&mut self, input: &X, target: &Y) -> NeuralResult; - - fn train_for(&mut self, input: &X, target: &Y, epochs: usize) -> NeuralResult { - let mut output = None; - - for _ in 0..epochs { - output = match self.train(input, target) { - Ok(o) => Some(o), - Err(e) => { - #[cfg(feature = "tracing")] - tracing::error!("Training failed: {e}"); - return Err(e); - } - } - } - output.ok_or_else(|| TrainingError::TrainingFailed.into()) - } -} diff --git a/neural/src/train/traits/trainers.rs b/neural/src/train/traits/trainers.rs deleted file mode 100644 index 3362938d..00000000 --- a/neural/src/train/traits/trainers.rs +++ /dev/null @@ -1,27 +0,0 @@ -/* - appellation: trainers - authors: @FL03 -*/ -use crate::train::Trainer; - -use crate::Model; -use concision_data::DatasetBase; - -pub trait ModelTrainer { - type Model: Model; - /// returns a model trainer prepared to train the model; this is a convenience method - /// that creates a new trainer instance and returns it. Trainers are lazily evaluated - /// meaning that the training process won't begin until the user calls the `begin` method. - fn trainer<'a, U, V>( - &mut self, - dataset: DatasetBase, - model: &'a mut Self::Model, - ) -> Trainer<'a, Self::Model, T, DatasetBase> - where - Self: Sized, - T: Default, - for<'b> &'b mut Self::Model: Model, - { - Trainer::new(model, dataset) - } -} diff --git a/neural/src/traits/network.rs b/neural/src/traits/network.rs deleted file mode 100644 index 05aacb12..00000000 --- a/neural/src/traits/network.rs +++ /dev/null @@ -1,35 +0,0 @@ -/* - appellation: network - authors: @FL03 -*/ -use super::{DeepModelRepr, RawHidden, ShallowModelRepr}; -use crate::config::NetworkConfig; -use ndarray::{Dimension, RawData}; - -pub trait NeuralNetwork -where - D: Dimension, - S: RawData, -{ - type Config: NetworkConfig; - type Hidden: RawHidden; - - fn config(&self) -> &Self::Config; - fn config_mut(&mut self) -> &mut Self::Config; -} - -pub trait ShallowNeuralNetwork: NeuralNetwork -where - D: Dimension, - S: RawData, - Self::Hidden: ShallowModelRepr, -{ -} - -pub trait DeepNeuralNetwork: NeuralNetwork -where - D: Dimension, - S: RawData, - Self::Hidden: DeepModelRepr, -{ -} diff --git a/neural/src/types/dropout.rs b/neural/src/types/dropout.rs deleted file mode 100644 index 5fa915d2..00000000 --- a/neural/src/types/dropout.rs +++ /dev/null @@ -1,45 +0,0 @@ -/* - Appellation: dropout - Contrib: FL03 -*/ - -/// The [Dropout] layer is randomly zeroizes inputs with a given probability (`p`). -/// This regularization technique is often used to prevent overfitting. -/// -/// -/// ### Config -/// -/// - (p) Probability of dropping an element -#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct Dropout { - pub(crate) p: f64, -} - -impl Dropout { - pub fn new(p: f64) -> Self { - Self { p } - } - - pub fn scale(&self) -> f64 { - (1f64 - self.p).recip() - } -} - -impl Default for Dropout { - fn default() -> Self { - Self::new(0.5) - } -} - -#[cfg(feature = "rand")] -impl cnc::Forward for Dropout -where - U: cnc::DropOut, -{ - type Output = ::Output; - - fn forward(&self, input: &U) -> Option { - Some(input.dropout(self.p)) - } -} diff --git a/neural/tests/default.rs b/neural/tests/default.rs deleted file mode 100644 index 62e1f28a..00000000 --- a/neural/tests/default.rs +++ /dev/null @@ -1,16 +0,0 @@ -/* - Appellation: default - Contrib: FL03 -*/ - -#[test] -fn lib_compiles() { - fn add(a: A, b: B) -> C - where - A: core::ops::Add, - { - a + b - } - assert_eq!(add(10, 10), 20); - assert_ne!(add(1, 1), 3); -} diff --git a/neural/tests/layers.rs b/neural/tests/layers.rs deleted file mode 100644 index 42172f87..00000000 --- a/neural/tests/layers.rs +++ /dev/null @@ -1,9 +0,0 @@ -/* - appellation: layers - authors: @FL03 -*/ -use concision_neural::NeuralResult; -#[test] -fn test_layer_base() -> NeuralResult<()> { - Ok(()) -} diff --git a/neural/tests/masks.rs b/neural/tests/masks.rs deleted file mode 100644 index 0780bed8..00000000 --- a/neural/tests/masks.rs +++ /dev/null @@ -1,20 +0,0 @@ -extern crate concision_core as cnc; -extern crate concision_neural as neural; - -use cnc::Forward; -use concision_neural::error::NeuralError; -use ndarray::prelude::*; -use neural::Dropout; - -#[test] -fn test_dropout() -> Result<(), NeuralError> { - let shape = (512, 2048); - let arr = Array2::::ones(shape); - let dropout = Dropout::new(0.5); - let out = dropout.forward(&arr).expect("Dropout forward pass failed"); - - assert!(arr.iter().all(|&x| x == 1.0)); - assert!(out.iter().any(|x| x == &0f64)); - - Ok(()) -} diff --git a/neural/tests/training.rs b/neural/tests/training.rs deleted file mode 100644 index ea435d2c..00000000 --- a/neural/tests/training.rs +++ /dev/null @@ -1,4 +0,0 @@ -/* - Appellation: training - Contrib: @FL03 -*/ diff --git a/params/Cargo.toml b/params/Cargo.toml index 5538e3f9..6c50c265 100644 --- a/params/Cargo.toml +++ b/params/Cargo.toml @@ -14,6 +14,16 @@ repository.workspace = true rust-version.workspace = true version.workspace = true +[package.metadata.docs.rs] +all-features = false +features = ["full"] +rustc-args = ["--cfg", "docsrs"] +version = "v{{version}}" + +[package.metadata.release] +no-dev-version = true +tag-name = "{{version}}" + [lib] crate-type = ["cdylib", "rlib"] bench = false @@ -21,16 +31,12 @@ doc = true doctest = true test = true -# ************* [Unit Tests] ************* -[[test]] -name = "default" - [[test]] -name = "params" +name = "create" required-features = ["std"] [dependencies] -concision-init = { optional = true, workspace = true } +concision-init = { workspace = true } concision-traits = { workspace = true } # custom variants = { workspace = true } @@ -62,31 +68,20 @@ full = [ "default", "approx", "complex", - "init", "json", "rand", "serde", ] nightly = [ - "concision-init?/nightly", + "concision-init/nightly", "concision-traits/nightly", ] -# ************* [FF:Features] ************* -init = [ - "concision_init", - "rand", -] - -json = ["alloc", "serde", "serde_json"] - -concision_init = ["dep:concision-init"] - # ************* [FF:Dependencies] ************* std = [ "alloc", - "concision-init?/std", + "concision-init/std", "concision-traits/std", "ndarray/std", "num-complex?/std", @@ -99,18 +94,18 @@ std = [ ] wasi = [ - "concision-init?/wasi", + "concision-init/wasi", "concision-traits/wasi", ] wasm = [ "getrandom?/wasm_js", - "concision-init?/wasm", + "concision-init/wasm", "concision-traits/wasm", ] # ************* [FF:Dependencies] ************* alloc = [ - "concision-init?/alloc", + "concision-init/alloc", "concision-traits/alloc", "serde?/alloc", "serde_json?/alloc", @@ -119,24 +114,26 @@ alloc = [ approx = [ "dep:approx", - "concision-init?/approx", + "concision-init/approx", "ndarray/approx", ] blas = [ - "concision-init?/blas", + "concision-init/blas", "ndarray/blas", ] complex = [ "dep:num-complex", - "concision-init?/complex", + "concision-init/complex", ] +json = ["alloc", "serde", "serde_json"] + rand = [ "dep:rand", "dep:rand_distr", - "concision-init?/rand", + "concision-init/rand", "concision-traits/rand", "num-complex?/rand", "rng", @@ -149,14 +146,14 @@ rayon = [ rng = [ "dep:getrandom", - "concision-init?/rng", + "concision-init/rng", "concision-traits/rng", "rand?/small_rng", "rand?/thread_rng", ] serde = [ - "concision-init?/serde", + "concision-init/serde", "dep:serde", "dep:serde_derive", "ndarray/serde", @@ -166,15 +163,3 @@ serde = [ ] serde_json = ["dep:serde_json"] - - -# ********* [Metadata] ********* -[package.metadata.docs.rs] -all-features = false -features = ["full"] -rustc-args = ["--cfg", "docsrs"] -version = "v{{version}}" - -[package.metadata.release] -no-dev-version = true -tag-name = "{{version}}" diff --git a/params/src/impls/impl_params.rs b/params/src/impls/impl_params.rs index 391028af..fdadbdfd 100644 --- a/params/src/impls/impl_params.rs +++ b/params/src/impls/impl_params.rs @@ -3,7 +3,7 @@ authors: @FL03 */ use crate::params::ParamsBase; - +use crate::traits::{Biased, Weighted}; use core::iter::Once; use ndarray::{ArrayBase, Data, DataOwned, Dimension, Ix1, Ix2, RawData}; @@ -43,6 +43,34 @@ where } } +impl Weighted for ParamsBase +where + S: RawData, + D: Dimension, +{ + fn weights(&self) -> &ArrayBase { + self.weights() + } + + fn weights_mut(&mut self) -> &mut ArrayBase { + self.weights_mut() + } +} + +impl Biased for ParamsBase +where + S: RawData, + D: Dimension, +{ + fn bias(&self) -> &ArrayBase { + self.bias() + } + + fn bias_mut(&mut self) -> &mut ArrayBase { + self.bias_mut() + } +} + impl core::fmt::Debug for ParamsBase where D: Dimension, diff --git a/params/src/impls/impl_params_init.rs b/params/src/impls/impl_params_init.rs index 063cda3c..319126a0 100644 --- a/params/src/impls/impl_params_init.rs +++ b/params/src/impls/impl_params_init.rs @@ -4,61 +4,11 @@ */ use crate::params::ParamsBase; -use concision_init::Initialize; -use ndarray::{ - ArrayBase, Axis, DataOwned, Dimension, RawData, RemoveAxis, ScalarOperand, ShapeBuilder, -}; -use num_traits::{Float, FromPrimitive}; -use rand::rngs::SmallRng; -use rand_distr::Distribution; +use ndarray::{Dimension, RawData}; impl ParamsBase where - A: Float + FromPrimitive + ScalarOperand, D: Dimension, S: RawData, { - /// generates a randomly initialized set of parameters with the given shape using the - /// output of the given distribution function `G` - pub fn init_rand(shape: Sh, distr: G) -> Self - where - D: RemoveAxis, - S: DataOwned, - Sh: ShapeBuilder, - Dst: Clone + Distribution, - G: Fn(&Sh) -> Dst, - { - let dist = distr(&shape); - Self::rand(shape, dist) - } -} - -impl Initialize for ParamsBase -where - D: RemoveAxis, - S: RawData, -{ - fn rand(shape: Sh, distr: Ds) -> Self - where - Ds: Distribution, - Sh: ShapeBuilder, - S: DataOwned, - { - use rand::SeedableRng; - Self::rand_with(shape, distr, &mut SmallRng::from_rng(&mut rand::rng())) - } - - fn rand_with(shape: Sh, distr: Ds, rng: &mut R) -> Self - where - R: rand::RngCore + ?Sized, - Ds: Distribution, - Sh: ShapeBuilder, - S: DataOwned, - { - let shape = shape.into_shape_with_order(); - let bias_shape = shape.raw_dim().remove_axis(Axis(0)); - let bias = ArrayBase::from_shape_fn(bias_shape, |_| distr.sample(rng)); - let weights = ArrayBase::from_shape_fn(shape, |_| distr.sample(rng)); - Self { bias, weights } - } } diff --git a/params/src/impls/impl_params_ops.rs b/params/src/impls/impl_params_ops.rs index bd06e02c..390e4cf0 100644 --- a/params/src/impls/impl_params_ops.rs +++ b/params/src/impls/impl_params_ops.rs @@ -3,12 +3,9 @@ Contrib: @FL03 */ use crate::{Params, ParamsBase}; -use concision_traits::{ - ApplyGradient, ApplyGradientExt, Backward, Biased, Forward, Norm, Weighted, -}; +use concision_traits::{ApplyGradient, ApplyGradientExt, Backward, Forward, Norm}; use ndarray::linalg::Dot; -use ndarray::{ArrayBase, Data, DataMut, Dimension, ScalarOperand}; -use ndarray::{RawData, prelude::*}; +use ndarray::{ArrayBase, Axis, Data, DataMut, Dimension, Ix0, Ix1, Ix2, ScalarOperand}; use num_traits::{Float, FromPrimitive}; impl ParamsBase @@ -104,34 +101,6 @@ where } } -impl Weighted for ParamsBase -where - S: RawData, - D: Dimension, -{ - fn weights(&self) -> &ArrayBase { - self.weights() - } - - fn weights_mut(&mut self) -> &mut ArrayBase { - self.weights_mut() - } -} - -impl Biased for ParamsBase -where - S: RawData, - D: Dimension, -{ - fn bias(&self) -> &ArrayBase { - self.bias() - } - - fn bias_mut(&mut self) -> &mut ArrayBase { - self.bias_mut() - } -} - impl ApplyGradient, A> for ParamsBase where A: Float + FromPrimitive + ScalarOperand, diff --git a/params/src/impls/impl_params_rand.rs b/params/src/impls/impl_params_rand.rs index d61fd71f..248d1b1c 100644 --- a/params/src/impls/impl_params_rand.rs +++ b/params/src/impls/impl_params_rand.rs @@ -1,11 +1,16 @@ /* - appellation: impl_params_init - authors: @FL03 + Appellation: impl_params_rand + Created At: 2025.11.26:15:28:12 + Contrib: @FL03 */ use crate::params::ParamsBase; -use ndarray::{DataOwned, Dimension, RawData, RemoveAxis, ScalarOperand, ShapeBuilder}; +use concision_init::InitRand; +use ndarray::{ + ArrayBase, Axis, DataOwned, Dimension, RawData, RemoveAxis, ScalarOperand, ShapeBuilder, +}; use num_traits::{Float, FromPrimitive}; +use rand::rngs::SmallRng; use rand_distr::Distribution; impl ParamsBase @@ -26,3 +31,54 @@ where Self::init_from_fn(shape, || distr.sample(&mut rand::rng())) } } + +impl ParamsBase +where + A: Float + FromPrimitive + ScalarOperand, + D: Dimension, + S: RawData, +{ + /// generates a randomly initialized set of parameters with the given shape using the + /// output of the given distribution function `G` + pub fn init_rand(shape: Sh, distr: G) -> Self + where + D: RemoveAxis, + S: DataOwned, + Sh: ShapeBuilder, + Dst: Clone + Distribution, + G: Fn(&Sh) -> Dst, + { + let dist = distr(&shape); + Self::rand(shape, dist) + } +} + +impl InitRand for ParamsBase +where + D: RemoveAxis, + S: RawData, +{ + fn rand(shape: Sh, distr: Ds) -> Self + where + Ds: Distribution, + Sh: ShapeBuilder, + S: DataOwned, + { + use rand::SeedableRng; + Self::rand_with(shape, distr, &mut SmallRng::from_rng(&mut rand::rng())) + } + + fn rand_with(shape: Sh, distr: Ds, rng: &mut R) -> Self + where + R: rand::RngCore + ?Sized, + Ds: Distribution, + Sh: ShapeBuilder, + S: DataOwned, + { + let shape = shape.into_shape_with_order(); + let bias_shape = shape.raw_dim().remove_axis(Axis(0)); + let bias = ArrayBase::from_shape_fn(bias_shape, |_| distr.sample(rng)); + let weights = ArrayBase::from_shape_fn(shape, |_| distr.sample(rng)); + Self { bias, weights } + } +} diff --git a/params/src/iter.rs b/params/src/iter.rs index 1cd4f8fc..1bee6aa1 100644 --- a/params/src/iter.rs +++ b/params/src/iter.rs @@ -2,6 +2,8 @@ Appellation: iter Contrib: @FL03 */ +//! Iterators for parameters within a neural network + use ndarray::Dimension; use ndarray::iter::{AxisIter, AxisIterMut}; use ndarray::iter::{Iter as NdIter, IterMut as NdIterMut}; diff --git a/params/src/lib.rs b/params/src/lib.rs index c1636594..17ddb852 100644 --- a/params/src/lib.rs +++ b/params/src/lib.rs @@ -1,15 +1,25 @@ /* - Appellation: params + Appellation: concision-params + Created At: 2025.11.26:13:15:32 Contrib: @FL03 */ -//! Parameters for constructing neural network models. This module implements parameters using -//! the [ParamsBase] struct and its associated types. The [ParamsBase] struct provides: +//! In machine learning, each layer is composed of some set of neurons that process input data +//! to produce some meaningful output. Each neuron typically has associated parameters, namely +//! weights and biases, which are adjusted during training to optimize the model's performance. +//! +//! ## Overview +//! +//! The [`params`](self) crate provides a generic and flexible structure for handling these +//! values. At its core, the [`ParamsBase`] object is defined as an object composed of two +//! independent tensors: //! //! - An $`n`$ dimensional weight tensor //! - An $`n-1`$ dimensional bias tensor //! -//! The associated types follow suite with the [`ndarray`] crate, each of which defines a -//! different style of representation for the parameters. +//! These tensors can be of any shape or size, allowing for a wide range of neural network +//! architectures to be represented. The crate also provides various utilities and traits for +//! manipulating and interacting with these parameters, making it easier to build and train +//! neural networks. //! #![cfg_attr(not(feature = "std"), no_std)] #![allow( @@ -21,21 +31,16 @@ #[cfg(feature = "alloc")] extern crate alloc; +extern crate concision_init as cnc_init; +extern crate concision_traits as cnc_traits; +extern crate ndarray as nda; #[cfg(all(not(feature = "alloc"), not(feature = "std")))] compiler_error! { "Either the \"alloc\" or \"std\" feature must be enabled for this crate." } -#[doc(inline)] -pub use self::{error::*, params::ParamsBase, types::*}; - -#[cfg(feature = "init")] -extern crate concision_init as init; - -/// Error handling for parameters pub mod error; -/// The [`iter`] module implements various iterators for parameters pub mod iter; mod params; @@ -44,7 +49,7 @@ mod impls { mod impl_params; #[allow(deprecated)] mod impl_params_deprecated; - #[cfg(feature = "init")] + #[cfg(feature = "rand")] mod impl_params_init; mod impl_params_iter; mod impl_params_ops; @@ -54,22 +59,29 @@ mod impls { mod impl_params_serde; } +pub mod traits { + //! Traits for working with model parameters + pub use self::wnb::*; + + mod wnb; +} + mod types { - //! Additional types supporting the params module + //! Supporting types and aliases for working with model parameters #[doc(inline)] - pub use self::prelude::*; + pub use self::aliases::*; mod aliases; - - mod prelude { - #[doc(inline)] - pub use super::aliases::*; - } } +// re-exports +#[doc(inline)] +pub use self::{error::*, params::ParamsBase, traits::*, types::*}; +// prelude #[doc(hidden)] pub mod prelude { pub use crate::error::ParamsError; pub use crate::params::*; + pub use crate::traits::*; pub use crate::types::*; } diff --git a/params/src/params.rs b/params/src/params.rs index d62977f2..3b2e943d 100644 --- a/params/src/params.rs +++ b/params/src/params.rs @@ -10,7 +10,7 @@ use ndarray::{ /// The [`ParamsBase`] struct is a generic container for a set of weights and biases for a /// model where the bias tensor is always `n-1` dimensions smaller than the `weights` tensor. /// Consequently, this constrains the [`ParamsBase`] implementation to only support dimensions -/// that can be reduced by one axis (i.e. $`\mbox{rank}(D)>0`$), which is typically the "zero-th" axis. +/// that can be reduced by one axis (i.e. $\mbox{rank}(D)>0$), which is typically the "zero-th" axis. pub struct ParamsBase::Elem> where D: Dimension, @@ -203,7 +203,7 @@ where self.bias().is_empty() } /// the total number of elements within the weight tensor - pub fn count_weight(&self) -> usize { + pub fn count_weights(&self) -> usize { self.weights().len() } /// the total number of elements within the bias tensor diff --git a/traits/src/wnb.rs b/params/src/traits/wnb.rs similarity index 100% rename from traits/src/wnb.rs rename to params/src/traits/wnb.rs diff --git a/params/tests/params.rs b/params/tests/create.rs similarity index 94% rename from params/tests/params.rs rename to params/tests/create.rs index 5dba0707..6e8abaef 100644 --- a/params/tests/params.rs +++ b/params/tests/create.rs @@ -33,9 +33,9 @@ fn test_params_zeros() { } #[test] -#[cfg(feature = "init")] -fn test_params_init() { - use concision_init::Initialize; +#[cfg(feature = "rand")] +fn test_params_init_rand() { + use concision_init::InitRand; let lecun = Params::::lecun_normal((3, 4)); assert_eq!(lecun.dim(), (3, 4)); diff --git a/traits/Cargo.toml b/traits/Cargo.toml index 9a1687a7..86370343 100644 --- a/traits/Cargo.toml +++ b/traits/Cargo.toml @@ -31,26 +31,29 @@ doc = true doctest = true test = true +[[test]] +name = "complex" +required-features = ["complex"] + [dependencies] # custom variants = { workspace = true } +# data structures +ndarray = { workspace = true } # error-handling thiserror = { workspace = true } # macros & utilities paste = { workspace = true } # mathematics approx = { optional = true, workspace = true } -ndarray = { workspace = true } num-complex = { optional = true, workspace = true } +num-integer = { workspace = true } num-traits = { workspace = true } # random getrandom = { default-features = false, optional = true, workspace = true } rand = { optional = true, workspace = true } rand_distr = { optional = true, workspace = true } -[dev-dependencies] -lazy_static = { workspace = true } - [features] default = ["std"] @@ -63,11 +66,12 @@ full = [ nightly = [] - # ************* [FF:Dependencies] ************* std = [ - "alloc", "ndarray/std", + "alloc", + "ndarray/std", "num-complex?/std", + "num-integer/std", "num-traits/std", "rand?/std", "rand?/std_rng", @@ -86,6 +90,15 @@ blas = ["ndarray/blas"] complex = ["dep:num-complex"] -rand = ["dep:rand", "dep:rand_distr", "num-complex?/rand", "rng"] +rand = [ + "dep:rand", + "dep:rand_distr", + "num-complex?/rand", + "rng" +] -rng = ["dep:getrandom", "rand?/small_rng", "rand?/thread_rng"] +rng = [ + "dep:getrandom", + "rand?/small_rng", + "rand?/thread_rng" +] diff --git a/traits/src/clip.rs b/traits/src/clip.rs index 8a17a55f..95f8b0da 100644 --- a/traits/src/clip.rs +++ b/traits/src/clip.rs @@ -39,13 +39,13 @@ pub trait ClipMut { ************* Implementations ************* */ use crate::norm::{L1Norm, L2Norm}; -use ndarray::{ArrayBase, Dimension, ScalarOperand}; +use ndarray::{ArrayBase, Data, DataMut, Dimension}; use num_traits::Float; impl Clip for ArrayBase where A: 'static + Clone + PartialOrd, - S: ndarray::Data, + S: Data, D: Dimension, { type Output = ndarray::Array; @@ -57,8 +57,8 @@ where impl ClipMut for ArrayBase where - A: Float + ScalarOperand, - S: ndarray::DataMut, + A: 'static + Clone + Float, + S: DataMut, D: Dimension, { fn clip_between(&mut self, min: A, max: A) { diff --git a/utils/src/traits/complex.rs b/traits/src/complex.rs similarity index 96% rename from utils/src/traits/complex.rs rename to traits/src/complex.rs index e21bfd68..04917d68 100644 --- a/utils/src/traits/complex.rs +++ b/traits/src/complex.rs @@ -3,8 +3,9 @@ Contrib: FL03 */ #![cfg(feature = "complex")] -use num::Num; -use num::complex::Complex; + +use num_complex::Complex; +use num_traits::Num; pub trait AsComplex { type Complex; diff --git a/traits/src/container.rs b/traits/src/container.rs new file mode 100644 index 00000000..18f2099c --- /dev/null +++ b/traits/src/container.rs @@ -0,0 +1,61 @@ +/* + Appellation: container + Created At: 2025.11.28:15:30:46 + Contrib: @FL03 +*/ +/// The [`Container`] trait defines a generic interface for container types. +pub trait Container { + type Cont: ?Sized; + type Item; +} + +macro_rules! container { + ($( + $($container:ident)::*<$A:ident $(, $B:ident)?> + ),* $(,)?) => { + $(container!(@impl $($container)::*<$A $(, $B)?>);)* + }; + + (@impl $($container:ident)::*<$T:ident>) => { + impl<$T> $crate::container::Container for $($container)::*<$T> { + type Cont = $($container)::*; + type Item = $T; + } + }; + (@impl $($container:ident)::*<$K:ident, $V:ident>) => { + impl<$K, $V> $crate::container::Container for $($container)::*<$K, $V> { + type Cont = $($container)::*<$K, U>; + type Item = $V; + } + }; +} + +impl Container for [T] { + type Cont = [U]; + type Item = T; +} + +container! { + core::option::Option, + core::result::Result, +} + +#[cfg(feature = "alloc")] +container! { + alloc::boxed::Box, + alloc::vec::Vec, + alloc::collections::BTreeMap, + alloc::collections::BTreeSet, + alloc::collections::VecDeque, + alloc::rc::Rc, + alloc::sync::Arc, +} + +#[cfg(feature = "std")] +container! { + std::collections::HashMap, + std::collections::HashSet, + std::cell::Cell, + std::sync::Mutex, + std::sync::RwLock, +} diff --git a/traits/src/entropy.rs b/traits/src/entropy.rs index 3562375c..a3fa711d 100644 --- a/traits/src/entropy.rs +++ b/traits/src/entropy.rs @@ -1,5 +1,6 @@ /* - Appellation: loss + Appellation: entropy + Created At: 2025.11.26:13:13:33 Contrib: @FL03 */ @@ -9,29 +10,16 @@ pub trait CrossEntropy { fn cross_entropy(&self) -> Self::Output; } -/// A trait for computing the mean absolute error of a tensor or array -pub trait MeanAbsoluteError { - type Output; - - fn mae(&self) -> Self::Output; -} -/// A trait for computing the mean squared error of a tensor or array -pub trait MeanSquaredError { - type Output; - - fn mse(&self) -> Self::Output; -} /* ************* Implementations ************* */ - -use ndarray::{ArrayBase, Data, Dimension, ScalarOperand}; +use ndarray::{ArrayBase, Data, Dimension}; use num_traits::{Float, FromPrimitive}; impl CrossEntropy for ArrayBase where - A: Float + FromPrimitive + ScalarOperand, + A: 'static + Float + FromPrimitive, D: Dimension, S: Data, { @@ -41,29 +29,3 @@ where self.mapv(|x| -x.ln()).mean().unwrap() } } - -impl MeanAbsoluteError for ArrayBase -where - A: Float + FromPrimitive + ScalarOperand, - D: Dimension, - S: Data, -{ - type Output = A; - - fn mae(&self) -> Self::Output { - self.abs().mean().unwrap() - } -} - -impl MeanSquaredError for ArrayBase -where - A: Float + FromPrimitive + ScalarOperand, - D: Dimension, - S: Data, -{ - type Output = A; - - fn mse(&self) -> Self::Output { - self.pow2().mean().unwrap() - } -} diff --git a/traits/src/lib.rs b/traits/src/lib.rs index e69b0d1f..69d93c29 100644 --- a/traits/src/lib.rs +++ b/traits/src/lib.rs @@ -13,7 +13,7 @@ #![cfg_attr(feature = "nightly", feature(allocator_api))] #![crate_type = "lib"] -#[cfg(not(all(feature = "std", feature = "alloc")))] +#[cfg(not(any(feature = "std", feature = "alloc")))] compiler_error! { "At least one of the 'std' or 'alloc' features must be enabled." } @@ -32,17 +32,40 @@ pub mod error; mod apply; mod clip; mod codex; +mod complex; +mod container; mod convert; -mod fill; -mod gradient; -mod like; +mod entropy; +mod loss; mod norm; +mod predict; mod propagation; -mod reshape; -mod shape; +mod rounding; mod store; -mod tensor_ops; -mod wnb; +mod training; + +pub mod math { + //! Mathematically oriented operators and functions useful in machine learning contexts. + #[doc(inline)] + pub use self::{difference::*, gradient::*, roots::*, stats::*, unary::*}; + + mod difference; + mod gradient; + mod roots; + mod stats; + mod unary; +} + +pub mod tensor { + #[doc(inline)] + pub use self::{fill::*, like::*, linalg::*, ndtensor::*, shape::*}; + + mod fill; + mod like; + mod linalg; + mod ndtensor; + mod shape; +} // re-exports #[doc(inline)] @@ -52,18 +75,23 @@ pub use self::prelude::*; #[doc(hidden)] pub mod prelude { + pub use crate::math::*; + pub use crate::tensor::*; + pub use crate::apply::*; pub use crate::clip::*; pub use crate::codex::*; + pub use crate::container::*; pub use crate::convert::*; - pub use crate::fill::*; - pub use crate::gradient::*; - pub use crate::like::*; + pub use crate::entropy::*; + pub use crate::loss::*; pub use crate::norm::*; + pub use crate::predict::*; pub use crate::propagation::*; - pub use crate::reshape::*; - pub use crate::shape::*; + pub use crate::rounding::*; pub use crate::store::*; - pub use crate::tensor_ops::*; - pub use crate::wnb::*; + pub use crate::training::*; + + #[cfg(feature = "complex")] + pub use crate::complex::*; } diff --git a/core/src/loss/traits/loss.rs b/traits/src/loss.rs similarity index 53% rename from core/src/loss/traits/loss.rs rename to traits/src/loss.rs index d5974555..c825a3fb 100644 --- a/core/src/loss/traits/loss.rs +++ b/traits/src/loss.rs @@ -1,6 +1,6 @@ /* - appellation: loss - authors: @FL03 + Appellation: loss + Contrib: @FL03 */ /// The [`Loss`] trait defines a common interface for any custom loss function implementations. @@ -19,3 +19,49 @@ pub trait Loss { /// compute the loss between two values, `lhs` and `rhs` fn loss(&self, lhs: &X, rhs: &Y) -> Self::Output; } + +/// A trait for computing the mean absolute error of a tensor or array +pub trait MeanAbsoluteError { + type Output; + + fn mae(&self) -> Self::Output; +} +/// A trait for computing the mean squared error of a tensor or array +pub trait MeanSquaredError { + type Output; + + fn mse(&self) -> Self::Output; +} + +/* + ************* Implementations ************* +*/ + +use ndarray::{ArrayBase, Data, Dimension}; +use num_traits::{Float, FromPrimitive}; + +impl MeanAbsoluteError for ArrayBase +where + A: 'static + Float + FromPrimitive, + D: Dimension, + S: Data, +{ + type Output = A; + + fn mae(&self) -> Self::Output { + self.abs().mean().unwrap() + } +} + +impl MeanSquaredError for ArrayBase +where + A: 'static + Float + FromPrimitive, + D: Dimension, + S: Data, +{ + type Output = A; + + fn mse(&self) -> Self::Output { + self.pow2().mean().unwrap() + } +} diff --git a/utils/src/traits/difference.rs b/traits/src/math/difference.rs similarity index 74% rename from utils/src/traits/difference.rs rename to traits/src/math/difference.rs index edb8e1cb..f8f1fdc6 100644 --- a/utils/src/traits/difference.rs +++ b/traits/src/math/difference.rs @@ -1,3 +1,9 @@ +/* + Appellation: difference + Created At: 2025.11.26:12:09:51 + Contrib: @FL03 +*/ + /// Compute the percentage difference between two values. /// The percentage difference is defined as: /// diff --git a/traits/src/gradient.rs b/traits/src/math/gradient.rs similarity index 96% rename from traits/src/gradient.rs rename to traits/src/math/gradient.rs index 1e3d957a..51a4573b 100644 --- a/traits/src/gradient.rs +++ b/traits/src/math/gradient.rs @@ -6,11 +6,11 @@ /// the [`Gradient`] trait defines the gradient of a function, which is a function that /// takes an input and returns a delta, which is the change in the output with respect to /// the input. -pub trait Gradient { - type Repr<_A>; +pub trait Gradient { + type Elem; type Delta<_S, _D>; - fn grad(&self, rhs: &Self::Delta, D>) -> Self::Delta, D>; + fn grad(&self, rhs: &Self::Delta) -> Self::Delta; } /// A trait declaring basic gradient-related routines for a neural network diff --git a/utils/src/traits/root.rs b/traits/src/math/roots.rs similarity index 95% rename from utils/src/traits/root.rs rename to traits/src/math/roots.rs index 54cf504c..b8c30da9 100644 --- a/utils/src/traits/root.rs +++ b/traits/src/math/roots.rs @@ -2,8 +2,8 @@ Appellation: arithmetic Contrib: @FL03 */ -use num::integer::Roots; -use num::traits::FromPrimitive; +use num_integer::Roots; +use num_traits::FromPrimitive; /// The [`Root`] trait provides methods for computing the nth root of a number. pub trait Root { diff --git a/utils/src/stats/summary.rs b/traits/src/math/stats.rs similarity index 96% rename from utils/src/stats/summary.rs rename to traits/src/math/stats.rs index 5be21462..fdcce8e4 100644 --- a/utils/src/stats/summary.rs +++ b/traits/src/math/stats.rs @@ -1,11 +1,12 @@ /* - Appellation: summary - Contrib: FL03 + Appellation: stats + Created At: 2025.11.26:12:23:22 + Contrib: @FL03 */ use crate::Root; use core::iter::{Product, Sum}; use ndarray::{ArrayBase, Data, Dimension}; -use num::traits::{FromPrimitive, Num, NumOps, Pow}; +use num_traits::{FromPrimitive, Num, NumOps, Pow}; /// This trait describes the fundamental methods of summary statistics. /// These include the mean, standard deviation, variance, and more. diff --git a/utils/src/ops/unary.rs b/traits/src/math/unary.rs similarity index 56% rename from utils/src/ops/unary.rs rename to traits/src/math/unary.rs index 1db1606b..ce5bc8b5 100644 --- a/utils/src/ops/unary.rs +++ b/traits/src/math/unary.rs @@ -3,8 +3,39 @@ Contrib: @FL03 */ use ndarray::{Array, ArrayBase, Data, Dimension}; -use num::complex::{Complex, ComplexFloat}; -use num::traits::Signed; +use num_traits::Signed; + +macro_rules! unary { + (@branch $name:ident::$call:ident($($rest:tt)*)) => { + unary!(@impl $name::$call($($rest)*)); + }; + (@impl $name:ident::$call:ident(self)) => { + pub trait $name { + type Output; + + fn $call(self) -> Self::Output; + } + }; + (@impl $name:ident::$call:ident(&self)) => { + pub trait $name { + type Output; + + fn $call(&self) -> Self::Output; + } + }; + (@impl $name:ident::$call:ident(&mut self)) => { + pub trait $name { + type Output; + + fn $call(&self) -> Self::Output; + } + }; + ($($name:ident::$call:ident($($rest:tt)*)),* $(,)?) => { + $( + unary!(@impl $name::$call($($rest)*)); + )* + }; +} unary! { Abs::abs(self), @@ -63,61 +94,20 @@ macro_rules! unary_impls { unary_impls! { Abs::<[f32, f64]>::abs, - Cos::<[f32, f64, Complex, Complex]>::cos, - Cosh::<[f32, f64, Complex, Complex]>::cosh, - Exp::<[f32, f64, Complex, Complex]>::exp, - Sinh::<[f32, f64, Complex, Complex]>::sinh, - Sine::<[f32, f64, Complex, Complex]>::sin, - Tan::<[f32, f64, Complex, Complex]>::tan, - Tanh::<[f32, f64, Complex, Complex]>::tanh, + Cos::<[f32, f64]>::cos, + Cosh::<[f32, f64]>::cosh, + Exp::<[f32, f64]>::exp, + Sinh::<[f32, f64]>::sinh, + Sine::<[f32, f64]>::sin, + Tan::<[f32, f64]>::tan, + Tanh::<[f32, f64]>::tanh, SquareRoot::<[f32, f64]>::sqrt } /* - ************* macro implementations ************* + ************* implementations ************* */ -macro_rules! impl_conj { - ($($t:ident<$res:ident>),*) => { - $( - impl_conj!(@impl $t<$res>); - )* - }; - (@impl $t:ident<$res:ident>) => { - impl Conjugate for $t { - type Output = $res<$t>; - - fn conj(&self) -> Self::Output { - Complex { re: *self, im: num_traits::Zero::zero() } - } - } - }; -} - -impl_conj!(f32, f64); - -impl Conjugate for Complex -where - T: Clone + Signed, -{ - type Output = Complex; - - fn conj(&self) -> Self { - Complex::::conj(self) - } -} - -impl Conjugate for Array -where - D: Dimension, - T: Clone + num::complex::ComplexFloat, -{ - type Output = Array; - fn conj(&self) -> Self::Output { - self.mapv(|x| x.conj()) - } -} - impl Abs for ArrayBase where A: Clone + Signed, @@ -155,17 +145,6 @@ where } } -impl SquareRoot for Complex -where - Complex: ComplexFloat, -{ - type Output = Self; - - fn sqrt(self) -> Self::Output { - ComplexFloat::sqrt(self) - } -} - impl SquareRoot for ArrayBase where A: Clone + SquareRoot, @@ -179,28 +158,112 @@ where } } -impl Exp for ArrayBase +#[cfg(not(feature = "complex"))] +impl Exp for &ArrayBase where - A: Clone + Exp, + A: Clone + Exp, D: Dimension, S: Data, { - type Output = Array; + type Output = Array; fn exp(self) -> Self::Output { self.mapv(|x| x.exp()) } } -impl Exp for &ArrayBase +impl Exp for ArrayBase where - A: Clone + ComplexFloat, + A: Clone + Exp, D: Dimension, S: Data, { - type Output = Array; + type Output = Array; fn exp(self) -> Self::Output { self.mapv(|x| x.exp()) } } + +#[cfg(feature = "complex")] +mod impl_complex { + use super::*; + use ndarray::{Array, Dimension}; + use num_complex::{Complex, ComplexFloat}; + + macro_rules! impl_conj { + ($($t:ident<$res:ident>),*) => { + $( + impl_conj!(@impl $t<$res>); + )* + }; + (@impl $t:ident<$res:ident>) => { + impl Conjugate for $t { + type Output = $res<$t>; + + fn conj(&self) -> Self::Output { + Complex { re: *self, im: num_traits::Zero::zero() } + } + } + }; +} + + impl_conj!(f32, f64); + + impl Conjugate for Complex + where + T: Clone + Signed, + { + type Output = Complex; + + fn conj(&self) -> Self { + Complex::::conj(self) + } + } + + impl Conjugate for Array + where + D: Dimension, + T: Clone + ComplexFloat, + { + type Output = Array; + fn conj(&self) -> Self::Output { + self.mapv(|x| x.conj()) + } + } + + impl Cos for Complex + where + Complex: ComplexFloat, + { + type Output = Self; + + fn cos(self) -> Self::Output { + ComplexFloat::cos(self) + } + } + + impl Exp for &ArrayBase + where + A: Clone + ComplexFloat, + D: Dimension, + S: Data, + { + type Output = Array; + + fn exp(self) -> Self::Output { + self.mapv(|x| x.exp()) + } + } + + impl SquareRoot for Complex + where + Complex: ComplexFloat, + { + type Output = Self; + + fn sqrt(self) -> Self::Output { + ComplexFloat::sqrt(self) + } + } +} diff --git a/traits/src/norm.rs b/traits/src/norm.rs index e4d9da00..b4b62639 100644 --- a/traits/src/norm.rs +++ b/traits/src/norm.rs @@ -28,9 +28,6 @@ pub trait Norm { /* ************* Implementations ************* */ -use ndarray::{ArrayBase, Data, Dimension, ScalarOperand}; -use num_traits::Float; - impl Norm for U where U: L1Norm + L2Norm, @@ -48,11 +45,11 @@ where macro_rules! impl_norm { ($trait:ident::$method:ident($($param:ident: $type:ty),*) => $self:ident$(.$call:ident())*) => { - impl $trait for ArrayBase + impl $trait for ndarray::ArrayBase where - A: Float + ScalarOperand, - D: Dimension, - S: Data, + A: 'static + Clone + num_traits::Float, + D: ndarray::Dimension, + S: ndarray::Data, { type Output = A; @@ -61,11 +58,11 @@ macro_rules! impl_norm { } } - impl<'a, A, S, D> $trait for &'a ArrayBase + impl<'a, A, S, D> $trait for &'a ndarray::ArrayBase where - A: Float + ScalarOperand, - D: Dimension, - S: Data, + A: 'static + Clone + num_traits::Float, + D: ndarray::Dimension, + S: ndarray::Data, { type Output = A; @@ -74,11 +71,11 @@ macro_rules! impl_norm { } } - impl<'a, A, S, D> $trait for &'a mut ArrayBase + impl<'a, A, S, D> $trait for &'a mut ndarray::ArrayBase where - A: Float + ScalarOperand, - D: Dimension, - S: Data, + A: 'static + Clone + num_traits::Float, + D: ndarray::Dimension, + S: ndarray::Data, { type Output = A; diff --git a/neural/src/traits/predict.rs b/traits/src/predict.rs similarity index 99% rename from neural/src/traits/predict.rs rename to traits/src/predict.rs index bc5b814e..e41d87bb 100644 --- a/neural/src/traits/predict.rs +++ b/traits/src/predict.rs @@ -2,7 +2,7 @@ Appellation: predict Contrib: @FL03 */ -use cnc::Forward; +use crate::Forward; /// The [`Predict`] trait is designed as a _**model-specific**_ interface for making /// predictions. In the future, we may consider opening the trait up allowing for an diff --git a/traits/src/propagation.rs b/traits/src/propagation.rs index 38a6bbc1..a2f6a14b 100644 --- a/traits/src/propagation.rs +++ b/traits/src/propagation.rs @@ -3,7 +3,23 @@ Contrib: @FL03 */ -/// [Backward] propagate a delta through the system; +/// The [`PropagationError`] type defines custom errors that can occur during forward and +/// backward propagation. +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum PropagationError { + #[error("Forward Propagation Error: {0}")] + ForwardError(&'static str), + #[error("Backward Propagation Error: {0}")] + BackwardError(&'static str), + #[error("Mismatched Dimensions")] + MismatchedDimensions, + #[error("Invalid Input")] + InvalidInput, +} + +/// The [`Backward`] trait establishes a common interface for completing a single backward +/// step in a neural network or machine learning model. pub trait Backward { type Elem; type Output; @@ -11,7 +27,8 @@ pub trait Backward { fn backward(&mut self, input: &X, delta: &Delta, gamma: Self::Elem) -> Option; } -/// This trait denotes entities capable of performing a single forward step +/// The [`Forward`] trait defines an interface that is used to perform a single forward step +/// within a neural network or machine learning model. pub trait Forward { type Output; /// a single forward step @@ -32,33 +49,28 @@ pub trait Forward { */ use ndarray::linalg::Dot; -use ndarray::{ArrayBase, Data, Dimension}; -// impl Backward for ArrayBase -// where -// A: LinalgScalar + FromPrimitive, -// D: Dimension, -// S: DataMut, -// Dx: core::ops::Mul, -// for<'a> X: Dot, -// for<'a> &'a Self: core::ops::Add, +use ndarray::{ArrayBase, Data, Dimension, LinalgScalar}; +use num_traits::FromPrimitive; -// { -// type Elem = A; -// type Output = (); - -// fn backward( -// &mut self, -// input: &X, -// delta: &Y, -// gamma: Self::Elem, -// ) -> Option { -// let grad = input.dot(delta); -// let next = &self + grad * gamma; -// self.assign(&next)?; -// Ok(()) +impl Backward for ArrayBase +where + A: LinalgScalar + FromPrimitive, + D: Dimension, + S: ndarray::DataMut, + Dx: core::ops::Mul, + for<'a> X: Dot, + for<'a> &'a Self: core::ops::Add<&'a Dx, Output = Self>, +{ + type Elem = A; + type Output = (); -// } -// } + fn backward(&mut self, input: &X, delta: &Y, gamma: Self::Elem) -> Option { + let dx = input.dot(delta); + let next = &*self + &(dx * gamma); + self.assign(&next); + Some(()) + } +} impl Forward for ArrayBase where diff --git a/traits/src/rounding.rs b/traits/src/rounding.rs new file mode 100644 index 00000000..92f9c340 --- /dev/null +++ b/traits/src/rounding.rs @@ -0,0 +1,53 @@ +/* + Appellation: precision + Created At: 2025.11.26:12:09:08 + Contrib: @FL03 +*/ +use num_traits::{Float, Num}; + +/// divide two values and round down to the nearest integer. +fn floor_div(numerator: T, denom: T) -> T +where + T: Copy + core::ops::Div + core::ops::Rem + core::ops::Sub, +{ + (numerator - (numerator % denom)) / denom +} + +/// Round the given value to the given number of decimal places. +fn round_to(val: T, decimals: usize) -> T +where + T: Float, +{ + let factor = T::from(10).expect("").powi(decimals as i32); + (val * factor).round() / factor +} + +pub trait FloorDiv { + type Output; + + fn floor_div(self, rhs: Rhs) -> Self::Output; +} + +pub trait RoundTo { + fn round_to(&self, places: usize) -> Self; +} + +impl FloorDiv for T +where + T: Copy + Num, +{ + type Output = T; + + fn floor_div(self, rhs: Self) -> Self::Output { + floor_div(self, rhs) + } +} + +impl RoundTo for T +where + T: Float, +{ + fn round_to(&self, places: usize) -> Self { + round_to(*self, places) + } +} diff --git a/traits/src/shape.rs b/traits/src/shape.rs deleted file mode 100644 index 45949076..00000000 --- a/traits/src/shape.rs +++ /dev/null @@ -1,13 +0,0 @@ -/// the [`RawDimension`] trait is used to define a type that can be used as a raw dimension. -/// This trait is primarily used to provide abstracted, generic interpretations of the -/// dimensions of the [`ndarray`] crate to ensure long-term compatibility. -pub trait RawDimension { - private! {} -} - -impl RawDimension for D -where - D: ndarray::Dimension, -{ - seal! {} -} diff --git a/traits/src/fill.rs b/traits/src/tensor/fill.rs similarity index 100% rename from traits/src/fill.rs rename to traits/src/tensor/fill.rs diff --git a/traits/src/like.rs b/traits/src/tensor/like.rs similarity index 100% rename from traits/src/like.rs rename to traits/src/tensor/like.rs diff --git a/traits/src/tensor_ops.rs b/traits/src/tensor/linalg.rs similarity index 100% rename from traits/src/tensor_ops.rs rename to traits/src/tensor/linalg.rs diff --git a/traits/src/tensor/ndtensor.rs b/traits/src/tensor/ndtensor.rs new file mode 100644 index 00000000..f89cd09f --- /dev/null +++ b/traits/src/tensor/ndtensor.rs @@ -0,0 +1,45 @@ +/* + Appellation: ndtensor + Created At: 2025.11.26:14:27:51 + Contrib: @FL03 +*/ + +pub trait RawTensorData { + type Elem; +} + +pub trait RawTensor +where + S: RawTensorData, +{ + type Elem; + type Cont<_R: RawTensorData, _D>: RawTensor<_R, _D, Elem = Self::Elem>; + /// returns the rank, or _dimensionality_, of the tensor + fn rank(&self) -> usize; + /// returns the shape of the tensor + fn shape(&self) -> &[usize]; + /// returns the total number of elements in the tensor + fn len(&self) -> usize; + + fn as_ptr(&self) -> *const Self::Elem; + + fn as_mut_ptr(&mut self) -> *mut Self::Elem; +} + +pub trait Tensor: RawTensor +where + S: RawTensorData, +{ + fn apply(&self, f: F) -> Self::Cont + where + F: Fn(&Self::Elem) -> U; +} + +pub trait TensorGrad: Tensor +where + S: RawTensorData, +{ + type Delta<_S: RawTensorData, _D>: RawTensor<_S, _D, Elem = Self::Elem>; + + fn grad(&self, rhs: &Self::Delta) -> Self::Delta; +} diff --git a/traits/src/reshape.rs b/traits/src/tensor/shape.rs similarity index 78% rename from traits/src/reshape.rs rename to traits/src/tensor/shape.rs index 59021f2f..7e6c066b 100644 --- a/traits/src/reshape.rs +++ b/traits/src/tensor/shape.rs @@ -1,8 +1,15 @@ /* - Appellation: reshape + Appellation: shape + Created At: 2025.11.26:13:10:09 Contrib: @FL03 */ -use ndarray::{ArrayBase, Axis, Dimension, RawData, RawDataClone, RemoveAxis}; + +/// the [`RawDimension`] trait is used to define a type that can be used as a raw dimension. +/// This trait is primarily used to provide abstracted, generic interpretations of the +/// dimensions of the [`ndarray`] crate to ensure long-term compatibility. +pub trait Dim { + private! {} +} /// The [`DecrementAxis`] trait defines a method enabling an axis to decrement itself, pub trait DecrementAxis { @@ -25,9 +32,19 @@ pub trait Unsqueeze { fn unsqueeze(self, axis: usize) -> Self::Output; } + /* ************* Implementations ************* */ +use ndarray::{ArrayBase, Axis, Dimension, RawData, RawDataClone, RemoveAxis}; + +impl Dim for D +where + D: ndarray::Dimension, +{ + seal! {} +} + impl DecrementAxis for D where D: RemoveAxis, diff --git a/traits/src/training.rs b/traits/src/training.rs new file mode 100644 index 00000000..7a8b1fb0 --- /dev/null +++ b/traits/src/training.rs @@ -0,0 +1,42 @@ +/* + Appellation: training + Created At: 2025.11.28:13:07:13 + Contrib: @FL03 +*/ + +/// This trait defines the training process for the network +pub trait Train { + type Error; + type Output; + + fn train(&mut self, input: &X, target: &Y) -> Result; + + fn train_for( + &mut self, + input: &X, + target: &Y, + epochs: usize, + ) -> Result { + let mut output = None; + let mut step: usize = 0; + + while step < epochs { + step += 1; + output = Some(self.train(input, target)?); + } + + for _ in 0..epochs { + output = match self.train(input, target) { + Ok(o) => Some(o), + Err(e) => { + return Err(e); + } + } + } + if let Some(o) = output { + Ok(o) + } else { + panic!("Training did not produce any output") + } + } +} diff --git a/utils/tests/traits.rs b/traits/tests/complex.rs similarity index 80% rename from utils/tests/traits.rs rename to traits/tests/complex.rs index 70739d9b..566a3bdc 100644 --- a/utils/tests/traits.rs +++ b/traits/tests/complex.rs @@ -1,11 +1,14 @@ -extern crate concision_utils as cnc; - +/* + Appellation: complex + Created At: 2025.11.26:12:28:18 + Contrib: @FL03 +*/ +use concision_traits::{AsComplex, Conjugate}; use ndarray::prelude::*; -use num::Complex; +use num_complex::Complex; #[test] fn test_as_complex() { - use cnc::AsComplex; let x = 1.0; let y = x.as_re(); assert_eq!(y, Complex::new(1.0, 0.0)); @@ -13,8 +16,6 @@ fn test_as_complex() { #[test] fn test_conj() { - use cnc::Conjugate; - use num::complex::Complex; let data = (1..5).map(|x| x as f64).collect::>(); let a = Array2::from_shape_vec((2, 2), data).unwrap(); let exp = array![[1.0, 2.0], [3.0, 4.0]]; diff --git a/traits/tests/traits.rs b/traits/tests/tensor.rs similarity index 100% rename from traits/tests/traits.rs rename to traits/tests/tensor.rs diff --git a/utils/Cargo.toml b/utils/Cargo.toml deleted file mode 100644 index 1f237cbd..00000000 --- a/utils/Cargo.toml +++ /dev/null @@ -1,179 +0,0 @@ -[package] -build = "build.rs" -name = "concision-utils" - -authors.workspace = true -categories.workspace = true -description.workspace = true -edition.workspace = true -homepage.workspace = true -keywords.workspace = true -license.workspace = true -readme.workspace = true -repository.workspace = true -rust-version.workspace = true -version.workspace = true - -[lib] -crate-type = ["cdylib", "rlib"] -bench = false -doc = true -doctest = true -test = true - -[dependencies] -# custom -variants = { workspace = true } -# concurrency & parallelism -rayon = { optional = true, workspace = true } -# data & serialization -serde = { optional = true, workspace = true } -serde_derive = { optional = true, workspace = true } -serde_json = { optional = true, workspace = true } -# error-handling -anyhow = { optional = true, workspace = true } -thiserror = { workspace = true } -# logging -tracing = { optional = true, workspace = true } -# macros & utilities -paste = { workspace = true } -smart-default = { workspace = true } -strum = { workspace = true } -# mathematics -approx = { optional = true, workspace = true } -ndarray = { workspace = true } -num = { workspace = true } -num-complex = { optional = true, workspace = true } -num-traits = { workspace = true } -rustfft = { optional = true, workspace = true } -# random -getrandom = { default-features = false, optional = true, workspace = true } -rand = { optional = true, workspace = true } -rand_distr = { optional = true, workspace = true } - -[dev-dependencies] -anyhow = { features = ["std"], workspace = true } -lazy_static = { workspace = true } - -[features] -default = [ - "std", -] - -full = [ - "approx", - "complex", - "default", - "json", - "rand", - "serde", - "tracing", -] - -nightly = [] - -# ************* [FF:Features] ************* -signal =[ - "complex", - "rustfft", -] - -# ************* [FF:Dependencies] ************* -std = [ - "alloc", - "ndarray/std", - "num/std", - "num-complex?/std", - "num-traits/std", - "rand?/std", - "rand?/std_rng", - "serde/std", - "strum/std", - "thiserror/std", - "tracing?/std", - "variants/std", -] - -wasi = [] - -wasm = [ - "getrandom?/wasm_js", -] -# ************* [FF:Dependencies] ************* -alloc = [ - "num/alloc", - "serde?/alloc", - "serde_json?/alloc", - "variants/alloc", -] - -approx = ["dep:approx", "ndarray/approx"] - -blas = ["ndarray/blas"] - -complex = ["dep:num-complex"] - -json = [ - "alloc", - "serde", - "serde_json", -] - -rand = [ - "dep:rand", - "dep:rand_distr", - "num/rand", - "num-complex?/rand", - "rng", -] - -rayon = ["dep:rayon", "ndarray/rayon"] - -rng = [ - "dep:getrandom", - "rand?/small_rng", - "rand?/thread_rng", -] - -rustfft = ["dep:rustfft"] - -serde = [ - "dep:serde", - "dep:serde_derive", - "ndarray/serde", - "num/serde", - "num-complex?/serde", - "rand?/serde", - "rand_distr?/serde", -] - -serde_json = ["dep:serde_json"] - -tracing = ["dep:tracing"] - -# ********* [Unit Tests] ********* -[[test]] -name = "default" - -[[test]] -name = "fft" -required-features = ["approx", "signal", "std"] - -[[test]] -name = "tensor" -required-features = ["approx", "std", "complex"] - -[[test]] -name = "traits" -required-features = ["approx", "std", "complex"] - -# ********* [Metadata] ********* -[package.metadata.docs.rs] -all-features = false -features = ["full"] -rustc-args = ["--cfg", "docsrs"] -version = "v{{version}}" - -[package.metadata.release] -no-dev-version = true -tag-name = "{{version}}" \ No newline at end of file diff --git a/utils/build.rs b/utils/build.rs deleted file mode 100644 index 940a4ce4..00000000 --- a/utils/build.rs +++ /dev/null @@ -1,8 +0,0 @@ -/* - Appellation: build - Contrib: FL03 -*/ - -fn main() { - println!("cargo::rustc-check-cfg=cfg(no_std)"); -} diff --git a/utils/src/error.rs b/utils/src/error.rs deleted file mode 100644 index be023377..00000000 --- a/utils/src/error.rs +++ /dev/null @@ -1,17 +0,0 @@ -/* - Appellation: error - Contrib: @FL03 -*/ - -#[allow(dead_code)] -pub(crate) type UtilityResult = Result; - -#[derive(Debug, thiserror::Error)] -pub enum UtilityError { - #[error("Dimension Error: {0}")] - DimensionalError(&'static str), - #[error("Infinity Error")] - InfinityError, - #[error("NaN Error")] - NaNError, -} diff --git a/utils/src/lib.rs b/utils/src/lib.rs deleted file mode 100644 index e0db509a..00000000 --- a/utils/src/lib.rs +++ /dev/null @@ -1,103 +0,0 @@ -/* - Appellation: concision-utils - Contrib: @FL03 -*/ -//! A suite of utilities tailored toward neural networks. -//! -//! Our focus revolves around the following areas: -//! -//! - **Numerical Operations**: Efficient implementations of mathematical functions. -//! - **Statistical Functions**: Tools for statistical analysis and operations. -//! - **Signal Processing**: Functions and utilities for signal manipulation. -//! - **Utilities**: General-purpose utilities to aid in mathematical computations. -//! -#![cfg_attr(not(feature = "std"), no_std)] - -#[cfg(feature = "alloc")] -extern crate alloc; - -#[macro_use] -pub(crate) mod macros { - #[macro_use] - pub(crate) mod seal; - #[macro_use] - pub(crate) mod unary; -} - -#[doc(inline)] -pub use self::{error::*, ops::*, traits::*, utils::*}; - -pub mod error; -#[cfg(feature = "signal")] -pub mod signal; -pub mod stats; - -mod ops { - #[doc(inline)] - pub use self::prelude::*; - - mod unary; - - mod prelude { - #[doc(inline)] - pub use super::unary::*; - } -} - -mod traits { - #[doc(inline)] - pub use self::prelude::*; - - #[cfg(feature = "complex")] - mod complex; - mod difference; - mod precision; - mod root; - - mod prelude { - #[doc(inline)] - #[cfg(feature = "complex")] - pub use super::complex::*; - #[doc(inline)] - pub use super::difference::*; - #[doc(inline)] - pub use super::precision::*; - #[doc(inline)] - pub use super::root::*; - } -} - -mod utils { - //! utilties supporting various mathematical routines for machine learning tasks. - #[doc(inline)] - pub use self::prelude::*; - - mod arith; - mod gradient; - mod norm; - mod patterns; - mod tensor; - - mod prelude { - #[doc(inline)] - pub use super::arith::*; - #[doc(inline)] - pub use super::gradient::*; - #[doc(inline)] - pub use super::norm::*; - #[doc(inline)] - pub use super::patterns::*; - #[doc(inline)] - pub use super::tensor::*; - } -} - -#[doc(hidden)] -pub mod prelude { - pub use crate::stats::prelude::*; - pub use crate::traits::*; - pub use crate::utils::*; - - #[cfg(feature = "signal")] - pub use crate::signal::prelude::*; -} diff --git a/utils/src/macros/seal.rs b/utils/src/macros/seal.rs deleted file mode 100644 index 05f2bbf6..00000000 --- a/utils/src/macros/seal.rs +++ /dev/null @@ -1,50 +0,0 @@ -/* - Appellation: seal - Contrib: FL03 -*/ -//! The public parts of this private module are used to create traits -//! that cannot be implemented outside of our own crate. This way we -//! can feel free to extend those traits without worrying about it -//! being a breaking change for other implementations. -//! -//! ## Usage -//! -//! To define a private trait, you can use the [`private!`] macro, which will define a hidden -//! method `__private__` that can only be implemented within the crate. - -/// If this type is pub but not publicly reachable, third parties -/// can't name it and can't implement traits using it. -#[allow(dead_code)] -pub struct Seal; -/// the [`private!`] macro is used to seal a particular trait, defining a hidden method that -/// may only be implemented within the bounds of the crate. -#[allow(unused_macros)] -macro_rules! private { - () => { - /// This trait is private to implement; this method exists to make it - /// impossible to implement outside the crate. - #[doc(hidden)] - fn __private__(&self) -> $crate::macros::seal::Seal; - }; -} -/// the [`seal!`] macro is used to implement a private method on a type, which is used to seal -/// the type so that it cannot be implemented outside of the crate. -#[allow(unused_macros)] -macro_rules! seal { - () => { - fn __private__(&self) -> $crate::macros::seal::Seal { - $crate::macros::seal::Seal - } - }; -} -/// this macros is used to implement a trait for a type, sealing it so that -/// it cannot be implemented outside of the crate. This is most usefuly for creating other -/// macros that can be used to implement some raw, sealed trait on the given _types_. -#[allow(unused_macros)] -macro_rules! sealed { - (impl$(<$($T:ident),*>)? $trait:ident for $name:ident$(<$($V:ident),*>)? $(where $($rest:tt)*)?) => { - impl$(<$($T),*>)? $trait for $name$(<$($V),*>)? $(where $($rest)*)? { - seal!(); - } - }; -} diff --git a/utils/src/macros/unary.rs b/utils/src/macros/unary.rs deleted file mode 100644 index 64614166..00000000 --- a/utils/src/macros/unary.rs +++ /dev/null @@ -1,57 +0,0 @@ -macro_rules! unary { - (@branch $name:ident::$call:ident($($rest:tt)*)) => { - unary!(@impl $name::$call($($rest)*)); - }; - (@impl $name:ident::$call:ident(self)) => { - pub trait $name { - type Output; - - fn $call(self) -> Self::Output; - } - }; - (@impl $name:ident::$call:ident(&self)) => { - pub trait $name { - type Output; - - fn $call(&self) -> Self::Output; - } - }; - (@impl $name:ident::$call:ident(&mut self)) => { - pub trait $name { - type Output; - - fn $call(&self) -> Self::Output; - } - }; - ($($name:ident::$call:ident($($rest:tt)*)),* $(,)?) => { - $( - unary!(@impl $name::$call($($rest)*)); - )* - }; -} -#[allow(unused_macros)] -macro_rules! unary_derivative { - (@impl $name:ident::$call:ident(self)) => { - paste::paste! { - pub trait $name { - type Output; - - fn $call(self) -> Self::Output; - - fn [<$call _derivative>](self) -> Self::Output; - } - } - }; - (@impl $name:ident::$call:ident(&self)) => { - pub trait $name { - type Output; - - fn $call(&self) -> Self::Output; - } - }; - ($($name:ident::$call:ident($($rest:tt)*)),* $(,)?) => { - $( - unary!(@impl $name::$call($($rest)*)); - )* - }; -} diff --git a/utils/src/signal/mod.rs b/utils/src/signal/mod.rs deleted file mode 100644 index f475389c..00000000 --- a/utils/src/signal/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -/* - Appellation: signal - Contrib: @FL03 -*/ -//! # Signal Processing -//! -//! This module contains functions for signal processing such as convolution, filtering, and Fourier transforms. - -#![cfg(feature = "signal")] -#[doc(inline)] -pub use self::prelude::*; - -pub mod fft; - -pub(crate) mod prelude { - pub use super::fft::prelude::*; -} diff --git a/utils/src/stats/mod.rs b/utils/src/stats/mod.rs deleted file mode 100644 index 5d4754c1..00000000 --- a/utils/src/stats/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -/* - Appellation: stats - Contrib: @FL03 -*/ -//! Statistical primitives and utilities commonly used in machine learning. -pub use self::summary::SummaryStatistics; - -pub mod summary; - -pub(crate) mod prelude { - pub use super::summary::*; -} diff --git a/utils/src/traits/precision.rs b/utils/src/traits/precision.rs deleted file mode 100644 index fede7176..00000000 --- a/utils/src/traits/precision.rs +++ /dev/null @@ -1,29 +0,0 @@ -pub trait FloorDiv { - type Output; - - fn floor_div(self, rhs: Rhs) -> Self::Output; -} - -pub trait RoundTo { - fn round_to(&self, places: usize) -> Self; -} - -impl FloorDiv for T -where - T: Copy + num_traits::Num, -{ - type Output = T; - - fn floor_div(self, rhs: Self) -> Self::Output { - crate::floor_div(self, rhs) - } -} - -impl RoundTo for T -where - T: num_traits::Float, -{ - fn round_to(&self, places: usize) -> Self { - crate::round_to(*self, places) - } -} diff --git a/utils/tests/default.rs b/utils/tests/default.rs deleted file mode 100644 index 62e1f28a..00000000 --- a/utils/tests/default.rs +++ /dev/null @@ -1,16 +0,0 @@ -/* - Appellation: default - Contrib: FL03 -*/ - -#[test] -fn lib_compiles() { - fn add(a: A, b: B) -> C - where - A: core::ops::Add, - { - a + b - } - assert_eq!(add(10, 10), 20); - assert_ne!(add(1, 1), 3); -}