Versioning in LUIS: How to update current LUIS application to add/remove artifacts

Time to Read: 9 minutes

Introduction

Recently while going through the Microsoft Forums on Language Understanding Intelligent Service (LUIS), I came across a question where the OP(original Poster) was asking it if possible to update the LUIS application with new data without loosing the old data that was used to train the application. In this post I will try to answer the question by using the concept of versioning available in the LUIS portal.

What is LUIS?

LUIS stands for Language Understanding Intelligent Service. It  is a cognitive service created by Microsoft which serves as a front-end consumable API for the Machine Learning Model which can understand the language used by the user/application and can predict what the user wants from the application/interface. It provides a way to utilize the conversation as service (CaaS) where the humans can communicate with the applications using their voice or by using complex sentences as they do in day to day life. LUIS provides the app the ability to take decisions upon the request by the user.  LUIS allows the user to create There are few important concepts in LUIS which are very basic building blocks of functioning of LUIS.

  1. Utterance: This is the actual query sent to the LUIS application. It can be out of a speech to text converter application(like a logic app) or directly a text input entered by the user while conversing with the application.
  2. Intents: These are the clauses which highlight the intent behind the input fed to the LUIS. Intents are populated based upon the prediction generated by the LUIS engine and the intent which scores the highest intent score is generally the real intent behind the user utterance.
  3. Entities: Entities are the objects that provide more information about the utterance by the user.

Now let us consider an example

The user says “Book a Ticket for Avengers: Infinity Wars for me.” to a ticket booking chat bot.

So here “Book a Ticket for Avengers:Infinity Wars for me” is the utterance(words spoken by the user). By saying this the user wants the bot to book the tickets for him. So Booking a ticket is the intent behind the utterance here. The movie name “Avengers: Infinity Wars” provide additional information to the bot that the tickets for this particular movie are to be booked. Hence the “Avengers: Infinity War” becomes the entity (movie name) in this case.

Thus each utterance can be thought of as being composed of Intents and Entities. There can be multiple entities in an utterance but generally there will be only one top scoring intent that will be populated with highest score by LUIS. LUIS also attaches the entities detected in that sentences which can help the application to take special actions in that particular intent.

Versioning: Why and How

Let us say that I have created a LUIS app to use it as an intelligence behind my chat bot. This chat bot accepts texts from the user and then sends those texts to the LUIS REST endpoint to determine the intent behind my input.  In LUIS portal my app looks something like below.

BasicLUIS app

This application has intents like Cancel, Greeting, Help and None and each of them is fed with sample utterances where the intents are mapped with the utterances.

This application is trained and published to the Production environment where it is consumed by my bot. The version of the app is v 0.1 which is available for consumption. This version of the application can be exported out to a json file. This json file contains the details of the application like the defined intents, entities, utterenaces mapped to each of the intents etc. The version can be exported as shown below.

ExportLUIS app.jpg

The json file for the exported version will look something like below.

{
  "luis_schema_version": "3.0.0",
  "versionId": "0.2",
  "name": "GitLuisBot-8619",
  "desc": "Default Intents for Azure Bot Service V2",
  "culture": "en-us",
  "intents": [
    {
      "name": "Cancel"
    },
    {
      "name": "Greeting"
    },
    {
      "name": "Help"
    },
    {
      "name": "None"
    }
  ],
  "entities": [],
  "composites": [],
  "closedLists": [],
  "patternAnyEntities": [],
  "regex_entities": [],
  "prebuiltEntities": [],
  "model_features": [],
  "regex_features": [],
  "patterns": [],
  "utterances": [
    {
      "text": "abort",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "cancel",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "disregard",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "do not do it",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "do not do that",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "don't",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "don't do it",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "don't do that",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "good afternoon",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "good evening",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "good morning",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "good night",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "hello",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "hello bot",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "help",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "help me",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "help me please",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "help please",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "hi",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "hi bot",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "hiya",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "how are you",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "how are you doing today?",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "how are you doing?",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "how are you today?",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "i am stuck",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "i would like to cancel",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "i'm stuck",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "my water bottle is green.",
      "intent": "None",
      "entities": []
    },
    {
      "text": "never mind",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "please cancel",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "please disregard",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "please help me",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "please stop",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "stop",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "there is a large deep dish pizza in your future.",
      "intent": "None",
      "entities": []
    },
    {
      "text": "what can i say",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "what can you do",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "what can you help me with",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "what do i do now?",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "what do i do?",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "why doesn't this work ?",
      "intent": "Help",
      "entities": []
    }
  ]
}

Now if we observe correctly above json definition file contains all the all the intents available in the LUIS portal and it contains all the utterances mapped under each of the intents.

Now let us say that I want to update the LUIS application by adding a new intent called “Closing” which sends the closing greeting like Good Bye, Arrivederci, See You Again etc, then in that case the easiest way to do this is :

  1. Create the intent “Closing” in the same version of the application.
  2. Map multiple utterances under the newly created intent “Closing”
  3. Keep on adding as many utterances as possible to make the detection of this intent more easy.
  4. Train  the application and then starting using the REST endpoint again.

But this method has one very big disadvantage that I need to map the new utterances to the newly created intent one at a time, the train the app after that and then use it. What if the number of intents and the utterances to be mapped to each of the intents is large? In such manually adding the intents and utterances is very difficult. In such cases, we can leverage the versioning feature of the LUIS app.

We can use the exported json for the existing version and we can modify it programatically using a simple console application which leverages the NewtonSoft.Json framework to add the utterances and intents to this json file.

Say I add “Closing” as the intent and “Good Bye””, “Arrivederci”, and “See you again” as the utterances for this intent, then my json for the application definition will look like following.

{
  "luis_schema_version": "3.0.0",
  "versionId": "0.2",
  "name": "GitLuisBot-8619",
  "desc": "Default Intents for Azure Bot Service V2",
  "culture": "en-us",
  "intents": [
    {
      "name": "Cancel"
    },
    {
      "name": "Greeting"
    },
    {
      "name": "Help"
    },
    {
        "name" : "Closing"
    },
    {
      "name": "None"
    }
  ],
  "entities": [],
  "composites": [],
  "closedLists": [],
  "patternAnyEntities": [],
  "regex_entities": [],
  "prebuiltEntities": [],
  "model_features": [],
  "regex_features": [],
  "patterns": [],
  "utterances": [
    {
      "text": "abort",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "cancel",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "disregard",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "do not do it",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "do not do that",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "don't",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "don't do it",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "don't do that",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "good afternoon",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "good evening",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "good morning",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "good night",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "hello",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "hello bot",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "help",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "help me",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "help me please",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "help please",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "hi",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "hi bot",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "hiya",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "how are you",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "how are you doing today?",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "how are you doing?",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "how are you today?",
      "intent": "Greeting",
      "entities": []
    },
    {
      "text": "i am stuck",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "i would like to cancel",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "i'm stuck",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "my water bottle is green.",
      "intent": "None",
      "entities": []
    },
    {
      "text": "never mind",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "please cancel",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "please disregard",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "please help me",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "please stop",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "stop",
      "intent": "Cancel",
      "entities": []
    },
    {
      "text": "there is a large deep dish pizza in your future.",
      "intent": "None",
      "entities": []
    },
    {
      "text": "what can i say",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "what can you do",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "what can you help me with",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "what do i do now?",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "what do i do?",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "why doesn't this work ?",
      "intent": "Help",
      "entities": []
    },
    {
      "text": "Good Bye",
      "intent": "Closing",
      "entities": []
     
    },
    {
      "text": "Arrivederci",
      "intent": "Closing",
      "entities": []
     
    },
    {
      "text": "See Ypu Again",
      "intent": "Closing",
      "entities": []
     
    }
  ]
}

Thus I have added a new intent “Closing” and three utterances to it in the json file. In similar fashion many intents and utterance mappings can be created programmatically.

This new json definition file can now be imported as a new version in the same LUIS application as shown below.

ImportNewVersion.JPG

Once the new version is imported, it will be show that it is not yet trained and not yet published as shown below.

NewIMportedVersion.JPG

Let us take a look at the build of the new version of the app to see if we have a new intent “Closing” and three utterances mapped to it.

BuildProperties1

BuildProperties2

Now we need to train the application so that the backedn LUIS system can associate above utterances with “Closing” intent. The LUIS can be trained using the Train button at the top right corner. Once trained, we can see how the Closing Intent gets a realistic score.

BuildProperties3.JPG

Let us now publish the app to the staging environment. It can be done from the Publish tab of the app. Once done the state of the app will change as shown below,

PublishedNewVersion.JPG

So the new version of the application is ready for the production now. Let us try testing it with a  sample utterance “Good Bye Bot. I enjoyed talking to you.”

{
  "query": "\"Good Bye\"",
  "topScoringIntent": {
    "intent": "Closing",
    "score": 0.209218472
  },
  "intents": [
    {
      "intent": "Closing",
      "score": 0.209218472
    },
    {
      "intent": "Greeting",
      "score": 0.0393234119
    },
    {
      "intent": "Help",
      "score": 0.03550244
    },
    {
      "intent": "Cancel",
      "score": 0.0247122161
    },
    {
      "intent": "None",
      "score": 0.0177557822
    }
  ],
  "entities": []
}

As seen above LUIS has recognized the intent as “Closing”. But what needs to be observed is that the value of the top scoring intent is very less because I trained the LUIS app with the new intent with only three utterances which are quite ambiguous with the Greeting intent.

Conclusion

It can be concluded, that in order to modify the current definition of LUIS application where there are multiple intents and utterance mappings, in such cases versioning the current application and training and publishing a higher version will always be beneficial and easy to do. We also saw in the same post that the number of utterances against each intent should be as many as possible to avoid the ambiguity between various intents.

In next post we will take a look at how to programatically create the versions of the LUIS applications and how to train them through a console application.

Programmer by profession, curious by nature.