안녕하세요. 

'Going deeper with convolutions' 라는 논문을 읽고 내용을 한번 정리해보고자 글을 작성하게 되었습니다.


 

https://kmhana.tistory.com/3

 

딥러닝을 위한 논문 가이드

☆딥러닝 공부를 위해서 꼭 읽어야 할 논문(사이트)들의 리스트를 정리해보고자한다. 순수히 개인적인 의견으로, 사용자의 관점에서 평가했다. 실제 AI 프로젝트를 진행할 때 도움이 되었던 논

kmhana.tistory.com

딥러닝 논문에 대해서 정말 정리가 잘되어있어서 어떠한 논문으로 시작할지 고민하던 중 많은 도움을 얻었습니다.

혹시나 어떤 논문을 보아야할지 고민이 되신다면 해당 사이트에 들어가셔서 읽어보시면 큰 도움이 되실 것 같습니다.

 

많은 논문 중 해당 논문을 선택한 이유는 ILSVRC 2014 에서 우승한 모델이기도 하며, 구글넷의 시초가 되는 모델이어서 좀 더 자세히 알고 싶어 선택하게 되었습니다.

 


1. Introduction 

Introduction에는 GoogleNet이 나오게 된 배경과, 이미지 인식 및 물체 탐지에 대한 딥러닝 기술의 동향에 대해서 소개하고 있습니다.

 

딥러닝에 더불어 CNN 분야에서 엄청난 발전이 이루어졌습니다. 고무적인 것은 이러한 발전이 발전된 하드웨어, 데이터 셋의 증가, 커진 모델만의 결과가 아닌 새로운 아이디어와 알고리즘 그리고 향상된 네트워크 구조에 의한 발전이라는 것입니다. 구글넷은 2012년 ILSVRC에서 우승했던 모델 보다도, 12배 적은 파라미터를 사용하며 정확도 또한 높습니다. 또한, 물체 탐지 분야의 발전은 단순히 심층 네트워크의 효율성 또는 커진 모델에 의한 것이 아니라 심층 구조와 기존 컴퓨터 비전의 시너지로 부터 이뤄냈습니다.

 

또 다른 주목할만한 요인은 모바일와 임베디드 컴퓨팅 분야에서 알고리즘의 효율성이 대두되고 있다는 점입니다.

이에 모델은 추론시간 내에 15억번의 multiply-adds 연산을 넘지 않도록 설계되어 합리적인 cost로 실사용 되게끔 설계되었습니다.

 

논문에서는, Inception이라는 코드네임을 가진 컴퓨터 비전의 심층 신경망의 효율성에 초점을 맞출 것입니다.

 

*정말 놀란 점은, 파라미터 수를 12배 가량 줄였다는 것입니다.

12배 감소로 인하여, Overfitting의 문제와 Computational Cost를 과연 얼마나 개선할 수 있을지 기대가 되면서도 어떻게 파라미터수를 12배 가량 감소할 수 있었는지 궁금하였습니다. 새삼 구글은 정말 대단하다는 것을 느낄 수 있는 대목이었습니다.


 

2. Related Work

CNN은 Convolution layer에 1개 이상의 FC Layer가 따라오는 표준적인 구조를 가지고 있습니다. ImageNet과 같이 더욱 커진 데이터셋에 대하여 Overfitting 문제를 다루기 위해 dropout을 적용하며 레이어의 갯수와 크기를 증가시키는 것이 최신 트렌드 입니다.

Max-pooling layer들이 정확한 공간적 정보에 대하여 손실을 야기할 수 있음에도 불구하고, AlexNet과 같이 같은 Convolutional Network 구조에서 성공적으로 적용되었습니다. Inception 모델의 모든 필터는 학습하며, Inception layer들이 반복되어 22개 레이어를 가진 GoogLeNet모델을 구성합니다.

 

Network-In-Network는 신경망의 표현력을 증가시키기 위해 Lin et al.에 의하여 제안된 접근법 입니다. NIN을 Convolution layer에 적용하는 것은 1x1 Convolutional layer에 ReLU 함수를 적용하는 것과 같은 관점으로 볼 수 있습니다. 이러한 방법은 현재 CNN 파이프라인에 쉽게 적용될 수 있도록 해줍니다. GoogLeNet도 이러한 접근법을 사용했는데요. 다만, GoogLeNet에서는 1x1 Convolution은 두가지 다른 목적을 가집니다. 첫번째로, 차원 감소 모듈로써 사용하여 병목현상을 제거하는 것이고, 두번째로는 엄청난 성능 감소 없이 네트워크의 깊이와 넓이를 증가시키기 위함입니다.

 

현재 물체 탐지 분야에서 가장 앞서고 있는 접근법은 R-CNN입니다. R-CNN은 두 단계를 거쳐 물체를 탐지합니다. 이에 우리는 우리의 탐지 기법 중 이와 유사한 파이프 라인을 채택하였습니다. 다만, 두 단계에 대하여 끊임없이 발전 시키는 방법과 더 나은 Bounding box proposal의 범주화를 위한 접근법들을 연구하고 있습니다.


 

3. Motivation and High Level Considerations

심층신경망의 성능 증가를 위한 가장 쉬운 방법은 depth(Level의 수)와  width(각 Level의 유닛의 수 증가)를 증가시키는 방법이 있습니다. 이 방법은 좋은 모델을 훈련시키는 쉽고 안전한 방법이지만 두가지 결점이 존재합니다. 큰 사이즈는 아주 많은 수의 파라미터를 의미하는데 이는 학습데이터가 제한된 상황에서 overfitting을 야기하기 쉽습니다.  고품질의 학습 데이터 셋을 만드는 것은 힘들고 비용이 많이 드는 작업이기 때문에 심각한 병목현상을 야기할 수 있습니다. 

 

또 다른 결점으로는 컴퓨팅 resource의 사용량 증가입니다. 예를들어, 심층 비전 신경망에서 일률적은 filter 수의 증가는 연산량을 제곱으로 증가시킵니다. 만약 추가된 기능이 비효율적으로 사용된다면 이는 많은 양의 컴퓨팅 자원의 낭비로 이어집니다. 

 

두가지 문제를 해결하기 위한 가장 근본적인 방법은 Convolution 안이라 할지라도 fully-connected 구조에서 sparsely connected 구조로 변경하는 것입니다. 핵심은 데이터셋의 확률분배를 크고 sparse한  심층신경망으로 표현할 수 있다면, 가장 최근 레이어의 활성화에 대한 연관성 분석과 연관성이 높은 클러스터링 뉴런을 바탕으로 가장 최적의 네트워크 구조를 구성할 수 있다는 것입니다. 이를 수학적으로 증명하기 위해서는 강력한 조건이 요구되지만, 핵심은 덜 엄격한 조건하에서도 실용적으로 적용 가능하다는 것입니다(by Hebbian principle).

 

다만, 오늘날의 컴퓨팅 환경은 균일적이지 않은 sparse data 구조를 다루기에는 cache 누락과 색인에 대한 오버헤드로 인하여 매우 비효율적입니다. 더군다나, 빠른 dense 행렬곱을 가능케하는 라이브러리의 성능 개선과 CPU와 GPU의 사용으로 인하여 그 차이는 점점 벌어졌습니다. 그럼에도 불구하고, sparse한 matrix 연산에 대한 많은 논문들이 sparse matrix를 상대적으로 dense한 submatrix로 클러스터링 하는 것을 제안하였고, 연산에 있어서 실용적인 성능을 보여주었습니다. 이에 Inception 구조는 위 제안을 바탕으로 sparse 구조를 구성하기 위한 정교한 네트워크 구조 구축 알고리즘의 결과를 측정하고 가설의 결과물을 보충하기 위한 하나의 case study로써 시작되었습니다.  추측에 근거한 작업에도 불구하고 우리는 Learnig rate, Hyperparmeter 그리고 발전된 학습 기법을 튜닝하는 것을 통하여 Inception 구조가 Localization과 object detection에 좋은 성능을 보임을 알 수 있었습니다 . 다만, 한가지 주의할 점은 제안된 구조가 컴퓨터 비전에 대하여 성공적이라 할지라도 이것이 의도했던 원칙에 의한 결과인지는 장담할 수 없습니다.


 

4. Architectural Details

출처: https://arxiv.org/pdf/1409.4842v1.pdf

* patch는 fliter라고 보셔도 무방합니다. 

* path-alignment issue란, 사이즈가 짝수일 경우 patch의 중심을 어디로 해야할지 정해야 하는 issue를 의미합니다.

 

Inception 구조의 핵심은 CNN에서 최적의 local sparse 구조를 이용가능한 dense component로 구성하는 방법을 찾는 것입니다. 즉, 최적의 local 구조를 찾아 공간적으로 반복하기만 하면 됩니다. Arora et al.의 연구는 마지막 레이어의 연관성을 분석하여 그들을 연관성에 따라 클러스터링 하는 layer-by layer 구조를 제안하였습니다. 이러한 클러스터링은 다음 레이어의 유닛을 구성하고 이전 레이어의 유닛과 연결 될 것입니다.  그렇게 되면 input에 가까운 낮은 레이어 들에서 연관성이 있는 유닛들은 local region에 집중되게 됩니다. 즉, 단일 region에 많은 클러스터가 집중될 것이며, 이는 NIN에서 제안되었다시피 다음 레이어의 1x1 convolution 레이어에 의해 구성될 수 있습니다.

큰 patch를 기반으로한  convolution에 의하여 cluster들이 공간적으로 넓게 퍼질 것이며, 이를 바탕으로 더욱 커진 region에 대한 patch의 수를 줄일 수 있습니다.  또한, path-alignmet issue들을 피하기 위하여 Inceptuon 구조의 필터 사이즈는 1x1, 3x3, 5x5로 제한하였습니다. (다만 이 결정은 필요성에 의한 것이 아닌 편하기 위한 결정입니다.)  추가적으로, pooling 연산은 필수적인 요소이기 때문에 Figure2(a)와 같이 대체 가능한 parallel pooling path를 추가해준다면  부가적인 이로운 효과를 가질 것입니다.

 

이러한 "Inception module"들이 서로 쌓이게 된다면, output의 연관성은 달라지게 될 것입니다.  높은 레이어에서 더욱 추상화된 feature들이 추출 되기 때문에, 공간적 집중도는 감소될 것이며 이는 레이어가 높아질 수록 3x3, 5x5 convolution의 비율이  증가될 것임을 의미합니다.

 

하지만 Figure 2(a)와 같이 초기 형태에 한하여 Inception Module들은 한가지 큰 문제점이 있습니니다. 적은 수의 5x5 convolution을 사용한다 하더라도 많은 수의 필터를 사용하게 된다면 비용이 너무 비싸질 수 있다는 것입니다. 이 문제는 pooling 유닛 추가되면 더욱 두드러 지는데, convolution layer에 pooling layer가 결합되게 된다면 엄청난 채널 수의 증가를 야기합니다. 설령 이러한 구조에 최적의 sparse 구조를 적용한다하더라도 매우 비효율적이며, 적은 단계로도 엄청난 계산량 증가를 일으킵니다.

 

그래서 이러한 문제점들을 해결하기 위하여 제안된 두번째 아이디어는 계산 요구량이 증가되는 부분에 차원 축소를 적용하는 것입니다. 

이는 성공적인 임베딩을 위한 것인데요. 적은 차원의 임베딩일지라도 큰 이미지 patch에 대한 많은 정보를 포함할 수 있습니다. 다만, 임베딩은 압축된 형식이기 때문에 처리하기 매우 어렵습니다.  또한, Arora et al.의 연구 조건에 따르면 우리는 representation을 최대한 sparse 하게 유지해야하며 신호들을 압축할 수 있어야 합니다. 이를 위해, cost가 큰 3x3 & 5x5 convolution 전에 계산량 감소를 위한 1x1 convolution이 사용됩니다. 그리고 1x1 convolution을 사용함으로써 계산량 감소에 더불어 ReLU 함수를 적용이라는 두가지 목적을 달성할 수 있습니다. 그 최종 결과는 Figure 2(b)와 같습니다.

 

*ReLU 함수를 적용한다는 것은 레이어에 비선형성을 더해준다는 것과 같습니다.

*따라서, 1x1 convolution 추가는 차원 감소를 통한 연산량 감소 & 레이어에 비선형성을 더해주는 역할로 두가지 이점이 생겨나게 됩니다.

 

이 구조의 장점 중 하나는 계산 복잡도의 증가를 신경쓰지 않고도 각 단계에서 유닛의 수를 늘릴 수 있다는 것입니다. 이는 차원 감소를 유기적으로 활용함에 따라, 큰 patch size의 차원 convolution에 앞서 차원을 감소 시킴으로써 input filter들의 수를 조절할 수 있기 때문입니다. 또 다른 장점은 서로 다른 scale의 feature들을 한번에 추상화 할 수 있다는 점입니다.

 

발전된 컴퓨팅 resource 사용을 바탕으로 stage의 수와 더불어 각 단계의 width도 증가시킬 수 있습니다. 성능은 약간 떨어질지 모르나, 값 싼 cost로 활용할 수 있다는 것이죠. 또한, Inception 구조에 포함되어 있는 장치(knobs and levers)들을 통한 컴퓨팅 자원의 밸런싱을 바탕으로 non-Inception 구조를 가진 비슷한 성능의 네트워크보다 2 ~ 3배 빠른 네트워크를 만들 수 있음을 알아내었습니다. 


5. GoogLeNet

출처: https://arxiv.org/pdf/1409.4842v1.pdf

ILSVRC14에 제출한 팀 이름인 GoogLeNet은 Yann LeCuns의 LeNet-5에서 오마주 하였으며, Inception 구조의 한 형태를 말합니다. 모델을 더 깊고 넓게 구성하는 것은 성능에 크게 영향을 미치지 못하였으며, 앙상블 기법을 적용했을 때는 약간의 성능 항샹을 보였습니다.

실험 결과 정확한 구조적 파라마터들의 영향은 미미한 것으로 밝혀져 네트워크에 대한 세부사항은 생략하도록 하겠습니다. Table 1은 Inception 구조에서 가장 성능이 잘나왔던 instance들을 나타낸 표입니다. 

 

Inception 모듈의 내부에 있는 것까지 포함하여 모든 convolution들은 ReLU함수가 적용되어 있습니다. 또한 Input은 평균차감(mean subtraction)이 적용된 224x224 사이즈의 RGB 채널입니다. '#3x3 reduce' & '#5x5 reduce'는 3x3, 5x5 convolution 전에 사용된 1x1 필터의 갯수를 의미합니다. 또한, 모든 reduction/projection 레이어들에도 ReLU함수가 적용되어 있습니다.

 

이 네트워크는 컴퓨팅 효율성와 실용성에 초점을 맞추어 고안되었으며, 파리미터가 있는 레이어만 셀 경우 22개의 레이어로 구성되어 있습니다. 네트워크를 구성하는 전체 레이어의 갯 수는 약 100개 입니다. NIN을 근거하여 classifier 이전에 average pooling을 사용하는 것은 각기 다른 데이터 셋을 편리하게 조정하고 연결할 수 있도록 해줍니다. 다만, 우리는 이것이 엄청난 영향을 끼친다고 생각하지는 않습니다.  FC layer를 average pooling으로 변경하는 것은 top-1 정확도를 약 0.6% 증진시켰습니다. 하지만, 여전히 dropout은 필수 요소로 남아있습니다.

 

네트워크가 깊어질수록 효과적인 방식으로 역전파를 진행하는 것이 우려되었습니다. 한가지 흥미로운 insight는 네트워크 중간의 레이어들에서 나오는 feature들이 명확하게 식별될수록 shallower network에서 성능이 향상된다는 것입니다. 따라서, 중간 레이어들에 보조 분류기(Auxiliary classifier)을 붙임으로써 낮은 stage들에서 식별성을 강화하고, 역전파 과정에서 gradient signal을 증가시켰으며, 추가적인 정규화를 적용하였습니다. 학습을 하는 동안, classifier의 loss은 0.3의 비중으로 total loss에 더해집니다. 또한 추론시간에는, 이러한 보조 네트워크는 버려집니다.

 

출처: https://arxiv.org/pdf/1409.4842v1.pdf

 

 

Average pooling layer(주황색 사각형 내부)

side에 위치한 보조 네트워크의 정확한 형태는 다음과 같습니다.

  • 필터 사이즈는 5x5이며 stride는 3인 average pooling layer -> (4a)에서는 4x4x512의 shape, (4d)에서는 4x4x528의 shape을 갖습니다.
  • 차원 감소와 ReLU 함수를 적용하기 위한 128개의 filter로 이루어진 1x1 convolution
  • dropout(0.7)
  • softmax를 사용하는 FC Layer

Figure3는 전체 네트워크를 도식화 한 것입니다.


6. Training Methodology

GooLeNet은 DistBelief 라는 분산 머신 러닝 시스템을 활용하여 학습하였습니다. 또한, momentum을 0.9로 한 비동기 SGD와 고정된 learning rate 스케쥴링(매 8 epoch 마다 learning rate 4% 감소)을 활용하였습니다. Polyak averaging은 추론 시간에 사용할 최종 모델을 만드는데 사용하였습니다.

 

대회가 끝난 후 증명된 한가지 사실이 있습니다. 크기가 이미지 영역의 약 8% ~ 100% 사이에 분포하고 가로-세로 비율이 3/4 ~ 4/3에 해당하는 이미지의 패치의 샘플링이 매우 좋은 성능을 보여준다는 것입니다. 또한, Andrew Howard의 'photometric distortions'연구가 어느정도 overfitting을 억제하는데 유용하다는 것을 알아냈습니다. 다만, random interpolation method(무작위 보간법)을 사용하였기 때문에 최종 결과들에 위와 같은 활용이 긍정적인 영향을 끼쳤는지는 명확히 이야기 할 수 없습니다.


7. Conclusions

GooLeNet이라는 결과물은 쉽게 이용 가능한 dense building block에 의해 최적의 sparse 구조를 근사화 하는 것이 컴퓨터 비전의 신경망을 발전 시키기 위해서 실행 가능한 방안이라는 것을 보여줍니다. 이 방식의 핵심적인 이점은 shallower 하고 less wide한 네트워크에 비해 약간의 cost 만으로도 엄청난 퀄리티를 얻을 수 있다는 것입니다. 또한, 우리의 탐지 작업은 문맥 활용과 bounding box regression의 수행 없이도 경쟁적이라는 것을 알 수 있으며, 이러한 사실은 Inception 구조의 강점을 보여줍니다. 비슷한 depth와 width를 가진 cost가 큰 네트워크에 의해서 비슷한 퀄리티를 기대해 볼 수는 있지만, 우리의 접근법은 sparser 아키텍쳐를 활용하는 것이 실행가능하고 유용한 아이디어라는 것을 보여줍니다. GoogLeNet을 통하여 NIN에 기반한 자동화된 방식으로 sparser한 아키텍쳐와 더욱 개선된 구조를 위한 미래 연구를 제안합니다.


첫 논문리뷰(?)를 해보았는데 단순히 요약하고 정리하는 과정인데도 불구하고 생각했던거보다 어려웠습니다. 내용도 어려운데 번역하는 과정을 거쳐야 하다보니 더 어렵게 느껴지더라구요. 다만, 1x1 convolution을 통한 차원 감소 등 아직은 생소하지만 획기적인 아이디어 덕분에 몰입하며 읽을 수 있었고 단순히 '~~ 했다'/ '~~한 구조다'가 아닌 '이러한 근거로 이렇게 적용했다 / 이러한 문제점이 있어 해결하기 위해 ~~ 했다' 라는 식으로 원인과 결과가 명확하게 서술되어 있어서 저의 호기심을 충족시켜주었을 뿐만 아니라 배워가는 재미도 많이 느꼈습니다. 저는 이유가 항상 궁금한 사람이거든요. 

 

이번 인셉션에 대해서 공부하던 와중 흥미로운 두가지 사실을 알게 됐기 때문인데요.

ILSVRC 2014에서 우승은 GooLeNet이 했지만, 실제 활용도는 VGGNet이 더 높았다는 사실과 

이듬해 ResNet이 2배의 성능으로 우승하여 결국 구글에서 Inception에 ResNet을 적용했다는 사실을 알게 되었습니다.

 

이에 다음 논문리뷰는 VGGNet과 ResNet에 대해서 써보려고 합니다.

 

아직 실력이 모자라기에 잘못 요약해놓은 부분들도 있을 수 있고, 핵심적인 내용을 캐치하지 못하고 놓쳤을 수도 있습니다.

글에 대한 피드백, 조언은 언제나 감사드리기에 부족한 점이 있다면 편하게 댓글 달아주시면 감사하겠습니다.

읽어주셔서 감사합니다.


References

https://arxiv.org/pdf/1409.4842v1.pdf

https://sike6054.github.io/blog/paper/second-post/

https://phil-baek.tistory.com/entry/3-GoogLeNet-Going-deeper-with-convolutions-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0  

https://jjuon.tistory.com/26 

https://velog.io/@whgurwns2003/Network-In-NetworkNIN-%EC%A0%95%EB%A6%AC

 

https://www.acmicpc.net/problem/2193

 

2193번: 이친수

0과 1로만 이루어진 수를 이진수라 한다. 이러한 이진수 중 특별한 성질을 갖는 것들이 있는데, 이들을 이친수(pinary number)라 한다. 이친수는 다음의 성질을 만족한다. 이친수는 0으로 시작하지 않

www.acmicpc.net

문제: 정수 N이 주어졌을 때, N자리 이친수의 갯수를 구하는 문제

 

풀이 방법: DP

 

풀이 코드:

n = int(input())

dp = [0] * (n+1)

dp[1] = 1

if n > 1:
    dp[2] = 1
    for i in range(3,n+1):
        dp[i] = dp[i-1] + dp[i-2]

print(dp[n])

 

 

풀이 설명:

이 문제는 규칙성이 눈에 보여서 점화식으로 간단하게 해결 할 수 있었습니다.

 

먼저 조건은 다음과 같습니다.

1. 첫번째 자리는 무조건 1 이어야 한다.

2. 1이 연속되게 나올 수 없다.

 

즉, N = 1일 때는 1만 가능하며 N = 2일 때 또한 10만 가능하다는 것입니다.

 

N = 3 이라면, 3번째 자리 수에는 0 또는 1이 올 수 있으므로 경우의 수는 2가지 입니다.

 

N = 4 라면, 3번째 자리 수가 0일 경우 4번째 자리 수에 0 또는 1이 올 수 있으며 세번째 자리 수가 1일 경우 0만 올 수 있습니다.

 

이를 도식화 하면 다음과 같습니다.

N = 3일 때는 0 , 1  두가지 경우의 수가 있습니다.

N = 4일 때는 N = 3 일 때 기준으로 0 - 0 , 0 - 1, 1 - 0 으로 이어지는 3가지 경우의 수가 있죠.

N= 5일 때는 그림과 같이 5가지 경우의 수, N =6일 때는 8가지 경우의 수가 생깁니다.

 

즉, 여기서 하나의 점화식을 구할 수 있었죠.

F(x) = F(x-1) + F(x-2) (x >= 3) 입니다.

 

따라서, 다음 점화식을 통해 문제를 간단하게 해결 할 수 있었습니다.

안녕하세요.

오늘은 사이킷런(scikit-learn)에서 제공해주는 보스턴 집값 데이터를 통해 신경망을 코드로 구현해보려고 합니다.

구글 코랩을 사용하였으며, 파이토치 기반으로 작성하였습니다.

 

크게 신경망을 구축하는데 있어서 필요한 요소는 다음과 같습니다.

 

1) 데이터 호출

2) 데이터 전처리

3) 신경망 모델 구현

4) 학습에 필요한 하이퍼파라미터, 손실함수 그리고 옵티마이저 세팅 및 학습 데이터셋 구성

5) 순전파와 역전파 과정을 통한 학습


 

각 과정에 맞춰 코드를 한번 살펴보겠습니다.

 

1) 데이터 호출

 

신경망에 있어 필요한 라이브러리 호출

먼저 신경망을 구성하는데 있어 필요한 라이브러리들을 호출해주었습니다.

 

이 부분은 난수 발생에 관련한 랜덤 시드를 고정시키는 부분입니다.

머신러닝에서는 랜덤성에 의하여 결과 값이 달라지기 때문에 모델에 따른 결과 값의 변화를 확인하기 위해 랜덤 시드를 고정하였습니다.

 

(다만, 현재 코드에서 DataLoader의 랜덤성을 고정시키는 방법을 찾지 못해 코드를 돌릴 때마다 다른 결과가 나올 수 있습니다.

추후, 방법을 찾게 된다면 정리하여 포스팅 하도록 하겠습니다.)

데이터를 호출하여 저장하는 코드입니다.

집값에 영향을 미치는 변수들은 총 13가지의 독립변수로 구성되어 있으며, 다음과 같습니다.

 

  • CRIM: 범죄율을 나타내는 수치
  • INDUS: 비소매상업지역 면적 비율을 나타내는 수치
  • NOX: 일산화질소 농도를 나타내는 수치
  • RM: 주택당 방 수를 나타내는 수치
  • LSTAT: 인구 중 하위 계층 비율을 나타내는 수치
  • B: 인구 중 흑인 비율을 나타내는 수치
  • PTRATIO: 학생/교사 비율을 나타내는 수치
  • ZN: 25,000 평방피트를 초과 거주지역 비율을 나타내는 수치
  • CHAS: 찰스강의 경계에 위치해 있는지 여부를 나타내는 수치
  • AGE: 1940년 이전에 건축된 주택의 비율을 나타내는 수치
  • RAD: 방사형 고속도로까지의 거리를 나타내는 수치
  • DIS: 직업센터의 거리를 나타내는 수치
  • TAX: 재산세율을 나타내는 수치

Target 데이터는 종속변수로 1978년 506개의 도시의 집값의 중앙값 입니다. (단위 1,000 달러)


2) 데이터 전처리

 

다음은 데이터를 전처리 하는 과정입니다.

 

먼저, 학습을 하기 위해 자료형은 Tensor이어야 하기 때문에 Tensor형으로 자료형을 변환해주었습니다.

 

그리고 학습을 위해서는 훈련을 위한 데이터와 훈련이 잘되었는지 검증하기 위한 검증 데이터가 필요합니다.

따라서, 주어진 입력데이터를 train_test_split 함수를 통해 데이터의 80%를 훈련용 데이터로, 20%를 검증용 데이터로 나누어 주었습니다.

 

데이터 자료형을 확인해 볼때, 13개의 독립변수(집값에 영향을 미치는 데이터)와 이에 따른 1개의 종속변수(집값)로 구성되어 있음을 알 수 있었습니다.

데이터의 shape에서도 확인할 수 있는데요.

x에 해당하는 데이터들은 13의 길이를 갖고 있고, y에 해당하는 데이터는 1의 길이를 갖고 있음을 확인할 수 있습니다.


위 코드는, 구글 코랩의 gpu를 사용하기 위한 코드입니다.

 


3) 신경망 모델 구현하기

다음은 신경망 모델을 구현한 코드입니다.

 

파이토치는 일반적으로 torch.nn.Module을 기본클래스로 지정하여 사용합니다.

따라서, 구현하고자 하는 신경망은 torch.nn.Module을 상속받는 클래스로 정의합니다.

 

이제 신경망을 어떻게 구현했는지 보겠습니다.

 

먼저, Input Layer - Hidden Layer - Output Layer로 이어지는 층을 구성하였습니다.

13개의 변수에 대하여 1개의 target 값을 예측하는 방향이므로,

Input dimension을 13으로, Hidden Layer를 거쳐 나오는 데이터의 Output dimension을 1로 설정해주었습니다.

 

또한, 각 층 사이 사이에 비선형성을 주기 위하여 ReLU를 사용하였습니다.


4) 학습에 필요한 하이퍼파라미터, 손실함수 그리고 옵티마이저 세팅 및 학습 데이터셋 구성

먼저, 신경망 모델에 필요한 하이퍼파라미터는 3가지 입니다.

 

한번 연산에 입력할 데이터 크기를 의미하는 batch_size,

학습에 있어서 한번에 얼만큼 학습할 것인지를 의미하는 learning_rate,

총 몇번 학습할 것인지를 의미하는 epochs(에포크)가 있습니다.

 

손실함수로는 MSE Loss 함수를 사용하였고, 옵티마이저로는 Adam을 사용하였습니다.

 

* model = Model().to(device) 는 모델을 학습에 gpu를 사용하기 위해, 모델을 gpu 상으로 올려주는 코드입니다.

 

학습에 필요한 데이터 셋을 구성한 코드입니다.

 

먼저, 각 train데이터와 test데이터를 TensorDataset 함수를 통해 묶어주었습니다.

이를 바탕으로, DataLoader를 활용하여 batch_size로 데이터를 나누어 저장해주었습니다.


5) 순전파와 역전파 과정을 통한 학습
 
 
 

마지막으로 학습을 시키는 과정입니다. 

세팅한 epoch(에포크) 수 만큼 학습이 진행되며, 한번의 epoch 안에서는 DataLoader를 통해 나누어 놓은 모든 데이터에 대한 학습을 진행합니다.

 

먼저, batch_size별로 구성된 x 데이터를 꺼내와 구현해놓은 신경망을 통해 순전파를 진행합니다.

 

그리고 역전파를 진행하기전, optimizer.zero_grad()를 통해 모델 매개변수 변화도를 조정합니다.

기본적으로 변화도는 계속해서 더해지기 때문에 변화도의 중복 계산을 막기 위하여 변화도를 0로 다시 세팅합니다.

 

순전파를 진행한 예측 값에 대하여, y 값과 손실함수를 통해 통해 손실을 구합니다.

이를 바탕으로, loss.backward() 함수를 통해 역전파를 진행하고 옵티마이저를 통해 매개변수 값을 조정합니다.

 


<학습된 모델의 손실도 및 정확도 확인 및 손실도 그래프 출력하기>

 

학습된 모델을 바탕으로, 테스트 데이터 셋을 이용하여 손실과 정확도를 구하는 부분입니다.

학습하는 부분과 다른 점은 with torch.no_grad() 입니다.

 

테스트 데이터 셋을 기반으로 손실과 정확도를 구할 때는 학습을 하는 것이 아니기에 더 이상 gradient를 계산할 필요가 없습니다.

따라서, 필요없는 연산을 수행하지 않게 하고 이를 바탕으로 연산 속도를 올려주기 위해 다음 코드를 활용합니다.

 

최종적으로 정확도는 약 74%가 나왔습니다.

마지막으로, 학습과정에서 각 epoch별 loss에 대해서 그래프를 그려보았습니다.

초반부에 loss가 확 떨어져, 전체적으로 감소하는 추이를 볼 수 있었습니다.

 

다만 정확도가 74% 정도로 나온 것은 조금 아쉬웠는데요.

 

데이터에 최적화된 신경망과 손실함수, 옵티마이저, 스케쥴러 등을 활용한다면 더 높은 정확도를 가진 모델을 구현할 수 있을 것 같습니다.

다만, 해당 포스팅은 정확도 보다는 신경망 자체를 코드로 보여드리는 것에 초점을 맞추었기에 이번 포스팅은 여기서 마무리 하도록 하겠습니다.

 

글에 대한 피드백은 언제나 감사드립니다. 

읽어주셔서 감사합니다.

 

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

[케라스] VGG16 모델 구현  (0) 2022.09.21
[파이토치] CNN  (0) 2022.08.06

 

https://www.acmicpc.net/problem/11727

 

11727번: 2×n 타일링 2

2×n 직사각형을 1×2, 2×1과 2×2 타일로 채우는 방법의 수를 구하는 프로그램을 작성하시오. 아래 그림은 2×17 직사각형을 채운 한가지 예이다.

www.acmicpc.net

 

문제: 2xn 직사각형을 1x2, 2x1, 2x2 타일로 채우는 방법의 수를 구하는 문제

 

풀이 방법: DP

 

풀이 코드:

n = int(input())
dp = [0] * (n+1)

dp[1] = 1

if n > 1:
    for i in range(2,n+1):
        if i % 2:
            dp[i] = dp[i-1] * 2 - 1
        else:
            dp[i] = dp[i-1]*2 + 1

print(dp[n] % 10007)

 

 

풀이 설명: 

 

문제를 보고 n 값에 따른 규칙성을 찾아보고자 하였습니다.

 

 

 

 

 

다음과 같이 2x1 , 2x2 , 2x3 짜리 직사각형이 있다고 할 때, 경우의 수는 다음과 같습니다.

 

2x1일 경우,  2x1 직사각형 1개로만 채울 수 있습니다.

 

2x2인 경우, 2x1 짜리 2개로 구성하는 방법, 1x2짜리 2개로 구성하는 방법, 2x2 짜리 1개로 구성하는 방법으로 총 3가지가 있습니다.

 

2x3 짜리는 어떨까요?

 

2x1 짜리 3개로 구성하는 방법, 2x1짜리 1 개와 2x2짜리 1개  또는 2x1짜리 1개와 2x1짜리 2개 로 구성하는 방법으로 총 5가지가 있습니다.

 

2x4짜리는 위와 같은 방법으로 총 11개의 방법이 있는데요.

 

n = 1 일 때부터 4일 때 까지 경우의 수를 보니 다음과 같은 규칙성을 발견할 수 있었습니다.

 

n이 홀수일 경우, n-1번째 경우의 수 * 2 - 1

n이 짝수일 경우, n-1번째 경우의 수 * 2 + 1 이라는 규칙을 구할 수 있었습니다.

 

따라서, 다음과 같이 코드를 구성해 n번째 경우의 수를 구하였습니다.

 

if n > 1:
    for i in range(2,n+1):
        if i % 2:
            dp[i] = dp[i-1] * 2 - 1
        else:
            dp[i] = dp[i-1]*2 + 1

 

또한, 10007로 나눈 나머지를 출력하라 하였으므로 다음 코드를 통해 구한 경우의 수를 10007로 나눈 나머지를 출력하도록 하였습니다.

print(dp[n] % 10007)

 

https://www.acmicpc.net/problem/2156

 

2156번: 포도주 시식

효주는 포도주 시식회에 갔다. 그 곳에 갔더니, 테이블 위에 다양한 포도주가 들어있는 포도주 잔이 일렬로 놓여 있었다. 효주는 포도주 시식을 하려고 하는데, 여기에는 다음과 같은 두 가지 규

www.acmicpc.net

문제: 포도주 잔이 일렬로 놓여 있을 때, 3잔 연속으로 마시지 않는 경우 중 제일 많이먹는 경우를 구하는 문제

 

풀이 방법: DP

 

풀이 코드:

import sys

n = int(sys.stdin.readline())
arr = [0]*(n+1)
dp = [0] *(n+1)
for i in range(1,n+1):
    arr[i] = int(sys.stdin.readline())

if n == 1:
    print(arr[1])

else:
    dp[1] = arr[1]
    dp[2] = arr[1] + arr[2]

    for i in range(3,n+1):
        dp[i] = max(dp[i-1],dp[i-3]+arr[i-1]+arr[i],dp[i-2] + arr[i])

    print(max(dp))

 


신경망이란?

인공 신경망은 뇌 신경계의 뉴런을 모방하여 만든 컴퓨터 계산 알고리즘으로, 딥러닝 알고리즘의 핵심이라고 할 수 있습니다.

신경망은 크게 입력층(Input layer) - 은닉층(hidden layer) - 출력층(Output layer)로 구성되어 있으며,

은닉층의 갯수가 2개 이상인 신경망을 심층 신경망(Deep Neural Network),

은닉층의 갯수가 수십, 수백 개 이상으로 구성된 신경망을 바탕으로 한 머신 러닝을 딥 러닝(Deep Learning)이라고 합니다.

 

출처:https://www.ibm.com/kr-ko/cloud/learn/neural-networks

 


그럼 신경망을 구성하는 뉴런(Neuron)과 신경망을 쓰는 이유와 동작방법 그리고 신경망의 학습에 있어서 핵심이 되는 활성화 함수 / 손실 함수 / 옵티마이저에 대해서 알아보겠습니다.

 

뉴런(Neuron)이란 무엇일까요?

앞서 설명해드렸듯이, 인공 신경망은 뇌 신경계를 모방하여 만들었습니다.

우리의 뇌에서 뉴런이 신경전달물질을 통해 신호를 전달하고 정보를 받아들이며, 처리하는 역할을 수행합니다.

 

이와 같이 신경망의 뉴런도 이전 뉴런의 값을 받아 연산을 거쳐 다음 뉴런으로 정보를 전달하는 역할을 수행합니다.


그럼 우리가 신경망을 사용하는 이유는 무엇일까요?

 

Universal Approximation Theorem이라는 하나의 정리가 있습니다.

 

특정 조건을 만족해야하지만 간단히 설명해보자면, 비선형성을 가진 활성화 함수를 가지는 뉴런들로 구성된 한 층의 은닉층이 있다면

어떠한 연속적인 함수에 대해서도 충분히 근사할 수 있다는 내용입니다.

 

즉, 하나의 뉴럴 네트워크 만으로 거의 모든 연속 함수에 근사하여 학습시킬 수 있다는 것이죠.

 

물론, 조건과 한계는 존재하지만 이러한 강력한 이점 덕분에 신경망은 현대 머신러닝 모델 구현에 있어서 핵심으로 자리잡고 있습니다.


이제 신경망을 어떻게 학습시키는지 알아볼 차례인데요. 그 전에 신경망을 학습에 필요한 핵심 요소와 순전파 / 역전파가 무엇인지 알아보겠습니다.

 

 

핵심 요소는 크게 활성화 함수 , 손실 함수, 옵티마이저 라는 것이 있습니다.

이 세가지 함수들을 왜 사용하는지 궁금하실텐데요. 

활성화 함수부터 알아보겠습니다.

 

 

활성화 함수는 쉽게 말해서 비선형성을 가진 함수입니다.

이를 바탕으로, 신경망에 비선형성을 부여하는 역할을 수행합니다.

자주 쓰이는 활성화 함수로는 ReLU(), 소프트맥스(Softmax) 함수 등이 있습니다.

 

여러번의 선형 결합은 한번의 선형결합으로 이루어 질 수 있기 때문에, 선형 결합만으로 심층 신경망을 구성하는 것은 의미가 없습니다.

선형 결합만으로 구성된 10개의 층은 1개의 층과 동일한 결과를 가질 수 있기 때문이죠.

( F(x) = x^2 , G(x) = ( x + 1 ), H(x)  = x^2 + 2x + 1 일 때, F(G(x)) = H(x)를 보시면 이해에 도움이 되실 것 입니다.)

또한 Universal Approximation Theorem에서도 보았듯이, 비선형 함수인 활성화 함수를 통과함으로서 보편적인 수학적 모델을 표현할 수 있음을 알 수 있습니다.

 

이에, 신경망을 구성하는 각 뉴런에 활성화 함수를 포함시켜주어 신경망에 비선형성을 부여해줍니다.

 

손실함수는 머신러닝에서 학습을 진행하며, 진행 과정에서 예측 값과 결과 값의 오차를 구해주는 함수입니다.

대표적으로 CrossEntropy 함수, 평균제곱오차(MSE) 함수 등이 있습니다.

 

옵티마이저는 신경망의 가중치를 업데이트 하는 알고리즘으로, 손실함수를 최소화 하는 파라미터를 찾아내는 최적화 알고리즘입니다.

대표적으로 SGD, Adam, 모멘텀(momentum) 등이 있습니다.

 

마지막으로 순전파와 역전파에 대해서 간단히 설명드리자면,

신경망에 데이터를 입력하여 각 Layer를 거치며 학습을 시켜나가는 과정을 순전파(Foward Propagation)이라고 합니다.

반대로, 순전파를 통해 구한 결과 값(손실함수의 편미분 계수)을 바탕으로 가중치를 업데이트 하는 과정을 역전파(Back Propagation)라고 합니다. 


 

신경망이 무엇이고, 신경망이 사용되는 이유까지 알아보았습니다. 그렇다면, 신경망을 통해 학습시키는 과정을 알아보도록 하겠습니다.

신경망 학습은 지도학습(Supervised Learning), 비지도학습(Unsupervised Learning)등 이미 알려진 방식들이 있지만 큰 갈래의 과정을 알아보도록 하겠습니다.

 

 

신경망 학습이라 함은 주어진 입력 데이터에 대하여 원하는 결과 값을 도출해내기 위한 최적의 가중치 값을 찾아가는 과정이라고 할 수 있습니다.

순전파(Forward Propagation) 과정을 통해, 입력 값을 활성화 함수를 통과 시켜 손실함수를 통해 예측 값과 결과 값의 오차의 편미분계수를 구합니다.

이를 바탕으로 옵티마이저(Optimizer)를 통해 가중치를 업데이트 하는 방식으로 역전파(Back Propagation)를 진행합니다.

다시 업데이트된 가중치를 통하여 순전파를 진행시켜 같은 과정을 진행합니다.

 

이 과정을 반복하여 결과 값과 오차가 최소가 되는 최적의 가중치 값을 찾아갑니다.

 

 

 

출처: http://wiki.hash.kr/index.php/%ED%99%95%EB%A5%A0%EC%A0%81_%EA%B2%BD%EC%82%AC%ED%95%98%EA%B0%95%EB%B2%95

 

최적의 가중치를 찾아 간다는 문장이 와닿지 않으시는 분도 계실거 같습니다.

 

결국 학습을 통해 하고싶은 것은 " 우리가 구하고자 하는 값과 신경망 학습을 통해 나오는 값이 같게 하는 것" 입니다.

이 말은 즉슨, 우리가 구하고자 하는 값과 학습을 통해 나오는 값이 같으면 되는 것이고, 이는 두 값의 오차가 0이 되게 하도록 하면 된다는 것입니다.

 

다만, 한정된 데이터 만으로 오차가 0이 되게 하는 것은 너무 어렵거나 불가능한 일이기에 결과 값의 오차를 최대한 작게해주는 값을 찾아 가는 과정이라고 이해하시면 보다 쉽게 이해하실 수 있지 않을까 생각합니다.

 

 

지금까지, 인공지능의 가장 기초가 되는 신경망과 신경망을 바탕으로 학습시키는 과정에 대하여 알아보았습니다.

 

읽어주셔서 감사합니다.

 

 

 

 

 

 

출처:

https://www.ibm.com/kr-ko/cloud/learn/neural-networks   

https://enfow.github.io/study/experiments/2020/10/23/universal_approximation_experiment/  

책 "파이썬 딥러닝 머신러닝 입문 (저자: 오승환)"

https://www.acmicpc.net/problem/13549

 

13549번: 숨바꼭질 3

수빈이는 동생과 숨바꼭질을 하고 있다. 수빈이는 현재 점 N(0 ≤ N ≤ 100,000)에 있고, 동생은 점 K(0 ≤ K ≤ 100,000)에 있다. 수빈이는 걷거나 순간이동을 할 수 있다. 만약, 수빈이의 위치가 X일

www.acmicpc.net

 

문제: 동생이 위치한 K에 도달할 수 있는 최단 기간을 출력하는 문제

 

풀이 방법: 다잌스트라

 

풀이 코드:

import sys
import heapq

MAX = 100001
n,k = map(int,sys.stdin.readline().rstrip().split())


distance = [1e9] * MAX
visited = [False] * MAX
distance[n] = 0

q = [[0,n]]

def dijkstra():
    while q:
        nowCost, nowNode = heapq.heappop(q)
        visited[nowNode] = True

        if nowNode == k:
            break

        move = [[nowCost, nowNode*2], [nowCost + 1, nowNode - 1], [nowCost+1, nowNode+1]]

        for i in range(3):

            if move[i][1] >= MAX or move[i][1] < 0 or visited[move[i][1]]:
                continue

            if distance[move[i][1]] > move[i][0]:
                distance[move[i][1]] = move[i][0]
                heapq.heappush(q,move[i])


dijkstra()
print(distance[k])

 

 

https://www.acmicpc.net/problem/17298

 

17298번: 오큰수

첫째 줄에 수열 A의 크기 N (1 ≤ N ≤ 1,000,000)이 주어진다. 둘째 줄에 수열 A의 원소 A1, A2, ..., AN (1 ≤ Ai ≤ 1,000,000)이 주어진다.

www.acmicpc.net

 

문제: 오른쪽에서 큰 수중 제일 왼쪽에 있는 수를 구하는 문제

 

풀이 방법: 스택

 

풀이 코드:

import sys
from collections import deque

n = int(input())
array = list(map(int, sys.stdin.readline().rstrip().split()))
stack = deque()
ans = [-1]*n

for i in range(n-1,-1,-1):
    while stack:
        if stack[-1] > array[i]:
            ans[i] = stack[-1]
            stack.append(array[i])
            break
        else:
            stack.pop()

    stack.append(array[i])

print(' '.join(map(str,ans)))

 

 

풀이 설명:

풀이에서 핵심으로 잡은 점은 두가지 입니다.

1. 스택의 활용

2. 오른쪽에서 큰 수 중 제일 가까운 수를 구하는 문제이므로, 오른쪽부터 탐색

 

핵심이 되는 코드를 보면 다음과 같습니다.

 

for i in range(n-1,-1,-1):
    while stack:
        if stack[-1] > array[i]:
            ans[i] = stack[-1]
            stack.append(array[i])
            break
        else:
            stack.pop()

    stack.append(array[i])

오른쪽부터 탐색을 하면서, 스택에 큰 값을 담습니다.

이때, 스택에 담긴  A라는 값보다 더 큰 B라는 값을 만나게 되면 B 왼쪽의 수들에게는 더 이상 A가 의미가 없어지므로 pop 하도록 하였습니다.

 

if stack[-1] > array[i]:
    ans[i] = stack[-1]
    stack.append(array[i])
    break

스택의 마지막 값이 더 크다면, 현재 배열의 값의 오큰수는 스택에 담긴 값 입니다.  

따라서, 정답 값을 저장하는 ans에 저장해줍니다.

 

또한, 현재 배열의 값이 오큰수 일 수 있으므로 스택에 담아줍니다.

 

else:
    stack.pop()

만약, 현재 배열의 값이 스택의 마지막 값보다 크다면 pop을 해줍니다.

이유는 배열의 값이 더 크기 때문에 앞으로 스택의 마지막 값이 오큰수가 될 가능성이 없기 때문입니다.

 

마지막으로, ans를 -1로 초기화 해주었기 때문에, 스택에 담겨있는 값들보다 큰 값의 경우 현재 값의 위치의 ans에는 -1이 담겨있게 됩니다.

 

 

https://www.acmicpc.net/problem/2493

 

2493번: 탑

첫째 줄에 탑의 수를 나타내는 정수 N이 주어진다. N은 1 이상 500,000 이하이다. 둘째 줄에는 N개의 탑들의 높이가 직선상에 놓인 순서대로 하나의 빈칸을 사이에 두고 주어진다. 탑들의 높이는 1

www.acmicpc.net

 

문제: 탑의 갯수와 높이가 주어졌을 때, 각 탑에서 쏘는 레이저를 수신할 수 있는 탑의 번호를 구하는 문제

 

풀이 방법: 스택

 

풀이 코드:

import sys
from collections import deque

n = int(input())
array = list(map(int, sys.stdin.readline().rstrip().split()))
stack = deque()
ans = [0]*n

for i in range(n):
    while stack:
        if stack[-1][0] >= array[i]:
            ans[i] = stack[-1][1]+1
            stack.append([array[i],i])
            break
        else:
            stack.pop()

    stack.append([array[i],i])
    
print(' '.join(map(str,ans)))

 

 

풀이 설명:

문제 풀이에 활용한 것은 두가지 입니다.

1. 탑에 대한 정보를 스택에 담는다.

2. A라는 탑 뒤에 A보다 높은 B라는 탑이 있을 경우, B 이후에 있는 탑의 레이저는 받지 못한다. 따라서, 스택에서 pop 한다.

 

풀이에 핵심이 되는 코드를 보겠습니다.

 

for i in range(n):
    while stack:
        if stack[-1][0] >= array[i]:
            ans[i] = stack[-1][1]+1
            stack.append([array[i],i])
            break
        else:
            stack.pop()

    stack.append([array[i],i])

 

먼저 stack에는 [높이, index]를 담습니다.

 

빈 스택이 아닐 경우, while 문이 돌아가게 됩니다.

 

조건 1) 앞선 탑에 높이가 현재 보다 높은 A라는 탑이 있다면 그 타워에 레이저가 닿을 것이므로, A 타워의 index를 저장해줍니다.

그리고 A 타워 뒤에 A보다 낮은 타워가 있을 수 있으므로 A 타워의 높이와 index를 스택에 저장해줍니다.

 

반대로, 뒤에 나온  B라는 탑보다 스택에 있는 A라는 탑이 더 낮다면  A에 닿을 레이저는 모두 B라는 탑에 닿아 더이상 필요가 없으므로 pop 하게 됩니다.

 

문제에 주어진 예시로 설명을 해보자면 다음과 같습니다. (6 9 5 7 4)

 

처음 6은 그대로 스택에 저장되게 됩니다.

stack = [[6,0]]

ans = [ 0, 0, 0, 0, 0]

 

그 다음 9를 만나게 되면, 반복문을 돌게 됩니다.

따라서 스택에 있는 6은 그대로 pop되고 9가 들어가게 됩니다.

stack = [[9,1]]

ans = [ 0, 0, 0, 0, 0]

 

5를 만나게 되면, 반복문을 도는데 이번에는 9 가 5보다 크므로 5 또한 스택에 담기게 됩니다. 그리고 9에 레이저가 닿으므로 9의 index를 저장하게 됩니다.

stack = [[9,1] , [ 5, 2 ]]

ans = [ 0, 0, 2, 0, 0]

 

7을 만나게 되면, 반복문에서 5보다 7이 더 크므로 5는 pop 되게 되고 다시 5와 같이 스택에 담기게 됩니다. 물론 레이저는 9에 닿으므로 9의 index를 저장하게 됩니다.

stack = [[9,1] , [7,3]]

ans = [ 0, 0, 2, 2, 0]

 

마지막으로 4를 만나게 되면, 4는 9로 가지 않아도 7에 닿습니다. 따라서, 7의 index를 저장하게 됩니다.

stack = [[9,1] , [7,3] , [4,4]]

ans = [ 0, 0, 2, 2, 4]

 

 

https://www.acmicpc.net/problem/1759

 

1759번: 암호 만들기

첫째 줄에 두 정수 L, C가 주어진다. (3 ≤ L ≤ C ≤ 15) 다음 줄에는 C개의 문자들이 공백으로 구분되어 주어진다. 주어지는 문자들은 알파벳 소문자이며, 중복되는 것은 없다.

www.acmicpc.net

 

문제: 주어진 문자들을 바탕으로 만들 수 있는 조건에 부합하는 문자열을 출력하는 문제

 

풀이 방법: 백트래킹

 

풀이 코드:

import sys

l, c = map(int,sys.stdin.readline().rstrip().split(' '))

if not (3 <= l <= c <= 15):
    sys.exit(0)

vowel = ['a','e','i','o','u']

stringList = sorted(list(sys.stdin.readline().rstrip().split(' ')))
case = []
vowel_cnt = 0

def backTracking(idx):
    global vowel_cnt
    if len(case) == l:
        if vowel_cnt < 1 or len(case)-vowel_cnt < 2:    # 모음 갯수가 1보다 작거나 자음 갯수가 2보다 작으면 출력하지 않고 return
            return
        for i in range(l):  # 문자열 출력
            print(case[i], end='')
        print()
        return

    for i in range(idx,c):
        if len(case) > 0 and case[-1] >= stringList[i]:  # 영문이 역순으로 들어가지 않도록 처리
            continue
        if stringList[i] in vowel:  # 모음 갯수 count
            vowel_cnt += 1

        case.append(stringList[i])
        backTracking(idx+1)
        pop_char = case.pop()

        if pop_char in vowel:   # 모음이 빠질 경우 모음 갯수에서 1 빼줌
            vowel_cnt -= 1

backTracking(0)

 

풀이 설명:

문제에서 주어진 조건은 다음과 같습니다.

1) 문자열은 오름차순으로 구성되어야 한다.

2) 최소 1개의 모음과 2개의 자음을 포함하고 있어야 한다.

 

즉, 문제의 핵심은 백트래킹 기법과 주어진 조건을 어떻게 만족시키느냐 입니다.

 

vowel = ['a','e','i','o','u']
def backTracking(idx):
    global vowel_cnt
    if len(case) == l:
        if vowel_cnt < 1 or len(case)-vowel_cnt < 2:    # 모음 갯수가 1보다 작거나 자음 갯수가 2보다 작으면 출력하지 않고 return
            return
        for i in range(l):  # 문자열 출력
            print(case[i], end='')
        print()
        return

    for i in range(idx,c):
        if len(case) > 0 and case[-1] >= stringList[i]:  # 영문이 역순으로 들어가지 않도록 처리
            continue
        if stringList[i] in vowel:  # 모음 갯수 count
            vowel_cnt += 1

        case.append(stringList[i])
        backTracking(idx+1)
        pop_char = case.pop()

        if pop_char in vowel:   # 모음이 빠질 경우 모음 갯수에서 1 빼줌
            vowel_cnt -= 1

 

코드를 보시면 먼저 모음을 담고 있는 array를 만들어 준 후, 백트래킹 함수를 구현하였습니다.

 

최종 출력할 문자들을 담고 있는 case라는 배열에 값을 넣어줄 때마다, 들어가는 문자가 모음인지 확인하여 모음의 갯수를 세어주었습니다.

 

영어는 자음 + 모음이므로 출력할 총 문자의 갯수에서 담긴 모음의 갯수를 빼주면 자음의 갯수가 나오므로 이를 활용하였습니다.

 

이를 바탕으로 최종 출력하기 전, 문제에서 주어진 조건이 부합하는지를 확인하고 조건에 부합하는 문자열만 출력하도록 구현하였습니다.

+ Recent posts