Skip to content

Commit da1935b

Browse files
Merge pull request #246 from SciML/fm/inits
Adding chaotic and chebyshev inits
2 parents 5d65ff8 + 2391a20 commit da1935b

File tree

5 files changed

+201
-9
lines changed

5 files changed

+201
-9
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ReservoirComputing"
22
uuid = "7c2d2b1e-3dd4-11ea-355a-8f6a8116e294"
33
authors = ["Francesco Martinuzzi"]
4-
version = "0.10.8"
4+
version = "0.10.9"
55

66
[deps]
77
Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"

docs/src/api/inits.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
weighted_init
88
informed_init
99
minimal_init
10+
chebyshev_mapping
1011
```
1112

1213
## Reservoirs
@@ -18,4 +19,5 @@
1819
cycle_jumps
1920
simple_cycle
2021
pseudo_svd
22+
chaotic_init
2123
```

src/ReservoirComputing.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ include("reca/reca_input_encodings.jl")
3838
export NLADefault, NLAT1, NLAT2, NLAT3
3939
export StandardStates, ExtendedStates, PaddedStates, PaddedExtendedStates
4040
export StandardRidge
41-
export scaled_rand, weighted_init, informed_init, minimal_init
42-
export rand_sparse, delay_line, delay_line_backward, cycle_jumps, simple_cycle, pseudo_svd
41+
export scaled_rand, weighted_init, informed_init, minimal_init, chebyshev_mapping
42+
export rand_sparse, delay_line, delay_line_backward, cycle_jumps,
43+
simple_cycle, pseudo_svd, chaotic_init
4344
export RNN, MRNN, GRU, GRUParams, FullyGated, Minimal
4445
export train
4546
export ESN, HybridESN, KnowledgeModel, DeepESN

src/esn/esn_inits.jl

Lines changed: 189 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,86 @@ function _create_irrational(irrational::Irrational, start::Int, res_size::Int,
282282
return T.(input_matrix)
283283
end
284284

285+
"""
286+
chebyshev_mapping([rng], [T], dims...;
287+
amplitude=one(T), sine_divisor=one(T),
288+
chebyshev_parameter=one(T), return_sparse=true)
289+
290+
Generate a Chebyshev-mapped matrix [^xie2024]. The first row is initialized
291+
using a sine function and subsequent rows are iteratively generated
292+
via the Chebyshev mapping. The first row is defined as:
293+
294+
```math
295+
w(1, j) = amplitude * sin(j * π / (sine_divisor * n_cols))
296+
```
297+
298+
for j = 1, 2, …, n_cols (with n_cols typically equal to K+1, where K is the number of input layer neurons).
299+
Subsequent rows are generated by applying the mapping:
300+
301+
```math
302+
w(i+1, j) = cos(chebyshev_parameter * acos(w(i, j)))
303+
```
304+
305+
# Arguments
306+
307+
- `rng`: Random number generator. Default is `Utils.default_rng()`
308+
from WeightInitializers.
309+
- `T`: Type of the elements in the reservoir matrix.
310+
Default is `Float32`.
311+
- `dims`: Dimensions of the matrix. Should follow `res_size x in_size`. Here, res_size is assumed to be K+1.
312+
313+
# Keyword arguments
314+
315+
- `amplitude`: Scaling factor used to initialize the first row.
316+
This parameter adjusts the amplitude of the sine function. Default value is one.
317+
- `sine_divisor`: Divisor applied in the sine function's phase. Default value is one.
318+
- `chebyshev_parameter`: Control parameter for the Chebyshev mapping in
319+
subsequent rows. This parameter influences the distribution of the
320+
matrix elements. Default is one.
321+
- `return_sparse`: If `true`, the function returns the matrix as a sparse matrix. Default is `false`.
322+
323+
# Examples
324+
325+
```jldoctest
326+
julia> input_matrix = chebyshev_mapping(10, 3)
327+
10×3 Matrix{Float32}:
328+
0.866025 0.866025 1.22465f-16
329+
0.866025 0.866025 -4.37114f-8
330+
0.866025 0.866025 -4.37114f-8
331+
0.866025 0.866025 -4.37114f-8
332+
0.866025 0.866025 -4.37114f-8
333+
0.866025 0.866025 -4.37114f-8
334+
0.866025 0.866025 -4.37114f-8
335+
0.866025 0.866025 -4.37114f-8
336+
0.866025 0.866025 -4.37114f-8
337+
0.866025 0.866025 -4.37114f-8
338+
```
339+
340+
[^xie2024]: Xie, Minzhi, Qianxue Wang, and Simin Yu.
341+
"Time Series Prediction of ESN Based on Chebyshev Mapping and Strongly
342+
Connected Topology."
343+
Neural Processing Letters 56.1 (2024): 30.
344+
"""
345+
function chebyshev_mapping(rng::AbstractRNG, ::Type{T}, dims::Integer...;
346+
amplitude::AbstractFloat=one(T), sine_divisor::AbstractFloat=one(T),
347+
chebyshev_parameter::AbstractFloat=one(T),
348+
return_sparse::Bool=false) where {T <: Number}
349+
input_matrix = DeviceAgnostic.zeros(rng, T, dims...)
350+
n_rows, n_cols = dims[1], dims[2]
351+
352+
for idx_cols in 1:n_cols
353+
input_matrix[1, idx_cols] = amplitude * sin(idx_cols * pi / (sine_divisor * n_cols))
354+
end
355+
for idx_rows in 2:n_rows
356+
for idx_cols in 1:n_cols
357+
input_matrix[idx_rows, idx_cols] = cos(chebyshev_parameter * acos(input_matrix[
358+
idx_rows - 1, idx_cols]))
359+
end
360+
end
361+
362+
return return_sparse ? sparse(input_matrix) : input_matrix
363+
end
364+
285365
### reservoirs
286366

287367
"""
@@ -672,11 +752,118 @@ function get_sparsity(M, dim)
672752
return size(M[M .!= 0], 1) / (dim * dim - size(M[M .!= 0], 1)) #nonzero/zero elements
673753
end
674754

755+
function digital_chaotic_adjacency(rng::AbstractRNG, bit_precision::Integer;
756+
extra_edge_probability::AbstractFloat=0.1)
757+
matrix_order = 2^(2 * bit_precision)
758+
adjacency_matrix = zeros(Int, matrix_order, matrix_order)
759+
for row_index in 1:(matrix_order - 1)
760+
adjacency_matrix[row_index, row_index + 1] = 1
761+
end
762+
adjacency_matrix[matrix_order, 1] = 1
763+
for row_index in 1:matrix_order, column_index in 1:matrix_order
764+
if row_index != column_index && rand(rng) < extra_edge_probability
765+
adjacency_matrix[row_index, column_index] = 1
766+
end
767+
end
768+
769+
return adjacency_matrix
770+
end
771+
772+
"""
773+
chaotic_init([rng], [T], dims...;
774+
extra_edge_probability=T(0.1), spectral_radius=one(T),
775+
return_sparse_matrix=true)
776+
777+
Construct a chaotic reservoir matrix using a digital chaotic system [^xie2024].
778+
779+
The matrix topology is derived from a strongly connected adjacency
780+
matrix based on a digital chaotic system operating at finite precision.
781+
If the requested matrix order does not exactly match a valid order the
782+
closest valid order is used.
783+
784+
# Arguments
785+
786+
- `rng`: Random number generator. Default is `Utils.default_rng()`
787+
from WeightInitializers.
788+
- `T`: Type of the elements in the reservoir matrix.
789+
Default is `Float32`.
790+
- `dims`: Dimensions of the reservoir matrix.
791+
792+
# Keyword arguments
793+
794+
- `extra_edge_probability`: Probability of adding extra random edges in
795+
the adjacency matrix to enhance connectivity. Default is 0.1.
796+
- `desired_spectral_radius`: The target spectral radius for the
797+
reservoir matrix. Default is one.
798+
- `return_sparse_matrix`: If `true`, the function returns the
799+
reservoir matrix as a sparse matrix. Default is `true`.
800+
801+
# Examples
802+
803+
```jldoctest
804+
julia> res_matrix = chaotic_init(8, 8)
805+
┌ Warning:
806+
807+
│ Adjusting reservoir matrix order:
808+
│ from 8 (requested) to 4
809+
│ based on computed bit precision = 1.
810+
811+
└ @ ReservoirComputing ~/.julia/dev/ReservoirComputing/src/esn/esn_inits.jl:805
812+
4×4 SparseArrays.SparseMatrixCSC{Float32, Int64} with 6 stored entries:
813+
⋅ -0.600945 ⋅ ⋅
814+
⋅ ⋅ 0.132667 2.21354
815+
⋅ -2.60383 ⋅ -2.90391
816+
-0.578156 ⋅ ⋅ ⋅
817+
```
818+
819+
[^xie2024]: Xie, Minzhi, Qianxue Wang, and Simin Yu.
820+
"Time Series Prediction of ESN Based on Chebyshev Mapping and Strongly
821+
Connected Topology."
822+
Neural Processing Letters 56.1 (2024): 30.
823+
"""
824+
function chaotic_init(rng::AbstractRNG, ::Type{T}, dims::Integer...;
825+
extra_edge_probability::AbstractFloat=T(0.1), spectral_radius::AbstractFloat=one(T),
826+
return_sparse_matrix::Bool=true) where {T <: Number}
827+
requested_order = first(dims)
828+
if length(dims) > 1 && dims[2] != requested_order
829+
@warn """\n
830+
Using dims[1] = $requested_order for the chaotic reservoir matrix order.\n
831+
"""
832+
end
833+
d_estimate = log2(requested_order) / 2
834+
d_floor = max(floor(Int, d_estimate), 1)
835+
d_ceil = ceil(Int, d_estimate)
836+
candidate_order_floor = 2^(2 * d_floor)
837+
candidate_order_ceil = 2^(2 * d_ceil)
838+
chosen_bit_precision = abs(candidate_order_floor - requested_order) <=
839+
abs(candidate_order_ceil - requested_order) ? d_floor : d_ceil
840+
actual_matrix_order = 2^(2 * chosen_bit_precision)
841+
if actual_matrix_order != requested_order
842+
@warn """\n
843+
Adjusting reservoir matrix order:
844+
from $requested_order (requested) to $actual_matrix_order
845+
based on computed bit precision = $chosen_bit_precision. \n
846+
"""
847+
end
848+
849+
random_weight_matrix = T(2) * rand(rng, T, actual_matrix_order, actual_matrix_order) .-
850+
T(1)
851+
adjacency_matrix = digital_chaotic_adjacency(
852+
rng, chosen_bit_precision; extra_edge_probability=extra_edge_probability)
853+
reservoir_matrix = random_weight_matrix .* adjacency_matrix
854+
current_spectral_radius = maximum(abs, eigvals(reservoir_matrix))
855+
if current_spectral_radius != 0
856+
reservoir_matrix .*= spectral_radius / current_spectral_radius
857+
end
858+
859+
return return_sparse_matrix ? sparse(reservoir_matrix) : reservoir_matrix
860+
end
861+
675862
### fallbacks
676863
#fallbacks for initializers #eventually to remove once migrated to WeightInitializers.jl
677864
for initializer in (:rand_sparse, :delay_line, :delay_line_backward, :cycle_jumps,
678-
:simple_cycle, :pseudo_svd,
679-
:scaled_rand, :weighted_init, :informed_init, :minimal_init)
865+
:simple_cycle, :pseudo_svd, :chaotic_init,
866+
:scaled_rand, :weighted_init, :informed_init, :minimal_init, :chebyshev_mapping)
680867
@eval begin
681868
function ($initializer)(dims::Integer...; kwargs...)
682869
return $initializer(Utils.default_rng(), Float32, dims...; kwargs...)

test/esn/test_inits.jl

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using ReservoirComputing, LinearAlgebra, Random, SparseArrays
22

3-
const res_size = 30
4-
const in_size = 3
3+
const res_size = 16
4+
const in_size = 4
55
const radius = 1.0
66
const sparsity = 0.1
77
const weight = 0.2
@@ -24,13 +24,15 @@ reservoir_inits = [
2424
delay_line_backward,
2525
cycle_jumps,
2626
simple_cycle,
27-
pseudo_svd
27+
pseudo_svd,
28+
chaotic_init
2829
]
2930
input_inits = [
3031
scaled_rand,
3132
weighted_init,
3233
minimal_init,
33-
minimal_init(; sampling_type=:irrational)
34+
minimal_init(; sampling_type=:irrational),
35+
chebyshev_mapping
3436
]
3537

3638
@testset "Reservoir Initializers" begin

0 commit comments

Comments
 (0)