![]() |
![]() |
![]() |
![]() |
はじめに
自然言語モデルは、しばしば異なる文字セットを使った異なる言語を扱います。 Unicodeは、ほぼすべての言語で文字表示に使われている標準的なエンコードの仕組みです。各文字は、0
から0x10FFFF
までの一意の整数の コードポイント(符号位置) を使ってエンコードされます。1つの Unicode文字列は、ゼロ個以上のコードポイントのシーケンスです。
このチュートリアルでは、TensorFlow での Unicode文字列の表現方法と、どうやって Unicode で標準的な文字列操作と同様の操作を行うかについて示します。また、スクリプト検出にもとづいて Unicode 文字列をトークンに分解します。
import tensorflow as tf
tf.string
データ型
標準的な TensorFlow のtf.string
型は、バイト列のテンソルを作ります。また、Unicode文字列はデフォルトでは utf-8 でエンコードされます。
tf.constant(u"Thanks 😊")
<tf.Tensor: shape=(), dtype=string, numpy=b'Thanks \xf0\x9f\x98\x8a'>
バイト列が最小限の単位として扱われるため、tf.string
型のテンソルは可変長のバイト文字列を保持できます。また、文字列長はテンソルの次元には含まれません。
tf.constant([u"You're", u"welcome!"]).shape
TensorShape([2])
注 : Pythonを使って文字列を構成するとき、v2.x系とv3.x系では Unicode の扱いが異なります。v2.x系では、Unicode文字列は上記のようにプレフィックス "u" で明示します。v3.x系では、デフォルトで Unicode としてエンコードされます。
Unicode 表現
TensorFlow での Unicode文字列表現は、2つの標準的な方法があります:
string
スカラー — コードポイントのシーケンスは既知の 文字符合化方式 でエンコードされるint32
ベクトル — 各文字には単一のコードポイントが入る
たとえば、以下3つはすべて Unicode文字列 "语言処理 "
(中国語で「言語処理」を意味します)を表します。
# Unicode文字列。UTF-8にエンコードされた文字列スカラーとして表される
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文字列。UTF-16-BEにエンコードされた文字列スカラーとして表される
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文字列。Unicodeコードポイントのベクトルとして表される
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)>
Unicode 表現間の変換
TensorFlowでは、これらの異なる Unicode 表現間で変換する方法を用意しています。
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
であり、最も内側の次元の長さは各文字列の文字数によって異なります。:
# Unicode文字列のバッチ。それぞれが、UTF8にエンコードされた文字列として表される
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.RaggedTensor.to_tensor
メソッドを使ってパディングを追加した密な tf.Tensor
に変換するか、あるいは tf.RaggedTensor.to_sparse
メソッドを使って tf.SparseTensor
に変換することもできます。
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()
同じ長さの複数の文字列をエンコードする場合、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)>
パディングされた、あるいはスパースな複数の文字列を含むテンソルがある場合は、unicode_encode
を呼び出す前に tf.RaggedTensor
に変換します。
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)>
Unicode 操作
文字列長
tf.strings.length
は、文字列長をどう計算するかを示す unit
パラメーターが使えます。unit
のデフォルトは "BYTE"
ですが、"UTF8_CHAR"
や "UTF16_CHAR"
など他の値に設定して、エンコードされた string
文字列のUnicodeコードポイントの数を決めることができます。
# 最後の絵文字は、UTF8で4バイトを占めることに注意する
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
" パラメーターを指定することで、オフセットの種類を決めることができます。
# デフォルト: unit='BYTE'. len=1 の場合、1バイトを返す
tf.strings.substr(thanks, pos=7, len=1).numpy()
b'\xf0'
# unit = 'UTF8_CHAR' を指定すると、単一の文字(この場合は4バイト)が返される
print(tf.strings.substr(thanks, pos=7, len=1, unit='UTF8_CHAR').numpy())
b'\xf0\x9f\x98\x8a'
Unicode文字列を分割する
tf.strings.unicode_split
は、Unicode文字列を個々の文字に分割します。
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
に似ていますが、各文字の開始オフセットを含む2番目のテンソルを返す点が異なります。
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
を提供しています。戻り値のスクリプトコードは、International Components for Unicode (ICU) の UScriptCode
に対応する int32
値になります。
uscript = tf.strings.unicode_script([33464, 1041]) # ['芸', 'Б']
print(uscript.numpy()) # [17, 8] == [USCRIPT_HAN, USCRIPT_CYRILLIC]
[17 8]
tf.strings.unicode_script
は、多次元のコードポイントの tf.Tensors
や 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]]>
例:シンプルなセグメンテーション
セグメンテーションは、テキストを単語のような粒度に分割するタスクです。これは、スペース文字を使用して単語を区切れる場合には簡単に行えますが、一部の言語(中国語や日本語など)はスペースを使いませんし、また、一部の言語(ドイツ語など)には、意味を解析するために分ける必要がある、単語を結合した長い複合語があります。Webテキストでは、「NY株価」(ニューヨーク株価)のように、異なる言語とスクリプトがしばしば混在しています。
単語の境界を推定してスクリプトを変更することにより、(MLモデルを実装せずに)非常に大まかなセグメンテーションを実行できます。これは、上記の「NY株価」の例のような文字列に対して機能します。さまざまな言語のスペース文字はすべて、実際のテキストとは異なる特別なスクリプトコードである USCRIPT_COMMON として分類されるため、スペースを使用するほとんどの言語でも機能します。
# dtype: string; shape: [num_sentences]
#
# 処理する文章。この行を編集して、さまざまな入力を試してみてください!
sentence_texts = [u'Hello, world.', u'世界こんにちは']
最初に、文章を文字ごとのコードポイントにデコードし、それから各文字のスクリプトコード(識別子)を調べます。
# dtype: int32; shape: [num_sentences, (num_chars_per_sentence)]
#
# sentence_char_codepoint[i, j] は、i番目の文のn番目の文字のコードポイント
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] は、i番目の文のn番目の文字のスクリプトコード
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] は、i番目の文のn番目の文字が単語の始まりである場合にTrue
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)
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] は、i番目の単語のn番目の文字のコードポイント
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
をコードポイント単位でセグメント化して、文章に戻します。:
# dtype: int64; shape: [num_sentences]
#
# sentence_num_words[i] は、i番目の文の単語数
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] は、i番目の文のn番目の単語のk番目の文字のコードポイント
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]]]>
最終的な結果を見やすくするために、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']]