Click here to Skip to main content
15,891,529 members
Articles / Desktop Programming / MFC

Neural Network for Recognition of Handwritten Digits

Rate me:
Please Sign up or sign in to vote.
4.97/5 (240 votes)
5 Dec 200668 min read 2M   57.5K   571  
A convolutional neural network achieves 99.26% accuracy on a modified NIST database of hand-written digits.
// DlgCharacterImage.cpp : implementation file
//

#include "stdafx.h"
#include "MNist.h"
#include "DlgCharacterImage.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CDlgCharacterImage dialog


CDlgCharacterImage::CDlgCharacterImage(CWnd* pParent /*=NULL*/)
: CDialog(CDlgCharacterImage::IDD, pParent),
m_pDoc( NULL )
{
	//{{AFX_DATA_INIT(CDlgCharacterImage)
	// NOTE: the ClassWizard will add member initialization here
	//}}AFX_DATA_INIT
}


void CDlgCharacterImage::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CDlgCharacterImage)
	DDX_Control(pDX, IDC_RADIO_TRAINING_SET, m_ctlRadioWhichSet);
	DDX_Control(pDX, IDC_CHECK_PATTERN_DISTORTION, m_ctlCheckDistortInputPattern);
	DDX_Control(pDX, IDC_EDIT_VALUE, m_ctlEditLabelValue);
	DDX_Control(pDX, IDC_EDIT_IMAGE_NUM, m_ctlEditImageNumber);
	//}}AFX_DATA_MAP
}


BEGIN_MESSAGE_MAP(CDlgCharacterImage, CDialog)
//{{AFX_MSG_MAP(CDlgCharacterImage)
ON_WM_SIZE()
ON_BN_CLICKED(IDC_BUTTON_GET, OnButtonGetImageData)
ON_BN_CLICKED(IDC_BUTTON_NEXT, OnButtonGetNextImageData)
ON_BN_CLICKED(IDC_BUTTON_PREVIOUS, OnButtonGetPreviousImageData)
ON_BN_CLICKED(IDC_BUTTON_NN_CALCULATE, OnButtonNnCalculate)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CDlgCharacterImage message handlers

BOOL CDlgCharacterImage::OnInitDialog() 
{
	CDialog::OnInitDialog();
	
	ASSERT( m_pDoc != NULL );
	
	
	
	// create the character image window, using static placeholder from the dialog template,
	// and set its size to the global size of the character image
	
	CRect rcPlace;
	CWnd* pPlaceholder = GetDlgItem( IDC_CHARACTER_IMAGE );
	if ( pPlaceholder != NULL )
	{
		pPlaceholder->GetWindowRect( &rcPlace );  // in screen coords
		::MapWindowPoints( NULL, m_hWnd, (POINT*)&rcPlace, 2 );  // map from screen to this window's coords
		
		rcPlace.right = rcPlace.left + g_cImageSize+2;  // +2 allows for the width of WS_EX_STATIC edge
		rcPlace.bottom = rcPlace.top + g_cImageSize+2;  // +2 allows for the width of WS_EX_STATIC edge
		
		m_wndCharImage.CreateEx( WS_EX_STATICEDGE,  NULL, _T("CharacterImage"), WS_CHILD|WS_VISIBLE, rcPlace, this, IDC_CHARACTER_IMAGE );
		
		// close placeholder window since it's no longer needed
		
		pPlaceholder->DestroyWindow();
	}
	
	
	// create the neuron viewer image window, using static placeholder from the dialog template
	
	pPlaceholder = GetDlgItem( IDC_NEURON_VIEWER );
	if ( pPlaceholder != NULL )
	{
		pPlaceholder->GetWindowRect( &rcPlace );  // in screen coords
		::MapWindowPoints( NULL, m_hWnd, (POINT*)&rcPlace, 2 );  // map from screen to this window's coords
		
		m_wndNeuronViewer.CreateEx( WS_EX_STATICEDGE,  NULL, _T("NeuronViewer"), WS_CHILD|WS_VISIBLE, rcPlace, this, IDC_NEURON_VIEWER );
		
		// close placeholder window since it's no longer needed
		
		pPlaceholder->DestroyWindow();
	}
	
	
	
	// initialize values of edit controls
	
	m_ctlEditImageNumber.SetWindowText( _T("0") );
	m_ctlEditLabelValue.SetWindowText( _T("0") );
	
	
	// initialize state of "Distort input pattern" check box
	
	m_ctlCheckDistortInputPattern.SetCheck( 1 );  // 1 == checked
	
	
	// initialize state of "Training Pattern/Testing Pattern" radio buttons
	
	m_ctlRadioWhichSet.SetCheck( 1 );  // 1 == checked (which implies that testing is un-checked
	
	
	
	// initialize resize helper
	
	m_resizeHelper.Init( m_hWnd );
	m_resizeHelper.Fix( IDC_CHARACTER_IMAGE, DlgResizeHelper::kLeftRight, DlgResizeHelper::kTopBottom );
	
	
	// clear m_NeuronOutputs
	
	m_NeuronOutputs.clear();
	
	
	return TRUE;  // return TRUE unless you set the focus to a control
	// EXCEPTION: OCX Property Pages should return FALSE
}


void CDlgCharacterImage::OnOK()
{
	// ordinarily do nothing, to prevent the dialog from closing when user hits the "Enter key
	// However, check if the user hit "return" after edting the pattern number window
	
	CWnd* pWnd = GetFocus();
	
	if ( pWnd->m_hWnd == m_ctlEditImageNumber.m_hWnd )
	{
		// current focus is the edit control that contains the index for the image number, and
		// user has just hit the "return" button.  We assume that he wants to calculate the neural
		// net, so we set the focus tot he Calculate button and call the corresponding
		// calculate functions
		
/////////////////////////////////////////////////
//////////  CODE REVIEW: There's no need to shift focus away from the edit.  This allows the user to 
		// continuously enter numbers and hit enter, without the need to re-click the edit window
		///////// CWnd* pAnotherWnd = GetDlgItem( IDC_BUTTON_NN_CALCULATE );
		///////// if ( pAnotherWnd != NULL )
		////////// 	pAnotherWnd->SetFocus();

		//////////// no point in SetSel here: the OnButtonNnCalculate function (which calls UpdateCharacterImageData)
		//////////// eventually sends WM_SETTEXT to the edit control, which un-selects all text.  
		//////////// We will SetSel right after the
		//////////// WM_SETTEXT, in the UpdateCharacterImageData function

		///////////// m_ctlEditImageNumber.SetSel( 0, -1 );  // select all text in control

		OnButtonNnCalculate();
	}
	
}


void CDlgCharacterImage::OnCancel()
{
	// do nothing, to override default behavior, which is to close the dialog when the ESC key is pressed
}



void CDlgCharacterImage::OnSize(UINT nType, int cx, int cy) 
{
	CDialog::OnSize(nType, cx, cy);
	
	// TODO: Add your message handler code here
	
	m_resizeHelper.OnSize();	
}

void CDlgCharacterImage::OnButtonGetImageData() 
{
	// gets image corresponding to value in edit control
	
	CString strNum;
	m_ctlEditImageNumber.GetWindowText( strNum );
	
	int iNum = _ttoi( strNum );
	
	UpdateCharacterImageData( iNum );
	
}

void CDlgCharacterImage::OnButtonGetNextImageData() 
{
	// increments the value of the edit control and gets corresponding image
	
	CString strNum;
	m_ctlEditImageNumber.GetWindowText( strNum );
	
	int iNum = _ttoi( strNum );
	++iNum;
	
	UpdateCharacterImageData( iNum );	
}

void CDlgCharacterImage::OnButtonGetPreviousImageData() 
{
	// decrements the value of the edit control and gets corresponding image
	
	CString strNum;
	m_ctlEditImageNumber.GetWindowText( strNum );
	
	int iNum = _ttoi( strNum );
	--iNum;
	
	UpdateCharacterImageData( iNum );		
}

void CDlgCharacterImage::UpdateCharacterImageData(int& iNumImage)
{
	// updates the viewing window that contains the character's image
	// and adjusts the value of the int iNumImage so that it returns a value
	// that lies inside a valid range of the selected image set (training or testing)
	
	unsigned char grayArray[ g_cImageSize * g_cImageSize ] = {0};
	int label = 0;
	
	// determine whether we are looking for the training patterns or the testing patterns
	
	BOOL bTraining = ( 1 == m_ctlRadioWhichSet.GetCheck() );  // 1 == checked
	
	if ( bTraining != FALSE )
	{
		// the training set has been selected
		// adjust numeric value of pattern so that it lies inside a valid range
		
		if ( iNumImage < 0 ) iNumImage = 0;
		if ( iNumImage >= ::GetPreferences().m_nItemsTrainingImages ) iNumImage = ::GetPreferences().m_nItemsTrainingImages - 1;
		
		// get gray-scale values for character image, and numeric value of its "answer"
		
		m_pDoc->GetTrainingPatternArrayValues( iNumImage, grayArray, &label, TRUE );
		
	}
	else
	{
		// testing set has been selected
		// adjust numeric value of pattern so that it lies inside a valid range
		
		if ( iNumImage < 0 ) iNumImage = 0;
		if ( iNumImage >= ::GetPreferences().m_nItemsTestingImages ) iNumImage = ::GetPreferences().m_nItemsTestingImages - 1;
		
		// get gray-scale values for character image, and numeric value of its "answer"
		
		m_pDoc->GetTestingPatternArrayValues( iNumImage, grayArray, &label, TRUE );
	}
	
	// update appearance of the dialog
	
	CString strNum;
	
	strNum.Format( _T("%i"), label );
	m_ctlEditLabelValue.SetWindowText( strNum );
	
	strNum.Format( _T("%i"), iNumImage );
	m_ctlEditImageNumber.SetWindowText( strNum );
	m_ctlEditImageNumber.SetSel( 0, -1 );  // select all text in control; will be highlighted only if edit also has focus

	
	m_wndCharImage.BuildBitmapFromGrayValues( grayArray );
	m_wndCharImage.Invalidate( );
	
}


void CDlgCharacterImage::OnButtonNnCalculate() 
{
	// runs the current character image through the neural net
	
	// first, get image corresponding to value in edit control
	// note that this code is identical to OnButtonGetImageData,
	// which is deliberate because the user might have changed the content
	// of the image label, but which is somewhat redundant if he didn't
	
	CString strNum;
	m_ctlEditImageNumber.GetWindowText( strNum );
	
	int iNum = _ttoi( strNum );
	
	UpdateCharacterImageData( iNum );
	
	// now get image data, based on whether the training set or the testing set has been 
	// selected (this is the part that's redundant to the above call to UpdateCharacterImageData)
	
	unsigned char grayArray[ g_cImageSize * g_cImageSize ] = {0};
	int label = 0;
	
	BOOL bTraining = ( 1 == m_ctlRadioWhichSet.GetCheck() );  // 1 == checked
	
	if ( bTraining != FALSE )
	{
		// the training set has been selected
		// no need to adjust numeric value of the pattern since UpdateCharacterImageData() has 
		// already done this for us
		
		// get gray-scale values for character image, and numeric value of its "answer"
		
		m_pDoc->GetTrainingPatternArrayValues( iNum, grayArray, &label, TRUE );
		
	}
	else
	{
		// testing set has been selected
		
		// get gray-scale values for character image, and numeric value of its "answer"
		
		m_pDoc->GetTestingPatternArrayValues( iNum, grayArray, &label, TRUE );
	}
	
	// pad the values to 29x29, convert to double precision, and run through the neural net
	// This operation is timed and the result is displayed
	
	DWORD tick = ::GetTickCount();
	
	int ii, jj;
	double inputVector[841];
	
	for ( ii=0; ii<841; ++ii )
	{
		inputVector[ ii ] = 1.0;  // one is white, -one is black
	}
	
	// top row of inputVector is left as zero, left-most column is left as zero 
	
	for ( ii=0; ii<g_cImageSize; ++ii )
	{
		for ( jj=0; jj<g_cImageSize; ++jj )
		{
			inputVector[ 1 + jj + 29*(ii+1) ] = (double)((int)(unsigned char)grayArray[ jj + g_cImageSize*ii ])/128.0 - 1.0;  // one is white, -one is black
		}
	}
	
	// get state of "Distort input pattern check box
	
	BOOL bDistort = ( 1 == m_ctlCheckDistortInputPattern.GetCheck() );  // 1 == checked
	
	double outputVector[10] = {0.0};
	double targetOutputVector[10] = {0.0};
	
	
	// initialize target output vector (i.e., desired values)
	
	for ( ii=0; ii<10; ++ii )
	{
		targetOutputVector[ ii ] = -1.0;
	}
	
	if ( label > 9 ) label = 9;
	if ( label < 0 ) label = 0;
	
	targetOutputVector[ label ] = 1.0;
	
	
	// calculate neural net
	
	m_pDoc->CalculateNeuralNet( inputVector, 841, outputVector, 10, &m_NeuronOutputs, bDistort );
	
	DWORD diff = ::GetTickCount() - tick;
	
	
	// write numerical result (and time taken to get them) to results window
	
	CString strLine, strResult;
	double dTemp, sampleMse = 0.0;
	
	strResult.Format( _T("Results:\n") );
	
	for ( ii=0; ii<10; ii++ )
	{
		strLine.Format( _T(" %2i = %+6.3f \n"), ii, outputVector[ii] );
		strResult += strLine;
		
		dTemp = targetOutputVector[ ii ] - outputVector[ ii ];
		sampleMse += dTemp * dTemp;
		
	}
	
	sampleMse = 0.5 * sampleMse;
	strLine.Format( _T("\nPattern Error\n Ep = %g\n\n%i mSecs"), sampleMse, diff );
	strResult += strLine;
	CWnd* pWnd = GetDlgItem(IDC_STATIC_TIME);
	if ( pWnd != NULL )
		pWnd->SetWindowText( strResult );
	
	
	// update the view of the outputs of the neurons
	
	m_wndNeuronViewer.BuildBitmapFromNeuronOutputs( m_NeuronOutputs );
	m_wndNeuronViewer.Invalidate();
	
	
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
United States United States
Mike O'Neill is a patent attorney in Southern California, where he specializes in computer and software-related patents. He programs as a hobby, and in a vain attempt to keep up with and understand the technology of his clients.

Comments and Discussions