工作的程序员

与我聊天,第 2 部分:ELIZA

Ted Neward

 

Ted Neward
当我们上次见面时,我们建立了一个简单的系统,通过使用对流层,云主持的语音短信服务电话语音输入响应。我们只要能够语音输入响应并提供响应,希望帮你摆脱与你的另一半在 Xbox 上花太多时间,在假日的热水。对于一些你,不幸的是,这不可能进行一天,和东西仍然是你们之间的紧张。这是时,作为一个富有同情心的人,我想能够作为业余的治疗师提供我的服务。遗憾的是,不过,我不能扩展很好,不能跟你们每一个人。所以,相反,让我们看一下另一种方法。当然,我发言的艾丽莎。

伊丽莎:历史

对于那些不熟悉伊丽莎,"她"是 chatterbot — — 和人工智能 (AI) 对第一和最受认可的步骤之一。伊丽莎由约瑟夫 · 魏岑鲍姆,口齿不清,在写回六十年代是一个相对简单 (以今天的标准) 的输入响应处理器,分析"键"的用户输入,然后生成基于这些密钥的人类的响应。所以,例如,如果你说的"我很难过,"艾丽莎,她可能响应,"你为什么伤心?"或"不会跟我说话让你伤心?"或者甚至"停止正在伤心!"事实上,反应可能是如此真实有时,一段时间,它被认为这可能是一种方法可以通过图灵测试的程序。

四个几十年后,但是,我们仍未有对话我们电脑阿瑟 C.的方式克拉克所想象的那样"2001年:太空漫游,",但这并不意味着我们应该忽略我们的程序中的"人样"通信。我们开始看到自然语言处理 (NLP) 巧妙地更滑入计算机化系统,并结合语音文本识别器引擎,当的人机交互作用的全新渠道开放。例如,甚至简单伊丽莎样密钥识别器系统可以帮助或路由客户中尝试创建的 Web 站点上的人权援助系统内一家大公司的右部而不需要非常令人沮丧,"为客户服务请按 1,按 2 人力资源,按 3 为 … …"调用关系树。

如果您已经永远不会涉足连最简单的自然语言处理,然而,尝试这样的事可以令人生畏。幸运的是,我们可以从甚至一些非常基本的尝试中得到一些好的结果。有一个很大的教程,在 bit.ly/uzBSM9,后者相当于要下一步,做的事,我们的灵感是在 F # 编写的伊丽莎实现。这里的 F # 的选择是双重的:第一,作为在原始的伊丽莎,Lisp 的使用致敬因为这两个功能的语言 ; 第二,因为我没做列样本 F # 中的在一段时间。自然,我们就叫她 F #-伊丽莎或短 (因为这听起来很有异国),和作为 F # 库实现她,所以她可以在各种不同的程序中嵌入的 Feliza。

Feliza:0 版本

Feliza 的接口应该简短、 甜蜜、 简单明了 — — 和隐藏一大堆的复杂性。从经典黑帮的四模式目录,这是门面模式 (""),F # 将便于创建门面通过其"模块"功能的使用:

module Feliza
open System
let respond input =
  "Hi, I'm Feliza"
Using Feliza in a console-mode program, for example, would then be as easy as this:
open Feliza
open System
let main =
  Console.WriteLine("Hello!")
  while true do
    Console.Write("> ")
      let input = Console.ReadLine()
      let responseText = respond input
      Console.WriteLine(responseText)
      if (input.ToLower().Equals("bye")) then
        Environment.Exit(0)
  ()

然后,这构成我们测试的床。 它还使它易于 Feliza 嵌入在其他环境中,如果我们能坚持这个超级简单的 API。

作为我们送去进行建设某些工作实现,顺便说一句,请记住 Feliza 并不打算成为通用的 NLP 引擎 — — 这需要很多的工作,比我有此列中的空间。 事实上,微软研究有专用于 NLP 整师 (请参见 research.microsoft.com/groups/nlp 详细信息对他们很正的调查)。 并请仔细注意,我不是其中之一。

Feliza:1 版本

到"工作"的第 1 版 Feliza 的最简单方法是创建一个简单的列表可能的响应和它们之间的随机选择:

let respond input =
  let rand = new Random()
  let responseBase =
    [| "I heard you!";
      "Hmm.
I'm not sure I know what you mean.";
      "Continue, I'm listening...";
      "Very interesting.";
      "Tell me more..."
    |]
  responseBase.[rand.Next(responseBase.Length - 1)]

一个数组,在这里使用惯用法上不是"F #-ish,"但它可以使它更容易选择随机从可能采取的对策。 它不是特别令人兴奋的谈话,虽然可能熟悉曾试图正试图在时间编写代码的程序员交谈的人。 尽管如此,一会儿,其实感觉是真正的交谈。 虽然我们可以做得更好。

Feliza:2 版本

下一个明显的实现是创建对特定输入从用户的罐头响应的"知识库"。 这是轻松建模 F # 正在我们希望作出响应的输入的短语的元组中的第一个元素和正在响应的第二个元素中使用元组中。 或者,这件事更有人性 (并避免在响应中的明显重复),我们可以使第二个元素列表可能采取的对策,并随机选择一个从该列表中,如中所示图 1

图 1 Feliza 知识文库

let knowledgeBase =
  [
    ( "Bye",
      [ "So long!
Thanks for chatting!";
        "Please come back soon, I enjoyed talking with you";
        "Eh, I didn't like you anyway" ] );
    ( "What is your name", 
      [ "My name is Feliza";
        "You can call me Feliza";
        "Who's asking?" ] );
    ( "Hi",
      [ "Hi there";
        "Hello!";
        "Hi yourself" ] );
    ( "How are you",
      [ "I'm fine, how are you?";
        "Just peachy";
        "I've been better" ] );
    ( "Who are you",
      [ "I'm an artificial intelligence";
        "I'm a collection of silicon chips";
        "That is a very good question" ] );
    ( "Are you intelligent",
      [ "But of course!";
        "What a stupid question!";
        "That depends on who's asking." ] );
    ( "Are you real",
      [ "Does that question really matter all that much?";
        "Do I seem real to you?";
        "Are you?" ] );
    ( "Open the pod bay doors",
      [ "Um...
No.";
        "My name isn't HAL, you dork.";
        "I don't know...
That didn't work so well last time." ] );
  ]
let unknownResponses =
  [ "I'm sorry, could you repeat that again?";
    "Wait, what?";
    "Huh?" ]
let randomResponse list =
  let listLength list = (List.toArray list).Length
  List.
nth list (rand.Next(listLength list))
let cleanInput (incoming : string) =
  incoming.
Replace(".", "").
Replace(",", "").
Replace("?", "").
Replace("!", "").
ToLower()
let lookup =
  (List.tryFind
    (fun (it : string * string list) ->
      (fst it).Equals(cleanInput input))
    knowledgeBase)
randomResponse (if Option.isSome lookup then
                    (snd (Option.get lookup))
                else
                    unknownResponses)

此版本不会输入的一些简单的清理和寻求一个匹配上的元组 ("知识基地"),列表中的第一部分,然后从列表中随机选择的响应。 如果没有"关键短语"发现知识基础,生成"未知"的响应,再次随机从列表中选择。 (当然,特别是效率低下的方式,完成清理但当我们谈论人类沟通,延误并不是一个问题。 事实上,某些 chatterbot 实现故意放慢反应和打印这些字符的字符,模仿有人在键盘上键入。)

如果因为输入的短语必须是精确匹配来触发响应,这是使用在真实世界的情况下,任何一种,我们显然需要更大知识文库的多,纳入每种可能的排列的人类语言。 呃 — — 不是一个可扩展的解决方案。 更重要的是,我们真的失去了很多时候 Feliza 不响应用户输入更有意义的方式。 伊丽莎的原始优势之一是如果你说的"我喜欢土豆,"她可以响应的"是土豆对您很重要吗?"— — 使谈话更多"个性化"。

Feliza:3 版本

此版本获取要稍微复杂,但它还提供了更多的灵活性和电源。 实质上,我们把精确匹配算法变成一个灵活的元组的列表转换为列表中的函数,每个评估和有机会创建响应。 这将打开一个巨大 Feliza 输入,可以进行交互和她可以如何挑从输入的单词,生成响应特定的选项列表。

它简单列表开头的"处理规则",在其中底部将会指示她不知道如何应对,道德相当于以前的版本中,从"unknownResponses"列表中所示的通用响应图 2

图 2 全部捕获响应的规则

let processingRules =
  [
    // ...
// Catchall rule for when nothing else matches before
    // this point; consider this the wildcard case.
This
    // must always be the last case considered!
(
      (fun (it : string) ->
        Some(randomResponse
          [
          "That didn't make sense.";
          "You cut out for a second there.
What did you say?";
          "Wait--the Seahawks are about to...
Never mind.
They lost.";
          "I'm sorry, could you repeat that again?";
          "Wait, what?";
          "Huh?"
          ]))
    )
  ]
List.head (List.choose (fun (it) -> it (cleanInput input)) processingRules)

此版本的核心是在最后一行 — — 该 List.choose 函数将每个 processingRule,并针对输入,执行它和 processingRule 返回一些值,如果该值已被添加到列表返回给调用方。 现在我们可以添加新的规则,具有每一个返回值,然后要么采取的第一个 (如图所示,在图 2,通过使用 List.head) 或甚至随机选择一个。 在未来的版本中,我们可能会产生一些值,则文本响应和适当,帮助选择是正确的答案"体重"。

现在书写新规则很容易。 我们可以有一个规则关闭的输入,这只是键使用 F # 模式匹配,便于匹配:

(fun (it : string) ->
  match it with
  | "Hi" | "Howdy" | "Greetings" ->
    Some(randomResponse
      [
      "Hello there yourself!";
      "Greetings and salutations!";
      "Who goes there?"
      ])
  | _ -> None
);

或者,我们可以使用"函数"做相同,构造,如中所示的速记图 3

图 3 函数构造

(function
  | "How are you?" ->
    Some(randomResponse
      [
      "I'm fine, how are you?";
      "Just peachy";
      "I've been better"
      ])
  | "open the pod bay doors" ->
    Some(randomResponse
      [
      "Um ...
No.";
      "My name isn't HAL, you dork.";
      "I don't know ...
That didn't work so well last time."
            ])
  | _ -> None
);

大部分时间,不过,Feliza 不用做这些罐头的词组,所以我们宁愿她以她的灵感来自用户的输入,输入的关键字为图 4 指定。

图 4 打结的关键字的回应

(fun (it : string) ->
  if it.Contains("hate") || it.Contains("despise") then
    Some(randomResponse
      [ "Why do you feel so strongly about this?";
        "Filled with hate you are, young one.";
        "Has this always bothered you so much?" ])
  else
    None
);
(fun (it : string) ->
  if it.StartsWith("what is your") then
    let subject =
      it.Substring(it.IndexOf("what is your") +
                         "what is your".Length).Trim()
    match subject with
      | "name" ->
        Some(randomResponse
          [ "Feliza."; "Feliza.
What's yours?";
            "Names are labels.
Why are they so important to you?" ])
      | "age" ->
        Some(randomResponse
          [ "Way too young for you, old man."; "Pervert!";
            "I was born on December 6th, 2011" ])
      | "quest" ->
        Some("To find the Holy Grail!")
      | "favorite color" ->
        Some("It's sort of green but more dimensions")
      | _ ->
        Some("Enough about me.
What's yours?")
  else
    None
);

F # 退伍军人会注意到 F #"活动模式"将完美适合一些此 ; 那些不熟悉用 F # 活动格局构造 (或 F # 模式匹配中的语法一般) 可以找出更多关于在克尔杰西卡的优秀两部分系列 bit.ly/ys4jtobit.ly/ABQkSN

下一主题:连接到 Feliza

Feliza 是伟大的但没有超出了键盘的输入通道,她并不很远去。 Feliza 想要帮助更多的人比只是那些人坐在前面的键盘 — — 她想要拥有移动电话或互联网连接,任何人都可以访问并在下一部分中,我们就会"挂钩她"做这些事情。

编码愉快 !

Ted Neward 是 Neudesic LLC 的建筑顾问。他写了一百多篇,是 C# MVP 和 INETA 的演讲者和创作并合著十几本书,包括最近发表"专业 F # 2.0"(Wrox,2010年)。他咨询、 定期指导。他到达 ted@tedneward.com,如果你有兴趣于将他来和你的团队工作或阅读他的博客,在 blogs.tedneward.com

多亏了以下的技术专家审查这篇文章: Matthew Podwysocki