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小时内与您取得联系。