/// <summary> /// 活性化関数(ReLU)を適用する /// </summary> /// <param name="layer">適用対象のLayerData2Dオブジェクト</param> public void ReLU(LayerData2D layer) { for (int i = 0; i < layer.Cells.Length; i++) { if (layer.Cells[i] < 0) { layer.Cells[i] = 0f; } } }
/// <summary> /// プーリング計算を行う /// </summary> /// <param name="outputLayer">出力層のデータを格納するLayerData2Dオブジェクト</param> /// <param name="inputLayer">入力層のデータが格納されたLayerData2Dオブジェクト</param> /// <param name="poolSize">プーリングサイズ</param> public static void Calc(LayerData2D outputLayer, LayerData2D inputLayer, int poolSize) { if (outputLayer.PlaneNum != inputLayer.PlaneNum) { throw new Exception("Plane数不整合"); } if (outputLayer.PlaneWidth != inputLayer.PlaneWidth / poolSize) { throw new Exception("Planeサイズ不整合"); } if (outputLayer.PlaneHeight != inputLayer.PlaneHeight / poolSize) { throw new Exception("Planeサイズ不整合"); } for (int outputPlane = 0; outputPlane < outputLayer.PlaneNum; outputPlane++) { int outputPlaneStartIdx = outputLayer.PlaneHeight * outputLayer.PlaneWidth * outputPlane; int inputPlane = outputPlane; int inputPlaneStartIdx = inputLayer.PlaneHeight * inputLayer.PlaneWidth * inputPlane; for (int outputY = 0; outputY < outputLayer.PlaneHeight; outputY++) { int outputRowStartIdx = outputPlaneStartIdx + outputLayer.PlaneWidth * outputY; int inputY0 = outputY * poolSize; for (int outputX = 0; outputX < outputLayer.PlaneWidth; outputX++) { int outputCellIdx = outputRowStartIdx + outputX; int inputX0 = outputX * poolSize; float maxVal = float.MinValue; for (int inputY = inputY0; inputY < inputY0 + poolSize; inputY++) { int inputRowStartIdx = inputPlaneStartIdx + inputLayer.PlaneWidth * inputY; for (int inputX = inputX0; inputX < inputX0 + poolSize; inputX++) { int inputCellIdx = inputRowStartIdx + inputX; float inputVal = inputLayer.Cells[inputCellIdx]; if (inputVal > maxVal) { maxVal = inputVal; } } } outputLayer.Cells[outputCellIdx] = maxVal; } } } }
static void Main(string[] args) { Console.WriteLine("<<<Mamecog sample>>>"); // Conv2DとDenseのインスタンスを生成する Conv2D conv1 = new Conv2D(32, 1, 3, 3); Conv2D conv2 = new Conv2D(64, 32, 3, 3); Dense dense = new Dense(10, 1600); // Conv2DとDenseのカーネルとバイアスをファイルから読み込む conv1.LoadKernelAndBias("conv2d_k.bin", "conv2d_b.bin"); conv2.LoadKernelAndBias("conv2d_1_k.bin", "conv2d_1_b.bin"); dense.LoadKernelAndBias("dense_k.bin", "dense_b.bin"); // 各層の入出力の格納先を用意する LayerData2D input0 = new LayerData2D(1, 28, 28); LayerData2D conv1output = new LayerData2D(32, 26, 26); LayerData2D pool1output = new LayerData2D(32, 13, 13); LayerData2D conv2output = new LayerData2D(64, 11, 11); LayerData2D pool2output = new LayerData2D(64, 5, 5); float[] pool2flatten = new float[64 * 5 * 5]; float[] denseOutput = new float[10]; // テスト用の入力データを用意する Console.WriteLine("<<<Input>>>"); input0.Cells = testInput; input0.PrintCellValues(); // 各層の出力を順に計算する conv1.Conv(conv1output, input0, false); conv1.ReLU(conv1output); MaxPool2D.Calc(pool1output, conv1output, 2); conv2.Conv(conv2output, pool1output, false); conv2.ReLU(conv2output); MaxPool2D.Calc(pool2output, conv2output, 2); pool2output.Flatten(pool2flatten); dense.Calc(denseOutput, pool2flatten); dense.Softmax(denseOutput); // Dense層の出力を表示する Console.WriteLine("<<<Output>>>"); for (int i = 0; i < denseOutput.Length; i++) { Console.WriteLine("Dense[" + i.ToString() + "] = " + denseOutput[i].ToString("F4")); } }
/// <summary> /// 畳み込み計算を行う /// </summary> /// <param name="outputLayer">出力層のデータを格納するLayerData2Dオブジェクト</param> /// <param name="inputLayer">入力層のデータが格納されたLayerData2Dオブジェクト</param> /// <param name="withPadding">パディングありのときtrue、なしのときfalse</param> public void Conv(LayerData2D outputLayer, LayerData2D inputLayer, bool withPadding) { int outputPlaneWH = outputLayer.PlaneWidth * outputLayer.PlaneHeight; int inputPlaneWH = inputLayer.PlaneWidth * inputLayer.PlaneHeight; int kernelWH = KernelWidth * KernelHeight; int kernelHalfWidth = KernelWidth / 2; int kernelHalfHeight = KernelHeight / 2; if (outputLayer.PlaneNum != OutputPlaneNum) { throw new Exception("OutputPlaneNum不整合"); } if (inputLayer.PlaneNum != InputPlaneNum) { throw new Exception("InputPlaneNum不整合"); } if (withPadding) { if (outputLayer.PlaneHeight != inputLayer.PlaneHeight) { throw new Exception("Planeサイズ不整合"); } if (outputLayer.PlaneWidth != inputLayer.PlaneWidth) { throw new Exception("Planeサイズ不整合"); } } else { if (outputLayer.PlaneHeight != inputLayer.PlaneHeight - kernelHalfHeight * 2) { throw new Exception("Planeサイズ不整合"); } if (outputLayer.PlaneWidth != inputLayer.PlaneWidth - kernelHalfWidth * 2) { throw new Exception("Planeサイズ不整合"); } } //カーネル(重み行列)のそれぞれの重みを入出力面の縦横のどの範囲に適用するか int[] outputStartX = new int[KernelWidth]; int[] outputStartY = new int[KernelHeight]; int[] inputStartX = new int[KernelWidth]; int[] inputStartY = new int[KernelHeight]; int[] loopLengthX = new int[KernelWidth]; int[] loopLengthY = new int[KernelHeight]; if (withPadding) { for (int kernelY = 0; kernelY < KernelHeight; kernelY++) { outputStartY[kernelY] = Math.Max(kernelHalfHeight - kernelY, 0); inputStartY[kernelY] = Math.Max(kernelY - kernelHalfHeight, 0); loopLengthY[kernelY] = outputLayer.PlaneHeight - Math.Abs(kernelHalfHeight - kernelY); } for (int kernelX = 0; kernelX < KernelWidth; kernelX++) { outputStartX[kernelX] = Math.Max(kernelHalfWidth - kernelX, 0); inputStartX[kernelX] = Math.Max(kernelX - kernelHalfWidth, 0); loopLengthX[kernelX] = outputLayer.PlaneWidth - Math.Abs(kernelHalfWidth - kernelX); } } else { for (int kernelY = 0; kernelY < KernelHeight; kernelY++) { outputStartY[kernelY] = 0; inputStartY[kernelY] = kernelY; loopLengthY[kernelY] = outputLayer.PlaneHeight; } for (int kernelX = 0; kernelX < KernelWidth; kernelX++) { outputStartX[kernelX] = 0; inputStartX[kernelX] = kernelX; loopLengthX[kernelX] = outputLayer.PlaneWidth; } } Array.Clear(outputLayer.Cells, 0, outputLayer.Cells.Length); //for (int outputPlane = 0; outputPlane < outputLayer.PlaneNum; outputPlane++) Parallel.For(0, outputLayer.PlaneNum, outputPlane => { int outputPlaneStartIdx = outputPlaneWH * outputPlane; for (int inputPlane = 0; inputPlane < inputLayer.PlaneNum; inputPlane++) { int inputPlaneStartIdx = inputPlaneWH * inputPlane; int kernelStartIdx = kernelWH * (InputPlaneNum * outputPlane + inputPlane); for (int kernelY = 0; kernelY < KernelHeight; kernelY++) { int kernelRowStartIdx = kernelStartIdx + KernelWidth * kernelY; int outputY0 = outputStartY[kernelY]; int inputY0 = inputStartY[kernelY]; int loopLenY = loopLengthY[kernelY]; for (int kernelX = 0; kernelX < KernelWidth; kernelX++) { //float w = GetKernelVal(outputPlane, inputPlane, kernelY, kernelX); float w = Kernel[kernelRowStartIdx + kernelX]; int outputX0 = outputStartX[kernelX]; int inputX0 = inputStartX[kernelX]; int loopLenX = loopLengthX[kernelX]; int outputY = outputY0; int inputY = inputY0; for (int y = 0; y < loopLenY; y++) { int outputRowStartIdx = outputPlaneStartIdx + outputLayer.PlaneWidth * outputY; int inputRowStartIdx = inputPlaneStartIdx + inputLayer.PlaneWidth * inputY; Span <float> outputSpan = new Span <float>(outputLayer.Cells, outputRowStartIdx + outputX0, loopLenX); Span <float> inputSpan = new Span <float>(inputLayer.Cells, inputRowStartIdx + inputX0, loopLenX); for (int x = 0; x < loopLenX; x++) { outputSpan[x] += w * inputSpan[x]; } outputY++; inputY++; } } } } }); }
static void Main(string[] args) { // テスト用の入力データを用意する Console.WriteLine("テスト画像読み込み"); LayerData2D input1 = new LayerData2D(3, 224, 224); string inputFilename = "test_input.png"; // 224x224ピクセルのRGB画像 using (Bitmap inputImage = new Bitmap(Image.FromFile(inputFilename))) { Debug.Assert(inputImage.Height == 224); Debug.Assert(inputImage.Width == 224); for (int y = 0; y < inputImage.Height; y++) { for (int x = 0; x < inputImage.Width; x++) { Color pixelData = inputImage.GetPixel(x, y); float r = (float)pixelData.R - 123.68f; float g = (float)pixelData.G - 116.779f; float b = (float)pixelData.B - 103.939f; input1.SetVal(0, y, x, b); // BGR input1.SetVal(1, y, x, g); input1.SetVal(2, y, x, r); } } } // Conv2DとDenseのインスタンスを生成する Conv2D block1Conv1 = new Conv2D(64, 3, 3, 3); Conv2D block1Conv2 = new Conv2D(64, 64, 3, 3); Conv2D block2Conv1 = new Conv2D(128, 64, 3, 3); Conv2D block2Conv2 = new Conv2D(128, 128, 3, 3); Conv2D block3Conv1 = new Conv2D(256, 128, 3, 3); Conv2D block3Conv2 = new Conv2D(256, 256, 3, 3); Conv2D block3Conv3 = new Conv2D(256, 256, 3, 3); Conv2D block4Conv1 = new Conv2D(512, 256, 3, 3); Conv2D block4Conv2 = new Conv2D(512, 512, 3, 3); Conv2D block4Conv3 = new Conv2D(512, 512, 3, 3); Conv2D block5Conv1 = new Conv2D(512, 512, 3, 3); Conv2D block5Conv2 = new Conv2D(512, 512, 3, 3); Conv2D block5Conv3 = new Conv2D(512, 512, 3, 3); Dense fc1 = new Dense(4096, 25088); Dense fc2 = new Dense(4096, 4096); Dense predictions = new Dense(1000, 4096); // Conv2DとDenseのカーネルとバイアスをファイルから読み込む Console.WriteLine("学習済みモデル読み込み"); block1Conv1.LoadKernelAndBias("block1_conv1_k.bin", "block1_conv1_b.bin"); block1Conv2.LoadKernelAndBias("block1_conv2_k.bin", "block1_conv2_b.bin"); block2Conv1.LoadKernelAndBias("block2_conv1_k.bin", "block2_conv1_b.bin"); block2Conv2.LoadKernelAndBias("block2_conv2_k.bin", "block2_conv2_b.bin"); block3Conv1.LoadKernelAndBias("block3_conv1_k.bin", "block3_conv1_b.bin"); block3Conv2.LoadKernelAndBias("block3_conv2_k.bin", "block3_conv2_b.bin"); block3Conv3.LoadKernelAndBias("block3_conv3_k.bin", "block3_conv3_b.bin"); block4Conv1.LoadKernelAndBias("block4_conv1_k.bin", "block4_conv1_b.bin"); block4Conv2.LoadKernelAndBias("block4_conv2_k.bin", "block4_conv2_b.bin"); block4Conv3.LoadKernelAndBias("block4_conv3_k.bin", "block4_conv3_b.bin"); block5Conv1.LoadKernelAndBias("block5_conv1_k.bin", "block5_conv1_b.bin"); block5Conv2.LoadKernelAndBias("block5_conv2_k.bin", "block5_conv2_b.bin"); block5Conv3.LoadKernelAndBias("block5_conv3_k.bin", "block5_conv3_b.bin"); fc1.LoadKernelAndBias("fc1_k.bin", "fc1_b.bin"); fc2.LoadKernelAndBias("fc2_k.bin", "fc2_b.bin"); predictions.LoadKernelAndBias("predictions_k.bin", "predictions_b.bin"); // 各層の入出力の格納先を用意する LayerData2D block1Conv1Output = new LayerData2D(64, 224, 224); LayerData2D block1Conv2Output = new LayerData2D(64, 224, 224); LayerData2D block1PoolOutput = new LayerData2D(64, 112, 112); LayerData2D block2Conv1Output = new LayerData2D(128, 112, 112); LayerData2D block2Conv2Output = new LayerData2D(128, 112, 112); LayerData2D block2PoolOutput = new LayerData2D(128, 56, 56); LayerData2D block3Conv1Output = new LayerData2D(256, 56, 56); LayerData2D block3Conv2Output = new LayerData2D(256, 56, 56); LayerData2D block3Conv3Output = new LayerData2D(256, 56, 56); LayerData2D block3PoolOutput = new LayerData2D(256, 28, 28); LayerData2D block4Conv1Output = new LayerData2D(512, 28, 28); LayerData2D block4Conv2Output = new LayerData2D(512, 28, 28); LayerData2D block4Conv3Output = new LayerData2D(512, 28, 28); LayerData2D block4PoolOutput = new LayerData2D(512, 14, 14); LayerData2D block5Conv1Output = new LayerData2D(512, 14, 14); LayerData2D block5Conv2Output = new LayerData2D(512, 14, 14); LayerData2D block5Conv3Output = new LayerData2D(512, 14, 14); LayerData2D block5PoolOutput = new LayerData2D(512, 7, 7); float[] flattenOutput = new float[25088]; float[] fc1Output = new float[4096]; float[] fc2Output = new float[4096]; float[] predictionsOutput = new float[1000]; // 各層の出力を順に計算する Console.WriteLine("CNN実行開始"); var sw = new System.Diagnostics.Stopwatch(); sw.Start(); Console.WriteLine("Block 1"); block1Conv1.Conv(block1Conv1Output, input1, true); block1Conv1.ReLU(block1Conv1Output); block1Conv2.Conv(block1Conv2Output, block1Conv1Output, true); block1Conv2.ReLU(block1Conv2Output); MaxPool2D.Calc(block1PoolOutput, block1Conv2Output, 2); Console.WriteLine("Block 2"); block2Conv1.Conv(block2Conv1Output, block1PoolOutput, true); block2Conv1.ReLU(block2Conv1Output); block2Conv2.Conv(block2Conv2Output, block2Conv1Output, true); block2Conv2.ReLU(block2Conv2Output); MaxPool2D.Calc(block2PoolOutput, block2Conv2Output, 2); Console.WriteLine("Block 3"); block3Conv1.Conv(block3Conv1Output, block2PoolOutput, true); block3Conv1.ReLU(block3Conv1Output); block3Conv2.Conv(block3Conv2Output, block3Conv1Output, true); block3Conv2.ReLU(block3Conv2Output); block3Conv3.Conv(block3Conv3Output, block3Conv2Output, true); block3Conv3.ReLU(block3Conv3Output); MaxPool2D.Calc(block3PoolOutput, block3Conv3Output, 2); Console.WriteLine("Block 4"); block4Conv1.Conv(block4Conv1Output, block3PoolOutput, true); block4Conv1.ReLU(block4Conv1Output); block4Conv2.Conv(block4Conv2Output, block4Conv1Output, true); block4Conv2.ReLU(block4Conv2Output); block4Conv3.Conv(block4Conv3Output, block4Conv2Output, true); block4Conv3.ReLU(block4Conv3Output); MaxPool2D.Calc(block4PoolOutput, block4Conv3Output, 2); Console.WriteLine("Block 5"); block5Conv1.Conv(block5Conv1Output, block4PoolOutput, true); block5Conv1.ReLU(block5Conv1Output); block5Conv2.Conv(block5Conv2Output, block5Conv1Output, true); block5Conv2.ReLU(block5Conv2Output); block5Conv3.Conv(block5Conv3Output, block5Conv2Output, true); block5Conv3.ReLU(block5Conv3Output); MaxPool2D.Calc(block5PoolOutput, block5Conv3Output, 2); Console.WriteLine("FC"); block5PoolOutput.Flatten(flattenOutput); fc1.Calc(fc1Output, flattenOutput); fc1.ReLU(fc1Output); fc2.Calc(fc2Output, fc1Output); fc2.ReLU(fc2Output); predictions.Calc(predictionsOutput, fc2Output); predictions.Softmax(predictionsOutput); sw.Stop(); TimeSpan ts = sw.Elapsed; Console.WriteLine("CNN実行完了:実行時間 = {0}", ts); // VGG16モデルの学習済み1000カテゴリのうち先頭10カテゴリ分の確率を出力 for (int i = 0; i < 10; i++) { Console.WriteLine("カテゴリ[{0}] = {1}", i, predictionsOutput[i]); } }