目录(注意本文jupyterlab编写)
预先导入数据
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)
(<tf.Tensor: shape=(5, 8), dtype=float64, numpy=
array([[ 1.49936543, 0.69295606, 0.17214511, -0.03404754, -0.47769067,
-0.18911355, -1.90258047, 1.69674941],
[-1.45498335, -3.27994787, -0.84853789, 0.31964019, -1.23181805,
0.21437985, -1.48121256, 1.36732314],
[-2.45057457, -0.73728936, -1.98759026, 0.33934708, -0.3717243 ,
-0.24314346, -1.4343939 , 1.22756654],
[ 1.04341686, -1.05512167, -0.33146131, 0.01883375, 0.28703334,
-0.20471606, -1.48121256, 1.12774039],
[-1.76101948, 1.96428532, -1.17506422, -0.39201263, 1.58865366,
0.25664931, -1.49994002, 1.39727098]])>, <tf.Tensor: shape=(5,), dtype=float64, numpy=array([5.00001, 1.375 , 3.25 , 5.00001, 1.408 ])>)
(<tf.Tensor: shape=(5, 8), dtype=float64, numpy=
array([[ 0.79938805, 3.71236305, 1.21865315, -0.23253737, -0.70375228,
-0.15856083, 2.7418303 , -1.87702652],
[ 2.89753049, 0.21620759, 1.16258848, -0.53988317, -1.07993291,
0.02099643, 1.56200015, -2.36617463],
[-1.79691845, 1.646453 , -1.33595353, 0.04766414, 0.06803616,
0.13342085, 2.02082298, -2.66565306],
[-0.63551819, -1.21403783, -0.16055853, -0.42936727, -0.89449175,
-0.14328661, 2.66692045, -3.08492286],
[-0.17999073, -0.73728936, -1.1617794 , 0.0164999 , 4.91776398,
-0.27524695, -1.39693897, 1.42721882]])>, <tf.Tensor: shape=(5,), dtype=float64, numpy=array([2.338, 3.325, 1.055, 1.762, 2.25 ])>)
(<tf.Tensor: shape=(5, 8), dtype=float64, numpy=
array([[ 1.61253668, 2.91778226, -0.42219841, -0.06155567, -1.49143567,
-0.17384215, -1.29393793, 0.9280881 ],
[-0.8213296 , -1.21403783, -0.60980633, 0.02916445, 0.1068905 ,
-0.21129153, 1.26236074, -2.36617463],
[ 0.33964957, -3.59778018, 1.00670563, -0.04921188, 0.75151929,
-0.07770384, -1.07857211, 2.30568888],
[ 0.3537565 , -2.00861861, 0.01195357, -0.19573477, -0.24279854,
0.14250147, 2.23618881, -2.3462094 ],
[ 0.15804918, 1.16970453, 0.01984725, -0.52330752, -1.57974098,
-0.03254749, 1.94591313, -2.57580953]])>, <tf.Tensor: shape=(5,), dtype=float64, numpy=array([2.53 , 2.643, 1.631, 1.25 , 1.915])>)
乱序数据
我们知道训练集中实例相互独立且分布均匀时,梯度下降效果最佳。
shuffle乱序(小数据集)
shuffle
乱序,需要设置缓冲区大小(也可设置随机种子),适合可以装入RAM的小型数据集。
原理:先将缓冲区填满,如果要求提供一个数据,从缓冲区随机取出一个数据,并用源数据集的新元素替换它,直到遍历完源数据集,然后把缓冲区抽空为止。
注意:缓冲区必须足够大,不然乱序不太有效。当然也不要超出数据集大小。
dataset=tf.data.Dataset.range(10).repeat(3)
dataset
<RepeatDataset element_spec=TensorSpec(shape=(), dtype=tf.int64, name=None)>
- 如果
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))
(<tf.Tensor: shape=(8,), dtype=float32, numpy=
array([ 0.7496827 , 0.34647802, 0.08607256, -0.01702377, -0.23884533,
-0.09455678, -0.95129025, 0.8483747 ], dtype=float32)>, <tf.Tensor: shape=(1,), dtype=float32, numpy=array([5.00001], dtype=float32)>)
(<tf.Tensor: shape=(8,), dtype=float32, numpy=
array([-0.7274917 , -1.6399739 , -0.42426893, 0.1598201 , -0.61590904,
0.10718992, -0.7406063 , 0.6836616 ], dtype=float32)>, <tf.Tensor: shape=(1,), dtype=float32, numpy=array([1.375], dtype=float32)>)
(<tf.Tensor: shape=(8,), dtype=float32, numpy=
array([-1.2252873 , -0.36864468, -0.99379516, 0.16967353, -0.18586215,
-0.12157173, -0.71719694, 0.61378324], dtype=float32)>, <tf.Tensor: shape=(1,), dtype=float32, numpy=array([3.25], dtype=float32)>)
将上述所有的操作合在一起,如下
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))
小结
- 对于
batch
和prefetch
大多要放在最后,其他的向map,shuffle,filter,repeat大多放在前面操作。batch
设置32,64,128等,可以设置drop_remainder=True
,删除不完整的batchshuffle
要设置大一点的buffer_size
repeat
,可以在shuffle
前或者后,buffer_size
如果较小,repeat
在后面,如果很大,都可以。map
和interleave
都可以设置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'a@b.com',b'c@d.com']))
}
)
)
person_example.SerializeToString() #将一个实例序列化,变成二进制格式。
b'\n@\n\x1e\n\x06emails\x12\x14\n\x12\n\x07a@b.com\n\x07c@d.com\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': <tensorflow.python.framework.sparse_tensor.SparseTensor at 0x1c0e1879ca0>,
'id': <tf.Tensor: shape=(), dtype=int64, numpy=123>,
'name': <tf.Tensor: shape=(), dtype=string, numpy=b'Alice'>}
parse_example['emails'].values #对于可变长度的张量,可以这样访问。
<tf.Tensor: shape=(2,), dtype=string, numpy=array([b'a@b.com', b'c@d.com'], dtype=object)>
- 像上面这样的例子,我们在写入的时候,可以写个函数,将数据序列化写入。读出时也可以写个函数,当然可以不用这样一个一个解析,可以读取时加上
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)
<tf.Tensor: shape=(5, 5), dtype=float32, numpy=
array([[-0.61885273, 1.0320938 , -0.69020134, 0. , -0.6527299 ],
[ 0.92827904, -1.1795356 , -1.6313851 , 0. , -0.6527299 ],
[ 1.4439896 , -1.1795356 , 0.8784382 , -1.5811388 , -0.9494253 ],
[-1.1345633 , 1.0320938 , 0.8784382 , 0. , 0.5340517 ],
[-0.61885273, 0.294884 , 0.5647103 , 1.5811388 , 1.7208334 ]],
dtype=float32)>
在实践中,如果数据集太大,可以随机抽样,在
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]]),
<tf.Tensor: shape=(5, 5), dtype=int64, numpy=
array([[1, 2, 1, 1, 1],
[1, 2, 1, 1, 1],
[2, 2, 2, 1, 1],
[1, 2, 2, 1, 2],
[1, 2, 2, 1, 2]], dtype=int64)>)
# 或者选择这个。
layer1=tf.keras.layers.Discretization(bin_boundaries=[0,5,10],output_mode='int')
a[:,[1]],layer1(a[:,[1]])
(array([[9],
[6],
[6],
[9],
[8]]),
<tf.Tensor: shape=(5, 1), dtype=int64, numpy=
array([[2],
[2],
[2],
[2],
[2]], dtype=int64)>)
layer1=tf.keras.layers.Discretization(bin_boundaries=[0,5,10],output_mode='int')
a[:,1],layer1(a[:,1])
(array([9, 6, 6, 9, 8]),
<tf.Tensor: shape=(5,), dtype=int64, numpy=array([2, 2, 2, 2, 2], dtype=int64)>)
独热码输出模式
这个模式只能输入一列,为了便于记忆,与上面一样都输入(None,1)的二维数据。
# 对上面划分好的类别(下标),编码为独热码。
# 数据预处理,选择这个或下面都可以,都是返回二维数组。
layer2=tf.keras.layers.Discretization(bin_boundaries=[0,5,10],output_mode='one_hot')
layer1(a[:,[1]]),layer2(a[:,[1]])
(<tf.Tensor: shape=(5, 1), dtype=int64, numpy=
array([[2],
[2],
[2],
[2],
[2]], dtype=int64)>,
<tf.Tensor: shape=(5, 4), dtype=float32, numpy=
array([[0., 0., 1., 0.],
[0., 0., 1., 0.],
[0., 0., 1., 0.],
[0., 0., 1., 0.],
[0., 0., 1., 0.]], dtype=float32)>)
# 或者这个
layer2=tf.keras.layers.Discretization(bin_boundaries=[0,5,10],output_mode='one_hot')
layer1(a[:,1]),layer2(a[:,1])
(<tf.Tensor: shape=(5,), dtype=int64, numpy=array([2, 2, 2, 2, 2], dtype=int64)>,
<tf.Tensor: shape=(5, 4), dtype=float32, numpy=
array([[0., 0., 1., 0.],
[0., 0., 1., 0.],
[0., 0., 1., 0.],
[0., 0., 1., 0.],
[0., 0., 1., 0.]], dtype=float32)>)
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)
(<tf.Tensor: shape=(5, 5), dtype=int64, numpy=
array([[1, 2, 1, 1, 1],
[1, 2, 1, 1, 1],
[2, 2, 2, 1, 1],
[1, 2, 2, 1, 2],
[1, 2, 2, 1, 2]], dtype=int64)>,
<tf.Tensor: shape=(5, 4), dtype=float32, numpy=
array([[0., 1., 1., 0.],
[0., 1., 1., 0.],
[0., 1., 1., 0.],
[0., 1., 1., 0.],
[0., 1., 1., 0.]], dtype=float32)>)
layer3=tf.keras.layers.Discretization(bin_boundaries=[0,5,10],output_mode='multi_hot')
layer1(a[:,1]),layer3(a[:,1])
(<tf.Tensor: shape=(5,), dtype=int64, numpy=array([2, 2, 2, 2, 2], dtype=int64)>,
<tf.Tensor: shape=(4,), dtype=float32, numpy=array([0., 0., 1., 0.], dtype=float32)>)
# 或者选择这个。不过这个只处理一列,相当于独热码。
layer3=tf.keras.layers.Discretization(bin_boundaries=[0,5,10],output_mode='multi_hot')
layer1(a[:,[1]]),layer3(a[:,[1]])
(<tf.Tensor: shape=(5, 1), dtype=int64, numpy=
array([[2],
[2],
[2],
[2],
[2]], dtype=int64)>,
<tf.Tensor: shape=(5, 4), dtype=float32, numpy=
array([[0., 0., 1., 0.],
[0., 0., 1., 0.],
[0., 0., 1., 0.],
[0., 0., 1., 0.],
[0., 0., 1., 0.]], dtype=float32)>)
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)
(<tf.Tensor: shape=(5, 5), dtype=int64, numpy=
array([[1, 2, 1, 1, 1],
[1, 2, 1, 1, 1],
[2, 2, 2, 1, 1],
[1, 2, 2, 1, 2],
[1, 2, 2, 1, 2]], dtype=int64)>,
<tf.Tensor: shape=(5, 4), dtype=float32, numpy=
array([[0., 4., 1., 0.],
[0., 4., 1., 0.],
[0., 2., 3., 0.],
[0., 2., 3., 0.],
[0., 2., 3., 0.]], dtype=float32)>)
layer4 = tf.keras.layers.Discretization(bin_boundaries=[0,5,10],output_mode='count')
layer1(a[:,1]),layer4(a[:,1])
(<tf.Tensor: shape=(5,), dtype=int64, numpy=array([2, 2, 2, 2, 2], dtype=int64)>,
<tf.Tensor: shape=(4,), dtype=float32, numpy=array([0., 0., 5., 0.], dtype=float32)>)
# 或者选择这个。不过这个只处理一列也相当于独热码。
layer4 = tf.keras.layers.Discretization(bin_boundaries=[0,5,10],output_mode='count')
layer1(a[:,[1]]),layer4(a[:,[1]])
(<tf.Tensor: shape=(5, 1), dtype=int64, numpy=
array([[2],
[2],
[2],
[2],
[2]], dtype=int64)>,
<tf.Tensor: shape=(5, 4), dtype=float32, numpy=
array([[0., 0., 1., 0.],
[0., 0., 1., 0.],
[0., 0., 1., 0.],
[0., 0., 1., 0.],
[0., 0., 1., 0.]], dtype=float32)>)
不指定区域界线
需要指定划分的区域个数,然后经过
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]]),
<tf.Tensor: shape=(5, 5), dtype=int64, numpy=
array([[1, 3, 2, 1, 1],
[2, 3, 0, 1, 1],
[2, 3, 3, 0, 0],
[0, 3, 3, 1, 2],
[1, 3, 3, 2, 3]], dtype=int64)>)
连续数值离散化小总结。
- 四个模式
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='<U10')
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)
(<tf.Tensor: shape=(7, 1), dtype=int64, numpy=
array([[4],
[2],
[3],
[2],
[4],
[2],
[3]], dtype=int64)>,
<tf.Tensor: shape=(8, 1), dtype=int64, numpy=
array([[4],
[2],
[3],
[2],
[4],
[2],
[3],
[1]], dtype=int64)>,
<tf.Tensor: shape=(7,), dtype=int64, numpy=array([4, 2, 3, 2, 4, 2, 3], dtype=int64)>)
# 对字符串编码,通过空格分隔字符串(分隔字符可以不分割: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)
<tf.Tensor: shape=(4, 4), dtype=int64, numpy=
array([[4, 2, 0, 0],
[5, 3, 0, 0],
[4, 3, 0, 0],
[5, 2, 0, 0]], dtype=int64)>
# 这里没有one_hot模式,但是可以在后面接一自定义层将其转换为独热码。注意独热码要输入一维。
tf.one_hot([1,0,2,5],depth=5)
<tf.Tensor: shape=(4, 5), dtype=float32, numpy=
array([[0., 1., 0., 0., 0.],
[1., 0., 0., 0., 0.],
[0., 0., 1., 0., 0.],
[0., 0., 0., 0., 0.]], dtype=float32)>
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 <function PreprocessingLayer.make_adapt_function.<locals>.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.
<tf.Tensor: shape=(4, 5), dtype=float32, numpy=
array([[0., 1., 0., 1., 0.],
[0., 0., 1., 0., 1.],
[0., 0., 1., 1., 0.],
[0., 1., 0., 0., 1.]], dtype=float32)>
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 <function PreprocessingLayer.make_adapt_function.<locals>.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.
<tf.Tensor: shape=(4, 5), dtype=float32, numpy=
array([[0., 1., 0., 1., 0.],
[0., 0., 1., 0., 1.],
[0., 0., 1., 1., 0.],
[0., 1., 0., 0., 1.]], dtype=float32)>
使用嵌入编码(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))
(<tf.Tensor: shape=(7, 2), dtype=float32, numpy=
array([[-0.03596386, 0.01693531],
[-0.02953193, -0.01553446],
[ 0.04661288, -0.00266797],
[-0.02953193, -0.01553446],
[-0.03596386, 0.01693531],
[-0.02953193, -0.01553446],
[ 0.04661288, -0.00266797]], dtype=float32)>,
<tf.Tensor: shape=(7, 1, 2), dtype=float32, numpy=
array([[[-0.03596386, 0.01693531]],
[[-0.02953193, -0.01553446]],
[[ 0.04661288, -0.00266797]],
[[-0.02953193, -0.01553446]],
[[-0.03596386, 0.01693531]],
[[-0.02953193, -0.01553446]],
[[ 0.04661288, -0.00266797]]], dtype=float32)>)
自定义预处理层(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]])
<tf.Tensor: shape=(4, 4), dtype=float32, numpy=
array([[0., 0., 1., 0.],
[0., 0., 0., 1.],
[0., 0., 1., 0.],
[0., 0., 0., 1.]], dtype=float32)>
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.Tensor: shape=(4, 2), dtype=float32, numpy=
array([[-0.00428744, 0.02857419],
[ 0.01162884, -0.00078434],
[-0.00428744, 0.02857419],
[ 0.01162884, -0.00078434]], dtype=float32)>
使用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
__________________________________________________________________________________________________
(<tf.Tensor: shape=(4, 1), dtype=float32, numpy=
array([[19.181803],
[55.4038 ],
[70.939125],
[21.767748]], dtype=float32)>,
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
__________________________________________________________________________________________________
写了这么多,测试了两天两夜,都是泪~~.~~。大家如果觉得不错,就收藏吧!!