source: trunk/project/modules/stable/tools/bayes/classes/bayesfactory.class.php @ 5410

Revision 5410, 12.1 KB checked in by gcroes, 13 months ago (diff)

Exception => CopixException

  • Property svn:eol-style set to native
Line 
1<?php
2/**
3 * @package tools
4 * @subpackage bayes
5 * @author Patrice Ferlet - <metal3d@copix.org>
6 * @copyright CopixTeam
7 * @licence GNU/GPL
8 */
9
10interface IBayesMethods {
11        public function train($pCat,$pText);
12        public function untrain($pCat,$pText);
13        public function getProba($A,$B);
14        public function setCategoriesProbas();
15}
16
17/**
18 * CopixBayes Class
19 * Used to get Bayesian values of probabilities
20 *
21 * @package tools
22 * @subpackage bayes
23 */
24class BayesFactory {
25
26        /**
27         * Get Bayes object from datamode
28         *
29         * @param const $datamode
30         * @param string $dataset name
31         * @param string $connectionname
32         * @return Bayes object cast for datamode
33         */
34        public function create($datamode = Bayes::STATIC_DATAMODE,$dataset=null,$connectionname=null){
35                if($datamode == Bayes::DB_DATAMODE){
36                        $b = new DBBayes();
37                }
38                else{
39                        $b = new StaticBayes();
40                }
41                $b->setDataMode($datamode,$dataset,$connectionname);
42                return $b;
43        }
44
45}
46
47
48abstract class Bayes implements IBayesMethods {
49        /**
50         * Array of categories
51         *
52         * @var array
53         */
54        protected $categories = array();
55       
56        /**
57         * Weight for all categories, in fact it's the whole data count
58         *
59         * @var integer
60         */
61        protected $numcat=0;
62       
63        /**
64         * Bayes mode, database or static
65         *
66         * @var const int
67         */
68        protected $mode=self::STATIC_DATAMODE;
69       
70        /**
71         * Dataset name for database mode
72         *
73         * @var string
74         */
75        protected $dataset="";
76       
77        /**
78         * Static mode
79         *
80         */
81        const  STATIC_DATAMODE = 0;
82       
83        /**
84         * Database mode
85         *
86         */
87        const  DB_DATAMODE = 1;
88       
89        /**
90         * Connection name to strore dataset
91         *
92         * @var string
93         */
94        protected $connectionName = null;
95       
96        /**
97         * True if calcul is done
98         *
99         * @var boolean
100         */
101        private $calculated = false;
102       
103        /**
104         * Human readable operation done
105         *
106         * @var string
107         */
108        private $operation = "";
109       
110        /**
111         * Constructor
112         *
113         */
114        public function __construct(){
115                $this->setDataMode();
116        }
117       
118
119        /**
120         * Set the data mode
121         * @param string $dataset_name
122         * @param string $mode Bayes::STATIC_DATAMODE or Bayes::DB_DATAMODE
123         * @param string connection_name
124         */
125        public function setDataMode($pMode=self::STATIC_DATAMODE ,$pDataset=null,$pConnectionName=null){
126                if(empty($pDataset) && $pMode==self::DB_DATAMODE){
127                        throw (new CopixException("No dataset given for CopixBayesian datas used with database"));
128                }
129                $this->dataset="bayesiantable_".$pDataset;
130                $this->mode = ($pMode==self::DB_DATAMODE) ? self::DB_DATAMODE :self::STATIC_DATAMODE;
131               
132                //get tables
133                if($this->mode==self::DB_DATAMODE){
134                        $this->connectionName = $pConnectionName;
135                        $ct = CopixDB::getConnection($pConnectionName);
136                        if(!in_array($this->dataset,$ct->getTableList())){
137                                if($ct instanceof CopixDBConnectionMySQL || $ct instanceof CopixDBConnectionPDO_MySQL){
138                                        $sql = CopixFile::read(dirname(__FILE__)."/../install/template_scripts/install.pdo_mysql.sql");
139                                }
140                                elseif($ct instanceof CopixDBConnectionPDO_SQLite){
141                                        $sql = CopixFile::read(dirname(__FILE__)."/../install/template_scripts/install.pdo_sqlite.sql");
142                                }
143                                elseif($ct instanceof CopixDBConnectionPDO_PgSQL){
144                                        $sql = CopixFile::read(dirname(__FILE__)."/../install/template_scripts/install.pdo_pgsql.sql");
145                                }
146                                else{
147                                        //throw new CopixException("Data type: ".get_class($ct)." not currently supported");
148                                }
149                                $sql = str_replace('%TABLENAME%',$this->dataset,$sql);
150                                _doQuery($sql,array(),$pConnectionName);
151                        }
152                }
153
154        }
155
156
157
158        /**
159         * Get the Bayesian value for a category
160         *
161         * @param string $category
162         * @param string $data_to_test
163         * @param boolean $naive to give same chance for each category
164         * @return float $bayesian_value (in percent %)
165         */
166        public function getBayes($B,$A,$pNaive=false){
167                $A = strtolower($A);
168                $this->setCategoriesProbas();
169                //P(B|A)
170                //numerator:  P(B) * P(A|B)...
171                //P(B) => getCategoriesProbas for B
172                //P(A|B) => find A in B
173                if(!isset($this->categories[$B])){
174                        return 0;
175                }
176                if(!$pNaive){
177                        $PB = $this->categories[$B]->percent;
178                } else {
179                        $PB = (100/count($this->categories));
180                }
181                $PAB = $this->getProba($A,$B);
182                $numerator = $PB * $PAB;
183                $this->operation = $PB.'*'.$PAB;
184                $this->operation .="\n";
185                //denominator: ( P(B)*(P(A|B) + P(B2)*P(A|B2) + ... )
186                //so for every categories, we have to check
187                $den = 0;
188                foreach($this->categories as $name=>$B){
189                        if(!$pNaive){
190                                $PB = $B->percent;
191                        }
192                        $PAB = $this->getProba($A,$name);
193                        $den += ($PB*$PAB);
194                        $this->operation.= '+('.$PB.'*'.$PAB.')';
195                }
196
197                if($den==0) return 0;
198                $this->calculated = true;
199                return 100*$numerator/$den;
200        }
201
202
203        public function getOperation(){
204                if(!$this->calculated){
205                        throw new CopixException("Calcul is not done yet");
206                }
207                $op = $this->operation;
208                list($num,$den) = explode("\n",$this->operation);
209                $den = preg_replace('/^\+/','',$den);
210                return '<table>
211                        <tr>
212                        <td style="text-align: center; border-bottom: 1px solid #000">'.$num.'</td>
213                        </tr>
214                        <tr><td "text-align: center;">'.$den.'
215                        </tr></td>
216</table>';
217        }
218
219
220        /**
221         * Prepare and split text to be registered into dataset
222         *
223         * @param strign $pText
224         * @return array of strings
225         */
226        protected function prepareText ($pText){
227                $text = $this->remove_accents($pText);
228                $t=preg_split('/\W/is',$text);
229                $texts=array();
230                foreach ($t as $text){
231                        if(strlen(trim($text))>0){
232                                $texts[]=strtolower(trim($text));
233                        }
234                }
235                return $texts;
236        }
237
238        /**
239         * By derernst at gmx dot ch: http://fr3.php.net/manual/fr/function.strtr.php#56973
240         */
241        protected function remove_accents($pString, $german=false) {
242                // Single letters
243                //$string=utf8_encode($string);
244                $single_fr = explode(" ", "À Á Â Ã Ä Å &#260; &#258; Ç &#262; &#268; &#270; &#272; Ð È É Ê Ë &#280; &#282; &#286; Ì Í Î Ï &#304; &#321; &#317; &#313; Ñ &#323; &#327; Ò Ó Ô Õ Ö Ø &#336; &#340; &#344; Å  &#346; &#350; &#356; &#354; Ù Ú Û Ü &#366; &#368; Ý Åœ &#377; &#379; à á â ã À Ã¥ &#261; &#259; ç &#263; &#269; &#271; &#273; Ú é ê ë &#281; &#283; &#287; ì í î ï &#305; &#322; &#318; &#314; ñ &#324; &#328; ð ò ó ÃŽ õ ö Þ &#337; &#341; &#345; &#347; Å¡ &#351; &#357; &#355; ù ú û ÃŒ &#367; &#369; Ü ÿ ÅŸ &#378; &#380;");
245                $single_to = explode(" ", "A A A A A A A A C C C D D D E E E E E E G I I I I I L L L N N N O O O O O O O R R S S S T T U U U U U U Y Z Z Z a a a a a a a a c c c d d e e e e e e g i i i i i l l l n n n o o o o o o o o r r s s s t t u u u u u u y y z z z");
246                $single = array();
247                for ($i=0; $i<count($single_fr); $i++) {
248                        $single[$single_fr[$i]] = $single_to[$i];
249                }
250                // Ligatures
251                $ligatures = array("Æ"=>"Ae", "Ê"=>"ae", "Œ"=>"Oe", "œ"=>"oe", "ß"=>"ss");
252                // German umlauts
253                $umlauts = array("Ä"=>"Ae", "À"=>"ae", "Ö"=>"Oe", "ö"=>"oe", "Ü"=>"Ue", "ÃŒ"=>"ue");
254                // Replace
255                $replacements = array_merge($single, $ligatures);
256                if ($german) $replacements = array_merge($replacements, $umlauts);
257                $pString = strtr($pString, $replacements);
258                return $pString;
259        }
260
261
262
263        /**
264         * Add category to the data set
265         *
266         * @param unknown_type $cat
267         */
268        protected function addCategory ($pCat){
269                $this->categories[$pCat]=new stdClass;
270                $this->categories[$pCat]->texts=array();
271                $this->categories[$pCat]->counter=0;
272                $this->categories[$pCat]->percent = 0;
273        }
274}
275
276/**
277 * StaticBayes is a "one shot" class to process datas and
278 * get bayesian probabilities
279 *
280 */
281class StaticBayes extends Bayes  {
282
283        /**
284         * Prepare categories
285         *
286         */
287        public function setCategoriesProbas(){
288                foreach($this->categories as $name=>$cat){                     
289                        $cat->percent = $cat->counter * 100 / $this->numcat;                   
290                }
291               
292        }
293
294        /**
295         * Get probability of A is in B
296         *
297         * @param string $category  category name
298         * @param string $data_to_check  data to check
299         * @return float $proba (in percent %)
300         */
301        public function getProba($A,$B){
302                $A = $this->remove_accents($A);
303                $A = preg_split('/\W/is',$A);
304                $numwords = 0;
305                $found = 0;
306                foreach($this->categories[$B]->texts as $words){
307                        $numwords += count($words);
308                        foreach($words as $word){
309                                foreach($A as $find){
310                                        if($find==$word){
311                                                $found++;
312                                        }
313                                }
314                        }
315                }
316                       
317                return $found * 100 / $numwords;
318        }
319
320        /**
321         * Add/update category and datas for this category
322         *
323         *
324         * @param string $category
325         * @param string $text
326         */
327        public function train($pCat,$pText){
328                $texts = $this->prepareText($pText);
329               
330                if(!isset($this->categories[$pCat])) $this->addCategory($pCat);
331               
332                $this->categories[$pCat]->texts[]=$texts;
333                $this->categories[$pCat]->counter+=count($texts);
334                $this->numcat+=count($texts);
335
336        }
337
338        /**
339         * Untrain/remove the data from dataset
340         *
341         * @param string $categoryname
342         * @param string $text
343         */
344
345        public function untrain($pCat,$pText){
346                $texts = $this->prepareText($pText);
347
348                if(isset($this->categories[$pCat]) && isset($this->categories[$pCat]->texts)){
349                        $i=0;
350                        foreach($this->categories[$pCat]->texts as $t){
351                                if($texts == $t){                                       
352                                        //remove one word for numcat:
353                                        $this->numcat--;
354                                        $this->categories[$cat]->counter--;     
355                                        unset($this->categories[$pCat]->texts[$i]);
356                                        break;
357                                }
358                                $i++;
359                        }
360                        if(count($this->categories[$pCat]->texts)<1){
361                                unset($this->categories[$pCat]);
362                        }
363                }
364
365        }
366
367}
368
369
370/**
371 * DBBayes store datas into database
372 *
373 */
374class DBBayes extends Bayes {
375
376        /**
377         * Prepare categories
378         *
379         */
380        public function setCategoriesProbas(){
381                //categories counter
382                $cats = _doQuery('select distinct category_bayes from '.$this->dataset,array(),$this->connectionName);
383               
384                //words counter
385                $acount = _doQuery("select sum(numdatas_bayes) count from ".$this->dataset,array(),$this->connectionName);
386                               
387                foreach($cats as $cat){
388                        $this->addCategory($cat->category_bayes);
389                        $count = _doQuery("select sum(numdatas_bayes) count from ".$this->dataset." where category_bayes='".$cat->category_bayes."'",array(),$this->connectionName);
390                        $this->categories[$cat->category_bayes]->percent= $count[0]->count * 100 / $acount[0]->count;
391                       
392                        $name=$cat->category_bayes;
393                }
394        }
395
396        /**
397         * Get probability of A is in B
398         *
399         *
400         * @param string $category  category name
401         * @param string $data_to_check  data to check
402         * @return float $proba (in percent %)
403         */
404        public function getProba($A,$B){
405                $A = $this->remove_accents($A);
406                $A = preg_split('/\W/is',$A);
407                $numwords = 0;
408                $found = 0;
409
410                $numwords = _doQuery('select sum (numdatas_bayes) numwords from '.$this->dataset.' where category_bayes="'.$B.'"',array(),$this->connectionName);
411                $numwords = $numwords[0]->numwords;
412                foreach($A as $find){
413                        $sets = _doQuery("select numdatas_bayes from ".$this->dataset." where category_bayes=\"$B\" AND datas_bayes ='$find'",array(),$this->connectionName);
414                        foreach($sets as $set){
415                                $found += $set->numdatas_bayes;
416                        }
417                }
418                return $found * 100 / $numwords;
419        }
420
421        /**
422         * Add/update category and datas for this category
423         *
424         *
425         * @param string $category
426         * @param string $text
427         */
428        public function train($pCat,$pText){
429                $texts = $this->prepareText($pText);
430                foreach ($texts as $word){
431                        $res = _ioDao($this->dataset,$this->connectionName)->findBy(_daoSP()
432                        ->addCondition('datas_bayes','=',$word)
433                        ->addCondition('category_bayes','=',$pCat)
434                        ->addCondition('dataset_bayes','=',$this->dataset)
435                        ,$this->connectionName);
436                        $method="update";
437                        if (count($res)<1 || !isset($res[0])) {
438                                $rec = CopixDAOFactory::createRecord($this->dataset,$this->connectionName,$this->connectionName);
439                                $rec->category_bayes = $pCat;
440                                $rec->numdatas_bayes = 1;
441                                $rec->datas_bayes = $word;
442                                $rec->dataset_bayes=$this->dataset;
443                                $method = "insert";
444                        } else {
445                                $rec = $res[0];
446                                $rec->numdatas_bayes ++;
447                        }
448                        _ioDao($this->dataset,$this->connectionName)->$method($rec);
449                }
450
451        }
452
453        /**
454         * Untrain, remove the data from dataset
455         *
456         * @param string $categoryname
457         * @param string $text
458         */
459        public function untrain($pCat,$pText){
460                $texts = $this->prepareText($pText);
461       
462                foreach($texts as $text){
463                        $rec = _ioDao($this->dataset,$this->connectionName)->findBy(_daoSp()
464                                ->addCondition('datas_bayes','=',$text)
465                                ->addCondition('category_bayes','=',$pCat)
466                        );
467               
468                        if(count($rec)){
469                                //decrements the data number for this word
470                                if($rec[0]->numdatas_bayes>1) {
471                                        $rec[0]->numdatas_bayes--;
472                                        _ioDao($this->dataset,$this->connectionName)->update($rec[0]);
473                                }else{
474                                        //this word have to be forget
475                                        _ioDao($this->dataset,$this->connectionName)->delete($rec[0]->id_bayes);       
476                                }                               
477                        }       
478                }
479               
480
481        }
482
483
484}
Note: See TracBrowser for help on using the repository browser.