From 6adaf0432ba04100331a39ab411790ced9e87504 Mon Sep 17 00:00:00 2001 From: Grace Gilbert Date: Thu, 12 Sep 2019 16:40:21 -0500 Subject: [PATCH 01/38] completed cpu impl and setup naive gpu scan --- Project2-Stream-Compaction/src/main.cpp | 2 +- .../stream_compaction/CMakeLists.txt | 2 +- .../stream_compaction/common.h | 8 +++ .../stream_compaction/cpu.cu | 48 ++++++++++++++++-- .../stream_compaction/naive.cu | 50 ++++++++++++++++++- 5 files changed, 103 insertions(+), 7 deletions(-) diff --git a/Project2-Stream-Compaction/src/main.cpp b/Project2-Stream-Compaction/src/main.cpp index d016553..fd98f07 100644 --- a/Project2-Stream-Compaction/src/main.cpp +++ b/Project2-Stream-Compaction/src/main.cpp @@ -13,7 +13,7 @@ #include #include "testing_helpers.hpp" -const int SIZE = 1 << 8; // feel free to change the size of array +const int SIZE = 1 << 3; // 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]; 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/common.h b/Project2-Stream-Compaction/stream_compaction/common.h index 996997e..4085b6a 100644 --- a/Project2-Stream-Compaction/stream_compaction/common.h +++ b/Project2-Stream-Compaction/stream_compaction/common.h @@ -108,6 +108,14 @@ namespace StreamCompaction { return prev_elapsed_time_gpu_milliseconds; } + bool getCpuTimerStarted() { + return cpu_timer_started; + } + + bool getGpuTimerStarted() { + return gpu_timer_started; + } + // remove copy and move functions PerformanceTimer(const PerformanceTimer&) = delete; PerformanceTimer(PerformanceTimer&&) = delete; diff --git a/Project2-Stream-Compaction/stream_compaction/cpu.cu b/Project2-Stream-Compaction/stream_compaction/cpu.cu index a2d3e6c..fd33591 100644 --- a/Project2-Stream-Compaction/stream_compaction/cpu.cu +++ b/Project2-Stream-Compaction/stream_compaction/cpu.cu @@ -18,9 +18,23 @@ namespace StreamCompaction { * (Optional) For better understanding before starting moving to GPU, you can simulate your GPU scan in this function first. */ void scan(int n, int *odata, const int *idata) { - timer().startCpuTimer(); + bool newTimer = true; + if (timer().getCpuTimerStarted()) { + newTimer = false; + } + if (newTimer) { + timer().startCpuTimer(); + } // TODO - timer().endCpuTimer(); + if (n > 0) { + odata[0] = 0; + for (int k = 1; k < n; ++k) { + odata[k] = odata[k - 1] + idata[k - 1]; + } + } + if (newTimer) { + timer().endCpuTimer(); + } } /** @@ -31,8 +45,16 @@ namespace StreamCompaction { int compactWithoutScan(int n, int *odata, const int *idata) { timer().startCpuTimer(); // TODO + int counter = 0; + for (int k = 0; k < n; ++k) { + int currVal = idata[k]; + if (currVal != 0) { + odata[counter] = currVal; + counter++; + } + } timer().endCpuTimer(); - return -1; + return counter; } /** @@ -43,8 +65,26 @@ namespace StreamCompaction { int compactWithScan(int n, int *odata, const int *idata) { timer().startCpuTimer(); // TODO + int *tempArray = new int[n]; + for (int k = 0; k < n; ++k) { + tempArray[k] = (int) idata[k] != 0; + } + int counter = 0; + int *scanResult = new int[n]; + scan(n, scanResult, tempArray); + for (int k = 0; k < n; ++k) { + if (tempArray[k]) { + int index = scanResult[k]; + odata[index] = idata[k]; + counter++; + } + } + + + delete[] scanResult; + delete[] tempArray; timer().endCpuTimer(); - return -1; + return counter; } } } diff --git a/Project2-Stream-Compaction/stream_compaction/naive.cu b/Project2-Stream-Compaction/stream_compaction/naive.cu index 4308876..624d32c 100644 --- a/Project2-Stream-Compaction/stream_compaction/naive.cu +++ b/Project2-Stream-Compaction/stream_compaction/naive.cu @@ -12,14 +12,62 @@ namespace StreamCompaction { return timer; } // TODO: __global__ + int blockSize = 128; + dim3 threadsPerBlock(blockSize); + + __global__ void kernSumPairs(int N, int d, int *srcArray, int *dstArray) { + + } /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { - timer().startGpuTimer(); + int bufferLength = 1 << ilog2ceil(n); + int *dev_arrayA; + int *dev_arrayB; + + cudaMalloc((void**)&dev_arrayA, bufferLength * sizeof(int)); + checkCUDAError("cudaMalloc dev_arrayA failed!"); + + cudaMalloc((void**)&dev_arrayB, bufferLength * sizeof(int)); + checkCUDAError("cudaMalloc dev_arrayB failed!"); + + cudaMemset(dev_arrayA, 0, bufferLength * sizeof(int)); + cudaMemset(dev_arrayB, 0, bufferLength * sizeof(int)); + + cudaMemcpy(dev_arrayA, idata, n * sizeof(int), cudaMemcpyHostToDevice); + cudaMemcpy(dev_arrayB, idata, n * sizeof(int), cudaMemcpyHostToDevice); + + timer().startGpuTimer(); + // TODO + int alternator = 0; + for (int d = 0; d < ilog2ceil(n); ++d) { + int numThreads = pow(2, d); + dim3 blocksPerGrid((numThreads + blockSize - 1) / blockSize); + + if (alternator % 2 == 0) { + kernSumPairs<<>>(numThreads, d, dev_arrayA, dev_arrayB); + cudaMemcpy(dev_arrayA, dev_arrayB, bufferLength * sizeof(int), cudaMemcpyDeviceToDevice); + alternator++; + + } + else { + kernSumPairs<<>>(numThreads, d, dev_arrayB, dev_arrayA); + cudaMemcpy(dev_arrayA, dev_arrayB, bufferLength * sizeof(int), cudaMemcpyDeviceToDevice); + alternator++; + + } + + } + + timer().endGpuTimer(); + + + cudaFree(dev_arrayA); + cudaFree(dev_arrayB); } } } From 1f7ff943248830272b22fd8e725ef1832fe87f10 Mon Sep 17 00:00:00 2001 From: Grace Gilbert Date: Thu, 12 Sep 2019 23:22:37 -0500 Subject: [PATCH 02/38] completed naive scan --- .../img/naiveScanWithBetterThreadcount.PNG | Bin 0 -> 15844 bytes .../img/naiveScanWithNaiveThreadCount.PNG | Bin 0 -> 16031 bytes Project2-Stream-Compaction/src/main.cpp | 4 +- .../stream_compaction/naive.cu | 55 +++++++++++++++--- 4 files changed, 48 insertions(+), 11 deletions(-) create mode 100644 Project2-Stream-Compaction/img/naiveScanWithBetterThreadcount.PNG create mode 100644 Project2-Stream-Compaction/img/naiveScanWithNaiveThreadCount.PNG diff --git a/Project2-Stream-Compaction/img/naiveScanWithBetterThreadcount.PNG b/Project2-Stream-Compaction/img/naiveScanWithBetterThreadcount.PNG new file mode 100644 index 0000000000000000000000000000000000000000..c5c69c5335faeada5eafa88a22f7e5b646b36e81 GIT binary patch literal 15844 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 E2Yw1dCIA2c literal 0 HcmV?d00001 diff --git a/Project2-Stream-Compaction/img/naiveScanWithNaiveThreadCount.PNG b/Project2-Stream-Compaction/img/naiveScanWithNaiveThreadCount.PNG new file mode 100644 index 0000000000000000000000000000000000000000..4de18a3533e733d3258980c596995f5bad19e00a GIT binary patch literal 16031 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 R<;eqCoUuNgbMjjF{{k_ML0kX; literal 0 HcmV?d00001 diff --git a/Project2-Stream-Compaction/src/main.cpp b/Project2-Stream-Compaction/src/main.cpp index fd98f07..109dc80 100644 --- a/Project2-Stream-Compaction/src/main.cpp +++ b/Project2-Stream-Compaction/src/main.cpp @@ -13,7 +13,7 @@ #include #include "testing_helpers.hpp" -const int SIZE = 1 << 3; // feel free to change the size of array +const int SIZE = 1 << 7; // 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]; @@ -51,7 +51,7 @@ int main(int argc, char* argv[]) { printDesc("naive scan, power-of-two"); StreamCompaction::Naive::scan(SIZE, c, a); printElapsedTime(StreamCompaction::Naive::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - //printArray(SIZE, c, true); + printArray(SIZE, c, true); printCmpResult(SIZE, b, c); /* For bug-finding only: Array of 1s to help find bugs in stream compaction or scan diff --git a/Project2-Stream-Compaction/stream_compaction/naive.cu b/Project2-Stream-Compaction/stream_compaction/naive.cu index 624d32c..6409178 100644 --- a/Project2-Stream-Compaction/stream_compaction/naive.cu +++ b/Project2-Stream-Compaction/stream_compaction/naive.cu @@ -16,6 +16,31 @@ namespace StreamCompaction { dim3 threadsPerBlock(blockSize); __global__ void kernSumPairs(int N, int d, int *srcArray, int *dstArray) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= N) { + return; + } + int power = powf(2, d - 1); + index += power; + dstArray[index] = srcArray[index - power] + srcArray[index]; + + /*int power = powf(2, d - 1); + if (index >= power) { + dstArray[index] = srcArray[index - power] + srcArray[index]; + }*/ + } + + __global__ void kernShift(int N, int *srcArray, int *dstArray) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= N) { + return; + } + + if (index == 0) { + dstArray[index] = 0; + return; + } + dstArray[index] = srcArray[index - 1]; } @@ -42,29 +67,41 @@ namespace StreamCompaction { timer().startGpuTimer(); // TODO - int alternator = 0; - for (int d = 0; d < ilog2ceil(n); ++d) { - int numThreads = pow(2, d); + bool alternator = true; + for (int d = 1; d <= ilog2ceil(n); ++d) { + int numThreads = bufferLength - pow(2, d - 1); // TODO: can this be smaller? + dim3 blocksPerGrid((numThreads + blockSize - 1) / blockSize); - if (alternator % 2 == 0) { + if (alternator) { kernSumPairs<<>>(numThreads, d, dev_arrayA, dev_arrayB); + checkCUDAError("kernSumPairs failed!"); + cudaMemcpy(dev_arrayA, dev_arrayB, bufferLength * sizeof(int), cudaMemcpyDeviceToDevice); - alternator++; + alternator = false; } else { kernSumPairs<<>>(numThreads, d, dev_arrayB, dev_arrayA); - cudaMemcpy(dev_arrayA, dev_arrayB, bufferLength * sizeof(int), cudaMemcpyDeviceToDevice); - alternator++; + checkCUDAError("kernSumPairs failed!"); - } + cudaMemcpy(dev_arrayB, dev_arrayA, bufferLength * sizeof(int), cudaMemcpyDeviceToDevice); + alternator = true; + } } + // Note: dev_arrayA/B are now inclusive scans. + // We will take B and shift it on the gpu, storing the exlusive scan in A + int numThreads = bufferLength; + dim3 blocksPerGrid((numThreads + blockSize - 1) / blockSize); - timer().endGpuTimer(); + kernShift<<>>(bufferLength * sizeof(int), dev_arrayB, dev_arrayA); + checkCUDAError("kernShift failed!"); + + cudaMemcpy(odata, dev_arrayA, n * sizeof(int), cudaMemcpyDeviceToHost); + timer().endGpuTimer(); cudaFree(dev_arrayA); cudaFree(dev_arrayB); From d753adbf1d3dea5d134607f225aa34775dfbac0b Mon Sep 17 00:00:00 2001 From: Grace Gilbert Date: Fri, 13 Sep 2019 12:00:41 -0500 Subject: [PATCH 03/38] completed work efficient scan --- .../img/workEfficientScanLessEfficient.PNG | Bin 0 -> 13806 bytes .../img/workEfficientScanMoreEfficient.PNG | Bin 0 -> 13896 bytes Project2-Stream-Compaction/src/main.cpp | 5 +- .../stream_compaction/efficient.cu | 78 ++++++++++++++++++ 4 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 Project2-Stream-Compaction/img/workEfficientScanLessEfficient.PNG create mode 100644 Project2-Stream-Compaction/img/workEfficientScanMoreEfficient.PNG diff --git a/Project2-Stream-Compaction/img/workEfficientScanLessEfficient.PNG b/Project2-Stream-Compaction/img/workEfficientScanLessEfficient.PNG new file mode 100644 index 0000000000000000000000000000000000000000..9374438d5673cc2e23ad088cfea9f1d2f5d561db GIT binary patch literal 13806 zcmd6tXIN9&8tR*>7|7%qjoLenIp);T-@rJg)|dZ~JSAq`K*;w+9GUs+qf>#_^z zSl8dyzdl-Ds86GJD_VlVTn`H6)=Qwrr_#YncAH#chlaaZ-2*TWv5oQ0E=xeQ^I{DQ zUMD!RU6?fPN}8MXseCb972o{ODFK`3(klmcZa!$eEU3X(2~Otqz8D(+7*lYjk-I>- zpOIBiq=R!8&Oa{_uJNR~E%fbZJ+mvsWi0ItA%oUnBvm(W9(H3J;Ygk0%9H3BvL_e> zYPX_1k1drl@mdZiUSdZM7RH-iJkcR|zQkBFj3%Rf9Nk@qJIoa0hnpJw`?1{+lY8=q zNeB6!>v;S{=j|u&eneAYWVGb;Gj}(rh^<&FnI zMR+;j2E8GKQA4>qg(D*xRg34#(N|y-CP(+I9B&m~_sHEa*gT6JO z`tO_nw9`6J?dTIjx4OkH8!a%bOfICIoEkK|4ict?cD%

$~I0+%dU*TR}^b4WAO` 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 R1?&Ks8d(}vK>xh`KLDF^HVOa$ literal 0 HcmV?d00001 diff --git a/Project2-Stream-Compaction/img/workEfficientScanMoreEfficient.PNG b/Project2-Stream-Compaction/img/workEfficientScanMoreEfficient.PNG new file mode 100644 index 0000000000000000000000000000000000000000..affa212ef9e5dffe5da8b7939916fc70668df155 GIT binary patch literal 13896 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 N) { + return; + } + + int k = index * 2 * power; + + opArray[k + 2 * power - 1] += opArray[k + power - 1]; + } + + __global__ void kernDownSweep(int N, int power, int *opArray) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= N) { + return; + } + + int k = index * 2 * power; + + int t = opArray[k + power - 1]; + int s = opArray[k + 2 * power - 1]; + opArray[k + power - 1] = s; + opArray[k + 2 * power - 1] = s + t; + } + + __global__ void kernSetLastZero(int N, int offset, int *opArray) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= N) { + return; + } + opArray[index + offset] = 0; + } + /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { + int bufferLength = 1 << ilog2ceil(n); + int *dev_inputArray; + int *host_upSweep; + + cudaMalloc((void**)&dev_inputArray, bufferLength * sizeof(int)); + checkCUDAError("cudaMalloc dev_inputArray failed!"); + + cudaMallocHost((void**)&host_upSweep, bufferLength * sizeof(int)); + checkCUDAError("cudaMallocHost host_upSweep failed!"); + + cudaMemset(dev_inputArray, 0, bufferLength * sizeof(int)); + + cudaMemcpy(dev_inputArray, idata, n * sizeof(int), cudaMemcpyHostToDevice); + timer().startGpuTimer(); + + for (int d = 0; d < ilog2ceil(n); ++d) { + int power = pow(2, d); + int numThreads = bufferLength / (2 * power); + + dim3 blocksPerGrid((numThreads + blockSize - 1) / blockSize); + kernUpSweep << > > (numThreads, power, dev_inputArray); + checkCUDAError("kernUpSweep failed!"); + } + + // NOTE: dev_inputArray is now in upsweep stage + + int numThreadsSetZero = 1; + dim3 blocksPerGridSetZero((numThreadsSetZero + blockSize - 1) / blockSize); + kernSetLastZero << > > (numThreadsSetZero, bufferLength - 1, dev_inputArray); + + for (int d = ilog2ceil(n) - 1; d >= 0; --d) { + int power = pow(2, d); + int numThreads = bufferLength / (2 * power); + dim3 blocksPerGrid((numThreads + blockSize - 1) / blockSize); + kernDownSweep << > > (numThreads, power, dev_inputArray); + checkCUDAError("kernDownSweep failed!"); + } + cudaMemcpy(odata, dev_inputArray, n * sizeof(int), cudaMemcpyDeviceToHost); + // TODO timer().endGpuTimer(); + cudaFree(dev_inputArray); + cudaFreeHost(host_upSweep); + } /** From ab168c4ac2ae75d2a37906f5d2bfb025dc09d6e0 Mon Sep 17 00:00:00 2001 From: Grace Gilbert Date: Fri, 13 Sep 2019 13:22:16 -0500 Subject: [PATCH 04/38] completed gpu Stream Compaction --- Project2-Stream-Compaction/src/main.cpp | 6 +- .../stream_compaction/efficient.cu | 122 ++++++++++++++++-- 2 files changed, 115 insertions(+), 13 deletions(-) diff --git a/Project2-Stream-Compaction/src/main.cpp b/Project2-Stream-Compaction/src/main.cpp index 60d6ec0..f77e95d 100644 --- a/Project2-Stream-Compaction/src/main.cpp +++ b/Project2-Stream-Compaction/src/main.cpp @@ -13,7 +13,7 @@ #include #include "testing_helpers.hpp" -const int SIZE = 1 << 7; // feel free to change the size of array +const int SIZE = 1 << 15; // 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]; @@ -140,14 +140,14 @@ int main(int argc, char* argv[]) { 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); + 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); + printArray(count, c, true); printCmpLenResult(count, expectedNPOT, b, c); system("pause"); // stop Win32 console from closing on exit diff --git a/Project2-Stream-Compaction/stream_compaction/efficient.cu b/Project2-Stream-Compaction/stream_compaction/efficient.cu index 7dd47d2..67ce61f 100644 --- a/Project2-Stream-Compaction/stream_compaction/efficient.cu +++ b/Project2-Stream-Compaction/stream_compaction/efficient.cu @@ -54,20 +54,21 @@ namespace StreamCompaction { void scan(int n, int *odata, const int *idata) { int bufferLength = 1 << ilog2ceil(n); int *dev_inputArray; - int *host_upSweep; cudaMalloc((void**)&dev_inputArray, bufferLength * sizeof(int)); checkCUDAError("cudaMalloc dev_inputArray failed!"); - cudaMallocHost((void**)&host_upSweep, bufferLength * sizeof(int)); - checkCUDAError("cudaMallocHost host_upSweep failed!"); - cudaMemset(dev_inputArray, 0, bufferLength * sizeof(int)); cudaMemcpy(dev_inputArray, idata, n * sizeof(int), cudaMemcpyHostToDevice); - timer().startGpuTimer(); - + bool newTimer = true; + if (timer().getCpuTimerStarted()) { + newTimer = false; + } + if (newTimer) { + timer().startCpuTimer(); + } for (int d = 0; d < ilog2ceil(n); ++d) { int power = pow(2, d); int numThreads = bufferLength / (2 * power); @@ -93,12 +94,47 @@ namespace StreamCompaction { cudaMemcpy(odata, dev_inputArray, n * sizeof(int), cudaMemcpyDeviceToHost); // TODO - timer().endGpuTimer(); + if (newTimer) { + timer().endCpuTimer(); + } cudaFree(dev_inputArray); - cudaFreeHost(host_upSweep); - } + + __global__ void kernComputeInOutArray(int N, int *srcArray, int *dstArray) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= N) { + return; + } + dstArray[index] = (int)(srcArray[index] != 0); + } + + __global__ void kernComputeSize(int N, int offset, int *dst, int *src1, int *src2) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= N) { + return; + } + + dst[index] = src1[index + offset] + src2[index + offset]; + } + + __global__ void kernScatter(int N, int *dst, int *src, int *indexFinder) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= N) { + return; + } + + int currVal = src[index]; + int dstIndex = -1; + if (currVal != 0) { + dstIndex = indexFinder[index]; + } + if (dstIndex >= 0) { + dst[dstIndex] = currVal; + } + } + + /** * Performs stream compaction on idata, storing the result into odata. * All zeroes are discarded. @@ -109,10 +145,76 @@ namespace StreamCompaction { * @returns The number of elements remaining after compaction. */ int compact(int n, int *odata, const int *idata) { + int bufferLength = 1 << ilog2ceil(n); + int *dev_inputArray; + int *dev_tempInOutArray; + int *dev_scanArray; + int *dev_resultLength; + + int *host_resultLength; + + cudaMalloc((void**)&dev_inputArray, bufferLength * sizeof(int)); + checkCUDAError("cudaMalloc dev_inputArray failed!"); + + cudaMalloc((void**)&dev_tempInOutArray, bufferLength * sizeof(int)); + checkCUDAError("cudaMalloc dev_tempInOutArray failed!"); + + cudaMalloc((void**)&dev_scanArray, bufferLength * sizeof(int)); + checkCUDAError("cudaMalloc dev_scanArray failed!"); + + cudaMalloc((void**)&dev_resultLength, sizeof(int)); + checkCUDAError("cudaMalloc dev_resultLength failed!"); + + cudaMallocHost((void**)&host_resultLength, sizeof(int)); + checkCUDAError("cudaMallocHost host_resultLength failed!"); + + + + cudaMemset(dev_inputArray, 0, bufferLength * sizeof(int)); + + cudaMemcpy(dev_inputArray, idata, n * sizeof(int), cudaMemcpyHostToDevice); + timer().startGpuTimer(); // TODO + int numThreads = bufferLength; + dim3 blocksPerGrid((numThreads + blockSize - 1) / blockSize); + + kernComputeInOutArray << > > (numThreads, dev_inputArray, dev_tempInOutArray); + checkCUDAError("kernComputeInOutArray failed!"); + + scan(bufferLength, dev_scanArray, dev_tempInOutArray); + + // NOTE: dev_scanArray now holds bufferLength scanned values of the 0/1 array + + int numThreadsComputeSize = 1; + dim3 blocksPerGridComputeSize((numThreadsComputeSize + blockSize - 1) / blockSize); + kernComputeSize << > > (numThreadsComputeSize, bufferLength - 1, dev_resultLength, dev_scanArray, dev_tempInOutArray); + checkCUDAError("kernComputeSize failed!"); + + cudaMemcpy(host_resultLength, dev_resultLength, sizeof(int), cudaMemcpyDeviceToHost); + int length = host_resultLength[0]; + + int *dev_final; + cudaMallocHost((void**)&dev_final, length * sizeof(int)); + + int numThreadsScatter = bufferLength; + dim3 blocksPerGridScatter((numThreadsScatter + blockSize - 1) / blockSize); + + kernScatter << > > (numThreadsScatter, dev_final, dev_inputArray, dev_scanArray); + + + + cudaMemcpy(odata, dev_final, length * sizeof(int), cudaMemcpyDeviceToHost); + + timer().endGpuTimer(); - return -1; + cudaFree(dev_inputArray); + cudaFree(dev_tempInOutArray); + cudaFree(dev_scanArray); + cudaFree(dev_resultLength); + cudaFreeHost(host_resultLength); + + return length; } } } From c137368d4b2f6a5cf0a9bd8811b3819bc2490570 Mon Sep 17 00:00:00 2001 From: Grace Gilbert Date: Sat, 14 Sep 2019 22:19:08 -0500 Subject: [PATCH 05/38] thrust scan, setup mlp project --- .../character_recognition/CMakeLists.txt | 2 +- .../character_recognition/mlp.cu | 6 + Project2-Character-Recognition/src/main.cpp | 268 +++++++++--------- Project2-Stream-Compaction/src/main.cpp | 8 +- .../stream_compaction/efficient.cu | 6 +- .../stream_compaction/thrust.cu | 22 ++ 6 files changed, 170 insertions(+), 142 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-Character-Recognition/character_recognition/mlp.cu b/Project2-Character-Recognition/character_recognition/mlp.cu index 5a3ed7f..7cb42fd 100644 --- a/Project2-Character-Recognition/character_recognition/mlp.cu +++ b/Project2-Character-Recognition/character_recognition/mlp.cu @@ -23,5 +23,11 @@ namespace CharacterRecognition { } */ + void mlp(int n, int *odata, const int *idata) { + + } + // TODO: implement required elements for MLP sections 1 and 2 here + + } diff --git a/Project2-Character-Recognition/src/main.cpp b/Project2-Character-Recognition/src/main.cpp index 11dd534..df67917 100644 --- a/Project2-Character-Recognition/src/main.cpp +++ b/Project2-Character-Recognition/src/main.cpp @@ -11,142 +11,142 @@ #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]; +//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]; 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; + // 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-Stream-Compaction/src/main.cpp b/Project2-Stream-Compaction/src/main.cpp index f77e95d..12638c6 100644 --- a/Project2-Stream-Compaction/src/main.cpp +++ b/Project2-Stream-Compaction/src/main.cpp @@ -13,7 +13,7 @@ #include #include "testing_helpers.hpp" -const int SIZE = 1 << 15; // feel free to change the size of array +const int SIZE = 1 << 20; // 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]; @@ -81,21 +81,21 @@ int main(int argc, char* argv[]) { 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); + 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); + 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); + printArray(NPOT, c, true); printCmpResult(NPOT, b, c); printf("\n"); diff --git a/Project2-Stream-Compaction/stream_compaction/efficient.cu b/Project2-Stream-Compaction/stream_compaction/efficient.cu index 67ce61f..982f4ef 100644 --- a/Project2-Stream-Compaction/stream_compaction/efficient.cu +++ b/Project2-Stream-Compaction/stream_compaction/efficient.cu @@ -63,11 +63,11 @@ namespace StreamCompaction { cudaMemcpy(dev_inputArray, idata, n * sizeof(int), cudaMemcpyHostToDevice); bool newTimer = true; - if (timer().getCpuTimerStarted()) { + if (timer().getGpuTimerStarted()) { newTimer = false; } if (newTimer) { - timer().startCpuTimer(); + timer().startGpuTimer(); } for (int d = 0; d < ilog2ceil(n); ++d) { int power = pow(2, d); @@ -95,7 +95,7 @@ namespace StreamCompaction { // TODO if (newTimer) { - timer().endCpuTimer(); + timer().endGpuTimer(); } cudaFree(dev_inputArray); } diff --git a/Project2-Stream-Compaction/stream_compaction/thrust.cu b/Project2-Stream-Compaction/stream_compaction/thrust.cu index 1def45e..096bfdc 100644 --- a/Project2-Stream-Compaction/stream_compaction/thrust.cu +++ b/Project2-Stream-Compaction/stream_compaction/thrust.cu @@ -18,10 +18,32 @@ namespace StreamCompaction { * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { + int bufferLength = n; + int *dev_inputArray; + int *dev_outputArray; + + cudaMalloc((void**)&dev_inputArray, bufferLength * sizeof(int)); + checkCUDAError("cudaMalloc dev_inputArray failed!"); + + cudaMalloc((void**)&dev_outputArray, bufferLength * sizeof(int)); + checkCUDAError("cudaMalloc dev_outputArray failed!"); + + cudaMemset(dev_inputArray, 0, bufferLength * sizeof(int)); + cudaMemset(dev_outputArray, 0, bufferLength * sizeof(int)); + + cudaMemcpy(dev_inputArray, idata, n * sizeof(int), cudaMemcpyHostToDevice); + + thrust::device_ptr dev_thrust_inputArray = thrust::device_pointer_cast(dev_inputArray); + thrust::device_ptr dev_thrust_outputArray = thrust::device_pointer_cast(dev_outputArray); + timer().startGpuTimer(); // TODO use `thrust::exclusive_scan` // 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_thrust_inputArray, dev_thrust_inputArray + bufferLength, dev_thrust_outputArray); + + cudaMemcpy(odata, dev_outputArray, n * sizeof(int), cudaMemcpyDeviceToHost); + timer().endGpuTimer(); } } From 78e4ce97d55c225e0acd7ae8b5591d74a912aa13 Mon Sep 17 00:00:00 2001 From: Grace Gilbert Date: Mon, 16 Sep 2019 10:37:31 -0500 Subject: [PATCH 06/38] in progress mlp --- .../character_recognition/mlp.cu | 167 ++++++++++- .../character_recognition/mlp.h | 7 + Project2-Character-Recognition/src/main.cpp | 265 +++++++++--------- 3 files changed, 302 insertions(+), 137 deletions(-) diff --git a/Project2-Character-Recognition/character_recognition/mlp.cu b/Project2-Character-Recognition/character_recognition/mlp.cu index 7cb42fd..0be2f26 100644 --- a/Project2-Character-Recognition/character_recognition/mlp.cu +++ b/Project2-Character-Recognition/character_recognition/mlp.cu @@ -2,6 +2,9 @@ #include #include "common.h" #include "mlp.h" +#include +#include +#include namespace CharacterRecognition { using Common::PerformanceTimer; @@ -23,7 +26,169 @@ namespace CharacterRecognition { } */ - void mlp(int n, int *odata, const int *idata) { + int blockSize = 128; + dim3 threadsPerBlock(blockSize); + + /*__host__ __device__ glm::vec3 generateRandomVec3(float time, int index) { + thrust::default_random_engine rng(hash((int)(index * time))); + thrust::uniform_real_distribution unitDistrib(-1, 1); + + return glm::vec3((float)unitDistrib(rng), (float)unitDistrib(rng), (float)unitDistrib(rng)); + }*/ + + __host__ __device__ unsigned int hash(unsigned int a) { + a = (a + 0x7ed55d16) + (a << 12); + a = (a ^ 0xc761c23c) ^ (a >> 19); + a = (a + 0x165667b1) + (a << 5); + a = (a + 0xd3a2646c) ^ (a << 9); + a = (a + 0xfd7046c5) + (a << 3); + a = (a ^ 0xb55a4f09) ^ (a >> 16); + return a; + } + + __global__ void kernFillRandom(int N, float *weights, float time) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= N) { + return; + } + + + thrust::default_random_engine rng(hash((int)(index * time))); + thrust::uniform_real_distribution unitDistrib(-50, 50); + + + weights[index] = (float)unitDistrib(rng); + } + + + void fillRandomWeights(int n, float *data) { + float *dev_weightsArray; + + cudaMalloc((void**)&dev_weightsArray, n * sizeof(float)); + checkCUDAError("cudaMalloc dev_weightsArray failed!"); + + int numThreads = n; + dim3 blocksPerGrid((numThreads + blockSize - 1) / blockSize); + + kernFillRandom<<>>(numThreads, dev_weightsArray, 2); + checkCUDAError("kernFillRandom failed!"); + + cudaMemcpy(data, dev_weightsArray, n * sizeof(float), cudaMemcpyDeviceToHost); + + cudaFree(dev_weightsArray); + } + + void updateWeights(int n, int *input, float *weights, const float *patialErrorDeriv, float error) { + + } + + + __global__ void kernLayer1Mult(int numHidden, float *hiddenLayers, int inputSize, const float* input, const float *weights) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= numHidden) { + return; + } + float sum = 0; + for (int i = 0; i < inputSize; ++i) { + sum += input[i] * weights[index + numHidden * i]; + } + + hiddenLayers[index] = 1 / (1 + exp(-sum)); + } + + __global__ void kernLayer2Mult(int n, int numHiddenlayers, float *output, const float *hiddenLayers, const float *weights) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= n) { + return; + } + float sum = 0; + for (int i = 0; i < numHiddenlayers; ++i) { + sum += hiddenLayers[i] * weights[i]; + } + output[index] = 1 / (1 + exp(-sum)); + } + + + float mlp(int inputSize, int numHiddenLayers, float expectedValue, + const float *weights1, const float *weights2, + const float *idata, + float *adjustedWeights1, float *adjustedWeights2) { + // size of input is 2 for xor and 512 by 512 for characters + // hidden layer somewhere between 1 and size of input + // first number of weights is size of hidden layer * size of input + // second number of weights is size of hidden layer * size of output(1) + + int numWeights1 = inputSize * numHiddenLayers; + int numWeights2 = numHiddenLayers; + + float *dev_inputData; + float *dev_hidden; + float *dev_weights1; + float *dev_weights2; + float *dev_output; + + float *host_output; + + float *host_hidden; + + + cudaMalloc((void**)&dev_inputData, inputSize * sizeof(float)); + checkCUDAError("cudaMalloc dev_inputData failed!"); + + cudaMalloc((void**)&dev_hidden, numHiddenLayers * sizeof(float)); + checkCUDAError("cudaMalloc dev_hidden failed!"); + + cudaMallocHost((void**)&host_hidden, numHiddenLayers * sizeof(float)); + checkCUDAError("cudaMallocHost host_hidden failed!"); + + cudaMalloc((void**)&dev_weights1, numWeights1 * sizeof(float)); + checkCUDAError("cudaMalloc dev_weights1 failed!"); + + cudaMalloc((void**)&dev_weights2, numWeights2 * sizeof(float)); + checkCUDAError("cudaMalloc dev_weights2 failed!"); + + cudaMalloc((void**)&dev_output, sizeof(float)); + checkCUDAError("cudaMalloc dev_output failed!"); + + cudaMallocHost((void**)&host_output, sizeof(float)); + checkCUDAError("cudaMallocHost host_output failed!"); + + cudaMemcpy(dev_inputData, idata, inputSize * sizeof(float), cudaMemcpyHostToDevice); + cudaMemcpy(dev_weights1, weights1, numWeights1 * sizeof(float), cudaMemcpyHostToDevice); + cudaMemcpy(dev_weights2, weights2, numWeights2 * sizeof(float), cudaMemcpyHostToDevice); + + + + int numThreads = numHiddenLayers; + dim3 blocksPerGrid((numThreads + blockSize - 1) / blockSize); + + kernLayer1Mult<<>>(numHiddenLayers, dev_hidden, inputSize, dev_inputData, dev_weights1); + + + int layer2_numThreads = 1; + dim3 layer2_blocksPerGrid((layer2_numThreads + blockSize - 1) / blockSize); + + + kernLayer2Mult<<>>(1, numHiddenLayers, dev_output, dev_hidden, dev_weights2); + + cudaMemcpy(host_output, dev_output, sizeof(float), cudaMemcpyDeviceToHost); + float output = host_output[0]; + + float error = (output - expectedValue) * (output - expectedValue); + std::cout << "error " << error << std::endl; + + + + cudaFree(dev_inputData); + cudaFree(dev_hidden); + cudaFree(dev_weights1); + cudaFree(dev_weights2); + cudaFree(dev_output); + cudaFreeHost(host_output); + cudaFreeHost(host_hidden); + + + return output; } diff --git a/Project2-Character-Recognition/character_recognition/mlp.h b/Project2-Character-Recognition/character_recognition/mlp.h index 2096228..34be758 100644 --- a/Project2-Character-Recognition/character_recognition/mlp.h +++ b/Project2-Character-Recognition/character_recognition/mlp.h @@ -6,4 +6,11 @@ namespace CharacterRecognition { Common::PerformanceTimer& timer(); // TODO: implement required elements for MLP sections 1 and 2 here + void fillRandomWeights(int n, float *data); + + void updateWeights(int n, int *input, float *weights, const float *patialErrorDeriv, float error); + + float mlp(int inputSize, int numHiddenLayers, float expectedValue, + const float *weights1, const float *weights2, + const float *idata, float *adjustedWeights1, float *adjustedWeights2); } diff --git a/Project2-Character-Recognition/src/main.cpp b/Project2-Character-Recognition/src/main.cpp index df67917..59ac5d0 100644 --- a/Project2-Character-Recognition/src/main.cpp +++ b/Project2-Character-Recognition/src/main.cpp @@ -10,143 +10,136 @@ #include #include #include "testing_helpers.hpp" +#include +#include +#include + + + // Define mode to training or running + // Define acceptedError 0.0001 +#define training 1 // If set to 1, indicates we are in training mode +#define acceptedError 0.001 +#define inputSize 2 +#define numInputs 4 -//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]; 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; + + if (training) { + // Fill in all data: + //std::vector inputArrays = std::vector(); + std::map inputOutputMap = std::map(); + float *data1 = new float[2]; + float *data2 = new float[2]; + float *data3 = new float[2]; + float *data4 = new float[2]; + + data1[0] = 0; + data1[1] = 0; + data2[0] = 0; + data3[1] = 0; + data2[1] = 1; + data3[0] = 1; + data4[0] = 1; + data4[1] = 1; + + inputOutputMap.insert(std::pair(data1, 0)); + inputOutputMap.insert(std::pair(data2, 1)); + inputOutputMap.insert(std::pair(data3, 1)); + inputOutputMap.insert(std::pair(data4, 0)); + + + + + /*for (std::pair i : inputOutputMap) { + std::cout << "(" << i.first[0] << ", " << i.first[1] << "): " << i.second << std::endl; + }*/ + + // Setup weights arrays: + int numHiddenLayers = ceil((inputSize + 1) / 2.0); + + int layer1_numWeights = inputSize * numHiddenLayers; + int layer2_numWeights = numHiddenLayers; + + float *layer1_weights = new float[layer1_numWeights]; + float *layer2_weights = new float[layer2_numWeights]; + + float *layer1_adjustedWeights = new float[layer1_numWeights]; + float *layer2_adjustedWeights = new float[layer2_numWeights]; + + CharacterRecognition::fillRandomWeights(layer1_numWeights, layer1_weights); + CharacterRecognition::fillRandomWeights(layer2_numWeights, layer2_weights); + + layer1_weights[0] = 10.1; + layer1_weights[1] = 0.9; + layer1_weights[2] = 20; + layer1_weights[3] = 0.87; + + layer2_weights[0] = 41; + layer2_weights[1] = -54; + + for (int i = 0; i < layer1_numWeights; ++i) { + std::cout << layer1_weights[i] << std::endl; + } + for (int i = 0; i < layer2_numWeights; ++i) { + std::cout << layer2_weights[i] << std::endl; + } + + float accumulatedError = 3.0; + while (accumulatedError > acceptedError) { + accumulatedError = 0.0; + for (std::pair i : inputOutputMap) { + float currExpected = i.second; + std::cout << i.first[0] << ", " << i.first[1] << std::endl; + + float output = CharacterRecognition::mlp(inputSize, numHiddenLayers, currExpected, layer1_weights, layer2_weights, i.first, layer1_adjustedWeights, layer2_adjustedWeights); + float currError = (output - currExpected) * (output - currExpected); + accumulatedError += currError; + } + accumulatedError /= 2.0; + for (int i = 0; i < layer1_numWeights; ++i) { + layer1_weights[i] = layer1_adjustedWeights[i]; + } + for (int i = 0; i < layer2_numWeights; ++i) { + layer2_weights[i] = layer2_adjustedWeights[i]; + } + accumulatedError = 0.0; + } + + /*std::cout << "layer1 weights:" << std::endl; + for (int i = 0; i < layer1_numWeights; ++i) { + std::cout << layer1_weights[i] << std::endl; + } + std::cout << "layer2 weights:" << std::endl; + for (int i = 0; i < layer2_numWeights; ++i) { + std::cout << layer2_weights[i] << std::endl; + }*/ + + + } + /* + // if training + + randomize weights (kernel function) + for 1 through 52, get the text numbers values into an array: + Read file data into an array of arrays for all inputs + + for every array in array of inputs, pass in to mlp as the input data + Accumulate all the error values + + while error is not within accepted error range. + + adjust the weights (adjust weights) + rerun mlp + get new error + + keep final weights + print out output weights, or write out to a txt file + + if not training + read weights from text file + read the input from the text file + run the mlp just to get the final result, no error calculation + */ + } From 3a80fbdbfb9e9709d763148526f29320d1418a27 Mon Sep 17 00:00:00 2001 From: Grace Gilbert Date: Tue, 17 Sep 2019 00:16:50 -0500 Subject: [PATCH 07/38] base implementation of training done --- .../character_recognition/mlp.cu | 110 +++++++++++++++-- .../character_recognition/mlp.h | 2 +- Project2-Character-Recognition/src/main.cpp | 111 +++++++++++++----- 3 files changed, 183 insertions(+), 40 deletions(-) diff --git a/Project2-Character-Recognition/character_recognition/mlp.cu b/Project2-Character-Recognition/character_recognition/mlp.cu index 0be2f26..c5833f1 100644 --- a/Project2-Character-Recognition/character_recognition/mlp.cu +++ b/Project2-Character-Recognition/character_recognition/mlp.cu @@ -54,14 +54,14 @@ namespace CharacterRecognition { thrust::default_random_engine rng(hash((int)(index * time))); - thrust::uniform_real_distribution unitDistrib(-50, 50); + thrust::uniform_real_distribution unitDistrib(0, 100); weights[index] = (float)unitDistrib(rng); } - void fillRandomWeights(int n, float *data) { + void fillRandomWeights(int n, float *data, float seed) { float *dev_weightsArray; cudaMalloc((void**)&dev_weightsArray, n * sizeof(float)); @@ -70,7 +70,7 @@ namespace CharacterRecognition { int numThreads = n; dim3 blocksPerGrid((numThreads + blockSize - 1) / blockSize); - kernFillRandom<<>>(numThreads, dev_weightsArray, 2); + kernFillRandom<<>>(numThreads, dev_weightsArray, seed); checkCUDAError("kernFillRandom failed!"); cudaMemcpy(data, dev_weightsArray, n * sizeof(float), cudaMemcpyDeviceToHost); @@ -108,6 +108,55 @@ namespace CharacterRecognition { output[index] = 1 / (1 + exp(-sum)); } + __global__ void kernPartialErrorDeriv1(int n, + float expectedValue, float output, float error, int inputSize, int numHidden, + const float *input, const float *hidden, const float *weights2, float *adjustedWeights) { + + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= n) { + return; + } + + float originalWeight = adjustedWeights[index]; // Do the memory acces first and let the following math hide latency + + int inputIndex = floorf(index / (numHidden)); + int hiddenIndex = index % numHidden; + + float inputValue = input[inputIndex]; + float hiddenValue = hidden[hiddenIndex]; + float hiddenWeight = weights2[hiddenIndex]; + + float partialErrorDeriv = -inputValue * (1 / (1 + exp(-hiddenValue))) * + (1 - (1 / (1 + exp(-hiddenValue)))) * (expectedValue - output) * + (1 / (1 + exp(-output))) * (1 - (1 / (1 + exp(-output)))) * + hiddenWeight; + + float deltaWeight = (error / 10.0) * partialErrorDeriv; + + adjustedWeights[index] = originalWeight + deltaWeight; + } + + __global__ void kernPartialErrorDeriv2(int n, + float expectedValue, float output, float error, + const float *hidden, float *adjustedWeights) { + + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= n) { + return; + } + + float originalWeight = adjustedWeights[index]; + + float partialErrorDeriv = (-(expectedValue - output)) * (1 / (1 + exp(-output))) * (1 - (1 / (1 + exp(-output)))) * hidden[index]; + + float deltaWeight = (error / 10.0) * partialErrorDeriv; + + adjustedWeights[index] = originalWeight + deltaWeight; + + + + } + float mlp(int inputSize, int numHiddenLayers, float expectedValue, const float *weights1, const float *weights2, @@ -121,17 +170,21 @@ namespace CharacterRecognition { int numWeights1 = inputSize * numHiddenLayers; int numWeights2 = numHiddenLayers; + + // Initialize buffers float *dev_inputData; float *dev_hidden; float *dev_weights1; float *dev_weights2; + float *dev_adjustedWeights1; + float *dev_adjustedWeights2; float *dev_output; float *host_output; float *host_hidden; - + // Malloc for buffers cudaMalloc((void**)&dev_inputData, inputSize * sizeof(float)); checkCUDAError("cudaMalloc dev_inputData failed!"); @@ -146,6 +199,12 @@ namespace CharacterRecognition { cudaMalloc((void**)&dev_weights2, numWeights2 * sizeof(float)); checkCUDAError("cudaMalloc dev_weights2 failed!"); + + cudaMalloc((void**)&dev_adjustedWeights1, numWeights1 * sizeof(float)); + checkCUDAError("cudaMalloc dev_adjustedWeights1 failed!"); + + cudaMalloc((void**)&dev_adjustedWeights2, numWeights2 * sizeof(float)); + checkCUDAError("cudaMalloc dev_adjustedWeights2 failed!"); cudaMalloc((void**)&dev_output, sizeof(float)); checkCUDAError("cudaMalloc dev_output failed!"); @@ -153,36 +212,67 @@ namespace CharacterRecognition { cudaMallocHost((void**)&host_output, sizeof(float)); checkCUDAError("cudaMallocHost host_output failed!"); + // Fille input and weights data cudaMemcpy(dev_inputData, idata, inputSize * sizeof(float), cudaMemcpyHostToDevice); cudaMemcpy(dev_weights1, weights1, numWeights1 * sizeof(float), cudaMemcpyHostToDevice); cudaMemcpy(dev_weights2, weights2, numWeights2 * sizeof(float), cudaMemcpyHostToDevice); + cudaMemcpy(dev_adjustedWeights1, adjustedWeights1, numWeights1 * sizeof(float), cudaMemcpyHostToDevice); + cudaMemcpy(dev_adjustedWeights2, adjustedWeights2, numWeights2 * sizeof(float), cudaMemcpyHostToDevice); - - + // Perform the multiplications for layer 1 to get the hidden layers int numThreads = numHiddenLayers; dim3 blocksPerGrid((numThreads + blockSize - 1) / blockSize); kernLayer1Mult<<>>(numHiddenLayers, dev_hidden, inputSize, dev_inputData, dev_weights1); - + // perform the multiplications for layer 2 to get the output value int layer2_numThreads = 1; dim3 layer2_blocksPerGrid((layer2_numThreads + blockSize - 1) / blockSize); + kernLayer2Mult<<>>(1, numHiddenLayers, dev_output, dev_hidden, dev_weights2); - kernLayer2Mult<<>>(1, numHiddenLayers, dev_output, dev_hidden, dev_weights2); - + // Copy the output onto the host cudaMemcpy(host_output, dev_output, sizeof(float), cudaMemcpyDeviceToHost); float output = host_output[0]; + // Find the error from the output float error = (output - expectedValue) * (output - expectedValue); - std::cout << "error " << error << std::endl; + //std::cout << "error " << error << std::endl; + + // Adjust the weights of the layer 1 weights + int weight1Adjust_numThreads = numWeights1; + dim3 weight1Adjust_blocksPerGrid((weight1Adjust_numThreads + blockSize - 1) / blockSize); + + kernPartialErrorDeriv1<<>>(numWeights1, expectedValue, + output, error, inputSize, + numHiddenLayers, dev_inputData, dev_hidden, + dev_weights2, dev_adjustedWeights1); + + // Copy the weights into the input array + cudaMemcpy(adjustedWeights1, dev_adjustedWeights1, numWeights1 * sizeof(float), cudaMemcpyDeviceToHost); + + + // Adjust the weights of the layer 2 weights + int weight2Adjust_numThreads = numWeights2; + dim3 weight2Adjust_blocksPerGrid((weight2Adjust_numThreads + blockSize - 1) / blockSize); + + kernPartialErrorDeriv2<<>>(numWeights2, + expectedValue, output, error, dev_hidden, dev_adjustedWeights2); + + cudaMemcpy(adjustedWeights2, dev_adjustedWeights2, numWeights2 * sizeof(float), cudaMemcpyDeviceToHost); + //for (int i = 0; i < numWeights1; ++i) { + // //std::cout << "adjusted weight: " << adjustedWeights1[i] << std::endl; + //} + // Free buffer memory cudaFree(dev_inputData); cudaFree(dev_hidden); cudaFree(dev_weights1); cudaFree(dev_weights2); + cudaFree(dev_adjustedWeights1); + cudaFree(dev_adjustedWeights2); cudaFree(dev_output); cudaFreeHost(host_output); cudaFreeHost(host_hidden); diff --git a/Project2-Character-Recognition/character_recognition/mlp.h b/Project2-Character-Recognition/character_recognition/mlp.h index 34be758..e528c9a 100644 --- a/Project2-Character-Recognition/character_recognition/mlp.h +++ b/Project2-Character-Recognition/character_recognition/mlp.h @@ -6,7 +6,7 @@ namespace CharacterRecognition { Common::PerformanceTimer& timer(); // TODO: implement required elements for MLP sections 1 and 2 here - void fillRandomWeights(int n, float *data); + void fillRandomWeights(int n, float *data, float seed); void updateWeights(int n, int *input, float *weights, const float *patialErrorDeriv, float error); diff --git a/Project2-Character-Recognition/src/main.cpp b/Project2-Character-Recognition/src/main.cpp index 59ac5d0..ea71b82 100644 --- a/Project2-Character-Recognition/src/main.cpp +++ b/Project2-Character-Recognition/src/main.cpp @@ -13,12 +13,14 @@ #include #include #include +#include +#include // Define mode to training or running // Define acceptedError 0.0001 #define training 1 // If set to 1, indicates we are in training mode -#define acceptedError 0.001 +#define acceptedError 0.01 #define inputSize 2 #define numInputs 4 @@ -67,45 +69,96 @@ int main(int argc, char* argv[]) { float *layer1_adjustedWeights = new float[layer1_numWeights]; float *layer2_adjustedWeights = new float[layer2_numWeights]; - CharacterRecognition::fillRandomWeights(layer1_numWeights, layer1_weights); - CharacterRecognition::fillRandomWeights(layer2_numWeights, layer2_weights); + //CharacterRecognition::fillRandomWeights(layer1_numWeights, layer1_weights); + //CharacterRecognition::fillRandomWeights(layer2_numWeights, layer2_weights); - layer1_weights[0] = 10.1; - layer1_weights[1] = 0.9; - layer1_weights[2] = 20; - layer1_weights[3] = 0.87; + ///*layer1_weights[0] = 10.1; + //layer1_weights[1] = 0.9; + //layer1_weights[2] = 20; + //layer1_weights[3] = 0.87; - layer2_weights[0] = 41; - layer2_weights[1] = -54; + //layer2_weights[0] = 41; + //layer2_weights[1] = -54;*/ - for (int i = 0; i < layer1_numWeights; ++i) { - std::cout << layer1_weights[i] << std::endl; - } - for (int i = 0; i < layer2_numWeights; ++i) { - std::cout << layer2_weights[i] << std::endl; - } + //for (int i = 0; i < layer1_numWeights; ++i) { + // layer1_adjustedWeights[i] = layer1_weights[i]; + // std::cout << layer1_adjustedWeights[i] << std::endl; + //} + //for (int i = 0; i < layer2_numWeights; ++i) { + // layer2_adjustedWeights[i] = layer2_weights[i]; + // std::cout << layer2_adjustedWeights[i] << std::endl; + //} + auto start = std::chrono::steady_clock::now(); + int numRandIters = 0; float accumulatedError = 3.0; - while (accumulatedError > acceptedError) { - accumulatedError = 0.0; - for (std::pair i : inputOutputMap) { - float currExpected = i.second; - std::cout << i.first[0] << ", " << i.first[1] << std::endl; - - float output = CharacterRecognition::mlp(inputSize, numHiddenLayers, currExpected, layer1_weights, layer2_weights, i.first, layer1_adjustedWeights, layer2_adjustedWeights); - float currError = (output - currExpected) * (output - currExpected); - accumulatedError += currError; - } - accumulatedError /= 2.0; + while (accumulatedError > acceptedError && numRandIters < 10000) { + auto end1 = std::chrono::steady_clock::now(); + CharacterRecognition::fillRandomWeights(layer1_numWeights, layer1_weights, std::chrono::duration_cast(end1 - start).count()); + auto end2 = std::chrono::steady_clock::now(); + CharacterRecognition::fillRandomWeights(layer2_numWeights, layer2_weights, std::chrono::duration_cast(end2 - start).count()); + + /*CharacterRecognition::fillRandomWeights(layer1_numWeights, layer1_weights, numRandIters); + CharacterRecognition::fillRandomWeights(layer2_numWeights, layer2_weights, numRandIters);*/ for (int i = 0; i < layer1_numWeights; ++i) { - layer1_weights[i] = layer1_adjustedWeights[i]; + layer1_adjustedWeights[i] = layer1_weights[i]; + std::cout << layer1_adjustedWeights[i] << std::endl; } for (int i = 0; i < layer2_numWeights; ++i) { - layer2_weights[i] = layer2_adjustedWeights[i]; + layer2_adjustedWeights[i] = layer2_weights[i]; + std::cout << layer2_adjustedWeights[i] << std::endl; + } + + std::cout << "NEW WEIGHTS" << std::endl; + int numInnerIters = 0.0; + while (accumulatedError > acceptedError && numInnerIters < 10000) { + accumulatedError = 0.0; + for (std::pair i : inputOutputMap) { + float currExpected = i.second; + //std::cout << i.first[0] << ", " << i.first[1] << std::endl; + + float output = CharacterRecognition::mlp(inputSize, numHiddenLayers, currExpected, layer1_weights, layer2_weights, i.first, layer1_adjustedWeights, layer2_adjustedWeights); + float currError = (output - currExpected) * (output - currExpected); + std::cout << "expected output: " << currExpected << " Result: " << output << std::endl; + accumulatedError += currError; + } + accumulatedError /= 2.0; + for (int i = 0; i < layer1_numWeights; ++i) { + layer1_weights[i] = layer1_adjustedWeights[i]; + } + for (int i = 0; i < layer2_numWeights; ++i) { + layer2_weights[i] = layer2_adjustedWeights[i]; + } + + + if (accumulatedError < acceptedError) { + std::cout << "WEIGHTS:" << std::endl; + for (int i = 0; i < layer1_numWeights; ++i) { + std::cout << "layer 1 weight " << i << ": " << layer1_weights[i] << std::endl; + } + for (int i = 0; i < layer2_numWeights; ++i) { + std::cout << "layer 2 weight " << i << ": " << layer2_weights[i] << std::endl; + } + break; + } + numInnerIters++; + } + if (accumulatedError < acceptedError) { + std::cout << "WEIGHTS:" << std::endl; + for (int i = 0; i < layer1_numWeights; ++i) { + std::cout << "layer 1 weight " << i << ": " << layer1_weights[i] << std::endl; + } + for (int i = 0; i < layer2_numWeights; ++i) { + std::cout << "layer 2 weight " << i << ": " << layer2_weights[i] << std::endl; + } + break; } - accumulatedError = 0.0; + + numRandIters++; } + std::cout << "FINAL ERROR: " << accumulatedError << std::endl; + /*std::cout << "layer1 weights:" << std::endl; for (int i = 0; i < layer1_numWeights; ++i) { std::cout << layer1_weights[i] << std::endl; From 119033ee2f75e1f6f23688061d1deebdd71ce43e Mon Sep 17 00:00:00 2001 From: gracelgilbert Date: Tue, 17 Sep 2019 00:19:53 -0500 Subject: [PATCH 08/38] Update README.md --- README.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 3a0b2fe..b1eec40 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,10 @@ CUDA Number Algorithms **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) +* Grace Gilbert + * gracelgilbert@gmail.com +* Tested on: Windows 10, i9-9900K @ 3.60GHz 64GB, GeForce RTX 2080 40860MB -### (TODO: Your README) +[Stream Compaction](/Project2-Stream-Compaction/README.md) -Link to the readmes of the other two subprojects. - -Add anything else you think is relevant up to this point. -(Remember, this is public, so don't put anything here that you don't want to share with the world.) From d0043f1bdd3796372e38ac34d30afac30f3faf67 Mon Sep 17 00:00:00 2001 From: gracelgilbert Date: Tue, 17 Sep 2019 00:27:08 -0500 Subject: [PATCH 09/38] Update README.md --- Project2-Stream-Compaction/README.md | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Project2-Stream-Compaction/README.md b/Project2-Stream-Compaction/README.md index 0e38ddb..f99e8d5 100644 --- a/Project2-Stream-Compaction/README.md +++ b/Project2-Stream-Compaction/README.md @@ -3,12 +3,22 @@ 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) +* Grace Gilbert + * gracelgilbert.com +* Tested on: Windows 10, i9-9900K @ 3.60GHz 64GB, GeForce RTX 2080 40860MB -### (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.) +## Overview +In this project, I implemented the stream compaction algorithm on the GPU in CUDA. Stream compaction is an algorithm that, given an array of values marked to remove or keep, removes the values and returns a new, shorter array with the values removed. This algorithm has many practical applications, including path tracing, as it lets us mark certain elements as unwanted and remove them. While there is a simple way to perform this algorithm using loops on the CPU, it can also be parallelized to be more efficiently performed on the GPU. I implemented 4 versions of this algorithm, one on the CPU, a naive version on the GPU, a more efficient version on the GPU, and then using the thrust implementation. + +## CPU + +## GPU +### Naive +### Efficient +### Thrust + +## Performance Analysis + +## Questions From 124361bfe298533acd108e907652cd0d4f3e91ef Mon Sep 17 00:00:00 2001 From: Grace Gilbert Date: Tue, 17 Sep 2019 00:30:06 -0500 Subject: [PATCH 10/38] changed random value range --- Project2-Character-Recognition/character_recognition/mlp.cu | 2 +- Project2-Character-Recognition/src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Project2-Character-Recognition/character_recognition/mlp.cu b/Project2-Character-Recognition/character_recognition/mlp.cu index c5833f1..e499620 100644 --- a/Project2-Character-Recognition/character_recognition/mlp.cu +++ b/Project2-Character-Recognition/character_recognition/mlp.cu @@ -54,7 +54,7 @@ namespace CharacterRecognition { thrust::default_random_engine rng(hash((int)(index * time))); - thrust::uniform_real_distribution unitDistrib(0, 100); + thrust::uniform_real_distribution unitDistrib(-50, 50); weights[index] = (float)unitDistrib(rng); diff --git a/Project2-Character-Recognition/src/main.cpp b/Project2-Character-Recognition/src/main.cpp index ea71b82..a82f62f 100644 --- a/Project2-Character-Recognition/src/main.cpp +++ b/Project2-Character-Recognition/src/main.cpp @@ -111,7 +111,7 @@ int main(int argc, char* argv[]) { std::cout << "NEW WEIGHTS" << std::endl; int numInnerIters = 0.0; - while (accumulatedError > acceptedError && numInnerIters < 10000) { + while (accumulatedError > acceptedError && numInnerIters < 1000) { accumulatedError = 0.0; for (std::pair i : inputOutputMap) { float currExpected = i.second; From 69a2c0abb8677815f88b7e4625b91ca3334981f8 Mon Sep 17 00:00:00 2001 From: gracelgilbert Date: Tue, 17 Sep 2019 00:36:53 -0500 Subject: [PATCH 11/38] Update README.md --- Project2-Stream-Compaction/README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Project2-Stream-Compaction/README.md b/Project2-Stream-Compaction/README.md index f99e8d5..7fe21a7 100644 --- a/Project2-Stream-Compaction/README.md +++ b/Project2-Stream-Compaction/README.md @@ -9,9 +9,26 @@ CUDA Stream Compaction ## Overview -In this project, I implemented the stream compaction algorithm on the GPU in CUDA. Stream compaction is an algorithm that, given an array of values marked to remove or keep, removes the values and returns a new, shorter array with the values removed. This algorithm has many practical applications, including path tracing, as it lets us mark certain elements as unwanted and remove them. While there is a simple way to perform this algorithm using loops on the CPU, it can also be parallelized to be more efficiently performed on the GPU. I implemented 4 versions of this algorithm, one on the CPU, a naive version on the GPU, a more efficient version on the GPU, and then using the thrust implementation. +In this project, I implemented the stream compaction algorithm on the GPU in CUDA. Stream compaction is an algorithm that, given an array of values marked to remove or keep, removes the values and returns a new, shorter array with the values removed. Below is a diagram representing the stream compaction algorithm: + +# INCLUDE SC DIAGRAM FROM LECTURE + +This algorithm has many practical applications, including path tracing, as it lets us mark certain elements as unwanted and remove them. While there is a simple way to perform this algorithm using loops on the CPU, it can also be parallelized to be more efficiently performed on the GPU. I implemented 4 versions of this algorithm, one on the CPU, a naive version on the GPU, a more efficient version on the GPU, and then using the thrust implementation. + +An important step in the stream compaction algorithm is the scan algorithm. This algorithm goes through an array and accumulates additively all of the elements in the array. An exclusive scan excludes the current index in the accumulated sum, whereas inclusive scan includes the current index. Steam compaction uses an exclusive scan. Below is a diagram representing the scan algorithm: + +# INCLUDE SCAN DIAGRAM FROM LECTURE ## CPU +#### Scan +The scan algorithm on the cpu is a simple loop over the data. For an exclusive scan, we set the first value of the output to 0, as no sum has been accumulated. Then from index 1 through arrayLength - 1, we set the output at that index to the sum of the output at the previous index and the input array at the previous index: + +``` +outputData[0] = 0; + for (int k = 1; k < n; ++k) { + outputData[k] = outoutData[k - 1] + inputData[k - 1]; + } +``` ## GPU ### Naive From 13f584655b93a293ac76047d94741bfb2c37f812 Mon Sep 17 00:00:00 2001 From: gracelgilbert Date: Tue, 17 Sep 2019 00:37:27 -0500 Subject: [PATCH 12/38] Update README.md --- Project2-Stream-Compaction/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Project2-Stream-Compaction/README.md b/Project2-Stream-Compaction/README.md index 7fe21a7..42a5eda 100644 --- a/Project2-Stream-Compaction/README.md +++ b/Project2-Stream-Compaction/README.md @@ -25,9 +25,9 @@ The scan algorithm on the cpu is a simple loop over the data. For an exclusive ``` outputData[0] = 0; - for (int k = 1; k < n; ++k) { - outputData[k] = outoutData[k - 1] + inputData[k - 1]; - } +for (int k = 1; k < n; ++k) { + outputData[k] = outoutData[k - 1] + inputData[k - 1]; +} ``` ## GPU From 5688aea5e20fed69f0bb23843225d458e3a600b6 Mon Sep 17 00:00:00 2001 From: gracelgilbert Date: Tue, 17 Sep 2019 00:41:13 -0500 Subject: [PATCH 13/38] Update README.md --- Project2-Stream-Compaction/README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Project2-Stream-Compaction/README.md b/Project2-Stream-Compaction/README.md index 42a5eda..6a02880 100644 --- a/Project2-Stream-Compaction/README.md +++ b/Project2-Stream-Compaction/README.md @@ -30,6 +30,19 @@ for (int k = 1; k < n; ++k) { } ``` +#### Stream Compaction without Scan +The basic stream compaction algorithm tracks a counter of how many elements to include that we have seen. If we see an element to include, it sets the output at the index of the counter to the element value, then increments the counter. +``` +int counter = 0; +for (int k = 0; k < n; ++k) { + int currentValue = inputData[k]; + if (currentValue != 0) { + outputData[counter] = currentValue; + counter++; + } +} +``` + ## GPU ### Naive ### Efficient From 539864d70586b34cb3ad5baf2977759b14135630 Mon Sep 17 00:00:00 2001 From: gracelgilbert Date: Tue, 17 Sep 2019 00:41:49 -0500 Subject: [PATCH 14/38] Update README.md --- Project2-Stream-Compaction/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Project2-Stream-Compaction/README.md b/Project2-Stream-Compaction/README.md index 6a02880..8394772 100644 --- a/Project2-Stream-Compaction/README.md +++ b/Project2-Stream-Compaction/README.md @@ -37,9 +37,9 @@ int counter = 0; for (int k = 0; k < n; ++k) { int currentValue = inputData[k]; if (currentValue != 0) { - outputData[counter] = currentValue; - counter++; - } + outputData[counter] = currentValue; + counter++; + } } ``` From 179fe9b23356eef1ad1b6f83336f6154c778809a Mon Sep 17 00:00:00 2001 From: gracelgilbert Date: Tue, 17 Sep 2019 00:50:19 -0500 Subject: [PATCH 15/38] Update README.md --- Project2-Stream-Compaction/README.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/Project2-Stream-Compaction/README.md b/Project2-Stream-Compaction/README.md index 8394772..cfd8c84 100644 --- a/Project2-Stream-Compaction/README.md +++ b/Project2-Stream-Compaction/README.md @@ -13,12 +13,14 @@ In this project, I implemented the stream compaction algorithm on the GPU in CUD # INCLUDE SC DIAGRAM FROM LECTURE -This algorithm has many practical applications, including path tracing, as it lets us mark certain elements as unwanted and remove them. While there is a simple way to perform this algorithm using loops on the CPU, it can also be parallelized to be more efficiently performed on the GPU. I implemented 4 versions of this algorithm, one on the CPU, a naive version on the GPU, a more efficient version on the GPU, and then using the thrust implementation. +This algorithm has many practical applications, including path tracing, as it lets us mark certain elements as unwanted and remove them. While there is a simple way to perform this algorithm using loops on the CPU, it can also be parallelized to be more efficiently performed on the GPU. An important step in the stream compaction algorithm is the scan algorithm. This algorithm goes through an array and accumulates additively all of the elements in the array. An exclusive scan excludes the current index in the accumulated sum, whereas inclusive scan includes the current index. Steam compaction uses an exclusive scan. Below is a diagram representing the scan algorithm: # INCLUDE SCAN DIAGRAM FROM LECTURE +I implemented 4 versions of the above algorithms, scan and stream compaction on the CPU, a naive version of scan on the GPU, a more efficient version of both on the GPU, and then using the thrust implementation of scan. + ## CPU #### Scan The scan algorithm on the cpu is a simple loop over the data. For an exclusive scan, we set the first value of the output to 0, as no sum has been accumulated. Then from index 1 through arrayLength - 1, we set the output at that index to the sum of the output at the previous index and the input array at the previous index: @@ -43,6 +45,27 @@ for (int k = 0; k < n; ++k) { } ``` +#### Stream Compaction with Scan and Scatter +In this version, I start by creating a temporary array that contains a 1 wherever the input array had a nonzero value, and 0 where the input array had a zero value: +``` +int *tempArray = new int[n]; +for (int k = 0; k < n; ++k) { + tempArray[k] = (int) inputData[k] != 0; +} +``` +I then call scan on that 0/1 array using the CPU implementation from above. I then iterate through all the indices in the input array, and if the value should be included, I get the scan result value at that index and put the input value at that scan result index in the output array. +``` +int *scanResult = new int[n]; +scan(n, scanResult, tempArray); +for (int k = 0; k < n; ++k) { + if (tempArray[k]) { + int index = scanResult[k]; + odata[index] = idata[k]; + } +} +``` + + ## GPU ### Naive ### Efficient From 2965dc1c87d1f61c3fa246d2e4c4d709ee042964 Mon Sep 17 00:00:00 2001 From: gracelgilbert Date: Tue, 17 Sep 2019 00:52:02 -0500 Subject: [PATCH 16/38] Update README.md --- Project2-Stream-Compaction/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Project2-Stream-Compaction/README.md b/Project2-Stream-Compaction/README.md index cfd8c84..cd5049e 100644 --- a/Project2-Stream-Compaction/README.md +++ b/Project2-Stream-Compaction/README.md @@ -70,6 +70,10 @@ for (int k = 0; k < n; ++k) { ### Naive ### Efficient ### Thrust +For this implementation, I simply cast the input and output array buffers to thrust device pointers and then run thrust's exclusive scane on the buffers. +``` +thrust::exclusive_scan(dev_thrust_inputArray, dev_thrust_inputArray + bufferLength, dev_thrust_outputArray); +``` ## Performance Analysis From 2785b87c28c7937c0d72b9eeedc1bd7f45abd9c7 Mon Sep 17 00:00:00 2001 From: Grace Gilbert Date: Tue, 17 Sep 2019 14:25:04 -0500 Subject: [PATCH 17/38] progress fixing error propogation --- .../character_recognition/mlp.cu | 2 +- .../character_recognition/mlp.h | 2 +- Project2-Character-Recognition/src/main.cpp | 214 +++++++++++++----- 3 files changed, 164 insertions(+), 54 deletions(-) diff --git a/Project2-Character-Recognition/character_recognition/mlp.cu b/Project2-Character-Recognition/character_recognition/mlp.cu index e499620..a608d73 100644 --- a/Project2-Character-Recognition/character_recognition/mlp.cu +++ b/Project2-Character-Recognition/character_recognition/mlp.cu @@ -161,7 +161,7 @@ namespace CharacterRecognition { float mlp(int inputSize, int numHiddenLayers, float expectedValue, const float *weights1, const float *weights2, const float *idata, - float *adjustedWeights1, float *adjustedWeights2) { + float *adjustedWeights1, float *adjustedWeights2, float *partialDerivatives1, float *partialDerivatives2) { // size of input is 2 for xor and 512 by 512 for characters // hidden layer somewhere between 1 and size of input // first number of weights is size of hidden layer * size of input diff --git a/Project2-Character-Recognition/character_recognition/mlp.h b/Project2-Character-Recognition/character_recognition/mlp.h index e528c9a..58acc58 100644 --- a/Project2-Character-Recognition/character_recognition/mlp.h +++ b/Project2-Character-Recognition/character_recognition/mlp.h @@ -12,5 +12,5 @@ namespace CharacterRecognition { float mlp(int inputSize, int numHiddenLayers, float expectedValue, const float *weights1, const float *weights2, - const float *idata, float *adjustedWeights1, float *adjustedWeights2); + const float *idata, float *adjustedWeights1, float *adjustedWeights2, float *partialDerivatives1, float *partialDerivatives2); } diff --git a/Project2-Character-Recognition/src/main.cpp b/Project2-Character-Recognition/src/main.cpp index a82f62f..b9f41dc 100644 --- a/Project2-Character-Recognition/src/main.cpp +++ b/Project2-Character-Recognition/src/main.cpp @@ -15,6 +15,10 @@ #include #include #include +#include +#include +#include + // Define mode to training or running @@ -50,13 +54,53 @@ int main(int argc, char* argv[]) { inputOutputMap.insert(std::pair(data3, 1)); inputOutputMap.insert(std::pair(data4, 0)); + // Read in training data: + //for (int i = 1; i <= numInputs; ++i) { + /*std::string filename = std::to_string(i) + "info.txt"; + if (i < 10) { + filename = "0" + filename; + } + filename = "../data-set/" + filename; + + std::ifstream inputFile; + inputFile.open(filename); + if (inputFile.is_open()) { + std::string firstLine; + getline(inputFile, firstLine); + int expectedVal = std::stoi(firstLine); + + std::string secondLine; + getline(inputFile, secondLine); + int inputLength = std::stoi(secondLine); + + float *currData = new float[inputLength]; + int counter = 0; + + std::string dataLine; + getline(inputFile, dataLine); + + std::stringstream stream(dataLine); + while (1) { + int n; + stream >> n; + if (!stream) { + break; + } + currData[counter] = n; + counter++; + } + inputOutputMap.insert(std::pair(currData, expectedVal)); + } + inputFile.close(); + }*/ + - - /*for (std::pair i : inputOutputMap) { - std::cout << "(" << i.first[0] << ", " << i.first[1] << "): " << i.second << std::endl; + /*for (std::pair i : inputOutputMap) { + std::cout << "(" << i.first[4] << ", " << i.first[5] << "): " << i.second << std::endl; }*/ + // Setup weights arrays: int numHiddenLayers = ceil((inputSize + 1) / 2.0); @@ -69,88 +113,85 @@ int main(int argc, char* argv[]) { float *layer1_adjustedWeights = new float[layer1_numWeights]; float *layer2_adjustedWeights = new float[layer2_numWeights]; - //CharacterRecognition::fillRandomWeights(layer1_numWeights, layer1_weights); - //CharacterRecognition::fillRandomWeights(layer2_numWeights, layer2_weights); - - ///*layer1_weights[0] = 10.1; - //layer1_weights[1] = 0.9; - //layer1_weights[2] = 20; - //layer1_weights[3] = 0.87; + + layer1_weights[0] = 10.1; + layer1_weights[1] = 0.9; + layer1_weights[2] = 20; + layer1_weights[3] = 0.87; - //layer2_weights[0] = 41; - //layer2_weights[1] = -54;*/ + layer2_weights[0] = 41; + layer2_weights[1] = -54; - //for (int i = 0; i < layer1_numWeights; ++i) { - // layer1_adjustedWeights[i] = layer1_weights[i]; - // std::cout << layer1_adjustedWeights[i] << std::endl; - //} - //for (int i = 0; i < layer2_numWeights; ++i) { - // layer2_adjustedWeights[i] = layer2_weights[i]; - // std::cout << layer2_adjustedWeights[i] << std::endl; - //} + float *partialDerivatives1 = new float[layer1_numWeights * numInputs]; + float *partialDerivatives2 = new float[layer2_numWeights * numInputs]; auto start = std::chrono::steady_clock::now(); int numRandIters = 0; - float accumulatedError = 3.0; - while (accumulatedError > acceptedError && numRandIters < 10000) { - auto end1 = std::chrono::steady_clock::now(); + float accumulatedError = 3.0; // Larger than accepted error + while (accumulatedError > acceptedError && numRandIters < 1) { + // Fill new random weights + /*auto end1 = std::chrono::steady_clock::now(); CharacterRecognition::fillRandomWeights(layer1_numWeights, layer1_weights, std::chrono::duration_cast(end1 - start).count()); auto end2 = std::chrono::steady_clock::now(); CharacterRecognition::fillRandomWeights(layer2_numWeights, layer2_weights, std::chrono::duration_cast(end2 - start).count()); - - /*CharacterRecognition::fillRandomWeights(layer1_numWeights, layer1_weights, numRandIters); - CharacterRecognition::fillRandomWeights(layer2_numWeights, layer2_weights, numRandIters);*/ + std::cout << "NEW WEIGHTS" << std::endl;*/ + + + // Copy new weights to adjusted weights array for (int i = 0; i < layer1_numWeights; ++i) { layer1_adjustedWeights[i] = layer1_weights[i]; - std::cout << layer1_adjustedWeights[i] << std::endl; } for (int i = 0; i < layer2_numWeights; ++i) { layer2_adjustedWeights[i] = layer2_weights[i]; - std::cout << layer2_adjustedWeights[i] << std::endl; } - std::cout << "NEW WEIGHTS" << std::endl; int numInnerIters = 0.0; - while (accumulatedError > acceptedError && numInnerIters < 1000) { + // Try refining weights iteratively + while (accumulatedError > acceptedError && numInnerIters < 1) { accumulatedError = 0.0; + for (std::pair i : inputOutputMap) { float currExpected = i.second; - //std::cout << i.first[0] << ", " << i.first[1] << std::endl; + float output = CharacterRecognition::mlp(inputSize, numHiddenLayers, currExpected, layer1_weights, layer2_weights, i.first, layer1_adjustedWeights, layer2_adjustedWeights, partialDerivatives1, partialDerivatives2); + - float output = CharacterRecognition::mlp(inputSize, numHiddenLayers, currExpected, layer1_weights, layer2_weights, i.first, layer1_adjustedWeights, layer2_adjustedWeights); float currError = (output - currExpected) * (output - currExpected); std::cout << "expected output: " << currExpected << " Result: " << output << std::endl; accumulatedError += currError; } accumulatedError /= 2.0; - for (int i = 0; i < layer1_numWeights; ++i) { - layer1_weights[i] = layer1_adjustedWeights[i]; - } - for (int i = 0; i < layer2_numWeights; ++i) { - layer2_weights[i] = layer2_adjustedWeights[i]; - } - - + // If error is low enough, print out weights and break if (accumulatedError < acceptedError) { - std::cout << "WEIGHTS:" << std::endl; + /*std::cout << "WEIGHTS:" << std::endl; for (int i = 0; i < layer1_numWeights; ++i) { std::cout << "layer 1 weight " << i << ": " << layer1_weights[i] << std::endl; } for (int i = 0; i < layer2_numWeights; ++i) { std::cout << "layer 2 weight " << i << ": " << layer2_weights[i] << std::endl; - } - break; + }*/ + //break; + } + + // Copy adjusted weights into actual weights for next iteration + for (int i = 0; i < layer1_numWeights; ++i) { + layer1_weights[i] = layer1_adjustedWeights[i]; + std::cout << layer1_weights[i] << std::endl; + } + for (int i = 0; i < layer2_numWeights; ++i) { + layer2_weights[i] = layer2_adjustedWeights[i]; + std::cout << layer2_weights[i] << std::endl; } numInnerIters++; } + // If error is low enough, print weights and break if (accumulatedError < acceptedError) { - std::cout << "WEIGHTS:" << std::endl; + /*std::cout << "WEIGHTS:" << std::endl; for (int i = 0; i < layer1_numWeights; ++i) { std::cout << "layer 1 weight " << i << ": " << layer1_weights[i] << std::endl; } for (int i = 0; i < layer2_numWeights; ++i) { std::cout << "layer 2 weight " << i << ": " << layer2_weights[i] << std::endl; - } + }*/ break; } @@ -159,17 +200,86 @@ int main(int argc, char* argv[]) { std::cout << "FINAL ERROR: " << accumulatedError << std::endl; - /*std::cout << "layer1 weights:" << std::endl; - for (int i = 0; i < layer1_numWeights; ++i) { - std::cout << layer1_weights[i] << std::endl; + // Delete data arrays stored in map + for (std::pair i : inputOutputMap) { + delete[] i.first; } - std::cout << "layer2 weights:" << std::endl; - for (int i = 0; i < layer2_numWeights; ++i) { - std::cout << layer2_weights[i] << std::endl; - }*/ + delete[] partialDerivatives; + } + else { + std::map inputOutputMap = std::map(); + // Read in data: + for (int i = 1; i <= numInputs; ++i) { + std::string filename = std::to_string(i) + "info.txt"; + if (i < 10) { + filename = "0" + filename; + } + filename = "../data-set/" + filename; + + std::ifstream inputFile; + inputFile.open(filename); + if (inputFile.is_open()) { + std::string firstLine; + getline(inputFile, firstLine); + int expectedVal = std::stoi(firstLine); + + std::string secondLine; + getline(inputFile, secondLine); + int inputLength = std::stoi(secondLine); + + float *currData = new float[inputLength]; + int counter = 0; + + std::string dataLine; + getline(inputFile, dataLine); + + std::stringstream stream(dataLine); + while (1) { + int n; + stream >> n; + if (!stream) { + break; + } + currData[counter] = n; + counter++; + } + inputOutputMap.insert(std::pair(currData, expectedVal)); + } + inputFile.close(); + } + // Setup weights arrays: + int numHiddenLayers = ceil((inputSize + 1) / 2.0); + int layer1_numWeights = inputSize * numHiddenLayers; + int layer2_numWeights = numHiddenLayers; + + float *layer1_weights = new float[layer1_numWeights]; + float *layer2_weights = new float[layer2_numWeights]; + + float *layer1_adjustedWeights = new float[layer1_numWeights]; + float *layer2_adjustedWeights = new float[layer2_numWeights]; + + auto start = std::chrono::steady_clock::now(); + + // Fill weights + auto end1 = std::chrono::steady_clock::now(); + CharacterRecognition::fillRandomWeights(layer1_numWeights, layer1_weights, std::chrono::duration_cast(end1 - start).count()); + auto end2 = std::chrono::steady_clock::now(); + CharacterRecognition::fillRandomWeights(layer2_numWeights, layer2_weights, std::chrono::duration_cast(end2 - start).count()); + + for (std::pair i : inputOutputMap) { + float currExpected = i.second; + float output = CharacterRecognition::mlp(inputSize, numHiddenLayers, currExpected, layer1_weights, layer2_weights, i.first, layer1_adjustedWeights, layer2_adjustedWeights); + std::cout << "expected output: " << currExpected << " Result: " << output << std::endl; + } + + // Delete data arrays stored in map + for (std::pair i : inputOutputMap) { + delete[] i.first; + } } + /* // if training From 405c99dd8e9dc929b68affdf31aa8c8636be5fc9 Mon Sep 17 00:00:00 2001 From: Grace Gilbert Date: Tue, 17 Sep 2019 16:20:08 -0500 Subject: [PATCH 18/38] converging for xor --- .../character_recognition/mlp.cu | 122 ++++++++------- .../character_recognition/mlp.h | 6 +- Project2-Character-Recognition/src/main.cpp | 148 ++++++++++-------- 3 files changed, 153 insertions(+), 123 deletions(-) diff --git a/Project2-Character-Recognition/character_recognition/mlp.cu b/Project2-Character-Recognition/character_recognition/mlp.cu index a608d73..f32efdb 100644 --- a/Project2-Character-Recognition/character_recognition/mlp.cu +++ b/Project2-Character-Recognition/character_recognition/mlp.cu @@ -13,29 +13,10 @@ namespace CharacterRecognition { static PerformanceTimer timer; return timer; } - - // TODO: __global__ - - /** - * 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(); - } - */ int blockSize = 128; dim3 threadsPerBlock(blockSize); - /*__host__ __device__ glm::vec3 generateRandomVec3(float time, int index) { - thrust::default_random_engine rng(hash((int)(index * time))); - thrust::uniform_real_distribution unitDistrib(-1, 1); - - return glm::vec3((float)unitDistrib(rng), (float)unitDistrib(rng), (float)unitDistrib(rng)); - }*/ - __host__ __device__ unsigned int hash(unsigned int a) { a = (a + 0x7ed55d16) + (a << 12); a = (a ^ 0xc761c23c) ^ (a >> 19); @@ -54,7 +35,7 @@ namespace CharacterRecognition { thrust::default_random_engine rng(hash((int)(index * time))); - thrust::uniform_real_distribution unitDistrib(-50, 50); + thrust::uniform_real_distribution unitDistrib(-12, 12); weights[index] = (float)unitDistrib(rng); @@ -78,10 +59,6 @@ namespace CharacterRecognition { cudaFree(dev_weightsArray); } - void updateWeights(int n, int *input, float *weights, const float *patialErrorDeriv, float error) { - - } - __global__ void kernLayer1Mult(int numHidden, float *hiddenLayers, int inputSize, const float* input, const float *weights) { int index = (blockIdx.x * blockDim.x) + threadIdx.x; @@ -109,15 +86,15 @@ namespace CharacterRecognition { } __global__ void kernPartialErrorDeriv1(int n, - float expectedValue, float output, float error, int inputSize, int numHidden, - const float *input, const float *hidden, const float *weights2, float *adjustedWeights) { + float expectedValue, float output, int inputSize, int numHidden, + const float *input, const float *hidden, const float *weights2, float *partials1) { int index = (blockIdx.x * blockDim.x) + threadIdx.x; if (index >= n) { return; } - float originalWeight = adjustedWeights[index]; // Do the memory acces first and let the following math hide latency + //float originalWeight = adjustedWeights[index]; // Do the memory acces first and let the following math hide latency int inputIndex = floorf(index / (numHidden)); int hiddenIndex = index % numHidden; @@ -131,27 +108,27 @@ namespace CharacterRecognition { (1 / (1 + exp(-output))) * (1 - (1 / (1 + exp(-output)))) * hiddenWeight; - float deltaWeight = (error / 10.0) * partialErrorDeriv; + //float deltaWeight = (error / 10.0) * partialErrorDeriv; - adjustedWeights[index] = originalWeight + deltaWeight; + partials1[index] = partialErrorDeriv; } __global__ void kernPartialErrorDeriv2(int n, - float expectedValue, float output, float error, - const float *hidden, float *adjustedWeights) { + float expectedValue, float output, + const float *hidden, float *partials2) { int index = (blockIdx.x * blockDim.x) + threadIdx.x; if (index >= n) { return; } - float originalWeight = adjustedWeights[index]; + //float originalWeight = adjustedWeights[index]; float partialErrorDeriv = (-(expectedValue - output)) * (1 / (1 + exp(-output))) * (1 - (1 / (1 + exp(-output)))) * hidden[index]; - float deltaWeight = (error / 10.0) * partialErrorDeriv; + //float deltaWeight = (error / 10.0) * partialErrorDeriv; - adjustedWeights[index] = originalWeight + deltaWeight; + partials2[index] = partialErrorDeriv; @@ -161,7 +138,7 @@ namespace CharacterRecognition { float mlp(int inputSize, int numHiddenLayers, float expectedValue, const float *weights1, const float *weights2, const float *idata, - float *adjustedWeights1, float *adjustedWeights2, float *partialDerivatives1, float *partialDerivatives2) { + float *partialDerivatives1, float *partialDerivatives2) { // size of input is 2 for xor and 512 by 512 for characters // hidden layer somewhere between 1 and size of input // first number of weights is size of hidden layer * size of input @@ -176,10 +153,11 @@ namespace CharacterRecognition { float *dev_hidden; float *dev_weights1; float *dev_weights2; - float *dev_adjustedWeights1; - float *dev_adjustedWeights2; float *dev_output; + float *dev_partials1; + float *dev_partials2; + float *host_output; float *host_hidden; @@ -199,12 +177,12 @@ namespace CharacterRecognition { cudaMalloc((void**)&dev_weights2, numWeights2 * sizeof(float)); checkCUDAError("cudaMalloc dev_weights2 failed!"); - - cudaMalloc((void**)&dev_adjustedWeights1, numWeights1 * sizeof(float)); - checkCUDAError("cudaMalloc dev_adjustedWeights1 failed!"); - - cudaMalloc((void**)&dev_adjustedWeights2, numWeights2 * sizeof(float)); - checkCUDAError("cudaMalloc dev_adjustedWeights2 failed!"); + + cudaMalloc((void**)&dev_partials1, numWeights1 * sizeof(float)); + checkCUDAError("cudaMalloc dev_partials1 failed!"); + + cudaMalloc((void**)&dev_partials2, numWeights2 * sizeof(float)); + checkCUDAError("cudaMalloc dev_partials2 failed!"); cudaMalloc((void**)&dev_output, sizeof(float)); checkCUDAError("cudaMalloc dev_output failed!"); @@ -216,9 +194,7 @@ namespace CharacterRecognition { cudaMemcpy(dev_inputData, idata, inputSize * sizeof(float), cudaMemcpyHostToDevice); cudaMemcpy(dev_weights1, weights1, numWeights1 * sizeof(float), cudaMemcpyHostToDevice); cudaMemcpy(dev_weights2, weights2, numWeights2 * sizeof(float), cudaMemcpyHostToDevice); - cudaMemcpy(dev_adjustedWeights1, adjustedWeights1, numWeights1 * sizeof(float), cudaMemcpyHostToDevice); - cudaMemcpy(dev_adjustedWeights2, adjustedWeights2, numWeights2 * sizeof(float), cudaMemcpyHostToDevice); - + // Perform the multiplications for layer 1 to get the hidden layers int numThreads = numHiddenLayers; dim3 blocksPerGrid((numThreads + blockSize - 1) / blockSize); @@ -236,7 +212,7 @@ namespace CharacterRecognition { float output = host_output[0]; // Find the error from the output - float error = (output - expectedValue) * (output - expectedValue); + //float error = (output - expectedValue) * (output - expectedValue); //std::cout << "error " << error << std::endl; // Adjust the weights of the layer 1 weights @@ -244,12 +220,12 @@ namespace CharacterRecognition { dim3 weight1Adjust_blocksPerGrid((weight1Adjust_numThreads + blockSize - 1) / blockSize); kernPartialErrorDeriv1<<>>(numWeights1, expectedValue, - output, error, inputSize, + output, inputSize, numHiddenLayers, dev_inputData, dev_hidden, - dev_weights2, dev_adjustedWeights1); + dev_weights2, dev_partials1); // Copy the weights into the input array - cudaMemcpy(adjustedWeights1, dev_adjustedWeights1, numWeights1 * sizeof(float), cudaMemcpyDeviceToHost); + cudaMemcpy(partialDerivatives1, dev_partials1, numWeights1 * sizeof(float), cudaMemcpyDeviceToHost); // Adjust the weights of the layer 2 weights @@ -257,9 +233,9 @@ namespace CharacterRecognition { dim3 weight2Adjust_blocksPerGrid((weight2Adjust_numThreads + blockSize - 1) / blockSize); kernPartialErrorDeriv2<<>>(numWeights2, - expectedValue, output, error, dev_hidden, dev_adjustedWeights2); + expectedValue, output, dev_hidden, dev_partials2); - cudaMemcpy(adjustedWeights2, dev_adjustedWeights2, numWeights2 * sizeof(float), cudaMemcpyDeviceToHost); + cudaMemcpy(partialDerivatives2, dev_partials2, numWeights2 * sizeof(float), cudaMemcpyDeviceToHost); //for (int i = 0; i < numWeights1; ++i) { // //std::cout << "adjusted weight: " << adjustedWeights1[i] << std::endl; @@ -271,8 +247,8 @@ namespace CharacterRecognition { cudaFree(dev_hidden); cudaFree(dev_weights1); cudaFree(dev_weights2); - cudaFree(dev_adjustedWeights1); - cudaFree(dev_adjustedWeights2); + cudaFree(dev_partials1); + cudaFree(dev_partials2); cudaFree(dev_output); cudaFreeHost(host_output); cudaFreeHost(host_hidden); @@ -282,7 +258,43 @@ namespace CharacterRecognition { } - // TODO: implement required elements for MLP sections 1 and 2 here + __global__ void kernAddDelta(int n, float accumulatedError, const float *partials, + float *weights) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= n) { + return; + } + + float delta = -(accumulatedError / 5.0) * partials[index]; + weights[index] += delta; + } + + void updateWeights(int numWeights, float accumulatedError, const float *partials, float *weights) { + float *dev_partials; + float *dev_weights; + + cudaMalloc((void**)&dev_partials, numWeights * sizeof(float)); + checkCUDAError("cudaMalloc dev_partials failed!"); + + cudaMalloc((void**)&dev_weights, numWeights * sizeof(float)); + checkCUDAError("cudaMalloc dev_weights failed!"); + + cudaMemcpy(dev_partials, partials, numWeights * sizeof(float), cudaMemcpyHostToDevice); + cudaMemcpy(dev_weights, weights, numWeights * sizeof(float), cudaMemcpyHostToDevice); + + + int numThreads = numWeights; + dim3 blocksPerGrid((numThreads + blockSize - 1) / blockSize); + + kernAddDelta<<>>(numWeights, accumulatedError, dev_partials, dev_weights); + + cudaMemcpy(weights, dev_weights, numWeights * sizeof(float), cudaMemcpyDeviceToHost); + + cudaFree(dev_weights); + cudaFree(dev_partials); + + + } } diff --git a/Project2-Character-Recognition/character_recognition/mlp.h b/Project2-Character-Recognition/character_recognition/mlp.h index 58acc58..cad98cb 100644 --- a/Project2-Character-Recognition/character_recognition/mlp.h +++ b/Project2-Character-Recognition/character_recognition/mlp.h @@ -12,5 +12,9 @@ namespace CharacterRecognition { float mlp(int inputSize, int numHiddenLayers, float expectedValue, const float *weights1, const float *weights2, - const float *idata, float *adjustedWeights1, float *adjustedWeights2, float *partialDerivatives1, float *partialDerivatives2); + const float *idata, float *partialDerivatives1, float *partialDerivatives2); + + void updateWeights(int numWeights, float accumulatedError, const float *partials, + float *weights); + } diff --git a/Project2-Character-Recognition/src/main.cpp b/Project2-Character-Recognition/src/main.cpp index b9f41dc..203ce5b 100644 --- a/Project2-Character-Recognition/src/main.cpp +++ b/Project2-Character-Recognition/src/main.cpp @@ -25,8 +25,8 @@ // Define acceptedError 0.0001 #define training 1 // If set to 1, indicates we are in training mode #define acceptedError 0.01 -#define inputSize 2 -#define numInputs 4 +#define inputSize 10201 +#define numInputs 52 int main(int argc, char* argv[]) { @@ -34,8 +34,10 @@ int main(int argc, char* argv[]) { if (training) { // Fill in all data: //std::vector inputArrays = std::vector(); - std::map inputOutputMap = std::map(); - float *data1 = new float[2]; + //std::map inputOutputMap = std::map(); + std::vector inputData = std::vector(); + std::vector expectedData = std::vector(); + /*float *data1 = new float[2]; float *data2 = new float[2]; float *data3 = new float[2]; float *data4 = new float[2]; @@ -49,14 +51,20 @@ int main(int argc, char* argv[]) { data4[0] = 1; data4[1] = 1; - inputOutputMap.insert(std::pair(data1, 0)); - inputOutputMap.insert(std::pair(data2, 1)); - inputOutputMap.insert(std::pair(data3, 1)); - inputOutputMap.insert(std::pair(data4, 0)); + inputData.push_back(data1); + inputData.push_back(data2); + inputData.push_back(data3); + inputData.push_back(data4); + expectedData.push_back(0); + expectedData.push_back(1); + expectedData.push_back(1); + expectedData.push_back(0);*/ + + // Read in training data: - //for (int i = 1; i <= numInputs; ++i) { - /*std::string filename = std::to_string(i) + "info.txt"; + for (int i = 1; i <= numInputs; ++i) { + std::string filename = std::to_string(i) + "info.txt"; if (i < 10) { filename = "0" + filename; } @@ -89,10 +97,11 @@ int main(int argc, char* argv[]) { currData[counter] = n; counter++; } - inputOutputMap.insert(std::pair(currData, expectedVal)); + inputData.push_back(currData); + expectedData.push_back(expectedVal); } inputFile.close(); - }*/ + } @@ -110,101 +119,106 @@ int main(int argc, char* argv[]) { float *layer1_weights = new float[layer1_numWeights]; float *layer2_weights = new float[layer2_numWeights]; - float *layer1_adjustedWeights = new float[layer1_numWeights]; - float *layer2_adjustedWeights = new float[layer2_numWeights]; + /*float *layer1_adjustedWeights = new float[layer1_numWeights]; + float *layer2_adjustedWeights = new float[layer2_numWeights];*/ - layer1_weights[0] = 10.1; + /*layer1_weights[0] = 10.1; layer1_weights[1] = 0.9; layer1_weights[2] = 20; layer1_weights[3] = 0.87; layer2_weights[0] = 41; - layer2_weights[1] = -54; + layer2_weights[1] = -54;*/ + + std::vector partials1 = std::vector(); + std::vector partials2 = std::vector(); - float *partialDerivatives1 = new float[layer1_numWeights * numInputs]; - float *partialDerivatives2 = new float[layer2_numWeights * numInputs]; + for (int i = 0; i < numInputs; ++i) { + partials1.push_back(new float[layer1_numWeights]); + partials2.push_back(new float[layer2_numWeights]); + } auto start = std::chrono::steady_clock::now(); int numRandIters = 0; float accumulatedError = 3.0; // Larger than accepted error - while (accumulatedError > acceptedError && numRandIters < 1) { + bool done = false; + while (!done && numRandIters < 10000) { // Fill new random weights - /*auto end1 = std::chrono::steady_clock::now(); + auto end1 = std::chrono::steady_clock::now(); CharacterRecognition::fillRandomWeights(layer1_numWeights, layer1_weights, std::chrono::duration_cast(end1 - start).count()); auto end2 = std::chrono::steady_clock::now(); CharacterRecognition::fillRandomWeights(layer2_numWeights, layer2_weights, std::chrono::duration_cast(end2 - start).count()); - std::cout << "NEW WEIGHTS" << std::endl;*/ - - - // Copy new weights to adjusted weights array - for (int i = 0; i < layer1_numWeights; ++i) { - layer1_adjustedWeights[i] = layer1_weights[i]; - } - for (int i = 0; i < layer2_numWeights; ++i) { - layer2_adjustedWeights[i] = layer2_weights[i]; - } + std::cout << "NEW WEIGHTS" << std::endl; int numInnerIters = 0.0; // Try refining weights iteratively - while (accumulatedError > acceptedError && numInnerIters < 1) { + while (!done && numInnerIters < 10) { accumulatedError = 0.0; - for (std::pair i : inputOutputMap) { - float currExpected = i.second; - float output = CharacterRecognition::mlp(inputSize, numHiddenLayers, currExpected, layer1_weights, layer2_weights, i.first, layer1_adjustedWeights, layer2_adjustedWeights, partialDerivatives1, partialDerivatives2); - + for (int k = 0; k < numInputs; ++k) { + float currExpected = expectedData.at(k); + float output = CharacterRecognition::mlp(inputSize, numHiddenLayers, currExpected, + layer1_weights, layer2_weights, inputData.at(k), partials1.at(k), partials2.at(k)); float currError = (output - currExpected) * (output - currExpected); std::cout << "expected output: " << currExpected << " Result: " << output << std::endl; accumulatedError += currError; } accumulatedError /= 2.0; - // If error is low enough, print out weights and break + std::cout << "Accumulated error: " << accumulatedError << std::endl; if (accumulatedError < acceptedError) { - /*std::cout << "WEIGHTS:" << std::endl; - for (int i = 0; i < layer1_numWeights; ++i) { - std::cout << "layer 1 weight " << i << ": " << layer1_weights[i] << std::endl; - } - for (int i = 0; i < layer2_numWeights; ++i) { - std::cout << "layer 2 weight " << i << ": " << layer2_weights[i] << std::endl; - }*/ - //break; + done = true; } - // Copy adjusted weights into actual weights for next iteration - for (int i = 0; i < layer1_numWeights; ++i) { - layer1_weights[i] = layer1_adjustedWeights[i]; - std::cout << layer1_weights[i] << std::endl; + if (!done) { + for (int k = 0; k < numInputs; ++k) { + float* partialValues1 = partials1.at(k); + float* partialValues2 = partials2.at(k); + + CharacterRecognition::updateWeights(layer1_numWeights, + accumulatedError, partialValues1, layer1_weights); + + CharacterRecognition::updateWeights(layer2_numWeights, + accumulatedError, partialValues2, layer2_weights); + + + /*for (int i = 0; i < layer1_numWeights; ++i) { + float delta = -(accumulatedError / 5.0) * partialValues1[i]; + layer1_weights[i] += delta; + } + for (int i = 0; i < layer2_numWeights; ++i) { + float delta = -(accumulatedError / 5.0) * partialValues2[i]; + layer2_weights[i] += delta; + }*/ + } } - for (int i = 0; i < layer2_numWeights; ++i) { - layer2_weights[i] = layer2_adjustedWeights[i]; - std::cout << layer2_weights[i] << std::endl; + if (done) { + std::cout << "DONE" << std::endl; } numInnerIters++; } - // If error is low enough, print weights and break - if (accumulatedError < acceptedError) { - /*std::cout << "WEIGHTS:" << std::endl; - for (int i = 0; i < layer1_numWeights; ++i) { - std::cout << "layer 1 weight " << i << ": " << layer1_weights[i] << std::endl; - } - for (int i = 0; i < layer2_numWeights; ++i) { - std::cout << "layer 2 weight " << i << ": " << layer2_weights[i] << std::endl; - }*/ - break; - } - numRandIters++; } - std::cout << "FINAL ERROR: " << accumulatedError << std::endl; + std::cout << "WEIGHTS:" << std::endl; + for (int i = 0; i < layer1_numWeights; ++i) { + std::cout << "layer 1 weight " << i << ": " << layer1_weights[i] << std::endl; + } + for (int i = 0; i < layer2_numWeights; ++i) { + std::cout << "layer 2 weight " << i << ": " << layer2_weights[i] << std::endl; + } // Delete data arrays stored in map - for (std::pair i : inputOutputMap) { - delete[] i.first; + for (float* i : inputData) { + delete[] i; + } + for (float* i : partials1) { + delete[] i; + } + for (float* i : partials2) { + delete[] i; } - delete[] partialDerivatives; } else { From 716034896f5d72f1dbef9af088a21298858e85bd Mon Sep 17 00:00:00 2001 From: Grace Gilbert Date: Tue, 17 Sep 2019 16:27:05 -0500 Subject: [PATCH 19/38] added screenshots of algorithm diagrams --- .../img/StreamCompaction.PNG | Bin 0 -> 6441 bytes .../img/StreamCompactionWithScan.PNG | Bin 0 -> 11760 bytes .../img/efficientScanDownSweep.PNG | Bin 0 -> 29654 bytes .../img/efficientScanUpSweep.PNG | Bin 0 -> 15436 bytes .../img/exclusiveScan.PNG | Bin 0 -> 4470 bytes .../img/naiveParallelScan.PNG | Bin 0 -> 22973 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Project2-Stream-Compaction/img/StreamCompaction.PNG create mode 100644 Project2-Stream-Compaction/img/StreamCompactionWithScan.PNG create mode 100644 Project2-Stream-Compaction/img/efficientScanDownSweep.PNG create mode 100644 Project2-Stream-Compaction/img/efficientScanUpSweep.PNG create mode 100644 Project2-Stream-Compaction/img/exclusiveScan.PNG create mode 100644 Project2-Stream-Compaction/img/naiveParallelScan.PNG diff --git a/Project2-Stream-Compaction/img/StreamCompaction.PNG b/Project2-Stream-Compaction/img/StreamCompaction.PNG new file mode 100644 index 0000000000000000000000000000000000000000..4e25110dc2a3d095ed047ace315b0d80fcbc3d60 GIT binary patch literal 6441 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 literal 0 HcmV?d00001 diff --git a/Project2-Stream-Compaction/img/StreamCompactionWithScan.PNG b/Project2-Stream-Compaction/img/StreamCompactionWithScan.PNG new file mode 100644 index 0000000000000000000000000000000000000000..6da2f07367e60ecc016fb2b9cd29d41fe9b1422b GIT binary patch literal 11760 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 literal 0 HcmV?d00001 diff --git a/Project2-Stream-Compaction/img/efficientScanDownSweep.PNG b/Project2-Stream-Compaction/img/efficientScanDownSweep.PNG new file mode 100644 index 0000000000000000000000000000000000000000..4c12fbdc3f6d0d8641dd1dfde932801f88e94a6b GIT binary patch literal 29654 zcmc$`1yGz_6EzrI1HlJ(cXyIO2e;r7+$Csm!r(r^o#2q*!GpU64=xEBAXsn->_hUt z-+ur8yS4k**49=*G4j~$+qb*#Iemy!Q;~a$PJ;gA$&;t@5NVAkPv9X>p1>)gA_Jdr zSxeCYf8g9S1Xs;o9Zcm1BM7jpm21)zZkj*5+pY`HfZOs3m;C==;(Q)EHDI)Oyrohin6R_EzS1xanA|F2h^}`X;{rL)nq6_r5#wrbrV&;F%C21I7B{?0ZG}byVd&?%Ca09HyMe?h%7_L82G{8zCWei zf34R%UflGm)aZ6?QP2}xME0ZBt$~`fhDPm01#z+8Fh1Ra^621q4tKLg$6NT>@&(7M zrps0R#YWF~-=j9{0y$LyzdC2iB7gJC_nG4`YBVrTPgq-lzx`YdMFAR`=2LQ7Svns! zh)l}Sh=oq*d z8lA!aUN1p{4Wd-q{l9R@`}S*xLU`*oBj)UcC3Gf@PIyLnpP46-bp!E7iYsXHPur8q(_qi8CeW}K-}d@iJ>eu#O~>R zQ||f-!=0iT%X_MJmZ>{|yT-=sH0RJH80rWdt3q2p3R4}=siHex=R;~|?367v}(3H!B8pMQ$S z|1$@3S-3g4D4HD~$@bu?vecN7q}crcT5i;|w>nL*F*>$oiGvT_-1@?(3p|1u--k$6_A&BxbM%f>1a2rAP$)pZAzec0E>&)4dI4BCjhzjqLe=NO6LLL=mTRW1xl7g}2 zp39vNT129f_iw83Pk2Xd-f}8R<58=QRmjMsvGv;h*oj-(>Voz*DRv?a@%VyBrqImE zGe0@9_Iu!{Vwg9GB!9&K+{Mr5w1!Q#%2!0enP)h*VZ<*rqw^^zgy8il8alW(qC<1= zaPz68pA-c4E2LKkXKIJ7-hi7%+jGMM%Nzr@zIsOP(xO79zvnods;KYli?xMc{wbTf zA$^tZi|w8ca=V`BZqWS!W(?d7jvxZMEht&E?RDI3Z|KKnpS*@Ojz^CL9wA*2^Q#Ak zA)V?CtWdHpL&;sj2iIEYA(z<+1xV)CgdE^W%tzl{boE}=y^t%4y~{C#{$-}stKaJqet2Py3Eob^rgBWr8!1|$bHX+ zVL2Z=B-n1>b8SBC9a33cWnIt@{*Sth&w9J-X3kT_JJF`J(`f9I#LFe`9BD=PWqJlO z^SAw#CyNWb-a759T08?Hlo31&gbvf^_aq-zmFb8DAsstZ9yV$&vWMB56b@W9-$?=( z2MLPq+;>wtcSL!zSF{G-y5{IUWseToNSzaJLZz`YaL`VZgMK`Hm5KdHz@p2{I1*?nE^fLLbxK?5kfxWh^5=rhb`5RzfJoHMK0Sr8VK&#J&qcc+MP}UT!4zn95r@RM zNuH8oN`07a;`z;0OQkjQWuOUKM?jln^PLZiF$vMO#FG3*-@0bf;@Q5^NcGJRQd06W zsPtSii`Iwy$}7=Agh|onS(%@KU{~e1Bt3@~bJ$tSR#q@zS|UO^!a{Tm)y;_RzcwsD z$Bk555!xv^hT+mhi7gSn;K!b__yjZ98^(48=bvPlnog#%i68ZH-!#qdNa`d7BH6O` z*MFu|Hnf4$i0ec2z9c=cFI8q^;so;88}3S~bUm0u&kv{t#S#I3W~_8ZrmM861^7d0 zX1qEu`GZUkX-<&SaiQ?RamKIMJCmx7Y&IU z{*iwB=UR&u9{R;CZs=p&K=gw(RB|0tIezAFd*pByqg51gSAz^D40Mgl3l|m`LkUxNFOHsviD0kl6-C_DsH5I zc~f(XK-lOHXdwfyqa80nJF55Y(2v#5dNEae2_ia6HeLTSg<0F0f>yf(G00hCVZUtt zDJZi@maTOLb;B2MJ%xa1+5sf*KW2O^ zvBu@JsOx4-_wDb;gQeBs1cAio@5c`DUNCrPO?#a3GZ{4biS=f0rIg;(3!Gm~YHitR zd#_+$-orn^?LH7pzTIa>+<|8T#(gJ&i({nWkU}CHV_U~%ips&it_)IzF~Q|-!dnyN z!Q>4vIGrWBs3GCpmDy6{EkalmO;9@N0Ivbj^V#>TE%8~{pGdFWn4prdR?G*isf$1$ zfga*|0as4t!9CcYXVVs4jlH*hOs$B;x7}^1=-^*G&-6a$)jeH1dq=97Uj>G{iuIZcbZx#QX~@dw?C==-%^0# zB@n;B=w5$P{p0n3Y9JC#>;LwW5mC+LT=B7i6NQxMX*oXRU3*tGtOptwmCiYx2Te(}qh&WGQxw00$&A46HV0w=`( zrWdH!B(=4*Cqft}zU&-+wnvolVu6gLa$*DJT-Z}mq{p{0x0^dV8$H<1XNez{ha(ab6Wh#` zzQe^9JASfQXB#~CD$r8^0VE`p17p6I? zf`Woto6ot1FgiB&z|Hx7eH-S+tG|QqIo|tNm4ZI?`*N+y(Gv6k)RtVBJ)kg;{aI#q zIXcKu`)y@IvgsXAh(2D5$?ofiP~g{euF`0cFznnV zKYxHX_WEwwBlmBu|9EH^A~XNjm?Fi~r{tO?#h<9SsUx*uo`+)gKS08+G(ed_2o$+$ z<^*&LNRtp?Y91@&KSeJhKk#gLx{p;iU?Vg;V7v^~|5V9%Gr$Wa6JVJ=E=q<{;6+*| zP$>U7S?$pVL67JBdDbw%!0>=2AM0>#@KeAJh=hc6&efgxgkjZ)(H9~~OtOp7>(0L1 z%6B@%4&2NbcydLldMs4`!{MbC(3ct!{r|-myZ$O>=mVvkhkDJX%Ua zwPe0nMe5S9+fzP{U|kT*GcLC_ds%>`SkpLVzH=ad4P}q#v{$m|G1OL=qr^Sz>mI^7S6E%TFu9$sM z5zZf5=U)8L8Yj_ITUF9)$n0DutESfhL_i=w)AHkl_?p-)z|*D(J8^c`ZNyq>w}6e?<@h=N^Y zdi;sMzV=;2rWsDINwgs>$}REPr5PdxI?-)iAmayBTsERQ_y+tPsxnmz+l7o+Gm~Ml zAIC?AeO*d;W+b+2^mo+K?dV94=hw>d!VHIO@{C3Fod zOz0-Z5h)K_TNPzK(~6cDM;x&%f^noEo(P6VyDr-2zm^SQ5jOjLvs8+&RBo*~YYbDs zid?ftY~zGKiSApgFVOuRkUbL`7jX2M zD?^w-;rriskz6Ae%|HeOywM)4FWNv$((jynxRjn96T#90V_4dGc8IE>tBc7)rf;vn zW1d9nJ<}@6B5g<@c-3#_Yw^M3GXwJa_A2{IyTf;}J+s=jT~YKYaAE7!v+e?3GX1w9 zeld8zOy|V4pfNTHNnL|AiDcB$v?LpO+b4UT74;>nsL}rR@eDfOkA&*rh0W$zr15DB z!mnDbY5R6$skYJ@^q9C_lAhI6BbTIyZza`zWBnj9-?Jc)%Umya1sO-=1a6vssqEw_V#CmIB|kw z@G{8VIgvUov#w+`Wr9XT>-8z<*4$0O0>vfnx$RU&?99?ZS3k?o=z1?BCC&G6KP5*` z+eXmTFn!7#Wbx#NxL%Fn+oh&njnY&!CE;3}*IbV&L~D@MD%2e;3ejl=I9QG-gI#Y# zhi}M@fM{6Od!-MXTK_;pV=93>Qx+9g^(9>R)ru2$Rd0k7Ik!oUeZu1qSg4sHDuODG zzUiiIIG?E?MH{fJhqeTA`MoQu1GgS~IjRq;VoLse=`NbEq@#TQ6yy1^;{eq$~Y93qwi){<`_qc?O~7cH>wJWnO^`?>%&VKqh0s)3ee-9C)x%tDp~f;K`m4w@EM)?80^{Q zE~pWAU23ZM1fv7~xX*KB+we#f`~`{Jr&B3)Z1}}f_3ycr49Jjw8WJAa;)i88YSs~3 zEyjwMQ>+Uvq-vqqE@63jUR{>geiNeDv7*+f8*e=|uK2o*L;c({xK1mw27U4CvX`ZH z*2Y_xBE$IC)EbK>hdk;J#m5pZ5jeS)i+PU4N1P!Xzw~Y#$z-HyAkm-8wj4g6`LRLH zu78cEL?^NXCQ;k42WxP2(m^!P7ST0Nb(KC1UM!$ICNN+EJvkJm6H|tCSr_CInYw+n z@Av|}=b}9_Iz09yVzD#cf-FT^DuncqBJ;V;iY=%Z1u>Wr>US^KKONa5_H?}AM>*k} zWfsE?*4zcc9fUl_sXlZtWV&Nxf_z)(%X87E#YD_=>dL*A1_o~B(|1ROjDtf;T_#0Y zN?DspDhG|E*6swt&eH-oDm1h>WXfSQct4z{UHPx|VTf@RkjgrW$7rLr;Rcl9vsDeP zkH6A z-PuFaVU~$Z8abWnzm5njyYN?ZzxdK2Zs%=tr@@ROaH*wczIZTZzYR0Whp4wZl4*F( zKd{a$7ScsI+{0*W>C|Fh#YiQHMuIAas1N#y%WCr=@f(*3=ql8;tJ-+4Q=(ER5zEfr zG=v%d7U_icBJ$rqqvKEX)I7^DOD?3@EMhG1MOctA0E%89bozpEYfL3X8p$H-rc#z^ z((pt`+{@mHLN^y3)3_h26}1f_aQ@(i?L8)K4=0qayY{7665l7i@{0oxWTWyFB<7bu zN`jJfpe3aKN$2;tbk6^5hW}H#Y@%Xll>T{?|L>}vk5om4Y*nzd=Wf&~U$Vj$m=%j| z#h*Tc508afKMO=L#evWLacQ8VX&5dWIQ3=H$YTABm6v+eb$8i9#hQoE;CIFr#~?89 zt`~JP#t%K)K%B$BN)Q-T6T)NSfl+xJ+_;9ymW*Yeyp|?kzO`UqewYa^U0TNxn{DCF z@o5*jg6x^I5_zgKW`AUnrqG-0_Tt3pn+>5Fmm+8krSpSCXVl!= zWt`49+s?eL&UD94>{kel&bH1;Kq2{OV{-D=pkui^@<^4ck&DRfVzlcAr)Fg)FzCN0?KTv?KN+AT+2T&tjvb zw82N9Ao*Q$S6g$uHWPn+$og&Ps7|m2sL=mxo{FK@-}kWR-#AffQ|Lt&I?97u0+?FZ zcA>J&u@hEc($<<^PyL#9r3w!edx|wPkNL&bd1KVCu4#@BB)-j)P>Z=eaVa-DH|YFj zL$zaMvh?zs3G(Yj_$2(ulFfjxQ;~8 zK7+4aMf^4J1gWgkg<5{?5Gml7^Bng-1L}WbNTm8*u0u5HoN?A%yNfMmlr&N&r43rL zBUXX>{h5XD1=?cZ*IV^4c%+jxs`njb9QThd?T2cBXrO8oc(s!<; zS0|XStwdjYAyseiQaR(B99Cc@-J9`M+ZT(xz4^)U&9&x2b_3W#{F!hxGn+fySo8c7 zCBDkYenIifi*laT$bRw3tCGS4`C%_65~A}0f!GW*$;wyo929UW0dPN6QP?6oTc}^i z3Z6gg@;h=4YmTU2Qvr#W8~$-gLnmT{Glb=Me^~v@$Qvy!NcZ6Dsq9-O9F7sq@Rs8} zwqc<7GFXuA9Rv5CFlQaUzAs!qK!Bl?9XvymB&y*^ou5Ku$KB$NjN0X64mOK<0{H(+ z9wr^UEr z+n)(shB2>&T>I{rZw$o>>A7n7TGPKR+(tRM)G($>$Ucy_Yk$901;5~2<{iS6J8@Fq z+3eq`N>nGv;{!PMUu2?+sogNf(YnF(B&ZM6;Vrg08i02|*dX~H9KJVd4#GygcZ4$yGrTK=)GHO*+^#Hj5b1Gf@Ys^%V-p$88yJFj6A^%|B5?_F# z`3rHJpb0v!^`0EX)0S|de0nq9fBUL6{>Rp2L>g(3)u+rnIVbHQoCn3v!7Mubwd^v@5ves#OFg-5Cb+f{-aeDaDilDk+OWXR}l zLH@R#+h}`xzv_m?Dg;zNj!o~K)}goYSl-uO$o5nI$4F8x+XK8&`0V&lo{N#1AD=#8 z=O_kZ#eea`dy?%`T^V&0>#-PPe;v<8cSS{{6zm#$Y(}|{pz(Pn+S%f9ajO|VsWDua$Nk9 z7*=lJxo|_yLe!_!F+0j(5Y9g#5YwE@$p}FKJ*Y|$0VM_ApyUbZDqKn%dif!W!wu>@ zFOg6UmQdrXY&~tPk^rI5yTohVYG=6q&G@c;)>m@-*A(tirX6M>#b=$xbJEQ&Z@E)j6OksN~t>DX8S#ljKF+>3y?s@&${HAR)y@Uivw; zGdZEhi?;DS=7+UFk3dV&gA&)BK#{7p#4^->%^mY*;{6Ur_zZZ059D*|W%A{PhY`2s z@Ezlxk2#;^{b%#e^KV8uA^I?MMf7AsdON**LIyiqJK?ILsbA9UYBRX}{yXkPmQnhp z0r?(tphlnw`V+ReIF6%RT*Z{bOKea0#3hdWJMToQe?9fM5mPQG**)W{$SI!F;R$HH zDBuZ;?B|(GV<1_QHqWvt*UUt6OsY}kLyqc~bQGzVCnoJ11dyM<7k%?SBw~$R%fOdO zorYeiQ2YJpVdo;)PEtmp9D+v3Nr7B>lH{%PUeC=y#aZ2Ia?+utsa&YeXkI(r^0Iq} z(*$?sP-#Z&8LC@H1UWmEYdMvT>o^oshllbKKVzsl&cdVdU^yt>~x zN>=)w#GSsxhF(qSd(>HxR~TLXgjy%p z|FlsS)nDSU^~D_x|1zBM<~gFg&X470H}%h+jx%MND1i^RYm=qww5?}Tii^f!C!CL2 z4ih#r@qTsM;H^~pUea}zmx&A~wUjeDGYJBIA^boHT_h<@<#FBV@Zi@>9ha+h({k~4 zLSE0YPT%IE$B4<7xq`{QUH_sEwtd;*MG1seStoUlxWG4Lg3RQ>;4}e8lKZ=z=d^5W z<$NvfyZHwI;tAwzE?Fp{anT_M;GBN-q@yC10e^}HkKwyA<+n}EK_xI=4%hD`7>|*j zC)6acb_)+vm-mT!u3X;z*^NzOH93$nF*XF_2i6gyYYs*Q{Cu|z4zlkzhLc%KwJY?? z$R+~=Px^@d9p;8J6De;jk?<#Sp;q|$6JnSkI@hH(iUOuQefKp7Dlq=m7$Cp@I;0&QGCSbcrW4iiFkrH^n!hzI6>etGcvE6$phe+#3=RnoH$Pf!`xlwkB;=b7?|&F|gj1@j>fhnC z(cBI%yZt}ZE)k|I)Qthnp?GV#YREy;CdZSnVq~ib+9o8kr1*4OeXZW~?(}X%>Zvk8 zrCsI*-h6*IPNCi}hf`Bi(*&U7Ne@W8`Q1)VYU9-p zHtdR^7O~G`ukr4=?+cJc zuy_H-oauIMa||Q7+`!e}`#+rJ?o{68KagC%y87)Ks7B-GhN!^`%KVCfpQ2*?Gv933j45R4@gqGi9$>1n+&VMcn{fv^ zUm!4xj`VkC=X)z`5D{G@`HRoHG8d_zCxn`Xea4QWT|QcwxvUb+w*%OZgG}GxJ3Kwu zpKuRGx4WZma zgOUL6!jYJpfUp0OZ|}p9n6H2no&I43{?{&3RtJ5V6xuaD`;zeMQ;hwv7&FJuaMg4Z zZ+N=$+pNTbk%rcwb;`A0LQTL4Jy86+YAPhbWKCGqyNi4n>==^9;|5FgeKx!o-A7o6 zsU#Vs!SK48N(++>67Y;p@j@hqlsH=8QcI>cO?--H3kf5E>;ZMkv@kYCHqrM-z9zs% z15jWcL__8IV;cj2(L0BfS!2A^$+@t9@AUayT7$NR3ghEfpQ$R0&@>%%`0(d6i_W)m zZhr7@RkxNazbvQHGszG14QYddWRzm)AsI?6J2?>{-zEhJ=_t_?=$oHs)W!9`<&Lhg zkiyY0@6m;^fYvZ@zZz)P$!#k1v z4z?1ZH}Gb4N2JE0xWUudG$ZujDY~lBW0l%7i@}6vp>@-X7zn^NABsP7r47R}cP&{2 z!8n#035vJbxy;fXa@_zDcf;VZ$>^Qdgond=GZI{mRA;9uT55$ZSrY(@l#O}6HWSvuE?-!7Z?9% z5Uk;x5Cx)(bQBJs`SpT&%BM6JhkDpL!k<_(Bm@l({pcOTh5jUBnYuZj6MlG-0}G?X z1GK?F($q&VB>G7GR0eNK`nud5FW)YdHf!^PVKcMzY!h0Vnw74a{Iw^IaLP%OY> zw>L}y6pAphinDD`VJUR=77a?PR;kQyEN~3w?mFGonEGQ40X^;1g;LS65yb zj}wq<4rk|kQ{-f+tEx^6 zZfGVyhr$u#h?~=s%75Lj=)tF2H}BhBP&`p>*3m{ zH3?QcCqTfS!ZLdd-)q=Yvy|Ty&xeBR+6FkGg?Mz$*^DW<7BHZRB^M*XOKq24`u+Fg z*aUw%NR8L)2@znXnG9fj!psMUEmHDMVtel}&xbTVS+Wiym3@=K#k(D6Qi^@qJ5tvN zw%~{!xA}w0zgAoLR!n6ZTvyhb?PoOBWg>++MTi>dMwrK=pMr_}*hQ%ZELxWCx*sGz zL=76^k>domZ0`wPd;?ncOaVmQjru#)V?ZLJQ<&8^cm>tCDKKqSrqRh}szQ<{Nax~< z%XzPqK0c(<6C&}hl(<+qvk{M|nX1gl;QW~&ErzgYRQ$% zm=+Od_3+ffo`*!(;PG6n*(UyyhD9X&wgoL~-wGxp zCK2L~x>;f-bW8-W+6&!VAp^M<$e}0ARFM!;{K&Bl#~d|5HrZFkc7@%?^;BDy&N4a5 z7nX__3Nb$x2>cUAj`Tl0W^;g$#z~U@Z1q{1tw!Z?d@aiMH;uYx2K|cgFcDo?4bc3W z3gvSyY!E6|S*8u7Nq9_#fvC8aGb#sj1RjorAIW#Z$khLaRu;*{*DmH-$kx#<_7emv za%15OU$R6VjiCr*nhn?v2XOU*F6ia}K-vTCs&RpcLqMPD6eBFS&IkanAM?~I(0jYV z@z_xd@cSCaK<-`!c0qsJX@PElznT0I%l{jEfV>X$J#+!B0RQyn0-Xk3*cckGfkg1P zG57z)WzeRoP0d;O7Ta!d&B88$o(Vpn8CMs{_Sfb$0U<`QAI-|4GVpMR8oNkk>IKCF z6oG+d40fLF*bh$ErS2V!T~NAkMEMI}Z@h=QogRh33vZxhz*R$2lU1xm`7liGc>w9XuA`5DsRmscknC%YxN zBFSYDCU+w`J00CKj+&3anF!n7+aUu+^P@)J`ZlG`W-rkE=O?2(#Gj%smo*Sb)voflh5Wq2C*}qidp++^lBDGe1kXSe)^HMg6L4 zi&-*cheH>9k~x4tyz)q*0BnkVWh`6(=rZO0wH6T?cWa$N}sr?*=yjXYw%RMeAnh>p>GQd%n81f23ZLv!LtnmgFBi zF_xI4y%-!-HMCz`lI#tOwi+6ly%$do2zy^Yvh`4_!X4MH~k| zVYxnl&&Cq781CN&&hd{;)92D4b+3wMCCw3qRGqEji41 zN%z8c$6h!p>;qIfAHTQtbWBvY&8Hr4VfcZ*TuzpSvsxEe}Z(J(_<8vgLZVu0A?1jKFFAVT8>u={I4+W-r2eeuaA;x&C$| znasV(e6^|WXU_`Y4l!MCo(G1hN|rR#}l z7W(+56XvKC&#s>`>Ry&k7YIKnp6FL4j4RKu7T= zIxF+A%yt)MD?Noa$(T)P`E=9Gyq#g2x0bKwW9jn9-^pSr|1MhKG+-=45KktPA)T?k zAxXk?zF)mo2uLlldcL*BJR)H+2&0~_2H#a+;xRGd-A_80qEnVeYTBCWW{0K)Hhrj^ zo`2*7cB%EDO#3#30mZhz!T+S#G|{`e>A{ z4myJn-lY=Vv70JAd!e{XriZo}t1)dlaTDC zpO$*dT*3A?8oktO^1`3* zK-GPY|BOh^(yl!h2n@|GGPZ{$+_-$&`D=RtgFBfSWC;-es>zb+cev(t;@HEyUC z!Q@T5#dLGu)WBP9_}TlZeOy)7@zb73FKu&r=GC3`TXos*FHW~Jo!3V=JAe-Fm~b`4 z3a=*s9q`r*q&6lKOs`UO3bX1^7ZmacpDu3#cu|0H5KZKU|0iilKF!#b3>LK&3r;A2 zh}GX;)i+G1Ew}h9>S+hf=I8U5`DFUCV)z6|8R5WDad2P}M#})n*y*@O{#&O8P^#$* zTwiyFN-e6GyZxT^0C0b8ZyRrb$s7iC7onsg7lx+(ot7>vcKR0!ZjFp(7+p^IM9Jn0 zdogKLmH-h9`rT*4mpe^1Ia0u_Bj%m^%baFd!vDr(gzzMjCx$f%ADvmh+$y0Y=9lE5 z%Sl87uTZo1Z7~l?{X zq)05u5J&_hCW+ghu`W9~_*K9)lJL6e&$g_~3BLNWgiD7>o#^9(E$4dNRWR{SS{j;( zR&O_et&tO^wAE(rL4+ryqb=Bk=oK-p^>?={aI(~sH}b$|QSV`PWq9k(&qkx6y(A~z(H{dX=*hZ~q9 zZp6?5HnOv@BC&cYxK+h%d$jA}Zu}w1IE~Wl8=NDyX4!l2SH7kUtH3D}dlGl$9ePcM zSNQ&UY=UPqStZ9A*ZlDe`i8AuIA1^NaEhZm+{|^DeaYbehpoAXT0wu_DfxbXB3`61 zEzZ9*ow&D(v`*u%wlvh#13vle-?E|UZahUiUh+KtBy+%gKv1-D_^IAZwdYJcYRaQcnoxHRXq0PO`k+OTMSM#T(OgcL3kT=ig8JOLdDCk3A>n zl+SFGdpIN3nj?GiV%5~)UYFjqh#g>DTz7>bD=RY<16)CGfz6ScegC>j=g*QuHX(1P zjwY&cxY|A>@87TMciR(Ut&jQTI4SyKvU!hP`$ufVJNP9Q&u|(hVxD)Aoh&`{pOMmE zV${FT+nvY@b~SCkJ^WnrrpoyfDQ`fCJ`dA*-`UMxcNJoGa8fn*zA}FhExl`DNSsn= z?d}W%orBVg-epuv?LB}FIzGpK#fr@sNPGzd(-Y^k7RL|T`a*kqUlm4hvcrLN^tFK} z=D`&Fi~r1>??LapTd9VIM3)$2Gw$W(3DN>5elou4#o>aNcO#OWa#?8gWuNAU z2s^wF)TbuJ8XUB=2!S`#xyPG{sw`FM&))VQ6g7T!!y0og;y3|&cmE(us&n9K53(xC z_@z87Jn!nHxO{$Mgi1;f_F>~={!VSqu!u{e^O`I$tLhc{{0?jFM!t+aR{)Xm3Vat>!&sjn)-Ha}Ko!ZM;OC%i;iR9Cy^F~?DMG~RBNcM$Ctej%sSACD90A2^s zCpX4Vwb%F#29*3VdwIchcM-g{Xq5Hfb{4q)hVl*zTgm4M@d!_n7^L>n2QTNvA9^U_ zQRD+rxqo>(dq{m-!9o*Yyo}JOnGtn*?1FQ#^F3a#r|^iGQw)3|Ec;Xy=C9)I@@)|A zZnx)oa!z1}&x6j@`Hxo{9THcZcU@;Ft%8^0<%Z1~H^$DvriMjI;0z%bnwv9kQKo#& z5)MFkP2O@XE$ewK&qg*w7U^8qog~4|eOf^XAq53lO6B%ngfQ6LX5EQfhL#9Y;udC$ zj+2OtZdXuXX#6FvYH9MN>|X}%J1wu>=3ZHk_LTnK>rlT;mOFvOBVXxMwM<=lk=ENk z7yvSDQ$Xl(WK{^_rkVU`{_-Nzn-p)=3)$SaQDdxU$Umpc+PR_g=RJ{z_{(&yttUKECS9dSwZbME9d>{j_?U;LM#x`Lzv4 zI(mBhHK6(^F$60>(pcK4NSJwe1$}|~_xm>OtrBf$BQz<)E+wGy9e^k0nh442RIMH) zQ+>&(m%M3~MR$&y+{qQZ=LRJ2f3)Meu&ZsnKbr1%bwK6)A62$s?jJ3fUku4#q4gh* zs{I=AQR)5NeCEI8-zQ%f|0u*L3;tU+<}C3?|9u7G|1VLv-znN3-Pl0sKf3Zqa%??Q z^^YoCLN@llq~dFSe*|jB+;;yZF}vE{@YwuEXPsQl6{7WDLTfNRWQlIcFyb3dVFgz` z{yEbg51;}c3h@3TRC}r7<)zY0O27e;(F5H%C~p~}{m&d(&ogL}ASX_CZfPEwjn<)k z5Fq^SreQ823L~OBy%U9ZQT4AoYQ*)fbrmt)%p6|?=$WqdvPSij;Faa}Z3yiv> z^az(-15+FiVw_>9g?U$&3>~z<~HwTX}%1m1-;6-Zq#J#C25d*MaeU zoRALCrcluX?o&`-J9%_wM#8qM&IUO|tmhG%cg}Xt6{f~jmTj4hvd{%9P)4FG6%87w z16aGlnOSPiFDM|(6aaK8{>i40!~?fSLFF9-picezN5)ML@xf-P46gw+yJmX@zO}v; zW^n*E$|M{S69SUvtBqqvACUTD4rq%aKI*?Sb1CSU?kkafd9y0YWAaqgnRFxl$l4*lgTEMA#x;v*yIpA(5%@qhMUrFa8lMQSsZw^vABl5|p=kcV10+Dl#b@g}puzw`;eN%wWn*c9MKq z@j4{YFqu^_bdW?44sSSyE%RuH92hBbHdzbG?^5w1k_{y)#p_}>h z4pBswJLr8Ns0Oo!fldy0CmktoOKmJRQK(Ydljagxj=b>>_2VG4Nmu%wa zl-VC=UfjOhhgs(x$P5~YP$KBR>1%QrxD*03{*hrU(sU6WMRBl*x$U0v>_Ea(z>u>z z2;0pJ&-w`pd7v#sluf*zmF2JXV3jHFuV6^R&BPF5K@j&BN{yBpF}y|kse^>*xnX;O(Gu z(#yi}(rFk6AZsMrC1iKESvt7v@&t!Esbkn6Gvb;ugYWn}P>VjiHuXyYp0={!kDAAJ z5B86m2h0pPyA-EDOToRUtf9EDqy@|~ZI#cfWg)DW>VSqVvV>bMQg=P*FHy8_{*qI&1WA55 z? zLYZpzu@Z-1AEAXFHO(_Dn>keKPSgp_T?u+lDTpNa#x4Mh2{bWenqHT8I8l(3=$45h zj8ez2K=>wFBUM;IPs=g)iGNPtMJE-e@-HpW-JDSY>m8_xC2XGE1h?SU;kHq5c7*l; z9SszzKDg5GIMKEWjb;ltNzg(iPZ#AUBZPGOSCsZ|=ExOXH`L=TN>x)b*bTrO5G2Zk zwD~|;Qx-G_^Rxz`h*8TE;@EDrPmbtVETqf$bdE5A=S=tM)9>?no;N!>an0y; zq~qQWhDV+PqrBs3()PqLZz^=l3x87zb}!qt+pib=t!(h-uXYcEycpiTh z!(C7T`-c|Xo<*A5d4hYiCpO6}d`puz}mZZ{kvCG}XV>|X*qdnCFM)nx{tPtU&+b_SXXlYLf3!hQBpWD9^%9S&QJf;@V4 z>^tYuNBiTlwJlw!PenQx@nvc?DhsZ~G4r3ot7wgs8@;F(v4}E5NhycrCBNKx+^KDe ziGZ7z7Wm^CX!{!#8oh|$k`%nA1}QdrQN1N95dVOu1Z=y17c|*eO$1NhkDmO3YcWiF zbPjQ!w&@zd;Yi|A`yBrIMwbrZ<>6tAoyk&Cs)Qm{=_&j&&H!1B32kB-2}&j&q@8=f zdw_&XZ>e!yB+>smL*?-|zTmnI)80 z_$|IEuAeqTlwEA5eC|cOh!wy4qAeVwS0b`Ot6AaPgXo_v3%KhdB1kAnVLR)>!M`Np zDhBZzhfJ)PrM6SA_0F46iMy!luk(C7t9C$MLv<_B9`qgcJRyq~_0P$65xVM2jLC|# zj;Hw5tUO4FyXW6hOMt9giJ`w8-+!WfG+q=_M}!KtlYlJ^Wo|7X->*g5p#xlHcWzMg z#gGvSUrlmBAG|ohj}?f(b1Lh&E}F;qOIQo)?CAI9ax*bqN^x+Q*D8d*65Wt0#A|Uo z;NUrcxJ%2?xyg8f1+$F9oIc@KlZUV9r;CBRfAZRw_ztzJ5}uasUj7r;hUPk(>1 z!`_u_G~yy$&gk&+5z_Cpbb8 zjQQC47VF!%sg56sk>AbJyq#OU(k^u}=9SrDYrssBW{=ih991Zv&T z4xO=|>g*`H1p%)lVP2WzTFL`R)#B!B3%DfdB`#ITlv8vQNMv%k8?atg`;vwfXZwP= z`uS0P2LYz*2|U?5RaBB)OalAhO*LpARvYRiWpA8XNI|I(ca65whoKT{l$OPomfs2!{5+sPP1EtAj#;`SDW2d~C0Pd$Vex9bJwmZnA0P3|T6J`zdyvf{l zOKo><26aBrzb0gO=vL7YO#3;3I*Cbe<0m*YNe*CF%30i@m*v2>>tHTplKBPjmL<#a%e->usWz zxGPW?wBXC0QbG?iDfhpu%tSi;ef8%|rBZ#Jl$q=A`ADBp?`=;h9_~1-#pJ8=?Xc&g z3#%H?gCxz^3UAcZCOr9vFsRKR&?2l0AN9vre1^(oAo=q}4~JDN2>=y~2AHvBjTIU5 zBx0OIzSC!-*qtmHY;#`0h*GJSy*2GKD@dDU%*dI!FsX?%cH*8Sjm(LvaV^Uy@{SXZ zoHjNQI}Cm|D}Z4WX?TC9Fi3WBER&&PQ+OP7i+7uzw!*rj8*1G%d3@P5`%y(d#)ZmX~&`bMn&5Al1&jtpmak{NC z>5y156GNzV)mxfxk2A}4EpR|DcK=>^z8B|JPcH^xI*I!<9S$x@v2$c2P)kKL7jvr` zh6VqT9?8hdKrPzne2}nTjcu9pt;RVHbV8wIH^4Nw8L^+~U9GjCp)-H)E>ybok5Blq zgBpQ0?Y=i&@v4(PyEilZbO?~mxIDrEj5hHT1Gf&xUAig;EbSYISlhp_Y~Ek@1Bnk# z8?=mR(Wma`-7PgIIr!@`YIZ3ugc#GH%2*mQX0H;n#@oCz;3NXvWdM(&&b9uy1o@Tj zEe+D)mLK}L8joASt;zE*=L&2kW2XxI=_R05PbggyTnU>Cx>U(feKdOV&&U)2BhzBR zi?W};d!6|Qu$y*8eGsnGB<)XfK29U0X+* z12oXP86osL`;)uca>=j3B1vF(&aXTP8ORtq-4eSYfAoR0_wqU%6L+@A z=m0ocM!vP7+}~TrH(_a7w3%4EJW98I8Cs?PwN_;=`a^2ts9!%M*bI304=WWBx;>HO}aP2h|_p#Yjb?4LkaaahNRKK zXIx3!ebJ{1Lq_hue&Sr#84Ir=u&^~Ry>Hh0R8P;B=uyI&SekW2cE>}uQ2kVsTMZ_V z@Om10sSSe8=!?k2&Z{YU+(Aj_?MTqzV_rHkwsUIWgei;p?Iz2$Ms@!+J*ARj0_;Q2 zJgay2T%7KF;7c`tAE7dlnq3%CG{RPIY97q>jp2LmsIYJHNL{IDC9e}8n``nOu0kAp z=y&z~BId~~gXaMr{N=rGn9V|RTRT+HH5Mk;@~c$kM;`m8$S-ORJd$$^;#!99PgFb| zD@4I2aZff{qDFvf!0JrJdqACIM@wSR8xTw6$jlwLmgAc?w;Y@N_?2|iUE1OdEyB$@ zcpkvy#4TqXPrG+iPq#EtTy2mpQOOA`07{7W>PLVI90S+!9<@VhFOE%sDS7G1tA@8+ zYZBZFRWkU`65saJpx76bmd}I1rO$Jq-sL;shefBavb10J^ENWjrA*)Qp6|{99ez19!zdM*{HlqwY z%vwy8s#x;p`)kp{6P>J1p(|_TKRVAwpb7)*)kbhE58x=T#~z29ENGBx}W6(Oo`D9cLILLtjtPv#=MC83m_HgO2-NO&iTRzd`4T9tv2aWd0sA z>OEhIvQ+7L)?gA=66`a>H3BW+In^Y*zcd)P(8HTh$@2?r6GfMo6m*9IAIk-Cz8Y{;)Dj(M-6PRr|Ii^y`H1A4v+fim6029 z@^l=PlI_vY{h6832Cxh=AUV~mn5dQCcRi(Q$obc$N&Bfjvn}(*B_;j)OuMST(zN`w ztYi`)qD9M%MN1-6CM(@72a}B9)#R;!76iZI;41@Fmiau&#}NOfUiG~1%1eE6dxzt^ z{4`}zHABH;R#YtCxGMxM!pUdodug^uOhNfU&0Y?09jcyAZ{$m^l&|?MyvBgg6nAOz z?K>Q=DMD_Wh`eddZln}2<4N_j?)+jHWS|i?a)B2^ilvBHth_; zyg!^@=Cx;-@AM)E=vTGfNLJaF$~U6QABcUAMx8Zw^Z+tnz1l~LB|@FcrTS$tf*v1{Ob|J0~v|@m{Ny zKSakxxcdJTbXD(6dtp%k)dp!-8ya}Iu>{52Y9?Fj1GRk_NNCLz7$q;+j z)v=dvU1GxIYQl0>B6-HD?JV;B zUV<{)4uUl~UOxc@wZgi$0~Y{~d5AQ+1A79iubnAhUHOr)*t!ubti+U>6vSDcqnyyG ze2p7KM=*P)zm(4C;s!mVNMAujT~my+*ex(q5E5UPb&}`0H#;lJ#h6&1wUgh9iObxX z_-8y9kKD{v2l2qUqU+pgX=0Jx0x;?p&TD@4;avW{nU#P@GQ^>Ir4HDzgPv$kkletU ze3ZY|IcGGHi6CfuLenKC7uh!ve2s@cCNUCubtnbHP#q{_M*+0(AHJuBvbT}KDAuya z0vWC(tVYQayc2HkES-`p>Gvj$7X{e9GgSA5uxHta*!ta-y|d~w7CXxX)zw`e4Xqsb z-L;%mDTQ60ib9=^N*c!f4LHuA@=`AlHkv~HUvKWj&PL&dL|dIVvF=y($wN5>Id~-G z(Mju&&tm!|g-A&yp4-L<8snN{j%i}`~2J+rUQO&jhJQc-u`b5z9K#C1fR!a*_r~Zk{)ZY&?U|ePGxW20cp&( z`{oLv7o2Uh=d0w?qY>QzPuuiMB~aNQdmU+(2y2^DD{Ltvc(N_gchh)AjEO$XUF9-7 zY$ybXncXut{Yi~E=THDY{d3-N*H+g~6KF2~UP(dS78!g@I-@$!0aamxSXjT&$ELlM zokaRYqVQXqb176jDr|I}y`vUTOEH(?cm}Tu_S$4XpyaxC)1$MwD}xOjt#M!+wt}3+ zARO#`7-m+(o)aGQl^C+fN88_!yS#$gbMyP%g@Bh{W40Joz2^6N&t8Qb8`JN4ZmEC` zW-?Z{ri8KH;8;N)0eHo4VW!TB(f%;gltAw92KAUzhu_@~U@}7)XBz4uALZc_6NRVi z6oiC?v!a}o={;S6CbSyrLtK?;9llL}+Y1dIDt?LOnS7~aP!J83-CS;WzAjKUR*zf~ zGrSHGm6exQM*b3zxY~NQN1ODN3O^au>P)NQMlJOM6Y@59)wlUsa1m!5rBRLTSe{Z-+?nT$cC32^ zHgm8<8W_mXy1y3QrH z3_z*QUd%PvFY$_}*#;Ip(-ASP^zxK?+{9uMq;EC`W-N_whGP!_9x~OW#WpWx2!c4!Jve1Pj|78+|c3O4j4<5CML03U^~?Ia!qBa$+h z@`P?QMeVdaQmA8|oz|lqpEl;6`)1sAf9QI(`tOK|IMvCEO~e&t^tDRm7JCx0%>+!Q zac++CB}JxF)OEbkZ?f`FG2<%nefdX?4@S}dZ2QX7}TT(ZWZ~clG3MFje zL~Ju7zHF9I&OQNq%DPHxjlnUJBA6U=>np2a0Z*oN#;#O0t1`OoqV76GPuJL6@T9P5 z=QPVfJdj%rZlkJfd@g238eJohsrGz4zNbmw$)}$Zw%q9-&-WML&G|VlG&)uK<4T}M zc3~Y;t`q@v>;0@^`kb;Mt7~Dfy!F=P*%ocfZ9=_U4YBLpS};e*5q&Qa^I$HMsQ(aF zW@-NZ8|;^Zs()agNLV^2*tyIkRAj0IZ(Qh7riHNs6oK^$gU_HP$nV`%C~zMO05bwf2$K>I-FIvH3ZS~%h`rgv z=+>;0PiI;?pS}17B-8AT7(|r3kW<$3oja<=nRDs zH?=wlyl9|vE!Uk;-0K`p>J*Z(Nf)xsP_+Vs*RG%kDcVn|6kJVDwG7=a(L~EXCfE69 zbmkgD$~@wdl|C-D`>3AEYm_FFaX5eUdA&Slu)+(#`MW#4Q|_)mGn0e6yJaD?Rr;mb z1FzR|DDz;zvP_|@b*&~%TAc-|ZTNeGJ;6X80wFZa3&%w3C1jp64cHHnaSIpi2UR0* zL#cO{SW%UNjnUj@zJ-9jQHU_JMe5V;a$ZVec3FjfXbvJD9 zYvT5)TCeu)4EgaZ*1Y^!+zlSCLERUX+<-@fp_1G!fGCV8flaNUyh8-K!#>A_ZiZXo zR?lnO%xw_d8~vWv=slQkUslyP`q%@=4sA7{)uf76`Z;X5H@bj9kIZLaArS8{Oa z8xY%mP(4d5SuV6S8Z^uDHT5$wkS>g!z8Vf*%#pVK6|U~QIYtrYd|ij{LJKapHeFPx zBOni1in`1^=}rm>DO*R3j*G;8~P3`S%o%$$#PRr&kYfm z70wmNFzKNA(y(oNZ%J1~_9f+53rKw#2P0!r$c!H;enby%F5W7YbA=BEsG9C@6~7yz zTNCHRADSj(iDOtlaX2qcPG+l7NmHAM=0Zn?4kDGv1g&j*tgW6)fzJWRc29;A4;&Z; zCf@pV4fsS3%f~z75h*5Oi6;bAOU==Ev?7U!<)hWbsB%m!%V4)bBEE7yyCeDFITooC{bwoPfu%h3yx+=~0Img(&vhw}l_?m^=)&(JLES>DUv| z!?-NTN_py-q~K@FE(WutFC|JDE4kVL8wD5eu||I?wa5~iJblEaP}f8b{m2ojMEIw!mLL8- z5+3Z1=v15X;po>DJAt8+KDxjz3LFYj)rM_}A_XVZjQEOW6^k~3@$T?=0YRBK^RHKF zt4s8K84J#bSA)|-l^r|bQ%Q2+2j4g83h}tsiBuwupJp%&AeO<$MBfzkC!;GLR(Y3` z3gQ{vUA!dQH!4}XiiM(&&1%Yo;Aj+4hl(xjI#hLBR!-K)3X%mQcAhin*y(G;E8!VP zPWqnZkqJJFPNqwHJz*?`P?r_@mepIMo+5zzo3yWmFc&&cwrh|x??9h%m1A&wyAinb zapO2`aQNj~3fbj)vgdGJrKICKnH%2USryT^t#++9YD*E;qQm<6I2SPIZ$Ry9Fs}}{ zJ54VBhVj_SU~2Ra9v8Mt$yG9U=8qTi{9liCx6AFLe%c4-5RTS7dBOHS{A#2 z9w;#p5^p(X!<-$|H?J%&>%2RPjT24k7GKnB)DIgKT(@H8)8v!T%TLRXHXQq8g~UaV z&yL;_pUb0Unn%+WR8@hDh^|Dn?*WO4$9NIdYg8ga2br5S4A7n9ijx;nB;f-xt<9JH zK);fj>#a}Kb`l#IrT2YI+@1dd7}_nnSO>UyVdA%sO}Ja%RtU$x{uJ3h_C4x;Z(>QG zbRZ}kXf5&o9$ow2pzO45ZWv}jKn(15SD)qU<)23zifHd*Z%)oNfU=v%VT*PX1F6}4 zQgIUWuCue5F<*Di?g2OEN#?t6merJ_{6P$~1M~q~psB>il{+1D^1Hh;uQ>kU*a{xg z8()0!lYcJ~w|`6CoEGmO3sPmn;Zq<_`Q&p&&<$VuEkNgGW;XZ~=>I@D2U`_rl^37C z(`nwC557*90~k+DMZH^~1}PF3w06!keq!WSO782}dNwLJG&CDz6r-UQ-AE7r_NR>u zObe0;q^r^OPy&)4Za5VrQ{(ueziU*BM^hQMebWT>3ORX|LRr4hhJ_=q* zx~6>Sv*dpYZ#coTbT~+$NP11}H99b~6>Jo(5fjrW$m~`bx{N#sTa3HWt;_if@9CJ3 z3=pIaH=vhqd?>>|6d#hXGE2B!#^B+w_&UT7EqsK7v83im*H1~bSXD*dX}3dNrhi_j z_;`iRct4tq&-F*iOn5)}f!?KI>O-`FN5q(0Prc9uB2p=oVjEJm^`@CP7v8AYt`q1= zHKztG$3T0Dioa-65zRq=JR;9N1PB=AxcMVT=6&b+_9x0uCcD_bth#zow4)DD(HWKm z!oKW<&K!hCpgP{bzV0ebi6?0 zE1XZHE+qnMdTf=xMqXn3qDRj5tF_uz2r08Nw-xfGWA#CFnGBJE6%ghCXZr}WMIqYD zl07Dzk=VRP%c7d52(~9$%8m7*MC`tAnYU@2Q+$KTt+hg+C#t`oQ!enScRlB#sNsVY zvVr)3OP?k%v~$Yo2w8Cl%UGGm3>$B^ci&UMQI!niPC-*lXno#AJ&-q=(w2=HVK2x; z7*eGIC>U6NdnBo$&PMNJu108bUeWcCn;q+b_;=dJq`tQ9~exPknTDoAz zkrkX4`Q`dGjCgE)q5#FJM!wY!%DU0OJ}{uj;+wMoqSmjPo++61#GNQ;`*hrpiDzTN zryiwJVRDp^2LCLj>>n7G0yW3RY%srE4jTf!rCi?GepJJUDZot7mXPR#UaYL%`V2#{ zO_B3cEOWy;jpoqfU~utY-(rRVZF?OC%a8#u@H>pgC0buhseF9nG^3dNsjDR3!puX}dqrT~J*&=ng>=!*+ayGkfmuWR& z5w4Z#>HlOHdk1frJw` zjD^%gFQhm>hl8E}3FGT`FdN;yIQ zya>KSAMkmdcbN8{fO9L1(yeC-vHEcYDg>5)_R}1)tZlZv5sWSK-FD2u1g_a84 zrG=b;#G*9{YsP3EH=v7+PJlLlO-*b>Z`dfWY`B42>bnqqV23Zmg&@XrwD4o>xL>%* zznp5B$8ruMY;KR9oHk+!y=J=aA)|_uncz03ePo&|{O|0XP)_g&y1oaD`p3i#M}S_e zei~E;n5Oc&z0Q%!u}wdlo#?1Qz`5YbmFa>rC>uWVQ*yW?G6z{A$-KxTB_fY0P|j#o z=EcRT+a(-OFrWzeukksn0w%*E9y}&zJ@2>q{F6560fB>0gwYkL3slzk){stkPpHje z&$b1RJ`>SsU74Y2$0qt$51VQg_MG)eGioriNP20;v%|Twvh)wK08JE_XA4|v7Gjpa zSEnS ztGCzFr)t9Ah5}qv_2-`5Mcdi^dH}^S`ag!!H9Z8!CgRKo@JOPqrvzDf1L%R36FVl2tBGb(*o zT4;?)5_H|-vSR3gEH+8xQ@}BaVMH64<8Nm0pq82lSTjaLQZTzQQJ@W^KfN>&B;Zl- z#RIxieE#Ojc--;ORhGl3`cAtEH165RYRF<>)Fk3wS=5XK&6A*Js#wwRLqvcV-%A z*mLDz)QmErN~bqN6z79x$H!Vb)QK`T#k`})>)Hzr?|5YOsnY3s^Az*m&7spZ#6&r` zC{b!+fMlcc$EPZOB9!mcH_dk z0hsq0tekXUjA}qHUK_rq4Yv;m)e{Yj(f+gj0BVo%4=GvlFk?UpM;e0vzod*~L+zRV zAr;-m9~i@E?>nmdDvnzKjxPYCfBV;DaCOY!mul>mTU}3lPjZd1wQ?*!57ifb0v}p) zuxv%Q5Ydu_=l^Q?@xg-`bP(4l6cc2%^=zayIBLU}aI3#vsElIwAwyJPrxQ<)G>BKh zl;*tgeu3AOaG3Wpu!b`Oc(-d@ymF>6Q(4iql0#AOpn}ySXA^#8D&p?BK!9DZ{CUne zvXqONT~jG~v0>FH;%e*(cFU4bG6FH_tI<7%4O=T=D;(@Ru9MOIlVh<#+)jP}!3<1M zAGYgp^lWND;-#%U^T8>f{K>EU*Qs{jqnyi7oS1F`v4hCk!dF&Z%K7^{b4kC-n%i_7 znNXdn{DM~of_oA~KU-+0Z0d&eUs1x=;$xoWa*7 zf=_uj-}uixc%*`Q^N4RDCd%)@dpAE@bkC3 zn=nPxanB%lszeB&_6dg2EQ&DkRC zIT34S?DH!z^1D7cdfIuMQpm;om;rLv818=T?$d7CDct+VRWP>R+e;0WwY=l}0`);f z>GBtq6;-`{%qVE?mcCg=`bY6)zkgchW(sSaGg-j-I>V6$q;9^WzCR;6D zR@)ck9yiVX79`Xw1LBo{I7@yxQ$V=uWgvd{;medahqX}GLyhD3bGG#XZK}dm=}Z_A zU1dXlM?O50A`d7IpPMeW` zu>39j&V$ArSo5J-B#*+&^p+bm4oQip*`q<@mK-qgC5gg>Su7D-ftUeMdRXO2&gT8c zK69oox@YdIntbrNE`JO`nAncwN}4oy%joOFVgzYU`zt|l%t==MM=kxnrL9?p>>4AH`hqykSbKQ8F9To$ zZi1qpg{hUTY49T5avysE9mh{W_3XNpS(8V(2%|~Y@i9}FI46pys2NPh;3a>ZQPbzD zQ%sT$p%19d(mKH^WVO zB=a5f*H-=~gP6q+bqwJpeblU{Dx}G)5h`rlgGaXgjN!3h{I{?-#olH~&;ks(9n9i# zoH8C%g@R_r2fZ-hBvevrS=n=blk2LF>FUBPYKYl~fmd%zXIbbn?Gdpj?sK8CGe|$W z$CTI)>cF_{$0Bs48&qSo1sCrc9neR;1;ne^^ZMzSi%)kkjkAIsOuSZd_sR0!jxRH_ zd4sTKGVl)i5EZ`1dvqeM7dHy>VlOh6U*6-9A(aw0**5zV4Ne>czw*hy9FK|TE@dRK zaO>&#c3X_t{5(E@V+(VhPK(`OzsCQb^?v&p_MH1)Cg#*?i_9*MNHC#K@TZOku~z^_ zoItv*0OOpGqTLNBjSNrDO5MSdj19QsZjv`aD~a1nl0+svj1V(8RDe-Pxw>X`!HDa? zn!P|d^$&M9)Ox=eTpuubn%~}8%)!`Bx%DVcfHzyTfKhVR>_JZMXDa*Ipz2hDl=$$1{$>%dd-=brVz{ZPQ)UP^_t!yOb! zu}z_uVW+D4K&{Hx7==vPSBWF&m9WP03ghI1sOB!oTh^Jdrw5y-Tl(Kle~sR)WaSg1 z*r74wAGyZfW=@}49T-b3d+~Zj)sYE8XxYdEF)XSAXIh+dShVNetH)Db=_1rw={QpW zZlNuUK$F)im%yFqPyHWQa8`@aZ~qrm;A+FS!a+h2{{R)dKs)ju?Mhh~H2^Y*|I=F= zPLU5D$AQ}8{R31O{|6oXFQAH>A)HhgzvsY(z3AvjxASBRxEcN%av*R(L3?fd1HBN~ z6I1CP_fPIhON4H>(Z&o{kLsnb1a7j{o>wTKCtFfe;Sy>0pOo52n;Dy zmU*zR06(3hEDw}Sx4T)>D_M>P%gZx-Qj|>IJo@H~hITUNE2J}BbSokk{_UKZ5Qn}H z7Tf3AZ%svx#IG)Yg!B%M5Jyz+3kGM}si`#_0MjLi>=bs3jHTq8X5VE51yv~fkIr5WVfhMO>PzO{ zh-PUxMXaw$N6O|Ac9dqzDDKXirUe>?nm@S?JvC@ARXtiu7Fiw{Q?B{sS#sn2r6^H4 zCfm6gr#F#daM`Q75Qv8Co{yX0M+9m%}WKEVzDI)kt6mb|ZfPwGZn znTWLUC9(C+`?tqflgk@~%EyM(0s-reV~>omJdp2;s=1a2`Pvocn!VgQ@kJPUc{R){ z2+-I+$-(`qcbQXMuekq-g&Afcg));Q3#~?}pyK3*D&K(fnRg%EFf#(%?zL$C?$Dx~ z-JYimzTPpsZV%>QV!AVF=96>sE7hRVc++A04SKGHZu^t5XBR7z&yhZ2DUGa_e+Is8 z@K<9Q7uS8Uv`bt}|7}Q=$q@O-{M(nTYX{dq-(;`T=Sy{pl%R>09!gc(K6~?(jb(SB zYS>IdajEN?uMU%k-Zci$D^)VQ4OYJ_xc+d04WkX!td+2M_8lXRml{_&`tD4kQ!nh$ z=E(3O(rbI7Kr=@?6UuE^#-f}sW-|U7m{t--$&nt{f!g6yrx`&!6CpglC%(j$@cL2a zTi)$rjfP|={ndDp`Dc~J^>s@{G%l^gjkE5t?)q(L6-`k!*{4+~H^rn?s5X_SRj4-& zq*Z7%ZKYKVH<^DQuTokqA9Maa)2O9xZOuOKzg;;v$Sn&ak&D7lx=eK-{4l<2I1r@X z6vB1NzZwjmolLvTcF%+C`V0gO<7PhDsK%o`I6Nd~V5sIp+()lFhLQZEE)EZQjqW2J z@=v;4Jmj@nLxE})`RQuLh_dtnW5mZwRc$zME}pPh^5PPb+gtKM@!)aYY5XvdFr>$J z;JL=5S}scwC|+sYDl2*kW1?2VdDWW1B}2O`wO4(PGNA3mZ#OJ}`EYy&;z{~Oz*>8c zPTWH!gpEJ!uew%e@{rwdmh8Gm0ZLUg(UY022{zW!MMDBSheHFcwV>M%-{g4?M+RE! zx}-QEa@aNeAeLc~Qsl^5w8qbr&;mdC{f;ZjKO1$*BmVvlN5)gQorZ+HM^303{{{Qs z5+w#DcKOf#>PYnXkjqh`0UtMp@+EkYsrmx zKNrcf9PXF;d^E#$o!*s)R@{Mh?sE89U9c0^;Oo29 zxgp&sF>kj2gxw`8?!A;4=S>6EI$zGDx$5r+=#8ak#3Unra_J9aS0XNs(hcWYB6|~~ zqF8nx>!IM+lrLz_6XB{DR(Lk4O;&h*Ju#Y9-cCucGTJOmuQJ~JlwM`B*^ypls_T&t z#$i%Wj`1rlQ;rEJ?o*BlD&A0z2`R=_iFs4Z$r%*TY{1IeEfRh`?axAd zmgGfN&SzjjOJhUprgnj(groU%L zO4XSHq~2t*Zr}NaZ@gMmPh9B#Jkwe*!w{1q(D|x5(_Dg6ew1-kY)8W%f@)N4;UK zX6$a6{j)gxM=NeG4xaP*wN7@$lh*eJ^;@>D?3*7@J@un~CrJUOCuC2=N$q`0 zT!@LD#(G~^(dwPi14cW){5r|!#`I5l39~Y=G~0778%{9n{fO!%lZ@%X8c_r2?XPxS z9K9bHbl~uKp@p{JB!%C1_2tpK$-BI|#=mOndjF`M@I(zJcE<5pzub9KFY3FNf1@V% z8w=RN{iy#kNtdgpamF$auQN{MFO7DHL1OoEtbICOzrRCeB1n-yg4Mt+I) zSM+vea7yXNw2n%GXN2)P4e&c-abGA@*j3i`g$#<@r8-%e$k~$sD_H@#k8>I(x|4Gm zrn>8M8D_eta~Wp4k8>I39eNL=(6%Ox2snqjEXjavIjJ5&+m=B?+G^WVev{&cAG;0% z*BVX3T&EJN$n|b>zO8E3a4NtUk>sn~|De~Qruadxr{{6v2sgCkm9Qj>=|7DMTli;S zjZuk4wbmN-HuEulRKhk?%r**zooNKVuCrmbXR7ky8hG-Fc2ZAWC!WQbJ|z*z82Yka zAtMqxNA#H9>HP0 z^ZGln6kem75I_IkR$f-p7g?)|BUBDv$l)&Bve(%-Zdwu$qk2*5%`lL5kChpfMusKq zBwpy*6Q8XLOL<*v3Rh|mNGG9@hyk&6;SHn_-2z%6!2q`RjB+3?2?MbDUF|^Y9tNP* zo3|h~Rt#Vp_W6L;9SmTVAq8L@91P%8Ug1L3R4@R8vSbdlFfslk>Yx4#Qos?CKV7x} zA~uScF~EbWVUkY*obLlMziohx)4DWS_%DghA8f*uei1Vny*ZP5z$<J4k%i`TJ5Vc-26yh)7{r&{iNe zw)(HA`3yVFKvCmf7mwY~6~vPQc*kLb6}~LQBE9~Td}wtILye<8|M9n_F}M9;O)+Wy zZ_%hY>G~0WU-3ekaixvHP1lvoNM&xXC|up9s<Hz0$bcFrRp3K6)2%?3I>vZg zdh`NY-yOif$#y3XWQM5HNA?iC3Wvh%8^Kn$O={f3)+_({H#>w-9+vW~yA6|dArd{- z|D8`k_X&#Ma@0%>yo5z1Xn!vvfj)k*sCOw$i=BBfl}qJ{b=);T>+fbNzzL>fr+422bD5)Ud1T z^C@>vTs0)m*TvdMw~KYuRILwTRr`(qw=5y`g%l#)97oAkVwQ>cXGJ*if6U!(y^M;F zl_on!t26Cq1RW;u>K!I~3gjc}py_~dXbM##r719w;~i7|P?Uo|sqrCphQy^a1pk)L zbaGQ%^5$&$A|sqgi%VQDJaIBOu0ddWsmX8HVqlG{k7o#V(k9b$OPmM}xM!Yor8aXC z%ZGdh;O+fVnQI&_F0N_ue~r`bzo!cRVsU%cORc@$Z*&60^`Z}% zQssYy*o9c_e(OqRNXXys>d8{M6~1wK-sU1CeRF1cwcUQRy-H^^j)^U&_iE@yhzjX1 zYOLY#HtIgH%1my=T^oXp%BmXsaqd|FD`%d{s+yX&q{xj8;$7!m=c;v zfec4;ivodMN?cWP7gyJ(&dr(Oo!3ov)u?Ls0^))i+a%s0^Pf0=HUbD|$RU?s{L2xZejbDvz@+fAt=!+5mz=Zcw zg^dP;99p?!pKM2%@#rPb4nC;?vhzhfPl&8{-n~VG=1Y!+M5Ow~QY~HH;xAa(1;y^+ ztjDm$WR=+k36oH&dB`O>57Ow(S01dQpJUchrex7Z5;luxiM_|Xt;)f{(o4(B$87ca z;OL#7!HyE_j_+UZdbF@O`cp-$;VbUun!a!G)m$j__q{n^Pv`Mx?4mUN?0Kgtz+-+g^nMi08fJdi(D9c!ePZ9`4&Q!jQo1U`SDz zsaVuss!_*W5XT4lxR>UzdYsF6s@~{4SI$RF;`_eEl|UW&BDB$cVf3UHpl}v9yXR=o zAB2m(vX>r6X#MUgj!&(7GY2v+oh?chSY-%4mtE*+Y&{xPpKATtV%Q#jM*YYp$1EjY z#AAMJ3vVUd%VJ;dy;z9Sf*c)Sg&(_aPO49T)`Zcq0o)ah!#0kKY8HPy0ERdi;HiGO z+!GI|`(*VupN)_O5?~k_`OLb+ylzpw&1U5ulSd=K@6c!8Q+Q#XRHc?pH6P2h;Y#q`^Rm|P~pup7v1fd;A1?Yof zMB^0QK-!on@^VvRC->XT;6hI)gkb_sk=kF`eo^(?P0f4dc+$K#OUU7fO>Rt<9h}s! zW`+6InrrCk#-Z$V*F)V%A{A|Tu@d7j9r$NfXeKS6oj_5P3LP6kdxCm}=Edn=_0G5O zve$T&sff$s@6X@|sipM&XWI+Gml_MqfAYbqUzms4(@K|p-i_bck#;DR+AySVB#Ua@ z@1f}%90aDlFBLgVKf#_D@^k-_Fs${PM4P?&klzL-a92M-V4EI|Gq=_*(d{@sr>{(F zWqZ`UaR26R?P@=HUdT-oQX$Gk)b--E!Qzw4EXnWST<^4YK695!BW-xs#BZ@CEL5Y$ zWG?pp%Y!hgcM{q>HK)g|?{C&B8|OO?RtJyS6EooGFNp;p7mDJW zf}M9gY$b{+g#$lq5OF*}6KpkI|8hMZ>m>J!A6oMDi0-vOF|6KTNw|w_mDP`Y+2^gk zHNesXRK{E0mko4oL8rfk*W*n2zC-C51K;q#$IUwC#RB+_!S-3kL=HGg(siCl-fwgcZDic%YdE|6@a4`&Ne%=(D&z5>+`zY zdE;%(tdAC$NYR_O$on+|KMLe)To>hnqLV6E8dXw+SqnU@ZJegwN!D3x>qvJDlX60q zAO!ElUJrW*Mo7nA+G>R-MOWJFy|0YdVc9*L57@J~xjF|vH~pV(fml7{@tw7`dE-4V z)cJ_COe+Wn(tPQ}mg(9pB>y_F9`EqCHf~9Mm}=>0AT_S&yWDF}Z1Py&QV&n7xt{V4 z{B%RAk$W)pEEypgo2e%Q+!5XiDa`80@IDADHwY?{a+;(p1BF62)r;9#E@60dskTf7qltajizaVS^u@JC|L#toSZ~eHkpiqc;R5bmtnbfSQ?e7 zqVck`0AJ;K=4%ccNd!Lh-1D%sA^0G94p1`GDk=s~KbeUK9p#HZ&JJvdy-oKzf~}&vi4cwfb2rSxs0iteO3=a2uz*G zt}cb*($Z~p)+A2R=lL279LHFo%S`Zg@3j`-e$l=ED2%-}zb}CYb=of&49dv^Z!3>k z9c_%flMZS|D*XOFeb-_CX6v**dId03g~BygH>k^FgF@ys#L!fWPqiV);rcP2sVGq; zyiRUrtbwY|mS~N9NMLp%J*k}PI!98&(eW{W8}Mc%@ewg23;n1*X(}lhYC=taqL93O zpSW*y(Owc0kDRyFRP^I+G;@{gDIVZay_{OJlLUeZ`NhTe78mKtrA`$Dd)lPr|Je5S z;?2ydhCK)wkr8T2DJw%pgz#w{{ORP)@$33Hl-kwRS`lLJ&W2AtYj<{fz>06)N_o3? z&Tc!0th8v6u$wvIx+YEK+3#`FJCSku9k$%dv6(j=#0j~(qWtHZK+Z=y6!EG&jG6_c zq3T9;Go&~0*-d~8LaG2)3K2;E0`LxiR5?)qv8G0ZI+f&67w_y+|5Y0r)P)z}OAY zjsUTx*#UfVnqgpU9>Cxre1K(M%EP8<2GY)y;ffEoAOCgK|CO@;9PUg{YK9NpQVe86 zg|aoNM8zx2NH`P$QS?ryDsmtppn6bH=z*-PY>!=LoMaK2M+o(DfQHQDKj&JM6$SYm z|M+b7n0<}P(S`)@?E6@49q{w_HeH(#+s>7}_=)rhz&1Vw%NgW;ZD20|w?#Dq6l08G zC^zbvXCV70gEVF~P!y=rI*=J9tlGzK?}2vZ&u7{_K@+lCX;>du(yH!?7--+SSEWmg z+r@zv7{t(OYl5Gjm!d#}nV+?u$Zz^|9sE_exOJTPYy7LEl#uQQLmc#qyr9PP19caG z>Jl5FSt5lrp#j!~`D#E=S;2VQEFV1NwmwiNnM+H$TFlAzi7Bg_g1Sy0t&4vtjA3s8 zpvfH$EZjq7JJ_I$aLc|xAc89ogn(sW;;FL?vglBBx*W_Oh-siz-#Y_}cOXsp%%m1BSC$0+JHk~?16h+!jU%{fPtKR%PD_UPj zo2@AP&N)6C!#0~O6}g$0>;1&gEn;LEV)hnks&VW381#Io;@^_0Nj6_?fy$>oR=NYB zn^sRRavU=Et1+zZ7`}fhWigxt+c??Q9r8YaDj#g$nSc3U$hoeCnf)c&6=%j$yi^b?ZkoeS^gwIlY1fxG(Hd_{0+Z78+*9t#I zLC+o`Ych^b34y>zhiDw#HmLu#$;)#*z#Dkj@~%bLPTaMaL7d?E_f|*tx`YKmc-#sa za==zQ!Pa=W6z0i0-#yF#90SA7QEHzIB!RO$v|j~|<%b7gPb;xAb?XHBG0di!M@3(E z=&YYOt9%q?V+96jeF9le*ZA2r&E6#SfNRx5;{}j(!;jop8_Ld!EAwW$@G+t}Hc|Su zqgs_irUPH|HH3dBE%XuJdaL|>uS!+qC`u!;^DPrO`rFDxANv|IF@$Q=E^R_jW2Osb z=t*+_`;8amfkDtMRZfWImGSEpIrPenURwzPTa;@Gy8!)5QU5Qk3kxVm%%kZ5Nbljw z1k#YeCED;8#aGJOIXK%##f$y`KL<>q&_w`(YGCXg<%_6~8Y=oLqV`9Px6NOGNYASPDu>*byKK0kSrCyyU@^~BTE-?DF|YA3z+uAN>? z81c#9Eo=UK6Shq(D2VNo*iN%>eUyVt&(emE$C-AUid>2BFkZFK-(X>*)93KnE6glg3!d}<)9G|5O6X!+!1i8NS0Sz3H#Wi9P^BW}Vr`!JV&1g~%?NUv; zS??LAsrHce8}33-Z-`_94Qa4UK^t_1m%Q0WIe|7>=JtH}d98IkQi%?j&a;bS25>MD zL|AJiLuT#@(U9sQX}bTV#c4)(ta6*oNt>wId#AjbjhpdQUb&hf-T-C1c+-DsF3njj z^^rnMOziWy_4=Ke^S17;u8nF&?mwd)nNJve*+dds&kus%<>pq=`QHAClzBOj_Q^}Z zY{mC{c6R;8P3_6_=^2CDtxk!}9)KXnPdWhxzU46X26X&dYGF@yfh}m?_{3rAddE4a zQcOOQfH&ZErGR@+;MJL1+uJizF2R)DlEsS1<)d@dRIp=(lc+y%;n8Qx5-VL6yO!OFfOFd3~nhVzmUo}p`r+?S23An zF1b$~IEo=-cBX1V@$lipx)%@5rB@4AuFenrV*w|EGqV}tXnZzO%FNuuX%~69J1B9A zFEX2-BSw1bwp2<=)$0DyTT3t`1c_{5UI@8u^In47wDpshn<7YVnGN~p);v39WNta& zPT6oTe6`H0Mwy_BdP+)4^WFO?eX_m)>oca&!D?FI<@kgSZ`lmzjZbEnm7#qk=G-je z;ui;}ekctQi^Yz&e<$?h)AAWXr32!2^k&M(KM#2_{n(@dPh{SAd6gkdVVq`D=*>?F z8;8z!J(THYL0SCHv!bRU*T>|@MvlBd04(49NFo4*;8$!ye%OsIgR@7dgH=O1BW8<5KDHqNm0zmQ6h)A3B&} zyxX)+U+1zwA9QgvViL{AC8<|Ne%8I#y)-sldQ{)jr~1P(Q7Qt9-WTI^Aw*`n!6}wd z=1Lowx|QrcrX)bd09sU3-6R}K!P~)bd!}58t^Gu8<+dd0urHbAg5m}MYtPQ(_k9dn zi}2S@!=(aF7`eIC%$bo2ni2GB!Pt2_XJB(nIs|=OY%D4>s#^82O4sscqUFzA=moU{ z;8ny0Jb4!+`y7{=JDoIx^Iw24PS-?Hl~2>)JBy!$CUv$2*NqBnoG1rI@?0PmX^Q0E zlB;yCB#cPfcZNV(kC9oKrKEhUgGgqqUW4t5=6k=RSiO z$Ydut$_<}O@iW@IR<{f>1I!2ks52gKYVT=9Sjm1wl^pEi0pe2z$CLj4AH$R(6O#rq z%#cU9aAqL`xN@*)Xk%u3{QA0-dF9-j$xu+G^ph)p+3W~#c_m#pK~SYnKDdIRsgfD% z7xA`|?~YMT8p0=n0N=pGL?!Io_r zbo?&;r%US{=+-0U5YZSP!`9d;BeS+(MkEO!`1!@Lu9MuM&bIQVLn|)S_!=GD+_}Tx zPCMuKsuVdPq78reG-$@NQdIF~0f>O_3V8H_cXWJGg*8UeQyUW~?sEaW#?z|W=$pI} z7`P%6VFE7%{CcMTUzEK7eGxZdrVDU3F{krDLgZo13Gc6wS=Zb^zDU7Y%d4PDmjdvd zvNho$Goj2(I87Yfr%J2z2p}*~#b0InUigp6?(i9q z^X#?aW;@EpH%yft;}G%;=AQsYaC69mh-2D`Vcr`*WX*MBU#B7T*45Ih^tS26D!xPR z-y0aC2H_@r>*qELTOBSt0&<`o9#6Glpd-~oq03sFkW-_27jp6MTV>?2X4_m+NZ-~)500j}(b&?7^c6DM% z>lf4m`rgs~$yIVrbvXa+h2Q>CIVL5f{3DY>q zc2-u!NIj3_nuy_{AOds>1b}$99LC>*Dx>fcH`?pkPd*Hw{V3)YfO!!!&pjilhPK1; z#dyxE6wE7x)9i$pEhkpQ(siqxlrub zfwYQgDQ7K)i{B2Ms85@sQ0k^W#pH&jlmajhvy>cY*D#@^9G|8R&@BJ3! zl{yM3nGS*h@q?8^ob)zDjZ_3}xV#L0y`o#^co_n8z133vBf!n&$Y>TTpnajDx}GqN zt4@bNw~R~c`Ne)m;)QX59IW{b&`BEqYn>D7Na&5MM)Kx~obk_3Uo8?{5A`)t4}^&&deFVh0SLF^l(o^3cF%^!fsAxT&9b0 zLFb|14kFGmR@i_vCt+k#P5Y(h9%^zrHH#ba^OPQTg55)rFg|(j!5t2i$NG0aHqwV2 zzussuJe$rC?DY0o=2qI%G5gu%!SN#a9NH=DvrM40w``X|!PhPDX!zxi16fh`t*Axe z-7dPg`QFsW=1AI_nwkiw+#aA^Dh{%^z89`Zpm>qti57PJ*f} zvGiwD;w31jH7&%Pg`>_sJTq#O=p8!y}~icA83T z=&sr+m?E5Z;U4Q%7o`oFs;be80f=`G@NWu%Vi4OOzcf2f4_SRYonPM`cHIg&dWh`I z$W#Co8c{J@4rjsTtJNXb**DQ}u>b|~y)iJR_^Lvwko8d*zKEjNi4*&L5#<`1zzpLtP>oxK()IE$Li ze5)5w90Icue}pWx2eJ~C_DXeW6XX)DvQn0w2AZcYMT2+#(9nTcM_6FgZKY~P&7Kv? zpd4|pg2l^nGA(pI!`7{mrKPeCeDeQy{l`D5NPwKsdJO2+Te3$VYJmci|1LNwR_YVv z<0UquuZqmiXS}!YD1v^!Lg(ej93#CmpRx`plhjtLBWOdIUyW* zPvaW5mbX*Y*@jidfNyNPtkND6D3tipHdCD4%`Zti^r|xF90Is9j-RCiylT&1=F z_qrLo%PMt$`wiC-xm9)$H+BKtelw!t?l9R3mpSGYfq9giU~1Kd*#4~7^8rhmjvS~~ zPfxeSaABYtVWhO-oRAT4LoW6MOROLRoFOg@W#C%oM>}DbNfu)UTn%Z6lReyg9!S&Z z-{OlxK%O0Jz-50?K{RgKG-}R@o6=HNN`TkL2CELN_ynH0@t@KLK3GN$mq>#zU#ByO z_jyWmS;jHuMy?%yk7t$PVwdnQE9&I%v?&$Amf8k`I^7o=wyVP7K;}OMtD-T|#kYP+ z9bWQoN&wqT$!Kq)stJ@X03A4T|IG?f4pbgXE(C=M7fs2-o1&0yt)U;8nggxO{$}wj zzK-WP#=M^bRuulK(Y&}*qv@A%%DO`N>Q&y;aPjKrm8L(-9-{^q^l$9Dj`1$rwpyXA zOKZ>fF6z_4B`;V+F)dB0gr32u>(Rws3DrQV3Hh5J?EESST|H}h6Ehe^dPj0r)Dw-Y zqTaG+pFzA`Ns*mTt%AFc->^as$yf@^ppKUOX6zECp4Ze*Xo)?+Fk9NX6s8FP6Q zi$|%*T~h+iNsjTTI4##pNBj;WfAGanv&BmNl-!92vhHK+0~=^(;^f4%#5a)_^7Dh9!bv~8 z|B`}$6bH$DBh&+Yfn_sJ(%(J3C9|Yx3-sTtt)#2hlKBaw{wPwF3O}haz~M%FB6o4j znR-0&WnBlU;$RTz?iXC{y2~RsC;qlBHQXj%u#BPCW3+8O86h^S?I_p5xq_1<#5aGd zO4|?anC9^BD{Q@mK}9_5%?yjvAMZi|k7|XMuoFqCqwZsZ3qvh91$#b0_@g9)4?XXu zRO(y`wN|R}#atf>GjcR%ZcV5y6m&B`8*lORVDBzcA3WImNKf4uE8F3?ZAq4n_7Ok8 ziyz?#t8q6)yr*V@Xjx|RK(q?|@1$_lY)e55U!m&`EGIEpD{9_?rQ(7fCe7mhGo}Au z6@%z1!vEUt zRq^=mwh$1=v5x^$^34}}mdgVdZv{|ZY3ID_U9yY-$InAb^dfA_HGO=MNMLW-Vjg$jG?Ni LsZb^NGW34{&{zPi literal 0 HcmV?d00001 diff --git a/Project2-Stream-Compaction/img/exclusiveScan.PNG b/Project2-Stream-Compaction/img/exclusiveScan.PNG new file mode 100644 index 0000000000000000000000000000000000000000..eb84b1d386baa23138b43f690214fefd425d22ab GIT binary patch literal 4470 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 literal 0 HcmV?d00001 diff --git a/Project2-Stream-Compaction/img/naiveParallelScan.PNG b/Project2-Stream-Compaction/img/naiveParallelScan.PNG new file mode 100644 index 0000000000000000000000000000000000000000..2460e49820d044cdd7521968f3b1147afa0f6c73 GIT binary patch literal 22973 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 literal 0 HcmV?d00001 From 66b066104f0fc48e7e710f4d6144be0251d6dee5 Mon Sep 17 00:00:00 2001 From: Grace Gilbert Date: Tue, 17 Sep 2019 16:36:07 -0500 Subject: [PATCH 20/38] changed to only 2 character input --- Project2-Character-Recognition/src/main.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Project2-Character-Recognition/src/main.cpp b/Project2-Character-Recognition/src/main.cpp index 203ce5b..591a6b3 100644 --- a/Project2-Character-Recognition/src/main.cpp +++ b/Project2-Character-Recognition/src/main.cpp @@ -26,7 +26,7 @@ #define training 1 // If set to 1, indicates we are in training mode #define acceptedError 0.01 #define inputSize 10201 -#define numInputs 52 +#define numInputs 2 int main(int argc, char* argv[]) { @@ -143,7 +143,7 @@ int main(int argc, char* argv[]) { int numRandIters = 0; float accumulatedError = 3.0; // Larger than accepted error bool done = false; - while (!done && numRandIters < 10000) { + while (!done && numRandIters < 1000) { // Fill new random weights auto end1 = std::chrono::steady_clock::now(); CharacterRecognition::fillRandomWeights(layer1_numWeights, layer1_weights, std::chrono::duration_cast(end1 - start).count()); @@ -153,18 +153,25 @@ int main(int argc, char* argv[]) { int numInnerIters = 0.0; // Try refining weights iteratively - while (!done && numInnerIters < 10) { + while (!done && numInnerIters < 10000) { accumulatedError = 0.0; + bool resultAll1 = true; for (int k = 0; k < numInputs; ++k) { float currExpected = expectedData.at(k); float output = CharacterRecognition::mlp(inputSize, numHiddenLayers, currExpected, layer1_weights, layer2_weights, inputData.at(k), partials1.at(k), partials2.at(k)); + if (output != 1) { + resultAll1 = false; + } float currError = (output - currExpected) * (output - currExpected); std::cout << "expected output: " << currExpected << " Result: " << output << std::endl; accumulatedError += currError; } + if (resultAll1) { + break; + } accumulatedError /= 2.0; std::cout << "Accumulated error: " << accumulatedError << std::endl; if (accumulatedError < acceptedError) { From 9b08ec4682f780f2ef5985a65459b94d7411735b Mon Sep 17 00:00:00 2001 From: gracelgilbert Date: Tue, 17 Sep 2019 18:29:00 -0500 Subject: [PATCH 21/38] Update README.md --- Project2-Stream-Compaction/README.md | 84 +++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 7 deletions(-) diff --git a/Project2-Stream-Compaction/README.md b/Project2-Stream-Compaction/README.md index cd5049e..4c863ec 100644 --- a/Project2-Stream-Compaction/README.md +++ b/Project2-Stream-Compaction/README.md @@ -11,15 +11,15 @@ CUDA Stream Compaction ## Overview In this project, I implemented the stream compaction algorithm on the GPU in CUDA. Stream compaction is an algorithm that, given an array of values marked to remove or keep, removes the values and returns a new, shorter array with the values removed. Below is a diagram representing the stream compaction algorithm: -# INCLUDE SC DIAGRAM FROM LECTURE +![](/img/StreamCompaction.PNG) This algorithm has many practical applications, including path tracing, as it lets us mark certain elements as unwanted and remove them. While there is a simple way to perform this algorithm using loops on the CPU, it can also be parallelized to be more efficiently performed on the GPU. An important step in the stream compaction algorithm is the scan algorithm. This algorithm goes through an array and accumulates additively all of the elements in the array. An exclusive scan excludes the current index in the accumulated sum, whereas inclusive scan includes the current index. Steam compaction uses an exclusive scan. Below is a diagram representing the scan algorithm: -# INCLUDE SCAN DIAGRAM FROM LECTURE +![](/img/exclusiveScan.PNG) -I implemented 4 versions of the above algorithms, scan and stream compaction on the CPU, a naive version of scan on the GPU, a more efficient version of both on the GPU, and then using the thrust implementation of scan. +I implemented 4 versions of the above algorithms, scan and stream compaction on the CPU, a naive version of scan on the GPU, a work efficient version of both on the GPU, and then using the thrust implementation of scan. ## CPU #### Scan @@ -65,17 +65,87 @@ for (int k = 0; k < n; ++k) { } ``` - ## GPU +In order to parallelize scan to run on the GPU, we add pairs of elements in the array in parallel. We do this in multiple steps, eventually filling out the entire scanned array. The way in which we group the pairs of elements to sum together in each step affects the efficiency. + +These groupings involve splitting the array by 2. They work only when the array is a power of 2. Therefore, we pad the input array with 0's, which do not affect scan, filling it until it is as large as the next power of 2. + ### Naive -### Efficient +#### Scan +The first implementation of a GPU scan is the naive one. This implementation starts by adding all adjacent pairs of indices. Then in the next step, it adds indices one farther apart from each other. The distance between the indices added together in parallel doubles at each step, until eventuall the first half of indices is added to the second half of indices. The following diagram outlines the naive implementation: + +![](/img/naiveParallelScan.PNG) + +This process has O(logn) steps, and O(n) adds run in parallel per step, making the total number of additions O(nlogn). + +In this version, the output was an inclusive scan, so I had to shift all of the values right and insert a 0 in the first index. I performed this shift operation in a kernel, where I checked if the index was 0, in which case I filled the shifted array with 0, and otherwise shifted everything right. + +### Work Efficient +#### Scan +In a more efficient GPU implementation, we utilize a binary tree structure in order to further optimize the scan algorithm. In this version, there are two stages, the up-sweep and the down-sweep. + +In the up-sweep, we first sum adjacent pairs, creating n/2 sums. We then sum adjacent pairs of the n/2 sums, creating n/4. We continue this until there is one final sum, as picutred below: + +![](/img/efficientScanUpSweep.PNG) + +Next comes the down-sweep. Because we can treat the array as a tree, I will refer to the left and right children of indices. The root of the tree is the final index, initially holding the final value from the up-sweep. We start by replacing this value with 0. Then we store its left child. In the left child's spot, we copy the current value, which is initially the root. Then we sum the current value and its left child and store that in the right child. We the continue this at every level of the tree, running all the values at a level in parallel until we've filled out n leaves. This algorithm is pictured below: + +![](/img/efficientScanDownSweep.PNG) + +The up-sweep process has O(n) adds. The down-sweep process has O(n) adds and O(n) swaps. This makes the total runtime O(n), exponentially more efficient than the naive scan. + ### Thrust +#### Scan For this implementation, I simply cast the input and output array buffers to thrust device pointers and then run thrust's exclusive scane on the buffers. ``` thrust::exclusive_scan(dev_thrust_inputArray, dev_thrust_inputArray + bufferLength, dev_thrust_outputArray); ``` -## Performance Analysis +## Performance Analysis and Questions +For each of the algorithms, I ran them with a block size of 128. In order for the block size to be optimal, it must be a power of two. Most of the thread counts that we are sending are multiples or powers of two, so having a block size that is a power of two helps ensure that the blocks are filled to their capacity, making it most efficient. + +### Optimizations (extra credit) +#### Naive thread count +In the naive algorithm, within the parallel portion of the algorithm, there is a check to see if the index is at least a certain value: + +# INSERT IMAGE OF PSEUDOCODE + +I realized that for all threads where the index was too small, the thread was simply returning without doing any work. To optimize this, I calculated exactly how many threads would actually end up doing work: +``` +for (int d = 1; d <= ilog2ceil(n); ++d) { + int numThreads = bufferLength - pow(2, d - 1); +} +``` +I only launched this many threads. However, the indices were now incorrect, as I was starting at 0, but the desired threads were the larger portion. To fix this, I offset all of the thread indices by the cutoff value, enabling proper indexing without launching any redundent threads. + +This optimization improved the runtime of the scan, as shown in the data below: +Prior to thread count optimization: + +![](/imd/naiveScanWithNaiveThreadCount.PNG) + +With thread count optimization: + +![](/img/naiveScanWithBetterThreadCount.PNG) + +Note that this data shows a relative improvement, not the absolute performance of the algorithm as shown in the charts above. I had not yet started documenting performance properly, so this was not run in release mode and the timers included the final memory operations. However, they are both run under the same conditions, scanning an array of size 2^7 and 2^7 - 3. + +#### Work Efficient Implementation Optimizations +When I implemented the efficient implementation, it was slower than the naive implementation at first, so I made some changes to optimize it. Like in the naive implementation, I made sure to only launch exactly as many threads as needed, and this number gets cut in half each iteration for both the up and down sweeps: +``` +for (int d = 0; d < ilog2ceil(n); ++d) { + int power = pow(2, d); + int numThreads = bufferLength / (2 * power); +} +``` +To then ensure proper indexing within the kernel, I multiplied the index by 2 * power. + +I also realized that the thread operations were using 2^d frequently. At first, I was calculating pow(2, d) within the kernel, but realized this to be inefficient, as this is not a trivial operation. Instead, I calculated the exponent before launching the threads and passed in the value. For the cased where we used 2^(d + 1) instead, I simply multiplied the power value by 2. + +The final optimization I made is in the part of the algorithm where we have to set the hypothetical root of the tree to 0 before beginning the down-sweep. Initially, I copied the device buffer data to the CPU, indexed into the root position and changed it to 0, then copied it back into the decide buffer. I realized that this copying between the host and device was inefficient. Instead, in order to keep all the data on the GPU, I launched a kernel with a single thread and an offset value. This kernel indexed into the offset value, as the only index is 0, so I used index 0 + offset. For the offset, I made it the index of the root, and then set the value to 0 in the kernel. This saved two copies of the data between host and device. + +Overall, these changes fairly dramatically improved the runtime of the scan operation. The improvement can be seen in the following two images, the first showing the runtime prior to the optimizations, the second showing the runtime afterwards. As mentioned above, these are relative improvements, as I did not perform these tests in release mode, and the timers include final data copying. Both of these are run on arrays length 2^7 and 2^7 - 3: + +![](/img/workEfficientScanLessEfficient.PNG) -## Questions +![](/img/workEfficientScanMoreEfficient.PNG) From 0d942c6a5df12aa67c35a3b42e65ef7af343aeaf Mon Sep 17 00:00:00 2001 From: gracelgilbert Date: Tue, 17 Sep 2019 18:30:50 -0500 Subject: [PATCH 22/38] Update README.md --- Project2-Stream-Compaction/README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Project2-Stream-Compaction/README.md b/Project2-Stream-Compaction/README.md index 4c863ec..1606c8d 100644 --- a/Project2-Stream-Compaction/README.md +++ b/Project2-Stream-Compaction/README.md @@ -11,13 +11,13 @@ CUDA Stream Compaction ## Overview In this project, I implemented the stream compaction algorithm on the GPU in CUDA. Stream compaction is an algorithm that, given an array of values marked to remove or keep, removes the values and returns a new, shorter array with the values removed. Below is a diagram representing the stream compaction algorithm: -![](/img/StreamCompaction.PNG) +![](img/StreamCompaction.PNG) This algorithm has many practical applications, including path tracing, as it lets us mark certain elements as unwanted and remove them. While there is a simple way to perform this algorithm using loops on the CPU, it can also be parallelized to be more efficiently performed on the GPU. An important step in the stream compaction algorithm is the scan algorithm. This algorithm goes through an array and accumulates additively all of the elements in the array. An exclusive scan excludes the current index in the accumulated sum, whereas inclusive scan includes the current index. Steam compaction uses an exclusive scan. Below is a diagram representing the scan algorithm: -![](/img/exclusiveScan.PNG) +![](img/exclusiveScan.PNG) I implemented 4 versions of the above algorithms, scan and stream compaction on the CPU, a naive version of scan on the GPU, a work efficient version of both on the GPU, and then using the thrust implementation of scan. @@ -74,7 +74,7 @@ These groupings involve splitting the array by 2. They work only when the array #### Scan The first implementation of a GPU scan is the naive one. This implementation starts by adding all adjacent pairs of indices. Then in the next step, it adds indices one farther apart from each other. The distance between the indices added together in parallel doubles at each step, until eventuall the first half of indices is added to the second half of indices. The following diagram outlines the naive implementation: -![](/img/naiveParallelScan.PNG) +![](img/naiveParallelScan.PNG) This process has O(logn) steps, and O(n) adds run in parallel per step, making the total number of additions O(nlogn). @@ -86,11 +86,11 @@ In a more efficient GPU implementation, we utilize a binary tree structure in or In the up-sweep, we first sum adjacent pairs, creating n/2 sums. We then sum adjacent pairs of the n/2 sums, creating n/4. We continue this until there is one final sum, as picutred below: -![](/img/efficientScanUpSweep.PNG) +![](img/efficientScanUpSweep.PNG) Next comes the down-sweep. Because we can treat the array as a tree, I will refer to the left and right children of indices. The root of the tree is the final index, initially holding the final value from the up-sweep. We start by replacing this value with 0. Then we store its left child. In the left child's spot, we copy the current value, which is initially the root. Then we sum the current value and its left child and store that in the right child. We the continue this at every level of the tree, running all the values at a level in parallel until we've filled out n leaves. This algorithm is pictured below: -![](/img/efficientScanDownSweep.PNG) +![](img/efficientScanDownSweep.PNG) The up-sweep process has O(n) adds. The down-sweep process has O(n) adds and O(n) swaps. This makes the total runtime O(n), exponentially more efficient than the naive scan. @@ -121,11 +121,11 @@ I only launched this many threads. However, the indices were now incorrect, as This optimization improved the runtime of the scan, as shown in the data below: Prior to thread count optimization: -![](/imd/naiveScanWithNaiveThreadCount.PNG) +![](img/naiveScanWithNaiveThreadCount.PNG) With thread count optimization: -![](/img/naiveScanWithBetterThreadCount.PNG) +![](img/naiveScanWithBetterThreadCount.PNG) Note that this data shows a relative improvement, not the absolute performance of the algorithm as shown in the charts above. I had not yet started documenting performance properly, so this was not run in release mode and the timers included the final memory operations. However, they are both run under the same conditions, scanning an array of size 2^7 and 2^7 - 3. @@ -145,7 +145,7 @@ The final optimization I made is in the part of the algorithm where we have to s Overall, these changes fairly dramatically improved the runtime of the scan operation. The improvement can be seen in the following two images, the first showing the runtime prior to the optimizations, the second showing the runtime afterwards. As mentioned above, these are relative improvements, as I did not perform these tests in release mode, and the timers include final data copying. Both of these are run on arrays length 2^7 and 2^7 - 3: -![](/img/workEfficientScanLessEfficient.PNG) +![](img/workEfficientScanLessEfficient.PNG) -![](/img/workEfficientScanMoreEfficient.PNG) +![](img/workEfficientScanMoreEfficient.PNG) From aed1d93186faa886d51f85d4214bab7290ea2313 Mon Sep 17 00:00:00 2001 From: gracelgilbert Date: Tue, 17 Sep 2019 18:32:01 -0500 Subject: [PATCH 23/38] Update README.md --- Project2-Stream-Compaction/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Project2-Stream-Compaction/README.md b/Project2-Stream-Compaction/README.md index 1606c8d..29cf78f 100644 --- a/Project2-Stream-Compaction/README.md +++ b/Project2-Stream-Compaction/README.md @@ -119,13 +119,14 @@ for (int d = 1; d <= ilog2ceil(n); ++d) { I only launched this many threads. However, the indices were now incorrect, as I was starting at 0, but the desired threads were the larger portion. To fix this, I offset all of the thread indices by the cutoff value, enabling proper indexing without launching any redundent threads. This optimization improved the runtime of the scan, as shown in the data below: + Prior to thread count optimization: ![](img/naiveScanWithNaiveThreadCount.PNG) With thread count optimization: -![](img/naiveScanWithBetterThreadCount.PNG) +![](img/naiveScanWithBetterThreadcount.PNG) Note that this data shows a relative improvement, not the absolute performance of the algorithm as shown in the charts above. I had not yet started documenting performance properly, so this was not run in release mode and the timers included the final memory operations. However, they are both run under the same conditions, scanning an array of size 2^7 and 2^7 - 3. From 3f27050f37d96fb584c32b3f89053b72065b6cbb Mon Sep 17 00:00:00 2001 From: Grace Gilbert Date: Tue, 17 Sep 2019 18:35:43 -0500 Subject: [PATCH 24/38] image of pseudocode for naive scan --- .../img/NaiveScanPseudoCode.PNG | Bin 0 -> 10242 bytes .../stream_compaction/efficient.cu | 8 +++++--- .../stream_compaction/naive.cu | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 Project2-Stream-Compaction/img/NaiveScanPseudoCode.PNG diff --git a/Project2-Stream-Compaction/img/NaiveScanPseudoCode.PNG b/Project2-Stream-Compaction/img/NaiveScanPseudoCode.PNG new file mode 100644 index 0000000000000000000000000000000000000000..e2861553511bd413e04e27933052d87059241a17 GIT binary patch literal 10242 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 > (numThreads, power, dev_inputArray); checkCUDAError("kernDownSweep failed!"); } - cudaMemcpy(odata, dev_inputArray, n * sizeof(int), cudaMemcpyDeviceToHost); - // TODO if (newTimer) { timer().endGpuTimer(); } + cudaMemcpy(odata, dev_inputArray, n * sizeof(int), cudaMemcpyDeviceToHost); + + // TODO + cudaFree(dev_inputArray); } @@ -202,12 +204,12 @@ namespace StreamCompaction { kernScatter << > > (numThreadsScatter, dev_final, dev_inputArray, dev_scanArray); + timer().endGpuTimer(); cudaMemcpy(odata, dev_final, length * sizeof(int), cudaMemcpyDeviceToHost); - timer().endGpuTimer(); cudaFree(dev_inputArray); cudaFree(dev_tempInOutArray); cudaFree(dev_scanArray); diff --git a/Project2-Stream-Compaction/stream_compaction/naive.cu b/Project2-Stream-Compaction/stream_compaction/naive.cu index 6409178..acb7e20 100644 --- a/Project2-Stream-Compaction/stream_compaction/naive.cu +++ b/Project2-Stream-Compaction/stream_compaction/naive.cu @@ -99,9 +99,9 @@ namespace StreamCompaction { kernShift<<>>(bufferLength * sizeof(int), dev_arrayB, dev_arrayA); checkCUDAError("kernShift failed!"); - cudaMemcpy(odata, dev_arrayA, n * sizeof(int), cudaMemcpyDeviceToHost); + timer().endGpuTimer(); - timer().endGpuTimer(); + cudaMemcpy(odata, dev_arrayA, n * sizeof(int), cudaMemcpyDeviceToHost); cudaFree(dev_arrayA); cudaFree(dev_arrayB); From cf90d24a2f5ef03d4592424683726766ff2d88f8 Mon Sep 17 00:00:00 2001 From: gracelgilbert Date: Tue, 17 Sep 2019 18:36:13 -0500 Subject: [PATCH 25/38] Update README.md --- 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 29cf78f..9572def 100644 --- a/Project2-Stream-Compaction/README.md +++ b/Project2-Stream-Compaction/README.md @@ -108,7 +108,7 @@ For each of the algorithms, I ran them with a block size of 128. In order for t #### Naive thread count In the naive algorithm, within the parallel portion of the algorithm, there is a check to see if the index is at least a certain value: -# INSERT IMAGE OF PSEUDOCODE +![](img/naiveScanPseudoCode.PNG) I realized that for all threads where the index was too small, the thread was simply returning without doing any work. To optimize this, I calculated exactly how many threads would actually end up doing work: ``` From 20a4bb0267c9e825ce347bc0978f20d9bdc0c5d9 Mon Sep 17 00:00:00 2001 From: gracelgilbert Date: Tue, 17 Sep 2019 18:36:49 -0500 Subject: [PATCH 26/38] Update README.md --- 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 9572def..b0cf7a0 100644 --- a/Project2-Stream-Compaction/README.md +++ b/Project2-Stream-Compaction/README.md @@ -108,7 +108,7 @@ For each of the algorithms, I ran them with a block size of 128. In order for t #### Naive thread count In the naive algorithm, within the parallel portion of the algorithm, there is a check to see if the index is at least a certain value: -![](img/naiveScanPseudoCode.PNG) +![](img/NaiveScanPseudoCode.PNG) I realized that for all threads where the index was too small, the thread was simply returning without doing any work. To optimize this, I calculated exactly how many threads would actually end up doing work: ``` From ced7274b1d647b79a411daa60460d8cb8c9de963 Mon Sep 17 00:00:00 2001 From: Grace Gilbert Date: Tue, 17 Sep 2019 19:26:18 -0500 Subject: [PATCH 27/38] added performance charts --- .../img/ScanPerformanceChart.PNG | Bin 0 -> 40030 bytes .../img/ScanPerformanceChartNonPower2.PNG | Bin 0 -> 38318 bytes .../ScanPerformanceChartNonPower2SmallSizes.PNG | Bin 0 -> 40486 bytes .../img/ScanPerformanceChartSmallSizes.PNG | Bin 0 -> 55556 bytes .../img/StreamCompactionPerformanceChart.PNG | Bin 0 -> 33625 bytes ...treamCompactionPerformanceChartNonPower2.PNG | Bin 0 -> 32047 bytes ...ctionPerformanceChartNonPower2SmallSizes.PNG | Bin 0 -> 29961 bytes ...reamCompactionPerformanceChartSmallSizes.PNG | Bin 0 -> 36854 bytes Project2-Stream-Compaction/src/main.cpp | 2 +- 9 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 Project2-Stream-Compaction/img/ScanPerformanceChart.PNG create mode 100644 Project2-Stream-Compaction/img/ScanPerformanceChartNonPower2.PNG create mode 100644 Project2-Stream-Compaction/img/ScanPerformanceChartNonPower2SmallSizes.PNG create mode 100644 Project2-Stream-Compaction/img/ScanPerformanceChartSmallSizes.PNG create mode 100644 Project2-Stream-Compaction/img/StreamCompactionPerformanceChart.PNG create mode 100644 Project2-Stream-Compaction/img/StreamCompactionPerformanceChartNonPower2.PNG create mode 100644 Project2-Stream-Compaction/img/StreamCompactionPerformanceChartNonPower2SmallSizes.PNG create mode 100644 Project2-Stream-Compaction/img/StreamCompactionPerformanceChartSmallSizes.PNG diff --git a/Project2-Stream-Compaction/img/ScanPerformanceChart.PNG b/Project2-Stream-Compaction/img/ScanPerformanceChart.PNG new file mode 100644 index 0000000000000000000000000000000000000000..c0721b61b0b63843289a635b59e85cec84cce802 GIT binary patch literal 40030 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

a|1Qr5hd3%!$i+m&-~s+H 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 LBfjSY3Hkp5F}sjm literal 0 HcmV?d00001 diff --git a/Project2-Stream-Compaction/img/ScanPerformanceChartNonPower2.PNG b/Project2-Stream-Compaction/img/ScanPerformanceChartNonPower2.PNG new file mode 100644 index 0000000000000000000000000000000000000000..d5f85b7621b13038194f9873245417f0bc94dc0f GIT binary patch literal 38318 zcmdqJXIPSL{5Nj5Ws_`|lB3L)sb#L*=3SOMN9Jz070H1EMZFu^GR>I-N3OIKGbe~v zmJ4$$2?Dn(L_`z?6dccmcK@C?|Cj$azo+BqAg-?Sy3X(VeCNq?3o}E|F7aJLLPDUM zH~zL15)vL068g(?hX`d?a<98o%oHIl)yea{`kG@-WEEoM|~4 zfoSh+bZ<9N>X0TT?n%6WyML_xzPr2o(6jXPuw!sIJVU36XPqOHVm~O{XTtqLFKG=6 z!k0{sc_Bk`rl%&hFMFGVE2Zvl2I%!380&zxQnyd`idtzwwadPtua{t&>ah<4SkG2raX4iiI%x5NpuZrM}r7H4c9C z*`wQt&tZ_*<_Zu()tfG)CaxiDp`f8*q~TrgYEm32jigoXvKH}(c4r=+w4uSc!s zC_@O!mw&tWdRnVO$q|fIy_Y(`BFy*umoD4fN1wAQY8xoAB2fYEpO?{h1j~qM@DQ0OT3u^b%)VSs!COo7u%I8PYAJ~a=#J8}NpIe*=eK1ow} zbLJ6#Z8@`qN48IA)A1Pn5$1Y@we5?k^}`YxK?!9JwHe|Z5*q{z9yc=G7cUuwr-GrxMBUZs>s3>MPEgeJEY*4@_AFX4srIKairfu){Y)--zg;u9yhG=#BWv-9(_;_%0A`1=T;}s0kiut$V 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 literal 0 HcmV?d00001 diff --git a/Project2-Stream-Compaction/img/ScanPerformanceChartSmallSizes.PNG b/Project2-Stream-Compaction/img/ScanPerformanceChartSmallSizes.PNG new file mode 100644 index 0000000000000000000000000000000000000000..7bf9170a08b89f79670a6aba9d47dbd06a852db0 GIT binary patch literal 55556 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 T#StqvLF8qWpA|fP>HmKKn3y7g literal 0 HcmV?d00001 diff --git a/Project2-Stream-Compaction/img/StreamCompactionPerformanceChart.PNG b/Project2-Stream-Compaction/img/StreamCompactionPerformanceChart.PNG new file mode 100644 index 0000000000000000000000000000000000000000..05c7db09d3443a1c2deb0026accb2d1da2790315 GIT binary patch literal 33625 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 acbT0?r5Azxrj;!4um88n^{Q)kp8OwC7n}$H literal 0 HcmV?d00001 diff --git a/Project2-Stream-Compaction/img/StreamCompactionPerformanceChartNonPower2.PNG b/Project2-Stream-Compaction/img/StreamCompactionPerformanceChartNonPower2.PNG new file mode 100644 index 0000000000000000000000000000000000000000..075d4cefd79382c4d64153dbab600129875ac8cc GIT binary patch literal 32047 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

A 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 literal 0 HcmV?d00001 diff --git a/Project2-Stream-Compaction/img/StreamCompactionPerformanceChartNonPower2SmallSizes.PNG b/Project2-Stream-Compaction/img/StreamCompactionPerformanceChartNonPower2SmallSizes.PNG new file mode 100644 index 0000000000000000000000000000000000000000..faf3af6b40ef601412aca869610191affa78a00a GIT binary patch literal 29961 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 P2;?+WwQd*Ov #include "testing_helpers.hpp" -const int SIZE = 1 << 20; // feel free to change the size of array +const int SIZE = 1 << 25; // 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]; From 0dcd7024cf28734de7ed06ca92d3b66b6d2bebc3 Mon Sep 17 00:00:00 2001 From: gracelgilbert Date: Tue, 17 Sep 2019 19:41:05 -0500 Subject: [PATCH 28/38] Update README.md --- Project2-Stream-Compaction/README.md | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Project2-Stream-Compaction/README.md b/Project2-Stream-Compaction/README.md index b0cf7a0..bce5c20 100644 --- a/Project2-Stream-Compaction/README.md +++ b/Project2-Stream-Compaction/README.md @@ -104,6 +104,38 @@ thrust::exclusive_scan(dev_thrust_inputArray, dev_thrust_inputArray + bufferLeng ## Performance Analysis and Questions For each of the algorithms, I ran them with a block size of 128. In order for the block size to be optimal, it must be a power of two. Most of the thread counts that we are sending are multiples or powers of two, so having a block size that is a power of two helps ensure that the blocks are filled to their capacity, making it most efficient. +I did perfomance analyes comparing the different implementations of scan and stream compaction on both arrays of size power of 2, and arrays of size non power of 2. The following charts show this analysis: + +### Scan +#### Power of 2 size +##### Full data +![](img/ScanPerformanceChart.PNG) +##### Close up on smaller sizes +![](img/ScanPerformanceChartSmallSizes.PNG) +#### Non-power of 2 size +##### Full data +![](img/ScanPerformanceChartNonPower2.PNG) +##### Close up on smaller sizes +![](img/ScanPerformanceChartNonPower2SmallSizes.PNG) + +### Stream Compaction +#### Power of 2 size +##### Full data +![](img/StreamCompactionPerformanceChart.PNG) +##### Close up on smaller sizes +![](img/StreamCompactionPerformanceChartSmallSizes.PNG) +#### Non-power of 2 size +##### Full data +![](img/StreamCompactionPerformanceChartNonPower2.PNG) +##### Close up on smaller sizes +![](img/StreamCompactionPerformanceChartNonPower2SmallSizes.PNG) + +The results of the performance data indicates that at smaller values, the CPU implementations run faster than either of the GPU implementations. For scan, the work efficient version is consistently faster than the naive version, even at smaller array sizes. However, once the array size reaches a large enough value, start at size roughly 2^20, the GPU versions begin to outpace the CPU versions, and the work efficient implementation more dramatically beats the naive implementation. + +The reason for the above phenomenon may be because the GPU implementations, while we did not count the buffer setup and final copying time in the performance analysis, still involves more overhead than the CPU implementation. Only once the array becomes large enough do the performance benefits outweigh the GPU overhead. This indicates that the performance bottleneck of the GPU is memory i/o, as this would be where the overhead is coming from. Because eventually the GPU beats the CPU implementations, we know that the overhead of the memory i/o is not increasing as quickly as the computation time of the CPU loops as the array becomes longer. + +Additionally, most of the implementations do not drastically differ between the power of 2 length array and the non power of 2 length array, except for the thrust implementation, which becomes less efficient when the array is not a power of 2. + ### Optimizations (extra credit) #### Naive thread count In the naive algorithm, within the parallel portion of the algorithm, there is a check to see if the index is at least a certain value: From 2c10cdba491c9e07eda545492df142321c7f616f Mon Sep 17 00:00:00 2001 From: Grace Gilbert Date: Tue, 17 Sep 2019 19:42:17 -0500 Subject: [PATCH 29/38] resetting array size in stream compaction to small value --- Project2-Stream-Compaction/src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project2-Stream-Compaction/src/main.cpp b/Project2-Stream-Compaction/src/main.cpp index 0598e5c..8e86b7a 100644 --- a/Project2-Stream-Compaction/src/main.cpp +++ b/Project2-Stream-Compaction/src/main.cpp @@ -13,7 +13,7 @@ #include #include "testing_helpers.hpp" -const int SIZE = 1 << 25; // feel free to change the size of array +const int SIZE = 1 << 3; // 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]; From ff533eda530fd33f851eab6791f8f01a8fcd5caa Mon Sep 17 00:00:00 2001 From: Grace Gilbert Date: Tue, 17 Sep 2019 20:24:21 -0500 Subject: [PATCH 30/38] cleaned up code so that user can run xor or character, training or testing --- .../character_recognition/mlp.cu | 88 ++++++- .../character_recognition/mlp.h | 6 +- Project2-Character-Recognition/src/main.cpp | 216 +++++++----------- 3 files changed, 169 insertions(+), 141 deletions(-) diff --git a/Project2-Character-Recognition/character_recognition/mlp.cu b/Project2-Character-Recognition/character_recognition/mlp.cu index f32efdb..7e40401 100644 --- a/Project2-Character-Recognition/character_recognition/mlp.cu +++ b/Project2-Character-Recognition/character_recognition/mlp.cu @@ -160,7 +160,6 @@ namespace CharacterRecognition { float *host_output; - float *host_hidden; // Malloc for buffers cudaMalloc((void**)&dev_inputData, inputSize * sizeof(float)); @@ -168,9 +167,6 @@ namespace CharacterRecognition { cudaMalloc((void**)&dev_hidden, numHiddenLayers * sizeof(float)); checkCUDAError("cudaMalloc dev_hidden failed!"); - - cudaMallocHost((void**)&host_hidden, numHiddenLayers * sizeof(float)); - checkCUDAError("cudaMallocHost host_hidden failed!"); cudaMalloc((void**)&dev_weights1, numWeights1 * sizeof(float)); checkCUDAError("cudaMalloc dev_weights1 failed!"); @@ -251,7 +247,6 @@ namespace CharacterRecognition { cudaFree(dev_partials2); cudaFree(dev_output); cudaFreeHost(host_output); - cudaFreeHost(host_hidden); return output; @@ -297,4 +292,87 @@ namespace CharacterRecognition { } + + + + float mlpNoError(int inputSize, int numHiddenLayers, float expectedValue, + const float *weights1, const float *weights2, + const float *idata) { + // size of input is 2 for xor and 512 by 512 for characters + // hidden layer somewhere between 1 and size of input + // first number of weights is size of hidden layer * size of input + // second number of weights is size of hidden layer * size of output(1) + + int numWeights1 = inputSize * numHiddenLayers; + int numWeights2 = numHiddenLayers; + + + // Initialize buffers + float *dev_inputData; + float *dev_hidden; + float *dev_weights1; + float *dev_weights2; + float *dev_output; + + float *dev_partials1; + float *dev_partials2; + + float *host_output; + + // Malloc for buffers + cudaMalloc((void**)&dev_inputData, inputSize * sizeof(float)); + checkCUDAError("cudaMalloc dev_inputData failed!"); + + cudaMalloc((void**)&dev_hidden, numHiddenLayers * sizeof(float)); + checkCUDAError("cudaMalloc dev_hidden failed!"); + + cudaMalloc((void**)&dev_weights1, numWeights1 * sizeof(float)); + checkCUDAError("cudaMalloc dev_weights1 failed!"); + + cudaMalloc((void**)&dev_weights2, numWeights2 * sizeof(float)); + checkCUDAError("cudaMalloc dev_weights2 failed!"); + + cudaMalloc((void**)&dev_output, sizeof(float)); + checkCUDAError("cudaMalloc dev_output failed!"); + + cudaMallocHost((void**)&host_output, sizeof(float)); + checkCUDAError("cudaMallocHost host_output failed!"); + + // Fille input and weights data + cudaMemcpy(dev_inputData, idata, inputSize * sizeof(float), cudaMemcpyHostToDevice); + cudaMemcpy(dev_weights1, weights1, numWeights1 * sizeof(float), cudaMemcpyHostToDevice); + cudaMemcpy(dev_weights2, weights2, numWeights2 * sizeof(float), cudaMemcpyHostToDevice); + + // Perform the multiplications for layer 1 to get the hidden layers + int numThreads = numHiddenLayers; + dim3 blocksPerGrid((numThreads + blockSize - 1) / blockSize); + + kernLayer1Mult << > > (numHiddenLayers, dev_hidden, inputSize, dev_inputData, dev_weights1); + + // perform the multiplications for layer 2 to get the output value + int layer2_numThreads = 1; + dim3 layer2_blocksPerGrid((layer2_numThreads + blockSize - 1) / blockSize); + + kernLayer2Mult << > > (1, numHiddenLayers, dev_output, dev_hidden, dev_weights2); + + // Copy the output onto the host + cudaMemcpy(host_output, dev_output, sizeof(float), cudaMemcpyDeviceToHost); + float output = host_output[0]; + + + + // Free buffer memory + cudaFree(dev_inputData); + cudaFree(dev_hidden); + cudaFree(dev_weights1); + cudaFree(dev_weights2); + cudaFree(dev_output); + cudaFreeHost(host_output); + + + return output; + + } + + } diff --git a/Project2-Character-Recognition/character_recognition/mlp.h b/Project2-Character-Recognition/character_recognition/mlp.h index cad98cb..10f1d8f 100644 --- a/Project2-Character-Recognition/character_recognition/mlp.h +++ b/Project2-Character-Recognition/character_recognition/mlp.h @@ -8,12 +8,14 @@ namespace CharacterRecognition { // TODO: implement required elements for MLP sections 1 and 2 here void fillRandomWeights(int n, float *data, float seed); - void updateWeights(int n, int *input, float *weights, const float *patialErrorDeriv, float error); - float mlp(int inputSize, int numHiddenLayers, float expectedValue, const float *weights1, const float *weights2, const float *idata, float *partialDerivatives1, float *partialDerivatives2); + float mlpNoError(int inputSize, int numHiddenLayers, float expectedValue, + const float *weights1, const float *weights2, + const float *idata); + void updateWeights(int numWeights, float accumulatedError, const float *partials, float *weights); diff --git a/Project2-Character-Recognition/src/main.cpp b/Project2-Character-Recognition/src/main.cpp index 591a6b3..ec97002 100644 --- a/Project2-Character-Recognition/src/main.cpp +++ b/Project2-Character-Recognition/src/main.cpp @@ -21,23 +21,29 @@ - // Define mode to training or running - // Define acceptedError 0.0001 -#define training 1 // If set to 1, indicates we are in training mode -#define acceptedError 0.01 -#define inputSize 10201 -#define numInputs 2 +// Define run presets +#define training 0 // If set to 1, indicates we are in training mode +#define acceptedError 0.001 +#define numRandomWeightAttempts 10 +#define numConvergeIterations 1000 +#define useXor 1 int main(int argc, char* argv[]) { - - if (training) { - // Fill in all data: - //std::vector inputArrays = std::vector(); - //std::map inputOutputMap = std::map(); - std::vector inputData = std::vector(); - std::vector expectedData = std::vector(); - /*float *data1 = new float[2]; + // Setup input data depending on xor or character data + int inputSize = 10201; + int numInputs = 52; + if (useXor) { + inputSize = 2; + numInputs = 4; + } + + // Fill in all data: + std::vector inputData = std::vector(); + std::vector expectedData = std::vector(); + + if (useXor) { + float *data1 = new float[2]; float *data2 = new float[2]; float *data3 = new float[2]; float *data4 = new float[2]; @@ -58,11 +64,11 @@ int main(int argc, char* argv[]) { expectedData.push_back(0); expectedData.push_back(1); expectedData.push_back(1); - expectedData.push_back(0);*/ - - + expectedData.push_back(0); + } - // Read in training data: + else { + // Read in character training data: for (int i = 1; i <= numInputs; ++i) { std::string filename = std::to_string(i) + "info.txt"; if (i < 10) { @@ -102,14 +108,10 @@ int main(int argc, char* argv[]) { } inputFile.close(); } - - - - /*for (std::pair i : inputOutputMap) { - std::cout << "(" << i.first[4] << ", " << i.first[5] << "): " << i.second << std::endl; - }*/ - + } + + if (training) { // Setup weights arrays: int numHiddenLayers = ceil((inputSize + 1) / 2.0); @@ -118,18 +120,17 @@ int main(int argc, char* argv[]) { float *layer1_weights = new float[layer1_numWeights]; float *layer2_weights = new float[layer2_numWeights]; - - /*float *layer1_adjustedWeights = new float[layer1_numWeights]; - float *layer2_adjustedWeights = new float[layer2_numWeights];*/ - - /*layer1_weights[0] = 10.1; - layer1_weights[1] = 0.9; - layer1_weights[2] = 20; - layer1_weights[3] = 0.87; - layer2_weights[0] = 41; - layer2_weights[1] = -54;*/ + if (useXor) { + layer1_weights[0] = 10.1; + layer1_weights[1] = 0.9; + layer1_weights[2] = 20; + layer1_weights[3] = 0.87; + + layer2_weights[0] = 41; + layer2_weights[1] = -54; + } std::vector partials1 = std::vector(); std::vector partials2 = std::vector(); @@ -139,24 +140,29 @@ int main(int argc, char* argv[]) { partials2.push_back(new float[layer2_numWeights]); } + // Begin loop iteration auto start = std::chrono::steady_clock::now(); int numRandIters = 0; float accumulatedError = 3.0; // Larger than accepted error bool done = false; - while (!done && numRandIters < 1000) { - // Fill new random weights - auto end1 = std::chrono::steady_clock::now(); - CharacterRecognition::fillRandomWeights(layer1_numWeights, layer1_weights, std::chrono::duration_cast(end1 - start).count()); - auto end2 = std::chrono::steady_clock::now(); - CharacterRecognition::fillRandomWeights(layer2_numWeights, layer2_weights, std::chrono::duration_cast(end2 - start).count()); - std::cout << "NEW WEIGHTS" << std::endl; + int numRandAttempts = useXor ? 1 : numRandomWeightAttempts; + while (!done && numRandIters < numRandAttempts) { + // Fill new random weights (if xor, use preset weights) + if (!useXor) { + auto end1 = std::chrono::steady_clock::now(); + CharacterRecognition::fillRandomWeights(layer1_numWeights, layer1_weights, std::chrono::duration_cast(end1 - start).count()); + auto end2 = std::chrono::steady_clock::now(); + CharacterRecognition::fillRandomWeights(layer2_numWeights, layer2_weights, std::chrono::duration_cast(end2 - start).count()); + std::cout << "NEW WEIGHTS" << std::endl; + } int numInnerIters = 0.0; // Try refining weights iteratively - while (!done && numInnerIters < 10000) { + while (!done && numInnerIters < numConvergeIterations) { accumulatedError = 0.0; bool resultAll1 = true; + // Run each input through mlp, accumulating error for (int k = 0; k < numInputs; ++k) { float currExpected = expectedData.at(k); float output = CharacterRecognition::mlp(inputSize, numHiddenLayers, currExpected, @@ -177,7 +183,7 @@ int main(int argc, char* argv[]) { if (accumulatedError < acceptedError) { done = true; } - + // Adjust weights based on accumulated error if (!done) { for (int k = 0; k < numInputs; ++k) { float* partialValues1 = partials1.at(k); @@ -188,16 +194,6 @@ int main(int argc, char* argv[]) { CharacterRecognition::updateWeights(layer2_numWeights, accumulatedError, partialValues2, layer2_weights); - - - /*for (int i = 0; i < layer1_numWeights; ++i) { - float delta = -(accumulatedError / 5.0) * partialValues1[i]; - layer1_weights[i] += delta; - } - for (int i = 0; i < layer2_numWeights; ++i) { - float delta = -(accumulatedError / 5.0) * partialValues2[i]; - layer2_weights[i] += delta; - }*/ } } if (done) { @@ -207,6 +203,7 @@ int main(int argc, char* argv[]) { } numRandIters++; } + // Print out final weights and error after either converging to good weights or failing to converge std::cout << "FINAL ERROR: " << accumulatedError << std::endl; std::cout << "WEIGHTS:" << std::endl; for (int i = 0; i < layer1_numWeights; ++i) { @@ -226,49 +223,10 @@ int main(int argc, char* argv[]) { for (float* i : partials2) { delete[] i; } + delete[] layer1_weights; + delete[] layer2_weights; } else { - - std::map inputOutputMap = std::map(); - // Read in data: - for (int i = 1; i <= numInputs; ++i) { - std::string filename = std::to_string(i) + "info.txt"; - if (i < 10) { - filename = "0" + filename; - } - filename = "../data-set/" + filename; - - std::ifstream inputFile; - inputFile.open(filename); - if (inputFile.is_open()) { - std::string firstLine; - getline(inputFile, firstLine); - int expectedVal = std::stoi(firstLine); - - std::string secondLine; - getline(inputFile, secondLine); - int inputLength = std::stoi(secondLine); - - float *currData = new float[inputLength]; - int counter = 0; - - std::string dataLine; - getline(inputFile, dataLine); - - std::stringstream stream(dataLine); - while (1) { - int n; - stream >> n; - if (!stream) { - break; - } - currData[counter] = n; - counter++; - } - inputOutputMap.insert(std::pair(currData, expectedVal)); - } - inputFile.close(); - } // Setup weights arrays: int numHiddenLayers = ceil((inputSize + 1) / 2.0); @@ -278,52 +236,42 @@ int main(int argc, char* argv[]) { float *layer1_weights = new float[layer1_numWeights]; float *layer2_weights = new float[layer2_numWeights]; - float *layer1_adjustedWeights = new float[layer1_numWeights]; - float *layer2_adjustedWeights = new float[layer2_numWeights]; - auto start = std::chrono::steady_clock::now(); + if (useXor) { + layer1_weights[0] = 10.1; + layer1_weights[1] = 0.9; + layer1_weights[2] = 20; + layer1_weights[3] = 0.87; - // Fill weights - auto end1 = std::chrono::steady_clock::now(); - CharacterRecognition::fillRandomWeights(layer1_numWeights, layer1_weights, std::chrono::duration_cast(end1 - start).count()); - auto end2 = std::chrono::steady_clock::now(); - CharacterRecognition::fillRandomWeights(layer2_numWeights, layer2_weights, std::chrono::duration_cast(end2 - start).count()); + layer2_weights[0] = 41; + layer2_weights[1] = -54; + } - for (std::pair i : inputOutputMap) { - float currExpected = i.second; - float output = CharacterRecognition::mlp(inputSize, numHiddenLayers, currExpected, layer1_weights, layer2_weights, i.first, layer1_adjustedWeights, layer2_adjustedWeights); - std::cout << "expected output: " << currExpected << " Result: " << output << std::endl; + auto start = std::chrono::steady_clock::now(); + // if not xor, use random weights because never found good weights + if (!useXor) { + auto end1 = std::chrono::steady_clock::now(); + CharacterRecognition::fillRandomWeights(layer1_numWeights, layer1_weights, std::chrono::duration_cast(end1 - start).count()); + auto end2 = std::chrono::steady_clock::now(); + CharacterRecognition::fillRandomWeights(layer2_numWeights, layer2_weights, std::chrono::duration_cast(end2 - start).count()); + std::cout << "NEW WEIGHTS" << std::endl; } + // Run input data through mlp to get output with given weights + for (int k = 0; k < numInputs; ++k) { + float currExpected = expectedData.at(k); + float output = CharacterRecognition::mlpNoError(inputSize, numHiddenLayers, currExpected, + layer1_weights, layer2_weights, inputData.at(k)); + // Print out results, rounding output to integer to match input type + std::cout << "expected output: " << currExpected << " Result: " << round(output) << std::endl; + } + // Delete data arrays stored in map - for (std::pair i : inputOutputMap) { - delete[] i.first; + for (float* i : inputData) { + delete[] i; } + delete[] layer1_weights; + delete[] layer2_weights; } - /* - // if training - - randomize weights (kernel function) - for 1 through 52, get the text numbers values into an array: - Read file data into an array of arrays for all inputs - - for every array in array of inputs, pass in to mlp as the input data - Accumulate all the error values - - while error is not within accepted error range. - - adjust the weights (adjust weights) - rerun mlp - get new error - - keep final weights - print out output weights, or write out to a txt file - - if not training - read weights from text file - read the input from the text file - run the mlp just to get the final result, no error calculation - */ - } From c242ad5d54212acb4238c340b7fe329ecbf65337 Mon Sep 17 00:00:00 2001 From: gracelgilbert Date: Tue, 17 Sep 2019 20:26:03 -0500 Subject: [PATCH 31/38] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b1eec40..53c2b94 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,10 @@ CUDA Number Algorithms **University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 2** * Grace Gilbert - * gracelgilbert@gmail.com + * gracelgilbert.com * Tested on: Windows 10, i9-9900K @ 3.60GHz 64GB, GeForce RTX 2080 40860MB [Stream Compaction](/Project2-Stream-Compaction/README.md) +[Character Recognition](/Project2-Character-Recognition/README.md) From 3f1f1be89146e6c2980c048269f18fb6ad501b02 Mon Sep 17 00:00:00 2001 From: gracelgilbert Date: Tue, 17 Sep 2019 20:26:14 -0500 Subject: [PATCH 32/38] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 53c2b94..a167dee 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ CUDA Number Algorithms * Tested on: Windows 10, i9-9900K @ 3.60GHz 64GB, GeForce RTX 2080 40860MB [Stream Compaction](/Project2-Stream-Compaction/README.md) + [Character Recognition](/Project2-Character-Recognition/README.md) From 9b9f9cb7ab1c3ad10494dac5b45b6c2edee66321 Mon Sep 17 00:00:00 2001 From: Grace Gilbert Date: Tue, 17 Sep 2019 21:17:31 -0500 Subject: [PATCH 33/38] added math for mlp --- Project2-Character-Recognition/img/Delta.PNG | Bin 0 -> 2638 bytes .../img/PartialDeriv.PNG | Bin 0 -> 1426 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Project2-Character-Recognition/img/Delta.PNG create mode 100644 Project2-Character-Recognition/img/PartialDeriv.PNG diff --git a/Project2-Character-Recognition/img/Delta.PNG b/Project2-Character-Recognition/img/Delta.PNG new file mode 100644 index 0000000000000000000000000000000000000000..0bed70a9046bb24b3bf8b7f7417f1b42f0af51d9 GIT binary patch literal 2638 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 literal 0 HcmV?d00001 diff --git a/Project2-Character-Recognition/img/PartialDeriv.PNG b/Project2-Character-Recognition/img/PartialDeriv.PNG new file mode 100644 index 0000000000000000000000000000000000000000..bc500421690e275c3c74f6d4fe6414ed0ddb3b8c GIT binary patch literal 1426 zcmV;D1#S9?P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1t&>FK~!i%?U`99 zZC@P6jn|Mo;Kj}G;DJcuHGBOgPqM~47VS#!-53$AP?Je=?>gp1KY~a0i zc6Q+5;XyqwoSvRSVqzjZJUmbjJ0BYJ_*F5K*f=;ifG=OZKtVwP^}K+hySuxoC7cfx ze|B~TU0q$&VrFP)2ox0+VP+8ep>@4YOY-?+4P+eUO zBO@d9uD9IW+<=vp6*xFJ5F1}0J3AYG{P;m^u5cE=xw#2iT3XQD+)O<$6crV*#{Bm7 zHpxuI#l=K84)~Ce5U8oCp?7eDvv@y0Klt|T8~KX-l$V!3XCNjpq}0{bVR?C(dH^OS zCU|Tva1xLC7xQycQWEv>5*{8-#)2S%gM-1y$cQHuj*X4+#6K4}iN`O5-Oe-_VK}w7 zw^Qpa7Z(>~0tyNWqMo-1num87}W)>i6yAuKG6WE(-?Sil#rudk;bUNFTD z4-ZpIG&qUJ+0okC8q(9#spoNGDJv_3>FH_ec?xHL zf|iyR7#|;prluw^F)@LJgamqrWDrR_7K8YA_qcIzc6Nr7lN0LseBKrFST$qYaR1x9 zOFWK)(a}+AeLfpA#>Sd7ic$RH^fs|9SuDN$h+kL5WO&vayyoSZDM zERCleW@cva=g%KvV-NWH`a(oR1eBJRlA7g5z$$)jZVnU_6nJ74!7PInVL{+;+S=MY zUpFQu1`G@gSgXiS`1|`mCxY16SeT!mC)fP2ipPhChK7F%S!C5N}S_j5%qGcyxhU0nqpJO~y4 z*`VO3Uc;jwwyY#|;ypb*pJ!qm@PgtUkt*>h8kY=D Date: Tue, 17 Sep 2019 21:18:30 -0500 Subject: [PATCH 34/38] Update README.md --- Project2-Character-Recognition/README.md | 55 +++++++++++++++++++++--- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/Project2-Character-Recognition/README.md b/Project2-Character-Recognition/README.md index 4503fac..89d0f8e 100644 --- a/Project2-Character-Recognition/README.md +++ b/Project2-Character-Recognition/README.md @@ -3,12 +3,55 @@ 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) +* Grace Gilbert + * gracelgilbert.com +* Tested on: Windows 10, i9-9900K @ 3.60GHz 64GB, GeForce RTX 2080 40860MB + +## Overview +In this project I implemented a multilayer perceptron (mlp), a simple neural network that is used for machine learning. There are two states of the project, the training state and the predicting state. In the training state, we are given a set of data inputs and their desired output values. In this case, the main data set is 52 English characters (upper and lower case), each with a .txt file containing gray scale values of an image of the character. We train the mlp using the .txt file data and the expected values, adjusting weights based on error calculations until they converge on good weights that match the output value with high consistency. Once we have these good weights, we can then use the mlp in the predicting state with these weights to predict the output value of a .txt file data without knowing it's desired output in advance. + +### Project Run Options +At the top of main.cpp, there are multiple setting that the user can adjust. +- The first, training, indicates if we are in the training (1) or predicting state (0). +- Next is the accepted error, which determines what error value is low enough that we can consider the weights that achieved that error good enough weights. The lower this value is, the longer it will take for the weights to converge, but the more accurate the predictions will be. +- Next is the number of random weight attempts. To find good weights, we start with randomly assigned weights. We then see if these weights converge. If they do not converge after a certain number of iterations of training on the input data, then we reset and try new random weights. The number of random weight attempts determines how many times we will reset and try new weights before giving up on convergence. +- Related is the number of convergence iterations. When testing certain weights, we repeatedly run the data through the mlp network and refine the weights until the error is low enough. This number determines how many times we will refine the weights before giving up on this set of weights and resetting. +- The final option is the use xor value. I had some trouble training the character recognition data, but was successfully able to get weights for the simpler xor example to work, where the input is a pair of 0's and 1's, and the output is the xor of the pair. If this value is set to 1, the training and prediction will run on the xor example rather than the character data. If set to 0, it will read the character data from the text files and run training and prediction on that. + +## Training +The overall training pipeline is to generate a set of random weights, run each input data through the mlp network, and iteratively adjust the weights based on the output and error returned from the mlp network until the error is sufficiently low. It is not guaranteed that all sets of initial weight guesses will converge to good final weight values, so after some number of iterations of testing the input data and adjusting weights, we reset and make new random weight guesses. If after many resets of the guesses, we still haven't gotten convergence, we stop to avoid an infinitely running program. Once the converged weights are found, we output these weights. +### Random Weight Generation +The random weights are generated on the GPU, as the weights do not depend on each other, so can be generated in parallel. For the random seed, I take the elapsed time between the start of training and the time at which the weights are generated, so that each set of random weights has a different seed. +### MLP Network +The mlp network takes in out piece of input data at a time. This input data is make up of a certain number of values. In the case of the xor data, the data is made up of two numbers, whereas in the case of the character data, the input is made of up 10201 numbers, 101 by 101 numbers. There is then a second layer of values called the hidden layer. The number of hidden layers is somewhere between 1 and the number of input numbers. For each pair of input numbers and hidden layer numbers, there is a corresponding. predetermined weight. The value of the hidden layer values is found by taking a dot product between the input values and their weights corresponding to that hidden layer index. That dot product value is then put into an equation to find the hidden layer value: + +``` +hidden layer value = 1/(1 + exp(-dot product value)) +``` + +This completes the first layer of the mlp. Each hidden layer value also has a weight. The final layer is just one value, as this will be our output. To find that final value, we fist sum the products of each hidden layer value and its weight. Then we perform the same operation described above on this product we just found. This is our output. If we were in the prediction state, this would be the conclusion of the mlp, as we would return this output as the prediction based on the given weights. + +Below is a diagram illustrating the layers and weights in the mlp network: + +![](img/MLP.png) + +### Error propagation +In training mode, we want to use the mlp output to calculate error and adjust the weights to lower error, a process called error propagation. To calculate the error of an indivual data input, we find: +``` +error = (output - expected value)^2 +``` +Additionally, we calculate the partial derivative of the individual data value's error over the weight for each weight: + +![](img/PartialDeriv.PNG) + +We then take the accumulation of all of the error values from each input's mlp run. Using this value, combined with the partial derivative mentioned above, we come out with a delta that gets added to each weight: + +![](img/Delta.PNG) + +In my implementation, I run the mlp on each piece of input data, where I calculate the output, the error, and the partial derivatives for each weight. During this iteratation of all the data, I accumulate the total error, which I then use to calculate the delta values that I then add these to the weights, modifying the weights for the next iteration over the data. If the total accumulated error is low enough, no more error propagation is needed, as the weights are sufficiently converged. + +## Predicting + -### (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.) From b22275c9208f66bc47d6de1db1efd4e2266a2a40 Mon Sep 17 00:00:00 2001 From: gracelgilbert Date: Tue, 17 Sep 2019 21:23:27 -0500 Subject: [PATCH 35/38] Update README.md --- Project2-Character-Recognition/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Project2-Character-Recognition/README.md b/Project2-Character-Recognition/README.md index 89d0f8e..2f677f6 100644 --- a/Project2-Character-Recognition/README.md +++ b/Project2-Character-Recognition/README.md @@ -40,7 +40,9 @@ In training mode, we want to use the mlp output to calculate error and adjust th ``` error = (output - expected value)^2 ``` -Additionally, we calculate the partial derivative of the individual data value's error over the weight for each weight: +The expected value is a numerical value representing what the input data's correct value is. For the xor example, it is 1 for true, 0 for false, and for the characters, the characters are number 1 through 52. + +Next, we calculate the partial derivative of the individual data value's error over the weight for each weight: ![](img/PartialDeriv.PNG) @@ -51,7 +53,7 @@ We then take the accumulation of all of the error values from each input's mlp r In my implementation, I run the mlp on each piece of input data, where I calculate the output, the error, and the partial derivatives for each weight. During this iteratation of all the data, I accumulate the total error, which I then use to calculate the delta values that I then add these to the weights, modifying the weights for the next iteration over the data. If the total accumulated error is low enough, no more error propagation is needed, as the weights are sufficiently converged. ## Predicting - +In the predicting phase, there are no iterative loops of running the mlp. Instead, we assign predetermined weights, ideally weights that have been found as good predictors in training. We then run the mlp on the input data with these weights, and just find the output, no error propagation. This output becomes our prediction. In both the xor and character data, the expected values are all discrete integers, so when outputting the prediction, I round the mlp result to the nearest integer. In my network, I was unable to find converged weights for the character data, so the predictor will almost always output all 0's, not close to the expected values ranging from 1 to 52. However for the xor data, given the weights I found that converged, the predictor is accurate on all inputs. From abff22d753b47322af0e3dae7416b3402f8eefee Mon Sep 17 00:00:00 2001 From: Grace Gilbert Date: Tue, 17 Sep 2019 21:25:33 -0500 Subject: [PATCH 36/38] adjusted prediction weights to be more converged weights --- Project2-Character-Recognition/src/main.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Project2-Character-Recognition/src/main.cpp b/Project2-Character-Recognition/src/main.cpp index ec97002..4320cd5 100644 --- a/Project2-Character-Recognition/src/main.cpp +++ b/Project2-Character-Recognition/src/main.cpp @@ -238,13 +238,13 @@ int main(int argc, char* argv[]) { if (useXor) { - layer1_weights[0] = 10.1; - layer1_weights[1] = 0.9; - layer1_weights[2] = 20; - layer1_weights[3] = 0.87; + layer1_weights[0] = 10.1302; + layer1_weights[1] = 0.854664; + layer1_weights[2] = 20.0219; + layer1_weights[3] = 0.837066; - layer2_weights[0] = 41; - layer2_weights[1] = -54; + layer2_weights[0] = 41.009; + layer2_weights[1] = -53.9937; } auto start = std::chrono::steady_clock::now(); From 51f44bfe4f0157511033130245b5ba50918bf0e0 Mon Sep 17 00:00:00 2001 From: gracelgilbert Date: Tue, 17 Sep 2019 21:37:49 -0500 Subject: [PATCH 37/38] Update README.md --- Project2-Character-Recognition/README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Project2-Character-Recognition/README.md b/Project2-Character-Recognition/README.md index 2f677f6..2c5bf83 100644 --- a/Project2-Character-Recognition/README.md +++ b/Project2-Character-Recognition/README.md @@ -53,7 +53,12 @@ We then take the accumulation of all of the error values from each input's mlp r In my implementation, I run the mlp on each piece of input data, where I calculate the output, the error, and the partial derivatives for each weight. During this iteratation of all the data, I accumulate the total error, which I then use to calculate the delta values that I then add these to the weights, modifying the weights for the next iteration over the data. If the total accumulated error is low enough, no more error propagation is needed, as the weights are sufficiently converged. ## Predicting -In the predicting phase, there are no iterative loops of running the mlp. Instead, we assign predetermined weights, ideally weights that have been found as good predictors in training. We then run the mlp on the input data with these weights, and just find the output, no error propagation. This output becomes our prediction. In both the xor and character data, the expected values are all discrete integers, so when outputting the prediction, I round the mlp result to the nearest integer. In my network, I was unable to find converged weights for the character data, so the predictor will almost always output all 0's, not close to the expected values ranging from 1 to 52. However for the xor data, given the weights I found that converged, the predictor is accurate on all inputs. +In the predicting phase, there are no iterative loops of running the mlp. Instead, we assign predetermined weights, ideally weights that have been found as good predictors in training. We then run the mlp on the input data with these weights, and just find the output, no error propagation. This output becomes our prediction. In both the xor and character data, the expected values are all discrete integers, so when outputting the prediction, I round the mlp result to the nearest integer. In my network, I was unable to find converged weights for the character data, so the predictor will almost always output all 0's, not close to the expected values ranging from 1 to 52. However for the xor data, given the weights I found that converged, the predictor is accurate on all inputs. + +## Challenges +I ran into multiple challenges working on this project. Initially, when calculating the error propagation, instead of using the total accumulated error over all inputs to find the delta value for the weights, I was finding an error value per input. This threw off my weight adjustments, so my weights were never converging. Once I caught this, I reorganized my code to output the partial derivatives and output value for each input, and then after accumulating all of the error, finally calculate the delta. + +Another challenge I faced and was unable to fix was that once I changed the data to the character data, I found that it never converged and all of the data points kept outputting the same value even though they contained different data and different expected values. There may have been an error in how I was reading in and passing along the larger data sets. Another possibility is that I did not let the weights converge long enough. Once I increased the data to be 10201 values per input, and 52 inputs, the mlp loops ran significantly slower and I ran out of time to let them run long enough to potentially converge. I am somewhat skeptical that they would have converged, however, as it seemed incorrect that they would all output the same value on every iteration. Due to this challenge, I was unable to find good weights for the character data, so my prediction for that data set is almost meaningless, just using random weights on a potentially buggy input data set. From cdf83ff2dfd6728331a3c58541d871b1031f2c91 Mon Sep 17 00:00:00 2001 From: Grace Gilbert Date: Tue, 17 Sep 2019 21:38:34 -0500 Subject: [PATCH 38/38] training turned on --- Project2-Character-Recognition/src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project2-Character-Recognition/src/main.cpp b/Project2-Character-Recognition/src/main.cpp index 4320cd5..2dd498f 100644 --- a/Project2-Character-Recognition/src/main.cpp +++ b/Project2-Character-Recognition/src/main.cpp @@ -22,7 +22,7 @@ // Define run presets -#define training 0 // If set to 1, indicates we are in training mode +#define training 1 // If set to 1, indicates we are in training mode #define acceptedError 0.001 #define numRandomWeightAttempts 10 #define numConvergeIterations 1000