퍼셉트론은 프랑크 로젠블라트(Frank Rosenblatt)가 고안한 인공 신경망으로 실제 뇌의 뉴런과 비슷하게 동작합니다.

퍼셉트론 모델은 여러 입력값을 받아 가중치를 곱한 뒤 합하여 활성함수에 넣어 결과를 얻어냅니다.

이 간단한 단층 퍼셉트론을 이용하여 비트연산을 학습시켜 보겠습니다.

학습시키기 전에 학습시킬 데이터를 생성해야 하기 때문에 데이터를 생성해주는 함수를 만들겠습니다.

def generate_or_data():
    x = np.random.randint(0, 2, size=(100, 2))
    y = np.array([x[:, 0] | x[:, 1]]).T
    return x, y

def generate_and_data():
    x = np.random.randint(0, 2, size=(100, 2))
    y = np.array([x[:, 0] & x[:, 1]]).T
    return x, y

def generate_xor_data():
    x = np.random.randint(0, 2, size=(100, 2))
    y = np.array([x[:, 0] ^ x[:, 1]]).T
    return x, y

def plot_data(x, y):
    plt.scatter(x[:, 0], x[:, 1], c=y[:, 0], cmap=plt.cm.bwr)
    plt.show()

plot_data(*generate_or_data())
plot_data(*generate_and_data())
plot_data(*generate_xor_data())

OR

class Model(tf.Module):
    def __init__(self):
        self.w = tf.Variable(tf.random.uniform(shape=[2, 1], minval=-1, maxval=1))
        self.b = tf.Variable(tf.random.uniform(shape=[1], minval=-1, maxval=1))

    def __call__(self, x):
        y = tf.sigmoid(tf.matmul(x, self.w) + self.b)
        return y

def loss_fn(x, y):
    return tf.reduce_mean(tf.square(model(x) - y))

학습시킬 모델과 손실함수를 정의 해 주겠습니다.

비트연산자는 두개의 입력을 받아 하나의 출력을 내보내기 때문에

가중치는 (2, 1) 형태의 배열이 필요합니다.

model = Model()

def train(x, y, lr=0.1):
    with tf.GradientTape() as tape:
        loss = loss_fn(x, y)
    grads = tape.gradient(loss, [model.w, model.b])
    model.w.assign_sub(lr * grads[0])
    model.b.assign_sub(lr * grads[1])

def predict(x):
    return model(x)

def accuracy(x, y):
    return tf.reduce_mean(tf.cast(tf.equal(tf.round(predict(x)), y), tf.float32))

모델을 생성하고, 학습을 시켜줄 경사하강법 알고리즘으로 작성된 함수를 만들어 줍니다.

그 뒤 모델의 정확도를 구하기 위하여 정확도를 계산해주는 함수를 만들어 줍니다.

x, y = generate_or_data()

x = tf.constant(x, dtype=tf.float32)
y = tf.constant(y, dtype=tf.float32)

plot_data(x, y)

for i in range(1000):
    train(x, y)
    if i % 100 == 0:
        print('loss: {:.4f}, accuracy: {:.4f}'.format(loss_fn(x, y), accuracy(x, y)))

데이터를 생성하고 학습시키면 정확도가 1이 나올정도로 정확하게 학습됐다는건 알 수 있게 됐습니다.

어떤 방식으로 값을 판단하는지 알아보기 위해 출력되는 값의 정도를 투명도로 정해서 어떻게 나오는지 보겠습니다.

def plot_decision_boundary():
    x = np.linspace(0, 1, 100)
    y = np.linspace(0, 1, 100)
    x = tf.constant(x, dtype=tf.float32)
    y = tf.constant(y, dtype=tf.float32)
    xx, yy = np.meshgrid(x, y)
    z = predict(np.c_[xx.ravel(), yy.ravel()])
    z = z.numpy().reshape(xx.shape)
    plt.contourf(xx, yy, z, cmap=plt.cm.bwr, alpha=0.2)
    plt.show()

plot_decision_boundary()

AND

or 코드를 가져다 사용하겠습니다.

model = Model()

x, y = generate_and_data()

x = tf.constant(x, dtype=tf.float32)
y = tf.constant(y, dtype=tf.float32)

plot_data(x, y)

for i in range(1000):
    train(x, y)
    if i % 100 == 0:
        print('loss: {:.4f}, accuracy: {:.4f}'.format(loss_fn(x, y), accuracy(x, y)))

plot_decision_boundary()

변한건 데이터를 생성해주는게 or에서 and로 변한 것 밖에 없습니다.

학습시켜보면 아까전 or과 다르게 (0, 1), (1, 0) 일때가 빨간색에서 파란색으로 변했습니다.

이걸로 AND도 학습이 제대로 되었다는걸 볼 수 있습니다.

XOR

model = Model()

x, y = generate_xor_data()

x = tf.constant(x, dtype=tf.float32)
y = tf.constant(y, dtype=tf.float32)

plot_data(x, y)

for i in range(10000):
    train(x, y)
    if i % 100 == 0:
        print('loss: {:.4f}, accuracy: {:.4f}'.format(loss_fn(x, y), accuracy(x, y)))

plot_decision_boundary()

데이터를 xor로 바꿔서 학습시켜 보겠습니다. 학습도 10배 더 늘렸습니다.

이번엔 정확도도 0.8로 나오고 판정 기준도 뭔가 이상한걸 볼 수 있습니다.

단층 퍼셉트론으로는 XOR 문제를 해결할 수 없기 때문입니다.

단층 퍼셉트론은 최고차항이 1로 일차 함수인데 일차 함수로는 아무리 선을 잘 그어봐도 XOR을 판정을 내릴 수 없습니다.

다층 퍼셉트론을 이용해서 XOR 문제를 풀어보겠습니다.

class Layer(tf.Module):
    def __init__(self, out_dim, weight_init=tf.random.uniform, activation=tf.identity):
        self.out_dim = out_dim
        self.weight_init = weight_init
        self.activation = activation
        self.w = None
        self.b = None

    @tf.function
    def __call__(self, x):
        self.in_dim = x.shape[1]
        if self.w is None:
            self.w = tf.Variable(self.weight_init(shape=[self.in_dim, self.out_dim]))
        if self.b is None:
            self.b = tf.Variable(tf.zeros(shape=[self.out_dim]))
        z = tf.add(tf.matmul(x, self.w), self.b)
        return self.activation(z)

class MLP(tf.Module):
    def __init__(self, layers):
        self.layers = layers

    @tf.function
    def __call__(self, x, preds=False): 
        for layer in self.layers:
            x = layer(x)
        return x

model = MLP([
    Layer(2, activation=tf.sigmoid),
    Layer(1, activation=tf.sigmoid)
])

x, y = generate_xor_data()

x = tf.constant(x, dtype=tf.float32)
y = tf.constant(y, dtype=tf.float32)

plot_data(x, y)

def accuracy(x, y):
    return tf.reduce_mean(tf.cast(tf.equal(tf.round(model(x)), y), tf.float32))

def loss_fn(x, y):
    return tf.reduce_mean(tf.square(model(x) - y))

def train(x, y, lr=0.1):
    with tf.GradientTape() as tape:
        loss = loss_fn(x, y)
    optimizer = tf.keras.optimizers.Adam(learning_rate=lr)
    grads = tape.gradient(loss, model.trainable_variables)
    optimizer.apply_gradients(zip(grads, model.trainable_variables))

def plot_decision_boundary():
    x = np.linspace(0, 1, 100)
    y = np.linspace(0, 1, 100)
    x = tf.constant(x, dtype=tf.float32)
    y = tf.constant(y, dtype=tf.float32)
    xx, yy = np.meshgrid(x, y)
    z = model(np.c_[xx.ravel(), yy.ravel()])
    z = z.numpy().reshape(xx.shape)
    print(z)
    plt.contourf(xx, yy, z, cmap=plt.cm.bwr, alpha=0.2)
    plt.show()

for i in range(1000):
    train(x, y)
    if i % 100 == 0:
        print('loss: {:.4f}, accuracy: {:.4f}'.format(loss_fn(x, y), accuracy(x, y)))

plot_decision_boundary()

Gradient Descent를 사용하면 학습이 잘 되지 않아 Adam Optimizer를 사용했습니다.

아주 아름답게 학습되서 양쪽 끝만 빨간색인걸 볼 수 있습니다.

이러한 것도 볼 수 있습니다.

BitwisePerceptron.ipynb
0.15MB

728x90

'인공지능' 카테고리의 다른 글

선형 회귀(Linear Regression)  (1) 2022.10.28

선형 회귀(Linear Regression)는 지도학습(Supervised Learning)의 일종으로 단순 선형 회귀(Simple)와 다중 선형 회귀(Multiple)가 있습니다.

선형 회귀는 선형이라는 이름에 맞게 일차원 함수의 형태로 나타납니다. 

 

단순 선형 회귀(Simple Linear Regression)

단순 선형 회귀는 하나의 종속변수(Dependent Variable)와 하나의 독립변수(Independent Variable) 사이의 관계를 모델링 하는 기법입니다.

이걸 Tensorflow를 이용하여 구현하며 알아보도록 하겠습니다.

일단 선형 회귀를 하기 위해선 데이터가 필요합니다.

y = 2x + 3을 기준으로 랜덤한 값을 더해서 데이터를 만들어 보겠습니다.

import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
@tf.function
def f(x):
    y = 2 * x + 3
    return y

x = tf.linspace(0, 5, 101)
x = tf.cast(x, tf.float32)
y = f(x) + tf.random.normal(shape=[101])

그리고 이 데이터를 예측할 모델과 가설(Hypothesis)를 만들어 보겠습니다.

class Model(tf.Module):
    def __init__(self, seed=22):
        rand_init = tf.random.uniform(shape=[2], minval=0., maxval=5., seed=seed)

        self.w = tf.Variable(rand_init[0])
        self.b = tf.Variable(rand_init[1])
    
    @tf.function
    def __call__(self, x):
        y = self.w * x + self.b
        return y

지금은 가중치(Weight, w)와 편향(Bias, b)를 랜덤값으로 초기화 해 뒀기 때문에 실제 값과 동떨어진 가설 그래프가 나올 것 입니다.

이제 이 그래프가 얼마나 실제 값과 동떨어져 있는지를 나타내주는 비용(Cost) 또는 손실(Loss)라 부르는 함수를 만들어 줄 것 입니다.

예측값과 실제값의 차를 구하기 위해서 주로 절대값을 이용하거나 제곱을 이용하는데, 제곱이 더 계산하기 편하기 때문에 제곱을 이용한 평균 제곱 오차(Mean Squared Error)라는 방식을 사용할 것 입니다.

def loss(y, y_pred):
    return tf.reduce_mean(tf.square(y - y_pred))

print(loss(y, model(x)))

tf.Tensor(37.40411, shape=(), dtype=float32)

37.40411이 예측값과 실제값을 손실함수에 넣어서 나온 결과입니다.

from mpl_toolkits.mplot3d import Axes3D
from tqdm import tqdm

ax = plt.subplot(111, projection='3d')

w = np.linspace(0, 5, 101)
b = np.linspace(0, 5, 101)

W, B = np.meshgrid(w, b)

Z = np.zeros(shape=[101, 101])

pbar = tqdm(total=101*101)
for i in range(101):
    for j in range(101):
        model = Model()
        model.w = W[i, j]
        model.b = B[i, j]
        Z[i, j] = loss(y, model(x))
        pbar.update(1)

ax.plot_surface(W, B, Z, cmap='viridis')
ax.set_xlabel('w')
ax.set_ylabel('b')
ax.set_zlabel('loss')

그래프 이미지가 조금 잘리긴 했지만 대충 어느 정도에서 loss함수가 최소값을 갖는지 알아볼 수 있습니다.

이제 가설 함수의 가중치와 편향을 조정해 loss를 최소로 만들어야 하는데 이를 해주는 알고리즘이 Optimizer라 불리는 경사하강법, Adam등의 것들 입니다.

model = Model()

with tf.GradientTape() as tape:
    tape.watch(model.variables)
    loss_v = loss(y, model(x))
print(tape.gradient(loss_v, model.variables))

tensorflow에서는 자동 미분을 지원해주기 때문에 위와 같이 코드를 작성하면 각각의 편미분값을 알 수 있습니다.

하지만 w와 b가 랜덤인 상태이기 때문에 값이 랜덤하게 나올것입니다.

경사하강법에 따라 미분값과 learning rate를 곱하여 각 변수에서 빼주면 됩니다.

epochs = 100
learning_rate = 0.01

model = Model()

with tf.GradientTape() as tape:
    tape.watch(model.variables)
    batch_loss = loss(y, model(x))
grad = tape.gradient(batch_loss, model.variables)
print(grad)
model.w.assign_sub(learning_rate * grad[0])
model.b.assign_sub(learning_rate * grad[1])

with tf.GradientTape() as tape:
    tape.watch(model.variables)
    batch_loss = loss(y, model(x))
grad = tape.gradient(batch_loss, model.variables)
print(grad)
model.w.assign_sub(learning_rate * grad[0])
model.b.assign_sub(learning_rate * grad[1])

with tf.GradientTape() as tape:
    tape.watch(model.variables)
    batch_loss = loss(y, model(x))
print(tape.gradient(batch_loss, model.variables))
(<tf.Tensor: shape=(), dtype=float32, numpy=-5.1804476>, <tf.Tensor: shape=(), dtype=float32, numpy=-12.510265>)
(<tf.Tensor: shape=(), dtype=float32, numpy=-4.671219>, <tf.Tensor: shape=(), dtype=float32, numpy=-11.017024>)
(<tf.Tensor: shape=(), dtype=float32, numpy=-4.217317>, <tf.Tensor: shape=(), dtype=float32, numpy=-9.6837435>)

몇번 돌려보게 되면 미분값이 점점 0에 근접해간다는걸 알 수 있습니다.

이걸 한 100번정도 돌리게 되면 loss를 거의 최소로 만들 수 있습니다.

epochs = 100
learning_rate = 0.01
losses = []

model = Model()

for epoch in range(epochs):
    with tf.GradientTape() as tape:
        tape.watch(model.variables)
        batch_loss = loss(y, model(x))
    grads = tape.gradient(batch_loss, model.variables)
    for g,v in zip(grads, model.variables):
        v.assign_sub(learning_rate*g)

    loss_v = loss(y, model(x))
    losses.append(loss_v)
    if epoch % 10 == 0:
        print(f'Loss of step {epoch}: {loss_v.numpy():0.3f}')

plt.plot(range(epochs), losses)
plt.xlabel("Epoch")
plt.ylabel("Loss (MSE)")

학습된 결과를 그래프로 찍어보겠습니다.

plt.figure()
plt.plot(x, y, '.')
plt.plot(x, f(x))
plt.plot(x, model(x))

그리고 원본 그래프와 loss 를 비교해 보겠습니다.

print(loss(y, model(x)))
print(loss(y, f(x)))

tf.Tensor(0.89216924, shape=(), dtype=float32)
tf.Tensor(0.8852339, shape=(), dtype=float32)

0.89216924와 0.8852339로 학습된 모델의 loss가 약간 더 크긴 하지만 학습이 잘 됐다고 볼 수 있겠습니다.

SimpleLinearRegression.ipynb
0.22MB

다중 선형 회귀(Multiple Linear Regression)

import tensorflow as tf
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
@tf.function
def f(x, y):
    z = 2 * x + y + 3
    return z

x = tf.linspace(0, 5, 101)
x = tf.cast(x, tf.float32)
y = tf.linspace(0, 5, 101)
y = tf.cast(y, tf.float32)

z = f(x, y) + tf.random.normal(shape=[101])

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(x, y, z, c='r', marker='o')

class Model(tf.Module):
    def __init__(self, seed=22):
        rand_init = tf.random.uniform(shape=[3], minval=0., maxval=5., seed=seed)

        self.w_x = tf.Variable(rand_init[0])
        self.w_y = tf.Variable(rand_init[1])
        self.b = tf.Variable(rand_init[2])
    
    @tf.function
    def __call__(self, x, y):
        z = self.w_x * x + self.w_y * y + self.b
        return z
model = Model()

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(x, y, z, c='r', marker='o')
ax.plot3D(x, y, f(x, y), c='r')
ax.plot3D(x, y, model(x, y), c='b')

def loss(z, z_pred):
    return tf.reduce_mean(tf.square(z - z_pred))

print(loss(z, model(x, y)))
tf.Tensor(194.73457, shape=(), dtype=float32)
epochs = 100
learning_rate = 0.01

model = Model()

with tf.GradientTape() as tape:
    tape.watch(model.variables)
    batch_loss = loss(z, model(x, y))
grads = tape.gradient(batch_loss, model.variables)
print(grads)
for g,v in zip(grads, model.variables):
    v.assign_sub(learning_rate*g)

with tf.GradientTape() as tape:
    tape.watch(model.variables)
    batch_loss = loss(z, model(x, y))
grads = tape.gradient(batch_loss, model.variables)
print(grads)
for g,v in zip(grads, model.variables):
    v.assign_sub(learning_rate*g)

with tf.GradientTape() as tape:
    tape.watch(model.variables)
    batch_loss = loss(z, model(x, y))
print(tape.gradient(batch_loss, model.variables))
(<tf.Tensor: shape=(), dtype=float32, numpy=-12.747607>, <tf.Tensor: shape=(), dtype=float32, numpy=-38.576015>, <tf.Tensor: shape=(), dtype=float32, numpy=-38.576015>)
(<tf.Tensor: shape=(), dtype=float32, numpy=-8.635053>, <tf.Tensor: shape=(), dtype=float32, numpy=-25.01567>, <tf.Tensor: shape=(), dtype=float32, numpy=-25.01567>)
(<tf.Tensor: shape=(), dtype=float32, numpy=-5.9607844>, <tf.Tensor: shape=(), dtype=float32, numpy=-16.203669>, <tf.Tensor: shape=(), dtype=float32, numpy=-16.203669>)
epochs = 1000
learning_rate = 0.01
losses = []

model = Model()

for epoch in range(epochs):
    with tf.GradientTape() as tape:
        tape.watch(model.variables)
        batch_loss = loss(z, model(x, y))
    grads = tape.gradient(batch_loss, model.variables)
    for g,v in zip(grads, model.variables):
        v.assign_sub(learning_rate*g)

    loss_v = loss(z, model(x,y ))
    losses.append(loss_v)
    if epoch % 10 == 0:
        print(f'Loss of step {epoch}: {loss_v.numpy():0.3f}')

plt.plot(range(epochs), losses)
plt.xlabel("Epoch")
plt.ylabel("Loss (MSE)")

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(x, y, z, c='r', marker='o')
ax.plot3D(x, y, f(x, y), c='r')
ax.plot3D(x, y, model(x, y), c='b')

print(loss(z, model(x, y)))
print(loss(z, f(x,y )))
tf.Tensor(0.9223384, shape=(), dtype=float32)
tf.Tensor(0.9513635, shape=(), dtype=float32)

MultipleLinearRegression.ipynb
0.26MB

728x90

'인공지능' 카테고리의 다른 글

퍼셉트론(Perceptron) + 비트연산(Bitwise Operation)  (0) 2022.10.29

+ Recent posts