日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > python >内容正文

python

python实现神经网络的正向传播(fp)函数_如何自己从零实现一个神经网络?

發(fā)布時(shí)間:2025/4/16 python 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python实现神经网络的正向传播(fp)函数_如何自己从零实现一个神经网络? 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

我是從高三開始入門的,一直是用C++來做神經(jīng)網(wǎng)絡(luò)。從造輪子開始,到實(shí)現(xiàn)模型,到封裝模型,再到真正用seq2vec,seq2seq模型訓(xùn)練成功一些小玩意,所有的東西都是自己寫的,但是資料都是從網(wǎng)上搜,學(xué)習(xí)也遇到好多坑。我將會(huì)在這里具體說說造輪子的過程。

造輪子之前,必須要廣泛查閱資料,自己推導(dǎo)前向傳播和反向傳播的所有過程,這個(gè)過程需要的知識(shí)點(diǎn)是偏導(dǎo)數(shù)以及鏈?zhǔn)椒▌t,高中生其實(shí)是可以理解的(將偏導(dǎo)數(shù)理解為對(duì)多元函數(shù)中其中一個(gè)變量求導(dǎo),把其他所有量都看作常數(shù)即可),不過稍微有點(diǎn)困難,這直接導(dǎo)致了我高三其實(shí)學(xué)的一知半解。

首先入門實(shí)現(xiàn)要從最簡單的BP開始,公式等內(nèi)容不再一一贅述,網(wǎng)上有非常多的資料,個(gè)人草稿紙上推導(dǎo)的內(nèi)容也早就丟失。確認(rèn)理解后,就可以著手用C++開始寫B(tài)P的輪子了。

首先是各種激勵(lì)函數(shù)。一開始你需要了解的激勵(lì)函數(shù)可能只有sigmoid,但是隨著學(xué)習(xí)深入,了解其他的激勵(lì)函數(shù)很有必要。

在激勵(lì)函數(shù)方面需要寫的是這個(gè)函數(shù)本身,以及它的導(dǎo)函數(shù)。sigmoid和tanh,elu等激勵(lì)函數(shù)需要用到exp()函數(shù),這個(gè)函數(shù)需要cmath頭文件。IDE自選吧,怎么方便怎么來,我高中參加NOIP用Dev,一直到現(xiàn)在我才改用VScode(這個(gè)只能算工作臺(tái))。sigmoidtanh

tanh其實(shí)在cmath庫里就有寫好的函數(shù),可以直接調(diào)用的。ReLU

relu用三目運(yùn)算符就很舒服。elu和leaky relu也可以這么玩兒。不過我比較推薦leaky relu吧,因?yàn)閞elu很容易出現(xiàn)神經(jīng)元死亡的情況(神經(jīng)元無論接受什么樣的數(shù)據(jù),其總和都是負(fù)值,那么這個(gè)神經(jīng)元沒有任何輸出,在反向過程中也無法進(jìn)行權(quán)值更新,具體看自己推導(dǎo))。

以上是激勵(lì)函數(shù)的一些例子。下面是寫神經(jīng)元的例子。

寫神經(jīng)元的話,你可以單純只用二維數(shù)組來寫,直接做矩陣運(yùn)算,但是當(dāng)時(shí)我沒有接觸線性代數(shù),所以用了個(gè)非常直觀但是后期效率低下的方法。一般用struct

首先定義輸入神經(jīng)元個(gè)數(shù),隱藏層神經(jīng)元個(gè)數(shù),輸出神經(jīng)元個(gè)數(shù),分別對(duì)應(yīng)InputNum,HideNum,OutputNum。如果你覺得這個(gè)寫法有點(diǎn)長,可以縮成INUM,HNUM,ONUM等等,怎么舒服怎么寫。

然后用struct來分別定義隱藏層神經(jīng)元,輸出層神經(jīng)元。顯然這個(gè)用C++的class有點(diǎn)大炮打蚊子的感覺,當(dāng)然還是那一句,怎么舒服怎么寫。如果你覺得我這么寫有點(diǎn)重復(fù)的內(nèi)容,可以使用template。用template靈活編寫,還可復(fù)用

然后bp必須要有的一些變/常量有:

常量const double learningrate,大小自己定

input[INUM]用于存儲(chǔ)輸入數(shù)據(jù)(單個(gè)batch),expect[ONUM]用于存儲(chǔ)期望數(shù)據(jù)(單個(gè)batch)

error,sigma_error,分別用于記錄單個(gè)batch的誤差,以及對(duì)所有batch的誤差求和,記得初始化為一個(gè)很大的值,這是進(jìn)入訓(xùn)練循環(huán)的必要條件。

hidden[HNUM],output[ONUM]這兩個(gè)是結(jié)構(gòu)體數(shù)組,之后在寫前向和反向過程中是必要的。

以上所有變量都是全局變量。最基本的需要的函數(shù)

TxtCheck()用于檢查神經(jīng)網(wǎng)絡(luò)的數(shù)據(jù)是否存在,不存在的話進(jìn)行INIT()并且輸出數(shù)據(jù)保存為一個(gè)文件。INIT()即為初始化函數(shù)。INIT函數(shù),第一句先確定隨機(jī)數(shù)種子

INIT()函數(shù)首先要先開始生成隨機(jī)數(shù),srand()加上unsigned (time(NULL))很不錯(cuò),需要頭文件ctime。在下面的語句里,對(duì)每個(gè)神經(jīng)元的weight和bia進(jìn)行初始化,具體內(nèi)容按照需求來,這個(gè)自己可以靈活編輯,我這里只給個(gè)大框架。

Datain和Dataout不多說了,顯而易見是進(jìn)行數(shù)據(jù)的輸出和讀取用的,用于在幾個(gè)epoch后保存數(shù)據(jù)文件,避免下一次打開的時(shí)候重復(fù)訓(xùn)練。

用ifstream和ofstream進(jìn)行讀取和輸出,需要頭文件fstream。上圖雙引號(hào)內(nèi)填文件名(文件在該文件夾下)或者絕對(duì)地址(文件在其他地方)。

Mainwork()函數(shù)用于讀取訓(xùn)練集,首先sigma_error=0,接著依次循環(huán)對(duì)訓(xùn)練集中每個(gè)batch進(jìn)行處理,讀入input和expect中,然后調(diào)用Calc()函數(shù)進(jìn)入前向傳播階段,再調(diào)用ErrorCalc()進(jìn)入本次誤差計(jì)算階段,error在本次計(jì)算中被賦值。然后進(jìn)行Training()反向傳播,接著sigma_error+=error。Mainwork預(yù)覽

Calc()里面進(jìn)行前向傳播,基本上都是循環(huán),不用我多說了吧?

ErrorCalc()也是如此,同理Training()也是,所以寫的這些東西里面,占了絕大多數(shù)的語句都是循環(huán)語句。*0.5比/2快不少哦,尤其是需要很多步驟的時(shí)候

把整個(gè)過程都寫下來啦。。。這是我個(gè)人喜好的寫法,要是覺得看不明白或者覺得效率很低,也可以自己寫的,反正能實(shí)現(xiàn)功能是關(guān)鍵!

(有個(gè)小trick上圖沒體現(xiàn)出來,一般bia的增量是2*learningrate*diff,親測(cè)效果不錯(cuò))

main里面基本上寫一些調(diào)用的內(nèi)容

然后在C++里面,如果數(shù)據(jù)里出現(xiàn)了Inf,很有可能下面會(huì)出現(xiàn)NaN,然后循環(huán)會(huì)被動(dòng)停止,給你輸出含有一堆NaN的垃圾數(shù)據(jù),為了避免這個(gè),C++其實(shí)是有一個(gè)宏可以檢測(cè)Inf和NaN的。

isnan()和isinf()是cmath/math.h庫里的宏,可以直接調(diào)用來判斷

到這里,我已經(jīng)把寫簡單BP的訣竅說完了,如果你想寫深度的,框架其實(shí)也差不多。以后我可能會(huì)更新的內(nèi)容里面也基本上都是建立在這個(gè)框架體系之上的,希望能有所幫助。即使你可能不太能接受我這種不用矩陣運(yùn)算的寫法,但這也是一個(gè)用C++造輪子從零開始的例子,希望能給予你鼓勵(lì)。

下面貼個(gè)代碼,當(dāng)然不能直接復(fù)制了用,要自己修改的哦

#include#include#include#define INUM 2#define HNUM 5#define ONUM 2using namespace std;

template

struct neuron

{

double w[NUM],bia,diff;

double in,out;

};

neuron hide[HNUM];

neuron output[ONUM];

const double learningrate=0.1;

double input[INUM];

double expect[ONUM];

double sigma_error=1e8;

double error=1e8;

double sigmoid(double x)

{

return 1.0/(1.0+exp(-x));

}

double diffsigmoid(double x)

{

x=1.0/(1.0+exp(-x));

return x*(1-x);

}

double tanh(double x)

{

return (exp(x)-exp(-x))/(exp(x)+exp(-x));

}

double difftanh(double x)

{

x=tanh(x);

return 1-x*x;

}

double relu(double x)

{

return x>0? x:0;

}

double diffrelu(double x)

{

return x>0? 1:0;

}

void TxtCheck();

void INIT();

void Datain();

void Dataout();

void Mainwork();

void Calc();

void ErrorCalc();

void Training();

int main()

{

int epoch=0;

TxtCheck();

while(sigma_error>0.001)

{

epoch++;

Mainwork();

if(epoch%(一個(gè)數(shù))==0)

Dataout();

//也可以寫其他操作}

Dataout();

return 0;

}

void INIT()

{

srand(unsigned(time(NULL)));

/*statement*/

return;

}

void Datain()

{

ifstream fin(" ");

fin>>...

fin.close();

}

void Dataout()

{

ofstream fout(" ");

fout<<...>

fout.close();

}

void Mainwork()

{

ifstream fin("數(shù)據(jù)集");

sigma_error=0;

for(int b=0;b

{

/*處理batch數(shù)據(jù),讀入input和expect*/

Calc();

ErrorCalc();

Training();

sigma_error+=error;

}

fin.close();

return;

}

void Calc()

{

for(int i=0;i

{

hide[i].in=0;

hide[i].in+=hide[i].bia;

for(int j=0;j

hide[i].in+=input[j]*hide[i].w[j];

hide[i].out=sigmoid(hide[i].in);

}

/*other statements*/

}

void ErrorCalc()

{

double trans;

error=0;

for(int i=0;i

{

trans=output[i].out-expect[i];

error+=trans*trans;

}

error*=0.5;

}

void Training()

{

for(int i=0;i

output[i].diff=(expect[i]-output[i].out)*diffsigmoid(output[i].in);

//負(fù)號(hào)直接舍棄,因?yàn)檎麄€(gè)傳遞過程這里的負(fù)號(hào)不帶來影響//而且在最后更新數(shù)據(jù)的時(shí)候也不需要再*(-1)for(int i=0;i

{

hide[i].diff=0;

for(int j=0;j

hide[i].diff+=output[j].diff*output[j].w[i];

hide[i].diff*=diffsigmoid(hide[i].in);

}

for(int i=0;i

{

output[i].bia+=learningrate*output[i].diff;

for(int j=0;j

output[i].w[j]+=learningrate*output[i].diff*hide[j].out;

}

for(int i=0;i

{

hide[i].bia+=learningrate*hide[i].diff;

for(int j=0;j

hide[i].w[j]+=learningrate*hide[i].diff*input[j];

}

return;

}

2019/3/14 21:59更新AutoEncoder

最近進(jìn)軍深度學(xué)習(xí),少不了自動(dòng)編碼器,于是在LSTM的seq2seq模型上加入了AutoEncoder部分,由于初期的架構(gòu),循環(huán)很多,代碼量很大,不過可以從以前的代碼里復(fù)制,然后微微修改,再粘貼下來,等到有空之后,我會(huì)把自己RNN和LSTM的東西也分享分享的。

2019/3/15更新

功能函數(shù)的大體結(jié)構(gòu)都如之前寫的那樣,現(xiàn)在講述的都是其他一些神經(jīng)元單元的設(shè)計(jì)和使用。我是做NLP自然語言處理的,自然語言處理必然少不了RNN,LSTM,GRU這些基本單元,那么按照上面的思路,RNN和LSTM的寫法應(yīng)該不難得出,不過變成了下面這樣:

#define MAXTIME 100

template

struct rnn_neuron

{

double wi[InputNum],wh[HideNum];

double bia,diff[Maxtime];

double in[Maxtime],out[Maxtime];

};

template

struct nor_neuron

{

double w[InputNum],bia,diff[Maxtime];

double in[Maxtime],out[Maxtime];

};

const double learningrate=0.1;

rnn_neuron hide[HNUM];

nor_neuron output[ONUM];

double input[INUM][MAXTIME];

double expect[ONUM][MAXTIME];

double sigma_error=1e8;

double error=1e8;

可以看出來出現(xiàn)了MAXTIME這個(gè)東西,這個(gè)輔助量是用于記錄時(shí)間序列中每個(gè)時(shí)間刻的數(shù)據(jù)的,因?yàn)槊總€(gè)數(shù)據(jù)在最后BPTT的過程中都是必需的。rnn中的wi是對(duì)輸入端的權(quán)重,wh是對(duì)前一時(shí)間刻隱藏層輸出的權(quán)重。

但是這樣寫還有個(gè)缺陷。struct中diff是記錄這個(gè)單元在t時(shí)刻的訓(xùn)練增量的,顯然如果直接遍歷所有時(shí)間,把增量依次賦給數(shù)據(jù)是不太行的。因?yàn)槊總€(gè)時(shí)間刻內(nèi),增量可能數(shù)量級(jí)很小很小,甚至有可能到1e-8以及更小(在非常長的時(shí)間序列下,可以到1e-20的級(jí)別),直接賦給數(shù)據(jù),就相當(dāng)于給數(shù)據(jù)加上了0,丟失了精度。

舉個(gè)例子:double x=0.1,y=1e-10;

x+y后,得出的結(jié)果仍然是0.1,顯然是丟失了精度。

那么為了避免出現(xiàn)這個(gè)問題,我們還需要再加上一個(gè)sigmadiff用于把所有時(shí)間刻的diff累加起來一起賦給數(shù)據(jù)。不過這樣做的話,就要對(duì)每個(gè)時(shí)間下的每個(gè)數(shù)據(jù)(包括權(quán)重)做sigmadiff了,因?yàn)橐婚_始求的diff是對(duì)bia的偏導(dǎo)數(shù),如果直接全部加起來,獲得的sigmadiff僅僅是對(duì)bia的sigmadiff。

于是

template

struct rnn_neuron

{

double wi[InputNum],wh[HideNum],sigmawi[InputNum],sigmawh[HideNum];

double bia,diff[Maxtime],sigmabia;

double in[Maxtime],out[Maxtime];

};

template

struct nor_neuron

{

double w[InputNum],bia,diff[Maxtime],sigmaw[InputNum],sigmabia;

double in[Maxtime],out[Maxtime];

};

就變成了這樣。

那么同理,lstm是一樣的思路,不過數(shù)據(jù)更加多,而且隨著數(shù)據(jù)量增加,訓(xùn)練速度也明顯會(huì)變得非常慢(真的非常顯著的變化!)

template

struct LSTM_neuron

{

double cell[Maxtime];

double out[Maxtime];

double fog_in[Maxtime],fog_out[Maxtime],fog_bia,fog_wi[InputNum],fog_wh[HideNum],fog_diff[Maxtime];

double sig_in[Maxtime],sig_out[Maxtime],sig_bia,sig_wi[InputNum],sig_wh[HideNum],sig_diff[Maxtime];

double tan_in[Maxtime],tan_out[Maxtime],tan_bia,tan_wi[InputNum],tan_wh[HideNum],tan_diff[Maxtime];

double out_in[Maxtime],out_out[Maxtime],out_bia,out_wi[InputNum],out_wh[HideNum],out_diff[Maxtime];

double fog_transbia,fog_transwi[InputNum],fog_transwh[HideNum];

double sig_transbia,sig_transwi[InputNum],sig_transwh[HideNum];

double tan_transbia,tan_transwi[InputNum],tan_transwh[HideNum];

double out_transbia,out_transwi[InputNum],out_transwh[HideNum];

};

那么針對(duì)rnn和lstm的Calc()和Training()函數(shù)都要重新編寫哦!

接著就是利用這些單元來寫一些模型,然后對(duì)測(cè)試好的模型進(jìn)行封裝。

先拿一開始的BP做個(gè)例子吧。思想其實(shí)是很簡單的,BP的神經(jīng)元我們已經(jīng)有個(gè)一個(gè)struct來定義了。那么我們用這個(gè)struct做一個(gè)class,把一些函數(shù)也包含進(jìn)去。bp.h用于放template和class

/*bp.h header file by ValK*/

/* 2019/3/15 15:25 */

#ifndef __BP_H__#define __BP_H__#include #include #include #include #include #include using namespace std;

template

struct neuron

{

double w[NUM],bia,diff;

double in,out;

};

class ActivateFunction

{

public:

double sigmoid(double x)

{

return 1.0/(1.0+exp(-x));

}

double diffsigmoid(double x)

{

x=1.0/(1.0+exp(-x));

return x*(1-x);

}

double tanh(double x)

{

return (exp(x)-exp(-x))/(exp(x)+exp(-x));

}

double difftanh(double x)

{

x=tanh(x);

return 1-x*x;

}

double relu(double x)

{

return x>0? x:0;

}

double diffrelu(double x)

{

return x>0? 1:0;

}

};

ActivateFunction fun;

template

class bp_neural_network

{

private:

neuron hide[HNUM];

neuron output[ONUM];

double learningrate;

double input[INUM];

double expect[ONUM];

int batch_size;

double sigma_error;

double error;

public:

int epoch;

void TxtCheck()

{

if(!fopen("data.ai","r"))

{

INIT();

Dataout();

}

if(!fopen("trainingdata.txt","r"))

{

cout<

cout<

exit(0);

}

}

bp_neural_network()

{

epoch=0;

sigma_error=1e8;

error=1e8;

batch_size=1;

learningrate=0.01;

TxtCheck();

}

void SetBatch(int Batch)

{

batch_size=Batch;

return;

}

void INIT()

{

srand(unsigned(time(NULL)));

/*statement*/

return;

}

void Datain()

{

ifstream fin("data.ai");

/*statement*/

fin.close();

}

void Dataout()

{

ofstream fout("data.ai");

/*statement*/

fout.close();

}

void Mainwork()

{

ifstream fin("trainingdata.txt");

sigma_error=0;

for(int b=0;b

{

/*處理batch數(shù)據(jù),讀入input和expect*/

Calc();

ErrorCalc();

Training();

sigma_error+=error;

}

fin.close();

return;

}

void Calc()

{

for(int i=0;i

{

hide[i].in=hide[i].bia;

for(int j=0;j

hide[i].in+=input[j]*hide[i].w[j];

hide[i].out=fun.sigmoid(hide[i].in);

}

for(int i=0;i

{

output[i].in=output[i].bia;

for(int j=0;j

output[i].in+=hide[j].out*output[i].w[j];

output[i].out=fun.sigmoid(output[i].in);

}

}

void ErrorCalc()

{

double trans;

error=0;

for(int i=0;i

{

trans=output[i].out-expect[i];

error+=trans*trans;

}

error*=0.5;

}

void Training()

{

for(int i=0;i

output[i].diff=(expect[i]-output[i].out)*fun.diffsigmoid(output[i].in);

//負(fù)號(hào)直接舍棄,因?yàn)檎麄€(gè)傳遞過程這里的負(fù)號(hào)不帶來影響//而且在最后更新數(shù)據(jù)的時(shí)候也不需要再*(-1)for(int i=0;i

{

hide[i].diff=0;

for(int j=0;j

hide[i].diff+=output[j].diff*output[j].w[i];

hide[i].diff*=fun.diffsigmoid(hide[i].in);

}

for(int i=0;i

{

output[i].bia+=learningrate*output[i].diff;

for(int j=0;j

output[i].w[j]+=learningrate*output[i].diff*hide[j].out;

}

for(int i=0;i

{

hide[i].bia+=learningrate*hide[i].diff;

for(int j=0;j

hide[i].w[j]+=learningrate*hide[i].diff*input[j];

}

return;

}

};

#endif

bpneuralnetwork這個(gè)template初始三個(gè)傳參便是建立一個(gè)網(wǎng)絡(luò)必須要的參數(shù),這種思想在寫其他template封裝時(shí)很重要。

neuron是struct單元,包括了基本bp神經(jīng)元需要的數(shù)據(jù),ActivateFunction類包括了一些需要使用的激勵(lì)函數(shù)。

省時(shí)間,一些函數(shù)的內(nèi)容就不多寫了。設(shè)計(jì)構(gòu)造函數(shù)的時(shí)候可以自己創(chuàng)新,想怎么寫怎么寫,我這里構(gòu)造函數(shù)先初始化了epoch,sigmerror,error,batch_size,還有l(wèi)earningrate。(直接把函數(shù)內(nèi)容寫class里面是被template逼的……教授要是看到了會(huì)罵死我)

Mainwork函數(shù)一般推薦你不要封裝進(jìn)去。。因?yàn)閎p可能會(huì)被用來處理各種各樣的問題,為了保證靈活性,Mainwork還是自己在外面寫吧,要什么功能再加進(jìn)去就是了。

寫個(gè)小bug(誤)來看看是否運(yùn)行正常:

沒有問題,因?yàn)槲覜]有訓(xùn)練集,所以在構(gòu)造函數(shù)里判斷出來了,直接退出了程序。

更新內(nèi)容基本結(jié)束~

2019.5.14更新

這次課設(shè)就寫了相關(guān)的代碼,不過和答案里提供的方法不太一樣,這個(gè)頭文件庫里面所有的網(wǎng)絡(luò)建立都是通過constructor傳參+內(nèi)存分配完成的,沒有使用template。https://github.com/ValKmjolnir/easyNLP

《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀

總結(jié)

以上是生活随笔為你收集整理的python实现神经网络的正向传播(fp)函数_如何自己从零实现一个神经网络?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。