Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Model input types #31

Merged
merged 4 commits into from
Sep 27, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/iris.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ extern crate csv;
extern crate vikos;
extern crate rustc_serialize;

use vikos::{Teacher, Model};
use vikos::{Teacher, Expert};
use std::default::Default;

const PATH : &'static str = "examples/data/iris.csv";
Expand Down
6 changes: 3 additions & 3 deletions examples/mean.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
extern crate vikos;
use vikos::{model, cost, teacher, learn_history};
use vikos::{cost, teacher, learn_history};

fn main() {

// mean is 9, but of course we do not know that yet
let history = [1.0, 3.0, 4.0, 7.0, 8.0, 11.0, 29.0];
// The mean is just a simple number ...
let mut model = model::Constant::new(0.0);
let mut model = 0.0;
// ... which minimizes the square error
let cost = cost::LeastSquares {};
// Use stochasic gradient descent with an annealed learning rate
Expand All @@ -18,5 +18,5 @@ fn main() {
&mut model,
history.iter().cycle().take(100).map(|&y| ((), y)));
// Since we know the model's type is `Constant`, we could just access the members
println!("{}", model.c);
println!("{}", model);
}
54 changes: 24 additions & 30 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,6 @@
//! independently of the model trained or the cost function that is meant to
//! be minimized. To get started right away, you may want to
//! have a look at the [tutorial](./tutorial/index.html).
//!
//! # Design
//! The three most important traits are [Model], [Cost] and [Teacher].
//!
//! [Model]: ./trait.Model.html
//! [Cost]: ./trait.Cost.html
//! [Teacher]: ./trait.Teacher.html

#![warn(missing_docs)]
#![cfg_attr(feature="clippy", feature(plugin))]
Expand All @@ -21,27 +14,27 @@ extern crate num;

use std::iter::IntoIterator;

/// A Model is a parameterized expert algorithm
///
/// Implementations of this trait can be found in
/// [models](./model/index.html)
/// Allows accessing and changing coefficents
pub trait Model {
/// Input features
type Input;

/// Predicts a target for the inputs based on the internal coefficents
fn predict(&self, &Self::Input) -> f64;

/// The number of internal coefficents this model depends on
fn num_coefficents(&self) -> usize;

/// Value predict derived by the n-th `coefficent` at `input`
fn gradient(&self, coefficent: usize, input: &Self::Input) -> f64;

/// Mutable reference to the n-th `coefficent`
fn coefficent(&mut self, coefficent: usize) -> &mut f64;
}

/// A parameterized expert algorithm
///
/// Implementations of this trait can be found in
/// [models](./model/index.html)
pub trait Expert<X>: Model {
/// Predicts a target for the inputs based on the internal coefficents
fn predict(&self, &X) -> f64;

/// Value predict derived by the n-th `coefficent` at `input`
fn gradient(&self, coefficent: usize, input: &X) -> f64;
}

/// Representing a cost function whose value is supposed be minimized by the
/// training algorithm.
///
Expand Down Expand Up @@ -91,22 +84,23 @@ pub trait Teacher<M: Model> {
fn new_training(&self, model: &M) -> Self::Training;

/// Changes `model`s coefficents so they minimize the `cost` function (hopefully)
fn teach_event<Y, C>(&self,
training: &mut Self::Training,
model: &mut M,
cost: &C,
features: &M::Input,
truth: Y)
fn teach_event<X, Y, C>(&self,
training: &mut Self::Training,
model: &mut M,
cost: &C,
features: &X,
truth: Y)
where C: Cost<Y>,
Y: Copy;
Y: Copy,
M: Expert<X>;
}

/// Teaches `model` all events in `history`
pub fn learn_history<M, C, T, H, Truth>(teacher: &T, cost: &C, model: &mut M, history: H)
where M: Model,
pub fn learn_history<X, M, C, T, H, Truth>(teacher: &T, cost: &C, model: &mut M, history: H)
where M: Expert<X>,
C: Cost<Truth>,
T: Teacher<M>,
H: IntoIterator<Item = (M::Input, Truth)>,
H: IntoIterator<Item = (X, Truth)>,
Truth: Copy
{
let mut training = teacher.new_training(model);
Expand Down
140 changes: 50 additions & 90 deletions src/model.rs
Original file line number Diff line number Diff line change
@@ -1,64 +1,23 @@
use Model;
use Expert;
use linear_algebra::Vector;
use std::marker::PhantomData;

/// Models the target as a constant `c`
///
/// This model predicts a number. The cost function used during training decides
/// whether this number is a mean, median, or something else.
///
/// # Examples
///
/// Estimate mean
///
/// ```
/// use vikos::model::Constant;
/// use vikos::cost::LeastSquares;
/// use vikos::teacher::GradientDescentAl;
/// use vikos::learn_history;
///
/// let features = ();
/// let history = [1f64, 3.0, 4.0, 7.0, 8.0, 11.0, 29.0]; //mean is 9
///
/// let cost = LeastSquares{};
/// let mut model = Constant::new(0.0);
///
/// let teacher = GradientDescentAl{ l0 : 0.3, t : 4.0 };
/// learn_history(&teacher, &cost, &mut model, history.iter().cycle().map(|&y|((),y)).take(100));
/// println!("{}", model.c);
/// ```
#[derive(Debug, Default, RustcDecodable, RustcEncodable)]
pub struct Constant<Input> {
/// Any prediction made by this model will have the value of `c`
pub c: f64,
_phantom: PhantomData<Input>,
}

impl<I> Constant<I> {
/// Creates a new Constant from a `f64`
pub fn new(c: f64) -> Constant<I> {
Constant {
c: c,
_phantom: PhantomData::<I> {},
}
impl Model for f64 {
fn num_coefficents(&self) -> usize {
1
}
}

impl<I> Clone for Constant<I> {
fn clone(&self) -> Self {
Constant::new(self.c)
fn coefficent(&mut self, coefficent: usize) -> &mut f64 {
match coefficent {
0 => self,
_ => panic!("coefficent index out of range"),
}
}
}

impl<I> Model for Constant<I> {
type Input = I;

impl<I> Expert<I> for f64 {
fn predict(&self, _: &I) -> f64 {
self.c
}

fn num_coefficents(&self) -> usize {
1
*self
}

fn gradient(&self, coefficent: usize, _: &I) -> f64 {
Expand All @@ -67,13 +26,6 @@ impl<I> Model for Constant<I> {
_ => panic!("coefficent index out of range"),
}
}

fn coefficent(&mut self, coefficent: usize) -> &mut f64 {
match coefficent {
0 => &mut self.c,
_ => panic!("coefficent index out of range"),
}
}
}

/// Models the target as `y = m * x + c`
Expand All @@ -88,14 +40,24 @@ pub struct Linear<V: Vector> {
impl<V> Model for Linear<V>
where V: Vector<Scalar = f64>
{
type Input = V;
fn num_coefficents(&self) -> usize {
self.m.dimension() + 1
}

fn predict(&self, input: &V) -> V::Scalar {
self.m.dot(input) + self.c
fn coefficent(&mut self, coefficent: usize) -> &mut V::Scalar {
if coefficent == self.m.dimension() {
&mut self.c
} else {
self.m.mut_at(coefficent)
}
}
}

fn num_coefficents(&self) -> usize {
self.m.dimension() + 1
impl<V> Expert<V> for Linear<V>
where V: Vector<Scalar = f64>
{
fn predict(&self, input: &V) -> V::Scalar {
self.m.dot(input) + self.c
}

fn gradient(&self, coefficent: usize, input: &V) -> V::Scalar {
Expand All @@ -108,14 +70,6 @@ impl<V> Model for Linear<V>
input.at(coefficent)
}
}

fn coefficent(&mut self, coefficent: usize) -> &mut V::Scalar {
if coefficent == self.m.dimension() {
&mut self.c
} else {
self.m.mut_at(coefficent)
}
}
}

/// Models target as `y = 1/(1+e^(m * x + c))`
Expand All @@ -125,24 +79,26 @@ pub struct Logistic<V: Vector>(Linear<V>);
impl<V> Model for Logistic<V>
where V: Vector<Scalar = f64>
{
type Input = V;
fn num_coefficents(&self) -> usize {
self.0.num_coefficents()
}

fn predict(&self, input: &V) -> f64 {
1.0 / (1.0 + self.0.predict(input).exp())
fn coefficent(&mut self, coefficent: usize) -> &mut f64 {
self.0.coefficent(coefficent)
}
}

fn num_coefficents(&self) -> usize {
self.0.num_coefficents()
impl<V> Expert<V> for Logistic<V>
where V: Vector<Scalar = f64>
{
fn predict(&self, input: &V) -> f64 {
1.0 / (1.0 + self.0.predict(input).exp())
}

fn gradient(&self, coefficent: usize, input: &V) -> f64 {
let p = self.predict(input);
-p * (1.0 - p) * self.0.gradient(coefficent, input)
}

fn coefficent(&mut self, coefficent: usize) -> &mut f64 {
self.0.coefficent(coefficent)
}
}

/// Models the target as `y = g(m*x + c)`
Expand Down Expand Up @@ -197,23 +153,27 @@ impl<V, F, Df> Model for GeneralizedLinearModel<V, F, Df>
Df: Fn(f64) -> f64,
V: Vector<Scalar = f64>
{
type Input = V;
fn num_coefficents(&self) -> usize {
self.linear.num_coefficents()
}

fn coefficent(&mut self, coefficent: usize) -> &mut f64 {
self.linear.coefficent(coefficent)
}
}

impl<V, F, Df> Expert<V> for GeneralizedLinearModel<V, F, Df>
where F: Fn(f64) -> f64,
Df: Fn(f64) -> f64,
V: Vector<Scalar = f64>
{
fn predict(&self, input: &V) -> f64 {
let f = &self.g;
f(self.linear.predict(&input))
}

fn num_coefficents(&self) -> usize {
self.linear.num_coefficents()
}

fn gradient(&self, coefficent: usize, input: &V) -> f64 {
let f = &self.g_derivate;
f(self.linear.predict(&input)) * self.linear.gradient(coefficent, input)
}

fn coefficent(&mut self, coefficent: usize) -> &mut f64 {
self.linear.coefficent(coefficent)
}
}