Cで画像の記憶!?連想記憶をホップフィールドネットワークをつくってみた
2023-12-19
azblob://2023/12/19/eyecatch/2023-12-19-storage-image-on-hopfield-000.jpg

ニューラルネットワークがつくりたい!!

というわけで謎の衝動に駆られたため作ります。

機械学習や深層学習はライブラリも豊富でそれらを使えば楽に動かすことができますが、今回は理論から学んでほぼ一から作っていこうと思います。

適当に二値画像に変換した画像を用意しましたので、これを記憶するホップフィールドネットワークを作りましょう。

ホップフィールドネットワークは連想記憶というものができて適当な情報(今回は画像)を入力すると記憶の中から近いものを出力してくれます。ノイズの除去に使えそう

記憶させる画像

今回はここの画像を使わせてもらいます。

一番小さいのが255*256のやつだったんでこれを雑に二値化しときました。

画像の読み込みまでCでやるのは環境作るのがめんどくさかったのであらかじめpythonで読み込んでファイルに書き出してお書き出しておくことにしました。

Pythonfrom PIL import Image
import numpy as np
import os


files = os.listdir(dir)

f = open("./sample/sample_data.txt", 'w')

for file in files:
    img = Image.open(dir + "/" + file)
    width, height = img.size

    img_pixels = np.array(img.convert('1'), int)

    img_pixels = np.concatenate(img_pixels)
    img_pixels = list(map(str, img_pixels))
    string = ''.join(img_pixels)

    f.write(string + "\n")

f.close()

画像データは容易できたのでホップフィールドを作っていきます。

実装

タイトルにある通りC言語で書いていきます。

Cでやる理由は処理の速さです。今回作るホップフィールドはかなり処理に時間がかかることが予想されるのでなるべく速くしたいっていうのが主な理由です。あとは単純に使い慣れてるからってのもありますが。

以下は作るときに参考にした記事です。

C#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#include "SFMT.h"
#include "SFMT.c"

#define NUM_UNIT 65536
// #define NUM_UNIT 256 * 256
#define NUM_SAMPLE 8
#define NUM_NOICE 8
#define NUM_STEP 200000

sfmt_t sfmt;
int seed = 10;
short int *net;

void InitNet(void);
void ReadData(short int *lxdata, short int *xdata);
void MemorizeImage(short int *data);
void RememberImage(short int *data);

int main(void)
{
    short int *lx;
    short int *x;
    
    int j, k;
    char str[NUM_UNIT + 1];
    FILE *fp;

    lx = (short int *)malloc(NUM_SAMPLE * NUM_UNIT * sizeof(short int));
    x = (short int *)malloc(NUM_NOICE * NUM_UNIT * sizeof(short int));
    
    net = (short int *)calloc(sizeof(short int), (size_t)NUM_UNIT * NUM_UNIT);

    sfmt_init_gen_rand(&sfmt, seed);

    ReadData(lx, x);

    InitNet();

    MemorizeImage(lx);

    if ((fp = fopen("result/result_data.txt", "w")) == NULL){
        printf("ファイルオープンに失敗\n");
        exit(1);
    }

    for (j = 0; j < NUM_NOICE; j++){
        RememberImage(x + j * NUM_UNIT);

        for (k = 0; k < NUM_UNIT; k++){
            str[k] = (*(x + k + j * NUM_UNIT) + 1) / 2 + '0';
        }
        str[NUM_UNIT] = '\0';

        fprintf(fp, "%s\n", str);
    }

    fclose(fp);

    free(lx);
    free(x);
    free(net);

    return (0);
}

// 初期化
void InitNet(void)
{
    int i, j;

    for (i = 0; i < NUM_UNIT; i++){
        for (j = 0; j < NUM_UNIT; j++){
            *(net + j + i * (size_t)NUM_UNIT) = 0;
        }
    }
}

void ReadData(short int *lxdata, short int *xdata)
{
    FILE *fp;
    char s[NUM_UNIT + 1];
    int j, i;

    if ((fp = fopen("sample/sample_data.txt", "r")) == NULL){
        printf("ファイルオープンに失敗\n");
        exit(1);
    }

    for (j = 0; j < NUM_SAMPLE; j++){
        fscanf(fp, "%s\n", s);
        if (strlen(s) != NUM_UNIT){
            printf("サンプルデータの読み込みに失敗。入力の数が違う。");
            exit(1);
        }
        for (i = 0; i < NUM_UNIT; i++){
            if (s[i] == '1'){
                *(lxdata + i + j * NUM_UNIT) = 1;
            }
            else if (s[i] == '0'){
                *(lxdata + i + j * NUM_UNIT) = -1;
            }
            else {
                printf("サンプルデータの読み込みに失敗。1,0以外のデータ。\n");
                exit(1);
            }
        }
    }
    fclose(fp);
    
    if ((fp = fopen("noice_image2/noice_data2.txt", "r")) == NULL){
        printf("ファイルオープンに失敗\n");
        exit(1);
    }

    for (j = 0; j < NUM_NOICE; j++){
        fscanf(fp, "%s\n", s);
        if (strlen(s) != NUM_UNIT){
            printf("サンプルデータの読み込みに失敗。入力の数が違う。");
            exit(1);
        }
        for (i = 0; i < NUM_UNIT; i++){
            if (s[i] == '1'){
                *(xdata + i + j * NUM_UNIT) = 1;
            }
            else if (s[i] == '0'){
                *(xdata + i + j * NUM_UNIT) = -1;
            }
            else {
                printf("サンプルデータの読み込みに失敗。1,0以外のデータ。\n");
                exit(1);
            }
        }
    }
    fclose(fp);
}

// 記憶
void MemorizeImage(short int *data)
{
    int n, i, j;
    
    for (n = 0; n < NUM_SAMPLE; n++){
        for (i = 0; i < NUM_UNIT; i++){
            for (j = 0; j < NUM_UNIT; j++){
                *(net + j + i * (size_t)NUM_UNIT) += (size_t)(*(data + i + n * NUM_UNIT) * *(data + j + n * NUM_UNIT));
            }
        }
    }
}

// 想起
void RememberImage(short int *data)
{
    long int net_input = 0;
    int step, i, j;

    for (step = 0; step < NUM_STEP; step++){
        net_input = 0;
        i = (size_t)((NUM_UNIT) * sfmt_genrand_real2(&sfmt));
        for (j = 0; j < NUM_UNIT; j++){
            net_input += *(net + j + (size_t)NUM_UNIT * i) * *(data + j);
        }
        if (net_input > 0){
            *(data + i) = 1;
        }
        else if (net_input < 0){
            *(data + i) = -1;
        }
    }
}

実行結果

さっそく作ったプログラムで実験してみましょう。

入力データとしてこのような画像を用意しました。ドーン

一部が欠けてしまった画像です。

これを入力して元の画像が思い出してくれるでしょうか。

10万回想起後

20万回想起後

50万回想起後

という感じできれいに思いだすことができました。やった

ちなみに一回実行するのに5分くらいかかった。意外と短いな。

最後に

次はボルツマンマシンをつくる。多分。気が向けば。