文档章节

神经网络入门--连载5

dake
 dake
发布于 2014/01/29 13:34
字数 3293
阅读 15
收藏 0

4.5 神经网络的编码
(Encoding the Network)


在本书的开始几章中,你已看到过怎样用各种各样的方法为遗传算法编码。但当时并没有向你介绍用实数来进行编码的具体例子,因为我知道我要留在这里向你 介绍。我曾经讲到,为了设计一个前馈型神经网络,编码是很容易的。我们从左到右读每一层神经细胞的权重,读完第一个隐藏层,再向上读它的后面一层,把所读 到的数据依次保存到一个向量中,这样就实现了网络的编码。因此,如果我们有图14所示的网络,则它的权重编码向量将为:

0.3, -O.8, -O.2, 0.6, O.1, -0.l, 0.4, 0.5

在这一网络中,为了简单,我没有把偏移值的权重包括进去。但在实际实现编码时,你必须包含偏移值这个权重,否则你肯定无法获得你所需要的结果。

图14 为权重编码。

在此之前讲的事情你都懂了吗?好极了,那下面就让我们转来考虑,怎样用遗传算法来操纵已编码的基因吧。


4.6 遗传算法(The Genetic Algorithm)

..//到此,所有的权重已经象二进制编码的基因组那 样,形成了一个串,我们就可以象本书早先讨论过的那样来应用遗传算法了。遗传算法(GA)是在扫雷机已被允许按照用户指定的帧数(为了某种缘故, 我下面更喜欢将帧数称作滴答数,英文是ticks)运转后执行的。你可以在ini文件中找到这个滴答数(iNumTicks)的设置。 下面是基因组结构体的代码。这些对于你应该是十分面熟的东西了。

Struct SGenome
{
vector <double> vecWeights;

double dFitness;

SGenome():dFitness(0) {}

SGenome(vector <double> w, double f):vecWeights(w),dFitness(f){}

//重载'<'的排序方法
friend bool operator<(const SGenome& lhs, const SGenome& rhs)
{
return (lhs.dFitness < rhs.dFitness);
}
};

从上面的代码你可看出,这一SGenome结构和我们在本书所有其他地方见到的SGenome结构几乎完全一致,唯一的差别就是这里的染色体是一个双 精度向量std::vector。因此,可以和通常一样来应用杂交操作和选择操作。但突变操作则稍微有些不同,这里的权重值是用一个最大值为 dMaxPerturbation的随机数来搔扰的。这一参数dMaxPerturbation在ini文件中已作了声明。另外,作为浮点数遗传算法,突 变率也被设定得更高些。在本工程中,它被设成为0.1。

下面就是扫雷机工程遗传算法类中所见到的突变函数的形式:

void CGenAlg::Mutate(vector<double> &chromo)
{
// 遍历权重向量,按突变率将每一个权重进行突变
for (int i=0; i<chromo.size(); ++i)
{
// 我们要骚扰这个权重吗?
if (RandFloat() < m_dMutationRate)
{
// 为权重增加或减小一个小的数量
chromo[i] += (RandomClamped() * CParams::dMaxPerturbatlon);
}
}
}

如同以前的工程那样,我已为v1.0版本的Smart Minesweepers工程保留了一个非常简单的遗传算法。这样就能给你留下许多余地,可让你利用以前学到的技术来改进它。就象大多数别的工程一样,v1.O版只用轮盘赌方式选精英,并采用单点式杂交。

注意: 当程序运行时,权重可以被演化成为任意的大小,它们不受任何形式的限制。


4.7 扫雷机类(The CMinesweeper Class)

这一个类用来定义一个扫雷机。就象上一章描述的登月艇类一样,扫雷机类中有一个包含了扫雷机位置、速度、以及如何转换方向等数据的纪录。类中还包含扫 雷机的视线向量(look-at vector);它的2个分量被用来作为神经网络的2个输入。这是一个规范化的向量,它是在每一帧中根据扫雷机本身的转动角度计算出来的,它指示了扫雷机 当前是朝着哪一个方向,如图11所示。

下面就是扫雷机类CMinesweeper的声明:

class CMinesweeper
{
private:
// 扫雷机的神经网络
CNeuralNet m_ItsBrain;

// 它在世界坐标里的位置
SVector2D m_vPosition;

// 扫雷机面对的方向
SVector2D m_vLookAt;

// 它的旋转(surprise surprise)
double m_dRotation;

double m_dSpeed;

// 根据ANN保存输出
double m_lTrack,
m_rTrack;

m_lTrack和m_rTrack根据网络保存当前帧的输出。
这些就是用来决定扫雷机的移动速率和转动角度的数值。

// 用于度量扫雷机适应性的分数
double m_dFitness;

每当扫雷机找到一个地雷,它的适应性分数就要增加。

// 扫雷机画出来时的大小比例
double m_dScale;

// 扫雷机最邻近地雷的下标位置
int m_iClosestMine;

在控制器类CControl1er中,有一个属于所有地雷的成员向量std::vector。
而m_iClosestMine就是代表最靠近扫雷机的那个地雷在该向量中的位置的下标。

public:

CMinesweeper();

// 利用从扫雷机环境得到的信息来更新人工神经网
bool Update(vector<SVector2D> &mines);

// 用来对扫雷机各个顶点进行变换,以便接着可以画它出来
void WorldTransform(vector<SPoint> &sweeper);

// 返回一个向量到最邻近的地雷
5Vector2D GetClosestMine(vector<SVector2D> &objects);

// 检查扫雷机看它是否已经发现地雷
int CheckForMine(vector<SVector2D> &mines, double size);

void Reset();

// ----------------- 定义各种供访问用的函数
SVector2D Position()const { return m_vPosition; }
void IncrementFitness(double val) { m_dFitness += val; }
double Fitness()const { return m_dFitness; }
void PutWeights(vector<double> &w) { m_ItsBrain.PutWeights(w); }
int GetNumberOfWeights()const
{ return m_ItsBrain.GetNumberOfWeights(); }
};


4.7.1 The CMinesweeper::Update Function

(扫雷机更新函数)

需要更详细地向你说明的CMinesweeper类的方法只有一个,这就是Update更新函数。该函数在每一帧中都要被调用,以更新扫雷机神经网络。让我们考察这函数的肚子里有些什么货色:

bool CMinesweeper::Update(vector<SVector2D> &mines)

{
//这一向量用来存放神经网络所有的输入
vector<double> inputs;

//计算从扫雷机到与其最接近的地雷(2个点)之间的向量
SVector2D vClosestMine = GetClosestMine(mines);

//将该向量规范化
Vec2DNormalize(vClosestMine);

首先,该函数计算了扫雷机到与其最靠近的地雷之 间的向量,然后使它规范化。(记住,向量规范化后它的长度等于1。)但扫雷机的视线向量(look-at vector)这时不需要再作规范化,因为它的长度已经等于1了。由于两个向量都有效地化成了同样的大小范围,我们就可以认为输入已经是标准化了,这我前 面已讲过了。

//加入扫雷机->最近地雷之间的向量
Inputs.push_back(vClosestMine.x);
Inputs.push_back(vCIosestMine.y);

//加入扫雷机的视线向量
Inputs.push_back(m_vLookAt.x);
Inputs.push_back(m_vLookAt.y);

//更新大脑,并从网络得到输出
vector<double> output = m_ItsBrain.Update(inputs);

然后把视线向量,以及扫雷机与它最接近的地雷之间的向量,都输入到神经网络。函数CNeuralNet::Update利用这些信息来更新扫雷机网络,并返回一个std::vector向量作为输出。

//保证在输出的计算中没有发生错误
if (output.size() < CParams::iNumOutputs)
{
return false;
}

// 把输出赋值到扫雷机的左、右轮轨
m_lTrack = output[0];
m_rTrack = output[1];

在更新神经网络时,当检测到确实没有错误时,程序把输出赋给m_lTrack和m_rTrack。 这些值代表施加到扫雷机左、右履带轮轨上的力。

// 计算驾驶的力
double RotForce = m_lTrack - m_rTrack;

// 进行左转或右转
Clamp(RotForce, -CParams::dMaxTurnRate, CParams::dMaxTurnRate);

m_dSpeed = (m_lTrack + m_rTrack);

扫雷机车的转动力是利用施加到它左、右轮轨上的力之差来计算的。并规定,施加到左轨道上的力减去施加到右轨道上的力,就得到扫雷机车辆的转动力。然后就把 此力施加给扫雷机车,使它实行不超过ini文件所规定的最大转动率的转动。而扫雷机车的行进速度不过就是它的左侧轮轨速度与它的右侧轮轨速度的和。既然我 们知道了扫雷机的转动力和速度,它的位置和偏转角度也就都能更新了。

//更新扫雷机左右转向的角度
m_dRotation += RotForce;

// 更新视线角度
m_vLookAt.x = -sin(m_dRotation);
m_vLookAt.y = cos(m_dRotation);

// 更新它的位置
m_vPosition += (m_vLookAt* m_dSpeed);

// 如果扫雷机到达窗体四周,则让它实行环绕,使它不至于离开窗体而消失
If (m_vPosition.x > CParams::WindowWidth) m_vPosition.x = 0;
If (m_vPosition.x < 0) m_vPosition.x = CParams::WindowWidth;
If (m_vPosition.y > CParams::WindowHeight) m_vPosition.y = 0;
If (m_vPosition.y < D) m_vPosition.y = CParams::WindowHeight;

为了使事情尽可能简单,我已让扫雷机在碰到窗体边框时就环绕折回(wrap)。采用这种方法程序就不再需
要做任何碰撞-响应方面的工作。环绕一块空地打转对我们人来说是一桩非常不可思议的动作,但对扫雷机,这
就像池塘中的鸭子。

Returen true;
}

4.8 CController Class(控制器类)


CController类是和一切都有联系的类。图15指出了其他的各个类和CController类的关系。
下面就是这个类的定义:

class CController
{
private:
// 基因组群体的动态存储器(一个向量)
vector<SGenome> m_vecThePopulation;



图15 minesweeper工程的程序流程图


// 保存扫雷机的向量
vector<CMinesweeper> m_vecSweepers;

// 保存地雷的向量
vector<SVector2D> m_vecMines;

// 指向遗传算法对象的指针
CGenAIg* m_pGA;

int m_NumSweepers;

int m_NumMines;

// 神经网络中使用的权重值的总数
int m_NumWeightsInNN;

// 存放扫雷机形状各顶点的缓冲区
vector<SPoint> m_SweeperVB;

// 存放地雷形状各顶点的缓冲区
vector<SPoint> m_MineVB;

// 存放每一代的平均适应性分数,供绘图用
vector<double> m_vecAvFitness;

// 存放每一代的最高适应性分
vector<double> m_vecBestFitness;

// 我们使用的各种不同类型的画笔
HPEN m_RedPen;
HPEN m_BluePen;
HPEN m_GreenPen;
HPEN m_OldPen;

// 应用程序窗口的句柄
HWND m_hwndMain;

// 切换扫雷机程序运行的速度
bool m_bFastRender;

// 每一代的帧数(滴答数)
int m_iTicks;

// 代的计数
int m_iGenerations;

// 窗体客户区的大小
int cxClient,cyClient;

// 本函数在运行过程中画出具有平均-,和最优适应性值的图
void PlotStats(HDC surface);

public:

CController(HWND hwndMain);

~CController();

void Render(HDC surface);

void WorldTransform(vector<SPoint> &VBuffer,
SVector2D vPos);

bool Update();

// 几个公用的访问方法
bool FastRender() { return m_bFastRender; }
void FastRender(bool arg){ m_bFastRender = arg; }
void FastRenderToggle() { m_bFastRender = !m_bFastRender; }
};

当创建CController类的某个实例时,会有一系列的事情发生:

创建CMinesweeper对象。
统计神经网络中所使用的权重的总数,然后此数字即被利用来初始化遗传算法类的一个实例。
从遗传算法对象中随机提取染色体(权重)并(利用细心的脑外科手术)插入到扫雷机的经网络中。
创建了大量的地雷并被随机地散播到各地。
为绘图函数创建了所有需要用到的GDI画笔。
为扫雷机和地雷的形状创建了顶点缓冲区。

所有的一切现都已完成初始化,由此Update方法就能在每一帧中被调用来对扫雷机进行演化。


4.8.1 CController::Update Method(控制器的更新方法)

控制器更新方法CController::Update方法(或函数)在每一帧中都要被调用。当调用update函数时,函数的前一半通过对所有扫雷 机进行循环,如发现某一扫雷机找到了地雷,就update该扫雷机的适应性分数。由于m_vecThePopulation包含了所有基因组的拷贝,相关 的适应性分数也要在这时进行调整。如果为完成一个代(generation)所需要的帧数均已通过,本方法就执行一个遗传算法的时代来产生新一代的权重。 这些权重被用来代替扫雷机神经网络中原有的旧的权重,使扫雷机的每一个参数被重新设置,从而为进入新一generation做好准备。

bool CController::Update()
{
// 扫雷机运行总数为CParams::iNumTicks次的循环。在此循环周期中,扫雷机的神经网络
// 不断利用它周围特有的环境信息进行更新。而从神经网络得到的输出,使扫雷机实现所需的
// 动作。如果扫雷机遇见了一个地雷,则它的适应性将相应地被更新,且同样地更新了它对应
// 基因组的适应性。
if (m_iTicks++ < CParams::iNumTicks)
{
for (int i=O; i<m_NumSweepers; ++i)
{
//更新神经网络和位置
if (!m_vecSweepers[i].Update(m_vecMines))
{
//处理神经网络时出现了错误,显示错误后退出
MessageBox(m_hwndMain, 'Wrong amount of NN inputs!",
"Error", MB_OK);

return false;
}

// 检查这一扫雷机是否已经发现地雷
int GrabHit = m_vecSweepers[i].CheckForMine(m_vecMines,
CParams::dMineScale);

if (GrabHit >= 0)
{
// 扫雷机已找到了地雷,所以要增加它的适应性分数
m_vecSweepers[i].IncrementFitness();

// 去掉被扫雷机找到的地雷,用在随机位置放置的一个新地雷来代替
m_vecMines[GrabHit] = SVector2D(RandFloat() * cxClient,
RandFloat() * cyClient);
}

// 更新基因组的适应性值
m-vecThePopulation[i].dFitness = m_vecSweepers[i].Fitness();
}
}

© 著作权归作者所有

dake

dake

粉丝 36
博文 239
码字总数 336338
作品 1
乌鲁木齐
CEO
私信 提问
最近一段时间文章整理<

最近一段时间文章整理 推荐分享 观点|朱松纯:初探计算机视觉三个源头兼谈人工智能 推荐理由:为学正本清源,近一段时间比较担心的就是基地学弟狂热于深度学习中无法自拔,忘记CV的传统和根本...

reasonW
2017/04/21
0
0
强化学习 10: 实践中的一些技巧

在实践中有时候为了达到更好的效果需要用一些技巧。 1. 我们知道在交叉熵方法中,例如进行一百次实验,那么只需要选择其中最好的25次。这样的采样其实是效率很低的。 所以可以用一些小窍门来...

不会停的蜗牛
2018/11/27
0
0
Python进阶系列连载(13)——Python内置高阶函数filter(下)

前言 进阶部分连载继续~ 如果还没看过我的入门连载部分,先看: https://ask.hellobi.com/blog/wangdawei/10288 当然,小编的免费入门课程已经有咯,看过连载的朋友可以看看视频再快速梳理一...

ID王大伟
2018/04/28
0
0
2015年用户界面工具干货资源精选

| 更多干货资源请移步用户界面专题 | DevExpress 2015.1超详细更新说明文档中文版 历时两月吐血翻译整理的DevExpress 2015.1超详细更新说明文档 | 附PDF版下载地址>> DevExpress 15.2帮助文档...

百mumu
2015/12/25
263
0
强化学习 9: 当 Action 的空间连续时

如果 Action 的空间不是离散的而是连续的时候要怎么做呢? 之前骑自行车的例子中,action 可以是向左或者向右,现在的话可能是一个实数值的区间。 例如在机器人控制中就经常是这样的情况,我...

不会停的蜗牛
2018/11/25
0
0

没有更多内容

加载失败,请刷新页面

加载更多

c 基础教程六:c 循环结构

有的时候,我们可能需要多次执行同一块代码,c 语言提供了如下几种循环,各有特色。 while 循环 for 循环 do-while 循环 while 循环 只要给定的条件为真,C 语言中的 while 循环语句会重复执...

故城以南丶思念不安
21分钟前
4
0
spark 常见操作

为spark DataFrom 添加一个为 空的新列,使用UDF函数 想产生一个IntegerType类型列为null的DataFrame该怎么做。 import org.apache.spark.sql.functions._import org.apache.spark.sql.type...

蜉先生
32分钟前
2
0
Flutter for Web 详细预研

首先感谢@栖冰 @祖建国 一起对FFW的预研做的投入! 背景 Google在最新的Google I/O上推出了Flutter for Web,旨在进一步解决一次代码,多端运行的问题。Flutter for Web还处于早期试验版,官...

阿里云云栖社区
42分钟前
1
0
mongodb自动备份脚本

mongodb自动备份脚本 2019年04月08日 13:27:28 遗失的曾经! 阅读数 73 #!/bin/bash# 要备份的数据库名'多个数据库用空格分开# 备份文件要保存的目录basepath="/data/backup/dump$(da...

linjin200
43分钟前
1
0
如何使用pagehelper分页

<c:if test="${page != null && page.getTotal() > 0 }"> <nav style="text-align: center"><ul class="pagination pagination-lg"><li><a>共 ${page.total } 条记录</a></l......

南桥北木
52分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部