使用PyTorch实现简单的AlphaZero的算法(2):理解和实现蒙特卡洛树搜索

篇文章将实现AlphaZero的核心搜索算法:蒙特卡洛树搜索

蒙特卡洛树搜索(MCTS)

你可能熟悉术语蒙特卡洛[1],这是一类算法,反复进行随机抽样以获得某个结果。

例如上图,在单位正方形中选择随机点,计算圆内有多少个点,可以用来估计pi/4的值

本文中我们将详细介绍MCTS的所有步骤。但首先我们从更广泛的理解层面来说,在游戏的MCTS中,我们从给定的棋盘状态开始重复模拟玩法,一般情况下的MCTS我们会一直执行这些模拟直到游戏结束。但AlphaZero的[2]MCTS实现与传统的MCTS不同,因为在AlphaZero中我们也有一个神经网络,它正在接受训练,为给定的板子状态提供策略和值。

AlphaZero中搜索算法的输入是一个棋盘的状态(比如σ)和我们想要运行MCTS的迭代次数(也称为播放次数)。在这个游戏的例子中,搜索算法的输出是从σ中抽样一个执行动作的策略。

该树将迭代构建。树的每个节点都包含一个棋盘状态和关于在该状态下可能采取的有效操作的信息。

节点由一个状态板和键-值对组成,其中键是一个动作元组,对应的值是在父节点的状态上应用该动作元组后获得的节点。子节点是惰性初始化的(即仅在需要时初始化)

一开始,树只有根节点。它将包含输入状态σ和在σ下可以采取的有效动作。

下面是Node类的代码。

MCTS的每一次模拟分为4个步骤:选择(selection)、展开expansion)、求值(evaluation)和回溯(backup),下面我们详细进行说明

选择

MCTS算法的第一步是选择。从根节点开始选择最佳边,直到到达树的末端(表示游戏结束的终端节点/尚未探索的节点,例如上图中标记为None的节点)。

但“最佳边”是什么意思呢?应该如何遍历树?如何做到树遍历的方式是在探索和使用之间取得平衡呢?(这里的探索也是神经网络主导的)

首先解释下这里的探索(exploration)和使用(exploitation)的含义:

想象一下:你从来没有吃过豆腐脑,你也不知道甜的还是咸的好吃(比如对于北方人来说可能都没听有甜的豆腐脑)。所以只能自己尝试,假设吃了一个甜的感觉很好。但当你听到还有咸的的时候,因为还没有尝试过,肯定想尝试下,这样找到一个新的口味,这个就是探索。但是如果假设你一天只能吃一种口味的,而你更新换甜味的,想吃甜的,这就是使用。

简单总结下:选择的行动的目标都是能够获得积极奖励的,但是如果行动已经了解,这就是使用;行动是找到一些能给你带来更好奖励的行动(以前没有的),这就是探索。但是因为一次只能进行一个动作,所以就需要在两者之间取得良好的平衡。

AlphaZero使用PUCT(应用于树的预测器置信上限)规则来实现这种平衡。该规则是根据经验设计的,也是受到Rosin’s work in a bandits-with-predictors setting[3]的工作的推动。DeepMind最近的一篇论文[4]讨论了PUCT的一些替代方案。我们将在后面关于未来方向的部分中讨论这些替代方案。我们先试着理解PUCT规则。

动作值Q(s, a)表示在状态s下通过动作a获得的平均奖励。一开始,Q(s, a)是零。这个action-value代表我们在任何给定时间对奖励函数的了解,因此它与使用有关。

假设我们训练过的神经网络以0.3的概率表示我们应该执行某个动作a。那么将0.3的概率包含在PUCT规则的探索部分。状态s属于父节点,通过在“s”上执行动作“a”获得的状态属于子节点。但是这样会导致经常访问MCTS中的某个节点,为了避免这种情况并鼓励探索其他节点,子节点的访问计数包含在分母中,并使用父节点的总访问数进行规范化。

为什么要取父节点访问次数的平方根?PUCT规则是根据经验设计的,这是作者所尝试的所有选择中最有效的,也就是说是一个可以配置的超参数。我们也可以直接将其视为一种对子节点进行归一化的方式:分母中的 N+1 项。

在上图中可以看到超参数c_puct。这个常数平衡探索采和使用条件。这个超参数的典型值是2。

现在已经对如何获得PUCT(s, a)有了一定的了解,让我们继续MCTS中的选择步骤。

选择步骤如下面的块所示,即从根节点开始,重复查找具有最大PUCT值的子节点,直到到达状态仍然为None(尚未展开/初始化)的节点。

 consider_node = root// terminal nodes also have a None statewhile consider_node.state is not None:
     best_node = find_child_with_maximum_puct_value(consider_node)
     consider_node = best_nodeconsider_node has to be expanded

上面动图显示了重复查找pput值最大的子节点,直到到达状态仍然为None的节点

展开和求值

在选择了特定的动作后,下一步就就是展开并对该节点进行求值(因为其状态仍为 None)。这里的展开表示通过初始化选定节点的状态来扩展树。这种新的状态是从游戏规则中获得的。如果它是一个终端节点,我们将状态保留为 None 并在节点中设置一个标志,将其标记为带有获胜者信息的终端节点。

所选节点的所有新边也被初始化。例如上面动图中显示的树在展开所选节点后将如下图所示。

接下来就是展开节点的计算,评估指玩家在该节点的期望奖励。传统的 MCTS 使用 rollout 策略从扩展节点执行 rollout,以找出游戏结束时的值, 这个策略可以是均匀随机的。而AlphaZero的MCTS与传统的MCTS不同,在AlphaZero的MCTS中,使用神经网络的值输出来确定展开节点的值。

比如当评估一个国际象棋的位置时,我们会在脑海中计算一些走法,然后在计算结束时只使用的直觉来判断结果会有多好。在计算结束时不会像传统的 MCTS 那样进行操作,也不会在游戏结束之前使用随机动作模拟那个操作,我们只选择几个我们认为比较好的位置进行操作。

下面是代码的实现。

回溯

在对展开的节点进行评估之后,还需要更新从根节点到展开节点的所有节点的Q值(由总奖励值和总访问次数实现)。这被称为MCTS的回溯(Backup)步骤。

这一点的实现比较简单方法是使用递归地实现选择函数,

开始游戏

上面的四个步骤在一定次数的迭代中运行。如果它们运行了1000次迭代,那么总共将扩展最多1000个新节点(我们之所以说最大值,是因为某些终端节点可能被访问多次)。

在这些迭代结束后,观察根节点和它的子节点可能看起来像这样。

这些访问计数在根节点输出策略。AlphaZero 使用的想法是,如果一个节点被更多地访问,那么我们应该分配一个更高的概率来执行给该节点的根节点的动作。

某个动作的输出策略概率值与N^(1/τ)成正比,其中N是通过该动作获得的根节点子节点的访问次数,τ是某个温度(temperature )值。

从上图中我们可以看到,从AlphaZero中搜索获得的每个动作的输出策略与被提升为1/τ的结果子节点的访问计数成正比,其中τ是某个温度值。τ的高值将导致更统一的策略。可以在代码中设置为1。

然后从这个输出策略中抽样一些动作,为给定的状态进行一些操作。使用访问计数来构造输出策略是合理的,因为使用PUCT值来指导蒙特卡罗树搜索。这些PUCT价值观平衡了探索和使用。向根节点返回更多值的节点将被更频繁地访问,而一些节点将通过探索被随机访问。

这样整个AlphaZero的最基本的概念就介绍完了

References

[1] https://en.wikipedia.org/wiki/Monte_Carlo_method

[2] Silver, D., Hubert, T., Schrittwieser, J., Antonoglou, I., Lai, M., Guez, A., Lanctot, M., Sifre, L., Kumaran, D., Graepel, T., Lillicrap, T., Simonyan, K., & Hassabis, D. (2018). A general reinforcement learning algorithm that Masters Chess, Shogi, and go through self-play. Science, 362(6419), 1140–1144. https://doi.org/10.1126/science.aar6404

[3] Rosin, C. D. (2011). Multi-armed bandits with episode context. Annals of Mathematics and Artificial Intelligence, 61(3), 203–230. https://doi.org/10.1007/s10472-011-9258-6

[4] Danihelka, I., Guez, A., Schrittwieser, J., & Silver, D. Policy Improvement by planning with Gumbel. Deepmind. Retrieved February 23, 2022, from https://deepmind.com/research/publications/2022/Policy-improvement-by-planning-with-Gumbel

作者:Bentou

本文转载于网络 如有侵权请联系删除

相关文章

  • Kibana 启用 PHP APM

    本篇文章主要是解释如何使用ES的APM功能进行获取运行状态。其实官网有安装流程,我仅仅是把我安装的过程记录下。前言因为阿里云有ARMS。正好跟网上的朋友沟通的时候知道了APM。突然发现阿里云的AMRS不就是APM么。又因为ARMS只最高支持PHP7.3,所以我特意过来看下ES的APM如何安装,因为它支持PHP8.0。支持的版本也比较新。首先请按照前面两篇文章进行设置ubuntu安装ElasticSearch和ubuntu安装kibana。安装所需的包这里我们需要的是apm-server。执行以下命令即可安装:sudoaptinstallapm-server复制最后执行启动命令即可。管理命令启动:sudosystemctlstartapm-server复制停止:sudosystemctlstopapm-server复制重新启动:sudosystemctlrestartapm-server复制设置开机启动:sudosystemctlenableapm-server复制取消开机启动sudosystemctldisableapm-server复制查看运行状态sudosystemctlstatu

  • 欧盟AI监管新规:公共场合人脸识别将被限制、甚至禁止!

    作者|陈彩娴 编辑|刘冰一继2018年5月25生效的《欧盟一般数据保护条例》(江湖人称“GDPR”)后,今年4月21日,欧盟首次发布了针对人工智能技术的监管法规草案(如下),长达108页,探讨了企业与政府应该如何使用人工智能技术。链接:https://digital-strategy.ec.europa.eu/en/library/proposal-regulation-laying-down-harmonised-rules-artificial-intelligence-artificial-intelligence在这项草案中,AI的一系列应用范围都受到了不同程度的限制,包括自动驾驶、招聘简历过滤、银行贷款、入学筛选与测试评分等等,原因是:可能威胁到公民的人身安全与基本权利。其中,计算机视觉应用最火的人脸识别技术,在公共场合的应用更是被高度禁止,除非是用于寻找失踪儿童、防止恐怖活动威胁或识别刑事犯罪人员等特殊情况。即使有特殊情况,在公共场合应用人脸识别技术,也需要获得司法机构或其他独立机构的授权,并有时间、地理范围和所搜索数据库的限制。违反新法规的公司,可能会面临高达全球销售额6

  • Android使用多线程进行网络聊天室通信

    TCP/IP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket,从而在通信的两端之间形成网络虚拟链路。一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信了。Java对基于TCP协议的网络通信提供了良好的封装,Java使用Socket对象来代表两端通信接口,并通过Socket产生IO流来进行网络通信。下面的程序Demo是实现一个简单的C/S聊天室的应用,每个客户端该包含两条线程:一条负责生成主界面,响应用户动作,并将用户输入的数据写入Socket对应的输出流中;另一条负责读取Socket对应的输入流中的数据(从服务器发送过来的数据),并负责将这些数据在程序界面上显示出来。 客户端程序是一个Android应用,因此需要创建一个Android项目,这个Android应用的界面中包含两个文本框:一个用于接收用户的输入;另一个用于显示聊天信息。界面中还有一个按钮,当用户单击该按钮时,程序向服务器发送聊天信息。 layout/activity_main.xml界面布局代码如下:<?xmlversion="1.0"encoding="u

  • Java 中 Hashtable 、HashMap 、TreeMap 有什么不同?

    Java中Hashtable、HashMap、TreeMap有什么不同?HashTable最早期的Java类库提供的一个Hash表实现,本身是同步的,不支持null键和值,对同步有导致性能开销,很少被推荐使用。HashMap是应该更加广泛的哈希表实现,行为上与hashtable一致,主要区别是Hashmap不是同步的,支持null建和值。HashMap进行put或者get操作,可以达到常熟时间的性能,所以绝大多数场景都使用HashMap。TreeMap则是基于红黑树提供的顺序访问的。与HashMap不同,它的getputremove之类的操作都是O(log(N))的时间复杂度,具体顺序可以通过的Comparator或者根据键的自然顺序来判断。Map整体结构Hashtable是扩展了Dictonary类,类结构上与HashMap之类不同,HashMap继承的是abstractMapHashMap等其他Map都是扩展了AbstractMap,里面包含了通用方法抽象。HashMap的性能表现非常依赖哈希表的有效性。equals和hashcode基本约定equals相等,hashcode一定要

  • Java基础--线程池

    1.为什么要使用线程池?我们知道,操作系统创建线程、切换线程状态、终结线程都要进行CPU调度--这是一个耗费时间和系统资源的事情。服务端应用程序例如web应用中,比较常见的情况是:每当一个请求到达就创建一个新线程,然后在新线程中为请求服务。 每个请求对应一个线程(thread-per-request)方法的不足之一是:为每个请求创建一个新线程的开销很大;为每个请求创建新线程的服务器在创建和销毁线程上花费的时间和消耗的系统资源要比花在处理实际的用户请求的时间和资源更多。除了创建和销毁线程的开销之外,活动的线程也消耗系统资源(线程的生命周期!)。在一个JVM里创建太多的线程可能会导致系统由于过度消耗内存而用完内存或“切换过度”。为了防止资源不足,服务器应用程序需要一些办法来限制任何给定时刻处理的请求数目。 线程池为线程生命周期开销问题和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。其好处是,因为在请求到达时线程已经存在,所以无意中也消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使应用程序响应更快。而且,通过适当地调整线程池中的线程数目,也

  • Google搜索大调整:搜索结果如有精准答案,不再显示网页结果

    Google就是Google!今天在浏览科技新闻的时候,突然看到这样一条新闻: 谷歌搜索开始进行一个重大调整,如果谷歌自有的工具能够直接给出搜索答案,谷歌将取消传统的网页搜索结果。什么意思呢?有用户发现谷歌目前已经在移动端开发测试不显示搜索结果的回答,当谷歌对于某个用户搜索的请求或者问题有精确答案或者足够有把握的时候,将会直接回答,不再显示其他的搜索结果,取消之前传统网页的搜索结果,目前范围包括:计算器,单位换算,时钟等等。据国外科技媒体报道,Google经常通过优化排名和添加新功能来更新搜索。最新版本涉及图像缩略图以及移动设备上的Google搜索结果,以帮助用户确定是否要点击页面。一些业内人士最近在谷歌网页搜索的移动版、安卓版和iOS版中发现了谷歌的调整,而谷歌也证实了这一改动发生。过去,谷歌已经在网页搜索结果中提供了一些系统工具,其中包括内置计算器、单位换算器、时钟等。但是在提供答案之后谷歌继续显示网页结果。不过在谷歌最新的界面设计中,除了一个搜索框之外只有底部的答案,网页结果已经消失。据报道,谷歌曾经在今年二月初对于精简版网页结果进行了试验,以便加快网页载入速度。实际上,在系统工

  • 教你用翻译软件快速阅读大量英文文献

    对于一些引用的英文文献,我们需要快速地了解整篇文献讲了什么内容,来判断是否可以作为“国内外研究现状”来进行详细分析。 通常文献都是CAJ或者PDF格式的,这样格式文献的缺点在于,复制粘贴后会产生断行,例如完整的一段“摘要”在复制粘贴后变成了一行一行的: 原因在于PDF排版的时候添加了这样的换行符,这样带来的后果是百度翻译把每一行当作单独的一句话,造成歧译、错译、漏译,或者完全不是一句话: 可以看到, 被翻译成了: 上一行通过猜测得到了正确翻译,下一行误以为EST是一个专有名词,所以需要想办法解决这个问题。 1用WORD去掉换行符,愉快地翻译 当然,一种方法是手动地去掉换行符,我试过,手很累,心更累。 正确姿势是用WORD替换: 【第一步】复制PDF中的文字 【第二步】粘贴到word文档中,按CTRL+H呼出替换界面 【第三步】<替换>选项中,查找内容设置为^p,替换内容为一个空格 【第四步】点击全部替换 【第五步】复制到百度翻译,正确地翻译,然后一行一行地看大概意思(百度翻译得并不好) 如果有条件访问外国网站的话,可以用Google翻译,毕竟是Google翻译是神经网络,效

  • 图片延迟加载

    官网:http://www.appelsiini.net/projects/lazyload使用例子:http://demo.phpfs.com/lazyload/代码下载:lazyload代码解析$(function(){ varERROR_IMG='./img/image_err.gif'; varLOADER_IMG='./img/lazy_loading.gif'; $("img").one("error",function(){ $(this).attr("src",ERROR_IMG); }); $.each($("img"),function(i,n){ if($(n).attr("src")=='') $(n).attr("src",ERROR_IMG); }); $(".lazy").lazyload({ placeholder:LOADE

  • 使用WPF教你一步一步实现连连看(一)

    第一步:  问题,怎样动态的建立一个10*10的grid(布局)第二步:  问题,怎样将button放到上边那个布局中(这个是我查资料才知道的,一般都是用Grid在前台布局)for(inti=0;i<10;i++) {   for(intj=0;j<10;j++) {   Buttonbtn=newButton();   btn.Content=i+","+j;   Grid.SetColumn(btn,j);   Grid.SetRow(btn,i);   GridGame.Children.Add(btn); } }复制效果如图1第三步:  问题3,怎样将Button的内容换成随机图片呢? Randomrandom=newRandom(); for(inti=0;i<10;i++) { for(intj=0;j<10;j++) { intimgName=random.Next(1,10);//生成一个>=1,<10的随机数 Imageimg=newImage(); img.Source=newBitmapImage

  • create database 要几次 IO

    背景好久没有更新文章了,请相信我绝对不是因为投资亏麻了心情不好。而是因为我去准备黑科技了。 我搞了一个叫`bcc-python`的项目,通过eBPF非常简单的拿到`linuxkernelevent`。如果内核的事件我都知道,那程序做了些什么不就完全暴露在我眼皮子下了吗?老夫现在再也不怕mysqlhang死在我面前了。 安装工具第一步安装依赖 yum-yinstallbcc-tools复制第二步安装我的`bcc-python`前端pip3installbcc-python复制到了这里你就可以直接使用bcc-python提供的90几个观测程序,包括内存、网络、磁盘、cpu、进程...!好了不吹了,就到这里开始干正事。观测createdatabase会有几个IO第一步找到mysqld的PID ps-ef|grepmysqld mysql33+27789581020:07?00:00:04/usr/local/mysql-8.0.29-linux-glibc2.12-x86_64/bin/mysqld--defaults-file=/etc/my-3306.cnf root2794044279

  • 可在广域网部署运行的QQ高仿版 -- GG叽叽V3.0,完善基础功能(源码)

      (前段时间封闭式开发完了一个项目,最近才有时间继续更新GG的后续版本,对那些关注GG的朋友来说,真的是很抱歉。)GG的前面几个版本开发了一些比较高级的功能,像视频聊天、远程桌面、文件传送、远程磁盘等,但是,有一些基础且必需的功能一直未实现,比如注册、添加好友、加入群、群聊天等等。经常有朋友留言问这些功能要怎么做,GG3.0终于可以给出一个答案了。   先提醒一下,GG3.0中这些基础功能的实现方式是比较粗糙的,我还没有时间深入考虑性能、缓存等问题(源码中我以“建议”的字样标注了需要优化的地方),后续版本,我会将它们一一优化。由于长时间未更新,迫不及待地先放个版本出来给大家参考。 一.GGV3.0新增功能展现  (1)注册新帐号。 (2)添加好友(包括:通知对方、好友上下线通知)。 (3)加入群(包括:通知其它群友、群友上下线通知)。 (4)群聊天。 (5)以前版本的帐号只能为数字(就像QQ一样),但是,3.0及以后版本,帐号中可以包含字母。    废话不多说,还是先上图。   注册:      &nbs

  • EntityFramework批量写入和修改数据

    GitHub上项目地址:https://github.com/shendashan/BulkCopy 最近在工作中遇到一些性能问题,在大批量的数据写入和修改数据库时太慢了,甚至会出现操作超时。 所以去网上找了下资料,找到了一些解决方案SqlBulkCopy和SqlDataAdapter(SqlDataAdapter实测了下,批量修改数据的时候速度不快,可能是我使用的姿势不对。哪位大神知道正确使用姿势,望留言指点)。下面主要介绍下SqlBulkCopy: SqlBulkCopy   SqlBulkCopy用于做批量写入的操作,通过调用WriteToServer方法来实现批量写入的功能(WriteToServer方法的实现原理没有深入研究)。从测试结果来看,执行效率完全可以满足公司的需求。   壹.先来说下批量写入的具体实现:     1.在调用WriteToServer方法之前,需要准备一个DataTable实例,DataTable的实例中存放的是需要批量写入的数据集。       如图,先创建一个生成DataTable的方法:      

  • M2

    1#1 2print(sum(range(1,101))) 3#2 4a=1 5deffunc(): 6globala 7a=2 8print(a) 9func() 10 11#4、字典如何删除键和合并两个字典 12dict={ 13'name':'A', 14'age':18 15} 16dict2={ 17'score':100, 18'sex':'nv' 19} 20 21#dict.pop('name')#删除方法1 22#deldict['name']#删除方法2 23dict.update(dict2) 24print(dict) 25 26#6列表去重方法 27li=[1,2,1,2,3,4,5,1] 28print(list(set(li))) 29 30#7fun(*args,**kwargs)中的*args,**kwargs什么意思? 31print('*'*50) 32deffoo(*args,**kwargs): 33print('args=',args) 34print('kwargs=',kwargs) 35print('******************

  • 1068 Find More Coins (30分) 01背包+路径

    题目 https://pintia.cn/problem-sets/994805342720868352/problems/994805402305150976 题意 N个硬币,选出一些,刚好凑成M元 按01背包求解,背包容量M,若能装满,说明有解 若有多解,输出序列最小的(难点) SampleInput1: 89 59872341 SampleOutput1: 135(234、9...) SampleInput2: 48 7243 SampleOutput2: NoSolution 思路 对01背包算法改进下,序列降序排序 标记f[][] 参考:https://www.liuchuo.net/archives/2323 code #include<bits/stdc++.h> usingnamespacestd; typedeflonglongll; intN,M; intw[10006]; intdp[10006][101];//前i个硬币选出价值为j的 intf[10006][101]; boolcmp(intx,inty){returnx>y;} intmai

  • pytorch在损失函数中为权重添加L1正则化

    L1正则化可以使权重变稀疏,应用场景:对one-hot词袋模型中的词表进行裁剪时,根据权重weight筛选,此时需要权重越稀疏越好; L1_Weight为超参数,可设定为1e-4 1deftrain(model,iterator,optimizer,criteon): 2avg_acc,avg_loss=[],[] 3model.train() 4 5forbatchintqdm(iterator): 6text,label=batch[0].cuda(),batch[1].cuda() 7 8pred=model(text) 9l1_penalty=L1_Weight*sum([p.abs().sum()forpinmodel.fc.parameters()]) 10loss=criteon(pred,label.long()) 11loss_with_penalty=loss+l1_penalty 12 13acc=utils.binary_acc(torch.argmax(pred.cpu(),dim=1),label.cpu().long()) 14avg_acc.appen

  • MySQL建立索引的原则

    1、表的主键、外键必须有索引; 2、数据量超过300的表应该有索引; 3、经常与其他表进行连接的表,在连接字段上应该建立索引; 4、经常出现在Where子句中的字段,特别是大表的字段,应该建立索引; 5、索引应该建在选择性高的字段上; 6、索引应该建在小字段上,对于大的文本字段甚至超长字段,不要建索引;

  • Oracle常用分支语句

    case简单函数:写法简单,但表达范围受限,适合处理简单逻辑。 CASEsex WHEN'1'THEN'男' WHEN'2'THEN'女' ELSE'其他'END 复制 case搜索函数:写法灵活,可以写出复杂逻辑。 SELECT 'INTEGRAT'ASorigin, end_time+length_daysASbegin_time, CASE WHENfffff='0'THENend_time ELSE CASEWHENend_time-length_days>sysdateTHENsysdateELSEend_time-length_daysEND ENDASend_time FROM zg_time_config WHERE upper(table_name)=upper('tb_mz_sfmxb') ANDupper(origin)=upper('INTEGRAT') 复制 DECODE函数。 DECODE(求值表达式,值1,返回值1,值2,返回值2,…值n,返回值n,缺省值),非常有用,在简单求值计算场景下,能大大简化冗长的sql语句

  • C#.NET最好用的主流开发框架 - 开源框架平台

    C#.NET最好用的主流开发框架-开源框架平台 C#.NETC/S系统快速开发框架旗舰版V5.0(UltimateEdition)    适用开发:制造、服务、零售、商贸等行业的ERP、MRP、MES、CRM、MIS、HIS、POS数据管理应用系统 运行平台:Windows+.NETFramework4.5 开发工具:VisualStudio2015+,C#语言 数据库:MicrosoftSQLServer2008R2+(支持多数据库:Oracle/MySql)     开发框架详情:http://www.csframework.com/cs-framework-5.0.htm  产品介绍   C/S系统开发框架旗舰版为软件团队提供强大的技术支撑以及快速开发能力,开发框架集成大量的通用开发包与工具实用类,提供丰富的例子,借助技术文档、网站资源、演示源码以及在线技术指导,用户能快速投入研发自己的项目。自2007年首发开发框架以来,我们成功积累了2000多位用户,其中包括数百家软

  • DI(Dependency Injection) of FASTAPI

    DI(DependencyInjection) https://www.freecodecamp.org/news/a-quick-intro-to-dependency-injection-what-it-is-and-when-to-use-it-7578c84fa88f/ 依赖注入 当一个类A依赖另外一个类B时候,此类A不需要自己创建被依赖类B, 而是有外部创建,一般是框架创建,然后注入到类A中。 WhenclassAusessomefunctionalityofclassB,thenitssaidthatclassAhasadependencyofclassB. InJava,beforewecanusemethodsofotherclasses,wefirstneedtocreatetheobjectofthatclass(i.e.classAneedstocreateaninstanceofclassB). So,transferringthetaskofcreatingtheobjecttosomeoneelseanddirectlyusingthedependency

  • 红黑树新解(插入)

    1. 简介 红黑树是一种自平衡二叉查找树,在查找,插入和删除几个方面,性能都可以做到O(lgN)。 那怎么实现呢,首先要先看看红黑树的5个特性,只有满足这5个特性,才是红黑树。                   每个结点都有父结点(parent),左子结点(left)和右子结点(right),root的父结点是leaf结点。 下图便是一个简单的红黑树, 60为根结点,60的左子结点为30,60的右子结点为80,60的父结点为leaf结点;  30的父结点为60,30的左子结点和右子结点为leaf结点; 80的父结点为60,80的左子结点和右子结点为leaf结点;   为了简洁,后续图片将不显示leaf结点和left,right和parent关系描述。       2.旋转  在插入和删除元素的时候,都会用到旋转,所以有必要先介绍一下。 2.1左转 比如选定一结点60,进行左转,实际是将xl转成x.right的left,有点绕

  • 7.10 上周内容回顾

    上周内容回顾 与**在实参的作用 如果我们在实参前面加星号,本质是将实参进行for循环然后得出来的值当做实参传入形参当然*args后可以跟多个可循环的数据类型如果是字典只传入k**kwargs只能使用字典表示将k=v的的格式传入 #列表上使用 deffunc(*args,**kwargs): print(args) print(kwargs) l1=[1,2,3,4,5,6] func(*l1)#(1,2,3,4,5,6) #字符串上使用 deffunc(*args,**kwargs): print(args) print(kwargs) s1='heihei' func(*s1)#('h','e','i','h','e','i') #字典上使用 deffunc(*args,**kwargs): print(args) print(kwargs) d1={'name':'joekr','age':18} func(**d1)#{'name':'joker','age':18} func(name='joker',age=18)#{'name':'joker','age':18}

相关推荐

推荐阅读