pytorch 에서 필요한 파일들은 링크에서 다운받아 사용하세요
1. 인공지능 신경망 펼치기/접기
1. 머신러닝과 딥러닝
펼치기/접기
2. 파이토치 기초
3. 머신 러닝 알고리즘
펼치기/접기
4. 딥러닝 시작
5. 합성곱 신경망
펼치기/접기
1. 머신 러닝과 딥러닝
1-1. 머신 러닝과 딥러닝의 차이
2. 파이토치 기초
2-1. 파이토치 특징 (09.04)
파이토치는 CPU/GPU 자원을 이용하여 텐서 조작 및 동적 신경망을 구축할 수 있는 프레임워크중 하나이다 파이토치는 Autograd, Aten, JIT 등의 C++ 엔진 등의 다양한 아키텍처로 이뤄져 있다.
파이토치에서는 기본적으로 텐서 연산 및 텐서 조작이 가능하다
2-2. 파이토치 데이터셋 (09.04)
파이토치를 사용하기에 앞서, 모델에 필요한 데이터셋을 불러올 때, 메모리에서 한번에 불러올 경우, 프로그램이 멈추거나 하는 등 효율적이지 않기 때문에 데이터셋
을 만들어 사용한다.
또한 파이토치에서 제공하는 MNIST 등을 사용할 수도 있다
# 데이터셋 예시
class MyDataset(Dataset):
def __init__(self, csv_file):
self.label = pd.read_csv(csv_file)
def __len__(self):
return len(self.label)
def __getitem__(self, idx):
sample = torch.tensor(self.label.iloc[idx, 0:3]).int()
label = torch.tensor(self.label.iloc[idx, 3]).int()
return sample, label
tensor_dataset = MyDataset('./test.csv')
dataset = DataLoader(tensor_dataset, batch_size=4, shuffle=True)
2-3. 파이토치 모델 (09.05)
모델은 다음과 같은 요소들로 이뤄져 있다.
- 계층(layer):
- 가중치(weight) 와 편차(bias) 를 가져 연산을 수행한다
- 특정 개수의 입력 노드로부터 연산을 거쳐, 또 다른 개수의 출력노드로 값이 도출된다
- 합성곱층, 선형계층 등이 있다
- 모듈(module):
- 계층이 모여 구성된 것으로, 모듈이 모여 모듈을 구성할 수 있다
- 모델(model):
- 최종적으로 원하는 네트워크로, 한 개의 모듈이 모델 그 자체가 될 수도 있다
모델을 구현할 때, 대부분 Module 을 상속받아 사용하는데 그 경우 __init__
에서 모듈, 활성화 함수 등을 정의하고, forawrd
에서는 모델에서 실행될 연산을 정의한다
# single layer, single module
class SLP(nn.Module):
def __init__(self, inputs):
super().__init__()
self.layer = nn.Linear(
in_features=inputs,
out_features=1
)
self.activation = nn.Sigmoid
def forward(self, x):
x = self.layer(x)
x = self.activation(x)
return x
# multi layer, single module
class MLP(nn.Module):
def __init__(self):
super().__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(
in_channels=3,
out_channels=64,
kernel_size=5
),
nn.ReLU(inplace=True),
nn.MaxPool2d(2)
)
self.layer2 = nn.Sequential(
nn.Conv2d(
in_channels=64,
out_channels=30,
kernel_size=5
),
nn.ReLU(inplace=True),
nn.MaxPool2d(2)
)
self.layer3 = nn.Sequential(
nn.Linear(
in_features=30 * 5 * 5,
out_features=10
),
nn.ReLU(inplace=True),
nn.MaxPool2d(2)
)
def forward(self, x):
x = self.layer1(x)
x = self.layer2(x)
x = x.view(x.shape[0], -1)
x = self.layer3(x)
return x
3. 머신 러닝 알고리즘
3-1. 지도학습 (09.05)
지도학습은 모델을 훈련할 때, 사전에 입력되는 데이터에 정답을 알려 주고 학습을 하는 방법이다
지도학습의 종류
- 분류
- 이산형 데이터를 받아 사전에 훈련받은 데이터들의 레이블 중 하나로 예측하는 방식이다
- 회귀
- 연속된 데이터를 받아 연속된 값을 예측하여 연속된 값을 예측하는 방식으로, 보통 흐름에 따라 연속적으로 변하는 값을 예측할 때 사용한다
3-1-1. k-최근접 이웃(KNN) (09.05)
k 최근접 이웃은 미리 라벨이 붙은 클러스터 들 중에, 새로운 입력 데이터가 있으면 해당 데이터 근처 k
개의 데이터를 보고, 새로운 데이터에 어떤 라벨이 붙일지를 정하는 알고리즘 이다
k값에 따라 비교할 데이터 대상이 달라져 결과가 크게 달라짐으로 초기 설정이 중요하다
# 모델을 생성 및 훈련하고, 정확도를 계산하는 예시
classifier = KNeighborsClassifier(n_neighbors = k).fit(x_train, y_train)
y_pred = classifier.predict(x_test)
acc = metrics.accuracy_score(y_test, y_pred)
3-1-2. 서포트 벡터 머신(SVM) (09.06)
서포트 벡터 머신이란, 데이터 클러스터에서 분류를 위한 기준점인 결정 경계
를 결정하는 알고리즘이다
결정 경계로부터 가장 가까이 있는 데이터를 서포트 벡터
라고 하고, 서포트 벡터와 결정 경계 사이의 거리를 마진
이라고 하는데
최적의 결정 경계를 정하기 위해서는 마진이 최대가 되도록 해야 한다
# SVM 모델 생성 및 훈련 예시
svm = svm.SVC(kernel='linear', C=1, gamma=0.5)
# 훈련 데이터로 svm 모델 훈련
svm.fit(x_train, y_train)
# 테스트데이터로 예측
predictions = svm.predict(x_test)
score = metrics.accuracy_score(y_test, predictions)
예시에서 보면 c
와 gamma
가 있는데
C
값은 오류를 얼마나 허용할지를 정하는 파라미터이며, 클 수록 하드마진이다
gamma
값은 각 결정 경계를 얼마나 유연하게 가져갈지, 즉 경계의 곡선이 얼마나 휘어질지를 정하는 파라미터로 값이 클수록 급격하게 휜다.
하지만 해당 값이 너무 클 경우, 훈련 데이터에 많이 의존하기 때문에 과적합을 초래할 수 있으니 주의해야 한다
※ 추가 정보
비선형 문제, 즉 결정 곡선이 비선형일 때 찾는 방법이 저차원 데이터를 고차원으로 보내는 것인데, 이것은 연산량이 너무 많아 다음과 같은 커널 트릭으로 해결한다
선형 커널(linear kernel):
선형으로 분류 가능한 데이터에 적용하며 커널 트릭을 사용하지 않겠다는 의미이다.
$$K(a, b) = a^T * b$$
$(a, b)$ 는 입력 벡터
다항식 커널(polynomial kernel):
실제로는 특정을 추가하지 않지만, 다항식 특성을 많이 추가한 것과 같은 결과를 얻을 수 있는 방법이다. 때문에 고차원 매핑이 가능하다
$$K(a, b) = (\gamma a^t * b)^d$$ $\begin{pmatrix} a, b & 입력 벡터 \ \gamma & 감마 \ d & 차원 \end{pmatrix}$ 단, 이때
$\gamma, d$ 는 하이퍼파라미터
가우시안 RBF 커널(Gaussian RBF kernel):
입력 벡터를 차원이 무한한 고차원으로 매핑하는 것으로 모든 차수의 모든 다항식을 고려, 다항식 커널은 차수에 한계가 있는 문제를 해결
$$K(a, b) = \exp(-\gamma \rVert a= b\rVert ^ 2)$$ 이때
$\gamma$ 는 하이퍼파라미터
3-1-3. 결정 트리 (09.06)
결정 트리는 데이터를 분류하거나 결과를 예측하는 분석 방법이다
결정 트리 예시
graph TD;
A{바퀴가 있는가?} -->|바퀴가 있다| B;
A-->|바퀴가 없다| C;
B{엔진이 있는가?} -->|엔진이 있다| D;
B-->|엔진이 없다| E;
D[자동차];
E[자전거];
C{날개가 있는가?} -->|날개가 있다| F;
C-->|날개가 없다| G;
F[참새];
G[사람];
자동차 | 자전거 | 참새 | 사람 |
---|---|---|---|
엔진이 있다 | 엔진이 없다 | 날개가 있다 | 날개가 없다 |
바퀴가 있다 | ` | 바퀴가 없다 | ` |
결정 트리는 데이터를 1차로 분류한 후 각 영역의 순도가 증가하고, 불순도와 불확실성은 감소하는 방향으로 학습을 진행시킨다.
이중, 순도는 범주 안에 같은 데이터가 모여있는 정도이고 불순도는 계산을 통해 구한다
# 결정 트리 예시
# 결정 트리 모델 생성
model = tree.DecisionTreeClassifier()
# 모델 훈련
model.fit(x_train, y_train)
# 모델 예측
y_predict = model.predict(x_test)
print(accuracy_score(y_test, y_predict))
# 혼동 행렬로 성능측정
print(pd.DataFrame(
confusion_matrix(y_test, y_predict),
columns=['Pred Negative', 'Pred Positive'],
index=['Actual Negative', 'Actual Positive']
))
※ 혼동 행렬이란 True/False, Positive/Negative 의 조건으로
- 예측값이 Positive 인데 실제값도 Positive 인 경우
- 예측값이 Positive 인데 실제값은 Negative 인 경우
- 예측값이 Negative 인데 실제값은 Positive 인 경우
- 예측값이 Negative 인데 실제값도 Negative 인 경우
를 표현하는 행렬이다
3-1-4. 로지스틱 회귀 (09.06)
회귀란 두 변수에서 한 변수로 다른 변수를 예측하거나 두 변수의 관계를 규명할 때 사용하는 방법으로 이 때 사용하는 변수는 다음과 같다
- 독립 변수(예측 변수): 영향을 미칠 것으로 예상되는 변수
- 종속 변수(기준 변수): 영향을 받을 것으로 예상되는 변수
예시로는 몸무게(종속 변수) 와 키(독립 변수)가 있다
로지스틱 회귀는 일반적인 회귀와는 다르게 분석하고자 하는 대상들이 두 집단 혹은 그 이상의 집단으로 나누어진 경우, 개별 관측치들이 어느 집단으로 분류될 수 있는지 분석하고 이를 예측하는 모형을 개발하는데 사용되는 통계 기법이다.
#로지스틱 회귀 모델
# 로지스틱 회귀 모델 생성
logisticRegr = LogisticRegression()
# 훈련
logisticRegr.fit(x_train, y_train)
# 테스트셋을 사용해 모델 예측
predictions = logisticRegr.predict(x_test)
score = logisticRegr.score(x_test, y_test)
print('score: ', score)
# 추가적으로 혼동행렬을 이용해 시각화 할 수도 있다
3-1-5. 선형 회귀 (09.06)
선형 회귀는 독립 변수와 종속 변수가 선형 관계를 가질 때 사용하면 유용하며, 선형 특징상 복잡한 과정이 없어 제한된 환경에서도 사용할 수 있다
로지스틱 회귀와의 차이는 선형 회귀는 변수 x 와 y 의 관계가 직선으로 나타나며 때문에 예측값 y는 0~1 을 초과할 수 있다
하지만 로지스틱 회귀는 x 와 y 의 관계가 S-커브 로 나타나며 예측값은 0~1 사이이다 (종속변수가 예/아니오 로 나타나기 때문)
# 선형 회귀 모델
# 선형 회귀 모델 생성
regressor = LinearRegression()
# 훈련
regressor.fit(x_train, y_train)
# 모델 예측
y_pred = regressor.predict(x_test)
df = pd.DataFrame({'Actural': y_test.flatten(), 'Predicted': y_pred.flatten()})
print(df)
# 테스트셋으로 회귀선 표현
plt.scatter(x_test, y_test, color='gray')
plt.plot(x_test, y_pred, color='red', linewidth=2)
plt.show()
선형 회귀 모델을 평가할 때는 평균 제곱법과 루트평균 제곱법을 사용하여 모델을 평가한다
평균 제곱법이
루트 평균 제곱법은
3-2. 비지도학습 (09.06)
비지도 학습은 분류되거나 레이블을 붙이지 않은 데이터로 훈련시키는 학습이며
비지도 학습에는 군집(cluster)과 축소(dimensionality reduction) 이 있다
군집은 데이터를 그룹화 하여 분류하는데 사용하고,
차원 축소는 데이터를 압축하거나, 필요한 속성을 도출해내는데 사용한다
3-2-1. K 평균 군집화 (09.06)
K 평균 군집화는 데이터를 입력받아 여러 그룹으로 묶는 알고리즘 이다.
해당 알고리즘은 데이터를 받아 각 데이터에 레이블을 할당해 클러스터링을 수행하는데 학급 과정은 다음과 같다.
- 중심점 선택: 랜덤하게 초기 중심점을 K개 선택한다
- 클러스터 할당: K개의 중심점과 각각 데이터간의 거리를 측정 후, 가장 가까운 중심점을 기준으로 데이터를 할당 하는것으로 클러스터화 하여 레이블을 할당한다
- 새로운 중심점 선택: 클러스터마다 새로운 중심점을 계산한다.
- 범위 확인: 선택된 중심점에 변화가 없다면 진행을 멈추고, 있다면 2~3 과정을 반복한다
하지만 K-평균 군집화 알고리즘은 다음 상황에서는 사용하지 않는것이 권장된다
- 데이터가 비선형일때: 해당 알고리즘은 각 클러스터간의 거리가 가장 중요하게 동작하는데, 거리라는 조건에 따라 클러스터를 설정하는 행위는 선형적이라고까지 할 수 있게 동작하기 때문에, 데이터가 비 선형적이라면 클러스터가 정상적으로 형성되지 않을 가능성이 높다
- 군집 크기가 다를때: 군집 크기가 다르다면 자연스레 큰 군집의 외각에 있는 데이터가 해당 클러스터의 중심점과 거리가 멀어 다른 클러스터로 합쳐질 가능성이 높은데, 해당 경우가 많이 발생하면 클러스터가 원하는 대로 형성되지 않게 된다
- 군집마다 밀집도와 거리가 다를 때: 위와 거의 동일하다. 밀집도가 낮은 클러스터의 외각에 있는 데이터는 해당 클러스터의 중심점과 거리가 멀어 다른 클러스터와 합쳐지며 데이터가 오염된다
#KMC
km = KMeans(n_clusters=k)
km = km.fit(data_transformed)
print('거리 제곱의 합:', km.inertia_)
거리 제곱의 합(Sum of Squared Distances) 은 가장 가까운 클러스터 중심까지 거리를 제곱한 값을 구할 때 사용하며 다음과 같은 수식이다
※ 추가 정보
KMC 의 단점으로 소수의 데이터가 적절한 클러스터와 거리가 멀리 떨어져 있는, 즉 오목하거나 볼록한 부분을 잘 처리하지 못한다는 점이 있는데
연산량은 조금 더 많지만 이런 노이즈와 이상치를 잘 처리할 수 있는 밀도 기반 군집 분석 (DBSCAN)
이 있다
3-2-2. 주성분 분석(PCA) (09.06)
PCA 는 고차원 데이터에서는 중요하지 않은 변수가 많아지고 성능도 나빠지는 경향이 있어 고차원 데이터를 저차원으로 축소시켜 데이터의 대표 특성만 추출하는 알고리즘 이다.
차원 축소는 다음과 같은 단계로 진행된다
- 데이터들의 분포 특성을 잘 설명하는 벡터 2개 선택: 간단하게는 원형으로 된 클러스터가 있다면 원의 중심을 수직으로 지나는 벡터 2개를 예시로 들 수 있는데, 해당 벡터들의 방향과 크기로 클러스터의 위치, 모양을 예상할 수 있기 때문이다
- 벡터 2개를 위한 가중치를 찾을 때까지 학습: 즉 PCA는 데이터 하나하나의 성분이 아닌, 여러 데이터가 모인 클러스터에서 해당 클러스터의 주성분을 분석하는 방법이기 때문이다
# PCA 학습 예시
# 2차원으로 차원 축소 선언
pca = PCA(n_components=2)
x_principal = pca.fit_transform(x_normalized)
x_principal = pd.DataFrame(x_principal)
x_principal.columns = ['P1', 'P2']
# 모델 튜닝
db = DBSCAN(eps=0.0375, min_samples=50).fit(x_principal)
# min_samples 수를 변경해서 큰 값을 넣는다면 작은 규모의 클러스터가 무시된다
labels = db.labels_
colors = ['r', 'g', 'b', 'c', 'y', 'm', 'k']
cvec = [colors[l] for l in labels]
plt_color = [
plt.scatter(x_principal['P1'], x_principal['P2'],marker='o', color=c)
for c in colors1
]
plt.figure(figsize=(9, 9))
plt.scatter(x_principal['P1'], x_principal['P2'], c=cvec)
plt.legend(
plt_color,
['Label' + str(i) for i in range(7)],
scatterpoints=1,
loc='upper left',
ncol=3,
fontsize=8
)
plt.show()
4. 딥러닝 시작
4-1. 인공 신경망의 한계와 딥러닝의 출현 (09.07)
오늘날 인공 신경망의 시초는 1957년에 고안된 퍼셉트론
이라는 머신으로부터 시작되었는데.
퍼셉트론의 원리는 입력노드 n개의
이 퍼셉트론을 이용하여 and, or 게이트의 경우에는
그래서 이를 해결하기 위해 입력 레이어와 출력 레이어 사이 하나 이상의 은닉 레이어를 두어 비선형적 데이터에 대해서도 학습이 가능하도록 다층 퍼셉트론
머신을 고안했다.
그리고 이렇게 은닉층이 여러 개 있는 신경망을 심층 신경망(Deep Neural network, DNN) 이라고 하며 이를 또 다른 이름으로는 딥 러닝이라고 한다.
4-2. 딥러닝 구조 (09.07)
딥러닝은 다음과 같은 요소들로 이루어져 있다
이름 | 설명 |
---|---|
입력층 | 데이터를 받아들이는 층 |
은닉층 | 입력 노드로부터 값을 받아 연산을 한 뒤, 활성화 함수에 적용하여 다음 레이어로 전달 |
출력층 | 최종 결괏값이 포함된 층 |
가중치(weight) | 노드와 노드간 연결 강도, 낮을수록 이전 노드를 무시하고 높을수록 이전 노드에 영향을 많이 받음 |
바이어스(bias) | 가중합에 더해 주는 상수, 최종적으로 출력되는 값을 조절함 |
전달 함수 | 가중치와 신호의 곱을 합한 것, |
활성화 함수 | 노드에서 연산된 결과를 적절히 처리하여 출력하는 비선형 함수 |
손실 함수 | 예측 결과와 실제 값의 오차를 측정하는 함수, 역전파에 사용된다 |
4-2-1. 활성화 함수 (09.07)
활성화 함수에는 다음과 같은 함수들이 있다
시그모이드 함수
시그모이드 함수는 결과를 0~1 범위의 비선형으로 변환시켜 준다. 과거에는 자주 쓰였으나 신경망의 깊이가 깊어지며 역전파 과정에서 출력층에 거리가 먼 레이어에는 기울기값이 극도로 작아져 최악의 경우 훈련이 멈추는 문제가 발생한다. 이런 문제를 기울기 소멸 문제
라고 한다
하이퍼볼릭 탄젠트 함수
하이퍼볼릭 탄젠트 함수는 결과를 -1~1 범위의 비선형으로 변환시켜 준다 시그모이드 함수에서 결과의 평균이 양수로 편향되는 문제는 해결됐지만 기울기 소멸 문제는 해결되지 않음
렐루 함수
최근 가장 많이 사용되는 함수로 렐루는 입력이 음수일때는 0을 출력하고 양수일때는 x를 출력하는 함수다. 경사 하강법에 영향을 주지 않아 학습 속도가 빠르고, 기울기 소멸이 발생하지 않는 장점이 있지만
반대로 음수값을 입력받으면 항상 0을 반환해 학습 능력이 감소하는 문제가 있다
리키 렐루 함수
입력값이 음수이면 이 아닌 0.001 처럼 매우 작은 수를 반환해 입력값이 수렴하는 구간이 제거되어 렐루함수의 문제를 일부 해결한다
소프트맥스 함수 소프트맥스는 입력값을 0~1 사이에 출력되도록 정규화하여 출력 값들의 합이 항상 1이 되도록 한다.
소프트맥스는 연산량이 많아 은닉층에서 보다 출력 노드의 활성화 함수로 많이 사용한다
해당 함수는 출력층의 뉴런 개수, 출력층의 n번째 뉴런등에 영향을 받아 그래프를 그리기에 적합하지 않음
4-2-2. 손실함수 (09.07)
경사 하강법은 학습률과 손실함수의 순간 기울기(미분)를 이용하여 은닉층의 가중치를 업데이트 하는 방법이다.
대표적인 손실 함수로는 평균 제곱 오차와 크로스 엔트로피 오차가 있다
평균 제곱 오차(MSE)
선형 회귀 에서 간단히 평가하는 법으로 설명했지만 자세히 한번 더 서술하겠다
실제 값과 예측 값의 차이를 제곱하여 평균을 낸 것이 평균 제곱 오차(MSE)이다. 위에서 말한것 처럼 회귀에서 손실함수로 주로 사용되는 함수이다
크로스 엔트로피 오차(CEE)
크로스 엔트로피 오차는 분류 문제에서 원 핫 인코딩 했을 때만 사용할 수 있는 오차 계산법이다
일반적인 시그모이드 함수를 사용하면 시그모이드 함수의 자연상수
4-2-3. 딥러닝 학습 (09.07)
딥 러닝은 크게 순전파와 역전파로 진행되는데
순전파는 간단히 말해 입력 레이어에 데이터를 넣어 출력값을 계산해 보는 과정이다. 역전파는 순전파를 통해 얻은 예측값을 이용해 손실 함수를 계산하고 손실 함수 결과가 0에 가깝도록 가중치를 출력층에서 입력층 방향으로 조정한다
딥 러닝을 하며 발생할 수 있는 문제들로는 다음과 같은 문제들이 있다
과적합 문제는 SVM에서 파라미터들과 함께 설명되어 있다
과적합을 해결하는 방법으로 드롭아웃이 있는데, 드롭아웃은 학습 과정중 임의로 일부 노드들을 학습에서 제외시키는 방법이다
nn.Dropout(0.25) # 25%의 노드를 무작위로 사용하지 않겠다
같이 사용할 수 있다
기울기 소멸 문제는 시그모이드 함수에서 자세히 설명했다
성능이 나빠지는 문제는 경사 하강법이 손실함수의 결과가 최소가 되는 방향으로 기울기를 계속 이동시키는데 이 때, 점점 손실함수 결과의 변화량이 적어지는 문제가 있다.
개인적으로는 문제라고는 생각 안하는데 책은 문제란다.
이를 해결하기 위해 다음과 같은 방법을 쓴다
-
배치 경사 하강법(Batch Gradient Descent, BGD) 은 전체 데이터셋에 대한 오류를 구한 후 기울기를 한 번만 계산하여 모델의 파라미터를 업데이트 한다. 즉, 전체 훈련 데이터셋에 대해 가중치를 편미분한다. 하지만 한 스텝에 모든 훈련 데이터셋을 사용해서 학습이 오래 걸리는 단점이 있다
$W = W-a \nabla J(W, b)$
($a$ : 학습률,$J$ : 손실 함수) -
확률적 경사 하강법(Stochastic Gradient Descent, SGD) 은 무작위로 선택한 데이터에 대해 기울기를 계산한다.
파라미터의 변경 폭이 불안정하고, 때로는 배치 경사 하강법보다 정확도가 낮을 수 있지만 속도가 빠르다 -
미니 배치 경사 하강법(mini-batch gradient descent) 은 전체 데이터셋을 미니 배치 여러 개로 나누고 각각의 기울기를 구해 전체의 평균을 이용하여 학습한다.
BGD 처럼 모든 데이터를 계산하는 것 보다 빠르고, SGD 보다 안정적이기 때문에 실제로 가장 많이 사용한다.
옵티마이저는 SGD의 파리미터 변경 폭이 불안정한 문제를 해결하기 위해 학습 속도와 운동량을 조절하는 역할을 한다. 옵티마이저는 전체 weight를 조절하는것이 아닌, 노드마다 각각 적용되는것으로 보인다.
다양한 옵티마이저가 있지만 아담 이라는 옵티마이저가 가장 보편적으로 쓰인다
4-3. 딥러닝 신경망의 종류 (09.07)
딥러닝 알고리즘은 심층 신경망을 사용한다는 공통점이 있는데 목적에 따라 다음과 같이 분리된다
-
심층 신경망(DNN): 입력층과 출력층 사이에 다수의 은닉층을 포함하는 신경망이다. 머신 러닝에서는 비선형 분류를 위해 다양한 트릭을 사용했는데, DNN은 다수의 은닉층 덕분에 별다른 트릭 없이 비선형 분류가 가능하다.
하지만 노드와 레이어가 다른 신경망보다 많아 학습을 위한 연산량이 많고 기울기 소멸 문제 등이 발생할 수 있다. 때문에 앞서 설명한 드롭아웃, ReLU 등을 적용해야 한다. - 합성곱 신경망(CNN): 합성곱층, 풀링층을 포함하는 신경망이다. 합성곱층 특징상 인접한 픽셀의 관계를 유지할 수 있어 이미지 처리 성능이 좋다. 또한 추출한 이미지의 특징을 모으고 강화하는 풀링층 덕분에 이미지에서 객체를 찾는 목적으로도 많이 쓰인다.
-
순환 신경망(RNN): 음악이나 영상 등 시간 흐름에 따라 변화하는 데이터를 학습하기 위한 신경망으로, 현재 결과가 이전 결과와 연관이 있다는 의미이다.
RNN은 시간에 따라 내용이 변하므로 데이터는 동적이고 길이가 가변적이라는 특징이 있다. 자연어 처리 분야와 궁합이 맞아 텍스트 생성, 자동 번역, 음성인식 등에 사용된다. -
제한된 볼츠만 머신(RBM): 볼츠만 머신은 가시층과 은닉층으로 구성된 모델인데 이중, 가시층간, 은닉층간의 연결이 없어 오직 가시층과 은닉층 끼리만 연결된 신경망이다.
RBM은 차원 감소, 분류, 선형 회귀 분석 등 특성을 추출하는데 많이 쓰이고 기울기 소멸 문제를 해결하기 위한 사전 학습으로도 활용 가능하다.
RBM은 단독으로는 잘 쓰이지 않고 심층 신뢰 신경망의 요소로 활용된다. -
심층 신뢰 신경망(DBN): RBM을 블록처럼 여러 층으로 쌓은 형태로 연결된 신경망이다.
훈련할 때는 기존 레이어의 값을 고정한 뒤, RBM을 하나 쌓아 훈련하는 방식으로 훈련한다.
비지도 학습으로 학습하며, 출력레이어에 가까운 레이어일수록 추상적인 특성을 추출한다.
5. 합성곱 신경망
5-1 합성곱 신경망 특징 (09.07)
다차원 배열에서 인접한 요소끼리 관련이 있을 때 이를 레이어에 넣기 위해 1차원 데이터로 변환시켜 넣을 경우, 인접한 요소끼리의 연관성이 소멸되어 버리기 때문에 인접한 요소의 연관성을 유지하기 위해 사용한다. 대표적으로는 이미지 및 영상이 있다.
합성곱 신경망은 다음과 같은 구조로 이루어져 있다.
- 입력층
- 합성곱층 + 렐루 등의 활성화함수
- 풀링층
- 완전 연결층 (Fully Connected layer)
- 출력층 + softmax 등의 활성화 함수 여기서 2~3 단계는 중첩되어 다차원 레이어를 구성할 수 있다.
각 요소는 다음과 같은 특징을 가지고 있다
-
입력층: 이미지 데이터가 입력되는 층으로 높이, 넓이, 채널의 3차원 데이터이다. (gray scale 의 경우 단일채널로 2d)
-
합성곱층: 말로 설명하기 힘든점이 많지만 최대한 말로 설명해보자면 다음과 같다.
- 합성곱은 단일 채널에 대해 이뤄진다.
- n*m 의 크기를 가진 사각형을 그리며 이를
커널/필터
라 부른다 - 가중치가 매겨진 커널과 이미지를 겹쳐 가중치 연산을 수행하고 그 합을 새로운 2차원 배열에 넣는다.
- 왼쪽 위에서부터
스트라이드
파라미터만큼 커널을 우측 혹은 하단으로 움직이며 모든 위치에 수행한다.
컬러 이미지는 각 채널에 대해 가중치가 다른 동일한 필터를 적용시켜 3개의 행렬을 얻고, 이를 더해 특성 맵을 얻는다
또한 커널은 여러 개 있을 수 있으며 이 경우에는 특성 각각 동일한 계산을 한 뒤, 특성맵의 채널이 채널의 개수와 동일하게 된다 -
풀링층: 풀링층은 특서어 맵을 다운샘플링하여 연산량을 감소시키고, 주요한 특성을 추출하는 레이어다.
탐색은 합성곱층과 유사하게 커널을 사용하지만, 가중치 계산의 합이 아닌, 단순히 해당 커널 범위의 수중 최대/평균 값을 결과로 한다. 하지만 대부분 특성이 희미해지는 문제를 피하기 위해 최대값을 사용한다 -
완전연결층: 풀링층을 거치며 차원이 축소된 특성 맵이 해당 레이어에서 완전이 펼쳐저 1차원 벡터로 반환한다.
-
출력층: 마지막 출력층에서는 활성화함수를 거쳐 각 레이블에 속할 확률이 나오며 이중, 가장 높은 확률값의 레이블이 최종값으로 선정된다.
합성곱은 다양한 입력차원, 다양한 필터차원으로 구성될 수 있어서
그래프를 완화하는데 쓰이는 1D 합성곱 부터
3D 입력 M, H, L 을 받아 2D 필터 k, k,L을 적용시켜 2차원의 출력을 갖는 합성곱도 있다
또한 모든 레이어끼리는 출력 개수와 입력 개수가 동일해야 하는데, 각 계층마다 출력 크기를 구하는 공식은 다음과 같다
-
Conv2D:
$(W-F+2P)/S + 1$ -
$W$ : 입력 데이터의 크기 -
$F$ : 커널 크기 -
$P$ : 패딩 크기 -
$S$ : 스트라이드
-
-
MaxPool2D:
$IF / F$ -
$IF$ : 입력 필터의 크기 -
$F$ : 커널 크기
-
5-2. 합성곱 신경망 실습 (09.07)
# 심층 신경망 모델 정의
class FashionCNN(nn.Module):
def __init__(self):
super().__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
)
self.layer2 = nn.Sequential(
nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(2)
)
self.fc1 = nn.Linear(in_features=64*6*6, out_features=600)
self.drop = nn.Dropout(0.25)
self.fc2 = nn.Linear(in_features=600, out_features=120)
self.fc3 = nn.Linear(in_features=120, out_features=10)
def forward(self, x):
out = self.layer1(x)
out = self.layer2(out)
out = out.view(out.size(0), -1)
out = self.fc1(out)
out = self.drop(out)
out = self.fc2(out)
out = self.fc3(out)
return out
위 모델을 이용한 훈련과정 예시
# 파라미터 설정
learning_rate = 0.001
path = './data/CNN_test'
# 필요한 파라미터 정의
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
model = FashionCNN()
model.to(device)
# 데이터셋 다운로드
train_dataset = torchvision.datasets.FashionMNIST(path, download=True, train=True, transform=transform.Compose([transform.ToTensor()]))
test_dataset = torchvision.datasets.FashionMNIST(path, download=True, train=False, transform=transform.Compose([transform.ToTensor()]))
# 데이터로더에 데이터 전달
train_loader = DataLoader(train_dataset, batch_size=100)
test_loader = DataLoader(test_dataset, batch_size=100)
# 손실함수 및 옵티마이저
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate)
# 모델 구조 및 각 레이어의 입/출력 노드 확인
print(model)
# 학습
num_epochs = 5
count = 0
loss_list = list()
iteration_list = list()
accuracy_list = list()
prediction_list = list()
labels_list = list()
for epoch in range(num_epochs):
for img, lab in train_loader:
img, lab = img.to(device), lab.to(device)
train = Variable(img.view(100, 1, 28, 28))
lab = Variable(lab)
outputs = model(train)
loss = criterion(outputs, lab)
optimizer.zero_grad()
# 역전파
loss.backward()
optimizer.step()
count += 1
if count % 50 == 0:
total = 0
correct = 0
for images, labels in test_loader:
images, labels = images.to(device), labels.to(device)
labels_list.append(labels)
test = Variable(images.view(100, 1, 28, 28))
outputs = model(test)
predictions = torch.max(outputs, 1)[1].to(device)
prediction_list.append(predictions)
correct += (predictions == labels).sum()
total += len(labels)
accuracy = correct * 100 / total
loss_list.append(loss.data)
iteration_list.append(count)
accuracy_list.append(accuracy)
if count % 500 == 0:
print('Iteration: {}, Loss: {}, Accuracy: {}%'.format(count, loss.data, accuracy))
5-3. 전이 학습 (09.11)
전이 학습이란, 작은 규모로 AI를 만들 때, 그때그때 필요한 방대한 규모의 데이터셋을 얻어 훈련하는것이 불가능하기 때문에 아주 큰 데이터셋을 사용해서 훈련된 모델의 가중치를 가져와 목적에 맞게 보정하여 사용하는것을 의미한다.
이미 어느 정도 기능하는 모델을 기반으로, 우리가 원하는 방향으로 수정하는것이기 때문에 적은 데이터셋으로도 효율적인 결과를 얻어낼 수 있다.
5-3-1. 특성 추출 기법 (09.11)
특성 추출 기법이란, 사전 훈련된 모델의 FC 층만 새로 학습하고 나머지 레이어는 그대로 사용한다.
때문에 주로 이미지의 특성을 추출해내는 CV 레이어는 그대로 사용하고, 마지막에 레이블을 붙이는 단계만 목적에 맞게 학습하게 된다.
여기에서 사용 가능한 이미지 분류 모델은 VGG, ResNet 등이 있다.
전이학습 예시
# 사전 훈련된 모델 가져오기
resnet18 = models.resnet18(pretrained=True)
def set_parameter_requires_grad(model, feature_extracting=True):
if feature_extracting:
for param in model.parameters():
param.requires_grad = False
# 사전 훈련된 모델은 학습(가중치 조정)을 진행하지 않음
set_parameter_requires_grad(resnet18)
# 완전연결층 추가
resnet18.fc = nn.Linear(512, 2)
for name, param in resnet18.named_parameters():
if param.requires_grad:
print(name, param.data)
# 모델 훈련 및 테스트 시작
5-3-2.미세 조정 기법 (09.11)
미세 조정 기법이란, 특성 추출 기법에서 더 나아가 사전 훈련된 모델과 합성곱층, 데이터 분류기의 가중치까지 업데이트하여 훈련시키는 방식이다. 특성 추출 기법은 목표 특성과 다른 특성이 추출되어있는 경우 쓰기 어렵지만, 미세 조정 기법은 새로운 데이터로 특성을 다시 추출할 수 있다. 미세 조정 기법은 훈련시키려는 데이터셋의 크기와 사전 훈련된 모델에 따라 다음 전략을 사용할 수 있다
- 데이터셋이 크고, 사전 훈련 모델과 유사성이 작을 경우: 모델 전체를 재학습 한다. 유사성이 작지만 데이터 셋이 크기 때문에 전체를 재학습이 가능하며, 효율적이다
- 데이터셋이 크고, 사전 훈련 모델과 유사성이 클 경우: CV층의 뒷부분(FC층과 가까운 부분)과 데이터 분류기를 학습시킨다. 유사성이 크기 때문에 전체를 학습하는것 보다 강한 특징이 나타나는 CV층의 뒷부분과 데이터 분류기만 새로 학습해도 최적의 효율을 낼 수 있다.
- 데이터셋이 작고 사전 훈련 모델과 유사성이 작을 경우: 합성곱층의 일부분과 데이터 분류기를 학습시킨다. 데이터가 작아 아무리 미세 조정 기법을 사용해도 효과가 없을 수 있다. 따라서 그나마 최적의 방법으로 합성곱층을 적당히 새로 학습시킨다.
- 데이터셋이 작고 사전 훈련 모델과 유사성이 클 경우: 데이터 분류기만 학습한다. 데이터가 적어 많은 계층을 새로 훈련시키면 과적합의 가능성이 크며, 효과가 크지 않을 것이다. 따라서 최종 데이터 분류기인 FC층에 대해서만 재학습한다.
5-3-3.그래프 합성곱 네트워크 (09.12)
그래프 합성곱 네트워크는 그래프 데이터를 위한 신경망으로, 그래프 신경망은 다음과 같은 2 행렬로 표현한다
- 인접 행렬: 노드 n개를 n*n 개의 행렬로 표현하고, 간단한 예시로 노드
$A_{ji}$ 가 1이면$A_i$ 와$A_j$ 가 연결되어있다고 표현할 수 있다 - 특성 행렬:
- 인접 행렬은 노드간 연결관계만 알 수 있기 때문에 특성을 파악하기 위해 단위 행렬을 사용한다.
- 각 입력 데이터에서 이용할 특성을 선택
- 특성 행렬에서 각 행은 선택된 특성에 대해 각 노드가 갖는 값이다. 즉 특성 행렬은 노드는 n개고, 특성은 m 개인
$n*m$ 으로 표현되는 행렬이다.
그래프 신경망은 보통 SNS에서 관계 네트워크, 3D Mesh, 학술 연구에서 인용 네트워크 등에서 사용된다.
6. 합성곱 신경망
6-1 이미지 분류를 위한 신경망 (09.12)
이미지 분류를 위해서 보통 Conv 레이어와 maxPool 층, FC층 등을 사용하는데, 이 때, 각 레이어마다 사용할 입/출력 노드 수가 고민될 수 있다. 그때, 각 파라미터와 배치 순서, 횟수 등을 일종의 템플릿처럼 사용하는 방식이 있다.
6-1-1. LeNet-5 (09.12)
얄 르쿤(Yann LeCun)이 최초로 개발한 개념으로, 1995년 얀 르쿤, 레옹 보토, 요슈아 벤지오, 패트릭 하프너가 손글씨를 인식하는 A구조를 발표했고, 이것이 현재 CNN의 초석이 되었다.
LeNet-5는 다음과 같은 계층으로 구성되어 있다
계층 유형 | 특성 맵 | 크기 | 커널 크기 | 스트라이드 | 활성화 함수 |
---|---|---|---|---|---|
이미지 | 1 | 32 x 32 | - | - | - |
Conv | 6 | 28 x 28 | 5 x 5 | 1 | ReLU |
MaxPool | 6 | 14 x 14 | 2 x 2 | 2 | - |
Conv | 16 | 10 x 10 | 5 x 5 | 1 | ReLU |
MaxPool | 16 | 5 x 5 | 2 x 2 | 2 | - |
FC | - | 120 | - | - | ReLU |
FC | - | 84 | - | - | ReLU |
FC | - | 2 | - | - | SoftMax |
6-1-2. AlexNet (09.12)
ImageNet 영상 데이터베이스를 기반으로 한 화상 인식 대회인 'ILSVRC 2012' 에서 우승한 NCC 구조이다.
AlexNet 은 합성곱층 5개와 FC층 3개로 구성되어 있으며 맨 마지막 FC층은 카테고리 100개를 분류하기 위해 활성화 함수로 SoftMax 를 사용하고 있다. 전체적으로는 GPU 2개를 기반으로 한 병렬 구조인것을 제외하면 LeNet-5 와 큰 차이가 없다
AlexNet 의 구조는 위 사진과 같은데, 특징으로 위와 아래를 GPU 1, 2 에서 독립적으로 계산한다.
GPU-1 에서는 주로 색상과 관련 없는 정보를 추출하기 위한 커널이 생성되고, GPU-2 에서는 주로 색상과 관련된 정보를 추출하기 위한 커널이 학습된다.
계층 유형 | 특성 맵 | 크기 | 커널 크기 | 스트라이드 | 활성화 함수 |
---|---|---|---|---|---|
이미지 | 1 | 227 x 227 | - | - | - |
Conv | 96 | 55 x 55 | 11 x 11 | 4 | ReLu |
MaxPool | 96 | 27 x 27 | 3 x 3 | 2 | - |
Conv | 256 | 27 x 27 | 5 x 5 | 1 | ReLU |
MaxPool | 256 | 13 x 13 | 3 x 3 | 2 | - |
Conv | 384 | 13 x 13 | 3 x 3 | 1 | ReLU |
Conv | 384 | 13 x 13 | 3 x 3 | 1 | ReLU |
Conv | 256 | 13 x 13 | 3 x 3 | 1 | ReLU |
MaxPool | 256 | 6 x 6 | 3 x 3 | 2 | - |
FC | - | 4096 | - | - | ReLU |
FC | - | 4096 | - | - | ReLU |
FC | - | 1000 | - | - | SoftMax |
6-1-3. VGGNet (09.12)
VGGNet 은 카렌 시모니안(karen Simonyan) 과 엔드류 지서만이 2015 ICLR에 게재한 논문인 "Very deep convolutional networks for large-scale image recognition" 에서 처음 발표됐다.
합성곱층의 파라미터 수를 줄이고 훈련 시간을 개선하려고 탄생했다.
깊이의 영향만 최대한 확인하고자 모든 합성곱층의 필터/커널 크기를 3x3 로 고정했다.
모든 합성곱 커널의 크기는 3x4, 최대 풀링 커널은 2x2, 스트라이드는 2이고, 마지막 16번째 레이어를 제외하고는 모두 ReLU 함수가 적용된다
VGG16 구조 상세
계층 유형 | 특성 맵 | 크기 | 커널 크기 | 스트라이드 | 활성화 함수 |
---|---|---|---|---|---|
이미지 | 1 | 224 x 224 | - | - | - |
Conv | 64 | 224 x 224 | 3 x 3 | 1 | ReLU |
Conv | 64 | 224 x 224 | 3 x 3 | 1 | ReLU |
MaxPool | 64 | 112 x 112 | 2 x 2 | 2 | - |
Conv | 128 | 112 x 112 | 3 x 3 | 1 | ReLU |
Conv | 128 | 112 x 112 | 3 x 3 | 1 | ReLU |
MaxPool | 128 | 56 x 56 | 2 x 2 | 2 | - |
Conv | 256 | 56 x 56 | 3 x 3 | 1 | ReLU |
Conv | 256 | 56 x 56 | 3 x 3 | 1 | ReLU |
Conv | 256 | 56 x 56 | 3 x 3 | 1 | ReLU |
Conv | 256 | 56 x 56 | 3 x 3 | 1 | ReLU |
MaxPool | 256 | 28 x 28 | 2 x 2 | 2 | - |
Conv | 512 | 28 x 28 | 3 x 3 | 1 | ReLU |
Conv | 512 | 28 x 28 | 3 x 3 | 1 | ReLU |
Conv | 512 | 28 x 28 | 3 x 3 | 1 | ReLU |
Conv | 512 | 28 x 28 | 3 x 3 | 1 | ReLU |
MaxPool | 512 | 14 x 14 | 2 x 2 | 2 | - |
Conv | 512 | 14 x 14 | 3 x 3 | 1 | ReLU |
Conv | 512 | 14 x 14 | 3 x 3 | 1 | ReLU |
Conv | 512 | 14 x 14 | 3 x 3 | 1 | ReLU |
Conv | 512 | 14 x 14 | 3 x 3 | 1 | ReLU |
MaxPool | 512 | 7 x 7 | 2 x 2 | 2 | - |
FC | - | 4096 | - | - | ReLU |
FC | - | 4096 | - | - | ReLU |
FC | - | 1000 | - | - | SoftMax |
6-1-4. GoogLeNet (09.12)
GoogLeNet 은 하드웨어 자원을 최대한 효율적으로 이용하면서 학습 능력은 극대화할 수 있는 깊고 넓은 신경망이다.
특징으로 인셉션 모듈 을 사용하는데, 이 모듈에서는 특징을 효율적으로 추출하기 위해 1x1, 3x3, 5x5의 합성곱 연산을 각각 수행한다. 3x3 최대 풀링에서는 입력과 출력의 높이와 넓이가 동일해야 해서 풀링 연산에서는 드물게 패딩을 추가한다.
결과적으로 GoogLeNet 에 적용된 해결 방법은 희소 연결(sparse connectivity) 이다. 의소 연결은 빽빽하게 연결된 신경망 대신 연관성이 높은 노드끼리만 연결하는 방법이다.
이것으로 연산량이 적어지며 과적합도 해결할 수 있다.
6-1-5. ResNet (09.12)
ResNet 은 MS에서 개발한 알고리즘으로 "Deep Residual Learning for Image Recognition" 이라는 논문에서 발표됐다.
해당 알고리즘의 핵심은 깊어진 신경망을 효과적으로 학습하기 위해 레지듀얼(residual) 개념을 고안한 것이다.
일반적으로 신경망 깊이가 깊어질수록 좋아질 것 같지만 앞의 논문에 따르면 깊어질수록 좋아지다가 일정한 단계에 도달하면 오히려 성능이 나빠진다고 한다.
앞에 말한 GoogLeNet 은 22개의 레이어인것에 비해 ResNet 은 1512개의 레이어로 활성화함수로 ReLU 를 선택해도 기울기 소멸 문제가 발생할 수 있다. 때문에 역전파 과정을 진행하며 레이어를 거쳐가는 방법이 아닌, 아이덴티티(숏컷 매핑 함수) 를 도입해 중간 레이어를 건너 뛰어 가중치를 학습하는 것 처럼 하여 문제를 방지했다.
ResNet 에는 블록 이라는 개념이 있는데, 블록은 모듈 처럼 레이어의 묶음이다. 이를 레지듀얼 블록이라고 하고 이것을 모아 ResNet 모델이 된다.
하지만 이렇게 레이어를 계속 올릴 경우 파라미터가 증가해 전체 파라미터가 수백만까지 올라갈 수 있는데 이를 해결하기 위해 병목 블록이라는 것으로 해결했다
ResNet50 에서는 3x3 합성곱층 앞뒤로 1x1 합성곱층이 붙어 있는데, 1x1 합성곱층의 채널 수를 조절하여 차원을 줄였다 늘리는 것이 가능하기 때문에 파라미터 수를 줄일 수 있다. 그리고 이 부분이 병목과 같다고 해서 병목 블록이라고 한다
6-2. 객체 인식을 위한 신경망 (09.12)
객체 인식은 이미지나 영상 내에 있는 객체를 식별하기 위한 컴퓨터 비전 기술이다. 즉, 이미지 내에 있는 여러 객체에 대해 각 객체가 무엇이고, 그 위치가 어디인지 박스로 나타내는 위치 검출 문제를 다루는 분야이다. 따라서 다음과 같이 정의할 수 있다.
1단계 객체 인식은 객체 인식의 두 문제를 동시에 행하는 방법이고 2단계 객체 인식은 두 문제를 순차적으로 행하는 방법이다. 따라서 1단계 객체 인식이 비교적 빠르지만 정확도가 낮고 2단계 객체 인식은 그 반대이다.
1단계 객체 인식에는 YOLO 계열과 SSD 계열이 있고, 2단계 객체 인식은 CNN 을 처음으로 적용시킨 R-CNN 계열이 대표적이다.
객체 인식은 자율 주행 자동차, CCTV, 무인 점포 등에서 활용된다.
객체 인식 알고리즘은 다음과 같은 것들이 있다
- R-CNN
- 공간 피라미드 풀링
- Fast R-CNN
- Faster R-CNN
6-3. 이미지 분헐을 위한 신경망 (09.12)
이미지 분할은 신경망을 훈련시켜 이미지를 픽셀 단위로 분할하는 것이다. 즉, 이미지를 픽셀 단위로 분할하여 이미지에 포함된 객체를 추출한다. 종류로는 다음과 같은 것들이 있다.
- 완전 합성곱 네트워크
- 합성곱 & 역합성곱 네트워크
- U-Net
- PSPNet
- DeepLabv3/DeepLabv3+
2. 파이썬 Flask
Flask 는 파이썬 기반으로 만든 웹 프레임워크로 적당히 가볍고 적당히 다양한 기능을 제공해 많이 쓰인다.
Flask 는 호스트 아이피, 포트 등을 설정할 수 있다.
디자인 패턴으로 MVT 패턴을 채용하고 있다
단순히 Html 을 외부에 보여 주는 기능이 아닌, GET, POST, PUT, DELETE 등의 메소드를 지원해 RestFul API 서버를 만드는데에도 쓸 수 있다.
엔드포인트 추가
@app.route("/smaplePage")
def samplePage():
return '<h1> Hello Flask! </h1>'
메소드 추가
@app.route("/samplePage", methods=["GET", "POST"]):
def samplePage():
pass
경로 변수 획득
@app.route("/samplePage/<var1>")
def samplePage(var1):
pass
html 문서 표시 및 변수 전달
@app.route("/showHtml/<var1>"):
def showHtml(var1):
return render_template("target.html", var1=var1)
html 문서에서 전달받은 변수 사용
<h1> var1: {{var1}}</h1>
- 애플리케이션 컨텍스트
앱 레벨의 데이터를 사용할 수 있도록 하는 컨텍스트. 애플리케이션 레벨의 데이터는 다음과 같은 종류가 있다- 실행중인 앱의 인스턴스인 current_app
- 요청을 통해 이용할수 있는 일시 영역(매 요청 마다 초기화됨)을 사용하는 g
의 컨텍스트가 있다.
만일 컨텍스트를 사용하지 않고, 앱에 직접 접근할 경우에는 앱의 규모가 커지면 순환 참조가 발생하기 쉬운데, 이를 해결하기 위해 요청을 하면 스택에 push 하며 각 컨텍스트에서 사용할 수 있게 된다.
- 요청 컨텍스트
요청이 있는 동안 요청 레벨의 데이터를 이용할 수 있도록 한 것 - Flash 메시지
동작 후에 간단한 메시지를 표시하는 기능으로 다음과 같이 사용한다
주의점으로 세션에 메시지를 남기는 것임으로 세션을 만들기 위해 config 의 SECRET_KEY 를 설정해야 한다
# 파이썬
flash('test message')
<!--html -->
<h1> Test Flash is {{get_flashed_messages()}}</h1>
- 로깅
app.logger.serLevel(logging.DEBUG)
app.logger.critical('fatal error')
app.logger.error('error')
app.logger.warning('warning')
app.logger.info('info')
app.logger.debug('debug')
- 쿠키/세션
# 값 획득
cookie_value = request.cookies.get('key')
session_value = session['key']
# 값 설정
response.set_cookie('key', 'value')
session['key'] = 'value'
# 값 삭제
response.delete_cookie('key')
session.pop('key', None)
블루프린트는 앱을 분할하기 위한 flask 의 기능이다. 앱의 규모가 커져도 간결한 상태가 유지되어 유지보수가 수월해진다.
# App.py
from sampleFolder import view
app.register_blueprint(sampleFolder.blueprintApp, url_prefix="/sampleBlueprint")
# sampleFolder.view.py
blueprintApp = Blueprint(
"blueprintApp",
__name__,
template_folder="template",
static_folder="static",
)
@blueprintApp.route('/')
def home():
return '<h1> samplebluePrint App</h1>'
만일 위의 코드를 실행한다면, localhost:5000/sampleBlueprint 도메인 아래에 blueprintApp 이 표시된다
# SQLAlchemy 선언
db = SQLAlchemy()
# 모델 및 모델을 넣을 테이블 정의
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String, index=True)
email = db.Column(db.String, unique=True, index=True)
password_hash = db.Column(db.String)
created_at = db.Column(db.DateTime, default=datetime.now)
update_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
@property
def password(self):
raise AttributeError("읽어 들일 수 없음")
@password.setter
def password(self, password):
self.password_hash = generate_password_hash(password)
# 테이블에 새로운 row 추가 (Create)
user = User(
username='normaly nickname',
email='[email protected]',
password='1q2w3e4r', # 모델에 해시함수가 있어 자동으로 해싱된 값이 테이블에 들어감
)
db.session.add(user)
db.session.commit()
# 테이블의 모든 데이터 가져오기 (Read)
users = User.query.all()
# 테이블의 개별 row 에 대해 수정 (Update)
user = get_spesific_user() # 임의의 유저를 가져오는 함수 (read)
user.username = 'updated user name'
user.email = '[email protected]'
user.password = 'updated password' # Create 와 동일한 이유로 해싱됨
db.session.add(user)
db.session.commit()
# 테이블의 개별 row 삭제 (Delete)
user = get_spesific_user() # 임의의 유저를 가져오는 함수
db.session.delete(user)
db.session.commit()
위와 같은 Create, Read, Update, Delete 동작을 데이터베이스 조작에 필요한 최소조건으로 말하며 일명 CRUD 라고 한다.
Html 파일들을 이용해 다양한 페이지를 작성하다 보면, navBar 나 Home button 등, 공통적으로 사용되는 디자인이나 css, js 등이 있는데 이를 매 페이지마다 새로 작성하면 다음과 같은 문제가 발생할 수 있다.
- 각 페이지마다 모든 디자인을 따로 저장하기 때문에 페이지마다 다른 내용이 들어갈 수 있다.
- 만일 공통적으로 사용되는 디자인이 바뀐다면 모든 페이지에 대해 작업을 다시 해야 한다.
이를 해결하기 위해 Flask 와 함께 사용하는 Jinja 템플릿에서는 하나의 html 파일을 만들고, 이를 다른 html 에서도 재사용 및 수정 을 가능하게 해 준다.
base.html
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
{% block title %}
{% endblock %}
</title>
</head>
<body>
<h1> Base html file</h1>
{% block content %}
{% endblock %}
</body>
</html>
child1.html
{% extends 'base.html' %}
{% block title %}
child 1
{% endblock %}
{% block content}
<h1> child1 htmle file</h1>
{% endblock %}
이와 같이 작성하게 되면 child1.html 은 base 에서 표시하는 글자인 <h1> Base html file</h1>
과 child 에서 표시하는 <h1> child1 htmle file</h1>
이 표시될 것이다
# apps/config.py
basedir = Path(__file__).parent.parent
class Config1:
var1 = "val1"
var2 = "val2"
class Config2(Config1):
var2 = "val2"
var3 = "val3"
var4 = "val4"
config = {
"con1": Config1,
"con2": Config2,
}
# apps/app.py
def create_app(config_key):
app = Flask(__name__)
app.config.from_object(config[config_key])
# 이후 생략
위와 같이 설정 키를 받아 이를 앱에 적용시킬 수 있으며
# "projectDir/.env
FLASK_APP = apps.app:create_app("con1")
와 같이 사용하여 flask run
실행 시 con1 이라는 키를 넘겨 줄 수 있다.
이 단원에서는 모두 flask-login
이라는 패키지를 이용하여 진행된다
기본적으로 flask-login 이 설치되어있다는 가정 하에 진행한다
# apps/app.py
from flask_login import LoginManager
login_manager = LoginManager()
# 미 로그인시 리다이렉트할 엔드포인트
login_manager.login_view = "auth.signup"
# 로그인 후 표시할 메세지
login_manager.login_message = ""
# 앱 등록
login_manager.init_app(app)
# apps/crud/models.py
from apps.app import db, login_manager
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
class User(db.Model, UserMixin):
# ...생략...
def verify_password(self, password):
return check_password_hash(self.password_hash, password)
def is_duplacate_email(self):
return User.query.filter_by(email=self.email).first() is not None
@login_manager.user_loader
def load_user(user_id):
return User.query.get(user_id)
# 이후로는 apps/crud/views.py 의 모든 엔드포인트에 대해 login_required 데코레이터를 붙여 설정을 완료한다
여기에서 crud 의 model 을 다시 정의 할 때 사용된 UserMixin
은 로그인 기능 관련에 필요한 프로퍼티/메서드 를 가지고 있다
프로퍼티/메서드 | 이름 | 설명 |
---|---|---|
method | is_authenticated | 로그인 여부에 대해 boolean 반환 |
method | is_active | 사용자 계정이 활성 상태인지에 대한 boolean 반환 |
method | is_anonymous | 로그인 사용자는 false 를 반환, 익명 사용자는 true 를 반환 |
property | get_id | 로그인 사용자의 유니크 ID |
# 회원가입
if user.is_duplicate_email():
return # 이미 가입된 계정 처리
db.session.add(user)
db.session.commit()
login_user()
next = request.args.get('next') # 쿼리 파라미터에 next 를 가져옴
if next is None or not next.startsWith('/'):
next = '' # 회원가입 후 이동할 기본 페이지
return redirect(next)
# 로그인
user = User.quer.filter_by(email=form.email.data).first()
if user is not None and user.verify_password(password):
login_user(user)
return redirect(page) # 로그인 후 이동할 페이지
else:
# 로그인 실패 처리
# 로그아웃
from flask_login import logout_user
logout_user()
return redirect(page) # 로그아웃 후 이동할 페이지
각 python 코드들을 적당한 html 템플릿과 함께 사용하여 구현한다
# inner join
db.session.query(Model1, Model2).join(Modl2).filter(Model1.id == Model2.id).all()
# F key 가 설정되어 있다면 filter 생략 가능
db.session.query(Model1, Model2).join(Model2).all()
# outter join
db.session.query(Model1, Model2).outerjoin(Model2).filter(Model1.id == Model2.id).all()
릴레이션을 통해 모델에서 관련한 테이블의 객체를 추출할 수 있으며 다음과 같이 사용한다
model1 = relationship("Model1")
릴레이션의 주요 옵션
옵션 명 | 설명 |
---|---|
backref | 다른 모델에 대해서 양방향으로 릴레이션 한다 |
lazy | 관련한 모델을 지연하여 취득하는 옵션, 기본값은 select 며 immediate, joined, subquery, noload, dynamic 등의 옵션이 있다 |
order_by | 정렬할 컬럼을 지정한다 |
이때 backref는 모델 객체에서 다른 모델 객체를 얻어 올 떄 사용된다.