Cette page a été traduite par l'API Cloud Translation.
Switch to English

Chaînes Unicode

Voir sur TensorFlow.org Exécuter dans Google Colab Afficher la source sur GitHub Télécharger le carnet

introduction

Les modèles qui traitent le langage naturel gèrent souvent différentes langues avec différents jeux de caractères. Unicode est un système de codage standard utilisé pour représenter des caractères de presque toutes les langues. Chaque caractère est codé en utilisant un point de code entier unique entre 0 et 0x10FFFF . Une chaîne Unicode est une séquence de zéro ou plusieurs points de code.

Ce didacticiel montre comment représenter des chaînes Unicode dans TensorFlow et les manipuler à l'aide d'équivalents Unicode d'opérations de chaîne standard. Il sépare les chaînes Unicode en jetons en fonction de la détection de script.

import tensorflow as tf

Le type de données tf.string

Le tensorflow de base tf.string dtype vous permet de créer tenseurs de chaînes d'octets. Les chaînes Unicode sont encodées en utf-8 par défaut.

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

Un tenseur tf.string peut contenir des chaînes d'octets de différentes longueurs car les chaînes d'octets sont traitées comme des unités atomiques. La longueur de la chaîne n'est pas incluse dans les dimensions du tenseur.

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

Représenter Unicode

Il existe deux méthodes standard pour représenter une chaîne Unicode dans TensorFlow:

  • string scalaire - où la séquence de points de code est codée à l'aide d'un codage de caractères connu.
  • vecteur int32 - où chaque position contient un seul point de code.

Par exemple, les trois valeurs suivantes représentent toutes la chaîne Unicode "语言处理" (qui signifie «traitement du langage» en chinois):

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

Conversion entre représentations

TensorFlow fournit des opérations de conversion entre ces différentes représentations:

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'>

Dimensions du lot

Lors du décodage de plusieurs chaînes, le nombre de caractères de chaque chaîne peut ne pas être égal. Le résultat renvoyé est un tf.RaggedTensor , où la longueur de la dimension la plus interne varie en fonction du nombre de caractères dans chaque chaîne:

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

Vous pouvez utiliser ce tf.RaggedTensor directement, ou le convertir en un tf.Tensor dense avec remplissage ou un tf.SparseTensor utilisant les méthodes tf.RaggedTensor.to_tensor et 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()

Lors de l'encodage de plusieurs chaînes de même longueur, un tf.Tensor peut être utilisé comme entrée:

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

Lors de l'encodage de plusieurs chaînes de longueur variable, un tf.RaggedTensor doit être utilisé comme entrée:

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 vous avez un tenseur avec plusieurs chaînes au format padded ou sparse, convertissez-le en tf.RaggedTensor avant d'appeler 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)>

Opérations Unicode

Longueur des caractères

L'opération tf.strings.length a une unit paramètre, qui indique comment les longueurs doivent être calculées. unit défaut, l' unit est "BYTE" , mais elle peut être définie sur d'autres valeurs, telles que "UTF8_CHAR" ou "UTF16_CHAR" , pour déterminer le nombre de points de code Unicode dans chaque string codée.

# 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

Sous-chaînes de caractères

De même, l'opération tf.strings.substr accepte le paramètre " unit " et l'utilise pour déterminer quel type de décalage les paramètres " pos " et " len " contiennent.

# 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'

Diviser les chaînes Unicode

L'opération tf.strings.unicode_split divise les chaînes Unicode en sous-chaînes de caractères individuels:

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)

Décalages d'octets pour les caractères

Pour aligner le tenseur de caractères généré par tf.strings.unicode_decode avec la chaîne d'origine, il est utile de connaître le décalage du début de chaque caractère. La méthode tf.strings.unicode_decode_with_offsets est similaire à unicode_decode , sauf qu'elle renvoie un deuxième tenseur contenant le décalage de début de chaque caractère.

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

Scripts Unicode

Chaque point de code Unicode appartient à une seule collection de points de code appelée script . L'écriture d'un caractère est utile pour déterminer dans quelle langue le caractère peut être. Par exemple, savoir que «Б» est en caractères cyrilliques indique que le texte moderne contenant ce caractère provient probablement d'une langue slave telle que le russe ou l'ukrainien.

TensorFlow fournit l'opération tf.strings.unicode_script pour déterminer le script utilisé par un point de code donné. Les codes de script sont des valeurs int32 correspondant aux valeurs UScriptCode de International Components for Unicode (ICU).

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

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

L'opération tf.strings.unicode_script peut également être appliquée aux tf.Tensor multidimensionnels ou tf.RaggedTensor s de tf.RaggedTensor :

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

Exemple: segmentation simple

La segmentation consiste à diviser le texte en unités de type mot. C'est souvent facile lorsque les caractères d'espacement sont utilisés pour séparer les mots, mais certaines langues (comme le chinois et le japonais) n'utilisent pas d'espaces, et certaines langues (comme l'allemand) contiennent de longs composés qui doivent être divisés afin d'analyser leur signification. Dans le texte Web, différentes langues et écritures sont fréquemment mélangées, comme dans "NY 株 価" (New York Stock Exchange).

Nous pouvons effectuer une segmentation très grossière (sans implémenter de modèles ML) en utilisant des changements de script pour approcher les limites des mots. Cela fonctionnera pour les chaînes comme l'exemple "NY 株 価" ci-dessus. Cela fonctionnera également pour la plupart des langues qui utilisent des espaces, car les caractères d'espacement de divers scripts sont tous classés comme USCRIPT_COMMON, un code de script spécial qui diffère de celui de tout texte réel.

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

Tout d'abord, nous décodons les phrases en points de code de caractères et trouvons l'identifiant du script pour chaque caractère.

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

Ensuite, nous utilisons ces identificateurs de script pour déterminer où les limites de mots doivent être ajoutées. Nous ajoutons une limite de mot au début de chaque phrase, et pour chaque caractère dont le script diffère du caractère précédent:

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

Nous pouvons ensuite utiliser ces décalages de départ pour construire un RaggedTensor contenant la liste des mots de tous les lots:

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

Et enfin, nous pouvons segmenter le mot RaggedTensor en phrases:

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

Pour rendre le résultat final plus facile à lire, nous pouvons le recoder en chaînes 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']]