use super::neural_types::*;
use super::neural_activations::*;
use super::neural_utilities::*;
pub struct ScaledDotProductAttention {
pub d_k: usize,
pub temperature: f64,
pub dropout_rate: f64,
}
impl ScaledDotProductAttention {
pub fn new(d_k: usize, temperature: f64, dropout_rate: f64) -> Self {
Self {
d_k,
temperature,
dropout_rate,
}
}
pub fn forward(
&self,
q: &Array2<f64>,
k: &Array2<f64>,
v: &Array2<f64>,
mask: Option<&Array2<bool>>,
) -> (Array2<f64>, Array2<f64>) {
let scores = self.compute_attention_scores(q, k);
let masked_scores = self.apply_mask(&scores, mask);
let attention_weights = self.apply_softmax(&masked_scores);
let dropout_weights = self.apply_dropout(&attention_weights);
let output = dropout_weights.dot(v);
(output, attention_weights)
}
fn compute_attention_scores(&self, q: &Array2<f64>, k: &Array2<f64>) -> Array2<f64> {
let scores = q.dot(&k.t());
let scale = (self.d_k as f64).sqrt() * self.temperature;
&scores / scale
}
fn apply_mask(&self, scores: &Array2<f64>, mask: Option<&Array2<bool>>) -> Array2<f64> {
let mut masked_scores = scores.clone();
if let Some(mask) = mask {
for i in 0..scores.nrows() {
for j in 0..scores.ncols() {
if !mask[(i, j)] {
masked_scores[(i, j)] = f64::NEG_INFINITY;
}
}
}
}
masked_scores
}
fn apply_softmax(&self, scores: &Array2<f64>) -> Array2<f64> {
let mut result = Array2::zeros(scores.raw_dim());
for i in 0..scores.nrows() {
let row = scores.row(i);
let max_val = row.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let exp_row: Vec<f64> = row.iter().map(|&val| (val - max_val).exp()).collect();
let sum_exp: f64 = exp_row.iter().sum();
for (j, &exp_val) in exp_row.iter().enumerate() {
result[(i, j)] = exp_val / sum_exp;
}
}
result
}
fn apply_dropout(&self, attention_weights: &Array2<f64>) -> Array2<f64> {
if self.dropout_rate == 0.0 {
return attention_weights.clone();
}
let mut rng = Random::seed_from_u64(42);
let mut result = attention_weights.clone();
for elem in result.iter_mut() {
if rng.random_range(0.0..1.0) < self.dropout_rate {
*elem = 0.0;
} else {
*elem /= 1.0 - self.dropout_rate;
}
}
result
}
}
pub struct MultiHeadAttention {
pub d_model: usize,
pub n_heads: usize,
pub d_k: usize,
pub d_v: usize,
pub dropout_rate: f64,
pub w_q: Array2<f64>,
pub w_k: Array2<f64>,
pub w_v: Array2<f64>,
pub w_o: Array2<f64>,
}
impl MultiHeadAttention {
pub fn new(d_model: usize, n_heads: usize, dropout_rate: f64, random_state: Option<u64>) -> Self {
let d_k = d_model / n_heads;
let d_v = d_model / n_heads;
let w_q = xavier_normal_init(d_model, d_model, random_state);
let w_k = xavier_normal_init(d_model, d_model, random_state);
let w_v = xavier_normal_init(d_model, d_model, random_state);
let w_o = xavier_normal_init(d_model, d_model, random_state);
Self {
d_model,
n_heads,
d_k,
d_v,
dropout_rate,
w_q,
w_k,
w_v,
w_o,
}
}
pub fn forward(
&self,
q: &Array2<f64>,
k: &Array2<f64>,
v: &Array2<f64>,
mask: Option<&Array2<bool>>,
) -> (Array2<f64>, Vec<Array2<f64>>) {
let batch_size = q.nrows();
let q_proj = q.dot(&self.w_q);
let k_proj = k.dot(&self.w_k);
let v_proj = v.dot(&self.w_v);
let q_heads = self.split_heads(&q_proj, batch_size);
let k_heads = self.split_heads(&k_proj, batch_size);
let v_heads = self.split_heads(&v_proj, batch_size);
let mut head_outputs = Vec::new();
let mut attention_weights = Vec::new();
let scaled_attention = ScaledDotProductAttention::new(self.d_k, 1.0, self.dropout_rate);
for i in 0..self.n_heads {
let (head_output, head_attention) = scaled_attention.forward(
&q_heads[i],
&k_heads[i],
&v_heads[i],
mask,
);
head_outputs.push(head_output);
attention_weights.push(head_attention);
}
let concatenated = self.concatenate_heads(&head_outputs);
let output = concatenated.dot(&self.w_o);
(output, attention_weights)
}
fn split_heads(&self, tensor: &Array2<f64>, batch_size: usize) -> Vec<Array2<f64>> {
let seq_len = tensor.nrows();
let mut heads = Vec::new();
for head in 0..self.n_heads {
let start_idx = head * self.d_k;
let end_idx = start_idx + self.d_k;
let mut head_tensor = Array2::zeros((seq_len, self.d_k));
for i in 0..seq_len {
for j in 0..self.d_k {
head_tensor[(i, j)] = tensor[(i, start_idx + j)];
}
}
heads.push(head_tensor);
}
heads
}
fn concatenate_heads(&self, heads: &[Array2<f64>]) -> Array2<f64> {
let seq_len = heads[0].nrows();
let mut result = Array2::zeros((seq_len, self.d_model));
let mut col_offset = 0;
for head in heads {
let head_dim = head.ncols();
for i in 0..seq_len {
for j in 0..head_dim {
result[(i, col_offset + j)] = head[(i, j)];
}
}
col_offset += head_dim;
}
result
}
}
pub struct AdditiveAttention {
pub d_model: usize,
pub d_hidden: usize,
pub w_q: Array2<f64>,
pub w_k: Array2<f64>,
pub w_v: Array1<f64>,
pub dropout_rate: f64,
}
impl AdditiveAttention {
pub fn new(d_model: usize, d_hidden: usize, dropout_rate: f64, random_state: Option<u64>) -> Self {
let w_q = xavier_normal_init(d_model, d_hidden, random_state);
let w_k = xavier_normal_init(d_model, d_hidden, random_state);
let w_v = Array1::zeros(d_hidden);
Self {
d_model,
d_hidden,
w_q,
w_k,
w_v,
dropout_rate,
}
}
pub fn forward(
&self,
q: &Array2<f64>,
k: &Array2<f64>,
v: &Array2<f64>,
mask: Option<&Array2<bool>>,
) -> (Array2<f64>, Array2<f64>) {
let q_proj = q.dot(&self.w_q);
let k_proj = k.dot(&self.w_k);
let energy = self.compute_energy(&q_proj, &k_proj);
let masked_energy = self.apply_mask(&energy, mask);
let attention_weights = self.apply_softmax(&masked_energy);
let dropout_weights = self.apply_dropout(&attention_weights);
let output = dropout_weights.dot(v);
(output, attention_weights)
}
fn compute_energy(&self, q_proj: &Array2<f64>, k_proj: &Array2<f64>) -> Array2<f64> {
let q_len = q_proj.nrows();
let k_len = k_proj.nrows();
let mut energy = Array2::zeros((q_len, k_len));
for i in 0..q_len {
for j in 0..k_len {
let mut sum = 0.0;
for l in 0..self.d_hidden {
sum += self.w_v[l] * (q_proj[(i, l)] + k_proj[(j, l)]).tanh();
}
energy[(i, j)] = sum;
}
}
energy
}
fn apply_mask(&self, energy: &Array2<f64>, mask: Option<&Array2<bool>>) -> Array2<f64> {
let mut masked_energy = energy.clone();
if let Some(mask) = mask {
for i in 0..energy.nrows() {
for j in 0..energy.ncols() {
if !mask[(i, j)] {
masked_energy[(i, j)] = f64::NEG_INFINITY;
}
}
}
}
masked_energy
}
fn apply_softmax(&self, energy: &Array2<f64>) -> Array2<f64> {
let mut result = Array2::zeros(energy.raw_dim());
for i in 0..energy.nrows() {
let row = energy.row(i);
let max_val = row.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let exp_row: Vec<f64> = row.iter().map(|&val| (val - max_val).exp()).collect();
let sum_exp: f64 = exp_row.iter().sum();
for (j, &exp_val) in exp_row.iter().enumerate() {
result[(i, j)] = exp_val / sum_exp;
}
}
result
}
fn apply_dropout(&self, attention_weights: &Array2<f64>) -> Array2<f64> {
if self.dropout_rate == 0.0 {
return attention_weights.clone();
}
let mut rng = Random::seed_from_u64(42);
let mut result = attention_weights.clone();
for elem in result.iter_mut() {
if rng.random_range(0.0..1.0) < self.dropout_rate {
*elem = 0.0;
} else {
*elem /= 1.0 - self.dropout_rate;
}
}
result
}
}
pub struct SelfAttention {
pub attention: MultiHeadAttention,
}
impl SelfAttention {
pub fn new(d_model: usize, n_heads: usize, dropout_rate: f64, random_state: Option<u64>) -> Self {
Self {
attention: MultiHeadAttention::new(d_model, n_heads, dropout_rate, random_state),
}
}
pub fn forward(
&self,
x: &Array2<f64>,
mask: Option<&Array2<bool>>,
) -> (Array2<f64>, Vec<Array2<f64>>) {
self.attention.forward(x, x, x, mask)
}
}
pub struct CrossAttention {
pub attention: MultiHeadAttention,
}
impl CrossAttention {
pub fn new(d_model: usize, n_heads: usize, dropout_rate: f64, random_state: Option<u64>) -> Self {
Self {
attention: MultiHeadAttention::new(d_model, n_heads, dropout_rate, random_state),
}
}
pub fn forward(
&self,
q: &Array2<f64>,
kv: &Array2<f64>,
mask: Option<&Array2<bool>>,
) -> (Array2<f64>, Vec<Array2<f64>>) {
self.attention.forward(q, kv, kv, mask)
}
}
pub struct LocalAttention {
pub attention: ScaledDotProductAttention,
pub window_size: usize,
}
impl LocalAttention {
pub fn new(d_k: usize, window_size: usize, dropout_rate: f64) -> Self {
Self {
attention: ScaledDotProductAttention::new(d_k, 1.0, dropout_rate),
window_size,
}
}
pub fn forward(
&self,
q: &Array2<f64>,
k: &Array2<f64>,
v: &Array2<f64>,
) -> (Array2<f64>, Array2<f64>) {
let mask = self.create_local_mask(q.nrows(), k.nrows());
self.attention.forward(q, k, v, Some(&mask))
}
fn create_local_mask(&self, q_len: usize, k_len: usize) -> Array2<bool> {
let mut mask = Array2::from_elem((q_len, k_len), false);
for i in 0..q_len {
let start = if i >= self.window_size { i - self.window_size } else { 0 };
let end = (i + self.window_size + 1).min(k_len);
for j in start..end {
mask[(i, j)] = true;
}
}
mask
}
}
pub struct SparseAttention {
pub attention: ScaledDotProductAttention,
pub sparsity_pattern: String,
pub block_size: usize,
}
impl SparseAttention {
pub fn new(d_k: usize, sparsity_pattern: String, block_size: usize, dropout_rate: f64) -> Self {
Self {
attention: ScaledDotProductAttention::new(d_k, 1.0, dropout_rate),
sparsity_pattern,
block_size,
}
}
pub fn forward(
&self,
q: &Array2<f64>,
k: &Array2<f64>,
v: &Array2<f64>,
) -> (Array2<f64>, Array2<f64>) {
let mask = self.create_sparse_mask(q.nrows(), k.nrows());
self.attention.forward(q, k, v, Some(&mask))
}
fn create_sparse_mask(&self, q_len: usize, k_len: usize) -> Array2<bool> {
match self.sparsity_pattern.as_str() {
"strided" => self.create_strided_mask(q_len, k_len),
"fixed" => self.create_fixed_mask(q_len, k_len),
"random" => self.create_random_mask(q_len, k_len),
_ => Array2::from_elem((q_len, k_len), true),
}
}
fn create_strided_mask(&self, q_len: usize, k_len: usize) -> Array2<bool> {
let mut mask = Array2::from_elem((q_len, k_len), false);
for i in 0..q_len {
for j in (0..k_len).step_by(self.block_size) {
if j < k_len {
mask[(i, j)] = true;
}
}
}
mask
}
fn create_fixed_mask(&self, q_len: usize, k_len: usize) -> Array2<bool> {
let mut mask = Array2::from_elem((q_len, k_len), false);
let num_blocks = (k_len + self.block_size - 1) / self.block_size;
for i in 0..q_len {
let block_idx = i / self.block_size;
let start = block_idx * self.block_size;
let end = (start + self.block_size).min(k_len);
for j in start..end {
mask[(i, j)] = true;
}
if block_idx > 0 {
let prev_start = (block_idx - 1) * self.block_size;
let prev_end = (prev_start + self.block_size).min(k_len);
for j in prev_start..prev_end {
mask[(i, j)] = true;
}
}
}
mask
}
fn create_random_mask(&self, q_len: usize, k_len: usize) -> Array2<bool> {
let mut rng = Random::seed_from_u64(42);
let mut mask = Array2::from_elem((q_len, k_len), false);
let sparsity = 0.1;
let num_true = (q_len * k_len as f64 * sparsity) as usize;
for _ in 0..num_true {
let i = rng.gen_range(0..q_len);
let j = rng.gen_range(0..k_len);
mask[(i, j)] = true;
}
mask
}
}
pub fn create_causal_mask(seq_len: usize) -> Array2<bool> {
let mut mask = Array2::from_elem((seq_len, seq_len), false);
for i in 0..seq_len {
for j in 0..=i {
mask[(i, j)] = true;
}
}
mask
}
pub fn create_padding_mask(seq_lengths: &[usize], max_len: usize) -> Array2<bool> {
let batch_size = seq_lengths.len();
let mut mask = Array2::from_elem((batch_size, max_len), false);
for (i, &length) in seq_lengths.iter().enumerate() {
for j in 0..length.min(max_len) {
mask[(i, j)] = true;
}
}
mask
}
pub fn create_lookahead_mask(seq_len: usize, lookahead: usize) -> Array2<bool> {
let mut mask = Array2::from_elem((seq_len, seq_len), false);
for i in 0..seq_len {
let start = if i >= lookahead { i - lookahead } else { 0 };
let end = (i + lookahead + 1).min(seq_len);
for j in start..end {
mask[(i, j)] = true;
}
}
mask
}
pub fn attention_entropy(attention_weights: &Array2<f64>) -> Array1<f64> {
let seq_len = attention_weights.nrows();
let mut entropy = Array1::zeros(seq_len);
for i in 0..seq_len {
let mut h = 0.0;
for j in 0..attention_weights.ncols() {
let p = attention_weights[(i, j)];
if p > 0.0 {
h -= p * p.ln();
}
}
entropy[i] = h;
}
entropy
}
pub fn attention_variance(attention_weights: &Array2<f64>) -> Array1<f64> {
let seq_len = attention_weights.nrows();
let mut variance = Array1::zeros(seq_len);
for i in 0..seq_len {
let mean = attention_weights.row(i).mean().unwrap_or(0.0);
let mut var = 0.0;
for j in 0..attention_weights.ncols() {
let diff = attention_weights[(i, j)] - mean;
var += diff * diff;
}
variance[i] = var / attention_weights.ncols() as f64;
}
variance
}