Elastic Search

Elastic은 텍스트, 숫자, 위치 기반 정보, 정형 및 비정형 데이터 등 모든 유형의 데이터를 위한 무료 검색 및 분석엔진으로 분산과 개방형이 특징입니다.

Elastic은 강력한 검색 기능을 통해 사람들이 차별화된 방식으로 데이터를 탐색하고 분석할 수 있도록 도와줍니다 – Elastic Search

ElasticSearch는 Apache Lucene을 기반으로 구축되었습니다. 무료인데다 처리 할 수 있는 데이터도 다양하고 성능도 뛰어나서 다양한 분야에서 사용되고 있습니다.

Elastic은 2010년에 최초로 출시된 이후로 REST API 형태의 편리한 사용성, 고성능을 필요로 하는 환경에서의 확장과 분산 처리는 Elastic의 빠른 성장을 이끌어 왔습니다. 또한 ELK(Elasticsearch, Logstash, Kibna) Stack라고 불리는 강력한 도구는 데이터의 수집, 검색, 시각화 등을 가능하게 해주었습니다.

https://www.elastic.co/kr/what-is/elk-stack

Elastic의 사용성은 웹사이트 검색, 로깅과 로그 분석, 애플리케이션 성능 모니터링, 위치 기반 정보 분석 및 시각화, 보안 분석, 비지니스 분석 등 다양 분야에서 활용되고 있습니다.

Elasticsearch의 작동 방식

시스템에서 만들어지는 로그 데이터, 웹 애플리케이션에서 만들어지는 다양한 정형(비정형) 데이터 등 다양한 원시 데이터가 Elastic에 들어갑니다. 데이터 수집은 원시 데이터가 Elastic에서 색인되기 전에 수행되는 구문 분석, 정규화를 포함합니다.

Elastic에서 색인되면 사용자는 분석을 위해 복잡한 쿼리를 수행하고 다양한 형태의 집계를 사용해서 데이터를 분석, 요약, 검색을 할 수 있습니다. Kibana 이러한 작업을 수행하는 사용자를 위한 다양한 형태의 시각화 도구를 제공해줍니다. Elastic의 데이터 색인은 기본적으로 시계열 데이터를 포함하여 수집되는 데이터를 분석할 수 있으며 위치 기반의 정보 분석를 이용한 분석이 가능합니다.

Elasticsearch 색인

Elastic의 색인(Index)는 서로 관련되어 있는 문서(Document)들의 모음입니다. Elastic은 json 문서로 데이터를 저장하며 각 문서는 일련의 키와 그에 해당하는 문자열, 숫자, 부울, 배열, 위치 데이터 또는 기타 유형의 데이터를 서로 연결되어 있습니다.

Elastic은 역 인덱스라고 하는 데이터의 구조를 사용하는데 이것은 아주 빠른 풀텍스트 검색을 할 수 있도록 설계된 것입니다. 역 인덱스는 문서에 나타나는 모든 고유한 단어들의 목록을 만들고 각 단어가 발생하는 문서를 식발하게 됩니다.

색인 프로세스 중에 Elastic은 문서를 저장하고 역 인덱스를 구축하여 거의 실시간으로 문서를 검색할 수 있습니다. 인덱스 API를 사용해 색인이 시작되고 이를 통해서 특정한 인덱스에 json 문서를 새롭게 추가하거나 업데이트 할 수 있습니다.

Inverted Indexes and Index Terms

위의 이미지는 역 인덱스를 설명하는 좋은 예제입니다.

왼쪽의 이미지는 3개의 문장이 있고 각 문장은 인덱스 번호(1,2,3)가 부여되어 있습니다. 이것은 마치 관계형데이터베이스(RDBMS)의 한 테이블에 3개의 문장이 있는 것과 같은 의미고 각 문장의 PK는 1,2,3입니다.

이 상태에서 보통의 RDBMS에서는 ‘choice’를 찾기 위해서 1번부터 순차적으로 단어를 검색하며 내려가게 됩니다. 그리고 3번째 문장에서 그 답을 찾을 수 있고 사용자에게 정답을 리턴합니다.

이제 오른쪽은 Elastic의 역 인덱스 방식입니다. 각 문장의 단어를 분리해서 빈도수를 포함한 Dictionary를 만들게 됩니다. 이것은 마치 python에서 dict를 와 비슷한 개념입니다. 또 각 단어가 어떤 문장의 위치에 있는지 그 정보를 저장하게됩니다. 이런 정보는 이전에 ‘choice’라는 단어를 찾을 때에 바로 문서의 위치를 리턴해주기 때문에 아주 빠른 검색이 가능하게됩니다.

Elastic을 사용하는 이유

Elastic을 이용하는 이유는 앞선 글에서와 같은 다양한 장점이 있기 때문입니다. 이를 요약하면 3가지로 이야기 할 수 있습니다.

첫째, Elastic은 빠릅니다. Elastic은 Lucene을 기반으로 구축되어 있기 때문에 풀 텍스트 검색에서 뛰어납니다. 거의 실시간이라고 할 수 있을 정도로 색인 될 때부터 검색이 가능해질 때까지의 대기 시간이 아주 짧다는 의미입니다. 이 대기 시간은 보통 1초입니다. 결과적으로 Elastic은 보안 분석, 인프라 모니터링 같은 시간이 중요한 사례에 가장 이상적인 엔진입니다.

둘째, Elastic은 분산적입니다. Elastic에 저장된 문서는 샤드라고 하는 여러 다른 컨테이너에 걸쳐 분산되며 이 샤드는 복제되어 하드웨어 장애 시에 중복되는 데이터 산본을 제공합니다. Elastic의 분산적인 특징은 수백 개(심지어 수천 개)의 서버까지 확장하고 페타 바이트의 데이터를 처리 할 수 있도록 해줍니다.

셋째, Elastic은 광범위한 기능 세트와 함께 제공됩니다. 속도, 확장성, 복원력 뿐만 아니라 데이터의 롤업, 인덱스 수명 주기 관리 등과 같이 데이터를 훨씬 더 효율적으로 저장하고 검색 할 수 있게 해주는 다수의 기본 기능이 탑재 되어 있습니다.

넷째, Elastic은 데이터 수집, 시각화, 보고를 간소화합니다. Beats와 Logstash의 통합은 Elastic으로 색인 하기 전에 데이터를 훨씬 더 쉽게 처리 할 수 있도록 만들어줍니다. 사용자는 Elastic에 데이터를 입력하기 위해서 별도의 특별한 도구를 개발하지 않아도 파일 형태의 다양한 데이터, RDBMS 데이터 등을 빠르게 입력 할 수 있습니다. 또한 Kibana와 같은 시각화 도구는 실시간으로 입력되는 데이터를 분석, 모니터링 할 수 있도록 훌륭한 UI를 제공합니다.

https://www.elastic.co/guide/kr/logstash/current/introduction.html

Elastic은 국내에도 많은 사용자가 있고 기술 공유와 교육을 위한 활동을 계속 해오고 있습니다.

또한 많은 자료들이 공식 사이트를 통해서 공유되어 있으니 쉽고 빠르게 소식을 접할 수 있습니다.

챗봇 슬롯 채우기(Slot-Filling)

대화는 어떤 목적을 이루기 위한 대화(TOD, Task Oriented Dialog)가 있고 특별한 목적 없이 자신의 생각을 자유롭게 이야기 하는 소통을 목적으로 하는 대화(Chit-Chat Dialog)가 있습니다.

목적이 있는 대화의 경우에는 대화의 특징에 따라서 n개의 대화턴(Turn)으로 구성된 대화의 묶음으로 나눌 수 있는데 그것을 태스크(Task)라고 할 수 있습니다. 그리고 각 태스크는 또 n개의 액티비티(Activity)로 나눌 수 있습니다. 그리고 각각의 액티비티에는 태스크의 원활한 진행을 위해서 반드시 입력되어야 할 필요한 정보들이 있습니다.

이처럼 특정한 태스크의 발화(Utterance)에서 반드시 필요한 의미 있는 정보들을 슬롯(Slot)이라고 하고 이 슬롯을 채우는 것을 슬롯 필링(Slot Filling)이라고 합니다.

https://d2.naver.com/helloworld/2110494

그리고 이 슬롯을 채우기 위해서 거듭해서 질문을 하게되는데 이러한 것을 Follow-Up Question이라고 합니다. 아래의 그림과 같이 좌석을 예약할 때에 기본적으로 필요한 정보들이 있고 이러한 정보들이 입력되지 않으면 시스템은 이용자에게 질문을 통해 해당 정보를 얻게 됩니다.

https://d2.naver.com/helloworld/2110494

이런 과정을 수행하기 위해서는 개체명인식(NEG, Named Entity Recognition) 작업이 필요합니다.
해당 작업은 사용자가 입력한 텍스트를 사전에 정의된 몇가지 단어들… 예를 들어서 메뉴명, 지역명, 호텔명 등에 태깅하는 작업을 의미하고 이것은 비슷한 말로 엔티티 태깅(Entity Tagging)이라고도 합니다.

여기서 중요한 것은 기존에 정의된 태그 목록이라는 것입니다. 태그 목록은 기존에 범용적으로 사용되는 것도 있고 대화가 진행되는 특정한 도메인에서 활용되는 것도 있습니다.

이처럼 사용자가 입력한 단어들이 기존에 정의된 개체명에 포함되어 있는지를 살펴보고 없다면 필요한 정보를 다시 요구하는 방식으로 요청 정보를 채우게됩니다.

다음에 구현한 예제는 이러한 과정을 간단히 작성한 내용입니다. 주문상황을 가정하고 챗봇을 통해서 주문을 받아보는 방식을 생각해보겠습니다.
먼저 아래와 같이 슬롯을 정의합니다.

#Intent Slot 구성
slot_entity = { "주문": {"메뉴":None, "장소":None, "날짜":None},
                "예약": {"장소":None, "날짜":None},
                "날씨": {"장소":None, "시간":None}
              }

사용자가 입력된 텍스트가 “주문 부탁해”입니다. 챗봇 시스템이 먼저 파악해야 할 것이 있다면 텍스트를 입력한 사람이 어떤 의도로 이런 텍스트를 입력했는지를 알아내는 것입니다.

간단히는 ‘주문’이라는 키워드를 확인해서 대화의 의도를 파악할 수 있고 몇가지 ‘주문’ 상황에서 쓸 수 있는 문장들을 입력하고 유사도 검사를 통해서도 이용자의 의도를 파악할 수 있습니다. 최근에는 딥러닝을 활용해서 해당 문장이 어떤 의도인지 알아내기도 합니다. 딥러닝(LSTM)을 활용한 문장 유사도를 찾는 것은 예제로 구현되어 있으니 참고하시기 바랍니다.

입력한 문장 ‘햄버거 주문할께요’라는 단어가 ‘주문’이라는 의도를 가지고 있다는 것을 파악한 다음에 ‘햄버거 주문할께요’라는 입력 문장을 분석해서 예약에 필요한 정보들을 담고 있는지 확인해야합니다. 그러기 위해서 형태소 분석(Morphology Analysis) 과정이 필요합니다.

input_txt = '햄버거 주문할께요'
intent_code = '주문'

from konlpy.tag import Kkma
kkma = Kkma()
morpheme = kkma.pos(input_txt)
print(input_txt,'\n',morpheme)
# [('햄버거', 'NNG'), ('주문', 'NNG'), ('하', 'XSV'), ('ㄹ게요', 'EFN')]

입력된 문장의 형태소 분석을 통해서 ‘햄버거’,’주문’이라는 개체명(명사)을 찾아내었습니다. 이제 다음으로는 이런 개체명이 사전에 정의해둔 개체명과 일치하는 내용을 찾는 과정을 수행합니다. 입력된 단어 중에서 ‘햄버거’란 단어는 이미 메뉴 아이템에 사전으로 등록했기 때문에 ‘햄버거’는 menu_item이라고 인식합니다.

# 개체명
menu_item = ['피자','햄버거','치킨','떡볶이']
loc_item = ['세종','대전','공주']
date_item = ['지금','내일','모래']

# 개체명 태깅
for pos_tag in morpheme:
    if (pos_tag[1] in ['NNG', 'NNP']): #명사, 영어만 사용
        if pos_tag[0] in menu_item: #메뉴 item 검색
            slot_value["메뉴"] = pos_tag[0] 
        elif pos_tag[0] in loc_item: #장소 item 검색
            slot_value["장소"] = pos_tag[0] 
        elif pos_tag[0] in date_item: #날짜 item 검색
            slot_value["날짜"] = pos_tag[0] 
print (slot_entity.get(intent_code))

입력된 문장을 분석해본 결과 주문에 필요한 나머지 정보들 즉, ‘주소’ 정보와 ‘시간’ 정보는 입력되지 않았습니다. 챗봇 시스템은 빠진 두개의 정보를 입력할 것을 요청합니다. 이것을 Follow-Up Question이라고 합니다.

if(None in slot_value.values()): #빈 Slot 출력
    key_values = ""
    for key in slot_value.keys():
        if(slot_value[key] is None):
            key_values = key_values + key + ","
    output_data = key_values[:-1] + '를 입력해주세요.'
else:
    output_data = "주문이 완료 되었습니다."
            
print (output_data)
#메뉴,장소,날짜를 입력해주세요.

이러한 방법으로 주문에 필요한 모든 정보가 입력되면 챗봇 시스템은 주문 정보를 기반으로 실제로 요청한 내용들을 주문하게됩니다.

참고로 본 글을 작성하기 위해서 잘 정리된 아래의 두개 글을 참고했습니다. 관심이 있으신 분은 아래의 글을 검색해보시기 바랍니다.

REF
[Naver] 챗봇을 위한 대화는 어떻게 디자인할까
[KAKAO] 카카오 미니의 슬롯 태깅

챗봇(Chatbot) 이란

채팅은 보통 일상의 소소한 대화를 주고 받는 경우와 어떤 목적을 위해 당사자간 정보를 주고 받는 경우 혹은 정보의 이동이 단방향인 경우 세가지가 있습니다. 이중에서 어떤 목적을 위해 당사자간에 정보를 주고 받는 경우는 대화하라고 하고 정보의 요구 주체가 있어서 상대방은 정보를 주기만 하는 것을 QnA라고 할 수 있습니다.

그렇다면 이러한 챗봇은 어떤 방법으로 사람의 말을 이해하고 또 어떻게 구현할 수 있을까? (사실 챗봇과 음성인식은 거의 비슷한 기술을 사용합니다. 단지 앞 단에 음성을 텍스트로 변환해주거나 텍스트를 음성으로 변환해주는 기능을 수행하는 STT/TTS와 같은 기술이 적용될 뿐입니다.)
먼저 어떻게 이해하는지에 대해서 간단히 그림으로 살펴보면 아래와 같습니다.

사람은 머리속에 생각이나 감정 등을 언어의 형태로 상대방에게 전달합니다. 그 언어를 자연어(Natural Language)라고 합니다. 이 자연어는 인간의 언어이기 때문에 당연히 컴퓨터가 이해할 수 없습니다. 그렇기 때문에 인간의 언어를 컴퓨터에게 이해시키기 위해서 NLU(자연어이해, Natural Language Understanding)라는 분석과정이 필요합니다. 컴퓨터는 이러한 특별한 처리 단계를 통해서 인간의 이 말이 어떤 의미가 있는가를 이해하게 됩니다.

NLU의 과정은 축적된 데이터가 큰 역활을 합니다. 마치 아이들이 언어를 배울 때 좋은 환경에서 말을 배우는 것과 때로 거친 환경에서 말을 배우는 것이 사용하는 어휘의 차이가 있듯이 컴퓨터도 인간의 언어를 학습하는데 데이터가 절대적인 영향을 미치게됩니다.

어떤 데이터를 어떻게 축적 했느냐에 따라서 인간의 말을 더 잘 이해할 수 있습니다. 실제로 이러한 일을 수행하기에는 굉장히 큰 사전 데이터를 필요로합니다. 대화처리기는 학습된 데이터를 통해서 인간의 말에 어떤 응답을 해야할지를 선택하게 되고 그것을 NLG(자연어생성, Natural Language Generator)에 전달하게됩니다. NLG는 인간의 언어를 이해한 내용을 바탕으로 다시 인간이 이해할 수 있는 음성과 글의 형태로 출력해줍니다.

사실 이러한 과정은 사람에게서도 비슷하게 일어나고 있습니다. 인간 역시 화자의 관념화된 사상을 말이나 글로 표현하게 되고 상대방은 이러한 글을 다시 해석해서 관념화 하는 과정이 일어나고 있습니다.

위의 그림은 서정연교수님의 <대화 인터페이스, 챗봇, 그리고 자연어처리>라는 강의자료에 첨부되어 있는 그림입니다. 입력 데이터를 문장으로 가정할 때에 문장이 입력되고 그 문장이 어떤 과정에 의해서 어떻게 이해되는지에 대한 그림이 도식화되어 있습니다. 이후에 사용하는 자료도 해당 슬라이드에서 인용했습니다.

가장 기본이 되는 것은 보라색으로 표시되어 있는 부분입니다.

형태소분석기(Morphology) – 구문분석기(Syntax) – 의미분석기(Semantics) – 담화분석기(Discourse)

형태소분석(Morphology) : 명사, 조사 따위로 분리하는 단계, 의미를 가지는 가장 작은 단위로 분리
구문분석(Syntax) : 형태소들이 결합하여 문장이나 구절을 만드는 구문 규칙에 따라서 문장 내에서 각 형태소들이 가지는 역할을 분석
의미분석(Semantics) : 문장의 각 품사들이 어떤 역할을 하는지 보고 분석하는 단계, 각 어휘간 같은 단어라도 문장에서 어떤 의미로 사용되는가를 분석
담화분석기(Discourse) : 담화는 글의 흐름 및 연속체란 의미로 한 문장이 아닌 전체 문장간의 관계를 연구하여 글의 결합력과 통일성을 보는 연구, 언어가 사용되는 상황을 고려한 문맥의 이해

이러한 방식으로 자연어를 이해하고 처리합니다. 이런 과정을 통합하여 자연어처리(NLP, Natural Language Processing)라고 합니다.

챗봇을 만드는 방법은 여러가지가 있습니다. 우선 말꼬리를 이어서 대화를 이어가는 방식이 있습니다. 말을 계속해서 이어가기 때문에 적절한 응답을 할 수는 있지만 문맥의 흐름이 맞지 않을 수 있다는 단점이 있습니다.

또 하나는 미리 만들어진 대화쌍을 DB에 저장하고 사용자의 발화와 가장 유사한 대화쌍을 찾아 대화를 이어가는 방식이 있습니다. 그리고 이러한 방법을 확장을 확장하여 아래와 같이 표현합니다.

어디 사는지 물어봐도 되요? 라는 질문은 아래와 같이 6개의 질문으로 확장할 수 있습니다. 이에 따른 답변도 오른쪽과 같이 7개로 제공합니다. 이러한 대화쌍이 풍성해지면 채팅 이용자의 다양한 질문에 재밌는 방식으로 대응 할 수 있게 됩니다.

대화형 챗봇에서 가장 많이 사용되는 것은 시나리오 기반의 챗봇입니다.

물론 현재 딥러닝 기술의 발전으로 인해서 인공지능이 번역, 대화인식 등 많은 일을 해내고 있으나 아직 입력된 문장을 통해서 자연스러운 대화를 만들어 내는 것은 더 많은 연구와 노력이 필요한 단계입니다.

앞서 이야기한 시나리오 기반의 챗봇은 미리 입력한 시나리오를 통해서 대화가 진행되도록 합니다. 가장 유명한 것은 Google의 DialogFlow입니다. 이 외에도 국내에 많은 회사들이 이러한 시나리오 기반의 챗봇을 활용해서 다양한 서비스를 제공하고 있습니다.

최근 세종학당에서 개발한 <인공지능 기반의 한국어 교육용 서비스>도 이러한 방식으로 구현되었습니다. 사전에 전문가와 함께 다수의 상황별 교육용 시나리오를 제작하고 이를 챗봇 엔진에 탑재해서 이를 통해서 한글 학습을 할 수 있도록 개발되었습니다.

시나리오 기반의 챗봇은 주어진 흐름에 따른 대화만 인식한다는 단점이 있기 때문에 이를 극복하기 위한 자연스러운 예외처리가 필요합니다. 또 주제를 이탈했을 경우 어떻게 다시 주제로 복귀하는지에 대한 기술도 필요합니다.

이외에도 또 중요한 것은 대화의 의도를 파악하는 일입니다. 이것을 의도(Intention)라고 합니다.

https://d2.naver.com/helloworld/2110494

예를 들어서 대화의 순서가 “인사-주문-결제-감사”의 순으로 진행된다면 입력된 사용자의 대화가 어떤 의도로 말한 것인지 정확하게 판단해야 합니다. 의도를 파악하지 못하면 챗봇은 사용자의 질문에 엉뚱한 대답을 하게됩니다.

이런 의도를 파악하는데 다양한 인공지능 기법이 사용됩니다. 인공지능은 사용자의 글을 통해서 이것이 어떤 내용인지 분류하고 해당 분류에 있는 대답중에서 하나를 출력합니다. 예제로 구현한 테스트 코드에서는 BiLSTM을 사용합니다. 해당 알고리즘은 RNN 기법 중 하나로 분류에서 RNN에서 좋은 성능을 나타내는 알고리즘입니다.

이 외에도 슬롯-필링(Slot Filling)이라는 기법이 있습니다. 이것은 말 그대로 빈 칸을 채우는 기법입니다.

예를 들어서 날씨를 묻는 사용자의 질문에 기본적으로 시스템이 알아야 할 정보를 사전에 정의하고 부족한 정보를 다시 사용자에게 요청하는 것입니다. 만약 사용자가 “날씨를 알려줘”라고 질문하게 되면 시스템은 시간, 장소 등을 다시 물어보게 됩니다.

아래의 그림과 같이 예약을 원하는 사용자에게는 메뉴, 가격, 사이드 메뉴, 결제 방법 등을 추가로 물어볼 수 있고 이것을 Follow-Up Questions 이라고 할 수 있습니다.

https://d2.naver.com/helloworld/2110494

시나리오 기반 챗봇(Naive Scenario Chatbot)

이번 예제에서는 간단한 시나리오 기반 챗봇을 구현해보겠습니다.

해당 예제를 실행한 결과는 아래의 영상과 같습니다.

이 대화는 아래와 같이 4개의 턴(Turn)으로 이루어져있습니다. 대화의 흐름은 “인사-간단한 일상 대화-주문-끝인사”로 이뤄져있습니다. 각 턴을 수행하면 자연스럽게 다음 턴으로 연결됩니다. 대화가 예상된 흐름으로 넘어가지 않을 때는 사전에 정의된 간단한 대화를 출력하고 다시 이전 질문을 다시 수행합니다.

Dialog Flow : Greeting – Where – Order – Bye

테스트에 사용할 간단한 시나리오는 아래와 같습니다. 아래에 order – bye가 화면상에는 표시되어 있지 않지만 내용은 위와 다르지 않습니다.

category에 NaN으로 되어 있는 부분은 시스템의 발화 부분입니다. 그 외의 부분은 시스템에 입력되는 기대값들입니다.

예를 들어 greeting 카테고리를 살펴보면 시스템이 “안녕하세요”라고 발화 했을 때에 해당 발화에 답변으로는 기대되는 값들을 greeting 카테고리에 등록합니다. 현재 시스템 발화에는 하나만 등록했지만 만약 시스템 발화 부분을 다양하게 하고자 한다면 여러개의 답변을 넣고 그중에서 하나의 답을 랜덤하게 표시해주는 방법으로 해도 됩니다. 실제로 많은 채팅 시나리오가 같은 방법으로 제작되고 있습니다. 여기서는 간단하게 시스템에서는 하나의 답변만 낼 수 있도록 합니다.

시스템 메세지에 대한 사용자의 기대되는 “안녕하세요”, “안녕”, “헬로”, “네 반갑습니다”, “hi hello” 5가지 중에 하나로 입력된다고 가정합니다.

이와 같은 방법으로 “어디서 오셨나요?”라는 시스템의 질문에도 사용자는 몇가지 대답을 할 수 있습니다. 그에 대한 답변을 미리 등록해봅니다.

동일한 방법으로 나머지 시나리오도 입력해봅니다.

그렇지만 안타깝게도 위와 같이 정의된 답변만 사용자가 입력하지는 않습니다. 사용자는 여러가지 답변을 입력할 수 있습니다. 기본적으로는 챗봇에게 많은 내용을 학습시킬 수 있다면 좋겠지만 실제로 그렇게 하기는 쉽지 않습니다. 또 하나의 문제는 시스템은 사용자가 어떤 순서로 답변을 낼지 알지 못한다는 것입니다.

그렇기 때문에 챗봇 시스템은 사용자의 입력한 답변이 입력한 시나리오에 있는지 그렇다면 어떤 질문인지 만약에 아니라면 어떻게 예외적인 사항을 처리해야 하는지 알아야합니다. 즉, NLU(자연어이해, Natural Language Understanding)가 필요합니다.

이부분에 형태소 분석과 구문분석 등의 과정이 필요하고 더 높은 이해를 위해서 사전 구축등의 작업이 필요합니다. 하지만 비슷한 예제를 이미 구현했기 때문에 여기서는 간단히 해당 문장이 어떤 카테고리에 속하는지 분류하는 분류기 정도로만 구현해 보겠습니다.

딥러닝을 활용하여 텍스트 분류를 수행할 수 있습니다.

위와 같은 텍스트 분류 예제를 참고하시기 바랍니다.

간단히 작성한 시나리오를 통해서 학습을 위해 아래와 같이 각 카테고리(Category 혹은 Intent)에 코드값을 부여해줍니다. 이때 시스템의 발화는 제외하고 사용자의 발화만 코드값을 부여합니다.

index2category = {0:'greeting',1:'where',2:'ask',3:'bye'}
def category_define(x):
    code = ''
    if x=='greeting': code=0
    elif x=='where': code=1
    elif x=='ask': code=2
    elif x=='bye': code=3
    else: code='NaN'
    return code

# category code!
df['code'] = df['category'].apply(category_define)

# only answer
df=df[df['category'].notnull()]

사용자가 입력할 예상 문장들을 아래와 같이 추출할 수 있습니다.

sentence = df['text'].values
print(sentence)
array(['안녕하세요', '안녕', '헬로', '네 반갑습니다', 'hi hello', '세종에서 왔습니다',
       '세종에서 살아요', '대전 살아요', '세종이요', '세종요', '세종시에서 왔지', '서울요', '부산요',
       '빵을 사고 싶어요', '음료수 사고 싶어요', '커피 주세요', '빵 주세요', '케이크 주세요',
       '아이스 아메리카노 주세요', '베이글 주세요', '감사합니다', '고맙습니다', '잘먹을께요', 'Thank you'],
      dtype=object)

각 문장을 형태소분석이나 구분분석 등의 과정을 생략하고 단순히 문장을 공백으로 분리하여 각 단어의 집합을 생성합니다. 집합 생성시에 입력되는 문장에 단어가 없는 경우를 위해서 unk 코드와 자리수를 맞추기 위한 padding 값을 부여합니다.

sentence = df['text'].values

words = list(set([w for word in sentence for w in word.split(' ')]))
words = np.insert(words,0,'!') # padding 1
words = np.insert(words,0,'#') # unk 0

이제 생성한 문장을 단어 단위로 분리하고 각각 Index 값을 부여했기 때문에 각 문장을 숫자로 표현할 수 있습니다. word2index의 경우는 입력되는 단어들을 Index 값으로 바꿔주는 python dictionary이고 index2word는 그 반대의 경우입니다.

해당 과정을 거치면 문장은 숫자의 형태로 변경됩니다. 이렇게 하는 이유는 컴퓨터가 인간의 문장을 이해하지 못하기 때문입니다. 이제 학습을 위해 각 단어의 입력 Sequence Length를 맞춰주는 작업이 필요합니다. 이때 지나치게 패딩을 많이 입력하면 훈련 데이터에 노이즈가 많이 들어가기 때문에 예측 결과가 좋지 않을 수 있습니다. 그리고 마지막에 각 문장이 어떤 카테고리 혹은 의도(Intent)에 속하는지 Label 데이터를 입력해줍니다.

word2index = {w:i for i,w in enumerate(words)}
index2word = {i:w for i,w in enumerate(words)}

def xgenerator(x):
    return [ word2index['#'] if x_ not in word2index else word2index[x_] for x_ in x]

x_data = [xgenerator(words.split(' ')) for words in sentence]

for ndx,d in enumerate(x_data):
    x_data[ndx] = np.pad(d, (0, config.max_length), 'constant', constant_values=0)[:config.max_length]

y_data = df['code'].values

아래와 같이 학습에 필요한 파이토치 라이브러리를 import하고 DataLoader를 통해서 학습용 데이터셋을 만들어줍니다. 배치 사이즈는 데이터 셋이 크지 않기 때문에 한번에 학습을 하는 것으로 설정하시면 됩니다.

import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F

config.vocab_size = len(word2index)
config.input_size = 30
config.hidden_size = len(df['code'].unique())

from torch.utils.data import Dataset, DataLoader

class TxtDataSet(Dataset):
    def __init__(self, data, labels):
        super().__init__()
        self.data = data
        self.labels = labels
        
    def __len__(self):
        return len(self.data)
    
    def __getitem__(self, idx):
        return self.data[idx], self.labels[idx]

train_loader = DataLoader(dataset=TxtDataSet(x_data, y_data), batch_size=config.batch_size, shuffle=True)

학습용 모델을 생성합니다. 학습은 아래의 3개의 레이어를 통과하고 나온 결과 값을 사용합니다. 제가 작성한 여러 예제에 해당 모델에 대한 설명이 있기 때문에 자세한 설명은 하지 않고 넘어가겠습니다.

Embedding Layer – LSTM Layer – Linear Layer

class RNN(nn.Module):
    def __init__(self, vocab_size, input_size, hidden_size):
        super().__init__()
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.vocab_size = vocab_size
        
        self.embedding = nn.Embedding(self.vocab_size, self.input_size)
        self.rnn = nn.LSTM(
            input_size = self.input_size, 
            hidden_size = self.hidden_size, 
            num_layers=4, 
            batch_first=True, 
            bidirectional=True
        )
        
        self.layers = nn.Sequential(
            nn.ReLU(), 
            nn.Linear(hidden_size*2, hidden_size),
            
        )
        
    def forward(self,x):
        x = self.embedding(x)
        y, _ = self.rnn(x)
        y = self.layers(y[:,-1]) # last output dim...

        return F.softmax(y, dim=-1)
    
model = RNN(config.vocab_size, config.input_size, config.hidden_size)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters())
print(model)

생성한 모델을 출력하면 아래와 같이 표시됩니다.

RNN(
  (embedding): Embedding(35, 30)
  (rnn): LSTM(30, 4, num_layers=4, batch_first=True, bidirectional=True)
  (layers): Sequential(
    (0): ReLU()
    (1): Linear(in_features=8, out_features=4, bias=True)
  )
)
model.train()

hist_loss = []
hist_accr = []

for epoch in range(config.number_of_epochs):
    epoch_loss = 0
    for x_i, y_i in train_loader:
        y_hat = model(x_i) 
        loss = criterion(y_hat, y_i)
        
        accr = torch.argmax(y_hat, axis=1)== y_i
        accr = accr.data.numpy()
        accr = accr.sum()/len(y_i)
        
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        
        epoch_loss += float(loss)
    
    if epoch % 100 == 0:
        print('epoch:{}, loss:{:.5f}, accr:{:.5f}'.format(epoch, epoch_loss/config.number_of_epochs, accr))
epoch:0, loss:0.00070, accr:0.20833
epoch:100, loss:0.00068, accr:0.29167
epoch:200, loss:0.00065, accr:0.50000
epoch:300, loss:0.00062, accr:0.62500
epoch:400, loss:0.00056, accr:0.62500
epoch:500, loss:0.00054, accr:0.62500
epoch:600, loss:0.00052, accr:0.62500
epoch:700, loss:0.00051, accr:0.83333
epoch:800, loss:0.00050, accr:0.83333
epoch:900, loss:0.00049, accr:0.83333
epoch:1000, loss:0.00048, accr:0.83333
epoch:1100, loss:0.00048, accr:0.83333
epoch:1200, loss:0.00047, accr:1.00000
epoch:1300, loss:0.00046, accr:1.00000
epoch:1400, loss:0.00046, accr:1.00000
epoch:1500, loss:0.00045, accr:1.00000
epoch:1600, loss:0.00044, accr:1.00000
epoch:1700, loss:0.00044, accr:1.00000
epoch:1800, loss:0.00043, accr:1.00000
epoch:1900, loss:0.00043, accr:1.00000

학습한 모델을 통해서 입력한 어떤 내용으로 발화한 것인지를 예측해봅니다.

test_sentence = ['잘먹을께요']
x_test = [xgenerator(words.split(' ')) for words in test_sentence]
for ndx,d in enumerate(x_test):
    x_test[ndx] = np.pad(d, (0, config.max_length), 'constant', constant_values=0)[:config.max_length]
    
with torch.no_grad():
    x_test = torch.tensor(x_test, dtype=torch.long)
    predict = model(x_test)
    print(predict)
    result = torch.argmax(predict,dim=-1).data.numpy()
    print([index2category[p] for p in result])

학습을 완료한 후 모델을 아래와 같이 저장합니다.

torch.save({
  'model': model.state_dict(), 'config':config
}, './model/model.scenario')

import pickle
def save_obj(obj, name):
    with open('./pkl/'+ name + '.pkl', 'wb') as f:
        pickle.dump(obj, f, pickle.HIGHEST_PROTOCOL)
    
save_obj(index2category,'index2category')
save_obj(word2index,'word2index')
save_obj({'vocab_size':config.vocab_size,'input_size':config.input_size,'hidden_size':config.hidden_size,'max_length':config.max_length},'config')

django를 통해서 간단한 웹서버를 만들고 해당 모델을 활용해서 간단한 챗봇을 만들어봅니다.

웹서버의 사용자의 입력을 받아서 시나리오에서 어떤 흐름에 속하는 대화인지를 찾아내고 그 흐름에 맞으면 다음 대화를 진행하고 맞지 않을 경우 다시 발화를 할 수 있도록 유도합니다.

CNN을 활용한 비음성 구간 검출

합성곱신경망(CNN, Convolutional Neural Network)은 처음에는 이미지 분류에 많이 사용되었지만 현재는 이미지 분류 외에도 아주 다양한 분류 문제 해결에 사용되고 있습니다.

이번에는 CNN을 활용하여 음성신호를 분류하는 문제를 다뤄보겠습니다.

일단 음성신호를 몇개 만들어봅니다. 사용하는 음성신호는 wav(waveform audio file format)입니다.

위키피디아 정의에 의하면 WAV 또는 WAVE는 개인용 컴퓨터에서 오디오를 재생하는 마이크로소프트와 IBM 오디오 파일 표준 포맷이다. 가공되지 않은 오디오를 위한 윈도우 시스템의 기본 포맷이다. WAV는 비압축 오디오 포맷으로 프로그램 구동음이나 일반 수준의 녹으로 사용되지만 전문 녹음용으로는 사용되지 않는다.

구조적으로 보면 wav 파일은 PCM 파일에 Header를 붙인 것이라고 할 수 있습니다. 다시 말하면 PCM 정보를 이용하면 wav 파일을 만들수 있다는 의미가 됩니다. Header에는 Sampling Rate와 같은 정보, 채널 같은 정보가 필요합니다. wav 파일을 읽을 때에 별도의 조치가 필요하지 않은 이유는 바로 Header에 이런 정보가 포함되어 있기 때문입니다.

Sampling Rate는 아나로그인 음성 파일을 초당 몇번의 샘플링을 하는가에 대한 숫자입니다. 높으면 높을 수록 손실되는 정보가 적어지니 음질이 좋아지겠지만 저장을 위해서 더 많은 공간을 필요로 하기 때문에 목적에 따른 적절한 수준의 정의가 필요합니다. 이번 예제에서는 22,050 Sampling Rate를 사용합니다.

이 외에도 Mono, Stereo 를 표시하는 Channel과 8-bit, 16-bit, 32-bit를 정의하는 Resolution 정보가 헤더에 포함되어 있습니다. 이 헤더 정보를 자세히 살펴보면 아래와 같은 구조로 되어 있습니다.

보면 파일 구조 중에서 0-44 byte까지가 헤더 부분이고 실제 데이터는 그 이후에 추가됩니다.

wav 파일에 대한 자세한 정보는 인터넷에 공개된 다른 정보를 참고해 보시기 바랍니다.

테스트를 위한 개략적인 개요는 아래의 그림과 같습니다.

먼저 음성신호를 입력 받아서 각 신호를 1초 단위로 slicing 합니다. 그렇게 되면 60초의 음성 wav 파일이 있다면 각 파일은 1초 단위의 wav 파일로 나눠지게됩니다. 이제 각 분리한 음성파일을 들어보고 음성이면 0, 비음성이면 1로 레이블링합니다.

이 과정이 시간이 오래걸립니다. 두말할 것도 없이 이러한 데이터가 많으면 많을 수록 비음성을 찾을 확률이 커집니다. 비음성은 녹음 환경이 어디야에 따라서 많은 차이가 있습니다. 차안, 방안, 야외, 놀이터, 사무실 등에서 발생하는 비음성 데이터를 샘플링하면 더 좋은 예측 모델을 만들 수 있습니다.

1초 단위로 샘플링하면 이 데이터는 [0-22049]의 벡터로 변환할 수 있습니다. 만약 음성 길이가 60초라면 [60×22050]의 데이터가 됩니다. 이제 이 데이터를 CNN을 통해서 학습할 수 있도록 데이터의 형태를 바꿔주면 해당 데이터는 [60×1×n×m] 형태의 데이터가 됩니다. n과 m은 각각 원하시는 사이즈로 만드실 수 있습니다.

이제 해당 음성 데이터를 Google Drive에 업로드하고 Google Colab(Pro)를 사용해서 모델을 학습해보겠습니다. 아래의 그림은 테스트에 사용되는 Colab GPU 정보입니다.

gpu_info = !nvidia-smi
gpu_info = '\n'.join(gpu_info)
if gpu_info.find('failed') >= 0:
  print('Select the Runtime > "Change runtime type" menu to enable a GPU accelerator, ')
  print('and then re-execute this cell.')
else:
  print(gpu_info)
def load_wav2():
    file_list = sorted(glob.glob("gdrive/.../*sound/*.wav"))
    file_class = []

    for f in file_list:
      if f[40:47]=='av/nois':
          file_class.append([1,0])
      else:
          file_class.append([0,1])
  
    return file_list, file_class

import time
def time_per_epoch(st, et):
  elt = et - st
  elaps_min = int(elt/60)
  elaps_sec = int(elt - (elaps_min*60))
  
  return elaps_min, elaps_sec

# noise 0, norm 1
wav_list, wav_class = load_wav2()

음성 파일이 있는 위치에서 학습 파일 경로를 읽어옵니다. 학습 파일을 읽은 후에 노이즈(비음성)일 경우 0, 음성일 경우 1로 표시합니다. 즉 class는 2가됩니다. 이 말은 예측의 결과 값도 결국 0 or 1의 형태를 가진다는 말이됩니다. 그런 다음 1 epoch에 걸리는 시간을 기록하기 위해서 time_per_epoch() 이라는 함수를 선언해줍니다.

파일 리스트와 레이블 정보를 출력해보면 아래의 정보와 같습니다.

for w,c in zip(wav_list[0:10], wav_class[0:10]):
  print(w,c)

#gdrive/.../noise_sound/basic_10_0.wav [1, 0]
#gdrive/.../noise_sound/basic_10_0_cp.wav [1, 0]
#gdrive/.../noise_sound/basic_10_1.wav [1, 0]
#gdrive/.../noise_sound/basic_10_1_cp.wav [1, 0]
#gdrive/.../noise_sound/basic_10_21.wav [1, 0]
...

이제 음성 파일을 읽어서 벡터로 변환합니다.
각 음성 파일은 librosa 패키지 안에 있는 load() 함수를 사용해서 아나로그 신호를 벡터 정보로 변환합니다. Sampling Rate가 22050로 설정했기 때문에 벡터의 길이는 [0-22049]가 됩니다. 전체 데이터를 읽어서 메모리에 저장하는데 많은 시간이 필요합니다.

예제에서는 전체 파일이 아닌 일부 데이터만 읽어 오기 때문에 한번에 읽을 수 있지만 파일이 큰 경우는 메모리가 제한되어 있기 때문에 한번에 데이터를 읽을 수 없습니다. 그럴 경우는 batch_size를 사전에 정의하고 파이토치의 데이터로더(Dataloader)와 같은 도구를 사용해서 파일을 부분적으로 읽어서 학습을 수행할 수 있습니다. 데이터로더 예제는 블로그의 다른 글에 있으니 참고하시기 바랍니다.

wav_data = []
start_time = time.time()

for idx, wav in enumerate(wav_list):
  y, sr = librosa.load(wav)
  wav_data.append(y)

end_time = time.time()
lap_mins, lap_secs = time_per_epoch(start_time, end_time)
print(f"Lap Time: {lap_mins} mins, {lap_secs} secs")

데이터를 읽은 후에 학습용 데이터셋과 테스트용 데이터셋으로 분리[8:2]로 분리합니다.

xx = torch.tensor(x_data, dtype=torch.float, device=device)
yy = torch.tensor(y_data, dtype=torch.long, device=device)

train_cnt = int(xx.size(0) * config.train_rate)
valid_cnt = xx.size(0) - train_cnt

# Shuffle dataset to split into train/valid set.
indices = torch.randperm(xx.size(0)).to(device) # random 순열 리턴
x = torch.index_select(xx, dim=0, index=indices).to(device).split([train_cnt, valid_cnt], dim=0)
y = torch.index_select(yy, dim=0, index=indices).to(device).split([train_cnt, valid_cnt], dim=0)

이제 분석을 위한 CNN 학습 모듈을 생성합니다. 학습 모듈은 Conv2d() → ReLU() → BatchNorm2d() → Linear() → Softmax()로 구성되어 있습니다. 최종 출력값은 0 or 1의 형태를 가지게 됩니다.

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        
        self.convs = nn.Sequential(
            nn.Conv2d(1, 10, kernel_size=3), # input_channel, output_channel, kernel_size
            nn.ReLU(),
            nn.BatchNorm2d(10),
            nn.Conv2d(10, 20, kernel_size=3, stride=2),
            nn.ReLU(),
            nn.BatchNorm2d(20),
            nn.Conv2d(20, 40, kernel_size=3, stride=2),
            nn.ReLU(),
            nn.BatchNorm2d(40),
            nn.Conv2d(40, 80, kernel_size=3, stride=2)
        )
        
        self.layers = nn.Sequential(
            nn.Linear(80*17*17, 500),
            nn.ReLU(),
            nn.BatchNorm1d(500),
            nn.Linear(500,250),
            nn.Linear(250,100),
            nn.ReLU(),
            nn.BatchNorm1d(100),
            nn.Linear(100,50),
            nn.Linear(50, 10),
            nn.ReLU(),
            nn.BatchNorm1d(10),
            nn.Linear(10, 2),
            nn.Softmax(dim=-1)
        )

    def forward(self, x):
        x = self.convs(x)
        x = x.view(-1, 80*17*17)
        return self.layers(x)
    
model = Net()
model.to(device)

이제 모델을 아래와 같이 학습합니다.

optimizer = optim.Adam(model.parameters(), lr=config.lr)
criterion = nn.CrossEntropyLoss()

hist_loss = []
hist_accr = []

model.train()
for epoch in range(config.number_of_epochs):
    output = model(x[0]) # Train 
    loss = criterion(output, torch.argmax(y[0], dim=1)) # Train
    
    predict = torch.argmax(output, dim=-1) == torch.argmax(y[0], dim=-1)
    accuracy = predict.sum().item()/x[0].size(0)
    
    
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    
    hist_loss.append(loss)
    hist_accr.append(accuracy)
    
    if epoch % 5 == 0:
        print('epoch{}, loss : {:.5f}, accuracy : {:.5f}'.format(epoch, loss.item(), accuracy))

plt.plot(hist_loss)
plt.plot(hist_accr)
plt.legend(['Loss','Accuracy'])
plt.title('Loss/Legend')
plt.xlabel('Epoch')
plt.show()

학습이 완료된 후 아래와 같이 Loss/Accuracy를 표시해봅니다.

테스트 데이터를 통해서 해당 모델의 정확률을 테스트해보니 0.94 값을 얻었습니다.

with torch.no_grad():
  output = model(x[1])

  predict = torch.argmax(output, dim=1)
  accur = (predict == torch.argmax(y[1], dim=1)).to('cpu').numpy()
  print(accur.sum()/len(accur)) # 0.94

파이썬 제어문과 반복문

제어구문이란?

제어 구문이란 입력이나 출력 등 어떤 값에 의해서 프로그램의 흐름을 바꿀 수 있는 명령어를 의미합니다. 쉽게 말하면 프로그램의 흐름이 코딩 순서대로 흘러가다가 어떤 조건(Condition)에 의해서 프로그램의 흐름이 달라지는 것을 의미합니다. 실제로 이런 일은 프로그램을 하면서 매우 자연스러운 것입니다.

이런 조건문을 말로 풀어 본다면 “만일 000 라면 000 하라” 라고 쓸 수 있습니다. 이 안에 여러가지를 넣어서 문장을 만들 수 있습니다. “만일 성적이 90점 이상이라면 A를 주어라” 라고 한다면 입력되는 점수에 따라서 그 성적이 바뀔 수 있을 것입니다. 조건을 하나 더 붙인다면 “만일 90점 이상이라면 A를 주고 80점 이상이라면 B를 주고 70점 이상이라면 C를 주고 60점 이상이라면 D를 주고 그 이하라면 F를 주어라” 라고 조건을 여러개 주어서 프로그램을 만들 수도 있습니다.

IF 조건문

파이썬에서 이런 조건에 따라서 수행할 코드를 분기 할때에 사용하는 것이 if 조건문입니다. if 조건문은 굉장히 간단한 구조로 되어 있기 때문에 쉽게 이해하고 사용할 수 있습니다.

if 조건식:
    # 조건식이 참(True)일 경우에 실행
else:
    # 조건식이 거짓(False)일 경우에 실행

if 조건문에 들어가는 조건식에는 주로 하나 이상의 변수를 사용해서 조건식을 만듭니다. 예를 들어서 하나의 조건문이 True/False 인지를 확인할 수 있고 두개의 변수를 서로 비교해서 같거나 다르거나 크거나 같거나를 표현 할 수도 있습니다.
그것을 정리하면 아래의 표와 같습니다.

a == b # 두개의 변수 a와 b가 같다. 맞으면 True 틀리면 False
a != b # 두개의 변수 a와 b는 같지 않다. 
a > b  # 두개의 변수 a는 b보다 크다.
a >= b  # 두개의 변수 a는 b보다 크거나 같다.
a < b # 두개의 변수 a는 b보다 작다.
a <= b # 두개의 변수 a는 b보다 작거나 같다.

이 두개의 변수는 숫자값이 들어가도 되지만 문자열도 입력할 수 있습니다. 문자열의 경우에는 문자의 비교만 가능합니다. 예를 들어서 a=”apple”, b=”pear” 일때 a == b 조건은 당연히 False가 됩니다. 이런 방법으로 문자가 같은지를 확인하는 조건식을 만들고 활용할 수 있습니다.
주의 해야 할 것은 a와 b가 같은지를 볼때에 ‘=’ 하나를 쓰는 것이 아니라 ‘==’ double equal을 사용한다는 것입니다. 하나만 사용할 경우에는 해당 변수에 값을 대입하는 것으로 인식하기 때문에 조건문을 사용할 때에 이 부분을 조심하셔야 합니다.

이 조건문을 사용해서 이전에 간단히 설명했던 성적코드를 표시하는 프로그램을 작성해보면 아래와 같이 할 수 있습니다. 여기서 보면 “if 조건문 elif 조건문 else…”의 형태가 나오는데 여기서 나오는 “elif”는 새로운 것이 아니라 “else if”의 축약된 형태로 조건문이 False일 경우에 다른 조건문을 사용하는 간단히 말하면 중첩된 조건문에서 사용하는 코드입니다. “if … elif…”를 사용하지 않고 “if … else …” 형태를 반복해서 사용할 수 있지만 이렇게 작성된 코드는 코딩이 길어지기 때문에 간결하지 않고 또 간결하지 않은 코드는 수정이 쉽지 않다는 단점이 있습니다.

그렇기 때문에 가급적이면 간단하게 코딩하는 습관을 기르는 것이 좋습니다.

score = 88

if score > 90:
    print('A')
elif score > 80:
    print('B')
elif score > 70:
    print('C')
elif score > 60:
    print('D')
else:
    print('F')

위의 코드를 실행하면 출력값은 ‘B’가 출력됩니다.

아래의 코드는 문자열 값을 입력해서 비교하는 코드입니다. 아래와 같은 경우는 출력값이 False가 됩니다.

a = "apple"
b="pear"

if a == b:
    print('True')
else:
    print('False')

그리고 비교하는 변수는 꼭 두개가 있어야 하는 것은 아닙니다. 아래와 같이 flag를 bool 자료형으로 선언하고 True 값을 주면 해당 값의 조건에 따라서 True / False를 출력합니다.

flag = True

if flag:
    print('True')
else:
    print('False')

또 아래와 같이 조건식을 여러개 함께 사용할 수도 있습니다. 그렇게 되면 이전에 논리연산자를 함께 사용해야 합니다. 논리 연산자는 ‘and’,’or’ 연산자입니다. ‘and’의 경우에는 조건 모두를 만족해야 참(True)이고 하나라도 만족하지 않으면 거짓(False)가 됩니다. 그러나 ‘or’의 경우에는 조건 중 하나만 만족해도 참(True)이 됩니다.

아래의 조건문의 경우에는 a==b가 False이지만 score > 80이 True 임으로 출력값은 True를 표시하게됩니다.

if (a==b) or score > 80:
    print('True')
else:
    print('False')

반복문

제어문과 마치 한쌍과 같이 사용되는 것이 바로 반복문입니다. 이 반복문은 리스트와 같은 어떤 연속된 자료를 반복해서 처리하면서 사전에 정의된 조건문을 처리합니다. 가장 많이 사용되는 반복문은 for나 while과 같은 명령어입니다. 이 두 반복문에도 역시 반복에 필요한 조건들이 들어갑니다.

만약 조건이 들어가지 않는다면 해당 반복문들은 무한히 명령어를 실행하게됩니다. 이것을 무한루프라고 합니다. 시스템이 무한루프에 들어가면 무한정 리소스를 사용하기 때문에 결국 응용프로그램이 에러를 출력하게됩니다.

WHILE 루프

while은 for 루프와 함께 파이썬의 대표적인 반목문입니다. 어떤 조건을 주고 그 조건이 참일때까지 해당 명령어를 수행하게됩니다. 아래의 예와 같이 사용하는 방법도 굉장히 간단합니다.

while 조건식:
    #반복하면서 처리할 코드

이제 while 루프를 사용해서 간단한 코딩을 해보겠습니다. 일단 score라는 리스트를 선언하고 해당 리스트에 성적을 입력합니다. 그리고 리스트의 크기만큼 루프를 반복하며 성적을 출력해보는 예제입니다. 여기서 len() 함수는 리스트의 크기를 리턴해줍니다. 그러므로 아래의 예제는 i라는 변수에 0을 입력하고 i가 리스트의 크기보다 작은 조건을 만족할 때까지 while 루프안에 있는 print 구문을 수행하고 i의 값에 1을 계속적으로 더해줍니다. 결국 i는 0-4까지의 조건을 만족하겠고 5가 되는 순간 리스트의 크기와 같아지면서 해당 조건을 만족하지 않기 때문에 while 루프에서 벗어나게 됩니다.

실제로 아래 예제를 수행해보면 100부터 87까지의 값을 출력하고 해당 루프를 빠져나가는 것을 확인할 수 있습니다.

score = [100,80,99,92,87]
i = 0
while i < len(score):
    print(score[i])
    i += 1

만약 while의 조건식 부분에 True를 입력하게 되면 어떻게 될까요?
해당 구문은 무한루프로 들어가게됩니다. 언제 이런 구문을 사용하는가 생각해보면 사용자가 값을 계속 입력하는 상황을 생각해 볼 수 있겠습니다. 그러다가 마지막으로 입력을 종료하고 싶으면 약속된 키를 입력하면 종료되는 상황이 있을 수 있습니다.

my_score = []
while True:
    s = input('Score?')
    if s == 'q': break
    my_score.append(s)

print('입력하신 스코어는 {}입니다.'.format(','.join(my_score)))

::: 출력 형태 :::
Score? 100
Score? 90
Score? 50
Score? 60
Score? 80
Score? q
입력하신 스코어는 100,90,50,60,80입니다.

해당 코드를 실행하면 위와 같이 q라는 값이 들어오기 전까지 while 안에 있는 명령어를 계속 실행하게 되고 q 값이 입력되면 break 문이 실행되면서 while 루프를 벗어나게 됩니다. 만약 break 문이 없다면 해당 코드를 강제로 종료해야 하는 일이 생기게됩니다.

FOR 루프

for 루프는 while과 함께 대표적인 반복문입니다. while 루프도 많이 사용하지만 아마도 가장 많이 사용하는 것이 for 문법이라고 생각합니다. for는 사용하기가 while 비해서는 약간 복잡하게 느껴질 수도 있지만 다양한 표현이 가능하다는 것은 많은 부분에서 사용할 수 있다는 의미도 됩니다.

몇가지 예제를 통해서 그 쓰임을 알아보겠습니다.

for 루프 변수 in 리스트(또는 튜플, 문자열):
    # 수행할 명령어
    ...

위의 문법을 보면 for 다음에는 ‘루프 변수’가 입력됩니다. ‘루프 변수’는 쉽게 말해서 in 다음에 표시되는 리스트(또는 튜블, 문자열)의 내용을 담아내는 변수입니다. 다시 말하면 for 루프는 리스트의 크기 만큼 실행되며 리스트의 갯수 하나씩 루프 변수에 담는다는 의미입니다. 말로 풀어서 쓰면 복잡하지만 실제로 사용하는 것은 굉장히 간단하고 직관적입니다.

# case1 숫자 리스트
score = [100,80,99,92,87]
for s in score:
    print(s)

# case2 문자열
message = "Hello, World!"
for c in message:
    print(c)

# case3 범위
for i in range(10):
    print(i)

위의 코드를 실행해보면 case1은 score안에 있는 점수가 하나씩 출력됩니다. case2는 message에 입력된 문자열 데이터가 한글자씩 표시됩니다. case3를 실행해보면 0-9까지의 숫자가 출력됩니다.

여기에서 사용하는 range() 문은 굉장히 많이 사용하는 파이썬 내장함수이기 때문에 좀 더 알아보면 방금 사용했던것처럼 range(10)이라고 하면 0-9까지를 출력하고 range(5,10)으로 선언하면 시작하는 숫자가 5부터 시작하니 5-9까지를 출력하게됩니다. range(0,10,2)로 표시하면 0-9까지 2씩 증가한 값 즉, 0,2,4,6,8을 표시하게됩니다.

이 외에도 앞서 살펴본 while에서 break 문도 역시 for에서 사용할 수 있고 또 continue 문도 사용할 수 있습니다. break 문은 앞서 보았기 때문에 이번에는 continue 문을 어떻게 사용하는지 보겠습니다.

5개의 과목 중에서 90점이 넘는 과목은 A로 표시한다고 하면 아래와 같이 continue를 사용해서 표현할 수 있습니다. 조건을 만족하면 조건문 아래 라인을 실행하지 않고 조건을 만족하지 않으면 아래 라인을 실행하게 됩니다. break문의 경우에는 조건을 만족하면 for문을 빠져나가는데 이와 비슷한 개념이지만 약간의 차이가 있습니다.

# case1 숫자 리스트
score = [100,80,99,92,87]
for s in score:
    if s <= 90:
        continue
    print('{}점은 A입니다.'.format(s))

또 하나 파이썬에서 사용하는 for문의 독특하면서도 강력한 기능 중에 하나는 for 문을 한줄로 나열해서 쓸 수 있다는 것입니다. 이것도 예제를 통해서 살펴보겠습니다.

점수를 가지는 하나의 배열이 있는데 이 배열은 2개의 차원으로 되어있다고 가정합니다.

array2d[0] 번지에는 [100,99,98]의 값이 저장되어 있습니다.
array2d[1] 번지에는 [99,78,89] 값이 저장되어 있습니다.
array2d[2] 번지에는 [98,88,91] 값이 저장되어 있습니다.

이 배열에서 해당 점수를 표시하려면 위의 경우와 같이 ‘for s in array2d’와 같은 형식으로도 사용할 수 있지만 아래와 같이 그냥 한줄로 표시할 수도 있습니다.

array2d = [[100,99,98], [99,78,89], [98,88,91]]
['socre:{},{},{}'.format(arr[0],arr[1],arr[2]) for arr in array2d]

이러한 표현 방법은 numpy이나 pytorch에서 여러 줄의 코드를 간단히 한줄로 표현할 수 있게 해주는 굉장히 유용한 방식입니다.

파이썬 자료형

파이썬 자료형 – 리스트

파이썬에는 여러 개의 값을 하나의 데이터 저장소에 담아서 관리 할 수 있는 리스트(List)라는 자료형이 있습니다. 이 리스트에는 여러개의 값을 하나의 변수에 담을 수 있기 때문에 거의 모든 코드에 사용된다고 할 수 있습니다.

예를 들어서 학생의 10개 과목에 대한 성적을 입력할 때에 국어,영어,수학 등 10여개의 과목을 각각 변수에 담지 않고 하나의 변수명을 선언하고 [99,98,89 …] 와 같이 차례로 성적을 저장하는 방식입니다. 이때 중요한 것이 있다면 저장의 순서입니다. 모든 학생의 과목이 일정한 순서대로 정렬되어야 정상적인 결과를 얻을 수 있습니다.

score_list = [99,98,89,78,80...] #각 과목의 점수를 하나의 변수에 저장

dummy_list = [99,98,89,'A',3.4] #다른 형의 자료를 동일한 리스트에 저장할 수 있음
print([type(n) for n in dummy_list])
결과 : [<class 'int'>, <class 'int'>, <class 'int'>, <class 'str'>, <class 'float'>]

len(dummy_list)
결과 : 5

또 대부분 경우는 하나의 리스트에 같은 자료형을 입력하지만 다른 자료형을 입력하는 것도 가능합니다. 배열에 선언된 값에 접근하기 위해서는 변수[index] 형태로 사용하면 됩니다. 중요한 것은 index는 0부터 시작된다는 것입니다.

배열의 크기를 알기 위해서는 len()이라는 내장함수를 사용해서 확인 할 수 있습니다.

리스트에서 꼭 알아야 할 중요한것 중에 하나는 슬라이싱(Slicing)의 개념입니다. 이것은 리스트에서 원하는 범위를 지정해서 사용할 수 있는 굉장히 편리하고 많이 사용하는 개념입니다.

dl = [0,1,2,3,4,5,6,7,8,9]
print(dl[0:3]) #[0, 1, 2]
print(dl[5:]) #[5, 6, 7, 8, 9]
print(dl[3:-1]) #[3, 4, 5, 6, 7, 8]

cl = 'hello world'
print(cl[0:5]) #hello

위의 예제에서 보다시피 리스트의 인덱스 값을 활용해서 해당 자료를 출력합니다.
알아두면 좋은 것은 이것이 리스트 배열에서만 사용하는 것이 아니라 문자 배열에서도 동일하게 사용할 수 있다는 것입니다. 다른 언어의 substring과 같은 개념입니다. 앞에 -(minus) 기호를 붙이면 마지막에서부터 카운트를 하기 때문에 이를 응용해서 다양한 활용이 가능합니다.

파이썬 자료형 – 딕셔너리

리스트와 함께 또 하나의 중요한 데이터 저장소는 딕셔너리(Dictionary)입니다.

딕셔너리는 키(Key), 값(Value)의 쌍으로 되어 있는 데이터 저장소입니다. Java에 익숙하신 분은 맵(Map)이라는 객체를 생각하시면 되겠습니다. 딕셔너리도 선언과 활용이 간단합니다. 해당 내용을 출력하기 위해서는 키(Key) 값을 입력하면 그에 해당하는 값(Value)가 출력되는 구조입니다.

score_dict = {'kor':98, 'eng':90, 'math':100, 'hist':93} #과목을 Key로 선언하고 성적을 Value로 정의
print(score_dict['kor']) #98

print(score_dict.keys()) #dict_keys(['kor', 'eng', 'math', 'hist'])
print(sorted(score_dict.keys())) #['eng', 'hist', 'kor', 'math']
print(score_dict.values()) #dict_values([98, 90, 100, 93])

이전에 리스트에서 성적을 입력할 때에 순서가 중요했다면 딕셔너리에서는 Key가 존재하기 때문에 순서는 중요하지 않습니다. 딕셔너리에서 하나 주의해야 할 것은 Key 가 중복되지 않도록 관리해야 한다는 것입니다.

딕셔너리는 Key 값을 추출한다거나 이를 정렬하는 기능들을 제공하기 때문에 이를 활용해서 다양한 데이터를 처리할 수 있습니다.

파이썬 자료형 – 튜플

튜플(Tuple)은 리스트와 유사하지만 차이가 있다면 내용을 바꿀 수 없다는 점에 큰 차이가 있습니다. tutple로 선언한 변수에 어떤 다른 값을 적용하고자 하면 ‘tuple’ object does not support item assignment 라는 에러 메세지를 표시합니다.

dummy_tuple = (99,88,87,94,92)
print(dummy_tuple[0]) #99

dummy_tuple[0] = 100 #TypeError: 'tuple' object does not support item assignment

파이썬 자료형 – 집합

집합(Set)도 리스트와 유사한 개념입니다. 그러나 각 요소간의 순서는 존재하지 않으며 각 요소간의 중복된 값을 허용하진 않습니다. 또 집합의 수학적인 연산이 가능하다는 특징이 있습니다.

dummy_set0 = set(list(score_dict.keys()))
dummy_set1 = set(['sci','kor'])

print(dummy_set0 | dummy_set1) # 합집합 {'math', 'eng', 'sci', 'kor', 'hist'}
print(dummy_set0 - dummy_set1) # 차집합 {'math', 'eng', 'hist'}
print(dummy_set0 & dummy_set1) # 교집합 {'kor'}

이전에 사용했던 score_dict의 키 값을 모아서 하나의 리스트로 만든 후에 이것을 다시 집합으로 선언합니다. 그리고 또 하나의 집합을 생성합니다. 그리고 이 집합을 활용해서 집합 연산을 수행해봅니다.

집합 연산은 합집합, 차집합, 교집합이 있습니다.
합집합은 ‘+’ 연산자를 사용하지 않고 ‘|’ 연산자를 사용합니다. 집합은 중복을 허용하지 않기 때문에 두 집합의 요소가 하나씩 존재하게됩니다.
차집합은 공통된 요소를 삭제합니다. 교집합은 공통된 요소만 추출합니다.

파이썬 기초

문자화된 언어를 다른 언어로 변환하는 것을 번역(Translate)라고 합니다. 반면 언어를 다른 언어로 실시간으로 전달하는 것을 통역(Interpret)이라고 합니다. 비슷하지만 이렇게 차이가 있죠.
번역은 글로된 전체 문장을 번역하는 것이기 때문에 시간이 많이 걸리지만 통역은 실시간으로 말을 듣고 말로 전하기 때문에 실시간으로 빠른 처리가 가능합니다.

컴퓨터에서도 이러한 일들이 비슷하게 일어납니다. 어떤 언어는 번역을 하듯이 전체 프로그램 코드를 일괄적으로 한번에 기계어로 변환하고 어떤 언어는 실시간으로 Line By Line으로 한줄 한줄 실행해나갑니다.

전자의 언어를 Compile 언어라고 하고 Java와 C 언어가 대표적입니다. 반면 파이썬은 한줄 한줄 읽고 바로 실행하기 때문에 Interpret 언어라고 합니다.

인터프리트 언어의 장점은 컴퓨터와 대화하듯이 코딩을 할 수 있다는 장점이 있습니다. 예를 들어서 프로그래머가 “1+2″는 뭐지? 라고 물어보면 컴퓨터는 “3”이라는 결과를 바로 보여주는 식입니다. 이것을 파이썬 코드로 표현하면 어떻게 될까요?

명령어 입력라인에 아래와 같이 입력하면 됩니다. 이전에 java나 C언어를 경험하신 분은 이런 형식의 코드가 얼마나 간결하다는 것을 쉽게 이해할 수 있을 것입니다.

1+2
결과 : 3

이처럼 파이썬 코딩은 굉장히 직관적이고 간단합니다. 또 파이썬은 인터프리트 방식으로 대화식 프로그래밍이 가능합니다. 다음으로는 몇가지 기본적인 산술 연산에 대해서 알아보겠습니다.
참고로 결과라고 쓴 부분은 코드를 실행한 결과가 표시되는 값을 나타낸 것입니다. 실제로 코드를 실행하면 “결과”라는 단어는 나오지 않습니다.

이제부터 파이썬에서 많이 사용하는 중요한 몇가지 기초적인 코드를 설명하고자 합니다. 처음 파이썬을 접하시는 분을 대상으로 최대한 간단하게 그리고 많이 쓰는 것들 위주로 설명하겠습니다. 몇가지 기본적인 코드의 문법만 이해해도 제가 블로그에 작성한 예제 코드를 이해하는데 문제가 없으리라 생각됩니다.

파이썬 사칙연산

2-3
결과 : -1
4*3
결과 : 12
6/4
결과 : 1.5
5**2
결과 : 25

프로그램에서 *는 곱셈, /은 나눗셈, **은 제곱을 의미합니다. 참고로 나눗셈을 표시하는 기호가 /라고 말씀드렸습니다. 그러나 //을 사용하는 경우도 있습니다. 이것은 **과 같이 제곱을 의미하는 것이 아니라 나눈 결과 값에서 소수점 이하를 버린다는 것을 의미합니다. 예를 들어서 6/4의 결과가 1.5로 표시되는데 6//4로 수행하면 결과값은 1로 표시됩니다. 또 가끔 % 연산자를 만나기도 합니다. 해당 연산자는 나눈 값의 나머지를 출력합니다.
6/3 하면 나머지가 0이 됩니다. 그러나 5/3하면 나머지가 2개 생기는데 % 연산자는 이 값을 출력해줍니다.

파이썬 자료형

type(1)
결과 : int
type(3.14)
결과 : float
type('Hello World')
결과 : str

프로그래밍 언어는 각각 자료형(Data Type)이 있습니다. 자료형은 말 그대로 자료(Data)의 형태를 의미합니다. 예를 들어서 위에서 표현한것과 같이 정수형, 실수형, 문자형의 자료 형태가 있고 type()이라는 내장함수로 데이터의 자료형을 알아 볼 수 있습니다.

참고로 내장함수라는 것은 중요하고 자주 사용하는 기능들은 사용자가 별도로 개발하지 않도록 프로그램 언어가 기본적으로 제공하는 함수(function) 입니다. print(), len(), range() 등 많은 내장함수가 있습니다. 이러한 함수들을 중요하고 또 너무나 많이 사용하기 때문에 다른 라이브러리들과는 달리 import 하지 않고 바로 사용할 수 있는 특징이 있습니다.

내장 함수를 모두 외울 필요는 없지만 관심있게 내용을 살펴볼 필요는 있습니다. 아래의 링크에 파이썬 내장함수가 잘 설명된 페이지를 링크합니다.

https://docs.python.org/ko/3/library/functions.html

파이썬 변수

변수(變數)는 Variable이라고 합니다. 말 그대로 변하는 수입니다. 이는 상수(常數)와 반대되는 개념입니다.
변수를 사용하는 가장 큰 이유는 코드의 재활용성과 가독성을 높여 주기 때문입니다. 이는 코드의 복잡성을 낮춰 줄 수 있고 이는 이후의 기능 수정과 변경에 용이한 특징이 있습니다.

a = 10
b = 10
a + b
출력 : 20

여기서 말하는 a, b와 같은 것이 바로 변수입니다. 즉 변할 수 있는 수라는 의미입니다. 변수는 숫자나 문자만 넣을 수 있는 것은 아닙니다. 거의 모든 것을 담을 수 있습니다.

변수는 마치 번지수와 같다고 할 수 있습니다. 예를 들어 10이라는 값은 메모리 영역에 한 부분에 저장됩니다. 그리고 그 주소는 id(a)로 표시해보면 “4521043376’와 같은 숫자 형태의 메모리 주소가 표시됩니다. 그러나 이것은 사람이게 친절한 표기법은 아닙니다. 인간에게는 a,b,c… 혹은 조합된 단어와 같은 인간의 언어코드로 표시하는게 훨씬 이해하기 쉽습니다.

예를 들어서 사각형의 높이를 구하는 공식은 가로×세로 입니다. 이러한 결과 값을 위해서 각 가로와 세로의 값을 곱해줄 수도 있지만 그렇지 않고 변수를 사용해서 변수간의 곱을 할 수도 있습니다. 이렇게 하는 것이 코드를 더 쉽게 이해할 수 있을 것입니다.

# 변수를 사용하지 않음
5 * 5
출력 : 35

# 변수를 사용
width = 5
height = 5
width * height
출력 : 35 

위와 같이 변수를 사용해서 프로그램을 하는 것이 훨씬 이해하기 쉽습니다. 변수 없이 숫자만으로 나열된 코드를 본다는 것은 생각하기도 힘든 일일 것입니다. 보통 변수로 사용되는 것은 알파벳 대소문자와 숫자입니다. 그러나 숫자로 시작하는 변수는 사용할 수 없습니다.

파이썬 아나콘다와 주피터 랩

파이썬을 개발 환경을 준비하는 방법 중에 가장 대중적인 것은 아나콘다를 활용하는 법입니다. 아나콘다는 패키지 관리와 디플로이를 효율적으로 할 수 있도록 하는 파이썬과 프로그래밍 언어의 자유-오픈 소스 배포판입니다. 개발에 사용하는 패키지 버전들은 패키지 관리 시스템 conda를 통해 관리됩니다.

예를 들어 내 컴퓨터에서 개발을 위해 여러 개발환경을 구축한다고 생각해봅니다. 때로 RNN 프로젝트나 Django를 활용한 웹개발, CNN을 활용한 Classification 문제를 해결한다고 생각해보면 각 프로젝트마다 python의 버전이나 활용하는 패키지가 있을 것입니다.

또 다른 사람이 만들었던 개발환경을 내 컴퓨터에 그대로 복사하고 싶을 때가 있습니다. 이미 내 컴퓨터에 있는 환경과 같다면 크게 문제될 것이 없겠지만 다른 것이 많이 있다면 좀 난감한 상황이겠죠.

아나콘다는 이러한 환경에 독립적인 프로젝트를 만들 수 있도록 도와줍니다. 이러한 툴이 없다면 개발 환경마다 python 버전과 의존성 있는 패키지들을 수정해야 하는 번거로운 작업들을 해야합니다. 그만큼 개발하는데 많은 시간이 소요되겠죠.

아나콘다 홈페이지에는 해당 제품들을 소개하며 모든 데이터 사이언티스트를 위한 도구라고 소개하고 있습니다. 그만큼 사용하기 쉽고 범용적이고 대중적인 도구라는 의미가 되겠습니다.

아나콘다는 Window, Mac, Linux를 모두 지원하니 사용하시는 OS에 맞춰서 다운로드 받으시면 되겠습니다. 저의 경우에는 Mac을 사용하기 때문에 Mac용 아나콘다를 다운로드 받아서 사용하고 있습니다.

다운 받은 후에 실행하면 아래와 같은 형태의 화면이 표시됩니다. 이중에서 이번 파이썬 코드 리뷰에 사용하는 도구는 Jupyter Lab이기 때문에 간단히 살펴보겠습니다. 사실 Jupyter Lab은 Jupyter Notebook의 확장판이라고 생각하시면 되기 때문에 이전에 Jupyter Notebook을 사용해 보신 분이라면 쉽게 사용하실 수 있습니다. 사용 방식이나 UI 구성은 거의 같습니다.
이 외에도 VS Code라는 훌륭한 개발도구도 있으니 사용해보시는 것도 추천합니다.

Jupyter Lab의 가장 좋은 장점은 Interactive하고 Reproducible한 특징이라고 할 수 있습니다. 마땅히 우리말로 설명하기가 어렵기 때문에 Jupyter Lab에서 소개하는 단어를 그대로 사용하겠습니다.
이러한 특징이 있기 때문에 사용자는 코딩 하고 바로 그 결과를 확인 할 수 있습니다. 무엇보다 무료이고 가볍고 웹 기반의 형식으로 되어 있기 때문에 UI도 따로 설명할 것이 없을 정도로 간단합니다.

그러나 그 기능과 활용도는 매우 강력하다고 할 수 있습니다. 아래의 이미지는 Jupyter Lab 공식 홈페이지에서 제공하는 이미지입니다. 보시는 것처럼 풍성한 시각화 도구를 지원하고 있습니다. 자세한 내용은 아래의 링크로 접속하시면 확인할 수 있습니다.

https://jupyter.org/

이 외에도 Colab(Colaboratory) 이라는 강력한 도구가 있습니다.

줄여서 ‘Colab’이라고도 하는 Colaboratory를 사용하면 브라우저에서 Python을 작성하고 실행할 수 있습니다. 자세한 소개 영상은 아래의 영상을 확인하시기 바랍니다.

Colab을 사용하면서 얻을 수 있는 장점은 별도의 개발환경을 구축하지 않아도 된다는 점, GPU를 제한적이지만 무료로 사용할 수 있다는 점, 그리고 공유가 간편하다는 장점이 있습니다. 조금 더 좋은 환경을 사용하려면 유료 결제를 통해서 Colab Pro 버전을 활용할 수도 있습니다.

저도 Colab Pro 버전을 사용하고 있는데 확실히 Colab 무료 버전 보다는 조금 더 나은 테스트 환경을 제공해줍니다. (많이는 아니지만…)

파이썬 소개

파이썬이 세상에 등장한지도 이미 20년이 넘었습니다. 많은 언어가 등장했다가 역사속으로 사라지기를 반복하지만 파이썬의 경우는 시간이 갈 수록 꾸준히 인기를 얻고 있는 언어입니다. 현재는 인공지능의 발전과 함께 가장 인기있는 언어가 되었습니다.

위의 그래프는 TIOBE에서 발표한 프로그램언어의 순위입니다. JAVA는 아주 오랜기간 C와 함께 가장 많이 사용하는 언어로 군림해왔습니다. 아마 JAVA 만큼 객체지향을 잘 표현한 언어는 없을 것입니다. 그만큼 자바는 객체지향의 대명사로 수십년동안 가장 많이 사용되는 언어였습니다. C언어는 그 역사와 활용도는 언급할 필요도 없이 지금도 강력한 영향력을 미치는 언어입니다.

파이썬은 2002년 당시에는 사용자가 많지 않은 언어였습니다. 그러나 2018년 이후 급격하게 사용자 수가 증가해서 현재는 C, JAVA를 이어서 그 다음으로 인기있는 언어가 되었습니다.

파이썬이 급성장한 이유는 인공지능의 발전과 함께 하기 때문이라는데는 반론의 여지가 없을 것입니다. 그렇다면 파이썬의 어떤 특징으로 인해서 현재 이토록 많은 사람이 사랑하는 언어가 되었을까요?

일단 파이썬은 배우기 쉬운 프로그램 언어입니다. 또 오픈소스이기 때문에 무료로 사용할 수 있고 풍성한 라이브러리를 지원하기 때문에 다양한 곳에 많이 사용됩니다. 또 영어와 유사한 문법으로 되어 있기 때문에 친근하다고 할 수 있습니다. 코드를 작성하기 쉽다는 것은 그만큼 읽기도 쉽다는 뜻이 됩니다. 또 컴파일의 과정도 없이 바로 실행하고 결과를 확인 할 수 있는 구조로 되어 있기 때문에 빠른 응답이 필요한 업무에 굉장히 편리합니다. 물론 그렇다고 컴파일 언어가 매력이 없다는 뜻은 아닙니다. 컴파일 언어는 그 나름대로 장점이 많이 있습니다.

파이썬은 이러한 여러가지 특징 때문에 고등학교, 대학교에서 프로그램을 강의하는데 처음 접하는 언어로 파이썬을 많이 사용하고 있습니다. 그렇다고 초보자만 사용하는 언어는 아닙니다. 인공지능을 개발하는 많은 분야의 전문가들이 파이썬을 사용하고 있습니다.

파이썬은 특히 인공지능이나 기계학습 분야에서 많이 사용됩니다.

그 이유는 파이썬 자체의 특별한 장점도 있지만 거기에 더해서 Numpy, SciPy와 같은 강력한 수치계산과 통계를 지원하는 다양한 라이브러리와 데이터를 다루는데 탁월한 Pandas, 시각화에 최적화되어 있는 matplotlib의 강력한 기능이 파이썬을 더 특별한 언어로 만들어 줍니다.

실제로 현재 가장 많이 사용하는 인공지능 프레임워크인 TensorFlow와 제가 이 블로그에 다양한 예제를 구현하는데 사용하는 PyTorch 같은 훌륭한 오픈소스 인공지능 프레임워크들이 파이썬 코드로 개발되어 있습니다.

또 Django와 같은 웹 어플리케이션 개발을 위한 프레임워크까지 구성되어 있기 때문에 만일 인공지능을 배우고자하는 분이 있다면 파이썬은 반드시 경험하고 배워야할 언어가 되었습니다.

본 예제에서는 파이썬의 간단한 문법을 소개해드리겠습니다.

물론 많은 다양한 블로그에서 다루고 있기 때문에 모든 것을 다 다루지는 않고 제가 그동안 구현하면서 필요하다고 생각되는 많은 사용하는 부분이나 꼭 필요한 부분들을 다뤄가도록 하겠습니다.