下面學(xué)習(xí)一下Tensorflow張量的進(jìn)階操作犀盟,如張量的合并與分割而晒、范數(shù)統(tǒng)計(jì)、張量填充阅畴、張量限幅等操作倡怎。
1. 張量的合并與分割操作
1.1 張量的合并
合并是指將多個張量在某個維度上合并為一個張量。
張量的合并可以使用拼接和堆疊操作實(shí)現(xiàn)恶阴,拼接操作并不會產(chǎn)生新的維度诈胜,僅在現(xiàn)有的維度上合并豹障,而堆疊會創(chuàng)建新維度冯事。選擇使用拼接還是堆疊操作來合并張量,取決于具體的場景是否需要創(chuàng)建新維度血公。
拼接 在Tensorflow中昵仅,可以通過 tf.concat(tensors, axis) 函數(shù)拼接張量,其中參數(shù)tensors保存了所有需要合并的張量 list累魔,axis 參數(shù)指定需要合并的維度索引摔笤。
下面看示例:
a = tf.random.normal([4,35,8])
b = tf.random.normal([6,35,8])
c = tf.concat([a,b],axis=0)#拼接操作
print(c.shape)
(10, 35, 8)
再看一例:
a = tf.random.normal([10,35,4])
b = tf.random.normal([10,35,4])
c = tf.concat([a,b],axis=2)#拼接操作
print(c.shape)
(10, 35, 8)
從語法上來說,拼接合并操作可以在任意的維度上進(jìn)行垦写,唯一的約束是非合并維度的長度必須一致吕世。
堆疊如果在合并數(shù)據(jù)時,希望創(chuàng)建一個新的維度梯投,則需要使用 tf.stack(tensors, axis) 操作命辖。
tf.stack(tensors, axis) 中的tensors表示需要合并的張量,參數(shù)axis指定新維度插入的位置分蓖,axis的用法與tf.expand_dims一致尔艇,當(dāng)axis>=0時,在axis之前插入么鹤;當(dāng)axis<0時终娃,在axis之后插入新維度。
下面是使用示例:
a = tf.random.normal([35,8])
b = tf.random.normal([35,8])
c = tf.stack([a,b],axis=0)# 堆疊操作
print(c.shape)
(2, 35, 8)
tf.stack也需要滿足張量堆疊合并條件蒸甜,它需要所有待合并的張量shape完全一致才可合并棠耕。
1.2 張量的分割
合并操作的逆過程就是分割,將一個張量拆分為多個張量柠新。
tf.split(x, num_or_size_splits, axis)可以完成張量的分割操作窍荧,參數(shù)意義如下:
- x 參數(shù)::待分割張量。
- num_or_size_splits 參數(shù):切割方案登颓。當(dāng) num_or_size_splits 為單個數(shù)值時搅荞,如10,表示等長切割為10份;當(dāng) num_or_size_splits 為 list 時咕痛,list的每個元素表示每份的長度痢甘,如[2,4,2,2]表示切割為4份,每份的長度依次是2茉贡、4塞栅、2、2 腔丧。
- axis 參數(shù):指定分割的維度索引號放椰。
下面看示例:
a = tf.random.normal([10,35,8])
results = tf.split(a, num_or_size_splits=10, axis=0)#把張量拆分成10份
print(len(results))
10
#查看切割后的某個張量的形狀
print(results[0].shape)#可以看到,切割后的張量仍然保留了切割的維度愉粤,這一點(diǎn)要注意
(1, 35, 8)
下面砾医,示例一下不等長切割:
a = tf.random.normal([10,35,8])
results = tf.split(a, num_or_size_splits=[4,2,2,2], axis=0)#切割為不等長 4 份
print(len(results))
4
#查看切割后的某個張量的形狀
print(results[0].shape)
(4, 35, 8)
特別地,如果希望在某個維度上全部按長度為 1 的方式切割衣厘,還可以使用 tf.unstack(x, axis)函數(shù)如蚜。這是tf.split的一種特殊情況,切割長度固定為1影暴,只需要指定切割維度的索引號即可错邦。
下面看示例:
a = tf.random.normal([10,35,8])
results = tf.unstack(a,axis=0)#張量切割
print(len(results))
10
#查看切割后張量的形狀
print(results[0].shape)#可以看到,這種切割方式去掉了切割維度
(35, 8)
2. 張量數(shù)據(jù)統(tǒng)計(jì)
在神經(jīng)網(wǎng)絡(luò)的計(jì)算過程中型宙,經(jīng)常需要統(tǒng)計(jì)數(shù)據(jù)的各種屬性撬呢,如最值、最值位置妆兑、均值魂拦、和。
通過tf.reduce_max箭跳、tf.reduce_min晨另、tf.reduce_mean、tf.reduce_sum 可以求解張量在某個維度上的最大谱姓、最小借尿、均值、和屉来,也可以求全局最大路翻、最小、均值茄靠、和信息茂契。
下面看一些使用示例:
x = tf.random.normal([4,10])
out = tf.reduce_max(x, axis=1)#求樣本的最大值
print(out.shape)
4
out = tf.reduce_min(x, axis=1)#求樣本的最小值
print(out.shape)
4
out = tf.reduce_mean(x, axis=1)#求樣本的均值
print(out.shape)
4
當(dāng)不指定 axis 參數(shù)時,tf.reduce_*函數(shù)會求解出全局元素的最大慨绳、最小掉冶、均值真竖、和。
x = tf.random.normal([4,10])
tf.reduce_max(x)#求解樣本全局的最大值
tf.reduce_min(x)#求解樣本全局的最小值
tf.reduce_mean(x)#求解樣本全局的均值
在求解誤差函數(shù)時厌小,通過Tensorflow 的 MSE 誤差函數(shù)可以求得每個樣本的誤差恢共。若是需要計(jì)算樣本的平均誤差,此時可以通過 tf.reduce_mean 在樣本數(shù)維度上計(jì)算均值璧亚。
看下面示例:
import tensorflow as tf
from tensorflow import keras
out = tf.random.normal([4,10])#網(wǎng)絡(luò)預(yù)測輸出
y = tf.constant([1,2,2,0])#真實(shí)標(biāo)簽
y = tf.one_hot(y,depth=10)#one-hot編碼
loss = keras.losses.mse(y,out)#計(jì)算每個樣本的誤差
loss = tf.reduce_mean(loss)#計(jì)算平均誤差
print(float(loss))
1.067490816116333
除了希望獲得張量的最值信息讨韭,還希望獲得最值所在的索引號。
通過 tf.argmax(x, axis)癣蟋,tf.argmin(x, axis) 可以求解在 axis 軸上透硝,x的最大值、最小值所在的索引號疯搅。
out = tf.random.normal([2,10])#網(wǎng)絡(luò)輸出值
out = tf.nn.softmax(out,axis=1)#通過softmax轉(zhuǎn)換為概率值
pred = tf.argmax(out,axis=1)#求出最值所在的索引號
print(pred)
tf.Tensor([7 3], shape=(2,), dtype=int64)
3.張量比較操作
為了計(jì)算分類任務(wù)的準(zhǔn)確率等指標(biāo)濒生,一般需要將預(yù)測結(jié)果和真實(shí)標(biāo)簽比較,統(tǒng)計(jì)比較結(jié)果中正確的數(shù)量來計(jì)算準(zhǔn)確率秉撇。
下面看一個示例:
out = tf.random.normal([100,10])
out = tf.nn.softmax(out,axis=1)#輸出轉(zhuǎn)換為概率
pred = tf.argmax(out,axis=1)#選取預(yù)測值
pred = tf.cast(pred,dtype=tf.int32)
#print(pred)
y = tf.random.uniform([100],maxval=10,dtype=tf.int32)#真實(shí)標(biāo)簽
out = tf.equal(pred,y)#預(yù)測值與真實(shí)值比較
out = tf.cast(out,dtype=tf.int32)
#print(out)
correct = tf.reduce_sum(out)#計(jì)算正確個數(shù)
print(float(correct)/100)
0.12
可以看到甜攀,我們隨機(jī)產(chǎn)生的預(yù)測數(shù)據(jù)的準(zhǔn)確度是12%,這也是隨機(jī)預(yù)測模型的正常水平琐馆。
4.填充與復(fù)制操作
填充操作可以通過 tf.pad(x, paddings) 函數(shù)實(shí)現(xiàn),paddings 是包含了多個[left padding, right padding]的嵌套方案 list恒序,如[[0,0],[2,1],[1,2]]表示第一個維度不填充瘦麸,第二個維度左起(起始處)填充兩個單元,右邊(結(jié)束處)填充一個單元歧胁,第三個維度左邊填充一個單元滋饲,右邊填充兩個單元。
下面看使用示例:
a = tf.constant([1,2,3,4,5,6])
b = tf.constant([7,8,1,6])
b = tf.pad(b, [[0,2]])#填充操作
print(b)
tf.Tensor([7 8 1 6 0 0], shape=(6,), dtype=int32)
out = tf.stack([a,b],axis=0)#填充后喊巍,合并兩個張量
print(out)
tf.Tensor(
[[1 2 3 4 5 6]
[7 8 1 6 0 0]], shape=(2, 6), dtype=int32)
下面再看一例:
total_words = 10000 #設(shè)定詞匯量大小
max_review_len = 80 #最大句子長度
embedding_len = 100 #詞向量長度
#加載IMDB數(shù)據(jù)集
(x_train,y_train),(x_test,y_test) = keras.datasets.imdb.load_data(num_words = total_words)
#將句子截?cái)嗷蛱畛涞较嗤L度屠缭,設(shè)置為末尾填充和末尾截?cái)喾绞絉
x_train = keras.preprocessing.sequence.pad_sequences(x_train,maxlen=max_review_len,truncating='post',padding='post')
x_test = keras.preprocessing.sequence.pad_sequences(x_test,maxlen=max_review_len,truncating='post',padding='post')
print(x_train.shape,x_test.shape)
(25000, 80) (25000, 80)
上述代碼中,我們將句子的最大長度 max_review_len 設(shè)置為80個單詞崭参,通過keras.preprocessing.sequence.pad_sequences 可以快速完成句子的填充和截?cái)喙ぷ鳌?/p>
下面我們介紹對多個維度進(jìn)行填充的例子呵曹。
x = tf.random.normal([4,28,28,3])
print('before: ',x.shape)
x = tf.pad(x,[[0,0],[2,2],[2,2],[0,2]])#對第二維,第三維進(jìn)行填充
print('after:',x.shape)
before: (4, 28, 28, 3)
after: (4, 32, 32, 5)
復(fù)制操作通過 tf.tile 函數(shù)可以對長度為1的維度進(jìn)行復(fù)制若干份何暮,也可以對任意長度的維度進(jìn)行復(fù)制若干份奄喂。
下面看使用示例:
x = tf.random.normal([4,32,32,3])
x = tf.tile(x,[2,3,3,1])#進(jìn)行復(fù)制操作
print(x.shape)
(8, 96, 96, 3)
5.數(shù)據(jù)限幅操作
通過 tf.maximum(x,a) 實(shí)現(xiàn)數(shù)據(jù)的下限幅,通過 tf.minimum(x,a)實(shí)現(xiàn)數(shù)據(jù)的上限幅海洼。
使用示例如下:
x = tf.range(9)
print(x)
x = tf.maximum(x,2)#下限幅2
print(x)
tf.Tensor([0 1 2 3 4 5 6 7 8], shape=(9,), dtype=int32)
tf.Tensor([2 2 2 3 4 5 6 7 8], shape=(9,), dtype=int32)
x = tf.range(9)
print(x)
x = tf.minimum(x,7)# 上限幅7
print(x)
tf.Tensor([0 1 2 3 4 5 6 7 8], shape=(9,), dtype=int32)
tf.Tensor([0 1 2 3 4 5 6 7 7], shape=(9,), dtype=int32)
更方便地跨新,我們可以使用 tf.clip_by_value 實(shí)現(xiàn)上下限幅。
x = tf.range(9)
x = tf.clip_by_value(x,2,7)#實(shí)現(xiàn)上下限幅
print(x)
tf.Tensor([2 2 2 3 4 5 6 7 7], shape=(9,), dtype=int32)
6. Tensorflow的高級操作
6.1 tf.gather操作
tf.gather 可以實(shí)現(xiàn)根據(jù)索引號收集數(shù)據(jù)的目的坏逢∮蛘剩考慮班級成績冊例子赘被,共有4個班級,每個班級35個學(xué)生肖揣,8門科目帘腹,保存成績冊張量 shape 為 [4,35,8]。
下面看使用示例:
x = tf.random.uniform([4,35,8],maxval=100,dtype=tf.int32)
print(x.shape)
results = tf.gather(x,[0,1],axis=0)#在班級維度收集第1-2號班級成績冊
print(results.shape)
(4, 35, 8)
(2, 35, 8)
#收集第1,4,9,12,13,27號同學(xué)的成績
results = tf.gather(x,[0,3,8,11,12,26],axis=1)
print(results.shape)
(4, 6, 8)
#收集所有同學(xué)的第3许饿、5等科目成績
results = tf.gather(x,[2,4],axis=2)
print(results.shape)
(4, 35, 2)
通過上面的例子可以看到阳欲,tf.gather非常適合索引沒有規(guī)則的場合,其中索引號可以亂序排列陋率,此時收集的數(shù)據(jù)也是對應(yīng)順序球化。
看如下例子:
x = tf.range(8)
x = tf.reshape(x,[4,2])
print(x)
results = tf.gather(x,[3,1,0,2],axis=0)#收集第4,2,1,3號元素
print(results)
tf.Tensor(
[[0 1]
[2 3]
[4 5]
[6 7]], shape=(4, 2), dtype=int32)
tf.Tensor(
[[6 7]
[2 3]
[0 1]
[4 5]], shape=(4, 2), dtype=int32)
下面看一個問題,如果希望抽查第[2,3]班級的第[3,4,6,27]號同學(xué)的科目成績瓦糟,則可以通過組合多個tf.gather實(shí)現(xiàn)筒愚。
x = tf.random.uniform([4,35,8],maxval=100,dtype=tf.int32)
students = tf.gather(x,[1,2],axis=0)#收集第2,3號班級
print(students.shape)
results = tf.gather(students,[2,3,5,26],axis=1)#收集第3,4,6,27號同學(xué)
print(results.shape)
(2, 35, 8)
(2, 4, 8)
下面將問題再復(fù)雜一點(diǎn),如果希望抽查第2個班級的第2個同學(xué)的所有科目菩浙,第3個班級的第3個同學(xué)的所有科目巢掺,第4個班級的第4個同學(xué)的所有科目【Ⅱ撸可以怎么實(shí)現(xiàn)呢陆淀?
可以通過笨方式一個一個手動提取,然后合并數(shù)據(jù)先嬉。
x = tf.random.uniform([4,35,8],maxval=100,dtype=tf.int32)
results = tf.stack([x[1,1],x[2,2],x[3,3]],axis=0)
print(results.shape)
(3, 8)
雖然這種方法可以得到正確的結(jié)果轧苫,但是效率很低,下面來看一種更加高效的方法疫蔓。
6.2 tf.gather_nd 操作
通過 tf.gather_nd含懊,可以通過指定每次采樣的坐標(biāo)來實(shí)現(xiàn)多個采樣的目的。
在上面的那個問題中衅胀,我們可以將這個采樣方案合并為一個list參數(shù):[[1,1],[2,2],[3,3]]岔乔,通過 tf.gather_nd 實(shí)現(xiàn)如下。
x = tf.random.uniform([4,35,8],maxval=100,dtype=tf.int32)
print(x.shape)
results = tf.gather_nd(x,[[1,1],[2,2],[3,3]])
print(results.shape)
(4, 35, 8)
(3, 8)
可以看到滚躯,這種實(shí)現(xiàn)方式更加簡潔雏门,計(jì)算效率大大提升。
results = tf.gather_nd(x,[[1,1,2],[2,2,3],[3,3,4]])
print(results.shape)
(3,)
上述代碼中哀九,我們抽出了班級1剿配,學(xué)生1的科目2;班級2阅束,學(xué)生2的科目3呼胚;班級3,學(xué)生3的科目4的成績息裸,共有3個成績數(shù)據(jù)蝇更,結(jié)果匯總為一個shape為[3]的張量沪编。
6.3 tf.boolean_mask 操作
除了可以通過給定索引號的方式采樣,還可以通過給定掩碼(mask)的方式采樣年扩。通過 tf.boolean_mask(x, mask, axis)可以在 axis 軸上根據(jù) mask 方案進(jìn)行采樣蚁廓。
下面看使用示例:
x = tf.random.uniform([4,35,8],maxval=100,dtype=tf.int32)
print(x.shape)
results = tf.boolean_mask(x,[True,False,False,True],axis=0)#采樣第一個和第四個數(shù)據(jù)
print(results.shape)
(4, 35, 8)
(2, 35, 8)
注意掩碼的長度必須與對應(yīng)維度的長度一致。
現(xiàn)要我們來對比一下tf.gather_nd 和 tf.boolean_mask的用法厨幻。
x = tf.random.uniform([2,3,8],maxval=100,dtype=tf.int32)
print(x.shape)
#采集第一個班級的第一和第二個學(xué)生的成績相嵌。第二個班級的第二和第三個學(xué)生的成績。
results = tf.gather_nd(x,[[0,0],[0,1],[1,1],[1,2]])#多維座標(biāo)采集
print(results.shape)
(2, 3, 8)
(4, 8)
#使用掩碼來實(shí)現(xiàn)上述方案
results = tf.boolean_mask(x,[[True,True,False],[False,True,True]])
print(results.shape)
(4, 8)
可以看到况脆,掩碼采樣的結(jié)果與tf.gather_nd的結(jié)果完全一樣饭宾。可見格了,tf.boolean_mask既可以實(shí)現(xiàn) tf.gather方式的一維掩碼采樣看铆,又可以實(shí)現(xiàn) tf.gather_nd 方式的多維掩碼采樣。
6.4 tf.where 操作
通過tf.where(cond,a,b)操作可以根據(jù)cond條件的真假從a 或 b中讀取數(shù)據(jù)盛末。
下面看使用示例:
a = tf.ones([3,3])# 構(gòu)造 a 為全1
b = tf.zeros([3,3])# 構(gòu)造 b 為全0
cond = tf.constant([[True,False,False],[False,True,False],[False,True,True]])
results = tf.where(cond,a,b)#根據(jù)條件從 a,b 中采樣
print(results)
tf.Tensor(
[[1. 0. 0.]
[0. 1. 0.]
[0. 1. 1.]], shape=(3, 3), dtype=float32)
這個例子可以看到弹惦,返回的張量中為 1 的位置來自張量a,返回的張量中為0的位置來自張量b悄但。
當(dāng)a=b=None 即a,b參數(shù)不指定時棠隐,tf.where會返回 cond 張量中所有True的元素的索引座標(biāo)。
看如下示例:
results = tf.where(cond)#返回cond 中True無素的索引座標(biāo)
print(results)
tf.Tensor(
[[0 0]
[1 1]
[2 1]
[2 2]], shape=(4, 2), dtype=int64)
那么這個操作有什么作用呢算墨,考慮一個例子宵荒,我們需要提取張量中所有正數(shù)的數(shù)據(jù)和索引。
= tf.random.normal([3,3])#構(gòu)造 a
mask = a > 0#通過比較運(yùn)算净嘀,得到正數(shù)的掩碼
indices = tf.where(mask)#提取此掩碼處True元素的索引座標(biāo)
results = tf.gather_nd(a,indices)#拿到索引后,即可提取出所有正數(shù)
print(results)
tf.Tensor([0.11021124 0.553751 1.3484484 1.2101223 ], shape=(4,), dtype=float32)
results = tf.boolean_mask(a,mask)#通過掩碼提取正數(shù)侠讯,和tf.gather_nd的結(jié)果是一致的
print(results)
tf.Tensor([0.11021124 0.553751 1.3484484 1.2101223 ], shape=(4,), dtype=float32)
6.5 scatter_nd 操作
通過 tf.scatter_nd(indices,updates,shapes)可以高效地刷新張量的部分?jǐn)?shù)據(jù)挖藏,但是只能在全0張量的白板上面刷新。
下面看使用示例:
indices = tf.constant([[4],[3],[1],[7]])#構(gòu)造需要刷新數(shù)據(jù)的位置
updates = tf.constant([4.4,3.3,1.1,7.7])#構(gòu)造需要寫入的數(shù)據(jù)
#在長度為8的全0向量上根據(jù)indices寫入updates數(shù)據(jù)
tf.scatter_nd(indices,updates,[8])
<tf.Tensor: shape=(8,), dtype=float32, numpy=array([0. , 1.1, 0. , 3.3, 4.4, 0. , 0. , 7.7], dtype=float32)>
下面考慮3維張量的刷新例子:
indices = tf.constant([[1],[3]])#構(gòu)造寫入位置
updates = tf.constant([[[5,5,5,5],[6,6,6,6],[7,7,7,7],[8,8,8,8]],
[[1,1,1,1],[2,2,2,2],[3,3,3,3],[4,4,4,4]]])#構(gòu)造刷新數(shù)據(jù)
#在shape為[4,4,4]的白板上根據(jù)indices寫入updates數(shù)據(jù)
results = tf.scatter_nd(indices,updates,[4,4,4])
print(results)
tf.Tensor(
[[[0 0 0 0]
[0 0 0 0]
[0 0 0 0]
[0 0 0 0]]
[[5 5 5 5]
[6 6 6 6]
[7 7 7 7]
[8 8 8 8]]
[[0 0 0 0]
[0 0 0 0]
[0 0 0 0]
[0 0 0 0]]
[[1 1 1 1]
[2 2 2 2]
[3 3 3 3]
[4 4 4 4]]], shape=(4, 4, 4), dtype=int32)
根據(jù)打印數(shù)據(jù)可以看到厢漩,數(shù)據(jù)被刷新到第2,4個通道上膜眠。