![]() | ![]() | ![]() | ![]() |
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
: convierte un escalar de cadena codificado en un vector de puntos de código. -
tf.strings.unicode_encode
: convierte un vector de puntos de código en un escalar de cadena codificada. -
tf.strings.unicode_transcode
: convierte un escalar de cadena codificada en una codificación diferente.
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']]