프로젝트/나무위키문서추천기

tensorflow.js custom layer 적용하기

kmikey1004 2021. 9. 23. 09:13

 크롬 확장프로그램에 Tensorflow.js(이하 tfjs) 를 사용하여 keras 모델을 적용하던 중 한가지 문제점을 직면하였다. Keras의 AveragePooling1D 레이어가 tensorflow 2.6 버전을 기준으로 Masking을 지원하지만,  tfjs 3.9 버전에서는 Masking을 지원하지않는다. 따라서 Masking 을 지원하는 MeanPool 레이어를 구현하여야한다.

Custom Layer

class MyDenseLayer(tf.keras.layers.Layer):
  def __init__(self, num_outputs):
    super(MyDenseLayer, self).__init__()
    self.num_outputs = num_outputs

  def build(self, input_shape):
    self.kernel = self.add_weight("kernel",
                                  shape=[int(input_shape[-1]),
                                         self.num_outputs])

  def call(self, inputs):
    return tf.matmul(inputs, self.kernel)

 우선 Keras Docs(사용자 정의 층  |  TensorFlow Core)에서 설명하는 custom layer 는 위와 같다. 여기서 필수적으로 init 과 call 을 작성해주어야하며, 학습이 필요한 Layer의 경우 build 또한 작성해주어야한다. 우리가 작성하려는 MeanPool 레이어는 인풋을 받아 마스크가 있다면 마스킹을 한 뒤 평균을 구하는 레이어로 다음과 같이 작성할 수 있다.

class MeanPool(tf.keras.layers.Layer):
    def __init__(self, **kwargs):
        self.supports_masking = True
        super(MeanPool, self).__init__(**kwargs)

    def compute_mask(self, input, input_mask=None):
        # do not pass the mask to the next layers
        return None

    def call(self, x, mask=None):
        if mask is not None:
          mask = tf.cast(mask, x[0].dtype)
          mask = tf.expand_dims(mask, 2 )
          x *= mask
          return tf.reduce_sum(
              x, axis=1) / tf.reduce_sum(
              mask, axis=1)
        else:
          return tf.reduce_mean(x, axis=1)

    def compute_output_shape(self, input_shape):
          # remove temporal dimension
          return (input_shape[0], input_shape[2])

 해당 코드는 Keras에서 지원하는 AveragePooling1D 레이어를 적당히 수정한 것이다. 여기서 compute_output_shape는 인풋이 해당 레이어를 지나고 나서 아웃풋의 shape를 return 하는 method로 모델을 만들 때 내부적으로 계산에 쓰이니 필요하다. 하지만 이 레이어는 학습이 필요로 하지않으므로 build는 구현하지않았다. 현재 코드는 python으로 구현된 상태이니 이 것을 크롬 확장프로그램에서 사용하기 위해서는 javascript로 재작성 해주어야한다.

CustomLayer in Javascripts

class MeanPool extends tf.layers.Layer {
	constructor(config) {
		super(config);
		this.supportsMasking = true;
	}

    call(x, args){
      if (Object.keys(args).length > 0){

        return tf.tidy(() =>{
        var mask = tf.cast(args.mask, 'float32')
        mask = tf.expandDims(mask, 2)
        x = tf.mul(x[0], mask)

        return tf.div(tf.sum(x, 1), tf.sum(mask, 1))})
      }
      else
      	return tf.mean(x, 1)
    }


    computeOutputShape(input_shape){
      // remove temporal dimension
      return [input_shape[0], input_shape[2]]
    }

    static get className() {
      return 'MeanPool';
    }
}

 우리는 Mask를 사용할 것이므로 Construct에서 supportMasking을 활성화해주어야한다.( 꼭 필요한 것인지는 모르겠으나 tfjs의 마스크를 사용하는 layer들은 이를 활성화시킨다.) 다른 부분들은 크게 다를 것이 없으나 한가지 알아야할 것은 tfjs 에서는 tensor 간의 연산에서 기호를 사용할 수 없다. 즉, a * b 같은 문법은 사용할 수 없으니 tf.mul 을 사용하자. 이렇게 custom Layer을 작성하였다면, 다음 구문을 꼭 추가하여서 이런 Layer가 있음을 알리자

tf.serialization.registerClass(MeanPool);

결과

const dummy = tf.tensor([[[1, 2, 3], [0, 0, 0], [0, 0, 0]], [[1, 1, 1], [0, 0, 0], [3, 3, 3]]]);
const mask = tf.tensor([[true, true, false], [true, false, true]])

const Meanlayer = new MeanPool();

console.log(Meanlayer.apply(dummy).dataSync());
console.log(Meanlayer.apply([dummy], {mask : mask}).dataSync());

 간단한 더미 코드를 작성하였고, 알아야할 것은 레이어를 테스트 하기 위해서는 apply method를 호출하면된다. 또한, console.log를 통해서 tensor 내부 값을 알 수 없으니 dataSync를 사용하도록하자. 결과는 다음과 같다.

[[0.3333, 0.6666, 1], [1.3333, 1.3333, 1.3333]]
[[0.5, 1, 1.5], [2, 2, 2]]

잘 작동하는 것을 확인해볼 수 있다.