神经网络入门 ,源码3
来源:互联网 发布:阿里云服务器升级带宽 编辑:程序博客网 时间:2024/04/29 12:50
神经网络入门 源码3
说明:以下为与《神经网络入门》连载9-11相关的程序“有监督的学习”全部C++语言源码,不含头文件。这是《游戏编程中的人工智能技术》一书所附CD中的内容,仅供已阅读《神经网络入门》连载9-11的读者进一步深入阅读参考,我在源码中未添加任何中文解释,而且今后也不会为其中内容进行解释、答疑,甚至不关心是否有读者“到此一游”过。但可以肯定代码不会有差错,我已实际使用这些源文件编译过(用Borland C++,V3),完全能通过,并生成能正确运行的执行程序。
目 录
1. C2DMatrix.cpp
#include "C2DMatrix.h"
/////////////////////////////////////////////////////////////////////
//
// Matrix methods
//
/////////////////////////////////////////////////////////////////////
//create an identity matrix
void C2DMatrix::Identity()
{
m_Matrix._11 = 1.0f; m_Matrix._12 = 0.0f; m_Matrix._13 = 0.0f;
m_Matrix._21 = 0.0f; m_Matrix._22 = 1.0f; m_Matrix._23 = 0.0f;
m_Matrix._31 = 0.0f; m_Matrix._32 = 0.0f; m_Matrix._33 = 1.0f;
}
//create a transformation matrix
void C2DMatrix::Translate(double x, double y)
{
S2DMatrix mat;
mat._11 = 1.0f; mat._12 = 0.0f; mat._13 = 0.0f;
mat._21 = 0.0f; mat._22 = 1.0f; mat._23 = 0.0f;
mat._31 = x; mat._32 = y; mat._33 = 1.0f;
//and multiply
S2DMatrixMultiply(mat);
}
//create a scale matrix
void C2DMatrix::Scale(double xScale, double yScale)
{
S2DMatrix mat;
mat._11 = xScale; mat._12 = 0.0f; mat._13 = 0.0f;
mat._21 = 0.0f; mat._22 = yScale; mat._23 = 0.0f;
mat._31 = 0.0f; mat._32 = 0.0f; mat._33 = 1.0f;
//and multiply
S2DMatrixMultiply(mat);
}
//create a rotation matrix
void C2DMatrix::Rotate(double rot)
{
S2DMatrix mat;
double Sin = sin(rot);
double Cos = cos(rot);
mat._11 = Cos; mat._12 = Sin; mat._13 = 0.0f;
mat._21 = -Sin; mat._22 = Cos; mat._23 = 0.0f;
mat._31 = 0.0f; mat._32 = 0.0f;mat._33 = 1.0f;
//and multiply
S2DMatrixMultiply(mat);
}
//multiply two matrices together
void C2DMatrix::S2DMatrixMultiply(S2DMatrix &mIn)
{
S2DMatrix mat_temp;
//first row
mat_temp._11 = (m_Matrix._11*mIn._11) + (m_Matrix._12*mIn._21) + (m_Matrix._13*mIn._31);
mat_temp._12 = (m_Matrix._11*mIn._12) + (m_Matrix._12*mIn._22) + (m_Matrix._13*mIn._32);
mat_temp._13 = (m_Matrix._11*mIn._13) + (m_Matrix._12*mIn._23) + (m_Matrix._13*mIn._33);
//second
mat_temp._21 = (m_Matrix._21*mIn._11) + (m_Matrix._22*mIn._21) + (m_Matrix._23*mIn._31);
mat_temp._22 = (m_Matrix._21*mIn._12) + (m_Matrix._22*mIn._22) + (m_Matrix._23*mIn._32);
mat_temp._23 = (m_Matrix._21*mIn._13) + (m_Matrix._22*mIn._23) + (m_Matrix._23*mIn._33);
//third
mat_temp._31 = (m_Matrix._31*mIn._11) + (m_Matrix._32*mIn._21) + (m_Matrix._33*mIn._31);
mat_temp._32 = (m_Matrix._31*mIn._12) + (m_Matrix._32*mIn._22) + (m_Matrix._33*mIn._32);
mat_temp._33 = (m_Matrix._31*mIn._13) + (m_Matrix._32*mIn._23) + (m_Matrix._33*mIn._33);
m_Matrix = mat_temp;
}
//applies a 2D transformation matrix to a std::vector of SPoints
void C2DMatrix::TransformSPoints(vector<SPoint> &vPoint)
{
for (int i=0; i<vPoint.size(); ++i)
{
double tempX =(m_Matrix._11*vPoint[i].x) + (m_Matrix._21*vPoint[i].y) + (m_Matrix._31);
double tempY = (m_Matrix._12*vPoint[i].x) + (m_Matrix._22*vPoint[i].y) + (m_Matrix._32);
vPoint[i].x = tempX;
vPoint[i].y = tempY;
}
}
/////////////////////////////////////////////////////////////////////
//
// Matrix methods
//
/////////////////////////////////////////////////////////////////////
//create an identity matrix
void C2DMatrix::Identity()
{
m_Matrix._11 = 1.0f; m_Matrix._12 = 0.0f; m_Matrix._13 = 0.0f;
m_Matrix._21 = 0.0f; m_Matrix._22 = 1.0f; m_Matrix._23 = 0.0f;
m_Matrix._31 = 0.0f; m_Matrix._32 = 0.0f; m_Matrix._33 = 1.0f;
}
//create a transformation matrix
void C2DMatrix::Translate(double x, double y)
{
S2DMatrix mat;
mat._11 = 1.0f; mat._12 = 0.0f; mat._13 = 0.0f;
mat._21 = 0.0f; mat._22 = 1.0f; mat._23 = 0.0f;
mat._31 = x; mat._32 = y; mat._33 = 1.0f;
//and multiply
S2DMatrixMultiply(mat);
}
//create a scale matrix
void C2DMatrix::Scale(double xScale, double yScale)
{
S2DMatrix mat;
mat._11 = xScale; mat._12 = 0.0f; mat._13 = 0.0f;
mat._21 = 0.0f; mat._22 = yScale; mat._23 = 0.0f;
mat._31 = 0.0f; mat._32 = 0.0f; mat._33 = 1.0f;
//and multiply
S2DMatrixMultiply(mat);
}
//create a rotation matrix
void C2DMatrix::Rotate(double rot)
{
S2DMatrix mat;
double Sin = sin(rot);
double Cos = cos(rot);
mat._11 = Cos; mat._12 = Sin; mat._13 = 0.0f;
mat._21 = -Sin; mat._22 = Cos; mat._23 = 0.0f;
mat._31 = 0.0f; mat._32 = 0.0f;mat._33 = 1.0f;
//and multiply
S2DMatrixMultiply(mat);
}
//multiply two matrices together
void C2DMatrix::S2DMatrixMultiply(S2DMatrix &mIn)
{
S2DMatrix mat_temp;
//first row
mat_temp._11 = (m_Matrix._11*mIn._11) + (m_Matrix._12*mIn._21) + (m_Matrix._13*mIn._31);
mat_temp._12 = (m_Matrix._11*mIn._12) + (m_Matrix._12*mIn._22) + (m_Matrix._13*mIn._32);
mat_temp._13 = (m_Matrix._11*mIn._13) + (m_Matrix._12*mIn._23) + (m_Matrix._13*mIn._33);
//second
mat_temp._21 = (m_Matrix._21*mIn._11) + (m_Matrix._22*mIn._21) + (m_Matrix._23*mIn._31);
mat_temp._22 = (m_Matrix._21*mIn._12) + (m_Matrix._22*mIn._22) + (m_Matrix._23*mIn._32);
mat_temp._23 = (m_Matrix._21*mIn._13) + (m_Matrix._22*mIn._23) + (m_Matrix._23*mIn._33);
//third
mat_temp._31 = (m_Matrix._31*mIn._11) + (m_Matrix._32*mIn._21) + (m_Matrix._33*mIn._31);
mat_temp._32 = (m_Matrix._31*mIn._12) + (m_Matrix._32*mIn._22) + (m_Matrix._33*mIn._32);
mat_temp._33 = (m_Matrix._31*mIn._13) + (m_Matrix._32*mIn._23) + (m_Matrix._33*mIn._33);
m_Matrix = mat_temp;
}
//applies a 2D transformation matrix to a std::vector of SPoints
void C2DMatrix::TransformSPoints(vector<SPoint> &vPoint)
{
for (int i=0; i<vPoint.size(); ++i)
{
double tempX =(m_Matrix._11*vPoint[i].x) + (m_Matrix._21*vPoint[i].y) + (m_Matrix._31);
double tempY = (m_Matrix._12*vPoint[i].x) + (m_Matrix._22*vPoint[i].y) + (m_Matrix._32);
vPoint[i].x = tempX;
vPoint[i].y = tempY;
}
}
2. CController.cpp
#include "CController.h"
//these hold the geometry of the sweepers and the mines
const int NumSweeperVerts = 16;
const SPoint sweeper[NumSweeperVerts] = {SPoint(-1, -1),
SPoint(-1, 1),
SPoint(-0.5, 1),
SPoint(-0.5, -1),
SPoint(0.5, -1),
SPoint(1, -1),
SPoint(1, 1),
SPoint(0.5, 1),
SPoint(-0.5, -0.5),
SPoint(0.5, -0.5),
SPoint(-0.5, 0.5),
SPoint(-0.25, 0.5),
SPoint(-0.25, 1.75),
SPoint(0.25, 1.75),
SPoint(0.25, 0.5),
SPoint(0.5, 0.5)};
const int NumObjectVerts = 42;
const SPoint objects[NumObjectVerts] = {SPoint(80, 60),
SPoint(200,60),
SPoint(200,60),
SPoint(200,100),
SPoint(200,100),
SPoint(160,100),
SPoint(160,100),
SPoint(160,200),
SPoint(160,200),
SPoint(80,200),
SPoint(80,200),
SPoint(80,60),
SPoint(250,100),
SPoint(300,40),
SPoint(300,40),
SPoint(350,100),
SPoint(350,100),
SPoint(250, 100),
SPoint(220,180),
SPoint(320,180),
SPoint(320,180),
SPoint(320,300),
SPoint(320,300),
SPoint(220,300),
SPoint(220,300),
SPoint(220,180),
SPoint(12,15),
SPoint(380, 15),
SPoint(380,15),
SPoint(380,360),
SPoint(380,360),
SPoint(12,360),
SPoint(12,360),
SPoint(12,340),
SPoint(12,340),
SPoint(100,290),
SPoint(100,290),
SPoint(12,240),
SPoint(12,240),
SPoint(12,15),
//---------------------------------------constructor---------------------
//
// initilaize the sweepers, their brains and the GA factory
//
//-----------------------------------------------------------------------
CController::CController(HWND hwndMain): m_NumSweepers(CParams::iNumSweepers),
m_pGA(NULL),
m_bFastRender(false),
m_iTicks(0),
m_hwndMain(hwndMain),
m_iGenerations(0),
cxClient(CParams::WindowWidth),
cyClient(CParams::WindowHeight)
{
//let's create the mine sweepers
for (int i=0; i<m_NumSweepers; ++i)
{
m_vecSweepers.push_back(CMinesweeper());
}
//get the total number of weights used in the sweepers
//NN so we can initialise the GA
m_NumWeightsInNN = m_vecSweepers[0].GetNumberOfWeights();
//calculate the neuron placement in the weight vector
vector<int> SplitPoints = m_vecSweepers[0].CalculateSplitPoints();
//initialize the Genetic Algorithm class
m_pGA = new CGenAlg(m_NumSweepers,
CParams::dMutationRate,
CParams::dCrossoverRate,
m_NumWeightsInNN,
SplitPoints);
//Get the weights from the GA and insert into the sweepers brains
m_vecThePopulation = m_pGA->GetChromos();
for (i=0; i<m_NumSweepers; i++)
{
m_vecSweepers[i].PutWeights(m_vecThePopulation[i].vecWeights);
}
//create the pens for the graph drawing
m_BluePen = CreatePen(PS_SOLID, 1, RGB(0, 0, 255));
m_RedPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
m_GreenPen = CreatePen(PS_SOLID, 1, RGB(0, 255, 0));
m_GreyPenDotted = CreatePen(PS_DOT, 1, RGB(200, 200, 200));
m_RedPenDotted = CreatePen(PS_DOT, 1, RGB(250, 200, 200));
m_OldPen = NULL;
//and the brushes
m_BlueBrush = CreateSolidBrush(RGB(0,0,244));
m_RedBrush = CreateSolidBrush(RGB(150,0,0));
//fill the vertex buffers
for (i=0; i<NumSweeperVerts; ++i)
{
m_SweeperVB.push_back(sweeper[i]);
}
for (i=0; i<NumObjectVerts; ++i)
{
m_ObjectsVB.push_back(objects[i]);
}
}
//--------------------------------------destructor-------------------------------------
//
//--------------------------------------------------------------------------------------
CController::~CController()
{
if(m_pGA)
{
delete m_pGA;
}
DeleteObject(m_BluePen);
DeleteObject(m_RedPen);
DeleteObject(m_GreenPen);
DeleteObject(m_OldPen);
DeleteObject(m_GreyPenDotted);
DeleteObject(m_RedPenDotted);
DeleteObject(m_BlueBrush);
DeleteObject(m_RedBrush);
}
//---------------------WorldTransform--------------------------------
//
// sets up the translation matrices for the mines and applies the
// world transform to each vertex in the vertex buffer passed to this
// method.
//-------------------------------------------------------------------
void CController::WorldTransform(vector<SPoint> &VBuffer,
SVector2D vPos,
double rotation,
double scale)
{
//create the world transformation matrix
C2DMatrix matTransform;
//scale
matTransform.Scale(scale, scale);
//rotate
matTransform.Rotate(rotation);
//translate
matTransform.Translate(vPos.x, vPos.y);
//transform the ships vertices
matTransform.TransformSPoints(VBuffer);
}
//-------------------------------------Update---------------------------------------
//
// This is the main workhorse. The entire simulation is controlled from here.
//
// The comments should explain what is going on adequately.
//--------------------------------------------------------------------------------------
bool CController::Update()
{
//run the sweepers through CParams::iNumTicks amount of cycles. During this loop each
//sweepers NN is constantly updated with the appropriate information from its
//surroundings. The output from the NN is obtained and the sweeper is moved. If
//it encounters a mine its fitness is updated appropriately,
if (m_iTicks++ < CParams::iNumTicks)
{
for (int i=0; i<m_NumSweepers; ++i)
{
//update the NN and position
if (!m_vecSweepers[i].Update(m_ObjectsVB))
{
//error in processing the neural net
MessageBox(m_hwndMain, "Wrong amount of NN inputs!", "Error", MB_OK);
return false;
}
}
}
//**We have completed another generation.
//Time to run the GA and update the sweepers with their new NNs
else
{
int BestCellCoverage = 0;
for (int swp=0; swp<m_vecSweepers.size(); ++swp)
{
//add up all the penalties and calculate a fitness score
m_vecSweepers[swp].EndOfRunCalculations();
//update the fitness score stored in the GA with this score
m_vecThePopulation[swp].dFitness = m_vecSweepers[swp].Fitness();
if (m_vecSweepers[swp].NumCellsVisited() > BestCellCoverage)
{
BestCellCoverage = m_vecSweepers[swp].NumCellsVisited();
}
}
//update the stats to be used in our stat window
m_vecAvFitness.push_back(fabs(m_pGA->AverageRawFitness()));
m_vecBestFitness.push_back(fabs(m_pGA->BestRawFitness()));
//increment the generation counter
++m_iGenerations;
//reset cycles
m_iTicks = 0;
//insert the sweepers chromosones into the GA factory and
//run the factory to create a new population
m_vecThePopulation = m_pGA->Epoch(m_vecThePopulation);
//insert the new (hopefully)improved brains back into the sweepers
//and reset their positions etc
for (int i=0; i<m_NumSweepers; ++i)
{
m_vecSweepers[i].PutWeights(m_vecThePopulation[i].vecWeights);
m_vecSweepers[i].Reset();
}
}
return true;
}
//------------------------------------Render()--------------------------------------
//
//----------------------------------------------------------------------------------
void CController::Render(HDC surface)
{
//render the stats
string s = "Generation: " + itos(m_iGenerations);
TextOut(surface, 5, 0, s.c_str(), s.size());
//do not render if running at accelerated speed
if (!m_bFastRender)
{
//display the penalties
m_vecSweepers[0].Render(surface);
//render the objects
for (int i=0; i<NumObjectVerts; i+=2)
{
MoveToEx(surface, m_ObjectsVB[i].x, m_ObjectsVB[i].y, NULL);
LineTo(surface, m_ObjectsVB[i+1].x, m_ObjectsVB[i+1].y);
}
//we want the fittest displayed in green
m_OldPen = (HPEN)SelectObject(surface, m_GreenPen);
//render the sweepers
for (i=0; i<m_vecSweepers.size(); i++)
{
if (i < CParams::iNumElite)
{
SelectObject(surface,m_OldPen);
}
else
{
SelectObject(surface, m_GreyPenDotted);
}
//render red if collided and elite
if ( m_vecSweepers[i].Collided() && (i < CParams::iNumElite) )
{
SelectObject(surface, m_RedPen);
}
//render dotted red if collided and not elite
if ( m_vecSweepers[i].Collided() && (i > CParams::iNumElite) )
{
SelectObject(surface, m_RedPenDotted);
}
//grab the sweeper vertices
vector<SPoint> sweeperVB = m_SweeperVB;
//transform the vertex buffer
m_vecSweepers[i].WorldTransform(sweeperVB, m_vecSweepers[i].Scale());
//draw the sweeper left track
MoveToEx(surface, (int)sweeperVB[0].x, (int)sweeperVB[0].y, NULL);
for (int vert=1; vert<4; ++vert)
{
LineTo(surface, (int)sweeperVB[vert].x, (int)sweeperVB[vert].y);
}
LineTo(surface, (int)sweeperVB[0].x, (int)sweeperVB[0].y);
//draw the sweeper right track
MoveToEx(surface, (int)sweeperVB[4].x, (int)sweeperVB[4].y, NULL);
for (vert=5; vert<8; ++vert)
{
LineTo(surface, (int)sweeperVB[vert].x, (int)sweeperVB[vert].y);
}
LineTo(surface, (int)sweeperVB[4].x, (int)sweeperVB[4].y);
MoveToEx(surface, (int)sweeperVB[8].x, (int)sweeperVB[8].y, NULL);
LineTo(surface, (int)sweeperVB[9].x, (int)sweeperVB[9].y);
MoveToEx(surface, (int)sweeperVB[10].x, (int)sweeperVB[10].y, NULL);
for (vert=11; vert<16; ++vert)
{
LineTo(surface, (int)sweeperVB[vert].x, (int)sweeperVB[vert].y);
}
}
//render the sensors
for (i=0; i<CParams::iNumElite; ++i)
{
//grab each sweepers sensor data
vector<SPoint> tranSensors = m_vecSweepers[i].Sensors();
vector<double> SensorReadings = m_vecSweepers[i].SensorReadings();
vector<double> MemoryReadings = m_vecSweepers[i].MemoryReadings();
for (int sr=0; sr<tranSensors.size(); ++sr)
{
if (SensorReadings[sr] > 0)
{
SelectObject(surface, m_RedPen);
}
else
{
SelectObject(surface, m_GreyPenDotted);
}
//make sure we clip the drawing of the sensors or we will get
//unwanted artifacts appearing
if (!((fabs(m_vecSweepers[i].Position().x - tranSensors[sr].x) >
(CParams::dSensorRange+1))||
(fabs(m_vecSweepers[i].Position().y - tranSensors[sr].y) >
(CParams::dSensorRange+1))))
{
MoveToEx(surface,
(int)m_vecSweepers[i].Position().x,
(int)m_vecSweepers[i].Position().y,
NULL);
LineTo(surface, (int)tranSensors[sr].x, (int)tranSensors[sr].y);
//render the cell sensors
RECT rect;
rect.left = (int)tranSensors[sr].x - 2;
rect.right = (int)tranSensors[sr].x + 2;
rect.top = (int)tranSensors[sr].y - 2;
rect.bottom= (int)tranSensors[sr].y + 2;
if (MemoryReadings[sr] < 0)
{
FillRect(surface, &rect, m_BlueBrush);
}
else
{
FillRect(surface, &rect, m_RedBrush);
}
}
}
}
SelectObject(surface, m_OldPen);
}//end if
else
{
PlotStats(surface);
}
}
//--------------------------PlotStats-------------------------------------
//
// Given a surface to draw on this function displays stats and a crude
// graph showing best and average fitness
//------------------------------------------------------------------------
void CController::PlotStats(HDC surface)
{
string s = "Best Fitness: " + ftos(m_pGA->BestRawFitness());
TextOut(surface, 5, 20, s.c_str(), s.size());
s = "Average Fitness: " + ftos(m_pGA->AverageRawFitness());
TextOut(surface, 5, 40, s.c_str(), s.size());
//render the graph
float HSlice = (float)cxClient/(m_iGenerations+1);
float VSlice = (float)cyClient/((m_pGA->BestRawFitness()+1) * 1.5);
//plot the graph for the best fitness
float x = 0;
m_OldPen = (HPEN)SelectObject(surface, m_RedPen);
MoveToEx(surface, 0, cyClient, NULL);
for (int i=0; i<m_vecBestFitness.size(); ++i)
{
LineTo(surface, x, (cyClient - VSlice*m_vecBestFitness[i]));
x += HSlice;
}
//plot the graph for the average fitness
x = 0;
SelectObject(surface, m_BluePen);
MoveToEx(surface, 0, cyClient, NULL);
for (i=0; i<m_vecAvFitness.size(); ++i)
{
LineTo(surface, x, (cyClient - VSlice*m_vecAvFitness[i]));
x += HSlice;
}
//replace the old pen
SelectObject(surface, m_OldPen);
}
//these hold the geometry of the sweepers and the mines
const int NumSweeperVerts = 16;
const SPoint sweeper[NumSweeperVerts] = {SPoint(-1, -1),
SPoint(-1, 1),
SPoint(-0.5, 1),
SPoint(-0.5, -1),
SPoint(0.5, -1),
SPoint(1, -1),
SPoint(1, 1),
SPoint(0.5, 1),
SPoint(-0.5, -0.5),
SPoint(0.5, -0.5),
SPoint(-0.5, 0.5),
SPoint(-0.25, 0.5),
SPoint(-0.25, 1.75),
SPoint(0.25, 1.75),
SPoint(0.25, 0.5),
SPoint(0.5, 0.5)};
const int NumObjectVerts = 42;
const SPoint objects[NumObjectVerts] = {SPoint(80, 60),
SPoint(200,60),
SPoint(200,60),
SPoint(200,100),
SPoint(200,100),
SPoint(160,100),
SPoint(160,100),
SPoint(160,200),
SPoint(160,200),
SPoint(80,200),
SPoint(80,200),
SPoint(80,60),
SPoint(250,100),
SPoint(300,40),
SPoint(300,40),
SPoint(350,100),
SPoint(350,100),
SPoint(250, 100),
SPoint(220,180),
SPoint(320,180),
SPoint(320,180),
SPoint(320,300),
SPoint(320,300),
SPoint(220,300),
SPoint(220,300),
SPoint(220,180),
SPoint(12,15),
SPoint(380, 15),
SPoint(380,15),
SPoint(380,360),
SPoint(380,360),
SPoint(12,360),
SPoint(12,360),
SPoint(12,340),
SPoint(12,340),
SPoint(100,290),
SPoint(100,290),
SPoint(12,240),
SPoint(12,240),
SPoint(12,15),
//---------------------------------------constructor---------------------
//
// initilaize the sweepers, their brains and the GA factory
//
//-----------------------------------------------------------------------
CController::CController(HWND hwndMain): m_NumSweepers(CParams::iNumSweepers),
m_pGA(NULL),
m_bFastRender(false),
m_iTicks(0),
m_hwndMain(hwndMain),
m_iGenerations(0),
cxClient(CParams::WindowWidth),
cyClient(CParams::WindowHeight)
{
//let's create the mine sweepers
for (int i=0; i<m_NumSweepers; ++i)
{
m_vecSweepers.push_back(CMinesweeper());
}
//get the total number of weights used in the sweepers
//NN so we can initialise the GA
m_NumWeightsInNN = m_vecSweepers[0].GetNumberOfWeights();
//calculate the neuron placement in the weight vector
vector<int> SplitPoints = m_vecSweepers[0].CalculateSplitPoints();
//initialize the Genetic Algorithm class
m_pGA = new CGenAlg(m_NumSweepers,
CParams::dMutationRate,
CParams::dCrossoverRate,
m_NumWeightsInNN,
SplitPoints);
//Get the weights from the GA and insert into the sweepers brains
m_vecThePopulation = m_pGA->GetChromos();
for (i=0; i<m_NumSweepers; i++)
{
m_vecSweepers[i].PutWeights(m_vecThePopulation[i].vecWeights);
}
//create the pens for the graph drawing
m_BluePen = CreatePen(PS_SOLID, 1, RGB(0, 0, 255));
m_RedPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
m_GreenPen = CreatePen(PS_SOLID, 1, RGB(0, 255, 0));
m_GreyPenDotted = CreatePen(PS_DOT, 1, RGB(200, 200, 200));
m_RedPenDotted = CreatePen(PS_DOT, 1, RGB(250, 200, 200));
m_OldPen = NULL;
//and the brushes
m_BlueBrush = CreateSolidBrush(RGB(0,0,244));
m_RedBrush = CreateSolidBrush(RGB(150,0,0));
//fill the vertex buffers
for (i=0; i<NumSweeperVerts; ++i)
{
m_SweeperVB.push_back(sweeper[i]);
}
for (i=0; i<NumObjectVerts; ++i)
{
m_ObjectsVB.push_back(objects[i]);
}
}
//--------------------------------------destructor-------------------------------------
//
//--------------------------------------------------------------------------------------
CController::~CController()
{
if(m_pGA)
{
delete m_pGA;
}
DeleteObject(m_BluePen);
DeleteObject(m_RedPen);
DeleteObject(m_GreenPen);
DeleteObject(m_OldPen);
DeleteObject(m_GreyPenDotted);
DeleteObject(m_RedPenDotted);
DeleteObject(m_BlueBrush);
DeleteObject(m_RedBrush);
}
//---------------------WorldTransform--------------------------------
//
// sets up the translation matrices for the mines and applies the
// world transform to each vertex in the vertex buffer passed to this
// method.
//-------------------------------------------------------------------
void CController::WorldTransform(vector<SPoint> &VBuffer,
SVector2D vPos,
double rotation,
double scale)
{
//create the world transformation matrix
C2DMatrix matTransform;
//scale
matTransform.Scale(scale, scale);
//rotate
matTransform.Rotate(rotation);
//translate
matTransform.Translate(vPos.x, vPos.y);
//transform the ships vertices
matTransform.TransformSPoints(VBuffer);
}
//-------------------------------------Update---------------------------------------
//
// This is the main workhorse. The entire simulation is controlled from here.
//
// The comments should explain what is going on adequately.
//--------------------------------------------------------------------------------------
bool CController::Update()
{
//run the sweepers through CParams::iNumTicks amount of cycles. During this loop each
//sweepers NN is constantly updated with the appropriate information from its
//surroundings. The output from the NN is obtained and the sweeper is moved. If
//it encounters a mine its fitness is updated appropriately,
if (m_iTicks++ < CParams::iNumTicks)
{
for (int i=0; i<m_NumSweepers; ++i)
{
//update the NN and position
if (!m_vecSweepers[i].Update(m_ObjectsVB))
{
//error in processing the neural net
MessageBox(m_hwndMain, "Wrong amount of NN inputs!", "Error", MB_OK);
return false;
}
}
}
//**We have completed another generation.
//Time to run the GA and update the sweepers with their new NNs
else
{
int BestCellCoverage = 0;
for (int swp=0; swp<m_vecSweepers.size(); ++swp)
{
//add up all the penalties and calculate a fitness score
m_vecSweepers[swp].EndOfRunCalculations();
//update the fitness score stored in the GA with this score
m_vecThePopulation[swp].dFitness = m_vecSweepers[swp].Fitness();
if (m_vecSweepers[swp].NumCellsVisited() > BestCellCoverage)
{
BestCellCoverage = m_vecSweepers[swp].NumCellsVisited();
}
}
//update the stats to be used in our stat window
m_vecAvFitness.push_back(fabs(m_pGA->AverageRawFitness()));
m_vecBestFitness.push_back(fabs(m_pGA->BestRawFitness()));
//increment the generation counter
++m_iGenerations;
//reset cycles
m_iTicks = 0;
//insert the sweepers chromosones into the GA factory and
//run the factory to create a new population
m_vecThePopulation = m_pGA->Epoch(m_vecThePopulation);
//insert the new (hopefully)improved brains back into the sweepers
//and reset their positions etc
for (int i=0; i<m_NumSweepers; ++i)
{
m_vecSweepers[i].PutWeights(m_vecThePopulation[i].vecWeights);
m_vecSweepers[i].Reset();
}
}
return true;
}
//------------------------------------Render()--------------------------------------
//
//----------------------------------------------------------------------------------
void CController::Render(HDC surface)
{
//render the stats
string s = "Generation: " + itos(m_iGenerations);
TextOut(surface, 5, 0, s.c_str(), s.size());
//do not render if running at accelerated speed
if (!m_bFastRender)
{
//display the penalties
m_vecSweepers[0].Render(surface);
//render the objects
for (int i=0; i<NumObjectVerts; i+=2)
{
MoveToEx(surface, m_ObjectsVB[i].x, m_ObjectsVB[i].y, NULL);
LineTo(surface, m_ObjectsVB[i+1].x, m_ObjectsVB[i+1].y);
}
//we want the fittest displayed in green
m_OldPen = (HPEN)SelectObject(surface, m_GreenPen);
//render the sweepers
for (i=0; i<m_vecSweepers.size(); i++)
{
if (i < CParams::iNumElite)
{
SelectObject(surface,m_OldPen);
}
else
{
SelectObject(surface, m_GreyPenDotted);
}
//render red if collided and elite
if ( m_vecSweepers[i].Collided() && (i < CParams::iNumElite) )
{
SelectObject(surface, m_RedPen);
}
//render dotted red if collided and not elite
if ( m_vecSweepers[i].Collided() && (i > CParams::iNumElite) )
{
SelectObject(surface, m_RedPenDotted);
}
//grab the sweeper vertices
vector<SPoint> sweeperVB = m_SweeperVB;
//transform the vertex buffer
m_vecSweepers[i].WorldTransform(sweeperVB, m_vecSweepers[i].Scale());
//draw the sweeper left track
MoveToEx(surface, (int)sweeperVB[0].x, (int)sweeperVB[0].y, NULL);
for (int vert=1; vert<4; ++vert)
{
LineTo(surface, (int)sweeperVB[vert].x, (int)sweeperVB[vert].y);
}
LineTo(surface, (int)sweeperVB[0].x, (int)sweeperVB[0].y);
//draw the sweeper right track
MoveToEx(surface, (int)sweeperVB[4].x, (int)sweeperVB[4].y, NULL);
for (vert=5; vert<8; ++vert)
{
LineTo(surface, (int)sweeperVB[vert].x, (int)sweeperVB[vert].y);
}
LineTo(surface, (int)sweeperVB[4].x, (int)sweeperVB[4].y);
MoveToEx(surface, (int)sweeperVB[8].x, (int)sweeperVB[8].y, NULL);
LineTo(surface, (int)sweeperVB[9].x, (int)sweeperVB[9].y);
MoveToEx(surface, (int)sweeperVB[10].x, (int)sweeperVB[10].y, NULL);
for (vert=11; vert<16; ++vert)
{
LineTo(surface, (int)sweeperVB[vert].x, (int)sweeperVB[vert].y);
}
}
//render the sensors
for (i=0; i<CParams::iNumElite; ++i)
{
//grab each sweepers sensor data
vector<SPoint> tranSensors = m_vecSweepers[i].Sensors();
vector<double> SensorReadings = m_vecSweepers[i].SensorReadings();
vector<double> MemoryReadings = m_vecSweepers[i].MemoryReadings();
for (int sr=0; sr<tranSensors.size(); ++sr)
{
if (SensorReadings[sr] > 0)
{
SelectObject(surface, m_RedPen);
}
else
{
SelectObject(surface, m_GreyPenDotted);
}
//make sure we clip the drawing of the sensors or we will get
//unwanted artifacts appearing
if (!((fabs(m_vecSweepers[i].Position().x - tranSensors[sr].x) >
(CParams::dSensorRange+1))||
(fabs(m_vecSweepers[i].Position().y - tranSensors[sr].y) >
(CParams::dSensorRange+1))))
{
MoveToEx(surface,
(int)m_vecSweepers[i].Position().x,
(int)m_vecSweepers[i].Position().y,
NULL);
LineTo(surface, (int)tranSensors[sr].x, (int)tranSensors[sr].y);
//render the cell sensors
RECT rect;
rect.left = (int)tranSensors[sr].x - 2;
rect.right = (int)tranSensors[sr].x + 2;
rect.top = (int)tranSensors[sr].y - 2;
rect.bottom= (int)tranSensors[sr].y + 2;
if (MemoryReadings[sr] < 0)
{
FillRect(surface, &rect, m_BlueBrush);
}
else
{
FillRect(surface, &rect, m_RedBrush);
}
}
}
}
SelectObject(surface, m_OldPen);
}//end if
else
{
PlotStats(surface);
}
}
//--------------------------PlotStats-------------------------------------
//
// Given a surface to draw on this function displays stats and a crude
// graph showing best and average fitness
//------------------------------------------------------------------------
void CController::PlotStats(HDC surface)
{
string s = "Best Fitness: " + ftos(m_pGA->BestRawFitness());
TextOut(surface, 5, 20, s.c_str(), s.size());
s = "Average Fitness: " + ftos(m_pGA->AverageRawFitness());
TextOut(surface, 5, 40, s.c_str(), s.size());
//render the graph
float HSlice = (float)cxClient/(m_iGenerations+1);
float VSlice = (float)cyClient/((m_pGA->BestRawFitness()+1) * 1.5);
//plot the graph for the best fitness
float x = 0;
m_OldPen = (HPEN)SelectObject(surface, m_RedPen);
MoveToEx(surface, 0, cyClient, NULL);
for (int i=0; i<m_vecBestFitness.size(); ++i)
{
LineTo(surface, x, (cyClient - VSlice*m_vecBestFitness[i]));
x += HSlice;
}
//plot the graph for the average fitness
x = 0;
SelectObject(surface, m_BluePen);
MoveToEx(surface, 0, cyClient, NULL);
for (i=0; i<m_vecAvFitness.size(); ++i)
{
LineTo(surface, x, (cyClient - VSlice*m_vecAvFitness[i]));
x += HSlice;
}
//replace the old pen
SelectObject(surface, m_OldPen);
}
3. CGenAlg.cpp
#include "CGenAlg.h"
//-----------------------------------constructor-------------------------
//
// sets up the population with random floats
//
//-----------------------------------------------------------------------
CGenAlg::CGenAlg(int popsize,
double MutRat,
double CrossRat,
int numweights,
vector<int> splits) :m_iPopSize(popsize),
m_dMutationRate(MutRat),
m_dCrossoverRate(CrossRat),
m_iChromoLength(numweights),
m_dTotalFitness(0),
m_cGeneration(0),
m_iFittestGenome(0),
m_dBestFitness(0),
m_dWorstFitness(99999999),
m_dAverageFitness(0),
m_vecSplitPoints(splits)
{
//initialise population with chromosomes consisting of random
//weights and all fitnesses set to zero
for (int i=0; i<m_iPopSize; ++i)
{
m_vecPop.push_back(SGenome());
for (int j=0; j<m_iChromoLength; ++j)
{
m_vecPop[i].vecWeights.push_back(RandomClamped());
}
}
}
//---------------------------------Mutate--------------------------------
//
// mutates a chromosome by perturbing its weights by an amount not
// greater than MAX_PERTURBATION
//-----------------------------------------------------------------------
void CGenAlg::Mutate(vector<double> &chromo)
{
//traverse the chromosome and mutate each weight dependent
//on the mutation rate
for (int i=0; i<chromo.size(); ++i)
{
//do we perturb this weight?
if (RandFloat() < m_dMutationRate)
{
//add a small value to the weight
chromo[i] += (RandomClamped() * CParams::dMaxPerturbation);
}
}
}
//---------------------------- CrossoverAtSplits -------------------------
//
// performs crossover at the neuron bounderies. See the end of chapter 7
// for details
//-------------------------------------------------------------------------
void CGenAlg::CrossoverAtSplits(const vector<double> &mum,
const vector<double> &dad,
vector<double> &baby1,
vector<double> &baby2)
{
//just return parents as offspring dependent on the rate
//or if parents are the same
if ( (RandFloat() > m_dCrossoverRate) || (mum == dad))
{
baby1 = mum;
baby2 = dad;
return;
}
//determine two crossover points
int Index1 = RandInt(0, m_vecSplitPoints.size()-2);
int Index2 = RandInt(Index1, m_vecSplitPoints.size()-1);
int cp1 = m_vecSplitPoints[Index1];
int cp2 = m_vecSplitPoints[Index2];
//create the offspring
for (int i=0; i<mum.size(); ++i)
{
if ( (i<cp1) || (i>=cp2) )
{
//keep the same genes if outside of crossover points
baby1.push_back(mum[i]);
baby2.push_back(dad[i]);
}
else
{
//switch over the belly block
baby1.push_back(dad[i]);
baby2.push_back(mum[i]);
}
}
return;
}
//---------------------------- TournamentSelection -----------------
//
// performs standard tournament selection given a number of genomes to
// sample from each try.
//------------------------------------------------------------------------
SGenome CGenAlg::TournamentSelection(int N)
{
double BestFitnessSoFar = -999999;
int ChosenOne = 0;
//Select N members from the population at random testing against
//the best found so far
for (int i=0; i<N; ++i)
{
int ThisTry = RandInt(0, m_iPopSize-1);
if (m_vecPop[ThisTry].dFitness > BestFitnessSoFar)
{
ChosenOne = ThisTry;
BestFitnessSoFar = m_vecPop[ThisTry].dFitness;
}
}
//return the champion
return m_vecPop[ChosenOne];
}
//-----------------------------------Epoch()-----------------------------
//
// takes a population of chromosones and runs the algorithm through one
// cycle.
// Returns a new population of chromosones.
//-----------------------------------------------------------------------
vector<SGenome> CGenAlg::Epoch(vector<SGenome> &old_pop)
{
//assign the given population to the classes population
m_vecPop = old_pop;
//reset the appropriate variables
Reset();
//create a temporary vector to store new chromosones
vector <SGenome> vecNewPop;
CalculateBestWorstAvTot();
//sort the population (for scaling and elitism)
sort(m_vecPop.begin(), m_vecPop.end());
//Now to add a little elitism we shall add in some copies of the
//fittest genomes. Make sure we add an EVEN number or the roulette
//wheel sampling will crash
if (!(CParams::iNumCopiesElite * CParams::iNumElite % 2))
{
GrabNBest(CParams::iNumElite, CParams::iNumCopiesElite, vecNewPop);
}
//--------------now to enter the GA loop
//repeat until a new population is generated
while (vecNewPop.size() < m_iPopSize)
{
//select using tournament selection for a change
SGenome mum = TournamentSelection(CParams::iTournamentCompetitors);
SGenome dad = TournamentSelection(CParams::iTournamentCompetitors);
//create some offspring via crossover
vector<double>baby1, baby2;
CrossoverAtSplits(mum.vecWeights, dad.vecWeights, baby1, baby2);
//now we mutate
Mutate(baby1);
Mutate(baby2);
//now copy into vecNewPop population
vecNewPop.push_back( SGenome(baby1, 0) );
vecNewPop.push_back( SGenome(baby2, 0) );
}
//finished so assign new pop back into m_vecPop
m_vecPop = vecNewPop;
return m_vecPop;
}
//-------------------------GrabNBest----------------------------------
//
// This works like an advanced form of elitism by inserting NumCopies
// copies of the NBest most fittest genomes into a population vector
//--------------------------------------------------------------------
void CGenAlg::GrabNBest(int NBest,
const int NumCopies,
vector<SGenome> &Pop)
{
//add the required amount of copies of the n most fittest
//to the supplied vector
while(NBest--)
{
for (int i=0; i<NumCopies; ++i)
{
Pop.push_back(m_vecPop[(m_iPopSize - 1) - NBest]);
}
}
}
//-----------------------CalculateBestWorstAvTot-----------------------
//
// calculates the fittest and weakest genome and the average/total
// fitness scores
//---------------------------------------------------------------------
void CGenAlg::CalculateBestWorstAvTot()
{
m_dTotalFitness = 0;
double HighestSoFar = 0;
double LowestSoFar = 9999999;
for (int i=0; i<m_iPopSize; ++i)
{
//update fittest if necessary
if (m_vecPop[i].dFitness > HighestSoFar)
{
HighestSoFar = m_vecPop[i].dFitness;
m_iFittestGenome = i;
m_dBestFitness= HighestSoFar;
}
//update worst if necessary
if (m_vecPop[i].dFitness < LowestSoFar)
{
LowestSoFar = m_vecPop[i].dFitness;
m_dWorstFitness = LowestSoFar;
}
m_dTotalFitness+= m_vecPop[i].dFitness;
}//next chromo
m_dAverageFitness = m_dTotalFitness / m_iPopSize;
}
//-------------------------Reset()------------------------------
//
// resets all the relevant variables ready for a new generation
//--------------------------------------------------------------
void CGenAlg::Reset()
{
m_dTotalFitness= 0;
m_dBestFitness= 0;
m_dWorstFitness= 9999999;
m_dAverageFitness= 0;
}
//-----------------------------------constructor-------------------------
//
// sets up the population with random floats
//
//-----------------------------------------------------------------------
CGenAlg::CGenAlg(int popsize,
double MutRat,
double CrossRat,
int numweights,
vector<int> splits) :m_iPopSize(popsize),
m_dMutationRate(MutRat),
m_dCrossoverRate(CrossRat),
m_iChromoLength(numweights),
m_dTotalFitness(0),
m_cGeneration(0),
m_iFittestGenome(0),
m_dBestFitness(0),
m_dWorstFitness(99999999),
m_dAverageFitness(0),
m_vecSplitPoints(splits)
{
//initialise population with chromosomes consisting of random
//weights and all fitnesses set to zero
for (int i=0; i<m_iPopSize; ++i)
{
m_vecPop.push_back(SGenome());
for (int j=0; j<m_iChromoLength; ++j)
{
m_vecPop[i].vecWeights.push_back(RandomClamped());
}
}
}
//---------------------------------Mutate--------------------------------
//
// mutates a chromosome by perturbing its weights by an amount not
// greater than MAX_PERTURBATION
//-----------------------------------------------------------------------
void CGenAlg::Mutate(vector<double> &chromo)
{
//traverse the chromosome and mutate each weight dependent
//on the mutation rate
for (int i=0; i<chromo.size(); ++i)
{
//do we perturb this weight?
if (RandFloat() < m_dMutationRate)
{
//add a small value to the weight
chromo[i] += (RandomClamped() * CParams::dMaxPerturbation);
}
}
}
//---------------------------- CrossoverAtSplits -------------------------
//
// performs crossover at the neuron bounderies. See the end of chapter 7
// for details
//-------------------------------------------------------------------------
void CGenAlg::CrossoverAtSplits(const vector<double> &mum,
const vector<double> &dad,
vector<double> &baby1,
vector<double> &baby2)
{
//just return parents as offspring dependent on the rate
//or if parents are the same
if ( (RandFloat() > m_dCrossoverRate) || (mum == dad))
{
baby1 = mum;
baby2 = dad;
return;
}
//determine two crossover points
int Index1 = RandInt(0, m_vecSplitPoints.size()-2);
int Index2 = RandInt(Index1, m_vecSplitPoints.size()-1);
int cp1 = m_vecSplitPoints[Index1];
int cp2 = m_vecSplitPoints[Index2];
//create the offspring
for (int i=0; i<mum.size(); ++i)
{
if ( (i<cp1) || (i>=cp2) )
{
//keep the same genes if outside of crossover points
baby1.push_back(mum[i]);
baby2.push_back(dad[i]);
}
else
{
//switch over the belly block
baby1.push_back(dad[i]);
baby2.push_back(mum[i]);
}
}
return;
}
//---------------------------- TournamentSelection -----------------
//
// performs standard tournament selection given a number of genomes to
// sample from each try.
//------------------------------------------------------------------------
SGenome CGenAlg::TournamentSelection(int N)
{
double BestFitnessSoFar = -999999;
int ChosenOne = 0;
//Select N members from the population at random testing against
//the best found so far
for (int i=0; i<N; ++i)
{
int ThisTry = RandInt(0, m_iPopSize-1);
if (m_vecPop[ThisTry].dFitness > BestFitnessSoFar)
{
ChosenOne = ThisTry;
BestFitnessSoFar = m_vecPop[ThisTry].dFitness;
}
}
//return the champion
return m_vecPop[ChosenOne];
}
//-----------------------------------Epoch()-----------------------------
//
// takes a population of chromosones and runs the algorithm through one
// cycle.
// Returns a new population of chromosones.
//-----------------------------------------------------------------------
vector<SGenome> CGenAlg::Epoch(vector<SGenome> &old_pop)
{
//assign the given population to the classes population
m_vecPop = old_pop;
//reset the appropriate variables
Reset();
//create a temporary vector to store new chromosones
vector <SGenome> vecNewPop;
CalculateBestWorstAvTot();
//sort the population (for scaling and elitism)
sort(m_vecPop.begin(), m_vecPop.end());
//Now to add a little elitism we shall add in some copies of the
//fittest genomes. Make sure we add an EVEN number or the roulette
//wheel sampling will crash
if (!(CParams::iNumCopiesElite * CParams::iNumElite % 2))
{
GrabNBest(CParams::iNumElite, CParams::iNumCopiesElite, vecNewPop);
}
//--------------now to enter the GA loop
//repeat until a new population is generated
while (vecNewPop.size() < m_iPopSize)
{
//select using tournament selection for a change
SGenome mum = TournamentSelection(CParams::iTournamentCompetitors);
SGenome dad = TournamentSelection(CParams::iTournamentCompetitors);
//create some offspring via crossover
vector<double>baby1, baby2;
CrossoverAtSplits(mum.vecWeights, dad.vecWeights, baby1, baby2);
//now we mutate
Mutate(baby1);
Mutate(baby2);
//now copy into vecNewPop population
vecNewPop.push_back( SGenome(baby1, 0) );
vecNewPop.push_back( SGenome(baby2, 0) );
}
//finished so assign new pop back into m_vecPop
m_vecPop = vecNewPop;
return m_vecPop;
}
//-------------------------GrabNBest----------------------------------
//
// This works like an advanced form of elitism by inserting NumCopies
// copies of the NBest most fittest genomes into a population vector
//--------------------------------------------------------------------
void CGenAlg::GrabNBest(int NBest,
const int NumCopies,
vector<SGenome> &Pop)
{
//add the required amount of copies of the n most fittest
//to the supplied vector
while(NBest--)
{
for (int i=0; i<NumCopies; ++i)
{
Pop.push_back(m_vecPop[(m_iPopSize - 1) - NBest]);
}
}
}
//-----------------------CalculateBestWorstAvTot-----------------------
//
// calculates the fittest and weakest genome and the average/total
// fitness scores
//---------------------------------------------------------------------
void CGenAlg::CalculateBestWorstAvTot()
{
m_dTotalFitness = 0;
double HighestSoFar = 0;
double LowestSoFar = 9999999;
for (int i=0; i<m_iPopSize; ++i)
{
//update fittest if necessary
if (m_vecPop[i].dFitness > HighestSoFar)
{
HighestSoFar = m_vecPop[i].dFitness;
m_iFittestGenome = i;
m_dBestFitness= HighestSoFar;
}
//update worst if necessary
if (m_vecPop[i].dFitness < LowestSoFar)
{
LowestSoFar = m_vecPop[i].dFitness;
m_dWorstFitness = LowestSoFar;
}
m_dTotalFitness+= m_vecPop[i].dFitness;
}//next chromo
m_dAverageFitness = m_dTotalFitness / m_iPopSize;
}
//-------------------------Reset()------------------------------
//
// resets all the relevant variables ready for a new generation
//--------------------------------------------------------------
void CGenAlg::Reset()
{
m_dTotalFitness= 0;
m_dBestFitness= 0;
m_dWorstFitness= 9999999;
m_dAverageFitness= 0;
}
0 0
- 神经网络入门 ,源码3
- 神经网络入门 ,源码1
- 神经网络入门 ,源码2
- 神经网络入门 ,源码4
- 神经网络入门 ,源码5
- 神经网络入门 ,源码6
- 神经网络入门 ,源码7
- 神经网络入门 ,源码8
- 神经网络入门--连载3
- 神经网络入门3
- 神经网络入门
- 神经网络入门
- 神经网络入门
- 神经网络入门
- 神经网络入门
- 神经网络入门
- 神经网络入门
- 神经网络入门
- 语句
- windows安装Apache,注册服务出现“(OS 5)拒绝访问。 : AH00369: Failed to open the WinNT service manager..."错误
- eclipse常用插件在线安装地址或下载地址
- c语言实现单链表
- Android六大布局的介绍
- 神经网络入门 ,源码3
- nodejs中一个简单的TCP服务器端和客户端的聊天服务器
- 序列的算法(一·b)隐马尔可夫模型
- springmvc 快速入门
- 【IO】ObjectInputStream的使用方法及其注意事项
- 基于Bootstrap垂直响应的jQuery时间轴特效
- 神经网络入门 ,源码4
- hdu3786——找出直系亲属
- openCV学习笔记-Mat类的七种显示创建