tensorflow2笔记:简单数据预处理(TF专属)

目录(注意本文jupyterlab编写)

    • 预先导入数据
    • 数据API
      • 数据样式
      • 乱序数据
        • shuffle乱序(小数据集)
        • 大数据集乱序
        • 训练测试和绘制图像
      • 小结
    • TFRecord格式
      • TFRecord的简单创建读取
      • TFRecord压缩和读取
      • 协议缓冲区(TensorFlow协议):
        • 序列化写入
        • 加载和解析Example
    • 预处理输入特征
      • 标准化
      • 连续数值离散化
        • int输出模式
        • 独热码输出模式
        • multi_hot输出模式
        • count(计数)输出模式
        • 不指定区域界线
      • 连续数值离散化小总结。
      • 字符编码
        • int输出模式
        • multi_hot输出模式
        • count输出模式
        • 使用嵌入编码(Embedding)
    • 自定义预处理层(one_hot和Embedding)
      • Input_onehot自定义层实现
      • Input_embedding自定义层实现
      • 使用tf.keras自带层实现包含embed的模型(1)
      • 使用tf.keras自带层实现包含embed的模型(2)
      • 使用自定义层实现包含embed的模型
      • 使用自定义层实现包含one_hot的模型

预先导入数据

from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
import numpy as np
import pandas as pd
import tensorflow as tf
housing=fetch_california_housing()
scaler=StandardScaler()
x_data=scaler.fit_transform(housing.data)
x_train_full,x_test,y_train_full,y_test=train_test_split(x_data,housing.target)
x_train,x_valid,y_train,y_valid=train_test_split(x_train_full,y_train_full)

数据API

  • from_tensor_slices将数据按照第一维分隔(一一对应)
  • from_tensor_slices后面,可以按照链式进行转换(每次都产生一个新数据集,如果要保存每次都要赋予一个新变量)
  • batch是将数据集分隔打包,drop_remainder是让去掉最后不完整的数据包(可以让TF函数少生成一个AutoGraph,加快运行速度)
  • map函数中要注意,前面from_tensor_slices的元组有几个参数,这里lambda就要几个参数(分别对应),此外这个函数要是能够转换为TF函数的函数。
  • num_parallel_calls是多线程设置,加快速度。
  • 注意:这里batch为了方便显示,设置了5,最好设置大一点的数如32,64,128等,如果GPU好的话可以再大一点。
  • 其他函数filter,过滤数据集,参数(函数)。take取出前几行。
train_db = tf.data.Dataset.from_tensor_slices((x_train,y_train)).batch(5,drop_remainder=True).map((lambda x,y:(x*2,y)),num_parallel_calls=5)

数据样式

可以看到由于上面传入(x_train,y_train),这里的item是一个元组。如果原本是特征和标签合并在一起的完整数据,也可以在map中,通过函数处理,分开为特征集合标签。

for item in train_db.take(3):
    print(item)
(, )
(, )
(, )

乱序数据

我们知道训练集中实例相互独立且分布均匀时,梯度下降效果最佳。

shuffle乱序(小数据集)

shuffle乱序,需要设置缓冲区大小(也可设置随机种子),适合可以装入RAM的小型数据集。
原理:先将缓冲区填满,如果要求提供一个数据,从缓冲区随机取出一个数据,并用源数据集的新元素替换它,直到遍历完源数据集,然后把缓冲区抽空为止。
注意:缓冲区必须足够大,不然乱序不太有效。当然也不要超出数据集大小。

dataset=tf.data.Dataset.range(10).repeat(3)
dataset

  • 如果batch设置drop_remainder=True则最后的Tensor去掉。
  • 如果在shuffle后面调用repeat,则默认情况下,每次重复都会生成一个新的次序(这是个好主意)。但是,如果你希望每次迭代都重用相同的顺序(例如调试或者测试),则在shuffle函数设置中reshuffle_each_iteration=True
dataset = dataset.shuffle(buffer_size=5,seed=42).batch(7)
for item in dataset:
    print(item)
tf.Tensor([0 2 3 6 7 9 4], shape=(7,), dtype=int64)
tf.Tensor([5 0 1 1 8 6 5], shape=(7,), dtype=int64)
tf.Tensor([4 8 7 1 2 3 0], shape=(7,), dtype=int64)
tf.Tensor([5 4 2 7 8 9 9], shape=(7,), dtype=int64)
tf.Tensor([3 6], shape=(2,), dtype=int64)

大数据集乱序

对于大数据集,缓冲区乱序可能并不够用。一种想法是将源数据进行乱序。或者为了进一步乱序,我们可以将数据集拆成几个的文件,然后再随机地读取它们。但是对于同一个文件的数据,仍然相互接近,为了避免这种情况,可以随机选择多个文件同时读取,交错它们的记录(元组)。当然,最后还可以再增加个shuffle

def split_tocsv(x,y,name,n_split=3,columns_name=None):
    import pandas as pd
    import numpy as np
    file_name_list=[]
    length_each_csv=x.shape[0]//n_split
    y = y.reshape(x.shape[0],-1)
    db=np.concatenate([x,y],axis=1)
    for i in range(0,x.shape[0],length_each_csv):
        left = i
        right = x.shape[0]  if (i+length_each_csv) > x.shape[0] else i+length_each_csv
        temp_db = pd.DataFrame(db[left:right],columns=columns_name)
        file_name = name + '_db_' + str(i)+'.csv'
        file_name_list.append(file_name)
        temp_db.to_csv(file_name,index=False)
    return file_name_list
train_filepaths=split_tocsv(x_train,y_train,name='train',columns_name=housing.feature_names+housing.target_names)
test_filepaths=split_tocsv(x_test,y_test,'test',columns_name=housing.feature_names+housing.target_names)
valid_filepaths=split_tocsv(x_valid,y_valid,'valid',columns_name=housing.feature_names+housing.target_names)
train_filepaths
['train_db_0.csv', 'train_db_3870.csv', 'train_db_7740.csv']

为了创造多文件的条件,我写了上述的函数。实际上如果是大数据集的话,源数据是多个文件的,就不像这边这样分开,也就不用这个函数。当然也可以手动分成多个文件。接下来的才是正文。
注意:要尽量让文件等长,同时一个文件既包括特征也包括标签。

#建立文件名数据集。乱序
filepath_dataset = tf.data.Dataset.list_files(train_filepaths,seed=42)
for item in filepath_dataset:
    print(item)
tf.Tensor(b'train_db_0.csv', shape=(), dtype=string)
tf.Tensor(b'train_db_7740.csv', shape=(), dtype=string)
tf.Tensor(b'train_db_3870.csv', shape=(), dtype=string)

这里用interleave交错方法对文件名数据集中每个文件名调用lambda函数(即新创建一个数据集,跳过第一行特征名),cycle_length=2表示2个新创建的数据集一起交错,block_length=6表示每个新创建的数据集连续6条记录就交错另一个新创建的数据集。最终返回一个交错记录(元组)的数据集。

dataset = filepath_dataset.interleave(lambda x:tf.data.TextLineDataset(x).skip(1),
                                      cycle_length=2,
                                      block_length=6,
                                      num_parallel_calls=2)
for item in dataset.take(3):
    print(item)
tf.Tensor(b'0.7496827131540564,0.34647802955744084,0.08607255372709229,-0.017023771445335806,-0.23884533660976928,-0.0945567753546111,-0.9512902346352202,0.8483747044943901,5.00001', shape=(), dtype=string)
tf.Tensor(b'-0.7274916753876317,-1.6399739348833928,-0.4242689446759151,0.15982009534455993,-0.6159090247996127,0.10718992328963842,-0.7406062783379425,0.6836615678839947,1.375', shape=(), dtype=string)
tf.Tensor(b'-1.2252872849959682,-0.36864467764125924,-0.993795128142578,0.16967353779210897,-0.1858621485737257,-0.12157173190923926,-0.717196949860465,0.6137832675038262,3.25', shape=(), dtype=string)

返回完交错记录的数据集,还没完,因为每个item都是tf.string的记录,需要一个解析函数

n_features = 8
def parseprocess(line):
    #一行包括8个特征和1个标签。这里设置默认值包括类型,标签没有默认值,如果数据没有则报错。
    defaults = [0.] * n_features + [tf.constant([],dtype=tf.float32)]
    fields = tf.io.decode_csv(line,record_defaults=defaults)
    x = tf.stack(fields[:-1])  #将标量张量列表(list)转换为一维张量数组(有shape属性)
    y = tf.stack(fields[-1:])
    return x,y

测试如下

for item in dataset.take(3):
    print(parseprocess(item))
(, )
(, )
(, )

将上述所有的操作合在一起,如下

def csv_reader_dataset(filepaths,repeat=1,n_interleaves=3,n_read_threads=None,shuffle_buffer_size=10000,n_map_threads=5,batch_size=32,prefetch=1):
    filepath_dataset = tf.data.Dataset.list_files(filepaths)
    dataset = filepath_dataset.interleave(lambda x:tf.data.TextLineDataset(x).skip(1),
                                          cycle_length=n_interleaves,
                                          num_parallel_calls=n_read_threads)
    dataset = dataset.map(parseprocess,num_parallel_calls=n_map_threads)
    dataset = dataset.shuffle(buffer_size=shuffle_buffer_size).repeat(repeat)
    return dataset.batch(batch_size).prefetch(prefetch)

上面的prefetch函数是实现预取,即该数据集尽可能地提前准备一个批次,当模型在训练一个批次时,数据集已经并行工作准备好下一批次。这样CPU和GPU可以并行的工作加快了训练速率。

train_db = csv_reader_dataset(train_filepaths)
test_db = csv_reader_dataset(test_filepaths)
valid_db = csv_reader_dataset(valid_filepaths)

此时,已经制成了三个数据集,每个数据集包括特征和标签。

训练测试和绘制图像

input_=tf.keras.layers.Input(shape=[8])
hidden1=tf.keras.layers.Dense(30,activation='elu',kernel_initializer='he_normal')(input_)
hidden2=tf.keras.layers.Dense(30,activation='elu',kernel_initializer='he_normal')(hidden1)
concat=tf.keras.layers.Concatenate()([input_,hidden2])
output=tf.keras.layers.Dense(1)(concat)
model=tf.keras.Model(inputs=[input_],outputs=[output])
model.compile(loss=tf.keras.losses.mean_squared_error,optimizer=tf.keras.optimizers.SGD(learning_rate=0.05,momentum=0.9,nesterov=True,clipnorm=1,decay=1.0/200))
earlystop=tf.keras.callbacks.EarlyStopping(patience=5,restore_best_weights=True)
history=model.fit(train_db,epochs=100,validation_data=valid_db,callbacks=[earlystop])
model.evaluate(test_db)
Epoch 1/100
363/363 [==============================] - 5s 9ms/step - loss: 0.6353 - val_loss: 0.4421
Epoch 2/100
363/363 [==============================] - 2s 5ms/step - loss: 0.4002 - val_loss: 0.3841
Epoch 3/100
363/363 [==============================] - 2s 6ms/step - loss: 0.3970 - val_loss: 0.3906
Epoch 4/100
363/363 [==============================] - 2s 6ms/step - loss: 0.3729 - val_loss: 0.3671
Epoch 5/100
363/363 [==============================] - 3s 7ms/step - loss: 0.3477 - val_loss: 0.3583
Epoch 6/100
363/363 [==============================] - 2s 6ms/step - loss: 0.3405 - val_loss: 0.3577
Epoch 7/100
363/363 [==============================] - 2s 6ms/step - loss: 0.3314 - val_loss: 0.3746
Epoch 8/100
363/363 [==============================] - 2s 6ms/step - loss: 0.3326 - val_loss: 0.3631
Epoch 9/100
363/363 [==============================] - 2s 6ms/step - loss: 0.3300 - val_loss: 0.3375
Epoch 10/100
363/363 [==============================] - 2s 6ms/step - loss: 0.3227 - val_loss: 0.3402
Epoch 11/100
363/363 [==============================] - 2s 5ms/step - loss: 0.3249 - val_loss: 0.3397
Epoch 12/100
363/363 [==============================] - 2s 6ms/step - loss: 0.3230 - val_loss: 0.3416
Epoch 13/100
363/363 [==============================] - 2s 6ms/step - loss: 0.3164 - val_loss: 0.3335
Epoch 14/100
363/363 [==============================] - 2s 6ms/step - loss: 0.3115 - val_loss: 0.3313
Epoch 15/100
363/363 [==============================] - 2s 6ms/step - loss: 0.3122 - val_loss: 0.3292
Epoch 16/100
363/363 [==============================] - 2s 6ms/step - loss: 0.3091 - val_loss: 0.3303
Epoch 17/100
363/363 [==============================] - 2s 6ms/step - loss: 0.3090 - val_loss: 0.3319
Epoch 18/100
363/363 [==============================] - 2s 6ms/step - loss: 0.3065 - val_loss: 0.3309
Epoch 19/100
363/363 [==============================] - 2s 6ms/step - loss: 0.3059 - val_loss: 0.3312
Epoch 20/100
363/363 [==============================] - 2s 6ms/step - loss: 0.3082 - val_loss: 0.3351
162/162 [==============================] - 0s 2ms/step - loss: 0.3151





0.31514933705329895
pd.DataFrame(history.history).plot(figsize=(18,10))

tensorflow2笔记:简单数据预处理(TF专属)_第1张图片

小结

  • 对于batchprefetch大多要放在最后,其他的向map,shuffle,filter,repeat大多放在前面操作。
  • batch设置32,64,128等,可以设置drop_remainder=True,删除不完整的batch
  • shuffle要设置大一点的buffer_size
  • repeat,可以在shuffle前或者后,buffer_size如果较小,repeat在后面,如果很大,都可以。
  • mapinterleave都可以设置num_parallel_calls,多线程。
  • prefetch:对训练速率有较大影响,但是要考虑好预取的数量,不要爆炸。
  • 以上都是建议,如果有错误的,欢迎在评论区留言。
  • 如果觉得还不错,不要忘了收藏(包括函数)

TFRecord格式

TFRecord格式是Tensorflow首选的格式。这是一种非常简单的二进制格式,只包含二进制记录序列(每个记录由一个长度,一个用于检查长度的CRC校验和,实际数据以及最后一个CRC检验和组成)。
这部分比较少,因为比较不懂。

TFRecord的简单创建读取

with tf.io.TFRecordWriter('my_data.tfrecord') as f:
    f.write(b'This is the first record.')
    f.write(b'And this is the second record.')
    
filepaths = ['my_data.tfrecord']
dataset = tf.data.TFRecordDataset(filepaths)
for item in dataset:
    print(item)
tf.Tensor(b'This is the first record.', shape=(), dtype=string)
tf.Tensor(b'And this is the second record.', shape=(), dtype=string)

TFRecord压缩和读取

tfrecord_options = tf.io.TFRecordOptions(compression_type='GZIP')
with tf.io.TFRecordWriter('my_compressed_data.tfrecord',tfrecord_options) as f:
    f.write(b'This is the first compressed record.')
    f.write(b'And this is the second compressed record.')
dataset = tf.data.TFRecordDataset(['my_compressed_data.tfrecord'],compression_type='GZIP')
for item in dataset:
    print(item)
tf.Tensor(b'This is the first compressed record.', shape=(), dtype=string)
tf.Tensor(b'And this is the second compressed record.', shape=(), dtype=string)

协议缓冲区(TensorFlow协议):

这是一种可移植、可扩展且高效的二进制格式。如果上述的csv已经够用那么就了解一下。
由于笔者太过懒惰且对于这部分也不熟悉,所以这部分就一个例子。

序列化写入

from tensorflow.train import BytesList,FloatList,Int64List #特征的数据类型
from tensorflow.train import Feature,Features,Example #嵌套定义的类型
person_example = Example(  #一个实例
    features=Features(   #一个实例的所有特征
        feature={    #每个特征都用名字 + 值
            'name':Feature(bytes_list=BytesList(value=[b'Alice'])),
            'id':Feature(int64_list=Int64List(value=[123])),
            'emails':Feature(bytes_list=BytesList(value=[b'[email protected]',b'[email protected]']))
        }
    )
)
person_example.SerializeToString()  #将一个实例序列化,变成二进制格式。
b'\n@\n\x1e\n\x06emails\x12\x14\n\x12\n\[email protected]\n\[email protected]\n\x0b\n\x02id\x12\x05\x1a\x03\n\x01{\n\x11\n\x04name\x12\t\n\x07\n\x05Alice'
#将序列化的数据写入文件。
with tf.io.TFRecordWriter('my_person_example.tfrecord') as f:
    f.write(person_example.SerializeToString())

加载和解析Example

# 定义特征描述字典
feature_description = {
    'name':tf.io.FixedLenFeature([],tf.string,default_value=""),  #对于定长特征,用FixedLenFeature
    'id':tf.io.FixedLenFeature([],tf.int64,default_value=0),  # 并且参数有shape,dtype,default_value
    'emails':tf.io.VarLenFeature(tf.string)  #对于不定长的用VarLenFeature,参数只要dtype
}
# 有了特征描述字典,我们可以对读出的序列化数据进行解析。
for serialized_example in tf.data.TFRecordDataset(['my_person_example.tfrecord']):
    parse_example = tf.io.parse_single_example(serialized_example,feature_description)
parse_example
{'emails': ,
 'id': ,
 'name': }
parse_example['emails'].values  #对于可变长度的张量,可以这样访问。

  • 像上面这样的例子,我们在写入的时候,可以写个函数,将数据序列化写入。读出时也可以写个函数,当然可以不用这样一个一个解析,可以读取时加上batch(),解析时用函数tf.io.parse_example()。由于笔者太懒,就没写了。
  • 除了上面比较规则的数据序列化,还有针对文本不规则数据(例如一篇文章许多句子,一个句子用许多词表示)的序列化SeauenceExample,需要对应的类型序列化写,对应的函数解析,想了解的自己搜吧。

预处理输入特征

包括数值标准化,连续数值离散化,字符串编码。

标准化

相当于sklearn库的StandardScaler
每列做各自的标准化。

layer = tf.keras.layers.Normalization()
a = np.random.randint(0,10,(5,5))
a
array([[1, 9, 3, 2, 1],
       [4, 6, 0, 2, 1],
       [5, 6, 8, 0, 0],
       [0, 9, 8, 2, 5],
       [1, 8, 7, 4, 9]])
layer.adapt(a)
layer(a)

在实践中,如果数据集太大,可以随机抽样,在Normalization()加入model之前,先对抽样实例进行adapt()(相当于StandardScaler.fit()),然后再加入model。

连续数值离散化

下面展示了四种模式,不同输入的输出。但是主要的思路还是将连续数值,划分多个区域,给区域编码进行离散。
除了独热码模式,其他模式,输出维数与输入维数一样。

int输出模式

笔者认为,这个模式大多数应该是只输入一列,转换为下标(类别)。输入一列,最好是(None,1)的二维数据,以便和其他数据向concatenate。

# 对给定界线,每个区域标上下标,int输出模式返回对应数值区域的下标(相当于给连续数据分类,下标从0开始,将连续的数据转换成离散的数据)
# 数据预处理,选择这个。或下面的,都是返回二维数组。
layer1=tf.keras.layers.Discretization(bin_boundaries=[0,5,10],output_mode='int')
a,layer1(a)
(array([[1, 9, 3, 2, 1],
        [4, 6, 0, 2, 1],
        [5, 6, 8, 0, 0],
        [0, 9, 8, 2, 5],
        [1, 8, 7, 4, 9]]),
 )
# 或者选择这个。
layer1=tf.keras.layers.Discretization(bin_boundaries=[0,5,10],output_mode='int')
a[:,[1]],layer1(a[:,[1]])
(array([[9],
        [6],
        [6],
        [9],
        [8]]),
 )
layer1=tf.keras.layers.Discretization(bin_boundaries=[0,5,10],output_mode='int')
a[:,1],layer1(a[:,1])
(array([9, 6, 6, 9, 8]),
 )

独热码输出模式

这个模式只能输入一列,为了便于记忆,与上面一样都输入(None,1)的二维数据。

# 对上面划分好的类别(下标),编码为独热码。
# 数据预处理,选择这个或下面都可以,都是返回二维数组。
layer2=tf.keras.layers.Discretization(bin_boundaries=[0,5,10],output_mode='one_hot')
layer1(a[:,[1]]),layer2(a[:,[1]])
(,
 )
# 或者这个
layer2=tf.keras.layers.Discretization(bin_boundaries=[0,5,10],output_mode='one_hot')
layer1(a[:,1]),layer2(a[:,1])
(,
 )

multi_hot输出模式

这个模式,笔者认为大多数应该是输入多列(笔者认为应该是同一度量或者说同一类型的数据)(否则输入一类就变成独热码了)。输入数据就输入二维数据。

# 对于划分好的类别(下标),每个样本有什么类别就在对应的列填上1(有点像独热码,但是独热码每行只有一个1,这里可以有多个1)
# 比如这里第一个样本[1,1,1,1,1],只有下标1,就在下标为1的列填上1,说明有类别1
# 预处理,选这个。返回了二维矩阵。
layer3=tf.keras.layers.Discretization(bin_boundaries=[0,5,10],output_mode='multi_hot')
layer1(a),layer3(a)
(,
 )
layer3=tf.keras.layers.Discretization(bin_boundaries=[0,5,10],output_mode='multi_hot')
layer1(a[:,1]),layer3(a[:,1])
(,
 )
# 或者选择这个。不过这个只处理一列,相当于独热码。
layer3=tf.keras.layers.Discretization(bin_boundaries=[0,5,10],output_mode='multi_hot')
layer1(a[:,[1]]),layer3(a[:,[1]])
(,
 )

count(计数)输出模式

笔者认为应该是输入多列(笔者认为应该是同一度量或者说同一类型的数据)(否则输入一类就变成独热码了)。输入数据就输入二维数据。

# 对于划分好的类别,计算类别的个数,在对应的下标,填上个数。
# 如果将转换后的矩阵数值大于0的,都置为1,则变成multi_hot输出的矩阵。multi_hot说明存在,count计算个数
# 预处理,选择这个。
layer4 = tf.keras.layers.Discretization(bin_boundaries=[0,5,10],output_mode='count')
layer1(a),layer4(a)
(,
 )
layer4 = tf.keras.layers.Discretization(bin_boundaries=[0,5,10],output_mode='count')
layer1(a[:,1]),layer4(a[:,1])
(,
 )
# 或者选择这个。不过这个只处理一列也相当于独热码。
layer4 = tf.keras.layers.Discretization(bin_boundaries=[0,5,10],output_mode='count')
layer1(a[:,[1]]),layer4(a[:,[1]])
(,
 )

不指定区域界线

需要指定划分的区域个数,然后经过adapt()(相当于适应数据)之后可以加入模型。

layer = tf.keras.layers.Discretization(num_bins=4,output_mode='int')
layer.adapt(a)  # 这个学习的区域范围会变化,不是固定不变的。每次运行可能不一样
a,layer(a)
(array([[1, 9, 3, 2, 1],
        [4, 6, 0, 2, 1],
        [5, 6, 8, 0, 0],
        [0, 9, 8, 2, 5],
        [1, 8, 7, 4, 9]]),
 )

连续数值离散化小总结。

  • 四个模式int,one_hot,multi_hot,count,前两个应该是输入(None,1),后面两个应该是输入(None,>1)。都是输入二维数据
  • 四个模式要么设置bin_boundaries,要么设置num_bins,而且都要adapt()

字符编码

  • tf.keras.layers.TextVectorization层中只能adapt一维的向量或者最后一维是1size的。
  • tf.keras.layers.TextVectorization层,输出都是二维。
  • 如果不想adapt,可以设置vocabulary
  • 在输出模式中,由于对tf_idf不熟悉,所以不测试。
b = np.array([['male on'],['female off'],['male off'],['female on']])
b
array([['male on'],
       ['female off'],
       ['male off'],
       ['female on']], dtype='

int输出模式

这里是对字符串划分编码(下标),如果只是对标签编码,只要传入一列标签值。这里可以看出大多是对文档(包括多个单词的一个字符串)处理。

# 对类别编码(下标),从2开始(后面无论是one_hot还是embed列数都要是:类别数+2(>=2)(oov=2)),如果在adapt之后有新的值传入,编码为1,注意输出二维。
text_layer = tf.keras.layers.TextVectorization(output_mode='int')
label = ['a','b','c','b','a','b','c']
text_layer.adapt(label)
text_layer(label),text_layer(label+['d']),tf.reshape(text_layer(label),-1)
(,
 ,
 )
# 对字符串编码,通过空格分隔字符串(分隔字符可以不分割:None,空格: whitespace(默认),每个字符:character),编码下标。
text_layer1=tf.keras.layers.TextVectorization(max_tokens=5000,output_sequence_length=4,output_mode='int')
text_layer1.adapt(b)
text_layer1(b)

# 这里没有one_hot模式,但是可以在后面接一自定义层将其转换为独热码。注意独热码要输入一维。
tf.one_hot([1,0,2,5],depth=5)

multi_hot输出模式

原理与上面的连续值离散化一样,oov=1(从1下标开始)类别数(4)+1 = 5(列)

text_layer2=tf.keras.layers.TextVectorization(max_tokens=5000,output_mode='multi_hot')
text_layer2.adapt(b)
text_layer2(b)
WARNING:tensorflow:5 out of the last 5 calls to .adapt_step at 0x000001C0D3A06160> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has reduce_retracing=True option that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for  more details.






count输出模式

原理与上面一样,oov=1(从1开始)类别数(4) + 1 =5(列)

text_layer3=tf.keras.layers.TextVectorization(max_tokens=5000,output_mode='count')
text_layer3.adapt(b)
text_layer3(b)
WARNING:tensorflow:6 out of the last 6 calls to .adapt_step at 0x000001C177D1B700> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has reduce_retracing=True option that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for  more details.






使用嵌入编码(Embedding)

  • 通过一个向量表示一个词,需要现将词转换为下标。
  • 注意输出的维度。
  • 嵌入编码可训练
embed = tf.keras.layers.Embedding(input_dim=5,output_dim=2)
embed(tf.Variable([4,2,3,2,4,2,3])),embed(text_layer(label))
(,
 )

自定义预处理层(one_hot和Embedding)

  • 笔者这里对字符串(一列数据(特征)),进行字符串编码的自定义层实现。
  • 由于是第一次写,自定义预处理层写到笔者都要崩了(到处报错),但是错误的教训是深刻的。
  • 一定要记住,要写输入层Input,之前由于没写输入层,导致对字符串的处理一直卡在concatenate层(和原数据是数值的拼接)。
  • 实现都很基础,就是要变换shape,为了让字符串处理后可以和其他输入拼接,所以都是输出二维,同时注意要输入data[:,[1]],而不是data[:,1].

Input_onehot自定义层实现

还是要在前面加上Input输入层。

data = np.array([[13,'male','on',4],[35,'female','off',8],[44,'male','off',9],[15,'female','on',5]])
# 测试代码,不用理睬。
# textvec_layer = tf.keras.layers.TextVectorization(output_mode='int',input_shape=[1,])
# textvec_layer.adapt(data[:,[1]])
# onehot_layer= tf.keras.layers.Lambda(lambda inputs_2D:tf.one_hot(inputs_2D[:,0],2+2))
# model = tf.keras.models.Sequential([textvec_layer,onehot_layer])
# model(data[:,[1]])

# 使用Lambda方法
class Input_onehot(tf.keras.layers.Layer):
    def __init__(self,num_categories,oov=2,vocabulary=None,**kwargs):
        self.num_categories=num_categories
        self.oov = oov
        self.vocabulary=vocabulary
        self.textvec_layer = tf.keras.layers.TextVectorization(output_mode='int',vocabulary=self.vocabulary,input_shape=[1,])
        self.onehot_layer= tf.keras.layers.Lambda(lambda inputs_2D:tf.one_hot(inputs_2D[:,0],self.num_categories+self.oov))
        #self.reshape = tf.keras.layers.Reshape([self.num_categories+self.oov,])
        super().__init__(**kwargs)
    def adapt(self,data_sample_2D):
        self.textvec_layer.adapt(data_sample_2D[:,0])  #(None,1)
    def call(self,inputs_2D):
        z = self.textvec_layer(inputs_2D)
        z = self.onehot_layer(z)
        #z = self.reshape(z)
        return z
    def get_config(self):
        base_config = super().get_config()
        return {**base_config,'num_categories':self.num_categories,'oov':self.oov,'vocabulary':self.vocabulary}
temp_layer = Input_onehot(2)
temp_layer.adapt(data[:,[1]])
temp_layer(data[:,[1]])

Input_embedding自定义层实现

还是要在前面加上Input输入层。

# 测试代码,不用理睬。
# textvec_layer = tf.keras.layers.TextVectorization(output_mode='int',input_shape=[1,])
# textvec_layer.adapt(data[:,[1]])
# zhong = tf.keras.layers.Lambda(lambda inputs_2D:inputs_2D[:,0])
# embed = tf.keras.layers.Embedding(input_dim=4,output_dim=2)
# model = tf.keras.models.Sequential([textvec_layer,zhong,embed])
# model(data[:,[1]])

#使用reshape方法
class Input_embedding(tf.keras.layers.Layer):
    def __init__(self,num_categories,outputdim,oov=2,vocabulary=None,**kwargs):
        self.num_categories=num_categories
        self.outputdim = outputdim
        self.oov = oov
        self.vocabulary=vocabulary
        self.textvec_layer = tf.keras.layers.TextVectorization(output_mode='int',vocabulary=self.vocabulary,input_shape=[1,])
        self.embed = tf.keras.layers.Embedding(input_dim=self.num_categories+self.oov,output_dim=self.outputdim)
        self.trans = tf.keras.layers.Reshape((self.outputdim,))
        super().__init__(**kwargs)
    def compute_output_shape(self,batch_input_shape):
        return tf.TensorShape(batch_input_shape[:-1]+[self.outputdim])
    def adapt(self,data_sample_2D):
        self.textvec_layer.adapt(data_sample_2D[:,0])  #(None,1)
    def call(self,inputs_2D):
        z = self.textvec_layer(inputs_2D)  #(None,1)
        z = self.embed(z)
        z = self.trans(z)
        return z
    def get_config(self):
        base_config = super().get_config()
        return {**base_config,'num_categories':self.num_categories,'oov':self.oov,'outputdim':self.outputdim,'vocabulary':self.vocabulary}

temp_layer = Input_embedding(2,2)
temp_layer.adapt(data[:,[1]])
temp_layer(data[:,[1]])

使用tf.keras自带层实现包含embed的模型(1)

# 使用上面测试的Lambda 方法实现embed
num_input = tf.keras.layers.Input(shape=[2,],name='num_input')  
text_input = tf.keras.layers.Input(shape=[1,],dtype=tf.string,name='text_input')
num_layer = tf.keras.layers.Dense(4,activation='elu',kernel_initializer='he_normal',name='num_layer')(num_input)
textvec_layer = tf.keras.layers.TextVectorization(output_mode='int',vocabulary=tf.unique(data[:,0])[0])(text_input)
zhong = tf.keras.layers.Lambda(lambda inputs_2D:inputs_2D[:,0])(textvec_layer)
embed = tf.keras.layers.Embedding(input_dim=4,output_dim=2)(zhong)
concat = tf.keras.layers.Concatenate(name='concat')([num_layer,embed])
output = tf.keras.layers.Dense(1,name='output')(concat)
model = tf.keras.Model(inputs=[num_input,text_input],outputs=[output])
model.summary()
Model: "model_1"
__________________________________________________________________________________________________
 Layer (type)                   Output Shape         Param #     Connected to                     
==================================================================================================
 text_input (InputLayer)        [(None, 1)]          0           []                               
                                                                                                  
 text_vectorization_6 (TextVect  (None, None)        0           ['text_input[0][0]']             
 orization)                                                                                       
                                                                                                  
 num_input (InputLayer)         [(None, 2)]          0           []                               
                                                                                                  
 lambda_1 (Lambda)              (None,)              0           ['text_vectorization_6[0][0]']   
                                                                                                  
 num_layer (Dense)              (None, 4)            12          ['num_input[0][0]']              
                                                                                                  
 embedding_2 (Embedding)        (None, 2)            8           ['lambda_1[0][0]']               
                                                                                                  
 concat (Concatenate)           (None, 6)            0           ['num_layer[0][0]',              
                                                                  'embedding_2[0][0]']            
                                                                                                  
 output (Dense)                 (None, 1)            7           ['concat[0][0]']                 
                                                                                                  
==================================================================================================
Total params: 27
Trainable params: 27
Non-trainable params: 0
__________________________________________________________________________________________________

使用tf.keras自带层实现包含embed的模型(2)

# 使用reshape方法实现embed
num_input = tf.keras.layers.Input(shape=[2,],name='num_input')  # 这层数据输入层可以不要,只要在num_layer设置input_shape=[2,]
text_input = tf.keras.layers.Input(shape=[1,],dtype=tf.string,name='text_input')   # 如果要输入文本,要设置输入类型为tf.string,否则会报错。
num_layer = tf.keras.layers.Dense(4,activation='elu',kernel_initializer='he_normal',name='num_layer')(num_input)
# 估计不能用text_layer直接当做第一层,设置不了输入数据类型(如果可以,欢迎评论区告知)
text_layer = tf.keras.layers.TextVectorization(output_mode='int',vocabulary=tf.unique(data[:,1])[0],name='text_layer')(text_input)  #tf.unique返回列表,取第一个
embed_layer= tf.keras.layers.Embedding(input_dim=4,output_dim=2,name='embed_layer')(text_layer)
# 为了保证字符编码输出二维,这里就变成在embed之后加了reshape层。
reshape_layer = tf.keras.layers.Reshape((2,),name='reshape')(embed_layer)  # 这里是将每个实例展开为一维,但是不能直接写-1,会报错。
# 将两个二维矩阵拼接。
concat = tf.keras.layers.Concatenate(name='concat')([num_layer,reshape_layer])
output = tf.keras.layers.Dense(1,name='output')(concat)
model = tf.keras.Model(inputs=[num_input,text_input],outputs=[output])
a = np.array(data[:,[0,3]],np.float32)  #将数据转换为tf.float32
a = tf.cast(a,tf.float32)
model([a,data[:,[1]]]),model.summary()
Model: "model_2"
__________________________________________________________________________________________________
 Layer (type)                   Output Shape         Param #     Connected to                     
==================================================================================================
 text_input (InputLayer)        [(None, 1)]          0           []                               
                                                                                                  
 text_layer (TextVectorization)  (None, None)        0           ['text_input[0][0]']             
                                                                                                  
 num_input (InputLayer)         [(None, 2)]          0           []                               
                                                                                                  
 embed_layer (Embedding)        (None, None, 2)      8           ['text_layer[0][0]']             
                                                                                                  
 num_layer (Dense)              (None, 4)            12          ['num_input[0][0]']              
                                                                                                  
 reshape (Reshape)              (None, 2)            0           ['embed_layer[0][0]']            
                                                                                                  
 concat (Concatenate)           (None, 6)            0           ['num_layer[0][0]',              
                                                                  'reshape[0][0]']                
                                                                                                  
 output (Dense)                 (None, 1)            7           ['concat[0][0]']                 
                                                                                                  
==================================================================================================
Total params: 27
Trainable params: 27
Non-trainable params: 0
__________________________________________________________________________________________________





(,
 None)

使用自定义层实现包含embed的模型

## 测试,自己写的Input_embedding类终于可以啦!
# 使用reshape
num_input = tf.keras.layers.Input(shape=[2,],name='num_input')  
text_input = tf.keras.layers.Input(shape=[1,],dtype=tf.string,name='text_input')
num_layer = tf.keras.layers.Dense(4,activation='elu',kernel_initializer='he_normal',name='num_layer')(num_input)
text_layer = Input_embedding(2,2,vocabulary=tf.unique(data[:,1])[0],name='text_layer')(text_input)
concat = tf.keras.layers.Concatenate(name='concat')([num_layer,text_layer])
output = tf.keras.layers.Dense(1,name='output')(concat)
model = tf.keras.Model(inputs=[num_input,text_input],outputs=[output])
model.summary()
Model: "model_3"
__________________________________________________________________________________________________
 Layer (type)                   Output Shape         Param #     Connected to                     
==================================================================================================
 num_input (InputLayer)         [(None, 2)]          0           []                               
                                                                                                  
 text_input (InputLayer)        [(None, 1)]          0           []                               
                                                                                                  
 num_layer (Dense)              (None, 4)            12          ['num_input[0][0]']              
                                                                                                  
 text_layer (Input_embedding)   (None, 2)            8           ['text_input[0][0]']             
                                                                                                  
 concat (Concatenate)           (None, 6)            0           ['num_layer[0][0]',              
                                                                  'text_layer[0][0]']             
                                                                                                  
 output (Dense)                 (None, 1)            7           ['concat[0][0]']                 
                                                                                                  
==================================================================================================
Total params: 27
Trainable params: 27
Non-trainable params: 0
__________________________________________________________________________________________________

使用自定义层实现包含one_hot的模型

## 测试,自己写的Input_onehot终于可以啦!
# Lambda
num_input = tf.keras.layers.Input(shape=[2,],name='num_input')  
text_input = tf.keras.layers.Input(shape=[1,],dtype=tf.string,name='text_input')
num_layer = tf.keras.layers.Dense(4,activation='elu',kernel_initializer='he_normal',name='num_layer')(num_input)
text_layer = Input_onehot(2,vocabulary=tf.unique(data[:,1])[0],name='text_layer')(text_input)
concat = tf.keras.layers.Concatenate(name='concat')([num_layer,text_layer])
output = tf.keras.layers.Dense(1,name='output')(concat)
model = tf.keras.Model(inputs=[num_input,text_input],outputs=[output])
model.summary()
Model: "model_4"
__________________________________________________________________________________________________
 Layer (type)                   Output Shape         Param #     Connected to                     
==================================================================================================
 num_input (InputLayer)         [(None, 2)]          0           []                               
                                                                                                  
 text_input (InputLayer)        [(None, 1)]          0           []                               
                                                                                                  
 num_layer (Dense)              (None, 4)            12          ['num_input[0][0]']              
                                                                                                  
 text_layer (Input_onehot)      (None, 4)            0           ['text_input[0][0]']             
                                                                                                  
 concat (Concatenate)           (None, 8)            0           ['num_layer[0][0]',              
                                                                  'text_layer[0][0]']             
                                                                                                  
 output (Dense)                 (None, 1)            9           ['concat[0][0]']                 
                                                                                                  
==================================================================================================
Total params: 21
Trainable params: 21
Non-trainable params: 0
__________________________________________________________________________________________________

写了这么多,测试了两天两夜,都是泪~~.~~。大家如果觉得不错,就收藏吧!!

你可能感兴趣的:(tensorflow2,tensorflow,机器学习,python)