web-dev-qa-db-ja.com

Kerasを使用してCNNで可変サイズの入力を処理する方法

MNISTデータベースで通常の分類を実行しようとしていますが、数字がランダムに切り取られています。画像は次の方法でトリミングされます:最初/最後および/または行/列をランダムに削除します。

Keras(およびTensorflowバックエンド)を使用した畳み込みニューラルネットワークを使用して、畳み込みと通常の分類を実行したいと思います。

入力は可変サイズであり、それを機能させることができません。

数字を切り取る方法は次のとおりです

import numpy as np
from keras.utils import to_categorical
from sklearn.datasets import load_digits

digits = load_digits()

X = digits.images
X = np.expand_dims(X, axis=3)

X_crop = list()
for index in range(len(X)):
    X_crop.append(X[index, np.random.randint(0,2):np.random.randint(7,9), np.random.randint(0,2):np.random.randint(7,9), :])
X_crop = np.array(X_crop)

y = to_categorical(digits.target)

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X_crop, y, train_size=0.8, test_size=0.2)

そして、これは私が使用したいモデルのアーキテクチャです

from keras.layers import Dense, Dropout
from keras.layers.convolutional import Conv2D
from keras.models import Sequential

model = Sequential()

model.add(Conv2D(filters=10, 
                 kernel_size=(3,3), 
                 input_shape=(None, None, 1), 
                 data_format='channels_last'))

model.add(Dense(128, activation='relu'))
model.add(Dropout(0.2))

model.add(Dense(10, activation='softmax'))


model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])

model.summary()

model.fit(X_train, y_train, epochs=100, batch_size=16, validation_data=(X_test, y_test))
  1. 誰かが私のニューラルネットワークで可変サイズの入力を処理する方法を知っていますか?

  2. そして、分類を実行する方法は?

15
Thomas Grsp

TL/DR-ポイント4に移動

それで-ポイントに達する前に-あなたのネットワークに関するいくつかの問題を修正しましょう:

  1. アクティベーションのためにネットワークは機能しません:_categorical_crossentropy_では、softmaxアクティベーションが必要です:

    _model.add(Dense(10, activation='softmax'))
    _
  2. 空間テンソルのベクトル化:ダニエルが述べたように-いくつかの段階で、ベクトルを空間(画像)からベクトル化(ベクトル)に切り替える必要があります。現在-Denseを_Conv2D_からの出力に適用することは_(1, 1)_畳み込みと同等です。基本的に-ネットワークからの出力は空間的であり、ベクトル化されていないため、次元の不一致が発生します(ネットワークを実行するか、model.summary()を確認することで確認できます。変更するには、 _GlobalMaxPooling2D_ または _GlobalAveragePooling2D_ 例:

    _model.add(Conv2D(filters=10, 
                 kernel_size=(3, 3), 
                 input_shape=(None, None, 1),
                 padding="same",
                 data_format='channels_last'))
    model.add(GlobalMaxPooling2D())
    model.add(Dense(128, activation='relu'))
    model.add(Dropout(0.2))
    
    model.add(Dense(10, activation='softmax'))
    _
  3. 連結されたnumpy配列は同じ形状である必要があります:_X_crop_の形状をチェックすると、空間マトリックスではありません。異なる形状のマトリックスを連結したためです。悲しいことに-_numpy.array_の形状を固定する必要があるため、この問題を克服することは不可能です。

  4. ネットワークを異なる形状の例で訓練する方法:これを行う上で最も重要なことは、2つのことを理解することです。 1つ目-単一のバッチでは、すべての画像が同じサイズである必要があります。 2番目-fitを複数回呼び出すのは悪い考えです-内部モデルの状態をリセットするためです。そのため、ここで何をする必要があります:

    a。 単一バッチを切り取る関数を書く-例えばマトリックスを指定した_get_cropped_batches_generator_は、バッチを切り取り、ランダムにトリミングします。

    b。 _train_on_batch_ メソッドを使用します。コードの例を次に示します。

    _from six import next
    
    batches_generator = get_cropped_batches_generator(X, batch_size=16)
    losses = list()
    for Epoch_nb in range(nb_of_epochs):
        Epoch_losses = list()
        for batch_nb in range(nb_of_batches):
            # cropped_x has a different shape for different batches (in general)
            cropped_x, cropped_y = next(batches_generator) 
            current_loss = model.train_on_batch(cropped_x, cropped_y)
            Epoch_losses.append(current_loss)
        losses.append(Epoch_losses.sum() / (1.0 * len(Epoch_losses))
    final_loss = losses.sum() / (1.0 * len(losses))
    _

したがって-上記のコードへのコメント:最初に、 _train_on_batch_ はニースkerasプログレスバーを使用しません。 (特定のバッチに対して)単一の損失値を返します。そのため、損失を計算するロジックを追加しました。 Progbar コールバックも使用できます。 2番目-_get_cropped_batches_generator_を実装する必要があります-私の答えをもう少し明確にするためのコードを書いていません。実装方法について別の質問をすることができます。最後に、sixを使用して_Python 2_と_Python 3_の間の互換性を保ちます。

23
Marcin Możejko

通常、Denseレイヤーを含むモデルは、出力も可変でない限り、可変サイズの入力を持つことはできません。しかし、回避策および_GlobalMaxPooling2D_を使用した他の回答も参照してください-回避策は_GlobalAveragePooling2D_と同等です。これらは、高密度レイヤーの前に可変サイズを排除し、空間次元を抑制することができるレイヤーです。

画像分類の場合、モデル外の画像のサイズを変更することができます。

画像がnumpy形式の場合、次のようにサイズを変更します。

_from PIL import Image
im = Image.fromarray(imgNumpy)
im = im.resize(newSize,Image.LANCZOS) #you can use options other than LANCZOS as well
imgNumpy = np.asarray(im)
_

なぜ?

畳み込み層には、フィルターとしての重みがあります。静的なフィルターサイズがあり、同じフィルターが何度も画像に適用されます。

ただし、密度の高いレイヤーには、入力に基づいた重みがあります。入力が1つある場合、重みのセットがあります。 2つの入力がある場合、2倍の重みがあります。ただし、ウェイトはトレーニングする必要があります。ウェイトの量を変更すると、モデルの結果が確実に変わります。

@Marcinがコメントしたように、密なレイヤーの入力形状が_(batchSize,inputFeatures)_の2つの次元を持つ場合、私が言ったことは真実です。

しかし、実際には、ケラスの高密度レイヤーは、より多くの次元の入力を受け入れることができます。これらの追加の次元(畳み込み層から生じる)はサイズが異なる場合があります。しかし、これにより、これらの高密度レイヤーの出力のサイズも可変になります。

それにもかかわらず、最後には分類のために固定サイズが必要になります:10クラスとそれだけです。寸法を縮小するために、人々はFlattenレイヤーを使用することが多く、ここにエラーが表示されます。


潜在的な魚の回避策(テストされていない):

モデルの畳み込み部分の最後で、ラムダ層を使用して、すべての値を固定サイズのテンソルに凝縮します。おそらく側面の平均を取り、チャネルを維持します(チャネルは可変ではありません)

最後の畳み込み層が次のとおりであると仮定します。

_model.add(Conv2D(filters,kernel_size,...))
#so its output shape is (None,None,None,filters) = (batchSize,side1,side2,filters)
_

空間次元を凝縮し、フィルター次元のみを保持するためにラムダレイヤーを追加しましょう。

_import keras.backend as K

def collapseSides(x):

    axis=1 #if you're using the channels_last format (default)   
    axis=-1 #if you're using the channels_first format

    #x has shape (batchSize, side1, side2, filters)
    step1 = K.mean(x,axis=axis) #mean of side1
    return K.mean(step1,axis=axis) #mean of side2

    #this will result in a tensor shape of (batchSize,filters)
_

フィルターの量は固定されているため(Noneディメンションをキックアウトしました)、おそらく密なレイヤーが機能するはずです。

_model.add(Lambda(collapseSides,output_shape=(filters,)))
model.add(Dense.......)
.....
_

これがおそらく機能するためには、最後の畳み込み層のフィルターの数を少なくとも10にすることをお勧めします。

これにより、input_shape=(None,None,1)を作成できます

これを行う場合は、バッチごとに固定サイズの入力データのみを渡すことができることに注意してください。そのため、データ全体を小さなバッチに分割する必要があります。各バッチはすべて同じサイズの画像を持ちます。こちらをご覧ください: Kerasはトレーニングデータの形状を誤って解釈します

3
Daniel Möller