├── CART.php ├── Handling_Array.php ├── Listing_Combination.php ├── Make_Decision_Tree.php ├── Readme └── sample.php /CART.php: -------------------------------------------------------------------------------- 1 | $value) { 18 | //var_dump($value); 19 | $gini_array[$key] = CART::calc_gini_index($value,$base); 20 | } 21 | //var_dump($gini_array); 22 | $gini_root = CART::calc_gini_index($data,$base); 23 | 24 | // delta-Iを計算 25 | $delta_I = $gini_root; 26 | 27 | foreach ($split_array as $key => $value) { 28 | $odd = CART::probability_calculation($data,$pred,$key); 29 | $gini = $gini_array[$key]; 30 | $delta_I -= $odd * $gini; 31 | } 32 | return $delta_I; 33 | 34 | } 35 | 36 | function calc_gini_index($data,$base) 37 | { 38 | $base_array = array(); 39 | $feat_array = array(); 40 | 41 | $tmp_sum = 0; 42 | $odds = array(); 43 | 44 | 45 | // 予測変数の値毎の個体数を抽出 46 | $feat_array = Handling_Array::make_feat_array($data,$base); 47 | 48 | 49 | // 予測変数の値毎の出現確率を計算 50 | foreach ($feat_array as $key => $value) { 51 | $odd = CART::probability_calculation($data,$base,$value); 52 | array_push($odds , $odd); 53 | } 54 | 55 | 56 | // Gini係数を計算 57 | $gini = 1; 58 | foreach ($odds as $key => $value) { 59 | $gini -= pow($value,2); 60 | } 61 | 62 | return $gini; 63 | } 64 | 65 | function probability_calculation($data,$index,$value) 66 | { 67 | $tmp_sum = 0; 68 | 69 | foreach ($data as $dvalue) { 70 | if($dvalue[$index] == $value){$tmp_sum++;} 71 | } 72 | 73 | $odd = $tmp_sum / count($data); 74 | 75 | return $odd; 76 | } 77 | 78 | } 79 | 80 | ?> 81 | -------------------------------------------------------------------------------- /Handling_Array.php: -------------------------------------------------------------------------------- 1 | $value) { 11 | $base_array[$key] = $value[$pred]; 12 | } 13 | $feat_array = array_unique($base_array); 14 | 15 | $i = 0; 16 | foreach ($feat_array as $key => $value) { 17 | $result[$i] = $value; 18 | $i++; 19 | } 20 | return $result; 21 | 22 | } 23 | 24 | // 指定したカテゴリ変数で$dataを分割する。 25 | //public function fbv($data,$pred) 26 | public function split_by_pred($data,$pred) 27 | { 28 | $split_array = array(); 29 | 30 | // data 配列を予測変数の種類ごとに抽出する. 31 | $feat_array = Handling_Array::make_feat_array($data,$pred); 32 | 33 | // 予測変数の値毎の出現確率を計算 34 | foreach ($feat_array as $value) { 35 | 36 | $i = 0; 37 | $newarray = array(); 38 | 39 | if(is_array($data) && count($data)>0){ 40 | foreach(array_keys($data) as $key2){ 41 | $temp[$i] = $data[$key2][$pred]; 42 | if ($temp[$i] == $value) { 43 | $newarray[$i] = $data[$key2]; 44 | $i++; 45 | } 46 | } 47 | } 48 | $split_array[$value] = $newarray; 49 | } 50 | return $split_array; 51 | 52 | } 53 | 54 | } 55 | ?> 56 | -------------------------------------------------------------------------------- /Listing_Combination.php: -------------------------------------------------------------------------------- 1 | $value) { 9 | // //echo implode($value)."\n"; 10 | // //echo var_dump($value); 11 | // //echo "--------------\n"; 12 | //} 13 | 14 | function list_comb($data) 15 | { 16 | $result = array(); 17 | $return = array(); 18 | 19 | // 組み合わせを抽出 20 | for ($i=0; $i < count($data)-1; $i++) { 21 | $res = Listing_Combination::calc_combination($data , $i+1); 22 | foreach ($res as $key => $value) { 23 | array_push($result,$value); 24 | } 25 | } 26 | 27 | // 重複を排除 28 | $length = floor( count($data) / 2); 29 | $flg = count($data) % 2; 30 | 31 | foreach ($result as $key => $value) { 32 | if(count($value) <= $length){ 33 | switch ($flg) { 34 | case 0: 35 | $max = max($data); 36 | if(!array_search($max, $value)){ 37 | array_push($return,$value); 38 | } 39 | break; 40 | case 1: 41 | array_push($return,$value); 42 | break; 43 | } 44 | } 45 | } 46 | return $return; 47 | } 48 | 49 | function calc_combination($array,$num) 50 | { 51 | $arrnum = count($array); 52 | if($arrnum < $num){ 53 | return; 54 | }else if ($num==1) { 55 | for ($i=0; $i < $arrnum; $i++) { 56 | $arrs[$i] = array($array[$i]); 57 | } 58 | }elseif ($num>1) { 59 | $j=0; 60 | for ($i=0; $i < $arrnum-$num+1; $i++) { 61 | $ts = Listing_Combination::calc_combination(array_slice($array,$i+1),$num-1); 62 | foreach ($ts as $t ) { 63 | array_unshift($t,$array[$i]); 64 | $arrs[$j]= $t; 65 | $j++; 66 | } 67 | } 68 | } 69 | return $arrs; 70 | } 71 | } 72 | ?> 73 | -------------------------------------------------------------------------------- /Make_Decision_Tree.php: -------------------------------------------------------------------------------- 1 | data = $data; 36 | } 37 | public function classify($base_key,$base_value,$false_value){ 38 | 39 | $this->base_key = $base_key; 40 | $this->base_value = $base_value; 41 | $this->false_value = $false_value; 42 | 43 | // 2値変数に変換 44 | $this->bv_data = Decision_Tree::make_binary_variable_data($this->data,$base_key); 45 | 46 | 47 | // Decision_Treeの生成 48 | $tree = Decision_Tree::make_decision_tree($this->bv_data, 49 | $this->base_key, 50 | $this->base_value, 51 | $this->base_key, 52 | $this->base_value); 53 | 54 | $this->tree = $tree; 55 | return $tree; 56 | } 57 | public function prognosis($target){ 58 | $result = Decision_Tree::exe_prognosis($this->tree,$target); 59 | return $result; 60 | } 61 | private function make_binary_variable_data($ori_data,$base_key) 62 | { 63 | $multiple_param = array(); 64 | $continuous_param = array(); 65 | 66 | $feat_array = array(); 67 | $delta_I_array = array(); 68 | 69 | 70 | // dataをコピー 71 | $data = $ori_data; 72 | 73 | 74 | // 各変数の種類を確認 (3種類以上ある変数はどれか) 75 | $keys = array_keys($data[0]); 76 | foreach ($keys as $k => $key) { 77 | // 目的変数は対象外 78 | if ($key == $base_key) { 79 | continue; 80 | } 81 | 82 | // 「連続変数」と「多値変数」の判断 83 | $feat_array = Handling_Array::make_feat_array($data,$key); 84 | if(count($feat_array)>=3){ 85 | if(is_numeric($feat_array[0])){ 86 | // 値の種類を取出し、3つ以上あり値が数値なら「連続変数」 87 | array_push($continuous_param,$key); 88 | }else { 89 | // 値が数値でなければ「多値変数」と判断する. 90 | array_push($multiple_param,$key); 91 | } 92 | } 93 | } 94 | 95 | 96 | 97 | // 2値変数へ圧縮 98 | foreach ($multiple_param as $key => $pred) { 99 | // 指定した多値変数を2値変数に圧縮する 100 | $data = Decision_Tree::multiple_to_binary($data,$base_key,$pred); 101 | } 102 | foreach ($continuous_param as $key => $pred) { 103 | // 指定した連続変数を2値変数に圧縮する 104 | $data = Decision_Tree::continuous_to_binary($data,$base_key,$pred); 105 | } 106 | 107 | return $data; 108 | } 109 | private function continuous_to_binary($data,$base_key,$pred) 110 | { 111 | 112 | // カテゴリ変数の種類を抽出 113 | $feat_array = Handling_Array::make_feat_array($data,$pred); 114 | 115 | // 昇順に並べ替える 116 | asort($feat_array); 117 | 118 | 119 | // グループ分けして, delta_Iを計算する 120 | for ($i=1; $i < count($feat_array); $i++) { 121 | 122 | // groupを作成 123 | $combs[$i] = array_slice($feat_array,0,$i); 124 | 125 | // 2値変数に圧縮 126 | $tmpdata = Decision_Tree::to_binary_data($data,$pred,$combs[$i],'type1','type2'); 127 | 128 | // delta_Iを計算 129 | $delta_I_array[$i] = CART::calc_delta_I($tmpdata,$base_key,$pred); 130 | 131 | } 132 | 133 | // delta_Iが最大になるグループのキーを調べる 134 | $maxdikeys = array_keys($delta_I_array,max($delta_I_array)); 135 | $maxdikey = $maxdikeys[0]; 136 | 137 | 138 | // type1 , type2 の名前を設定 139 | $type1_name = ""; 140 | $type2_name = ""; 141 | $groupmax = max($combs[$maxdikey]); 142 | 143 | $type1_name = $groupmax . " <=x"; 144 | $type2_name = $groupmax . " >x"; 145 | 146 | 147 | 148 | // 名前を変更したdataを作成する。 149 | foreach ($data as $num => $array) { 150 | $chk = $array[$pred]; 151 | if(in_array($chk,$combs[$maxdikey])){ 152 | $tmparray = $array; 153 | $tmparray[$pred] = $type1_name; 154 | $tmpdata[$num] = $tmparray; 155 | }else{ 156 | $tmparray = $array; 157 | $tmparray[$pred] = $type2_name; 158 | $tmpdata[$num] = $tmparray; 159 | } 160 | } 161 | 162 | return $tmpdata; 163 | } 164 | private function multiple_to_binary($data,$base,$pred) 165 | { 166 | 167 | // カテゴリ変数の種類を抽出 168 | $feat_array = Handling_Array::make_feat_array($data,$pred); 169 | 170 | // 組み合わせを抽出 171 | //$combs = split_data($feat_array); 172 | $combs = Listing_Combination::list_comb($feat_array); 173 | 174 | 175 | // グループ分けして、各グループのdelta_Iを計算する 176 | foreach ($combs as $combkey => $comb) { 177 | // 2値変数に圧縮した配列を作成 178 | $tmpdata = Decision_Tree::to_binary_data($data,$pred,$comb,'type1','type2'); 179 | 180 | // delta_Iを計算 181 | $delta_I_array[$combkey] = CART::calc_delta_I($tmpdata,$base,$pred); 182 | } 183 | 184 | 185 | // delta_Iが最大になるグループのキーを調べる 186 | $maxdikeys = array_keys($delta_I_array,max($delta_I_array)); 187 | $maxdikey = $maxdikeys[0]; 188 | 189 | 190 | // type1 , type2 の名前を設定 191 | $type1_name = ""; 192 | $type2_name = ""; 193 | foreach ($feat_array as $key => $value) { 194 | if(in_array($value,$combs[$maxdikey])){ 195 | $type1_name .= $value; 196 | }else { 197 | $type2_name .= $value; 198 | } 199 | } 200 | 201 | // 名前を変更したdataを作成する。 202 | $tmpdata = Decision_Tree::to_binary_data($data,$pred,$combs[$maxdikey],$type1_name,$type2_name); 203 | 204 | 205 | return $tmpdata; 206 | } 207 | 208 | private function to_binary_data($data,$pred,$comb,$name1,$name2) 209 | { 210 | // 2値変数に圧縮した配列を作成 211 | foreach ($data as $num => $array) { 212 | $chk = $array[$pred]; 213 | if(in_array($chk,$comb)){ 214 | $tmparray = $array; 215 | $tmparray[$pred] = $name1; 216 | $tmpdata[$num] = $tmparray; 217 | }else { 218 | $tmparray = $array; 219 | $tmparray[$pred] = $name2; 220 | $tmpdata[$num] = $tmparray; 221 | } 222 | } 223 | return $tmpdata; 224 | } 225 | 226 | public function exe_prognosis($tree,$target){ 227 | 228 | // 終端ノードなら、そのノードにおける目的変数の値を返す 229 | if($tree->terminal){ 230 | // 目的変数値の種類ごとの数を確認 231 | $true_num = $tree->dtdata->match; 232 | $false_num = $tree->dtdata->unmatch; 233 | 234 | // 235 | if($true_num > $false_num){ 236 | $pars = $true_num / ($true_num + $false_num); 237 | echo $pars."\n"; 238 | return $this->base_value; 239 | }else { 240 | $pars = $false_num / ($true_num + $false_num); 241 | echo $pars."\n"; 242 | return $this->false_value; 243 | } 244 | // 終端ノードでなければ、分岐条件を確認する 245 | }else { 246 | $split_key = $tree->left->dtdata->split_key; 247 | $lval = $tree->left->dtdata->split_value; 248 | $rval = $tree->right->dtdata->split_value; 249 | } 250 | 251 | 252 | // 連続変数かどうかを確認する 253 | $feat_array = Handling_Array::make_feat_array($this->data,$split_key); 254 | if(count($feat_array)>3 && is_numeric($feat_array[0])){ 255 | if(!(strstr('<=x',$lval)==false)){ 256 | $flg = 1; 257 | $border = trim($lval,'<=x'); 258 | }else { 259 | $flg = 2; 260 | $border = trim($rval,'<=x'); 261 | } 262 | }else { 263 | $flg = 0; 264 | } 265 | 266 | // 分岐条件に従い、次のノードの計算を行う。 267 | switch ($flg) { 268 | case 0: 269 | // カテゴリ変数が元連続変数でない場合の分岐方法 270 | if(!(strstr($lval,$target[$split_key])==false)) 271 | { 272 | return Decision_Tree::exe_prognosis($tree->left,$target); 273 | } 274 | else if (!(strstr($rval,$target[$split_key])==false)) 275 | { 276 | return Decision_Tree::exe_prognosis($tree->right,$target); 277 | } 278 | break; 279 | case 1: 280 | // カテゴリ変数が元連続変数である場合の分岐方法 281 | if($border >= $target[$split_key]){ 282 | return Decision_Tree::exe_prognosis($tree->right,$target); 283 | }else{ 284 | return Decision_Tree::exe_prognosis($tree->left,$target); 285 | } 286 | break; 287 | case 2: 288 | // カテゴリ変数が元連続変数である場合の分岐方法2 289 | if($border >= $target[$split_key]){ 290 | return Decision_Tree::exe_prognosis($tree->left,$target); 291 | }else{ 292 | return Decision_Tree::exe_prognosis($tree->right,$target); 293 | } 294 | break; 295 | default: 296 | break; 297 | } 298 | } 299 | 300 | private function make_decision_tree($data,$base,$base_value,$split_key,$split_value){ 301 | 302 | $delta_I_array = array(); 303 | $dtnode = new DT_Node(); 304 | $dtdata = new DT_Data(); 305 | 306 | // dtdataをセット 307 | $dtdata = Decision_Tree::set_DtData($data,$base,$base_value); 308 | $dtdata->split_key = $split_key; 309 | $dtdata->split_value = $split_value; 310 | $dtnode->dtdata = $dtdata; 311 | $dtnode->terminal = false; 312 | 313 | 314 | 315 | // カテゴリ変数ごとにdelta_Iを計算する 316 | $keys = array_keys($data[0]); 317 | foreach ($keys as $k => $key) { 318 | if ($key == $base) { 319 | continue; 320 | } 321 | // delta_Iを計算 322 | $delta_I_array[$key] = CART::calc_delta_I($data,$base,$key); 323 | } 324 | 325 | 326 | // delta_Iが全部ゼロなら終了 327 | // この辺の終了条件はかなり適当 328 | $flg =0; 329 | foreach ($delta_I_array as $key => $value) { 330 | if($value != 0.0){$flg=1;} 331 | } 332 | if($flg==0){ 333 | $dtnode->terminal= true; 334 | return $dtnode; 335 | } 336 | 337 | 338 | // 最大のdelta_Iを抽出 339 | $split_key = array_keys($delta_I_array,max($delta_I_array)); 340 | 341 | // delta_Iが最大となるカテゴリ変数でdataを分割する 342 | $split_array = Handling_Array::split_by_pred($data,$split_key[0]); 343 | $i = 0; 344 | foreach ($split_array as $key => $value) { 345 | switch ($i) { 346 | case 0: 347 | $dtnode->left = Decision_Tree::make_decision_tree($value,$base,$base_value,$split_key[0],$key); 348 | break; 349 | case 1: 350 | $dtnode->right = Decision_Tree::make_decision_tree($value,$base,$base_value,$split_key[0],$key); 351 | break; 352 | default: 353 | break; 354 | } 355 | $i++; 356 | } 357 | 358 | 359 | // 決まったら、DT_Nodeを返却 360 | return $dtnode; 361 | 362 | } 363 | private function set_DtData($data,$base,$value) 364 | { 365 | // カテゴリ変数の抽出 366 | $split_array = Handling_Array::split_by_pred($data,$base); 367 | 368 | 369 | // DT_Dataを作成 370 | $dtdata = new DT_Data(); 371 | $dtdata->number = count($data); 372 | if(isset($split_array[$value])){ 373 | $dtdata->match = count($split_array[$value]); 374 | }else { 375 | $dtdata->match = 0; 376 | } 377 | $dtdata->unmatch = $dtdata->number - $dtdata->match; 378 | 379 | return $dtdata; 380 | } 381 | 382 | } 383 | 384 | ?> 385 | 386 | 387 | -------------------------------------------------------------------------------- /Readme: -------------------------------------------------------------------------------- 1 | 2 | 【ソフト名】決定木作成モジュール 3 | 【著作権者】狩野達也 4 | 【制作日】2011/11/18 5 | 【種 別】決定木、CART 6 | 【連絡先】kokuban.kumasan@gmail.com 7 | 【動作環境】CentOS 6 8 | 【開発環境】CentOS 6 9 | 10 | ――――――――――――――――――――――――――――――――――――― 11 | ≪著作権および免責事項≫ 12 | 13 | 14 | ――――――――――――――――――――――――――――――――――――― 15 | 16 | 【はじめに】 17 | このモジュールは、CARTアルゴリズムを使った決定木の作成、 18 | 利用を行うためのモジュールです。 19 | 20 | 21 | 【インストール方法】 22 | 以下の通り. 23 | 24 | ・php-decision-tree 25 | $ git clone git://github.com/kokukuma/php-decision-tree.git 26 | 27 | 28 | 【使い方例】 29 | 1, モジュールの読み込み 30 | require_once('Make_Decision_Tree.php'); 31 | 32 | 33 | 2, データの準備 34 | $data = array(); 35 | $data[0] = array("生死"=>"生還","年齢"=>"35","性別"=>"男","等級"=>"1等"); 36 | $data[1] = array("生死"=>"生還","年齢"=>"12","性別"=>"女","等級"=>"1等"); 37 | $data[2] = array("生死"=>"生還","年齢"=>"35","性別"=>"女","等級"=>"1等"); 38 | $data[3] = array("生死"=>"死亡","年齢"=>"26","性別"=>"男","等級"=>"1等"); 39 | $data[4] = array("生死"=>"生還","年齢"=>"23","性別"=>"女","等級"=>"1等"); 40 | 41 | 目的変数は2値変数のみ、 42 | カテゴリ変数は「2値変数」「多値変数」「連続変数」に対応している。 43 | 判別基準としては、以下のようになっている. 44 | 45 | ・多値変数 : 値の種類が3種類以上、数字のみでない。 46 | ・連続変数 : 値の種類が3種類以上、数字のみ 47 | ・2値変数 : 上記以外の場合 48 | 49 | 50 | 3, データの読み込み 51 | $dt = new Decision_Tree($data); 52 | 53 | 54 | 4, 決定木の生成 55 | $tree = $dt->classify('生死','生還','死亡'); 56 | 57 | 第1引数 : 目的変数名 58 | 第2引数 : 正とする目的変数の値 59 | 第3引数 : 誤とする目的変数の値 60 | 61 | 62 | 5, 決定木の利用 63 | $target = array("年齢"=>"40","性別"=>"男","等級"=>"2等"); 64 | $res = $dt->prognosis($target); 65 | 66 | $targetのデータ構造は, 67 | $dataの1要素から目的変数を除いた形式とする。 68 | 返値には、決定木から想定される目的変数の値が返却される。 69 | 70 | 71 | ――――――――――――――――――――――――――――――――――――― 72 | -------------------------------------------------------------------------------- /sample.php: -------------------------------------------------------------------------------- 1 | "生還","年齢"=>"35","性別"=>"男","等級"=>"1等"); 8 | $data[1] = array("生死"=>"生還","年齢"=>"12","性別"=>"女","等級"=>"1等"); 9 | $data[2] = array("生死"=>"生還","年齢"=>"35","性別"=>"女","等級"=>"1等"); 10 | $data[3] = array("生死"=>"死亡","年齢"=>"26","性別"=>"男","等級"=>"1等"); 11 | $data[4] = array("生死"=>"生還","年齢"=>"23","性別"=>"女","等級"=>"1等"); 12 | $data[5] = array("生死"=>"死亡","年齢"=>"31","性別"=>"男","等級"=>"2等"); 13 | $data[6] = array("生死"=>"生還","年齢"=>"32","性別"=>"男","等級"=>"2等"); 14 | $data[7] = array("生死"=>"生還","年齢"=>"23","性別"=>"男","等級"=>"2等"); 15 | $data[8] = array("生死"=>"死亡","年齢"=>"25","性別"=>"男","等級"=>"2等"); 16 | $data[9] = array("生死"=>"死亡","年齢"=>"29","性別"=>"女","等級"=>"2等"); 17 | $data[10] = array("生死"=>"生還","年齢"=>"40","性別"=>"女","等級"=>"2等"); 18 | $data[11] = array("生死"=>"生還","年齢"=>"12","性別"=>"女","等級"=>"2等"); 19 | $data[12] = array("生死"=>"生還","年齢"=>"35","性別"=>"女","等級"=>"2等"); 20 | $data[13] = array("生死"=>"死亡","年齢"=>"34","性別"=>"男","等級"=>"3等"); 21 | $data[14] = array("生死"=>"生還","年齢"=>"23","性別"=>"女","等級"=>"3等"); 22 | $data[15] = array("生死"=>"死亡","年齢"=>"31","性別"=>"男","等級"=>"3等"); 23 | $data[16] = array("生死"=>"死亡","年齢"=>"32","性別"=>"男","等級"=>"3等"); 24 | $data[17] = array("生死"=>"死亡","年齢"=>"23","性別"=>"男","等級"=>"乗組員"); 25 | $data[18] = array("生死"=>"死亡","年齢"=>"25","性別"=>"男","等級"=>"乗組員"); 26 | $data[19] = array("生死"=>"死亡","年齢"=>"29","性別"=>"女","等級"=>"乗組員"); 27 | $data[20] = array("生死"=>"生還","年齢"=>"40","性別"=>"女","等級"=>"乗組員"); 28 | 29 | 30 | // データの引き渡し 31 | $dt = new Decision_Tree($data); 32 | 33 | 34 | //木の生成(分類木) 35 | $tree = $dt->classify('生死','生還','死亡'); 36 | echo "-------- Decision_Tree\n"; 37 | var_dump($tree); 38 | 39 | 40 | //ルールの使用方法 41 | $target = array("年齢"=>"40","性別"=>"男","等級"=>"2等"); 42 | $res = $dt->prognosis($target); 43 | $res = $dt->exe_prognosis($tree,$target); 44 | 45 | echo "-------- estimate\n"; 46 | var_dump($res); 47 | 48 | ?> 49 | 50 | --------------------------------------------------------------------------------