(* File: c45_io.ml AIFAD - Automated Induction of Functions over Algebraic Datatypes Author: Markus Mottl email: markus.mottl@gmail.com WWW: http://www.ocaml.info Copyright (C) 2002 Austrian Research Institute for Artificial Intelligence Copyright (C) 2003- Markus Mottl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *) (* $Id: c45_io.ml,v 1.35 2006/01/17 00:23:37 mottl Exp $ *) open Printf open Pcre open Utils open Algdt_types open Algdt_utils open Model_data open Data_io module StrSet = Set.Make (struct type t = string let compare = compare end) let hash_size = 541 let flags = [`DOTALL] (* makes matching faster *) let comma_rex = regexp ~flags "\\s*(?:,|\\.)\\s*" let def_rex = regexp ~flags "^(\\w+):\\s*(\\w+(?:\\s*,\\s*\\w+)*)\\s*\\.\\s*$" let cmp_inv_str_len s1 s2 = compare (String.length s2) (String.length s1) let read_c45_spec names = let failwith msg = failwith (sprintf "read_c45_spec:%s: %s" names msg) in let var_set = ref StrSet.empty in let var_lst = ref [] in let n_lines_ref = ref 1 in let rev_pat_lst = ref [] in let target_attr_lst = do_open_in names (fun names_ic -> let target_line = input_line names_ic in foreach_line ~ic:names_ic (fun line -> incr n_lines_ref; match extract ~full_match:false ~rex:def_rex line with | [| name; "continuous" |] -> eprintf "read_c45_spec: \ ignoring variable %s in line (data column): %s:%d\n" name names !n_lines_ref; flush stderr; rev_pat_lst := "[^,]+" :: !rev_pat_lst | [| name; rhs |] -> if StrSet.mem name !var_set then failwith ("variable appears twice: " ^ name); var_set := StrSet.add name !var_set; let attrs = split ~rex:comma_rex rhs in var_lst := (name, Array.of_list attrs) :: !var_lst; let pat = ["("; String.concat "|" attrs; "|\\?)"] in rev_pat_lst := String.concat "" pat :: !rev_pat_lst | x -> assert false (* impossible *)); let lix = String.length target_line - 1 in let target_line = String.sub target_line 0 lix in let target_attr_lst = split ~rex:comma_rex target_line in if target_attr_lst = [] then failwith "no target attributes" else target_attr_lst) in let input_pat = String.concat "," (List.rev !rev_pat_lst) in let target_attrs = Array.of_list target_attr_lst in let target_pat_lst = ["("; String.concat "|" target_attr_lst; "|\\?)"] in let target_pat = String.concat "" target_pat_lst in let data_pat = if input_pat = "" then target_pat else String.concat "," [ input_pat; target_pat ] in let tp_names = Array.of_list (List.rev !var_lst) in tp_names, target_attrs, data_pat, input_pat let make_start_prod_el n = TpVal (n + 1) let target_attr_name = "__target" let missing = "Missing__" let value = "Value__" let __maybe_sample = "__maybe_sample" let __sample = "__sample" let maybe_ = "maybe_" let calc_dispec_info mv (tp_names, _, _, _) = let n_vars = Array.length tp_names in match mv with | Ignore -> let n_vars1 = n_vars + 1 in let dtp_tbl = Array.make n_vars1 "" in let dcnstr_tbl = Array.make n_vars1 [||] in let dtp_htbl = Hashtbl.create hash_size in let dcnstr_htbl = Hashtbl.create hash_size in let dtp = ProdEls (Array.init n_vars make_start_prod_el) in let dispec = Array.make n_vars1 (Prod dtp) in let acti_defs ix (tp_name, attrs) = let tp = ix + 1 in dtp_tbl.(tp) <- tp_name; dcnstr_tbl.(tp) <- attrs; Hashtbl.add dtp_htbl tp_name tp; let acti_cnstrs cnstr cnstr_name = Hashtbl.add dcnstr_htbl (tp, cnstr_name) cnstr in Array.iteri acti_cnstrs attrs; dispec.(tp) <- Sums (Array.make (Array.length attrs) IAtom) in Array.iteri acti_defs tp_names; { tp_tbl = dtp_tbl; cnstr_tbl = dcnstr_tbl; tp_htbl = dtp_htbl; cnstr_htbl = dcnstr_htbl; ispec = dispec; } | MProb -> let n_vars1 = n_vars + 1 in let n_vars2 = n_vars1 + 1 in let dtp_tbl = Array.make n_vars2 "" in dtp_tbl.(n_vars1) <- __maybe_sample; let dcnstr_tbl = Array.make n_vars2 [||] in dcnstr_tbl.(n_vars1) <- [| missing; value; |]; let dtp_htbl = Hashtbl.create hash_size in Hashtbl.add dtp_htbl __maybe_sample n_vars1; let dcnstr_htbl = Hashtbl.create hash_size in Hashtbl.add dcnstr_htbl (n_vars1, missing) 0; Hashtbl.add dcnstr_htbl (n_vars1, value) 1; let dispec = Array.make n_vars2 (Prod (TpVal n_vars1)) in let attr_prod = Array.init n_vars make_start_prod_el in dispec.(n_vars1) <- Sums [| IAtom; IStrct (ProdEls attr_prod); |]; let acti_defs ix (tp_name, attrs) = let tp = ix + 1 in dtp_tbl.(tp) <- tp_name; dcnstr_tbl.(tp) <- attrs; Hashtbl.add dtp_htbl tp_name tp; let acti_cnstrs cnstr cnstr_name = Hashtbl.add dcnstr_htbl (tp, cnstr_name) cnstr in Array.iteri acti_cnstrs attrs; dispec.(tp) <- Sums (Array.make (Array.length attrs) IAtom) in Array.iteri acti_defs tp_names; { tp_tbl = dtp_tbl; cnstr_tbl = dcnstr_tbl; tp_htbl = dtp_htbl; cnstr_htbl = dcnstr_htbl; ispec = dispec; } | Flat -> let n_vars1 = n_vars + 1 in let dtp_tbl = Array.make n_vars1 "" in dtp_tbl.(n_vars) <- __sample; let dcnstr_tbl = Array.make n_vars1 [||] in let dtp_htbl = Hashtbl.create hash_size in Hashtbl.add dtp_htbl __sample n_vars; let dcnstr_htbl = Hashtbl.create hash_size in let dtp = ProdEls (Array.init n_vars make_start_prod_el) in let dispec = Array.make n_vars1 (Prod dtp) in let attr_prod = Array.init n_vars make_start_prod_el in dispec.(n_vars) <- Prod (ProdEls attr_prod); let acti_defs ix (tp_name, attrs) = let tp = ix + 1 in dtp_tbl.(tp) <- tp_name; dcnstr_tbl.(tp) <- Array.append [| missing |] attrs; Hashtbl.add dtp_htbl tp_name tp; let acti_cnstrs cnstr cnstr_name = Hashtbl.add dcnstr_htbl (tp, cnstr_name) (cnstr + 1) in Array.iteri acti_cnstrs attrs; Hashtbl.add dcnstr_htbl (tp, missing) 0; dispec.(tp) <- Sums (Array.make (Array.length attrs + 1) IAtom) in Array.iteri acti_defs tp_names; { tp_tbl = dtp_tbl; cnstr_tbl = dcnstr_tbl; tp_htbl = dtp_htbl; cnstr_htbl = dcnstr_htbl; ispec = dispec; } | LiftAll -> let n_vars2 = n_vars * 2 in let n_vars21 = n_vars2 + 1 in let n_vars1 = n_vars + 1 in let dtp_tbl = Array.make n_vars21 "" in let dcnstr_tbl = Array.make n_vars21 [| missing; value; |] in dcnstr_tbl.(0) <- [||]; let dtp_htbl = Hashtbl.create hash_size in let dcnstr_htbl = Hashtbl.create hash_size in for tp = n_vars1 to n_vars2 do Hashtbl.add dcnstr_htbl (tp, missing) 0; Hashtbl.add dcnstr_htbl (tp, value) 1; done; let dtp = ProdEls (Array.init n_vars (fun n -> TpVal (n + n_vars1))) in let dispec = Array.make n_vars21 (Prod dtp) in let acti_defs ix (tp_name, attrs) = let tp = ix + 1 in let sample_tp = tp + n_vars in dtp_tbl.(tp) <- tp_name; let sample_tp_name = maybe_ ^ tp_name in dtp_tbl.(sample_tp) <- sample_tp_name; dcnstr_tbl.(tp) <- attrs; Hashtbl.add dtp_htbl tp_name tp; Hashtbl.add dtp_htbl sample_tp_name sample_tp; let acti_cnstrs cnstr cnstr_name = Hashtbl.add dcnstr_htbl (tp, cnstr_name) cnstr in Array.iteri acti_cnstrs attrs; dispec.(tp) <- Sums (Array.make (Array.length attrs) IAtom); dispec.(sample_tp) <- Sums [| IAtom; IStrct (TpVal tp); |] in Array.iteri acti_defs tp_names; { tp_tbl = dtp_tbl; cnstr_tbl = dcnstr_tbl; tp_htbl = dtp_htbl; cnstr_htbl = dcnstr_htbl; ispec = dispec; } let calc_cispec_info (_, target_attrs, _, _) = let ctp_htbl = Hashtbl.create 1 in Hashtbl.add ctp_htbl target_attr_name 1; let ccnstr_htbl = Hashtbl.create 13 in let acti cnstr attr = Hashtbl.add ccnstr_htbl (1, attr) cnstr in Array.iteri acti target_attrs; let cispec = [| Prod (TpVal 1); Sums (Array.make (Array.length target_attrs) IAtom) |] in { tp_tbl = [| ""; target_attr_name |]; cnstr_tbl = [| [||]; target_attrs |]; tp_htbl = ctp_htbl; cnstr_htbl = ccnstr_htbl; ispec = cispec; } let calc_vars n_samples n_vars dcnstr_tbl target_attrs rows = let dvars = Array.init n_vars (fun ix -> let tp = ix + 1 in { tp = tp; samples = Array.make n_samples dummy_fdsum; histo = Array.make (Array.length dcnstr_tbl.(tp)) 0; }) in let csamples = Array.make n_samples dummy_fdsum in let chisto = Array.make (Array.length target_attrs) 0 in let cvars = [| { tp = 1; samples = csamples; histo = chisto; } |] in let n_samples_1 = n_samples - 1 in list_iteri (fun ix (sample, ccnstr) -> let sample_ix = n_samples_1 - ix in csamples.(sample_ix) <- FDAtom ccnstr; chisto.(ccnstr) <- chisto.(ccnstr) + 1; let acti var_ix { samples = dsamples; histo = dhisto } = let dcnstr = sample.(var_ix) in dhisto.(dcnstr) <- dhisto.(dcnstr) + 1; dsamples.(sample_ix) <- FDAtom dcnstr in Array.iteri acti dvars) rows; dvars, cvars let read_c45_data (tp_names, target_attrs, pat, _ as c45_spec) mv ic = let rex = regexp ~flags pat in let { cnstr_tbl = dcnstr_tbl; cnstr_htbl = dcnstr_htbl; } as dispec_info = calc_dispec_info mv c45_spec in let { cnstr_htbl = ccnstr_htbl; } as cispec_info = calc_cispec_info c45_spec in let n_lines_ref = ref 1 in let n_samples_ref = ref 0 in let n_vars = Array.length tp_names in let n_vars_1 = n_vars - 1 in match mv with | Ignore -> let rows_ref = ref [] in foreach_line ~ic (fun line -> try let string_row = extract ~full_match:false ~rex line in let sample = Array.make n_vars 0 in (* MV = 0! *) for ix = 0 to n_vars_1 do sample.(ix) <- Hashtbl.find dcnstr_htbl (ix + 1, string_row.(ix)) done; let target_attr = Hashtbl.find ccnstr_htbl (1, string_row.(n_vars)) in rows_ref := (sample, target_attr) :: !rows_ref; incr n_lines_ref; incr n_samples_ref; with Not_found -> eprintf "invalid data line (maybe missing value?): %d\n" !n_lines_ref; incr n_lines_ref); close_in ic; flush stderr; let dvars, cvars = calc_vars !n_samples_ref n_vars dcnstr_tbl target_attrs !rows_ref in dispec_info, dvars, cispec_info, cvars | MProb -> let rows_ref = ref [] in foreach_line ~ic (fun line -> try let string_row = extract ~full_match:false ~rex line in let sample = Array.make n_vars dummy_fdsum in let target_attr = Hashtbl.find ccnstr_htbl (1, string_row.(n_vars)) in (try for ix = 0 to n_vars_1 do let attr = string_row.(ix) in if attr = "?" then raise Exit else sample.(ix) <- FDAtom (Hashtbl.find dcnstr_htbl (ix + 1, attr)) done; rows_ref := (Some sample, target_attr) :: !rows_ref; with Exit -> rows_ref := (None, target_attr) :: !rows_ref); incr n_lines_ref; incr n_samples_ref; with Not_found -> eprintf "invalid data line (maybe missing value in target?): %d\n" !n_lines_ref; incr n_lines_ref); close_in ic; flush stderr; let n_samples = !n_samples_ref in let n_samples_1 = n_samples - 1 in let { samples = dsamples; histo = dhisto; } as dvar = { tp = n_vars + 1; samples = Array.make n_samples dummy_fdsum; (* dummy_fdsum = MV! *) histo = [| 0; 0; |] } in let dvars = [| dvar |] in let csamples = Array.make n_samples dummy_fdsum in let chisto = Array.make (Array.length target_attrs) 0 in let cvars = [| { tp = 1; samples = csamples; histo = chisto; } |] in list_iteri (fun ix (maybe_sample, ccnstr) -> let sample_ix = n_samples_1 - ix in csamples.(sample_ix) <- FDAtom ccnstr; chisto.(ccnstr) <- chisto.(ccnstr) + 1; match maybe_sample with | None -> dhisto.(0) <- dhisto.(0) + 1 | Some sample -> dhisto.(1) <- dhisto.(1) + 1; dsamples.(sample_ix) <- FDStrct (1, sample)) !rows_ref; dispec_info, dvars, cispec_info, cvars | Flat -> let rows_ref = ref [] in foreach_line ~ic (fun line -> try let string_row = extract ~full_match:false ~rex line in let sample = Array.make n_vars 0 in (* MV = 0! *) for ix = 0 to n_vars_1 do let attr = string_row.(ix) in if attr <> "?" then sample.(ix) <- Hashtbl.find dcnstr_htbl (ix + 1, attr) done; let target_attr = Hashtbl.find ccnstr_htbl (1, string_row.(n_vars)) in rows_ref := (sample, target_attr) :: !rows_ref; incr n_lines_ref; incr n_samples_ref; with Not_found -> eprintf "invalid data line (maybe missing value in target?): %d\n" !n_lines_ref; incr n_lines_ref); close_in ic; flush stderr; let dvars, cvars = calc_vars !n_samples_ref n_vars dcnstr_tbl target_attrs !rows_ref in dispec_info, dvars, cispec_info, cvars | LiftAll -> let rows_ref = ref [] in foreach_line ~ic (fun line -> try let string_row = extract ~full_match:false ~rex line in let sample = Array.make n_vars dummy_fdsum in (* dummy_fdsum = MV! *) let target_attr = Hashtbl.find ccnstr_htbl (1, string_row.(n_vars)) in for ix = 0 to n_vars_1 do let attr = string_row.(ix) in if attr <> "?" then let data = FDAtom (Hashtbl.find dcnstr_htbl (ix + 1, attr)) in sample.(ix) <- FDStrct (1, [| data |]) done; rows_ref := (sample, target_attr) :: !rows_ref; incr n_lines_ref; incr n_samples_ref; with Not_found -> eprintf "invalid data line (maybe missing value in target?): %d\n" !n_lines_ref; incr n_lines_ref); close_in ic; flush stderr; let n_samples = !n_samples_ref in let n_samples_1 = n_samples - 1 in let dvars = let n_vars1 = n_vars + 1 in Array.init n_vars (fun ix -> { tp = ix + n_vars1; samples = Array.make n_samples dummy_fdsum; histo = [| 0; 0 |]; }) in let csamples = Array.make n_samples dummy_fdsum in let chisto = Array.make (Array.length target_attrs) 0 in let cvars = [| { tp = 1; samples = csamples; histo = chisto; } |] in list_iteri (fun ix (sample, ccnstr) -> let sample_ix = n_samples_1 - ix in csamples.(sample_ix) <- FDAtom ccnstr; chisto.(ccnstr) <- chisto.(ccnstr) + 1; let acti var_ix { samples = dsamples; histo = dhisto } = let fdsum = sample.(var_ix) in let dcnstr = fdsum_cnstr fdsum in dhisto.(dcnstr) <- dhisto.(dcnstr) + 1; dsamples.(sample_ix) <- fdsum in Array.iteri acti dvars) !rows_ref; dispec_info, dvars, cispec_info, cvars (* Read C4.5-rhs-data *) let target_rex = regexp ~flags:[`DOTALL] "(?:^|.*,)(\\w+)\\s*$" let read_rhs ic cnstr_htbl = try let strs = extract ~full_match:false ~rex:target_rex (input_line ic) in Some [| FDAtom (Hashtbl.find cnstr_htbl (1, strs.(0))) |] with End_of_file -> None