Ciągi Unicode

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na GitHub Pobierz notatnik

Wstęp

Modele NLP często obsługują różne języki z różnymi zestawami znaków. Unicode to standardowy system kodowania, który jest używany do reprezentowania znaków z prawie wszystkich językach. Każdy znak Unicode jest kodowany za pomocą unikalnego całkowitą punkt kodu pomiędzy 0 i 0x10FFFF . Ciąg Unicode to sekwencja zero lub więcej punktów kodowych.

Ten samouczek pokazuje, jak reprezentować ciągi Unicode w TensorFlow i manipulować nimi za pomocą odpowiedników Unicode standardowych operacji ciągów. Rozdziela ciągi Unicode na tokeny na podstawie wykrywania skryptów.

import tensorflow as tf
import numpy as np

tf.string typ danych

Podstawowym TensorFlow tf.string dtype pozwala budować tensory ciągów bajtów. Ciągi są Unicode UTF-8 zakodowany domyślnie.

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

A tf.string traktuje tensor bajt sznurki jak jednostkach atomowych. Umożliwia to przechowywanie ciągów bajtów o różnej długości. Długość struny nie jest uwzględniona w wymiarach napinacza.

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

Jeśli używasz Python ciągów zbudować, uwaga, że literały łańcuchowe są kodowane Unicode domyślnie.

Reprezentowanie Unicode

Istnieją dwa standardowe sposoby reprezentowania ciągu Unicode w TensorFlow:

  • string skalarne - gdzie sekwencja punktów kodów jest kodowane przy użyciu znanego kodowania znaków .
  • int32 wektor - gdzie każde stanowisko zawiera pojedynczy punkt kodu.

Na przykład, następujące trzy wartości wszystkich reprezentacji ciąg Unicode "语言处理" (co oznacza „przetwarzanie języka” po chińsku):

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

Konwersja między reprezentacjami

TensorFlow udostępnia operacje do konwersji między tymi różnymi reprezentacjami:

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

Wymiary partii

Podczas dekodowania wielu ciągów liczba znaków w każdym ciągu może nie być równa. Wynik powrót jest tf.RaggedTensor , gdzie najbardziej wewnętrzna długość wymiar zależy od liczby znaków w każdej struny.

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

Można użyć tej tf.RaggedTensor bezpośrednio lub przekształcić go w gęstym tf.Tensor z wyściółką lub tf.SparseTensor wykorzystaniem metod tf.RaggedTensor.to_tensor i 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,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _]]

Przy kodowaniu wielu ciągów o tych samych długościach, należy użyć tf.Tensor jako wejście.

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

Przy kodowaniu wielu ciągów z różnej długości, użyj tf.RaggedTensor jako wejście.

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

Jeśli masz tensor z wielu ciągów w formacie wyściełane lub rozrzedzony, przekształcić go najpierw do tf.RaggedTensor przed wywołaniem 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)>

Operacje Unicode

Długość znaków

Użyj unit parametru tf.strings.length op aby wskazać, jak długość postać powinna być obliczona. unit domyślnie "BYTE" , ale mogą być ustawione dla innych wartości, takich jak "UTF8_CHAR" lub "UTF16_CHAR" , w celu określenia liczby codepoints unikodowymi każdego zakodowanego ciągu.

# 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

Podciągi znaków

tf.strings.substr op akceptuje unit parametr, i używa go do określenia, jakiego rodzaju kompensuje pos i len parametrach, zawierają.

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

Podziel ciągi Unicode

tf.strings.unicode_split operacja dzieli unicode ciągi znaków na podciągi poszczególnych znaków.

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)

Przesunięcia bajtów dla znaków

Aby wyrównać tensor znaków generowanych przez tf.strings.unicode_decode z oryginalnego łańcucha, to warto wiedzieć, gdzie offset dla zaczyna każda postać. Sposób ten tf.strings.unicode_decode_with_offsets jest podobna do unicode_decode , z wyjątkiem tego, że zwraca drugi tensor zawierający początek przesunięcia każdego znaku.

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

Skrypty Unicode

Każdy punkt kodowy Unicode należący do jednej kolekcji codepoints znanych postaci skryptu . Pismo postaci jest pomocne w ustaleniu, w jakim języku może ona być. Na przykład wiedza, że ​​„Б” jest zapisane cyrylicą, wskazuje, że współczesny tekst zawierający ten znak prawdopodobnie pochodzi z języka słowiańskiego, takiego jak rosyjski lub ukraiński.

TensorFlow zapewnia tf.strings.unicode_script operację celu określenia, które scenariusz danego zastosowania punkt kodowy. Kody skryptów są int32 wartości odpowiadające Międzynarodowym Komponentów do Unicode (ICU) UScriptCode wartości.

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

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

tf.strings.unicode_script operacja może być również stosowany do wielowymiarowego tf.Tensor S lub tf.RaggedTensor s 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]]>

Przykład: Prosta segmentacja

Segmentacja to zadanie polegające na podzieleniu tekstu na jednostki podobne do wyrazów. Jest to często łatwe, gdy do oddzielania słów używa się spacji, ale niektóre języki (np. chiński i japoński) nie używają spacji, a niektóre języki (np. niemiecki) zawierają długie związki złożone, które należy rozdzielić, aby przeanalizować ich znaczenie. W tekście internetowym różne języki i pismo często są ze sobą mieszane, jak w przypadku „NY株価” (New York Stock Exchange).

Możemy przeprowadzić bardzo zgrubną segmentację (bez implementowania jakichkolwiek modeli ML), używając zmian w skrypcie w celu przybliżenia granic słów. To zadziała w przypadku ciągów, takich jak w powyższym przykładzie „NY株価”. Będzie również działać w większości języków używających spacji, ponieważ znaki spacji w różnych skryptach są klasyfikowane jako USCRIPT_COMMON, specjalny kod skryptu, który różni się od każdego rzeczywistego tekstu.

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

Najpierw zdekoduj zdania na punkty kodowe znaków i znajdź identyfikator skryptu dla każdego znaku.

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

Użyj identyfikatorów skryptu, aby określić, gdzie należy dodać granice słów. Dodaj granicę słowa na początku każdego zdania i dla każdego znaku, którego pismo różni się od poprzedniego znaku.

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

Następnie można korzystać z tych przesunięć zacząć budować RaggedTensor zawierający listę słów ze wszystkich partii.

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

Do wykończenia segment słowo codepoints RaggedTensor powrotem do zdań i zakodować do UTF-8 ciągi dla czytelności.

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