Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
avaScript的Proxy對(duì)象是一種強(qiáng)大且靈活的特性,它允許你攔截并自定義對(duì)對(duì)象執(zhí)行的操作。自ECMAScript 6(ES6)引入以來,Proxy對(duì)象為控制對(duì)象的基本操作行為提供了一種機(jī)制,使高級(jí)用例和改進(jìn)的安全性成為可能。
一個(gè)Proxy是由兩個(gè)主要組件創(chuàng)建的:目標(biāo)對(duì)象和處理器。目標(biāo)對(duì)象是你想攔截操作的原始對(duì)象,處理器是一個(gè)包含名為陷阱的方法的對(duì)象,這些方法定義了這些操作的自定義行為。
創(chuàng)建一個(gè)Proxy
const targetObject={
name: 'John',
age: 25,
};
const handler={
get(target, prop) {
console.log(`獲取屬性 ${prop}`);
return target[prop];
},
};
const proxy=new Proxy(targetObject, handler);
console.log(proxy.name); // 輸出: 獲取屬性 name, John
在這個(gè)例子中,get陷阱攔截屬性訪問并在返回實(shí)際屬性值之前記錄一條消息。
理解目標(biāo)、屬性和值
1. 數(shù)據(jù)驗(yàn)證
使用代理對(duì)象可以通過驗(yàn)證或修改屬性值來強(qiáng)制執(zhí)行數(shù)據(jù)約束。
const validatedUser=new Proxy({}, {
set(target, prop, value) {
if (prop==='age' && (typeof value !=='number' || value < 0 || value > 120)) {
throw new Error('無效的年齡');
}
target[prop]=value;
return true;
},
});
validatedUser.age=30; // 有效賦值
validatedUser.age=-5; // 拋出錯(cuò)誤: 無效的年齡
2. 日志記錄
代理對(duì)象可以輕松記錄屬性訪問情況,為調(diào)試或性能監(jiān)控提供見解。
const loggedObject=new Proxy({}, {
get(target, prop) {
console.log(`訪問屬性: ${prop}`);
return target[prop];
},
});
loggedObject.name='Alice'; // 訪問屬性: name
console.log(loggedObject.name); // 訪問屬性: name
3. 安全性
代理對(duì)象可以通過防止未授權(quán)的屬性訪問或操作來增強(qiáng)對(duì)象安全性。
const securedObject=new Proxy({ secret: 'classified' }, {
get(target, prop) {
if (prop==='secret') {
throw new Error('未授權(quán)的訪問');
}
return target[prop];
},
});
console.log(securedObject.publicInfo); // 訪問允許
console.log(securedObject.secret); // 拋出錯(cuò)誤: 未授權(quán)的訪問
4. 記憶化
代理對(duì)象可用于記憶化,緩存耗時(shí)的函數(shù)調(diào)用結(jié)果以提高性能。
function fibonacci(n) {
if (n <=1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
const memoizedFibonacci=new Proxy({}, {
get(target, prop) {
if (!(prop in target)) {
target[prop]=fibonacci(Number(prop));
}
return target[prop];
},
});
console.log(memoizedFibonacci[10]); // 計(jì)算并緩存
console.log(memoizedFibonacci[5]); // 從緩存中獲取
實(shí)戰(zhàn)示例:電商場景
考慮一個(gè)電商場景,你想使用代理對(duì)象來強(qiáng)制執(zhí)行某些業(yè)務(wù)規(guī)則。
const product={
name: 'Smartphone',
price: 500,
quantity: 10,
};
const securedProduct=new Proxy(product, {
set(target, prop, value) {
if (prop==='quantity' && value < 0) {
throw new Error('無效的數(shù)量');
}
target[prop]=value;
return true;
},
});
securedProduct.quantity=15; // 有效賦值
securedProduct.quantity=-5; // 拋出錯(cuò)誤: 無效的數(shù)量
在這個(gè)例子中,Proxy確保產(chǎn)品的數(shù)量不能被設(shè)置為負(fù)值,從而在電商上下文中執(zhí)行了一個(gè)業(yè)務(wù)規(guī)則。
JavaScript Proxy對(duì)象為創(chuàng)建動(dòng)態(tài)和可定制的對(duì)象行為提供了一個(gè)多功能工具。無論是用于數(shù)據(jù)驗(yàn)證、日志記錄、安全性還是性能優(yōu)化,代理對(duì)象都為開發(fā)者提供了對(duì)對(duì)象交互的細(xì)粒度控制。理解并利用Proxy對(duì)象可以在各種實(shí)際場景中編寫出更干凈、可維護(hù)和安全的代碼。
這篇文章中,谷歌 Robotics 研究科學(xué)家 Eric Jang 對(duì)生物學(xué)可信深度學(xué)習(xí)(BPDL)研究提出了質(zhì)疑。他認(rèn)為,設(shè)計(jì)反向傳播的生物學(xué)可信替代方法壓根就是一個(gè)錯(cuò)誤的問題。機(jī)器學(xué)習(xí)領(lǐng)域的一個(gè)嚴(yán)重錯(cuò)誤就是,對(duì)統(tǒng)計(jì)學(xué)工具和最優(yōu)控制算法賦予了太多生物學(xué)意義。
選自Eric Jang博客,作者: Eric Jang,機(jī)器之心編譯,編輯:魔王、張倩
生物學(xué)可信深度學(xué)習(xí) (BPDL) 是神經(jīng)科學(xué)與機(jī)器學(xué)習(xí)交叉領(lǐng)域中的一個(gè)活躍研究課題,主要研究如何利用在大腦中可實(shí)現(xiàn)的「學(xué)習(xí)規(guī)則」來訓(xùn)練深度神經(jīng)網(wǎng)絡(luò)。
2015 年,深度學(xué)習(xí)巨頭 Yoshua Bengio 發(fā)表論文《Towards Biologically Plausible Deep Learning》,探索了更加符合生物學(xué)邏輯的深度表征學(xué)習(xí)版本。該論文的主要觀點(diǎn)如下:
次年,在 NIPS 2016 Workshop 上,Yoshua Bengio 做了同名演講,其中就探討了「反向傳播」機(jī)制的生物學(xué)可信性。
在學(xué)習(xí)過程中,大腦會(huì)調(diào)整突觸以優(yōu)化行為。在皮層中,突觸嵌入在多層網(wǎng)絡(luò)中,這導(dǎo)致我們難以確定單個(gè)突觸的調(diào)整對(duì)整個(gè)系統(tǒng)行為的影響。而反向傳播算法在深度神經(jīng)網(wǎng)絡(luò)中解決了上述問題,不過長期以來人們一直認(rèn)為反向傳播在生物層面上存在問題。
去年 4 月,來自 DeepMind、牛津大學(xué)和谷歌大腦的 Timothy P. Lillicrap、Adam Santoro、Geoffrey Hinton 等人在 Nature 子刊《Nature Reviews Neuroscience》發(fā)表文章,認(rèn)為反向連接可能會(huì)引發(fā)神經(jīng)活動(dòng),而其中的差異可用于局部逼近誤差信號(hào),從而促進(jìn)大腦深層網(wǎng)絡(luò)中的有效學(xué)習(xí)。即盡管大腦可能未實(shí)現(xiàn)字面形式的反向傳播,但是反向傳播的部分特征與理解大腦中的學(xué)習(xí)具備很強(qiáng)的關(guān)聯(lián)性。
大腦對(duì)反向傳播算法的近似。
然而,討論并未終止。最近,谷歌 Robotics 研究科學(xué)家 Eric Jang 發(fā)表博客,對(duì) BPDL 中的反向傳播觀點(diǎn)提出質(zhì)疑。
反向傳播為什么一定要有生物學(xué)對(duì)應(yīng)?
Eric Jang 首先列舉了推動(dòng) BPDL 發(fā)展的主要原因:
有人曾列舉了反向傳播并非生物學(xué)可信的諸多理由,以及提出修復(fù)辦法的多種算法。
來源:https://psychology.stackexchange.com/questions/16269/is-back-prop-biologically-plausible
而 Eric Jang 的反對(duì)意見主要在于,設(shè)計(jì)反向傳播的生物學(xué)可信替代方法壓根就是一個(gè)錯(cuò)誤的問題。BPDL 的重要前提中包含了一個(gè)錯(cuò)誤的假設(shè):層激活是神經(jīng)元,權(quán)重是突觸,因此借助反向傳播的學(xué)習(xí)必須在生物學(xué)習(xí)中有對(duì)應(yīng)的部分。
盡管 DNN 叫做深度「神經(jīng)網(wǎng)絡(luò)」,并在多項(xiàng)任務(wù)中展現(xiàn)出了卓越能力,但它們本質(zhì)上與生物神經(jīng)網(wǎng)絡(luò)毫無關(guān)聯(lián)。機(jī)器學(xué)習(xí)領(lǐng)域的一個(gè)嚴(yán)重錯(cuò)誤就是,對(duì)統(tǒng)計(jì)學(xué)工具和最優(yōu)控制算法賦予了太多生物學(xué)意義。這往往使初學(xué)者感到困惑。
DNN 是一系列線性操作和非線性操作的交織,序列應(yīng)用于實(shí)值輸入,僅此而已。它們通過梯度下降進(jìn)行優(yōu)化,利用一種叫做「反向傳播」的動(dòng)態(tài)規(guī)劃機(jī)制對(duì)梯度進(jìn)行高效計(jì)算。
動(dòng)態(tài)規(guī)劃是世界第九大奇跡,Eric Jang 認(rèn)為這是計(jì)算機(jī)科學(xué)領(lǐng)域 Top 3 成就之一。反向傳播在網(wǎng)絡(luò)深度方面具備線性時(shí)間復(fù)雜度,因而從計(jì)算成本的角度來看,它很難被打敗。許多 BPDL 算法往往不如反向傳播,因?yàn)樗鼈儑L試在更新機(jī)制中利用高效的優(yōu)化機(jī)制,且具備額外的約束。
如果目標(biāo)是構(gòu)建生物學(xué)可信的學(xué)習(xí)機(jī)制,那么 DNN 中的單元不應(yīng)與生物神經(jīng)元一一對(duì)應(yīng)。嘗試使用生物神經(jīng)元模型模仿 DNN 是落后的,就像用人腦模擬 Windows 操作系統(tǒng)一樣。這很難,而且人腦無法很好地模擬 Windows 系統(tǒng)。
我們反過來試一下呢:優(yōu)化函數(shù)逼近器,以實(shí)現(xiàn)生物學(xué)可信的學(xué)習(xí)規(guī)則。這種方式較為直接:
用來尋找學(xué)習(xí)規(guī)則的函數(shù)逼近器的選擇是無關(guān)緊要的——我們真正在乎的是生物大腦如何學(xué)習(xí)像感知這樣的困難任務(wù),同時(shí)遵循已知的限制條件,如生物神經(jīng)元不把所有的激活都存儲(chǔ)在記憶中,或者只使用局部的學(xué)習(xí)規(guī)則。我們應(yīng)該利用深度學(xué)習(xí)的能力找出優(yōu)秀的函數(shù)逼近器,并以此來尋找優(yōu)秀的生物學(xué)習(xí)規(guī)則。
「元學(xué)習(xí)」是另一種選擇?
「我們應(yīng)該(人工地)學(xué)習(xí)如何以生物的方式學(xué)習(xí)」并非一個(gè)全新的觀點(diǎn),但對(duì)于神經(jīng)科學(xué) + AI 社區(qū)來說,這一點(diǎn)還不夠明顯。元學(xué)習(xí)(學(xué)習(xí)如何學(xué)習(xí))是近年來興起的一個(gè)領(lǐng)域,它給出了獲取能夠執(zhí)行學(xué)習(xí)行為的系統(tǒng)的方法,該系統(tǒng)有超越梯度下降的潛力。如果元學(xué)習(xí)可以幫我們找到更加樣本高效或者更優(yōu)秀、更魯棒的學(xué)習(xí)器,那它為什么不能幫我們找到遵循生物學(xué)習(xí)約束的規(guī)則呢?其實(shí),最近的幾項(xiàng)研究 [1, 2, 3, 4, 5] 已經(jīng)探索了這一問題。你確實(shí)可以使用反向傳播來訓(xùn)練一個(gè)優(yōu)于普通反向傳播的獨(dú)立學(xué)習(xí)規(guī)則。
Eric Jang 認(rèn)為,很多研究者之所以還沒理解這個(gè)觀點(diǎn)(即我們應(yīng)該用元學(xué)習(xí)方法來模擬生物學(xué)可信的回路),是因?yàn)槟壳八懔€不夠強(qiáng),無法同時(shí)訓(xùn)練元學(xué)習(xí)器和學(xué)習(xí)器。要想制定元優(yōu)化方案,我們還需要強(qiáng)大的算力和研究基礎(chǔ)設(shè)施,但 JAX 等工具的出現(xiàn)已經(jīng)讓這一任務(wù)變得簡單得多。
真正的生物學(xué)純粹主義者可能會(huì)說,利用梯度下降和反向傳播尋找學(xué)習(xí)規(guī)則不是一種「進(jìn)化上可信的學(xué)習(xí)規(guī)則」,因?yàn)檫M(jìn)化明顯缺乏執(zhí)行動(dòng)態(tài)規(guī)劃甚至是梯度計(jì)算的能力。但如果使元學(xué)習(xí)器在進(jìn)化上可信,這一點(diǎn)就能得到修正。例如,用來選擇優(yōu)秀函數(shù)逼近器的機(jī)制其實(shí)根本不需要依賴反向傳播。相反,我們可以制定一個(gè)元 - 元問題,讓選擇過程本身遵守進(jìn)化選擇的規(guī)則,但是選擇過程還是使用反向傳播。
所以,不要再給反向傳播賦予生物學(xué)意義了!
原文鏈接:https://blog.evjang.com/2021/02/backprop.html
自己是一名從事了多年開發(fā)的web前端老程序員,目前辭職在做自己的web前端私人定制課程,今年年初我花了一個(gè)月整理了一份最適合2019年學(xué)習(xí)的web前端學(xué)習(xí)干貨,各種框架都有整理,送給每一位前端小伙伴,想要獲取的可以關(guān)注我的頭條號(hào)并在后臺(tái)私信我:前端,即可免費(fèi)獲取。
Proxy 對(duì)象(Proxy)是 ES6 的一個(gè)非常酷卻鮮為人知的特性。雖然這個(gè)特性存在已久,但是我還是想在本文中對(duì)其稍作解釋,并用一個(gè)例子說明一下它的用法。
什么是 Proxy
正如 MDN 上簡單而枯燥的定義:
Proxy 對(duì)象用于定義基本操作的自定義行為(如屬性查找,賦值,枚舉,函數(shù)調(diào)用等)。
雖然這是一個(gè)不錯(cuò)的總結(jié),但是我卻并沒有從中搞清楚 Proxy 能做什么,以及它能幫我們實(shí)現(xiàn)什么。
首先,Proxy 的概念來源于元編程。簡單的說,元編程是允許我們運(yùn)行我們編寫的應(yīng)用程序(或核心)代碼的代碼。例如,臭名昭著的 eval 函數(shù)允許我們將字符串代碼當(dāng)做可執(zhí)行代碼來執(zhí)行,它是就屬于元編程領(lǐng)域。
Proxy API 允許我們在對(duì)象和其消費(fèi)實(shí)體中創(chuàng)建中間層,這種特性為我們提供了控制該對(duì)象的能力,比如可以決定怎樣去進(jìn)行它的 get 和 set,甚至可以自定義當(dāng)訪問這個(gè)對(duì)象上不存在的屬性的時(shí)候我們可以做些什么。
Proxy 的 API
var p=new Proxy ( target , handler );
Proxy 構(gòu)造函數(shù)獲取一個(gè) target 對(duì)象,和一個(gè)用來攔截 target 對(duì)象不同行為的 handler對(duì)象。你可以設(shè)置下面這些攔截項(xiàng):
這只是一部分?jǐn)r截項(xiàng),你可以在 MDN 上找到完整的列表。
下面是將 Proxy 用在驗(yàn)證上的一個(gè)簡單的例子:
const Car={ maker : 'BMW' , year : 2018 , }; const proxyCar=new Proxy ( Car , { set ( obj , prop , value ) { if ( prop==='maker' && value . length < 1 ) { throw new Error ( 'Invalid maker' ); } if ( prop==='year' && typeof value !=='number' ) { throw new Error ( 'Invalid year' ); } obj [ prop ]=value ; return true ; } }); proxyCar . maker='' ; // throw exception proxyCar . year='1999' ; // throw exception
可以看到,我們可以用 Proxy 來驗(yàn)證賦給被代理對(duì)象的值。
使用 Proxy 來調(diào)試
為了在實(shí)踐中展示 Proxy 的能力,我創(chuàng)建了一個(gè)簡單的監(jiān)測庫,用來監(jiān)測給定的對(duì)象或類,監(jiān)測項(xiàng)如下:
這是通過在訪問任意對(duì)象、類、甚至是函數(shù)時(shí),調(diào)用一個(gè)名為 proxyTrack 的函數(shù)來完成的。
如果你希望監(jiān)測是誰給一個(gè)對(duì)象的屬性賦的值,或者一個(gè)函數(shù)執(zhí)行了多久、執(zhí)行了多少次、誰執(zhí)行的,這個(gè)庫將非常有用。我知道可能還有其他更好的工具來實(shí)現(xiàn)上面的功能,但是在這里我創(chuàng)建這個(gè)庫就是為了用一用這個(gè) API。
使用 proxyTrack
首先,我們看看怎么用:
function MyClass () {} MyClass . prototype={ isPrime : function () { const num=this . num ; for ( var i=2 ; i < num ; i ++) if ( num % i===0 ) return false ; return num !==1 && num !==0 ; }, num : null , }; MyClass . prototype . constructor=MyClass ; const trackedClass=proxyTrack ( MyClass ); function start () { const my=new trackedClass (); my . num=573723653 ; if (! my . isPrime ()) { return ` $ { my . num } is not prime `; } } function main () { start (); } main ();
如果我們運(yùn)行這段代碼,控制臺(tái)將會(huì)輸出:
MyClass . num is being set by start for the 1 time MyClass . num is being get by isPrime for the 1 time MyClass . isPrime was called by start for the 1 time and took 0 mils . MyClass . num is being get by start for the 2 time
proxyTrack 接受 2 個(gè)參數(shù):第一個(gè)是要監(jiān)測的對(duì)象/類,第二個(gè)是一個(gè)配置項(xiàng)對(duì)象,如果沒傳遞的話將被置為默認(rèn)值。我們看看這個(gè)配置項(xiàng)默認(rèn)值長啥樣:
const defaultOptions={ trackFunctions : true , trackProps : true , trackTime : true , trackCaller : true , trackCount : true , stdout : null , filter : null , };
可以看到,你可以通過配置你關(guān)心的監(jiān)測項(xiàng)來監(jiān)測你的目標(biāo)。比如你希望將結(jié)果輸出出來,那么你可以將 console.log 賦給 stdout。
還可以通過賦給 filter 的回調(diào)函數(shù)來自定義地控制輸出哪些信息。你將會(huì)得到一個(gè)包括有監(jiān)測信息的對(duì)象,并且如果你希望保留這個(gè)信息就返回 true,反之返回 false。
在 React 中使用 proxyTrack
因?yàn)?React 的組件實(shí)際上也是類,所以你可以通過 proxyTrack 來實(shí)時(shí)監(jiān)控它。比如:
class MyComponent extends Component {...} export default connect ( mapStateToProps )( proxyTrack ( MyComponent , { trackFunctions : true , trackProps : true , trackTime : true , trackCaller : true , trackCount : true , filter : ( data )=> { if ( data . type==='get' && data . prop==='componentDidUpdate' ) return false ; return true ; } }));
可以看到,你可以將你不關(guān)心的信息過濾掉,否則輸出將會(huì)變得雜亂無章。
實(shí)現(xiàn) proxyTrack
我們來看看 proxyTrack 的實(shí)現(xiàn)。
首先是這個(gè)函數(shù)本身:
export function proxyTrack ( entity , options=defaultOptions ) { if ( typeof entity==='function' ) return trackClass ( entity , options ); return trackObject ( entity , options ); }
沒什么特別的嘛,這里只是調(diào)用相關(guān)函數(shù)。
再看看 trackObject:
function trackObject ( obj , options={}) { const { trackFunctions , trackProps }=options ; let resultObj=obj ; if ( trackFunctions ) { proxyFunctions ( resultObj , options ); } if ( trackProps ) { resultObj=new Proxy ( resultObj , { get : trackPropertyGet ( options ), set : trackPropertySet ( options ), }); } return resultObj ; } function proxyFunctions ( trackedEntity , options ) { if ( typeof trackedEntity==='function' ) return ; Object . getOwnPropertyNames ( trackedEntity ). forEach (( name )=> { if ( typeof trackedEntity [ name ]==='function' ) { trackedEntity [ name ]=new Proxy ( trackedEntity [ name ], { apply : trackFunctionCall ( options ), }); } }); }
可以看到,假如我們希望監(jiān)測對(duì)象的屬性,我們創(chuàng)建了一個(gè)帶有 get 和 set 攔截器的被監(jiān)測對(duì)象。下面是 set 攔截器的實(shí)現(xiàn):
function trackPropertySet ( options={}) { return function set ( target , prop , value , receiver ) { const { trackCaller , trackCount , stdout , filter }=options ; const error=trackCaller && new Error (); const caller=getCaller ( error ); const contextName=target . constructor . name==='Object' ? '' : ` $ { target . constructor . name }.`; const name=` $ { contextName } $ { prop }`; const hashKey=` set_$ { name }`; if ( trackCount ) { if (! callerMap [ hashKey ]) { callerMap [ hashKey ]=1 ; } else { callerMap [ hashKey ]++; } } let output=` $ { name } is being set `; if ( trackCaller ) { output +=` by $ { caller . name }`; } if ( trackCount ) { output +=` for the $ { callerMap [ hashKey ]} time `; } let canReport=true ; if ( filter ) { canReport=filter ({ type : 'get' , prop , name , caller , count : callerMap [ hashKey ], value , }); } if ( canReport ) { if ( stdout ) { stdout ( output ); } else { console . log ( output ); } } return Reflect . set ( target , prop , value , receiver ); }; }
更有趣的是 trackClass 函數(shù)(至少對(duì)我來說是這樣):
function trackClass ( cls , options={}) { cls . prototype=trackObject ( cls . prototype , options ); cls . prototype . constructor=cls ; return new Proxy ( cls , { construct ( target , args ) { const obj=new target (... args ); return new Proxy ( obj , { get : trackPropertyGet ( options ), set : trackPropertySet ( options ), }); }, apply : trackFunctionCall ( options ), }); }
在這個(gè)案例中,因?yàn)槲覀兿M麛r截這個(gè)類上不屬于原型上的屬性,所以我們給這個(gè)類的原型創(chuàng)建了個(gè)代理,并且創(chuàng)建了個(gè)構(gòu)造函數(shù)攔截器。
別忘了,即使你在原型上定義了一個(gè)屬性,但如果你再給這個(gè)對(duì)象賦值一個(gè)同名屬性,JavaScript 將會(huì)創(chuàng)建一個(gè)這個(gè)屬性的本地副本,所以賦值的改動(dòng)并不會(huì)改變這個(gè)類其他實(shí)例的行為。這就是為何只對(duì)原型做代理并不能滿足要求的原因。
作者:前端下午茶 公號(hào) / SHERlocked93
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。