AttributeError: Layer has no inbound nodes

Lupin·2023년 6월 14일
0

AI

목록 보기
2/2

현재 대학원 졸업을 위해 시각화 실험을 하고 있다.
나의 목적은 t-SNE를 사용한 학습된 모델의 시각화가 목적이었다.
따라서 분류 레이어 이전까지(gap 전까지)의 레이어만을 사용해 결과물을 확보하고자 하였다.
그러던 중 발견했던 에러와 해결방법을 공유하고자 한다.

진행사항 전부를 나열했으니 해결방법을 바로 보고 싶다면,
[03. 해결방법]부터 보면 된다.

01. 모델구성


나의 모델 구성이다.
졸업논문 내용이라 모델 코드를 공개할 수 없는 점 양해 부탁드린다.
(대부분의 내용을 삭제한 모델 코드이며, 포스팅에 지장은 없다.)

class Model(tf.keras.Model):

  def __init__(self, num_classes):
    super().__init__()
    
    self.conv = Sequential([
      
    ])
    
    self.module =     # tensorflow.keras.layers가 아닌 custom layer
    
    self.gap = GlobalAveragePooling1D()
    self.dropout = Dropout(0.2)
    self.classifier = Dense(num_classes, activation='softmax')
    
    
  def call(self, inputs):
    x = inputs
    
    x = self.conv(x)
    x = self.module(x)
    
    gap = self.gap(capsule)
    drop = self.dropout(gap)
    
    output_softmax = self.classifier(drop)
    return output_softmax

02. 다양한 시도


다음과 같은 코드를 사용해 레이어 슬라이싱을 진행했다.

# 이미 학습된 모델을 불러온다.
origin = Model(len(CLASS_LABELS))
origin.build(input_shape=x_test.shape)
origin.load_weights(h5_path)

model = tf.keras.Model(inputs=origin.layers[0].input, outputs=origin.layers[-4].output)    # error

그랬더니 마지막 줄에서 에러가 발생했다.
(나머지는 고정된 문자열이고, 'module'은 본인이 설정한 레이어의 변수 이름으로 값이 바뀐다.)
AttributeError: Layer module has no inbound nodes.

stackoverflow, tensorflow github issue page를 살펴보니,
tensorflow-2.x 버전에서 비슷한 문제를 겪은 사람들이 많이 있었다.

발견했던 몇 가지 해결 방법은 다음과 같다.

  • origin.layers[-4].outputorigin.get_output_at(-4)로 대체
    • RuntimeError: The layer model has never been called and thus has no defined output.
  • K.Model([origin.layers[0].input], [origin.layers[-4].output])
    • RuntimeError: The layer model has never been called and thus has no defined output.

상기 언급된 해결방법은 keras의 레이어라면 해결 가능하다고 한다.
from tensorflow.keras.layers

그러나 직접 구성한 custom layer라면 이 방법은 통하지 않았다.

03. 해결방법


내가 참고한 자료들은 다음과 같다.
1. Saving & loading only the model's weights values
2. How do I get the weights of a layer in Keras?

우선 다음 두 API를 살펴보자.

  • tf.keras.layers.Layer.get_weights()
    • 해당 레이어와 관련된 훈련 가능한 가중치와 훈련 불가능한 가중치 모두 반환
    • numpy 배열의 리스트로 반환
  • tf.keras.layers.Layer.set_weights()
    • get_weights()로부터 반환된 numpy 배열에서 레이어의 가중치 설정
    • 가중치 값은 생성된 순서대로 전달되어야 함.

다시 말해,
get_weights()로 가중치와 바이어스 값을 구해서,
set_weights()로 가중치와 바이어스 값을 덮어 씌우면 된다.

  1. 원하는 레이어까지 남겨두고 나머지를 지운 모델을 구성한다.
### 원본 모델
class Model(tf.keras.Model):

  def __init__(self, num_classes):
    super().__init__()
    
    self.conv = Sequential([
      
    ])
    
    self.module =     # tensorflow.keras.layers가 아닌 custom layer
    
    self.gap = GlobalAveragePooling1D()
    self.dropout = Dropout(0.2)
    self.classifier = Dense(num_classes, activation='softmax')
    
    
  def call(self, inputs):
    x = inputs
    
    x = self.conv(x)
    x = self.module(x)
    
    gap = self.gap(capsule)
    drop = self.dropout(gap)
    
    output_softmax = self.classifier(drop)
    return output_softmax
   

## 슬라이싱을 위한 모델 구조 선언
## module 이후의 레이어가 전부 지워진 모습을 볼 수 있다.
class Model2(tf.keras.Model):

  def __init__(self, num_classes):
    super().__init__()
    
    self.conv = Sequential([
      
    ])
    
    self.module =     # tensorflow.keras.layers가 아닌 custom layer
    
    
  def call(self, inputs):
    x = inputs
    
    x = self.conv(x)
    x = self.module(x)
    
    return x
  1. 학습된 모델과 가중치 불러오기
  • load_weights()는 가중치만 저장되어 있다.
  • 따라서 해당 가중치를 이용해 fit()하거나 predict()를 하려면 위와 같이 모델 구조를 똑같이 구성해야 한다.
  • 이렇게 해야 저장된 가중치를 순서대로 붙여넣을 수 있기 때문이다.
    • 즉, 모델의 구성 순서를 원본 코드와 달리하면 안된다.
  • model.build(input_shape)를 반드시 해줘야 함.
    • subclassing model은 별도의 input 레이어가 없기 때문에 이를 알려줘야 함.
origin = Model(len(CLASS_LABELS))
origin.build(input_shape=data.shape)
origin.load_weights(h5_path)
  1. 학습된 모델의 가중치 확보
  • 앞서 언급한 get_weights() 사용
  • 단, 모델 전부가 아니라, 레이어마다 가중치를 확보한다.
  • layer[0].get_weights()와 같이 코드를 작성하면 0번째 위치에 있는 레이어의 가중치를 얻을 수 있다.
def get_layers_weights(origin_model, until):
    """get weights of model

    Args:
        origin_model (keras.Model): Original model (+weights)
        until (int): Index of wanted final layer

    Returns:
        list: weights for each layer
    """
    
    ret = []
    
    copy_layers = origin_model.layers[:until]
    for cur_layer in copy_layers:
        ret.append(cur_layer.get_weights())
    
    return ret
    
origin_layers_weights = get_layers_weights(origin, until=-3)
  1. 슬라이싱 모델 선언
test_model = Model2(len(CLASS_LABELS))
test_model.build(input_shape=data.shape)
  1. 슬라이싱 모델 가중치 덮어쓰기
test_model_layers = test_model.layers
for i, cur_test_layer in enumerate(test_model_layers):
    cur_test_layer.set_weights(origin_layers_weights[i])
  1. 결과값
  • 드디어 결과를 볼 수 있다.
predictions = test_model(x_test, training=False)
print(predictions)

result.png

4. 꿀팁

  • 모델의 뼈대는 vscode에서 Tensorflow 2.0 Snippets extention을 설치하면 매우 간단히 만들 수 있다.

    ## tf:ctrl:model   이걸 입력하면 윈도우 박스에 model block이 보일 것이다.
    ## enter 키를 입력하면 아래와 같은 구조가 만들어진다.
    
    class MyModel(tf.keras.Model):
    
      def __init__(self):
        super(MyModel, self).__init__()
        self.dense1 = tf.keras.layers.[Dense Like Layer]
    
      def call(self, inputs):
        x = self.dense1(inputs)
        return x

0개의 댓글