頭と尻尾はくれてやる!

iOSアプリなどの開発日記です


Kerasで学習時に損失関数の値を得る

class TestCallbackFunction(keras.callbacks.Callback) :
    def on_epoch_end(self, epoch, logs={}) :
        self.latestLoss = logs.get('loss')
↑1エポック終了時にコールされるコールバック関数。
ここで上記のように記録しとけばfit関数の後に
print("latestLoss=%f"%test_cb.latestLoss)
と記述すれば損失関数の値を得ることができる。

これもいいんだけど、なんだか面倒だなあと思って調べたら訓練を実行するfitの返り値で得られる。
print(history.history['loss'])
とすると↓のように得られる。
[0.8780770791371664, 0.5104841385046641, 0.42453359955151876]
print("last loss = %f"%history.history['loss'][-1])
↑最後のが欲しかったのでこれでOK。
スポンサーサイト






Switch版フォートナイトの二段階認証

息子がSwitchで楽しんでいるFortniteで、二段階認証を設定するとエモートと呼ばれる踊るモーションが入手できるとかで、やってくれ!と息子が言ってきたんだが、やろうとしたらえらくハマったのでメモ。

そもそもアカウントがどういう状態かよくわからない。
Epic Gamesのアカウントが必要らしいのだが、俺が息子のために作成してやった記録も覚えもない。息子もパスワード設定した覚えもないと思うけどわからない、、、って感じ。
おそらく面倒なことはスキップしたんだろう。
それでも下記の参考ページによるととりあえずアカウントはできてるとか?

多分、任天堂アカウント使ってログインしているんだろう。任天堂アカウントで登録したメールアドレスにEpic Gamesからメールが届いてるし。

二段階認証ということはメールが届くデバイスが必要なんだよね?ってことで息子のiPhoneからいろいろと設定した。
なお、息子iPhoneのSafariには機能制限をしてて指定したWebサイトのみOKしてたのだが、設定時には機能制限はオフで。
設定時にSwitchは使わなかった。
やったことは以下の通り。


まず息子iPhoneのSafariでEpic Gamesのサイトでログインしておく。

Epic Gamesログイン画面

↑この時、パスワードを入力するような画面でログインするんじゃなくてSwitchのマーク(中央の赤いところをタップ)からログインできる。

二段階認証を設定しようと思うとその前に「メールアドレスの認証」というのを先にする必要があった。
すでに(おそらく任天堂アカウントで)指定してたメールアドレス先にメールが送信されるので、そこにあるボタンをクリック、みたいな流れ。
ただ、このメールが送信されるのに数日かかった(みんなエモートが欲しくて殺到していたのか?)。後日やったら数分で来たが。

次に、二段階認証を設定する。同じような感じで「2段階サインインコード」というメールが送信されてきてそこに書いてる6桁の数字を入力すれば完了。

しばらくすると息子が「エモートいけた!」と喜んでたので無事二段階認証の設定はできたらしい。


(参考)
フォートナイトのアカウントについて教えて下さい。スイッチで子供がフォート... - Yahoo!知恵袋


Objective-CからC++を動かす

Objective-CからC++を動かそうとした。
やりたいことができなかったし、今後そうそう使うことはないだろうけど、もしかしたらいつか必要になるかもしれないのでメモしとく。

ios - How to include C++ headers in an Objective C++ header? - Stack Overflow

↑この記事がすごく参考になった。

(1)関数を呼び出す
{
    NSInteger number = _cppMembers->member1.getNumber();
    NSLog(@"number = %ld",(long)number);
}
↑呼び出し部分はこんなの。ほぼそのままなので他のコードは略。

(2)引数(文字列)を渡す
{
    NSString *text = @"Hello, C++!";
    std::string textcpp = std::string([text UTF8String]);
     _cppMembers->member1.say(textcpp);
}
↑呼び出し元(Objective-C++で拡張子はmm)はこんな感じでNSStringを変換する必要がある。
class CPPClass
{
public:
    void say(string word)
    {
        cout << word << "\n";
    }
};
↑受け取る CPPClass.h はこんな感じでOK。

(3)返り値(文字列)をもらう
{
    std::string answercpp = _cppMembers->member1.getAnswer();
    NSString *answer = [NSString stringWithCString:answercpp.c_str() encoding:[NSString defaultCStringEncoding]];
    NSLog(@"answer=%@",answer);
}
↑返ってきた文字はこんな感じでNSStringにできる。
class CPPClass
{
public:
    string getAnswer()
    {
        return "Hi, Objective-C++!";
    }
};
↑C++側はこんな感じで返り値を渡せる。

(4)C++ってヘッダーだけでいいの?
C++のコードを見るとヘッダーに全部書いてたりするけどいいの?XcodeでC++のファイルを追加すると実装側(.cppファイル)も生成されるし?
どうなってんのかな?と思って調べたら↓こんな記事があった。

C++ ヘッダとソースでファイルを分ける 基本編


なるほど〜。
まあ二度と使わないと思うけど。


MPSでsoftmax関数を使う

KerasとMPSで同じ計算をする(2)
↑実質、この続き。
活性化関数にsoftmaxを使おうとしたらMPS側がすんなりじゃなかった。
Kerasの方は
model.add(Dense(3, activation='relu'))
↓
model.add(Dense(3, activation=‘softmax’))
これだけでいいんだけど、Metal Performance Shaders の方はsoftmax用にMPSImageを準備するなどしなければならない。
{
    // 準備
    MPSCNNSoftMax *softmax;
    softmax = [[MPSCNNSoftMax alloc] initWithDevice:device];


    // 計算する部分
    [self->h1 encodeToCommandBuffer:commandBuffer sourceImage:self->srcImage destinationImage:self->h1Image];
    [self->h2 encodeToCommandBuffer:commandBuffer sourceImage:self->h1Image destinationImage:self->h2Image];
    [self->softmax encodeToCommandBuffer:commandBuffer sourceImage:self->h2Image destinationImage:self->finalImage];
}
これで意図通りの計算結果になったんだけど、なんだかなあ。
ReLUだとMPSCNNFullyConnectedクラスにすんなり活性化関数として渡せるのにな。


KerasとMPSで同じ計算をする(2)

KerasとMPSで同じ計算をする(1)
↑この続き。今回はMetal Performance Shaders側、およびその比較結果。

(2)MPS側
Kerasで保存した係数をmacOS側で読み込んでMetal Performance Shadersで同じNN構成で計算する。
//入力用構造体
typedef struct
{
    float angle0;
    float angle1;
} Inputs_t;

//出力用構造体
typedef  struct
{
    float q0;
    float q1;
    float q2;
} MPSResult_t;

{
    //インスタンス
    id <MTLDevice> device;
    id <MTLCommandQueue> commandQueue;
    MPSCNNNeuronReLU *relu;
    MPSImage *srcImage;
    MPSImage *h1Image;
    MPSImage  *finalImage;
    SlimMPSCNNFullyConnected *h1;

    float *angles;//入力
    float *qResults;//出力

    MTLRegion srcImageRegion;
    MTLRegion filnalImageRegion;
}

-(void)setupNN
{
    NUM_INPUT = 2;
    NUM_HIDDEN1 = 4;
    NUM_OUTPUT = 3;
    
    angles = calloc(NUM_INPUT, sizeof(float));
    qResults = calloc(NUM_OUTPUT , sizeof(float));
    
    srcImageRegion = MTLRegionMake2D(0, 0, NUM_INPUT, 1);
    filnalImageRegion = MTLRegionMake2D(0, 0, 1, 1);

    MPSImageDescriptor *sid = [MPSImageDescriptor imageDescriptorWithChannelFormat:MPSImageFeatureChannelFormatFloat32 width:NUM_INPUT height:1 featureChannels:1];//入力側
    
    MPSImageDescriptor *h1id = [MPSImageDescriptor imageDescriptorWithChannelFormat:MPSImageFeatureChannelFormatFloat32 width:1 height:1 featureChannels:NUM_HIDDEN1];
    
    MPSImageDescriptor *did = [MPSImageDescriptor imageDescriptorWithChannelFormat:MPSImageFeatureChannelFormatFloat32 width:1 height:1 featureChannels:NUM_OUTPUT];//出力側
    
    
    device = MTLCreateSystemDefaultDevice();
    commandQueue = [device newCommandQueue];
    
    
    // Initialize MPSImage from descriptors
    srcImage = [[MPSImage alloc] initWithDevice:device imageDescriptor:sid];
    h1Image = [[MPSImage alloc] initWithDevice:device imageDescriptor:h1id];
    finalImage = [[MPSImage alloc] initWithDevice:device  imageDescriptor:did];
    
    relu = [[MPSCNNNeuronReLU alloc] initWithDevice:device a:0];
}

-(void)makeLayers
{
    h1 = [[SlimMPSCNNFullyConnected alloc]
          initWithKernelWidth:NUM_INPUT
          kernelHeight:1
          inputFeatureChannels:1
          outputFeatureChannels:NUM_HIDDEN1
          neuronFilter:relu
          device:device
          kernelParamsBinaryName:@"1"];
    
    
    h2 = [[SlimMPSCNNFullyConnected alloc]
          initWithKernelWidth:1
          kernelHeight:1
          inputFeatureChannels:NUM_HIDDEN1
          outputFeatureChannels:NUM_OUTPUT
          neuronFilter:relu
          device:device
          kernelParamsBinaryName:@"2"];
}

-(void)checkNN
{
    Inputs_t inputs;
    inputs.angle0 = 0.1;
    inputs.angle1 = 0.2;
    //↑Keras側と同じ入力
    MPSResult_t r = [self inferenceForInputs:inputs];
    NSLog(@"q0=%f,q1=%f,q2=%f",r.q0,r.q1,r.q2);
}


-(MPSResult_t)inferenceForInputs:(Inputs_t)inputs
{
    __block MPSResult_t mpsResult;
    
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
       dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        
        self->angles[0] = inputs.angle0;
        self->angles[1] = inputs.angle1;
        
        [self->srcImage.texture replaceRegion:self->srcImageRegion
                                  mipmapLevel:0
                                        slice:0
                                    withBytes:self->angles
                                  bytesPerRow:sizeof(float)*4
                                bytesPerImage:0];
        
        @autoreleasepool{
            id <MTLCommandBuffer> commandBuffer = [self->commandQueue commandBuffer];
            
            [self->h1 encodeToCommandBuffer:commandBuffer sourceImage:self->srcImage destinationImage:self->h1Image];
            [self->h2 encodeToCommandBuffer:commandBuffer sourceImage:self->h1Image destinationImage:self->finalImage];
            
            [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
                
                [self->finalImage.texture getBytes:&self->qResults[0]
                                       bytesPerRow:sizeof(float)*4
                                        fromRegion:self->filnalImageRegion
                                       mipmapLevel:0];
                
                mpsResult.q0 = self->qResults[0];
                mpsResult.q1 = self->qResults[1];
                mpsResult.q2 = self->qResults[2];
                
                dispatch_semaphore_signal(semaphore);
                
            }];
            
            [commandBuffer commit];
        }
        
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
    return mpsResult;
}
SlimMPSCNNFullyConnectedクラスはAPPLEのサンプルコードを参考にしてる。というかほとんどそのまま。
実装部分はこんな感じで↓
- (instancetype)initWithKernelWidth:(NSUInteger)kernelWidth
                       kernelHeight:(NSUInteger)kernelHeight
               inputFeatureChannels:(NSUInteger)inputFeatureChannels
              outputFeatureChannels:(NSUInteger)outputFeatureChannels
                       neuronFilter:(MPSCNNNeuron *)neuronFilter
                             device:(id<MTLDevice>)device
             kernelParamsBinaryName:(NSString *)kernelParamsBinaryName
{
    
    NSUInteger nofWeight = inputFeatureChannels*kernelHeight*kernelWidth*outputFeatureChannels;
    NSUInteger nofBias = outputFeatureChannels;
    float *weightP = calloc(nofWeight, sizeof(float));
    float *biasP = calloc(nofBias, sizeof(float));
    
    NSString *wbdataFolder = [NSString stringWithFormat:@"%@",BaseFolder];
    NSString *fileName_w = [NSString stringWithFormat:@"%@_%@.dat",STR_WEIGHTS , kernelParamsBinaryName];
    NSString *fileName_b = [NSString stringWithFormat:@"%@_%@.dat",STR_BIAS, kernelParamsBinaryName];
    NSString *filePath_w = [NSString stringWithFormat:@"%@%@",wbdataFolder,fileName_w];
    NSString *filePath_b = [NSString stringWithFormat:@"%@%@",wbdataFolder,fileName_b];
    
    
    NSData *wData = [[NSData alloc] initWithContentsOfFile:filePath_w];
    NSData *bData = [[NSData alloc] initWithContentsOfFile:filePath_b];
    
    [wData getBytes:weightP length:nofWeight*sizeof(float)];
    [bData getBytes:biasP length:nofBias*sizeof(float)];
    
    // 係数チェック
    //for (int ite=0;ite<nofWeight;ite++) {
    //    NSLog(@"weight%@:%f",kernelParamsBinaryName,weightP[ite]);
    //}
    //for (int ite=0;ite<nofBias;ite++) {
    //    NSLog(@"bias%@:%f",kernelParamsBinaryName,biasP[ite]);
    //}
    
    
    MPSCNNConvolutionDescriptor *convDesc = [MPSCNNConvolutionDescriptor
                                             cnnConvolutionDescriptorWithKernelWidth:kernelWidth
                                             kernelHeight:kernelHeight
                                             inputFeatureChannels:inputFeatureChannels
                                             outputFeatureChannels:outputFeatureChannels
                                             neuronFilter:neuronFilter];
    
    
    self = [super initWithDevice:device
           convolutionDescriptor:convDesc
                   kernelWeights:weightP
                       biasTerms:biasP
                           flags:MPSCNNConvolutionFlagsNone];
    self.destinationFeatureChannelOffset = 0;
    
    free(weightP);
    free(biasP);
    
    return self;
}


(3)結果比較
もちろん入力は同じ。

resutl=[[0.69752 0.10237998 1.01858 ]]
↑Keras側の実行結果

q0=0.697416,q1=0.102600,q2=1.018707
↑MPS側の結果

いつものことながら、微妙に違うけどまあこんなもんかな。
とにかくこれでKerasの学習で得た係数をMPSで使い、同じ計算をすることができた。







Copyright ©頭と尻尾はくれてやる!. Powered by FC2 Blog. Template by eriraha.