157 lines
6.1 KiB
C#
157 lines
6.1 KiB
C#
using BotSharp.Core.Abstractions;
|
|
using BotSharp.Core.Agents;
|
|
using BotSharp.MachineLearning.NLP;
|
|
using DotNetToolkit;
|
|
using EntityFrameworkCore.BootKit;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Newtonsoft.Json.Linq;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace BotSharp.Core.Engines.CRFsuite
|
|
{
|
|
public class CRFsuiteEntityRecognizer : INlpPipeline
|
|
{
|
|
public IConfiguration Configuration { get; set; }
|
|
public PipeSettings Settings { get; set; }
|
|
|
|
public async Task<bool> Train(Agent agent, JObject data, PipeModel meta)
|
|
{
|
|
var dc = new DefaultDataContextLoader().GetDefaultDc();
|
|
var corpus = agent.Corpus;
|
|
|
|
meta.Model = "ner-crf.model";
|
|
|
|
List<List<NlpToken>> tokens = data["Tokens"].ToObject<List<List<NlpToken>>>();
|
|
List<TrainingIntentExpression<TrainingIntentExpressionPart>> userSays = corpus.UserSays;
|
|
List<List<TrainingData>> list = new List<List<TrainingData>>();
|
|
|
|
string rawTrainingDataFileName = Path.Join(Settings.TrainDir, "ner-crf.corpus.txt");
|
|
string parsedTrainingDataFileName = Path.Join(Settings.TrainDir, "ner-crf.parsed.txt");
|
|
string modelFileName = Path.Join(Settings.ModelDir, meta.Model);
|
|
|
|
using (FileStream fs = new FileStream(rawTrainingDataFileName, FileMode.Create))
|
|
{
|
|
using (StreamWriter sw = new StreamWriter(fs))
|
|
{
|
|
for (int i = 0; i < tokens.Count; i++)
|
|
{
|
|
List<TrainingData> curLine = Merge(tokens[i], userSays[i].Entities);
|
|
curLine.ForEach(trainingData =>
|
|
{
|
|
string[] wordParams = { trainingData.Entity, trainingData.Token, trainingData.Pos, trainingData.Chunk };
|
|
string wordStr = string.Join(" ", wordParams);
|
|
sw.Write(wordStr + "\n");
|
|
});
|
|
list.Add(curLine);
|
|
sw.Write("\n");
|
|
}
|
|
sw.Flush();
|
|
}
|
|
}
|
|
|
|
var fields = Configuration.GetValue<String>($"CRFsuiteEntityRecognizer:fields");
|
|
var uniFeatures = Configuration.GetValue<String>($"CRFsuiteEntityRecognizer:uniFeatures");
|
|
var biFeatures = Configuration.GetValue<String>($"CRFsuiteEntityRecognizer:biFeatures");
|
|
|
|
new MachineLearning.CRFsuite.Ner()
|
|
.NerStart(rawTrainingDataFileName, parsedTrainingDataFileName, fields, uniFeatures.Split(" "), biFeatures.Split(" "));
|
|
|
|
CmdHelper.Run(Path.Join(Settings.AlgorithmDir, "crfsuite"), $"learn -m {modelFileName} {parsedTrainingDataFileName}"); // --split=3 -x
|
|
|
|
Console.WriteLine($"Saved model to {modelFileName}");
|
|
meta.Meta = new JObject();
|
|
meta.Meta["fields"] = fields;
|
|
meta.Meta["uniFeatures"] = uniFeatures;
|
|
meta.Meta["biFeatures"] = biFeatures;
|
|
|
|
return true;
|
|
}
|
|
|
|
public List<TrainingData> Merge(List<NlpToken> tokens, List<TrainingIntentExpressionPart> entities)
|
|
{
|
|
List<TrainingData> trainingTuple = new List<TrainingData>();
|
|
HashSet<String> entityWordBag = new HashSet<String>();
|
|
int wordCandidateCount = 0;
|
|
|
|
for (int i = 0; i < tokens.Count; i++)
|
|
{
|
|
TrainingIntentExpressionPart curEntity = null;
|
|
if (entities != null)
|
|
{
|
|
bool entityFinded = false;
|
|
entities.ForEach(entity => {
|
|
if (!entityFinded)
|
|
{
|
|
string[] words = entity.Value.Split(" ");
|
|
for (int j = 0; j < words.Length; j++)
|
|
{
|
|
if (tokens[i + j].Text == words[j])
|
|
{
|
|
wordCandidateCount++;
|
|
if (j == words.Length - 1)
|
|
{
|
|
curEntity = entity;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
wordCandidateCount = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (wordCandidateCount != 0)
|
|
{
|
|
String entityName = curEntity.Entity.Contains(":")? curEntity.Entity.Substring(curEntity.Entity.IndexOf(":") + 1): curEntity.Entity;
|
|
foreach(string s in words)
|
|
{
|
|
trainingTuple.Add(new TrainingData(entityName, s, tokens[i].Pos, "I"));
|
|
}
|
|
entityFinded = true;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
if (wordCandidateCount == 0)
|
|
{
|
|
trainingTuple.Add(new TrainingData("O", tokens[i].Text, tokens[i].Pos, "O"));
|
|
}
|
|
else
|
|
{
|
|
i = i + wordCandidateCount - 1;
|
|
}
|
|
}
|
|
|
|
return trainingTuple;
|
|
}
|
|
|
|
public async Task<bool> Predict(Agent agent, JObject data, PipeModel meta)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public class TrainingData
|
|
{
|
|
public String Token { get; set; }
|
|
public String Entity { get; set; }
|
|
public String Pos { get; set; }
|
|
public String Chunk { get; set; }
|
|
|
|
public TrainingData(string entity, string token, string pos, string chunk)
|
|
{
|
|
Token = token;
|
|
Entity = entity;
|
|
Pos = pos;
|
|
Chunk = chunk;
|
|
}
|
|
}
|
|
}
|