【機械学習】VC++で手書き数字認識のWindowsアプリを作ってみた 1/2
ここ最近のAI・機械学習ブームは飛ぶ鳥を落とす勢いですね。
この分野におけるHello Worldは手書き数字認識らしいですので、作ってみました↓
概要
- 3層の順伝播型ニューラルネットワーク(FFNN)
・入力層…784次元 (28画素x28画素)
・隠れ層…100次元
・出力層…10次元 (0から9) - 訓練・テストにMNIST画像を使用
- 認識率向上のため、入力した手書き文字を自動でセンタリング
- MFCでWindowsアプリとして作成
【C/C++】MNISTの手書き数字の画像/ラベルデータをサクっと読み込む
機械学習で定番の数字認識では、サンプルデータとしてMNISTの手書き数字画像を使用する事が多いですね。PythonではMNIST画像を読み書きするライブラリがあるため手間いらずですが、C/C++では自力で読み書きする必要があるため、サンプルを紹介します。
なお、MNIST画像データの詳細な仕様は下記ブログが参考になります。
MNIST データの仕様を理解しよう
【VS2017】MFCアプリのビルドエラー "RC2135 file not found:" の対処
Visual Studio 2017でダイアログベースのMFCアプリを新規作成し、ダイアログの編集を行うと、以下の様なエラーが発生する場合があります。
>エラー RC2135 file not found: 17
>エラー RC2146 missing comma in LANGUAGE statement
これはリソースファイル内の一部項目が、ダイアログ編集後に日本語に置き換わる事が原因と考えられます。
○ダイアログ編集前の初期状態(ビルドエラーなし)
3 TEXTINCLUDE BEGIN … "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_JPN)\r\n" "LANGUAGE 17、1\r\n" // TEXTINCLUDE 3 リソースから生成されました。 … #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_JPN) LANGUAGE 17, 1
○ダイアログ編集後(ビルドエラー)
3 TEXTINCLUDE BEGIN … "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_JPN)\r\n" "LANGUAGE 17、1\r\n" // TEXTINCLUDE 3 リソースから生成されました。 … #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_JPN) LANGUAGE 17、1
↑「LANGUAGE 17, 1」が「LANGUAGE 17、1」となり、カンマが読点に置き換わっているのが解ります。
この現象はマイクロソフトも認識しており、2018/4/7時点でfix対応中とのこと。
○対処法
リソースファイル中の「言語 17、1」や「LANGUAGE 17、1」となっている個所を
「LANGUAGE 17, 1」に変更してリビルドする事で解消します。
恒久対策版のVS2017を早くリリースして欲しい所ですね。
【C/C++】ファイルパスに指定の拡張子(複数)が含まれるか安全にチェックする
ファイルパスの拡張子を単一の拡張子と比較する処理はネット上で散見されますが、ここではカンマ区切りの複数拡張子を安全に比較する処理を紹介します。
#include <stdio.h>
#include <string.h>
bool CheckExtension(const char *pszPath, const char *pszCmpExts){
// ファイルパスの拡張子を抽出
const char *pszExt = strrchr(pszPath, '.');
if(pszExt && *(pszExt + 1) != '\0'){
pszExt += 1;
}else{
return false; // ファイルパスに拡張子が存在しない場合はfalse
}
// 拡張子をチェック(大文字小文字区別あり)
char szCmpExts[1024];
strncpy(szCmpExts, pszCmpExts, sizeof(szCmpExts)-1);
for(const char *pszCmpExt = strtok(szCmpExts, ","); pszCmpExt; pszCmpExt = strtok(NULL, ",")){
if(strcmp(pszExt, pszCmpExt) == 0) return true;
}
return false;
}
int main(){
CheckExtension("ほげ.jpg", "jpg"); // true
CheckExtension("ほげ.jpg", "png,jpg"); // true
CheckExtension("ほげ.jpg", "JPG"); // false
return 0;
}
ファイルパスから拡張子を抽出する処理では、strrchrでパスの後方から"."の位置を取得します。
"."は複数箇所に含まれる可能性があるため、strchrやstrstrでパスの前方から検索してはいけません。
また、拡張子無し("."なし)のファイルパスが渡される可能性もあるため、strrchrの戻り値は必ずNULLチェックする必要があります。
拡張子のチェック処理では、カンマ区切りで渡された拡張子をstrtokで分解し、strcmpで拡張子を比較しています。拡張子の大文字小文字を区別しない場合はstricmpやstrcasecmpで比較を行ってください。
なお、比較可能な拡張子の文字列(szCmpExts)は1024文字(終端含む)で固定としています。通常の使用ではオーバーしませんが、必要に応じて動的にszCmpExtsを確保してください。
ビットフラグの基本的な操作
ビットフラグの基本的な操作についてまとめました。
各種機能をビットフラグで管理する処理では、次の3パターンを抑えればOKです。
特定のビット有無を確認する
確認したいビットとANDを取ります
if (bitflag & 0x10){
// bitflagに0x10のビットが立っている
}
特定のビットを立てる
立てたいビットとORを取ります
// bitflagに0x10を立てる
bitflag |= 0x10;
特定のビットを落とす
落としたいビットを反転させてANDを取ります
// bitflagの0x10を落とす
bitflag &= ~0x10;
※落としたいビットの有無を確認してxorを取る方法もありますが、1行で済ませる方がスッキリしますね↓
// bitflagの0x10を落とす(xor版)
if (bitflag & 0x10){
bitflag ^= 0x10;
}