Строки Unicode

Посмотреть на TensorFlow.org Запускаем в Google Colab Посмотреть исходный код на GitHub Скачать блокнот

Введение

Модели НЛП часто работают с разными языками с разными наборами символов. Unicode является стандартной системой кодирования, которая используется для представления символов из почти всех языков. Каждый символ Юникода кодируются с использованием уникальных целочисленных точек коды между 0 и 0x10FFFF . Строка Юникода представляет собой последовательность из нуля или более кодовых точек.

В этом руководстве показано, как представлять строки Unicode в TensorFlow и управлять ими с помощью эквивалентов Unicode стандартных операций со строками. Он разделяет строки Unicode на токены на основе обнаружения сценария.

import tensorflow as tf
import numpy as np

tf.string тип данных

Основная TensorFlow tf.string dtype позволяет строить тензоры байтовых строк. Unicode строки UTF-8 кодируются по умолчанию.

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

A tf.string тензор обрабатывает байты строки в качестве атомных единиц. Это позволяет хранить байтовые строки различной длины. Длина строки не входит в тензорные измерения.

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

Если вы используете Python для построения строк, обратите внимание , что строковые литералы являются Unicode кодировки по умолчанию.

Представление Unicode

Существует два стандартных способа представления строки Unicode в TensorFlow:

  • string скалярной - где последовательность кодовых точек кодируются с использованием известной кодировки символов .
  • int32 вектора - , где каждая позиция содержит единственную точку коды.

Например, следующие три значения представляют все строки Unicode "语言处理" (что означает «обработка язык» по - китайски):

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

Преобразование между представлениями

TensorFlow предоставляет операции для преобразования между этими различными представлениями:

  • tf.strings.unicode_decode : преобразует закодированные строки скалярного вектора кодовых точек.
  • tf.strings.unicode_encode : преобразует вектор кодовых точек к кодированной строке скаляру.
  • tf.strings.unicode_transcode : преобразует закодированные строки скалярной в другую кодировку.
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'>

Размер партии

При декодировании нескольких строк количество символов в каждой строке может быть не равным. Результат является возвращение tf.RaggedTensor , где внутреннего размером длина изменяется в зависимости от количества символов в каждой строке.

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

Вы можете использовать этот tf.RaggedTensor непосредственно, или преобразовать его в плотную tf.Tensor с прокладкой или tf.SparseTensor с использованием методов tf.RaggedTensor.to_tensor и 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,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _,      _]]

При кодировании нескольких строк с одинаковыми длинами, использовать tf.Tensor в качестве входных данных.

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

При кодировании нескольких строк с разной длиной, использует tf.RaggedTensor в качестве входных данных.

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

Если у вас есть тензор с несколькими строками в мягком или разреженном формате, преобразовать его первым в tf.RaggedTensor перед вызовом 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)>

Юникод операции

Длина символа

С помощью unit параметр tf.strings.length оп , чтобы указать , как длина символов должно быть вычислено. unit по умолчанию "BYTE" , но он может быть установлен в другие значения, такие как "UTF8_CHAR" или "UTF16_CHAR" , чтобы определить число кодовых Unicode в каждой кодированной строки.

# 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

Подстроки символов

tf.strings.substr оп принимает unit параметров, и использует его , чтобы определить , какой тип коррекций pos и len параметры при содержат.

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

Разделить строки Unicode

tf.strings.unicode_split операция разбивает юникод строки на подстроки отдельных символов.

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)

Смещения байтов для символов

Для выравнивания тензорного характера , порожденная tf.strings.unicode_decode с исходной строкой, это полезно знать смещение , где начинается каждый символ. Метод tf.strings.unicode_decode_with_offsets похож на unicode_decode , за исключением того, что она возвращает второй тензор , содержащий начальное смещение каждого символа.

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

Скрипты Unicode

Каждая точка коды Unicode принадлежит к одной коллекции , известные как кодовые в сценарии . Сценарий персонажа помогает определить, на каком языке может быть персонаж. Например, знание того, что буква «Б» написана кириллицей, указывает на то, что современный текст, содержащий этот символ, скорее всего, из славянского языка, такого как русский или украинский.

TensorFlow обеспечивает tf.strings.unicode_script операцию , чтобы определить , какой скрипт данной элемент кода использования. Коды сценария являются int32 значения , соответствующее Международных компоненты для Unicode (СИС) UScriptCode значений.

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

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

tf.strings.unicode_script операция также может быть применен к многомерным tf.Tensor с или 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]]>

Пример: простая сегментация

Сегментация - это задача разбиения текста на словоподобные блоки. Часто это легко сделать, когда для разделения слов используются символы пробела, но в некоторых языках (например, в китайском и японском) пробелы не используются, а в некоторых языках (например, в немецком) содержатся длинные составные части, которые необходимо разделить, чтобы проанализировать их значение. В веб-тексте разные языки и сценарии часто смешиваются вместе, как в «NY 株 価» (Нью-Йоркская фондовая биржа).

Мы можем выполнить очень грубую сегментацию (без реализации каких-либо моделей машинного обучения), используя изменения в скрипте для приблизительного определения границ слова. Это будет работать для строк, подобных приведенному выше примеру «NY 株 価». Он также будет работать для большинства языков, в которых используются пробелы, поскольку все символы пробелов в различных сценариях классифицируются как USCRIPT_COMMON, специальный код сценария, который отличается от кода любого реального текста.

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

Сначала расшифруйте предложения в кодовые точки символов и найдите идентификатор сценария для каждого символа.

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

Используйте идентификаторы скрипта, чтобы определить, где следует добавить границы слов. Добавьте границу слова в начале каждого предложения и для каждого символа, чей сценарий отличается от предыдущего символа.

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

Затем вы можете использовать эти начальные смещения построить RaggedTensor , содержащий список слов из всех партий.

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

Для завершения сегмента слово кодовых RaggedTensor обратно на предложения и кодирование в UTF-8 строк для удобства чтения.

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