【牧马指标源码】【生产计划 源码】【仿腾讯timqq 源码】联邦学习源码

来源:钻石杀入线源码

1.����ѧϰԴ��
2.umi3源码解析之核心Service类初始化
3.关于FATE-NN模块升级详细介绍
4.什么是联邦 FHIR服务?
5.RTFM读手册的解释

联邦学习源码

����ѧϰԴ��

       引言:本文记录了FATE框架中横向和纵向联邦学习的案例使用,并与笔者近期使用过的学习谷歌TFF(TensorFlow-Federated)框架对比,阐述使用感受,源码对研究联邦学习及使用FATE的联邦用户极有价值。

       1.横向联邦学习案例

       在本节中,学习以逻辑回归为例记录横向联邦学习案例使用。源码牧马指标源码

       实验设置文件说明

       上传数据json文件

       upload_my_homolr_guest.json,联邦 upload_my_homolr_host.json

       组件

       test_my_homolr_train_dsl.json

       运行时配置文件

       test_my_homolr_train_conf.json

       实验步骤

       进入实验环境

       1.上传训练数据

       注意是先上传guest数据,后上传host数据

       2.提交训练任务

       3.查看结果

       点击提示url可以查看相关结果:

       2.纵向联邦学习案例

       在本节中,学习以secureboost为例记录纵向联邦学习案例使用。源码

       实验设置实验步骤

       上传数据、联邦提交任务和查看结果

       展示结果

       实验结果

       1.训练中

       2.查看结果

       点击view this 学习job查看所有结果,同时可以通过切换job查看guest端和host端的源码数据和模型输出,此处只做部分展示:

       结果分析

       模型输出的联邦各项指标都很好,结果非常令人满意,学习通过阅读过SecureBoost论文了解到这个算法是源码无损的,所以达到了如此高的指标;secureboost是由xgboost改进而来的,所以训练速度也很快。

       小结

       读者可以根据需求修改以上列举的文件进行不同训练。笔者记录以上案例时使用的是FATE的早期版本,横向联邦学习支持的模块很少,目前FATE框架已经支持深度学习,读者可以尝试使用。

       3.使用感受

       本节笔者将对比FATE框架和TFF框架的使用感受。

       1.环境部署

       FATE框架需要较多软件,安装操作比较琐碎,安装过程中可能出一些小问题,但对软件版本对应要求不是很高,环境部署可以参考 这里和 官方GitHub。

       TFF框架官方没有中文文档,安装过程中也会出各种各样的错误,支持conda安装;如果使用gpu,需要严格按照版本对应安装,笔者在此吃了不少苦头。

       2.应用场景

       FATE框架支持横向、纵向联邦学习;而TFF仅支持横向联邦。

       FATE目前已经支持多种算法,TFF同样支持各种算法和深度学习。生产计划 源码

       3.上手难度

       FATE如果只是修改部分json文件,还可以接受,但是如果需要大片重置,在linux系统下不是很方便;但FATE源码中已搭建好大部分组件结构,基本不需要编写代码,用户可以0基础训练并查看结果。

       TFF编译器可以配置到IDE中,编写代码比较方便;但TFF有其federated core等编程范式和API,有一定上手难度,使用时需要编写大量代码。

       4.可视化

       FATE在可视化部分做的比较好,轻松点击便可查看结果。

       TFF需要自行编写代码从tf.Session中取出中间和最终结果。

       5.调试

       FATE代码涉及多种语言,且代码封装性较高,笔者由于对python了解甚少,未曾尝试修改FATE中的python代码,更未尝试debug。

       TFF笔者使用较少,不过TFF计算时使用静态图并tf.Session保存中间结果,调试也比较困难。

       6.GPU支持

       据笔者目前了解FATE暂时不支持GPU计算,在这一方面TFF更有优势。

       7.使用场景

       FATE支持多方部署,同时可以由多方发起联邦计算,可以用于实验测试和真实环境部署。

       TFF虽然是谷歌已经用于实际训练终端Gboard的框架,但据笔者至目前的使用并未发现开放给用户的多方联邦接口,仅适用于实验测试。

       本文由盛立杰撰写,微信号:urinmydreaM_,FATE社区特邀作者发布于FATE社区,未经许可,禁止转载。

       欢迎关注更多AI技术干货:

       FedAI联邦学习知乎机构号: FedAI联邦学习- 知乎

umi3源码解析之核心Service类初始化

       前言

       umi是一个插件化的企业级前端应用框架,在开发中后台项目中应用颇广,仿腾讯timqq 源码确实带来了许多便利。借着这个契机,便有了我们接下来的“umi3源码解析”系列的分享,初衷很简单就是从源码层面上帮助大家深入认知umi这个框架,能够更得心应手的使用它,学习源码中的设计思想提升自身。该系列的大纲如下:

       开辟鸿蒙,今天要解析的就是第一part,内容包括以下两个部分:

       邂逅umi命令,看看umidev时都做了什么?

       初遇插件化,了解源码中核心的Service类初始化的过程。

       本次使用源码版本为?3.5.,地址放在这里了,接下来的每一块代码笔者都贴心的为大家注释了在源码中的位置,先clone再食用更香哟!

邂逅umi命令

       该部分在源码中的路径为:packages/umi

       首先是第一部分umi命令,umi脚手架为我们提供了umi这个命令,当我们创建完一个umi项目并安装完相关依赖之后,通过yarnstart启动该项目时,执行的命令就是umidev

       那么在umi命令运行期间都发生了什么呢,先让我们来看一下完整的流程,如下图:

       接下来我们对其几个重点的步骤进行解析,首先就是对于我们在命令行输入的umi命令进行处理。

处理命令行参数//packages/umi/src/cli.tsconstargs=yParser(process.argv.slice(2),{ alias:{ version:['v'],help:['h'],},boolean:['version'],});if(args.version&&!args._[0]){ args._[0]='version';constlocal=existsSync(join(__dirname,'../.local'))?chalk.cyan('@local'):'';console.log(`umi@${ require('../package.json').version}${ local}`);}elseif(!args._[0]){ args._[0]='help';}

       解析命令行参数所使用的yParser方法是基于yargs-parser封装,该方法的两个入参分别是进程的可执行文件的绝对路径和正在执行的JS文件的路径。解析结果如下:

//输入umidev经yargs-parser解析后为://args={ //_:["dev"],//}

       在解析命令行参数后,对version和help参数进行了特殊处理:

       如果args中有version字段,并且args._中没有值,将执行version命令,并从package.json中获得version的值并打印

       如果没有version字段,args._中也没有值,将执行help命令

       总的来说就是,如果只输入umi实际会执行umihelp展示umi命令的使用指南,如果输入umi--version会输出依赖的版本,如果执行umidev那就是接下来的步骤了。

       提问:您知道输入umi--versiondev会发什么吗?

       运行umidev

//packages/umi/src/cli.tsconstchild=fork({ scriptPath:require.resolve('./forkedDev'),});process.on('SIGINT',()=>{ child.kill('SIGINT');process.exit(0);});//packages/umi/src/utils/fork.tsif(CURRENT_PORT){ process.env.PORT=CURRENT_PORT;}constchild=fork(scriptPath,process.argv.slice(2),{ execArgv});child.on('message',(data:any)=>{ consttype=(data&&data.type)||null;if(type==='RESTART'){ child.kill();start({ scriptPath});}elseif(type==='UPDATE_PORT'){ //setcurrentusedportCURRENT_PORT=data.portasnumber;}process.send?.(data);});

       本地开发时,大部分脚手架都会采用开启一个新的神之浩劫 源码线程来启动项目,umi脚手架也是如此。这里的fork方法是基于node中child_process.fork()方法的封装,主要做了以下三件事:

       确定端口号,使用命令行指定的端口号或默认的,如果该端口号已被占用则prot+=1

       开启子进程,该子进程独立于父进程,两者之间建立IPC通信通道进行消息传递

       处理通信,主要监听了RESTART重启和UPDATE_PORT更新端口号事件

       接下来看一下在子进程中运行的forkedDev.ts都做了什么。

//packages/umi/src/forkedDev.ts(async()=>{ try{ //1、设置NODE_ENV为developmentprocess.env.NODE_ENV='development';//2、InitwebpackversiondeterminationandrequirehookinitWebpack();//3、实例化Service类,执行run方法constservice=newService({ cwd:getCwd(),//umi项目的根路径pkg:getPkg(process.cwd()),//项目的package.json文件的路径});awaitservice.run({ name:'dev',args,});//4、父子进程通信letclosed=false;process.once('SIGINT',()=>onSignal('SIGINT'));process.once('SIGQUIT',()=>onSignal('SIGQUIT'));process.once('SIGTERM',()=>onSignal('SIGTERM'));functiononSignal(signal:string){ if(closed)return;closed=true;//退出时触发插件中的onExit事件service.applyPlugins({ key:'onExit',type:service.ApplyPluginsType.event,args:{ signal,},});process.exit(0);}}catch(e:any){ process.exit(1);}})();

       设置process.env.NODE_ENV的值

       initWebpack(接下来解析)

       实例化Service并run(第二part的内容)

       处理父子进程通信,当父进程监听到SIGINT、SIGTERM等终止进程的信号,也通知到子进程进行终止;子进程退出时触发插件中的onExit事件

       initWebpack

//packages/umi/src/initWebpack.tsconsthaveWebpack5=(configContent.includes('webpack5:')&&!configContent.includes('//webpack5:')&&!configContent.includes('//webpack5:'))||(configContent.includes('mfsu:')&&!configContent.includes('//mfsu:')&&!configContent.includes('//mfsu:'));if(haveWebpack5||process.env.USE_WEBPACK_5){ process.env.USE_WEBPACK_5='1';init(true);}else{ init();}initRequreHook();

       这一步功能是检查用户配置确定初始化webpack的版本。读取默认配置文件.umirc和config/config中的配置,如果其中有webpack5或?mfsu等相关配置,umi就会使用webpack5进行初始化,否则就使用webpack4进行初始化。这里的mfsu是webpack5的模块联邦相关配置,umi在3.5版本时已经进行了支持。

初遇插件化

       该部分在源码中的路径为:packages/core/src/Service

       说起umi框架,最先让人想到的就是插件化,这也是框架的核心,该部分实现的核心源码就是Service类,接下来我们就来看看Service类的实例化和init()的过程中发生了什么,可以称之为插件化实现的开端,该部分的大致流程如下

       该流程图中前四步,都是在Service类实例化的过程中完成的,接下来让我们走进Service类。

Service类的实例化//packages/core/src/Service/Service.tsexportdefaultclassServiceextendsEventEmitter{ constructor(opts:IServiceOpts){ super();this.cwd=opts.cwd||process.cwd();//当前工作目录//repoDirshouldbetherootdirofrepothis.pkg=opts.pkg||this.resolvePackage();//package.jsonthis.env=opts.env||process.env.NODE_ENV;//环境变量//在解析config之前注册babelthis.babelRegister=newBabelRegister();//通过dotenv将环境变量中的变量从.env或.env.local文件加载到process.env中this.loadEnv();//1、getuserconfigconstconfigFiles=opts.configFiles;this.configInstance=newConfig({ cwd:this.cwd,service:this,localConfig:this.env==='development',configFiles});this.userConfig=this.configInstance.getUserConfig();//2、getpathsthis.paths=getPaths({ cwd:this.cwd,config:this.userConfig!,env:this.env,});//3、getpresetsandpluginsthis.initialPresets=resolvePresets({ ...baseOpts,jquery 小程序源码presets:opts.presets||[],userConfigPresets:this.userConfig.presets||[],});this.initialPlugins=resolvePlugins({ ...baseOpts,plugins:opts.plugins||[],userConfigPlugins:this.userConfig.plugins||[],});}}

       Service类继承自EventEmitter用于实现自定义事件。在Service类实例化的过程中除了初始化成员变量外主要做了以下三件事:

       1、解析配置文件

//packages/core/src/Config/Config.tsconstDEFAULT_CONFIG_FILES=[//默认配置文件'.umirc.ts','.umirc.js','config/config.ts','config/config.js',];//...if(Array.isArray(opts.configFiles)){ //配置的优先读取this.configFiles=lodash.uniq(opts.configFiles.concat(this.configFiles));}//...getUserConfig(){ //1、找到configFiles中的第一个文件constconfigFile=this.getConfigFile();this.configFile=configFile;//潜在问题:.local和.env的配置必须有configFile才有效if(configFile){ letenvConfigFile;if(process.env.UMI_ENV){ //1.根据UMI_ENV添加后缀eg:.umirc.ts-->.umirc.cloud.tsconstenvConfigFileName=this.addAffix(configFile,process.env.UMI_ENV,);//2.去掉后缀eg:.umirc.cloud.ts-->.umirc.cloudconstfileNameWithoutExt=envConfigFileName.replace(extname(envConfigFileName),'',);//3.找到该环境下对应的配置文件eg:.umirc.cloud.[ts|tsx|js|jsx]envConfigFile=getFile({ base:this.cwd,fileNameWithoutExt,type:'javascript',})?.filename;}constfiles=[configFile,//eg:.umirc.tsenvConfigFile,//eg:.umirc.cloud.tsthis.localConfig&&this.addAffix(configFile,'local'),//eg:.umirc.local.ts].filter((f):fisstring=>!!f).map((f)=>join(this.cwd,f))//转为绝对路径.filter((f)=>existsSync(f));//clearrequirecacheandsetbabelregisterconstrequireDeps=files.reduce((memo:string[],file)=>{ memo=memo.concat(parseRequireDeps(file));//递归解析依赖returnmemo;},[]);//删除对象中的键值require.cache[cachePath],下一次require将重新加载模块requireDeps.forEach(cleanRequireCache);this.service.babelRegister.setOnlyMap({ key:'config',value:requireDeps,});//requireconfigandmergereturnthis.mergeConfig(...this.requireConfigs(files));}else{ return{ };}}

       细品源码,可以看出umi读取配置文件的优先级:自定义配置文件?>.umirc>config/config,后续根据UMI_ENV尝试获取对应的配置文件,development模式下还会使用local配置,不同环境下的配置文件也是有优先级的

       例如:.umirc.local.ts>.umirc.cloud.ts>.umirc.ts

       由于配置文件中可能require其他配置,这里通过parseRequireDeps方法进行递归处理。在解析出所有的配置文件后,会通过cleanRequireCache方法清除requeire缓存,这样可以保证在接下来合并配置时的引入是实时的。

       2、获取相关绝对路径

//packages/core/src/Service/getPaths.tsexportdefaultfunctiongetServicePaths({ cwd,config,env,}:{ cwd:string;config:any;env?:string;}):IServicePaths{ letabsSrcPath=cwd;if(isDirectoryAndExist(join(cwd,'src'))){ absSrcPath=join(cwd,'src');}constabsPagesPath=config.singular?join(absSrcPath,'page'):join(absSrcPath,'pages');consttmpDir=['.umi',env!=='development'&&env].filter(Boolean).join('-');returnnormalizeWithWinPath({ cwd,absNodeModulesPath:join(cwd,'node_modules'),absOutputPath:join(cwd,config.outputPath||'./dist'),absSrcPath,//srcabsPagesPath,//pagesabsTmpPath:join(absSrcPath,tmpDir),});}

       这一步主要获取项目目录结构中node_modules、dist、src、pages等文件夹的绝对路径。如果用户在配置文件中配置了singular为true,那么页面文件夹路径就是src/page,默认是src/pages

       3、收集preset和plugin以对象形式描述

       在umi中“万物皆插件”,preset是对于插件的描述,可以理解为“插件集”,是为了方便对插件的管理。例如:@umijs/preset-react就是一个针对react应用的插件集,其中包括了plugin-access权限管理、plugin-antdantdUI组件等。

//packages/core/src/Service/Service.tsthis.initialPresets=resolvePresets({ ...baseOpts,presets:opts.presets||[],userConfigPresets:this.userConfig.presets||[],});this.initialPlugins=resolvePlugins({ ...baseOpts,plugins:opts.plugins||[],userConfigPlugins:this.userConfig.plugins||[],});

       在收集preset和plugin时,首先调用了resolvePresets方法,其中做了以下处理:

       3.1、调用getPluginsOrPresets方法,进一步收集preset和plugin并合并

//packages/core/src/Service/utils/pluginUtils.tsgetPluginsOrPresets(type:PluginType,opts:IOpts):string[]{ constupperCaseType=type.toUpperCase();return[//opts...((opts[type===PluginType.preset?'presets':'plugins']asany)||[]),//env...(process.env[`UMI_${ upperCaseType}S`]||'').split(',').filter(Boolean),//dependencies...Object.keys(opts.pkg.devDependencies||{ }).concat(Object.keys(opts.pkg.dependencies||{ })).filter(isPluginOrPreset.bind(null,type)),//userconfig...((opts[type===PluginType.preset?'userConfigPresets':'userConfigPlugins']asany)||[]),].map((path)=>{ returnresolve.sync(path,{ basedir:opts.cwd,extensions:['.js','.ts'],});});}

       这里可以看出收集preset和plugin的来源主要有四个:

       实例化Service时的入参

       process.env中指定的UMI_PRESETS或UMI_PLUGINS

       package.json中dependencies和devDependencies配置的,需要命名规则符合?/^(@umijs\/|umi-)preset-/这个正则

       解析配置文件中的,即入参中的userConfigPresets或userConfigPresets

       3.2、调用pathToObj方法:将收集的plugin或preset以对象的形式输出

//输入umidev经yargs-parser解析后为://args={ //_:["dev"],//}0

       umi官网中提到过:每个插件都会对应一个id和一个key,id是路径的简写,key是进一步简化后用于配置的唯一值。便是在这一步进行的处理

       形式如下:

//输入umidev经yargs-parser解析后为://args={ //_:["dev"],//}1

       思考:为什么要将插件以对象的形式进行描述?有什么好处?

执行run方法,初始化插件

       在Service类实例化完毕后,会立马调用run方法,run()执行的第一步就是执行init方法,init()方法的功能就是完成插件的初始化,主要操作如下:

       遍历initialPresets并init

       合并initpresets过程中得到的plugin和initialPlugins

       遍历合并后的plugins并init

       这里的initialPresets和initialPlugins就是上一步收集preset和plugin得到的结果,在这一步要对其逐一的init,接下来我们看一下init的过程中做了什么。

       Initplugin

//输入umidev经yargs-parser解析后为://args={ //_:["dev"],//}2

       这段代码主要做了以下几件事情:

       getPluginAPI方法:newPluginAPI时传入了Service实例,通过pluginAPI实例中的registerMethod方法将register方法添加到Service实例的pluginMethods中,后续返回pluginAPI的代理,以动态获取最新的register方法,以实现边注册边使用。

//输入umidev经yargs-parser解析后为:/

关于FATE-NN模块升级详细介绍

       微众银行的最新投稿聚焦于FATEV1.版本中NN模块(Homo NN & Hetero NN)的显著升级。文章从整体结构、工作原理、示范案例三个方面进行深入解析,由微众银行人工智能部的算法工程师陈伟敬撰写,以详实的内容和专业角度展示了FATE-NN模块的升级亮点。

       首先,文章介绍了背景与需求。基于深度学习的联邦学习是当前隐私计算领域的热点研究方向,工业界也对复杂数据上进行多方联邦学习有强烈需求。FATEV1.版本的升级,旨在优化框架的整体易用性,并全面支持Pytorch的使用,同时提供模型、数据集、训练器三大自定义功能,以适应更广泛的定制化需求。

       文章继续深入,详细阐述了NN模块的四个核心子模块:model_zoo、dataset、homo、hetero,构建了一个清晰的框架结构。随后,通过流程图展示了Homo-NN的基本训练流程,强调了用户在模型、数据集上的自由定制能力,以及通过自定义Trainer控制训练与聚合策略的可能性。

       对于Hetero-NN的介绍,文章指出其与Homo-NN的相似性与差异性,特别强调了在编写pipeline时需区分guest方与host方,以及在特征提取与label计算等方面的具体操作。文章还特别指出Hetero-NN仅支持回归与分类任务。

       在使用示例部分,文章提供了从零到一的实践指南。首先,通过FATE-1.中对torch的支持,用户可以轻松定义或指定自定义模型,通过fate_torch_hook增强torch功能。用户可以基于torch Sequential定义模型结构,或开发并部署自定义模型脚本,并通过pipeline接口指定其使用。文章还展示了如何使用自定义数据集和Trainer,以及一个完整的HomoNN示例,从绑定地址到构建pipeline组件,直观展示了模块的集成与应用。

       最后,文章提供了最新分支的链接,包括源代码、使用文档和教程,鼓励感兴趣的用户尝试并提供反馈,共同推动FATE技术的发展。

什么是 FHIR服务?

       HL7®FHIR®规范是用于交换医疗保健信息的公认标准。FHIR服务是一种基于此标准的、兼容的API,适用于临床运行状况数据,并适用于分析和机器学习解决方案。

       美国国家协调员办公室(ONC)最近发布了一项联邦法规,旨在推广创新医疗保健应用。该法规要求通过FHIR API免费访问所有结构化和/或非结构化医疗数据。这意味着电子病历供应商将需要进行API认证。API允许开发人员制作访问操作系统、应用程序或其他服务的功能或数据。换句话说,它们可以将您的医疗数据从医院电子病历系统转移到手机端。

       这一规则促使人们急于了解FHIR规范以及如何使用它来创建现代API。接口开发人员发现创建API并不难,但管理它们却是具有挑战性的。这成功开拓了API管理解决方案的新市场,其中包括全生命周期的API管理。

       这些API管理解决方案如何帮助开发人员?开发者门户提供了便捷获取各类信息和功能的渠道,包括文档、示例代码和用于测试API的交互式API控制台。

       API网关充当API前端,接收API请求,执行限制和安全策略,将请求传递给后端服务,然后将响应传递回请求者。网关还可以提供收集分析数据的功能,并支持身份验证、授权、安全性、审计和法规遵从性。

       FHIR是RESTful API,这意味着它使用基于互联网的请求来GET、PUT、POST和DELETE数据。FHIR满足REST的所有条件,因为FHIR资源可通过单个URL唯一标识,并使用底层方法如DELETE、PUT和GET来操纵资源。FHIR在客户端和服务器之间的松耦合使得它们之间有清晰的界限。FHIR操作是无状态的,状态管理在客户端而不是服务器上进行。

       Azure医疗保健API(特此称为FHIR服务)中的FHIR服务可通过快速医疗保健互操作性资源,云中的托管平台即服务(PaaS)提供程序来快速交换数据。借助它,使用健康数据的任何人都可以更轻松地在云中引入、管理和保存受保护健康信息(PHI)。

       FHIR服务允许你在几分钟内创建和部署FHIR服务器,以利用云的弹性缩放。无论所管理的数据集是什么样的,Azure服务支持FHIR服务的功能都可实现快速性能。

       RESTful API的优点是可以使用OpenAPI规范(OAS)来描述它们,OAS是RESTful API的一种与语言无关的标准接口,它使开发人员无需访问源代码或文档即可发现服务的功能。

       FHIR实现了具有标准化语义和数据交换的强大可扩展数据模型,可让使用FHIR的所有系统协同工作。通过将数据转换为FHIR,可以快速连接现有数据源,如电子健康记录系统或研究数据库。FHIR还支持在移动和Web开发的新式实现中快速交换数据。最重要的是,FHIR可以通过分析和机器学习工具简化数据引入并加速开发。

       通过无与伦比的安全智能保护PHI。数据被隔离到每个API实例的唯一数据库中,并通过多区域故障转移进行保护。FHIR服务为你的数据实现分层的深度防御和高级威胁防护。

RTFM读手册的解释

       RTFM,全称为 "Read The Fucking Manual",是一个广泛用于网络交流中的英文缩写,用以提醒他人在遇到问题时应首先查阅相关文档,而不是浪费他人时间提问。其最初的含义带有一定程度的攻击性,因为包含了 "fuck" 这个词。为降低这种语气,RTFM也被解释为 "Read The Friendly Manual" 或 "Read The Fine Manual",甚至简化为 "Read The Manual" (RTM),意指阅读手册即可解决问题。

       在一些使用特定语言的文化背景下,如某些加入大英联邦的国家,黑客群体可能会使用 RTBM,即 "Read The Bloody Manual",因为 "bloody" 在这些地区与 "fuck" 含义相近。然而,在美国,这种用法相对较少见。类似的黑客用语还有 RTFS,意为 "Read The Fucking Source" 或 "Read The Fucking Binary",有时会戏谑地引用《星际大战》中的台词 "Use The Force, Luke",将其简化为 "Use The Source, Luke" (UTSL),以增加幽默感,减轻攻击性。在讨论编程或软件时,如果程序文档不足,UTSL可能更合适,因为直接阅读源码被视为更直接的解决方法。

       另一个类似的表达是 RTFA,即 "Read The Fucking Article",用于回应那些没有仔细阅读原文就发表评论的人,暗示他们应先做自我学习。在年左右,Usenet上开始出现 "Search The Fucking Web" (STFW) 和 "Use the Fucking Google" (UTFG),这两个短语用来鼓励人们在提问前先进行充分的搜索,避免无谓的提问。

文章所属分类:休闲频道,点击进入>>