以下是Euclidean loss layer的代碼分析,轉(zhuǎn)自:
https://blog.csdn.net/seashell_9/article/details/68064294
一. 前向函數(shù)
template <typename Dtype>
void EuclideanLossLayer<Dtype>::Forward_gpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
int count = bottom[0]->count(); //這里的count就是你的batchsize的大小
caffe_gpu_sub(
count,
bottom[0]->gpu_data(), //網(wǎng)絡的輸出值
bottom[1]->gpu_data(), //標簽值
diff_.mutable_gpu_data());//存儲bottom[0] - bottom[1]
Dtype dot;
caffe_gpu_dot(count, diff_.gpu_data(), diff_.gpu_data(), &dot);//做點乘運算
Dtype loss = dot / bottom[0]->num() / Dtype(2); //除以總數(shù)再除以2
top[0]->mutable_cpu_data()[0] = loss; //將loss值賦給輸出
}
1.Euclidean loss函數(shù)
首先明確caffe中Euclidean loss函數(shù):(多除了個2,,方便后面求梯度時剛好約掉)
其次是代碼里面一些變量的意義:
count: count其實等于num*channels*height*width,,也就是整個Blob元素的數(shù)量,但是因為此層中channels height width都為1,,所以這里的count()與num()實際上是相等的,,都代表輸入圖片的數(shù)量,也就是batchsize的大小,,也即公式里的N
bottom[0]->gpu_data(): 網(wǎng)絡的輸出值,,注意這個變量并不只是單個的輸出值,而是包含整個batchsize每張圖片的輸出值,,也就是一個含有N個元素的向量
bottom[1]->gpu_data():?真實標簽值,,也是個含有N個元素的向量
diff_.mutable_gpu_data(): 上述兩個向量做元素減法得到的向量,。這里說明一下為什么是diff_.mutable_gpu_data()而不是diff_.gpu_data(),,因為caffe定義了gpu_data()??為只讀變量,,而mutable_gpu_data()為可變變量,,也就是說讀操作用gpu_data(),寫操作用mutable_gpu_data()
dot: 對diff_.gpu_data()進行點乘運算得到的值(點乘運算得到的是一個數(shù)值)
top[0]->mutable_cpu_data()[0]:該層的輸出給下一層的變量,。這里top[0]的“0”指的是第一個輸出值(就像上面bottom[0]指第一個輸入值,,bottom[1]指第二個輸入值),由于這個層只有一個輸出值,,因此也就只有0這個索引
2.caffe_gpu_sub()函數(shù)
在math_function.cu里可以查到caffe_gpu_sub()函數(shù),,如下:
template <>
void caffe_gpu_sub<float>(const int N, const float* a, const float* b,
float* y) {
// NOLINT_NEXT_LINE(whitespace/operators)
sub_kernel<float><<<CAFFE_GET_BLOCKS(N), CAFFE_CUDA_NUM_THREADS>>>(
N, a, b, y);
}
可以看到,,該函數(shù)又調(diào)用了sub_kernel函數(shù):
template <typename Dtype>
__global__ void sub_kernel(const int n, const Dtype* a,
const Dtype* b, Dtype* y) {
CUDA_KERNEL_LOOP(index, n) {
y[index] = a[index] - b[index];
}
}
從這個函數(shù)就可以明白了,,caffe_gpu_sub()就是做了這樣一個運算:把a向量和b向量對應元素相減,,然后賦給y向量。放到我們Euclidean loss代碼里面,,也就是bottom[0]->gpu_data()(網(wǎng)絡輸出值)和bottom[1]->gpu_data()(真實標簽值)做對應元素相減,然后賦給diff_.mutable_gpu_data() 3.caffe_gpu_dot()函數(shù):
template <>
void caffe_gpu_dot<float>(const int n, const float* x, const float* y,
float* out) {
CUBLAS_CHECK(cublasSdot(Caffe::cublas_handle(), n, x, 1, y, 1, out));
}
可以看到調(diào)用了cublasSdot()函數(shù),在cuda文檔中看到該函數(shù)的作用:
即計算兩個輸入向量的點乘。最后就是除以2N了
二.反向函數(shù)
template <typename Dtype>
void EuclideanLossLayer<Dtype>::Backward_gpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {
for (int i = 0; i < 2; i) {
if (propagate_down[i]) { //對于輸入的第i個Blob propagate_dowm 為1(該變量即為該Blob輸入后是否要向前面的層提供反向傳播的梯度)
const Dtype sign = (i == 0) ? 1 : -1;
const Dtype alpha = sign * top[0]->cpu_diff()[0] / bottom[i]->num();
caffe_gpu_axpby(
bottom[i]->count(), // count
alpha, // alpha
diff_.gpu_data(), // a
Dtype(0), // beta
bottom[i]->mutable_gpu_diff()); // b
}
}
}
1.因為loss層沒有參數(shù),,所以求導時是對兩個輸入求偏導,,即:
Derive (X1) = [(x11-x21) … (x1n-x2n)]/N
Derive (X2) =?-[(x11-x21) … (x1n-x2n)]/N(注意前面有個負號)
所以代碼中for循環(huán)兩次就是分別對X1和X2求偏導
2.propagate_down[i]:這里兩個propagate_down都是1的,。如果要了解propagate_down的含義,可以看來自百度的一個描述:
caffe中怎么固定前面的網(wǎng)絡參數(shù),訓練后面層的參數(shù),??
這里面就用到了propagate_down,,?有兩種情況:比如有4個全連接層A->B->C->D?
a. 你希望C層的參數(shù)不會改變,C前面的AB層的參數(shù)也不會改變,,這種情況也就是D層的梯度不往前反向傳播到D層的輸入blob(也就是C層的輸出blob 沒有得到梯度),你可以通過設置D層的propagate_down為false來做到。?
propagate_down的數(shù)量與輸入blob的數(shù)量相同,,假如你某個層有2個輸入blob,,那么你應該在該layer的Param里面寫上兩行:?
propagate_down : 0 # 第1個輸入blob不會得到反向傳播的梯度?
propagate_down : 0 # 第2個輸入blob不會得到反向傳播的梯度?
這樣的話,,你這個layer的梯度就不會反向傳播啦,,前面的所有l(wèi)ayer的參數(shù)也就不會改變了?
b. 你希望C層的參數(shù)不會改變,,但是C前面的AB層的參數(shù)會改變,,這種情況,,只是固定了C層的參數(shù),,C層得到的梯度依然會反向傳播給前面的B層,。只需要將對應的參數(shù)blob的學習率調(diào)整為0:?
layer {
type: "InnerProduct"
param { # 對應第1個參數(shù)blob的配置,,也就是全連接層的參數(shù)矩陣的配置
lr_mult: 0 # 學習率為0,其他參數(shù)可以看caffe.proto里面的ParamSpec這個類型
}
param { # 對應第2個參數(shù)blob的配置,,也就是全連接層的偏置項的配置
lr_mult: 0 # 學習率為0
}
}
3.sign的作用:
? ?第一次求偏導是對X1,,前面不需要加負號,;第二次求偏導是對X2,,前面需要乘-1
4.top[0]->cpu_diff()[0]:
在反向傳播中,top代表從高一層反向傳過來的變量,所以top[0]->cpu_diff()表示從高一層傳過來的error,。但問題來了,這明明是loss層,,也就是最后一層,,為什么還有所謂的再高一層呢,?其實大家可以發(fā)現(xiàn),這里用的是top[0]->cpu_diff()[0],,而不是top[0]->cpu_diff(),。caffe中反向傳給低層error時其實用戶還可以給這個error乘以一個倍數(shù),這個倍數(shù)就存儲在top[0]->cpu_diff()的第一個元素,,也就是top[0]->cpu_diff()[0],。而用戶設置這個倍數(shù)則是通過在layer參數(shù)中添加loss_weight參數(shù)
layer {
name: "loss"
type: "SoftmaxWithLoss"
bottom: "pred"
bottom: "label"
top: "loss"
loss_weight: 1
}
默認的loss_weight都是1
參見:http:///questions/31099233/euclidean-loss-layer-in-caffe
5.caffe_gpu_axpby()函數(shù):
template <>
void caffe_gpu_axpby<float>(const int N, const float alpha, const float* X,
const float beta, float* Y) {
caffe_gpu_scal<float>(N, beta, Y); // Y = beta*Y
caffe_gpu_axpy<float>(N, alpha, X, Y);// Y = Y alpha*X
}
做的運算其實就是bottom[i]->mutable_gpu_diff() = alpha*diff.gpu_data() beta*bottom[i]->mutable_gpu_diff() =?alpha*diff.gpu_data() (因為beta = 0)
?
來源:http://www./content-1-27551.html
|