[PR]
[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。
プログラミング、3DCGとその他いろいろについて
[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。
Deep Learningに使うことがある、制限(制約)付きボルツマンマシン(Restricted Boltzmann Machine : RBM)をまた実装してみました。
ただし今度はGPUで動きます。
といってもあまり考えずにプログラムしたのでGPGPUにしては遅い気がします。
パフォーマンスチューニングはこれからして行きましょう。
今回は5つのファイルからなります:
CPU側はC#で動き、GPU側はOpenCLで動きます。
このクラスはあまり変わっていません。
やることといえばやはり制限付きボルツマンマシンにデータを与えて学習させ、あとでヒントのデータを与えて何を学習したか思い出させるといったところです。
かわったところは、パフォーマンスを計測しているというところです。
せっかくGPUを使っていますからどれだけ速くなったか気になりますよね。
あと、学習の繰り返し回数を10倍にしました。
より時間がかかるようにして、パフォーマンスの計測をやりやすくしました。
using System; namespace DeepLearning.RealValue.Gpu { class Program { static void Main() { float[][] trainingDataList = { new float[]{1.0f, 0.5f, 1.0f, 0.0f, 0.0f}, new float[]{0.0f, 0.0f, 1.0f, 0.5f, 1.0f}, new float[]{0.0f, 1.0f, 0.5f, 1.0f, 0.0f} }; var hiddenNeuronCount = 3; var visibleNeuronCount = trainingDataList[0].Length; var restrictedBoltzmannMachine = new RestrictedBoltzmannMachine( visibleNeuronCount, hiddenNeuronCount, new Random(0) ); var trainingEpochCount = 10000; var basicLearningRate = 0.3f; System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); stopwatch.Start(); // train restrictedBoltzmannMachine.LearnFromData( trainingDataList, basicLearningRate / trainingDataList.Length, trainingEpochCount ); stopwatch.Stop(); Console.WriteLine("calculation : " + stopwatch.Elapsed.TotalMilliseconds + "[ms]"); float[][] testDataList = { new float[]{1, 1, 0, 0, 0}, new float[]{0, 0, 0, 1, 1}, new float[]{0, 1, 1, 1, 0} }; stopwatch.Restart(); foreach (var input in testDataList) { var stopwatch2 = new System.Diagnostics.Stopwatch(); stopwatch2.Start(); restrictedBoltzmannMachine.SetVisibleNeuronValues(input); stopwatch2.Stop(); Console.WriteLine("set data to gpu : " + stopwatch2.Elapsed.TotalMilliseconds + "[ms]"); restrictedBoltzmannMachine.Associate(); foreach (var output in restrictedBoltzmannMachine.GetVisibleNeuronValues()) { Console.Write("{0:F2}\t", output); } Console.WriteLine(); } stopwatch.Stop(); Console.WriteLine("get results from gpu : " + stopwatch.Elapsed.TotalMilliseconds + "[ms]"); } } }
今回一番変わったクラスです。
といっても変わったのは実装の仕方だけで、インターフェースは極力変わらないようにしました。
以前はCPUで動くよう実装していましたが、今回はGPUで動くよう実装しました。
本当はもっとこう中身を抽象化して隠蔽すべきなのかもしれませんが、逆にわけがわからなくなる危険性もあったのでそのままです。
OpenCLのオブジェクトはIDisposableなので本当はこれらをDispose()しなくてはいけませんが、まあテストバージョンということでやっていません。
あとでやります。
あ、いくつかのオブジェクトはstaticでもいい気がしますね。
(でもstaticな変数はグローバル変数を連想して嫌なのです。今回は実害無さそうですが)
using System; using System.Linq; using Cloo; namespace DeepLearning.RealValue.Gpu { public class RestrictedBoltzmannMachine { // you should dispose these objects... private ComputeProgram program; private ComputeCommandQueue commandQueue; private ComputeContext context; private ComputeBuffer<float> weights; private ComputeBuffer<float> deltaWeights; private ComputeKernel learnConnectionKernel; private ComputeKernel endConnectionLearningKernel; private ComputeBuffer<float> visibleNeuronBiases; private ComputeBuffer<float> visibleNeuronValues; private ComputeBuffer<float> visibleNeuronDeltaBiases; private ComputeKernel updateVisibleNeuronKernel; private ComputeKernel learnAsVisibleKernel; private ComputeKernel endNeuronLearningKernel; private ComputeBuffer<float> hiddenNeuronBiases; private ComputeBuffer<float> hiddenNeuronValues; private ComputeBuffer<float> hiddenNeuronProbabilities; private ComputeBuffer<Xorshift128Random> hiddenNeuronRandoms; private ComputeBuffer<float> hiddenNeuronDeltaBiases; private ComputeKernel updateHiddenNeuronKernel; private ComputeKernel learnAsHiddenKernel; public RestrictedBoltzmannMachine(float[][] weights, float[] visibleBiases, float[] hiddenBiases, Random random) { ComputePlatform platform = ComputePlatform.Platforms .First(p => p.Devices.Any(d => d.Type == ComputeDeviceTypes.Gpu)); ComputeDevice[] devices = platform .Devices .Where(d => d.Type == ComputeDeviceTypes.Gpu) .ToArray(); context = new ComputeContext( devices, new ComputeContextPropertyList(platform), null, System.IntPtr.Zero ); commandQueue = new ComputeCommandQueue( context, devices[0], ComputeCommandQueueFlags.None ); program = new ComputeProgram( context, System.IO.File.ReadAllText("restrictedBoltzmannMachineFunctions.cl") ); try { program.Build(devices, null, null, System.IntPtr.Zero); } catch { throw new Exception(program.GetBuildLog(devices[0])); } // Connections. this.weights = new ComputeBuffer<float>( context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, weights.SelectMany(w => w).ToArray() ); this.deltaWeights = new ComputeBuffer<float>( context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, Enumerable .Range(0, (int)this.weights.Count) .Select(i => 0f) .ToArray() ); learnConnectionKernel = program.CreateKernel("learnConnection"); endConnectionLearningKernel = program.CreateKernel("endConnectionLearning"); // neurons endNeuronLearningKernel = program.CreateKernel("endNeuronLearning"); // Visible Neurons. visibleNeuronBiases = new ComputeBuffer<float>( context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, visibleBiases ); visibleNeuronDeltaBiases = new ComputeBuffer<float>( context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, Enumerable.Range(0, visibleBiases.Length).Select(i => 0f).ToArray() ); visibleNeuronValues = new ComputeBuffer<float>( context, ComputeMemoryFlags.ReadWrite, visibleBiases.Length ); updateVisibleNeuronKernel = program.CreateKernel("updateVisibleNeuron"); learnAsVisibleKernel = program.CreateKernel("learnAsVisible"); // Hidden Neurons. hiddenNeuronBiases = new ComputeBuffer<float>( context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, hiddenBiases ); hiddenNeuronDeltaBiases = new ComputeBuffer<float>( context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, Enumerable.Range(0, hiddenBiases.Length).Select(i => 0f).ToArray() ); hiddenNeuronValues = new ComputeBuffer<float>( context, ComputeMemoryFlags.ReadWrite, hiddenBiases.Length ); hiddenNeuronProbabilities = new ComputeBuffer<float>( context, ComputeMemoryFlags.ReadWrite, hiddenBiases.Length ); hiddenNeuronRandoms = new ComputeBuffer<Xorshift128Random>( context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, Enumerable .Range(0, hiddenBiases.Length) .Select(i => new Xorshift128Random(random.Next())).ToArray() ); updateHiddenNeuronKernel = program.CreateKernel("updateHiddenNeuron"); learnAsHiddenKernel = program.CreateKernel("learnAsHidden"); } public RestrictedBoltzmannMachine(int visibleNeuronCount, int hiddenNeuronCount,Random random) :this( SymmetricConnection.CreateRandomWeights(random, visibleNeuronCount, hiddenNeuronCount), new float[visibleNeuronCount], new float[hiddenNeuronCount], random) { } public void SetVisibleNeuronValues(float[] input) { commandQueue.WriteToBuffer( input, visibleNeuronValues, true, null ); } public float[] GetVisibleNeuronValues() { return Read(visibleNeuronValues); } private T[] Read<T>(ComputeBuffer<T> buffer) where T : struct{ var result = new T[buffer.Count]; commandQueue.ReadFromBuffer(buffer, ref result, true, null); return result; } public void LearnFromData( float[][] trainingData, float learningRate, int trainingEpochCount, int freeAssociationStepCount = 1) { ComputeBuffer<float> trainingDataBuffer = new ComputeBuffer<float>( context, ComputeMemoryFlags.ReadOnly | ComputeMemoryFlags.CopyHostPointer, trainingData.SelectMany(data => data).ToArray() ); for (int epoch = 0; epoch < trainingEpochCount; epoch++) { for(int dataIndex = 0; dataIndex < trainingData.Length;dataIndex++) { commandQueue.CopyBuffer( trainingDataBuffer, visibleNeuronValues, dataIndex * trainingData[dataIndex].Length, 0, visibleNeuronValues.Count, null ); wake(learningRate); sleep(learningRate, freeAssociationStepCount); endLearning(); } } trainingDataBuffer.Dispose(); } private void wake(float learningRate) { UpdateHiddenNeurons(); learn(learningRate); } public void UpdateVisibleNeurons() { updateVisibleNeuronKernel.SetMemoryArgument(0, visibleNeuronValues); updateVisibleNeuronKernel.SetMemoryArgument(1, visibleNeuronBiases); updateVisibleNeuronKernel.SetMemoryArgument(2, hiddenNeuronValues); updateVisibleNeuronKernel.SetMemoryArgument(3, weights); updateVisibleNeuronKernel.SetValueArgument(4, (int)hiddenNeuronValues.Count); commandQueue.Execute( updateVisibleNeuronKernel, null, new long[] { visibleNeuronValues.Count }, new long[] { 1 }, null); } public void UpdateHiddenNeurons() { updateHiddenNeuronKernel.SetMemoryArgument(0, hiddenNeuronValues); updateHiddenNeuronKernel.SetMemoryArgument(1, hiddenNeuronProbabilities); updateHiddenNeuronKernel.SetMemoryArgument(2, hiddenNeuronBiases); updateHiddenNeuronKernel.SetMemoryArgument(3, hiddenNeuronRandoms); updateHiddenNeuronKernel.SetMemoryArgument(4, visibleNeuronValues); updateHiddenNeuronKernel.SetMemoryArgument(5, weights); updateHiddenNeuronKernel.SetValueArgument(6, (int)visibleNeuronValues.Count); updateHiddenNeuronKernel.SetValueArgument(7, (int)hiddenNeuronValues.Count); commandQueue.Execute( updateHiddenNeuronKernel, null, new long[] { hiddenNeuronValues.Count }, new long[] { 1 }, null); } private void learn(float learningRate) { learnConnectionKernel.SetMemoryArgument(0, deltaWeights); learnConnectionKernel.SetMemoryArgument(1, hiddenNeuronProbabilities); learnConnectionKernel.SetMemoryArgument(2, visibleNeuronValues); learnConnectionKernel.SetValueArgument(3, learningRate); commandQueue.Execute( learnConnectionKernel, null, new[] { visibleNeuronValues.Count, hiddenNeuronValues.Count }, new long[] { 1, 1 }, null ); learnAsVisibleKernel.SetMemoryArgument(0, visibleNeuronDeltaBiases); learnAsVisibleKernel.SetMemoryArgument(1, visibleNeuronValues); learnAsVisibleKernel.SetValueArgument(2, learningRate); commandQueue.Execute( learnAsVisibleKernel, null, new[] { visibleNeuronValues.Count }, new long[] { 1 }, null ); learnAsHiddenKernel.SetMemoryArgument(0, hiddenNeuronDeltaBiases); learnAsHiddenKernel.SetMemoryArgument(1, hiddenNeuronProbabilities); learnAsHiddenKernel.SetValueArgument(2, learningRate); commandQueue.Execute( learnAsHiddenKernel, null, new[] { hiddenNeuronValues.Count }, new long[] { 1 }, null ); } private void sleep(float learningRate, int freeAssociationStepCount) { doFreeAssociation(freeAssociationStepCount); learn(-learningRate); } //Gibbs sampling private void doFreeAssociation(int freeAssociationStepCount) { for (int step = 0; step < freeAssociationStepCount; step++) { UpdateVisibleNeurons(); UpdateHiddenNeurons(); } } private void endLearning() { endConnectionLearningKernel.SetMemoryArgument(0, deltaWeights); endConnectionLearningKernel.SetMemoryArgument(1, weights); commandQueue.Execute( endConnectionLearningKernel, null, new[] { weights.Count }, new long[] { 1 }, null ); endNeuronLearningKernel.SetMemoryArgument(0, visibleNeuronBiases); endNeuronLearningKernel.SetMemoryArgument(1, visibleNeuronDeltaBiases); commandQueue.Execute( endNeuronLearningKernel, null, new[] { visibleNeuronBiases.Count }, new long[] { 1 }, null ); endNeuronLearningKernel.SetMemoryArgument(0, hiddenNeuronBiases); endNeuronLearningKernel.SetMemoryArgument(1, hiddenNeuronDeltaBiases); commandQueue.Execute( endNeuronLearningKernel, null, new[] { hiddenNeuronBiases.Count }, new long[] { 1 }, null ); } public void Associate() { UpdateHiddenNeurons(); UpdateVisibleNeurons(); } } }
何の変哲もない、ただニューラルネットワークの結合の重みデータを生成するだけのクラスです。
やり方は以前と変わっていません。
using System; using System.Linq; namespace DeepLearning.RealValue.Gpu { class SymmetricConnection { public static float[][] CreateRandomWeights(Random random, int visibleNeuronCount, int hiddenNeuronCount) { float minMax = 1f / visibleNeuronCount; return Enumerable .Range(0, visibleNeuronCount) .Select(v => Enumerable.Range(0, hiddenNeuronCount) .Select(h => uniform((float)random.NextDouble(), -minMax, minMax)) .ToArray()) .ToArray(); } private static float uniform(float normalizedValue, float min, float max) { return normalizedValue * (max - min) + min; } } }
疑似乱数を司る構造体です。
といっても本体はGPU側で動くので、CPU側のここはコンストラクタだけです。
本体はrestrictedBoltzmannMachineFunctions.cl内にあります。
namespace DeepLearning.RealValue.Gpu { struct Xorshift128Random { public int w, x, y, z; public Xorshift128Random(int seed) { if (seed == 0) { seed += 11; } w = seed; x = seed << 16 + seed >> 16; y = w + x; z = x ^ y; } } }
今回新しく登場したファイルです。
制限付きボルツマンマシンがGPUでどのように動くのか記述したOpenCL Cのコードです。
正直あまり読みたいコードではありませんね。
typedef struct
{
int w;
int x;
int y;
int z;
} Xorshift128Random;
int next(Xorshift128Random* random)
{
int t = (random->x ^ (random->x << 11));
random->x = random->y;
random->y = random->z;
random->z = random->w;
random->w = (random->w = (random->w ^ (random->w >> 19)) ^ (t ^ (t >> 8)));
return random->w;
}
float nextFloat(Xorshift128Random* random)
{
return ((float)next(random) / INT_MAX);
}
__kernel void updateRandom(
__global float *resultBuffer,
__global Xorshift128Random *randomGeneratorBuffer)
{
Xorshift128Random random = randomGeneratorBuffer[get_global_id(0)];
resultBuffer[get_global_id(0)] = nextFloat(&random);
randomGeneratorBuffer[get_global_id(0)] = random;
}
float sigmoid(float x)
{
return 1.0f / (1.0f + exp(-x));
}
// update
__kernel void updateVisibleNeuron(
__global float *visibleNeuronValues,
__global float *visibleNeuronBiases,
__global float *hiddenNeuronValues,
__global float *weights,
int hiddenNeuronCount
)
{
int visibleNeuronIndex = get_global_id(0);
float sum = 0;
for(int hiddenNeuronIndex = 0; hiddenNeuronIndex < hiddenNeuronCount; hiddenNeuronIndex++)
{
sum += weights[visibleNeuronIndex * hiddenNeuronCount + hiddenNeuronIndex]
* hiddenNeuronValues[hiddenNeuronIndex];
}
visibleNeuronValues[visibleNeuronIndex] = sigmoid(sum + visibleNeuronBiases[visibleNeuronIndex]);
}
float nextFloatFromRandoms(__global Xorshift128Random *randoms, int index)
{
Xorshift128Random random = randoms[index];
float result = nextFloat(&random);
randoms[index] = random;
return result;
}
int nextBool(__global Xorshift128Random *randoms, int index, float probability)
{
return nextFloatFromRandoms(randoms, index) < probability;
}
__kernel void updateHiddenNeuron(
__global float *hiddenNeuronValues,
__global float *hiddenNeuronProbabilities,
__global float *hiddenNeuronBiases,
__global Xorshift128Random *hiddenNeuronRandoms,
__global float *visibleNeuronValues,
__global float *weights,
int visibleNeuronCount,
int hiddenNeuronCount
)
{
int hiddenNeuronIndex = get_global_id(0);
float sum = 0;
for(int visibleNeuronIndex = 0; visibleNeuronIndex < visibleNeuronCount; visibleNeuronIndex++)
{
sum += weights[visibleNeuronIndex * hiddenNeuronCount + hiddenNeuronIndex]
* visibleNeuronValues[visibleNeuronIndex];
}
float probability = sigmoid(sum + hiddenNeuronBiases[hiddenNeuronIndex]);
hiddenNeuronProbabilities[hiddenNeuronIndex] = probability;
hiddenNeuronValues[hiddenNeuronIndex]
= nextBool(hiddenNeuronRandoms, hiddenNeuronIndex, probability) ? 1 : 0;
}
// learning
__kernel void learnConnection(
__global float *deltaWeights,
__global float *hiddenNeuronProbabilities,
__global float *visibleNeuronValues,
float learningRate
)
{
int visibleNeuronIndex = get_global_id(0);
int hiddenNeuronIndex = get_global_id(1);
int hiddenNeuronCount = get_global_size(1);
deltaWeights[visibleNeuronIndex * hiddenNeuronCount + hiddenNeuronIndex] +=
learningRate
* visibleNeuronValues[visibleNeuronIndex]
* hiddenNeuronProbabilities[hiddenNeuronIndex];
}
__kernel void learnAsVisible(
__global float *visibleNeuronDeltaBiases,
__global float *visibleNeuronValues,
float learningRate
)
{
int visibleNeuronIndex = get_global_id(0);
visibleNeuronDeltaBiases[visibleNeuronIndex]
+= learningRate * visibleNeuronValues[visibleNeuronIndex];
}
__kernel void learnAsHidden(
__global float *hiddenNeuronDeltaBiases,
__global float *hiddenNeuronProbabilities,
float learningRate
)
{
int hiddenNeuronIndex = get_global_id(0);
hiddenNeuronDeltaBiases[hiddenNeuronIndex]
+= learningRate * hiddenNeuronProbabilities[hiddenNeuronIndex];
}
// end learning
__kernel void endConnectionLearning(
__global float *deltaWeights,
__global float *weights
)
{
int index = get_global_id(0);
weights[index] += deltaWeights[index];
deltaWeights[index] = 0;
}
__kernel void endNeuronLearning(
__global float *neuronBiases,
__global float *neuronDeltaBiases
)
{
int index = get_global_id(0);
neuronBiases[index] += neuronDeltaBiases[index];
neuronDeltaBiases[index] = 0;
}
実行結果は次のようになりました。
学習の結果及び、パフォーマンスを表示しています。
calculation : 5275.8573[ms] set data to gpu : 7683.175[ms] 1.00 0.50 1.00 0.00 0.00 set data to gpu : 0.5679[ms] 0.00 0.00 1.00 0.50 1.00 set data to gpu : 0.3452[ms] 0.00 1.00 0.50 1.00 0.00 get results from gpu : 7699.836[ms]
学習結果は申し分ないですね。
完全に学習データを思い出しています。
まあ過学習という言葉もありますが。
パフォーマンスはどうでしょう?
学習に5275ミリ秒(約5秒)かかっています。
私の環境ではCPUだけで動くよう実装したバージョンでは10032ミリ秒(約10秒)かかりました。
速度は約2倍になっていますから、まあ悪くはありません。
[追記]:実はこれ速くなっていません。むしろ遅くなっています。実は、正確に計測するにはComputeCommandQueue.Finish()を呼ぶ必要があったようです。
本当のことを言うと10倍100倍と速くなって欲しかったのですが、このニューロン数の少なさではGPUの並列処理のメリットも活かしにくいでしょうしね。
不思議なのはGPUにデータをセットするのに意外と時間がかかっていることです。
初回だけ異常に時間がかかっています(2回目以降はすぐ終わっていますが)。
なんと7683ミリ秒、つまり約8秒もかかっています。
どういうことでしょう?
もしかすると最初だけ必要な初期化処理があるのかもしれませんね。
[追記]:初期化処理ではなく、実は計算をやっていました。コマンドキューに溜まったコマンドを実行していたのです。
このままではGPUの威力を実感しにくいので、ニューロンの数を増やしてみました。
後いろいろパラメーターをいじっています。
以下の様な感じです:
visible neuron count : 10000
hidden neuron count : 300
training epoch count : 10
さあどうなるでしょうか?
calculation : 6130.912[ms] set data to cpu : 0.2321[ms] set data to cpu : 0.1332[ms] set data to cpu : 0.1604[ms] get results from cpu : 264.4902[ms]
calculation : 479.2816[ms] set data to gpu : 27722.3127[ms] set data to gpu : 8.207[ms] set data to gpu : 10.8323[ms] get results from gpu : 28669.8565[ms]
GPUバージョンはCPUバージョンより、学習速度が10倍以上速くなっていますね!
でもデータをセットするのにかなり時間がかかっており台無しです。
しかし何故こんなに時間がかかるのでしょう…?
[追記]:くどいようですが、これは全然速くなっていません!データをセットするのに必要だと思っていた時間は、実は計算時間だったのです。