Compartilhar via


Este artigo foi traduzido por máquina.

O trabalho programador

Fale comigo, Parte 2: ELIZA

Ted Neward

 

Ted NewardQuando encontramos última, desenvolvemos um sistema simple para responder às entradas de voz pelo telefone usando Tropo, um serviço de voz e SMS nuvem hospedado. Nós temos tanto quanto ser capaz de responder às entradas de voz e oferecer respostas, esperançosamente você afiançar para fora de água quente com seu outro significativo para gastando muito tempo no Xbox durante as férias. Para alguns de vocês, infelizmente, que não poderia ter transportado o dia, e as coisas ainda são tensas entre você. Isso é quando, como um ser humano compassivo, gostaria de ser capaz de oferecer meus serviços como um terapeuta amador. Infelizmente, no entanto, eu não escala muito bem e não pode falar a cada um de vocês. Assim, em vez disso, vamos examinar uma alternativa. Falo, obviamente, de ELIZA.

ELIZA: Uma história

Para aqueles não familiarizados com ELIZA, "ela" é um chatterbot — e um dos primeiros e mais reconhecíveis passos em direção a inteligência artificial (AI). Escrito de volta nos anos 60 por Joseph Weizenbaum, em Lisp, ELIZA é um processador de entrada-resposta relativamente simple (pelos padrões de hoje) que analisa a entrada do usuário para "chaves" e, em seguida, gera respostas semelhantes a humanos com base nessas chaves. Assim, por exemplo, se você disse, "eu estou triste," ELIZA, ela poderia responder, "porque você está triste?" ou "Falando comigo faz você triste?" ou mesmo "deixar de ser triste!" Na verdade, as respostas poderiam ser tão autênticas às vezes que, por um tempo, pensava que isso pode ser uma maneira de começar um programa para passar no teste de Turing.

Quatro décadas mais tarde, no entanto, nós ainda não está tendo conversas com nossos computadores a maneira Arthur c. Clarke imaginou em "2001: Uma Odisséia no espaço", mas isso não significa que devemos ignorar"humanos-como"comunicação em nossos programas. Estamos começando a ver o processamento de linguagem natural (NLP) sutilmente escorregar mais para sistemas computadorizados, e quando combinado com os motores de reconhecimento de fala para texto, inteiramente novas vias de interação homem-computador abram. Por exemplo, mesmo um simple sistema de chave-reconhecedor ELIZA-como pode ser útil na tentativa de criar sistemas humanos-assistência em sites ou em clientes de roteamento para o departamento de direito dentro de uma grande empresa sem exigir a altamente frustrante, árvore de chamada "Pressione 1 para atendimento ao cliente, prima 2 para recursos humanos, pressione 3 para...".

Se você tiver nunca interessou até mesmo o mais simples NLP, no entanto, tentar algo como isto pode ser um pouco assustador. Felizmente, nós podemos obter alguns bons resultados até mesmo algumas tentativas muito básico. Há um excelente tutorial em bit.ly/uzBSM9, que serve como inspiração para o que estamos prestes a fazer a seguir, que é escrever uma implementação de ELIZA em F #. A escolha de F # aqui é duplo: primeiro, como homenagem ao uso do Lisp em ELIZA original, porque ambas são linguagens funcionais e, segundo, porque ainda não fiz uma amostra de coluna no F # em um tempo. Naturalmente, nós vamos chamar seu F #-Eliza ou Feliza para implementar dela como uma biblioteca de F # para que ela pode ser incorporada em uma variedade de diferentes programas e curta (porque isso soa mais exótico).

Feliza: Versão 0

A interface para Feliza deve ser curto, doce e simples — e ocultar um monte de complexidade. Do catálogo de padrões do clássico Gang of Four, este é o padrão de fachada (""), e F # torna mais fácil para criar uma fachada com o uso de sua funcionalidade de "módulo":

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)
  ()

Em seguida, faz-se de nossa cama de teste. Também torna mais fácil de incorporar Feliza em outros ambientes se nós pode furar a esta API über-simples.

Como podemos obter diante vai construir algumas implementações de trabalho, a propósito, lembrar que Feliza não está destinada a ser um mecanismo de uso geral PNL — que leva o trabalho muito mais do que eu tenho espaço para esta coluna. Na verdade, Microsoft Research tem uma divisão inteira dedicada a PNL (ver research.microsoft.com/groups/nlp para obter mais informações sobre o que eles estão investigando). E tome boa nota que eu não sou um deles.

Feliza: Versão 1

A maneira mais fácil para obter uma versão de "trabalhar" 1 do Feliza é criar uma simples lista de respostas possíveis e escolher aleatoriamente entre eles:

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)]

Trabalhar com uma matriz aqui não é idiomaticamente "F #-ish," mas ele mais fácil selecionar aleatoriamente as respostas possíveis. Não é uma conversa particularmente emocionante, embora pode parecer familiar para qualquer pessoa que já tentou falar com um programador que está tentando escrever código no momento. Ainda assim, por pouco tempo, ele realmente pode sentir como uma conversa real. Nós podemos fazer melhor, porém.

Feliza: Versão 2

Uma próxima aplicação óbvia é criar uma "base de conhecimento" respostas enlatadas às entradas específicas do usuário. Isso é facilmente modelado em F # usando tuplas, com o primeiro elemento na tupla sendo a frase de entrada para o qual desejamos responder e o segundo elemento sendo a resposta. Ou, para ser mais humano sobre ele (e evitar repetições evidentes nas respostas), podemos fazer o segundo elemento de uma lista de respostas possíveis e escolher aleatoriamente um dessa lista, conforme mostrado na Figura 1.

Figura 1 O Feliza da Base de conhecimento

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)

Esta versão faz alguma limpeza simple da entrada e procura uma correspondência com a primeira parte da lista de tuplas (o "knowledge base"), em seguida, seleciona aleatoriamente uma resposta na lista. Se nenhuma "frase-chave" é encontrado no conhecimento básico, que uma resposta "desconhecida" é gerada, novamente selecionado aleatoriamente em uma lista. (Concedido, a limpeza é feita de forma particularmente ineficiente, mas quando estamos falando de comunicação humana, atrasos não são um problema. Na verdade, algumas implementações de chatterbot deliberadamente retardar as respostas em os imprima caractere por caractere, para imitar alguém digitar em um teclado.)

Obviamente, se isso fosse para ser usado em qualquer tipo de cenário do mundo real, porque a frase de entrada tem de ser uma correspondência exata para desencadear a resposta, seria necessário uma maior conhecimento base de muito, muito, incorporando cada permutação possível de fala humana. Ugh — não é uma solução escalável. Além do mais, nós realmente perder muito no quando de Feliza não responde a entrada do usuário em uma forma mais significativa. Uma das qualidades originais de ELIZA era que se você disse, "Eu gosto de batatas," ela poderia responder com, "São batatas importantes para você?"— tornando a conversa muito mais "personalizado".

Feliza: Versão 3

Esta versão começa a ser um pouco mais complicado, mas também oferece mais flexibilidade e poder. Essencialmente, podemos transformar o algoritmo de correspondência exata em um flexível, convertendo a lista de tuplas em uma lista de funções que são cada uma avaliada e uma oportunidade para criar uma resposta. Abre-se uma lista enorme de opções para como Feliza pode interagir com a entrada, e como ela pode escolher palavras da entrada para gerar respostas específicas.

Ele começa com uma lista simples de "regras de processamento," na parte inferior do que vai ser uma resposta catchall indicando que ela não sabia como responder, o equivalente moral da lista "unknownResponses" da versão anterior, como mostrado na Figura 2.

Figura 2 Catchall regra para responder

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)

O núcleo desta versão está na última linha — a função de List.choose leva cada processingRule e executa-o contra a entrada, e se o processingRule retorna um algum valor, esse valor obtém adicionado a uma lista retornada para o chamador. Então agora podemos adicionar novas regras, cada um retorno um valor e, em seguida, quer tomar primeiro (como mostrado na Figura 2, usando List.head) ou mesmo aleatoriamente seleccionar um. Em uma versão futura, nós pode produzir um certo valor que é uma resposta de texto e um "peso" da sua adequação, para ajudar a selecionar qual é a resposta certa.

Novas regras de escrita se torna mais fácil agora. Podemos ter uma regra que chaves apenas fora da entrada, utilizando a correspondência de padrão F # para torná-lo mais fácil de combinar:

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

Ou podemos usar a forma abreviada "funcionar" construir a fazer o mesmo, conforme mostrado na Figura 3.

Figura 3 A construção de função

(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
);

Na maioria das vezes, porém, Feliza não estar recebendo essas frases em lata, assim que nós prefere ela levar sua sugestão de palavras-chave na entrada do usuário, como Figura 4 especifica.

Figura 4 amarrando Reponses para palavras-chave

(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 # veteranos notará que F # "ativos padrões" seria um ajuste perfeito para algumas destas; aqueles que não estão tão familiarizados com a construção de padrões ativo F # (ou com a sintaxe da F # a correspondência de padrões em geral) podem encontrar mais informações da excelente série de duas partes Jessica Kerr sobre o assunto no bit.ly/ys4jto e bit.ly/ABQkSN.

Próximo: Conectando-se a Feliza

Feliza é grande, mas sem um canal de entrada que transpõe o teclado, ela não vai muito longe. Feliza quer ajudar as pessoas muito mais do que apenas aqueles que estão sentados na frente do teclado — ela quer ser acessível a qualquer pessoa com um telefone celular ou conexão com a Internet, e na próxima edição, nós vai "ligá-la" para fazer exatamente isso.

Codificação feliz!

Ted Neward é um consultor de arquitetura com LLC de Neudesic. Ele escreveu mais de 100 artigos, é um MVP c# e INETA orador e tem o autor e co-autor de uma dúzia de livros, incluindo o recém-lançado "profissional F # 2.0" (Wrox, 2010). Ele consulta, mentores regularmente. Pluralsight.com ted@tedneward.com se você estiver interessado em ter-lhe vir trabalhar com sua equipe ou ler seu blog em blogs.tedneward.com.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Matthew Podwysocki