近日在閱讀Social GAN文獻的實驗代碼,加深對模型的理解,發現源代碼的工程化很強,也比較適合構建實驗模型的學習,故細致閱讀。下文是筆者閱讀中一些要點總結,有關於pytorch
,也有關於模型自身的。
GPU -> CPU
SGAN的實驗代碼在工程化方面考慮比較充分,考慮到了在CPU和GPU兩種平台上模型的運行。原生平台是GPU,若要切換為CPU,需要做如下改動(目前只改動了訓練過程所需的,測試評估還未進行,但估計類似):
args.use_gpu
需要置為0,以保證int_dtype
和float_dtype
不是cuda。- 檢索
cuda()
,可以發現在model.py
還有些殘缺未考慮的cuda定義,使用torch.cuda.is_available()
判斷是否GPU可使用,只有可行采用cuda()
定義:
x = xxx()
if torch.cuda.is_available():
x = x.cuda()
池化層實現細節
Social GAN相較於Social LSTM提出了新的池化模型以滿足不同行人軌跡間信息共享與相互作用,具體有以下幾個方面的變動:
- Social GAN的池化頻率為一次,只在利用已知軌跡編碼后進行一次池化。(代碼中一個額外選項是在預測的每一步都進行池化)
- 池化范圍為全局而不是固定的范圍區間,代碼使用max pooling的手段使得在場景人數不確定的情況下可以保持數據維度固定。
- 池化輸入數據由兩方面組成:LSTMs的隱藏狀態+最后位置的相對信息。
而在代碼實現時,計算相對位置信息時顯得比較巧妙,例如在同場景的行人位置信息,代碼通過兩次不同的repeat
策略將原有N個人的位置信息重復N次,從而形成了[P0, P0, P0, ...] [P1, P1, P1, ...] ... 和 [P0, P1, P2, ...] [P0, P1, P2, ...] ..兩個矩陣,通過矩陣相減即可得到一個N*N行的矩陣,第\(i\)行是第\(i \% N\)個人相對於第\(i / N\)個人的相對位置。
curr_hidden = h_states.view(-1, self.h_dim)[start:end]
curr_end_pos = end_pos[start:end]
# Repeat -> [H1, H2, H3, ...][H1, H2, H3, ...]...
curr_hidden_1 = curr_hidden.repeat(num_ped, 1)
# Repeat position -> [P1, P2, P3, ...][P1, P2, P3, ...]...
curr_end_pos_1 = curr_end_pos.repeat(num_ped, 1)
# Repeat position -> [P1, P1, P1, ...][P2, P2, P2, ...]...
curr_end_pos_2 = self.repeat(curr_end_pos, num_ped)
# 得到行人的end_pos間的相對關系,並交給感知機去具體處理。
# 每個行人與其他行人的相對位置關系由num_ped項,合計有num_ped**2項。
curr_rel_pos = curr_end_pos_1 - curr_end_pos_2
curr_rel_embedding = self.spatial_embedding(curr_rel_pos)
# 拼接H_i和處理過的pos,放入多層感知機,最后經過maxPooling。
mlp_h_input = torch.cat([curr_rel_embedding, curr_hidden_1], dim=1)
curr_pool_h = self.mlp_pre_pool(mlp_h_input)
DataLoader相關
安利一個知乎,上面對使用Pytorch實現dataLoader解釋得很細致
dataLoader迭代器的數據格式
從Dataset
繼承而來的TrajectoryDataSet
對__get_item__
進行了重寫,以方便dataLoader
使用並整合,每次函數返回的是一個列表:
out = [
self.obs_traj[start:end, :], self.pred_traj[start:end, :],
self.obs_traj_rel[start:end, :], self.pred_traj_rel[start:end, :],
self.non_linear_ped[start:end], self.loss_mask[start:end, :]
]
return out
列表中有6個元素,以obs_traj
為例,其大小為[N][2][seq_len]
,但是在使用dataLoader
進行迭代時出現了這種形式,不僅一個batch中解壓得到的變為7個,而且obs_traj
的大小變為[seq_len][batch][2],
,順序發生了變化.
batch = [tensor.cuda() for tensor in batch]
(obs_traj, pred_traj_gt, obs_traj_rel, pred_traj_gt_rel, non_linear_ped,loss_mask, seq_start_end) = batch
Solution
- 問題主要是忽視了
DataLoader
中collate_fn
函數的作用,這個函數是在trajectory.py
中自定義的函數,主要作用時當dataLoader
收集到batch_size
個item
后形成一個列表,而后交由自定義的collate_fn
做預處理,處理后的數據就會被輸出為batch
。 seq_collate
解答了數據格式的兩個疑問,包括使用permute
和cat
函數。
從dataLoader獲取的batch數據的概念辨析
Solution
-
batch != batch_size
:- 模型注釋中有多處使用
batch
來表示張量格式,一個batch
的數據常常有batch_size
行,但在該模型中不成立。 - 嚴格來說,一個
batch
中有batch_size
個item
,但一個item
可以用多行表示,這就是該模型的數據特點,其在一個batch
中額外新增了seq_start_end
列表(len(seq_start_end) == batch_size
),使用該列表即可抽取出一個item
。
\[batch = \Sigma_{i=0}^{batch\_size-1}N_i (N_i \ge min\_peds) \]\(N_i\)表示一個場景下的行人個數。
- 模型注釋中有多處使用
-
一個batch中有多場景的行人軌跡數據
- LSTM編碼和譯碼:每個軌跡都是獨立的,此時可以整個batch一起處理。
- 池化:設計同一場景下各行人序列數據交互,需要使用
seq_start_end
划分場景分別計算。