第一章 问题分类
我会把问题设定放在首位,也就是我们的第一章。毕竟所有生成式的 AI 都是需要基于问题给出答案。所以我一直不认同人工智能会取代人类,没有人类哪来问题呢?
ChatGPT 的神奇之处在于它可以根据你的问题去完成不同的工作,如归纳、翻译、分类等。我们的问题多种多样,除了问日常生活、通用知识外,还会遇到不同的时效性的问题和针对特定行业的问题。这个时候交给 ChatGPT,你往往会发现有时候它在胡说八道。这时我们需要去纠正这些错误。你可以用 Prompt 提示语,去设定一些规则让 ChatGPT 回答 “不太清楚”,“没法解答”,“我在努力学习”等等。这也是 Prompt 工程师工作的关键。其实在我们早期的对话机器人里,就有非常多针对实时问题和行业专有问题的解答。也许我们可以把这些语料重新添加到我们的 GPT 模型,让它变得更强大。也许把问题分类是一个最根本的做法。
举个例子,我们用了 OpenAI 的 davinci-03 模型去解答 “今天天气”,你会没法找到答案。也许你会说 ChatGPT 很笨,但实际上在 API 主导的年代,这不是一个困难的事情。
对问题进行分类,我们有两种方式。通过机器学习进行问题的分类是我们过往经常用的,除了分类外,还可以快速地提取不同的语义实体,基于问题进行更细致的划分,找到更符合的答案。现在还可以基于 OpenAI 去完成分类,这对于不熟悉机器学习的人来说是较好的选择。
通过 ML.NET 对问题进行分类
针对 .NET 我们有很好的 Machine Learning 工具 ML.NET。通过 ML.NET 你可以快速对文本进行分类。
通过 ML.NET 你可以快速地完成相关问题的分类。请参考 ./Code/01.HowToQuestionClassification/cn/MLdotNETTextClassification.ipynb
注意几个问题:
-
ML.NET 2.x 虽然支持了 NLP 的文本分类,但是现阶段还不支持中文,所以我用到 Jieba,如果你是用英语的话,可以直接采用 BERT 来完成。
-
在 Notebook 没办法加载 ML.NET 生成的模型,只能在传统程序中调用。
-
现在没办法转换为 ONNX。
通过 Azure OpenAI Service 对问题进行分类
你也可以通过 Azure OpenAI Service 对问题进行分类,这是更多开发者希望见到的,也是最傻瓜的方法。
打开 Azure Portal,进入创建好的 Azure OpenAI Service, 选择 davinci-003 部署:
部署成功后,进入 Azure OpenAI Studio,打开 Playground,选择刚才创建的 davinci-003 - GPT3Model,并选择文本分类:
修改 Prompt:
请帮我针对问题进行分类,包括天气,课程,生成式
问: 会下雨吗?类别:天气
问: 今天温度?类别:天气
问: 温度多少?类别:天气
问: 什么是新能源车?类别: 课程
问: 新能源车的特点?类别: 课程
问: 概念是什么?类别: 课程
问: 写一首诗歌?类别: 生成式
问: 翻译一下 类别: 生成式
问: 计算结果 类别: 生成式
问: 电动车特点 类别:
测试:
选择 C# 拷贝代码,打开 Visual Studio Code,创建 ./Code/01.HowToQuestionClassification/cn/AzureOpenAITextClassification.ipynb
具体请查看 ./Code/01.HowToQuestionClassification/cn/AzureOpenAITextClassification.ipynb
传统机器学习文本分类和 Azure OpenAI Service 文本分类对比
传统机器学习的文本分类优势是模型可以离线使用,但语料不足的情况下,难以获得准确的分类结果,而且技术要求较高。如果你处在需要隔离内部和外部业务数据的场景,可以选择传统机器学习文本分类。而 Azure OpenAI Service 可以在 Prompt 上设置少量样例来完成分类,但比较依赖网络。现在是网络通行的年代,Azure OpenAI Service 有更高的价值。而且 Azure OpenAI Service 的成本还非常低。请记住一句话,场景很重要,但 Azure OpenAI Service 并不是让你抛弃原有的技术。你需要结合众多的人工智能知识来打造智能化的解决方案。
用 Semantic Kernel 来做文本分类
OpenAI 是新物种,很多人希望除了能通过 Azure OpenAI Service 访问到 API 完成企业级的应用外,更希望能有一个好的架构来管理好基于 OpenAI 的项目。如果单纯从 REST 的角度看,作为 .NET 开发者已经熟能生巧。但实际上如果你深入了解 OpenAI 的应用,你会发现基于 OpenAI 的应用更多的集中在进行 Prompt 管理上。这与我们传统意义上的架构有所不同。首先它不再以代码为主导,更多是以 Prompt 为代表的文本。你可以通过 Prompt 去描述一段要求,让 OpenAI 完成。当我们希望结合 OpenAI 来构建智能系统的时候就会发现,我们需要非常多的 Prompt 来完成不同的业务。如何去管理 Prompt,以及如何优化好我们不同业务流的智能化工作,是架构一个好的 OpenAI 所必需的。综上所述,Semantic Kernel 是帮我们管理各式各样 Prompt (也就是 Skill )的框架。也许我们可以先用 Semantic Kernel 来做一个文本分类来进行学习。
要使用 Semantic Kernel,我们需要引入 .NET Senmantic Kernel 的库,我们先创建一个 SKTextClassification.ipynb,接下来操作如下:
1.创建一行 Cell, 引入 Microsoft.SemanticKernel 的 .NET 库:
#r "nuget: Microsoft.SemanticKernel, *-*"
2.引入 Microsoft.SemanticKernel 的命名空间:
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.SemanticFunctions;
3.创建 SK 内核实例:
IKernel kernel = Kernel.Builder.Build();
Semantic Kernel (SK) 中的内核是用户问题的编排器。内核结合技能,内存和连接器来实现用户的智能化预期,除了配置基本的 Azure OpenAI/OpenAI 的基本链接串/模型/参数外,还可以基于用户的要求配对相关技能,整合技能形成工作流等。
接下来添加信息,添加和 Azure OpenAI Service 相关的信息:
kernel.Config.AddAzureOpenAITextCompletionService(
"GPT3",
"text-davinci-003",
"Your Endpoint",
"Your Key"
);
4.设置和文本分类相关的 Prompt:
string skPrompt = """
请帮我把 {{$input}} 进行类别确认,类别包括天气,课程,生成式,如果不太清楚,请回答没法确认,分类参考如下:
问: 会下雨吗?类别:天气
问: 今天温度?类别:天气
问: 温度多少?类别:天气
问: 什么是新能源车?类别: 课程
问: 电动车的特点 类别: 课程
问: 概念是什么?类别: 课程
问: 课程相关的内容有哪些?类别: 课程
问: 写一首诗歌?类别: 生成式
问: 翻译一下 类别: 生成式
问: 计算结果 类别: 生成式
如果能确认类别,天气相关请只输出 1 , 课程相关请只输出 2 , 生成式相关请只输出 3 ,没法确认相关请只输出 0,并把{{$input}}和它的类别参考以下 json 格式输出
{""question"":""{{$input}}"",""label"":""{{$label}}""}
""";
Prompt 是非常非常重要的,对于生成式 AI 来说,有一个好的 Prompt 事半功倍。如何写 Prompt,网上有不同的方式,建议大家查阅。
5.和模型相关的配置:
var promptConfig = new PromptTemplateConfig
{
Completion =
{
MaxTokens = 60,
FrequencyPenalty = (float)0,
PresencePenalty = (float)0
}
};
var promptTemplate = new PromptTemplate(
skPrompt,
promptConfig,
kernel
);
PromptTemplateConfig 就是设定我们对应模型的参数,你可以基于你所使用的模型进行调整,这里我参照了通过 Playground 引用的参数。至于 PromptTemplate 这里面就是整合了我们创建的 kernel、prompt 以及模型参数。告诉计算机是该内核使用 Azure OpenAI Service 的 text-davinci-003 模型,prompt(skill)以及相关模型参数。
6.创建一个 SemanticFunctionConfig,绑定 promptConfig。promptTemplate,为内核添加一个具备文本分类功能的函数做准备:
var functionConfig = new SemanticFunctionConfig(promptConfig, promptTemplate);
7.通过内核注册技能和能力:
var classificationFunction = kernel.RegisterSemanticFunction("TextSkill", "TextClassification", functionConfig);
大家知道一个 OpenAI 的项目具备多个技能吗?而多个技能具备不同的能力。在 SK 你可以通过代码去描述技能和它具备的能力,以及相关参数设置,也可以通过文件夹的方式去描述(通过文件夹方式,会在下一章介绍)。
8.现在你可以设置输入来尝试看看是否能满足要求:
var input = """
今天广州天气怎么样?
""";
var classification = await kernel.RunAsync(input, classificationFunction);
Console.WriteLine(classification);
根据 Prompt 的要求,我要对问题分类,然后以 json 格式输出,结果还是令人满意的:
{"question": "今天广州天气怎么样?", "label": "1"}
注意:示例代码在 ./Code/01.HowToQuestionClassification/cn/SKTextClassification.ipynb
本章小结
本章正式进入 Azure OpenAI Service 的应用学习,基于文本分类,我们用传统的 ML.NET,Azure OpenAI .NET SDK,以及 Semantic Kernel .NET SDK 三种方法完成了相关的操作。对于开发者来说,可以体验基于传统的机器学习方式以及人工智能方式来完成文本分类的任务。希望大家能有所收获!
第二章 技能使用大全
为使生成式 AI 更好地工作,我们需要预先制定一些规则。这些规则包括但不限于回答的格式、语气、角色或者业务相关的内容。这些规则,在生成式 AI 中我们称之为 Prompt。生成式 AI 的快速发展,使撰写 Prompt 的工程师非常吃香,他们通过 Prompt 引导 AI 生成相应的答案。本章中,我们希望大家能掌握不同类型 Prompt 的使用技巧,并通过 Semantic Kernel 管理好 Prompt。
技能介绍
在日常生活中需要完成某一方面的专业工作,必须具备某一种或几种能力。生成式 AI 是人工智能的进化,也是更完整的百科全书。你可以把它看作是一个智脑。写 Prompt 实际上就是触发它去使用各种各样的技能。例如读写、翻译、总结、管理等。在融入到相关业务场景时,这些技能就必须细化,例如写的技能,在行业应用中,就包括了归纳总结、文案输出、公文生成等与业务场景契合的内容。这些能力的集合就是一项技能,一个行业的应用必须具备各种技能。这些技能多种多样,除了生成式 AI 常规具备的技能外,还有行业专属的技能。下面来归纳一下:
▍常规能力
通过简单描述就可以达到的属于常规能力,例如翻译、总结或提取内容,都是生成式 AI 的拿手好戏。我们用简单的文字描述就可以完成,例如:
“通过输入的内容,总结出100个字的内容,并且翻译成英文”
“帮我用诗词赞美一下广州”
“介绍煮 {{$input}} 的方法”
这些都是生成式 AI 模型带来的常规能力。写好 Prompt 来描述你需要的能力是很重要的。
▍时效性能力
生成式 AI 最大的问题之一是时效性缺失,当然现在 GPT-4 也开始具备一定的这种能力,但还是有局限的。例如一些日常、新闻、政策、查询实时天气等,都属于时效性的问题。这时候,我们需要结合搜索引擎,以及企业内部数据库的配合来赋予生成式 AI 一定的时效性能力。
▍专业性能力
如果不同行业想要用好生成式 AI,除了利用其强大的知识能力外,也需要补充结合了行业的相关内容。例如问一下新员工相关的问题,GPT 模型就无法提供准确的答案,因为这个是每个企业所唯一的。我们需要为生成式 AI 添加额外的知识,这个时候就需要回到我们原有的知识体系,例如非结构化数据的搜索,或者是调用企业内部的数据库。这些都是我们必须要考虑的。我们可以引入过往人工智能技术的相关专业性知识。例如,非结构化的文档数据/存储在数据库的业务数据/相关业务流程 BI 数据等。
Semantic Kernel 语义内核管理你的技能
Semantic Kernel (SK) 为我们提供了非常好的技能管理功能,开发者能针对业务通过 SK 更好组织不同的技能,从而让生成式 AI 可以完成企业级的任务。
▍SK 的重要概念
✦ Skills-技能
技能 - 生成式 AI 应用,都离不开技能。这里的技能,可以理解为要让生成式 AI 完成任务提供的一些能力的组合。每一个任务可以有多个能力,例如文书的技巧就可以有写作、归纳、总结、修改等相关能力,一个技能下面可以有不同的能力。在 SK 中通过文件夹来管理不同的技能,一个技能就包含了各种不同的能力。
在 SK 中定义技能是非常简单的,通过文件夹就可以定义不同的技能。每个技能下可以创建不同的子文件夹来设定能力,如图所示:
我们有一个包含技能集的文件夹叫 Skills,里面有不同的技能,包括 CustomSkill - 自定义技能,EmailSkill - 电子邮件技能,WriteSkill - 书写技能,通过子文件夹来标识。
每个技能都包含了不同的能力,都放在相应技能的子文件夹下。
-
CustomSkill - 自定义技能(我们会在函数/方法中讨论它)。
-
EmailSkill - 电子邮件技能有申请相关的邮件 ApplyMail,有商业相关的邮件 BizMail,也有天气相关的邮件 WeatherMail 的能力。
-
WriteSkill - 书写技能有文本分类 Classification,归纳 Summary,翻译 Translate 等能力。
每个能力都有一个 Prompt 和与其对应的配置来描述,如我需要书写技能中的分类能力,就包含一个 skprompt.txt 和 config.json (这是固定的格式,skprompt.txt 就是对应能力的 Prompt,config.json 就是 OpenAI 模型的参数)。
例如分类的 Prompt,我的 skprompt.txt 描述是这样写的:
请帮我把 {{$input}} 进行类别确认,类别包括天气,课程,生成式,如果不太清楚,请回答没法确认,分类参考如下:
问: 会下雨吗?类别:天气
问: 今天温度?类别:天气
问: 温度多少?类别:天气
问: 什么是新能源车?类别: 课程
问: 电动车的特点 类别: 课程
问: 概念是什么?类别: 课程
问: 课程相关的内容有哪些?类别: 课程
问: 写一首诗歌?类别: 生成式
问: 翻译一下 类别: 生成式
问: 计算结果 类别: 生成式
如果能确认类别,天气相关请只输出 1 , 课程相关请只输出 2 , 生成式相关请只输出 3 ,没法确认相关请只输出 0,并把{{$input}}和它的类别参考以下 json 格式输出
{""question"":""{{$input}}"",""label"":""{{$label}}""}
而模型设置参数对应 config.json:
{
"schema": 1,
"type": "completion",
"description": "文本分类",
"completion": {
"max_tokens": 60,
"presence_penalty": 0.0,
"frequency_penalty": 0.0
}
}
如果我们希望通过 Semantic Kernel 去调用文书技能的文本分类能力,只需要通过 kernel.ImportSemanticSkillFromDirectory 引入技能集和相应技能(Semantic Kernel 的定义请回看第一章),然后直接根据需要的能力调用就可以了(别忘记引入 Microsoft.SemanticKernel.KernelExtensions),代码如下:
var skillsDirectory = System.IO.Directory.GetCurrentDirectory() + "/Skills";
var write_skill = kernel.ImportSemanticSkillFromDirectory(skillsDirectory,"WriteSkill");
var questionLabel = await kernel.RunAsync("今天天气好吗", write_skill["Classification"]);
如果你想了解更多,请参考 ./Code/02.AddSkill/ImportNativeFunctionSkill.ipynb
✦ Fucntions-函数/方法
我们通过函数去封装不同技能下的能力,把函数分成两种:
-
语意技能函数 - 通过封装 Prompt 提示语和模型设置来定义函数,这在第一章里已经提及。
-
原生技能函数 - 用于结合时效性技能和业务技能的函数封装,可以非常快速地定义不同的技能,如我需要查询一些入职相关的内容以及实时天气状况,我们都可以通过原生函数定义这些技能并赋予系统答案,再让生成式 AI 完成接下来的工作,这是我们定义原生技能函数的一个例子:
using Microsoft.SemanticKernel.SkillDefinition;
using Microsoft.SemanticKernel.Orchestration;
public class CompanySearchSkill
{
[SKFunction("search employee infomation")]
public string EmployeeSearch(string input)
{
return "欢迎了解社保相关内容";
}
[SKFunction("search weather")]
public string WeatherSearch(string text)
{
return "欢迎搜索天气";
}
}
通过 SKFunction 定义能力,提供定制化的技能是每个企业应用所必须的,通过这个内容可以增强应用的专业性。调用也是非常简单的。
CompanySearchSkill companySearchSkill = new CompanySearchSkill();
var customSkill = kernel.ImportSkill (companySearchSkill, "CompanySearchSkill");
通过创建实例,直接像其他技能一样引入就可以了,你也可以理解为是用函数封装好的技能集。接下来要调用,就可以根据问题去绑定不同的技能。
var weatherOutput = await kernel.RunAsync("天气",customSkill["WeatherSearch"]);
var employeeOutput = await kernel.RunAsync("社保如何购买",customSkill["EmployeeSearch"]);
如果你想了解更多,请参考 ./Code/02.AddSkill/ImportNativeFunctionSkill.ipynb
✦ SequentialPlanner-有序计划
如何判断完成一个事情,是需要通过一系列的有序计划来制定的。例如有一个任务叫“查询广州天气,把结果翻译成中文后根据天气情况生成穿衣提示,并结合天气结果和穿衣提示写一封邮件”。要完成这个任务,我们需要具备四个技能:第一查询天气,第二翻译,第三生成穿衣提示,第四写一封邮件。对于发出任务的人,需要明确指出每一个步骤的工作,而作为开发者需要为完成这个任务提供能力。
在 Semantic Kernel 中,就有 SequentialPlanner,它能让我们根据目标,动态帮我们的任务按顺序设定执行并配对好技能。我们赋予给 Kernel 很多的技能, SequentialPlanner 可以根据我们需要完成的目标,去配对不同的能力来帮我们完成各种任务,定义 SequentialPlanner 非常简单:
var planner = new SequentialPlanner(kernel);
通过输入目标来制定执行的顺序
var plan = await planner.CreatePlanAsync("查找广州天气把结果翻译成中文后根据天气情况生成穿衣提示,并结合天气结果和穿衣提示写一封邮件");
通过
plan.Steps
可以非常清晰地看到要完成“查找广州天气,把结果翻译成中文后根据天气情况生成穿衣提示,并结合天气结果和穿衣提示写一封邮件”所需要明确的是每一个步骤的技能:
这也是一个工作的流程,针对一些复杂的任务,我们可以完成更多综合性的工作。以下是执行的逻辑代码:
var result = await kernel.RunAsync(plan);
result.Result
如果你想了解更多,请参考 ./Code/02.AddSkill/ImportPlanSkill.ipynb
本章小结
用好 Prompt 能让生成式 AI 给出更准确的答案。管理好 Prompt 的集合对于企业来说是非常重要的。通过 Semantic Kernel 我们可以快速地完成各种 Prompt 的整合,通过本章你可以学习管理不同的 Prompt,完成不同的企业工作流程。
相关资料
-
关于 ML.NET:
https://dotnet.microsoft.com/en-us/apps/machinelearning-ai/ml-dotnet
-
关于 Semantic Kernel:
https://github.com/microsoft/semantic-kernel
点我查看更多章节~