読者です 読者をやめる 読者になる 読者になる

Miyamoのブログ

技術ブログ(にしたい)

ゼロから作るDeep Learning 3章「ニューラルネットワーク」

 

前:ゼロから作るDeep Learning 2章「パーセプトロン」

ゼロから作るDeep Learning Pythonで学ぶディープラーニングの理論と実装を進めていく.第3回は3章「ニューラルネットワーク

f:id:miyamo765:20170426230335j:plain

 

1.パーセプトロンからニューラルネットワーク

パーセプトロンでは,期待する出力を得るための重みの決定を人間が行なっていた.ニューラルネットワークデータから重みを自動で学習する.今回はニューラルネットワークの概要と識別を行う際の処理に焦点を置き,データから重みを学習する方法は次回.

ニューラルネットワークは下図のようなもの.一番左の列を入力層,一番右の列を出力層,中間の列を中間層と呼ぶ(隠れ層と呼ぶこともある).以下では入力層から出力層に向かって順に第0層,第1層,第2層と呼ぶ(Pythonでの実装時に都合が良いため).

f:id:miyamo765:20170429230347p:plain

ここで,パーセプトロンについて再び考える.

f:id:miyamo765:20170429231628p:plain

このパーセプトロンを数式で表すと次のようになる.

{ \displaystyle y = \begin{cases}0 \, (b + w_1x_1 + w_2x_2 \le 0)\\1 \, (b + w_1x_1 + w_2x_2 > 0) \end{cases}} \tag{1} \label{1}  

上図には図示されていないバイアスbを明示すると次のような図になる.

f:id:miyamo765:20170429231646p:plain

{ \displaystyle h(x)}という新しい関数を導入し,式\eqref{1}を次のように書き換える.

{ \displaystyle y = h(b + w_1x_1 + w_2x_2)} \tag{2} \label{2} 

{ \displaystyle h(x) = \begin{cases}0 \, (x \le 0)\\1 \, (x > 0) \end{cases}} \tag{3} \label{3} 

式\eqref{2}は,入力信号の総和が{ \displaystyle h(x)}という関数によって変換され,その結果が出力{ \displaystyle y}になるということを表している.式\eqref{3}は関数{\displaystyle h(x)}の定義である.式\eqref{2}と式\eqref{3}で式\eqref{1}と同じ動作をする.

{\displaystyle h(x)}のように,入力信号の総和を出力信号に変換する関数のことを活性化関数(activation function)と呼ぶ.活性化関数には入力信号の総和がどのように活性化するか(発火するか)を決定する役割がある.

続いて,式\eqref{2}を次のように書き換える.

{ \displaystyle a = b + w_1x_1 + w_2x_2} \tag{4} \label{4} 

{ \displaystyle y = h(a)} \tag{5} \label{5} 

入力信号の総和をいったん{\displaystyle a}と置き,{\displaystyle a}{\displaystyle h()}で変換され{\displaystyle y}が出力される,という流れになる.

式\eqref{4}と式\eqref{5}による活性化関数のプロセスをニューロン内で明示すれば次のような図になる.

f:id:miyamo765:20170429234342p:plain

 

2.活性化関数

パーセプトロンは式\eqref{3}で表される活性化関数を用いているが,これは閾値(今回は0)を境にして出力が切り替わる関数で,「ステップ関数」と呼ばれる.活性化関数をステップ関数から別の関数に変更することで,ニューラルネットワークへと進むことができるニューラルネットワークではシグモイド関数と呼ばれる関数が古くから利用されてきた.最近は「ReLU関数」が主に用いられる.

2.1 ステップ関数

式\eqref{3}のステップ関数をPythonで実装し,グラフを表示する.

import numpy as np
import matplotlib.pyplot as plt

def step_function(x):
    return np.array(x > 0, dtype=np.int)
	
x = np.arange(-5.0, 5.0 , 0.1)
y = step_function(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1)
plt.show()

関数step_fuction()では,引数であるNumPy配列の対してx > 0という演算を行い,生成されるブーリアン型のNumPy配列の要素をint型に変換している(Pythonではブーリアン型からint型への変換では,Trueが1に,Falseが0になる).

f:id:miyamo765:20170430154035p:plain

 

2.2 シグモイド関数

シグモイド関数は次の式で表される.

{ \displaystyle h(x) = \frac{1}{1 + exp(-x)}} \tag{6} \label{6} 

import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x):
    return 1 / (1 + np.exp(-x))
	
x = np.arange(-5.0, 5.0 , 0.1)
y = sigmoid(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1)
plt.show()

f:id:miyamo765:20170430154924p:plain

ステップ関数とシグモイド関数の違いは次のような点.

・ステップ関数

 -0を境に出力が急激に0から1に変化

 -0か1のどちらかの値しか返さない

シグモイド関数

 -0から1まで滑らかに出力が変化

 -開区間(0, 1)における実数値を返す

 

2.3 非線形関数

ステップ関数とシグモイド関数は両者とも非線形関数である(線形関数は入力の定数倍を出力として返す関数.直線になる).

ニューラルネットワークでは活性化関数に非線形関数を用いる必要がある.線形関数を用いると,層を深くすることの意味がなくなってしまう(どんなに層を深くしても,それと同じことを行う「隠れ層のないネットワーク」が存在してしまう).例えば,{ \displaystyle h(x) = cx}を活性化関数とし,3層のネットワークを考えると,{ \displaystyle h(x) = cx}が3回適用されることになる({ \displaystyle y(x) = c(c(c(x)))}).しかしこれは一つの活性化関数{ \displaystyle s(x) = ax}(ただし,{ \displaystyle a = c^3})で表せてしまう.

 

2.4 ReLU関数

ニューラルネットワークでは古くからシグモイド関数が利用されてきたが,最近ではReLU関数(Rectified Linear Unit,正規化線形関数)という関数が用いられている.Yann LeCunやGeoffrey Hintonらの論文では2015年5月現在これが最善らしい.入力が0を超えていれば入力をそのまま出力し,0以下なら0を出力する関数.

{ \displaystyle h(x) = \begin{cases}0 \, (x \le 0)\\x \, (x > 0) \end{cases}} \tag{7} \label{7} 

import numpy as np
import matplotlib.pyplot as plt

def relu(x):
    return np.maximum(0, x)
	
x = np.arange(-5.0, 5.0 , 0.1)
y = relu(x)
plt.plot(x, y)
plt.ylim(-0.1, 5.5)
plt.show()

f:id:miyamo765:20170430162339p:plain

 

3.多次元配列の計算

ニューラルネットワークの実装に必要なNumPyの多次元配列の計算について.

行列の積を考える.行列の積は行列Aの列数と行列Bの行数が等しくなければ計算できない行列Aをij,行列Bをjk列とすると,計算結果である行列Cはik列の行列になる

#行列A(2行3列)と行列B(3行2列)の積
>>> A = np.array([1,2,3], [4,5,6])
>>> A.shape
(2, 3)
>>> B = np.array([[1,2], [3,4], [5,6]])
>>> B.shape
(3, 2)
>>> np.dot(A, B)
array([[22, 28],
       [49, 64]])

#行列A(2行3列)と行列C(2行2列)の積(エラーになる)
>>> C = np.array([[1,2], [3,4]])
>>> C.shape
(2, 2)
>>> A.shape
(2, 3)
>>> np.dot(A, C)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: shapes (2,3) and (2,2) not aligned: 3 (dim 1) != 2 (dim 0)

行列の積はNumPyの関数np.dot()で計算する.

 

4.ニューラルネットワークの実装

まず下図の簡単なニューラルネットワークを実装してみる.

f:id:miyamo765:20170430225909p:plain

{ \displaystyle  \mathbf{X} = \begin{pmatrix}x_1 \,\,\, x_2 \end{pmatrix} = \begin{pmatrix}1 \,\,\, 2 \end{pmatrix}} 

{ \displaystyle  \mathbf{W} = \begin{pmatrix}w_{11} \,\,\, w_{12} \,\,\, w_{13}\\ w_{21} \,\,\, w_{22} \,\,\, w_{23}\end{pmatrix} = \begin{pmatrix}1 \,\,\, 3 \,\,\, 5\\ 2 \,\,\, 4 \,\,\, 6\end{pmatrix}} 

{ \displaystyle  \mathbf{Y} = \begin{pmatrix}y_1 \,\,\, y_2 \,\,\, y_3\end{pmatrix}} 

>>> X = np.array([1,2])
>>> X.shape
(2,)
>>> W = np.array([[1,3,5], [2,4,6]])
>>> print(W)
[[1 3 5]
 [2 4 6]]
>>> W.shape
(2, 3)
>>> Y = np.dot(X, W)
>>> print(Y)
[ 5 11 17]

 

続いて3層ニューラルネットワークを実装する.活性化関数{ \displaystyle h()}シグモイド関数{ \displaystyle σ()}は恒等関数(入力をそのまま出力する関数)とする.

f:id:miyamo765:20170501230051p:plain

1.入力層(第0層)からひとつ目の隠れ層(第1層)

{ \displaystyle  \mathbf{A}^{(1)} = \mathbf{X}\mathbf{W}^{(1)} + \mathbf{B}^{(1)}, \,\,\, \mathbf{Z}^{(1)} = h(\mathbf{A}^{(1)})} 

{ \displaystyle  \mathbf{A}^{(1)} = \begin{pmatrix}a_1^{(1)} \,\,\, a_2^{(1)} \,\,\, a_3^{(1)}\end{pmatrix}} 

{ \displaystyle  \mathbf{X} = \begin{pmatrix}x_1 \,\,\, x_2 \end{pmatrix} = \begin{pmatrix}1.0 \,\,\, 0.5 \end{pmatrix}} 

{ \displaystyle  \mathbf{W}^{(1)} = \begin{pmatrix}w_{11}^{(1)} \,\,\, w_{12}^{(1)} \,\,\, w_{13}^{(1)}\\ w_{21}^{(1)} \,\,\, w_{22}^{(1)} \,\,\, w_{23}^{(1)}\end{pmatrix} = \begin{pmatrix}0.1 \,\,\, 0.3 \,\,\, 0.5\\ 0.2 \,\,\, 0.4 \,\,\, 0.6\end{pmatrix}} 

{ \displaystyle  \mathbf{B}^{(1)} = \begin{pmatrix}b_1^{(1)} \,\,\, b_2^{(1)} \,\,\, b_3^{(1)} \end{pmatrix} = \begin{pmatrix}0.1 \,\,\, 0.2 \,\,\, 0.5 \end{pmatrix}} 

 

2.ひとつ目の隠れ層(第1層)からふたつ目の隠れ層(第2層)

{ \displaystyle  \mathbf{A}^{(2)} = \mathbf{Z}^{(1)}\mathbf{W}^{(2)} + \mathbf{B}^{(2)}, \,\,\, \mathbf{Z}^{(2)} = h(\mathbf{A}^{(2)})} 

{ \displaystyle  \mathbf{A}^{(2)} = \begin{pmatrix}a_1^{(2)} \,\,\,a_2^{(2)}\end{pmatrix}} 

{ \displaystyle  \mathbf{W}^{(2)} = \begin{pmatrix}w_{11}^{(2)} \,\,\, w_{12}^{(2)}\\ w_{21}^{(2)} \,\,\, w_{22}^{(2)}\\ w_{31}^{(2)} \,\,\, w_{32}^{(2)}\end{pmatrix} = \begin{pmatrix}0.1 \,\,\, 0.4\\ 0.2 \,\,\, 0.5 \\ 0.3 \,\,\, 0.6\end{pmatrix}} 

{ \displaystyle  \mathbf{B}^{(2)} = \begin{pmatrix}b_1^{(2)} \,\,\, b_2^{(2)}\end{pmatrix} = \begin{pmatrix}0.1 \,\,\, 0.2\end{pmatrix}} 

 

3.ふたつ目の隠れ層(第2層)から出力層(第3層)

{ \displaystyle  \mathbf{A}^{(3)} = \mathbf{Z}^{(2)}\mathbf{W}^{(3)} + \mathbf{B}^{(3)}, \,\,\,\mathbf{Y} = σ(\mathbf{A}^{(3)})} 

{ \displaystyle  \mathbf{A}^{(3)} = \begin{pmatrix}a_1^{(3)} \,\,\,a_2^{(3)}\end{pmatrix}} 

{ \displaystyle  \mathbf{W}^{(3)} = \begin{pmatrix}w_{11}^{(3)} \,\,\, w_{12}^{(3)}\\ w_{21}^{(3)} \,\,\, w_{22}^{(3)}\end{pmatrix} = \begin{pmatrix}0.1 \,\,\, 0.3\\ 0.2 \,\,\, 0.4 \end{pmatrix}} 

{ \displaystyle  \mathbf{B}^{(3)} = \begin{pmatrix}b_1^{(3)} \,\,\, b_2^{(3)}\end{pmatrix} = \begin{pmatrix}0.1 \,\,\, 0.2\end{pmatrix}} 

 

import numpy as np

def init_network():
    network = {}
    network[ 'W1' ] = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
    network[ 'b1' ] = np.array([0.1, 0.2, 0.3])
    network[ 'W2' ] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
    network[ 'b2' ] = np.array([0.1, 0.2])
    network[ 'W3' ] = np.array([[0.1, 0.3], [0.2, 0.4]])
    network[ 'b3' ] = np.array([0.1, 0.2])
	
    return network

def forward(network, x):
    W1, W2, W3 = network[ 'W1' ], network[ 'W2' ], network[ 'W3' ]
    b1, b2, b3 = network[ 'b1' ], network[ 'b2' ], network[ 'b3' ]
	
    a1 = np.dot(x, W1) + b1
    z1 = sigmoid(a1)
    a2 = np.dot(z1, W2) + b2
    z2 = sigmoid(a2)
    a3 = np.dot(z2, W3) + b3
    y = identify_function(a3)
	
    return y
	
def sigmoid(x):
    return 1 / (1 + np.exp(-x))
	
def identify_function(x):
    return x
	
network = init_network()
x = np.array([1.0, 0.5])
y = forward(network, x)
print(y)

init_network()で重みとバイアスの初期化をしディクショナリ型変数networkに格納している.

 

5.出力層の設計

ニューラルネットワークは分類問題と回帰問題の両方に用いることが可能.

・分類問題

 データがどのクラスに属するかを判別する問題.

 例)人の写真から男性か女性のどちらかを判断する.

・回帰問題

 データから(連続的な)数値の予測を行う問題.

 例)人の写真から体重を予測する.

一般に,回帰問題では恒等関数,分類問題ではソフトマックス関数と呼ばれる関数を用いる

 

5.1 ソフトマックス関数

ソフトマックス関数は次の式で表される.

{ \displaystyle y_{k} = \frac{exp(a_k)}{\sum_{i=1}^n exp(a_i)} \tag{8} \label{8}}

{ \displaystyle n}個ある出力層の{ \displaystyle k}番目の出力{ \displaystyle y_{k}}を求める式である.出力の各ニューロンがすべての入力信号から影響を受ける.

f:id:miyamo765:20170503125626p:plain

Pythonの関数として次のように定義してみる.

def softmax(a):
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a

    return y

しかし,入力信号の値が大きくなるとオーバーフローしてしまう.

>>> a = np.array([1010, 1000, 990])
>>> np.exp(a) / np.sum(np.exp(a))
__main__:1: RuntimeWarning: overflow encountered in exp
__main__:1: RuntimeWarning: invalid value encountered in true_divide
array([ nan,  nan,  nan])

これを改善するために,式\eqref{8}を次のように変形する.

{ \displaystyle \begin{split} y_{k} = \frac{exp(a_k)}{\sum_{i=1}^n exp(a_i)} =  \frac{Cexp(a_k)}{C\sum_{i=1}^n exp(a_i)}\\ = \frac{exp(a_k + logC)}{\sum_{i=1}^n exp(a_i + logC)}\\ =\frac{exp(a_k + C')}{\sum_{i=1}^n exp(a_i + C')}    \end{split} \tag{9} \label{9} }

{ \displaystyle \TeX}split環境でイコールで揃える意味のアンパサンドが&amp;に強制置換されるので揃えられない問題.

任意の定数を分子と分母の両方にかけて指数関数の中に移動させることで,ソフトマックスの指数関数の計算時には何らかの定数を足し引きしても結果は変わらないということがわかる.定数の値としては入力信号の中の最大の値を用いることが一般的.

>>> c = np.max(a)
>>> a - c
array([  0, -10, -20])
>>> np.exp(a-c) / np.sum(np.exp(a-c))
array([  9.99954600e-01,   4.53978686e-05,   2.06106005e-09])

実装は次のようになる.

def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a-c) #オーバーフロー対策
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a

    return y

出力は次のように計算できる.

>>> a = np.array([0.3, 2.9, 4.0])
>>> y = softmax(a)
>>> print(y)
[ 0.01821127  0.24519181  0.73659691]
>>> np.sum(y)
1.0

ソフトマックス関数の出力は0から1の間の実数になり,総和は1になる.これにより,出力を確率として解釈することができる(この場合一番確率が高いのは3番目なので,答えは3番目のクラス,となる).

ソフトマックス関数を適用しても各要素の大小関係は変わらないので,分類問題では出力層のソフトマックス関数は省力するのが一般的出力層にソフトマックス関数を用いるのはニューラルネットワークの学習時に関係してくる機械学習の問題を解く手順は「学習」と「推論」の2段階あり,ここでは学習が終了したものとしている).

 

6.手書き数字認識

手書き数字画像の分類を行う(学習は完了しているとする).

6.1 MNISTデータセット

MNISTは機械学習の分野で有名なデータセットで,手書き数字の画像セットである.0から9までの数字画像で,訓練画像が60,000枚,テスト画像が10,000枚用意されている.28×28のグレー画像で各ピクセルは0〜255までの値をとる.それぞれの画像データには対応する数字のラベルが与えられている.

MNISTのダウンロードから画像データへの変換,NumPy配列への格納には著者提供スクリプトmnist.pyを用いる.導入も自分でやった方が色々分かっていいんだろうけどとりあえず今はパス.カレントディレクトリがch03,親ディレクトリのdatasetフォルダにmnist.pyが存在しているとする.以下のようにMNISTデータを読み込む.

import sys, os
sys.path.append(os.pardir)
from dataset.mnist import load_mnist

(x_train, t_train), (x_test, t_test) = \
    load_mnist(flatten=True, normalize=False)
	
print(x_train.shape)	#(60000, 784)
print(t_train.shape)	#(60000, )
print(x_test.shape)	#(10000, 784)
print(t_train.shape)	#(10000, )

load_mnist関数は,「(訓練画像,訓練ラベル),(テスト画像,テストラベル)」という形式で,読み込んだMNISTデータを返す.load_mnist(normalize=True, flatten=True, one_hot_label=False)のように3つの引数を設定できる.normalizeは入力画像のピクセル地を0.0〜1.0の値に正規化するかどうか,flattenは入力画像を一次元配列にするかどうか,one_hot_labelはラベルをone-hot表現するかどうかをそれぞれ設定する.one-hot表現とは,[0, 0, 1, 0, 0, 0, 0, 0, 0, 0]のように正解となるラベルを1としそれ以外を0で表した配列(この場合正解ラベルは2)のこと. 

MNIST画像を表示してみる.画像の表示にはPIL(Python Image Library)モジュールを使用する.

import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist
from PIL import Image

def img_show(img):
    pil_img = Image.fromarray(np.uint8(img))
    pil_img.show()

(x_train, t_train), (x_test, t_test) = \
    load_mnist(flatten=True, normalize=False)
	
img = x_train[0]
label = t_train[0]
print(label)

print(img.shape)
img = img.reshape(28, 28)
print(img.shape)

img_show(img)

f:id:miyamo765:20170504162642p:plain

faltten=Trueとして読み込んだ画像を28×28のサイズに再変形するためにreshape()を用いている.また,Image.fromarray()によってNumPy配列として格納されたデータをPIL用のオブジェクトに変換している.

 

6.2 ニューラルネットワークの推論処理

MNISTデータセットに対して推論処理を行うニューラルネットワークを実装する.

実装するネットワークについて

・入力層:784個(画像サイズの28×28=784より)

・出力層:10個(数字の0〜9の10クラスに対応)

・隠れ層2つ

 -ひとつ目:50個

 -ふたつ目:100個

・pickleファイルsample_weight.pklの学習済みの重みパラメータを使用

(pickle:プログラムの実行中のオブジェクトをファイルとして保存する機能)

参考:Python: オブジェクトを漬物 (Pickle) にする - CUBE SUGAR CONTAINER

import sys, os
sys.path.append(os.pardir)
import numpy as np
import pickle
from dataset.mnist import load_mnist

def get_data():
    (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, flatten=True, one_hot_label=False)
    return x_test, t_test


def init_network():
    with open("sample_weight.pkl", 'rb') as f:
        network = pickle.load(f)
    return network


def predict(network, x):
    W1, W2, W3 = network['W1'], network['W2'], network['W3']
    b1, b2, b3 = network['b1'], network['b2'], network['b3']

    a1 = np.dot(x, W1) + b1
    z1 = sigmoid(a1)
    a2 = np.dot(z1, W2) + b2
    z2 = sigmoid(a2)
    a3 = np.dot(z2, W3) + b3
    y = softmax(a3)

    return y
    
    
def sigmoid(x):
    return 1 / (1 + np.exp(-x)) 
    
    
def softmax(x):
    if x.ndim == 2:
        x = x.T
        x = x - np.max(x, axis=0)
        y = np.exp(x) / np.sum(np.exp(x), axis=0)
        return y.T 

    x = x - np.max(x)
    return np.exp(x) / np.sum(np.exp(x))


x, t = get_data()
network = init_network()
accuracy_cnt = 0
for i in range(len(x)):
    y = predict(network, x[i])
    p = np.argmax(y)
    if p == t[i]:
        accuracy_cnt += 1

print("Accuracy:" + str(float(accuracy_cnt) / len(x)))

MNISTデータセットの取得,ネットワークの生成の後,画像データを1枚ずつ取り出して分類し,最も確率の高い要素のインデックスが正解ラベルと等しい割合を求める.

実行結果は次のようになる.

python neuralnet_mnist.py
Accuracy:0.9352

93.52%正しく分類することができたことを表している.

 

6.3 バッチ処理

インタプリタで先のネットワークの各層の重みの形状を出力してみる.

>>> x, _ = get_data()
>>> network = init_network()
>>> W1, W2, W3 = network[ 'W1' ], network[ 'W2' ], network[ 'W3' ]
>>> x.shape
(10000, 784)
>>> x[0].shape
(784,)
>>> W1.shape
(784, 50)
>>> W2.shape
(50, 100)
>>> W3.shape
(100, 10)

多次元配列の対応する次元の要素数が一致していることがわかる. 

一枚あたりの処理時間を短縮するために画像を複数枚まとめて入力するバッチ処理をしたい100枚をまとめて入力することを考えると,xの形状は100×784となり,出力データの形状は100×10となる(行列の積の定義から導かれる).変更は以下のようになる.

x, t = get_data()
network = init_network()
batch_size = 100
accuracy_cnt = 0
for i in range(0, len(x), batch_size):
    x_batch = x[i:i+batch_size]
    y_batch = predict(network, x_batch)
    p= np.argmax(y_batch, axis=1)
    accuracy_cnt += np.sum(p == t[i:i+batch_size])

print("Accuracy:" + str(float(accuracy_cnt) / len(x)))

range()で0からbatch_size分ずつ増加する配列を生成しその回数分ループ(つまり100回)し,先頭から100枚ずつバッチとして取り出す.argmax()axis=1という引数を与えて,100×10の配列で1次元目(10の方)ごとに最も確率の高いインデックスを取り出している.

演算時に生成されるブーリアン型配列の利用が実装で多様されててなかなか慣れない…

 

参考にしたサイト

活性化関数 - Wikipedia

TensorFlowコトハジメ 手書き文字認識(MNIST)による多クラス識別問題 - デジタル・デザイン・ラボラトリーな日々

 

NumPy 配列の基礎 — 機械学習の Python との出会い

Pythonスクリプト実行後に対話的に操作したい - 唯物是真 @Scaled_Wurm