Me and my team have been using Rasa NLU as a replacement for MS LUIS for over 2 months now, and it has worked out pretty well for us so far. Now we have around 900 entries as Entity Synonyms(as we were using List entity in LUIS).
And only for some utterances, the entity is detected as synonyms and for the majority of utterances, it is unable to detect Entity Synonyms. In order to detect synonyms, I have to create another simple entity which again we are manually training with all the synonym values, once the intents are trained with this simple Entity Rasa seems to detect entity for this intent as both simple and synonyms.
And another quick question, Is the Entity Synonyms in Rasa designed to return only one matched entity(unlike LUIS which used to return all the matched entities values)?
Is there any alternative to list entity from LUIS here in Rasa?
Entity Synonyms in Rasa can lead to some confusion. The actual functionality that they provide is very simple. For each entity that is parsed by the model the value of that entity is checked against the list of entity synonyms. If the value matches an entity synonym then it is replaced with the synonym value.
The big catch in the above statement is that the the entity has to be identified by the model before it can be replaced with a synonym.
So take this as a simplified example. Here is my entity synonym definition:
{
"value": "New York City",
"synonyms": ["NYC", "nyc", "the big apple"]
}
If my training data only provides this example:
{
"text": "in the center of NYC",
"intent": "search",
"entities": [
{
"start": 17,
"end": 20,
"value": "New York City",
"entity": "city"
}
]
}
It is very unlikely that my model will be able to detect an entity in a sentence like In the center of the big apple. As I said above if the big apple isn't parsed as an entity by the model it cannot be replaced by the entity synonyms to read New York City.
For this reason you should include more examples in the actual common_examples of the training data with the entities labeled. Once all of the variations of the entity are being classified correctly then add those values to the entity synonym and they will be replaced.
[
{
"text": "in the center of NYC",
"intent": "search",
"entities": [
{
"start": 17,
"end": 20,
"value": "New York City",
"entity": "city"
}
]
},
{
"text": "in the centre of New York City",
"intent": "search",
"entities": [
{
"start": 17,
"end": 30,
"value": "New York City",
"entity": "city"
}
]
}
]
I've opened a pull request into the Rasa docs page to add a note to this effect.
Firstly, I have downloaded some LUIS model JSON for doing this, as shown in the following screenshot:
Next, I have written a sample C# console app for converting LUIS Model Schema into RASA.
Here is the LUISModel model class.
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
namespace JSONConversion.Models
{
public class LuisSchema
{
public string luis_schema_version { get; set; }
public string versionId { get; set; }
public string name { get; set; }
public string desc { get; set; }
public string culture { get; set; }
public List<Intent> intents { get; set; }
public List<entity> entities { get; set; }
public object[] composites { get; set; }
public List<Closedlist> closedLists { get; set; }
public List<string> bing_entities { get; set; }
public object[] actions { get; set; }
public List<Model_Features> model_features { get; set; }
public List<regex_Features> regex_features { get; set; }
public List<Utterance> utterances { get; set; }
}
public class regex_Features
{
public string name { get; set; }
public string pattern { get; set; }
public bool activated { get; set; }
}
public class Intent
{
public string name { get; set; }
}
public class entity
{
public string name { get; set; }
}
public class Closedlist
{
public string name { get; set; }
public List<Sublist> subLists { get; set; }
}
public class Sublist
{
public string canonicalForm { get; set; }
public List<string> list { get; set; }
}
public class Model_Features
{
public string name { get; set; }
public bool mode { get; set; }
public string words { get; set; }
public bool activated { get; set; }
}
public class Utterance
{
public string text { get; set; }
public string intent { get; set; }
[JsonProperty("entities")]
public List<Entities> Entities { get; set; }
}
public class Entities
{
[JsonProperty("entity")]
public string Entity { get; set; }
public int startPos { get; set; }
public int endPos { get; set; }
}
}
Here is the RASAModel model class:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
namespace JSONConversion.Models
{
public class RASASchema
{
public Rasa_Nlu_Data rasa_nlu_data { get; set; }
}
public class Rasa_Nlu_Data
{
public List<Entity_Synonyms> entity_synonyms { get; set; }
public List<Regex_Features> regex_features { get; set; }
public List<Common_Examples> common_examples { get; set; }
}
public class Entity_Synonyms
{
public string value { get; set; }
public List<string> synonyms { get; set; }
}
public class Common_Examples
{
public string text { get; set; }
public string intent { get; set; }
public List<Entity> entities { get; set; }
}
public class Entity
{
public string entity { get; set; }
public string value { get; set; }
public int start { get; set; }
public int end { get; set; }
}
public class Regex_Features
{
public string name { get; set; }
public string pattern { get; set; }
}
}
And I have written 2 methods which parse the LUISModel model class for synonyms from the phraselist section and adds them in the common_examples object in RASA_NLU training object.
using JSONConversion.Models;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace JSONConversion.Services
{
public static class JSONHelper
{
public static Task<string> ReadFromFile(string FilePath)
{
try
{
Task<string> readFromFileTask = Task.Run<string>(() =>
{
return File.ReadAllText(FilePath);
});
return readFromFileTask;
}
catch(Exception ex)
{
throw;
}
}
public static RASASchema ConvertLUISJSON(string StringifiedLUISJson)
{
try
{
LuisSchema luisSchema = JsonConvert.DeserializeObject<LuisSchema>(StringifiedLUISJson);
RASASchema rasaSchema = new RASASchema();
rasaSchema.rasa_nlu_data = new Rasa_Nlu_Data();
rasaSchema.rasa_nlu_data.common_examples = new List<Common_Examples>();
rasaSchema.rasa_nlu_data.entity_synonyms = new List<Entity_Synonyms>();
rasaSchema.rasa_nlu_data.regex_features = new List<Regex_Features>();
luisSchema.closedLists.ForEach(x =>
{
x.subLists.ForEach(y =>
{
rasaSchema.rasa_nlu_data.entity_synonyms.Add(new Entity_Synonyms()
{
value = y.canonicalForm,
synonyms = y.list
});
});
});
luisSchema.model_features.ForEach(x =>
{
rasaSchema.rasa_nlu_data.entity_synonyms.Add(new Entity_Synonyms()
{
value = x.name,
synonyms = x.words.Split(',').ToList()
});
});
luisSchema.regex_features.ForEach(x =>
{
rasaSchema.rasa_nlu_data.regex_features.Add(new Regex_Features()
{
name = x.name,
pattern = x.pattern
});
});
luisSchema.utterances.ForEach(x =>
{
Common_Examples rasaUtterances = new Common_Examples();
rasaUtterances.text = x.text;
rasaUtterances.intent = x.intent;
List<Entity> listOfRASAEntity = new List<Entity>();
x.Entities.ForEach(y =>
{
listOfRASAEntity.Add(new Entity()
{
start = y.startPos,
end = y.endPos,
entity = y.Entity,
value = x.text.Substring(y.startPos, (y.endPos - y.startPos) + 1)
});
});
rasaUtterances.entities = listOfRASAEntity;
rasaSchema.rasa_nlu_data.common_examples.Add(rasaUtterances);
});
return rasaSchema;
}
catch (Exception ex)
{
throw;
}
}
}
}
And just called those JSON conversion methods to convert LUIS Models into RASA models.
using System.Text;
using JSONConversion.Services;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace JSONConversion
{
class Program
{
static void Main(string[] args)
{
string json = JsonConvert.SerializeObject(JSONConversion.Services.JSONHelper.ConvertLUISJSON(JSONHelper.ReadFromFile(#"C:\Users\xyz\Documents\luis.json").Result), new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Formatting = Formatting.Indented
});
File.WriteAllText(#"C:\Users\xyz\Desktop\RASA\data\examples\RasaFormat.json", json, Encoding.UTF8);
}
}
}
After getting the RASA model, you can just simply train RASA for synonyms.
Related
I am calling an API that returns nested classes (example below) and I am struggling to bind these to a Picker.
Is it possible to bind them nested classes to a picker as is? or do I need to somehow add them to a IList?
<Picker Title="Select a Currency" ItemsSource="{Binding CurrencyClass}" ItemDisplayBinding="{Binding currencyName}"/>
class MainPageViewModel : INotifyPropertyChanged
{
private Currencies _CurrencyClass;
public Currencies CurrencyClass
{
get { return _CurrencyClass; }
set
{
_CurrencyClass = value;
OnPropertyChanged();
}
}
}
This is a cut of the class they get desterilized too
public class Currencies
{
public class Rootobject
{
public Results results { get; set; }
}
public class Results
{
public XCD XCD { get; set; }
public EUR EUR { get; set; }
}
public class XCD
{
public string currencyName { get; set; }
public string currencySymbol { get; set; }
public string id { get; set; }
}
public class EUR
{
public string currencyName { get; set; }
public string currencySymbol { get; set; }
public string id { get; set; }
}
}
And this a cut of the json I am receiving.
{
"results": {
"XCD": {
"currencyName": "East Caribbean Dollar",
"currencySymbol": "$",
"id": "XCD"
},
"EUR": {
"currencyName": "Euro",
"currencySymbol": "€",
"id": "EUR"
}
}
}
So I figured out a work around for what I was aiming to achieve, this may not be a direct answer to my question but it is a solution for my issue.
I ended up just deserializing the JSON differently into a list of a Currency and then binding easily like you normally would.
class Currency
{
public string currencyName { get; set; }
public string currencySymbol { get; set; }
public string id { get; set; }
}
and how I deserialized it to fit in that class here, I parsed the JSON into a JObject and then for each child of each child I deserialize it into my Currency class.
List<Currency> cList = new List<Currency>();
HttpResponseMessage response = await client.GetAsync(urlAPI);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
JObject jo = JObject.Parse(responseBody);
var children = jo.SelectToken("results").Children();
foreach(var child in children)
{
var childrenOfChild = child.Children();
foreach(var c in childrenOfChild)
{
cList.Add(JsonConvert.DeserializeObject<Currency>(JsonConvert.SerializeObject(c)));
}
}
How do work with a model with a model property inside of it?
I am pulling info from an api successfully but it does not work after I try to change my model from int to model like below:
public class TypeModel
{
[PrimaryKey]
public int pType { get; set; }
public DepartmentModel fDepartment { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Comments { get; set; }
public string Version { get; set; }
}
Here is the department model
public class DepartmentModel
{
public int pDepartment { get; set; }
public string Name { get; set; }
}
My ViewModel had this code and was working. Been trying to make changes as I think I need to change it in here somehow.
Types.Clear();
IEnumerable<TypesModel> types = await DataSource.GetTypesAsync(typeinfo.pType, true);
foreach (var column in types)
{
Types.Add(column);
}
Here is the deserialization from the api.
IEnumerable<TypeModel> TypeEnumerator;
public async Task<IEnumerable<TypeModel>> GetTypesAsync(bool r = false)
{
if (r)
{
var j = await HttpConstructor.GetStringAsync($"api/gettypes");
return await Task.Run(() => JsonConvert.DeserializeObject<IEnumerable<TypeModel>>(j));
}
return TypeEnumerator; ;
}
Here is the json information being produced from the api for types
{
"pType": 10,
"fDepartment": 1,
"title": "Bigwig",
"description": "For the bigwigs",
"comments": "high priority",
"version": "1.2.3"
},
{
"pType": 11,
"fDepartment": 1,
"title": "Frontdesk",
"description": "front end people",
"comments": "none",
"version": "1.2.4"
}
this is what I would do. There are undoubtedly other ways to approach it
public class TypeModel
{
[PrimaryKey]
public int pType { get; set; }
public int fDepartment { get; set; }
public DepartmentModel Department { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Comments { get; set; }
public string Version { get; set; }
}
List<TypesModel> types = await DataSource.GetTypesAsync(typeinfo.pType, true);
foreach (var type in types)
{
type.Department = new DepartmentModel
{
pDepartment = type.fDeparment,
Name = "???"
};
}
Got a solution going by using a Dictionary collection and avoided adjusting the model and mess up the business logic throughout the app.
I kept the original model and created a new one called TypesModel to use for list views.
public class TypesModel
{
[PrimaryKey]
public int pType { get; set; }
public Dictionary<int, string> fDepartment { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Comments { get; set; }
public string Version { get; set; }
}
Then I used Linq join to combine the information and also fill in the dictionary values.
var query = from t in types
join d in departments
on t.fDeparment equals d.pDepartment
select new TypesModel
{
pType = t.pType,
fDepartment = new Dictionary<int, string>()
{
{ d.pDepartment, d.Name }
},
Title = t.Title,
Description = t.Description
};
I have a Product class, which looks like this:
Public class Product
{
public int ID { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public string Barcode { get; set; }
public string InnerCode { get; set; }
public virtual ProductUnit ProductUnit { get; set; }
public int? ProductUnitID { get; set; }
public virtual ProductType ProductType { get; set; }
public int? ProductTypeID { get; set; }
}
In ASP.NET Core Web API Service I have a put method which returns OK(product).
The response in postman looks like this:
{
"result": {
"id": 22,
"name": "Bread",
"productType": {
"id": 4,
"name": "Food",
"remarks": null,
"products": []
},
"productTypeID": 4,
"code": "566",
"barcode": "855",
"innerCode": "145522",
"productUnit": {
"id": 4,
"name": "Box",
"remarks": null,
"products": []
},
"productUnitID": 4
},
"id": 592, ---> //probably this
"exception": null,
"status": 5,
"isCanceled": false,
"isCompleted": true,
"isCompletedSuccessfully": true,
"creationOptions": 0,
"asyncState": null,
"isFaulted": false
}
I am trying to get Product object as shown below:
var data = await httpResponseMessage.Content.ReadAsAsync<Product>();
But As a result, I get the product object with null properties, except the ID, which is random number and which as I think is the id above the exception in the json response.
What mistake do I have?
So, I believe you are trying to parse result which is the inner object in your case.
In order to parse the whole result you have to create a type for mentioned json, which you can create using https://app.quicktype.io/#l=cs&r=json2csharp.
Classes will be as follows :
public partial class ProductResult
{
[JsonProperty("result")]
public Result Result { get; set; }
[JsonProperty("id")]
public long Id { get; set; }
[JsonProperty("exception")]
public object Exception { get; set; }
[JsonProperty("status")]
public long Status { get; set; }
[JsonProperty("isCanceled")]
public bool IsCanceled { get; set; }
[JsonProperty("isCompleted")]
public bool IsCompleted { get; set; }
[JsonProperty("isCompletedSuccessfully")]
public bool IsCompletedSuccessfully { get; set; }
[JsonProperty("creationOptions")]
public long CreationOptions { get; set; }
[JsonProperty("asyncState")]
public object AsyncState { get; set; }
[JsonProperty("isFaulted")]
public bool IsFaulted { get; set; }
}
public partial class Result
{
[JsonProperty("id")]
public long Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("productType")]
public Product ProductType { get; set; }
[JsonProperty("productTypeID")]
public long ProductTypeId { get; set; }
[JsonProperty("code")]
[JsonConverter(typeof(ParseStringConverter))]
public long Code { get; set; }
[JsonProperty("barcode")]
[JsonConverter(typeof(ParseStringConverter))]
public long Barcode { get; set; }
[JsonProperty("innerCode")]
[JsonConverter(typeof(ParseStringConverter))]
public long InnerCode { get; set; }
[JsonProperty("productUnit")]
public Product ProductUnit { get; set; }
[JsonProperty("productUnitID")]
public long ProductUnitId { get; set; }
}
public partial class Product
{
[JsonProperty("id")]
public long Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("remarks")]
public object Remarks { get; set; }
[JsonProperty("products")]
public List<object> Products { get; set; }
}
now you can use ProductResult as
var data = await httpResponseMessage.Content.ReadAsAsync<ProductResult>();
Update
Another solution is, instead of creating type (class) for full JSON data you can use JObject class and using jsonpath you can select any property or object as follows :
string result = await httpResponseMessage.Content.ReadAsStringAsync();
Product product = JObject.Parse(result).SelectToken("$.result").ToObject<Product>()
I want to search on content field and return content and file name. The query below is taken from NEST github page
Connection string:
var node = new Uri("http://localhost:9200");
var settings = new ConnectionSettings(node);
var client = new ElasticClient(settings);
My class:
The class of search type is below (I feel problem might be here):
public class myclass
{
public string Content { get; set; }
public string filename { get; set; }
}
So I need only content and filename which is in file.filename, but in my search it return null for file.filename but content do return in same query.
NEST API CALL:
var request = new SearchRequest
{
From = 0,
Size = 10,
Query = new TermQuery { Name="Web", Field = "content", Value = "findthis" }
};
var response = client.Search<myclass>(request);
var twet = response.Documents.Select(t=>t.Content).ToList();
As I am new to elastic search so can't understand it. I even don't know why I am using term query to search a document while in kibana I user different queries and quite understandable match and match_phrase queries. So please help me get file.filename.
Edit: I have tried to include this too (later removed):
Source = new SourceFilter { Includes = ("file.filename") }
KIBANA Call:
This is the call from kibana console:
GET /extract/_search
{
"from" : 0, "size" : 1
, "query": {
"match": {
"content": "findthis"
}
}
}
The call returns following result I have used 1 result to show here:
Document in Elastic Search Index:
{
"took": 322,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 3330,
"max_score": 4.693223,
"hits": [
{
"_index": "extract",
"_type": "doc",
"_id": "29ebfd23bd7b276d7f3afc2bfad146d",
"_score": 4.693223,
"_source": {
"content": """
my title
A looooong document text to findthis.
""",
"meta": {
"title": "my title",
"raw": {
"X-Parsed-By": "org.apache.tika.parser.DefaultParser",
"Originator": "Microsoft Word 11",
"dc:title": "my title",
"Content-Encoding": "windows-1252",
"Content-Type-Hint": "text/html; charset=windows-1252",
"resourceName": "filename.htm",
"ProgId": "Word.Document",
"title": "my title",
"Content-Type": "text/html; charset=windows-1252",
"Generator": "Microsoft Word 11"
}
},
"file": {
"extension": "htm",
"content_type": "text/html; charset=windows-1252",
"last_modified": "2015-10-27T15:44:07.093+0000",
"indexing_date": "2018-02-10T08:16:23.329+0000",
"filesize": 32048,
"filename": "filename.htm",
"url": """file://D:\tmp\path\to\filename.htm"""
},
"path": {
"root": "e1a38f7da342f641e3eefad1ed1ca0f2",
"virtual": "/path/to/filename.htm",
"real": """D:\tmp\path\to\filename.htm"""
}
}
}
]
}
}
I am using NEST Api to get document file.filename from elastic search 6 on same server.
ISSUE:
Even though I have mentioned above too. Problem is filename is returned null in NEST API while content does return.
SOLUTION 1:
Using settings.DisableDirectStreaming(); I retrieved JSON result and created Following Class:
New Class:
public class Rootobject
{
public int took { get; set; }
public bool timed_out { get; set; }
public _Shards _shards { get; set; }
public Hits hits { get; set; }
}
public class _Shards
{
public int total { get; set; }
public int successful { get; set; }
public int skipped { get; set; }
public int failed { get; set; }
}
public class Hits
{
public int total { get; set; }
public float max_score { get; set; }
public Hit[] hits { get; set; }
}
public class Hit
{
public string _index { get; set; }
public string _type { get; set; }
public string _id { get; set; }
public float _score { get; set; }
public _Source _source { get; set; }
}
public class _Source
{
public string content { get; set; }
public Meta meta { get; set; }
public File file { get; set; }
public Path path { get; set; }
}
public class Meta
{
public string title { get; set; }
public Raw raw { get; set; }
}
public class Raw
{
public string XParsedBy { get; set; }
public string Originator { get; set; }
public string dctitle { get; set; }
public string ContentEncoding { get; set; }
public string ContentTypeHint { get; set; }
public string resourceName { get; set; }
public string ProgId { get; set; }
public string title { get; set; }
public string ContentType { get; set; }
public string Generator { get; set; }
}
public class File
{
public string extension { get; set; }
public string content_type { get; set; }
public DateTime last_modified { get; set; }
public DateTime indexing_date { get; set; }
public int filesize { get; set; }
public string filename { get; set; }
public string url { get; set; }
}
public class Path
{
public string root { get; set; }
public string _virtual { get; set; }
public string real { get; set; }
}
Query:
Instead of TermQuery I used MatchQuery here is my query, connection string is as above:
var request = new SearchRequest
{
From = 0,
Size = 1,
Query = new MatchQuery { Field = "content", Query = txtsearch.Text }
};
New Problem:
I tried much, though response does contain whole JSON result, but it is not being mapped properly.
I tried using Rootobject, Hits and Hit class but results only returned for _source as:
var response = client.Search<_Source>(request);
var twet = response.Documents.Select(t => t.file.filename).ToList();
Now I can retrieve content and file name but if I try using previous classes. The Hits and hits.total are returned as null.
I tried following queries:
var twet = response.Documents.SelectMany(t => t.hits.hits.Select(k => k._source.content)).ToList();
and
var twet1 = response.Hits.SelectMany(t => t.Source.hits.hits.Select(k => k._source.content)).ToList();
and
var twet1 = response.Documents.Select(t => t.Filename.fileName).ToList();
and
var twet = response.HitsMetadata.Hits.Select(t => t.Source.filename).ToList();
using Rootobject , Hits, Hit classes. While response does contain it.
So how can I use Rootobject class instead so that I can get whatever I want.
The elastic search server returns response as a JSON string and then Nest deserializes it into your required class.
In your case filename is a nested property inside the file property. To deserialize nested JSON properties check this link How to access nested object from JSON with Json.NET in C#
public class MyClass
{
public string Content { get; set; }
public FileClass File { get; set; }
}
public class Fileclass
{
public string Filename { get; set; }
}
And then you can read filename like response.Documents.Select(t=>t.File.Filename).ToList();
I want to create categories for news. It will be many-to-many relation. How do that properly? I have created two classes:
public class News
{
public News()
{
this.NewsCategories = new List<NewsCategory>();
}
public int ID { get; set; }
public DateTime Date { get; set; }
public string Title { get; set; }
public string Text { get; set; }
public IEnumerable<NewsCategory> NewsCategories { get; set; }
}
public class NewsCategory
{
public NewsCategory()
{
this.News = new List<News>();
}
public int ID { get; set; }
public string Name { get; set; }
public IEnumerable<News> News { get; set; }
}
But EF create just two tables...without Join table. I have created also custom DbInitializer:
public class TouristGuideDBInitializer : DropCreateDatabaseAlways<TouristGuideDB>
{
protected override void Seed(TouristGuideDB context)
{
base.Seed(context);
context.NewsCategories.Add(new NewsCategory { Name = "Default" });
context.NewsCategories.Add(new NewsCategory { Name = "Second" });
context.News.Add(new News { Date = DateTime.Now, Text = "asasdfas fasdfa sdf asf asf", Title = "Hello world" });
context.SaveChanges();
var news = context.News.First();
var cat = context.NewsCategories.Where(r => r.Name == "Default").Single();
news.NewsCategories.ToList().Add(cat);
context.SaveChanges();
}
}
But it just add one news and two categories...without relationships...
How it should be done properly (the relations)?
You need to use ICollection<T> for navigation properties.