From a009908e84f13f841fb1e4f2a0b8e1256832dbe1 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 11 Sep 2019 16:33:31 -0400 Subject: [PATCH 01/10] cpu.cu done, I am so stupid... --- .../character_recognition/CMakeLists.txt | 2 +- .../stream_compaction/CMakeLists.txt | 2 +- .../stream_compaction/cpu.cu | 57 ++++++++++++++++++- 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/Project2-Character-Recognition/character_recognition/CMakeLists.txt b/Project2-Character-Recognition/character_recognition/CMakeLists.txt index 7446175..c5e28b0 100644 --- a/Project2-Character-Recognition/character_recognition/CMakeLists.txt +++ b/Project2-Character-Recognition/character_recognition/CMakeLists.txt @@ -7,5 +7,5 @@ set(SOURCE_FILES cuda_add_library(character_recognition ${SOURCE_FILES} - OPTIONS -arch=sm_20 + OPTIONS -arch=sm_75 ) diff --git a/Project2-Stream-Compaction/stream_compaction/CMakeLists.txt b/Project2-Stream-Compaction/stream_compaction/CMakeLists.txt index cdbef77..185a604 100644 --- a/Project2-Stream-Compaction/stream_compaction/CMakeLists.txt +++ b/Project2-Stream-Compaction/stream_compaction/CMakeLists.txt @@ -13,5 +13,5 @@ set(SOURCE_FILES cuda_add_library(stream_compaction ${SOURCE_FILES} - OPTIONS -arch=sm_20 + OPTIONS -arch=sm_75 ) diff --git a/Project2-Stream-Compaction/stream_compaction/cpu.cu b/Project2-Stream-Compaction/stream_compaction/cpu.cu index a2d3e6c..a34ae65 100644 --- a/Project2-Stream-Compaction/stream_compaction/cpu.cu +++ b/Project2-Stream-Compaction/stream_compaction/cpu.cu @@ -2,6 +2,7 @@ #include "cpu.h" #include "common.h" +#include namespace StreamCompaction { namespace CPU { @@ -20,6 +21,11 @@ namespace StreamCompaction { void scan(int n, int *odata, const int *idata) { timer().startCpuTimer(); // TODO + for (int idx = 0; idx < n; ++idx) + { + if (idx == 0) odata[idx] = 0; + else odata[idx] = odata[idx - 1] + idata[idx - 1]; + } timer().endCpuTimer(); } @@ -30,9 +36,19 @@ namespace StreamCompaction { */ int compactWithoutScan(int n, int *odata, const int *idata) { timer().startCpuTimer(); - // TODO + int counter = 0; + for (int idx = 0; idx < n; ++idx) + { + int curr_val = idata[idx]; + if (curr_val != 0) + { + odata[counter] = curr_val; + counter++; + } + + } timer().endCpuTimer(); - return -1; + return counter; } /** @@ -42,9 +58,44 @@ namespace StreamCompaction { */ int compactWithScan(int n, int *odata, const int *idata) { timer().startCpuTimer(); + //do we need to allocate a new array? + //how to allocate in cpu + int *bool_array = new int[n](); + int *scattered_array = new int[n](); + for (int idx = 0; idx < n; ++idx) + { + if (idata[idx] == 0) bool_array[idx] = 0; + else bool_array[idx] = 1; + } + //scan -- if directly call scan will cause cpu timer error + for (int idx = 0; idx < n; ++idx) + { + if (idx == 0) scattered_array[idx] = 0; + else scattered_array[idx] = scattered_array[idx - 1] + bool_array[idx - 1]; + } + + for (int idx = 0; idx < n; ++idx) + { + int curr_odata_idx = scattered_array[idx]; + if (idx == n - 1) + { + if (curr_odata_idx != scattered_array[idx - 1]) odata[curr_odata_idx] = idata[idx]; + } + else + { + if (scattered_array[idx] != scattered_array[idx + 1]) + { + odata[curr_odata_idx] = idata[idx]; + } + } + + } // TODO timer().endCpuTimer(); - return -1; + int returned_val = scattered_array[n - 1]; //index start from 0 + free(bool_array); + free(scattered_array); + return returned_val; } } } From 4889a9786e59779ab2a1fcfd1c3cf0a9f1b28cff Mon Sep 17 00:00:00 2001 From: Tianming Xu Date: Wed, 11 Sep 2019 19:14:50 -0400 Subject: [PATCH 02/10] update the cpu.cu to run faster --- .../stream_compaction/cpu.cu | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/Project2-Stream-Compaction/stream_compaction/cpu.cu b/Project2-Stream-Compaction/stream_compaction/cpu.cu index a34ae65..7f8b820 100644 --- a/Project2-Stream-Compaction/stream_compaction/cpu.cu +++ b/Project2-Stream-Compaction/stream_compaction/cpu.cu @@ -77,22 +77,14 @@ namespace StreamCompaction { for (int idx = 0; idx < n; ++idx) { int curr_odata_idx = scattered_array[idx]; - if (idx == n - 1) - { - if (curr_odata_idx != scattered_array[idx - 1]) odata[curr_odata_idx] = idata[idx]; - } - else - { - if (scattered_array[idx] != scattered_array[idx + 1]) - { - odata[curr_odata_idx] = idata[idx]; - } - } - + //better way to determine non-zero + if(bool_array[idx] == 1) + { + odata[curr_odata_idx] = idata[idx]; + } } - // TODO - timer().endCpuTimer(); - int returned_val = scattered_array[n - 1]; //index start from 0 + timer().endCpuTimer(); + int returned_val = scattered_array[n - 1]; //the scan gives us the length of the output array free(bool_array); free(scattered_array); return returned_val; From 4b398bc56096d09ec23fce6fb8ec47270a05e319 Mon Sep 17 00:00:00 2001 From: Mark Date: Fri, 13 Sep 2019 14:55:24 -0400 Subject: [PATCH 03/10] finish naive and efficient --- .../stream_compaction/efficient.cu | 67 ++++++++++++++++++- .../stream_compaction/naive.cu | 67 ++++++++++++++++++- 2 files changed, 132 insertions(+), 2 deletions(-) diff --git a/Project2-Stream-Compaction/stream_compaction/efficient.cu b/Project2-Stream-Compaction/stream_compaction/efficient.cu index 2db346e..62dab81 100644 --- a/Project2-Stream-Compaction/stream_compaction/efficient.cu +++ b/Project2-Stream-Compaction/stream_compaction/efficient.cu @@ -6,19 +6,84 @@ namespace StreamCompaction { namespace Efficient { using StreamCompaction::Common::PerformanceTimer; + //intermediate arrays + int* temp_out; + PerformanceTimer& timer() { static PerformanceTimer timer; return timer; } + __global__ void kernComputePartialUpSweep(int n, const int pow_d_plus_one, int* idata) + { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) + { + return; + } + //no need to store the idata to temp_in, it is already there + int pow_d = pow_d_plus_one / 2; + //by 2^(d+1) means that able to be divided by 2^(d+1) + if (index % pow_d_plus_one == 0) idata[index + pow_d_plus_one - 1] += idata[index + pow_d - 1]; + } + __global__ void kernComputePartialDownSweep(int n, const int pow_d_plus_one, int* odata) + { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) + { + return; + } + //no need to store the idata to temp_in, it is already there + int pow_d = pow_d_plus_one / 2; + if (index % pow_d_plus_one == 0) + { + int temp = odata[index + pow_d - 1]; + odata[index + pow_d - 1] = odata[index + pow_d_plus_one - 1]; + odata[index + pow_d_plus_one - 1] += temp; + } + } /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { - timer().startGpuTimer(); + //init two new memory // TODO + //padding to the nearest 2^d + int logn = ilog2ceil(n); + int powd = std::pow(2, logn); + //init temp_out + cudaMalloc((void**)&temp_out, powd * sizeof(int)); + checkCUDAError("cudaMalloc device_out failed!"); + //assign idata value to temp_out + cudaMemcpy(temp_out, idata, n * sizeof(int), cudaMemcpyHostToDevice); + + int gridSize = (n + BLOCK_SIZE - 1) / BLOCK_SIZE; + dim3 blocksPerGrid(gridSize); + dim3 threadsPerBlock(BLOCK_SIZE); + ////first compute the new velocity + int ceil = logn - 1; + timer().startGpuTimer(); + for (int offset = 0; offset <= ceil; ++offset) { + const int pow_d_plus_one = std::pow(2, offset + 1); + //set last element to zero in temp_out in kernel + kernComputePartialUpSweep << < blocksPerGrid, threadsPerBlock >> > (n, pow_d_plus_one, temp_out); + } + + //assign 0 to root element + int last_value = 0; + cudaMemset(temp_out + powd - 1, last_value, sizeof(int)); + checkCUDAError("cudaMemSet temp_out last value to 0 failed!"); + //debug note --- If you have wrong operation in kernel, this will mess up your device memory, and next time you want to initalize them, it will fail -- so it is not problem in initialization, it is the operation having problem + for (int offset = ceil; offset >= 0; --offset) { + const int pow_d_plus_one = std::pow(2, offset + 1); + kernComputePartialDownSweep << < blocksPerGrid, threadsPerBlock >> > (n, pow_d_plus_one, temp_out); + } timer().endGpuTimer(); + + cudaMemcpy(odata, temp_out, n * sizeof(int), cudaMemcpyDeviceToHost); //Using the wrong tag to copy will cause the next time initalization fail + + cudaFree(temp_out); } /** diff --git a/Project2-Stream-Compaction/stream_compaction/naive.cu b/Project2-Stream-Compaction/stream_compaction/naive.cu index 4308876..5d30d83 100644 --- a/Project2-Stream-Compaction/stream_compaction/naive.cu +++ b/Project2-Stream-Compaction/stream_compaction/naive.cu @@ -13,13 +13,78 @@ namespace StreamCompaction { } // TODO: __global__ + __global__ void kernComputePartialNaive(int n, int pow_d, int* temp_in, int* temp_out) + { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) + { + return; + } + //no need to store the idata to temp_in, it is already there + if (index >= pow_d) temp_out[index] = temp_in[index - pow_d] + temp_in[index]; + else temp_out[index] = temp_in[index]; + } + + __global__ void kernShiftTempOut(int n, int* temp_in, int* temp_out) + { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) + { + return; + } + + if (index == 0) + { + temp_out[index] = 0; + } + else + { + temp_out[index] = temp_in[index - 1]; + } + } /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { - timer().startGpuTimer(); + int* temp_in; + int* temp_out; // TODO + //init two new memory + cudaMalloc((void**)&temp_in, n * sizeof(int)); + checkCUDAError("cudaMalloc temp_in failed!"); + + cudaMalloc((void**)&temp_out, n * sizeof(int)); + checkCUDAError("cudaMalloc temp_out failed!"); + int gridSize = (n + BLOCK_SIZE - 1) / BLOCK_SIZE; + dim3 blocksPerGrid(gridSize); + dim3 threadsPerBlock(BLOCK_SIZE); + + //copy idata to temp_in + timer().startGpuTimer(); + cudaMemcpy(temp_in, idata, n * sizeof(int), cudaMemcpyHostToDevice); //idata is in global memory + checkCUDAError("cudaMemcpy idata to temp_in failed!"); + //temp pointer + + //first compute the new velocity + int ceil = ilog2ceil(n); + for (int offset = 1; offset <= ceil; ++offset) { + const int pow_d_minus_one = std::pow(2, offset - 1); + kernComputePartialNaive << < blocksPerGrid, threadsPerBlock >> > (n, pow_d_minus_one, temp_in, temp_out); + + int* temp = temp_in; + temp_in = temp_out; + temp_out = temp; + + } timer().endGpuTimer(); + + //shift temp_out by one to get exclusive scan from reduction + kernShiftTempOut << < blocksPerGrid, threadsPerBlock >> > (n, temp_in,temp_out); + //pass the result from temp array to result + cudaMemcpy(odata, temp_out, n * sizeof(int), cudaMemcpyDeviceToHost); + checkCUDAError("cudaMemcpy temp_out to odata failed!"); + cudaFree(temp_in); + cudaFree(temp_out); } } } From 168c950c3229ac1f0dde10e248facff3c655c681 Mon Sep 17 00:00:00 2001 From: Mark Date: Fri, 13 Sep 2019 21:02:24 -0400 Subject: [PATCH 04/10] finish stream compaction --- Project2-Stream-Compaction/README.md | 52 ++++++++++++-- .../img/compaction_power_of_two.png | Bin 0 -> 50237 bytes .../img/initialization_error.png | Bin 0 -> 5482 bytes .../img/scan_non_power_of_two.png | Bin 0 -> 56999 bytes .../img/scan_power_of_2.png | Bin 0 -> 43290 bytes .../img/scan_power_of_2_without_thrust.png | Bin 0 -> 62792 bytes .../img/screen_shot_output.PNG | Bin 0 -> 38853 bytes .../stream_compaction/common.cu | 24 +++++++ .../stream_compaction/common.h | 1 + .../stream_compaction/cpu.cu | 13 +--- .../stream_compaction/efficient.cu | 68 ++++++++++++++++-- .../stream_compaction/thrust.cu | 18 ++++- 12 files changed, 155 insertions(+), 21 deletions(-) create mode 100644 Project2-Stream-Compaction/img/compaction_power_of_two.png create mode 100644 Project2-Stream-Compaction/img/initialization_error.png create mode 100644 Project2-Stream-Compaction/img/scan_non_power_of_two.png create mode 100644 Project2-Stream-Compaction/img/scan_power_of_2.png create mode 100644 Project2-Stream-Compaction/img/scan_power_of_2_without_thrust.png create mode 100644 Project2-Stream-Compaction/img/screen_shot_output.PNG diff --git a/Project2-Stream-Compaction/README.md b/Project2-Stream-Compaction/README.md index 0e38ddb..fa33892 100644 --- a/Project2-Stream-Compaction/README.md +++ b/Project2-Stream-Compaction/README.md @@ -3,12 +3,52 @@ CUDA Stream Compaction **University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 2** -* (TODO) YOUR NAME HERE - * (TODO) [LinkedIn](), [personal website](), [twitter](), etc. -* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab) +* #### Author information + + - Tianming Xu (Mark) + - www.linkedin.com/in/tianming-xu-8bb81816a (LinkedIn) + - Tested on: Windows 10, i7-8700 @ 3.20GHz 16GB, GTX 2080 8192MB (my personal desktop) + +### Output Screenshots(2^14 elements) + +![](img/screen_shot_output.png) + + + +### Features + +The first thing I did in this project is to implement the exclusive scan in CPU sequential approach, naive GPU approach and work-effiecient approach using upsweep and downsweep algorithm from GPU Gem. Besides, I explored the exclusive scan method in Thrust library. + +The other thing I did in my project is to implement the stream compaction method, which exclude certain elements in the array which satisfy some conditions(in my project, the condition is equal to 0). I use three approaches: sequential without scan, sequential with scan, and parallel with work-efficient scan. + +### Performance Analysis + +##### The comparison among CPU, Naive, work-efficient and Thrust in scan algorithm + +![](img/scan_power_of_2.png) + +The performance of thrust is very weird. It runs significantly slower than all the other three. However, we can see later in the non power of two part, its speed becomes much more reasonable. I am not quite sure why. I guess there might be some very heavy pre-process step in the thrust library. In order to see more detailed comparison among the rest, I have another chart without thrust + +![](img/scan_power_of_2_without_thrust.png) + +In this chart, surprisingly, the work-efficient one is never the best in both low number of element and high number of element. What I guess the problem might be, just like what instruction mentioned, the index of thread. As we have a 2^d stride of each iteration, when the value of d becomes larger and larger, the gap between the elements we need to compute the result is larger. Hence, as we need to access the data from global memory, we need to spend a lot of time reading the corresponding value we need. + +![](img/scan_non_power_of_two.png) + +In the non power of two cases, the situation is similar. Though the thrust library has a much more reasonable efficiency, it still is the slowest among the three. Another interesting find is that, the non power of two cases have a better performance than the power of two cases, which is unexpected. I am not quite sure why that happens. + +##### The comparison among CPU(no scan), CPU(with scan) work-efficient stream compaction + +![](img/compaction_power_of_two.png) + +The performance of compaction algorithm is more expected than the scan algorithm. The CPU scan one takes longer when the number of element is larger, as it will take multiple rounds of adding and swapping, which is unnecessary in sequential algorithm. But the work-efficient algorithm is not recognizably faster than the sequential one, even when the number of element is huge. I think it might be the same problem in the scan algorithm. + +### Debug Database + +- The corner case guard is very important. I used incorrect corner case guard (index > n) in my first project, and copy and paste to my project 2. It causes a very weird problem that the last element in the output data is always incorrect. Make sure to use (index >= n), as the nth element is out of range. +- The cuda initialization error might not be caused by the cuda initialization part. It might be other kernel function mess up something in cuda memory and cause the memory allocation to fail. + - ![](img/initialization_error.png) + - This error is actually because I use "cudaMemcpyDeviceToDevice" when I try to copy my temp_out intermediate array to odata array. -### (TODO: Your README) -Include analysis, etc. (Remember, this is public, so don't put -anything here that you don't want to share with the world.) diff --git a/Project2-Stream-Compaction/img/compaction_power_of_two.png b/Project2-Stream-Compaction/img/compaction_power_of_two.png new file mode 100644 index 0000000000000000000000000000000000000000..cfcb76527a2bfe5c5e31aba458d519f46c7d1e9b GIT binary patch literal 50237 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 literal 0 HcmV?d00001 diff --git a/Project2-Stream-Compaction/img/scan_non_power_of_two.png b/Project2-Stream-Compaction/img/scan_non_power_of_two.png new file mode 100644 index 0000000000000000000000000000000000000000..981644f6219089c7431cead99a686bfe788bbaf5 GIT binary patch literal 56999 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

sg9*4lJ`Rbr-J;BWAxK{NydJVN#f0qh1? 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 L-A>dw2HpM-k)+u% literal 0 HcmV?d00001 diff --git a/Project2-Stream-Compaction/img/scan_power_of_2_without_thrust.png b/Project2-Stream-Compaction/img/scan_power_of_2_without_thrust.png new file mode 100644 index 0000000000000000000000000000000000000000..fa7893eae59e80b2ffee9d7ca4f652ce0475b3a2 GIT binary patch literal 62792 zcmdqJi9eL<8$UkUw4699Ia$x4Qs&61q%f!?ZK%Z9r;?DEvNp^NB@v-wk}ya}mQnU? zrp0oMGGrN9n$R?iWy~;U=XVdz=lA{o2fx?PtMdx4GtYBB_kCUO>-~OT*VDuE)|Q($ zDsO~9Ae&F0I&l#KSxtgKR_$894*W*#hRr7MmrTG#%j1xecGYq4!y3


vTaw~3p? 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 DS;CkC literal 0 HcmV?d00001 diff --git a/Project2-Stream-Compaction/img/screen_shot_output.PNG b/Project2-Stream-Compaction/img/screen_shot_output.PNG new file mode 100644 index 0000000000000000000000000000000000000000..8d42d68652743231be3669b08019de3b2a010f5f GIT binary patch literal 38853 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 CO;}L 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 n) + { + return; + } + + if (idata[index] == 0) + { + bools[index] = 0; + } + else + { + bools[index] = 1; + } } /** @@ -33,6 +47,16 @@ namespace StreamCompaction { __global__ void kernScatter(int n, int *odata, const int *idata, const int *bools, const int *indices) { // TODO + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) + { + return; + } + + if (bools[index] != 0) + { + odata[indices[index]] = idata[index]; + } } } diff --git a/Project2-Stream-Compaction/stream_compaction/common.h b/Project2-Stream-Compaction/stream_compaction/common.h index 996997e..638836a 100644 --- a/Project2-Stream-Compaction/stream_compaction/common.h +++ b/Project2-Stream-Compaction/stream_compaction/common.h @@ -12,6 +12,7 @@ #define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) #define checkCUDAError(msg) checkCUDAErrorFn(msg, FILENAME, __LINE__) +#define BLOCK_SIZE 1024 /** * Check for CUDA errors; print and exit if there was a problem. diff --git a/Project2-Stream-Compaction/stream_compaction/cpu.cu b/Project2-Stream-Compaction/stream_compaction/cpu.cu index a34ae65..f9ea101 100644 --- a/Project2-Stream-Compaction/stream_compaction/cpu.cu +++ b/Project2-Stream-Compaction/stream_compaction/cpu.cu @@ -77,22 +77,15 @@ namespace StreamCompaction { for (int idx = 0; idx < n; ++idx) { int curr_odata_idx = scattered_array[idx]; - if (idx == n - 1) + if (bool_array[idx] != 0) { - if (curr_odata_idx != scattered_array[idx - 1]) odata[curr_odata_idx] = idata[idx]; - } - else - { - if (scattered_array[idx] != scattered_array[idx + 1]) - { - odata[curr_odata_idx] = idata[idx]; - } + odata[curr_odata_idx] = idata[idx]; } } // TODO timer().endCpuTimer(); - int returned_val = scattered_array[n - 1]; //index start from 0 + int returned_val = scattered_array[n - 1] + bool_array[n-1]; //index start from 0 free(bool_array); free(scattered_array); return returned_val; diff --git a/Project2-Stream-Compaction/stream_compaction/efficient.cu b/Project2-Stream-Compaction/stream_compaction/efficient.cu index 62dab81..e065a50 100644 --- a/Project2-Stream-Compaction/stream_compaction/efficient.cu +++ b/Project2-Stream-Compaction/stream_compaction/efficient.cu @@ -6,8 +6,13 @@ namespace StreamCompaction { namespace Efficient { using StreamCompaction::Common::PerformanceTimer; + using StreamCompaction::Common::kernMapToBoolean; + using StreamCompaction::Common::kernScatter; //intermediate arrays int* temp_out; + int* temp_bool; + int* temp_scattered; + int* temp_in; PerformanceTimer& timer() { @@ -23,7 +28,7 @@ namespace StreamCompaction { } //no need to store the idata to temp_in, it is already there int pow_d = pow_d_plus_one / 2; - //by 2^(d+1) means that able to be divided by 2^(d+1) + //by 2^(d+1) means a stride of two if (index % pow_d_plus_one == 0) idata[index + pow_d_plus_one - 1] += idata[index + pow_d - 1]; } @@ -54,7 +59,7 @@ namespace StreamCompaction { int powd = std::pow(2, logn); //init temp_out cudaMalloc((void**)&temp_out, powd * sizeof(int)); - checkCUDAError("cudaMalloc device_out failed!"); + checkCUDAError("cudaMalloc temp_out failed!"); //assign idata value to temp_out cudaMemcpy(temp_out, idata, n * sizeof(int), cudaMemcpyHostToDevice); @@ -97,9 +102,64 @@ namespace StreamCompaction { */ int compact(int n, int *odata, const int *idata) { timer().startGpuTimer(); - // TODO + int logn = ilog2ceil(n); + int powd = std::pow(2, logn); + //initialize intermediate arrays + cudaMalloc((void**)&temp_bool, powd * sizeof(int)); + checkCUDAError("cudaMalloc temp_bool failed!"); + cudaMalloc((void**)&temp_scattered, powd * sizeof(int)); + checkCUDAError("cudaMalloc temp_scattered failed!"); + cudaMalloc((void**)&temp_in, powd * sizeof(int)); + checkCUDAError("cudaMalloc temp_in failed!"); + cudaMalloc((void**)&temp_out, powd * sizeof(int)); + checkCUDAError("cudaMalloc temp_out failed!"); + cudaMemcpy(temp_in, idata, n * sizeof(int), cudaMemcpyHostToDevice); + checkCUDAError("cudaMemcpy idata to temp_in failed!"); + int gridSize = (n + BLOCK_SIZE - 1) / BLOCK_SIZE; + dim3 blocksPerGrid(gridSize); + dim3 threadsPerBlock(BLOCK_SIZE); + + //map values to bool array + Common::kernMapToBoolean << < blocksPerGrid, threadsPerBlock >> > (n, temp_bool, temp_in); + + //assign bool value to temp_scattered + cudaMemcpy(temp_scattered, temp_bool, n * sizeof(int), cudaMemcpyDeviceToDevice); + checkCUDAError("cudaMemcpy temp_scattered failed!"); + //apply scan on bool array + int ceil = logn - 1; + for (int offset = 0; offset <= ceil; ++offset) { + const int pow_d_plus_one = std::pow(2, offset + 1); + //set last element to zero in temp_out in kernel + kernComputePartialUpSweep << < blocksPerGrid, threadsPerBlock >> > (n, pow_d_plus_one, temp_scattered); + } + + //assign 0 to root element + int last_value = 0; + cudaMemset(temp_scattered + powd - 1, last_value, sizeof(int)); + checkCUDAError("cudaMemSet temp_scattered last value to 0 failed!"); + for (int offset = ceil; offset >= 0; --offset) { + const int pow_d_plus_one = std::pow(2, offset + 1); + kernComputePartialDownSweep << < blocksPerGrid, threadsPerBlock >> > (n, pow_d_plus_one, temp_scattered); + } + + //got the correct indices of non-zero element in the array + //map to odata + Common::kernScatter << < blocksPerGrid, threadsPerBlock >> > (n, temp_out, temp_in, temp_bool, temp_scattered); + timer().endGpuTimer(); - return -1; + //copy from temp_out to odata + cudaMemcpy(odata, temp_out, n * sizeof(int), cudaMemcpyDeviceToHost); + checkCUDAError("cudaMemcpy temp_out to odata failed!"); + + int last_scattered = 0; + int last_bool = 0; + cudaMemcpy(&last_scattered, &temp_scattered[n - 1], sizeof(int), cudaMemcpyDeviceToHost); + cudaMemcpy(&last_bool, &temp_bool[n - 1], sizeof(int), cudaMemcpyDeviceToHost); + int odata_size = last_scattered + last_bool; + + cudaFree(temp_bool); + cudaFree(temp_scattered); + return odata_size; } } } diff --git a/Project2-Stream-Compaction/stream_compaction/thrust.cu b/Project2-Stream-Compaction/stream_compaction/thrust.cu index 1def45e..2963533 100644 --- a/Project2-Stream-Compaction/stream_compaction/thrust.cu +++ b/Project2-Stream-Compaction/stream_compaction/thrust.cu @@ -9,6 +9,9 @@ namespace StreamCompaction { namespace Thrust { using StreamCompaction::Common::PerformanceTimer; + //intermediate arrays + int* temp_in; + int* temp_out; PerformanceTimer& timer() { static PerformanceTimer timer; @@ -18,11 +21,24 @@ namespace StreamCompaction { * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { - timer().startGpuTimer(); + // TODO use `thrust::exclusive_scan` + cudaMalloc((void**)&temp_in, n * sizeof(int)); + checkCUDAError("cudaMalloc temp_in failed!"); + cudaMalloc((void**)&temp_out, n * sizeof(int)); + checkCUDAError("cudaMalloc temp_out failed!"); + + cudaMemcpy(temp_in, idata, n * sizeof(int), cudaMemcpyHostToDevice); + timer().startGpuTimer(); + //warp to a device ptr + thrust::device_ptr dev_in(temp_in); + thrust::device_ptr dev_out(temp_out); // example: for device_vectors dv_in and dv_out: // thrust::exclusive_scan(dv_in.begin(), dv_in.end(), dv_out.begin()); + thrust::exclusive_scan(dev_in, dev_in + n, dev_out); timer().endGpuTimer(); + cudaMemcpy(odata, temp_out, n * sizeof(int), cudaMemcpyDeviceToHost); + } } } From f263cfafdb6a5a7dd726d9485c4c326ab32ae8f7 Mon Sep 17 00:00:00 2001 From: Tianming Xu Date: Fri, 13 Sep 2019 21:07:52 -0400 Subject: [PATCH 05/10] invisible screenshot --- Project2-Stream-Compaction/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project2-Stream-Compaction/README.md b/Project2-Stream-Compaction/README.md index fa33892..17830d0 100644 --- a/Project2-Stream-Compaction/README.md +++ b/Project2-Stream-Compaction/README.md @@ -11,7 +11,7 @@ CUDA Stream Compaction ### Output Screenshots(2^14 elements) -![](img/screen_shot_output.png) +![](img/screen_shot_output.PNG) From 29ee44b79b762b3a86f3de502a9b6656e6a70e2c Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 17 Sep 2019 23:47:54 -0400 Subject: [PATCH 06/10] trying to finish cpu version neural network, but still working --- .../character_recognition/common.h | 12 +- .../character_recognition/mlp.cu | 190 +++++- .../character_recognition/mlp.h | 3 + Project2-Character-Recognition/src/main.cpp | 555 +++++++++++++----- .../src/testing_helpers.hpp | 37 ++ .../stream_compaction/efficient.cu | 2 +- 6 files changed, 660 insertions(+), 139 deletions(-) diff --git a/Project2-Character-Recognition/character_recognition/common.h b/Project2-Character-Recognition/character_recognition/common.h index 6aede64..a602b92 100644 --- a/Project2-Character-Recognition/character_recognition/common.h +++ b/Project2-Character-Recognition/character_recognition/common.h @@ -4,15 +4,17 @@ #include #include +#include #include #include +#include #include #include #include #define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) #define checkCUDAError(msg) checkCUDAErrorFn(msg, FILENAME, __LINE__) - +#define BLOCK_SIZE 512 /** * Check for CUDA errors; print and exit if there was a problem. */ @@ -30,6 +32,13 @@ inline int ilog2ceil(int x) { return x == 1 ? 0 : ilog2(x - 1) + 1; } +//isInput = 1 -> input, isInput = 0 -> hidden +//n is number of element in input +inline int matrix_index(int n, int row, int col) +{ + return row * n + col; +} + namespace Common { /** @@ -38,6 +47,7 @@ namespace Common { * * Adapted from WindyDarian(https://github.com/WindyDarian) */ + class PerformanceTimer { public: diff --git a/Project2-Character-Recognition/character_recognition/mlp.cu b/Project2-Character-Recognition/character_recognition/mlp.cu index 5a3ed7f..ed721ac 100644 --- a/Project2-Character-Recognition/character_recognition/mlp.cu +++ b/Project2-Character-Recognition/character_recognition/mlp.cu @@ -1,5 +1,6 @@ #include #include +#include #include "common.h" #include "mlp.h" @@ -10,9 +11,97 @@ namespace CharacterRecognition { static PerformanceTimer timer; return timer; } + + //three buffers + float* device_input; + float* device_weight_matrix; + float* device_hidden; + float* device_ji_buffer; + float* device_output; // TODO: __global__ + __global__ void kernInputMultWeight(int n, float* idata, float* weight_matrix, float* odata) + { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) + { + return; + } + int row = index; + + float sum = 0; + for (int col = 0; col < n; ++col) + { + int idx = row * n + col; + float w = weight_matrix[idx]; + float input = idata[col]; + sum += w * input; //weight's row is fixed, but col different, similarly ,the weight corresponds to what element in idata + } + + odata[row] = sum; + //https://stackoverflow.com/questions/10375680/using-stdvector-in-cuda-device-code -- have to use thrust library, but don't know how + //float returned_val = 1 / (1 + thrust::pow((float)exp(1.0), -sum)); + //odata[row] = returned_val; + } + + //the same as inputMultWeight, except the max bound is different, here it is constrained by output_num + __global__ void kernHiddenMultWeight(int max_bound, int n, float* idata, float* weight_matrix, float* odata) + { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= max_bound) + { + return; + } + + int row = index; + + float sum = 0; + for (int col = 0; col < n; ++col) + { + int idx = row * n + col; + sum += weight_matrix[idx] * idata[col]; //weight's row is fixed, but col different, similarly ,the weight corresponds to what element in idata + } + + odata[row] = sum; + } + //better to use something like thrust library and calculate the reduction -- very slow + //__global__ void kernKJBufferToHidden(int n, float* idata, float* odata) + //{ + // int index = threadIdx.x + (blockIdx.x * blockDim.x); + // if (index >= n) + // { + // return; + // } + + // float sum = 0; + // for (int idx = index * n; idx < (index + 1) * n; ++idx) + // { + // sum += idata[idx]; + // } + + // float returned_val = 1 / (1 + std::pow(exp(1.0), -sum)); + // odata[index] = returned_val; + //} + + ////similar to kj one, should have a better naming + //__global__ void kernJIBufferToOutput(int n, float* idata, float* odata) + //{ + // int index = threadIdx.x + (blockIdx.x * blockDim.x); + // if (index >= n) + // { + // return; + // } + + // float sum = 0; + // for (int idx = index * n; idx < (index + 1) * n; ++idx) + // { + // sum += idata[idx]; + // } + + // float returned_val = 1 / (1 + std::pow(exp(1.0), -sum)); + // odata[index] = returned_val; + //} /** * Example of use case (follow how you did it in stream compaction) */ @@ -22,6 +111,105 @@ namespace CharacterRecognition { timer().endGpuTimer(); } */ - + //void CharacterRecognition::initSimulation(int n, int output_num) + //{ + // cudaMalloc((void**)&device_input, n * sizeof(float)); + // checkCUDAError("cudaMalloc device_input failed!"); + // cudaMalloc((void**)&device_kj_buffer, n * n * sizeof(float)); + // checkCUDAError("cudaMalloc device_kj_buffer failed!"); + // cudaMalloc((void**)&device_hidden, n * sizeof(float)); + // checkCUDAError("cudaMalloc device_hidden failed!"); + // cudaMalloc((void**)&device_ji_buffer, output_num * n * sizeof(float)); + // checkCUDAError("cudaMalloc device_ji_buffer failed!"); + // cudaMalloc((void**)&device_output, output_num * sizeof(float)); + // checkCUDAError("cudaMalloc device_output failed!"); + //} // TODO: implement required elements for MLP sections 1 and 2 here + + //feedforward functionality here, Sigmoid graduate descedent only need to add the number of group of training data and for loop them + void MLP_calculation(int n, int output_num, float* idata, float* weight_matrix, float* odata) + { + //init buffer + cudaMalloc((void**)&device_input, n * sizeof(float)); + checkCUDAError("cudaMalloc device_input failed!"); + cudaMalloc((void**)&device_weight_matrix, n * n * sizeof(float)); + checkCUDAError("cudaMalloc device_weight_matrix failed!"); + cudaMalloc((void**)&device_hidden, n * sizeof(float)); + checkCUDAError("cudaMalloc device_hidden failed!"); + cudaMalloc((void**)&device_ji_buffer, output_num * n * sizeof(float)); + checkCUDAError("cudaMalloc device_ji_buffer failed!"); + cudaMalloc((void**)&device_output, output_num * sizeof(float)); + checkCUDAError("cudaMalloc device_output failed!"); + + //two temp main memory buffer + float* temp_hidden = new float[n]; + + cudaMemcpy(device_input, idata, n * sizeof(float), cudaMemcpyHostToDevice); + checkCUDAError("cudaMemcpy idata to device_input failed!"); + + cudaMemcpy(device_weight_matrix, weight_matrix, n * n * sizeof(float), cudaMemcpyHostToDevice); + checkCUDAError("cudaMemcpy idata to device_weight_matrix failed!"); + + int gridSize = (n + BLOCK_SIZE - 1) / BLOCK_SIZE; + dim3 normalBlocksPerGrid(gridSize); + //int kjBufferSize = (n * n + BLOCK_SIZE - 1) / BLOCK_SIZE; + //dim3 kjBufferBlocksPerGrid(kjBufferSize); + //int jiBufferSize = (output_num * n + BLOCK_SIZE - 1) / BLOCK_SIZE; + //dim3 jiBufferBlocksPerGrid(jiBufferSize); + int outputSize = (output_num + BLOCK_SIZE - 1) / BLOCK_SIZE; + dim3 outputBlocksPerGrid(outputSize); + dim3 threadsPerBlock(BLOCK_SIZE); + + + //we first calcualte all the results into a matrix, and then read and sum each row in activate function and write in the hidden layer + timer().startGpuTimer(); + + kernInputMultWeight << < normalBlocksPerGrid, threadsPerBlock >> > (n, device_input, device_weight_matrix, device_hidden); + + timer().endGpuTimer(); + //need to apply the activate function on each element, currently each element is only the sum. + //copy to a temp array and copmute sequentially for now + cudaMemcpy(temp_hidden, device_hidden, n * sizeof(float), cudaMemcpyDeviceToHost); + checkCUDAError("cudaMemcpy device_hidden to temp_hidden failed!"); + + //compute activation function + for (int i = 0; i < n; ++i) + { + temp_hidden[i] = 1 / (1 + std::pow(exp(1.0), -temp_hidden[i])); + } + + //copy back to hidden + cudaMemcpy(device_hidden, temp_hidden, n * sizeof(float), cudaMemcpyHostToDevice); + checkCUDAError("cudaMemcpy temp_hidden to device_hidden failed!"); + + + //legacy + //kernKJBufferToHidden << < normalBlocksPerGrid, threadsPerBlock >> > (n, device_kj_buffer, device_hidden); + //kernHiddenMultWeight << < jiBufferBlocksPerGrid, threadsPerBlock >> > (output_num*n, n, device_hidden, weight_matrix, device_ji_buffer); + //kernJIBufferToOutput << < outputBlocksPerGrid, threadsPerBlock >> > (n, device_ji_buffer, device_output); + + + timer().startGpuTimer(); + //do we use the same weight? -- in XOR example, it is not + kernHiddenMultWeight << < outputBlocksPerGrid, threadsPerBlock >> > (output_num, n, device_hidden, device_weight_matrix, device_output); + timer().endGpuTimer(); + + //how to calculate the error and affect the next weights? -- how to get expcted result? -- read in? + cudaMemcpy(odata, device_output, output_num * sizeof(float), cudaMemcpyDeviceToHost); + checkCUDAError("cudaMemcpy device_output to odata failed!"); + + //do the activaition functoin here + for (int i = 0; i < output_num; ++i) + { + odata[i] = 1 / (1 + std::pow(exp(1.0), -odata[i])); + } + + cudaFree(device_input); + cudaFree(device_weight_matrix); + cudaFree(device_hidden); + cudaFree(device_ji_buffer); + cudaFree(device_output); + + delete[] temp_hidden; + } } diff --git a/Project2-Character-Recognition/character_recognition/mlp.h b/Project2-Character-Recognition/character_recognition/mlp.h index 2096228..c7404d7 100644 --- a/Project2-Character-Recognition/character_recognition/mlp.h +++ b/Project2-Character-Recognition/character_recognition/mlp.h @@ -6,4 +6,7 @@ namespace CharacterRecognition { Common::PerformanceTimer& timer(); // TODO: implement required elements for MLP sections 1 and 2 here + //void initSimulation(int n, int output_num); + //void SGD(); + void MLP_calculation(int n, int output_num, float* idata, float* weight_matrix, float* odata); } diff --git a/Project2-Character-Recognition/src/main.cpp b/Project2-Character-Recognition/src/main.cpp index 11dd534..89d27b9 100644 --- a/Project2-Character-Recognition/src/main.cpp +++ b/Project2-Character-Recognition/src/main.cpp @@ -6,147 +6,430 @@ * @copyright University of Pennsylvania */ +#include #include +#include +#include +#include +#include +#include +#include #include #include #include "testing_helpers.hpp" -const int SIZE = 1 << 8; // feel free to change the size of array -const int NPOT = SIZE - 3; // Non-Power-Of-Two -int *a = new int[SIZE]; -int *b = new int[SIZE]; -int *c = new int[SIZE]; +#define LAYER 2 +#define INPUT_NUM 52 + +const int INPUT_SIZE = 10201; // feel free to change the size of array +const int OUTPUT_SIZE = 52; +const int HIDDEN_SIZE = 10; +//float* input = new float[INPUT_SIZE]; +//float* output = new float[OUTPUT_SIZE]; +std::vector array_inputs; +std::vector array_target_outputs; + +void read_in_inputs() +{ + std::string input_file_postfix = "info.txt"; + std::string input_file_folder = "..\\data-set\\"; + for (int i = 1; i <= OUTPUT_SIZE; ++i) + { + //init the buffer we want to store the input data + float* input = new float[INPUT_SIZE]; + float* output = new float[OUTPUT_SIZE]; + + std::string input_file_prefix = i < 10 ? "0" + std::to_string(i) : std::to_string(i); + std::string input_file = input_file_folder + input_file_prefix + input_file_postfix; + + std::ifstream myfile(input_file); + if (!myfile) + { + std::cout << "Error opening input_file" << std::endl; + std::cout << input_file << std::endl; + system("pause"); + return; + } + std::cout << input_file_prefix << "." << std::endl; + int count = 0; + while (!myfile.eof()) + { + std::string intermediate_array; + std::vector tokens; + std::getline(myfile, intermediate_array, '\n'); + if (count == 0) + { + std::cout << intermediate_array << std::endl; + int output_result = std::stoi(intermediate_array); + for (int i = 1; i <= OUTPUT_SIZE; ++i) + { + if (i == output_result) output[i-1] = 1; + else output[i-1] = 0; + } + } + else if (count == 2) + { + std::stringstream tokens(intermediate_array); + std::string token; + // Tokenizing w.r.t. space ' ' + int count = 0; + //the first is a space, clean it + std::getline(tokens, token, ' '); + while (std::getline(tokens, token, ' ')) + { + input[count] = std::stoi(token); + count++; + } + if (count != INPUT_SIZE) + { + std::cout << "count != INPUT_SIZE, something wrong" << std::endl; + } + } + count++; + } + //store the input array into vector + array_inputs.push_back(input); + array_target_outputs.push_back(output); + } +} + +//first do in cpu version + +//utility +//def evaluate(self, test_data) : +// """Return the number of test inputs for which the neural +// network outputs the correct result.Note that the neural +// network's output is assumed to be the index of whichever +// neuron in the final layer has the highest activation.""" +// test_results = [(np.argmax(self.feedforward(x)), y) +// for (x, y) in test_data] +// return sum(int(x == y) for (x, y) in test_results) + +//def cost_derivative(self, output_activations, y) : +//"""Return the vector of partial derivatives \partial C_x / +//\partial a for the output activations.""" + //return (output_activations - y) + + +float sigmoid(float z) +{ + return 1 / (1 + std::pow(exp(1.0), -z)); +} + +float sigmoid_prime(float z) +{ + return sigmoid(z) * (1 - sigmoid(z)); +} + +void cost_derivative(int n, float* target_output, float* actual_output, float* error_vec) +{ + for (int i = 0; i < n; ++i) + { + error_vec[i] = actual_output[i] - target_output[i]; + } +} + +void argmax(int output_num, float* curr_output) +{ + int max_idx = -1; + float max = 0; + for (int i = 0; i < output_num; ++i) + { + if (curr_output[output_num] >= max) + { + max_idx = i; + max = curr_output[output_num]; + } + } +} + + +void feed_forward(int n, int hidden_num, int output_num, float* idata, float* odata, float* input_weight_matrix, float* hidden_weight_matrix, float* input_bias_vec, float* hidden_bias_vec) +{ + float* temp_hidden = new float[hidden_num]; + + //input to hidden + for (int row = 0; row < hidden_num; ++row) + { + float sum = 0; + for (int col = 0; col < n; ++col) + { + int idx = row * n + col; + float w = input_weight_matrix[idx]; + float input = idata[col]; + sum += w * input + input_bias_vec[col]; + + } + temp_hidden[row] = sigmoid(sum); + } + + //from hidden to output + //input to hidden + for (int row = 0; row < output_num; ++row) + { + float sum = 0; + for (int col = 0; col < hidden_num; ++col) + { + int idx = row * n + col; + float w = hidden_weight_matrix[idx]; + float input = temp_hidden[col]; + sum += w * input + hidden_bias_vec[col]; + + } + odata[row] = sigmoid(sum); + } +} + +//SGD +//we use training data as array_input and array_target_output, same as test data -- probably all size information should be inputs +void SGD(int training_round, int hidden_size, float eta, float* input_weight_matrix, float* hidden_weight_matrix, float* input_bias_vec, float* hidden_bias_vec) +{ + //init necessary buffers + float* updated_input_weight = new float[INPUT_SIZE * hidden_size]; + float* updated_hidden_weight = new float[OUTPUT_SIZE * hidden_size]; + float* updated_input_bias = new float[INPUT_SIZE]; + float* updated_hidden_bias = new float[hidden_size]; + + for (int round = 0; round < training_round; ++round) + { + //zero out all components for new round of change + std::fill(updated_input_weight, updated_input_weight + INPUT_SIZE * hidden_size, 0); + std::fill(updated_hidden_weight, updated_hidden_weight + OUTPUT_SIZE * hidden_size, 0); + std::fill(updated_input_bias, updated_input_bias + INPUT_SIZE, 0); + std::fill(updated_hidden_bias, updated_hidden_bias + hidden_size, 0); + //memcpy(updated_hidden_bias, hidden_bias_vec, HIDDEN_SIZE * sizeof(float)); + + for (int input_index = 0; input_index < OUTPUT_SIZE; ++input_index) + { + //update each element in the buffer arrays directly in back_prop + back_prop(array_inputs[input_index], array_target_outputs[input_index], updated_input_weight, updated_hidden_weight, updated_input_bias, updated_hidden_bias, hidden_size); + } + + //update the weights and bias after each round + for (int input_weight_index = 0; input_weight_index < INPUT_SIZE * hidden_size; ++input_weight_index) + { + input_weight_matrix[input_weight_index] -= (eta / OUTPUT_SIZE) * updated_input_weight[input_weight_index]; + } + + for (int hidden_weight_index = 0; hidden_weight_index < OUTPUT_SIZE * hidden_size; ++hidden_weight_index) + { + hidden_weight_matrix[hidden_weight_index] -= (eta / OUTPUT_SIZE) * updated_hidden_weight[hidden_weight_index]; + } + + for (int input_bias_index = 0; input_bias_index < INPUT_SIZE; ++input_bias_index) + { + input_bias_vec[input_bias_index] -= (eta / OUTPUT_SIZE) * updated_input_bias[input_bias_index]; + } + + for (int hidden_bias_index = 0; hidden_bias_index < hidden_size; ++hidden_bias_index) + { + hidden_bias_vec[hidden_bias_index] -= (eta / OUTPUT_SIZE) * updated_hidden_bias[hidden_bias_index]; + } + } + + free(updated_input_weight); + free(updated_hidden_weight); + free(updated_input_bias); + free(updated_hidden_bias); +} + +void back_prop(float* input_data, float* target_output_data, float* updated_input_weight, float* updated_hidden_weight, float* updated_input_bias, float* updated_hidden_bias, int hidden_size) +{ + +} + + +void Evaluate(int n, int hidden_num, int output_num, float* test_data, float* target_output, float* input_weight_matrix, float* hidden_weight_matrix, float* input_bias_vec, float* hidden_bias_vec) +{ + int sum = 0; + float* curr_output = new float[output_num]; + //unused (test_data, target_output) --> will use later + for (int i = 0; i < output_num; ++i) + { + //zero out output + std::fill(curr_output, curr_output + output_num, 0); + feed_forward(n, hidden_num, output_num, array_inputs[i], curr_output, input_weight_matrix, hidden_weight_matrix, input_bias_vec, hidden_bias_vec); + + argmax(curr_output); + if (curr_output[i] == array_target_outputs[i][i]) + { + //hack, we know that that element in target_output should be 1, so if both are one, it is correct + sum++; + } + } +} + + int main(int argc, char* argv[]) { - // Scan tests - - printf("\n"); - printf("****************\n"); - printf("** SCAN TESTS **\n"); - printf("****************\n"); - - genArray(SIZE - 1, a, 50); // Leave a 0 at the end to test that edge case - a[SIZE - 1] = 0; - printArray(SIZE, a, true); - - // initialize b using StreamCompaction::CPU::scan you implement - // We use b for further comparison. Make sure your StreamCompaction::CPU::scan is correct. - // At first all cases passed because b && c are all zeroes. - zeroArray(SIZE, b); - printDesc("cpu scan, power-of-two"); - StreamCompaction::CPU::scan(SIZE, b, a); - printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); - printArray(SIZE, b, true); - - zeroArray(SIZE, c); - printDesc("cpu scan, non-power-of-two"); - StreamCompaction::CPU::scan(NPOT, c, a); - printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); - printArray(NPOT, b, true); - printCmpResult(NPOT, b, c); - - zeroArray(SIZE, c); - printDesc("naive scan, power-of-two"); - StreamCompaction::Naive::scan(SIZE, c, a); - printElapsedTime(StreamCompaction::Naive::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - //printArray(SIZE, c, true); - printCmpResult(SIZE, b, c); - - /* For bug-finding only: Array of 1s to help find bugs in stream compaction or scan - onesArray(SIZE, c); - printDesc("1s array for finding bugs"); - StreamCompaction::Naive::scan(SIZE, c, a); - printArray(SIZE, c, true); */ - - zeroArray(SIZE, c); - printDesc("naive scan, non-power-of-two"); - StreamCompaction::Naive::scan(NPOT, c, a); - printElapsedTime(StreamCompaction::Naive::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - //printArray(SIZE, c, true); - printCmpResult(NPOT, b, c); - - zeroArray(SIZE, c); - printDesc("work-efficient scan, power-of-two"); - StreamCompaction::Efficient::scan(SIZE, c, a); - printElapsedTime(StreamCompaction::Efficient::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - //printArray(SIZE, c, true); - printCmpResult(SIZE, b, c); - - zeroArray(SIZE, c); - printDesc("work-efficient scan, non-power-of-two"); - StreamCompaction::Efficient::scan(NPOT, c, a); - printElapsedTime(StreamCompaction::Efficient::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - //printArray(NPOT, c, true); - printCmpResult(NPOT, b, c); - - zeroArray(SIZE, c); - printDesc("thrust scan, power-of-two"); - StreamCompaction::Thrust::scan(SIZE, c, a); - printElapsedTime(StreamCompaction::Thrust::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - //printArray(SIZE, c, true); - printCmpResult(SIZE, b, c); - - zeroArray(SIZE, c); - printDesc("thrust scan, non-power-of-two"); - StreamCompaction::Thrust::scan(NPOT, c, a); - printElapsedTime(StreamCompaction::Thrust::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - //printArray(NPOT, c, true); - printCmpResult(NPOT, b, c); - - printf("\n"); - printf("*****************************\n"); - printf("** STREAM COMPACTION TESTS **\n"); - printf("*****************************\n"); - - // Compaction tests - - genArray(SIZE - 1, a, 4); // Leave a 0 at the end to test that edge case - a[SIZE - 1] = 0; - printArray(SIZE, a, true); - - int count, expectedCount, expectedNPOT; - - // initialize b using StreamCompaction::CPU::compactWithoutScan you implement - // We use b for further comparison. Make sure your StreamCompaction::CPU::compactWithoutScan is correct. - zeroArray(SIZE, b); - printDesc("cpu compact without scan, power-of-two"); - count = StreamCompaction::CPU::compactWithoutScan(SIZE, b, a); - printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); - expectedCount = count; - printArray(count, b, true); - printCmpLenResult(count, expectedCount, b, b); - - zeroArray(SIZE, c); - printDesc("cpu compact without scan, non-power-of-two"); - count = StreamCompaction::CPU::compactWithoutScan(NPOT, c, a); - printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); - expectedNPOT = count; - printArray(count, c, true); - printCmpLenResult(count, expectedNPOT, b, c); - - zeroArray(SIZE, c); - printDesc("cpu compact with scan"); - count = StreamCompaction::CPU::compactWithScan(SIZE, c, a); - printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); - printArray(count, c, true); - printCmpLenResult(count, expectedCount, b, c); - - zeroArray(SIZE, c); - printDesc("work-efficient compact, power-of-two"); - count = StreamCompaction::Efficient::compact(SIZE, c, a); - printElapsedTime(StreamCompaction::Efficient::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - //printArray(count, c, true); - printCmpLenResult(count, expectedCount, b, c); - - zeroArray(SIZE, c); - printDesc("work-efficient compact, non-power-of-two"); - count = StreamCompaction::Efficient::compact(NPOT, c, a); - printElapsedTime(StreamCompaction::Efficient::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - //printArray(count, c, true); - printCmpLenResult(count, expectedNPOT, b, c); - - system("pause"); // stop Win32 console from closing on exit - delete[] a; - delete[] b; - delete[] c; + + read_in_inputs(); + //the data will be stored in array_inputs and array_outputs. + + //init input_weights and hidden weights + float *input_weight_matrix = new float[INPUT_SIZE * HIDDEN_SIZE]; + float *hidden_weight_matrix = new float[OUTPUT_SIZE * HIDDEN_SIZE]; + init_weight_matrix(INPUT_SIZE, HIDDEN_SIZE, input_weight_matrix); // Leave a 0 at the end to test that edge case + init_weight_matrix(OUTPUT_SIZE, HIDDEN_SIZE, hidden_weight_matrix); + float *input_bias_vec = new float[INPUT_SIZE]; + float *hidden_bias_vec = new float[HIDDEN_SIZE]; + std::fill(input_bias_vec, input_bias_vec + INPUT_SIZE, 0); + std::fill(hidden_bias_vec, hidden_bias_vec + HIDDEN_SIZE, 0); + + float target_error = 0.0003; + float total_error = 0.2; //will be modified + float target_val = 1; //tested on XOR example -- should this be a + //first generate the weight matrix to store all weight values for each element, in this homework, it should be a n * 2 matrix, such that n is the number of input + + for (int i = 0; i < OUTPUT_SIZE; ++i) + { + printFloatArray(INPUT_SIZE, array_inputs[i], true); + printFloatArray(OUTPUT_SIZE, array_target_outputs[i], true); + std::cout << std::endl; + } + //printFloatArray(INPUT_SIZE * HIDDEN_SIZE, weight_matrix, false); + + //get input from files? + //CharacterRecognition::MLP_calculation(INPUT_SIZE, OUTPUT_SIZE, input, weight_matrix, output); + //printElapsedTime(CharacterRecognition::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); + //printFloatArray(OUTPUT_SIZE, output, true); + + //assume we get the correct output, we should calculate the error and apply bp algorithm back to weight + + //how do we get the target value? -- from file? + + //understand bp algorithm + //how to make it keep running? -- while loop within a certain threshold + delete[] weight_matrix; + // // Scan tests + + // printf("\n"); + // printf("****************\n"); + // printf("** SCAN TESTS **\n"); + // printf("****************\n"); + + // genArray(SIZE - 1, a, 50); // Leave a 0 at the end to test that edge case + // a[SIZE - 1] = 0; + // printArray(SIZE, a, true); + + // // initialize b using StreamCompaction::CPU::scan you implement + // // We use b for further comparison. Make sure your StreamCompaction::CPU::scan is correct. + // // At first all cases passed because b && c are all zeroes. + // zeroArray(SIZE, b); + // printDesc("cpu scan, power-of-two"); + // StreamCompaction::CPU::scan(SIZE, b, a); + // printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); + // printArray(SIZE, b, true); + + // zeroArray(SIZE, c); + // printDesc("cpu scan, non-power-of-two"); + // StreamCompaction::CPU::scan(NPOT, c, a); + // printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); + // printArray(NPOT, b, true); + // printCmpResult(NPOT, b, c); + + // zeroArray(SIZE, c); + // printDesc("naive scan, power-of-two"); + // StreamCompaction::Naive::scan(SIZE, c, a); + // printElapsedTime(StreamCompaction::Naive::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); + // //printArray(SIZE, c, true); + // printCmpResult(SIZE, b, c); + + ///* For bug-finding only: Array of 1s to help find bugs in stream compaction or scan + //onesArray(SIZE, c); + //printDesc("1s array for finding bugs"); + //StreamCompaction::Naive::scan(SIZE, c, a); + //printArray(SIZE, c, true); */ + + // zeroArray(SIZE, c); + // printDesc("naive scan, non-power-of-two"); + // StreamCompaction::Naive::scan(NPOT, c, a); + // printElapsedTime(StreamCompaction::Naive::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); + // //printArray(SIZE, c, true); + // printCmpResult(NPOT, b, c); + + // zeroArray(SIZE, c); + // printDesc("work-efficient scan, power-of-two"); + // StreamCompaction::Efficient::scan(SIZE, c, a); + // printElapsedTime(StreamCompaction::Efficient::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); + // //printArray(SIZE, c, true); + // printCmpResult(SIZE, b, c); + + // zeroArray(SIZE, c); + // printDesc("work-efficient scan, non-power-of-two"); + // StreamCompaction::Efficient::scan(NPOT, c, a); + // printElapsedTime(StreamCompaction::Efficient::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); + // //printArray(NPOT, c, true); + // printCmpResult(NPOT, b, c); + + // zeroArray(SIZE, c); + // printDesc("thrust scan, power-of-two"); + // StreamCompaction::Thrust::scan(SIZE, c, a); + // printElapsedTime(StreamCompaction::Thrust::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); + // //printArray(SIZE, c, true); + // printCmpResult(SIZE, b, c); + + // zeroArray(SIZE, c); + // printDesc("thrust scan, non-power-of-two"); + // StreamCompaction::Thrust::scan(NPOT, c, a); + // printElapsedTime(StreamCompaction::Thrust::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); + // //printArray(NPOT, c, true); + // printCmpResult(NPOT, b, c); + + // printf("\n"); + // printf("*****************************\n"); + // printf("** STREAM COMPACTION TESTS **\n"); + // printf("*****************************\n"); + + // // Compaction tests + + // genArray(SIZE - 1, a, 4); // Leave a 0 at the end to test that edge case + // a[SIZE - 1] = 0; + // printArray(SIZE, a, true); + + // int count, expectedCount, expectedNPOT; + + // // initialize b using StreamCompaction::CPU::compactWithoutScan you implement + // // We use b for further comparison. Make sure your StreamCompaction::CPU::compactWithoutScan is correct. + // zeroArray(SIZE, b); + // printDesc("cpu compact without scan, power-of-two"); + // count = StreamCompaction::CPU::compactWithoutScan(SIZE, b, a); + // printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); + // expectedCount = count; + // printArray(count, b, true); + // printCmpLenResult(count, expectedCount, b, b); + + // zeroArray(SIZE, c); + // printDesc("cpu compact without scan, non-power-of-two"); + // count = StreamCompaction::CPU::compactWithoutScan(NPOT, c, a); + // printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); + // expectedNPOT = count; + // printArray(count, c, true); + // printCmpLenResult(count, expectedNPOT, b, c); + + // zeroArray(SIZE, c); + // printDesc("cpu compact with scan"); + // count = StreamCompaction::CPU::compactWithScan(SIZE, c, a); + // printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); + // printArray(count, c, true); + // printCmpLenResult(count, expectedCount, b, c); + + // zeroArray(SIZE, c); + // printDesc("work-efficient compact, power-of-two"); + // count = StreamCompaction::Efficient::compact(SIZE, c, a); + // printElapsedTime(StreamCompaction::Efficient::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); + // //printArray(count, c, true); + // printCmpLenResult(count, expectedCount, b, c); + + // zeroArray(SIZE, c); + // printDesc("work-efficient compact, non-power-of-two"); + // count = StreamCompaction::Efficient::compact(NPOT, c, a); + // printElapsedTime(StreamCompaction::Efficient::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); + // //printArray(count, c, true); + // printCmpLenResult(count, expectedNPOT, b, c); + + // system("pause"); // stop Win32 console from closing on exit + //delete[] a; + //delete[] b; + //delete[] c; } diff --git a/Project2-Character-Recognition/src/testing_helpers.hpp b/Project2-Character-Recognition/src/testing_helpers.hpp index b28a8d2..a54da6a 100644 --- a/Project2-Character-Recognition/src/testing_helpers.hpp +++ b/Project2-Character-Recognition/src/testing_helpers.hpp @@ -57,6 +57,31 @@ void genArray(int n, int *a, int maxval) { } } +//initialize weight matrix within a certain range -- should that be sequential? +void init_weight_matrix(int n, int hidden_num, float* weight_matrix, float range_min = 0, float range_max = 1) +{ + srand(time(nullptr)); + for (int i = 0; i < n * hidden_num; ++i) + { + weight_matrix[i] = range_min + static_cast (rand()) / (static_cast (RAND_MAX / (range_max - range_min))); + } +} + +void init_bais_vec(int n, float *mat) +{ + for (int i = 0; i < n; ++i) + { + mat[i] = 0; + } +} + +void init_input(int n, float* input) { + for (int i = 0; i < n; ++i) + { + input[i] = i % 2 == 0 ? 1 : 0; + } +} + void printArray(int n, int *a, bool abridged = false) { printf(" [ "); for (int i = 0; i < n; i++) { @@ -69,6 +94,18 @@ void printArray(int n, int *a, bool abridged = false) { printf("]\n"); } +void printFloatArray(int n, float *a, bool abridged = false) { + printf(" [ "); + for (int i = 0; i < n; i++) { + if (abridged && i + 2 == 15 && n > 16) { + i = n - 2; + printf("... "); + } + printf("%.3f ", a[i]); + } + printf("]\n"); +} + template void printElapsedTime(T time, std::string note = "") { diff --git a/Project2-Stream-Compaction/stream_compaction/efficient.cu b/Project2-Stream-Compaction/stream_compaction/efficient.cu index e065a50..83f66af 100644 --- a/Project2-Stream-Compaction/stream_compaction/efficient.cu +++ b/Project2-Stream-Compaction/stream_compaction/efficient.cu @@ -101,7 +101,6 @@ namespace StreamCompaction { * @returns The number of elements remaining after compaction. */ int compact(int n, int *odata, const int *idata) { - timer().startGpuTimer(); int logn = ilog2ceil(n); int powd = std::pow(2, logn); //initialize intermediate arrays @@ -126,6 +125,7 @@ namespace StreamCompaction { cudaMemcpy(temp_scattered, temp_bool, n * sizeof(int), cudaMemcpyDeviceToDevice); checkCUDAError("cudaMemcpy temp_scattered failed!"); //apply scan on bool array + timer().startGpuTimer(); int ceil = logn - 1; for (int offset = 0; offset <= ceil; ++offset) { const int pow_d_plus_one = std::pow(2, offset + 1); From 43d90fbbf0de9b46218a3cf53f6e6a3b998ba9e4 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 18 Sep 2019 01:39:04 -0400 Subject: [PATCH 07/10] add CPU version to main.cpp, but doesn't work --- Project2-Character-Recognition/src/main.cpp | 170 ++++++++++++++++---- 1 file changed, 137 insertions(+), 33 deletions(-) diff --git a/Project2-Character-Recognition/src/main.cpp b/Project2-Character-Recognition/src/main.cpp index 89d27b9..a8abd5a 100644 --- a/Project2-Character-Recognition/src/main.cpp +++ b/Project2-Character-Recognition/src/main.cpp @@ -29,6 +29,8 @@ const int HIDDEN_SIZE = 10; std::vector array_inputs; std::vector array_target_outputs; +void Evaluate(int n, int hidden_num, int output_num, float* test_data, float* target_output, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec); + void read_in_inputs() { std::string input_file_postfix = "info.txt"; @@ -50,7 +52,7 @@ void read_in_inputs() system("pause"); return; } - std::cout << input_file_prefix << "." << std::endl; + //std::cout << input_file_prefix << "." << std::endl; int count = 0; while (!myfile.eof()) { @@ -59,7 +61,7 @@ void read_in_inputs() std::getline(myfile, intermediate_array, '\n'); if (count == 0) { - std::cout << intermediate_array << std::endl; + //std::cout << intermediate_array << std::endl; int output_result = std::stoi(intermediate_array); for (int i = 1; i <= OUTPUT_SIZE; ++i) { @@ -135,16 +137,24 @@ void argmax(int output_num, float* curr_output) float max = 0; for (int i = 0; i < output_num; ++i) { - if (curr_output[output_num] >= max) + if (curr_output[i] >= max) { max_idx = i; - max = curr_output[output_num]; + max = curr_output[i]; + } + } + + for (int i = 0; i < output_num; ++i) + { + if (i == max_idx) { + curr_output[i] = 1; } + else curr_output[i] = 0; } } -void feed_forward(int n, int hidden_num, int output_num, float* idata, float* odata, float* input_weight_matrix, float* hidden_weight_matrix, float* input_bias_vec, float* hidden_bias_vec) +void feed_forward(int n, int hidden_num, int output_num, float* idata, float* odata, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec) { float* temp_hidden = new float[hidden_num]; @@ -157,7 +167,7 @@ void feed_forward(int n, int hidden_num, int output_num, float* idata, float* od int idx = row * n + col; float w = input_weight_matrix[idx]; float input = idata[col]; - sum += w * input + input_bias_vec[col]; + sum += w * input + hidden_bias_vec[row]; } temp_hidden[row] = sigmoid(sum); @@ -170,39 +180,116 @@ void feed_forward(int n, int hidden_num, int output_num, float* idata, float* od float sum = 0; for (int col = 0; col < hidden_num; ++col) { - int idx = row * n + col; + int idx = row * hidden_num + col; float w = hidden_weight_matrix[idx]; float input = temp_hidden[col]; - sum += w * input + hidden_bias_vec[col]; + sum += w * input + output_bias_vec[row]; } odata[row] = sigmoid(sum); } } +void back_prop(int hidden_num, float* input_data, float* target_output_data, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec, float* updated_input_weight, float* updated_hidden_weight, float* updated_hidden_bias, float* updated_output_bias) +{ + //generate intermediate hidden layer + float* hidden_layer = new float[hidden_num]; + float* output_layer = new float[OUTPUT_SIZE]; + float* hidden_weighted_input = new float[hidden_num]; + float* output_weighted_input = new float[OUTPUT_SIZE]; + float* output_cost_error = new float[OUTPUT_SIZE]; + float* hidden_cost_error = new float[hidden_num]; + //feedfoward + for (int row = 0; row < hidden_num; ++row) + { + float sum = 0; + for (int col = 0; col < INPUT_SIZE; ++col) + { + int idx = row * INPUT_SIZE + col; + float w = input_weight_matrix[idx]; + float input = input_data[col]; + sum += w * input + hidden_bias_vec[row]; + + } + hidden_weighted_input[row] = sum; + hidden_layer[row] = sigmoid(sum); + } + + for (int row = 0; row < OUTPUT_SIZE; ++row) + { + float sum = 0; + for (int col = 0; col < hidden_num; ++col) + { + int idx = row * hidden_num + col; + float w = hidden_weight_matrix[idx]; + float input = hidden_layer[col]; + sum += w * input + output_bias_vec[row]; + + } + output_weighted_input[row] = sum; + output_layer[row] = sigmoid(sum); + } + + //Get the cost derivative from the output layer result and target output data + cost_derivative(OUTPUT_SIZE, target_output_data, output_layer, output_cost_error); + //add the sigmoid prime to it + for (int row = 0; row < OUTPUT_SIZE; ++row) + { + output_cost_error[row] *= sigmoid_prime(output_weighted_input[row]); + //assign to updated weights and bias for output + updated_output_bias[row] = output_cost_error[row]; + for (int col = 0; col < hidden_num; ++col) + { + int mat_idx = row * hidden_num + col; + updated_hidden_weight[mat_idx] = hidden_layer[col] * output_cost_error[row]; + } + } + + //compute the hidden_cost_error by output_cost_error + //use transpose index + for (int row = 0; row < hidden_num; ++row) + { + for (int col = 0; col < OUTPUT_SIZE; ++col) + { + int mat_idx = row * OUTPUT_SIZE + col; + hidden_cost_error[row] += hidden_weight_matrix[mat_idx] * output_cost_error[col]; + } + + //apply sigmoid prime after we compute the derivative + hidden_cost_error[row] *= sigmoid_prime(hidden_weighted_input[row]); + //assign to updated matrix and vec + updated_hidden_bias[row] = hidden_cost_error[row]; + for (int mat_col = 0; mat_col < INPUT_SIZE; ++mat_col) + { + int mat_idx = row * INPUT_SIZE + mat_col; + updated_input_weight[mat_idx] = input_data[mat_col] * hidden_cost_error[row]; + } + } +} + //SGD //we use training data as array_input and array_target_output, same as test data -- probably all size information should be inputs -void SGD(int training_round, int hidden_size, float eta, float* input_weight_matrix, float* hidden_weight_matrix, float* input_bias_vec, float* hidden_bias_vec) +void SGD(int training_round, int hidden_size, float eta, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec) { //init necessary buffers float* updated_input_weight = new float[INPUT_SIZE * hidden_size]; float* updated_hidden_weight = new float[OUTPUT_SIZE * hidden_size]; - float* updated_input_bias = new float[INPUT_SIZE]; float* updated_hidden_bias = new float[hidden_size]; + float* updated_output_bias = new float[OUTPUT_SIZE]; for (int round = 0; round < training_round; ++round) { //zero out all components for new round of change std::fill(updated_input_weight, updated_input_weight + INPUT_SIZE * hidden_size, 0); std::fill(updated_hidden_weight, updated_hidden_weight + OUTPUT_SIZE * hidden_size, 0); - std::fill(updated_input_bias, updated_input_bias + INPUT_SIZE, 0); std::fill(updated_hidden_bias, updated_hidden_bias + hidden_size, 0); + std::fill(updated_output_bias, updated_output_bias + OUTPUT_SIZE, 0); //memcpy(updated_hidden_bias, hidden_bias_vec, HIDDEN_SIZE * sizeof(float)); for (int input_index = 0; input_index < OUTPUT_SIZE; ++input_index) { //update each element in the buffer arrays directly in back_prop - back_prop(array_inputs[input_index], array_target_outputs[input_index], updated_input_weight, updated_hidden_weight, updated_input_bias, updated_hidden_bias, hidden_size); + back_prop(hidden_size, array_inputs[input_index], array_target_outputs[input_index], input_weight_matrix, hidden_weight_matrix, hidden_bias_vec, output_bias_vec, updated_input_weight, updated_hidden_weight, updated_hidden_bias, updated_output_bias); } //update the weights and bias after each round @@ -216,30 +303,28 @@ void SGD(int training_round, int hidden_size, float eta, float* input_weight_mat hidden_weight_matrix[hidden_weight_index] -= (eta / OUTPUT_SIZE) * updated_hidden_weight[hidden_weight_index]; } - for (int input_bias_index = 0; input_bias_index < INPUT_SIZE; ++input_bias_index) + for (int hidden_bias_index = 0; hidden_bias_index < hidden_size; ++hidden_bias_index) { - input_bias_vec[input_bias_index] -= (eta / OUTPUT_SIZE) * updated_input_bias[input_bias_index]; + hidden_bias_vec[hidden_bias_index] -= (eta / OUTPUT_SIZE) * updated_hidden_bias[hidden_bias_index]; } - for (int hidden_bias_index = 0; hidden_bias_index < hidden_size; ++hidden_bias_index) + for (int output_bias_index = 0; output_bias_index < OUTPUT_SIZE; ++output_bias_index) { - hidden_bias_vec[hidden_bias_index] -= (eta / OUTPUT_SIZE) * updated_hidden_bias[hidden_bias_index]; + output_bias_vec[output_bias_index] -= (eta / OUTPUT_SIZE) * updated_output_bias[output_bias_index]; } } - free(updated_input_weight); - free(updated_hidden_weight); - free(updated_input_bias); - free(updated_hidden_bias); -} - -void back_prop(float* input_data, float* target_output_data, float* updated_input_weight, float* updated_hidden_weight, float* updated_input_bias, float* updated_hidden_bias, int hidden_size) -{ + std::cout << "we train " << training_round << " rounds" << std::endl; + Evaluate(INPUT_SIZE, HIDDEN_SIZE, OUTPUT_SIZE, nullptr, nullptr, input_weight_matrix, hidden_weight_matrix, hidden_bias_vec, output_bias_vec); + delete[] updated_input_weight; + delete[] updated_hidden_weight; + delete[] updated_hidden_bias; + delete[] updated_output_bias; } -void Evaluate(int n, int hidden_num, int output_num, float* test_data, float* target_output, float* input_weight_matrix, float* hidden_weight_matrix, float* input_bias_vec, float* hidden_bias_vec) +void Evaluate(int n, int hidden_num, int output_num, float* test_data, float* target_output, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec) { int sum = 0; float* curr_output = new float[output_num]; @@ -248,15 +333,26 @@ void Evaluate(int n, int hidden_num, int output_num, float* test_data, float* ta { //zero out output std::fill(curr_output, curr_output + output_num, 0); - feed_forward(n, hidden_num, output_num, array_inputs[i], curr_output, input_weight_matrix, hidden_weight_matrix, input_bias_vec, hidden_bias_vec); + feed_forward(n, hidden_num, output_num, array_inputs[i], curr_output, input_weight_matrix, hidden_weight_matrix, hidden_bias_vec, output_bias_vec); - argmax(curr_output); + argmax(output_num, curr_output); if (curr_output[i] == array_target_outputs[i][i]) { //hack, we know that that element in target_output should be 1, so if both are one, it is correct sum++; } + + if (curr_output[i] == 1) + { + std::cout << "curr_output[" << i << "] is 1" << std::endl; + } + } + + + std::cout << "Result: " << sum << " / " << output_num << std::endl; + + delete[] curr_output; } @@ -269,12 +365,12 @@ int main(int argc, char* argv[]) { //init input_weights and hidden weights float *input_weight_matrix = new float[INPUT_SIZE * HIDDEN_SIZE]; float *hidden_weight_matrix = new float[OUTPUT_SIZE * HIDDEN_SIZE]; - init_weight_matrix(INPUT_SIZE, HIDDEN_SIZE, input_weight_matrix); // Leave a 0 at the end to test that edge case - init_weight_matrix(OUTPUT_SIZE, HIDDEN_SIZE, hidden_weight_matrix); - float *input_bias_vec = new float[INPUT_SIZE]; + init_weight_matrix(INPUT_SIZE, HIDDEN_SIZE, input_weight_matrix, 0 , 0.1f); // Leave a 0 at the end to test that edge case + init_weight_matrix(OUTPUT_SIZE, HIDDEN_SIZE, hidden_weight_matrix, 0, 0.1f); float *hidden_bias_vec = new float[HIDDEN_SIZE]; - std::fill(input_bias_vec, input_bias_vec + INPUT_SIZE, 0); + float *output_bias_vec = new float[OUTPUT_SIZE]; std::fill(hidden_bias_vec, hidden_bias_vec + HIDDEN_SIZE, 0); + std::fill(output_bias_vec, output_bias_vec + OUTPUT_SIZE, 0); float target_error = 0.0003; float total_error = 0.2; //will be modified @@ -287,8 +383,13 @@ int main(int argc, char* argv[]) { printFloatArray(OUTPUT_SIZE, array_target_outputs[i], true); std::cout << std::endl; } - //printFloatArray(INPUT_SIZE * HIDDEN_SIZE, weight_matrix, false); + + //printFloatArray(INPUT_SIZE * HIDDEN_SIZE, input_weight_matrix, true); + printFloatArray(OUTPUT_SIZE * HIDDEN_SIZE, hidden_weight_matrix, false); + + SGD(100, HIDDEN_SIZE, 3.0f, input_weight_matrix, hidden_weight_matrix, hidden_bias_vec, output_bias_vec); + printFloatArray(OUTPUT_SIZE * HIDDEN_SIZE, hidden_weight_matrix, false); //get input from files? //CharacterRecognition::MLP_calculation(INPUT_SIZE, OUTPUT_SIZE, input, weight_matrix, output); //printElapsedTime(CharacterRecognition::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); @@ -300,7 +401,10 @@ int main(int argc, char* argv[]) { //understand bp algorithm //how to make it keep running? -- while loop within a certain threshold - delete[] weight_matrix; + delete[] input_weight_matrix; + delete[] hidden_weight_matrix; + delete[] hidden_bias_vec; + delete[] output_bias_vec; // // Scan tests // printf("\n"); From 7236b65cdeadd620d22f3ddd76d3aaae5d920794 Mon Sep 17 00:00:00 2001 From: Tianming Xu Date: Wed, 18 Sep 2019 13:53:40 -0400 Subject: [PATCH 08/10] Update main.cpp --- Project2-Character-Recognition/src/main.cpp | 22 +++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Project2-Character-Recognition/src/main.cpp b/Project2-Character-Recognition/src/main.cpp index a8abd5a..60d2daf 100644 --- a/Project2-Character-Recognition/src/main.cpp +++ b/Project2-Character-Recognition/src/main.cpp @@ -167,10 +167,10 @@ void feed_forward(int n, int hidden_num, int output_num, float* idata, float* od int idx = row * n + col; float w = input_weight_matrix[idx]; float input = idata[col]; - sum += w * input + hidden_bias_vec[row]; + sum += w * input; } - temp_hidden[row] = sigmoid(sum); + temp_hidden[row] = sigmoid(sum + hidden_bias_vec[row]); } //from hidden to output @@ -183,10 +183,10 @@ void feed_forward(int n, int hidden_num, int output_num, float* idata, float* od int idx = row * hidden_num + col; float w = hidden_weight_matrix[idx]; float input = temp_hidden[col]; - sum += w * input + output_bias_vec[row]; + sum += w * input; } - odata[row] = sigmoid(sum); + odata[row] = sigmoid(sum + output_bias_vec[row]); } } @@ -208,11 +208,11 @@ void back_prop(int hidden_num, float* input_data, float* target_output_data, flo int idx = row * INPUT_SIZE + col; float w = input_weight_matrix[idx]; float input = input_data[col]; - sum += w * input + hidden_bias_vec[row]; + sum += w * input; } - hidden_weighted_input[row] = sum; - hidden_layer[row] = sigmoid(sum); + hidden_weighted_input[row] = sum + hidden_bias_vec[row]; + hidden_layer[row] = sigmoid(sum + hidden_bias_vec[row]); } for (int row = 0; row < OUTPUT_SIZE; ++row) @@ -223,12 +223,14 @@ void back_prop(int hidden_num, float* input_data, float* target_output_data, flo int idx = row * hidden_num + col; float w = hidden_weight_matrix[idx]; float input = hidden_layer[col]; - sum += w * input + output_bias_vec[row]; + sum += w * input; } - output_weighted_input[row] = sum; - output_layer[row] = sigmoid(sum); + output_weighted_input[row] = sum + output_bias_vec[row]; + output_layer[row] = sigmoid(sum + output_bias_vec[row]); } + + //output cost here //Get the cost derivative from the output layer result and target output data cost_derivative(OUTPUT_SIZE, target_output_data, output_layer, output_cost_error); From 79c6964a47a648f693d70be6c0759cbe971d5034 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 18 Sep 2019 17:19:06 -0400 Subject: [PATCH 09/10] hopefully work? --- Project2-Character-Recognition/src/main.cpp | 145 ++++++++++++++---- .../src/testing_helpers.hpp | 12 +- 2 files changed, 124 insertions(+), 33 deletions(-) diff --git a/Project2-Character-Recognition/src/main.cpp b/Project2-Character-Recognition/src/main.cpp index 60d2daf..e007bd2 100644 --- a/Project2-Character-Recognition/src/main.cpp +++ b/Project2-Character-Recognition/src/main.cpp @@ -23,7 +23,7 @@ const int INPUT_SIZE = 10201; // feel free to change the size of array const int OUTPUT_SIZE = 52; -const int HIDDEN_SIZE = 10; +const int HIDDEN_SIZE = 30; //float* input = new float[INPUT_SIZE]; //float* output = new float[OUTPUT_SIZE]; std::vector array_inputs; @@ -31,6 +31,8 @@ std::vector array_target_outputs; void Evaluate(int n, int hidden_num, int output_num, float* test_data, float* target_output, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec); + + void read_in_inputs() { std::string input_file_postfix = "info.txt"; @@ -74,17 +76,17 @@ void read_in_inputs() std::stringstream tokens(intermediate_array); std::string token; // Tokenizing w.r.t. space ' ' - int count = 0; + int array_count = 0; //the first is a space, clean it std::getline(tokens, token, ' '); while (std::getline(tokens, token, ' ')) { - input[count] = std::stoi(token); - count++; + input[array_count] = std::stoi(token) / 255; //normalize + array_count++; } - if (count != INPUT_SIZE) + if (array_count != INPUT_SIZE) { - std::cout << "count != INPUT_SIZE, something wrong" << std::endl; + std::cout << "array_count != INPUT_SIZE, something wrong" << std::endl; } } count++; @@ -153,11 +155,23 @@ void argmax(int output_num, float* curr_output) } } +float compute_cost(int size, float* target_output_data, float* output_layer) +{ + float sum = 0; + for (int i = 0; i < size; ++i) + { + sum += std::pow(target_output_data[i] - output_layer[i], 2); + } + + sum /= 2; + //std::cout << "The cost of current data is: " << sum << std::endl; + return sum; +} -void feed_forward(int n, int hidden_num, int output_num, float* idata, float* odata, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec) +void feed_forward(int n, int hidden_num, int output_num, int idata_idx, float* odata, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec) { float* temp_hidden = new float[hidden_num]; - + float* idata = array_inputs[idata_idx]; //input to hidden for (int row = 0; row < hidden_num; ++row) { @@ -173,6 +187,9 @@ void feed_forward(int n, int hidden_num, int output_num, float* idata, float* od temp_hidden[row] = sigmoid(sum + hidden_bias_vec[row]); } + //test + //std::cout << "the 2064 of element in this file is: " << idata[2064] << std::endl; + //printFloatArray(hidden_num, temp_hidden, false); //from hidden to output //input to hidden for (int row = 0; row < output_num; ++row) @@ -188,9 +205,11 @@ void feed_forward(int n, int hidden_num, int output_num, float* idata, float* od } odata[row] = sigmoid(sum + output_bias_vec[row]); } + + delete[] temp_hidden; } -void back_prop(int hidden_num, float* input_data, float* target_output_data, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec, float* updated_input_weight, float* updated_hidden_weight, float* updated_hidden_bias, float* updated_output_bias) +void back_prop(int hidden_num, float* cost, float* input_data, float* target_output_data, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec, float* updated_input_weight, float* updated_hidden_weight, float* updated_hidden_bias, float* updated_output_bias) { //generate intermediate hidden layer float* hidden_layer = new float[hidden_num]; @@ -199,6 +218,15 @@ void back_prop(int hidden_num, float* input_data, float* target_output_data, flo float* output_weighted_input = new float[OUTPUT_SIZE]; float* output_cost_error = new float[OUTPUT_SIZE]; float* hidden_cost_error = new float[hidden_num]; + + //initialize + std::fill(hidden_layer, hidden_layer + hidden_num, 0); + std::fill(output_layer, output_layer + OUTPUT_SIZE, 0); + std::fill(hidden_weighted_input, hidden_weighted_input + hidden_num, 0); + std::fill(output_weighted_input, output_weighted_input + OUTPUT_SIZE, 0); + std::fill(hidden_cost_error, hidden_cost_error + hidden_num, 0); + std::fill(output_cost_error, output_cost_error + OUTPUT_SIZE, 0); + //feedfoward for (int row = 0; row < hidden_num; ++row) { @@ -231,6 +259,7 @@ void back_prop(int hidden_num, float* input_data, float* target_output_data, flo } //output cost here + *cost += compute_cost(OUTPUT_SIZE, target_output_data, output_layer); //Get the cost derivative from the output layer result and target output data cost_derivative(OUTPUT_SIZE, target_output_data, output_layer, output_cost_error); @@ -239,11 +268,11 @@ void back_prop(int hidden_num, float* input_data, float* target_output_data, flo { output_cost_error[row] *= sigmoid_prime(output_weighted_input[row]); //assign to updated weights and bias for output - updated_output_bias[row] = output_cost_error[row]; + updated_output_bias[row] += output_cost_error[row]; for (int col = 0; col < hidden_num; ++col) { int mat_idx = row * hidden_num + col; - updated_hidden_weight[mat_idx] = hidden_layer[col] * output_cost_error[row]; + updated_hidden_weight[mat_idx] += hidden_layer[col] * output_cost_error[row]; } } @@ -260,11 +289,11 @@ void back_prop(int hidden_num, float* input_data, float* target_output_data, flo //apply sigmoid prime after we compute the derivative hidden_cost_error[row] *= sigmoid_prime(hidden_weighted_input[row]); //assign to updated matrix and vec - updated_hidden_bias[row] = hidden_cost_error[row]; + updated_hidden_bias[row] += hidden_cost_error[row]; for (int mat_col = 0; mat_col < INPUT_SIZE; ++mat_col) { int mat_idx = row * INPUT_SIZE + mat_col; - updated_input_weight[mat_idx] = input_data[mat_col] * hidden_cost_error[row]; + updated_input_weight[mat_idx] += input_data[mat_col] * hidden_cost_error[row]; } } } @@ -288,10 +317,12 @@ void SGD(int training_round, int hidden_size, float eta, float* input_weight_mat std::fill(updated_output_bias, updated_output_bias + OUTPUT_SIZE, 0); //memcpy(updated_hidden_bias, hidden_bias_vec, HIDDEN_SIZE * sizeof(float)); + std::cout << "Round " << round << ":" << std::endl; + float cost = 0; for (int input_index = 0; input_index < OUTPUT_SIZE; ++input_index) { //update each element in the buffer arrays directly in back_prop - back_prop(hidden_size, array_inputs[input_index], array_target_outputs[input_index], input_weight_matrix, hidden_weight_matrix, hidden_bias_vec, output_bias_vec, updated_input_weight, updated_hidden_weight, updated_hidden_bias, updated_output_bias); + back_prop(hidden_size,&cost, array_inputs[input_index], array_target_outputs[input_index], input_weight_matrix, hidden_weight_matrix, hidden_bias_vec, output_bias_vec, updated_input_weight, updated_hidden_weight, updated_hidden_bias, updated_output_bias); } //update the weights and bias after each round @@ -314,6 +345,10 @@ void SGD(int training_round, int hidden_size, float eta, float* input_weight_mat { output_bias_vec[output_bias_index] -= (eta / OUTPUT_SIZE) * updated_output_bias[output_bias_index]; } + + //output cost + cost /= OUTPUT_SIZE; + std::cout << "The cost of current data is: " << cost << std::endl; } std::cout << "we train " << training_round << " rounds" << std::endl; @@ -325,6 +360,22 @@ void SGD(int training_round, int hidden_size, float eta, float* input_weight_mat delete[] updated_output_bias; } +bool check_data(int* file_idx, int* array_idx) +{ + for (int i = 0; i < OUTPUT_SIZE - 1; ++i) + { + for (int j = 0; j < INPUT_SIZE; ++j) + { + if (array_inputs[i][j] != array_inputs[i + 1][j]) + { + *file_idx = i; + *array_idx = j; + return true; + } + } + } + return false; +} void Evaluate(int n, int hidden_num, int output_num, float* test_data, float* target_output, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec) { @@ -335,19 +386,32 @@ void Evaluate(int n, int hidden_num, int output_num, float* test_data, float* ta { //zero out output std::fill(curr_output, curr_output + output_num, 0); - feed_forward(n, hidden_num, output_num, array_inputs[i], curr_output, input_weight_matrix, hidden_weight_matrix, hidden_bias_vec, output_bias_vec); + //for (int j = 0; j < output_num; ++j) + //{ + // std::cout << "curr_output[" << j << "] is " << curr_output[j] << std::endl; + //} + + feed_forward(n, hidden_num, output_num, i, curr_output, input_weight_matrix, hidden_weight_matrix, hidden_bias_vec, output_bias_vec); argmax(output_num, curr_output); - if (curr_output[i] == array_target_outputs[i][i]) + bool same = true; + for (int j = 0; j < output_num; ++j) { - //hack, we know that that element in target_output should be 1, so if both are one, it is correct - sum++; + if (curr_output[j] != array_target_outputs[i][j]) + { + same = false; + } } - if (curr_output[i] == 1) + for (int j = 0; j < output_num; ++j) { - std::cout << "curr_output[" << i << "] is 1" << std::endl; + if (curr_output[j] == 1) + { + std::cout << "curr_output[" << j << "] is " << curr_output[j] << std::endl; + } } + + if (same) sum++; } @@ -363,12 +427,18 @@ int main(int argc, char* argv[]) { read_in_inputs(); //the data will be stored in array_inputs and array_outputs. + int file_idx = -1; + int array_idx = -1; + if (check_data(&file_idx, &array_idx)) + { + std::cout << "the data has different in " << file_idx << " file " << ", array index is " << array_idx << std::endl; + } //init input_weights and hidden weights float *input_weight_matrix = new float[INPUT_SIZE * HIDDEN_SIZE]; float *hidden_weight_matrix = new float[OUTPUT_SIZE * HIDDEN_SIZE]; - init_weight_matrix(INPUT_SIZE, HIDDEN_SIZE, input_weight_matrix, 0 , 0.1f); // Leave a 0 at the end to test that edge case - init_weight_matrix(OUTPUT_SIZE, HIDDEN_SIZE, hidden_weight_matrix, 0, 0.1f); + init_weight_matrix(INPUT_SIZE, HIDDEN_SIZE, input_weight_matrix, -0.1f , 0.1f); // Leave a 0 at the end to test that edge case + init_weight_matrix(OUTPUT_SIZE, HIDDEN_SIZE, hidden_weight_matrix, -0.1f, 0.1f); float *hidden_bias_vec = new float[HIDDEN_SIZE]; float *output_bias_vec = new float[OUTPUT_SIZE]; std::fill(hidden_bias_vec, hidden_bias_vec + HIDDEN_SIZE, 0); @@ -379,19 +449,21 @@ int main(int argc, char* argv[]) { float target_val = 1; //tested on XOR example -- should this be a //first generate the weight matrix to store all weight values for each element, in this homework, it should be a n * 2 matrix, such that n is the number of input - for (int i = 0; i < OUTPUT_SIZE; ++i) - { - printFloatArray(INPUT_SIZE, array_inputs[i], true); - printFloatArray(OUTPUT_SIZE, array_target_outputs[i], true); - std::cout << std::endl; - } - + //printFloatArray(INPUT_SIZE * HIDDEN_SIZE, input_weight_matrix, false); + //for (int i = 0; i < OUTPUT_SIZE; ++i) + //{ + // printFloatArray(INPUT_SIZE, array_inputs[i], false); + // printFloatArray(OUTPUT_SIZE, array_target_outputs[i], true); + // std::cout << std::endl; + //} + + //printFloatArray(OUTPUT_SIZE, array_target_outputs[1], true); //printFloatArray(INPUT_SIZE * HIDDEN_SIZE, input_weight_matrix, true); - printFloatArray(OUTPUT_SIZE * HIDDEN_SIZE, hidden_weight_matrix, false); + //printFloatArray(OUTPUT_SIZE * HIDDEN_SIZE, hidden_weight_matrix, false); - SGD(100, HIDDEN_SIZE, 3.0f, input_weight_matrix, hidden_weight_matrix, hidden_bias_vec, output_bias_vec); + SGD(500, HIDDEN_SIZE, 0.1f, input_weight_matrix, hidden_weight_matrix, hidden_bias_vec, output_bias_vec); - printFloatArray(OUTPUT_SIZE * HIDDEN_SIZE, hidden_weight_matrix, false); + //printFloatArray(OUTPUT_SIZE * HIDDEN_SIZE, hidden_weight_matrix, false); //get input from files? //CharacterRecognition::MLP_calculation(INPUT_SIZE, OUTPUT_SIZE, input, weight_matrix, output); //printElapsedTime(CharacterRecognition::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); @@ -407,6 +479,15 @@ int main(int argc, char* argv[]) { delete[] hidden_weight_matrix; delete[] hidden_bias_vec; delete[] output_bias_vec; + + //delete the whole array + //for (int i = 0; i < OUTPUT_SIZE; ++i) + //{ + // delete[] array_inputs[i]; + // delete[] array_target_outputs[i]; + //} + array_inputs.clear(); + array_target_outputs.clear(); // // Scan tests // printf("\n"); diff --git a/Project2-Character-Recognition/src/testing_helpers.hpp b/Project2-Character-Recognition/src/testing_helpers.hpp index a54da6a..f326316 100644 --- a/Project2-Character-Recognition/src/testing_helpers.hpp +++ b/Project2-Character-Recognition/src/testing_helpers.hpp @@ -63,7 +63,17 @@ void init_weight_matrix(int n, int hidden_num, float* weight_matrix, float rang srand(time(nullptr)); for (int i = 0; i < n * hidden_num; ++i) { - weight_matrix[i] = range_min + static_cast (rand()) / (static_cast (RAND_MAX / (range_max - range_min))); + weight_matrix[i] = range_min + (static_cast (rand()) / (static_cast (RAND_MAX))) * (range_max - range_min); + if (weight_matrix[i] > range_max) + { + std::cout << "weight matrix index " << i << " has problem, its value is " << weight_matrix[i] << std::endl; + weight_matrix[i] = range_max; + } + else if (weight_matrix[i] < range_min) + { + std::cout << "weight matrix index " << i << " has problem, its value is " << weight_matrix[i] << std::endl; + weight_matrix[i] = range_min; + } } } From a9eba921833c5b69efb4079afc6203bd75b03cf3 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 18 Sep 2019 23:31:51 -0400 Subject: [PATCH 10/10] submit whole project --- Project2-Character-Recognition/README.md | 30 +- .../character_recognition/common.h | 1 + .../character_recognition/mlp.cu | 512 +++++++++--- .../character_recognition/mlp.h | 12 +- Project2-Character-Recognition/img/output.PNG | Bin 0 -> 39671 bytes Project2-Character-Recognition/src/main.cpp | 760 +++++++----------- 6 files changed, 709 insertions(+), 606 deletions(-) create mode 100644 Project2-Character-Recognition/img/output.PNG diff --git a/Project2-Character-Recognition/README.md b/Project2-Character-Recognition/README.md index 4503fac..c6fdc0f 100644 --- a/Project2-Character-Recognition/README.md +++ b/Project2-Character-Recognition/README.md @@ -3,12 +3,30 @@ CUDA Character Recognition **University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 2** -* (TODO) YOUR NAME HERE - * (TODO) [LinkedIn](), [personal website](), [twitter](), etc. -* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab) +* #### Author information + + - Tianming Xu (Mark) + - www.linkedin.com/in/tianming-xu-8bb81816a (LinkedIn) + - Tested on: Windows 10, i7-8700 @ 3.20GHz 16GB, GTX 2080 8192MB (my personal desktop) -### (TODO: Your README) +### Output Screenshots(50 Epochs and 30 neuron) -Include analysis, etc. (Remember, this is public, so don't put -anything here that you don't want to share with the world.) +![](img/output.PNG) +The cost drops from 6 to around + +#### Features + +I implemented a file loader to load in all the training data from the data-set folder and input that to the neural network class. In the class, according to how many epochs we will train, I will loop through all the training data I have in the back propagation algorithm, applying back the delta of weights and bias. The cost of out data set drops significantly after 5 epochs. + + + +#### Performance Analysis + +Due to time constraint, I don't have time to do a full performance analysis. However, the GPU can help me handle many matrix operations so, it should be more efficient than the CPU version. + + + +#### Acknowledgement + +This homework I especially want to thank Jiangping Xu for helping me figure out the concept of neural network. Also, http://neuralnetworksanddeeplearning.com/ is a fantastic website which describes the concept of neural network concisely. There are python codes for a hand written number recognition program, which helps me a lot for implementing my character recognition program. \ No newline at end of file diff --git a/Project2-Character-Recognition/character_recognition/common.h b/Project2-Character-Recognition/character_recognition/common.h index a602b92..ac6a52b 100644 --- a/Project2-Character-Recognition/character_recognition/common.h +++ b/Project2-Character-Recognition/character_recognition/common.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include diff --git a/Project2-Character-Recognition/character_recognition/mlp.cu b/Project2-Character-Recognition/character_recognition/mlp.cu index ed721ac..37a95d7 100644 --- a/Project2-Character-Recognition/character_recognition/mlp.cu +++ b/Project2-Character-Recognition/character_recognition/mlp.cu @@ -18,15 +18,63 @@ namespace CharacterRecognition { float* device_hidden; float* device_ji_buffer; float* device_output; + + //directly used in class function + int INPUT_SIZE = 0; + int HIDDEN_SIZE = 0; + int OUTPUT_SIZE = 0; + + std::vector array_inputs; + std::vector array_target_outputs; - // TODO: __global__ - __global__ void kernInputMultWeight(int n, float* idata, float* weight_matrix, float* odata) + void initSimulation(int num_object, int hidden_num, int output_num, std::vector inputs, std::vector target_outputs) + { + CharacterRecognition::INPUT_SIZE = num_object; + CharacterRecognition::HIDDEN_SIZE = hidden_num; + CharacterRecognition::OUTPUT_SIZE = output_num; + + array_inputs.assign(inputs.begin(), inputs.end()); + array_target_outputs.assign(target_outputs.begin(), target_outputs.end()); + } + + float sigmoid(float z) + { + return 1 / (1 + std::pow(exp(1.0), -z)); + } + + float sigmoid_prime(float z) + { + return sigmoid(z) * (1 - sigmoid(z)); + } + + //kernel function + void cost_derivative(int n, float* target_output, float* actual_output, float* error_vec) + { + for (int i = 0; i < n; ++i) + { + error_vec[i] = actual_output[i] - target_output[i]; + } + } + + __global__ void kernCostDerivative(int n, float* target_output, float* actual_output, float* error_vec) { int index = threadIdx.x + (blockIdx.x * blockDim.x); if (index >= n) { return; } + + error_vec[index] = actual_output[index] - target_output[index]; + } + + // TODO: __global__ + __global__ void kernInputMultWeight(int max_bound, int n, float* idata, float* weight_matrix, float* odata) + { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= max_bound) + { + return; + } int row = index; float sum = 0; @@ -39,177 +87,381 @@ namespace CharacterRecognition { } odata[row] = sum; - //https://stackoverflow.com/questions/10375680/using-stdvector-in-cuda-device-code -- have to use thrust library, but don't know how - //float returned_val = 1 / (1 + thrust::pow((float)exp(1.0), -sum)); - //odata[row] = returned_val; } - - //the same as inputMultWeight, except the max bound is different, here it is constrained by output_num - __global__ void kernHiddenMultWeight(int max_bound, int n, float* idata, float* weight_matrix, float* odata) + //kernel? + void argmax(int output_num, float* curr_output) { - int index = threadIdx.x + (blockIdx.x * blockDim.x); - if (index >= max_bound) + int max_idx = -1; + float max = 0; + for (int i = 0; i < output_num; ++i) { - return; + if (curr_output[i] >= max) + { + max_idx = i; + max = curr_output[i]; + } } - int row = index; + for (int i = 0; i < output_num; ++i) + { + if (i == max_idx) { + curr_output[i] = 1; + } + else curr_output[i] = 0; + } + } - float sum = 0; - for (int col = 0; col < n; ++col) + void feed_forward(int n, int hidden_num, int output_num, int idata_idx, float* odata, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec) + { + float* temp_hidden = new float[hidden_num]; + float* idata = array_inputs[idata_idx]; + //input to hidden + for (int row = 0; row < hidden_num; ++row) { - int idx = row * n + col; - sum += weight_matrix[idx] * idata[col]; //weight's row is fixed, but col different, similarly ,the weight corresponds to what element in idata + float sum = 0; + for (int col = 0; col < n; ++col) + { + int idx = row * n + col; + float w = input_weight_matrix[idx]; + float input = idata[col]; + sum += w * input; + + } + temp_hidden[row] = sigmoid(sum + hidden_bias_vec[row]); } - odata[row] = sum; - } + //test + //std::cout << "the 2064 of element in this file is: " << idata[2064] << std::endl; + //printFloatArray(hidden_num, temp_hidden, false); + //from hidden to output + //input to hidden + for (int row = 0; row < output_num; ++row) + { + float sum = 0; + for (int col = 0; col < hidden_num; ++col) + { + int idx = row * hidden_num + col; + float w = hidden_weight_matrix[idx]; + float input = temp_hidden[col]; + sum += w * input; + + } + odata[row] = sigmoid(sum + output_bias_vec[row]); + } - //better to use something like thrust library and calculate the reduction -- very slow - //__global__ void kernKJBufferToHidden(int n, float* idata, float* odata) - //{ - // int index = threadIdx.x + (blockIdx.x * blockDim.x); - // if (index >= n) - // { - // return; - // } - - // float sum = 0; - // for (int idx = index * n; idx < (index + 1) * n; ++idx) - // { - // sum += idata[idx]; - // } - - // float returned_val = 1 / (1 + std::pow(exp(1.0), -sum)); - // odata[index] = returned_val; - //} - - ////similar to kj one, should have a better naming - //__global__ void kernJIBufferToOutput(int n, float* idata, float* odata) - //{ - // int index = threadIdx.x + (blockIdx.x * blockDim.x); - // if (index >= n) - // { - // return; - // } - - // float sum = 0; - // for (int idx = index * n; idx < (index + 1) * n; ++idx) - // { - // sum += idata[idx]; - // } - - // float returned_val = 1 / (1 + std::pow(exp(1.0), -sum)); - // odata[index] = returned_val; - //} - /** - * Example of use case (follow how you did it in stream compaction) - */ - /*void scan(int n, int *odata, const int *idata) { - timer().startGpuTimer(); - // TODO - timer().endGpuTimer(); + delete[] temp_hidden; } - */ - //void CharacterRecognition::initSimulation(int n, int output_num) - //{ - // cudaMalloc((void**)&device_input, n * sizeof(float)); - // checkCUDAError("cudaMalloc device_input failed!"); - // cudaMalloc((void**)&device_kj_buffer, n * n * sizeof(float)); - // checkCUDAError("cudaMalloc device_kj_buffer failed!"); - // cudaMalloc((void**)&device_hidden, n * sizeof(float)); - // checkCUDAError("cudaMalloc device_hidden failed!"); - // cudaMalloc((void**)&device_ji_buffer, output_num * n * sizeof(float)); - // checkCUDAError("cudaMalloc device_ji_buffer failed!"); - // cudaMalloc((void**)&device_output, output_num * sizeof(float)); - // checkCUDAError("cudaMalloc device_output failed!"); - //} - // TODO: implement required elements for MLP sections 1 and 2 here - - //feedforward functionality here, Sigmoid graduate descedent only need to add the number of group of training data and for loop them - void MLP_calculation(int n, int output_num, float* idata, float* weight_matrix, float* odata) + + void back_prop(float* cost, float* input_data, float* target_output_data, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec, float* updated_input_weight, float* updated_hidden_weight, float* updated_hidden_bias, float* updated_output_bias) { - //init buffer - cudaMalloc((void**)&device_input, n * sizeof(float)); - checkCUDAError("cudaMalloc device_input failed!"); - cudaMalloc((void**)&device_weight_matrix, n * n * sizeof(float)); - checkCUDAError("cudaMalloc device_weight_matrix failed!"); - cudaMalloc((void**)&device_hidden, n * sizeof(float)); - checkCUDAError("cudaMalloc device_hidden failed!"); - cudaMalloc((void**)&device_ji_buffer, output_num * n * sizeof(float)); - checkCUDAError("cudaMalloc device_ji_buffer failed!"); - cudaMalloc((void**)&device_output, output_num * sizeof(float)); - checkCUDAError("cudaMalloc device_output failed!"); - - //two temp main memory buffer - float* temp_hidden = new float[n]; - - cudaMemcpy(device_input, idata, n * sizeof(float), cudaMemcpyHostToDevice); - checkCUDAError("cudaMemcpy idata to device_input failed!"); - - cudaMemcpy(device_weight_matrix, weight_matrix, n * n * sizeof(float), cudaMemcpyHostToDevice); - checkCUDAError("cudaMemcpy idata to device_weight_matrix failed!"); - - int gridSize = (n + BLOCK_SIZE - 1) / BLOCK_SIZE; + //generate intermediate hidden layer + float* hidden_layer = new float[HIDDEN_SIZE]; + float* output_layer = new float[OUTPUT_SIZE]; + float* hidden_weighted_input = new float[HIDDEN_SIZE]; + float* output_weighted_input = new float[OUTPUT_SIZE]; + float* hidden_cost_error = new float[HIDDEN_SIZE]; + float* output_cost_error = new float[OUTPUT_SIZE]; + + + float* device_hidden_layer; + float* device_output_layer; + float* device_hidden_weighted_input; + float* device_output_weighted_input; + float* device_hidden_cost_error; + float* device_output_cost_error; + float* device_input_data; + float* device_target_output_data; + float* device_input_weight_mat; + float* device_hidden_weight_mat; + + + cudaMalloc((void**)&device_hidden_layer, HIDDEN_SIZE * sizeof(float)); + checkCUDAError("cudaMalloc device_hidden_layer failed!"); + cudaMalloc((void**)&device_output_layer, OUTPUT_SIZE * sizeof(float)); + checkCUDAError("cudaMalloc device_output_layer failed!"); + cudaMalloc((void**)&device_hidden_weighted_input, HIDDEN_SIZE * sizeof(float)); + checkCUDAError("cudaMalloc device_hidden_weighted_input failed!"); + cudaMalloc((void**)&device_output_weighted_input, OUTPUT_SIZE * sizeof(float)); + checkCUDAError("cudaMalloc device_output_weighted_input failed!"); + cudaMalloc((void**)&device_hidden_cost_error, HIDDEN_SIZE * sizeof(float)); + checkCUDAError("cudaMalloc device_hidden_cost_error failed!"); + cudaMalloc((void**)&device_output_cost_error, OUTPUT_SIZE * sizeof(float)); + checkCUDAError("cudaMalloc device_output_cost_error failed!"); + cudaMalloc((void**)&device_input_data, INPUT_SIZE * sizeof(float)); + checkCUDAError("cudaMalloc device_input_data failed!"); + cudaMalloc((void**)&device_target_output_data, OUTPUT_SIZE * sizeof(float)); + checkCUDAError("cudaMalloc device_target_output_data failed!"); + cudaMalloc((void**)&device_input_weight_mat, HIDDEN_SIZE * INPUT_SIZE * sizeof(float)); + checkCUDAError("cudaMalloc device_hidden_weight_mat failed!"); + cudaMalloc((void**)&device_hidden_weight_mat, HIDDEN_SIZE * OUTPUT_SIZE * sizeof(float)); + checkCUDAError("cudaMalloc device_output_weight_mat failed!"); + + //memcpy input and target output + cudaMemcpy(device_input_data, input_data, INPUT_SIZE * sizeof(float), cudaMemcpyHostToDevice); + cudaMemcpy(device_target_output_data, target_output_data, OUTPUT_SIZE * sizeof(float), cudaMemcpyHostToDevice); + cudaMemcpy(device_input_weight_mat, input_weight_matrix, HIDDEN_SIZE * INPUT_SIZE * sizeof(float), cudaMemcpyHostToDevice); + cudaMemcpy(device_hidden_weight_mat, hidden_weight_matrix, HIDDEN_SIZE * OUTPUT_SIZE * sizeof(float), cudaMemcpyHostToDevice); + + cudaMemset(device_hidden_layer, 0, HIDDEN_SIZE * sizeof(float)); + cudaMemset(device_output_layer, 0, OUTPUT_SIZE * sizeof(float)); + cudaMemset(device_hidden_weighted_input, 0, HIDDEN_SIZE * sizeof(float)); + cudaMemset(device_output_weighted_input, 0, OUTPUT_SIZE * sizeof(float)); + cudaMemset(device_hidden_cost_error, 0, HIDDEN_SIZE * sizeof(float)); + cudaMemset(device_output_cost_error, 0, OUTPUT_SIZE * sizeof(float)); + + int gridSize = (INPUT_SIZE + BLOCK_SIZE - 1) / BLOCK_SIZE; dim3 normalBlocksPerGrid(gridSize); - //int kjBufferSize = (n * n + BLOCK_SIZE - 1) / BLOCK_SIZE; - //dim3 kjBufferBlocksPerGrid(kjBufferSize); + int hiddenBufferSize = (HIDDEN_SIZE + BLOCK_SIZE - 1) / BLOCK_SIZE; + dim3 hiddenBufferBlocksPerGrid(hiddenBufferSize); //int jiBufferSize = (output_num * n + BLOCK_SIZE - 1) / BLOCK_SIZE; //dim3 jiBufferBlocksPerGrid(jiBufferSize); - int outputSize = (output_num + BLOCK_SIZE - 1) / BLOCK_SIZE; + int outputSize = (OUTPUT_SIZE + BLOCK_SIZE - 1) / BLOCK_SIZE; dim3 outputBlocksPerGrid(outputSize); dim3 threadsPerBlock(BLOCK_SIZE); + //initialize + std::fill(hidden_layer, hidden_layer + HIDDEN_SIZE, 0); + std::fill(output_layer, output_layer + OUTPUT_SIZE, 0); + std::fill(hidden_weighted_input, hidden_weighted_input + HIDDEN_SIZE, 0); + std::fill(output_weighted_input, output_weighted_input + OUTPUT_SIZE, 0); + std::fill(hidden_cost_error, hidden_cost_error + HIDDEN_SIZE, 0); + std::fill(output_cost_error, output_cost_error + OUTPUT_SIZE, 0); - //we first calcualte all the results into a matrix, and then read and sum each row in activate function and write in the hidden layer timer().startGpuTimer(); - kernInputMultWeight << < normalBlocksPerGrid, threadsPerBlock >> > (n, device_input, device_weight_matrix, device_hidden); + kernInputMultWeight << < hiddenBufferBlocksPerGrid, threadsPerBlock >> > (HIDDEN_SIZE, INPUT_SIZE, device_input_data, device_input_weight_mat, device_hidden_layer); timer().endGpuTimer(); //need to apply the activate function on each element, currently each element is only the sum. //copy to a temp array and copmute sequentially for now - cudaMemcpy(temp_hidden, device_hidden, n * sizeof(float), cudaMemcpyDeviceToHost); - checkCUDAError("cudaMemcpy device_hidden to temp_hidden failed!"); + cudaMemcpy(hidden_layer, device_hidden_layer, HIDDEN_SIZE * sizeof(float), cudaMemcpyDeviceToHost); + checkCUDAError("cudaMemcpy device_hidden_layer to hidden_layer failed!"); //compute activation function - for (int i = 0; i < n; ++i) + for (int i = 0; i < HIDDEN_SIZE; ++i) { - temp_hidden[i] = 1 / (1 + std::pow(exp(1.0), -temp_hidden[i])); + hidden_weighted_input[i] = hidden_layer[i] + hidden_bias_vec[i]; + hidden_layer[i] = sigmoid(hidden_weighted_input[i]); } //copy back to hidden - cudaMemcpy(device_hidden, temp_hidden, n * sizeof(float), cudaMemcpyHostToDevice); - checkCUDAError("cudaMemcpy temp_hidden to device_hidden failed!"); - - - //legacy - //kernKJBufferToHidden << < normalBlocksPerGrid, threadsPerBlock >> > (n, device_kj_buffer, device_hidden); - //kernHiddenMultWeight << < jiBufferBlocksPerGrid, threadsPerBlock >> > (output_num*n, n, device_hidden, weight_matrix, device_ji_buffer); - //kernJIBufferToOutput << < outputBlocksPerGrid, threadsPerBlock >> > (n, device_ji_buffer, device_output); - + cudaMemcpy(device_hidden_layer, hidden_layer, HIDDEN_SIZE * sizeof(float), cudaMemcpyHostToDevice); + checkCUDAError("cudaMemcpy hidden_layer to device_hidden_layer failed!"); timer().startGpuTimer(); //do we use the same weight? -- in XOR example, it is not - kernHiddenMultWeight << < outputBlocksPerGrid, threadsPerBlock >> > (output_num, n, device_hidden, device_weight_matrix, device_output); + kernInputMultWeight << < outputBlocksPerGrid, threadsPerBlock >> > (OUTPUT_SIZE, HIDDEN_SIZE, device_hidden_layer, device_hidden_weight_mat, device_output_layer); timer().endGpuTimer(); //how to calculate the error and affect the next weights? -- how to get expcted result? -- read in? - cudaMemcpy(odata, device_output, output_num * sizeof(float), cudaMemcpyDeviceToHost); + cudaMemcpy(output_layer, device_output_layer, OUTPUT_SIZE * sizeof(float), cudaMemcpyDeviceToHost); checkCUDAError("cudaMemcpy device_output to odata failed!"); - //do the activaition functoin here + for (int i = 0; i < OUTPUT_SIZE; ++i) + { + output_weighted_input[i] = output_layer[i] + output_bias_vec[i]; + output_layer[i] = sigmoid(output_weighted_input[i]); + } + + //output cost here + *cost += compute_cost(OUTPUT_SIZE, target_output_data, output_layer); + + //Get the cost derivative from the output layer result and target output data + //test + cudaMemcpy(device_output_layer, output_layer, OUTPUT_SIZE * sizeof(float), cudaMemcpyHostToDevice); + kernCostDerivative <<< outputBlocksPerGrid, threadsPerBlock >> > (OUTPUT_SIZE, device_target_output_data, device_output_layer, device_output_cost_error); + cudaMemcpy(output_cost_error, device_output_cost_error, OUTPUT_SIZE * sizeof(float), cudaMemcpyDeviceToHost); + //add the sigmoid prime to it + for (int row = 0; row < OUTPUT_SIZE; ++row) + { + output_cost_error[row] *= sigmoid_prime(output_weighted_input[row]); + //assign to updated weights and bias for output + updated_output_bias[row] += output_cost_error[row]; + for (int col = 0; col < HIDDEN_SIZE; ++col) + { + int mat_idx = row * HIDDEN_SIZE + col; + updated_hidden_weight[mat_idx] += hidden_layer[col] * output_cost_error[row]; + } + } + + //compute the hidden_cost_error by output_cost_error + //use transpose index + for (int row = 0; row < HIDDEN_SIZE; ++row) + { + for (int col = 0; col < OUTPUT_SIZE; ++col) + { + int mat_idx = row * OUTPUT_SIZE + col; + hidden_cost_error[row] += hidden_weight_matrix[mat_idx] * output_cost_error[col]; + } + + //apply sigmoid prime after we compute the derivative + hidden_cost_error[row] *= sigmoid_prime(hidden_weighted_input[row]); + //assign to updated matrix and vec + updated_hidden_bias[row] += hidden_cost_error[row]; + for (int mat_col = 0; mat_col < INPUT_SIZE; ++mat_col) + { + int mat_idx = row * INPUT_SIZE + mat_col; + updated_input_weight[mat_idx] += input_data[mat_col] * hidden_cost_error[row]; + } + } + + cudaFree(device_hidden_layer); + cudaFree(device_output_layer); + cudaFree(device_hidden_weighted_input); + cudaFree(device_output_weighted_input); + cudaFree(device_hidden_cost_error); + cudaFree(device_output_cost_error); + cudaFree(device_input_data); + cudaFree(device_target_output_data); + + delete[] hidden_layer; + delete[] output_layer; + delete[] hidden_weighted_input; + delete[] output_weighted_input; + delete[] hidden_cost_error; + delete[] output_cost_error; + } + + //SGD + //we use training data as array_input and array_target_output, same as test data -- probably all size information should be inputs + void SGD(int training_round, int hidden_size, float eta, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec) + { + //init necessary buffers + float* updated_input_weight = new float[INPUT_SIZE * hidden_size]; + float* updated_hidden_weight = new float[OUTPUT_SIZE * hidden_size]; + float* updated_hidden_bias = new float[hidden_size]; + float* updated_output_bias = new float[OUTPUT_SIZE]; + + //need cuda malloc to init + + for (int round = 0; round < training_round; ++round) + { + //zero out all components for new round of change + std::fill(updated_input_weight, updated_input_weight + INPUT_SIZE * hidden_size, 0); + std::fill(updated_hidden_weight, updated_hidden_weight + OUTPUT_SIZE * hidden_size, 0); + std::fill(updated_hidden_bias, updated_hidden_bias + hidden_size, 0); + std::fill(updated_output_bias, updated_output_bias + OUTPUT_SIZE, 0); + //memcpy(updated_hidden_bias, hidden_bias_vec, HIDDEN_SIZE * sizeof(float)); + + std::cout << "Round " << round << ":" << std::endl; + float cost = 0; + for (int input_index = 0; input_index < OUTPUT_SIZE; ++input_index) + { + //update each element in the buffer arrays directly in back_prop + back_prop(&cost, array_inputs[input_index], array_target_outputs[input_index], input_weight_matrix, hidden_weight_matrix, hidden_bias_vec, output_bias_vec, updated_input_weight, updated_hidden_weight, updated_hidden_bias, updated_output_bias); + } + + //update the weights and bias after each round + for (int input_weight_index = 0; input_weight_index < INPUT_SIZE * hidden_size; ++input_weight_index) + { + input_weight_matrix[input_weight_index] -= (eta / OUTPUT_SIZE) * updated_input_weight[input_weight_index]; + } + + for (int hidden_weight_index = 0; hidden_weight_index < OUTPUT_SIZE * hidden_size; ++hidden_weight_index) + { + hidden_weight_matrix[hidden_weight_index] -= (eta / OUTPUT_SIZE) * updated_hidden_weight[hidden_weight_index]; + } + + for (int hidden_bias_index = 0; hidden_bias_index < hidden_size; ++hidden_bias_index) + { + hidden_bias_vec[hidden_bias_index] -= (eta / OUTPUT_SIZE) * updated_hidden_bias[hidden_bias_index]; + } + + for (int output_bias_index = 0; output_bias_index < OUTPUT_SIZE; ++output_bias_index) + { + output_bias_vec[output_bias_index] -= (eta / OUTPUT_SIZE) * updated_output_bias[output_bias_index]; + } + + //output cost + cost /= OUTPUT_SIZE; + std::cout << "The cost of current data is: " << cost << std::endl; + } + + std::cout << "we train " << training_round << " rounds" << std::endl; + Evaluate(INPUT_SIZE, HIDDEN_SIZE, OUTPUT_SIZE, nullptr, nullptr, input_weight_matrix, hidden_weight_matrix, hidden_bias_vec, output_bias_vec); + + delete[] updated_input_weight; + delete[] updated_hidden_weight; + delete[] updated_hidden_bias; + delete[] updated_output_bias; + } + + float compute_cost(int size, float* target_output_data, float* output_layer) + { + float sum = 0; + for (int i = 0; i < size; ++i) + { + sum += std::pow(target_output_data[i] - output_layer[i], 2); + } + + sum /= 2; + //std::cout << "The cost of current data is: " << sum << std::endl; + return sum; + } + + void Evaluate(int n, int hidden_num, int output_num, float* test_data, float* target_output, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec) + { + int sum = 0; + float* curr_output = new float[output_num]; + //unused (test_data, target_output) --> will use later for (int i = 0; i < output_num; ++i) { - odata[i] = 1 / (1 + std::pow(exp(1.0), -odata[i])); + //zero out output + std::fill(curr_output, curr_output + output_num, 0); + //for (int j = 0; j < output_num; ++j) + //{ + // std::cout << "curr_output[" << j << "] is " << curr_output[j] << std::endl; + //} + + feed_forward(n, hidden_num, output_num, i, curr_output, input_weight_matrix, hidden_weight_matrix, hidden_bias_vec, output_bias_vec); + + argmax(output_num, curr_output); + bool same = true; + for (int j = 0; j < output_num; ++j) + { + if (curr_output[j] != array_target_outputs[i][j]) + { + same = false; + } + } + + for (int j = 0; j < output_num; ++j) + { + if (curr_output[j] == 1) + { + std::cout << "curr_output[" << j << "] is " << curr_output[j] << std::endl; + } + } + + if (same) sum++; + } - cudaFree(device_input); - cudaFree(device_weight_matrix); - cudaFree(device_hidden); - cudaFree(device_ji_buffer); - cudaFree(device_output); - delete[] temp_hidden; + std::cout << "Result: " << sum << " / " << output_num << std::endl; + + delete[] curr_output; + } + + //the same as inputMultWeight, except the max bound is different, here it is constrained by output_num + __global__ void kernHiddenMultWeight(int max_bound, int n, float* idata, float* weight_matrix, float* odata) + { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= max_bound) + { + return; + } + + int row = index; + + float sum = 0; + for (int col = 0; col < n; ++col) + { + int idx = row * n + col; + sum += weight_matrix[idx] * idata[col]; //weight's row is fixed, but col different, similarly ,the weight corresponds to what element in idata + } + + odata[row] = sum; } + } diff --git a/Project2-Character-Recognition/character_recognition/mlp.h b/Project2-Character-Recognition/character_recognition/mlp.h index c7404d7..04e4831 100644 --- a/Project2-Character-Recognition/character_recognition/mlp.h +++ b/Project2-Character-Recognition/character_recognition/mlp.h @@ -6,7 +6,13 @@ namespace CharacterRecognition { Common::PerformanceTimer& timer(); // TODO: implement required elements for MLP sections 1 and 2 here - //void initSimulation(int n, int output_num); - //void SGD(); - void MLP_calculation(int n, int output_num, float* idata, float* weight_matrix, float* odata); + void initSimulation(int num_object, int hidden_num, int output_num, std::vector inputs, std::vector target_outputs); + float sigmoid(float z); + float sigmoid_prime(float z); + void feed_forward(int n, int hidden_num, int output_num, int idata_idx, float* odata, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec); + void argmax(int output_num, float* curr_output); + void SGD(int training_round, int hidden_size, float eta, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec); + void back_prop(float* cost, float* input_data, float* target_output_data, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec, float* updated_input_weight, float* updated_hidden_weight, float* updated_hidden_bias, float* updated_output_bias); + void Evaluate(int n, int hidden_num, int output_num, float* test_data, float* target_output, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec); + float compute_cost(int size, float* target_output_data, float* output_layer); } diff --git a/Project2-Character-Recognition/img/output.PNG b/Project2-Character-Recognition/img/output.PNG new file mode 100644 index 0000000000000000000000000000000000000000..91568f78498d426605cc86e7a24e144453d1cb8c GIT binary patch literal 39671 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 rbyW{`vV226GhyIgM^fQkUY~FLNx13K +#include #include #include #include @@ -31,8 +33,6 @@ std::vector array_target_outputs; void Evaluate(int n, int hidden_num, int output_num, float* test_data, float* target_output, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec); - - void read_in_inputs() { std::string input_file_postfix = "info.txt"; @@ -100,266 +100,252 @@ void read_in_inputs() //first do in cpu version //utility -//def evaluate(self, test_data) : -// """Return the number of test inputs for which the neural -// network outputs the correct result.Note that the neural -// network's output is assumed to be the index of whichever -// neuron in the final layer has the highest activation.""" -// test_results = [(np.argmax(self.feedforward(x)), y) -// for (x, y) in test_data] -// return sum(int(x == y) for (x, y) in test_results) - -//def cost_derivative(self, output_activations, y) : -//"""Return the vector of partial derivatives \partial C_x / -//\partial a for the output activations.""" - //return (output_activations - y) - - -float sigmoid(float z) -{ - return 1 / (1 + std::pow(exp(1.0), -z)); -} - -float sigmoid_prime(float z) -{ - return sigmoid(z) * (1 - sigmoid(z)); -} - -void cost_derivative(int n, float* target_output, float* actual_output, float* error_vec) -{ - for (int i = 0; i < n; ++i) - { - error_vec[i] = actual_output[i] - target_output[i]; - } -} - -void argmax(int output_num, float* curr_output) -{ - int max_idx = -1; - float max = 0; - for (int i = 0; i < output_num; ++i) - { - if (curr_output[i] >= max) - { - max_idx = i; - max = curr_output[i]; - } - } - - for (int i = 0; i < output_num; ++i) - { - if (i == max_idx) { - curr_output[i] = 1; - } - else curr_output[i] = 0; - } -} - -float compute_cost(int size, float* target_output_data, float* output_layer) -{ - float sum = 0; - for (int i = 0; i < size; ++i) - { - sum += std::pow(target_output_data[i] - output_layer[i], 2); - } - - sum /= 2; - //std::cout << "The cost of current data is: " << sum << std::endl; - return sum; -} - -void feed_forward(int n, int hidden_num, int output_num, int idata_idx, float* odata, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec) -{ - float* temp_hidden = new float[hidden_num]; - float* idata = array_inputs[idata_idx]; - //input to hidden - for (int row = 0; row < hidden_num; ++row) - { - float sum = 0; - for (int col = 0; col < n; ++col) - { - int idx = row * n + col; - float w = input_weight_matrix[idx]; - float input = idata[col]; - sum += w * input; - - } - temp_hidden[row] = sigmoid(sum + hidden_bias_vec[row]); - } - - //test - //std::cout << "the 2064 of element in this file is: " << idata[2064] << std::endl; - //printFloatArray(hidden_num, temp_hidden, false); - //from hidden to output - //input to hidden - for (int row = 0; row < output_num; ++row) - { - float sum = 0; - for (int col = 0; col < hidden_num; ++col) - { - int idx = row * hidden_num + col; - float w = hidden_weight_matrix[idx]; - float input = temp_hidden[col]; - sum += w * input; - - } - odata[row] = sigmoid(sum + output_bias_vec[row]); - } - - delete[] temp_hidden; -} - -void back_prop(int hidden_num, float* cost, float* input_data, float* target_output_data, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec, float* updated_input_weight, float* updated_hidden_weight, float* updated_hidden_bias, float* updated_output_bias) -{ - //generate intermediate hidden layer - float* hidden_layer = new float[hidden_num]; - float* output_layer = new float[OUTPUT_SIZE]; - float* hidden_weighted_input = new float[hidden_num]; - float* output_weighted_input = new float[OUTPUT_SIZE]; - float* output_cost_error = new float[OUTPUT_SIZE]; - float* hidden_cost_error = new float[hidden_num]; - - //initialize - std::fill(hidden_layer, hidden_layer + hidden_num, 0); - std::fill(output_layer, output_layer + OUTPUT_SIZE, 0); - std::fill(hidden_weighted_input, hidden_weighted_input + hidden_num, 0); - std::fill(output_weighted_input, output_weighted_input + OUTPUT_SIZE, 0); - std::fill(hidden_cost_error, hidden_cost_error + hidden_num, 0); - std::fill(output_cost_error, output_cost_error + OUTPUT_SIZE, 0); - - //feedfoward - for (int row = 0; row < hidden_num; ++row) - { - float sum = 0; - for (int col = 0; col < INPUT_SIZE; ++col) - { - int idx = row * INPUT_SIZE + col; - float w = input_weight_matrix[idx]; - float input = input_data[col]; - sum += w * input; - - } - hidden_weighted_input[row] = sum + hidden_bias_vec[row]; - hidden_layer[row] = sigmoid(sum + hidden_bias_vec[row]); - } - - for (int row = 0; row < OUTPUT_SIZE; ++row) - { - float sum = 0; - for (int col = 0; col < hidden_num; ++col) - { - int idx = row * hidden_num + col; - float w = hidden_weight_matrix[idx]; - float input = hidden_layer[col]; - sum += w * input; - - } - output_weighted_input[row] = sum + output_bias_vec[row]; - output_layer[row] = sigmoid(sum + output_bias_vec[row]); - } - - //output cost here - *cost += compute_cost(OUTPUT_SIZE, target_output_data, output_layer); - - //Get the cost derivative from the output layer result and target output data - cost_derivative(OUTPUT_SIZE, target_output_data, output_layer, output_cost_error); - //add the sigmoid prime to it - for (int row = 0; row < OUTPUT_SIZE; ++row) - { - output_cost_error[row] *= sigmoid_prime(output_weighted_input[row]); - //assign to updated weights and bias for output - updated_output_bias[row] += output_cost_error[row]; - for (int col = 0; col < hidden_num; ++col) - { - int mat_idx = row * hidden_num + col; - updated_hidden_weight[mat_idx] += hidden_layer[col] * output_cost_error[row]; - } - } - - //compute the hidden_cost_error by output_cost_error - //use transpose index - for (int row = 0; row < hidden_num; ++row) - { - for (int col = 0; col < OUTPUT_SIZE; ++col) - { - int mat_idx = row * OUTPUT_SIZE + col; - hidden_cost_error[row] += hidden_weight_matrix[mat_idx] * output_cost_error[col]; - } - - //apply sigmoid prime after we compute the derivative - hidden_cost_error[row] *= sigmoid_prime(hidden_weighted_input[row]); - //assign to updated matrix and vec - updated_hidden_bias[row] += hidden_cost_error[row]; - for (int mat_col = 0; mat_col < INPUT_SIZE; ++mat_col) - { - int mat_idx = row * INPUT_SIZE + mat_col; - updated_input_weight[mat_idx] += input_data[mat_col] * hidden_cost_error[row]; - } - } -} - -//SGD -//we use training data as array_input and array_target_output, same as test data -- probably all size information should be inputs -void SGD(int training_round, int hidden_size, float eta, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec) -{ - //init necessary buffers - float* updated_input_weight = new float[INPUT_SIZE * hidden_size]; - float* updated_hidden_weight = new float[OUTPUT_SIZE * hidden_size]; - float* updated_hidden_bias = new float[hidden_size]; - float* updated_output_bias = new float[OUTPUT_SIZE]; - - for (int round = 0; round < training_round; ++round) - { - //zero out all components for new round of change - std::fill(updated_input_weight, updated_input_weight + INPUT_SIZE * hidden_size, 0); - std::fill(updated_hidden_weight, updated_hidden_weight + OUTPUT_SIZE * hidden_size, 0); - std::fill(updated_hidden_bias, updated_hidden_bias + hidden_size, 0); - std::fill(updated_output_bias, updated_output_bias + OUTPUT_SIZE, 0); - //memcpy(updated_hidden_bias, hidden_bias_vec, HIDDEN_SIZE * sizeof(float)); - - std::cout << "Round " << round << ":" << std::endl; - float cost = 0; - for (int input_index = 0; input_index < OUTPUT_SIZE; ++input_index) - { - //update each element in the buffer arrays directly in back_prop - back_prop(hidden_size,&cost, array_inputs[input_index], array_target_outputs[input_index], input_weight_matrix, hidden_weight_matrix, hidden_bias_vec, output_bias_vec, updated_input_weight, updated_hidden_weight, updated_hidden_bias, updated_output_bias); - } - - //update the weights and bias after each round - for (int input_weight_index = 0; input_weight_index < INPUT_SIZE * hidden_size; ++input_weight_index) - { - input_weight_matrix[input_weight_index] -= (eta / OUTPUT_SIZE) * updated_input_weight[input_weight_index]; - } - - for (int hidden_weight_index = 0; hidden_weight_index < OUTPUT_SIZE * hidden_size; ++hidden_weight_index) - { - hidden_weight_matrix[hidden_weight_index] -= (eta / OUTPUT_SIZE) * updated_hidden_weight[hidden_weight_index]; - } - - for (int hidden_bias_index = 0; hidden_bias_index < hidden_size; ++hidden_bias_index) - { - hidden_bias_vec[hidden_bias_index] -= (eta / OUTPUT_SIZE) * updated_hidden_bias[hidden_bias_index]; - } - - for (int output_bias_index = 0; output_bias_index < OUTPUT_SIZE; ++output_bias_index) - { - output_bias_vec[output_bias_index] -= (eta / OUTPUT_SIZE) * updated_output_bias[output_bias_index]; - } - - //output cost - cost /= OUTPUT_SIZE; - std::cout << "The cost of current data is: " << cost << std::endl; - } - - std::cout << "we train " << training_round << " rounds" << std::endl; - Evaluate(INPUT_SIZE, HIDDEN_SIZE, OUTPUT_SIZE, nullptr, nullptr, input_weight_matrix, hidden_weight_matrix, hidden_bias_vec, output_bias_vec); - - delete[] updated_input_weight; - delete[] updated_hidden_weight; - delete[] updated_hidden_bias; - delete[] updated_output_bias; -} +//float sigmoid(float z) +//{ +// return 1 / (1 + std::pow(exp(1.0), -z)); +//} +// +//float sigmoid_prime(float z) +//{ +// return sigmoid(z) * (1 - sigmoid(z)); +//} +// +//void cost_derivative(int n, float* target_output, float* actual_output, float* error_vec) +//{ +// for (int i = 0; i < n; ++i) +// { +// error_vec[i] = actual_output[i] - target_output[i]; +// } +//} +// +//void argmax(int output_num, float* curr_output) +//{ +// int max_idx = -1; +// float max = 0; +// for (int i = 0; i < output_num; ++i) +// { +// if (curr_output[i] >= max) +// { +// max_idx = i; +// max = curr_output[i]; +// } +// } +// +// for (int i = 0; i < output_num; ++i) +// { +// if (i == max_idx) { +// curr_output[i] = 1; +// } +// else curr_output[i] = 0; +// } +//} +// +//float compute_cost(int size, float* target_output_data, float* output_layer) +//{ +// float sum = 0; +// for (int i = 0; i < size; ++i) +// { +// sum += std::pow(target_output_data[i] - output_layer[i], 2); +// } +// +// sum /= 2; +// //std::cout << "The cost of current data is: " << sum << std::endl; +// return sum; +//} +// +//void feed_forward(int n, int hidden_num, int output_num, int idata_idx, float* odata, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec) +//{ +// float* temp_hidden = new float[hidden_num]; +// float* idata = array_inputs[idata_idx]; +// //input to hidden +// for (int row = 0; row < hidden_num; ++row) +// { +// float sum = 0; +// for (int col = 0; col < n; ++col) +// { +// int idx = row * n + col; +// float w = input_weight_matrix[idx]; +// float input = idata[col]; +// sum += w * input; +// +// } +// temp_hidden[row] = sigmoid(sum + hidden_bias_vec[row]); +// } +// +// //test +// //std::cout << "the 2064 of element in this file is: " << idata[2064] << std::endl; +// //printFloatArray(hidden_num, temp_hidden, false); +// //from hidden to output +// //input to hidden +// for (int row = 0; row < output_num; ++row) +// { +// float sum = 0; +// for (int col = 0; col < hidden_num; ++col) +// { +// int idx = row * hidden_num + col; +// float w = hidden_weight_matrix[idx]; +// float input = temp_hidden[col]; +// sum += w * input; +// +// } +// odata[row] = sigmoid(sum + output_bias_vec[row]); +// } +// +// delete[] temp_hidden; +//} +// +//void back_prop(int hidden_num, float* cost, float* input_data, float* target_output_data, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec, float* updated_input_weight, float* updated_hidden_weight, float* updated_hidden_bias, float* updated_output_bias) +//{ +// //generate intermediate hidden layer +// float* hidden_layer = new float[hidden_num]; +// float* output_layer = new float[OUTPUT_SIZE]; +// float* hidden_weighted_input = new float[hidden_num]; +// float* output_weighted_input = new float[OUTPUT_SIZE]; +// float* output_cost_error = new float[OUTPUT_SIZE]; +// float* hidden_cost_error = new float[hidden_num]; +// +// //initialize +// std::fill(hidden_layer, hidden_layer + hidden_num, 0); +// std::fill(output_layer, output_layer + OUTPUT_SIZE, 0); +// std::fill(hidden_weighted_input, hidden_weighted_input + hidden_num, 0); +// std::fill(output_weighted_input, output_weighted_input + OUTPUT_SIZE, 0); +// std::fill(hidden_cost_error, hidden_cost_error + hidden_num, 0); +// std::fill(output_cost_error, output_cost_error + OUTPUT_SIZE, 0); +// +// //feedfoward +// for (int row = 0; row < hidden_num; ++row) +// { +// float sum = 0; +// for (int col = 0; col < INPUT_SIZE; ++col) +// { +// int idx = row * INPUT_SIZE + col; +// float w = input_weight_matrix[idx]; +// float input = input_data[col]; +// sum += w * input; +// +// } +// hidden_weighted_input[row] = sum + hidden_bias_vec[row]; +// hidden_layer[row] = sigmoid(sum + hidden_bias_vec[row]); +// } +// +// for (int row = 0; row < OUTPUT_SIZE; ++row) +// { +// float sum = 0; +// for (int col = 0; col < hidden_num; ++col) +// { +// int idx = row * hidden_num + col; +// float w = hidden_weight_matrix[idx]; +// float input = hidden_layer[col]; +// sum += w * input; +// +// } +// output_weighted_input[row] = sum + output_bias_vec[row]; +// output_layer[row] = sigmoid(sum + output_bias_vec[row]); +// } +// +// //output cost here +// *cost += compute_cost(OUTPUT_SIZE, target_output_data, output_layer); +// +// //Get the cost derivative from the output layer result and target output data +// cost_derivative(OUTPUT_SIZE, target_output_data, output_layer, output_cost_error); +// //add the sigmoid prime to it +// for (int row = 0; row < OUTPUT_SIZE; ++row) +// { +// output_cost_error[row] *= sigmoid_prime(output_weighted_input[row]); +// //assign to updated weights and bias for output +// updated_output_bias[row] += output_cost_error[row]; +// for (int col = 0; col < hidden_num; ++col) +// { +// int mat_idx = row * hidden_num + col; +// updated_hidden_weight[mat_idx] += hidden_layer[col] * output_cost_error[row]; +// } +// } +// +// //compute the hidden_cost_error by output_cost_error +// //use transpose index +// for (int row = 0; row < hidden_num; ++row) +// { +// for (int col = 0; col < OUTPUT_SIZE; ++col) +// { +// int mat_idx = row * OUTPUT_SIZE + col; +// hidden_cost_error[row] += hidden_weight_matrix[mat_idx] * output_cost_error[col]; +// } +// +// //apply sigmoid prime after we compute the derivative +// hidden_cost_error[row] *= sigmoid_prime(hidden_weighted_input[row]); +// //assign to updated matrix and vec +// updated_hidden_bias[row] += hidden_cost_error[row]; +// for (int mat_col = 0; mat_col < INPUT_SIZE; ++mat_col) +// { +// int mat_idx = row * INPUT_SIZE + mat_col; +// updated_input_weight[mat_idx] += input_data[mat_col] * hidden_cost_error[row]; +// } +// } +//} +// +////SGD +////we use training data as array_input and array_target_output, same as test data -- probably all size information should be inputs +//void SGD(int training_round, int hidden_size, float eta, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec) +//{ +// //init necessary buffers +// float* updated_input_weight = new float[INPUT_SIZE * hidden_size]; +// float* updated_hidden_weight = new float[OUTPUT_SIZE * hidden_size]; +// float* updated_hidden_bias = new float[hidden_size]; +// float* updated_output_bias = new float[OUTPUT_SIZE]; +// +// for (int round = 0; round < training_round; ++round) +// { +// //zero out all components for new round of change +// std::fill(updated_input_weight, updated_input_weight + INPUT_SIZE * hidden_size, 0); +// std::fill(updated_hidden_weight, updated_hidden_weight + OUTPUT_SIZE * hidden_size, 0); +// std::fill(updated_hidden_bias, updated_hidden_bias + hidden_size, 0); +// std::fill(updated_output_bias, updated_output_bias + OUTPUT_SIZE, 0); +// //memcpy(updated_hidden_bias, hidden_bias_vec, HIDDEN_SIZE * sizeof(float)); +// +// std::cout << "Round " << round << ":" << std::endl; +// float cost = 0; +// for (int input_index = 0; input_index < OUTPUT_SIZE; ++input_index) +// { +// //update each element in the buffer arrays directly in back_prop +// back_prop(hidden_size,&cost, array_inputs[input_index], array_target_outputs[input_index], input_weight_matrix, hidden_weight_matrix, hidden_bias_vec, output_bias_vec, updated_input_weight, updated_hidden_weight, updated_hidden_bias, updated_output_bias); +// } +// +// //update the weights and bias after each round +// for (int input_weight_index = 0; input_weight_index < INPUT_SIZE * hidden_size; ++input_weight_index) +// { +// input_weight_matrix[input_weight_index] -= (eta / OUTPUT_SIZE) * updated_input_weight[input_weight_index]; +// } +// +// for (int hidden_weight_index = 0; hidden_weight_index < OUTPUT_SIZE * hidden_size; ++hidden_weight_index) +// { +// hidden_weight_matrix[hidden_weight_index] -= (eta / OUTPUT_SIZE) * updated_hidden_weight[hidden_weight_index]; +// } +// +// for (int hidden_bias_index = 0; hidden_bias_index < hidden_size; ++hidden_bias_index) +// { +// hidden_bias_vec[hidden_bias_index] -= (eta / OUTPUT_SIZE) * updated_hidden_bias[hidden_bias_index]; +// } +// +// for (int output_bias_index = 0; output_bias_index < OUTPUT_SIZE; ++output_bias_index) +// { +// output_bias_vec[output_bias_index] -= (eta / OUTPUT_SIZE) * updated_output_bias[output_bias_index]; +// } +// +// //output cost +// cost /= OUTPUT_SIZE; +// std::cout << "The cost of current data is: " << cost << std::endl; +// } +// +// std::cout << "we train " << training_round << " rounds" << std::endl; +// Evaluate(INPUT_SIZE, HIDDEN_SIZE, OUTPUT_SIZE, nullptr, nullptr, input_weight_matrix, hidden_weight_matrix, hidden_bias_vec, output_bias_vec); +// +// delete[] updated_input_weight; +// delete[] updated_hidden_weight; +// delete[] updated_hidden_bias; +// delete[] updated_output_bias; +//} +// bool check_data(int* file_idx, int* array_idx) { for (int i = 0; i < OUTPUT_SIZE - 1; ++i) @@ -376,50 +362,50 @@ bool check_data(int* file_idx, int* array_idx) } return false; } - -void Evaluate(int n, int hidden_num, int output_num, float* test_data, float* target_output, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec) -{ - int sum = 0; - float* curr_output = new float[output_num]; - //unused (test_data, target_output) --> will use later - for (int i = 0; i < output_num; ++i) - { - //zero out output - std::fill(curr_output, curr_output + output_num, 0); - //for (int j = 0; j < output_num; ++j) - //{ - // std::cout << "curr_output[" << j << "] is " << curr_output[j] << std::endl; - //} - - feed_forward(n, hidden_num, output_num, i, curr_output, input_weight_matrix, hidden_weight_matrix, hidden_bias_vec, output_bias_vec); - - argmax(output_num, curr_output); - bool same = true; - for (int j = 0; j < output_num; ++j) - { - if (curr_output[j] != array_target_outputs[i][j]) - { - same = false; - } - } - - for (int j = 0; j < output_num; ++j) - { - if (curr_output[j] == 1) - { - std::cout << "curr_output[" << j << "] is " << curr_output[j] << std::endl; - } - } - - if (same) sum++; - - } - - - std::cout << "Result: " << sum << " / " << output_num << std::endl; - - delete[] curr_output; -} +// +//void Evaluate(int n, int hidden_num, int output_num, float* test_data, float* target_output, float* input_weight_matrix, float* hidden_weight_matrix, float* hidden_bias_vec, float* output_bias_vec) +//{ +// int sum = 0; +// float* curr_output = new float[output_num]; +// //unused (test_data, target_output) --> will use later +// for (int i = 0; i < output_num; ++i) +// { +// //zero out output +// std::fill(curr_output, curr_output + output_num, 0); +// //for (int j = 0; j < output_num; ++j) +// //{ +// // std::cout << "curr_output[" << j << "] is " << curr_output[j] << std::endl; +// //} +// +// feed_forward(n, hidden_num, output_num, i, curr_output, input_weight_matrix, hidden_weight_matrix, hidden_bias_vec, output_bias_vec); +// +// argmax(output_num, curr_output); +// bool same = true; +// for (int j = 0; j < output_num; ++j) +// { +// if (curr_output[j] != array_target_outputs[i][j]) +// { +// same = false; +// } +// } +// +// for (int j = 0; j < output_num; ++j) +// { +// if (curr_output[j] == 1) +// { +// std::cout << "curr_output[" << j << "] is " << curr_output[j] << std::endl; +// } +// } +// +// if (same) sum++; +// +// } +// +// +// std::cout << "Result: " << sum << " / " << output_num << std::endl; +// +// delete[] curr_output; +//} @@ -444,35 +430,11 @@ int main(int argc, char* argv[]) { std::fill(hidden_bias_vec, hidden_bias_vec + HIDDEN_SIZE, 0); std::fill(output_bias_vec, output_bias_vec + OUTPUT_SIZE, 0); - float target_error = 0.0003; - float total_error = 0.2; //will be modified - float target_val = 1; //tested on XOR example -- should this be a - //first generate the weight matrix to store all weight values for each element, in this homework, it should be a n * 2 matrix, such that n is the number of input - - //printFloatArray(INPUT_SIZE * HIDDEN_SIZE, input_weight_matrix, false); - //for (int i = 0; i < OUTPUT_SIZE; ++i) - //{ - // printFloatArray(INPUT_SIZE, array_inputs[i], false); - // printFloatArray(OUTPUT_SIZE, array_target_outputs[i], true); - // std::cout << std::endl; - //} - - //printFloatArray(OUTPUT_SIZE, array_target_outputs[1], true); - //printFloatArray(INPUT_SIZE * HIDDEN_SIZE, input_weight_matrix, true); - //printFloatArray(OUTPUT_SIZE * HIDDEN_SIZE, hidden_weight_matrix, false); - - SGD(500, HIDDEN_SIZE, 0.1f, input_weight_matrix, hidden_weight_matrix, hidden_bias_vec, output_bias_vec); - - //printFloatArray(OUTPUT_SIZE * HIDDEN_SIZE, hidden_weight_matrix, false); - //get input from files? - //CharacterRecognition::MLP_calculation(INPUT_SIZE, OUTPUT_SIZE, input, weight_matrix, output); - //printElapsedTime(CharacterRecognition::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); - //printFloatArray(OUTPUT_SIZE, output, true); + CharacterRecognition::initSimulation(INPUT_SIZE, HIDDEN_SIZE, OUTPUT_SIZE, array_inputs, array_target_outputs); + CharacterRecognition::SGD(50, HIDDEN_SIZE, 0.1f, input_weight_matrix, hidden_weight_matrix, hidden_bias_vec, output_bias_vec); //assume we get the correct output, we should calculate the error and apply bp algorithm back to weight - //how do we get the target value? -- from file? - //understand bp algorithm //how to make it keep running? -- while loop within a certain threshold delete[] input_weight_matrix; @@ -481,142 +443,6 @@ int main(int argc, char* argv[]) { delete[] output_bias_vec; //delete the whole array - //for (int i = 0; i < OUTPUT_SIZE; ++i) - //{ - // delete[] array_inputs[i]; - // delete[] array_target_outputs[i]; - //} array_inputs.clear(); array_target_outputs.clear(); - // // Scan tests - - // printf("\n"); - // printf("****************\n"); - // printf("** SCAN TESTS **\n"); - // printf("****************\n"); - - // genArray(SIZE - 1, a, 50); // Leave a 0 at the end to test that edge case - // a[SIZE - 1] = 0; - // printArray(SIZE, a, true); - - // // initialize b using StreamCompaction::CPU::scan you implement - // // We use b for further comparison. Make sure your StreamCompaction::CPU::scan is correct. - // // At first all cases passed because b && c are all zeroes. - // zeroArray(SIZE, b); - // printDesc("cpu scan, power-of-two"); - // StreamCompaction::CPU::scan(SIZE, b, a); - // printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); - // printArray(SIZE, b, true); - - // zeroArray(SIZE, c); - // printDesc("cpu scan, non-power-of-two"); - // StreamCompaction::CPU::scan(NPOT, c, a); - // printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); - // printArray(NPOT, b, true); - // printCmpResult(NPOT, b, c); - - // zeroArray(SIZE, c); - // printDesc("naive scan, power-of-two"); - // StreamCompaction::Naive::scan(SIZE, c, a); - // printElapsedTime(StreamCompaction::Naive::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - // //printArray(SIZE, c, true); - // printCmpResult(SIZE, b, c); - - ///* For bug-finding only: Array of 1s to help find bugs in stream compaction or scan - //onesArray(SIZE, c); - //printDesc("1s array for finding bugs"); - //StreamCompaction::Naive::scan(SIZE, c, a); - //printArray(SIZE, c, true); */ - - // zeroArray(SIZE, c); - // printDesc("naive scan, non-power-of-two"); - // StreamCompaction::Naive::scan(NPOT, c, a); - // printElapsedTime(StreamCompaction::Naive::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - // //printArray(SIZE, c, true); - // printCmpResult(NPOT, b, c); - - // zeroArray(SIZE, c); - // printDesc("work-efficient scan, power-of-two"); - // StreamCompaction::Efficient::scan(SIZE, c, a); - // printElapsedTime(StreamCompaction::Efficient::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - // //printArray(SIZE, c, true); - // printCmpResult(SIZE, b, c); - - // zeroArray(SIZE, c); - // printDesc("work-efficient scan, non-power-of-two"); - // StreamCompaction::Efficient::scan(NPOT, c, a); - // printElapsedTime(StreamCompaction::Efficient::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - // //printArray(NPOT, c, true); - // printCmpResult(NPOT, b, c); - - // zeroArray(SIZE, c); - // printDesc("thrust scan, power-of-two"); - // StreamCompaction::Thrust::scan(SIZE, c, a); - // printElapsedTime(StreamCompaction::Thrust::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - // //printArray(SIZE, c, true); - // printCmpResult(SIZE, b, c); - - // zeroArray(SIZE, c); - // printDesc("thrust scan, non-power-of-two"); - // StreamCompaction::Thrust::scan(NPOT, c, a); - // printElapsedTime(StreamCompaction::Thrust::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - // //printArray(NPOT, c, true); - // printCmpResult(NPOT, b, c); - - // printf("\n"); - // printf("*****************************\n"); - // printf("** STREAM COMPACTION TESTS **\n"); - // printf("*****************************\n"); - - // // Compaction tests - - // genArray(SIZE - 1, a, 4); // Leave a 0 at the end to test that edge case - // a[SIZE - 1] = 0; - // printArray(SIZE, a, true); - - // int count, expectedCount, expectedNPOT; - - // // initialize b using StreamCompaction::CPU::compactWithoutScan you implement - // // We use b for further comparison. Make sure your StreamCompaction::CPU::compactWithoutScan is correct. - // zeroArray(SIZE, b); - // printDesc("cpu compact without scan, power-of-two"); - // count = StreamCompaction::CPU::compactWithoutScan(SIZE, b, a); - // printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); - // expectedCount = count; - // printArray(count, b, true); - // printCmpLenResult(count, expectedCount, b, b); - - // zeroArray(SIZE, c); - // printDesc("cpu compact without scan, non-power-of-two"); - // count = StreamCompaction::CPU::compactWithoutScan(NPOT, c, a); - // printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); - // expectedNPOT = count; - // printArray(count, c, true); - // printCmpLenResult(count, expectedNPOT, b, c); - - // zeroArray(SIZE, c); - // printDesc("cpu compact with scan"); - // count = StreamCompaction::CPU::compactWithScan(SIZE, c, a); - // printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); - // printArray(count, c, true); - // printCmpLenResult(count, expectedCount, b, c); - - // zeroArray(SIZE, c); - // printDesc("work-efficient compact, power-of-two"); - // count = StreamCompaction::Efficient::compact(SIZE, c, a); - // printElapsedTime(StreamCompaction::Efficient::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - // //printArray(count, c, true); - // printCmpLenResult(count, expectedCount, b, c); - - // zeroArray(SIZE, c); - // printDesc("work-efficient compact, non-power-of-two"); - // count = StreamCompaction::Efficient::compact(NPOT, c, a); - // printElapsedTime(StreamCompaction::Efficient::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - // //printArray(count, c, true); - // printCmpLenResult(count, expectedNPOT, b, c); - - // system("pause"); // stop Win32 console from closing on exit - //delete[] a; - //delete[] b; - //delete[] c; }