Comment distinguer une bonne réparation d'une mauvaise, ou comment nous, dans SRG, avons créé une bibliothèque Java multi-thread à partir de l'analyseur Tomit

Cet article expliquera comment nous avons intégré l'analyseur Tomita développé par Yandex dans notre système, l'avons transformé en une bibliothèque dynamique, nous sommes liés d'amitié avec Java, l'avons rendu multithread et avons résolu le problème de la classification de texte pour l'évaluation immobilière avec lui.



Énoncé du problème


Dans l'évaluation immobilière, l'analyse des annonces de vente est d'une grande importance. Dès l'annonce, vous pouvez obtenir toutes les informations nécessaires sur la propriété, y compris des informations sur l'état de réparation de l'appartement. Habituellement, ces informations sont contenues dans le texte de l'annonce. Il est très important dans l'évaluation, car une bonne réparation peut ajouter plusieurs milliers au prix du mètre carré.

Nous avons donc un texte d'annonce qui doit être classé dans l'une des catégories en fonction de l'état de réparation dans l'appartement (inachevé, passable, moyen, bon, excellent, exclusif). À propos des réparations, une annonce peut dire une ou deux phrases, quelques mots ou rien, il est donc inutile de classer complètement le texte. En raison de la spécificité du texte et du nombre limité de mots liés au contexte de réparation, la seule solution raisonnable était d'extraire toutes les informations nécessaires du texte et de les classer.

image

Maintenant, nous devons apprendre à extraire du texte tous les faits sur l'état de la décoration. Plus précisément, ce qui se rapporte directement à la réparation, ainsi que tout ce qui peut indirectement parler de l'état de l'appartement - la présence de plafonds suspendus, d'appareils encastrés, de fenêtres en plastique, d'un jacuzzi, de l'utilisation de matériaux de finition coûteux, etc.

Dans ce cas, nous devons extraire uniquement des informations sur les réparations dans l'appartement lui-même, car l'état des entrées, des sous-sols et des greniers ne nous intéresse pas. Il est également nécessaire de prendre en compte le fait que le texte est écrit dans une langue naturelle avec toutes ses erreurs, fautes de frappe, abréviations et autres caractéristiques inhérentes - j'ai personnellement trouvé trois orthographes des mots "linoléum" et "stratifié" et cinq orthographes du mot "final"; certaines personnes ne comprennent pas pourquoi des espaces sont nécessaires entre les mots, tandis que d'autres n'ont pas entendu parler des virgules. Par conséquent, l'analyseur avec des grammaires contextuellement libres est devenu la solution la plus simple et la plus raisonnable.

Ainsi, au fur et à mesure de la décision, une deuxième tâche importante et intéressante s'est formée - apprendre à extraire toutes les informations suffisantes et nécessaires sur la réparation de l'annonce, à savoir fournir une analyse syntaxique et morphologique rapide du texte, qui peut fonctionner en parallèle sous charge en mode bibliothèque.

Passez à la solution


Parmi les moyens disponibles pour extraire des faits à partir d'un texte basé sur des grammaires sans contexte pouvant fonctionner avec la langue russe, notre attention a été attirée sur Tomita-parser et la bibliothèque Yagry en python. Yagry a été immédiatement rejeté, car il est entièrement écrit en python et n'est guère bien optimisé. Et Tomita avait d'abord l'air très attrayante: elle avait une documentation détaillée pour le développeur et beaucoup d'exemples, C ++ promettait une vitesse acceptable. Il n'était pas difficile de comprendre les règles d'écriture des grammaires, et la première version du classificateur avec son utilisation était prête dès le lendemain.

Exemples de règles de nos grammaires qui extraient des adjectifs et des verbes liés au contexte de réparation:

RepairW -> "" | "" | ""; StopWords -> "" | "" | "" | ""; Repair -> RepairW<gnc-agr[1]> Adj<gnc-agr[1]>+ interp (Repair.AdjGroup {weight = 0.5}); Repair -> Verb<gnc-agr[1]> Adj<gnc-agr[1]>* interp (Repair.Verb) RepairW<gnc-agr[1]> {weight = 0.5}; 

Règles utilisées pour garantir que les informations sur l'état des espaces publics ne sont pas récupérées:

 Repair -> StopWords Verb* Prep* Adj* RepairW; Repair -> Adj+ RepairW Prep* StopWords; 

Par défaut, le poids de la règle est 1, en attribuant un poids plus petit à la règle, nous définissons l'ordre de leur exécution.

C'était un peu gênant que seules l'application console et une tonne de code C ++ aient été téléchargées vers le public. Mais l'avantage incontestable était la facilité d'utilisation et les résultats rapides des expériences. Par conséquent, il a été décidé de réfléchir aux difficultés éventuelles de son introduction dans notre système plus près de la mise en œuvre elle-même.

Presque immédiatement, il a été possible d'obtenir une extraction de haute qualité de presque toutes les informations nécessaires sur la réparation. «Presque», car au départ, certains mots n'étaient extraits sous aucune condition ni grammaire. Cependant, il a été difficile d'évaluer immédiatement l'ampleur de ce problème, dans quelle mesure il peut affecter la qualité de la solution au problème de classification dans son ensemble.

Après nous être assurés que dans une première approximation, Tomita nous fournit les fonctionnalités nécessaires, nous avons réalisé que ce n'était pas une option pour l'utiliser comme une application console: premièrement, l'application console s'est avérée instable et s'est écrasée de temps en temps pour des raisons inconnues, et deuxièmement, elle ne fournirait pas la charge d'analyse requise de plusieurs millions d'annonces par jour. Ainsi, il est devenu clairement clair de quoi faire une bibliothèque.

Comment nous avons fait de Tomitha une bibliothèque multithread et sommes devenus amis avec Java


Notre système est écrit en Java, tomita-parser en C ++. Nous devions être en mesure d'appeler l'analyse du texte d'annonce à partir de Java.

Le développement de java-bindings pour Tomita-parser peut être conditionnellement divisé en deux composants - la mise en œuvre de la possibilité d'utiliser Tomita comme bibliothèque partagée et, en fait, l'écriture d'une couche d'intégration avec jvm. La principale difficulté concerne la première partie. Tomita elle-même a été initialement conçue pour être exécutée dans un processus distinct. Il s'ensuit que les principaux obstacles à l'utilisation de l'analyseur dans le processus de demande sont deux facteurs.

  1. L'échange de données a été effectué par le biais de divers types d'E / S. Il était nécessaire de mettre en œuvre la possibilité d'échanger des données avec l'analyseur par le biais de la mémoire. De plus, il était nécessaire de le faire de manière à affecter le moins possible le code de l'analyseur lui-même. L’architecture de Tomita a suggéré un moyen d’implémenter la lecture des documents d’entrée à partir de la mémoire en tant qu’implémentation des interfaces CDocStreamBase et CDocListRetrieverBase. C'était plus difficile avec la sortie - j'ai dû toucher au code du générateur xml.
  2. Le deuxième facteur découlant du principe «un analyseur - un processus» est l'état global, modifié à partir de différentes instances de l'analyseur. Si vous regardez le fichier src / util / generic / singleton.h , vous pouvez voir le mécanisme d'utilisation de l'état partagé. Il est facile d'imaginer que lors de l'utilisation de deux instances d'analyseur dans le même espace d'adressage, une condition de concurrence critique se produira. Afin de ne pas réécrire l'intégralité de l'analyseur, il a été décidé de modifier cette classe, en remplaçant l'état global par un état local par rapport au thread (thread_local). Par conséquent, avant tout appel d'analyseur dans l'encapsuleur JTextMiner, nous définissons ces variables thread_local sur l'instance d'analyseur en cours, après quoi le code d'analyseur fonctionne avec les adresses de l'instance d'analyseur en cours.

Après avoir éliminé ces deux facteurs, l'analyseur était disponible pour une utilisation en tant que bibliothèque partagée à partir de n'importe quel environnement. Écrire des jni-binders et un wrapper java n'était plus difficile.

L'analyseur Tomita doit être configuré avant utilisation. Les paramètres de configuration sont similaires à ceux utilisés lors de l'appel de l'utilitaire de console. L'analyse elle-même consiste à appeler la méthode parse (), qui reçoit les documents à analyser et renvoie xml sous forme de chaîne avec les résultats de l'analyseur.

La version multithread de Tomita - TomitaPooledParser utilise pour analyser un pool d'objets TomitaParser configurés de la même manière. Pour l'analyse, le premier analyseur libre est utilisé. Étant donné que le nombre d'analyseurs créés est égal au nombre de threads dans le pool, il y aura toujours au moins un analyseur disponible pour la tâche. La méthode d'analyse analyse de façon asynchrone les documents fournis dans le premier analyseur libre.

Un exemple d'appel de la bibliothèque Tomita depuis Java:

 /** * @param threadAmount number of threads in the pool * @param tomitaConfigFilename tomita config.proto * @param configDirname dir with configs: grammars, gazetteer, facttypes.proto */ tomitaPooledParser = new TomitaPooledParser(threadAmount, new File(configDirname), new String[]{tomitaConfigFilename}); Future<String> result = tomitaPooledParser.parse(documents); String response = result.get(); 

En réponse, une chaîne XML avec le résultat de l'analyse.

Problèmes rencontrés et comment nous les avons résolus


Ainsi, la bibliothèque est prête, nous démarrons le service avec son utilisation sur une grande quantité de données et nous nous souvenons du problème de ne pas extraire certains mots, réalisant que cela est très critique pour notre tâche.

Parmi ces mots figuraient «préfiltré», ainsi que «fait», «produit» et d'autres participes abrégés. Autrement dit, les mots que l'on trouve dans l'annonce très souvent, et parfois c'est la seule ou très importante information sur la réparation. La raison de ce comportement - le mot «préfiltré» s'est avéré être un mot de morphologie inconnue, c'est-à-dire que Tomita ne peut tout simplement pas déterminer de quelle partie du discours il s'agit et, par conséquent, ne peut pas l'extraire. Et pour les participes abrégés, j'ai dû écrire une règle distincte, et le problème a été résolu, mais il a fallu un certain temps pour comprendre qu'il s'agit de participes abrégés, pour l'extraction desquels une règle spéciale est nécessaire. Et pour la finale «finale» qui a duré longtemps, j'ai dû écrire une règle distincte comme pour un mot de morphologie inconnue.

Afin de résoudre les problèmes d'analyse syntaxique à l'aide de grammaires, nous ajoutons un mot de morphologie inconnue au répertoire géographique:

 TAuxDicArticle "adjNonExtracted" { key = "" | "-" } 

Pour les participes abrégés, nous utilisons les caractéristiques grammaticales de partcp, brev.

Et maintenant, nous pouvons écrire les règles pour ces cas:

 Repair -> RepairW<gnc-agr[1]> Word<gram="partcp,brev",gnc-agr[1]> interp (Repair.AdjGroup) {weight = 0.5}; Repair -> Word<kwtype="adjNonExtracted",gnc-agr[1]> interp (Repair.AdjGroup) RepairW<gnc-agr[1]> Prep* Adj<gnc-agr[1]>+; 

Et le dernier des problèmes que nous avons découverts - un service avec utilisation multithread de la bibliothèque Tomita produit des processus myStem qui ne sont pas détruits et après un certain temps remplissent toute la mémoire. La solution la plus simple était de limiter le nombre maximum et minimum de threads dans Tomcat.

Quelques mots sur le classement


Nous avons donc maintenant les informations de réparation extraites du texte. Il n'a pas été difficile de le classer en utilisant l'un des algorithmes de renforcement de gradient. Nous ne nous arrêterons pas ici longtemps sur ce sujet, beaucoup a été dit et écrit à ce sujet, et nous n'avons rien fait de radicalement nouveau dans ce domaine. Je ne donnerai que les indicateurs de qualité du classement que nous avons obtenus sur les tests:

  • Précision = 95%
  • Score F1 = 93%

Conclusion


Le service implémenté utilisant Tomita-parser en mode bibliothèque fonctionne actuellement régulièrement, analysant et classant plusieurs millions d'annonces par jour.

PS


Tout le code Tomita que nous avons écrit dans le cadre de ce projet est téléchargé sur le github. J'espère que cela est utile à quelqu'un, et cette personne gagnera un peu de temps sur quelque chose d'encore plus utile.

Source: https://habr.com/ru/post/fr439614/


All Articles