BackPropagationLearningをSIMDに対応してみた

Accord.NET(AForge.NET)のBackPropagationLearningをSIMDを使用するように修正してみた。
速度的には以前に作ったマルチスレッド対応版のBackPropagationLearningの1.2倍程度の早さになってます。
簡略版なので並列数は4に固定。したがってAVX非対応のCPUだと動きません。
並列数2だと、おそらく速度的に変わらないので作るつもりもありません。
DeepBeliefNetworkなど関連するクラスも合わせて修正すればもっと早くなるのだろうけど、とりあえずBackPropagationLearningのみの対応。流石に全部はボリューム多すぎるし・・・orz

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Numerics;
using System.Diagnostics;

using AForge.Neuro;
using AForge.Neuro.Learning;

namespace LernDetectImage
{
    class SimdBackPropagationLearning : BackPropagationLearning
    {
        // network to teach
        private ActivationNetwork network;

        // learning rate
        private double learningRate = 0.1;

        // momentum
        private double momentum = 0.0;

        // neuron's errors
        private Vector<double>[][] neuronErrors = null;

        // weight's updates
        private Vector<double>[][][] weightsUpdates = null;

        // threshold's updates
        private Vector<double>[][] thresholdsUpdates = null;

        public new double LearningRate
        {
            get { return learningRate; }
            set
            {
                learningRate = Math.Max(0.0, Math.Min(1.0, value));
            }
        }

        public new double Momentum
        {
            get { return momentum; }
            set
            {
                momentum = Math.Max(0.0, Math.Min(1.0, value));
            }
        }

        public SimdBackPropagationLearning(ActivationNetwork network) : base(network)
        {
            this.network = network;

            // create error and deltas arrays
            neuronErrors = new Vector<double>[network.Layers.Length][];
            weightsUpdates = new Vector<double>[network.Layers.Length][][];
            thresholdsUpdates = new Vector<double>[network.Layers.Length][];

            // initialize errors and deltas arrays for each layer
            for (int i = 0; i < network.Layers.Length; i++)
            {
                Layer layer = network.Layers[i];

                neuronErrors[i] = new Vector<double>[layer.Neurons.Length / 4];
                weightsUpdates[i] = new Vector<double>[layer.Neurons.Length][];
                thresholdsUpdates[i] = new Vector<double>[layer.Neurons.Length / 4];

                // for each neuron
                for (int j = 0; j < weightsUpdates[i].Length; j++)
                {
                    weightsUpdates[i][j] = new Vector<double>[layer.InputsCount / 4];
                }
            }
        }

        public new double Run(double[] input, double[] output)
        {
            // compute the network's output
            network.Compute(input);

            // calculate network error
            double error = CalculateError(output);

            // calculate weights updates
            CalculateUpdates(input);

            // update the network
            UpdateNetwork();

            return error;

        }

        public new double RunEpoch(double[][] input, double[][] output)
        {
            double error = 0.0;

            // run learning procedure for all samples
            for (int i = 0; i < input.Length; i++)
            {
                error += Run(input[i], output[i]);
            }

            // return summary error
            return error;
        }

        private double CalculateError(double[] desiredOutput)
        {
            // current and the next layers
            Layer layer, layerNext;
            // current and the next errors arrays
            Vector<double>[] errors, errorsNext;
            // error values
            double error = 0;
            // layers count
            int layersCount = network.Layers.Length;

            // vecrorize output
            Vector<double>[] desiredOutputVector = new Vector<double>[desiredOutput.Length / 4];
            for (int i = 0; i < desiredOutputVector.Length; i++)
            {
                desiredOutputVector[i] = new Vector<double>(desiredOutput, i * 4);
            }

            // assume, that all neurons of the network have the same activation function
            IActivationFunction function = (network.Layers[0].Neurons[0] as ActivationNeuron).ActivationFunction;

            // calculate error values for the last layer first
            layer = network.Layers[layersCount - 1];
            errors = neuronErrors[layersCount - 1];
            int outputLoopCnt = layer.Neurons.Length / 4;
            Vector<double>[] errorWork = new Vector<double>[outputLoopCnt];
            Parallel.For(0, outputLoopCnt, new ParallelOptions { MaxDegreeOfParallelism = 16 }, i =>
            {
                // neuron's output value
                double[] vectorInitTemp = new double[4];
                vectorInitTemp[0] = layer.Neurons[i * 4 + 0].Output;
                vectorInitTemp[1] = layer.Neurons[i * 4 + 1].Output;
                vectorInitTemp[2] = layer.Neurons[i * 4 + 2].Output;
                vectorInitTemp[3] = layer.Neurons[i * 4 + 3].Output;
                Vector<double> output = new Vector<double>(vectorInitTemp);

                // error of the neuron
                Vector<double> e = desiredOutputVector[i] - output;

                // error multiplied with activation function's derivative
                vectorInitTemp[0] = function.Derivative2(output[0]);
                vectorInitTemp[1] = function.Derivative2(output[1]);
                vectorInitTemp[2] = function.Derivative2(output[2]);
                vectorInitTemp[3] = function.Derivative2(output[3]);
                Vector<double> derivative = new Vector<double>(vectorInitTemp);
                errors[i] = e * derivative;

                // squre the error and sum it
                errorWork[i] = (e * e);
            });

            // エラー積算値の算出
            Vector<double> errorTemp = Vector<double>.Zero;
            for (int i = 0;i < outputLoopCnt;i++)
            {
                errorTemp += errorWork[i];
            }
            error = errorTemp[0] + errorTemp[1] + errorTemp[2] + errorTemp[3];

            // calculate error values for other layers
            for (int j = layersCount - 2; j >= 0; j--)
            {
                layer = network.Layers[j];
                layerNext = network.Layers[j + 1];
                errors = neuronErrors[j];
                errorsNext = neuronErrors[j + 1];

                // for all neurons of the layer
                int nextNyuronsLengthTemp = layerNext.Neurons.Length / 4;
                Parallel.For(0, (layer.Neurons.Length / 4), new ParallelOptions { MaxDegreeOfParallelism = 16 }, i =>
                {
                    double[] vectorInitTemp = new double[4];
                    Vector<double> sum = Vector<double>.Zero;

                    // for all neurons of the next layer
                    for (int k = 0; k < nextNyuronsLengthTemp; k++)
                    {
                        for (int l = 0; l < 4; l++)
                        {
                            vectorInitTemp[0] = layerNext.Neurons[k * 4 + l].Weights[i * 4 + 0];
                            vectorInitTemp[1] = layerNext.Neurons[k * 4 + l].Weights[i * 4 + 1];
                            vectorInitTemp[2] = layerNext.Neurons[k * 4 + l].Weights[i * 4 + 2];
                            vectorInitTemp[3] = layerNext.Neurons[k * 4 + l].Weights[i * 4 + 3];
                            Vector<double> weightsTemp = new Vector<double>(vectorInitTemp);
                            sum += errorsNext[k] * weightsTemp;
                        }
                    }

                    vectorInitTemp[0] = function.Derivative2(layer.Neurons[i * 4 + 0].Output);
                    vectorInitTemp[1] = function.Derivative2(layer.Neurons[i * 4 + 1].Output);
                    vectorInitTemp[2] = function.Derivative2(layer.Neurons[i * 4 + 2].Output);
                    vectorInitTemp[3] = function.Derivative2(layer.Neurons[i * 4 + 3].Output);
                    Vector<double> derivative = new Vector<double>(vectorInitTemp);

                    errors[i] = sum * derivative;
                });
            }

            // return squared error of the last layer divided by 2
            return error / 2.0;
        }

        private void CalculateUpdates(double[] input)
        {
            // current and previous layers
            Layer layer, layerPrev;

            // layer's weights updates
            Vector<double>[][] layerWeightsUpdates;

            // layer's thresholds updates
            Vector<double>[] layerThresholdUpdates;

            // layer's error
            Vector<double>[] errors;

            // vecrorize input
            Vector<double>[] inputVector = new Vector<double>[input.Length / 4];
            Parallel.For(0, inputVector.Length, new ParallelOptions { MaxDegreeOfParallelism = 16 }, i =>
            {
                inputVector[i] = new Vector<double>(input, i * 4);
            });

            // 1 - calculate updates for the first layer
            layer = network.Layers[0];
            errors = neuronErrors[0];
            layerWeightsUpdates = weightsUpdates[0];
            layerThresholdUpdates = thresholdsUpdates[0];

            // cache for frequently used values
            //double cachedMomentum = learningRate * momentum;
            //double cached1mMomentum = learningRate * (1 - momentum);
            Vector<double> cachedMomentum = Vector.Multiply(Vector<double>.One, learningRate * momentum);
            Vector<double> cached1mMomentum = Vector.Multiply(Vector<double>.One, learningRate * (1 - momentum));

            // for each neuron of the layer
            Parallel.For(0, (layer.Neurons.Length / 4), new ParallelOptions { MaxDegreeOfParallelism = 16 }, i =>
            {
                Vector<double> cachedError = Vector.Multiply(cached1mMomentum, errors[i]);
                Vector<double>[][] neuronWeightUpdates = new Vector<double>[4][];
                neuronWeightUpdates[0] = layerWeightsUpdates[i * 4 + 0];
                neuronWeightUpdates[1] = layerWeightsUpdates[i * 4 + 1];
                neuronWeightUpdates[2] = layerWeightsUpdates[i * 4 + 2];
                neuronWeightUpdates[3] = layerWeightsUpdates[i * 4 + 3];

                // for each weight of the neuron
                int neuronWeightUpdatesTemp = neuronWeightUpdates[0].Length;
                for (int j = 0; j < neuronWeightUpdatesTemp; j++)
                {
                    // calculate weight update
                    for (int k = 0;k < 4;k++)
                    {
                        neuronWeightUpdates[k][j] = Vector.Multiply(cachedMomentum, neuronWeightUpdates[k][j]) + Vector.Multiply(cachedError[k], inputVector[j]);
                    }
                }

                // calculate treshold update
                layerThresholdUpdates[i] = Vector.Multiply(cachedMomentum, layerThresholdUpdates[i]) + cachedError;
            });


            // 2 - for all other layers
            int layersLengthTemp = network.Layers.Length;
            for (int k = 1; k < layersLengthTemp; k++)
            {
                layerPrev = network.Layers[k - 1];
                layer = network.Layers[k];
                errors = neuronErrors[k];
                layerWeightsUpdates = weightsUpdates[k];
                layerThresholdUpdates = thresholdsUpdates[k];

                // for each neuron of the layer
                int neuronWeightUpdatesTemp = layerWeightsUpdates[0].Length;
                Parallel.For(0, (layer.Neurons.Length / 4), new ParallelOptions { MaxDegreeOfParallelism = 16 }, i =>
                {
                    double[] vectorInitTemp = new double[4];
                    Vector<double> cachedError = Vector.Multiply(cached1mMomentum, errors[i]);
                    Vector<double>[][] neuronWeightUpdates = new Vector<double>[4][];
                    neuronWeightUpdates[0] = layerWeightsUpdates[i * 4 + 0];
                    neuronWeightUpdates[1] = layerWeightsUpdates[i * 4 + 1];
                    neuronWeightUpdates[2] = layerWeightsUpdates[i * 4 + 2];
                    neuronWeightUpdates[3] = layerWeightsUpdates[i * 4 + 3];

                    // for each synapse of the neuron
                    for (int j = 0; j < neuronWeightUpdatesTemp; j++)
                    {
                        // calculate weight update
                        vectorInitTemp[0] = layerPrev.Neurons[j * 4 + 0].Output;
                        vectorInitTemp[1] = layerPrev.Neurons[j * 4 + 1].Output;
                        vectorInitTemp[2] = layerPrev.Neurons[j * 4 + 2].Output;
                        vectorInitTemp[3] = layerPrev.Neurons[j * 4 + 3].Output;
                        Vector<double> neuronsOutput = new Vector<double>(vectorInitTemp);
                        for (int l = 0; l < 4; l++)
                        {
                            neuronWeightUpdates[l][j] = Vector.Multiply(cachedMomentum, neuronWeightUpdates[l][j]) + Vector.Multiply(cachedError[l], neuronsOutput);
                        }
                    }

                    // calculate treshold update
                    layerThresholdUpdates[i] = Vector.Multiply(cachedMomentum, layerThresholdUpdates[i]) + cachedError;
                });
            }
        }

        private void UpdateNetwork()
        {
            // current layer
            Layer layer;
            // layer's weights updates
            Vector<double>[][] layerWeightsUpdates;
            // layer's thresholds updates
            Vector<double>[] layerThresholdUpdates;

            // for each layer of the network
            int layersLengthTemp = network.Layers.Length;
            for (int i = 0; i < layersLengthTemp; i++)
            {
                layer = network.Layers[i];
                layerWeightsUpdates = weightsUpdates[i];
                layerThresholdUpdates = thresholdsUpdates[i];

                // 誘導変数の使用
                int weightsLengthTemp = layer.Neurons[0].Weights.Length / 4;

                // for each neuron of the layer
                Parallel.For(0, (layer.Neurons.Length / 4), j =>
                {
                    ActivationNeuron[] neuron = new ActivationNeuron[4];
                    neuron[0] = layer.Neurons[j * 4 + 0] as ActivationNeuron;
                    neuron[1] = layer.Neurons[j * 4 + 1] as ActivationNeuron;
                    neuron[2] = layer.Neurons[j * 4 + 2] as ActivationNeuron;
                    neuron[3] = layer.Neurons[j * 4 + 3] as ActivationNeuron;

                    Vector<double>[][] neuronWeightUpdates = new Vector<double>[4][];
                    neuronWeightUpdates[0] = layerWeightsUpdates[j * 4 + 0];
                    neuronWeightUpdates[1] = layerWeightsUpdates[j * 4 + 1];
                    neuronWeightUpdates[2] = layerWeightsUpdates[j * 4 + 2];
                    neuronWeightUpdates[3] = layerWeightsUpdates[j * 4 + 3];

                    // for each weight of the neuron
                    for (int k = 0; k < weightsLengthTemp; k++)
                    {
                        for (int l = 0; l < 4; l++)
                        {
                            // update weight
                            neuron[l].Weights[k * 4 + 0] += neuronWeightUpdates[l][k][0];
                            neuron[l].Weights[k * 4 + 1] += neuronWeightUpdates[l][k][1];
                            neuron[l].Weights[k * 4 + 2] += neuronWeightUpdates[l][k][2];
                            neuron[l].Weights[k * 4 + 3] += neuronWeightUpdates[l][k][3];
                        }
                    }

                    // update treshold
                    neuron[0].Threshold += layerThresholdUpdates[j][0];
                    neuron[1].Threshold += layerThresholdUpdates[j][1];
                    neuron[2].Threshold += layerThresholdUpdates[j][2];
                    neuron[3].Threshold += layerThresholdUpdates[j][3];
                });
            }
        }
    }
}

Download:SimdBackPropagationLearning

C#でSIMDを使って高速化

そういえばVisual Studio 2015からC#でもSIMDが使えるようになったんだっけ・・・と言うことで使ってみた。
SIMDを有効にするには3つの条件がある。

  • .NET Framework 4.6を使用すること。
  • 64bitコードを選択すること。
  • NuGetからNumerics.Vectors(v4.1)をインストールすること。

プロジェクトのプロパティを開いて次の二ヶ所のオプションを変更しておく。
option#2
option#1
次にNuGetパッケージの管理を開いて、System.Numerics.Vectorsを追加する。
nuget

これで準備は出来たのでコードを書いてみる。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Numerics;

namespace SimdTest
{
    class Program
    {
        static void Main(string[] args)
        {
            DateTime startTime = DateTime.Now;
            double answer = 0.0;
            for (int i = 0;i < 1000000;i++)
            {
                answer = 1.0;
                for (int j = 1;j <= 100;j++)
                {
                    answer = answer * (double )j;
                }
            }
            DateTime lastTime = DateTime.Now;
            Console.WriteLine("{0}ms, answer = {1}", (lastTime - startTime).TotalMilliseconds, answer);

            Console.WriteLine("Count = {0}, Hardware = {1}", Vector<double>.Count, Vector.IsHardwareAccelerated);
            startTime = DateTime.Now;
            for (int i = 0; i < 1000000; i++)
            {
                Vector<double> answerTemp = new Vector<double>(new double[] {1, 1, 1, 1});
                Vector<double> multiTemp = new Vector<double>(new double[] {0, 25, 50, 75});
                for (int j = 1; j <= 25; j++)
                {
                    multiTemp += Vector<double>.One;
                    answerTemp *= multiTemp;
                }
                answer = answerTemp[0] * answerTemp[1] * answerTemp[2] * answerTemp[3];
            }
            lastTime = DateTime.Now;
            Console.WriteLine("{0}ms, answer = {1}", (lastTime - startTime).TotalMilliseconds, answer);
        }
    }
}

と言うわけで、100の階乗を求める処理を書いてみた。
非常にシンプルな例なのだが、35%程度の高速化にしかならない。
Vector.IsHardwareAcceleratedを参照すると確かにSIMDが使われている事が分かる。
Vector<double>.Countが4を返すのでSSEではなくIntel AVXにも対応している様子。AVX非対応(SSE2対応)PCだとVector<double>.Countが2を返してきます。
本来的にはVector<double>.Countの値を見て並列数を判断するべきなのかもしれないが、4で決め打ちしています。

#OpenCL使えば手っ取り早いダロという話もあるけど、自宅や会社で使ってるPCが対応していないんだよね。

C#でExcelに画像データを埋め込む

※追記…ClosedXML v0.88移行は標準で画像の埋め込みに対応しており、ClosedXMLImageSupportを使う必要がなくなりました。

OpenXML SDKで画像を埋め込んだExcelファイルを作成しようとすると、ステップ数が一気に増えてしまって現実的ではありません。ClosedXMLはExcelファイルを作るのにとっても便利なのですが、画像データを扱う機能がありません・・・・と思ったら、ClosedXMLをForkして画像データを扱う機能を追加している方が、ここは感謝して使わせていただきましょう。

ClosedXML.DLLをビルドする

NuGetでダウンロードできるCloseXMLは画像データを扱えませんから、自分でソースコードからビルドします。Fork: ajwhiteway/ClosedXMLImageSupportからソースコードをダウンロードします。
ClosedXML_for_image_download
ダウンロードしたzipファイルを解凍するとソースコードが得られるので、ClosedXML.slnファイルを開いてビルドします。ClosedXML\bin\Releaseに生成されるClosedXML.dllを参照設定します。

これで準備は出来ました。

using BarcodeLib;
using ClosedXML.Excel;
using ClosedXML.Excel.Drawings;
<<中略>>
            var book = new XLWorkbook();
            var sheet = book.AddWorksheet("sheet1");

            // 画像データを指定
            XLPicture pic = new XLPicture
            {
                NoChangeAspect = true,
                NoMove = true,
                NoResize = true,
                ImageStream = File.OpenRead("sample.png")
            };

            // 表示位置を指定
            XLMarker fMark = new XLMarker
            {
                ColumnId = 1,
                RowId = 1
            };
            pic.AddMarker(fMark);

            // 画像データを追加
            sheet.AddPicture(pic);
            book.SaveAs("sample.xlsx");
<<中略>>

と、かなりシンプルに記述できます。
こんなに便利なのだから是非mainに取り込んで欲しいよね。

Windows Azure上のWindowsをGUIなしにする

多少でも仮想マシンの利用料を下げるために、GUIを削除して軽くしたいと思います。これで仮想マシンのサイズを下げても、使用メモリ量が減っている分、それなりに使えるようになるはずです。

仮想OSのPowershellのリモート接続を有効にする

GUIを持たないWindowsの操作は主にPowerShellから行う事になります。まずはPowerSheelに対してリモート接続出来る状態にします。Azure仮想OSにRemote Desktopで接続して、PowerShellを管理者権限で起動してEnable-PSRemoteingを実行します。これにより必要なサービスの起動やファイアウォールの設定等が自動で行われますので「[A] Yes to All」を選択して継続してください。

PS C:\Users\username&gt; Enable-PSRemoting
WinRM Quick Configuration
Running command "Set-WSManQuickConfig" to enable remote management of this computer by using the Windows Remote
Management (WinRM) service.
 This includes:
    1. Starting or restarting (if already started) the WinRM service
    2. Setting the WinRM service startup type to Automatic
    3. Creating a listener to accept requests on any IP address
    4. Enabling Windows Firewall inbound rule exceptions for WS-Management traffic (for http only).

Do you want to continue?
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"):

クライアントへのSSL証明書のインストール

次にPowerShellのリモート接続で使用するためのSSL証明書のダウンロードとインストールを行います。少々変則的ですがhttps://[仮想マシン名].cloudapp.net:5986のURLにGoogle Chromeを使用してアクセスし、画面赤丸の部分をクリックして証明書を表示します。
chrome#1

次に↓画面の赤丸の部分をクリック。
chrome#2

最後に↓画面の赤丸部分をクリックして、後はウィザードに従ってデフォルト設定のままファイルに保存します。
chrome#3

証明書をダウンロードしたら、証明書ファイルを右クリックしてインストールを選択します。
inport#1

インストールはウィザードに従って勧めていきますが、証明書ストアの選択で「信頼されたルート証明機関」に保存するように設定をしてください。それ以外はデフォルトのままで進めます。
inport#3

クライアントから仮想OSにPowerShellで接続する

これでPowerShellでリモート接続する準備が整ったので、PowerShellを起動して次のコマンドを実行します。プロンプトに接続先コンピュータ名が表示され、リモート接続していることが分かります。

PS C:\Users\username> Enter-PSSession -ComputerName computername.cloudapp.net -Credential knarita -UseSSL
[computername.cloudapp.net]: PS C:\Users\username\Documents>

 

仮想OSからGUIを削除する

せっかくなので、このままAzure仮想OSからGUIをアイインストールしてしまいます。次のコマンドを実行してください。

Uninstall-WindowsFeature Server-Gui-Mgmt-Infra –restart
Success Restart Needed Exit Code      Feature Result
------- -------------- ---------      --------------
True    Yes            SuccessRest... {Windows PowerShell ISE, Graphical Managem...
警告: You must restart this server to finish the removal process.

これで再起動するとGUIが無い状態で起動します。

ちなみにGUIを戻すにはPowerShellから次のコマンドを実行すると、いつでも元に戻せます。

Install-WindowsFeature Server-Gui-Mgmt-Infra,Server-Gui-Shell –restart

apt-get upgradeでThe following packages have been kept backが発生

Ubuntu 15.10にてapt-get upgradeを実行したときに、下記のようにThe following packages have been kept backと表示されアップデートが行われない。既存パッケージの依存関係に問題が発生しているのが原因。

$ sudo apt-get upgrade
Reading package lists... Done
Building dependency tree
Reading state information... Done
Calculating upgrade... Done
The following packages have been kept back:
  linux-cloud-tools-virtual linux-generic linux-headers-generic
  linux-headers-virtual linux-image-extra-virtual linux-image-generic
  linux-image-virtual linux-virtual sosreport
0 upgraded, 0 newly installed, 0 to remove and 9 not upgraded.

sudo apt-get dist-upgradeを実行すると依存関係の問題が処理される。longer required: linux-cloud-tools-4.2.0-16と表示されているようにlinux-cloud-toolsのupdateを自動で処理できなかったのが原因。このままYとするとlinux-cloud-tools-4.2.0-22に更新されてインストールが完了する。

$ sudo apt-get dist-upgrade
Reading package lists... Done
Building dependency tree
Reading state information... Done
Calculating upgrade... The following packages were automatically installed and are no longer required:
  linux-cloud-tools-4.2.0-16 linux-cloud-tools-4.2.0-16-generic
Use 'apt-get autoremove' to remove them.
Done
The following NEW packages will be installed:
  libpython3.5-minimal libpython3.5-stdlib linux-cloud-tools-4.2.0-22
  linux-cloud-tools-4.2.0-22-generic linux-headers-4.2.0-22
  linux-headers-4.2.0-22-generic linux-image-4.2.0-22-generic
  linux-image-extra-4.2.0-22-generic python3.5 python3.5-minimal
The following packages will be upgraded:
  linux-cloud-tools-virtual linux-generic linux-headers-generic
  linux-headers-virtual linux-image-extra-virtual linux-image-generic
  linux-image-virtual linux-virtual sosreport
9 upgraded, 10 newly installed, 0 to remove and 0 not upgraded.
Need to get 70.8 MB of archives.
After this operation, 311 MB of additional disk space will be used.
Do you want to continue? [Y/n]

linux-cloud-toolsは不用意に更新すると接続が出来なくなったり、クラウドの管理コンソールから制御できなくなる恐れがあるので、自動更新を取り止めたのかと思われる。

Microsoft Azureの無料枠を超えてしまったので問い合わせてみた

以前にコンテストの参加賞で貰ったMicrosoft Azureの無料枠の範囲で使うつもりでいたのだが請求が発生していたので問い合わせてみた。

Microsoft Azure Machine Learningで作った機械学習結果を使って、数日連続処理をさせておいたところ3万円強の請求が発生してしまっていた。「使用制限オプション」も有効にしていたのだが、高い負荷で使っていたため、制限がかかる前に突き抜けてしまったのかもしれない。

サポートの問い合わせ画面はわかりにくいところにあるのですが、新しいポータルに移動した後、画面右上のサポートをクリックするとサポート画面に行けます。ここからメッセージを書き込み、問い合わせをします。

問い合わせ後、2~3日待つと「対象の請求期間でご利用いただいていたサービスの使用量が表示されず、明確な使用実績が確認出来ない状況であることを確認いたしました。~」とのことで、全額返金して貰えることになりました。問いああわせてみてよかった。

あらためて使ってみて分かる、Azure Machine Learningのコンピューティングフィーの高さ。実際にどの程度の計算リソースが割り当てられているのかは分かりませんが、大量のデータを常時計算させ続けた場合は、普通に8Coreの仮想マシンを借りるくらいの金額がかかるんですね。