TensorFlow 之中的 Unicode 数据格式的处理
在我们之前的数据处理过程之中,我们都是采用的 ASCII 码或者其他编码处理数据格式的,但是这些编码并不能够完全表示当前所有语言的所有字符,比如我们就无法使用 ASCII 码来表示汉语。因此这个时候我们就需要用到一种新的编码方式来进行字符的处理,于是这节课我们来学习如何在 TensorFlow 之中处理 Unicode 格式的数据。
1. 在 TensorFlow之中创建 Unicode 字符串以及张量
在 TensorFlow 之中,Unicode是存储在 tf.string 数据类型之中的,而在默认的情况之下,Unicode在 TensorFlow 之中的默认的编码格式是 UTF-8 编码的,我们可以通过以下示例查看具体的细节:
ch_string = u"你好呀!"
en_string = u"Hello"
ch_string_utf_8 = tf.constant(ch_string)
en_string_utf_8 = tf.constant(en_string)
print(ch_string_utf_8, en_string_utf_8, sep='\n')
在这段代码之中,我们着重进行了以下的操作:
- 在字符串前加上了 u,从而指示该字符串为 Unicode 格式;
- 我们使用 tf.constant 函数来将字符串转化为 Tensor 张量。
我们可以得到如下输出:
tf.Tensor(b'\xe4\xbd\xa0\xe5\xa5\xbd\xe5\x91\x80\xef\xbc\x81', shape=(), dtype=string)
tf.Tensor(b'Hello', shape=(), dtype=string)
我们可以发现以下几点:
- 这两个 Tensor 的数据类型都为 string ,这其实是 TensorFlow 内部的 tf.string 数据类型;
- 这两 个Tensor 的 Shape 都为空,因为在 TensoFlow 之中不会为 Unicode 字符串赋予形状,这是因为字符串的长度不尽相同;
- 第一个中文的字符串被按照 UTF-8 规则进行了编码,而英文并没有进行编码(严格来说,英文也进行了编码,只是编码前后相同,这一点可以由字符串前面的b就可以看出)。
2. TensorFlow 之中 Unicode 字符串的存在形式
在TensorFlow之 中, Unicode 字符串有两种表现形式,它们分别是:
- 编码格式:使用编码规则进行编码后的字符串,比如 UTF-8、UTF-16 等编码方式;
- 解码格式:对于每一个字符按照唯一的整数进行编码之后的格式,这些整数被称作“代码点”。
在第一小节之中我们看到的形式就是编码格式,而且编码方式为 UTF-8,对于两种格式,我们可以通过 tf.strings.unicode_decode 以及 tf.strings.unicode_encode 进行相应的转化,比如以下示例:
ch_string_utf_8_decode = tf.strings.unicode_decode(ch_string_utf_8, input_encoding='UTF-8')
ch_string_utf_8_encode = tf.strings.unicode_encode(ch_string_utf_8_decode, output_encoding='UTF-8')
print(ch_string_utf_8_decode)
print(ch_string_utf_8_encode)
在这 tf.strings.unicode_decode 函数之中,包含两个参数:
- 第一个参数就是我们要进行解码的字符串,比如我们的 ch_string_utf_8 ;
- 第二个参数是输入字符串的编码格式,因为我们的字符串编码格式为 UTF-8 ,因此在这里我们的参数为input_encoding=‘UTF-8’。
tf.strings.unicode_encode 函数与 tf.strings.unicode_decode 函数相似,只是第二个参数是输出字符串的编码方式,因为我们需要 UTF-8 编码的格式,因此这里我们选择 output_encoding=‘UTF-8’。
我们可以得到输出:
tf.Tensor([20320 22909 21568 65281], shape=(4,), dtype=int32)
tf.Tensor(b'\xe4\xbd\xa0\xe5\xa5\xbd\xe5\x91\x80\xef\xbc\x81', shape=(), dtype=string)
我们发现解码后的字符串就是一串整数数组,其中的每个整数代表着一个中文字符;于此同时,更重要的是解码产生的数组是拥有形状的,而正因如此,解码后的表示更加适合我们用作数据集。
同时我们也可以发现 ch_string_utf_8_encode 与 ch_string_utf_8 两个完全一样,因为 ch_string_utf_8 本来就是编码的字符串嘛。
3. 单个 Unicode 字符串的处理
无论 Unicode 格式怎么编码,Unicode 字符串终归是字符串,因此在实际应用之中就会进行各种的字符串操作,因此我们有必要来学习一下在 TensorFlow 之中的 Unicode 字符串的基本处理操作。
3.1 如何获取 Unicode 字符串的长度
我们可以使用 tf.strings.length 函数来获取 Unicode 字符串的长度,该函数含有两个重要的参数:
- str,要获取长度的字符串;
- unit,长度的单位,目前包含两个选项,一个是“BYTE”,另一个是“UTF8_CHAR”:
- BYTE,按照字节进行计数,从而获取字符串的长度;
- UTF8_CHAR,按照单个 Unicode 字符的单位进行计数,获取我们通常认知的长度。
同时该 API 返回的是一个 Tensor ,我们可以通过 numpy() 函数来将其转化为我们可以直接使用的数字长度。
比如以下代码:
len_bytes = tf.strings.length(ch_string_utf_8, unit='BYTE')
len_chars = tf.strings.length(ch_string_utf_8, unit='UTF8_CHAR')
print(len_bytes, len_chars)
print(len_bytes.numpy(), len_chars.numpy())
我们可以得到如下输出:
tf.Tensor(12, shape=(), dtype=int32) tf.Tensor(4, shape=(), dtype=int32)
12 4
可以看到,“你好呀!”字符串含有 12 个字节长度,而且正如我们看到的那样,包含 4 个汉字字符。
3.2 子字符串的操作
对于 Unicode 子字符串的操作,我们可以通过 tf.strings.substr 函数来实现,该 API 接收 4 个参数,它们分别是:
- str,要进行子字符串操作的 Unicode 字符串;
- unit,与前面的 unit 一样,表示截取的单位,包含“BYTE”以及“UTF8_CHAR”两个选项;
- pos,开始截取的位置;
- len,截取的长度。
我们可以通过以下示例进行查看:
print(tf.strings.substr(ch_string_utf_8, pos=3, len=1, unit='BYTE'))
print(tf.strings.substr(ch_string_utf_8, pos=3, len=1, unit='UTF8_CHAR'))
我们可以得到如下输出:
tf.Tensor(b'\xe5', shape=(), dtype=string)
tf.Tensor(b'\xef\xbc\x81', shape=(), dtype=string)
我们可以发现,b’\xe5 刚刚好是 3 位置的字符串,而 b’\xef\xbc\x81’ 刚刚好是最后一个“!”的 Unicode 表示。
3.3 字符串的拆分
通过拆分操作,我们可以将每个Unicode字符进行拆分,从而形成一个数组,每个数组包含一个 Unicode 字符的编码。
对于该操作,我们可以通过 tf.strings.unicode_split 函数实现,该函数的具体使用如下:
print(tf.strings.unicode_split(ch_string_utf_8, 'UTF-8'))
其中的第二个参数表示的是字符串的编码方式,我们可以得到如下输出:
tf.Tensor([b'\xe4\xbd\xa0' b'\xe5\xa5\xbd' b'\xe5\x91\x80' b'\xef\xbc\x81'], shape=(4,), dtype=string)
我们看到,我们的字符串已经成功进行了拆分的基本操作。
4. 使用 Unicode 数据构造数据集的示例
在实际的使用之中,我们大致分为以下几步来构造 Unicode 字符串的数据集:
- 首先将 Unicode 字符串数据进行解码,因为这样就可以计算长度;
- 将其统一为定长的形式;
- 构造数据集
对于解码,我们可以通过之前的 tf.strings.unicode_decode 函数进行解码,我们可以通过下面的示例查看解码的结果:
data_string = [u"你好呀", u"很高兴认识你", u"Hello", u"Nice to meet you"]
decode_data = tf.strings.unicode_decode(data_string, input_encoding='UTF-8')
print(decode_data, decode_data.shape, sep='\n')
我们可以得到的输出为:
<tf.RaggedTensor [[20320, 22909, 21568], [24456, 39640, 20852, 35748, 35782, 20320], [72, 101, 108, 108, 111], [78, 105, 99, 101, 32, 116, 111, 32, 109, 101, 101, 116, 32, 121, 111, 117]]>
(4, None)
可以发现,我们得到的数据为 tf.RaggedTensor 格式,而这种格式的每个元素都不是定长的,而这就到十六我们的数据的 shape 只能为(4, None),因此我们可以通过to_tensor()函数来将其转化为定长的张量。
decode_data_pad = decode_data.to_tensor()
print(decode_data_pad, decode_data_pad.shape, sep='\n')
我们可以得到如下结果:
tf.Tensor(
[[20320 22909 21568 0 0 0 0 0 0 0 0 0
0 0 0 0]
[24456 39640 20852 35748 35782 20320 0 0 0 0 0 0
0 0 0 0]
[ 72 101 108 108 111 0 0 0 0 0 0 0
0 0 0 0]
[ 78 105 99 101 32 116 111 32 109 101 101 116
32 121 111 117]], shape=(4, 16), dtype=int32)
(4, 16)
由此我们可以发现,我们的数据已经 Padding 到了统一的长度,而这个长度是根据最长的字符串的长度来决定的。这样之后,我们便可以进一步构造数据集,我们将会采用定长与不定长的数据分别构造数据集,来查看两者的区别。
在这里我们可以使用虚拟的标签进行操作, 我们依然使用传统的 tf.data.Dataset.from_tensor_slices 函数来进行数据集的构建:
labels = [0, 0, 0, 0]
dataset = tf.data.Dataset.from_tensor_slices((decode_data, labels))
dataset_pad = tf.data.Dataset.from_tensor_slices((decode_data_pad, labels))
print(dataset)
print(dataset_pad)
我们可以得到结果:
<TensorSliceDataset shapes: ((None,), ()), types: (tf.int32, tf.int32)>
<TensorSliceDataset shapes: ((16,), ()), types: (tf.int32, tf.int32)>
我们可以看到,没有采用 Padding 的数据集的形状为 ((None,), ()) ,而采用了 Padding 数据集的形状为((16,), ()),而后者是会对我们的使用有利的,因此我们推荐使用后者进行操作。
5. 小结
在这节课之中,我们学习了如何在 TensorFlow 之中使用 Unicode 字符串,我们同时学习了 Unicode 字符串的两种存在形式,又了解了 Unicode 字符串的基本操作,最后我们通过一个简单的示例了解了如何使用 Unicode 字符串构造数据集。