6 분 소요

데이터에 입각한 고전신경망과 현대신경망의 차이

9.1.1 전통적인 신경망

이번 장에서 전통적인 신경망이나 기타 고전적 기계학습 모형(지지벡터 기계, 무작위 숲 등)을 다룰 떄는 데이터를 2차원 배열로 표현하기로 합니다. 한 예로 제6장에서 소개한 붓꽃 데이터 집합의 각 특징 벡터는 특징 네개로 구성된다. 그런 특징 벡터들의 집합을 다음처럼 NumPy 행렬로 표현할 수 있다.

import numpy as np
from sklearn import datasets
iris = datasets.load_iris()
X = iris.data[:5]
X
array([[5.1, 3.5, 1.4, 0.2],
       [4.9, 3. , 1.4, 0.2],
       [4.7, 3.2, 1.3, 0.2],
       [4.6, 3.1, 1.5, 0.2],
       [5. , 3.6, 1.4, 0.2]])
Y = iris.target[:5]
Y
array([0, 0, 0, 0, 0])

붓꿏 데이터 집합의 견본들 중 상위 다섯개만 추출해보았다. 이 샘플들은 모두 분류명 0(I, Setosa)에 속한다. 이러한 지식을 모형에 전달하려면, 이 견본들과 대응되는 분류명들을 담은 벡터가 필요하다. Y가 그러한 벡터이다. X[i]는 견본i의 특징벡터이고, Y[i]는 그 특징벡터의 분류명이다. 일반적으로 분류명은 각 분류명 문자열에 대응되는 정수 일련번호로 부호화한다. 분류명을 위한 부호화 벡터로 표현하는 도구 모음들도 있지만, 정수 분류명을 One-Hot 부호하 벡터로 Convert하는 것은 간단한 문제이므로, 여기서는 정수를 사용하기로 한다.


다음과 같이 전통적인 뉴럴 네트워크에서는 데이터 셋을 무조건 행렬로 표현하며 각 층에 산재해있는 가중치들의 집합도 모두 행렬로 표현되어 있다. 층과층 사이의 입력과 출력은 벡터이다. 이는 아주 직접적인 방식이므로 이해하는데 어려움이 없을 것이다. 이번에는 더 나아가 좀더 현대적인 심층 신경망은 고전적인 것과 어떤 차이가 있는지도 알아보려고 한다.

9.1.2 심층 합성공 신경망

여기서도 마찬가지로 특징벡터들을 사용한다.그런데 고적 신경망과 달리 여기서의 핵심포인트는 합성공 층, 즉 컨볼루션 레이어이다. 이를 통해 데이터에 존재하는 공간적 관계들을 포착하고 활용하자는 데 그 의의가 있다. 많은 경우에 심층 합성곤 신경망에서 가장 많이 사용되는 입력데이터는 바로 2차원 배열로 구성된 이미지 데이터이다.(물론 꼭 이미지여야 할 필요는 없다. 그냥 가장 다루기 쉬워서 그런 것이다) 신경망의 입장에서는 그 입력데이터가 무엇을 의미하는지 알 수 없다.모형의 설계자만이 사전지식에 기초해서 그모형의 구조를 결정할 뿐이다.

from sklearn.datasets import load_sample_image
china = load_sample_image('china.jpg')
china.shape
(427, 640, 3)

china.jpg는 단순히 3차원으로 되어 있는 하나의 넘파이 배열일 뿐이다. 출력값을 보았을때 세개의 원소값이 써있는 것을 통해 3차원 데이터라는 것을 알 수 있다. 그러면 입체데이터냐?라고 할 수 있겠지만, 그냥 이미지일뿐이다. 일단 너비와 높이로 2차원을 만들어주고 나머지 하나의 축이 채널의 개수를 의미한다. 왜냐하면 어쨋든 색상이라는 것은 컴퓨터에서 보통 RGB색상원리를 가장 많이 사용한다. 이때 빨간색 성분을 나타내는 채널, 초록색 성분을 나타내는 채널, 파란색 성분을 나타내는 채널, 이렇게 총 세개의 채널로 하나의 이미지를 구축하는 것이다.

from PIL import Image
Image.fromarray(china).show()
Image.fromarray(china[:,:,0]).show()
Image.fromarray(china[:,:,1]).show()
Image.fromarray(china[:,:,2]).show()

세가지 회색조 이미지들을 모두 보았을때 모두 같지 않고 조금씩 다르다.원색 이미지는 이 세 채널의 색상성분들을 혼합한 결과이다. 심층 신경망의 입력은 2차원 이상일때가 많다. 지금 예처럼 원색 이미지가 입력일때는 3차원 텐서가 필요하다. 그런데 입력 견본 하나로 이야기가 끝나는 것이 아니다. 신경망을 훈련할 때는 흔히 데이터 집합에서 추출한 여러개의 견본으로 이루어진 미니배치를 신경망에 입력해서 평균 손실값을 구한다.

import torch
import torchvision
import torchvision.transforms as transforms
transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

batch_size = 4

trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size,
                                          shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=batch_size,
                                         shuffle=False, num_workers=2)

classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
Downloading https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to ./data\cifar-10-python.tar.gz


100%|███████████████████████████████████████████████████████████████| 170498071/170498071 [00:56<00:00, 2998651.67it/s]


Extracting ./data\cifar-10-python.tar.gz to ./data
Files already downloaded and verified

이 코드는 Pytorch의 torchvision 모듈을 사용하여 CIFAR-10 데이터셋을 로드하고 전처리하기 위한 것입니다. 각 부분에 대한 설명은 다음과 같습니다.

  1. 변환(Transforms):
    • transforms.ToTensor(): PIL 이미지 또는 Numpy 배열을 Pytorch 텐서로 변환합니다.
    • transforms.Normalizer(): 입력 이미지를 평균을 뺸 후 표준편차로 나누어 정규화합니다. 이는 딥러닝에서 흔한 전처리 단계입니다.
  2. 배치 크기(Batch size):
    • batch_size = 4: 각 미니 배치에 포함될 샘플의 수를 지정합니다. 이는 훈련중에 사용됩니다.
  3. 훈련세트(Traning Set):
    • trainset: 훈련을 위한 CIFAR-10 데이터셋입니다.
    • torchvision.datasets.CIFAR10: torchvision에서 CIFAR-10 데이터셋을 로드합니다.
    • root='./data': 데이터셋이 다운로드될 디렉토리를 지정합니다.
    • train=True: 훈련세트임을 나타냅니다.
import numpy as np

test_images = []
test_labels = []

for images, labels in testloader:
    test_images.append(images.numpy())
    test_labels.append(labels.numpy())

test_images = np.concatenate(test_images)
test_labels = np.concatenate(test_labels)

print(test_images.shape)
print(test_labels.shape)
(10000, 3, 32, 32)
(10000,)

위에서 test_images 배열의 차원이 4임을 주목하기 바란다. 첫 차원은 배열에 담긴 이미지 개수(1만개의 이미지)이다. 둘째 차원은 채널개수가 3개임을 나타내고, 셋째와 넷째차원은 이미지들이 32X32 픽셀임을 말해준다. 따라서 이 이미지들은 회색조 이미지가 아니라 원색 이미지일 것이다.이미지가 아닌 데이터에서도, 채널 개수는 데이터가 어떤 식으로 구성되어 있는지를 말해준다. labels는 벡터(1차원 배열)인데, 성분 수는 10000개이다. 여기에는 분류명들이 들어있다. CIFAR-10 데이터 집합의 분류명은 총 10개인데, 일부는 동물들이고, 일부는 탈것들이다. 다음은 123번 견본의 분류명과 이미지를 표시하는 예이다.

import numpy as np
from PIL import Image
import matplotlib.pyplot as plt

# test_images와 test_labels가 이미 정의되어 있다고 가정합니다.

# 이미지 보여주기
def show_image(img):
    img = img / 2 + 0.5  # 이미지를 정규화 해제
    img = np.transpose(img, (1, 2, 0))  # 이미지의 차원을 변경 (C, H, W) -> (H, W, C)
    plt.imshow(img)
    plt.show()

# 특정 이미지와 레이블 확인
index_to_display = 123
print(f"레이블: {test_labels[index_to_display]}")
show_image(test_images[index_to_display])

레이블: 2

output_15_1

  1. 이미지 보여주기 함수(show_image):
  • img = img / 2 + 0.5: 이미지 정규화를 해제하여 원래 범위로 되돌립니다.
  • img = np.transpose(img, (1, 2, 0)): 이미지의 차원을 변경하여 matplotlib에서 정상적으로 표시할 수 있도록 합니다.
  1. 특정 이미지와 레이블
index_to_display = 123
print(f"레이블: {test_labels[index_to_display]}")
show_image(test_images[index_to_display])
  • index_to_display: 확인할 이미지의 인덱스를 선택합니다.
  • print(f"레이블: {test_labels[index_to_display]}"): 선택된 이미지의 레이블을 출력합니다.
  • show_image(test_images[index_to_display]): 선택된 이미지를 시각화하여 표시합니다.

이 코드는 주어진 인덱스(여기서는 123)의 테스트 세트 이미지와 해당 레이블을 출력하고 시각적으로 보여줍니다.

미니배치를 사용한다는 것은 전체 데이터 집합의 한 부분집합을 신경망 모형에 입력한다는 것입니다. 모형이 사용하는 미니배치의 크기(견본 개수)가 24이면, CIFAR-10을 기준으로 입력은 (24, 32, 32, 3) 배열이다. 즉, 이미지가 24개이고 각 이미지는 32행 32열의 행렬(2차원 배열)이며 행렬의 각 성분은 3개의 채널로 구성됩니다. 이런 채널 개념이 심층 신경망의 입력에 국한된 것이 아니라, 층에서 층으로 전달되는 데이터의 형상에도 적용된다는 점을 볼 수 있을 것입니다. 일단 지금은 전통적인 순방향 신경망의 데이터 흐름에 초점을 맞추고 이후에 심층 신경망의 데이터 흐름도 짚고 넘어가겠습니다.

import matplotlib.pyplot as plt
import numpy as np

# 이미지를 보여주기 위한 함수

def imshow(img):
    img = img / 2 + 0.5     # unnormalize
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()


# 학습용 이미지를 무작위로 가져오기
dataiter = iter(trainloader)
images, labels = next(dataiter)

# 이미지 보여주기
imshow(torchvision.utils.make_grid(images))
# 정답(label) 출력
print(' '.join(f'{classes[labels[j]]:5s}' for j in range(batch_size)))

output_17_0

deer  deer  plane truck

이 코드는 훈련데이터로부터 무작위로 선택된 이미지를 시각화하여 보여주는 기능을 포함합니다.

  1. imshow():
    • 이미지를 시각화하기 위한 함수입니다.
    • img = img / 2 + 0.5: 이미지 정규화를 해제하여 원래 범위로 되돌립니다.
    • npimg = img.numpy(): PyTorch텐서를 Numpy배열로 변환합니다.
    • plt.imshow(np.transpose(npimg, (1,2,0))): 이미지를 보여줍니다.
    • plt.show():시각화된 이미지를 표시합니다.
  2. 데이터 불러오기:
    • detaiter = iter(trainloader): 훈련데이터 로더에서 반복자를 생성합니다.
    • imgaes, labels = next(detaiter): 다음 미니 배치의 이미지와 레이블을 가져옵니다.
  3. 이미지 시각화
    • torchvision.utils.make_grid(images): 미니 배치의 이미지를 하나의 그리드로 만듭니다.
    • imshow(...): 앞서 정의한 함수를 사용하여 이미지를 시각화합니다.
  4. 레이블 출력:
    • print(' '.join(f'{classes[labels[j]]:5s}' for j in range(batch_size))): 해당 미니 배치에 대한 정답 레이블을 출력합니다. 클래스 이름과 함께 출력되며, 각 클래스는 5자리로 정렬됩니다.

현재 동키카를 깔고 있다. 예전에 동키카를 깔다가 계속 막혔던 것은 opencv를 알맞게 깔지 못해서였다.

해당 이슈를 해결한 솔루션은 여기 (링크)에 있다. 방법을 나열하자면,

  • 라이브러리 조회를 위해 pkg-config를 설치하고 명령어를 입력해 줍니다.
~$ sudo apt-get install pkg-config
~$ pkg-config --modversion opencv

위 명령어를 치고,

Package opencv was not found in the pkg-config search path.
Perhaps you should add the directory containing `opencv.pc'
to the PKG_CONFIG_PATH environment variable
No package 'opencv' found

위 문구가 뜬다면, 바로 설치로 넘어가자!만약 만약 설치가 되어 있는데 그래도 opencv가 제대로 안된다면 어딘가가 꼬인것이다.(마치 나의 인생처럼…. 응??…이 뭔??) 설치는 다음과 같다.

설치준비

sudo apt update
sudo apt upgrade
sudo reboot

재부팅되면 이제 운영체제랑 컴파일러 버전을 확인해줍니다.

1>운영체제 버전 확인하기

uname -a

Linux raspberrypi 5.15.56-v8+ #1575 SMP PREEMPT Fri Jul 22 20:31:26 BST 2022 aarch GNU/Linux

위와 같이 aarch64라고 뜨면 제대로 된겁니다(참고로 aarch는 아치리눅스(Ditro)라고 합니다.)

이번에는

2>컴파일러 버전 확인하기

gcc -v

Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/usr/lib/gcc/aarch64-linux-gnu/10/lto-wrapper Target: aarch64-linux-gnu Configured with: ../src/configure -v –with-pkgversion=’Debian 10.2.1-6’ –with-bugurl=file:///usr/share/doc/gcc-10/README.Bugs –enable-languages=c,ada,c++,go,d,fortran,objc,obj-c++,m2 –prefix=/usr –with-gcc-major-version-only –program-suffix=-10 –program-prefix=aarch64-linux-gnu- –enable-shared –enable-linker-build-id –libexecdir=/usr/lib –without-included-gettext –enable-threads=posix –libdir=/usr/lib –enable-nls –enable-bootstrap –enable-clocale=gnu –enable-libstdcxx-debug –enable-libstdcxx-time=yes –with-default-libstdcxx-abi=new –enable-gnu-unique-object –disable-libquadmath –disable-libquadmath-support –enable-plugin –enable-default-pie –with-system-zlib –enable-libphobos-checking=release –with-target-system-zlib=auto –enable-objc-gc=auto –enable-multiarch –enable-fix-cortex-a53-843419 –disable-werror –enable-checking=release –build=aarch64-linux-gnu –host=aarch64-linux-gnu –target=aarch64-linux-gnu –with-build-config=bootstrap-lto-lean –enable-link-mutex Thread model: posix Supported LTO compression algorithms: zlib zstd gcc version 10.2.1 20210110 (Debian 10.2.1-6)

위와 같이 Taget이 aarch64라고 뜨면 제대로 된겁니다.

설치 여유공간 확보하기

~$ free -m

image-20240125102627215

명령어를 입력하면 위와 같이 표시된다. 우리가 설치하려는 것은 OpenCV 4.5.5이기 때문에 Mem+Swap이 6500이 넘어가게 맞춰줘야 합니다. 초기에는 Swap이 위와 같이 99로 되어 있을 겁니다.

Swap 늘려주기

  • 먼저 디스크 공간부터 확인
~$ df -h
  • swap파일 편집하기
sudo vi /etc/dphys-swapfile

안에 들어가서 CONF_SWAPSIZE=100으로 되어 있는 부분을 CONF_SWAPSIZE=4096으로 변경해줍니다.

  • 그리고 재시작!
~$ service dphys-swapfile restart
~$ free -m

용량을 확보했으면 이제 그 공간에 OpenCV를 설치해보겠습니다.

본격적으로 OpenCV 설치하기

~$ wget https://github.com/Qengineering/Install-OpenCV-Raspberry-Pi-64-bits/raw/main/OpenCV-4-5-5.sh
~$ sudo chmod 755 ./OpenCV-4-5-5.sh
~$ ./OpenCV-4-5-5.sh

를 입력하면 자동설치가 됩니다. 1시간에서 2시간까지 예상됩니다.

~$ python3
~$ import cv2
~$ cv2.__version__

이러면 설치완료입니다.

삽질현황…

git clone https://github.com/waveshare/donkeycar
cd donkeycar
git checkout master
pip install -e .[pi]

현재 위에서 install하는 부분에서 막혀버렸다.

그래서 pip install -e .로 바꿔서 명령어 때렸다. 하지만 아래 에러뜸..

ERROR: THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE. If you have updated the package versions, please update the hashes. Otherwise, examine the package contents carefully; someone may have tampered with them.
    h5py from https://www.piwheels.org/simple/h5py/h5py-3.10.0-cp39-cp39-linux_armv7l.whl#sha256=9bb99b9616a2238fd48b338efa61498b70f66f7c32126a57d009285d0eb570fb (from donkeycar==3.1.0):
        Expected sha256 9bb99b9616a2238fd48b338efa61498b70f66f7c32126a57d009285d0eb570fb
             Got        d5d0599a2a0590cac56fa9d0fc905d4d57c5695b188b5755febaab6ec2879452

대충 읽어보니까 모듈의 에러해시값이 불일치한다고 하는데, 내가 해시가 뭔들알겠는고 ㅅㅂ…

일단 명령어 하나하나 찔러보기로 했다. 우선 이거!

pip install h5py==3.10.0

효과는 없었다. Next!!

pip install h5py --upgrade --no-dependencies --force

효과는 없었다. 주변정보들을 훑어보았다.

알고보니 텐서플로우를 깔면 그안에 종속되서 깔리는 모듈중 하나라고 한다.

이 h5py라는 자동으로 설치되는 패키지가 충돌이 일어나서 pip으로 지웠다가 다시 재설치해준다.

sudo pip uninstall h5py
sudo pip install h5py

HuHu…. 효과는 없었다. 근데 이렇게 설치하면 최신으로 까는거고, 최신으로 깔때 해시오류가 발생한 것이었을수도 있으니, 이번에는 다운그레이드시켜서 깔아보기로 했다.

기존에는 3.10.0버전으로 깔았는데 이번에는 2.10.0 버전으로 깔아보려 한다.

pip install h5py==2.10.0

주변정보를 더 찾아보니 h5py는 아나콘다에 기본적으로 탑재되어 있다고 한다. 그래서 나는 아나콘다의 리눅스 버전인 미니콘다를 깔수있지 않을까 하고 찾아보다가 다음 순서로 깔수있다는 걸 알게 되었다.

sudo apt-get update
sudo apt-get upgrade
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-armv7l.sh
chmod +x Miniconda3-latest-Linux-armv7l.sh
sudo /bin/bash Miniconda3-latest-Linux-armv7l.sh

위에서 깔아주면 아래와 같이 yes/no하라는 문구가 세번정도 나오는데 두번 yes하고 나서,

아래와 같이 경로를 물어보는데 /home/pi/miniconda3라고 해주고 Enter!

image-20240125003927800

그리고 to PATH in your /root/.bashrc ? [yes/no]라고 뜨면, 무조건 YES!!

image-20240125003101626

그러면 제대로 깔리게 된다.

마지막으로 /home/pi/.bashrc 로 들어가줍니다.

sudo vi /home/pi/.bashrc

아래와 같이 맨 첫째줄에 export PATH="/home/pi/miniconda3/bin:SPATH"를 넣어줍니다.

image-20240125004708702

그러면 설치 끝!!!

미니콘다 가상환경 생성하기

conda create -n dcoworks python=3.7

image-20240125010052492

댓글남기기