YOLOv2原始碼分析(三)
文章全部YOLOv2原始碼分析
接著上一講沒有講完的make_convolutional_layer
函式
0x01 make_convolutional_layer
//make_convolutional_layer
l.forward = forward_convolutional_layer;
l.backward = backward_convolutional_layer;
l.update = update_convolutional_layer;
- 1
- 2
- 3
- 4
上來就是三坐大山^_^,我們先從第一個forward_convolutional_layer
開始。
0x0101 forward_convolutional_layer
void forward_convolutional_layer(convolutional_layer l, network net)
{//傳入卷積層引數和網路的總引數
int i, j;
fill_cpu(l.outputs*l.batch, 0, l.output, 1);
- 1
- 2
- 3
- 4
- 5
看這個fill_cpu
函式
void fill_cpu(int N, float ALPHA, float *X, int INCX)
{
int i;
for(i = 0; i < N; ++i) X[i*INCX] = ALPHA;
}
- 1
- 2
- 3
- 4
- 5
輸入的引數N
表示一個batch
中所有的影象元素個數,x
指向n
對應大小分配的記憶體空間。整個函式來看就是對輸出影象元素的一個初始化操作。
接著看後面
//forward_convolutional_layer
if(l.xnor){
binarize_weights(l.weights, l.n, l.c/l.groups*l.size*l.size, l.binary_weights);
swap_binary(&l);
binarize_cpu(net.input, l.c*l.h*l.w*l.batch, l.binary_input);
net.input = l.binary_input;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
判斷是否二值化操作,如果是的話,其中有兩個關鍵的函式binarize_weights
和binarize_cpu
void binarize_weights(float *weights, int n, int size, float *binary)
{
int i, f;
for(f = 0; f < n; ++f){
float mean = 0;
for(i = 0; i < size; ++i){
mean += fabs(weights[f*size + i]);
}
mean = mean / size;
for(i = 0; i < size; ++i){
binary[f*size + i] = (weights[f*size + i] > 0) ? mean : -mean;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
第一個引數就是指向分配給weight
記憶體空間 的指標,第二引數是卷積核個數,第三個引數是一個卷積核weight
的個數(這裡應該使用l.nweights
/l.n
),第四個引數是指向分配給二值化weight
記憶體空間 的指標。舉個例子
假設有兩個2x2卷積核
n=2 size=4
權重值總共8個 1 2 3 4 5 6 7 8
第一次迴圈 f=0
mean = 1+2+3+4 = 10
mean/4 = 2.5
binary[0]=2.5 binary[1]=2.5 binary[2]=2.5 binary[3]=2.5
第二次迴圈 f=1
mean = 5+6+7+8 = 26
mean/4 = 6.5
binary[0]=6.5 binary[1]=6.5 binary[2]=6.5 binary[3]=6.5
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
接著看後面的swap_binary
函式
void swap_binary(convolutional_layer *l)
{
float *swap = l->weights;
l->weights = l->binary_weights;
l->binary_weights = swap;
#ifdef GPU
swap = l->weights_gpu;
l->weights_gpu = l->binary_weights_gpu;
l->binary_weights_gpu = swap;
#endif
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
函式的作用很明顯了,就要把以前的權重值替換二值化後的
接著binarize_cpu
函式
void binarize_cpu(float *input, int n, float *binary)
{
int i;
for(i = 0; i < n; ++i){
binary[i] = (input[i] > 0) ? 1 : -1;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
函式的第一個引數指向輸入影象記憶體空間的指標,函式第二個引數表示一個batch的影象元素個數,函式第三個引數指向分配給二值化input
記憶體空間 的指標。
函式很簡單,總體來看函式的作用就是出入影象的二值化。
最後將得到的二值化輸入影象賦值給原來的輸入影象。
我們接著回到forward_convolutional_layer
函式
//forward_convolutional_layer
int m = l.n/l.groups;//一個group的卷積核個數
int k = l.size*l.size*l.c/l.groups;//一個group的卷積核元素個數
int n = l.out_w*l.out_h;//一個輸出影象的元素個數
for(i = 0; i < l.batch; ++i){
for(j = 0; j < l.groups; ++j){
float *a = l.weights + j*l.nweights/l.groups;
float *b = net.workspace;
float *c = l.output + (i*l.groups + j)*n*m;
im2col_cpu(net.input + (i*l.groups + j)*l.c/l.groups*l.h*l.w,
l.c/l.groups, l.h, l.w, l.size, l.stride, l.pad, b);
gemm(0,0,m,n,k,1,a,k,b,n,1,c,n);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
這裡有兩個非常重要的函式im2col_cpu
和gemm
。先看第一個
0x0102 im2col_cpu && gemm
float im2col_get_pixel(float *im, int height, int width, int channels,
int row, int col, int channel, int pad)
{
row -= pad;
col -= pad;
if (row < 0 || col < 0 ||
row >= height || col >= width) return 0;
return im[col + width*(row + height*channel)];
}
//From Berkeley Vision's Caffe!
//https://github.com/BVLC/caffe/blob/master/LICENSE
void im2col_cpu(float* data_im,
int channels, int height, int width,
int ksize, int stride, int pad, float* data_col)
{
int c,h,w;
int height_col = (height + 2*pad - ksize) / stride + 1;//卷積後的高度
int width_col = (width + 2*pad - ksize) / stride + 1;//卷積後的寬度
int channels_col = channels * ksize * ksize;
for (c = 0; c < channels_col; ++c) {
int w_offset = c % ksize;
int h_offset = (c / ksize) % ksize;
int c_im = c / ksize / ksize;
for (h = 0; h < height_col; ++h) {
for (w = 0; w < width_col; ++w) {
int im_row = h_offset + h * stride;
int im_col = w_offset + w * stride;
int col_index = (c * height_col + h) * width_col + w;
data_col[col_index] = im2col_get_pixel(data_im, height, width, channels,
im_row, im_col, c_im, pad);
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
這個函式是參考了早期caffe中的設計,但是現在caffe好像有了新的做法。首先說說這個函式的引數
data_im
:指向輸入資料的指標channels
:一個卷積組的通道數height
:輸入影象的高width
:輸入影象的寬ksize
:卷積核的大小stride
:步長大小pad
:pad大小data_col
:指向資料轉化後的記憶體空間
這個函式比較複雜,還是舉個例子說明
我們假設輸入圖片大小3x3,pad=1,stride=2,卷積核大小3x3,channels=1
0 0 0 0 0
0 1 2 3 0
0 4 5 6 0
0 7 8 9 0
0 0 0 0 0
height_col = (3+2-3)/2+1 = 2
width_col = (3+2-3)/2+1 = 2
channels = 1*3*3 = 9
進入第一個迴圈c = 0
w_offset = 0
h_offset = 0
c_im = 0
h=0 w=0
im_row = 0
im_col = 0
col_index = 0
data_col[0] = 0
h=0 w=1
im_row = 0
im_col = 2
col_index = 1
data_col[1] = 0
...
data_col[2]=0 data_col[3]=5
data_col[4]=0 data_col[5]=0 data_col[6]=4 data_col[7]=6
...
0 0 0 0 0
0 1 2 3 0
0 4 5 6 0 ==> 0 0 0 5 0 0 4 6 0 0 5 0 0 2 0 8 1 3 7 9 2 0 8 0 0 5 0 0 4 6 0 0 5 0 0 0
0 7 8 9 0
0 0 0 0 0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
翻譯成人能看得懂的就是
0 0 0 5
0 0 4 6
0 0 5 0
0 2 0 8
1 3 7 9
2 0 8 0
0 5 0 0
4 6 0 0
5 0 0 0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
這個矩陣有什麼特殊的含義呢?
我們不難發現,這個矩陣的每一列就表示卷積核對應的一個小視窗,例如第一個視窗0 0 0 0 1 2 0 4 5
,很有意思是不是?
接著我們再來看看這個gemm
函式
void gemm(int TA, int TB, int M, int N, int K, float ALPHA,
float *A, int lda,
float *B, int ldb,
float BETA,
float *C, int ldc)
{
gemm_cpu( TA, TB, M, N, K, ALPHA,A,lda, B, ldb,BETA,C,ldc);
}
void gemm_cpu(int TA, int TB, int M, int N, int K, float ALPHA,
float *A, int lda,
float *B, int ldb,
float BETA,
float *C, int ldc)
{
//printf("cpu: %d %d %d %d %d %f %d %d %f %d\n",TA, TB, M, N, K, ALPHA, lda, ldb, BETA, ldc);
int i, j;
for(i = 0; i < M; ++i){
for(j = 0; j < N; ++j){
C[i*ldc + j] *= BETA;//因為前面的BETA是1,所以這裡我們也不關心了
}
}
if(!TA && !TB)
gemm_nn(M, N, K, ALPHA,A,lda, B, ldb,C,ldc);
else if(TA && !TB)
gemm_tn(M, N, K, ALPHA,A,lda, B, ldb,C,ldc);
else if(!TA && TB)
gemm_nt(M, N, K, ALPHA,A,lda, B, ldb,C,ldc);
else
gemm_tt(M, N, K, ALPHA,A,lda, B, ldb,C,ldc);
}
void gemm_nn(int M, int N, int K, float ALPHA,
float *A, int lda,
float *B, int ldb,
float *C, int ldc)
{
int i,j,k;
#pragma omp parallel for
for(i = 0; i < M; ++i){
for(k = 0; k < K; ++k){
register float A_PART = ALPHA*A[i*lda+k];
for(j = 0; j < N; ++j){
C[i*ldc+j] += A_PART*B[k*ldb+j];
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
由於gemm
前面傳入的引數是0,0
,所以我這裡只看gemm_nn
這個函式,其他函式操作相似,不再贅述。
我們還是先看看這個函式的引數
M
: A的行數N
: B的列數K
: A的列數ALPHA
:係數A
:指向矩陣a的指標lda
: a的列數B
:指向矩陣b的指標ldb
: b的列數C
:指向矩陣c的指標ldc
: c的列數
我們知道這裡A
就是輸入weight
的矩陣,B
就是我們前面im2col_cpu
中得到的輸出矩陣,C
用來儲存我們最後得到的矩陣(其實是一個數組,前面說的矩陣也是)。M
一個group的卷積核個數,K
一個group的卷積核元素個數,N
一個輸出影象的元素個數,lda
一個group的卷積核元素個數,ldb
一個輸出影象的元素個數,ldc
一個輸出影象的元素個數。
我們還是舉個例子說明
這裡我們假設卷積核還是3x3
權重矩陣A為
1 2 3
4 5 6 ==> 1 2 3 4 5 6 7 8 9(應該這樣寫)
7 8 9
B為
0 0 0 5
0 0 4 6
0 0 5 0
0 2 0 8
1 3 7 9
2 0 8 0
0 5 0 0
4 6 0 0
5 0 0 0
C初始化後為
1 1 1 1
M=1 K=9 N=4 lda=9 ldb=4 ldb=4
C[0]=ALPHA*A[0]*B[0]+ALPHA*A[1]*B[4]+...+ALPHA*A[8]*B[32]=95
C[1]=107
C[2]=107
C[3]=95
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
換成人能看懂的
B
[0 0 0 5
0 0 4 6
0 0 5 0
A 0 2 0 8 C C
[1 2 3 4 5 6 7 8 9] * 1 3 7 9 + [1 1 1 1]==> [95 107 107 95]
2 0 8 0
0 5 0 0
4 6 0 0
5 0 0 0]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
所以這兩個函式的意圖很明顯了,就是將卷積變換成了矩陣運算。一些有意思的數學技巧^_^!!!
最後簡要的提一下gemm_nn
、gemm_tn
、gemm_tt
、gemm_nt
他們之間的區別,他們的命名都是有意義的。這裡的n
指的是not transpose
而t
指的是transpose
。例如nn
就表示AB
都不轉置。
接著我們回到forward_convolutional_layer
函式
//forward_convolutional_layer
if(l.batch_normalize){
forward_batchnorm_layer(l, net);
- 1
- 2
- 3
這裡有出現一個有用的函式forward_batchnorm_layer
0x0103 forward_batchnorm_layer
void forward_batchnorm_layer(layer l, network net)
{
if(l.type == BATCHNORM) copy_cpu(l.outputs*l.batch, net.input, 1, l.output, 1);
copy_cpu(l.outputs*l.batch, l.output, 1, l.x, 1);
- 1
- 2
- 3
- 4
上來就是一個函式copy_cpu
void copy_cpu(int N, float *X, int INCX, float *Y, int INCY)
{
int i;
for(i = 0; i < N; ++i) Y[i*INCY] = X[i*INCX];
}
- 1
- 2
- 3
- 4
- 5
我們先看一下輸入的引數分別表示的是什麼意思。如果我們定義了BATCHNORM
,那麼這裡的N
表示一個batch
中的輸出引數個數,x
表示指向輸入引數的指標,y
表示指向輸出引數的指標。那函式的目的很簡單,將net
中的輸入,複製到layer
中的輸出;如果沒有定義BATCHNORM
,那麼將layer
中的輸出複製到layer
中的x
。接著看後面(可以參考這篇論文Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift)
//forward_batchnorm_layer
if(net.train){
mean_cpu(l.output, l.batch, l.out_c, l.out_h*l.out_w, l.mean);
variance_cpu(l.output, l.mean, l.batch, l.out_c, l.out_h*l.out_w, l.variance);
scal_cpu(l.out_c, .99, l.rolling_mean, 1);
axpy_cpu(l.out_c, .01, l.mean, 1, l.rolling_mean, 1);
scal_cpu(l.out_c, .99, l.rolling_variance, 1);
axpy_cpu(l.out_c, .01, l.variance, 1, l.rolling_variance, 1);
normalize_cpu(l.output, l.mean, l.variance, l.batch, l.out_c, l.out_h*l.out_w);
copy_cpu(l.outputs*l.batch, l.output, 1, l.x_norm, 1);
} else {
normalize_cpu(l.output, l.rolling_mean, l.rolling_variance, l.batch, l.out_c, l.out_h*l.out_w);
}
scale_bias(l.output, l.scales, l.batch, l.out_c, l.out_h*l.out_w);
add_bias(l.output, l.biases, l.batch, l.out_c, l.out_h*l.out_w);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
我準備把這幾個函式放在一塊解析,因為這幾個函式都不大。先看mean_cpu
void mean_cpu(float *x, int batch, int filters, int spatial, float *mean)
{
float scale = 1./(batch * spatial);//求分母
int i,j,k;
for(i = 0; i < filters; ++i){
mean[i] = 0;
for(j = 0; j < batch; ++j){
for(k = 0; k < spatial; ++k){
int index = j*filters*spatial + i*spatial + k;
mean[i] += x[index];
}
}
mean[i] *= scale;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
x
:指向layer
的輸出batch
:一個batch的大小filters
:輸出的影象通道數,在這裡同樣可以理解為卷積核個數spatial
:輸出圖片的大小mean
:指向儲存結果的指標
還是舉個例子
x [95 107 107 95 1 2 3 4]
batch = 1
filters = 2
spatial = 2x2 = 4
scale = 1/(1x4) = 0.25
第一次迴圈
i=0 j=0
mean[0]=0
k=0
index=0
mean[0]=0+x[0]=95
...
mean[0]=101 mean[1]=2.5
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
那麼這個函式的意義就很明晰了。它要求出的是不同通道下所有輸入影象的均值。對應BN論文中的這個公式
- 1m∑mi=1xi1m∑i=1mxi //mini-batch mean
接著看variance_cpu
函式
void variance_cpu(float *x, float *mean, int batch, int filters, int spatial, float *variance)
{
float scale = 1./(batch * spatial - 1);//注意這裡的減1操作
int i,j,k;
for(i = 0; i < filters; ++i){
variance[i] = 0;
for(j = 0; j < batch; ++j){
for(k = 0; k < spatial; ++k){
int index = j*filters*spatial + i*spatial + k;
variance[i] += pow((x[index] - mean[i]), 2);
}
}
variance[i] *= scale;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
x
:指向layer
的輸出指標mean
:指向上面函式輸出的均值batch
:batch
大小filters
:輸出的影象通道數,在這裡同樣可以理解為卷積核個數spatial
:輸出圖片的大小variance
:指向儲存結果的指標
舉個例子
x [95 107 107 95 1 2 3 4]
mean [101 25]
batch = 1
filters = 2
spatial = 2x2 = 4
scale = 1/(1x4 - 1)=0.333
i=0
variance[0]=0
j=0 k=0
index=0
variance[0] = 0+(95-101)^2
...
variance[0]=48 variance[1]=1.66666675
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
那麼這個函式的意義就很明晰了。它要求出的是不同通道下所有輸入影象的樣本方差(對於n個數據,如果n-1個確定了,那麼剩下的那個就確定了(前提知道均值,均值*n - (n-1)數))。對應BN論文中的這個公式
- 1m∑mi=1(xi−μβ)1m∑i=1m(xi−μβ) //mini-batch variance
接著看scal_cpu
函式
void scal_cpu(int N, float ALPHA, float *X, int INCX)
{
int i;
for(i = 0; i < N; ++i) X[i*INCX] *= ALPHA;
}
- 1
- 2
- 3
- 4
- 5
這個函式很簡單,就是將輸入的資料乘以一個係數。
接著看axpy_cpu
函式
void axpy_cpu(int N, float ALPHA, float *X, int INCX, float *Y, int INCY)
{
int i;
for(i = 0; i < N; ++i) Y[i*INCY] += ALPHA*X[i*INCX];
}
- 1
- 2
- 3
- 4
- 5
這個函式也很簡單,就是Y =ALPHA*X + Y
接著看normalize_cpu
這個函式
void normalize_cpu(float *x, float *mean, float *variance, int batch, int filters, int spatial)
{
int b, f, i;
for(b = 0; b < batch; ++b){
for(f = 0; f < filters; ++f){
for(i = 0; i < spatial; ++i){
int index = b*filters*spatial + f*spatial + i;
x[index] = (x[index] - mean[f])/(sqrt(variance[f]) + .000001f);
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
x
:layer
的輸出影象mean
:前面算的均值variance
:前面算的樣本方差batch
:batch
大小filters
:輸出的影象通道數,在這裡同樣可以理解為卷積核個數spatial
:輸出圖片的大小
還是舉個例子
x [95 107 107 95 1 2 3 4]
mean [101 25]
variance [48 1.66666675]
batch=1
filters=2
spatial = 2x2 = 4
進入第一層迴圈
b=0 f=0 i=0
index = 0
x[0] = (x[0]-m[0])/(sqrt(variance[0]) + 0.000001f) = -1.44
...
x[0]=-0.866025329 x[0]=0.866025329 x[0]=0.866025329 x[0]=-0.866025329
x[0]=-1.16189408 x[0]=-0.387298018 x[0]=0.387298018 x[0]=1.16189408
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
這個函式的作用就是一個歸一化處理。對應BN論文中的這個公式
- xi−μβσ2β+ϵ√xi−μβσβ2+ϵ //normalize
接著看scale_bias
和add_bias
函式
void scale_bias(float *output, float *scales, int batch, int n, int size)
{
int i,j,b;
for(b = 0; b < batch; ++b){
for(i = 0; i < n; ++i){
for(j = 0; j < size; ++j){
output[(b*n + i)*size + j] *= scales[i];
}
}
}
}
void add_bias(float *output, float *biases, int batch, int n, int size)
{
int i,j,b;
for(b = 0; b < batch; ++b){
for(i = 0; i < n; ++i){
for(j = 0; j < size; ++j){
output[(b*n + i)*size + j] += biases[i];
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
這兩個函式的意義都很簡單。對應BN論文中的這個公式
- γxi^+βγxi^+β
接著我們回到forward_convolutional_layer
函式
//forward_convolutional_layer
if(l.batch_normalize){
forward_batchnorm_layer(l, net);
} else {
add_bias(l.output, l.biases, l.batch, l.n, l.out_h*l.out_w);
}
activate_array(l.output, l.outputs*l.batch, l.activation);
if(l.binary || l.xnor) swap_binary(&l);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
如果沒有設定batch_normalize
,直接新增偏向就完事了。接著是activate_array
函式
void activate_array(float *x, const int n, const ACTIVATION a)
{
int i;
for(i = 0; i < n; ++i){
x[i] = activate(x[i], a);
}
}
float activate(float x, ACTIVATION a)
{
switch(a){
case LINEAR:
return linear_activate(x);
case LOGISTIC:
return logistic_activate(x);
case LOGGY:
return loggy_activate(x);
case RELU:
return relu_activate(x);
case ELU:
return elu_activate(x);
case RELIE:
return relie_activate(x);
case RAMP:
return ramp_activate(x);
case LEAKY:
return leaky_activate(x);
case TANH:
return tanh_activate(x);
case PLSE:
return plse_activate(x);
case STAIR:
return stair_activate(x);
case HARDTAN:
return hardtan_activate(x);
case LHTAN:
return lhtan_activate(x);
}
return 0;
}
static inline float linear_activate(float x){return x;}
static inline float logistic_activate(float x){return 1./(1. + exp(-x));}
static inline float loggy_activate(float x){return 2./(1. + exp(-x)) - 1;}
static inline float relu_activate(float x){return x*(x>0);}
static inline float elu_activate(float x){return (x >= 0)*x + (x < 0)*(exp(x)-1);}
static inline float relie_activate(float x){return (x>0) ? x : .01*x;}
static inline float ramp_activate(float x){return x*(x>0)+.1*x;}
static inline float leaky_activate(float x){return (x>0) ? x : .1*x;}
static inline float tanh_activate(float x){return (exp(2*x)-1)/(exp(2*x)+1);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20