Discuţie PHP – Extragere automată a cuvintelor şi frazelor cheie dintr-un text dat

Uau… nu am mai scris de mult timp un tutorial în română. Mă miră că mai ştiu româneşte. Apropo de română, acest tutorial este tocmai despre limba română sau mai bine zis despre cum am început construrea unui “motor” de extragere a cuvintelor cheie din text.

Atenţionez că acesta nu este un script pe care să îl foloseşti cu copy/paste, ci o discuţie principială ce foloseşte PHP drept cod exemplificator.

Multă lume ar putea întreba “Păi de ce să construieşti aşa ceva? Nu există deja un script sau un api pentru asta?”. Există şi vă invit să căutaţi aşa ceva pentru a-l aplica şi la limba română.

Dar până atunci, să vorbim despre cum am lucrat eu. Poate mă puteţi ajuta la schimbarea codului.

Întâi să vorbim principial despre ce am vrut să fac şi care sunt problemele pe care am încercat să le abordez. Este vorba de un extractor de cuvinte şi fraze cheie din text. Cu alte cuvinte, dai un text scriptului şi acesta ar trebui să găsească cuvintele cheie şi frazele cheie şi să ordoneze totul în funcţie de numărul de apariţii (cuvinte) şi de scorul pe care frazele îl obţin. În funcţie de aceşti parametri să scoată în faţă cuvintele şi frazele cheie care definesc cel mai bine textul care a fost dat.

Care este problema? Ce face specială aceasta întreprindere? Faptul că vorbim de limba română şi nu de limba engleză, chiar dacă presupun că acelaşi script s-ar putea aplica la fel de bine şi pentru limba engleză. Ce este specific limbii române şi altor limbi asemenea ei? Faptul că mare parte din afixe de aici sunt sudate în cuvânt şi nu cum se întâmplă în limba engleză, să fie separate de cuvânt. Adică (pe linia “mai catolic decat Papa”) româna e “mai flexionară” decât engleza. Din păcate nu ştiu să explic exact ce vreau să spun. Poate se găseşte cineva să o facă în comentarii, motiv pentru care îi mulţumesc.

Un al doilea punct pe care doresc să îl subliniez este faptul că ce va face acest script nu este neapărat mulţumitor ochiului vizitatorului site-ului, rezultatele vizibile putând descuraja. Cu toate acestea, rezultatele invizibile fac ca o aplicaţie bazată pe un astfel de script să funcţioneze. Cu alte cuvinte, rostul scriptului nu este acela de a afişa în “limbaj uman”, cu flexionările de rigoare, cuvintele şi frazele cheie, ci, în corelaţie cu un script de căutare care să funcţioneze pe acelaşi principiu ca acest script, să afişeze articolele cele mai reprezentative pentru ceea ce caută un vizitator al site-ului.

Aşadar, să începem.

Presupunând ca avem un text pe care îl asignăm unei variabile numite $text încep prin a-l curăţa de diacritice şi de a transforma toate caracterele în litere mici. Pentru asta am o funcţie care primeşte textul şi returnează textul curăţat:

function clean_text($text)
{
$text = str_replace(PHP_EOL, '', $text);
$foreign_characters = array(
'/ä|æ|ǽ/' => 'ae',
'/ö|œ/' => 'oe',
'/ü/' => 'ue',
'/Ä/' => 'Ae',
'/Ü/' => 'Ue',
'/Ö/' => 'Oe',
'/À|Á|Â|Ã|Ä|Å|Ǻ|Ā|Ă|Ą|Ǎ|Α|Ά|Ả|Ạ|Ầ|Ẫ|Ẩ|Ậ|Ằ|Ắ|Ẵ|Ẳ|Ặ|А/' => 'A',
'/à|á|â|ã|å|ǻ|ā|ă|ą|ǎ|ª|α|ά|ả|ạ|ầ|ấ|ẫ|ẩ|ậ|ằ|ắ|ẵ|ẳ|ặ|а/' => 'a',
'/Б/' => 'B',
'/б/' => 'b',
'/Ç|Ć|Ĉ|Ċ|Č/' => 'C',
'/ç|ć|ĉ|ċ|č/' => 'c',
'/Д/' => 'D',
'/д/' => 'd',
'/Ð|Ď|Đ|Δ/' => 'Dj',
'/ð|ď|đ|δ/' => 'dj',
'/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě|Ε|Έ|Ẽ|Ẻ|Ẹ|Ề|Ế|Ễ|Ể|Ệ|Е|Э/' => 'E',
'/è|é|ê|ë|ē|ĕ|ė|ę|ě|έ|ε|ẽ|ẻ|ẹ|ề|ế|ễ|ể|ệ|е|э/' => 'e',
'/Ф/' => 'F',
'/ф/' => 'f',
'/Ĝ|Ğ|Ġ|Ģ|Γ|Г|Ґ/' => 'G',
'/ĝ|ğ|ġ|ģ|γ|г|ґ/' => 'g',
'/Ĥ|Ħ/' => 'H',
'/ĥ|ħ/' => 'h',
'/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ|Η|Ή|Ί|Ι|Ϊ|Ỉ|Ị|И|Ы/' => 'I',
'/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı|η|ή|ί|ι|ϊ|ỉ|ị|и|ы|ї/' => 'i',
'/Ĵ/' => 'J',
'/ĵ/' => 'j',
'/Ķ|Κ|К/' => 'K',
'/ķ|κ|к/' => 'k',
'/Ĺ|Ļ|Ľ|Ŀ|Ł|Λ|Л/' => 'L',
'/ĺ|ļ|ľ|ŀ|ł|λ|л/' => 'l',
'/М/' => 'M',
'/м/' => 'm',
'/Ñ|Ń|Ņ|Ň|Ν|Н/' => 'N',
'/ñ|ń|ņ|ň|ʼn|ν|н/' => 'n',
'/Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ|Ο|Ό|Ω|Ώ|Ỏ|Ọ|Ồ|Ố|Ỗ|Ổ|Ộ|Ờ|Ớ|Ỡ|Ở|Ợ|О/' => 'O',
'/ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º|ο|ό|ω|ώ|ỏ|ọ|ồ|ố|ỗ|ổ|ộ|ờ|ớ|ỡ|ở|ợ|о/' => 'o',
'/П/' => 'P',
'/п/' => 'p',
'/Ŕ|Ŗ|Ř|Ρ|Р/' => 'R',
'/ŕ|ŗ|ř|ρ|р/' => 'r',
'/Ś|Ŝ|Ş|Ș|Š|Σ|С/' => 'S',
'/ś|ŝ|ş|ș|š|ſ|σ|ς|с/' => 's',
'/Ț|Ţ|Ť|Ŧ|τ|Т/' => 'T',
'/ț|ţ|ť|ŧ|т/' => 't',
'/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ|Ũ|Ủ|Ụ|Ừ|Ứ|Ữ|Ử|Ự|У/' => 'U',
'/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ|υ|ύ|ϋ|ủ|ụ|ừ|ứ|ữ|ử|ự|у/' => 'u',
'/Ý|Ÿ|Ŷ|Υ|Ύ|Ϋ|Ỳ|Ỹ|Ỷ|Ỵ|Й/' => 'Y',
'/ý|ÿ|ŷ|ỳ|ỹ|ỷ|ỵ|й/' => 'y',
'/В/' => 'V',
'/в/' => 'v',
'/Ŵ/' => 'W',
'/ŵ/' => 'w',
'/Ź|Ż|Ž|Ζ|З/' => 'Z',
'/ź|ż|ž|ζ|з/' => 'z',
'/Æ|Ǽ/' => 'AE',
'/ß/' => 'ss',
'/IJ/' => 'IJ',
'/ij/' => 'ij',
'/Œ/' => 'OE',
'/ƒ/' => 'f',
'/ξ/' => 'ks',
'/π/' => 'p',
'/β/' => 'v',
'/μ/' => 'm',
'/ψ/' => 'ps',
'/Ё/' => 'Yo',
'/ё/' => 'yo',
'/Є/' => 'Ye',
'/є/' => 'ye',
'/Ї/' => 'Yi',
'/Ж/' => 'Zh',
'/ж/' => 'zh',
'/Х/' => 'Kh',
'/х/' => 'kh',
'/Ц/' => 'Ts',
'/ц/' => 'ts',
'/Ч/' => 'Ch',
'/ч/' => 'ch',
'/Ш/' => 'Sh',
'/ш/' => 'sh',
'/Щ/' => 'Shch',
'/щ/' => 'shch',
'/Ъ|ъ|Ь|ь/' => '',
'/Ю/' => 'Yu',
'/ю/' => 'yu',
'/Я/' => 'Ya',
'/я/' => 'ya'
);
if (empty($foreign_characters) OR ! is_array($foreign_characters))
{
$array_from = array();
$array_to = array();
}
else
{
$array_from = array_keys($foreign_characters);
$array_to = array_values($foreign_characters);
}

$text = preg_replace($array_from, $array_to, $text);
$text = strtolower($text);

return $text;
}

Nu vă uitaţi la înşiruirea de litere ciudate… Funcţia este în mare parte luată din CodeIgniter.

$text = clean_text($text);

După ce am curăţat textul, rămâne să îl împart în “fraze”, prin fraze înţelegând mai degrabă unităţi ce iniţial au fost despărţite prin semne de punctuaţie. Fac asta printr-o altă funcţie:

function add_stop_signs($text)
{
$text = str_replace(array('.',',',';',':','"','„','”','(',')','[',']','?','!'),'|',$text);
return $text;
}

$text = add_stop_signs($text);

Acum toate frazele sunt despărţite prin coloana verticală (semnul “pipe” în engleză). Acest pas mă ajută să sparg în continuare textul în frazele componente, realizând un array cu acestea:

function break_into_blocks($text)
{
$text_arr = explode('|',$text);

$blocks = array();
foreach($text_arr as $block)
{
if(strlen(trim($block))>0)
{
$block = str_replace('-',' ',$block);
$block = preg_replace('/ +/',' ',$block);
$blocks[] = trim($block);
}

}
return $blocks;
}


$blocks = break_into_blocks($text);

După ce am făcut asta, iau fiecare bloc-frază şi îl sparg la rândului lui în entităţile (cuvinte, cifre etc.) componente, având în acelaşi timp grijă ca dacă am o entitate nouă să o păstrez într-o listă de cuvinte pe care ulterior să o folosesc la căutarea acestora într-un dicţionar al limbii române:

$words = array();
$blocks_as_arrays = array();
$depth = 0;
$total_entities = 0;
foreach($blocks as $block)
{
$block = explode(' ',$block);
if(sizeof($block)>$depth) $depth = sizeof($block);
if(sizeof($block)>1)
{
$blocks_as_arrays[] = $block;
}
foreach($block as $entity)
{
$total_entities++;
if(!is_numeric(trim($entity)) && !in_array(trim($entity),$words))
{
$words[] = $entity;
}
}
}

OK… ce se întâmplă cu $words şi care este faza cu “dicţionarul limbii române”. Ei bine, trebuie cumva să gasim forma de bază (rădăcina) a cuvintelor pornind de la forma flexionată a lor. “Dicţionarul limbii române” îl putem construi de la zero, presupun că printr-o interfaţă în care să adaugi fiecare cuvânt în parte cu formele flexionare. Avantajul unei astfel de abordări ar fi acela că astfel vei avea un dicţionar specific aplicaţiei tale care să nu stocheze cuvinte nenecesare precum regionalisme, cuvinte învechite sau, cine ştie, neologisme chiar. Spre exemplu, la un test făcut de mine pe o ştire, “NATO” a fost găsit ca fiind o flexiune a cuvântului “nata”… care nu ştiu ce înseamnă.

Dacă însă doreşti, poţi oricând să te bucuri de ceea ce numim “open source”, mai exact de xml-ul cu toate fonemele de pe dexonline.ro (http://wiki.dexonline.ro/wiki/Protocol_de_exportare_a_datelor). Eu aşa am făcut.

Folosind un script precum cel de jos, am luat xml-ul (care, apropo, are cam 250MB) şi l-am exportat într-un tabel MySQL:

<?php
ini_set('memory_limit', '-1');
set_time_limit (0);
header('Content-Type: text/html; charset=utf-8');

$host = 'localhost';
$dbname = 'lexeme';
$user = '...';
$pass = '...';
try
{
$options = array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, /* You should pass the parameter which puts PDO into exception mode; other parameters would have been: PDO::ERRMODE_SILENT = error codes and PDO::ERRMODE_WARNING = E_WARNING */
PDO::ATTR_EMULATE_PREPARES => false, /* Turn off prepare emulation which is enabled in MySQL driver by default, but really should be turned off to use PDO safely, really only usable if you are using an old version of MySQL. */
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, /* If you want to get the results as associative arrays. I prefer them to objects (PDO::FETCH_OBJ instead of PDO::FETCH_ASSOC). */
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8' // Setting for reading correct special characters. This one only works before PHP 5.3
);
$db = new PDO("mysql:host=$host;dbname=$dbname;", $user, $pass, $options);
}
catch(PDOException $e)
{
echo $e->getMessage();
}


$xml = new XMLReader();
$xml->open("lexeme.xml");

$foreign_characters = array(
'/ä|æ|ǽ/' => 'ae',
'/ö|œ/' => 'oe',
'/ü/' => 'ue',
'/Ä/' => 'Ae',
'/Ü/' => 'Ue',
'/Ö/' => 'Oe',
'/À|Á|Â|Ã|Ä|Å|Ǻ|Ā|Ă|Ą|Ǎ|Α|Ά|Ả|Ạ|Ầ|Ẫ|Ẩ|Ậ|Ằ|Ắ|Ẵ|Ẳ|Ặ|А/' => 'A',
'/à|á|â|ã|å|ǻ|ā|ă|ą|ǎ|ª|α|ά|ả|ạ|ầ|ấ|ẫ|ẩ|ậ|ằ|ắ|ẵ|ẳ|ặ|а/' => 'a',
'/Б/' => 'B',
'/б/' => 'b',
'/Ç|Ć|Ĉ|Ċ|Č/' => 'C',
'/ç|ć|ĉ|ċ|č/' => 'c',
'/Д/' => 'D',
'/д/' => 'd',
'/Ð|Ď|Đ|Δ/' => 'Dj',
'/ð|ď|đ|δ/' => 'dj',
'/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě|Ε|Έ|Ẽ|Ẻ|Ẹ|Ề|Ế|Ễ|Ể|Ệ|Е|Э/' => 'E',
'/è|é|ê|ë|ē|ĕ|ė|ę|ě|έ|ε|ẽ|ẻ|ẹ|ề|ế|ễ|ể|ệ|е|э/' => 'e',
'/Ф/' => 'F',
'/ф/' => 'f',
'/Ĝ|Ğ|Ġ|Ģ|Γ|Г|Ґ/' => 'G',
'/ĝ|ğ|ġ|ģ|γ|г|ґ/' => 'g',
'/Ĥ|Ħ/' => 'H',
'/ĥ|ħ/' => 'h',
'/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ|Η|Ή|Ί|Ι|Ϊ|Ỉ|Ị|И|Ы/' => 'I',
'/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı|η|ή|ί|ι|ϊ|ỉ|ị|и|ы|ї/' => 'i',
'/Ĵ/' => 'J',
'/ĵ/' => 'j',
'/Ķ|Κ|К/' => 'K',
'/ķ|κ|к/' => 'k',
'/Ĺ|Ļ|Ľ|Ŀ|Ł|Λ|Л/' => 'L',
'/ĺ|ļ|ľ|ŀ|ł|λ|л/' => 'l',
'/М/' => 'M',
'/м/' => 'm',
'/Ñ|Ń|Ņ|Ň|Ν|Н/' => 'N',
'/ñ|ń|ņ|ň|ʼn|ν|н/' => 'n',
'/Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ|Ο|Ό|Ω|Ώ|Ỏ|Ọ|Ồ|Ố|Ỗ|Ổ|Ộ|Ờ|Ớ|Ỡ|Ở|Ợ|О/' => 'O',
'/ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º|ο|ό|ω|ώ|ỏ|ọ|ồ|ố|ỗ|ổ|ộ|ờ|ớ|ỡ|ở|ợ|о/' => 'o',
'/П/' => 'P',
'/п/' => 'p',
'/Ŕ|Ŗ|Ř|Ρ|Р/' => 'R',
'/ŕ|ŗ|ř|ρ|р/' => 'r',
'/Ś|Ŝ|Ş|Ș|Š|Σ|С/' => 'S',
'/ś|ŝ|ş|ș|š|ſ|σ|ς|с/' => 's',
'/Ț|Ţ|Ť|Ŧ|τ|Т/' => 'T',
'/ț|ţ|ť|ŧ|т/' => 't',
'/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ|Ũ|Ủ|Ụ|Ừ|Ứ|Ữ|Ử|Ự|У/' => 'U',
'/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ|υ|ύ|ϋ|ủ|ụ|ừ|ứ|ữ|ử|ự|у/' => 'u',
'/Ý|Ÿ|Ŷ|Υ|Ύ|Ϋ|Ỳ|Ỹ|Ỷ|Ỵ|Й/' => 'Y',
'/ý|ÿ|ŷ|ỳ|ỹ|ỷ|ỵ|й/' => 'y',
'/В/' => 'V',
'/в/' => 'v',
'/Ŵ/' => 'W',
'/ŵ/' => 'w',
'/Ź|Ż|Ž|Ζ|З/' => 'Z',
'/ź|ż|ž|ζ|з/' => 'z',
'/Æ|Ǽ/' => 'AE',
'/ß/' => 'ss',
'/IJ/' => 'IJ',
'/ij/' => 'ij',
'/Œ/' => 'OE',
'/ƒ/' => 'f',
'/ξ/' => 'ks',
'/π/' => 'p',
'/β/' => 'v',
'/μ/' => 'm',
'/ψ/' => 'ps',
'/Ё/' => 'Yo',
'/ё/' => 'yo',
'/Є/' => 'Ye',
'/є/' => 'ye',
'/Ї/' => 'Yi',
'/Ж/' => 'Zh',
'/ж/' => 'zh',
'/Х/' => 'Kh',
'/х/' => 'kh',
'/Ц/' => 'Ts',
'/ц/' => 'ts',
'/Ч/' => 'Ch',
'/ч/' => 'ch',
'/Ш/' => 'Sh',
'/ш/' => 'sh',
'/Щ/' => 'Shch',
'/щ/' => 'shch',
'/Ъ|ъ|Ь|ь/' => '',
'/Ю/' => 'Yu',
'/ю/' => 'yu',
'/Я/' => 'Ya',
'/я/' => 'ya'
);


if (empty($foreign_characters) OR ! is_array($foreign_characters))
{
$array_from = array();
$array_to = array();
}
else
{
$array_from = array_keys($foreign_characters);
$array_to = array_values($foreign_characters);
}


while($xml->read())
{
if($xml->nodeType == XMLREADER::ELEMENT && $xml->localName == 'Lexem')
{
//$parentId = $xml->getAttribute('id');
$node = new SimpleXMLElement($xml->readOuterXML());
$type = (string) $node->Description;
$theLexem = (string) $node->Form;
$baseLexem = str_replace('\'','',$theLexem);
$sth = $db->prepare("INSERT INTO words(clean_base, base ,lexem, parent_id, type, inflection_id) VALUES(?,?,?,?,?,?)");
$cleanBaseLexem = preg_replace($array_from, $array_to, $baseLexem);
$sth->execute(array($cleanBaseLexem, $baseLexem, $theLexem, '0', $type, '0'));
$parentId = $db->lastInsertId();
foreach($node->InflectedForm as $inflection)
{
$inflectionId = (int) $inflection->InflectionId;
$inflectionForm = (string) $inflection->Form;
$baseInflectionForm = str_replace('\'','',$inflectionForm);
$sth = $db->prepare("INSERT INTO words(clean_base, base, lexem, parent_id, type, inflection_id) VALUES(?,?,?,?,?,?)");
$cleanBase = preg_replace($array_from, $array_to, $baseInflectionForm);
$sth->execute(array($cleanBase,$baseInflectionForm, $inflectionForm, $parentId, $type, $inflectionId));
}
}


}

echo 'import realizat cu succes';

…Sper să nu te legi de cod…

Întorcându-ne la scriptul nostru…

Ziceam ceva de $words şi de dicţionar. Ei bine, cu cuvintele pe care le-am scos, încercăm să aflăm care este cuvântul rădăcină al acestora, interogând dicţionarul:

$the_bases = implode('" OR clean_base="',$words);
$the_bases = 'clean_base="'.$the_bases.'"';

$word_ids = array();

$sth = $db->query('SELECT id, clean_base, parent_id FROM words WHERE '.$the_bases.' GROUP BY clean_base COLLATE utf8_romanian_ci');

while($row = $sth->fetch(PDO::FETCH_ASSOC))
{
$the_word = strtolower($row['clean_base']);
//$keys = array_keys($words, $the_word);
if($row['parent_id']=='0')
{
$word_ids[$the_word] = $row['id'];
}
else
{
$word_ids[$the_word] = $row['parent_id'];
}
}

ksort($word_ids);

$word_ids va conţine toate cuvintele, cheia elementului fiind cuvântul, iar valoare fiind id-ul cuvântului rădăcină aferent cuvântului din cheie.

De asemenea, putem deja să facem un nou array cuvintele rădăcină, punând id-ul drept cheie pentru cuvânt. Vom avea nevoie de ele mai încolo:

$the_base_words = array();
$the_ids = implode(',',$word_ids);

echo $the_ids;

$sth = $db->query('SELECT id, clean_base FROM words WHERE id IN ( '.$the_ids.' ) ORDER BY clean_base');
while($row = $sth->fetch(PDO::FETCH_ASSOC))
{
$the_base_words[$row['id']] = $row['clean_base'];
}

Acum, că avem un dicţionar şi cuvinte de bază, putem să facem rost şi de “cuvintele zgomot”. Cuvintele zgomot sunt acele cuvinte care nu au o importanţă semantică luate individual, ci luate prin alăturarea lor altor cuvinte (conjuncţii, adverbe etc.):

Eu am scos câteva, dar s-ar putea ca id-urile să difere:

$noise_words = '4157 4166 18180 23546 45242 45243 45251 53139 54979 55304 58278 64971 106660 114900 119227 129413 131146 132432 132432 173916 201258 209041 211311 212972 215797 222552 238659 253634 260513 269305 275002 289640 310139 398898 459858 511524 574121 622600 626117 747324 758838 782409 787953 822856 875260 889910 890052 881987 917954 a-l i-a i-ar intr-un s-a s-au si-a si-au';

$noise_words = explode(' ',$noise_words);

Întorcându-ne la blocurile noastre de text, pentru a începe analiza (mai rapidă a) acestora vom avea nevoie să transformăm cuvintele în id-urile rădăcinilor lor, numărând de asemenea numărul de apariţii a acestora:

$degs = array(); //aici pastram toate cuvintele impreuna cu numarul aparitiilor
$word_appearances = array();

foreach($blocks_as_arrays as $block_key => $block)
{
$block_as_ids = array();
foreach($block as $entity_key=>$entity)
{

if(array_key_exists($entity,$word_ids))
{
$entity = $word_ids[$entity];
}
$block_as_ids[] = $entity;
if(!in_array($entity, $noise_words))
{
if (!array_key_exists($entity, $word_appearances)) {
$word_appearances[$entity] = 1;
} else {
$word_appearances[$entity] = $word_appearances[$entity] + 1;
}
$degs[$entity][$block_key][] = $entity_key;
}
}
$blocks_as_ids[] = $block_as_ids;
}

Acum $blocks_as_ids va conţine id-urile cuvintelor din blocurile de text. Iar $word_appearances va conţine numărul de apariţii ale cuvintelor.

În contextul unui text de dimensiuni mari nu ne vor interesa prea mult cuvintele care apar o singură dată. Aşadar, să le scoatem pe acestea din array-ul nostru de cuvinte:

$candidate_words = array();
foreach($word_appearances as $key=>$word)
{
if($word>1)
{
$candidate_words[$key] = $word;
}
}

Acestea reprezintă cuvintele candidate la Oscarul căutărilor.

Apoi vom lua fiecare bloc de text si vom incerca să facem toate combinaţiile posibile pentru a afla frazele candidat la Oscarul căutărilor:

$initial_phrases = array();
foreach($candidate_words as $word_key => $word)
{
$the_word = $degs[$word_key];
foreach($the_word as $block_key => $location)
{
foreach($location as $loc)
{
$new_key = '';
for ($k = 0; $k <= $depth; $k++)
{
if(array_key_exists(($loc + $k),$blocks_as_ids[$block_key]))
{
//echo $blocks_as_ids[$block_key][($loc + $k)].'<br />';
$new_key .= $blocks_as_ids[$block_key][($loc + $k)];
$new_key .= '|';
$trim_new_key = rtrim($new_key,'|');
//echo $trim_new_key.'<br />';
if (!array_key_exists($trim_new_key, $initial_phrases)) {
$initial_phrases[$trim_new_key] = 1;
} else {
$initial_phrases[$trim_new_key] = $initial_phrases[$trim_new_key] + 1;
}
}
else
{
break;
}
}
}
}
}

Vom exclude din aceste fraze frazele care au un singur cuvânt în componenţă:

foreach($initial_phrases as $phrase_key => $deg)
{
$word_keys = explode('|',$phrase_key);
if(sizeof($word_keys)>1 && $deg>1) {
$candidate_phrases[$phrase_key] = $deg;
}
$previous_key = $phrase_key;
}

Acum, vom afla frecvenţa cu care cuvintele apar în combinaţii de cuvinte. Cu cât frecvenţa este mai mare cu atât şansele ca cuvântul respectiv să faca parte din frază sunt mai mari:

$previous_key = '';
$word_frequencies = array();
foreach($candidate_phrases as $phrase_key => $deg)
{
$word_keys = explode('|',$phrase_key);
foreach ($word_keys as $key) {
if(strpos($previous_key,$phrase_key)===FALSE) {
if (!in_array($key, $noise_words)) {
if (!array_key_exists($key, $word_frequencies)) {
$word_frequencies[$key] = 1;
} else {
$word_frequencies[$key] = $word_frequencies[$key] + 1;
}
}
}
}
$previous_key = $phrase_key;
}

Apoi calculăm scorul frazelor în funcţie de numărul de apariţii al cuvintelor componente şi frecvenţa acestora în combinaţii de cuvinte:

$extracted_phrases = array();
foreach($candidate_phrases as $phrase_keys => $appearances)
{
$score = 0;
$word_keys = explode('|',$phrase_keys);
foreach($word_keys as $key)
{
$word_deg = isset($word_appearances[$key]) ? $word_appearances[$key] : 0;
$word_freq = isset($word_frequencies[$key]) ? $word_frequencies[$key] : 0;
if($word_deg != 0 & $word_freq != 0)
{
$add_score = ($word_deg / $word_freq);
if(in_array($key,$blocks_as_ids[0]))
{
$add_score = ($add_score * 2);
}
$score = $score + $add_score;

}
}
$extracted_phrases[$phrase_keys] = number_format($score,2) * $appearances;
}

Acum sa refacem fraza ca sa avem cuvintele radacina (la o adică te poţi opri aici, avand tot ce îţi trebuie pentru ca scriptul să funcţioneze automat, dar dacă vrei să vezi şi ce fraze au ieşit ar trebui să transformi id-urile cuvintelor în limbaj uman):

$the_phrases = array();

foreach($extracted_phrases as $key => $score)
{
$word_keys = explode('|',$key);
$strings = array();
foreach($word_keys as $word_id)
{
if(array_key_exists($word_id, $the_words)) {
$strings[] = $the_words[$word_id]['string'];
}
elseif(array_key_exists($word_id, $the_base_words))
{
$strings[] = $the_base_words[$word_id];
}
else
{
$strings[] = $word_id;
}
}
$the_phrases[$key] = array('string'=>$strings,'score'=>$score);
}

Şi ca să şi arate bine, cam aşa ni se înfăţişează:

echo '<div style="width: 50%; float: left; display: inline-block">';
echo '<h1>Cuvinte</h1>';
$score = array();
foreach ($the_words as $key => $row)
{
$score[$key] = $row['appearances'];
}

array_multisort($score, SORT_DESC, $the_words);
foreach($the_words as $the_word)
{
if($the_word['appearances']>1) {
echo '<strong>' . $the_word['string'] . '</strong> a aparut de <strong>' . $the_word['appearances'] . '</strong> ori in text, reprezentand o
densitate de <strong>' . $the_word['density'] . '%</strong>.<br />';
}
}
echo '</div>';
echo '</div style="width: 50%; float: left;">';
echo '<h1>Fraze</h1>';
foreach($the_phrases as $phrase)
{
echo '<strong>'.implode(' ',$phrase['string']).'</strong> are un scor de '.$phrase['score'].'<br />';
}
echo '</div>';
?>

Cam asta a fost tot… Încă o dată te rog să nu te cramponezi în felul cum am scris codul… E doar o discuţie amicală.

Lasă un răspuns

Your email address will not be published. Required fields are marked *

No spam? * Time limit is exhausted. Please reload CAPTCHA.