随机森林中的数学基础之大数定律
摘要:随机森林与决策树的一个显著的不同点就是它不会产生过度拟合。它的理论依据就是大数定律。
很熟悉的一个名词:大数定律,记得本科的概率论就有学,忘了差不多了,下面先复习下。
大量试验说明,随机事件A的频率R(A),当重复试验的次数n增大时,总呈现出稳定性,稳定在某一个常数的附件,意指数量越多,其平均值就越趋近与期望值。
一:大数定律回顾
切比雪夫定理:
设 a1,a2,a3,…,an 为相互独立的随机变量,其数学期望为:E(ai)=, 及方差D(ai)=
则序列收敛于u,即在定理条件下,当n无限变大时,n个随机变量的算术平均将趋于一个常数。
辛钦定理(切比雪夫的特殊情况):
设a1,a2,…an,…为服从同一分布且相互独立的随机变量,其数学期望为:,则对任意正数ε>0,下式成立:
表明,对于独立同分布的随机变量a1,a2,a3…an…,对于任意的ε>0,只要n充分大,事件
实际上几乎是必要发生的。
伯努利大数定理(切比雪夫的特殊情况):
设μ是n次独立试验中事件A发生的次数,p是事件A在每次试验中发生的概率,则对于任意的正数ε,有
伯努力大数定理说明,当试验次数n很大的时候,事件A发生的频率与概率有较大判别的可能性比较小,即: 用数学式表现出了频率的稳定性。
二:随机森林中的大数定律的应用
首先回顾下随机森林的定义:
随机森林是一个分类器,它由一些列的单株分类器组成的,其中的
是独立同分布的随机变量。在输入X后,每一棵决策树只投一票给它认为最合适的分类标签,最后选择投票最多的那个分类标签作为X的分类。
之所以引入随机变量,是为了控制每棵树的生长,通常针对于第K棵决策树引进随机变量
,它与前面的k-1个随机变量是独立同分布的,利用训练集和
来生成第k棵树,也就等价于生成一个分类器
,其中的X是一个输入向量。
给定一系列的分类器,然后随机的选择一些训练样本,设其中X为样本向量,Y为正确分类的分类标签向量。
则定义边际函数:
其中I(.)是示性函数,av(.)表示取平均值,边际函数表示了在正确分类Y之下X的得票数目超过其它错误分类的最大得票数目的程度。
该值越大表明分类的置信度越高。
泛化误差 公式为:
其中X,Y表示概率的定义空间。
根据大数定律中的辛钦定理,当决策树的数目增加时,对于所有的序列和PE都会收敛到:
对应于大数定律里的频率收敛于概率。
这一结果解释了为什么随机森林不会随着决策树的增加而产生过度拟合,并且有一个有限的泛化误差值。
创建线程(Background Thread)的N种方式
2013-11-15 16:59 by xchit, 412 阅读, 1 评论, 收藏, 编辑第一、Thread类
Thread类是实例化线程的主要方法;一个Thread实例管理一个线程,即执行序列。通过简单实例化一个对象,就可以创建一个线程,然后通过Thread对象提供的方法对线程进行管理。
Thread thread = new Thread(_ => BackgroundMethod("第一、Thread类创建线程")); thread.Start(); thread.Join();
第二、ThreadPool(线程池)类
提供一个线程池,该线程池可用于执行任务、发送工作项、处理异步 I/O、代表其他线程等待以及处理计时器。
ThreadPool.QueueUserWorkItem(_ => BackgroundMethod("第二、ThreadPool创建线程"));
第三、delegate(委托)
委托异步回调方法创建线程,只需调用BeginInvoke委托和触发线程。然后,调用EndInvoke将阻塞当前线程。
BackgroundMethodDelegate x = new BackgroundMethodDelegate(BackgroundMethod); IAsyncResult a = x.BeginInvoke("第三、delegate委托", null, null); x.EndInvoke(a);
第四、BackgroundWorker类
在单独线程上执行操作
BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += delegate { BackgroundMethod("第四、BackGroundWorker创建线程"); }; worker.RunWorkerAsync();
第五、System.Threading.Timer
创建一个计时器,提供以指定时间间隔执行方法机制。
Timer timer = new System.Threading.Timer(_ => BackgroundMethod("第五、Timer计时器"), null, 0, Timeout.Infinite);
第六、Task类
最简单的任务异步操作
using (Task task = new Task(() => BackgroundMethod("第六、Task类创建异步任务"))) { task.Start(); task.Wait(); }
第七、Task.Factory(Task另外一种创建方式)
创建实例工厂的访问
Task.Factory.StartNew(() => BackgroundMethod("第七、Task第二种创建异步方式"));
第八、Parallel类
System.Threading.Tasks命名空间下的,提供对并行循环和区域支持
Parallel.Invoke(() => BackgroundMethod("第八、Parallel类"));
第九、System.Reactive.Linq.Observable
Reactive Extensions (下面简称 Rx) 是在 Linq 可操作的数据源上针对 "异步"(BeginXXX/EndXXX) 和 "事件"(XXXCompleted) 上的扩展,也可以被称为 "Linq To Asynchronous" 和 " Linq To Events"。相比以前复杂的异步处理或者事件处理,Timer的处理等,结合Linq 形式的Rx编程模型更加简洁。
关于 Rx 的安装,可以通过 Reactive Extensions (Rx) 的主页 的直接下载安装,当然也可以利用 NuGet 导入 dll (http://nuget.org/packages/Rx-Main) [译注: NuGet 是VS集成的在线 dll部署工具,非常方便]。Rx 不管是 Installer 还是 NuGet 安装的,都有 Stable(稳定版) 和 Experimental(实验版) 两种。
Observable.Return("第九、Observable类", Scheduler.Default).Subscribe(BackgroundMethod);
第十、ProcessStartInfo类
指定启动进程时使用的一组值,严格上应该不处于创建线程这一栏,但已被建议作为一种方式在后台做一些事情,即使它没有资格作为一个后台线程。 (根据定义,一个后台线程是进程中的一个线程不会防止进程终止,而它仍然在运行。 )
ProcessStartInfo startInfo = new ProcessStartInfo("StartThreads.exe", "OutOfProcess"); startInfo.CreateNoWindow = false; startInfo.UseShellExecute = false; startInfo.RedirectStandardOutput = true; Process process = Process.Start(startInfo); Console.WriteLine(“第十:ProcessStartInfo”); process.WaitForExit();
测试源码地址:https://files.cnblogs.com/xchit/StartThreads.rar
SQL Server 2008R2发布与订阅的配置
使用SQL Server的发布与订阅可以将一个数据库的数据实时传送到另一个数据库中,使用这种方式与Link Server相比可以减少对数据库的连接次数。下面介绍SQL Server 2008R2发布与订阅时最基本的配置。
首先在发布服务器创建发布,注意不论创建发布还是订阅都要用机器名登录,而不能用IP地址或.等替代符。
1.在Replication-->Local Publications处右键单击选择New Publication弹出如下向导。
2.选择要创建发布的数据库。
3.选择要创建发布的类型。本例选择Transactional Publication,这种发布方式可以实时同步数据到订阅数据库,延时较小。
4.选择要发布的数据库对象,数据库对象包括表、视图、存储过程等。
注意当选择Transactional Publication只有有主键的表才可以发布。
5.在本步骤可以选择对表或视图做过滤。
6.定义是否生成一个快照。Transactional Publication在初始时生成一个快照,正式运行后不再重新生成。
7.维护快照运行代理的用户信息。单击Securrity Settings按钮弹出新窗口维护用户信息。
8.输入运行发布代理的用户,需确保该用户有运行代理的权限。
9.输入发布名称,生成发布。
10.如果订阅数据库与发布数据库不在一台服务器,订阅数据库的代理运行账户需对存放快照的目录有读取权限,可以在以下页面修改快照的存放目录为网络目录。
接下来创建订阅
1.在Replication-->Local Subscriptions处右键单击选择New Subscription弹出如下向导。
2.查找发布,选择发布数据库。
3.选择发布。
4.本步骤选择在发布端运行订阅代理还是订阅端运行订阅代理。
下面维护代理运行账户时要根据运行在哪一端来确定用哪一端的账户。对应跨域的情况需特别注意。
5.选择订阅的数据库
6.维护连接发布和订阅代理的用户,单击...按钮弹出维护用户信息页面。
维护用户信息,根据代理运行在哪一端来确定用哪一端的账户。
7.选择订阅的方式,本例选择Run continuously,数据延迟较少。
8.选择是否需对订阅进行初始化。初始化时会在订阅端创建对应的数据库对象,重新导入数据。
9.完成订阅
完成发布与订阅的配置后下面就是监控发布与订阅的运行情况。
在新建的发布上右键单击,在弹出菜单中选择Launch Replication Monitor弹出monitor窗口。
在本窗口中可以看到发布和订阅的运行情况。如果有异常图标中将有红色显示。双击Watch List中的明细可以弹出该明细的详细运行信息。
基础排序算法
——.NET数据结构与算法系列之二
追忆,2013年11月13日
前言
在计算机中实现存储数据最普遍的两种操作就是排序和查找。这是从计算机产业初始就已经确认的了。这意味着排序和查找也是计算机科学领域最值得研究的两种操作。本书提到的许
多数据结构的主要设计目的就是为了使排序和/或查找更加简单,同时也是为了数据在结构内的存储更加有效。
本章会介绍有关数据排序和查找的基础算法。这些算法仅依赖数组作为数据结构,而且所采用的“高级”编程技术只是递归。本章还介绍了用来非正式分析不同算法之间速度与效率的方法,此方法贯穿全书。
1.排序算法
人们在日常生活中所接触到的绝大多数数据都是经过排序的。比如,按照字母顺序查询字典中的定义。或者按照名字的字母顺序在电话本中查询电话号码。再或者邮局会按照下列几个
步骤对邮件进行排序分发:即首先按照邮政编码,然后再按照街道名称,最后还要按照姓名。排序在数据处理中是十分基础的过程,因而值得认真学习研究。正如先前提到的那样,这里对不同排序算法的操作有非常少量的分析研究。尽管已经对一些非常古老的算法做了改进,但是仍然应该先学习几种简单的排序算法。这些简单算法就是插入排序算法、冒泡排序算法以及选择排序算法。这些算法的每一种都很容易理解和实现。对于任意情况而言这些算法不是最好的全面算法,但是对于少量数据集合或者其他特殊情况而言,它们是可用的最好算法。
1.1数组类测试环境
为了检验这些算法,首先需要构造一个可以实现并测试算法的测试环境。这里将构造一个类来封装数组处理的一些常规操作,即元素插入操作,元素存取访问操作,以及显示数组内容的操组。下面就是程序的代码:
在保留CArray 类以便开始检测排序和查找算法之前,还是先来讨论一下如何在CArray 类对象内实际存储数据的问题。为了更有效地说明不同排序算法是如何运行的,数组内数据需
要随机放置。最好的实现方法就是使用随机数生成器来给数组的每个元素进行赋值。在C#中用Random 类可以产生随机数。这种类型的对象可以产生随机数。为了实例化Random 对象,需要给这个类的构造器传递一个种子。这里把这个种子看作是随机数生成器所能产生的随机数范围的上界。
下面另外看一个用CArray 类来存储数的程序,而且采用了随机数生成器来选择存储到数组内的数据:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Chapter2
{
class Program
{
static void Main(string[] args)
{
CArray nums = new CArray(10);
Random rnd = new Random(100);
for (int i = 0; i <10; i++)
{
nums.Insert(rnd.Next(0,100));
}
nums.DisplayElements();
Console.ReadKey();
}
}
}
1. 2冒泡排序
首先要讨论的排序算法就是冒泡排序。冒泡排序是可用的最慢排序算法之一,但是它也是最容易理解和实现的排序算法之一,所以这里把它作为最先介绍的排序算法。
这种排序算法的得名是由于数值“像气泡一样”从序列的一端浮动到另一端。假设现在要把一列数按升序方式进行排序,即较大数值浮动到列的右侧,而较小数值则浮动到列的左侧。
这种效果可以通过下列操作来实现:多次遍历整个列,并且比较相邻的数值,如果左侧的数值大于右侧数值就进行交换。
//冒泡排序
public void BubbleSort()
{
int temp;
int num = 0;
for (int outer = upper; outer >= 1; outer--)
{
num++;
for (int inner = 0; inner <= outer - 1; inner++)
{
if (arr[inner] > arr[inner + 1])
{
temp = arr[inner];
arr[inner] = arr[inner + 1];
arr[inner + 1] = temp;
}
}
Console.WriteLine(string.Format("\n第{0}次排序", num));
this.DisplayElements();
}
}
这段代码有几个地方需要注意。首先,交换数组元素的代码是写在主程序中的一行,而没有用子程序。如果多次调用交换子程序,就可能会降低排序的速度。既然交换代码只有短短三
行的长度,所以不把代码放在子程序内也不会影响代码的清晰度。更加需要注意的是程序中最外层的循环是从数组的末尾处开始,并且向数组的开始处移动。如果回顾上图过程就会知道,数组内最大值就在数组末尾的适当位置上。这意味着数组的索引比外层循环的值更大,而且它们已经在恰当的位置上了,因而算法不需要再访问这些数值了。内层循环从数组的第一个元素开始,并且在几乎达到数组最后位置的时候结束。内层循环会对用inner 和inner+1 标识的两个相邻位置的数值进行比较,并且在必要时交换它们的数值。
1.3选择排序
这种排序是从数组的起始处开始,把第一个元素与数组中其他元素进行比较。然后,将最小的元素放置在第0 个位置上,接着再从第1个位置开始再次进行排序操作。这种操作会一直到除最后一个元素外的每一个元素都作为新循环的起始点操作过后才终止。
在选择排序算法中使用了两层循环。外层循环从数组的第一个元素移动到数组最后一个元素之前的元素,而内层循环则从数组的第二个元素移动到数组的最后一个元素,并且查找比当前外层循环所指元素更小的数值。在内循环遍历一遍之后,就会把数组内最小值赋值到数组中合适的位置上。
实现SelectionSort 算法的代码如下所示:
//选择排序
public void SelectionSort()
{
int min, temp;
for (int outer = 0; outer <= upper; outer++)
{
min = outer;
for (int inner = outer + 1; inner <= upper; inner++)
{
if (arr[min] > arr[inner] )
min = inner;
}
temp = arr[outer];
arr[outer] = arr[min];
arr[min] = temp;
Console.WriteLine(string.Format("\n第{0}次排序", outer+1));
this.DisplayElements();
}
}
1.4插入排序
插入排序算法类似于人们通常按照数字顺序或者字母顺序进行排序的方法。假如我要求全班同学上交填有本人姓名、学号以及简短自我介绍的索引卡片。而学生们交回来的卡片是随机排列的。如果要把卡片按照字母排序排列,就可以构建出一张座次表了。所以,我把这些卡片带回了办公室,并且清理出了办公桌。紧接着我拿出了第一张卡片。卡片上的名字是Smith。我把它放在办公桌最左侧的位置上,然后又拿出了第二张卡片。这张是Brown。于是,我把Smith 的卡片移动到右侧,并且把Brown 的卡片放到Smith 原来的位置上。下一张卡片是Williams。不需要移动任何其他的卡片就可以把它放在最右侧的位置上。接下来的卡片是Acklin。它需要放置在队列的开始处,所以其他所有的卡片都必须向右移动一个位置以便腾出空间放Acklin。这就是插入排序算法的工作原理。
插入排序的代码如下所示,跟着的是对此算法工作原理的解释说明:
//插入排序
public void InsertionSort()
{
int inner, temp;
for (int outer = 1; outer <= upper; outer++)
{
temp = arr[outer];
inner = outer;
while (inner > 0 && arr[inner - 1] >= temp)
{
arr[inner] = arr[inner-1];
inner-=1;
}
arr[inner] = temp;
Console.WriteLine(string.Format("\n第{0}次排序", outer));
this.DisplayElements();
}
}
插入排序算法有两层循环。外层循环会逐个遍历数组元素,而内层循环则会把外层循环所选择的元素与该元素在数组内的下一个元素进行比较。如果外层循环选择的元素小于内层循环选择的元素,那么数组元素都向右移以便为内层循环元素留出位置,这就像前面例子描述的那样。现在就来看看选择排序是如何处理前面实例中用来排序的数据集合的。下面是程序的输出结果:
这个输出清楚地表明插入排序不是通过交换来处理的,而是通过把较大的数组元素向右移动来为数组左侧较小元素留出空间的方式进行操作的。
2.基础排序算法的时间比较
上述三种排序算法在复杂度和理论上都是十分相似的,所以在互相进行比较的时候应该操作近似。这里用Timing 类来比较三种算法,根据它们对庞大数据集合进行排序时所花费的时间判定出是否有算法会与众不同。为了进行测试,这里用到基本代码和之前为了说明每种算法的工作原理而使用的代码完全一样。但是,在下面这些测试中,为了说明三种算法是如何处理较小数据集合和较大数据集合的,数组的大小是有变化的。时间测试程序要分别运行处理元素量为100、1000、10000至更多的几种情况。下面是代码:
//基础排序算法的时间比较
class Test
{
public static void RunTest(int numItems)
{
Timing sortTime = new Timing();
Random rnd = new Random(100);
CArray theArray = new CArray(numItems);
//选择排序
for (int i = 0; i < numItems; i++)
theArray.Insert(rnd.Next(1, int.MaxValue) * 100);
sortTime.StartTime();
theArray.SelectionSort();
sortTime.StopTime();
Console.WriteLine("Time for Selection sort: " + sortTime.Result().TotalMilliseconds);
theArray.Clear();
//冒泡排序
for (int i = 0; i < numItems; i++)
theArray.Insert(rnd.Next(1, int.MaxValue) * 100);
sortTime.StartTime();
theArray.BubbleSort();
sortTime.StopTime();
Console.WriteLine("Time for Bubble sort: " + sortTime.Result().TotalMilliseconds);
theArray.Clear();
//插入排序
for (int i = 0; i < numItems; i++)
theArray.Insert(rnd.Next(1, int.MaxValue) * 100);
sortTime.StartTime();
theArray.InsertionSort();
sortTime.StopTime();
Console.WriteLine("Time for Insertion sort: " + sortTime.Result().TotalMilliseconds);
}
}
结果大概如上所示,实在是等不及啦,没等后两个测试出来,就把它给闭了!大家见谅!
尽管选择排序始终比其他两种算法快出许多倍,但是所有这三种排序算法的性能还是相当低的。准确地说,这些算法没有一种在对庞大数据集合进行排序时是理想选择。但是存在能高效处理庞大数据集合的排序算法。在后面的内容里将会和大家一起探讨。
小结
本章讨论了针对数据排序的三种算法,即选择排序、冒泡排序以及插入排序。所有这三种算法都是非常容易实现的,而且它们都可以很好地处理少量的数据集合。选择排序是三种算法中效率最高的,其次是冒泡排序和插入排序。正如本章末尾看到的那样,这三种算法没有一种是十分适合庞大数据集合的。(比如,多于万个元素的数据集合)。
源程序下载:DataStructAndAlgorithm.zip
参考书箱:<<数据结构与算法>>