Ir al contenido principal

Marcelino - Paso 1 - La tarea de la gramática

Paso 1 - La tarea de la gramática

Vamos a crear casi desde cero un asistente por voz para evolucionarlo y experimentar con el "machine learning"

Partes:
  Reconocimiento de voz STT
  Reaccion a comandos básicos
  Reproducción de respuesta por voz sintética TTS
  Control de aplicaciones
  Aprendizaje automático

Reconocimiento de voz STT:
  Para empezar vamos a utilizar el set Julius creando un modelo acústico. Más adelante se intentara discriminar los hablantes mediante la frecuencia fundamental de cada una de las voces.

  Todos los motores de reconocimiento de voz ("SRE") están formados por los siguientes componentes:

- Modelo de lenguaje o archivo de gramática:
    Los modelos de lenguaje contienen una gran lista de palabras y su probabilidad de que aparezcan en una secuencia concreta, son usadas para las aplicaciones de dictado.
    Los archivos de gramática (o Grammars) son unos pequeños archivos que contienen un set de combinaciones de palabras predefinidas. Son usadas en las aplicaciones para dar órdenes por voz como control de escritorio.
- Modelo acustico:
    Contiene una representacion estática de los distintos sonidos que componen cada palabra en el Modelo de lenguaje o gramática. Cada sonido diferente corresponde a un fonema.
- Decodificador:
    Pragrama que recoge los sonidos dichos por un usuario y busca en el modelo acústico una equivalencia. Cuando encuentra una coincidencia, el decodificador determina el fonema que corresponde al sonido, realiza un seguimiento de los fonemas coincidentes hasta que el usuario hace una pausa. A continuación, busca en el modelo de lenguaje o en el archivo de gramática, la serie de equivalencias de fonemas. Si hay una coincidencia, devuelve el texto de la palabra o frase correspondiente al programa que lo llamó.

    Aunque Julius utiliza modelos acústicos creados con el kit de herramientas HTK, utiliza su propio formato de definición de Gramática.

  Archivos de gramática:

    Una gramática de reconocimiento esencialmente define restricciones sobre lo que el SRE puede esperar como entrada. Es una lista de palabras y/o frases que el SRE escucha. Cuando se escucha una de estas palabras o frases predefinidas, el SRE devuelve la palabra o frase al programa que llama - normalmente un "Dialog Manager" (pero también podría ser un guión escrito en Perl, Python, etc.). El Dialog Manager entonces hace algún procesamiento basado en esta palabra o frase.

    El ejemplo en el libro HTK es el de una interfaz de voz para la marcación telefónica. Si la SRE escucha la secuencia de palabras: 'Llame a Steve Young', devuelve la representación textual de esta frase al Dialog Manager, que luego busca el número de teléfono de Steve y lo marca.

    Es muy importante entender que las palabras que puedes usar en tu Gramática están limitadas a las palabras que has "entrenado" en tu Modelo Acústico. Los dos están atados muy estrechamente.

  Modelo acústico:

    Un modelo acústico es un archivo que contiene una representación estadística de cada sonido distintivo que compone una palabra hablada. Debe contener los sonidos para cada palabra usada en su gramática. Las palabras de su gramática le dan a la SRE la secuencia de sonidos que debe escuchar. Entonces el SRE escucha la secuencia de sonidos que componen una palabra en particular y cuando encuentra una secuencia particular, devuelve la representación textual de la palabra al programa llamante. Por lo tanto, cuando una SRE está escuchando palabras, en realidad está escuchando la secuencia de sonidos que componen una de las palabras que definió en su gramática. La gramática y el modelo acústico trabajan juntos.

    Por lo tanto, cuando entrenas tu Modelo Acústico para reconocer la frase 'llamar a Paco', el SRE está realmente escuchando la secuencia de fonemas "ll", "a", "m", "a", "r", "a" , "p", "a", "k" y "o". Si usted dice cada uno de estos fonemas en voz alta en secuencia, le dará una idea de lo que el SRE está buscando.

    Los SRE comerciales usan grandes bases de datos de audio de voz para crear sus modelos acústicos. Debido a esto, las palabras más comunes que pueden ser utilizados en una gramática ya están incluidos en su modelo acústico.

    Al crear sus propios modelos acústicos y gramáticas, usted necesita cerciorarse de que todos los fonemas que componen las palabras en su gramática se incluyan en su modelo acústico.

  Antecedentes - Gramáticas de Julius

    En Julius, una gramática de reconocimiento se divide en dos archivos:

    El archivo ".grammar" que define un conjunto de reglas que rigen las palabras que el SRE espera reconocer; En lugar de enumerar cada palabra del archivo .grammar, un archivo de gramática Julius utiliza "Categorías de palabras", que es el nombre de una lista de palabras que se deben reconocer (que se definen en un archivo ".voca" separado).
    El archivo ".voca" que define los "Candidatos de Palabra" reales en cada Categoría de Palabra y su información de pronunciación (Nota: los fonemas que componen esta información de pronunciación deben ser los mismos que serán utilizados para entrenar su Modelo Acústico).

  Archivo .grammar:

    Las reglas que gobiernan las palabras permitidas se definen en el archivo .grammar usando un formato BNF modificado. Una especificación .grammar en Julius utiliza un conjunto de reglas de derivación, escritas como:

    Símbolo: [expresión con símbolos]

    dónde:

        - El Símbolo es un no-terminal.
        - [expresión con símbolos] es una expresión que consiste en secuencias de Símbolos, que pueden ser terminales y/o no-terminales.

    Un terminal es jerga BNF para un símbolo que representa un valor constante. Nunca aparece a la izquierda del colon (de los dos puntos). Los terminales en Julius representan Categorías de palabras - listas de palabras que se definen adicionalmente en un archivo ".voca" separado.

      [En ciencias de la computación, los símbolos terminales y no-terminales son los elementos léxicos utilizados para especificar las reglas de producción que constituyen una gramática formal. Los símbolos terminales son los símbolos elementales del lenguaje definido por una gramática formal. Los símbolos no-terminales (o variables sintácticas) son reemplazados por grupos de símbolos terminales según las reglas de producción.]

    Un no-terminal es jerga BNF para un símbolo que se puede expresar en términos de otros símbolos. Se puede reemplazar como resultado de reglas de sustitución.

    Por ejemplo, observe las siguientes reglas de derivación:

      S: NS_B BUSCA NS_E
      BUSCA: CONECTA NOMBRE

    En este ejemplo, "S" es el símbolo de oración inicial. NS_B y NS_E corresponden al silencio que se produce justo antes del enunciado que desea reconocer y después. "S", "NS_B" y "NS_E" son necesarios en todas las gramáticas de Julius.

    "NS_B", "NS_E", "CONECTA" y "NOMBRE" son terminales, y representan Categorías de Palabras que deben definirse en el archivo ".voca". En el archivo ".voca", "CONECTA" corresponde a dos palabras: "TELEFONO" y "LLAMA" y sus pronunciaciones. "NOMBRE" corresponde a dos palabras: "PACO" y "PEREZ" y sus pronunciaciones.

    "BUSCA" es un no-terminal, y no tiene ninguna definición en el archivo .voca. Tiene una definición adicional en el archivo .grammar, donde se sustituye por la expresión "CONECTA NOMBRE". Todos los no-terminales deben definirse adicionalmente en el archivo .grammar hasta que finalmente se representan por los terminales (que luego se definen en el archivo .voca como categorías de palabras).

    Con Julius, sólo se permite una Regla de Sustitución por línea, con el signo ":" como separador. Los caracteres ASCII alfanuméricos y el subrayado están permitidos para los nombres de símbolos y éstos son sensibles a mayúsculas y minúsculas.

  Archivo .voca

    El archivo ".voca" contiene definiciones de palabra para cada categoría de palabras definida en el archivo .grammar.

    Cada categoría de palabra debe definirse con "%" que la precede. Las definiciones de palabras en cada categoría de palabras se definen una por línea. La primera columna es la cadena que se emitirá cuando se reconoce, y el resto es la pronunciación. Los espacios y/o pestañas pueden actuar como separadores de campo.

    Formato:

    % [Categoría de palabras]
    [Definición de palabras] [pronunciación ...]
    ...

    Por ejemplo, las categorías de palabras "NS_B", "NS_E", "CONECTA" y "NOMBRE" se mencionaron en el archivo ".grammar" y se definen en un ".voca" de la siguiente manera:

      % NS_B
      <s>   sil

      % NS_E
      </s>  sil

      % CONECTA
      TELEFONO t e l e f o n o
      LLAMA ll a m a

      % NOMBRE
      PACO p a k o
      PEREZ p e r e z

    En el ejemplo anterior, las categorías de palabras NS_B y NS_E tienen una definición de palabra con un modelo de silencio ("sil" es un modelo de silencio especial definido en su modelo acústico). Éstos corresponden al silencio de cabeza y cola en la entrada de voz.

    "CONECTA" se divide en dos palabras "TELEFONO" y "LLAMA" con información de pronunciación, que son los fonemas que componen las palabras que se van a reconocer (y que corresponden a fonemas que serán incluidos en su Modelo Acústico). "NOMBRE" se compone de dos palabras: "PACO" y "PEREZ" y sus fonemas.

    Los fonemas utilizados aquí deben coincidir con los fonemas utilizados en la creación de su modelo acústico (que vamos a crear en pasos posteriores).

    Si tiene palabras con diferentes pronunciaciones, simplemente cree las entradas adicionales en líneas separadas para la misma palabra pero con la pronunciación diferente.

  Los archivos .grammar y .voca trabajando juntos

    Julius necesita un archivo de rejilla de palabras predefinido en el que cada palabra y cada transición de palabra a palabra se enumeran explícitamente. Conseguimos esto compilando los archivos ".grammar" y ".voca" juntos para generar la palabra archivo de rejilla (en realidad son dos archivos, pero eso más adelante) con un script. El script mkdfa.pl lo hace buscando el Símbolo de Sentencia Inicial "S" en el archivo .grammar y reemplazando las Categorías de Palabra con todos los posibles Candidatos de palabra del archivo .voca, haciendo una lista predefinida de todas las posibles combinaciones de Palabras y frases que Julius debe reconocer. En este caso, la lista de todas las frases posibles sería:

      <s> TELEFONO PACO </s>
      <s> TELEFONO PEREZ </s>
      <s> LLAMA PACO </s>
      <s> LLAMA PEREZ </s>

    Lo que esto significa es que cuando Julius oye los sonidos que componen una palabra o frase pronunciada por un usuario, intenta emparejar estos sonidos con las representaciones estadísticas de los sonidos contenidos en el Modelo Acústico. Cuando se hace una coincidencia, Julius determina el fonema correspondiente al sonido. Mantiene el seguimiento de los fonemas coincidentes hasta que alcanza una pausa en el discurso del usuario. A continuación, busca la gramática compilada para la serie equivalente de fonemas. Se puede pensar en la gramática compilada como algo parecido a esto:

      sil  t e l e f o n o p a k o sil
      sil  t e l e f o n o p e r e z sil
      sil  ll a m a p a k o sil
      sil  ll a m a p e r e z sil

    Si, por ejemplo, se hace una coincidencia con la lista de fonemas: "sil ll a m a p a k o sil", Julius devuelve las palabras "<s> LLAMA PACO </s>" al programa que lo llamó.


TUTORIAL

  Instalar Julius:
    sudo apt install julius
  Instalar Julia:
    sudo apt install julia

  Archivo .grammar

    Para este tutorial, vaya a la carpeta 'voxforge' que creó en su directorio personal. Cree un nuevo directorio llamado 'tutorial'.

    A continuación, cree un archivo llamado sample.grammar en su nueva carpeta 'voxforge/tutorial' y añada el texto siguiente:

      S: NS_B SENT NS_E
      SENT: LLAMA_V NOMBRE_N
      SENT: MARCA_V DIGITO

    En este caso, NS_B, NS_E, LLAMA_V, NOMBRE_N, MARCA_V, DIGITO son categorías de palabras (es decir, terminales en jerga BNF), y deben definirse en un archivo .voca independiente.

    "SENT" es el único símbolo no-terminal. El "SENT" en la primera línea será sustituido por cualquiera de las siguientes frases de la categoría de palabras:

      "LLAMA_V NOMBRE_N" (de la segunda línea); o
      "MARCA_V DIGITO" (desde la tercera línea).

    Cada categoría de palabra (es decir, "LLAMA_V", "NOMBRE_N", "MARCA_V" o "DIGITO") se reemplaza por una de las definiciones de palabra establecidas en el archivo .voca a continuación.

  Archivo .voca

    Para este tutorial, cree un archivo llamado: sample.voca En la carpeta 'voxforge / tutorial' y agregue el siguiente texto:

      % NS_B
      <s>        sil

      % NS_E
      </s>        sil

      % LLAMA_V
      TELEFONO  t e l e f o n o
      LLAMA   ll a m a

      % MARCA_V
      MARCA        m a r k a

      % NOMBRE_N
      PACO        p a k o
      PEREZ        p e r e z

      % DIGITO
      CINCO        z i n k o
      CUATRO        k u a t r o
      NUEVE        n u e b e
      OCHO        o ch o
      OH        o
      UNO        u n o
      SIETE        s i e t e
      SEIS        s e i s
      TRES        t r e s
      DOS        d o s
      CERO       z e r o

  Compilando su Gramática

    Los archivos .grammar y .voca ahora necesitan ser compilados en archivos ".dfa" y ".dict" para que Julius pueda usarlos.

    Descargue el script de compilador de gramática de Julia mkdfa.jl (https://raw.githubusercontent.com/VoxForge/develop/master/bin/mkdfa.jl) a su carpeta 'voxforge/bin'.

      wget https://raw.githubusercontent.com/VoxForge/develop/master/bin/mkdfa.jl

    Nota: el script mkdfa.jl asume que los siguientes programas de julius:

        Linux: dfa_minimize y mkfa (que deberían estar en /usr/bin)
        Windows: dfa_minimize.exe y mkfa.exe

    son accesibles desde su PATH (que debería ser el caso ya que se incluyen como parte del ejecutable Julius que acaba de descargar).

    Los archivos .grammar y .voca necesitan tener el mismo prefijo de archivo, y este prefijo se especifica en el script mkdfa.jl. Desde el símbolo del sistema en su directorio 'voxforge/tutorial', compile sus archivos (sample.grammar y sample.voca) usando el siguiente comando:

      julia ../bin/mkdfa.jl sample

    Donde 'julia' es el nombre del lenguaje de programación julia; Y "../bin/mkdfa.jl" le dice a Julia que suba un directorio, luego al directorio bin para ejecutar el script "mkdfa.jl"; Y "sample" es el nombre del prefijo para sus archivos de gramática (es decir, sus archivos de gramática son "sample.grammar" y "sample.dfa") .

    A continuación se muestra el resultado esperado de ejecutar el script mkdfa.jl:

      julia bin/mkdfa.jl sample

      sample.grammar has 3 rules
      ---
      sample.voca has 6 categories and 23 words
      generated: sample.term
      ---
      Now parsing grammar file
      Now modifying grammar to minimize states[-1]
      Now parsing vocabulary file
      Now making nondeterministic finite automaton[6/6]
      Now making deterministic finite automaton[6/6]
      Now making triplet list[6/6]
      6 categories, 6 nodes, 6 arcs
      -> minimized: 6 nodes, 6 arcs
      generated: sample.dict

    Los archivos sample.dfa y sample.term generados contienen información para el FSM (Finite-state machine) y el archivo sample.dict contiene información del diccionario de palabras. Todos están en formato Julius.

      [Una máquina de estados finitos (FSM) o un autómata de estados finitos (FSA, plural: autómatas), autómatas finitos o simplemente una máquina de estados, es un modelo matemático de cálculo. Es una máquina abstracta que puede estar en exactamente uno de un número finito de estados en un momento dado. El FSM puede cambiar de un estado a otro en respuesta a algunas entradas externas; El cambio de un estado a otro se llama transición. Un FSM se define por una lista de sus estados, su estado inicial y las condiciones para cada transición.]
      [Para mortales: Una máquina de estados finitos es un concepto que se usa de una forma concreta. Esta máquina puede ser un algoritmo que solo puede pasar a un estado concreto (predefinido en una matriz) partiendo de otro estado concreto; Solo se puede pasar a ciertos estados si estamos en cierto estado.]