Moteur de recherches.

Répondre


Cette question est un moyen d’empêcher des soumissions automatisées de formulaires par des robots.
Smileys
:D :) :( :o :shock: :? 8-) :lol: :x :P :oops: :cry: :evil: :twisted: :roll: :wink: :!: :?: :idea: :arrow: :| :mrgreen: =D> #-o =P~ :^o :non: :priere: 8-|
Voir plus de smileys
  Revue du sujet
 

  Étendre la vue Revue du sujet : Moteur de recherches.

par Hywan » 28 août 2007, 17:28

C'est pas mal du tout le stemmer. Je connaissais pas. La limite du système, c'est d'écrire toutes les règles.

Maintenant, laissez moi vous présenter les deux scripts que j'ai fais ici.

Le premier est une introduction au modèle vecteur-binaire, qui sert à créer une machine de recherches pour des mots de longueurs inférieures à des mots machines. On s'en sert pour faire des recherches avec joker (je n'ai pas encore fait le script). Par exemple, si on dit que le caractère joker est §, alors abc§ef, on recherche toutes les occurences avec abc[une lettre]ef. C'est pratique comme technique. Donc, le premier script montre comment fonctionne le modèle vecteur-binaire, qui sert à faire une recherche par motifs approchés à l'aide d'un joker.

Le second algo sert à faire une recherche par motifs approchés avec k différences près. Je l'ai pas encore documenté car j'ai eu du mal à l'adapter en PHP, et c'est surtout le résultat d'une looongue réflexion sur la monotie des diagonales dans les recherches par différences. Enfin bref, je vous passe les détails, c'est long et compliqué. On va juste considérer le résultat, à savoir que ça marche ;-).

Modèle Vecteur-Binaire :
<?php

header('content-type: text/plain');

/** Modèle Vecteur-Binaire **/


/**
 * On écrit un petit automate où :
 *   - X est le motif que l'on recherche, dans l'occurence y.
 *   - X est un ensemble de mots non vide, y est une suite de lettre de
 *     l'alphabet A.
 * 
 * init est le vecteur qui enregistre la position des mots de X qui commencent.
 * final est le vecteur qui enregistre la position des mots de X qui se
 * terminent.
 * Par exemple, si X = {aab}    , alors init = 110  , et final = 001.
 *              si X = {aab, cb}, alors init = 10010, et final = 00101 ;
 * 
 * mask est une table de vecteurs pour chaque lettre utilisée par y dans
 * l'alphabet A.
 * Par exemple, si y = abbcaabaacb, alors A = {a, b, c}.
 * mask va alors marquer la position de chaque lettre des mots de X.
 * Par exemple, X = {aab}, alors :
 *     mask[a] = 110
 *     mask[b ] = 001
 *     mask[c] = 000
 *
 */
function littleMachine ( $X, $y ) {

    $m     = strlen($X);
    $init  = 0 << $m;
    $final = 0 << $m;
    $y     = str_split($y);

    // check alphabet.
    foreach($y as $i => $a)
        $mask[$a] = 0 << $m;
    ksort($mask);

    $p = -1;
    $X = explode(' ', $X);

    // check each word of X.
    foreach($X as $e => $x) {

        $n     = strlen($x)-1;
        $init |= 1 << ($n-($p+1));
        $x     = str_split($x);

        // check each letter of x.
        foreach($x as $i => $a) {
            $p++;
            $mask[$a] |= 1 << ($n-$p);
        }

        $final |= 1 << ($n-$p);
    }

    return array(
        0 => $init,
        1 => $final,
        2 => $mask
    );
}

/**
 * On localise les mots courts.
 * 
 * y \in A, A = {a, b, c}, y = abbcaabaacb,
 * X \subseteq A*, le motif, tel que X = {aab},
 * m = |X| (la cardinalité de X).
 * 
 * Soit init    : 100
 *      final   : 001
 *      mask[a] : 110
 *          [b ] : 001
 *          [c] : 000
 *      r       : (init | (r >> 1)) & mask[a]
 *      offset  : r & final != 0
 * 
 * On a alors :
 * 
 *     +-----+---------+----------+----------+
 *     |  j  |  y[j]   |  bits r  |  offset  |
 *     +-----+---------+----------+----------+
 *     |  0  |   a     |  1 0 0   |  0 0 0   |
 *     |  1  |   b     |  0 0 0   |  0 0 0   |
 *     |  2  |   b     |  0 0 0   |  0 0 0   |
 *     |  3  |   c     |  0 0 0   |  0 0 0   |
 *     |  4  | / a \   |  1 0 0   |  0 0 0   | i = j-m-1 = 4
 *     |  5  | | a |=X |  1 1 0   |  0 0 0   |
 *     |  6  | \ b /   |  0 0 1   |> 0 0 1 < | j = 6, donc on déduit i.
 *     |  7  |   a     |  1 0 0   |  0 0 0   |
 *     |  8  |   a     |  1 1 0   |  0 0 0   |
 *     |  9  |   c     |  0 0 0   |  0 0 0   |
 *     | 10  |   b     |  0 0 0   |  0 0 0   |
 *     +-----+---------+----------+----------+
 * 
 * Il faut que les mots de X soient inférieurs à la longueur des mots machines.
 */
function matchShortWords ( $X, $y ) {

    list($init, $final, $mask) = littleMachine($X, $y);
    $m                         = strlen($X);
    $r                         = 0 << $m;
    $y                         = str_split($y);
    $j                         = 0;
    $offset                    = array();

    foreach($y as $i => $a) {

        $r = ($init | ($r >> 1)) & $mask[$a];

        if($r & $final != 0) {
            $i = $j-($m-1);
            $offset[] = array('i' => $i, 'j' => $j);
        }

        $j++;
    }

    return $offset;
}

$X      = 'aab';
$y      = 'abbcaabaacb';
$search = matchShortWords($X, $y);

$n      = count($search);
echo 'Recherche de "' . $X . '" dans "' . $y . '" : ' . "\n" .
     $n . ' occurence(s) trouvée(s).';

Affichera :

Code : Tout sélectionner

Recherche de "aab" dans "abbcaabaacb" : 1 occurence(s) trouvée(s).
Recherche par motifs approchés avec k différences :
<?php

header('content-type: text/plain');

/*  Recherche par motifs approchés, avec différences  */
/* sur le principe de la monotonie sur les diagonales */


function PatternMatchingWithDiff ( $x, $y, $k ) {

    $m      = strlen($x);
    $n      = strlen($y);
    $offset = array();

    for($d = -1; $d <= ($n-$m+$k+1); $d++)
        $L[-1][$d] = -2;

    for($q = 0; $q <= ($k-1); $q++) {

        $L[$q][-$q-1] = $q-1;
        $L[$q][-$q-2] = $q-1;
    }

    for($q = 0; $q <= $k; $q++) {

        for($d = -$q; $d <= ($n-$m+$k-$q); $d++) {

            $l         = max($L[$q-1][$d-1],
                             $L[$q-1][$d]  +1,
                             $L[$q-1][$d+1]+1);
            $l         = min($l, $m-1);
            $a         = substr($x, $l+1   , -($l)   +($m-1));
            $b         = substr($y, $d+$l+1, -($d+$l)+($n-1));
            $L[$q][$d] = $l+lcp($a, $b);

            if(   $L[$q][$d]    == $m-1
               || $d+$L[$q][$d] == $n-1) {
                $j            = $m+$d;
                $i            = $j-$m;
                $offset[$q][] = array('i' => $i, 'j' => $j);
            }
        }
    }

    return $offset;
}

function lcp ( $x, $y ) {

    $max = min(strlen($x), strlen($y));
    $i   = 0;

    while($i < $max && $x{$i} == $y{$i})
        $i++;

    return $i;
}

$x      = 'GATAA';
$y      = 'CAGATAAGAGAA';
$k      = 1;
$search = PatternMatchingWithDiff($x, $y, $k);

$n      = count($search);
echo 'Recherche de "' . $x . '" dans "' . $y . '" avec au plus ' . $k . ' différence(s) : ' . "\n";
foreach($search as $q => $n) {
    echo '  - pour ' . $q . ' différence(s), ' . count($n) . ' occurence(s) trouvée(s) :' . "\n";

    foreach($n as $i => $pos)
        echo '    - ' . substr($y, $pos['i'], -$pos['i']+$pos['j']) . ' ;' . "\n";
}
Affichera :

Code : Tout sélectionner

Recherche de "GATAA" dans "CAGATAAGAGAA" avec au plus 1 différence(s) : - pour 0 différence(s), 1 occurence(s) trouvée(s) : - GATAA ; - pour 1 différence(s), 4 occurence(s) trouvée(s) : - AGATA ; - GATAA ; - ATAAG ; - GAGAA ;
Vous pouvez tester, ça marche (normalement :P).


J'attends vos réactions sur les algos ci-dessus. Normalement, je peux encore optimiser une chouillat le modèle vecteur-binaire, mais c'est pas grand chose (comme virer le ksort() par exemple).

Je vais regarder de plus prêt le stemmer, ça à l'air d'être un bon compromis quand même. Merci Naholyr :).

Je pensais aussi à autre chose. Quand quelqu'un fait une recherche, et que la recherche est correcte, alors on enregistre sa requête dans une autre table. De cette façon, on obtient une liste des recherches qui ont le mieux porté leurs fruits. Ce serait encore une autre idée, nan ?

par naholyr » 28 août 2007, 10:27

Voilà le stemmer dont je parlais, il s'agit du stemmer "Paice/Husk". L'auteur original du script est en commentaires, je me suis contenté de faire un refactoring pour le passer en POO, et de compléter un peu la liste des règles FR (c'est là qu'est le gros du travail, qui n'est je pense jamais totalement terminé).

Note importante : j'ai écrit cette classe pour un site que j'ai repris et qui avait des problèmes assez bizarres d'encodage que je n'avais pas le temps de régler. Il y a donc quelques bidouilles de charset qui mériteraient d'être éclaircies pour une utilisation globale.

Pour l'utiliser c'est assez simple :
- Inclure "Stemmer_FR" ou "Stemmer_EN" (selon la langue à utiliser) ou s'assurer qu'ils seront autochargés
- Include "Stemmer" ou s'assurer qu'il sera autochargé
- Instancier l'objet avec $stemmer = Stemmer::factory('fr') ou Stemmer::factory('en')
- Appeler $stemmer->textStemIndex($monTexte) pour un texte
- On peut aussi appeler simplement $stemmer->stem($monMot) pour un simple mot


La class principale (note: dans la version que j'utilise réellement il y a également tout un set de fonctions destinées à ezPdo, afin de rendre automatique et persistante l'indexation de tout objet héritant de Stemmer, je vous en fais grâce) :
<?php

/**
 * Paice/Husk Stemmer
 * This code is in the public domain.
 * @author original author : Alexis Ulrich (http://alx2002.free.fr)
 * @author oop refactoring : Nicolas Chambrier ([email protected])
 */

if (!function_exists('mb_strrev')) {
	/**
	 * mb-string version of strrev
	 *
	 * @param string $string
	 * @param string $charset
	 * @return string
	 */
    function mb_strrev($string, $charset = null) {
        if ($charset === null) {
            $charset = mb_detect_encoding($string);
        }
        if ($charset == 'UTF-8') {
            $string = utf8_decode($string);
            $string = strrev($string);
            $string = utf8_encode($string);
        } else {
            $string = strrev($string);
        }
        return $string;
    }
}

if (!function_exists('mb_html_entity_decode')) {
	/**
	 * mb-string version of html_entity_decode
	 *
	 * @param string $string
	 * @param string $charset
	 * @return string
	 */
    function mb_html_entity_decode($string, $charset = null) {
        if ($charset === null) {
            $charset = mb_detect_encoding($string);
        }
        if ($charset == 'UTF-8') {
            $string = html_entity_decode($string);
            $string = utf8_encode($string);
        } else {
            $string = html_entity_decode($string);
        }
        return $string;
    }
}

/**
 * Main class
 *
 * @see Stemmer::factory()
 */
class Stemmer {

    protected $rulePattern = '/^([a-z]*)(\*){0,1}(\d)([a-z]*)([.|>])/';
    protected $rules = array('end0.');
    protected $stopWords = array();
    protected $vowels = 'aeiouy';
    protected $leastLettersIfFirstIsVowel = 0;
    protected $leastLettersIfFirstIsConsonant = 0;
    protected $language = null;
    
    protected static $instances = array();
    
    public $charset = 'UTF-8';

    /**
     * Returns the instance of the Stemmer for the given language 
     *
     * @param string $language
     * @return Stemmer
     */
    public static function factory($language = null) {
        if (!$language) {
            $language = MMVC_Lang::$code;
        }
        $key = strtolower($language);
        if (!isset(self::$instances[$key])) {
            $className = 'Stemmer_' . ucfirst($language);
            $parents = @class_parents($className);
            if ($parents && in_array('Stemmer',$parents)) {
                $instance = new $className;
                self::$instances[$key] = $instance;
            } else {
                return null;
            }
        }
        return @self::$instances[$key];
    }
    
    /**
     * Returns the language of the current Stemmer instance
     *
     * @return string
     */
    public function getLanguage() {
        return $this->language;
    }
    
    /**
     * returns the number of the first rule from the rule number $rule_number
     * that can be applied to the given reversed form
     * returns -1 if no rule can be applied, ie the stem has been found
     * 
     * @param string $reversedForm
     * @param int $ruleNumber
     * @return int
     */
    protected function getFirstRule($reversedForm, $ruleNumber = 0) {
        $nbRules = count($this->rules);
        for ($i=$ruleNumber; $i<$nbRules; ++$i) {
            $rule = $this->rules[$i];
            $rule = preg_replace($this->rulePattern, '\1', $rule);
            // var_dump($rule, $reversedForm, mb_substr($reversedForm,0,mb_strlen($rule))); echo '<br/>';
            if ($rule == mb_substr($reversedForm,0,mb_strlen($rule,$this->charset),$this->charset)) {
                return $i;
            }
        }
        return -1;
    }

    /**
     * Check the acceptability of a stem for a given language
     * 
     * @param string $reversed_stem the stem to check in reverse form
     */
    protected function checkAcceptability($reversed_stem) {
        if ( preg_match ("/[{$this->vowels}]$/", $reversed_stem) ) {
            // if the form starts with a vowel then at least X letters must remain after stemming (e.g.: "étaient" --> "ét")
            return mb_strlen($reversed_stem,$this->charset) > $this->leastLettersIfFirstIsVowel;
        } else {
            // if the form starts with a consonant then at X two letters must remain after stemming
            if (mb_strlen($reversed_stem,$this->charset) < $this->leastLettersIfFirstIsConsonant) {
                return False;
            }
            return preg_match("/[{$this->vowels}]/", $reversed_stem);
        }
    }

    /**
     * Returns the Stem indexing array for the given text. Each word's stem is stored as
     * a key of the array, and the value is the number of times this stem has been met.
     *
     * @param string $text
     * @return Array
     */
    public function textStemIndex($text) {
        preg_match_all('/\b[^\s\r\n\t\'\"]+\b/', mb_strtolower(mb_html_entity_decode(strip_tags($text), $this->charset), $this->charset), $matches);
        $words = $matches[0];
        $result = array();
        foreach ($words as $word) {
            if (strlen($word) > 1 && !in_array($word, $this->stopWords)) {
                $stem = $this->stem($word);
                if (!in_array($stem, $this->stopWords)) {
                    if (!isset($result[$stem])) {
                        $result[$stem] = 0;
                    }
                    $result[$stem] += 1;
                }
            }
        }
        return $result;
    }

    /**
     * the actual Paice/Husk stemmer
     * which returns a stem for the given form
     * 
     * @param string $form the word for which we want the stem
     */
    public function stem($form) {
        static $stems = array();
        if (!isset($stems[$form])) {
            $intact = True ;
            $stem_found = False ;
            $reversed_form = mb_strrev($form, $this->charset);
            $rule_number = 0 ;
            // that loop goes through the rules' array until it finds an ending one (ending by '.') or the last one ('end0.')
            while ( True ) {
                $rule_number = $this->getFirstRule($reversed_form, $rule_number);
                if ( $rule_number == - 1 ) {
                    // no other rule can be applied => the stem has been found
                    break;
                }
                $rule = $this->rules[$rule_number];
                preg_match($this->rulePattern, $rule, $matches);
                if (( $matches [ 2 ] != '*' ) || ( $intact )) {
                    $reversed_stem = $matches[ 4 ] . mb_substr($reversed_form , $matches[3], mb_strlen($reversed_form) - $matches[3], $this->charset);
                    if ( $this->checkAcceptability($reversed_stem) ) {
                        $reversed_form = $reversed_stem ;
                        if ( $matches[5] == '.' ) {
                            break;
                        }
                    }
                    else {
                        // go to another rule
                        $rule_number ++;
                    }
                }
                else {
                    // go to another rule
                    $rule_number ++;
                }
            }
            $stems[$form] = mb_strrev($reversed_form);
        }
        return $stems[$form];
    }

}
Le complément FR :
<?php

/**
 * @author nchambrier
 * @date 29 juin 07
 * @encoding UTF-8
 */

class Stemmer_Fr extends Stemmer {

    function __construct() {
        $this->language = 'fr';
        $this->charset = 'UTF-8';
        $this->rulePattern = '/^([a-zàâèéêëîïôûùç]*)(\*){0,1}(\d)([a-zàâèéêëîïôûùç]*)([.|>])/';
        $this->vowels = 'aàâeèéêëiîïoôuûùy';
        $this->leastLettersIfFirstIsVowel = 2;
        $this->leastLettersIfFirstIsConsonant = 2;
        $this->rules = array(
            'ug1>' ,            # { -gu > -g }
            'emsi4.',            # { -isme > - } 
            'etsi4.',            # { -iste > - }
            //'gol3.' ,           # { -log > - }
            'isme4.' ,           # { -isme > - }
            'iste4.' ,           # { -iste > - }
            'euq3>' ,           # { -que > - }
            'esre1>' ,             # { -erse > -ers }
            'esio1>' ,             # { -oise > -ois }
            'siol1.' ,             # { -lois > -loi }
            'siof0.' ,             # { -fois > -fois }
            'sioe0.' ,             # { -eois > -eois }
            'sio3>' ,              # { -ois > - }
            'st1>' ,               # { -ts > -t }
            'sf1>' ,               # { -fs > -f }
            'sle1>' ,              # { -els > -el }
            'slo1>' ,              # { -ols > -ol }
            'sé1>' ,               # { -és > -é }
            'étuae5.' ,            # { -eauté > - }
            'étuae2.' ,            # { -eauté > -eau }
            'tnia0.' ,             # { -aint > -aint }
            'tniv1.' ,             # { -vint > -vin }
            'tni3>' ,              # { -int > - }
            'suor1.' ,             # { -rous > -ou }
            'suo0.' ,              # { -ous > -ous }
            'sdrail5.' ,           # { -liards > -l }
            'sdrai4.' ,            # { -iards > -i }
            'erèi1>' ,             # { -ière > -ier }
            'sesue3x>' ,           # { -euses > -euse }
            'esuey5i.' ,           # { -yeuse > -i }
            'esue2x>' ,            # { -euse > -eux }
            'se1>' ,               # { -es > -e }
            'erèg3.' ,             # { -gère > -g }
            'eca1>' ,              # { -ace > -ac }
            'esiah0.' ,            # { -haise > - }
            'esi1>' ,              # { -ise > -is }
            'siss2.' ,             # { -ssis > -ss }
            'sir2>' ,              # { -ris > -r }
            'sit2>' ,              # { -tis > -t }
            'egané1.' ,            # { -énage > -énag }
            'egalli6>' ,           # { -illage > - }
            'egass1.' ,            # { -ssage > -sag }
            'egas0.' ,             # { -sage > - }
            'egat3.' ,             # { -tage > - }
            'ega3>' ,              # { -age > - }
            'ette4>' ,             # { -ette > - }
            'ett2>' ,              # { -tte > -t }
            'etio1.' ,             # { -oite > -oit }
            'tioç4c.' ,            # { -çoit > -c }
            'tio0.' ,              # { -oit > -oit }
            'et1>' ,               # { -te > -t }
            'eb1>' ,               # { -be > -b }
            'snia1>' ,             # { -ains > -ain }
            'eniatnau8>' ,         # { -uantaine > - }
            'eniatn4.' ,           # { -ntaine > -nt }
            'enia1>' ,             # { -aine > -ain }
            'niatnio3.' ,          # { -ointain > -oint }
            'niatg3.' ,            # { -gtain > -gt }
            'eé1>' ,               # { -ée > -é }
            'éhcat1.' ,            # { -taché > -tach }
            'éhca4.' ,             # { -aché > - }
            'étila5>' ,            # { -alité > - }
            'étici5.' ,            # { -icité > - }
            'étir1.' ,             # { -rité > -rit }
            'éti3>' ,              # { -ité > - }
            'égan1.' ,             # { -nagé > -nag }
            'éga3>' ,              # { -agé > - }
            'étehc1.' ,            # { -cheté > -chet }
            'éte3>' ,              # { -eté > - }
            'éit0.' ,              # { -tié > -tié }
            'é1>' ,                # { -é > - }
            'eire4.' ,             # { -erie > - }
            'eirue5.' ,            # { -eurie > - }
            'eio1.' ,              # { -oie > -oi }
            'eia1.' ,              # { -aie > -ai }
            'ei1>' ,               # { -ie > -i }
            'eng1.' ,              # { -gne > -gn }
            'xuaessi7.' ,          # { -isseaux > - }
            'xuae1>' ,             # { -eaux > -eau }
            'uaes0.' ,             # { -seau > -seau }
            'uae3.' ,              # { -eau > - }
            'xuave2l.' ,           # { -evaux > -eval }
            'xuav2li>' ,           # { -vaux > -vail }
            'xua3la>' ,            # { -aux > -al }
            'ela1>' ,              # { -ale > -al }
            'lart2.' ,             # { -tral > -tr }
            'lani2>' ,             # { -inal > -in }
            'laé2>' ,              # { -éal > -é }
            'siay4i.' ,            # { -yais > -i }    
            'siassia7.' ,          # { -aissais > - }    
            'siarv1*.' ,           # { -vrais > -vrai if intact }    
            'sia1>' ,              # { -ais > -ai }    
            'tneiayo6i.' ,         # { -oyaient > -oi }
            'tneiay6i.' ,          # { -yaient > -i }
            'tneiassia9.' ,        # { -aissaient > - }
            'tneiareio7.' ,        # { -oieraient > -oi }
            'tneia5>' ,            # { -aient > - }
            'tneia4>' ,            # { -aient > -a }
            'tiario4.' ,           # { -oirait > -oi }
            'tiarim3.' ,           # { -mirait > -mir }
            'tiaria3.' ,           # { -airait > -air }
            'tiaris3.' ,           # { -sirait > -sir }
            'tiari5.' ,            # { -irait > - }
            'tiarve6>' ,           # { -evrait > - }
            'tiare5>' ,            # { -erait > - }
            'iare4>' ,             # { -erai > - }
            'are3>' ,              # { -era > - }
            'tiay4i.' ,            # { -yait > -i }    
            'tia3>' ,              # { -ait > - }
            'tnay4i.' ,            # { -yant > -i }    
            'emèiu5>' ,            # { -uième > - }
            'emèi4>' ,             # { -ième > - }
            'tnaun3.' ,            # { -nuant > -nu }
            'tnauqo3.' ,           # { -oquant > -oqu }
            'tnau4>' ,             # { -uant > - }
            'tnaf0.' ,             # { -fant > -fant }
            'tnaté2>' ,            # { -étant > -ét }
            'tna3>' ,              # { -ant > - }
            'tno3>' ,              # { -ont > - }
            'zeiy4i.' ,            # { -yiez > -i }
            'zey3i.' ,             # { -yez > -i }
            'zeire5>' ,            # { -eriez > - }
            'zeird4.' ,            # { -driez > -d }
            'zeirio4.' ,           # { -oiriez > -oi }
            'ze2>' ,               # { -ez > - }
            'ssiab0.' ,            # { -baiss > - }
            'ssia4.' ,             # { -aiss > - }
            'ssi3.' ,              # { -iss > - }
            'tnemma6>' ,           # { -amment > - }
            'tnemesuey9i.' ,       # { -yeusement > -i }
            'tnemesue8>' ,         # { -eusement > - }
            'tnemevi7.' ,          # { -ivement > - }
            'tnemessia5.' ,        # { -aissement > -aiss }
            'tnemessi8.' ,         # { -issement > - }
            'tneme5>' ,            # { -ement > - }
            'tnemia4.' ,           # { -aiment > -ai }
            'tnemé5>' ,            # { -ément > - }
            'el2l>' ,              # { -le > -l }
            'lle3le>' ,            # { -ell > -el }
            'letô0.' ,             # { -ôtel > -ôtel }
            'lepp0.' ,             # { -ppel > -ppel }
            'le2>' ,               # { -el > - }
            'srei1>' ,             # { -iers > -ier }
            'reit3.' ,             # { -tier > -t }
            'reila2.' ,            # { -alier > -ali }
            'rei3>' ,              # { -ier > - }
            'ertâe5.' ,            # { -eâtre > - }
            'ertâé1.' ,            # { -éâtre > -éâtr }
            'ertâ4.' ,             # { -âtre > - }
            'drai4.' ,             # { -iard > - }
            'erdro0.' ,            # { -ordre > -ordre }
            'erute5.' ,            # { -eture > - }
            'ruta0.' ,             # { -atur > -atur }
            'eruta1.' ,            # { -ature > -atur }
            'erutiov1.' ,          # { -voiture > -voitur }
            'erub3.' ,             # { -bure > -b }
            'eruh3.' ,             # { -hure > -h }
            'erul3.' ,             # { -lure > -l }
            'er2r>' ,              # { -re > -r }
            'nn1>' ,               # { -nn > -n }
            'rèi3.' ,              # { -ièr > - }
            'srev0.' ,             # { -vers > -vers }
            'sr1>' ,               # { -rs > -r }
            'rid2>' ,              # { -dir > -d }
            're2>' ,               # { -er > - }
            'xuei4.' ,             # { -ieux > - }
            'esuei5.' ,            # { -ieuse > - }
            'lbati3.' ,            # { -itabl > -it }
            'lba3>' ,              # { -abl > - }
            'rueis0.' ,            # { -sieur > - }
            'ruehcn4.' ,           # { -ncheur > -nc }
            'ecirta6.' ,           # { -atrice > - }
            'ruetai6.' ,           # { -iateur > - }
            'rueta5.' ,            # { -ateur > - }
            'rueir0.' ,            # { -rieur > - }
            'rue3>' ,              # { -eur > - }
            'esseti6.' ,           # { -itesse > - }
            'essere6>' ,           # { -eresse > - }
            'esserd1.' ,           # { -dresse > -dress }
            'esse4>' ,             # { -esse > - }
            'essiab1.' ,           # { -baisse > -baiss }
            'essia5.' ,            # { -aisse > - }
            'essio1.' ,            # { -oisse > -oiss }
            'essi4.' ,             # { -isse > - }
            'essal4.' ,            # { -lasse > -l }
            'essa1>' ,             # { -asse > -ass }
            'ssab1.' ,             # { -bass > -bas }
            'essurp1.' ,           # { -prusse > -uss }
            'essu4.' ,             # { -usse > - }
            'essi1.' ,             # { -isse > -ss }
            'ssor1.' ,             # { -ross > -ros }
            'essor2.' ,             # { -rosse > -ros }
            'esso1>' ,             # { -osse > -oss }
            'ess2>' ,             # { -sse > -s }
            'tio3.' ,             # { -oit > - }
            'rès2re.' ,             # { -sèr > -ser }
            'rè0e.' ,             # { -èr > -ère }
            'esn1.' ,             # { -nse > -èns }
            'eu1>' ,                 # { -ue > -u }
            'sua0.' ,             # { -aus > -aus }
            'su1>' ,                 # { -us > -u }
            'utt1>' ,             # { -utt > -tt }
            'tuç3c.' ,             # { -çut > -c }
            'uç2c.' ,             # { -çu > -c }
            'ur1.' ,                 # { -ru > -r }
            'ehcn2>' ,             # { -nche > -nc }
            'ehcu1>' ,             # { -uche > -uch }
            'snorr3.' ,             # { -rrons > -rr }
            'snoru3.' ,             # { -urons > -ur }
            'snorua3.' ,             # { -aurons > -aur }
            'snorv3.' ,             # { -vrons > -vr }
            'snorio4.' ,             # { -oirons > -oi }
            'snori5.' ,             # { -irons > - }
            'snore5>' ,             # { -erons > - }
            'snortt4>' ,             # { -ttrons > -tt }
            'snortîa7.' ,             # { -aîtrons > - }
            'snort3.' ,             # { -trons > -tr }
            'snor4.' ,             # { -rons > - }
            'snossi6.' ,             # { -issons > - }
            'snoire6.' ,             # { -erions > - }
            'snoird5.' ,             # { -drions > -d }
            'snoitai7.' ,             # { -iations > - }
            'snoita6.' ,             # { -ations > - }
            'snoits1>' ,             # { -stions > -stion }
            'noits0.' ,             # { -stion > -stion }
            'snoi4>' ,             # { -ions > - }
            'noitaci7>' ,             # { -ication > - }
            'noitai6.' ,             # { -iation > - }
            'noita5.' ,             # { -ation > - }
            'noitu4.' ,             # { -ution > -u }
            'noi3>' ,             # { -ion > - }
            'snoya0.' ,             # { -ayons > -ayons }
            'snoy4i.' ,             # { -yons > -i }
            'snoça1.' ,             # { -açons > -açon }
            'snoçr1.' ,             # { -rçons > -rçon }
            'snoe4.' ,             # { -eons > - }
            'snosiar1>' ,             # { -raisons > - }
            'snola1.' ,             # { -alons > -alon }
            'sno3>' ,             # { -ons > - }
            'sno1>' ,             # { -ons > -on }
            'noll2.' ,             # { -llon > -ll }
            'tnennei4.' ,             # { -iennent > -ien }
            'ennei2>' ,             # { -ienne > -ien }
            'snei1>' ,             # { -iens > -ien }
            'sneé1>' ,             # { -éens > -éen }
            'enneé5e.' ,             # { -éenne > -e }
            'neé3e.' ,             # { -éen > -e }
            'neic0.' ,             # { -cien > -cien }
            'neiv0.' ,             # { -vien > -vien }
            'nei3.' ,             # { -ien > - }
            'sc1.' ,                 # { -cs > -c }
            'sd1.' ,                 # { -ds > -d }
            'sg1.' ,                 # { -gs > -g }
            'sni1.' ,             # { -ins > -in }
            'tiu0.' ,             # { -uit > - }
            'ti2.' ,                 # { -it > - }
            'sp1>' ,                 # { -ps > -p }
            'sna1>' ,             # { -ans > -an }
            'sue1.' ,             # { -eus > -eu }
            'enn2>' ,             # { -nne > -n }
            'nong2.' ,             # { -gnon > -gn }
            'noss2.' ,             # { -sson > -ss }
            'rioe4.' ,             # { -eoir > - }
            'riot0.' ,             # { -toir > -toir }
            'riorc1.' ,             # { -croir > -croi }
            'riovec5.' ,             # { -cevoir > -c }
            'rio3.' ,             # { -oir > - }
            'ric2.' ,             # { -cir > -l }
            'ril2.' ,             # { -lir > -l }
            'tnerim3.' ,             # { -mirent > -mir }
            'tneris3>' ,             # { -sirent > -sir }
            'tneri5.' ,             # { -irent > - }
            'tîa3.' ,             # { -aît > - }
            'riss2.' ,             # { -ssir > -ss }
            'tî2.' ,                 # { -ît > - }
            'tâ2>' ,                 # { -ât > - }
            'ario2.' ,             # { -oira > -oi }
            'arim1.' ,             # { -mira > -m }
            'ara1.' ,             # { -ara > -ar }
            'aris1.' ,             # { -sira > -sir }
            'ari3.' ,             # { -ira > - }
            'art1>' ,             # { -tra > -tr }
            'ardn2.' ,             # { -ndra > -nd }
            'arr1.' ,             # { -rra > -rr }
            'arua1.' ,             # { -aura > -aur }
            'aro1.' ,             # { -ora > -or }
            'arv1.' ,             # { -vra > -vr }
            'aru1.' ,             # { -ura > -ur }
            'ar2.' ,                 # { -ra > - }
            'rd1.' ,                 # { -dr > -d }
            'ud1.' ,                 # { -du > - }
            'ul1.' ,                 # { -lu > -l }
            'ini1.' ,             # { -ini > -in }
            'rin2.' ,             # { -nir > - }
            'tnessiab3.' ,             # { -baissent > -baiss }
            'tnessia7.' ,             # { -aissent > - }
            'tnessi6.' ,             # { -issent > - }
            'tnessni4.' ,             # { -inssent > -ins }
            'sini2.' ,             # { -inis > -in }
            'sl1.' ,                 # { -ls > -l }
            'iard3.' ,             # { -drai > -d }
            'iario3.' ,             # { -oirai > -oi }
            'ia2>' ,                 # { -ai > - }
            'io0.' ,                 # { -oi > -oi }
            'iule2.' ,             # { -elui > -el }
            'i1>' ,                 # { -i > - }
            'sid2.' ,             # { -dis > -d }
            'sic2.' ,             # { -cis > -c }
            'esoi4.' ,             # { -iose > - }
            'ed1.' ,                 # { -de > -d }
            'ai2>' ,                 # { -ia > - }
            'a1>' ,                 # { -a > - }
            'adr1.' ,             # { -rda > -rd }
            'tnerè5>' ,             # { -èrent > - }
            'evir1.' ,             # { -rive > -riv }
            'evio4>' ,             # { -oive > - }
            'evi3.' ,             # { -ive > - }
            'fita4.' ,             # { -atif > - }
            'fi2>' ,                 # { -if > - }
            'enie1.' ,             # { -eine > -ein }
            'sare4>' ,             # { -eras > - }
            'sari4>' ,             # { -iras > - }
            'sard3.' ,             # { -dras > -d }
            'sart2>' ,             # { -tras > -tr }
            'sa2.' ,                 # { -as > - }
            'tnessa6>' ,             # { -assent > - }
            'tnessu6>' ,             # { -ussent > - }
            'tnegna3.' ,             # { -angent > -ang }
            'tnegi3.' ,             # { -igent > -ig }
            'tneg0.' ,             # { -gent > -gent }
            'tneru5>' ,             # { -urent > - }
            'tnemg0.' ,             # { -gment > -gment }
            'tnerni4.' ,             # { -inrent > -in }
            'tneiv1.' ,             # { -vient > -vien }
            'tne3>' ,             # { -ent > - }
            'une1.' ,             # { -enu > -en }
            'en1>' ,                 # { -ne > -n }
            'nitn2.' ,             # { -ntin > - }
            'ecnay5i.' ,          # { -yance > -i }
            'ecnal1.' ,           # { -lance > -lanc }
            'ecna4.' ,            # { -ance > - }
            'ec1>' ,              # { -ce > -c }
            'nn1.' ,              # { -nn > -n }
            'rit2>' ,             # { -tir > - }
            'rut2>' ,             # { -tur > -t }
            'rud2.' ,             # { -dur > -d }
            'ugn1>' ,             # { -ngu > -ng }
            'eg1>' ,              # { -ge > -g }
            'tuo0.' ,             # { -out > -out }
            'tul2>' ,             # { -lut > -l }
            'tû2>' ,              # { -ût > - }
            'ev1>' ,              # { -ve > -v }
            'vè2ve>' ,            # { -èv > -ev }
            'rtt1>' ,             # { -ttr > -tt }
            'emissi6.' ,          # { -issime > - }
            'em1.' ,              # { -me > -m }
            'ehc1.' ,             # { -che > -ch }
            'céi2cè.' ,           # { -iéc > -ièc }
            'libi2l.' ,           # { -ibil > -ibl }
            'llie1.' ,            # { -eill > -eil }
            'liei4i.' ,           # { -ieil > -i }
            'xuev1.' ,            # { -veux > -veu }
            'xuey4i.' ,           # { -yeux > -i }
            'xueni5>' ,           # { -ineux > - }
            'xuell4.' ,           # { -lleux > -l }
            'xuere5.' ,           # { -ereux > - }
            'xue3>' ,             # { -eux > - }
            'rbé3rbè.' ,          # { -ébr > -èbr }
            'tur2.' ,             # { -rut > -r }
            'riré4re.' ,          # { -érir > -er }
            'rir2.' ,             # { -rir > -r }
            'câ2ca.' ,            # { -âc > -ac }
            'snu1.' ,             # { -uns > -un }
            'rtîa4.' ,            # { -aîtr > - }
            'long2.' ,            # { -gnol > -gn }
            'vec2.' ,             # { -cev > -c }
            'ç1c>' ,              # { -ç > -c }
            'ssilp3.' ,           # { -pliss > -pl }
            'silp2.' ,            # { -plis > -pl }
            'tèhc2te.' ,          # { -chèt > -chet }
            'nèm2ne.' ,           # { -mèn > -men }
            'llepp1.' ,           # { -ppell > -ppel }
            'tan2.' ,             # { -nat > -n }
            'rvè3rve.' ,          # { -èvr > -evr }
            'rvé3rve.' ,          # { -évr > -evr }
            'rè2re.' ,            # { -èr > -er }
            'ré2re.' ,            # { -ér > -er }
            'tè2te.' ,            # { -èt > -et }
            'té2te.' ,            # { -ét > -et }
            'epp1.' ,             # { -ppe > -pp }
            'eya2i.' ,            # { -aye > -ai }
            'ya1i.' ,             # { -ay > -ai }
            'yo1i.' ,             # { -oy > -oi }
            'esu1.' ,             # { -use > -us }
            'ugi1.' ,             # { -igu > -g }
            'tt1.' ,              # { -tt > -t }
            'e1.',
            
            # end rule: the stem has already been found
            'end0.'
        );
        $this->stopWords = array(
            'alors',
            'au',
            'aucuns',
            'aussi',
            'autre',
            'avant',
            'avec',
            'avoir',
            'bon',
            'car',
            'ce',
            'cela',
            'ces',
            'ceux',
            'chaque',
            'ci',
            'comme',
            'comment',
            'dans',
            'de',
            'des',
            'du',
            'dedans',
            'dehors',
            'depuis',
            'deux',
            'devrait',
            'doit',
            'donc',
            'dos',
            'droite',
            'début',
            'elle',
            'elles',
            'en',
            'encore',
            'essai',
            'est',
            'et',
            'eu',
            'fait',
            'faites',
            'fois',
            'font',
            'force',
            'haut',
            'hors',
            'ici',
            'il',
            'ils',
            'je',
            'juste',
            'la',
            'le',
            'les',
            'leur',
            'là',
            'ma',
            'maintenant',
            'mais',
            'mes',
            'mine',
            'moins',
            'mon',
            'mot',
            'même',
            'ni',
            'nommés',
            'notre',
            'nous',
            'nouveaux',
            'ou',
            'où',
            'par',
            'parce',
            'parole',
            'pas',
            'personnes',
            'peut',
            'peu',
            'pièce',
            'plupart',
            'pour',
            'pourquoi',
            'quand',
            'que',
            'quel',
            'quelle',
            'quelles',
            'quels',
            'qui',
            'sa',
            'sans',
            'se',
            'ses',
            'seulement',
            'si',
            'sien',
            'son',
            'sont',
            'sous',
            'soyez',
            'sujet',
            'sur',
            'ta',
            'tandis',
            'tellement',
            'tels',
            'tes',
            'ton',
            'tous',
            'tout',
            'trop',
            'très',
            'tu',
            'valeur',
            'voie',
            'voient',
            'vont',
            'votre',
            'vous',
            'vu',
            'ça',
            'étaient',
            'état',
            'étions',
            'été',
            'être',
        );
    }

}
Le complément EN :
<?php

/**
 * @author nchambrier
 * @date 29 juin 07
 * @encoding UTF-8
 */

class Stemmer_En extends Stemmer {

    function __construct() {
        $this->language = 'en';
        $this->charset = 'UTF-8';
        $this->rulePattern = '/^([a-z]*)(\*){0,1}(\d)([a-z]*)([.|>])/';
        $this->vowels = 'aeiouy';
        $this->leastLettersIfFirstIsVowel = 2;
        $this->leastLettersIfFirstIsConsonant = 3;
        $this->rules = array(
            'ai*2.',        # { -ia > -   if intact }
            'a*1.',            # { -a > -    if intact }
            'bb1.',            # { -bb > -b   }
            'city3s.',        # { -ytic > -ys }
            'ci2>',            # { -ic > -    }
            'cn1t>',        # { -nc > -nt  }
            'dd1.',            # { -dd > -d   }
            'dei3y>',        # { -ied > -y  }
            'deec2ss.',        # { -ceed > -cess }
            'dee1.',        # { -eed > -ee }
            'de2>',            # { -ed > -    }
            'dooh4>',        # { -hood > -  }
            'e1>',            # { -e > -     }
            'feil1v.',        # { -lief > -liev }
            'fi2>',            # { -if > -    }
            'gni3>',        # { -ing > -   }
            'gai3y.',        # { -iag > -y  }
            'ga2>',            # { -ag > -    }
            'gg1.',            # { -gg > -g   }
            'ht*2.',        # { -th > -   if intact }
            'hsiug5ct.',        # { -guish > -ct }
            'hsi3>',        # { -ish > -   }
            'i*1.',            # { -i > -    if intact }
            'i1y>',            # { -i > -y    }
            '@i1d.',        # { -i@ > -id   --  see nois4@> & vis3@> }
            'juf1s.',        # { -fuj > -fus }
            'ju1d.',        # { -uj > -ud  }
            'jo1d.',        # { -oj > -od  }
            'jeh1r.',        # { -hej > -her }
            'jrev1t.',        # { -verj > -vert }
            'jsim2t.',        # { -misj > -mit }
            'jn1d.',        # { -nj > -nd  }
            'j1s.',            # { -j > -s    }
            'lbaifi6.',        # { -ifiabl > - }
            'lbai4y.',        # { -iabl > -y }
            'lba3>',        # { -abl > -   }
            'lbi3.',        # { -ibl > -   }
            'lib2l>',        # { -bil > -bl }
            'lc1.',            # { -cl > c    }
            'lufi4y.',        # { -iful > -y }
            'luf3>',        # { -ful > -   }
            'lu2.',            # { -ul > -    }
            'lai3>',        # { -ial > -   }
            'lau3>',        # { -ual > -   }
            'la2>',            # { -al > -    }
            'll1.',            # { -ll > -l   }
            'mui3.',        # { -ium > -   }
            'mu*2.',        # { -um > -   if intact }
            'msi3>',        # { -ism > -   }
            'mm1.',            # { -mm > -m   }
            'nois4@>',        # { -sion > -@ }
            'noix4ct.',        # { -xion > -ct }
            'noi3>',        # { -ion > -   }
            'nai3>',        # { -ian > -   }
            'na2>',            # { -an > -    }
            'nee0.',        # { protect  -een }
            'ne2>',            # { -en > -    }
            'nn1.',            # { -nn > -n   }
            'pihs4>',        # { -ship > -  }
            'pp1.',            # { -pp > -p   }
            're2>',            # { -er > -    }
            'rae0.',        # { protect  -ear }
            'ra2.',            # { -ar > -    }
            'ro2>',            # { -or > -    }
            'ru2>',            # { -ur > -    }
            'rr1.',            # { -rr > -r   }
            'rt1>',            # { -tr > -t   }
            'rei3y>',        # { -ier > -y  }
            'sei3y>',        # { -ies > -y  }
            'sis2.',        # { -sis > -s  }
            'si2>',            # { -is > -    }
            'ssen4>',        # { -ness > -  }
            'ss0.',            # { protect  -ss }
            'suo3>',        # { -ous > -   }
            'su*2.',        # { -us > -   if intact }
            's*1>',            # { -s > -    if intact }
            's0.',            # { -s > -s    }
            'tacilp4y.',        # { -plicat > -ply }
            'ta2>',            # { -at > -    }
            'tnem4>',        # { -ment > -  }
            'tne3>',        # { -ent > -   }
            'tna3>',        # { -ant > -   }
            'tpir2b.',        # { -ript > -rib }
            'tpro2b.',        # { -orpt > -orb }
            'tcud1.',        # { -duct > -duc }
            'tpmus2.',        # { -sumpt > -sum }
            'tpec2iv.',        # { -cept > -ceiv }
            'tulo2v.',        # { -olut > -olv }
            'tsis0.',        # { protect  -sist }
            'tsi3>',        # { -ist > -   }
            'tt1.',            # { -tt > -t   }
            'uqi3.',        # { -iqu > -   }
            'ugo1.',        # { -ogu > -og }
            'vis3@>',        # { -siv > -@  }
            'vie0.',        # { protect  -eiv }
            'vi2>',            # { -iv > -    }
            'ylb1>',        # { -bly > -bl }
            'yli3y>',        # { -ily > -y  }
            'ylp0.',        # { protect  -ply }
            'yl2>',            # { -ly > -    }
            'ygo1.',        # { -ogy > -og }
            'yhp1.',        # { -phy > -ph }
            'ymo1.',        # { -omy > -om }
            'ypo1.',        # { -opy > -op }
            'yti3>',        # { -ity > -   }
            'yte3>',        # { -ety > -   }
            'ytl2.',        # { -lty > -l  }
            'yrtsi5.',        # { -istry > - }
            'yra3>',        # { -ary > -   }
            'yro3>',        # { -ory > -   }
            'yfi3.',        # { -ify > -   }
            'ycn2t>',        # { -ncy > -nt }
            'yca3>',        # { -acy > -   }
            'zi2>',            # { -iz > -    }
            'zy1s.',        # { -yz > -ys  }
            'end0.'            # end rule: the stem has already been found
        );
        $this->stopWords = array(
            'a',
            'about',
            'an',
            'are',
            'as',
            'at',
            'be',
            'by',
            'com',
            'de',
            'en',
            'for',
            'from',
            'how',
            'i',
            'in',
            'is',
            'it',
            'la',
            'my',
            'of',
            'on',
            'or',
            'that',
            'the',
            'this',
            'to',
            'very',
            'was',
            'what',
            'when',
            'where',
            'who',
            'will',
            'with',
            'und',
            'the',
            'www',
        );
    }

}
Je vous laisse faire mumuse avec ça ;) en sachant que c'est forcément toujours incomplet, je pense qu'il manquera toujours un stop-word ou une règle. Le but dans ces système c'est de choisir à partir de quel moment on s'arrête.

par momox » 28 août 2007, 10:19

La vous parlez qu'en Français en plus.
Si faut changer l'algo pour chaque langue, pas sorti de l'auberge.
Pas forcément, après on peut utiliser une couche d'abstraction qui permettra d'obtenir des résultats différends sans pour autant avoir a refaire tout l'algo.
C'est sur qu'après, pour les langues a déclinaison comme l'allemand, ca risque d'être comique :D

par icebreak » 28 août 2007, 06:20

La vous parlez qu'en Français en plus.
Si faut changer l'algo pour chaque langue, pas sorti de l'auberge.

par Hywan » 27 août 2007, 23:47

Hmm c'est très intéressant. J'en apprends beaucoup, merci.

Je lirais tous les liens demain matin, jsuis trop naze ce soir. En revanche, en algorithmique du texte, on est censé pouvoir détecter un suffixe, un préfixe, des bords, des périodicités etc. Ca pourrait peut-être être utile. On est également censé savoir faire une recherche par motifs approchés. Je l'ai testé sur papier sur mes genoux à la frontière Finlandaise, ça marchait :P.

Le soucis qu'on a avec les motifs approchés, c'est qu'on doit travailler sur les données de la base lors de la recherche. SQL ne sait pas faire ça, c'est bien dommage. Mais peut être quand faisant un bon algo, on pourrait espérer le mettre en oeuvre. Je ferai un petit test demain, et je vous soumettrais l'algo avec les explications (si j'ai le temps, sinon ce sera dans quelques jours).

Bonne nuit à tous et merci pour vos infos' ;-).

par naholyr » 27 août 2007, 20:58

Bah en pondérant les résultats par le nombre d'occurrences, on arrive à des résultats assez satisfaisants. J'entends bien *assez satisfaisant* *pour un système facile à mettre en place et à comprendre*.

Les algos plus puissants sont tellement lourds et complexes, qu'ils sont hors de propos en général pour nos applications.

par Hubert Roksor » 27 août 2007, 20:23

Pour le stemming, la référence du genre s'appelle Snowball. Tu verras sur leur site, toutes les règles sont clairement* expliquées. Attention, le stemming augmente le recall au prix de la pertinence, à vous de voir ce qui vous intéresse le plus... J'imagine que sur un site marchand on voudra avoir un super recall pour proposer le plus de produits/services pouvant correspondre, alors que sur un site de news (ou toute autre grosse banque de données), on voudra les résultats les plus pertinents.
Si tu veux que je te fasse un 'tit cours d'algorithmique de texte, je peux
C'est sympa, mais ma connaissance actuelle me suffit : j'en sais juste assez pour savoir que c'est un boulot à plein temps pour être vraiment bon :lol:



* clairement, pour un ordinateur. Pour un humain, ça dépend un peu plus :lol:

par naholyr » 27 août 2007, 19:54

Très simplement, selon la langue, en supprimant les "e" à la fin, les "ist", les "ogu", etc… Toute une liste de règles consistant à dire "si mot finit par XXX ou commence par YYY alors remplacer par ZZZ", certaines règles étant "finales" (dès qu'on applique cette règle, on n'en applique pas d'autre).

par Sékiltoyai » 27 août 2007, 19:39

Ouais problème, comment tu détermines la racine du mot, si ce n'est avec un dictionnaire très exhaustif ?

par naholyr » 27 août 2007, 19:36

En passant par la racinification (stemming) on obtient de très bon résultats. Je retrouverai ça au boulot j'en ai complété un existant qui m'a donné assez bonne satisfaction.

Pour rappel, la recherche par racine consiste en deux parties :
1. L'indexation. On prend la racine de chaque mot d'un texte, et en base on stocke un triplet (document, racine, nombre d'occurrences) pour chacune d'elle.
2. La recherche. On prend la racine de chaque mot de la recherche, et on fait une recherche sur cette racine, en considérant le nombre d'occurrences par document comme un indicateur de conformité à la recherche.

Exemple : si je cherche "archéologue", je trouverai aussi bien un texte intitulé "archéologie" quand bien même le mot exact d'"archéologue" ne figurerait nulle part.
Autre avantage, les analphabète pourront chercher archéologien ça marchera aussi :lol:

par Hywan » 27 août 2007, 10:32

Si tu veux que je te fasse un 'tit cours d'algorithmique de texte, je peux, mais je ne le ferai pas sur le forum. Je prépare un petit PDF en LaTeX qui explique 2 ou 3 choses en ce moment. C'est juste pour ma mémoire, rien de formelle.

Muè, bon, ok, en PHP, on ne pourra jamais faire de recherches correctement donc ? Sauf si on passe par un programme intermédiaire comme Sphinx (pas mal du tout, j'ai commencé à regarder les codes hier soir).

par Hubert Roksor » 26 août 2007, 23:58

y est également un mot (comme il peut être un groupe de mot, si on ne considère pas l'espace comme un caractère qui sépare les mots)
Ah, ben dans ce cas tu peux rechercher "bigram" ou "n-gram", et je ne sais pas comment les détecter/représenter.

Pour Perl vs PHP, je doute que Perl soit suffisamment rapide, c'est plutôt dans le C qu'il faudrait taper...

par Hywan » 26 août 2007, 23:30

Hey :)

Merci pour tes conseils. Je me doute bien que les performances de PHP sont lamentables. Et c'est pourquoi je pensais à autre chose. Si on faisait le script en Perl, ce serait sûrement plus rapide non ? Pourrait-on alors, lancer ce script dans PHP, et transférer des données de Perl à PHP. C'était une piste à laquelle j'avais penser.

Pour répondre à ta question, y est également un mot (comme il peut être un groupe de mot, si on ne considère pas l'espace comme un caractère qui sépare les mots). Mes connaissances en algorithmique du texte sont toutes récentes et donc pauvres également. C'est juste qu'en lisant ce livre, ça m'a donné pas mal d'idées, et je trouve ça dommage de ne pas en profiter ^^.

Sur Google, j'ai trouvé ceci : http://research.google.com/pubs/papers.html#category1. Ca pourrait sûrement être utile, mais j'ai pas le temps de lire tous ces livres proposés ^^. Je vais donc me tourner du côté de Sphinx, voir un peu ce qu'ils ont fait :). Merci pour le lien.

J'attends d'autres avis.

par Hubert Roksor » 26 août 2007, 20:29

Avant toute chose, plutôt que de créer son propre moteur de recherche, je ne saurais trop recommander d'utiliser celui de MySQL ou mieux encore Sphinx qui est très très puissant et dans une certaine mesure intégrable à MySQL (tout ça nécessite d'avoir un accès shell à son serveur, pas de mutualisé).

Pour en revenir au sujet, même si ma connaissance en algorithmie de texte est très limitée (lire "quasi-inexistante") je suis certain d'une chose : tu ne pourras rien implémenter de tout cela en PHP pour des raisons de performances. Les structures de données requises ne se marrient pas vraiment avec ce que propose PHP ou SQL, et tu te retrouveras à devoir choisir entre un code plein de hacks (= difficile à maintenir) avec des performances médiocres, ou un code clean avec des performances désastreuses. Je ne cherche pas à te convaincre d'arrêter tes recherches, mais je te recommande fortement de ne pas trop t'investir non plus.

Oups, j'allais me lancer dans un truc plus long quand je me suis aperçu que je n'allais peut-être pas parler de la même chose, quand tu dis "X est un mot de y", X est un mot, mais y c'est quoi ?

Ah au fait, je n'arrive plus à mettre la main sur la page précise, mais si tu t'aventures du côté de http://labs.google.com tu devrais trouver pas mal de trucs. Notamment, Google proposait une grosse collection de mots les plus couramment mal-orthographiés.

par Hywan » 24 août 2007, 22:59

Hmm très intéressant. Merci beaucoup pour ce lien.
Une autre idée ?