Se usó la API de Cloud Translation para traducir esta página.
Switch to English

Cadenas Unicode

Ver en TensorFlow.org Ejecutar en Google Colab Ver fuente en GitHub Descargar cuaderno

Introducción

Los modelos que procesan el lenguaje natural a menudo manejan diferentes idiomas con diferentes conjuntos de caracteres. Unicode es un sistema de codificación estándar que se utiliza para representar caracteres de casi todos los idiomas. Cada carácter se codifica utilizando un punto de código entero único entre 0 y 0x10FFFF . Una cadena Unicode es una secuencia de cero o más puntos de código.

Este instructivo muestra cómo representar cadenas Unicode en TensorFlow y manipularlas con equivalentes Unicode de operaciones de cadenas estándar. Separa las cadenas Unicode en tokens según la detección de secuencias de comandos.

import tensorflow as tf

El tipo de datos tf.string

El tipo tf.string dtype básico de TensorFlow tf.string permite construir tensores de cadenas de bytes. Las cadenas Unicode están codificadas en utf-8 de forma predeterminada.

tf.constant(u"Thanks 😊")
<tf.Tensor: shape=(), dtype=string, numpy=b'Thanks \xf0\x9f\x98\x8a'>

Un tensor tf.string puede contener cadenas de bytes de diferentes longitudes porque las cadenas de bytes se tratan como unidades atómicas. La longitud de la cuerda no está incluida en las dimensiones del tensor.

tf.constant([u"You're", u"welcome!"]).shape
TensorShape([2])

Representando Unicode

Hay dos formas estándar de representar una cadena Unicode en TensorFlow:

  • escalar de string : donde la secuencia de puntos de código se codifica utilizando una codificación de caracteres conocida.
  • vector int32 - donde cada posición contiene un solo punto de código.

Por ejemplo, los siguientes tres valores representan la cadena Unicode "语言处理" (que significa "procesamiento de idioma" en chino):

# Unicode string, represented as a UTF-8 encoded string scalar.
text_utf8 = tf.constant(u"语言处理")
text_utf8
<tf.Tensor: shape=(), dtype=string, numpy=b'\xe8\xaf\xad\xe8\xa8\x80\xe5\xa4\x84\xe7\x90\x86'>
# Unicode string, represented as a UTF-16-BE encoded string scalar.
text_utf16be = tf.constant(u"语言处理".encode("UTF-16-BE"))
text_utf16be
<tf.Tensor: shape=(), dtype=string, numpy=b'\x8b\xed\x8a\x00Y\x04t\x06'>
# Unicode string, represented as a vector of Unicode code points.
text_chars = tf.constant([ord(char) for char in u"语言处理"])
text_chars
<tf.Tensor: shape=(4,), dtype=int32, numpy=array([35821, 35328, 22788, 29702], dtype=int32)>

Conversión entre representaciones

TensorFlow proporciona operaciones para convertir entre estas diferentes representaciones:

tf.strings.unicode_decode(text_utf8,
                          input_encoding='UTF-8')
<tf.Tensor: shape=(4,), dtype=int32, numpy=array([35821, 35328, 22788, 29702], dtype=int32)>
tf.strings.unicode_encode(text_chars,
                          output_encoding='UTF-8')
<tf.Tensor: shape=(), dtype=string, numpy=b'\xe8\xaf\xad\xe8\xa8\x80\xe5\xa4\x84\xe7\x90\x86'>
tf.strings.unicode_transcode(text_utf8,
                             input_encoding='UTF8',
                             output_encoding='UTF-16-BE')
<tf.Tensor: shape=(), dtype=string, numpy=b'\x8b\xed\x8a\x00Y\x04t\x06'>

Dimensiones del lote

Al decodificar varias cadenas, es posible que el número de caracteres de cada cadena no sea igual. El resultado devuelto es un tf.RaggedTensor , donde la longitud de la dimensión más interna varía según la cantidad de caracteres en cada cadena:

# A batch of Unicode strings, each represented as a UTF8-encoded string.
batch_utf8 = [s.encode('UTF-8') for s in
              [u'hÃllo',  u'What is the weather tomorrow',  u'Göödnight', u'😊']]
batch_chars_ragged = tf.strings.unicode_decode(batch_utf8,
                                               input_encoding='UTF-8')
for sentence_chars in batch_chars_ragged.to_list():
  print(sentence_chars)
[104, 195, 108, 108, 111]
[87, 104, 97, 116, 32, 105, 115, 32, 116, 104, 101, 32, 119, 101, 97, 116, 104, 101, 114, 32, 116, 111, 109, 111, 114, 114, 111, 119]
[71, 246, 246, 100, 110, 105, 103, 104, 116]
[128522]

Puede usar estetf.RaggedTensor directamente, o convertirlo en un tf.Tensor denso con relleno o un tf.SparseTensor usando los métodos tf.RaggedTensor.to_tensor y tf.RaggedTensor.to_sparse .

batch_chars_padded = batch_chars_ragged.to_tensor(default_value=-1)
print(batch_chars_padded.numpy())
[[   104    195    108    108    111     -1     -1     -1     -1     -1
      -1     -1     -1     -1     -1     -1     -1     -1     -1     -1
      -1     -1     -1     -1     -1     -1     -1     -1]
 [    87    104     97    116     32    105    115     32    116    104
     101     32    119    101     97    116    104    101    114     32
     116    111    109    111    114    114    111    119]
 [    71    246    246    100    110    105    103    104    116     -1
      -1     -1     -1     -1     -1     -1     -1     -1     -1     -1
      -1     -1     -1     -1     -1     -1     -1     -1]
 [128522     -1     -1     -1     -1     -1     -1     -1     -1     -1
      -1     -1     -1     -1     -1     -1     -1     -1     -1     -1
      -1     -1     -1     -1     -1     -1     -1     -1]]

batch_chars_sparse = batch_chars_ragged.to_sparse()

Cuando se codifican varias cadenas con las mismas longitudes, se puede utilizar un tf.Tensor como entrada:

tf.strings.unicode_encode([[99, 97, 116], [100, 111, 103], [ 99, 111, 119]],
                          output_encoding='UTF-8')
<tf.Tensor: shape=(3,), dtype=string, numpy=array([b'cat', b'dog', b'cow'], dtype=object)>

Cuando se codifican varias cadenas con longitud variable, se debe utilizar untf.RaggedTensor como entrada:

tf.strings.unicode_encode(batch_chars_ragged, output_encoding='UTF-8')
<tf.Tensor: shape=(4,), dtype=string, numpy=
array([b'h\xc3\x83llo', b'What is the weather tomorrow',
       b'G\xc3\xb6\xc3\xb6dnight', b'\xf0\x9f\x98\x8a'], dtype=object)>

Si tiene un tensor con varias cadenas en formato relleno o disperso,tf.RaggedTensor untf.RaggedTensor antes de llamar a unicode_encode :

tf.strings.unicode_encode(
    tf.RaggedTensor.from_sparse(batch_chars_sparse),
    output_encoding='UTF-8')
<tf.Tensor: shape=(4,), dtype=string, numpy=
array([b'h\xc3\x83llo', b'What is the weather tomorrow',
       b'G\xc3\xb6\xc3\xb6dnight', b'\xf0\x9f\x98\x8a'], dtype=object)>
tf.strings.unicode_encode(
    tf.RaggedTensor.from_tensor(batch_chars_padded, padding=-1),
    output_encoding='UTF-8')
<tf.Tensor: shape=(4,), dtype=string, numpy=
array([b'h\xc3\x83llo', b'What is the weather tomorrow',
       b'G\xc3\xb6\xc3\xb6dnight', b'\xf0\x9f\x98\x8a'], dtype=object)>

Operaciones Unicode

Longitud del carácter

La operación tf.strings.length tiene una unit parámetro, que indica cómo se deben calcular las longitudes. unit defecto "BYTE" , pero se puede establecer en otros valores, como "UTF8_CHAR" o "UTF16_CHAR" , para determinar el número de puntos de código Unicode en cada string codificada.

# Note that the final character takes up 4 bytes in UTF8.
thanks = u'Thanks 😊'.encode('UTF-8')
num_bytes = tf.strings.length(thanks).numpy()
num_chars = tf.strings.length(thanks, unit='UTF8_CHAR').numpy()
print('{} bytes; {} UTF-8 characters'.format(num_bytes, num_chars))
11 bytes; 8 UTF-8 characters

Subcadenas de caracteres

De manera similar, la operación tf.strings.substr acepta el parámetro " unit " y lo usa para determinar qué tipo de compensaciones contienen los parámetros " pos " y " len ".

# default: unit='BYTE'. With len=1, we return a single byte.
tf.strings.substr(thanks, pos=7, len=1).numpy()
b'\xf0'
# Specifying unit='UTF8_CHAR', we return a single character, which in this case
# is 4 bytes.
print(tf.strings.substr(thanks, pos=7, len=1, unit='UTF8_CHAR').numpy())
b'\xf0\x9f\x98\x8a'

Dividir cadenas Unicode

La operación tf.strings.unicode_split divide las cadenas Unicode en subcadenas de caracteres individuales:

tf.strings.unicode_split(thanks, 'UTF-8').numpy()
array([b'T', b'h', b'a', b'n', b'k', b's', b' ', b'\xf0\x9f\x98\x8a'],
      dtype=object)

Desplazamientos de bytes para caracteres

Para alinear el tensor de caracteres generado por tf.strings.unicode_decode con la cadena original, es útil conocer el desplazamiento de donde comienza cada carácter. El método tf.strings.unicode_decode_with_offsets es similar a unicode_decode , excepto que devuelve un segundo tensor que contiene el desplazamiento inicial de cada carácter.

codepoints, offsets = tf.strings.unicode_decode_with_offsets(u"🎈🎉🎊", 'UTF-8')

for (codepoint, offset) in zip(codepoints.numpy(), offsets.numpy()):
  print("At byte offset {}: codepoint {}".format(offset, codepoint))
At byte offset 0: codepoint 127880
At byte offset 4: codepoint 127881
At byte offset 8: codepoint 127882

Secuencias de comandos Unicode

Cada punto de código Unicode pertenece a una única colección de puntos de código conocida como secuencia de comandos . La escritura de un personaje es útil para determinar en qué idioma podría estar el personaje. Por ejemplo, saber que 'Á' está en escritura cirílica indica que el texto moderno que contiene ese carácter probablemente provenga de un idioma eslavo como el ruso o el ucraniano.

TensorFlow proporciona la operación tf.strings.unicode_script para determinar qué secuencia de comandos usa un punto de código determinado. Los códigos de secuencia de comandos son int32 valores correspondientes a Componentes internacionales para Unicode (ICU) UScriptCode valores.

uscript = tf.strings.unicode_script([33464, 1041])  # ['芸', 'Б']

print(uscript.numpy())  # [17, 8] == [USCRIPT_HAN, USCRIPT_CYRILLIC]
[17  8]

La operación tf.strings.unicode_script también se puede aplicar a tf.Tensor stf.RaggedTensor de puntos de código:

print(tf.strings.unicode_script(batch_chars_ragged))
<tf.RaggedTensor [[25, 25, 25, 25, 25], [25, 25, 25, 25, 0, 25, 25, 0, 25, 25, 25, 0, 25, 25, 25, 25, 25, 25, 25, 0, 25, 25, 25, 25, 25, 25, 25, 25], [25, 25, 25, 25, 25, 25, 25, 25, 25], [0]]>

Ejemplo: segmentación simple

La segmentación es la tarea de dividir el texto en unidades parecidas a palabras. Esto suele ser fácil cuando se utilizan caracteres de espacio para separar palabras, pero algunos idiomas (como el chino y el japonés) no utilizan espacios, y algunos idiomas (como el alemán) contienen compuestos largos que deben dividirse para analizar su significado. En el texto web, con frecuencia se mezclan diferentes idiomas y escrituras, como en "NY 株 価" (Bolsa de Nueva York).

Podemos realizar una segmentación muy aproximada (sin implementar ningún modelo ML) mediante el uso de cambios en el script para aproximar los límites de las palabras. Esto funcionará para cadenas como el ejemplo "NY 株 価" anterior. También funcionará para la mayoría de los idiomas que usan espacios, ya que los caracteres de espacio de varios guiones se clasifican como USCRIPT_COMMON, un código de guión especial que difiere del de cualquier texto real.

# dtype: string; shape: [num_sentences]
#
# The sentences to process.  Edit this line to try out different inputs!
sentence_texts = [u'Hello, world.', u'世界こんにちは']

Primero, decodificamos las oraciones en puntos de código de caracteres y buscamos el identificador de secuencia de comandos para cada carácter.

# dtype: int32; shape: [num_sentences, (num_chars_per_sentence)]
#
# sentence_char_codepoint[i, j] is the codepoint for the j'th character in
# the i'th sentence.
sentence_char_codepoint = tf.strings.unicode_decode(sentence_texts, 'UTF-8')
print(sentence_char_codepoint)

# dtype: int32; shape: [num_sentences, (num_chars_per_sentence)]
#
# sentence_char_scripts[i, j] is the unicode script of the j'th character in
# the i'th sentence.
sentence_char_script = tf.strings.unicode_script(sentence_char_codepoint)
print(sentence_char_script)
<tf.RaggedTensor [[72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 46], [19990, 30028, 12371, 12435, 12395, 12385, 12399]]>
<tf.RaggedTensor [[25, 25, 25, 25, 25, 0, 0, 25, 25, 25, 25, 25, 0], [17, 17, 20, 20, 20, 20, 20]]>

A continuación, usamos esos identificadores de secuencia de comandos para determinar dónde se deben agregar los límites de las palabras. Agregamos un límite de palabra al principio de cada oración y para cada carácter cuyo guión difiera del carácter anterior:

# dtype: bool; shape: [num_sentences, (num_chars_per_sentence)]
#
# sentence_char_starts_word[i, j] is True if the j'th character in the i'th
# sentence is the start of a word.
sentence_char_starts_word = tf.concat(
    [tf.fill([sentence_char_script.nrows(), 1], True),
     tf.not_equal(sentence_char_script[:, 1:], sentence_char_script[:, :-1])],
    axis=1)

# dtype: int64; shape: [num_words]
#
# word_starts[i] is the index of the character that starts the i'th word (in
# the flattened list of characters from all sentences).
word_starts = tf.squeeze(tf.where(sentence_char_starts_word.values), axis=1)
print(word_starts)
tf.Tensor([ 0  5  7 12 13 15], shape=(6,), dtype=int64)

Luego podemos usar esas compensaciones de inicio para construir un RaggedTensor contenga la lista de palabras de todos los lotes:

# dtype: int32; shape: [num_words, (num_chars_per_word)]
#
# word_char_codepoint[i, j] is the codepoint for the j'th character in the
# i'th word.
word_char_codepoint = tf.RaggedTensor.from_row_starts(
    values=sentence_char_codepoint.values,
    row_starts=word_starts)
print(word_char_codepoint)
<tf.RaggedTensor [[72, 101, 108, 108, 111], [44, 32], [119, 111, 114, 108, 100], [46], [19990, 30028], [12371, 12435, 12395, 12385, 12399]]>

Y finalmente, podemos segmentar los puntos de código de la palabra RaggedTensor nuevamente en oraciones:

# dtype: int64; shape: [num_sentences]
#
# sentence_num_words[i] is the number of words in the i'th sentence.
sentence_num_words = tf.reduce_sum(
    tf.cast(sentence_char_starts_word, tf.int64),
    axis=1)

# dtype: int32; shape: [num_sentences, (num_words_per_sentence), (num_chars_per_word)]
#
# sentence_word_char_codepoint[i, j, k] is the codepoint for the k'th character
# in the j'th word in the i'th sentence.
sentence_word_char_codepoint = tf.RaggedTensor.from_row_lengths(
    values=word_char_codepoint,
    row_lengths=sentence_num_words)
print(sentence_word_char_codepoint)
<tf.RaggedTensor [[[72, 101, 108, 108, 111], [44, 32], [119, 111, 114, 108, 100], [46]], [[19990, 30028], [12371, 12435, 12395, 12385, 12399]]]>

Para que el resultado final sea más fácil de leer, podemos codificarlo nuevamente en cadenas UTF-8:

tf.strings.unicode_encode(sentence_word_char_codepoint, 'UTF-8').to_list()
[[b'Hello', b', ', b'world', b'.'],
 [b'\xe4\xb8\x96\xe7\x95\x8c',
  b'\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81\xa1\xe3\x81\xaf']]