use candle_core::{Module, Result, Tensor};
use candle_nn::{conv1d, embedding, linear, Conv1d, Conv1dConfig, Embedding, Linear, VarBuilder};
pub struct CharVocab {
char_to_idx: std::collections::HashMap<char, u32>,
vocab_size: usize,
}
impl CharVocab {
pub fn new() -> Self {
let mut char_to_idx = std::collections::HashMap::new();
let mut idx = 1u32;
for c in 'a'..='z' {
char_to_idx.insert(c, idx);
idx += 1;
}
for c in 'A'..='Z' {
char_to_idx.insert(c, idx);
idx += 1;
}
for c in '0'..='9' {
char_to_idx.insert(c, idx);
idx += 1;
}
for c in " .-_@:/\\#$%&*+='\"<>()[]{}|~`!?,;^".chars() {
char_to_idx.insert(c, idx);
idx += 1;
}
let vocab_size = idx as usize + 1;
Self {
char_to_idx,
vocab_size,
}
}
pub fn encode(&self, text: &str, max_len: usize) -> Vec<u32> {
let mut ids = Vec::with_capacity(max_len);
for c in text.chars().take(max_len) {
let idx = self
.char_to_idx
.get(&c)
.copied()
.unwrap_or(self.vocab_size as u32 - 1);
ids.push(idx);
}
while ids.len() < max_len {
ids.push(0); }
ids
}
pub fn vocab_size(&self) -> usize {
self.vocab_size
}
}
impl Default for CharVocab {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CharCnnConfig {
pub vocab_size: usize,
pub max_seq_length: usize,
pub embed_dim: usize,
pub num_filters: usize,
pub kernel_sizes: Vec<usize>,
pub hidden_dim: usize,
pub n_classes: usize,
pub dropout: f64,
}
impl Default for CharCnnConfig {
fn default() -> Self {
Self {
vocab_size: 100,
max_seq_length: 128,
embed_dim: 32,
num_filters: 64,
kernel_sizes: vec![2, 3, 4, 5],
hidden_dim: 128,
n_classes: 100,
dropout: 0.3,
}
}
}
pub struct CharCnn {
char_embedding: Embedding,
convs: Vec<Conv1d>,
fc1: Linear,
fc2: Linear,
config: CharCnnConfig,
}
impl CharCnn {
pub fn new(config: CharCnnConfig, vb: VarBuilder) -> Result<Self> {
let char_embedding = embedding(config.vocab_size, config.embed_dim, vb.pp("char_emb"))?;
let mut convs = Vec::with_capacity(config.kernel_sizes.len());
for (i, &kernel_size) in config.kernel_sizes.iter().enumerate() {
let conv_config = Conv1dConfig {
padding: 0,
stride: 1,
dilation: 1,
groups: 1,
};
let conv = conv1d(
config.embed_dim,
config.num_filters,
kernel_size,
conv_config,
vb.pp(format!("conv_{}", i)),
)?;
convs.push(conv);
}
let total_filters = config.num_filters * config.kernel_sizes.len();
let fc1 = linear(total_filters, config.hidden_dim, vb.pp("fc1"))?;
let fc2 = linear(config.hidden_dim, config.n_classes, vb.pp("fc2"))?;
Ok(Self {
char_embedding,
convs,
fc1,
fc2,
config,
})
}
pub fn forward(&self, input_ids: &Tensor) -> Result<Tensor> {
let (_batch_size, _seq_len) = input_ids.dims2()?;
let emb = self.char_embedding.forward(input_ids)?;
let emb = emb.transpose(1, 2)?;
let mut pooled_outputs = Vec::with_capacity(self.convs.len());
for conv in &self.convs {
let conv_out = conv.forward(&emb)?;
let conv_out = conv_out.relu()?;
let pooled = conv_out.max(2)?; pooled_outputs.push(pooled);
}
let concat = Tensor::cat(&pooled_outputs, 1)?;
let hidden = self.fc1.forward(&concat)?;
let hidden = hidden.relu()?;
let logits = self.fc2.forward(&hidden)?;
Ok(logits)
}
pub fn infer(&self, input_ids: &Tensor) -> Result<Tensor> {
let logits = self.forward(input_ids)?;
candle_nn::ops::softmax(&logits, 1)
}
pub fn config(&self) -> &CharCnnConfig {
&self.config
}
}