[JAVA] Créez une application de recherche de restaurant avec l'API IBM Watson + Guru Navi (avec source)

introduction

J'ai essayé de développer une application Web en utilisant Watson Developer Cloud comme support pour la présentation interne, donc ce sera un résumé et un mémorandum.

À propos de l'appli

Fonction (ne pas dire que cela peut être fait)

Si vous entrez un texte ou parlez et posez une question, Watson vous présentera les restaurants recommandés à partir des restaurants dans un rayon d'environ 1 km d'un certain point. Vous pouvez poser des questions telles que "Je veux manger un plat de karaage" ou "Un pub à base de poisson avec un budget allant jusqu'à 4000 yens".

search.png

Les boutiques sont affichées sous forme de liste. Le plus de ★, les restaurants les plus recommandés. Vous pouvez épingler les magasins qui vous intéressent dans la liste, alors décidez quel magasin visiter tout en vérifiant les informations sur le site Web de Guru Navi et la distance et les directions depuis votre emplacement actuel.

map.png

Si vous allez au magasin (j'y suis allé il y a longtemps, mais ce n'est pas grave), veuillez commenter vos impressions. Les résultats de la recherche seront améliorés en analysant et en apprenant le contenu des commentaires, tels que les boutiques qui peuvent être recommandées à d'autres personnes si elles sont "très délicieuses" et les boutiques qui ne peuvent pas être recommandées tant que "le commis avait une dispute".

comment.png

classify.png

Diagramme

Au début, j'ai pensé à tout construire sur Bluemix, mais je n'avais pas de niveau gratuit pour ClearDB (5 Mo), et cela signifiait également préparer un environnement de développement pour mon propre PC. ..

L'API REST préparée sur Node-RED est appelée à partir du client JS selon le cas pour afficher les résultats de la recherche et évaluer les commentaires des utilisateurs. kousei.png

Imaginez les opérations suivantes.

  1. Création / saisie de données R & R et NLC locales
  2. Lorsque l'utilisateur utilise le système, les journaux sont accumulés sur ClearDB.
  3. Lorsque les journaux sont collectés, exportez-les localement et créez / saisissez à nouveau les données.
  4. Résultats de recherche améliorés (pour ne pas dire)

Impressions

Retrieve and Rank

curl -k -X POST -u "**username:**password**" "https://gateway.watsonplatform.net/retrieve-and-rank/api/v1/solr_clusters" -d "{\"cluster_size\":\"\",\"cluster_name\":\"WatsonRestaurantCluster\"}"

schema.xml


   <field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" /> 

   <field name="shop_id" type="string" indexed="false" stored="true" required="true" multiValued="false" /> 
   <field name="vote_id" type="string" indexed="false" stored="true" required="true" multiValued="false" /> 
   <field name="shop_name" type="string" indexed="false" stored="true" required="true" multiValued="false" /> 
   <field name="shop_name_kana" type="string" indexed="false" stored="true" required="true" multiValued="false" /> 
   <field name="menu_name" type="string" indexed="false" stored="true" required="true" multiValued="false" /> 
   <field name="menu_name_kana" type="string" indexed="false" stored="true" required="true" multiValued="false" /> 
   <field name="latitude" type="string" indexed="false" stored="true" required="true" multiValued="false" /> 
   <field name="longitude" type="string" indexed="false" stored="true" required="true" multiValued="false" /> 
   <field name="shop_url" type="string" indexed="false" stored="true" required="true" multiValued="false" /> 
   <field name="image_url" type="string" indexed="false" stored="true" required="true" multiValued="false" /> 
   <field name="pr_text" type="string" indexed="false" stored="true" required="true" multiValued="false" /> 

   <field name="shop_text" type="watson_text_ja" indexed="false" stored="true" required="true" multiValued="false" /> 

   <field name="budget" type="int" indexed="true" stored="true" required="true" multiValued="false" />

schema.xml


  <fieldType name="watson_text_ja" indexed="true" stored="true" class="com.ibm.watson.hector.plugins.fieldtype.WatsonTextField">
      <analyzer type="index">
          <tokenizer class="solr.JapaneseTokenizerFactory" mode="search" userDictionary="lang/userdict_ja.txt" />
          <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true" tokenizerFactory="solr.JapaneseTokenizerFactory" userDictionary="lang/userdict_ja.txt"/>
          <filter class="solr.JapaneseBaseFormFilterFactory"/>
          <filter class="solr.CJKWidthFilterFactory"/>
          <filter class="solr.JapaneseKatakanaStemFilterFactory" minimumLength="4"/>
          <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
      <analyzer type="query">
          <tokenizer class="solr.JapaneseTokenizerFactory" mode="search" userDictionary="lang/userdict_ja.txt" />
          <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true" tokenizerFactory="solr.JapaneseTokenizerFactory" userDictionary="lang/userdict_ja.txt"/>
          <filter class="solr.JapaneseBaseFormFilterFactory"/>
          <filter class="solr.JapanesePartOfSpeechStopFilterFactory" tags="lang/stoptags_ja.txt"/>
          <filter class="solr.CJKWidthFilterFactory"/>
          <filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_ja.txt"/>
          <filter class="solr.JapaneseKatakanaStemFilterFactory" minimumLength="4"/>
          <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
  </fieldType>

Comment les données du document ont été générées (Récupérer)

  1. Génération de documents à partir du texte PR de la boutique Guru Navi
  1. Génération de documents à partir du bouche à oreille de soutien de Guru Navi --Un document pour l'identifiant du magasin + l'identifiant de bouche à oreille
  2. Génération de documents à partir des commentaires des utilisateurs du système

rar_documents.json


{
  "id" : "5497472",
  "shop_id" : "5497472",
  "vote_id" : "",
  "shop_name" : "Magasin Tenka Ippin Gotanda",
  "shop_name_kana" : "Tenkai Pingo Tandaten",
  "menu_name" : "",
  "menu_name_kana" : "",
  "latitude" : "35.624377",
  "longitude" : "139.723394",
  "shop_url" : "http://r.gnavi.co.jp/b5tzzw2g0000/",
  "image_url" : "",
  "pr_text" : "C'est une soupe faite en faisant bouillir du poulet et plusieurs sortes d'ingrédients au fil du temps. Il est plein de collagène bon pour la beauté et la santé.",
  "shop_text" : "Magasin Tenka Ippin Gotanda. Tenkai Pingotandaten. Plats de nouilles ramen et autres. C'est une soupe riche en collagène, qui est bonne pour la beauté et la santé, et ne peut jamais être dégustée ailleurs. En outre, un menu fixe comprenant du riz à moitié frit et du soba chinois. Repas Gyoza comprenant du Gyoza, un demi-riz et du soba chinois. Nous proposons également une grande variété de menus, tels qu'un menu de service comprenant du riz semi-frit, du gyoza et du soba chinois.",
  "budget" : -1
}

Comment les données d'entraînement ont été générées (classement)

--Créer environ 750 éléments lors de la phase de migration initiale (aucune donnée d'apprentissage générée lorsque l'utilisateur utilise le système)

  1. Générez une question à partir de la catégorie et ajoutez des points au document
  1. Générez une question à partir du nom du menu et ajoutez des points au document --En réponse à la recherche de «plat frit», des points sont ajoutés aux documents qui incluent «plat frit».
  2. Ajoutez des points au document pour lequel des informations détaillées ont été consultées
  1. Ajouts et soustractions au document commenté
  1. Ajoutez des points aux documents avec des notes d'utilisateurs élevées ――Parmi les résultats de recherche de toutes les requêtes recherchées par l'utilisateur, des points sont ajoutés aux boutiques qui ont beaucoup de critiques encourageantes

rar_training.csv


"%E3%83%AF%E3%83%83%E3%83%91%E3%83%BC","6364602.681550","2"
"%E3%82%BF%E3%82%A4%E3%82%AC%E3%83%91%E3%82%AA","7255599","1","7255599.4618610","3"
"%E5%A1%A9%E3%83%AC%E3%83%A2%E3%83%B3%E3%82%AC%E3%83%91%E3%82%AA","e584801.1192601","2","6408790.4601796","2","geyc200.4614278","4","g044108.4609358","4","6085706.1451291","1"

Données du dictionnaire (fonction Solr)

  1. Générez un dictionnaire utilisateur à partir du nom du magasin, du nom du menu, du nom de la catégorie «Je veux que Tenichi apparaisse en haut de la recherche avec le mot« ramen riche ».
  2. Générez un dictionnaire synonim de sorte que les catégories mineures de catégorie incluses soient recherchées lorsque la catégorie principale de catégorie est recherchée.
  1. Modifiez le dictionnaire de mots vides de manière appropriée ――Je pense qu'il est préférable de supprimer les mots tels que «délicieux» et «acheter».

userdict_ja.txt


Un article au monde,Un article au monde,Un article au monde,Nomenclature personnalisée
Oiseau au goût de poulet,Oiseau au goût de poulet,Toridori Midori,Nomenclature personnalisée
Magasin de poisson Takumi Gotanda,Magasin de poisson Takumi Gotanda,Wosho Gotandaten,Nomenclature personnalisée
〆Saba,〆Saba,〆Saba,Nomenclature personnalisée
Rafute,Rafute,Rafute,Nomenclature personnalisée

synonyms.txt


biologique=>Plats médicinaux Plats bio Plats de légumes Bio
Cuisine créative créative=>Cuisine japonaise créative Nourriture créative Nourriture non nationale
italien=>  italienイタリア料理 パスタ ピザ
français=> françaisフランス料理 ビストロ

Natural Language Classifier

――Il était beaucoup plus facile à utiliser que R & R. (Je ne dis pas qu'il sera jugé correctement)

Comment créer des données d'entraînement pour un jugement positif / négatif

  1. Positif (délicieux)
  1. Négatif (yacky) `` On s'attendait à ce que cela puisse être généré par le contraire du positif (faible note des critiques encourageantes), mais les notes négatives sont à peine enregistrées dans les critiques et c'est déroutant ... Est-ce de la censure? ――Après tout, je me suis inscrit à la main copie du blog de revue de 2 Channel et des restaurants.

nlc_training.csv


"Satisfait de l'estomac","yummy"
"Le préféré de mon fils","yummy"
"La dignité des artisans frais est également agréable","yummy"
"Gros ventre","yummy"
"Goût nostalgique","yummy"
"C'était un magasin terrible","yacky"
"La fraîcheur du sashimi était mauvaise","yacky"
"La qualité est médiocre. subtil","yacky"
"A mi-chemin et il n'y a pas de bon point","yacky"
"Pas savoureux. Ringard","yacky"

Autour du client

――Il a fallu plus de temps pour organiser les polices et les icônes que R & R et NLC, et pour comprendre le bootstrap et l'API Google Maps introduits avec intérêt. ――Pour affiner la recherche par budget, j'ai obtenu le montant du texte de recherche et utilisé la fonction de filtrage de Solr.

Jugement budgétaire


function getBudgets(query) {
    var B_MIN = 0;
    var B_MAX = 100000;
    var B_RANGE = 500;
    var budgets = null;
    query = query.replace(/0-9/g, function(s) {
        return String.fromCharCode(s.charCodeAt(0) - 0xFEE0);
    });
    var matches = query.match(/\d+(?=Cercle)|(De|c'est tout)|(Jusqu'à ce que|Dans|Moins que|Moins que|Pas assez)/g);
    if (matches) {
        var yens = matches.filter(function(y) {
            if (isFinite(y)) {
                return y > B_MIN && y < B_MAX
            } else {
                return false;
            }
        });
        if (yens.length == 1) {
            var condition;
            try {
                condition = matches[matches.indexOf(yens[0]) + 1];
            } catch (e) {
                condition = null;
            }
            yens = yens.map(function(y) {
                return Number(y.replace(/^0+/g, ""));
            });

            if (condition) {
                if (/De|c'est tout/g.test(condition)) {
                    budgets = {
                        budget_min: yens[0],
                        budget_max: B_MAX
                    };
                } else {
                    budgets = {
                        budget_min: B_MIN,
                        budget_max: yens[0]
                    };
                }
            } else {
                budgets = {
                    budget_min: yens[0] - B_RANGE,
                    budget_max: yens[0] + B_RANGE
                };
            }
        } else if (yens.length >= 2) {
            budgets = {
                budget_min: Math.min(yens[0], yens[1]),
                budget_max: Math.max(yens[0], yens[1])
            };
        }
    }
    if (budgets) {
        budgets.budget_min = Math.max(B_MIN, budgets.budget_min);
        budgets.budget_max = Math.min(B_MAX, budgets.budget_max);

        return budgets;
    } else {
        return null;
    }
}

Autour du serveur, environnement de développement

--Node-RED ressemble à ce qui suit. ―― ~~ R & R ne fonctionnait pas correctement sauf s'il s'agissait d'un nœud HTTP et NLC était un nœud dédié ... ~~ J'ai écrit les informations d'identification dans le formulaire du nœud en premier lieu, mais lorsque je l'ai vérifié maintenant, si la connexion de service est définie du côté Bluemix, fonctionnera-t-elle telle quelle sur le nœud dédié?

nodered.png

――Java n'a pas été beaucoup touché au travail, mais c'était amusant de pouvoir faire diverses choses telles que les appels REST, JSON → DB, DB → JSON ou CSV. ――Maven of Eclipse recevait une erreur chaque fois que j'ajoutais ou supprimais une référence, et quand j'y pensais, ça guérissait soudainement et je ne suis pas sûr. ――Je pense qu'environ 3/5 du stress généré pendant le travail est causé par Maven ...

--Lors de la génération de données d'entraînement pour la création de Ranker, si vous essayez d'ajouter ou de soustraire des points pour un document qui n'est pas renvoyé pour le résultat de la recherche d'une certaine instruction de recherche, une erreur se produira dans train.py, recherchez donc Solr à partir de Java et renvoyez-le. Nous sélectionnons les documents à utiliser.

Notes diverses

―― Il faut environ 4 à 5 jours-homme pour terminer les vacances de fin d'année et du nouvel an. ――Si vous ne considérez pas le quota gratuit, les frais de maintenance sont-ils d'environ 5000 yens / mois? (Instance Ranker1 1000 yens + instance NLC1 2000 yens + Node-RED ~ 1500 yens + API d'apprentissage) --Il y a un cadre gratuit et si c'est la seule application que vous créez, les frais de maintenance peuvent être de 0 yen (devrait?)

Code source

Je l'ai publié sur Github.

Recommended Posts

Créez une application de recherche de restaurant avec l'API IBM Watson + Guru Navi (avec source)
[Rails6] Créer une nouvelle application avec Rails [Débutant]
[Rails 5] Créer une nouvelle application avec Rails [Débutant]
[Java] Créez quelque chose comme une API de recherche de produits
Créer un serveur API Web avec Spring Boot
Créez une application de chat avec WebSocket (Tyrus) + libGDX + Kotlin
[Rails] J'ai essayé de créer une mini application avec FullCalendar
Créer un SlackBot avec AWS lambda et API Gateway en Java
Créer une API XML-RPC avec Wicket
Créez un terrain de jeu avec Xcode 12
Créez une application mémo avec Tomcat + JSP + Servlet + MySQL à l'aide d'Eclipse
Créer une JVM pour la distribution d'applications avec les modules JDK 9 et jlink
J'ai créé un formulaire de recherche simple avec Spring Boot + GitHub Search API.
Créez un environnement Vue3 avec Docker!
Créez une application avec Spring Boot 2
Créer une application de minuterie avec de la boue
Créer une nouvelle application avec Rails
Créez une application avec Spring Boot
Essayez de créer une application client serveur
Créez des exceptions avec une interface fluide
Créer une instruction SQL dynamique avec MyBatis [Autoriser la recherche par plusieurs mots]
Créez un projet de développement d'application Spring Boot avec la commande cURL + tar
Créez une application de résumé de nouvelles techniques de style LINEnews avec Rails x LineBot! [Partie 1]
Allons-y avec Watson Assistant (anciennement Conversation) ⑤ Créez un chatbot avec Watson + Java + Slack