(*
* lablai - An ML Artificial Inteligence library
* Copyright (C) 2006 Till Crueger
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*)
(* File $RCSfile$ *)
(* last edited by $Author: till_crueger $ *)
(* $Date: 2008-01-13 03:46:55 +0100 (So, 13 Jan 2008) $, $Revision: 39 $ *)
type transfer = Sigmoid | Linear | Zero;;
type neuron = float array * transfer;;
type layer = neuron array;;
type t = layer list;;
let empty : t = [];;
let weight_init = ref 1.0;;
let sigmoid x = 1.0 /. (1.0+.exp(-.x));;
let linear x = x;;
let zero _ = 0.0;;
let apply transfer =
match transfer with
Sigmoid -> sigmoid
| Linear -> linear
| Zero -> zero
;;
(* careful, this returns the derivative in terms of the output of the original function *)
let derive transfer =
match transfer with
Sigmoid -> (fun x -> x *. (1.0 -. x))
| Linear -> (fun _ -> 1.0)
| Zero -> (fun _ -> 0.0)
;;
let make_neuron num_syns transfer =
Array.init (num_syns+1)
(fun _ -> (Random.float (!weight_init*.2.0)) -. !weight_init),
transfer
;;
let make_layer num_in num_out transfer =
Array.init num_out ( fun _ -> make_neuron num_in transfer)
;;
let make_net ins outs hidden transfers =
let size = ins :: hidden @ [outs] in
let rec loop size transfers =
if List.length size -1 != List.length transfers then
raise (Invalid_argument "Layouts do not match")
else
match size with
n :: m :: size -> (make_layer n m (List.hd transfers))
:: (loop (m::size) (List.tl transfers))
| _ -> []
in
loop size transfers
;;
let make_classifier ins outs hidden =
let transfers = Sigmoid :: (List.rev_map (fun _ -> Sigmoid) hidden) in
make_net ins outs hidden transfers
;;
let make_approximator ins outs hidden =
let transfers = List.rev_map (fun _ -> Sigmoid) hidden in
make_net ins outs hidden (transfers @ [Linear])
;;
let copy net =
List.map (Array.map (fun (weights,transfer) -> (Array.copy weights,transfer))) net
;;
let get_synapses (weights,_) =
weights
;;
let get_transfer (_,transfer) =
transfer
;;
let get_in_size net =
let neurons = List.hd net in
Array.length (get_synapses neurons.(0)) -1
;;
let get_out_size net =
let neurons = List.hd (List.rev net) in
Array.length neurons
;;
let get_depth =
List.length
;;
let conc_in_place net1 net2 =
if get_out_size net1 != get_in_size net2 then
raise (Invalid_argument "Net size does not match")
else
net1 @ net2
;;
let conc net1 net2 =
conc_in_place (copy net1) (copy net2)
;;
let ( *| ) (v1,transfer) v2 =
let tmp = ref 0.0 in
for i = 0 to (Array.length v1) -1 do
tmp := !tmp +. Array.unsafe_get v1 i *. Array.unsafe_get v2 i
done;
(apply transfer) !tmp
;;
let ( |*| ) mat vec =
let res = Array.make (Array.length mat) 0.0 in
for i = 0 to Array.length mat -1 do
Array.unsafe_set res i ((Array.unsafe_get mat i) *| vec)
done;
res
;;
let evaluate net input =
(* helper to do the processing of all layers *)
let rec loop rep activation =
(* get the current layer *)
match rep with
neurons :: rest_rep ->
begin
if Array.length (get_synapses neurons.(0)) != Array.length activation +1 then
raise (Invalid_argument "Net size does not match input")
else ();
(* add the bias *)
let activation = Array.append [|1.0|] activation in
(* calculate the weighted sums *)
let output = neurons |*| activation in
(* descend to next layer *)
loop rest_rep output
end
| [] -> activation (* we have reached the output layer -> return *)
in
(* apply inputs to all layers *)
loop net input
;;
let train_in_place alpha ?(decay=0.0) () net (input,example) =
(* unrolled loop for the training *)
let loop rep activation =
(* this loop handles layers 2 to n *)
let rec loop2 rep activation =
match rep with
neurons :: rest_rep ->
begin
if Array.length (get_synapses neurons.(0)) != Array.length activation +1 then
raise (Invalid_argument "Net size does not match input")
else ();
(* add the bias *)
let activation = Array.append [|1.0|] activation in
(* calculate the outputs*)
let output = neurons |*| activation in
(* descend to next layer and get weighted sums of deltas from there*)
let delta_sums = loop2 rest_rep output in
(* calculate deltas from those sums *)
let deltas = Array.mapi (fun i x -> (derive (get_transfer neurons.(i)) output.(i))*.x) delta_sums in
(* create array to hold new deltas*)
let new_deltas = Array.make (Array.length activation) 0.0 in
(* calculate the differences*)
let n = Array.length neurons in
let m = Array.length (get_synapses neurons.(0)) in
for i = 0 to n - 1 do
let weightsi = get_synapses neurons.(i) in
let deltai = deltas.(i) in
for j = 0 to m - 1 do
let weightij = Array.unsafe_get weightsi j in
let change = alpha *. deltai *. Array.unsafe_get activation j -. decay*.weightij in
(* get this part of the corresponding new_delta *)
Array.unsafe_set new_deltas j (Array.unsafe_get new_deltas j +. deltai *. weightij);
Array.unsafe_set weightsi j (weightij +. change);
done
done;
(* pass the deltas to caller, but throw away the one for the bias *)
Array.sub new_deltas 1 (Array.length activation - 1)
end
| [] -> Array.mapi (fun i x -> example.(i) -. x) activation
in
(* Special faster case for the first (and most often largest) layer *)
match rep with
neurons :: rest_rep ->
begin
if Array.length (get_synapses neurons.(0)) != Array.length activation +1 then
raise (Invalid_argument "Net size does not match input")
else ();
(* add the bias *)
let activation = Array.append [|1.0|] activation in
(* calculate the weighted sums *)
let output = neurons |*| activation in
(* descend to next layer and get weighted sums of deltas from there*)
let delta_sums = loop2 rest_rep output in
(* calculate deltas from those sums *)
let deltas = Array.mapi (fun i x -> (derive (get_transfer neurons.(i)) output.(i))*.x) delta_sums in
(* calculate the differences*)
let n = Array.length neurons in
let m = Array.length (get_synapses neurons.(0)) in
for i = 0 to n - 1 do
let weightsi = get_synapses neurons.(i) in
let deltai = Array.unsafe_get deltas i in
for j = 0 to m - 1 do
let weightij = Array.unsafe_get weightsi j in
let change = alpha *. deltai *. Array.unsafe_get activation j -. decay*.weightij in
(* get this part of the corresponding new_delta *)
Array.unsafe_set weightsi j (weightij +. change);
done
done
(* from the first layer nothing has to be returned *)
end
| [] ->
()
in loop net input
;;
let train alpha ?decay () net (input,example) =
let new_net = copy net in
train_in_place alpha ?decay () new_net (input,example) ;
new_net
;;
let squared_error net (input,example) =
let est = evaluate net input in
let sum = ref 0.0 in
for i = 0 to Array.length example -1 do
sum := !sum +. ((example.(i)-.est.(i))**2.0)
done;
!sum +. 0.0
;;
(*
* $Log$
* Revision 1.8 2008/01/13 02:46:55 till_crueger
* - Added more types of special teachers to build more complex teaching tasks
*
* Revision 1.7 2008/01/12 17:15:07 till_crueger
*
* - Added possibilities for a weight decay term for MLPs
*
* Revision 1.5 2008/01/12 14:17:21 till_crueger
* - Changed teacher structure to add a framework for exams after each epoche
*
* Revision 1.4 2008/01/11 14:25:50 till_crueger
* - Changed teacher Interface to be able to combine different teachers
*
* Revision 1.3 2007/12/19 01:17:56 till_crueger
*
* - Changed Interface for MLPs
* - Fixed some small bugs for MLPs as approximators
* - Added q-Learner that uses an MLP as State-Value-Function
*
* Revision 1.2 2007/12/17 23:27:29 till_crueger
*
* - Added generic teacher for all kinds of supervised learning mechanism
*
* Revision 1.1 2007/12/17 22:10:54 till_crueger
*
* - Added module for non-object-oriented Multi-Layer-Perceptrons
*
*)