Chaînes Unicode

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

introduction

Les modèles de PNL gèrent souvent différentes langues avec différents jeux de caractères. Unicode est un système de codage standard qui est utilisé pour représenter les caractères de presque toutes les langues. Chaque caractère Unicode est codé en utilisant un nombre entier unique point de code entre 0 et 0x10FFFF . Une chaîne de caractères 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 des équivalents Unicode des 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
import numpy as np

Le tf.string type de données

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

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

A tf.string traite tensoriels octet chaînes comme unités atomiques. Cela lui permet de stocker des chaînes d'octets de différentes longueurs. La longueur de la corde n'est pas incluse dans les dimensions du tenseur.

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

Si vous utilisez Python pour les chaînes de construct, notez que les littéraux de chaîne sont codés en Unicode par défaut.

Représenter Unicode

Il existe deux manières standard de représenter une chaîne Unicode dans TensorFlow :

  • string scalaire - où la séquence de points de code est codé en utilisant un connue codage de caractères .
  • int32 vector - où chaque position contient un seul point de code.

Par exemple, les trois valeurs suivantes représentent toute la chaîne Unicode "语言处理" (qui signifie « le 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 les 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 dans chaque chaîne peut ne pas être égal. Le résultat obtenu 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 cette tf.RaggedTensor directement, ou le convertir en un dense tf.Tensor avec un rembourrage ou un tf.SparseTensor en 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()

nrows, ncols = batch_chars_sparse.dense_shape.numpy()
elements = [['_' for i in range(ncols)] for j in range(nrows)]
for (row, col), value in zip(batch_chars_sparse.indices.numpy(), batch_chars_sparse.values.numpy()):
  elements[row][col] = str(value)
# max_width = max(len(value) for row in elements for value in row)
value_lengths = []
for row in elements:
  for value in row:
    value_lengths.append(len(value))
max_width = max(value_lengths)
print('[%s]' % '\n '.join(
    '[%s]' % ', '.join(value.rjust(max_width) for value in row)
    for row in elements))
[[   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,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _]]

Lors de l' encodage des chaînes multiples avec les mêmes longueurs, utiliser une tf.Tensor 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 plusieurs chaînes de longueur variable, en utilisant un tf.RaggedTensor 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 en format rembourré ou clairsemés, le convertir en un premier tf.RaggedTensor avant d' appeler tf.strings.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

Utilisez l' unit paramètre de la tf.strings.length op pour indiquer comment les longueurs de caractères doivent être calculés. l' "BYTE" "UTF8_CHAR" "UTF16_CHAR" unit par défaut "BYTE" , mais il peut être réglé sur d' autres valeurs, telles que "UTF8_CHAR" ou "UTF16_CHAR" , afin de déterminer le nombre de points de code Unicode dans chaque chaîne de caractères 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

Le tf.strings.substr op accepte l' unit paramètre, et l' utilise pour déterminer quel type de compensation des pos et len paremeters contiennent.

# Here, unit='BYTE' (default). Returns a single byte with len=1
tf.strings.substr(thanks, pos=7, len=1).numpy()
b'\xf0'
# Specifying unit='UTF8_CHAR', returns a single 4 byte character in this case
print(tf.strings.substr(thanks, pos=7, len=1, unit='UTF8_CHAR').numpy())
b'\xf0\x9f\x98\x8a'

Diviser les chaînes Unicode

La tf.strings.unicode_split opération 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 pour où chaque personnage commence. Le procédé tf.strings.unicode_decode_with_offsets est similaire à unicode_decode , sauf qu'il renvoie un second 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 collection unique de codepoints connu sous le nom d' un scénario . L'écriture d'un personnage est utile pour déterminer dans quelle langue le personnage peut être. Par exemple, savoir que « Б » est en écriture cyrillique indique que le texte moderne contenant ce caractère provient probablement d'une langue slave telle que le russe ou l'ukrainien.

Tensorflow fournit la tf.strings.unicode_script opération pour déterminer quel script une utilisation de codepoint données. Les codes de script sont int32 des valeurs correspondant aux International Components for Unicode (ICU) UScriptCode valeurs.

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

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

La tf.strings.unicode_script opération peut également être appliquée à multidimensionnelle tf.Tensor s ou tf.RaggedTensor s de codepoints:

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 est la tâche de diviser le texte en unités semblables à des mots. C'est souvent facile lorsque des espaces 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 séparés afin d'analyser leur sens. 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 modifications de script pour approximer les limites des mots. Cela fonctionnera pour des 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'espace 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, décodez les phrases en points de code de caractères et trouvez l'identificateur de 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]]>

Utilisez les identificateurs de script pour déterminer où les limites des mots doivent être ajoutées. Ajoutez une limite de mot au début de chaque phrase et pour chaque caractère dont l'écriture 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)

Vous pouvez ensuite utiliser ces décalages de début pour construire une 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]]>

Pour finir, le segment le mot codepoints RaggedTensor retour en phrases et encode en chaînes de caractères UTF-8 pour une meilleure lisibilité.

# 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.strings.unicode_encode(sentence_word_char_codepoint, 'UTF-8').to_list()
<tf.RaggedTensor [[[72, 101, 108, 108, 111], [44, 32], [119, 111, 114, 108, 100], [46]], [[19990, 30028], [12371, 12435, 12395, 12385, 12399]]]>
[[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']]