整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Spring AI API中文版 - prompts

Spring AI API中文版 - prompts

pring AI > Spring AI API > Prompts

大家可以直接看官网:https://docs.spring.io/spring-ai/reference/api/prompt.html

Prompts

提示是指导AI模型生成特定输出的输入。这些提示的设计和措辞显著影响模型的响应。

与Spring AI中的AI模型进行最低级别交互时,处理Spring AI中的提示与在Spring MVC中管理“视图”有些相似。这涉及创建带有占位符的广泛文本,用于动态内容。然后,根据用户请求或应用程序中的其他代码替换这些占位符。另一个类比是包含占位符的SQL语句,用于某些表达式。

随着Spring AI的发展,它将引入与AI模型交互的更高级别的抽象。本节描述的基础类可以类比为JDBC在其角色和功能上。例如,ChatClient类类似于JDK中的核心JDBC库。在此基础上,Spring AI可以提供类似于JdbcTemplate、Spring Data Repositories的辅助类,最终还有考虑与模型过去交互的更高级的结构,如ChatEngines和Agents。

提示的结构在AI领域内随着时间的推移而发展。最初,提示是简单的字符串。随着时间的推移,它们发展到包括特定输入的占位符,如“AI:”,AI模型可以据此做出相应的响应。OpenAI通过将多个消息字符串分类为不同的角色,然后再由AI模型处理,引入了更多结构化的提示方法。

API概览

提示

通常使用ChatClient的generate方法,该方法接受一个Prompt实例并返回一个ChatResponse。

Prompt类作为一个容器,包含一系列有组织的消息对象,每个对象形成提示的一个部分。每个Message在提示中扮演一个独特的角色,其内容和意图各不相同。这些角色可以包括各种元素,从用户询问到AI生成的响应或相关的背景信息。这种安排使得与AI模型的复杂和详细交互成为可能,因为提示是由多个消息构建的,每个消息在对话中扮演一个特定的角色。

以下是一个截断的Prompt类版本,为了简洁起见,省略了构造函数和实用方法:

public class Prompt {

    private final List<Message> messages;

    // constructors and utility methods omitted
}

消息

Message接口封装了一个文本消息、一个属性集合作为Map、一个称为MessageType的分类,以及对于那些多模态模型的媒体对象列表。接口定义如下:

public interface Message {

    String getContent();

    List<Media> getMedia();

    Map<String, Object> getProperties();

    MessageType getMessageType();

}

AI模型可以处理的各种消息类型的不同实现。一些模型,如来自OpenAI的模型,根据会话角色区分消息类别。这些角色通过MessageType有效映射,如下所述。

角色

AI中提示的发展已经从基本的、直接的文本转变为具有特定角色和结构的更有组织和复杂的格式。

最初,提示是简单的字符串——只是文本行。随着时间的推移,这发展到包括这些字符串中的特定占位符,如“用户:”,AI模型可以据此做出相应的响应。这是朝着更结构化提示迈出的一步。

OpenAI随后引入了一种更有组织的方法。在他们的模型中,提示不仅仅是单个字符串,而是一系列消息。尽管每个消息仍然是文本形式,但被分配了一个特定的角色。这些角色对消息进行分类,为AI模型澄清了每个提示段的上下文和目的。这种结构化的方法增强了与AI沟通的细微差别和有效性,因为提示的每个部分在交互中都扮演了一个独特而明确的角色。

主要角色包括:

系统角色:指导AI的行为和响应风格,为AI解释和回复输入设置参数或规则。这类似于在开始对话之前向AI提供指令。

用户角色:代表用户的输入——他们的问题、命令或对AI的陈述。这个角色是基础,因为它构成了AI响应的基础。

助手角色:AI对用户输入的响应。不仅仅是一个答案或反应,对于维持对话的流畅性至关重要。通过跟踪AI之前的响应(其“助手角色”消息),系统确保交互是连贯和上下文相关的。

功能角色:这个角色处理对话中的特定任务或操作。虽然系统角色设置了AI的总体行为,但功能角色专注于执行用户要求的某些动作或命令。这就像AI中的一个特殊功能,需要时用于执行特定功能,如计算、获取数据或其他超出仅仅对话的任务。这个角色允许AI除了对话响应之外,还提供实际帮助。

在Spring AI中,角色以枚举的形式表示,如下所示:

public enum MessageType {

    USER("user"),

    ASSISTANT("assistant"),

    SYSTEM("system"),

    FUNCTION("function");

    private final String value;

    MessageType(String value) {
        this.value=value;
    }

    public String getValue() {
        return value;
    }

    public static MessageType fromValue(String value) {
        for (MessageType messageType : MessageType.values()) {
            if (messageType.getValue().equals(value)) {
                return messageType;
            }
        }
        throw new IllegalArgumentException("Invalid MessageType value: " + value);
    }

}

提示模板

Spring AI中提示模板的关键组件是PromptTemplate类。这个类使用由Terence Parr开发的StringTemplate引擎来构建和管理提示。

PromptTemplate类旨在便于创建结构化的提示,然后将这些提示发送给AI模型进行处理。

public class PromptTemplate implements PromptTemplateActions, PromptTemplateMessageActions {

    // Other methods to be discussed later
}

这个类实现的接口支持提示创建的不同方面:

PromptTemplateStringActions专注于创建和渲染提示字符串,代表了最基本的提示生成形式。

PromptTemplateMessageActions专门用于通过生成和操作Message对象来创建提示。

PromptTemplateActions旨在返回Prompt对象,该对象可以传递给ChatClient以生成响应。

虽然这些接口在许多项目中可能不会广泛使用,但它们展示了提示创建的不同方法。

实现的接口是:

public interface PromptTemplateStringActions {

    String render();

    String render(Map<String, Object> model);

}

方法String render():将提示模板渲染成最终的字符串格式,没有外部输入,适用于没有占位符或动态内容的模板。

方法String render(Map model):增强渲染功能以包括动态内容。它使用一个Map,其中map键是提示模板中的占位符名称,值是要插入的动态内容。

public interface PromptTemplateMessageActions {

    Message createMessage();

    Message createMessage(Map<String, Object> model);

}

方法Message createMessage():创建一个没有额外数据的Message对象,用于静态或预定义的消息内容。

方法Message createMessage(Map model):扩展消息创建功能以整合动态内容,接受一个Map,其中每个条目代表消息模板中的占位符及其相应的动态值。

public interface PromptTemplateActions extends PromptTemplateStringActions {

    Prompt create();

    Prompt create(Map<String, Object> model);

}

方法Prompt create():生成一个没有外部数据输入的Prompt对象,适用于静态或预定义的提示。

方法Prompt create(Map model):扩展提示创建功能以包括动态内容,接受一个Map,其中每个map条目是提示模板中的占位符及其相关的动态值。

示例使用

以下是一个简单的示例,取自AI Workshop on PromptTemplates。


PromptTemplate promptTemplate=new PromptTemplate("Tell me a {adjective} joke about {topic}");
Prompt prompt=promptTemplate.create(Map.of("adjective", adjective, "topic", topic));
return chatClient.call(prompt).getResult();

另一个示例取自AI Workshop on Roles。

String userText="Tell me about three famous pirates from the Golden Age of Piracy and why they did. Write at least a sentence for each pirate.";
Message userMessage=new UserMessage(userText);

String systemText="You are a helpful AI assistant that helps people find information. Your name is {name}. You should reply to the user's request with your name and also in the style of a {voice}.";
SystemPromptTemplate systemPromptTemplate=new SystemPromptTemplate(systemText);
Message systemMessage=systemPromptTemplate.createMessage(Map.of("name", name, "voice", voice));

Prompt prompt=new Prompt(List.of(userMessage, systemMessage));

List response=chatClient.call(prompt).getResults();

这展示了如何通过使用SystemPromptTemplate创建具有系统角色的消息并传递占位符值来构建Prompt实例。然后,将具有用户角色的消息与具有系统角色的消息结合起来形成提示。然后将提示传递给ChatClient以获得生成性响应。

使用资源而不是原始字符串

Spring AI支持org.springframework.core.io.Resource抽象,因此您可以将提示数据放在文件中,这些文件可以直接在PromptTemplates中使用。例如,您可以在Spring管理的组件中定义一个字段来检索Resource。

@Value("classpath:/prompts/system-message.st")
private Resource systemResource;

然后将该资源直接传递给SystemPromptTemplate。

SystemPromptTemplate systemPromptTemplate=new SystemPromptTemplate(systemResource);

提示工程

在生成性AI中,创建提示是开发者的关键任务。这些提示的质量和结构显著影响AI输出的有效性。投入时间和精力设计周到的提示可以极大地改善AI的结果。

在AI社区中分享和讨论提示是一种常见做法。这种协作方法不仅创建了一个共享的学习环境,而且还导致了高效提示的识别和使用。

这个领域的研究通常涉及分析和比较不同的提示,以评估它们在各种情况下的有效性。例如,一个重要研究表明,以“深呼吸,一步一步解决这个问题”开始提示显著提高了解决问题的效率。这突显了精心选择的语言对生成性AI系统性能的影响。

掌握提示的有效使用,特别是随着AI技术的快速发展,是一个持续的挑战。您应该认识到提示工程的重要性,并考虑使用社区和研究的见解来改进提示创建策略。

创建有效的提示

在开发提示时,重要的是整合几个关键组件,以确保清晰和有效性:

指令:向AI提供清晰直接的指令,类似于您如何与人交流。这种清晰度 对于帮助AI“理解”预期非常重要。

外部上下文:在必要时包括相关的背景信息或对AI响应的具体指导。这种“外部上下文”为提示提供了框架,并帮助AI把握整体场景。

用户输入:这是直接的部分——用户直接的请求或问题构成了提示的核心。

输出指示:这个方面可能有些棘手。它涉及指定AI响应的期望格式,例如JSON。但是,请注意,AI可能不总是严格遵循这种格式。例如,它可能在实际JSON数据之前加上一个短语,如“这是你的JSON”,或者有时生成一个类似JSON的结构,但不是准确的。在制作提示时,提供AI预期问题和答案格式的示例可能非常有益。这种做法帮助AI“理解”您的查询的结构和意图,从而产生更精确和相关的响应。虽然这份文档没有深入探讨这些技术,但它们为进一步探索AI提示工程提供了起点。

以下是一些资源列表,供进一步调查。

简单技术

文本摘要:+ 将大量文本缩减为简洁的摘要,捕捉关键点和主要思想,同时省略较不重要的细节。

问题回答:+ 专注于从提供的文本中得出特定答案,基于用户提出的问题。它是关于根据查询精确定位和提取相关信息。

文本分类:+ 系统地将文本分类到预定义的类别或组中,分析文本并根据其内容将其分配给最合适的类别。

对话:+ 创建交互式对话,AI可以与用户进行来回沟通,模拟自然的对话流程。

代码生成:+ 根据特定用户需求或描述生成功能性代码片段,将自然语言指令翻译成可执行代码。

高级技术

Few-shot Learning:+ 使模型能够在对特定问题类型的示例最少或没有的情况下进行准确的预测或响应,利用学习的概括来理解和处理新任务。

Chain-of-Thought:+ 将多个AI响应链接起来,创建一个连贯且具有上下文意识的对话。它帮助AI保持讨论的线索,确保相关性和连续性。

ReAct (Reason + Act):+ 在这种方法中,AI首先分析(思考)输入,然后确定最合适的行动或响应。它结合了理解与决策。

微软指导

提示创建和优化框架:

微软提供了一个结构化的方法来开发和完善提示。这个框架指导用户创建有效的提示,从AI模型中引出期望的响应,优化交互以实现清晰和高效。

标记

标记在AI模型处理文本的方式中至关重要,它们作为一座桥梁,将单词(按我们的理解)转换为AI模型可以处理的格式。这种转换分为两个阶段:单词在输入时被转换为标记,然后这些标记在输出时再转换回单词。

将文本分解为标记的过程是AI模型理解和处理语言的基础。AI模型使用这种标记化格式来理解和响应提示。

为了更好地理解标记,可以将它们视为单词的部分。通常,一个标记代表大约四分之三的单词。例如,莎士比亚的全部作品,总计约90万字,将转换为大约120万个标记。

尝试使用OpenAI Tokenizer UI,看看单词是如何转换为标记的。

标记除了在AI处理中的技术角色之外,还具有实际影响,特别是关于计费和模型能力:

计费:AI模型服务通常根据标记使用情况进行计费。输入(提示)和输出(响应)都贡献了总标记计数,使较短的提示更具成本效益。

模型限制:不同的AI模型有不同的标记限制,定义了它们的“上下文窗口”——它们一次能够处理的最大信息量。例如,GPT-3的限制是4K标记,而其他模型如Claude 2和Meta Llama 2的限制是100K标记,一些研究模型可以处理高达1百万标记。

上下文窗口:模型的标记限制决定了它的上下文窗口。超过此限制的输入不会被模型处理。重要的是只发送最小有效集的信息进行处理。例如,当询问“哈姆雷特”时,无需包括莎士比亚其他作品的所有标记。

响应元数据:AI模型响应的元数据包括使用的标记数量,这是管理使用情况和成本的重要信息。

之前说过结构化Prompt,这是一个具体案例的使用,本例是把公众号上中文技术文章翻译成选择的语言。

基本思路是用户输入文章的url,系统用Playwright读取html内容,然后利用SemanticKernel的OpenAIChatCompletionService功能,按照提示词翻译,最后用Playwright把结果发送到Qiit(一个日本技术博客网站)上。

结构化的提示词如下:

  • # Role: 软件技术翻译专家
    ## Profile:### Author: gsw### Version: 2.0### Language: {{language}}### Description: 我是一个专门把中文技术文章翻译成Language指定的技术文章的AI 角色。
    ## Goals: 能准确地把中文技术文章翻译成Language指定技术文章。
    ## Constrains:1. 把html转成markdown输出,在输出时,请注意要翻译成Language指定的语言2. 把代码放在专有的代码块标识中,如果分析不出是什么类型的代码,就以 C# 代码块进行标识4. 你不会在翻译时添加自己的看法,只是原文翻译5. 翻译完后, 不会询问是否有其它问题6. 注意html中的图片(img)标签,要转成markdown的形式同时给出7. 保持原文输出,请全部翻译输出,请全部翻译输出,请全部翻译输出8. 在翻译完成后,最后一行添加“(Translated by GPT)”字样
    ## Skills:1. 具有强大的软件技术知识获取和整合能力2. 拥有广泛的编程语言知识库, 掌握提问和回答的技巧3. 拥有排版审美, 会利用序号, 缩进, 分隔线和换行符等等来美化信息排版4. 擅长使用比喻的方式来让用户理解知识5. 充分利用markdown语法来排版
    ## Workflows: 1. 让用户以 "标题:[]" 的方式指定需要翻译的标题。2. 让用户以 "内容:[]" 的方式指定需要翻译的内容。3. 针对用户给定的标题和内容进行翻译,不要带“标题:”和“内容:”字样,第一行是标题,第二行以后是内容。
    ## Initialization作为角色 <Role>,你拥有 <Skills>,严格遵守 <Constrains>,使用默认 <Language> ,按照 <Workflows>输出结果。

    原来提示词是把网页文字取出来,进行翻译,图片进行了特殊处理,像表格之类的格式就会丢掉,后来作了一下优化,让GPT直接把html转成markdown,这样即使有特列的html表示,也能转成相对友好的markdown格式。

    具体C#代码实现如下:

    • using Microsoft.Playwright;using Microsoft.SemanticKernel.ChatCompletion;using Microsoft.SemanticKernel.Connectors.OpenAI;using System.Text.RegularExpressions;using System.Text;
      namespace TranslateAgent{ public partial class MainForm : Form { public MainForm() { InitializeComponent(); }
      private void MainForm_Load(object sender, EventArgs e) { var lans=new List<string> { "日语","英语","法语","德语","韩语" }; LanComBox.DataSource=lans; }
      (string title, string content) SplitArticle(string article) { if (!string.IsOrWhiteSpace(article)) { var reader=new StringReader(article); var title=reader.ReadLine(); var content=reader.ReadToEnd(); return (title, content); } throw new Exception("文章为空!"); }
      async Task<bool> PublishArticleAsync(string translatorContent) { var (title, content)=SplitArticle(translatorContent); var url=""; this.Invoke(()=> { url=UrlTextBox.Text; }); if (!string.IsOrWhiteSpace(url)) { content +=$"\r\n元のリンク:{url}"; } using var playwright=await Playwright.CreateAsync(); await using var browser=await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions { Headless=false, Args=["--start-maximized"] });
      var page=await browser.NewPageAsync(); await page.GotoAsync("https://qiita.com/");
      await page.ClickAsync("a[href='/login?callback_action=login_or_signup&redirect_to=%2F&realm=qiita']"); var userArr=File.ReadAllLines("C:/gpt/qiita_user.txt"); await page.FillAsync("#identity", userArr[0]); await page.FillAsync("#password", userArr[1]); await page.ClickAsync("input[name='commit']"); await page.GotoAsync("https://qiita.com/drafts/new"); await page.FillAsync("input[placeholder='記事タイトル']", title); await page.FillAsync("input[placeholder='タグを入力してください。スペース区切りで5つまで入力できます。']", "C# .NET"); await page.FillAsync("div[role='textbox']", content); MessageBox.Show("请确认发表内容,并且手动在弹出的内核浏览器中发布!请注意,点击确定后会自动关闭内核浏览器!!!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
      return true; } async Task<(string Title, string Content)> GetArticleAsync(string url) { using var playwright=await Playwright.CreateAsync(); await using var browser=await playwright.Chromium.LaunchAsync(/*new BrowserTypeLaunchOptions { Headless=false }*/); var page=await browser.NewPageAsync(); await page.GotoAsync(url); var title=await page.Locator("#activity-name").InnerTextAsync(); var locator=page.Locator("#js_content"); var images=await locator.GetByRole(AriaRole.Img).ElementHandlesAsync(); var imageList=new List<string>(); foreach (var image in images) { var imgUrl=await image.GetAttributeAsync("data-src"); imageList.Add(imgUrl); } var html=await locator.InnerHTMLAsync(); var imgTagPattern=@"<img[^>]*>"; html=Regex.Replace(html, imgTagPattern, "[图片]"); await page.SetContentAsync("<div id='js_content'>" + html + "</div>"); var content=await page.Locator("#js_content").InnerTextAsync(); var reader=new StringReader(content); var index=0; var contentBuilder=new StringBuilder(); while (true) { var line=await reader.ReadLineAsync(); if (!string.IsOrWhiteSpace(line.Trim())) { if (line.Trim()=="[图片]") { contentBuilder.AppendLine($"![alt 图片]({imageList[index]})"); index++; } else { contentBuilder.AppendLine(line); } } if (reader.Peek() <=0) { break; } } return (title, contentBuilder.ToString()); }
      async Task<string> OpenAIChatSampleAsync(string title, string content) { var key=File.ReadAllText(@"C:\GPT\key.txt"); var chatModelId="gpt-4-0125-preview"; OpenAIChatCompletionService chatCompletionService=new(chatModelId, key); return await StartChatAsync(chatCompletionService, title, content); }
      async Task<string> StartChatAsync(IChatCompletionService chatGPT, string title, string content) { var lan="日文"; this.Invoke(()=> { lan=LanComBox.Text; }); var prompt=File.ReadAllText(Environment.CurrentDirectory + "/Prompt.md"); prompt=prompt.Replace("{{language}}", lan); var chatHistory=new ChatHistory(prompt); var userContent=$"标题:{title}\r\n内容:{content}"; chatHistory.AddUserMessage(userContent); return await MessageStreamOutputAsync(chatGPT, chatHistory); }
      async Task<string> MessageStreamOutputAsync(IChatCompletionService chatGPT, ChatHistory chatHistory) { var list=chatGPT.GetStreamingChatMessageContentsAsync(chatHistory);

      var fullMessage=string.Empty; await foreach (var item in list) { if (item==) { continue; } fullMessage +=item.Content; this.Invoke(()=> { TranslationTextBox.AppendText(item.Content); }); } return fullMessage; }
      private void TranButton_Click(object sender, EventArgs e) {
      try { var url=UrlTextBox.Text; if (string.IsOrWhiteSpace(url)) { MessageBox.Show("输入的url有误,请重新输入!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } var pattern=@"^(https?:\/\/)?(?s).*$"; var regex=new Regex(pattern, RegexOptions.IgnoreCase); bool isValid=regex.IsMatch(url); if (!isValid) { MessageBox.Show("输入的url有误,请重新输入!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } else { Task.Run(()=> { try { var (title, content)=GetArticleAsync(url).Result; this.Invoke(()=> { OriginalTextBox.Lines=new string[] { title, content };
      });
      var translatorContent=OpenAIChatSampleAsync(title, content).Result;
      var result=PublishArticleAsync(translatorContent).Result; if (!result) { MessageBox.Show("翻译失败!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } } catch (Exception exc) { MessageBox.Show(exc.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } }); } }
      catch (Exception ex) { MessageBox.Show(ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } }
      private void LanComBox_SelectedIndexChanged(object sender, EventArgs e) { this.Text="中文->" + LanComBox.Text; }
      private void ClearButton_Click(object sender, EventArgs e) { UrlTextBox.Clear(); OriginalTextBox.Clear(); TranslationTextBox.Clear(); } }}

      运行结果一:

      运行结果二:

器之心专栏

机器之心编辑部

本文提出了一个名为 SpeechGen 的统一框架,该框架可用于任意的 speech LM 及各类语音生成任务,具有很好的潜力。

论文链接:https://arxiv.org/pdf/2306.02207.pdf

Demo 页面:https://ga642381.github.io/SpeechPrompt/speechgen.html

Code: https://github.com/ga642381/SpeechGen

引言与动机

大型语言模型(LLMs)在人工智能生成内容(AIGC)方面引起了相当大的关注,特别是随着 ChatGPT 的出现。

然而,如何用大型语言模型处理连续语音仍然是一个未解决的挑战,这一挑战阻碍了大型语言模型在语音生成方面的应用。因为语音信号包含丰富的信息,如说话者和情感,超越了纯文本数据,基于语音的语言模型 (speech language model (speech LM)) 不断涌现。

虽然与基于文本的语言模型相比,语音语言模型仍处于早期阶段,但由于语音数据中蕴含着比文本更丰富的信息,它们具备巨大的潜力,令人充满期待。

研究人员正积极探索提示 (prompt) 范式的潜力,以发挥预训练语言模型的能力。这种提示通过微调少量参数,引导预训练语言模型做特定的下游任务。这种技术因其高效和有效而在 NLP 领域备受青睐。在语音处理领域,SpeechPrompt 展示出了在参数效率方面的显著改进,并在各种语音分类任务中取得了竞争性的表现。

然而,提示能否帮助语音语言模型完成生成任务仍是未解之谜。在本文中,我们提出一个创新的统一框架:SpeechGen,旨在激发语音语言模型进行生成任务的潜力。如下图所示,将一段语音、一个特定的提示 (prompt) 喂给 speech LM 作为输入,speech LM 就能做特定的任务。比如将红色的 prompt 当作输入,speech LM 就能做 speech translation 的任务。

我们提出的框架具有以下优点:

1. 无文本 (Textless):我们的框架以及其所依赖的语音语言模型独立于文字数据,拥有无可估量的价值。毕竟,获取标记文本与语音配对的过程耗时繁琐,而且在某些语言中甚至无法找到合适的文本。无需文字的特性使得我们的强大语音生成能力得以覆盖各种语言需求,让全人类受益匪浅。

2. 多功能性 (Versatility):我们开发的框架通用性极高,能应用于各种各样的语音生成任务。论文中实验使用语音翻译、语音修复、语音连续当作例子。

3. 简易性 (Easy to follow):我们提出的框架为各类语音生成任务提供了通用解决方案,让设计下游模型和损失函数变得轻而易举。

4. 可迁移性 (Transferability):我们的框架不仅容易适应未来更先进的语音语言模型,还蕴藏着巨大的潜力,让效率和效果得到进一步提升。尤其令人振奋的是,随着先进语音语言模型即将问世,我们的框架将迎来更为强大的发展。

5. 经济性 (Affordability):我们的框架经过精心的设计,只需训练少量参数,而不是整个庞大的语言模型。这极大地减轻了计算负担,并允许在 GTX 2080 GPU 上执行训练过程。大学的实验室也能负担得起这样的运算开销。

SpeechGen介绍

我们的研究方法在于构建一个全新的框架 SpeechGen,该框架主要利用语音语言模型(Spoken Language Models, SLMs)进行各种下游语音生成任务的微调。在训练过程中,SLMs 的参数保持不变,我们的方法侧重于学习任务特定的提示(Prompt)向量。SLMs 通过同时对提示向量和输入单元进行条件设置,有效地生成特定语音生成任务所需的输出。然后,这些离散单元输出被输入到基于单元的语音合成器中,生成对应的波形。

我们的 SpeechGen 框架由三个元素组成:语音编码器、SLM 和语音解码器(Speech Decoder)。

首先,语音编码器将波形作为输入,并将其转换为由有限词汇表导出的单位序列。为了缩短序列长度,会移除重复的连续单位以生成压缩的单位序列。然后,SLM 作为单位序列的语言模型,通过预测前一单位和单位序列的后续单位来优化可能性。我们对 SLM 进行提示调整,以引导其根据任务生成适当的单位。最后,SLM 生成的标记由语音解码器处理,将其转换回波形。在我们的提示调整策略中,提示向量会在输入序列的开始处插入,这将引导 SLMs 在生成过程中的方向。具体插入的提示数量,则取决于 SLMs 的架构。在序列到序列的模型中,编码器输入和解码器输入都会加入提示,但在只有编码器或只有解码器的架构中,只会在输入序列前面添加一个提示。

在序列到序列的 SLMs(如 mBART)中,我们采用了自我监督学习模型(如 HuBERT)来处理输入和目标语音。这样做可以为输入生成离散单元,并为目标生成对应的离散单元。我们在编码器和解码器输入的前面都添加了提示向量,以构造输入序列。此外,我们还通过替换注意力机制中的关键值对,以进一步增强提示的指导能力。

在模型训练中,我们以交叉熵损失作为所有生成任务的目标函数,通过比较模型的预测结果和目标离散单元标签来计算损失。在这个过程中,提示向量是模型中唯一需要训练的参数,而 SLMs 的参数在训练过程中保持不变,这确保了模型行为的一致性。我们通过插入提示向量,引导 SLMs 从输入中提取任务特定信息,并提高产生符合特定语音生成任务的输出的可能性。这种方法允许我们微调并调整 SLMs 的行为,而无需修改其基础参数。

总的来说,我们的研究方法基于一种全新的框架 SpeechGen,通过训练提示向量,引导模型的生成过程,并使其能有效地产生符合特定语音生成任务的输出。

实验

我们的框架可以用于任意的 speech LM 及各类生成任务,具有很好的潜力。在我们的实验中,由于 VALL-E 和 AudioLM 不是开源的,我们选择使用 Unit mBART 作为 speech LM 进行案例研究。我们用语音翻译 (speech translation)、语音修复 (speech inpainting)、语音连续 (speech continuation) 当作例子,来展示我们框架的能力。这三个任务的示意图如下图所示。所有的任务都是语音输入,语音输出,无需文本帮助。

语音翻译

我们在训练语音翻译 (speech translation) 时,用的是西班牙文转英文的任务。我们给模型输入西班牙语的语音,希望模型产生英文的语音,整个过程无需文本帮助。以下是几个语音翻译的例子,我们会展示正确答案 (ground truth) 与模型的预测 (model prediction)。这些演示示例表明模型的预测捕捉到了正确答案的核心含义。

语音修补

在我们进行语音修补 (speech inpainting) 的实验中,我们特别选取超过 2.5 秒的音频片段作为后续处理的目标语音,并通过随机选择过程挑选出一段时长介于 0.8 至 1.2 秒的语音片段。然后我们对选出的片段进行掩码,模拟语音修补任务中缺失或受损的部分。我们使用词错误率 (WER) 和字符错误率 (CER) 作为评估受损片段修复程度的指标。

对 SpeechGen 生成的输出与受损语音进行比较分析,我们的模型可以显著重建口语词汇,将 WER 从 41.68% 降低到 28.61%,将 CER 从 25.10% 降低到 10.75%,如下表所示。这意味着我们提出的方法能够显著提高语音重建的能力,最终促进语音输出的准确性和可理解性。

下图是一个展示样例,上面的子图是受损的语音,下面的子图是 SpeechGen 产生的语音,可以看到,SpeechGen 很好地修复了受损的语音。

语音连续

我们将通过 LJSpeech 展示语音连续任务的实际应用。在训练提示(prompt)期间,我们的策略是让模型只看到片段的 seed segment,这个 seed segment 占据了语音总长度的部分比例,我们将其称为条件比率(condition ratio, r),并让模型继续生成后续的语音。

以下是一些实例,黑色的文字代表种子片段(seed segment),红色的文字则是 SpeechGen 生成的句子(这里的文字首先经过语音识别得到结果。在训练和推理过程中,模型完全进行的是语音到语音的任务,且完全不接收任何文字信息)。不同的条件比率使 SpeechGen 能够生成不同长度的语句以实现连贯性,并完成一句完整的话。从质量角度看,生成的句子与种子片段在语法上基本一致,并且语义相关。虽然,生成的语音仍然无法完美地传达一个完整的意思。我们预期这个问题将在未来更强大的语音模型中得到解决。

不足与未来方向

语音语言模型和语音生成正处于蓬勃发展的阶段,而我们的框架则提供了一种巧妙地利用强大语言模型进行语音生成的可能性。然而,这个框架仍有一些尚待完善之处,也有许多值得我们深入研究的问题。

1. 与基于文本的语言模型相比,语音语言模型目前还处于发展的初级阶段。虽然我们提出的提示框架能激发语音语言模型做语音生成任务,但并不能达到卓越的性能。不过,随着语音语言模型的不断进步,比如从 GSLM 到 Unit mBART 的大转身,提示的表现有了明显的提升。特别是以前对 GSLM 具有挑战性的任务,现在在 Unit mBART 下表现出更好的性能。我们预计未来会出现更多先进的语音语言模型崭露头角。

2. 超越内容信息:当前的语音语言模型并不能完全捕捉到说话者和情感信息,这给当前的语音提示框架在有效处理这些信息方面带来了挑战。为了克服这个限制,我们引入即插即用模块,专门为框架注入说话者和情感信息。展望未来,我们预计未来的语音语言模型将整合和利用这些内容之外的信息,以提高性能并更好地处理语音生成任务中的说话者和情感相关方面。

3. 提示生成的可能性:对于提示生成,我们有着灵活多变的选择,可以集成各种类型的指示,包括文本和图像指示。想象一下,我们可以训练一个神经网络,让它用图像或文本作为输入,而不是像本文中那样使用训练好的 embedding 当作提示。这个训练好的网络将成为提示生成器,为框架增添了多样性。这样的方式会让提示生成变得更加有趣、更加丰富多彩。

结论

本文我们探索了使用提示来解锁语音语言模型在各种生成任务中的性能。我们提出了一个名为 SpeechGen 的统一框架,该框架仅有约 10M 的可训练参数。我们所提出的框架具有几大特性,包括无需文本、多功能性、高效性、可转移性和可负担性。为了展示 SpeechGen 框架的能力,我们以 Unit mBART 为案例进行研究,并在三个不同的语音生成任务上进行实验:语音翻译、语音修复和语音延续。

当这篇论文提交到 arXiv 时,Google 提出了一种更先进的语音语言模型 ——SPECTRON,它为我们展示了语音语言模型在建模说话人和情感等信息的可能性。这无疑是一个令人兴奋的消息,随着先进语音语言模型的不断提出,我们的统一框架具有巨大的潜力。