///|
/// Checks that all parameters define a finite, usable kernel.
pub fn validate_kernel(kernel : Kernel) -> Result[Unit, SvmError] {
match kernel {
Linear => Ok(())
Rbf(gamma) =>
if !finite_double(gamma) || gamma <= 0.0 {
Err(InvalidKernelParameter("gamma", gamma))
} else {
Ok(())
}
Polynomial(gamma, coefficient, degree) => {
if !finite_double(gamma) || gamma <= 0.0 {
return Err(InvalidKernelParameter("gamma", gamma))
}
if !finite_double(coefficient) {
return Err(InvalidKernelParameter("coefficient", coefficient))
}
if degree <= 0 {
return Err(InvalidKernelParameter("degree", degree.to_double()))
}
Ok(())
}
}
}
///|
fn checked_vectors(
left : Array[Double],
right : Array[Double],
) -> Result[Unit, SvmError] {
if left.length() != right.length() {
return Err(KernelDimensionMismatch(left.length(), right.length()))
}
for index, value in left {
if !finite_double(value) {
return Err(NonFiniteFeature(0, index))
}
}
for index, value in right {
if !finite_double(value) {
return Err(NonFiniteFeature(1, index))
}
}
Ok(())
}
///|
fn dot_product(left : Array[Double], right : Array[Double]) -> Double {
let mut total = 0.0
for index = 0; index < left.length(); index = index + 1 {
total = total + left[index] * right[index]
}
total
}
///|
fn squared_distance(left : Array[Double], right : Array[Double]) -> Double {
let mut total = 0.0
for index = 0; index < left.length(); index = index + 1 {
let difference = left[index] - right[index]
total = total + difference * difference
}
total
}
///|
fn unchecked_kernel_value(
kernel : Kernel,
left : Array[Double],
right : Array[Double],
) -> Double {
match kernel {
Linear => dot_product(left, right)
Rbf(gamma) => @math.exp(-gamma * squared_distance(left, right))
Polynomial(gamma, coefficient, degree) =>
@math.pow(
gamma * dot_product(left, right) + coefficient,
degree.to_double(),
)
}
}
///|
/// Evaluates a checked kernel on two finite vectors of equal length.
pub fn kernel_value(
kernel : Kernel,
left : Array[Double],
right : Array[Double],
) -> Result[Double, SvmError] {
match validate_kernel(kernel) {
Err(error) => return Err(error)
Ok(_) => ()
}
match checked_vectors(left, right) {
Err(error) => return Err(error)
Ok(_) => ()
}
let value = unchecked_kernel_value(kernel, left, right)
if !finite_double(value) {
return Err(NumericFailure("kernel evaluation"))
}
Ok(value)
}
///|
/// Builds a symmetric kernel matrix for a validated dataset.
pub fn kernel_matrix(
kernel : Kernel,
data : Dataset,
) -> Result[Array[Array[Double]], SvmError] {
match validate_kernel(kernel) {
Err(error) => return Err(error)
Ok(_) => ()
}
let rows = data.features()
let count = rows.length()
let matrix : Array[Array[Double]] = Array::makei(count, fn(_) {
Array::make(count, 0.0)
})
for left = 0; left < count; left = left + 1 {
for right = left; right < count; right = right + 1 {
let value = unchecked_kernel_value(kernel, rows[left], rows[right])
if !finite_double(value) {
return Err(NumericFailure("kernel matrix construction"))
}
matrix[left][right] = value
matrix[right][left] = value
}
}
Ok(matrix)
}