In [ ]:
#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
|
|
|
Note: Вся информация в этом разделе переведена с помощью русскоговорящего Tensorflow сообщества на общественных началах. Поскольку этот перевод не является официальным, мы не гарантируем что он на 100% аккуратен и соответствует официальной документации на английском языке. Если у вас есть предложение как исправить этот перевод, мы будем очень рады увидеть pull request в tensorflow/docs репозиторий GitHub. Если вы хотите помочь сделать документацию по Tensorflow лучше (сделать сам перевод или проверить перевод подготовленный кем-то другим), напишите нам на docs-ru@tensorflow.org list.
Модели обрабатывающие естественные языки, часто имеют дело с разными языками и разными наборами символов. Unicode - это стандартная система кодирования, которая используется для представления символов практически всех языков. Каждый символ кодируется с использованием уникального целого числа кодовой точки между 0
и0x10FFFF
. Юникод-строка - это последовательность из нуля или более таких кодовых точек.
Это руководство показывает как представлять юникод-строки в Tensorflow и манипулировать ими используя юникодовские эквиваленты стандартной строковой. Она выделяет юникод-строки в токены на основе обнаружения скрипта.
In [ ]:
import tensorflow as tf
In [ ]:
tf.constant(u"Спасибо 😊")
Тензор tf.string
может содержать байт-строки различной длины поскольку байт-строки обрабатываются как отдельные единицы. Длина строки не включена в размерность тензора.
In [ ]:
tf.constant([u"Добро", u"пожаловать!"]).shape
Замечание: При использовании python при конструировании строк обработка юникода отличается между v2 и v3. В v2, юникод-строки отмечены префиксом "u", как и выше. В v3, строки закодированы в юникоде по умолчанию.
Есть два стандартных способа представления юникод-строк в TensorFlow:
string
скаляр — где последовательность кодовых точек закодирована с использованием набора символов.int32
вектор — где каждая позиция содержит единственную кодовую точку.Например, следующие три значения все представляют юникод-строку "语言处理"
(что значит "обработка языка" на китайском):
In [ ]:
# Юникод-строки, представленные как UTF-8 закодированные строки скаляры.
text_utf8 = tf.constant(u"语言处理")
text_utf8
In [ ]:
# Юникод-строки представленные как UTF-16-BE закодированные строки скаляры.
text_utf16be = tf.constant(u"语言处理".encode("UTF-16-BE"))
text_utf16be
In [ ]:
# Юникод строки представленные как векторы юникодовских кодовых точек.
text_chars = tf.constant([ord(char) for char in u"语言处理"])
text_chars
TensorFlow предоставляет операции для конвертации между этими различными представлениями:
tf.strings.unicode_decode
: Конвертирует закодированную строку скаляр в вектор кодовых точек.tf.strings.unicode_encode
: Конвертирует вектор кодовых точек в закодированную строку скаляр.tf.strings.unicode_transcode
: Конвертирует строку скаляр в другую кодировку.
In [ ]:
tf.strings.unicode_decode(text_utf8,
input_encoding='UTF-8')
In [ ]:
tf.strings.unicode_encode(text_chars,
output_encoding='UTF-8')
In [ ]:
tf.strings.unicode_transcode(text_utf8,
input_encoding='UTF8',
output_encoding='UTF-16-BE')
При декодировании нескольких строк размер символов в каждой строке может не совпадать, Возвращаемый результат это tf.RaggedTensor
, где длина самого внутреннего измерения меняется в зависимости от количества символов в каждой строке:
In [ ]:
# Пакет юнокод-строк каждая из которых представлена в виде строки в юникод-кодировке.
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)
Вы можете использовать tf.RaggedTensor
напрямую, или конвертировать его в плотный tf.Tensor
с паддингом или в tf.SparseTensor
используя методы tf.RaggedTensor.to_tensor
и tf.RaggedTensor.to_sparse
.
In [ ]:
batch_chars_padded = batch_chars_ragged.to_tensor(default_value=-1)
print(batch_chars_padded.numpy())
In [ ]:
batch_chars_sparse = batch_chars_ragged.to_sparse()
При кодировании нескольких строк одинаковой длины tf.Tensor
может быть использован в качестве входных данных:
In [ ]:
tf.strings.unicode_encode([[99, 97, 116], [100, 111, 103], [ 99, 111, 119]],
output_encoding='UTF-8')
При кодировании нескольких строк различной длины нужно использовать tf.RaggedTensor
в качестве входных данных:
In [ ]:
tf.strings.unicode_encode(batch_chars_ragged, output_encoding='UTF-8')
Если у вас тензор с несколькими строками с паддингом или в разреженном формате, то ковертируйте его в tf.RaggedTensor
перед вызовом unicode_encode
:
In [ ]:
tf.strings.unicode_encode(
tf.RaggedTensor.from_sparse(batch_chars_sparse),
output_encoding='UTF-8')
In [ ]:
tf.strings.unicode_encode(
tf.RaggedTensor.from_tensor(batch_chars_padded, padding=-1),
output_encoding='UTF-8')
Операция tf.strings.length
имеет параметр unit
, который показывает как должна быть посчитана длина. По умолчанию размер unit
равен "BYTE"
, но он может быть установлен с другим значением, таким как "UTF8_CHAR"
или "UTF16_CHAR"
, чтобы определить число кодовых точек в каждой закодированой string
.
In [ ]:
# Заметьте что последний символ занимает до 4 байтов в 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))
In [ ]:
# по умолчанию: unit='BYTE'. С len=1, мы возвращаем один байт.
tf.strings.substr(thanks, pos=7, len=1).numpy()
In [ ]:
# Установив unit='UTF8_CHAR', мы возвратим один символ, размер которого в этом случае
# 4 байта.
print(tf.strings.substr(thanks, pos=7, len=1, unit='UTF8_CHAR').numpy())
In [ ]:
tf.strings.unicode_split(thanks, 'UTF-8').numpy()
Чтобы выровнять тензор символа порожденный tf.strings.unicode_decode
с оригинальной строкой, полезно знать смещение начала каждого символа. Метод tf.strings.unicode_decode_with_offsets
аналогичен unicode_decode
, за исключением того, что он возвращает второй тензор содержащий размер отступа от начала для каждого символа.
In [ ]:
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))
Каждая кодовая точка принадлежит коллекции символов известной как система письма . Система письма полезна для определения того, какому языку может принадлежать символ. Например, зная что 'Б' из кириллицы указывает на то, что современный текст содержащий этот символ скорее всего из славянского языка, такого как русский или украинский.
В TensorFlow есть операция tf.strings.unicode_script
для определения какой системе письма принадлежит данная кодовая точка. Коды систем письма это int32
числа соответствующие Международным компонентам для
юникода (ICU) UScriptCode
значения.
In [ ]:
uscript = tf.strings.unicode_script([33464, 1041]) # ['芸', 'Б']
print(uscript.numpy()) # [17, 8] == [USCRIPT_HAN, USCRIPT_CYRILLIC]
Операция tf.strings.unicode_script
может быть также применена к многомерному tf.Tensor
s или кодовым точкам tf.RaggedTensor
:
In [ ]:
print(tf.strings.unicode_script(batch_chars_ragged))
Сегментация это задача разбиения текста на словоподобные юниты. Часто это легко когда испольуются символы пробела для отделения слов, но некоторые языки (например китайский и японский) не используют пробелы, а некоторые языки (например немецкий) содержат длинные соединения, которые должны быть разделены для анализа их значений. В веб текстах, различные языки и скрипты часто перемешаны между собой , как например в "NY株価" (New York Stock Exchange).
Мы можем выполнить грубую сегментацию (без реализации каких-либо моделей ML), используя изменения систем письма для приблизительного определения границ слов. Это будет работать для строк наподобие вышеприведенного примера "NY株価". Это также будет работать для всех языков, которые используют пробелы, так как символы пробела в различных системах письма все классифицируются как USCRIPT_COMMON, специальный код, который отличается от кода любого актуального текста.
In [ ]:
# dtype: string; shape: [num_sentences]
#
# Предложения для обработки. Поменяйте эту строку чтобы попробовать разные входные данные!
sentence_texts = [u'Hello, world.', u'世界こんにちは']
Сперва мы декодируем предложения в кодовые точки, и определим идентификатор системы письма для каждого символа.
In [ ]:
# dtype: int32; shape: [num_sentences, (num_chars_per_sentence)]
#
# sentence_char_codepoint[i, j] кусок кода для j-го символа
# в i-м предложении.
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] код системы письма для j-го символа в
# i-м предложении.
sentence_char_script = tf.strings.unicode_script(sentence_char_codepoint)
print(sentence_char_script)
Далее мы используем эти идентификаторы систем письма чтобы определить куда должны быть добавлены границы слов. Мы добавим границу слова в начало каждого предложения и для каждого символа чья система письма отличается от предыдущего символа:
In [ ]:
# dtype: bool; shape: [num_sentences, (num_chars_per_sentence)]
#
# sentence_char_starts_word[i, j] является True если j'th символ в i'th
# предложении является началом слова.
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] это индекс символа начинающего i-е слово (в
# выпрямленном списке символов всех предложений).
word_starts = tf.squeeze(tf.where(sentence_char_starts_word.values), axis=1)
print(word_starts)
Затем мы можем использовать эти сдвиги от начала для построения RaggedTensor
содержащего список слов из всех пакетов:
In [ ]:
# dtype: int32; shape: [num_words, (num_chars_per_word)]
#
# word_char_codepoint[i, j] is the кодовая точка для j-го символа в
# i-м слове.
word_char_codepoint = tf.RaggedTensor.from_row_starts(
values=sentence_char_codepoint.values,
row_starts=word_starts)
print(word_char_codepoint)
И наконец, мы можем сегментировать кодовые точки слов RaggedTensor
обратно в предложения:
In [ ]:
# dtype: int64; shape: [num_sentences]
#
# sentence_num_words[i] число слов в i'th предложении.
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] это кодовая точка для k-го символа
# в j-м слове i-го предложения.
sentence_word_char_codepoint = tf.RaggedTensor.from_row_lengths(
values=word_char_codepoint,
row_lengths=sentence_num_words)
print(sentence_word_char_codepoint)
Чтобы сделать итоговый результат проще для чтения, мы можем закодировать его обратно в UTF-8 строки:
In [ ]:
tf.strings.unicode_encode(sentence_word_char_codepoint, 'UTF-8').to_list()