avaScript的Proxy對象是一種強大且靈活的特性,它允許你攔截并自定義對對象執行的操作。自ECMAScript 6(ES6)引入以來,Proxy對象為控制對象的基本操作行為提供了一種機制,使高級用例和改進的安全性成為可能。
一個Proxy是由兩個主要組件創建的:目標對象和處理器。目標對象是你想攔截操作的原始對象,處理器是一個包含名為陷阱的方法的對象,這些方法定義了這些操作的自定義行為。
創建一個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
在這個例子中,get陷阱攔截屬性訪問并在返回實際屬性值之前記錄一條消息。
理解目標、屬性和值
1. 數據驗證
使用代理對象可以通過驗證或修改屬性值來強制執行數據約束。
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; // 拋出錯誤: 無效的年齡
2. 日志記錄
代理對象可以輕松記錄屬性訪問情況,為調試或性能監控提供見解。
const loggedObject=new Proxy({}, {
get(target, prop) {
console.log(`訪問屬性: ${prop}`);
return target[prop];
},
});
loggedObject.name='Alice'; // 訪問屬性: name
console.log(loggedObject.name); // 訪問屬性: name
3. 安全性
代理對象可以通過防止未授權的屬性訪問或操作來增強對象安全性。
const securedObject=new Proxy({ secret: 'classified' }, {
get(target, prop) {
if (prop==='secret') {
throw new Error('未授權的訪問');
}
return target[prop];
},
});
console.log(securedObject.publicInfo); // 訪問允許
console.log(securedObject.secret); // 拋出錯誤: 未授權的訪問
4. 記憶化
代理對象可用于記憶化,緩存耗時的函數調用結果以提高性能。
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]); // 計算并緩存
console.log(memoizedFibonacci[5]); // 從緩存中獲取
實戰示例:電商場景
考慮一個電商場景,你想使用代理對象來強制執行某些業務規則。
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('無效的數量');
}
target[prop]=value;
return true;
},
});
securedProduct.quantity=15; // 有效賦值
securedProduct.quantity=-5; // 拋出錯誤: 無效的數量
在這個例子中,Proxy確保產品的數量不能被設置為負值,從而在電商上下文中執行了一個業務規則。
JavaScript Proxy對象為創建動態和可定制的對象行為提供了一個多功能工具。無論是用于數據驗證、日志記錄、安全性還是性能優化,代理對象都為開發者提供了對對象交互的細粒度控制。理解并利用Proxy對象可以在各種實際場景中編寫出更干凈、可維護和安全的代碼。
這篇文章中,谷歌 Robotics 研究科學家 Eric Jang 對生物學可信深度學習(BPDL)研究提出了質疑。他認為,設計反向傳播的生物學可信替代方法壓根就是一個錯誤的問題。機器學習領域的一個嚴重錯誤就是,對統計學工具和最優控制算法賦予了太多生物學意義。
選自Eric Jang博客,作者: Eric Jang,機器之心編譯,編輯:魔王、張倩
生物學可信深度學習 (BPDL) 是神經科學與機器學習交叉領域中的一個活躍研究課題,主要研究如何利用在大腦中可實現的「學習規則」來訓練深度神經網絡。
2015 年,深度學習巨頭 Yoshua Bengio 發表論文《Towards Biologically Plausible Deep Learning》,探索了更加符合生物學邏輯的深度表征學習版本。該論文的主要觀點如下:
次年,在 NIPS 2016 Workshop 上,Yoshua Bengio 做了同名演講,其中就探討了「反向傳播」機制的生物學可信性。
在學習過程中,大腦會調整突觸以優化行為。在皮層中,突觸嵌入在多層網絡中,這導致我們難以確定單個突觸的調整對整個系統行為的影響。而反向傳播算法在深度神經網絡中解決了上述問題,不過長期以來人們一直認為反向傳播在生物層面上存在問題。
去年 4 月,來自 DeepMind、牛津大學和谷歌大腦的 Timothy P. Lillicrap、Adam Santoro、Geoffrey Hinton 等人在 Nature 子刊《Nature Reviews Neuroscience》發表文章,認為反向連接可能會引發神經活動,而其中的差異可用于局部逼近誤差信號,從而促進大腦深層網絡中的有效學習。即盡管大腦可能未實現字面形式的反向傳播,但是反向傳播的部分特征與理解大腦中的學習具備很強的關聯性。
大腦對反向傳播算法的近似。
然而,討論并未終止。最近,谷歌 Robotics 研究科學家 Eric Jang 發表博客,對 BPDL 中的反向傳播觀點提出質疑。
反向傳播為什么一定要有生物學對應?
Eric Jang 首先列舉了推動 BPDL 發展的主要原因:
有人曾列舉了反向傳播并非生物學可信的諸多理由,以及提出修復辦法的多種算法。
來源:https://psychology.stackexchange.com/questions/16269/is-back-prop-biologically-plausible
而 Eric Jang 的反對意見主要在于,設計反向傳播的生物學可信替代方法壓根就是一個錯誤的問題。BPDL 的重要前提中包含了一個錯誤的假設:層激活是神經元,權重是突觸,因此借助反向傳播的學習必須在生物學習中有對應的部分。
盡管 DNN 叫做深度「神經網絡」,并在多項任務中展現出了卓越能力,但它們本質上與生物神經網絡毫無關聯。機器學習領域的一個嚴重錯誤就是,對統計學工具和最優控制算法賦予了太多生物學意義。這往往使初學者感到困惑。
DNN 是一系列線性操作和非線性操作的交織,序列應用于實值輸入,僅此而已。它們通過梯度下降進行優化,利用一種叫做「反向傳播」的動態規劃機制對梯度進行高效計算。
動態規劃是世界第九大奇跡,Eric Jang 認為這是計算機科學領域 Top 3 成就之一。反向傳播在網絡深度方面具備線性時間復雜度,因而從計算成本的角度來看,它很難被打敗。許多 BPDL 算法往往不如反向傳播,因為它們嘗試在更新機制中利用高效的優化機制,且具備額外的約束。
如果目標是構建生物學可信的學習機制,那么 DNN 中的單元不應與生物神經元一一對應。嘗試使用生物神經元模型模仿 DNN 是落后的,就像用人腦模擬 Windows 操作系統一樣。這很難,而且人腦無法很好地模擬 Windows 系統。
我們反過來試一下呢:優化函數逼近器,以實現生物學可信的學習規則。這種方式較為直接:
用來尋找學習規則的函數逼近器的選擇是無關緊要的——我們真正在乎的是生物大腦如何學習像感知這樣的困難任務,同時遵循已知的限制條件,如生物神經元不把所有的激活都存儲在記憶中,或者只使用局部的學習規則。我們應該利用深度學習的能力找出優秀的函數逼近器,并以此來尋找優秀的生物學習規則。
「元學習」是另一種選擇?
「我們應該(人工地)學習如何以生物的方式學習」并非一個全新的觀點,但對于神經科學 + AI 社區來說,這一點還不夠明顯。元學習(學習如何學習)是近年來興起的一個領域,它給出了獲取能夠執行學習行為的系統的方法,該系統有超越梯度下降的潛力。如果元學習可以幫我們找到更加樣本高效或者更優秀、更魯棒的學習器,那它為什么不能幫我們找到遵循生物學習約束的規則呢?其實,最近的幾項研究 [1, 2, 3, 4, 5] 已經探索了這一問題。你確實可以使用反向傳播來訓練一個優于普通反向傳播的獨立學習規則。
Eric Jang 認為,很多研究者之所以還沒理解這個觀點(即我們應該用元學習方法來模擬生物學可信的回路),是因為目前算力還不夠強,無法同時訓練元學習器和學習器。要想制定元優化方案,我們還需要強大的算力和研究基礎設施,但 JAX 等工具的出現已經讓這一任務變得簡單得多。
真正的生物學純粹主義者可能會說,利用梯度下降和反向傳播尋找學習規則不是一種「進化上可信的學習規則」,因為進化明顯缺乏執行動態規劃甚至是梯度計算的能力。但如果使元學習器在進化上可信,這一點就能得到修正。例如,用來選擇優秀函數逼近器的機制其實根本不需要依賴反向傳播。相反,我們可以制定一個元 - 元問題,讓選擇過程本身遵守進化選擇的規則,但是選擇過程還是使用反向傳播。
所以,不要再給反向傳播賦予生物學意義了!
原文鏈接:https://blog.evjang.com/2021/02/backprop.html
自己是一名從事了多年開發的web前端老程序員,目前辭職在做自己的web前端私人定制課程,今年年初我花了一個月整理了一份最適合2019年學習的web前端學習干貨,各種框架都有整理,送給每一位前端小伙伴,想要獲取的可以關注我的頭條號并在后臺私信我:前端,即可免費獲取。
Proxy 對象(Proxy)是 ES6 的一個非常酷卻鮮為人知的特性。雖然這個特性存在已久,但是我還是想在本文中對其稍作解釋,并用一個例子說明一下它的用法。
什么是 Proxy
正如 MDN 上簡單而枯燥的定義:
Proxy 對象用于定義基本操作的自定義行為(如屬性查找,賦值,枚舉,函數調用等)。
雖然這是一個不錯的總結,但是我卻并沒有從中搞清楚 Proxy 能做什么,以及它能幫我們實現什么。
首先,Proxy 的概念來源于元編程。簡單的說,元編程是允許我們運行我們編寫的應用程序(或核心)代碼的代碼。例如,臭名昭著的 eval 函數允許我們將字符串代碼當做可執行代碼來執行,它是就屬于元編程領域。
Proxy API 允許我們在對象和其消費實體中創建中間層,這種特性為我們提供了控制該對象的能力,比如可以決定怎樣去進行它的 get 和 set,甚至可以自定義當訪問這個對象上不存在的屬性的時候我們可以做些什么。
Proxy 的 API
var p=new Proxy ( target , handler );
Proxy 構造函數獲取一個 target 對象,和一個用來攔截 target 對象不同行為的 handler對象。你可以設置下面這些攔截項:
這只是一部分攔截項,你可以在 MDN 上找到完整的列表。
下面是將 Proxy 用在驗證上的一個簡單的例子:
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 來驗證賦給被代理對象的值。
使用 Proxy 來調試
為了在實踐中展示 Proxy 的能力,我創建了一個簡單的監測庫,用來監測給定的對象或類,監測項如下:
這是通過在訪問任意對象、類、甚至是函數時,調用一個名為 proxyTrack 的函數來完成的。
如果你希望監測是誰給一個對象的屬性賦的值,或者一個函數執行了多久、執行了多少次、誰執行的,這個庫將非常有用。我知道可能還有其他更好的工具來實現上面的功能,但是在這里我創建這個庫就是為了用一用這個 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 ();
如果我們運行這段代碼,控制臺將會輸出:
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 個參數:第一個是要監測的對象/類,第二個是一個配置項對象,如果沒傳遞的話將被置為默認值。我們看看這個配置項默認值長啥樣:
const defaultOptions={ trackFunctions : true , trackProps : true , trackTime : true , trackCaller : true , trackCount : true , stdout : null , filter : null , };
可以看到,你可以通過配置你關心的監測項來監測你的目標。比如你希望將結果輸出出來,那么你可以將 console.log 賦給 stdout。
還可以通過賦給 filter 的回調函數來自定義地控制輸出哪些信息。你將會得到一個包括有監測信息的對象,并且如果你希望保留這個信息就返回 true,反之返回 false。
在 React 中使用 proxyTrack
因為 React 的組件實際上也是類,所以你可以通過 proxyTrack 來實時監控它。比如:
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 ; } }));
可以看到,你可以將你不關心的信息過濾掉,否則輸出將會變得雜亂無章。
實現 proxyTrack
我們來看看 proxyTrack 的實現。
首先是這個函數本身:
export function proxyTrack ( entity , options=defaultOptions ) { if ( typeof entity==='function' ) return trackClass ( entity , options ); return trackObject ( entity , options ); }
沒什么特別的嘛,這里只是調用相關函數。
再看看 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 ), }); } }); }
可以看到,假如我們希望監測對象的屬性,我們創建了一個帶有 get 和 set 攔截器的被監測對象。下面是 set 攔截器的實現:
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 函數(至少對我來說是這樣):
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 ), }); }
在這個案例中,因為我們希望攔截這個類上不屬于原型上的屬性,所以我們給這個類的原型創建了個代理,并且創建了個構造函數攔截器。
別忘了,即使你在原型上定義了一個屬性,但如果你再給這個對象賦值一個同名屬性,JavaScript 將會創建一個這個屬性的本地副本,所以賦值的改動并不會改變這個類其他實例的行為。這就是為何只對原型做代理并不能滿足要求的原因。
作者:前端下午茶 公號 / SHERlocked93
*請認真填寫需求信息,我們會在24小時內與您取得聯系。