React Router V4使用介绍(一)

本文将和大家一起来看一下react router V4的使用,主要是通过一些实例来给大家展示如何编写一些代码。 环境准备 首先,我假设你基本的环境tool已经有了,比如npm,node等都已经装好了。 根据react官网,使用下面命令创建一个react的app React-router-tutorial-V4\lessons>npx create-react-app 01-setting-up 一切完成之后,让我们运行一下看看效果如何: npm start 这是我们最经典的react demo界面了,下面我们对代码做一些调整。把App.js中的代码进行一些简化,就直接返回一个div好了     这样我们看到的ui界面就如下所示了: 下面我们回到主题,我们要学习的是react router,所以,是时候加入react router了。 npm install –save react-router 在react router v4中,我们其实把router分成了三个包,除了我们之前熟悉的react-router外,还有两个包一个是react-router-dom,一个在react-router-native。这两个包其实是为了不同的运行环境提供其所需的特定组件。前者服务于我们常见的桌面浏览器,后者则专用于native部分。因此,我们再安装一下react router dom。 npm install –save react-router-dom 其实,react-router-dom中也包含了react-router的内容,只要安装它也就可以了。喜欢代码整洁,并不考虑跨平台移植代码的同学可以直接只使用react-router-dom。 至此,我们的环境准备部分就结束了。 假如你想直接使用我们相应的code,可以从github中直接下载相应的code: https://github.com/xdyang1986/React-router-tutorial-V4.git 基本router的rendering 首先我们需要让我们的url和对应的页面保持一致,也就是说在改变url,浏览器前进后退等操作的时候,我们能够同步对应的页面进行显示。在react router中目前提供了两种方法,一种是<BrowserRouter>,一种是<HashRouter>,前者是使用HTML5的history API(pushState, replaceState和poststate event)来保持同步的,后者则是使用URL的hash(比如window.location.hash)来保持URL和UI的同步的。需要重点注意的,<HashRouter>目前不支持location.key和location.state。总得来说,<HashRouter>更多的是为了和之前的浏览器兼容,目前还是推荐使用<BrowserRouter>。 所以,我们在代码中使用BrowserRouter。 先import我们需要使用的内容,下面再加入对应的route 这是我们可以看到其实浏览器的显示并没有什么特别大的改变,但其实此时我们是有了很多关于history的信息处理的,下面我们来多加入几个页面进行测试。 在src目录下加入About.js和Repos.js About.js    Repos.js 这时,我们继续修改index.js文件,为这几个page加上route: 这时,我们再进行测试,主要测试这两个页面: http://localhost:3000/about 以及http://localhost:3000/repos 并做一些浏览器的前进和后退的操作,我们可以看到一切和我们预想的还是一样的。 Link的使用 在我们的项目中其实用的比较多的就是link,达到的效果就是click某一个link之后,就会跳转到某一个页面,click另一个link则会跳转到另外一个页面,在react reouter里面可以通过link来简单实现。我们直接修改app.js如下,import进link 这是,打开对应的网页如下所示: 我们点击对应的about和repos超链接,可以到达不同的页面:…

Read More »

SQL Server中index column顺序不同所产生的性能差别

众所周知,我们在SQL Server性能调优的时候第一反应大概就是去check是否有index了,很多文章都会去讲如何加index,甚至SQL Server自己也会有很多index miss的提示,让你能够去build一个index。然而,真正去做性能调优的人会发现我们在工程产品中,一个query并不仅仅是一个简单的where或者一个join on,有时会涉及到一个表中多个column的filter,那么是不是简单的把这些column加到一个index中就万事大吉了呢,我看并不见得。本文就和大家一起来看看,index中column的不同顺序所带来的performance差异。 前期准备 本文有很多的测试来说明我们的讲解,所以需要大家准备一些软件和数据 SQL Server,我装的是SQL Server 2017 SQL Server sample data: https://github.com/Microsoft/sql-server-samples/releases, 本文使用的是AdventureWorks-oltp-install-script.zip进行测试。 数据准备 因为原来的sample table中没有一个很好的测试table,我们用他们的数据来重新create一个,这样也可以重新全面控制我们的index: 我们创建了一个dbo.SalesOrderHeader来进行测试,并对其中的数据进行了一些简单的处理。 测试步骤 我们主要来看一下下面这个query的performance如何: 我们可以看到这个进行的是一个table scan,这样的performance必然不会太好,所以我们的第一反应肯定就是创建一个index,我们可以看到我们的where是在revisionnumber,status,modifieddate这三个列上,那我们是不是就直接在这三列中创建index呢,所以我们可以创建如下所示的index: 在创建index后,我们看一下execution plan,果然不出所料,他如我们所想地用了clustered index seek: 我们从下面IO info可以看到 (可以通过set statistics io on 打开),有6次scan,logical read是363次。 这时候是不是就万事大吉了呢,假如我们把index的顺序更换一下结果相同吗,让我们来实验一下: 这个时候我们再执行上面的query的,我们会发现,虽然还是clustered index seek,但是这次的io read次数和之前的index有了很大不同: Logical reads的数目从之前的363变到了811次,显然performance有了很大的不同。 也就是说index中column不同的顺序,其实会引起performance的差异的,这个差异会随着你涉及的column的数目越多越明显。 追根溯源 实验可以用来说明结论,那我们可能还需要再进一步去看看这究竟是为什么呢?我们来看一下这两个不同的index究竟是怎么存储的。 先来看看我们后面这次的index情况,也就是modifiedDate是第一个,status和revisionnumber紧随其后。 我们下面的语句来看看index的情况 可以得到下面这个结果: 这个结果中的各个column的含义如下: PageFID:页面中的File的ID PagePID: PageId IAMFID: 和这个页面对应的IAM页面的file ID IAMPID: 和这个页面对应的IAM页面的page id ObjectID:这个页面的objectID…

Read More »

SQL Server执行计划详细介绍(三)

前面的SQL Server执行计划详细介绍(一)和SQL Server执行计划详细介绍(二)中我们介绍了SQL Server执行计划中的数据访问操作、书签查询以及聚合,本章节我们来介绍平时遇到得最多的join操作。 总的来说,我们会介绍三种join操作,分别是Nested Loops Join,Merge Join以及Hash Join,它们三者并没有绝对的谁好谁坏,只能说在不同的情况下有不同的join类型更适合。 Nested Loops Join 我们先来看一个Nested Loops Join的例子: 这里我们可以看到Nested Loops Join有两个输入,一个在上面,一个在下面。我们把在上面的称之为Outer Input,把在下面的称之为Inner Input。在真正执行的时候,Outer Input只会执行一次,而Inner Input则会执行Inner Input中的行数的次数,也就是说如果outer Input中有10行,我们就会执行Inner Input十次,就是会遍历Outer Input中的所有record。 在这个例子中我们可以看到上面的salesPerson的index seek的Number Of Executions是1,就是只执行了一次,而它的record数目就是Actual Number Of Batches是17,从而在下面的Employee的clustered index seek的Number of Executions就是上面的record数目,也就是执行了17次。 假如我们加一些过滤让上面的结果record变得少一点,那下面的也就会少执行几次: 我们可以点击看看对应的property,这是上面的record只有3,这样下面的执行次数也就只有3了。具体的数值这里我就不截图了。 从我们上面的解释不难看出,假如我们outter input的数目比较少,而下面的inner input又在join的key上有index的情况下,Nested Loops Join会是一个很好的选择。 Merge Join 下面我们来看一个Merge Join的例子: 和Nested Loops Join不同的是,Merge Join的两个输入都只执行一次,这一点大家可以通过查看对应的属性来确认,另外一个不同就是join的操作需要时一个相等的操作,我们这个例子里是SalesOrderId,并且他们的输入必须是join predicate有序的,我们这里可以看到都是index的scan而且都是salesorderid。 正是因为这个在join join predicatte上有序,所以在merge join的时候,我们只要同时从输入拿出一行进行比较,如果相等,我们就返回,如果不等,我们就把值小的那一行拿去除掉,因为他们是有序排列的,所以肯定不会再有一行和值小的这一行匹配了。当其中一个遍历完成之后,我们就结束了,哪怕另外一个还有很多行,也没有必要再去check了。 从这个角度来看,merge join的最大复杂度就是两个输入的和。 假如我们输入的两个列在join…

Read More »

SQL Server执行计划详细介绍(二)

我们在前文SQL Server执行计划详细介绍(一)中详细介绍了执行计划中的数据访问操作和书签查询,本文继续来介绍聚合操作。 聚合(Aggregation) 聚合一般用在一些总结的操作上,比如我们常见的求和或者平均数等操作(SUM, AVG, MAX等),在SQL Server中有两种聚合的操作:流聚合(Stream Aggregate)和哈希聚合(Hash Aggregate)。 排序和哈希 在具体介绍聚合操作之前,简单提一下排序和哈希,流聚合其实是要求数据是有序的,我们可以使用已存在的index或者就显式地进行排序操作。当然排序会使用到memory,假如可用的memory不足,它会使用tempdb,这种情况下必然也就会影响到performance了。同样的,哈希会在哈希聚合中使用,我们需要在memory中建立一个哈希表。无论排序还是哈希操作都是一个block的操作,也就是说我们必须等他们全部完成了才能进行下一步的操作。 流聚合 AVG 首先我们来看一下流聚合的操作例子,我们来看一个ListPrice的平均值: 我们查看Stream Aggregate的property(鼠标右击选择属性),可以看到整个Stream Aggregate的过程,他其实是先求的count和SUM,他们分别保存在Expr1003和Expr1004中。 在随后的Compute Scalar中,会check count数目(本例就是Expr1003)是不是0,如果是0就直接返回null,否则就用sum除以count来得到平均数。具体看下图Compute Scalar中的属性: Group by 下面我们继续来看一个Group by的例子: Group by总是要求输入是一个有序的值,所以我们可以看到在我们的例子这种会先根据ProductLine进行排序,在排序之后,我们就可以发现同样值的ProductLine就在一起了,那么在Stream Aggregate中计算count的时候的就很简单了,直接一条一条判断就可以了,是一个group的就加一,不是就新建一行。具体的Stream Aggregate操作如下图所示: 就像我们之前提到的,Group by其实是可以利用index来进行的,这样就不需要额外的排序操作了: 这个例子中,salesorderid其实是一个index,我们知道它肯定是排好序的,所以我们不需要额外的排序操作。 哈希聚合 哈希聚合一般会用在数据量比较大的表格中,当我们group的column是没有index的,并且输出的结果行也比较少的时候,我们会发现去排序整个表不太值得,我们就有可能会决定不去对整个表排序,而使用哈希聚合,见下面这个例子,我们在revisionNumber这一列上没有index,整个表有三万多行,但聚合后只有两行输出: 哈希聚合就是首先在内存中创建一个哈希表,因为我们之前也会遍历整个表,所以在遍历的时候,我们就会为每一行创建哈希key,这个哈希key可以在hash match的属性表中看到,我们这里很明显哈希key就是revisionnumber,然后每一行就会看对应的哈希值是不是已经在表中存在了,若是不存在就加一行。整个哈希表每一个哈希值一行,这也是为什么我们会说在输出(其实就是哈希值)行比较少的情况下选择会比较好,因为这样我们所建的哈希表就会相对较小。 就像我们上面提到的,哈希聚合只是说在你没有排序的情况下会使用,假如我们创建了一个index,这个时候,我们可能就直接选择一个流聚合而不需要再使用哈希聚合了,如下所示: 在继续之前让我们先把上面创建的index拿drop掉: 我想这个时候,你可能会问假如我们的表格没有排序,但是我们的query中有显式的要求排序,这个时候我们排序还是不排序呢,其实这时候我们有两种选择,一种选择就是和之前的流聚合一样,就是先排序,然后再流聚合。但是也有一个可能就是我们会先进行哈希聚合,然后再对结果进行排序。这个也不难理解,就看这两者谁的cost更小,我们就会选择谁。 还是上面的例子,我们在query中显式要求按照revisionnumber进行排序: 我们可以看到这次,我们选择的是先哈希聚合,然后再对结果进行排序。 最后我们来看一下distinct操作,其实distinct和group by是类似的,我们其实一般可以用group by来重写distinct,假如有index的话,我们显然可以直接使用流聚合操作。假如没有index,我们就需要使用distinct sort或者哈希聚合了。 可以看到这个例子中,就是使用了distinct sort,会排序并去除重复的行。 同样的,对于一个没有index的大表,我们可能会选择哈希聚合操作,如下所示: 至此,我们关于聚合操作的介绍就到这里结束了,下一章节我们会讲一讲join操作,敬请期待。 转载请注明出处:http://www.softlifelogging.com/?p=179&preview=true 更多精彩内容,敬请关注微信公众号:随手记生活

Read More »

SQL Server执行计划详细介绍(一)

众所周知,SQL Server是我们常见的关系型数据库之一。简单地写出一个存储过程实现businees功能并不是一件难事,难的是如何写出一个高效的存储过程,快速地得到我们想要的结果。也就是我们通常所说的高性能存储过程,或者说性能优化。在进行具体说明之前我们首先来看一下一个查询的执行流程是什么样的。 查询过程 SQL Server数据库引擎由两个主要部分组成,一个是存储引擎,一个是查询过程。前者主要负责磁盘和内存中进行的数据读取以及对数据一致性的保证。而后者则顾名思义,主要就是接受查询,设计出一个优化的计划,然后执行这个计划,并最终返回结果。 整个查询过程所做的事情可以用下图来表示: 当我们接收到一个查询语句的时候,第一件事就是解析这个查询语句,主要是看看他要查的是哪一个表啊,做得是一个什么join啊之类的。这一步也至少保证了我们的接收的查询是能够被解析的,否则我们也不能知道究竟要做些什么,这里的检查更多的是语法方面的检查。 在解析完成之后,就是进行绑定,这个步骤更多的是一个名字的对应处理,就是把上一步解析出来的内容和我们系统中真实存在一些object进行对应,顺便也检查这些object是否真实存在,从而进一步对查询的有效性进行验证,当然这里的检查就更多地是逻辑方面的检查了。 在绑定之后,我们就需要找到一个最优的执行计划,这里会有列出一些可能的执行计划,然后从中找到最优的执行计划。然而现实并不总是如我们所料,比如说一个查询语句,可能有几万,几十万设置几百万种执行计划,我们不可能遍历检查所有的执行计划,然后找出最优的执行计划。因为这个遍历也是要花时间的,所以这里会有一个trade off。因为我们只能说这里会找到一个局部最优解(想到了AI,哈哈)来设置执行计划。 有了查询优化后的结果,在后面两步中我们就不需要动太多的脑筋,只要执行这个查询,并返回结果就可以了。 了解了这样的整个过程之后,我们知道其实所有的关键就是查询优化这一块,为了让这一步能够选择出最优的执行计划,我们首先要来看一下它究竟会产生哪些执行计划,然后再来分析这些执行计划在我们的场景中是好还是不好,或者在我们的场景中哪些步骤占据了大的时间块,从而才可以给我们优化提供具体的建议。因此本文主要就是介绍SQL Server中的执行计划。 前期准备 本文有很多的测试来说明我们的讲解,所以需要大家准备一些软件和数据 SQL Server,我装的是SQL Server 2017 SQL Server sample data: https://github.com/Microsoft/sql-server-samples/releases, 本文使用的是AdventureWorks-oltp-install-script.zip进行测试。 数据访问的操作 首先我们来看一下数据访问的操作,所谓数据访问就是直接访问数据,可以是访问一个表也可以是访问一个索引。通常有两种方法:一种是扫描(scan)一种是查找(seek)。扫描就是读取整个结构,可以访问一个heap或者一个clustered索引或者一个non-clustered索引。而查找则不会读取整个结构,他则是更高效地通过索引访问一行,所以从这个角度来看,查找就只能应用在索引上面了。简单总结如下表所示: 下面我们来看一下几个扫描的例子,在开始具体的例子之前,先说明一下如何得到执行计划,其实得到执行计划有很多种方法,一种最简单的方法就是SSMS中打开‘include actual exectution plan’选项,如下图所示: 全表扫描 这样我们就可以看到下面的执行计划: 我们可以清楚的看到这里就是一个全表的扫描。 Clustered index 扫描 这样我们可以看到下面的执行计划: 这里有一点需要强调的是,尽管我们的clustered index在保存的时候是有序的,但并不保证我们通过他进行scan出来的结果也是有序排列的,因此如果你想要按照clustered index进行排序的结果,那么请显式加上order by的语句,如下图所示: 此时把鼠标放到clustered index scan的图标上可以看到如下结果: 这里我们可以看到在弹出的property窗口中的ordered属性被置为了true,这就表明我们的结果是有序的,而你要是返回去看看之前的这个属性,毫无疑问,它是false的。 Non-clustered index 扫描我们可以看到下面的结果: 这个例子中,我们查询了addressId, city以及stateProvinceId三列,而我们查询的是IX_AddressLine1_AddressLine2_City_StateProvinceID_PostalCode,这个index是基于AddressLine1,AddressLine2,City,StateProvinceID和PostalCode这几列产生的,很显然city和stateProvinceId是处于这个index中的,但是addressID却不在其中,那我们究竟怎么得到这一列的呢?其实原因很简单,addressId是一个clustered index,所有的non clustered index都默认包含 clustered index这一列,所以我们在设置non clustered index的时候没有必要显式包含clustered index的列。 上面所说的三种扫描都会扫描所有的结构,下面我们来看看查询的一些例子:…

Read More »

Azure Service Bus简单介绍和使用

我们知道现如今不同的应用或者服务之间decouple的趋势是越来越明显,而随之而来的问题就是这些应用或者服务之间如何进行可靠以及安全的数据或者状态传输,我们可以通过基于MSMQ或者RabbitMQ之类消息中间件来自我搭建和维护一套方案,也可以选择微软为我们提供的企业级的基于Azure的Service Bus来达到我们的目的。本文就来详细讨论一下Azure Service Bus。 两种基本的场景 目前Azure service Bus支持两种基本的场景,一种是基于Queue的message传输,一种是基于Topics的传输: Queue 发送端会把Message发送到queue,然后接收端从queue中接收message,在queue中的message是有序的,也就是所谓的FIFO,并且接收方只能是单一的,如下图所示: Topics 和Queue不同的是,一般来说Queue是点对点的传输,而Topic这是一个publish/subscribe的情况,也就是说可以有多接收方(我们称之为Subscriber),每个接收方都可以接收到一份完整的message的拷贝,你甚至可以设置一些filter的条件来决定在什么情况下哪些message可以被其中特定的receiver接收。 基本场景的实现 在我们实现sender和receiver端的代码之前,我们需要首先在azure上创建用来接收的queue或者topics,这个时候你需要一个Azure的订阅,或者注册一个免费的用户。 基于Queue的基本场景的实现 下面我们先来看一下基本场景中基于Queue的message的发送和接收如何用.NET代码实现: Azure portal中queue的创建 在Azure Portal中创建一个resource,选择service bus。 然后我们创建一个namespace,所谓的namespace就是一个container,他用来存放所有的service bus相关的内容,我们的queue或者topics都是建立于它的基础上。 在创建好了namespace之后,我们可以点击到刚刚创建的namespace,选择“Shared access policies”,然后点击“RootManageShareAccessKey”来得到我们在代码中需要使用的connection string,这里我们把primary connection string拷贝下来,后面的代码中会用到。 在我们的namespace中创建一个queue Send端的代码实现 在Portal创建好queue之后,我们就可以连接这个queue,进行发送message了。首先新建一个queueClient: 这里的ServiceBusConnectionString就是我们刚刚在portal那边拷贝的primary connection string。QueueName是我们创建的queue的名字。 然后利用下面的SendMessagesAsync来发送message到queue中: receiver端的代码实现 在我们发送message到queue中后,我们需要些一个简单的receiver端的代码来接收message,同样首先要创建一个queueClient: 通过RegisterMessageHandler来注册receiver的handler函数: 运行的结果 send端代码运行后的结果如下: 此时我们去azure portal的queue的界面可以看到有10个message在那边 此时再运行receiver端的代码,结果如下: 我们可以看到我们在receiver端按照顺序把所有的message都接收到了,此时再去看azure portal queue中,此时我们的active message count已经是0了。 至此,我们基于Queue的基本场景实现已经完成。 基于Topics的基本场景实现 基于Topics和基于Queue的实现基本很类似,就是在create queue那一步稍有不同,我们需要创建topic,毕竟这里使用的不是queue: 在namespace中create topic 在已经创建的topic上面创建subscriptions Send端代码的实现 和queue的场景类似,我们需要新建对应的topicClient,代码如下: 相应的参数意义从名字上看应该是一目了然。这之后的sendmessage代码就和queue比较类似了: Receiver端代码的实现…

Read More »

WebSocket 简单介绍和使用

今天我们来和大家聊聊HTML5的Web Socket。 什么样的背景让这些疯狂的程序员们想出了WebSocket 在正式解释Web Socket之前,我们先来看这样一个场景:假设我们现在正在一个网站上看股价的实时变化图 ,我们希望能够及时得到股票的变化信息,那么从技术角度来讲,我们就需要及时得到server端数据变化的情况。一个简单的解决方案就是和PM argue,让user去不停地刷新页面,可惜很显然这样做的后果很可能会被PM拉去祭天,哈哈。 在和PM讨论无果败下阵来之后,我们该怎么办呢?因为HTTP连接是一个半双工的连接,它是基于request/response这样的形式来进行数据传输的,要想在这个技术上面实现实时的数据变化,我们无非有这样几种思路,一种就是我们定期进行轮询,发送request过去,等待response的到来。另外一种就是把一次connection的时间放长,让他能够在有更新时及时发送回来。基于这样的基础,有了以下几种实现: 轮询(polling) 轮询就是我们定期发送request,立即接受response。看起来还不错,但很显然问题就出在这个定期上面,因为我们并不知道server端的数据是什么时候能好,所以很难设置这个轮询的interval。假如设置小了,轮询太频繁,会有很多无效的request。假如设置大了,轮询的频率太低,就无法及时得到server端的数据更新了。 长轮询(long polling) 所谓长轮询就是在一定时间内没有response就保持连接存在,就像乞讨一样,不给钱就不走。当然计算机还没有这么无赖,在一定时间没有response,也会timeout。这种方法就解决了上面轮询设置interval的苦恼,如是能设置一个合理的timeout,也不需要担心有太多无效的request了。他的模型如下图一所示: 图一 长轮询示意图 毫无疑问,长轮询解决了之前轮询中的一些问题,但是仍然有很多问题没有解决,比如说假设我们的数据更新比较频繁,那么长轮询和轮询基本就没有差别了,这样我们为了获取及时的数据,就需要不停地发送request,这必然会有performance的issue。 流(stream) 既然长轮询也有问题,天才的ITers显然不会止步于此,你不是说我总是发送request会让performance不好,那好,我建立连接之后就不close了,一直在等待数据的刷新,直到被关闭或者超时(比如防火墙丢弃时间过长的连接,或者服务器端的timeout等)。流模式的模型如下图二所示 图二 流模式的示意图 那么这样的流模式是否就是完美无缺的呢,事实上也不是,因为流模式依然基于HTTP,他很有可能被防火墙或者代码buffer住response从而导致latency,这时我们就需要选择TLS(SSL)来避免response被buffer,在这种情况下连接的建立和关闭就会很重。另外一个问题就是,这仍然是一个半双工的情况,就是client如果想在连接过程中发送数据给server,仍然只能建立一个新的连接。 什么是WebSocket 好了,讲了这么多,该是时候回归到本文想讲的内容了,WebSocket。 我想不用我多说,大家应该可以猜到了,websocket就是用来解决上面这些问题的。WebSocket是一个全双工的双向的通信通道。只需要建立一次连接,可以一直保持,而且是双向都可以主动进行通信。WebSocket的模式示意图如下图三所示: 图三 websocket的模式示意图 WebSocket的特点 WebSocket有以下几个特点: (1)建立在 TCP 协议之上,服务器端的实现比较容易。 (2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。 (3)数据格式比较轻量,性能开销小,通信高效。 (4)可以发送文本,也可以发送二进制数据。 (5)没有同源限制,客户端可以与任意服务器通信。 (6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。 WebSocket Client如何使用 我们从图三可以看出,WebSocket其实有Client端和Server端两块,那么Client端是如何编程的呢,首先我们第一步需要和server端建立一个连接,建立连接的代码如下: 其中第一个url就是我们要连接的url,也就是server端的url。第二个参数protocol是可选的,指定了可接受的子协议。 在我们使用了上面的代码建立了连接之后,我们可以通过Socket.send()的方法主动向server发送message,也可以通过Socket.close()来关闭连接了。 除了主动出击外,我们还可以设置各种事件的回调函数,用来处理连接的开关,出错以及收到message等事件,websocket支持的事件如下表所示: 事件 事件处理程序 描述 Open Socket.onopen 连接建立时触发 message Socket.onmessage 客户端接收到服务端的数据时触发 error Socket.onerror…

Read More »

AI从零开始之线性回归系数评估

从本节开始,我们来关注有监督学习中的最简单的方法,线性回归。虽然它很简单,但从实际情况来看,对于定量的情况分析中,它是非常有用的。虽然现在有很多更好更新的方法可以用来进行预测,但是线性回归仍然可以说是他们的基础。因此,线性回归的重要性也就不言而喻了。 顾名思义,线性回归就是说变量X和预测值Y之间是简单的近似线性的关系,我们可以用下面的公式表示: 这里的a和b是两个我们不知道的常量,我们称之为截距(intercept)和斜率(slope)。a和b就是我们所说的模型系数或者称之为参数。当我们用我们的training data来预测了 和 之后,我们就可以用他们来进行预测x对应的值: 现在假设我们现在有n个观察数据,我们记为(,), (,),…,(,),那么我们的目标就很简单了,我们需要得到一组和,使得,其中i=1,….,n,也就是说我们要找到和使得所有的这些观察数据和最终的结果的那条线足够接近。那么怎么评估这个远近呢,一个比较流行的方法就是最小二乘准则(least squares criterion)。 那什么是最小二乘准则呢,我们现在假设对第i个观察点的预测值 ,那么就表示第i个观察点的残差,他其实就是第i个观察点的真实的值和预测值之间的差别。我们可以用下面公式来定义残差平方和(residual sum of squares RSS): 我们把上面的公式带入就可以写成这样: 那么当RSS最小的时候, 和的值是多少呢,我们见下图的推导: 图一 RSS最小的推导 所以我们可以得到当RSS最小时: 至此,我们就把线性回归下满足RSS最小的时候的 和的值。 转载请注明出处: AI从零开始之线性回归系数评估 更多精彩内容,请关注公众号:随手记生活

Read More »

AI从零开始之数学基础(一)

在我们进一步进行深入介绍之前,我们来把一些常见的数学概念和大家回忆一下,这样在后面的文章中,可以更加容易进行理解。 均值(mean) 均值就是我们常说的平均数,也被称之为算术平均数,它的计算方法就是把所有的数加起来,然后除以数的个数。它是计算平均数的一种方法(这里的平均数是一个更广泛的概念,和均值的平均数不是一个概念,这也是为什么我们严格意义上应该把均值称之为算法平均数的原因)。这个概念,我相信大家应该都还是比较理解的,就不多费笔墨在这里,而是直接举个例子说明一下: 例:假设我们有数据集1, 6,1,2,3, 8,4,5,5,7共十个数字,那么 均值=(1+6+1+2+3+8+4+5+5+7)/8=42/8=5.25 中位数(median) 中位数又称中值,顾名思义就是处于中间位置的数,但是这个中间位置是有一定要求的,它本质是希望这个中位数能够把数据集分成两个部分,一个部分是都比中位数大,另一个部分则是都比中位数小,所以这个中间位置其实是需要先把数据集进行排序的。也就是说它其实是排序之后处于中间位置的数。 例:假设我们有数据集1, 6,1,2,3, 8,4,5,5,7共十个数字,那么在求中位数之前我们需要先进行一下排序,排序后的结果是1,1,2,3,4,5,5,6,7,8,这样就是十个数字,10是一个偶数,中间其实是两个数字,也就是第五和第六,在例子中他们分别是4和5,这个时候的中位数就需要取他们的平均值: 中位数=(4+5)/2=4.5 假如我们的数据集是奇数个,那么就直接取中间位置的数即可。 众数(mode) 众数,就是说哪些数喜欢聚众闹事,我们就把它找出来。准确地讲就是在数据集中出现最频繁也就是次数最多的数,我们称之为众数。众数可以有多个,即假如有多个数出现的次数是一样的,同时又是最多的,那么我们就认为众数是这几个数。一个特殊情况,即所有的数出现次数都是相同的,那么没有众数。 例:假设我们有数据集1, 6,1,2,3, 8,4,5,5,7共十个数字,我们可以看到在这个数据集中1和5都是出现了两次,而其他数则只出现了一次,因此我们就说这个数据集的众数是1和5. 极差(Range) 极差又称之为范围误差或全距,它其实就是用来表示一组数据的范围的,说白了就是最大值和最小值之间的差距,因此他的计算方法也就比较简单,直接用最大值减去最小值即可。一般来说,极差越大表示整个数据集分得越开,越离散。 例:假设我们有数据集1, 6,1,2,3, 8,4,5,5,7共十个数字,我们可以看到这个数据集中最大值是8,最小值1,所以 极差= 8-1=7 中程数(mid-range) 中程数是一组数据中最大值和最小值的平均数,它用来反映数据集中趋势的一项指标。其实用脑袋想想,这样的算法并不是很合理,毕竟他忽略了整个数据集中其他的很多数据。因此他在实际情况中用得并不是很多。 例:假设我们有数据集1, 6,1,2,3, 8,4,5,5,7共十个数字,我们可以看到这个数据集中最大值是8,最小值1,所以 中程数=(1+8)/2=4.5 方差(Variance) 方差在统计学和概率论中有不同的定义,本文重点关注统计学方面的定义。 在统计学中,方差有总体方差和样本方差两种说法,在进一步介绍之前,我们先来看两个基本概念:样本和总体。 不难理解,总体就是全部所有,而样本则是我们观察或者调查的部分。之所以有这样的两个概念出现,主要原因是在很多情况下我们很难获得总体的信息,可能是总体太大了或者变化太快了,我们没法或者很难调查清楚。比如说我们想调查全球人口的年龄平均值,因为全球人口时刻都在变化,一秒钟有人生有人死,要想拿到全面真实的数据是很难的。所以我们可能会随机抽样一些人口来计算,那么这些抽样就是样本了。 总体方差就是每一个观察值与总体均值之间的差异,它是用来衡量随机变量的离散程度的度量。公式如下:   其中就是总体方差,X就是变量,是总体均值,N为总体的个数。 事实上,我们很难拿到总体的情况,可能总体的个数都拿不到,所以就用了样本方差的概念,它的计算公式如下: 其中就是样本方差,X是变量,是样本均值,n为样本的个数。 我想精明如你的人肯定会有一个疑问,为什么这里是n-1而不是n呢,简单地说n-1会更加准确,但我们为什么选择的是n-1而不是n-2之类的,我们会在后面专门写一篇文章来推导一下,这里不再展开。 标准差(Standard Deviation) 标准差我们也称之为均方差,他其实很简单就是方差的算术平方根,用表示。他主要用来反映一个数据集的离散程度。这个时候我想你不禁会问我们有了方差为什么还要这个标准差啊,其实随便想想我们应该也能了解,比如说我们想统计学生的身高,以厘米为单位,那么方差计算出来的单位是什么呢,因为要平方,所以应该是平方厘米吧,是不是感觉有点乖乖的,平方厘米是计算身高还是计算面积呢?这大概也许可能就是我们需要标准差的原因吧,哈哈。它的计算公式如下: 期望 期望是实验中每次可能结果的概率乘以其结果的总和,它反映了随机变量平均取值的大小。根据大数定律,数值的平均值基本收敛于期望。 有两种情况,一种是离散情况,我们知道每一个可能取值对应的概率为p(),那么他的期望E(x)可以用下面的公式来表示: 例:学校有1000个学生,其中有一部手机的人有900人,两部手机的人有80个,三部手机的人有10人,则此学校中任意一个学生的手机量是一个随机变量,我们记为X,他的可能取值有0,1,2,3。其中取0的概率是0.01,取1的概率是0.9,取2的概率是0.08,取3的概率是0.01。所以期望E(X)=0*0.01+1*0.9+2*0.08+3*0.01=1.09。 另外一种是连续情况,这种情况下我们假设X的概率密度函数是f(X),且积分绝对收敛,那么他的期望值E(X)可以用下面的公式来表示: 至此,本文介绍了八个基本的数学概念:均值、中位数、众数、极差、中程数、方差、标准差、期望。希望能够对您有所帮助。 转载请注明出处: http://www.softlifelogging.com/2018/06/05/ai从零开始之数学基础(一)/ ‎ 更多精彩内容请关注公众: 随手记生活

Read More »

AI从零开始之分类器介绍

我们在之前的文章中讨论的都是一些回归的问题,本章我们来继续讨论一下分类相关的内容。 现在假设我们基于观察到的数据集{(, ), … , (, )} 来进行评估f,当然这里的 ,…, 现在是一些定性的值。那么最常见的用来计算我们 的准确度的就是训练数据的误差率 (training error rate) ,他的公式如下: 其中就是对于第i个观察数据应用 之后得到的类标签,I其实是一个指标变量(indicator variable), 当时, 就是1,当 时,它就是0。这样一看,我们就可以发现其实上面的公式就是计算的我们分类不正确的情况。 和回归的情况类似,上面的公式仍然只是计算的training data的误差率,而这并不是我们关注的,我们真正关注的其实是测试数据的误差率,所以假设我们有测试数据集( ,),那我们真正关注的就是: 其中就是 的预测分类标签。很显然,我们总是希望我们的测试误差是最小的。 贝叶斯分类器(Bayes Classifier) 我们简单想想,就可以发现,若是想让我们的测试误差最小,我们只要把我们的测试值分到他最有可能的那个分类中就好了,用条件概率来讲就是我们只要把 的预测值分类到j,使得 Pr (Y = j | X = )的值最大就好了。而这就是贝叶斯分类器。 现在假设我们的数据就只有两类结果:分类1(y=1)和分类2,那么根据贝叶斯分类的理论,假如Pr (Y = 1| X = ) > 0.5那么他其实就是分类1,剩下的就是分类2了。 下面我们来看一个简单的例子,如下图一所示:   图一 简单的二值分类图 我们的training数据有两种分类结果,在图中用橙色圈圈和蓝色圈圈来表示。现在我们假设我们已经知道了整个平面所有点的分类情况,背景是橙色点的表示它是橙色圈圈的概率大于50%,背景是蓝色点的表示它是蓝色圈圈的概率大于50%。紫色的这条虚线就是橙色圈圈和蓝色圈圈概率都正好是50%的线。这条线我们就称之为贝叶斯的决策边界。贝叶斯分类器的预测就是根据这个决策边界来的,在橙色这边的点,我们就会预测为橙色圈圈,在另外一边的我们就会预测为蓝色圈圈。 从图中我们可以看到,这样的预测大多数情况还是比较正确的,但是不可避免肯定是有误差的,我们可以看到图中蓝色点中的橙色圈圈,以及橙色点上的蓝色圈圈就都是一些误差的点,那么贝叶斯分类器的测试误差率是怎样的呢? 因为我们上面提到过其实贝叶斯找到的概率最大的值,那么对于测试点X= 来说,它的误差率就是,所以总的贝叶斯误差率可以用下面这个公式来表示: K最近邻(KNN)分类 我们在上面介绍了贝叶斯分类,理论上我们当然希望用他来进行分类,但实际上我们很难知道一个点分类的真实条件概率,所以就出现了很多方法来预估这个条件概率,这里面比较基础的一个方法就是KNN,这也是我们这一节要讲解的内容。 KNN首先找我们测试点 附近的K个点(K是正整数),我们把这个K个点的集合称之为 ,然后我们就可以用下面公式来估计条件概率了:   这样我们就可以根据K的值来计算出相应的条件概率了。 下面我们来看一个具体的例子,如下图二所示: 图二 KNN 例子 从图上可以看到,我们已经知道了一些training data,这里是12个点,其中橙色圈圈6个点,蓝色圈圈也是6个点,我们现在来预测黑色叉叉的情况。我们选择K=3,那么也就是看黑色叉叉周围三个点的情况,如图中绿色圆圈所示,我们可以看到,这个圆圈中有两个蓝色的圈圈,一个橙色的圈圈。所以,我们预估黑色叉叉是蓝色圈圈的概率就是2/3,是橙色圈圈的概率就是1/3。 我们对图中所有的点都用K=3来进行分类,就可以得到下图三所示的情况: 图三 KNN得到的决策边界 图三中黑色的线我们就称之为KNN决策边界。 KNN听起来很简单,但是如果我们使用一个合理的K,有时会得到一个和贝叶斯分类惊人类似的结果,我们使用K=10来决策图一中的数据,可以得到KNN的决策边界如图四所示: 图四 K=10的KNN决策边界和贝叶斯对比图…

Read More »