Agent 的核心组件:大脑、记忆、手脚与心跳
掘金
前言:Agent 不是“一个模型”,而是一套系统 很多人第一次接触 Agent,会以为: 但真正跑起来你会发现,LLM 只是其中一块。 一个能完成真实任务的 Agent,至少需要六个部分协同: 感知:
Балл: 57.06Уверенность: 54%
ПодробнееЗагружаем каталог…
НАВИГАТОР ПО ВОЗМОЖНОСТЯМ ИИ
Найдите свой ИИ-инструмент. Бесплатный доступ, пробные периоды и кредиты — в одном месте.
掘金
前言:Agent 不是“一个模型”,而是一套系统 很多人第一次接触 Agent,会以为: 但真正跑起来你会发现,LLM 只是其中一块。 一个能完成真实任务的 Agent,至少需要六个部分协同: 感知:
Балл: 57.06Уверенность: 54%
Подробнее掘金
Company Brain 是运行在 Cloudflare Workers 上的 Slack 机器人,持续记忆团队对话并基于权限隔离的记忆容器检索答案,能在 GitHub、Linear 等工具中自动执
Балл: 57.06Уверенность: 54%
Подробнееvelog
1. 토큰화란? 자연어 처리(NLP)에서는 크롤링, 문서 수집, 로그 데이터 등을 통해 얻은 텍스트 데이터인 코퍼스(corpus) 를 그대로 사용하기 어렵다. 목적에 맞게 텍스트를 전처리해야 하며, 대표적인 전처리 과정에는 다음이 있다. 토큰화(Tokenization) 정제(Cleaning) 정규화(Normalization) 이 중 토큰화란 문장이나 문서에서 의미 있는 최소 단위인 토큰(token) 으로 나누는 작업이다. 토큰은 상황에 따라 단어, 형태소, 문장, 서브워드 등이 될 수 있다. wikidocs 예를 들어 다음 문장이 있다고 가정해보자. Time is an illusion. Lunchtime double so! 구두점을 제거하고 공백을 기준으로 단어 토큰화를 하면 다음과 같이 나눌 수 있다. ["Time", "is", "an", "illusion", "Lunchtime", "double", "so"] 하지만 실제 자연어 처리에서는 단순히 공백 기준으로 문장을 나누는 것만으로 충분하지 않다. 줄임말, 구두점, 숫자, 이메일 주소, 날짜, 한국어 조사처럼 예외적으로 처리해야 하는 요소가 많기 때문이다. wikidocs 2. 단어 토큰화 토큰의 기준을 단어로 설정해 나누는 방식을 단어 토큰화(Word Tokenization) 라고 한다. 영어는 띄어쓰기 단위가 단어와 비교적 잘 일치하기 때문에, 기본적인 경우 공백을 기준으로 나누는 방식이 어느 정도 작동한다. 하지만 아포스트로피, 하이픈, 약어, 고유명사, 숫자 표현 등이 등장하면 토큰화 기준을 세밀하게 설정해야 한다. 예를 들어 아래 문장에서 Don't , Jone's 는 어떻게 분리해야 할까? Don't be fooled by the dark sounding name, Mr. Jone's Orphanage. 토큰화 도구별 결과는 다를 수 있다. 도구 Don't 처리 방식 Jone's 처리 방식 word_tokenize Do , n't Jone , 's WordPunctTokenizer Don , ' , t Jone , ' , s text_to_word_sequence don't 유지 jone's 유지 from nltk.tokenize import word_tokenize, WordPunctTokenizer from tensorflow.keras.preprocessing.text import text_to_word_sequence text = "Don't be fooled by the dark sounding name, Mr. Jone's Orphanage." print(word_tokenize(text)) print(WordPunctTokenizer().tokenize(text)) print(text_to_word_sequence(text)) 이처럼 어느 결과가 정답이라고 단정하기보다, 분석 목적과 이후 모델링 방식에 적합한 토큰화 기준을 선택하는 것 이 중요하다. wikidocs 3. 토큰화 시 고려할 점 구두점과 특수문자는 무조건 제거하지 않는다 구두점이나 특수문자는 항상 불필요한 노이즈가 아니다. 문장의 경계나 특정 의미를 표현할 수 있기 때문에, 단순 삭제 전에 데이터의 역할을 확인해야 한다. 다음과 같은 사례가 있다. 표현 의미 주의점 Ph.D. 학위 약어 마침표를 제거하면 약어 정보가 훼손될 수 있음 AT&T 기업명 특수문자가 이름의 일부임 m.p.h 속도 단위 점을 기준으로 분리하면 의미가 달라질 수 있음 $45.55 가격 $ , 소수점이 의미를 가짐 01/02/06 날짜 / 가 날짜 구분자 역할을 함 123,456,789 큰 수 , 가 숫자 단위 표현에 필요함 즉, 구두점 제거는 일괄적으로 수행하기보다 데이터의 도메인과 모델의 목적에 따라 결정해야 한다. 금융 텍스트에서는 금액, 수익률, 날짜, 종목 코드와 같은 정보가 많으므로 숫자·통화기호·소수점·쉼표의 처리 기준을 특히 주의할 필요가 있다. wikidocs 줄임말과 띄어쓰기가 포함된 표현 영어에는 아포스트로피를 통해 단어가 축약되는 경우가 많다. I'm → I am we're → we are what're → what are doesn't → does not 또한 여러 단어가 하나의 의미 단위로 사용되기도 한다. New York rock 'n' roll 따라서 토큰화 과정은 단순히 공백이나 기호를 기준으로 자르는 작업이 아니라, 문맥과 언어 규칙을 고려해 의미 단위를 보존하는 작업 이라고 볼 수 있다. wikidocs Penn Treebank 토큰화 예시 Penn Treebank 토큰화 규칙의 대표적 특징은 다음과 같다. 하이픈으로 연결된 단어는 하나의 토큰으로 유지한다. doesn't 처럼 접어(clitic)가 붙은 축약형은 분리한다. from nltk.tokenize import TreebankWordTokenizer tokenizer = TreebankWordTokenizer() text = ( "Starting a home-based restaurant may be an ideal. " "it doesn't have a food chain or restaurant of their own." ) print(tokenizer.tokenize(text)) 출력 예시는 다음과 같다. [ 'Starting', 'a', 'home-based', 'restaurant', 'may', 'be', 'an', 'ideal.', 'it', 'does', "n't", 'have', 'a', 'food', 'chain', 'or', 'restaurant', 'of', 'their', 'own', '.' ] home-based 는 하나의 토큰으로 유지되고, doesn't 는 does 와 n't 로 분리되는 것을 확인할 수 있다. wikidocs 4. 문장 토큰화 문장을 기준으로 텍스트를 나누는 작업을 문장 토큰화(Sentence Tokenization) 또는 문장 분리(Sentence Segmentation)라고 한다. 가장 단순한 방법은 마침표( . ), 물음표( ? ), 느낌표( ! )를 기준으로 나누는 것이다. 하지만 마침표는 문장의 끝뿐 아니라 약어, 이메일 주소, IP 주소, 소수점 등에도 사용된다. 예를 들어 아래 문장을 단순히 마침표 기준으로 나누면 문제가 생긴다. IP 192.168.56.31 서버에 접속해 주세요. 결과는 aaa@gmail.com으로 보내주세요. IP 주소의 점, 이메일 주소의 점, 문장 종결의 점을 모두 같은 방식으로 처리하면 잘못된 문장 분리가 발생할 수 있다. 또 Ph.D. 처럼 약어 내부에 포함된 마침표도 문장 끝으로 판단하면 안 된다. wikidocs NLTK 영어 문장 토큰화 NLTK의 sent_tokenize() 는 단순한 마침표 분할보다 더 정교하게 문장을 나눈다. from nltk.tokenize import sent_tokenize text = ( "I am actively looking for Ph.D. students. " "and you are a Ph.D student." ) print(sent_tokenize(text)) [ 'I am actively looking for Ph.D. students.', 'and you are a Ph.D student.' ] Ph.D. 내부의 마침표를 문장 종결로 오해하지 않고 처리한다. wikidocs 한국어 문장 토큰화 한국어 문장 분리에는 KSS(Korean Sentence Splitter)를 사용할 수 있다. pip install kss import kss text = ( "딥 러닝 자연어 처리가 재미있기는 합니다. " "그런데 문제는 영어보다 한국어로 할 때 너무 어렵습니다. " "이제 해보면 알걸요?" ) print(kss.split_sentences(text)) [ '딥 러닝 자연어 처리가 재미있기는 합니다.', '그런데 문제는 영어보다 한국어로 할 때 너무 어렵습니다.', '이제 해보면 알걸요?' ] 한국어 NLP에서는 한국어의 문장 종결 표현과 문장 부호 특성을 고려한 도구를 사용하는 것이 유리하다. wikidocs 5. 한국어 토큰화가 어려운 이유 한국어는 영어와 달리 단순한 띄어쓰기 기준 토큰화만으로 충분한 결과를 얻기 어렵다. 가장 큰 이유는 한국어가 교착어 이기 때문이다. 교착어는 조사, 어미, 접사 등이 어근이나 명사 뒤에 붙어 문법적 의미를 만드는 언어를 말한다. 예를 들어 다음 문장을 보자. 에디가 책을 읽었다. 띄어쓰기 기준으로 분리하면 다음과 같다. ['에디가', '책을', '읽었다'] 하지만 형태소 단위로 나누면 다음처럼 분석할 수 있다. 에디 / 가 / 책 / 을 / 읽 / 었 / 다 구분 예시 설명 자립 형태소 에디, 책 독립적으로 의미를 지니는 단위 의존 형태소 가, 을, 었, 다 다른 형태소와 결합해 문법적 기능을 하는 단위 한국어 자연어 처리에서는 명사, 동사 어간, 조사, 어미 등을 분리해야 하는 경우가 많다. 따라서 한국어에서는 어절 단위 토큰화보다 형태소 토큰화(Morpheme Tokenization) 가 중요하다. wikidocs 또한 실제 한국어 데이터에는 띄어쓰기 오류가 흔하다. 제가이렇게띄어쓰기를전혀하지않고글을썼다고하더라도 글을이해할수있습니다. 한국어는 띄어쓰기가 완벽하지 않아도 문맥으로 의미를 추론할 수 있는 경우가 많다. 하지만 모델 입장에서는 단어 경계가 불명확해질 수 있으므로, 실제 서비스 데이터에서는 띄어쓰기 교정이나 형태소 분석 품질이 NLP 성능에 영향을 줄 수 있다. wikidocs 6. 품사 태깅 품사 태깅(Part-of-Speech Tagging, POS Tagging) 은 토큰마다 문법적 역할을 부여하는 작업이다. 같은 표기를 가진 단어라도 품사에 따라 의미가 달라질 수 있다. fly 동사: 날다 명사: 파리 한국어도 마찬가지다. 못 명사: 망치로 박는 물건 부사: 어떤 행동을 할 수 없다는 의미 따라서 단어의 문맥상 의미를 이해하거나, 명사만 추출해 키워드 분석을 수행하거나, 동사·형용사 중심의 감성 분석을 수행하려면 품사 정보가 유용하다. wikidocs NLTK 영어 품사 태깅 from nltk.tokenize import word_tokenize from nltk.tag import pos_tag text = "I am actively looking for Ph.D. students." tokens = word_tokenize(text) print(pos_tag(tokens)) [ ('I', 'PRP'), ('am', 'VBP'), ('actively', 'RB'), ('looking', 'VBG'), ('for', 'IN'), ('Ph.D.', 'NNP'), ('students', 'NNS'), ('.', '.') ] 대표적인 Penn Treebank 품사 태그는 다음과 같다. 태그 의미 PRP 인칭대명사 VBP 동사 RB 부사 VBG 현재분사 IN 전치사 NNP 고유명사 NNS 복수 명사 CC 접속사 DT 관사 7. KoNLPy를 이용한 한국어 형태소 분석 한국어 NLP에서는 KoNLPy를 이용해 형태소 분석과 품사 태깅을 수행할 수 있다. KoNLPy에서 활용할 수 있는 대표적인 형태소 분석기는 다음과 같다. Okt(Open Korean Text) Mecab Komoran Hannanum Kkma 대표적으로 Okt 는 다음 메서드를 제공한다. 메서드 기능 morphs() 형태소 추출 pos() 형태소와 품사 태그 추출 nouns() 명사 추출 from konlpy.tag import Okt okt = Okt() text = "열심히 코딩한 당신, 연휴에는 여행을 가봐요" print(okt.morphs(text)) print(okt.pos(text)) print(okt.nouns(text)) 출력 예시는 다음과 같다. # 형태소 분석 ['열심히', '코딩', '한', '당신', ',', '연휴', '에는', '여행', '을', '가봐요'] # 품사 태깅 [ ('열심히', 'Adverb'), ('코딩', 'Noun'), ('한', 'Josa'), ('당신', 'Noun'), (',', 'Punctuation'), ('연휴', 'Noun'), ('에는', 'Josa'), ('여행', 'Noun'), ('을', 'Josa'), ('가봐요', 'Verb') ] # 명사 추출 ['코딩', '당신', '연휴', '여행'] 같은 문장이라도 분석기마다 결과가 달라질 수 있다. 예를 들어 Okt는 가봐요 를 하나의 동사 형태로 처리할 수 있지만, Kkma는 가보 , 아요 처럼 더 세분화할 수 있다. 따라서 형태소 분석기를 선택할 때는 다음을 기준으로 판단하는 것이 좋다. 처리 속도가 중요한가 신조어·SNS 문체 처리가 중요한가 문어체·뉴스 데이터가 중심인가 명사 추출만 필요한가 세밀한 문법 분석이 필요한가 프로젝트 환경에서 설치와 운영이 쉬운가 특히 대규모 한국어 데이터 처리에서는 처리 속도가 중요한 경우가 많아 Mecab을 고려할 수 있고, SNS·구어체 데이터 분석에서는 Okt를 실험 후보로 사용할 수 있다. wikidocs 8. 정리 토큰화는 텍스트를 모델이 처리할 수 있는 의미 단위로 나누는 NLP 전처리 과정이다. 토큰의 단위는 단어, 문장, 형태소, 서브워드 등 목적에 따라 달라질 수 있다. 구두점, 특수문자, 숫자, 날짜, 통화기호는 무조건 제거하지 않고 의미를 고려해야 한다. 영어는 공백 기반 토큰화가 어느 정도 가능하지만 축약형, 약어, 하이픈 등의 예외 처리가 필요하다. 문장 토큰화에서는 마침표가 약어·이메일·IP 주소·소수점 등에 포함될 수 있음을 고려해야 한다. 한국어는 조사와 어미가 결합하는 교착어이므로, 띄어쓰기 단위보다 형태소 단위 토큰화가 중요하다. 품사 태깅은 단어의 문법적 역할을 부여해 의미 분석, 키워드 추출, 감성 분석 등에 활용할 수 있다. 한국어 형태소 분석기마다 토큰 분리와 품사 태깅 결과가 다르므로, 실제 데이터와 목적에 맞춰 비교·선택해야 한다.
Балл: 54.4Уверенность: 49%
Подробнееvelog
코드잇 데이터분석가 부트캠프를 통해 배우고 느낀 것을 적습니다. 강의/미션/위클리페이퍼/프로젝트로 나누어 작성합니다. 📅 2026.05.21 | 🗺️ 과정: 1개월차 ▓░░░░░░ | 📖 커리큘럼: 미션 4 큰 데이터를 처음부터 끝까지 탐색하며, 그래프가 그려졌다고 분석이 맞는 것은 아니라는 것을 확인한 하루였다. 🗓️ 오늘의 일정 교시 시간 내용 1~3교시 09:00~12:00 미션 진행, 정리와 시각화 4~7교시 13:00~17:00 미션 마무리, 제출, 해설 강의 8~9교시 17:00~19:00 해설과 내 코드 비교, 자습 미션 4는 하루에 끝나서, 진행한 과정부터 해설 비교까지 오늘 한 편에 모두 적는다. 1️⃣ 오늘 진행한 일 어제 배운 EDA 흐름을 큰 규모의 건강검진 데이터에 처음부터 끝까지 적용하는 미션이었다. 데이터를 불러오고 구조와 컬럼 의미를 확인하는 연습 결측치와 이상치, 중복값을 확인하고 처리하는 연습 코드로 적힌 값을 이름표로 바꾸고, 수치형과 범주형의 기술통계를 확인하는 연습 분포, 변수 간 관계, 연령대별 차이, 성별에 따른 생활습관을 시각화하는 연습 체중과 신장으로 새 지표를 만들고, 그 지표를 기준으로 집단을 비교하는 연습 마지막으로 자유롭게 탐색하는 심화 과제 미션에는 탐색하며 알아낸 내용을 보고서로 정리하는 단계도 함께 안내되어 있었다. 2️⃣ 내가 진행한 과정 2-1. 불러오고, 의미를 먼저 확인했다 🛠️ 무엇을 했나. 한글 글꼴을 설치하고 설정하는 셀을 먼저 실행한 뒤, 드라이브를 연결해 파일을 인코딩을 지정해서 불러왔다. 행과 열의 수, 컬럼 이름, 정보 요약을 차례로 확인했다. 숫자로 저장되어 있지만 의미가 범주인 컬럼은 복사본을 만들어 범주형으로 바꾸었다. 성별, 연령대, 청력, 흡연, 음주 컬럼이 여기에 해당한다. 💭 어제 배운 "숫자라고 다 숫자가 아니다"를 바로 써 볼 수 있어서 뿌듯했다. 다만 지역 코드는 범주형 목록에서 빠뜨렸다는 것을 나중에 알았다. 2-2. 결측치를 확인하고 채웠다 🛠️ 무엇을 했나. 컬럼별 결측 개수를 센 뒤, 불리언의 평균이 비율이 된다는 힌트를 이용해 비율도 퍼센트로 확인했다. 결측이 하나라도 있는 행의 수도 따로 세었다. 채울 때는 숫자형 컬럼을 하나씩 돌며 중앙값으로, 범주형 컬럼을 하나씩 돌며 최빈값의 첫 번째 값으로 채웠다. 💭 비율을 보니 결측이 가장 많은 컬럼도 전체에서 아주 작은 부분이라는 것을 알았고 마음이 놓였다. 한편 같은 셀을 복사해서 고쳐 가며 실행하다 보니 비슷한 코드가 여러 셀에 남아서 어느 셀이 최종인지 헷갈렸다. 2-3. 이상치를 확인하고 지웠다 🛠️ 무엇을 했나. 허리둘레, 수축기혈압, 식전혈당의 히스토그램을 한 줄에 세 개로 그리고 기술통계를 확인했다. 이후 키, 체중, 허리둘레, 시력, 혈압, 혈당 컬럼마다 상식적으로 불가능한 범위를 직접 정해서 그 밖의 값이 있는지 먼저 확인했다. 그다음 IQR 경계를 계산해서 경계 밖의 행을 삭제했다. 여덟 개 컬럼을 하나의 반복문에 넣어 차례로 삭제했다. 같은 셀을 다시 돌렸더니 이상치 개수가 모두 영으로 나와서 삭제가 적용된 것을 확인했다. 💭 상식 범위를 컬럼마다 따로 정하는 일이 생각보다 어려웠다. 그리고 반복문 한 번으로 여덟 개 컬럼을 연달아 지우니 행이 눈에 띄게 줄어서, 너무 많이 지우는 것은 아닌지 불안했다. 2-4. 중복을 확인하고 이름표를 붙였다 🛠️ 무엇을 했나. 중복은 안내된 순서대로 여부 확인, 개수 확인, 중복 행 확인, 제거, 인덱스 정리, 크기 확인을 차례로 했고, 중복 개수는 영이었다. 지역 코드와 연령대 코드는 제공된 매핑표를 이용해 새 컬럼으로 바꿨다. 성별 매핑표는 직접 만들어서 같은 방식으로 이름표 컬럼을 붙였다. 💭 중복이 없다는 결과를 보고 허무했지만, 없다는 것을 확인하는 것도 분석의 일부라고 생각했다. 2-5. 기술통계를 보고 분포를 그렸다 🛠️ 무엇을 했나. 숫자형 컬럼만 골라 기술통계를 출력하고, 범주형 컬럼은 반복문으로 컬럼마다 빈도를 출력했다. 이어서 수축기혈압, 신장, 체중, 허리둘레의 히스토그램을 그리고, 신장은 성별로 색을 나누어 다시 그렸다. 색과 테두리를 바꿔 보기도 했다. 체중과 혈압의 성별 비교는 같은 방식으로 반복했고, 박스플롯과 바이올린 플롯도 연습 삼아 시도했다. 그래프마다 아래에 해석을 주석으로 적었다. 💭 그래프를 그리고 곧바로 해석 한두 줄을 적는 습관이 생겨서 만족스러웠다. 다만 바이올린 플롯 셀은 제목과 코드의 대상 변수가 달랐는데, 주석의 해석도 그대로 이어졌다. 2-6. 변수 사이의 관계를 확인했다 🛠️ 무엇을 했나. 그래프에 쓸 표본을 뽑는 셀까지 만들었지만 실제 그래프를 그리는 코드는 주석으로만 남아 있다. 상관계수는 네 개 변수를 골라 계산하고 히트맵으로 그렸다. 체중과 허리둘레의 관계가 가장 강하게 나타났다. 💭 상관 그래프를 반만 완성한 채 지나간 것이 아쉬웠다. 히트맵으로는 강도를 확인했지만 점들이 어떤 모양인지는 보지 못했다. 2-7. 연령대별 차이와 성별 생활습관을 비교했다 🛠️ 무엇을 했나. 연령대별로 평균을 구해 신장, 체중, 허리둘레, 혈압을 막대그래프로 그리고 각각 해석을 적었다. 성별에 따른 흡연과 음주는 교차표로 개수와 비율을 만들고 누적 막대그래프로 나타냈다. 흡연은 남성이 과거와 현재를 합쳐 훨씬 높았고, 음주도 남성이 더 높았다. 💭 교차표에서 비율로 바꾸니 인원수 차이에 가려져 있던 모습이 보였다. 다만 흡연은 원본 표로, 음주는 정리한 표로 그리는 식으로 서로 다른 데이터를 섞어 썼다는 점이 걸린다. 2-8. BMI를 만들고 집단을 비교했다 🛠️ 무엇을 했나. 체중을 신장 제곱으로 나누어 BMI 열을 만들고, 구간을 나누는 함수로 저체중, 정상 체중, 과체중, 비만 열을 만들었다. 그 뒤 BMI 분포, 성별 비교, 연령대별 평균과 비만도 비율, BMI와 다른 지표의 산점도와 상관 히트맵, 비만도별 흡연과 음주 비교까지 이어서 그렸다. 연령대별 해석에는 BMI가 나이가 들수록 올라간다고 적었고, 흡연은 비만일수록 비흡연이 늘어난다고 적었다. 💭 그래프가 계속 그려지니 잘 되고 있다고 믿었다. 해석 문장도 그럴듯하게 이어져서 의심하지 않았다. 2-9. 마지막 심화 과제는 비어 있다 🛠️ 무엇을 했나. 자유롭게 탐색하는 마지막 심화 과제에는 손을 대지 못했다. 💭 시간이 다 되어서 비워 둔 것이 아쉬웠다. 해설을 듣고 나서 처음부터 다시 해 보고 싶다. 3️⃣ 강의에서 배우고 써먹은 점 강의에서 배운 것 미션에서 써먹은 곳 의미가 범주인 숫자는 범주형으로 바꾼다 성별, 연령대, 청력, 흡연, 음주 컬럼 변환 수치형은 중앙값, 범주형은 최빈값으로 채운다 결측치 반복문 처리 이상치는 상식 확인 뒤에 IQR로 본다 컬럼별 비현실 범위 확인과 경계 계산 기술통계표를 질문하며 읽는다 평균과 중앙값, 최솟값과 최댓값 점검 산점도와 히트맵의 역할 구분 상관 히트맵으로 관계의 강도 확인 코드값을 이름표로 바꾸어 읽는다 지역명, 연령대, 성별 컬럼 생성 연령대별 여러 지표를 함께 읽는다 연령대별 신장, 체중, 허리둘레, 혈압 비교 바이올린 플롯으로 분포 모양을 본다 허리둘레의 성별, 연령대별 분포 💡 어제 강의에서 하나씩 배운 것이 미션에서는 한 줄로 이어졌다. 같은 흐름을 한 번 더 직접 돌려 보면서 순서가 몸에 붙는 느낌이 들었다. 4️⃣ 해설 강의에서 배운 것: 질문과 답, 그리고 느낀 점 해설은 정답을 알려 주기보다 왜 그 순서로 하는지를 짚었다. 내가 막힌 곳에 맞춰 정리한다. 4-1. 특수 코드는 결측 처리 전에 분리한다 ❓ 의문. 시력 컬럼은 다른 수치 컬럼처럼 중앙값으로 채우면 되는지 헷갈렸다. 📖 배움. 미션의 데이터 설명에는 시력의 실명을 9.9로 표기한다고 적혀 있었다. 즉 시력에는 측정값이 아닌 특수 코드가 섞여 있었다. 해설은 결측을 채우기 전에 그 코드를 먼저 별도의 표시 열로 남기고, 원래 열에서는 결측으로 바꾼 뒤에 수치형 결측과 함께 처리했다. 순서가 바뀌면 특수 코드가 평균과 중앙값에 섞여 통계가 왜곡된다. import numpy as np import pandas as pd check = pd.DataFrame({'점수': [8.5, 7.0, 99, 6.5]}) # 99는 미응시 표시라고 가정 check['미응시'] = np.where(check['점수'] == 99, 1, 0) # 1단계 표시 열 남기기 check['점수'] = check['점수'].replace(99, np.nan) # 2단계 결측으로 바꾸기 check['점수'] = check['점수'].fillna(check['점수'].median()) # 3단계 중앙값으로 채우기 print(check) 💭 데이터 설명에 분명히 적혀 있었는데도 나는 특수 코드를 따로 확인하지 않고 이상치 삭제 반복문에 그대로 맡겼다. 지워야 할 값이 아니라 의미가 있는 값이었다는 점에서 뼈아팠다. 어제 강의에서 정확히 이 부분을 경고했는데도 놓쳤다. 4-2. 이상치는 탐지 기준일 뿐이다 ❓ 의문. IQR 경계 밖의 값은 모두 지워도 되는지 궁금했다. 📖 배움. IQR은 이상치 후보를 찾는 기준이지 무조건 지우라는 뜻이 아니다. 해설은 가장 기본적인 연습에서는 간단하게 삭제했고, 실무에서는 이상치 집단을 따로 뽑아 특성을 비교하는 확장이 필요하다고 짚었다. 또 해설은 허리둘레, 수축기혈압, 식전혈당 세 컬럼만 대상으로 삼았고, 한 컬럼씩 삭제 뒤의 크기를 확인했다. 💭 나는 여덟 개 컬럼에 같은 기준을 한꺼번에 적용했다. 대상 컬럼을 고르는 것부터가 판단의 일부였다는 것을 알았다. 4-3. 순서가 있는 범주는 순서를 알려 준다 📖 배움. 연령대는 문자열로만 두면 정렬과 그래프에서 순서가 꼬일 수 있다. 순서가 있는 범주라고 지정하면 집계와 그래프가 자연스러운 순서로 나온다. ages = pd.Series(['30대', '10대', '20대']) order = ['10대', '20대', '30대'] ages = pd.Categorical(ages, categories=order, ordered=True) # 순서를 알려 준다 print(pd.Series(ages).sort_values()) 💭 내 데이터에서는 우연히 순서가 맞게 나왔지만, 문자열 정렬에 기대고 있었다는 점이 찜찜했다. 4-4. 집단 차이는 박스플롯이 설명하기 좋다 📖 배움. 성별 차이처럼 두 집단의 차이를 말할 때는 겹쳐 그린 히스토그램보다 박스플롯이 중앙값과 사분위 범위를 한눈에 비교하게 해 주어 설명력이 좋다. 히스토그램은 겹침이 심하면 읽기 어렵다. 💭 나는 히스토그램에 색만 나누어 그렸는데, 비교가 목적이라면 도구도 목적에 맞게 골라야 한다는 것을 배웠다. 4-5. 비율을 볼 때는 어느 쪽 기준인지 정한다 ❓ 의문. 교차표를 비율로 바꿀 때 행 기준과 열 기준의 차이가 무엇인지 헷갈렸다. 📖 배움. 행 기준은 각 집단 안에서 구성비를 보는 것이고, 열 기준은 각 상태 안에서 집단이 차지하는 비중을 보는 것이다. 어떤 질문을 하느냐에 따라 기준이 달라진다. ct = pd.crosstab( ['남', '남', '여', '여', '여'], ['흡연', '비흡연', '비흡연', '비흡연', '흡연'], normalize='index' # 행 기준, 열 기준이 필요하면 'columns' ) print((ct * 100).round(1)) 💭 나는 행 기준으로만 썼는데, 열 기준이라는 선택지가 있다는 것을 알고 나니 질문에 맞게 고르는 것이 중요하다고 느꼈다. 4-6. 같은 데이터프레임을 끝까지 쓴다 📖 배움. 해설은 처음부터 끝까지 하나의 표를 정리해 가며 이어서 썼다. 각 단계에서 만든 컬럼도 같은 표에 쌓았다. 새 지표를 만들 때도 같은 표 안의 컬럼끼리 계산했다. 💭 나는 원본 표와 복사본을 번갈아 쓰면서 각 그래프가 어느 표의 결과인지 구분하기 어려워졌다. 이번 미션에서 가장 크게 느낀 교훈이다. 4-7. 평균만 보면 구성의 차이가 가려진다 📖 배움. 연령대별 평균 BMI는 큰 차이가 없어 보였지만, 비만도를 구간으로 나누어 비율을 보니 중년층에서 비만이 늘고 고령층에서 다시 줄어드는 구성의 차이가 드러났다. 평균과 구성비를 함께 연결해서 설명해야 한다. 💭 내가 쓴 해석은 이 설명과 정반대였다. 그래프가 그려졌다는 이유로 맞다고 믿은 것이 문제였다. 💡 평균 하나로 말하는 것과 구성을 나누어 보는 것은 같은 데이터에서도 전혀 다른 결론으로 이어질 수 있다. 4-8. 자유 EDA는 질문에서 시작한다 📖 배움. 해설의 자유 EDA는 식전혈당이 높은 집단을 따로 뽑아 나머지와 평균과 구성을 비교하는 것부터 시작했다. 지역별 평균 지표, 연령대와 성별 조합의 변화, 흡연 상태에 따른 혈압과 혈당 비교처럼 질문을 먼저 정한 뒤 그에 맞는 그래프를 고르는 식이었다. 💭 나는 마지막 과제를 시작도 못 했는데, 해설을 보니 이상한 값을 별도의 집단으로 보는 발상은 어제 배운 이상치 처방과 이어져 있었다. 다시 도전하면 해 볼 수 있을 것 같다. 5️⃣ 해설과 내 코드의 다른 점 내 노트북과 해설을 문제별로 나란히 놓고 비교했다. 항목 내 코드 해설 컬럼 역할 정리 의미가 범주인 컬럼 일부를 범주형으로 바꿨다. 지역 코드는 빠졌다. 역할별로 컬럼 목록을 먼저 만들고 시작한다. 특수 코드 데이터 설명에 적혀 있었지만 따로 확인하지 못했다. 표시 열을 남기고 결측으로 바꾼 뒤 처리한다. 결측 처리 숫자형과 범주형을 반복문으로 채웠다. 같은 셀을 여러 번 복사했다. 역할별로 목록을 나누어 한 번에 채운다. 이상치 대상 컬럼 여덟 개를 한 반복문에서 삭제했다. 세 컬럼만 대상으로 삼고 단계마다 크기를 확인한다. 이상치 판단 상식 범위를 직접 정해 확인한 뒤 삭제했다. 탐지 기준일 뿐이고 실무에서는 집단 분석으로 확장한다고 짚는다. 중복값 안내된 순서대로 처리했다. 같은 순서로 처리한다. 이름표 컬럼 지역과 연령대, 성별을 새 컬럼으로 만들었다. 같은 방식이고 흡연, 음주, 청력까지 모두 만든다. 연령대 순서 순서를 지정하지 않았다. 순서형 범주로 지정한다. 기술통계 숫자형과 범주형을 나누어 확인했다. 같은 흐름이고, 교차표까지 이어서 본다. 분포 비교 색을 나눈 히스토그램을 주로 썼다. 박스플롯을 비교의 중심에 둔다. 변수 관계 표본 추출까지만 하고 페어플롯은 그리지 못했다. 표본을 뽑아 페어플롯을 그리고 해석한다. 상관 히트맵 코드와 식별자 컬럼까지 포함해서 큰 히트맵을 그렸다. 의미 있는 컬럼만 골라서 그린다. 사용한 표 원본 표와 정리한 표를 섞어 썼다. 하나의 표를 끝까지 이어서 쓴다. BMI 계산 체중은 원본 표에서, 신장은 정리한 표에서 가져와 계산했다. 같은 표의 두 컬럼으로 계산한다. 비만도 구간 미션 안내의 기준 대신 정상 체중의 상한을 더 낮게 잡은 구간을 썼다. 나와 같은 구간을 쓰고, 경계를 포함하는지를 주석으로 설명한다. 연령대별 BMI 해석 나이가 들수록 올라간다고 해석했다. 평균은 큰 변화가 없고 구성비에서 차이가 난다고 해석한다. 비만도별 흡연 비만일수록 비흡연이 는다고 해석했다. 비만일수록 흡연 경험이 많다고 해석한다. 자유 EDA 시작하지 못했다. 질문을 정해 집단 비교를 한다. 💡 비교해 보니 앞부분은 해설과 거의 같은 흐름이었지만, 달랐던 곳은 대부분 "결과는 나오지만 맞는지 확인하지 않은" 부분이었다. 특히 BMI를 서로 다른 표에서 가져와 계산한 것이 뒤의 해석을 모두 어긋나게 만들었다. 그래프가 그려진다는 것과 분석이 맞다는 것은 다르다는 것을, 5일차와 11일차에 이어 다시 확인했다. 📝 오늘의 정리 주제 내가 이해한 핵심 더 공부할 점 컬럼 정리 의미로 역할을 나누고 특수 코드를 먼저 분리한다 코드 종류 점검 습관 이상치 탐지 기준일 뿐이고 대상과 처방을 판단한다 이상치 집단 따로 분석 표 관리 하나의 표를 끝까지 이어서 쓴다 셀 정리와 최종본 구분 계산 검증 새 지표를 만들면 값이 맞는지 앞부분을 확인한다 결과가 이상하면 계산부터 의심 시각화 비교의 목적에 맞는 그래프를 고른다 박스플롯과 구성비 그래프 해석 평균과 구성을 함께 보고 결론을 쓴다 자유 EDA 처음부터 다시 📌 다음 일차 다음 일차: 14일차 (2026-05-22) 커리큘럼: Tableau 다음 강의자료 예고: 코드가 아닌 도구로 대시보드를 만드는 시각화를 시작한다.
velog
1. 텍스트 임베딩이란? 딥러닝 모델은 문장이나 단어 자체를 그대로 이해하지 못한다. 모델은 숫자 연산을 수행하므로, 자연어 텍스트를 숫자 형태로 변환해야 한다. 예를 들어 다음과 같은 단어가 있다고 하자. 사과, 바나나, 오렌지 컴퓨터는 사과 가 과일이고, 바나나 와 의미적으로 유사하다는 사실을 문자열 자체만으로 알 수 없다. 따라서 각 단어를 숫자로 표현하고, 모델이 학습을 통해 단어 간 관계를 파악할 수 있도록 만들어야 한다. 이처럼 단어·문장·문서를 연속적인 숫자 벡터로 표현하는 방식을 임베딩(Embedding) 이라고 한다. 텍스트 임베딩은 자연어 처리에서 필수적인 표현 방법이며, 대표적으로 다음 내용을 다룬다. 원-핫 인코딩(One-hot Encoding) 워드 임베딩(Word Embedding) 문서 벡터화(Document Vectorization) wikidocs 2. 텍스트를 숫자로 바꾸는 이유 자연어는 비정형 데이터다. 사람이 읽을 수 있는 문자열 형태이지만, 신경망은 벡터와 행렬 같은 수치 데이터를 입력으로 받아야 한다. 예를 들어 다음 문장을 감성 분석 모델에 입력한다고 가정해보자. 이 영화는 정말 재미있다. 모델은 문장을 바로 처리하는 것이 아니라, 보통 아래와 같은 흐름을 거친다. 텍스트 → 토큰화 → 정수 인코딩 또는 원-핫 인코딩 → 임베딩 벡터 → 신경망 모델 입력 → 예측 결과 토큰화는 문장을 단어·형태소·서브워드 등의 단위로 나누는 작업이고, 임베딩은 각 토큰을 숫자 벡터로 변환하는 작업이다. 예를 들어 재미있다 라는 단어는 아래처럼 임베딩 벡터로 표현될 수 있다. [0.15, -0.72, 0.33, 0.91, ...] 실제 임베딩은 수십 차원부터 수백 또는 수천 차원의 벡터로 구성될 수 있다. 각 숫자 하나를 사람이 직접 해석하기는 어렵지만, 벡터 전체의 위치와 방향을 통해 단어 사이의 의미적 관계를 계산할 수 있다. wikidocs 3. 원-핫 인코딩 원-핫 인코딩(One-hot Encoding) 은 단어를 표현하는 가장 기초적인 방법이다. 먼저 전체 단어 집합인 단어 사전(Vocabulary) 을 만든다. 인덱스 단어 0 나는 1 학생 2 입니다 3 딥러닝 4 공부한다 각 단어는 사전에서 자신에게 해당하는 인덱스 위치만 1이고, 나머지는 모두 0인 벡터로 표현된다. 나는 → [1, 0, 0, 0, 0] 학생 → [0, 1, 0, 0, 0] 입니다 → [0, 0, 1, 0, 0] 딥러닝 → [0, 0, 0, 1, 0] 공부한다 → [0, 0, 0, 0, 1] 벡터 안에서 1이 단 하나만 존재하기 때문에 원-핫 벡터라고 부른다. from tensorflow.keras.preprocessing.text import Tokenizer sentences = [ "나는 딥러닝을 공부한다", "나는 자연어 처리를 공부한다" ] tokenizer = Tokenizer() tokenizer.fit_on_texts(sentences) print(tokenizer.word_index) 출력 예시는 다음과 같다. { '나는': 1, '공부한다': 2, '딥러닝을': 3, '자연어': 4, '처리를': 5 } 원-핫 인코딩은 단어를 명확하게 구분할 수 있지만, 실제 자연어 처리에서는 여러 한계가 있다. 4. 원-핫 인코딩의 한계 희소 벡터 문제 단어 사전의 크기가 커질수록 원-핫 벡터의 차원도 커진다. 예를 들어 단어가 100,000개라면 하나의 단어를 표현하기 위해 길이가 100,000인 벡터가 필요하다. 하지만 실제 값은 대부분 0이고 단 하나의 위치만 1이다. [0, 0, 0, 0, ..., 0, 1, 0, ..., 0] 이처럼 대부분이 0으로 채워진 벡터를 희소 벡터(Sparse Vector) 라고 한다. 희소 벡터는 메모리와 연산 측면에서 비효율적이다. 단어 수가 늘수록 벡터 차원이 계속 증가하므로 대규모 텍스트 데이터에 그대로 적용하기 어렵다. 단어 간 의미 관계를 표현하지 못함 원-핫 벡터에서는 서로 다른 단어의 유사도를 표현하기 어렵다. 예를 들어 다음 단어들을 비교해보자. 고양이, 강아지, 자동차 사람은 고양이 와 강아지 가 동물이라는 점에서 의미적으로 더 가깝고, 자동차 와는 거리가 멀다고 판단한다. 그러나 원-핫 벡터 관점에서는 모든 단어가 서로 독립된 차원에 위치한다. 따라서 고양이 와 강아지 의 거리도, 고양이 와 자동차 의 거리도 동일하게 계산된다. 즉, 원-핫 인코딩은 단어의 동일성 은 구분하지만 단어의 의미적 유사성 은 담지 못한다. 5. 워드 임베딩 워드 임베딩(Word Embedding) 은 단어를 밀집된 실수 벡터(Dense Vector)로 표현하는 방법이다. 원-핫 인코딩과 달리 워드 임베딩은 단어 사전 크기보다 훨씬 작은 차원의 벡터를 사용한다. 사과 → [ 0.12, -0.08, 0.45, 0.31] 바나나 → [ 0.10, -0.05, 0.41, 0.29] 자동차 → [-0.72, 0.61, -0.33, 0.18] 위 숫자는 설명을 위한 예시다. 실제 값은 학습 과정에서 자동으로 결정된다. 사과 와 바나나 의 벡터 값이 비슷하고, 자동차 는 다른 방향에 위치한다면 모델은 과일 관련 단어들이 의미적으로 가깝다는 패턴을 학습할 수 있다. 워드 임베딩의 장점은 다음과 같다. 단어 사전 크기보다 훨씬 작은 차원으로 표현할 수 있다. 희소 벡터 문제를 완화할 수 있다. 학습 데이터의 문맥을 바탕으로 단어 간 유사성을 반영할 수 있다. 코사인 유사도와 같은 방법으로 단어 벡터 간 유사도를 계산할 수 있다. 딥러닝 모델의 입력으로 효율적으로 활용할 수 있다. wikidocs 6. 임베딩 레이어 딥러닝에서는 Embedding 레이어를 통해 단어 인덱스를 임베딩 벡터로 변환한다. 입력으로 단어 자체가 아니라 정수 인코딩된 단어 인덱스를 받는다는 점이 중요하다. import torch import torch.nn as nn vocab_size = 10000 embedding_dim = 128 embedding = nn.Embedding( num_embeddings=vocab_size, embedding_dim=embedding_dim ) input_ids = torch.tensor([ [1, 25, 103, 8], [7, 15, 0, 0] ]) embedded = embedding(input_ids) print(embedded.shape) 출력 텐서의 크기는 다음과 같다. torch.Size([2, 4, 128]) 차원 의미 2 배치 크기 4 시퀀스 길이 128 임베딩 차원 각 단어 인덱스는 길이 128의 밀집 벡터로 변환된다. 이 임베딩 벡터의 값은 학습 과정에서 손실 함수를 최소화하는 방향으로 업데이트된다. 7. Word2Vec Word2Vec은 주변 단어의 문맥을 활용해 단어 임베딩을 학습하는 대표적인 방법이다. 기본 아이디어는 특정 단어가 등장했을 때 주변에 어떤 단어들이 함께 등장하는지를 학습하면, 의미적으로 비슷한 단어가 유사한 벡터 공간에 배치될 수 있다는 것이다. 대표적인 구조는 다음 두 가지다. 모델 입력 예측 대상 CBOW(Continuous Bag of Words) 주변 단어 중심 단어 Skip-gram 중심 단어 주변 단어 예를 들어 아래 문장을 보자. 나는 딥러닝을 공부한다. 딥러닝을 이 중심 단어라면, CBOW는 주변 단어인 나는 , 공부한다 를 활용해 딥러닝을 을 예측한다. 반대로 Skip-gram은 딥러닝을 을 입력받아 주변 단어를 예측한다. Word2Vec은 비슷한 문맥에서 사용되는 단어를 가까운 벡터로 학습한다. 그래서 단어 간 유사도 검색, 추천, 문서 분류, 검색 시스템의 특징 추출 등에 활용할 수 있다. wikidocs 8. 문서 벡터화 단어뿐 아니라 문장과 문서도 벡터로 표현할 수 있다. 이를 문서 벡터화(Document Vectorization) 라고 한다. 문서 벡터화 방식은 크게 전통적인 통계 기반 방법과 신경망 기반 임베딩 방법으로 구분할 수 있다. 방법 핵심 아이디어 특징 BoW 단어의 등장 여부 또는 빈도 사용 단순하지만 단어 순서를 반영하지 못함 DTM 문서별 단어 빈도를 행렬로 표현 문서 분류·분석의 기본 표현 TF-IDF 문서 내 중요 단어에 가중치 부여 검색·키워드 분석에 자주 활용 평균 임베딩 문서 내 단어 임베딩의 평균 사용 간단하지만 어순과 문맥 손실 가능 Doc2Vec 문서 자체의 벡터를 함께 학습 문서 유사도·분류에 활용 가능 Transformer 기반 임베딩 문맥을 고려한 문장·문서 벡터 생성 의미 검색, RAG, 문서 군집화 등에 활용 예를 들어 금융 뉴스나 공시 문서를 분석한다면, 단순 키워드 빈도만 사용할 수도 있지만 문장의 맥락이 중요하다면 Transformer 기반 문장 임베딩을 활용하는 편이 유리할 수 있다. 9. 임베딩 활용 예시 임베딩은 자연어 처리의 다양한 작업에 활용된다. 감성 분석: 리뷰·뉴스·커뮤니티 글을 긍정, 부정, 중립 등으로 분류 문서 분류: 뉴스 기사, 고객 문의, 금융 공시, 계약서 등을 주제별로 분류 의미 기반 검색: 키워드가 완전히 일치하지 않아도 유사한 의미의 문서 검색 추천 시스템: 사용자 행동, 상품 설명, 콘텐츠 텍스트를 벡터화해 유사 항목 추천 챗봇과 RAG: 질문과 문서를 임베딩하여 관련 문서를 검색한 뒤 LLM에 제공 이상 탐지: 정상 문서와 의미적으로 크게 다른 텍스트를 탐지 예를 들어 “기준금리 인상 가능성”이라는 질문을 검색할 때, 임베딩 기반 검색은 문장에 같은 단어가 없더라도 “통화 긴축”, “금리 상향”, “중앙은행의 매파적 기조”와 관련된 문서를 유사하게 찾는 데 활용될 수 있다. 10. 정리 텍스트 임베딩은 단어·문장·문서를 딥러닝 모델이 처리할 수 있는 숫자 벡터로 변환하는 방법이다. 원-핫 인코딩은 단어를 구분하기 쉽지만, 차원이 커지고 희소하며 단어 간 의미 관계를 표현하지 못한다. 워드 임베딩은 단어를 저차원의 밀집 벡터로 표현해 메모리 효율성과 의미적 유사성 표현을 개선한다. 임베딩 레이어는 정수 인코딩된 토큰을 학습 가능한 벡터로 변환한다. Word2Vec의 CBOW와 Skip-gram은 주변 문맥을 이용해 단어 임베딩을 학습한다. 문서도 BoW, TF-IDF, Doc2Vec, Transformer 기반 방식 등으로 벡터화할 수 있다. 임베딩은 문서 분류, 유사도 검색, 추천 시스템, 챗봇, RAG 등 현대 NLP 시스템의 핵심 구성 요소다.
velog
1. 프로젝트 개요 이번 실습에서는 네이버 영화 리뷰 데이터(NSMC, Naver Sentiment Movie Corpus)를 활용해 리뷰가 긍정인지 부정인지 분류하는 감성 분석 모델을 구현했다. 감성 분석(Sentiment Analysis)은 문장이나 문서에 담긴 감정·평가를 분류하는 자연어 처리 작업이다. 영화 리뷰에서는 일반적으로 다음과 같이 이진 분류 문제로 설정한다. 레이블 의미 0 부정 리뷰 1 긍정 리뷰 데이터는 리뷰 내용이 담긴 document , 감성 레이블인 label , 데이터 식별자인 id 로 구성된다. 모델 학습에는 document 와 label 을 사용하고, id 는 분류에 직접적인 의미가 없으므로 제외한다. wikidocs 2. 데이터 구성 NSMC 데이터는 총 200,000개의 네이버 영화 리뷰로 구성된다. 데이터 구분 리뷰 수 훈련 데이터 150,000개 테스트 데이터 50,000개 전체 데이터 200,000개 훈련 데이터의 예시는 다음과 같은 구조를 가진다. id document label 9976970 아 더빙.. 진짜 짜증나네요 목소리 0 3819312 흠... 포스터보고 초딩영화줄... 1 10265843 너무재밓었다그래서보는것을추천한다 1 한국어 리뷰 데이터에는 띄어쓰기 오류, 이모티콘, 반복 문자, 숫자, 영어, 특수문자, 신조어 등이 포함될 수 있다. 따라서 모델 학습 전 데이터 정제와 형태소 기반 토큰화가 중요하다. wikidocs 3. 데이터 전처리 중복 데이터 제거 먼저 같은 리뷰 문장이 여러 번 존재할 수 있으므로 document 열을 기준으로 중복 데이터를 제거한다. train_data.drop_duplicates(subset=['document'], inplace=True) 원본 훈련 데이터는 150,000개였지만, 중복 제거 후에는 146,183개가 남는다. document 의 고유값 수는 146,182개이며, 여기에는 결측값 1개가 포함되어 있다. wikidocs 결측값 제거 리뷰 내용이 없는 행은 감성 분석에 사용할 수 없으므로 제거한다. train_data = train_data.dropna(how='any') 결측값을 제거한 뒤 텍스트 전처리를 수행한다. 한글 이외 문자 처리 실습에서는 한글 자음·모음·완성형 한글과 공백을 제외한 문자를 제거한다. train_data['document'] = train_data['document'].str.replace( "[^ᄀ-하-ᅵ가-힣 ]", "", regex=True ) 정규표현식의 의미는 다음과 같다. 표현 의미 ᄀ-ᄒ 한글 자음 ᅡ-ᅵ 한글 모음 가-힣 완성형 한글 음절 공백 띄어쓰기 유지 [^ ... ] 대괄호 안에 포함되지 않는 문자 특수문자 제거 이후에는 영어, 숫자, 이모티콘만으로 작성된 리뷰가 빈 문자열이 될 수 있다. 이런 데이터는 NaN 으로 바꾼 뒤 다시 제거한다. train_data['document'] = train_data['document'].str.replace( '^ +', '', regex=True ) train_data['document'].replace('', np.nan, inplace=True) train_data = train_data.dropna(how='any') 전처리 후 훈련 데이터는 145,393개, 테스트 데이터는 48,852개가 남는다. wikidocs 4. 형태소 토큰화와 불용어 제거 한국어는 조사와 어미가 단어에 결합하는 교착어이므로, 단순히 공백 기준으로 나누는 방식보다 형태소 분석 기반 토큰화가 유용하다. 실습에서는 KoNLPy의 Mecab 형태소 분석기를 사용한다. from konlpy.tag import Mecab mecab = Mecab() sentence = "와 이런 것도 영화라고 차라리 뮤직비디오를 만드는 게 나을 뻔" print(mecab.morphs(sentence)) [ '와', '이런', '것', '도', '영화', '라고', '차라리', '뮤직', '비디오', '를', '만드', '는', '게', '나을', '뻔' ] 이후 감성 분류에 상대적으로 도움이 적을 수 있는 조사, 어미, 접속 표현 등을 불용어로 정의해 제거한다. stopwords = [ '도', '는', '다', '의', '가', '이', '은', '한', '에', '하', '고', '을', '를', '인', '듯', '과', '와', '네', '들', '지', '임', '게' ] X_train = [] for sentence in train_data['document']: tokens = mecab.morphs(sentence) tokens = [word for word in tokens if word not in stopwords] X_train.append(tokens) 예를 들어 다음 리뷰는 형태소 토큰화와 불용어 제거 후 여러 토큰으로 분리된다. 원문: 아 더빙.. 진짜 짜증나네요 목소리 토큰화 결과: ['아', '더', '빙', '진짜', '짜증', '나', '네요', '목소리'] 다만 불용어는 고정된 정답 목록이 아니다. 분석 목적, 도메인, 모델 구조에 따라 중요한 단어가 달라질 수 있으므로 실제 프로젝트에서는 데이터 탐색과 성능 평가를 바탕으로 조정해야 한다. wikidocs 5. 학습·검증·테스트 데이터 분리 모델은 훈련 데이터로 학습하고, 검증 데이터로 하이퍼파라미터와 과적합 여부를 점검하며, 마지막으로 테스트 데이터에서 최종 성능을 측정한다. 훈련 데이터의 20%를 검증 데이터로 분리한다. from sklearn.model_selection import train_test_split X_train, X_valid, y_train, y_valid = train_test_split( X_train, y_train, test_size=0.2, random_state=0, stratify=y_train ) 여기서 stratify=y_train 은 분리 이후에도 긍정·부정 레이블의 비율이 최대한 유지되도록 한다. 데이터 부정 비율 긍정 비율 학습 데이터 약 50.238% 약 49.762% 검증 데이터 약 50.239% 약 49.761% 테스트 데이터 약 49.808% 약 50.192% 레이블이 거의 균형을 이루므로 정확도(Accuracy)를 기본 평가 지표로 사용할 수 있다. 다만 실제 서비스에서 한쪽 감성이 압도적으로 많다면 정확도만으로 성능을 판단하지 말고 Precision, Recall, F1-score, ROC-AUC 등을 함께 확인해야 한다. wikidocs 6. 단어 집합과 정수 인코딩 딥러닝 모델은 문자열이 아니라 숫자를 입력으로 받는다. 따라서 토큰화된 단어에 고유한 정수 인덱스를 부여하는 정수 인코딩(Integer Encoding) 을 수행한다. 먼저 학습 데이터에서 단어의 등장 빈도를 계산한다. from collections import Counter word_list = [] for sentence in X_train: for word in sentence: word_list.append(word) word_counts = Counter(word_list) 학습 데이터에는 총 45,296개의 서로 다른 단어가 등장한다. 이 중 등장 횟수가 2회 이하인 희귀 단어는 26,105개로, 전체 단어 종류의 약 57.63%를 차지한다. 하지만 전체 등장 빈도에서 차지하는 비중은 약 2.28%에 불과하다. wikidocs 따라서 등장 빈도 3회 미만의 희귀 단어를 단어 사전에서 제외한다. threshold = 3 vocab_size = total_cnt - rare_cnt vocab = vocab[:vocab_size] 희귀 단어 제거 후 실제 단어 사전 크기는 19,191개가 된다. 특수 토큰 시퀀스 길이를 맞추기 위한 패딩과 단어 사전에 없는 단어 처리를 위해 특수 토큰을 추가한다. word_to_index = {} word_to_index['<PAD>'] = 0 word_to_index['<UNK>'] = 1 토큰 인덱스 역할 <PAD> 0 길이가 짧은 문장을 채우는 패딩 토큰 <UNK> 1 단어 사전에 없는 미지의 단어 최종 단어 사전 크기는 19,193개가 된다. for index, word in enumerate(vocab): word_to_index[word] = index + 2 토큰을 정수 시퀀스로 바꾸는 함수는 다음과 같다. def texts_to_sequences(tokenized_X_data, word_to_index): encoded_X_data = [] for sent in tokenized_X_data: index_sequences = [] for word in sent: index_sequences.append( word_to_index.get(word, word_to_index['<UNK>']) ) encoded_X_data.append(index_sequences) return encoded_X_data 예를 들어 토큰 시퀀스가 다음과 같다고 가정해보자. ['이야', '어쩜', '이렇게', '나', '지루', '할', '수'] 정수 인코딩 후에는 다음처럼 변환된다. [924, 1866, 128, 7, 80, 48, 34] 7. 패딩 리뷰마다 길이가 다르기 때문에, 배치 단위 연산을 하려면 모든 입력 시퀀스의 길이를 동일하게 맞춰야 한다. 이 과정을 패딩(Padding) 이라고 한다. 훈련 데이터에서 리뷰의 최대 길이는 74, 평균 길이는 약 12.3이다. 실습에서는 max_len = 30 으로 설정하며, 이 길이는 훈련 샘플의 약 92.5%를 포함한다. wikidocs max_len = 30 def pad_sequences(sentences, max_len): features = np.zeros((len(sentences), max_len), dtype=int) for index, sentence in enumerate(sentences): features[index, :len(sentence)] = np.array(sentence)[:max_len] return features 패딩 후 첫 번째 리뷰는 다음과 같은 형태가 된다. [ 924, 1866, 128, 7, 80, 48, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] 문장 뒤쪽에 붙은 0 은 <PAD> 토큰이다. 데이터 패딩 후 크기 훈련 데이터 (116,314, 30) 검증 데이터 (29,079, 30) 테스트 데이터 (48,852, 30) 각 샘플은 길이 30의 정수 시퀀스로 변환된다. wikidocs 8. LSTM 감성 분류 모델 모델은 다음 순서로 구성된다. 정수 인코딩된 리뷰 → Embedding → LSTM → 마지막 은닉 상태 → Fully Connected Layer → 긍정/부정 분류 PyTorch 구현은 다음과 같다. import torch import torch.nn as nn class TextClassifier(nn.Module): def __init__(self, vocab_size, embedding_dim, hidden_dim, output_dim): super(TextClassifier, self).__init__() self.embedding = nn.Embedding(vocab_size, embedding_dim) self.lstm = nn.LSTM( input_size=embedding_dim, hidden_size=hidden_dim, batch_first=True ) self.fc = nn.Linear(hidden_dim, output_dim) def forward(self, x): # x: (batch_size, sequence_length) embedded = self.embedding(x) # embedded: (batch_size, sequence_length, embedding_dim) lstm_out, (hidden, cell) = self.lstm(embedded) # hidden: (num_layers, batch_size, hidden_dim) last_hidden = hidden.squeeze(0) # logits: (batch_size, output_dim) logits = self.fc(last_hidden) return logits 입력과 출력의 텐서 형태는 다음과 같이 변한다. 단계 텐서 형태 설명 입력 (batch_size, sequence_length) 정수 인코딩된 단어 인덱스 임베딩 (batch_size, sequence_length, embedding_dim) 각 단어가 벡터로 변환됨 LSTM 마지막 은닉 상태 (batch_size, hidden_dim) 문장 전체를 요약한 벡터 출력층 (batch_size, 2) 부정·긍정 클래스별 점수 예를 들어 배치 크기 32, 문장 길이 30, 임베딩 차원 100, 은닉 상태 차원 128이라면 텐서 흐름은 다음과 같다. (32, 30) → (32, 30, 100) → (32, 128) → (32, 2) 9. 학습 설정 실습에서 사용한 주요 하이퍼파라미터는 다음과 같다. 항목 값 단어 사전 크기 19,193 최대 문장 길이 30 임베딩 차원 100 LSTM 은닉 상태 차원 128 출력 클래스 수 2 배치 크기 32 옵티마이저 Adam 학습률 0.001 손실 함수 Cross Entropy Loss 에포크 5 embedding_dim = 100 hidden_dim = 128 output_dim = 2 model = TextClassifier( vocab_size=vocab_size, embedding_dim=embedding_dim, hidden_dim=hidden_dim, output_dim=output_dim ) criterion = nn.CrossEntropyLoss() optimizer = torch.optim.Adam( model.parameters(), lr=0.001 ) 이진 분류 문제이지만 출력 노드를 2개로 두고 nn.CrossEntropyLoss() 를 사용한다. 모델은 각 클래스의 점수(logits)를 출력하며, 가장 높은 점수를 가진 클래스를 최종 예측으로 선택한다. wikidocs 10. 학습과 검증 학습 과정에서는 다음 순서가 반복된다. 배치 데이터를 모델에 입력한다. 예측 결과인 logits를 계산한다. 실제 레이블과 예측값의 손실을 계산한다. loss.backward() 로 역전파를 수행한다. optimizer.step() 으로 모델 파라미터를 업데이트한다. 각 에포크가 끝날 때 검증 데이터 성능을 확인한다. 검증 손실이 가장 낮은 모델을 저장한다. best_val_loss = float('inf') for epoch in range(num_epochs): model.train() for batch_X, batch_y in train_dataloader: batch_X = batch_X.to(device) batch_y = batch_y.to(device) logits = model(batch_X) loss = criterion(logits, batch_y) optimizer.zero_grad() loss.backward() optimizer.step() val_loss, val_accuracy = evaluate( model, valid_dataloader, criterion, device ) if val_loss < best_val_loss: best_val_loss = val_loss torch.save(model.state_dict(), 'best_model_checkpoint.pth') 모델 평가 시에는 model.eval() 과 torch.no_grad() 를 사용한다. model.eval() with torch.no_grad(): logits = model(batch_X) 코드 역할 model.train() 학습 모드 활성화 model.eval() 평가 모드 활성화 torch.no_grad() 평가 중 기울기 계산 비활성화 optimizer.zero_grad() 이전 배치의 기울기 초기화 loss.backward() 손실 기준 역전파 수행 optimizer.step() 모델 파라미터 업데이트 특히 model.eval() 은 드롭아웃, 배치 정규화처럼 학습·평가 동작이 다른 레이어가 포함된 모델에서 중요하다. torch.no_grad() 는 평가 시 기울기 계산을 생략해 메모리 사용량과 연산 시간을 줄여준다. wikidocs 11. 모델 성능 검증 손실이 가장 낮았던 모델을 불러와 최종 성능을 평가한다. 평가 데이터 Loss Accuracy 검증 데이터 0.3392 84.90% 테스트 데이터 0.3435 84.92% 훈련에 참여하지 않은 테스트 데이터에서도 약 84.92%의 정확도를 보였다. 검증 성능과 테스트 성능 차이가 크지 않기 때문에, 예시 결과에서는 극단적인 과적합 징후가 크지 않다고 해석할 수 있다. wikidocs 12. 새로운 리뷰 예측 학습한 모델은 새로운 영화 리뷰에 대해 긍정 또는 부정을 예측할 수 있다. index_to_tag = { 0: '부정', 1: '긍정' } def predict(text, model, word_to_index, index_to_tag): model.eval() tokens = mecab.morphs(text) tokens = [word for word in tokens if word not in stopwords] token_indices = [ word_to_index.get(token, word_to_index['<UNK>']) for token in tokens ] input_tensor = torch.tensor( [token_indices], dtype=torch.long ).to(device) with torch.no_grad(): logits = model(input_tensor) predicted_index = torch.argmax(logits, dim=1) return index_to_tag[predicted_index.item()] 예측 결과는 다음과 같다. 입력 리뷰 예측 결과 이 영화 개꿀잼 ᄏᄏᄏ 긍정 이딴게 영화냐 ᄍᄍ 부정 감독 뭐하는 놈이냐? 부정 와 개쩐다 정말 세계관 최강자들의 영화다 긍정 모델은 토큰화, 불용어 제거, 정수 인코딩, 임베딩, LSTM 연산을 거쳐 입력 문장에 포함된 감성 정보를 바탕으로 최종 클래스를 예측한다. 다만 신조어, 비꼬는 표현, 복합 감정, 문맥 의존적 표현에서는 오분류가 발생할 수 있다. wikidocs 13. 정리 NSMC는 긍정·부정 레이블을 가진 총 20만 건의 네이버 영화 리뷰 데이터셋이다. 중복값과 결측값을 제거하고, 정규표현식으로 텍스트를 정제했다. 한국어는 교착어이므로 Mecab 형태소 분석기를 이용해 토큰화했다. 희귀 단어를 제거하고 <PAD> , <UNK> 특수 토큰을 추가해 단어 사전을 구성했다. 토큰을 정수로 인코딩하고, 모든 리뷰 길이를 30으로 맞추기 위해 패딩을 적용했다. Embedding → LSTM → Fully Connected Layer 구조의 감성 분류 모델을 구현했다. 예시 모델은 검증 정확도 84.90%, 테스트 정확도 84.92%를 기록했다. 실제 성능 개선을 위해서는 양방향 LSTM, GRU, 사전학습 임베딩, Transformer 기반 언어 모델, 하이퍼파라미터 튜닝 등을 추가로 실험할 수 있다.
velog
등록해 둔 GitHub 이슈 15건을 "처리해줘" 한 줄로 던졌습니다. 워크트리 15개, tmux 워커 15개가 붙어서 각자 하나씩 맡는 구조입니다. 사람 개입은 게이트 두 번 — 태스크 분할 승인과 병합 전 최종 확인뿐이었고요. 끝나고 보니 전체 스위트 2467개가 그린이었는데, 정작 기억에 남는 건 테스트 숫자가 아니라 워커들이 이슈에 적힌 대로 구현하기를 거부한 순간들 이었습니다. 먼저 결론부터 봅시다 이슈 문면은 계약이 아니라 가설 입니다. 이슈를 쓸 때 저는 코드를 다 보고 쓰지 않았어요. "이렇게 하면 되겠지"를 적어둔 겁니다. 그런데 워커는 그 문장을 명세로 받아서 그대로 구현하려 들죠. 여기서 "문면과 실측이 어긋나면 실측을 이겨서 보고하라"는 규칙을 걸어두면, 이슈를 쓸 때 잘못 생각한 것들이 구현 단계에서 드러납니다. "그 PK, 같은 DB에서 두 번 돌리면 충돌합니다" 가장 명확했던 건 아웃박스 테이블 이슈였습니다. 이벤트를 sqlite 테이블에 쌓아두고 외부에서 드레인하는 구조인데, 저는 이슈에 이렇게 적었습니다. emission_id 를 PK로 쓰라고요. 이벤트마다 붙는 고유 ID니까 당연히 PK라고 생각했습니다. 워커가 반박해왔습니다. emission_id 는 프로세스-로컬 카운터 라서 프로세스가 새로 뜨면 다시 처음부터 매겨진다는 것. 그래서 같은 DB 파일에 두 번째 실행을 붙이면 PK 충돌이 납니다. 추론이 아니라 실제로 돌려서 확인한 결과였습니다. 이건 제가 이슈를 쓸 때 놓친 게 맞습니다. 행의 정체성을 프로세스가 아니라 저장소가 소유 해야 한다는 걸 놓친 거죠. 결론은 seq를 도입해서 PK를 저장소 쪽으로 옮기는 것이었습니다. lnpl_outbox( seq INTEGER PRIMARY KEY AUTOINCREMENT, emission_id TEXT NOT NULL, event, payload TEXT(JSON), created_at, delivered_at NULL ) emission_id 는 PK 자리에서 내려왔지만 사라지진 않았습니다. 소비자 쪽 중복 감지(dedupe)에는 여전히 필요하거든요. 그리고 삭제 대신 delivered_at 상태 마킹을 쓰는 건 그대로 뒀습니다 — at-least-once가 성립하려면 소비 기록이 남아야 하니까요. 재밌는 건 이 결정이 뒤에 붙은 태스크로 그대로 흘러갔다는 점입니다. seq가 단조 증가하는 커서라서, 그다음 SSE 구독 태스크의 Last-Event-ID 로 그대로 쓸 수 있었습니다. 이슈 문면대로 갔으면 커서가 없어서 거기서 또 막혔을 겁니다. 이슈 안에서 두 문장이 서로 싸우고 있었다 다른 태스크에서는 워커가 구현을 멈추고 질문을 올렸습니다. 이슈에 이런 두 조건이 같이 적혀 있었거든요. 새 기능에 시드 정책을 적용한다 기존 예제의 출력은 바이트 동일 해야 한다 (회귀 기준) 시드를 적용하면 값이 바뀌고, 값이 바뀌면 바이트 동일이 깨집니다. 제가 이슈를 쓰면서 두 요구를 각각 적었는데, 붙여놓으면 동시에 만족할 수 없는 조합이었던 겁니다. 코디네이터로서 판정해야 했습니다. 시드는 조건 없이 일반 적용하고, "바이트 동일"의 범위를 컴파일 표면으로 한정 해석 하기로 했습니다. 대신 조건 세 개를 붙였어요 — 의미가 바뀐다는 사실을 RFC 문면에 명시할 것, 테스트 두 건이 시드 값 자체까지 단언하도록 강화할 것. 해석으로 넘어간 부분은 문서에 남겨야 다음 사람이 안 헤맵니다. 판정 근거를 문서에 남기지 않으면 반박이 무의미하다 이 런에서 규칙처럼 굳어진 게 하나 있습니다. 이슈 문면에서 이탈할 때는 이탈 근거를 문서에 남긴다. 새로 만든 RFC가 세 편(0028/0029/0030)인데, 그중 두 편이 이런 판정 기록입니다. 안 그러면 몇 주 뒤에 이슈를 다시 읽은 사람이 "여기 emission_id PK라고 적혀 있는데 코드는 왜 seq지?"에서 멈춥니다. 워커의 반박이 아무리 정확해도, 기록이 없으면 그냥 구현이 명세를 안 지킨 걸로 보입니다. 정작 사고는 병합에서 났다 자율 처리가 매끄러웠던 것에 비해, 제 손이 닿는 병합 단계에서 같은 실수를 두 번 했습니다. 브랜치를 합치면서 README 충돌을 --ours / --theirs 로 한 방에 정리했는데, 그때마다 다른 태스크가 추가한 RFC 표 행이 같이 날아갔습니다. 두 번 다 통합 스위트의 README 최신성 테스트가 즉시 잡아냈습니다. 복원 커밋으로 해소하긴 했지만, 두 번째에는 절차를 바꿨습니다 — 카운트 충돌을 해소하기 전에 양쪽 diff에서 카운트가 아닌 변경분을 먼저 대조 하는 것으로요. 충돌 해소는 "둘 중 하나 고르기"처럼 보이지만, 표가 걸려 있으면 사실은 병합입니다. 배운 것 자율 처리 런의 품질은 워커가 얼마나 똑똑한가보다 어긋남이 올라올 채널이 있는가 에 더 달려 있었습니다. 이슈 문면을 계약으로 두면 워커는 틀린 설계를 성실하게 구현해냅니다. 가설로 두면 실측이 올라오고, 대신 코디네이터가 매번 판정을 해야 합니다. 15건 중 두 건에서 판정이 필요했으니 비용이 크진 않았어요. 그리고 사람이 개입한 구간이 제일 위험했다는 게 좀 웃깁니다. 워커 15개가 만든 코드는 리뷰와 스위트를 통과했는데, 정작 제가 손으로 한 --ours 두 번이 데이터를 날렸으니까요. 테스트가 없었으면 조용히 넘어갔을 겁니다.
velog
JavaScript_Day.7 DOM 트리를 타고 이동하기, 이벤트 객체, 타이머, 그리고 구조 분해 할당까지. 어제까지가 "태그 하나를 골라서 바꾸기"였다면, 오늘은 "태그 사이의 관계를 따라 움직이기"였다. 학습 일곱 번째 날이다. 오늘 배운 내용은 이렇다. 입력값 계산 : value 와 eval DOM 탐색 : 부모·자식·형제 노드, 텍스트 노드와 요소 노드의 차이 이벤트 객체 : type , target , currentTarget , preventDefault() 폼 요소 다루기 : name 접근, keyup , checked , disabled , label display 속성 : block, inline, inline-block data- 사용자 정의 속성 타이머 : setInterval , setTimeout , 외부 JS 파일, window.open , location classList 구조 분해 할당 1. 입력값으로 계산하기 클릭하면 정답이 나타나는 계산 문제 <h1>계산해봅시다</h1> <hr> <!-- 구분선 --> <div><span class="ans">3+4=</span><span class="btn">답</span></div> <div><span class="ans">5*20+60=</span><span class="btn">답</span></div> <div><span class="ans">20/5-4*5=</span><span class="btn">답</span></div> <script> // 같은 이름의 CSS 선택자가 여러 개 있으면 목록(배열처럼)으로 선택된다 let ans = document.querySelectorAll('.ans'); let btn = document.querySelectorAll('.btn'); btn.forEach(function (btn, i) { btn.addEventListener('click', function () { let ans2 = ans[i].textContent.replace('=', ''); // '3+4=' → '3+4' btn.textContent = eval(ans2); // 계산 결과로 교체 }); }); </script> 식 클릭 후 3+4= 7 5*20+60= 160 20/5-4*5= -16 querySelectorAll 로 가져온 목록을 forEach 로 돌면서, 각 버튼에 클릭 이벤트를 붙였다. forEach 의 두 번째 인자 i (인덱스)로 같은 순서의 식( ans[i] )을 찾는 것이 핵심이다. replace('=', '') 로 = 를 지워야 eval 이 식으로 계산할 수 있다. 추가로 정리: forEach는 NodeList에는 되고, HTMLCollection에는 안 된다 가져오는 방법 결과 타입 forEach querySelectorAll NodeList 사용 가능 getElementsByTagName , getElementsByClassName , children HTMLCollection 사용 불가 Day.5에서 getElementsByTagName 결과를 for in 으로 돌 때 오류가 났던 것도 이 HTMLCollection 때문이다. HTMLCollection은 일반 for 문으로 돌면 안전하다. 식을 입력해서 계산하기 식<input type="text" id="susik"><br> 값<input type="text" id="result"><br> <input type="button" value="계산" onclick="calcu()"> <script> function calcu() { let susik = document.querySelector('#susik'); let result = document.querySelector('#result'); result.value = eval(susik.value); console.log(typeof(result.value)); // string console.log(typeof(susik.value)); // string } </script> input 요소의 값은 .value 로 읽고 쓴다. value 는 사용자가 직접 입력한 값이며, 숫자를 입력해도 문자열 이다. input으로 계산 기능을 만들 때는 value 를 쓴다. 필기에는 result.value = eval(...) 뒤의 typeof 가 문자열이라고 되어 있다. 숫자 결과를 value 에 넣으면 문자열로 바뀌기 때문이다. 2. DOM 탐색: 노드 사이를 이동하기 부모 노드: parentNode <div class="frame"> <h1 class="head">제목</h1> <hr> <p class="p">본문 문단이다.</p> <button id="bu1">클릭</button> </div> <script> let bu1 = document.querySelector('#bu1'); bu1.addEventListener('click', function () { this.parentNode.style.backgroundColor = 'yellow'; // 버튼의 부모(div) this.parentNode.style.border = '3px solid blue'; }); </script> this 는 클릭된 버튼이고, this.parentNode 는 그 부모 노드 ( div.frame )다. border 는 두께 모양 색 순서라서 3px solid blue 는 "3px짜리 실선, 파란색 테두리"다. 탐색 속성 정리 속성 설명 parentNode 부모 노드 childNodes 모든 자식 노드 (텍스트, 주석 포함) children 자식 요소 노드만 firstChild / lastChild 첫 / 마지막 자식 노드 (텍스트, 주석 포함) firstElementChild / lastElementChild 첫 / 마지막 자식 요소 (공백 무시) nextElementSibling 다음 형제 요소 (공백 무시) previousElementSibling 이전 형제 요소 (공백 무시) 바로잡기 : 필기에는 firstChild 를 "첫번째 자손 노드", 이전 형제를 prevElementSibling 이라고 적었다. firstChild 는 바로 아래 단계인 자식 노드이고(자손은 손자까지 포함하는 말이다), 이전 형제 속성의 정확한 이름은 previousElementSibling 이다. 텍스트 노드와 요소 노드 <div class="frame"> <h1 class="head">제목</h1> <p class="p">본문 문단이다.</p> <button id="bu1">클릭</button> </div> <script> let f = document.querySelector('.frame'); console.log(f.childNodes.length); // 7 f.childNodes[1].style.color = 'red'; // <h1>이 빨간색으로 console.log(f.children.length); // 3 </script> childNodes 가 7인 이유는 태그 사이의 줄바꿈과 공백도 텍스트 노드 로 세기 때문이다. 인덱스 노드 0 텍스트 노드: <div> 여는 태그 뒤의 줄바꿈+공백 1 요소 노드: <h1> 2 텍스트 노드: </h1> 와 <p> 사이의 줄바꿈+공백 3 요소 노드: <p> 4 텍스트 노드: </p> 와 <button> 사이의 줄바꿈+공백 5 요소 노드: <button> 6 텍스트 노드: </button> 뒤의 줄바꿈+공백 children 은 요소 노드만 세므로 h1 , p , button 으로 3 이다. 그래서 childNodes[1] 처럼 인덱스를 쓸 때는 공백 때문에 번호가 어긋나기 쉽고, 요소만 쓰고 싶다면 children 이나 ~Element~ 속성을 쓰는 것이 편하다. firstChild와 firstElementChild <div id="frame"> <p>one</p> <p>two</p> </div> <script> let f1 = document.getElementById('frame'); let first = f1.firstElementChild; document.write(f1.firstChild + '<br>'); // [object Text] (공백 텍스트 노드) document.write(first); // [object HTMLParagraphElement] (<p>one</p>) </script> firstChild : 모든 노드 중 첫 번째. 텍스트와 주석도 포함한다. firstElementChild : 요소 노드 중 첫 번째. 텍스트와 공백은 무시한다. 형제 노드와 텍스트 노드 바꾸기 <div class="frame"> <!-- 처음으로 추가된 텍스트 --> <h1>javascript</h1> <div class="c"> <h2>css</h2> <p>h2 다음에 오는 문단이다.</p> </div> <!-- 마지막에 추가된 텍스트 --> </div> <script> let f1 = document.querySelector('.frame'); f1.firstChild.textContent = '처음으로 추가된 텍스트'; f1.lastChild.textContent = '마지막에 추가된 텍스트'; let c1 = document.querySelector('.c'); let h2 = document.querySelector('.c h2'); console.log(h2.textContent); // css // h2의 다음 형제 요소 = p h2.nextElementSibling.style.border = '2px solid red'; // div.c의 이전 형제 요소 = h1 c1.previousElementSibling.style.border = '2px dotted blue'; </script> 여기서 firstChild 와 lastChild 는 주석이 아니라, 주석 앞뒤의 공백 텍스트 노드 다. 그 노드의 textContent 를 바꾼 것이라 화면에 글이 나타난다. h2.nextElementSibling 은 h2 와 같은 부모 아래에서 바로 다음에 있는 요소( p )다. 3. 이벤트 객체 <p id="mouse1">마우스</p> <!-- html 인라인 이벤트에서는 매개변수 이름을 event로 쓴다 --> <button onclick="on(event)">클릭!</button> <script> function on(event) { // event: 현재 발생한 이벤트 객체 let event1 = event.type + '<br>' // 이벤트 종류 + event.target + '<br>' // 이벤트가 발생한 요소 + event.currentTarget + '<br>' // 이벤트가 연결된 요소 + event.defaultPrevented; // 기본 동작이 취소되었는가 document.querySelector('#mouse1').innerHTML = event1; } </script> 속성 설명 이 예제의 값 event.type 이벤트 종류 click event.target 이벤트가 발생한 요소 버튼 event.currentTarget 이벤트 핸들러가 연결된 요소 버튼 event.defaultPrevented 기본 동작이 취소되었는지 (취소됨: true) false 이 예제에서는 버튼에 직접 연결했기 때문에 target 과 currentTarget 이 같다. 자식 요소를 클릭했는데 핸들러가 부모에 달려 있는 경우에는 둘이 달라질 수 있다. preventDefault: 기본 동작 막기 태그에는 기본 동작 이 있다. 링크는 클릭하면 페이지가 이동하고, 체크박스는 클릭하면 체크된다. 이 기본 동작을 코드로 막을 수 있다. <!-- 링크의 기본 동작은 페이지 이동이다 --> <!-- onclick이 true를 반환하면 이동, false를 반환하면 이동 취소 --> <a href="https://example.com" onclick="return on()">이동할 거냐</a><br> <!-- 체크박스의 기본 동작은 체크되는 것이다 --> <input type="checkbox">커피<br> <input type="checkbox" onclick="no(event)">빵 <script> function on() { let n = confirm('정말 이동할건가요?'); // 확인: true, 취소: false return n; } function no(e) { e.preventDefault(); // 이벤트의 기본 동작을 강제로 취소 } </script> e.preventDefault() : 기본 동작을 취소시키는 메소드 다. e.defaultPrevented : 기본 동작이 취소되었는지 확인 하는 값이다. 함수가 아니라 속성이므로 () 를 붙이지 않는다. 이름이 비슷해서 헷갈리는데, "막아라"는 preventDefault() , "막혔나?"는 defaultPrevented 로 구분하기로 했다. 위 예제에서 "빵" 체크박스는 클릭해도 체크되지 않고, "커피"는 정상적으로 체크된다. 4. 폼 요소 다루기 폼 요소에 접근하는 3가지 방법 <form name="test"> <input type="text" name="test2" placeholder="입력해주세요"> </form> <script> // 1. document.폼이름.요소이름 let n = document.test.test2; n.style.border = '2px solid red'; // 2. document.forms['폼이름'].elements['요소이름'] let n2 = document.forms['test'].elements['test2']; n2.style.backgroundColor = 'yellow'; // 3. document.forms['폼이름']['요소이름'] let n3 = document.forms['test']['test2']; n3.style.fontSize = '50px'; </script> 세 방법 모두 같은 input 을 가리킨다. 형태가 짧은 순서대로 1번, 3번, 2번이다. keyup: 입력할 때마다 실시간으로 반영하기 <form name="test"> <input type="text" id="aa" name="test2" placeholder="입력해주세요"> </form> <div id="div1"></div> <script> let input = document.forms['test']['test2']; let n2 = document.querySelector('#div1'); // 키보드를 입력했을 때의 이벤트 (고전 이벤트 방식) input.onkeyup = function () { let n3 = this.value; // input에 입력한 값 n2.textContent = n3; // div 영역에 띄운다 }; </script> 키를 눌렀다 뗄 때마다 input 의 값이 div 에 그대로 나타난다. 필기에서는 innerHTML 로 넣었는데, 사용자 입력을 그대로 넣는 곳이라서 Day.6에서 정리한 이유로 textContent 로 바꿨다. disabled, label, checked 주민번호 뒷자리 첫 숫자를 입력하면 성별 라디오 버튼이 자동으로 선택되는 실습이다. <form name="test" class="a1"> <input type="text" name="input_1" value="001111" disabled> <!-- disabled: 입력하지 못하도록 막는다 --> <input type="text" name="input_2" placeholder="주민번호 뒷자리 입력하기"> <input type="radio" name="ch" id="male"> <label for="male">남</label> <!-- label로 구분 --> <input type="radio" name="ch" id="female"> <label for="female">여</label> </form> <script> let n = document.test.input_2; let male = document.querySelector('#male'); let female = document.querySelector('#female'); n.onkeyup = function () { let num = this.value; if (num >= 1 && num <= 4) { if (num == 1 || num == 3) { female.checked = false; male.checked = true; // 체크한다 } else { male.checked = false; female.checked = true; } } }; </script> disabled : 입력을 막는 속성이다. <label for="id"> : for 에 input 의 id 를 넣으면 글자를 눌러도 해당 입력이 선택된다. radio 나 checkbox 타입에는 checked 라는 상태 필드가 있어서, true / false 로 체크 상태를 바꾼다. 추가로 정리: 라디오 버튼은 한쪽만 true로 해도 된다 name 이 같은 라디오 버튼은 하나만 선택되므로, male.checked = true 만 해도 female 은 자동으로 해제된다. 위 코드의 female.checked = false 는 없어도 결과가 같다. 5. display 속성: 블록, 인라인, 인라인 블록 구분 블록 박스 인라인 박스 인라인 블록 박스 속성 display: block display: inline display: inline-block 예시 태그 div , p , h1~h6 label , a , span , strong (속성으로 지정) 새 라인에서 시작 항상 새 라인 못 함 (라인 안에 있음) 못 함 (인라인 특성) 옆에 다른 요소 배치 불가능 가능 가능 (인라인 특성) width, height 조절 가능 불가능 가능 (블록 특성) padding, margin 조절 가능 위아래 margin은 적용 안 됨 가능 (블록 특성) 필기에는 인라인 박스의 예로 textarea 와 input 도 적었는데, 이 둘은 인라인처럼 줄 안에 놓이지만 width와 height를 조절할 수 있다. 그래서 기본값이 사실상 인라인 블록에 가깝다. 위 표의 인라인 예시는 label , a , span , strong 만 남겼다. <style> div { border: 1px solid salmon; color: blue; background-color: yellow; } </style> css <div style="display: none;">재밌음</div> <br> css <div style="display: inline; height: 50px;">재밌음</div> css <div style="display: inline-block; height: 50px;">재밌음</div> css <div style="display: block;">재밌음</div> 같은 div 에 속성만 바꿔서 확인했다. none : 화면에서 사라지고 자리도 차지하지 않는다. inline : 글자 크기만큼만 차지한다. height: 50px 을 줘도 적용되지 않는다. inline-block : 옆에 붙으면서 height: 50px 이 적용된다. block : 새 줄에서 시작해 한 줄을 다 차지한다. 6. 폼 제출 이벤트로 목록 만들기 <h3>정보입력</h3> <form id="info"> <p>이름 <input type="text" id="name"></p> <p>주소 <input type="text" id="addr"></p> <input type="submit" value="정보입력"> </form> <p id="output"></p> <!-- 완성되면 이런 모양: <p>홍길동 , 서울</p> --> <script> let form = document.getElementById('info'); let nameInput = document.getElementById('name'); let addr = document.getElementById('addr'); let output = document.getElementById('output'); // submit 버튼을 눌렀을 때 발생하는 이벤트 → onsubmit form.onsubmit = function (e) { e.preventDefault(); // 기본 제출 동작(새
velog
노트북: 09-01. tokenization.ipynb (셀 18개, 코드 셀 12개) 예상 시간: 약 12분 · 짝꿍 글: 4.3_토큰화.md 개념 1분 — 이것만 알고 코드 보기 토큰화(tokenization) = 문장을 모델이 다룰 단위(토큰) 로 자르는 것. 4.1~4.2의 RNN에 "단어 하나 = 시점 하나"로 넣으려면, 먼저 "단어"가 뭔지 정해야 한다. 자르는 단위에 따라 이름이 붙는다. 단어 토큰화 : 단어 단위 문장 토큰화 : 문장 단위 형태소 토큰화 : 뜻을 가진 가장 작은 조각 단위 영어 는 띄어쓰기만으로 꽤 잘 잘린다. 문제는 Don't , Mr. , Ph.D. 같은 아포스트로피와 마침표다. 한국어 는 띄어쓰기로 자르면 안 된다. "영화가", "영화를", "영화는"이 전부 다른 단어가 되기 때문이다. 그래서 형태소 분석기 로 "영화 / 가"처럼 조사를 떼어 낸다. 4.5에서 쓰는 mecab.morphs() 가 바로 이것이다. 정답 토큰화는 없다. 도구마다 자르는 규칙이 다르고, 어떤 걸 쓸지는 우리가 고른다. 이 노트북은 그 차이를 눈으로 비교하는 노트북이다. 발표 전 체크 ⚠️ 셀 0을 그대로 실행하면 셀 3에서 멈출 수 있다. 최신 nltk(3.9 이상)는 word_tokenize 에 punkt 가 아니라 punkt_tab 이 필요하다. 그래서 LookupError: Resource 'punkt_tab' not found 가 난다. pos_tag 도 averaged_perceptron_tagger_eng 를 요구한다. 셀 0에 두 줄을 미리 추가해 두자. nltk.download('punkt_tab') nltk.download('averaged_perceptron_tagger_eng') nltk 3.10에서 직접 확인했다. 두 줄을 넣으면 나머지 출력은 노트북에 저장된 것과 완전히 같다. 셀 2는 tensorflow.keras.preprocessing.text 를 임포트한다. 코랩에는 텐서플로가 깔려 있고, Keras 3에도 이 함수가 레거시로 남아 있어서 그대로 돈다. 셀 11·15의 pip install kss , pip install konlpy 는 앞에 ! 가 없지만 코랩에서는 돌아간다. kss는 설치가 1분 넘게 걸리니 세션 전에 미리 실행 해 두자. 흐름 한눈에 파트 셀 한 줄 메시지 시간 1. 영어 단어 토큰화 3종 비교 0~5 같은 문장도 도구마다 다르게 자른다 3분 2. 트리뱅크 규칙 7 하이픈은 붙이고, 줄임말은 뗀다 1분 3. 문장 토큰화 9~12 마침표 = 문장 끝이 아니다 2분 4. 품사 태깅 14 토큰에 품사를 붙인다 1분 5. 한국어 형태소 분석 15~17 한국어는 형태소로 자른다 → 4.5로 연결 4분 읽는 법: 🗣 = 그대로 말해도 되는 문장, ❓ = 나올 만한 질문과 답 Part 1. 영어 단어 토큰화 3종 비교 (셀 0~5) 셀 0 — nltk 데이터 내려받기 import nltk nltk.download('punkt') # 문장/단어 토큰화 규칙 nltk.download('averaged_perceptron_tagger') # 품사 태깅 모델 # (추가) nltk.download('punkt_tab'); nltk.download('averaged_perceptron_tagger_eng') nltk는 코드와 데이터(규칙, 모델)가 따로 있다. 쓰기 전에 데이터를 내려받아야 한다. 셀 2~5 — 같은 문장, 세 도구 from nltk.tokenize import word_tokenize from nltk.tokenize import WordPunctTokenizer from tensorflow.keras.preprocessing.text import text_to_word_sequence s = "Don't be fooled by the dark sounding name, Mr. Jone's Orphanage is as cheery as cheery goes for a pastry shop." word_tokenize(s) WordPunctTokenizer().tokenize(s) text_to_word_sequence(s) 결과에서 갈리는 부분만 모으면 이렇다. 원문 word_tokenize WordPunctTokenizer text_to_word_sequence Don't Do , n't Don , ' , t don't Mr. Mr. Mr , . mr Jone's Jone , 's Jone , ' , s jone's , . 남김 남김 지움 대소문자 그대로 그대로 전부 소문자 토큰 수 25 28 21 word_tokenize → 영어 문법을 안다. n't (not), 's (소유격)처럼 뜻이 있는 단위 로 뗀다. WordPunctTokenizer → 규칙이 단순하다. 구두점을 무조건 따로 뗀다. 그래서 Don , ' , t 처럼 뜻이 없는 조각이 생긴다. text_to_word_sequence → 소문자로 바꾸고 구두점을 지운 뒤 띄어쓰기로만 자른다. 가장 거칠지만 가장 단순하다. 🗣 "같은 문장인데 토큰 수가 25, 28, 21로 다 달라요. 토큰화에는 정답이 없고, 어떤 단위가 우리 문제에 맞는지 고르는 거예요." ❓ 뭘 써야 해? 문제에 따라 다르다. 감성 분류처럼 "좋다/싫다"만 보면 거친 토큰화로도 충분하다. 번역처럼 문법이 중요하면 n't 같은 단위를 살리는 게 낫다. Part 2. 트리뱅크 토크나이저 (셀 7) from nltk.tokenize import TreebankWordTokenizer tokenizer = TreebankWordTokenizer() text = "Starting a home-based restaurant may be an ideal. it doesn't have a food chain or restaurant of their own." tokenizer.tokenize(text) ['Starting', 'a', 'home-based', 'restaurant', 'may', 'be', 'an', 'ideal.', 'it', 'does', "n't", ..., 'own', '.'] 하이픈은 붙여 둔다 → home-based 가 한 토큰이다. 줄임말은 뗀다 → doesn't → does , n't . 눈여겨볼 곳: ideal. 의 마침표가 안 떨어졌다. 트리뱅크는 문자열 맨 끝의 마침표만 뗀다. 문장 중간 마침표는 단어에 붙은 채로 남는다. 그래서 보통은 문장 토큰화를 먼저 하고 단어 토큰화를 한다( word_tokenize 가 안에서 그렇게 한다). 🗣 "ideal 뒤 마침표가 안 떨어진 게 보이시죠. 트리뱅크는 문장 하나를 넣는다고 가정해서 맨 끝 마침표만 떼요. 그래서 문장부터 나누고 단어를 자르는 순서가 필요합니다." Part 3. 문장 토큰화 (셀 9~12) 셀 9~10 — 영어 from nltk.tokenize import sent_tokenize sent_tokenize("His barber kept his word. But keeping such a huge secret ...") # 5문장으로 잘 나뉨 sent_tokenize("I am actively looking for Ph.D. students. and you are a Ph.D student.") # → ['I am actively looking for Ph.D. students.', 'and you are a Ph.D student.'] 마침표를 기준으로 자르지만, Ph.D. 의 마침표에서는 자르지 않았다. 약어 목록을 학습해 둔 덕분이다. 두 번째 문장은 소문자 and 로 시작하는데도 잘 나눴다. 셀 11~12 — 한국어 (kss) import kss kss.split_sentences('딥 러닝 자연어 처리가 재미있기는 합니다. 그런데 문제는 영어보다 한국어로 할 때 너무 어렵습니다. 이제 해보면 알걸요?') # → ['딥 러닝 자연어 처리가 재미있기는 합니다.', '그런데 문제는 ...', '이제 해보면 알걸요?'] 한국어 문장 분리기다. 함께 찍힌 WARNING ... mecab ... 은 에러가 아니다. "환경에 mecab이 있어서 그걸 백엔드로 쓴다"는 안내다. 🗣 "마침표가 곧 문장 끝은 아니에요. Ph.D.처럼 약어에도 마침표가 들어가니까, 문장 토큰화도 규칙이나 학습이 필요합니다." Part 4. 품사 태깅 (셀 14) from nltk.tag import pos_tag tokenized_sentence = word_tokenize("I am actively looking for Ph.D. students. and you are a Ph.D. student.") pos_tag(tokenized_sentence) [('I', 'PRP'), ('am', 'VBP'), ('actively', 'RB'), ('looking', 'VBG'), ('for', 'IN'), ('Ph.D.', 'NNP'), ('students', 'NNS'), ('.', '.'), ...] 태그 뜻 예 PRP 인칭 대명사 I, you VBP / VBG 동사 현재형 / 동명사·현재분사 am / looking RB 부사 actively IN 전치사 for NNP / NNS / NN 고유명사 / 복수명사 / 단수명사 Ph.D. / students / student DT 한정사 a 토큰화한 결과에 품사 꼬리표 를 붙인다. 4.1의 입출력 구조로 보면 다 대 다 다(단어마다 출력 하나). Part 5. 한국어 형태소 분석 — KoNLPy (셀 15~17) from konlpy.tag import Okt, Kkma okt, kkma = Okt(), Kkma() s = "열심히 코딩한 당신, 연휴에는 여행을 가봐요" okt.morphs(s); okt.pos(s); okt.nouns(s) kkma.morphs(s); kkma.pos(s); kkma.nouns(s) Okt 꼬꼬마(Kkma) morphs 열심히 / 코딩 / 한 / 당신 / , / 연휴 / 에는 / 여행 / 을 / 가봐요 열심히 / 코딩 / 하 / ᄂ / 당신 / , / 연휴 / 에 / 는 / 여행 / 을 / 가보 / 아요 토큰 수 10 13 nouns 코딩, 당신, 연휴, 여행 코딩, 당신, 연휴, 여행 morphs → 형태소 단위로 자른 리스트. 4.5에서 쓰는 게 이것 이다(분석기는 Mecab). pos → 형태소 + 품사 튜플. nouns → 명사만 뽑는다. 꼬꼬마가 더 잘게 자른다. 코딩한 → 코딩 / 하 / ᄂ , 가봐요 → 가보 / 아요 . 눈여겨볼 곳: Okt는 한 을 조사(Josa) 로 태깅했다. 실제로는 하다 의 활용형( 하 + ᄂ )이다. 분석기마다 틀리는 곳이 다르다. 🗣 "한국어는 '연휴에는'을 그대로 두면 '연휴', '연휴에', '연휴를'이 전부 다른 단어가 돼요. 그래서 형태소로 떼서 '연휴'를 같은 단어로 만드는 겁니다. 4.5에서 mecab.morphs 한 줄이 이 역할을 해요." ❓ 4.5에서는 왜 Mecab을 써? 빠르기 때문이다. 4.5는 리뷰 19만 개를 토큰화한다. Mecab은 노트북 기준 14만 5천 개를 10초 정도에 끝낸다. 메서드 이름( morphs )은 Okt, 꼬꼬마와 같다. 마무리 (1분) 🗣 "토큰화는 문장을 RNN에 넣을 단위로 자르는 거고, 정답은 없고 도구마다 규칙이 달라요. 영어는 아포스트로피와 마침표 처리가 갈리고, 한국어는 조사 때문에 형태소 분석기가 필요합니다. 4.5에서는 Mecab으로 형태소를 자르고, 조사 같은 불용어를 빼고 씁니다." 치트시트 도구 언어 단위 특징 word_tokenize 영어 단어 n't , 's 분리. 가장 무난 WordPunctTokenizer 영어 단어 구두점 전부 분리 text_to_word_sequence 영어 단어 소문자화 + 구두점 삭제 TreebankWordTokenizer 영어 단어 하이픈 유지, 끝 마침표만 분리 sent_tokenize 영어 문장 약어 마침표 처리 kss.split_sentences 한국어 문장 Okt / Kkma / Mecab .morphs() 한국어 형태소 4.5는 Mecab
velog
앞에서 프로세스와 스레드가 무엇인지 알아봤다. 프로그램이 실행되면 프로세스가 되고, 여러 프로세스가 동시에 실행될 수 있다. 그렇다면 한 가지 문제가 생긴다. CPU는 한정되어 있는데, 실행해야 할 프로세스는 여러 개라면 CPU를 누구에게 먼저 줘야 할까? 운영체제는 이 문제를 해결하기 위해 CPU 스케줄링 을 사용한다. CPU 스케줄링은 여러 프로세스 중에서 CPU를 사용할 프로세스를 선택하고, CPU 사용 순서를 결정하는 것 이다. 1. 프로세스마다 우선순위가 다르다 모든 프로세스가 똑같은 중요도를 가지는 것은 아니다. 운영체제는 프로세스의 우선순위 를 고려하여 CPU를 할당한다. 예를 들어 어떤 프로세스는 CPU를 오래 사용해야 하고, 어떤 프로세스는 잠깐 CPU를 사용한 뒤 입출력을 기다려야 할 수도 있다. 프로세스의 특성을 이해하기 위해 크게 두 종류로 나눌 수 있다. CPU 바운드 프로세스 CPU를 많이 사용하는 프로세스다. 예를 들어 복잡한 계산처럼 CPU에서 처리해야 하는 작업이 많은 경우가 이에 해당한다. I/O 바운드 프로세스 입출력 작업을 많이 사용하는 프로세스다. CPU를 사용하다가도 파일 읽기나 입력 등의 입출력 작업을 기다리는 상태가 될 수 있다. 즉, CPU 바운드 → CPU를 사용하는 시간이 상대적으로 김 I/O 바운드 → CPU 사용 후 입출력을 기다리는 경우가 많음 운영체제는 이러한 프로세스들의 특성과 우선순위를 고려하면서 CPU를 배분한다. 2. 프로세스는 어디에서 기다릴까? 실행할 프로세스가 많다고 해서 모든 프로세스가 CPU를 바로 사용할 수 있는 것은 아니다. 프로세스들은 자신의 상태에 따라 여러 큐에서 기다리게 된다. 대표적으로 준비 큐(Ready Queue) 와 대기 큐(Waiting Queue) 가 있다. 준비 큐 CPU를 사용할 준비가 되어 있는 프로세스들이 기다리는 곳이다. 프로세스 A ─┐ 프로세스 B ─┼→ 준비 큐 → CPU 프로세스 C ─┘ CPU 스케줄러는 준비 큐에 있는 프로세스 중 하나를 선택해 CPU를 할당한다. 대기 큐 입출력 작업 등을 기다리는 프로세스가 머무르는 곳이다. CPU ↓ 입출력 요청 ↓ 대기 큐 ↓ 입출력 완료 ↓ 준비 큐 따라서 프로세스는 실행되는 동안 준비 큐와 대기 큐 등을 오가게 된다. 3. 선점형과 비선점형 CPU 스케줄링 방법을 이해할 때 중요한 기준 중 하나가 선점 여부 다. 비선점형 스케줄링 한 프로세스가 CPU를 사용하기 시작하면, 스스로 CPU를 반납할 때까지 다른 프로세스가 강제로 CPU를 빼앗을 수 없는 방식 이다. 프로세스 A ████████████ ↓ CPU 반납 ↓ 프로세스 B ████████ 프로세스가 실행을 끝내거나 대기 상태로 들어가야 다음 프로세스가 CPU를 사용할 수 있다. 선점형 스케줄링 현재 실행 중인 프로세스의 CPU를 다른 프로세스가 강제로 빼앗을 수 있는 방식 이다. 프로세스 A ██████ ↓ 선점 프로세스 B ███████ 예를 들어 더 높은 우선순위의 프로세스가 준비되었다면 현재 실행 중인 프로세스를 멈추고 새로운 프로세스에게 CPU를 할당할 수 있다. 둘의 차이를 정리하면 다음과 같다. 구분 선점형 비선점형 CPU 강제 회수 가능 불가능 프로세스 전환 필요에 따라 가능 CPU 반납 후 전환 응답성 상대적으로 높일 수 있음 상대적으로 낮을 수 있음 특징 여러 프로세스를 빠르게 번갈아 실행 가능 구조가 비교적 단순 4. CPU 스케줄링 알고리즘 CPU를 어떤 순서로 할당할지는 여러 가지 방법으로 결정할 수 있다. 대표적인 CPU 스케줄링 알고리즘에는 다음과 같은 것들이 있다. FCFS SJF Round Robin SRT Priority Scheduling Multilevel Queue Multilevel Feedback Queue 각 알고리즘마다 프로세스를 선택하는 기준이 다르다. 4-1. FCFS — First Come First Served 먼저 온 프로세스부터 처리하는 방식 이다. 말 그대로 먼저 준비 큐에 들어온 프로세스가 먼저 CPU를 사용한다. 준비 큐 A → B → C ↓ A → B → C 은행에서 줄을 선 순서대로 처리하는 것과 비슷하게 생각할 수 있다. 구조가 단순하다는 특징이 있지만, 먼저 실행된 프로세스의 작업이 너무 오래 걸리면 뒤의 프로세스들이 오랫동안 기다릴 수 있다. 4-2. SJF — Shortest Job First 실행 시간이 짧은 프로세스를 먼저 실행하는 방식 이다. A : ██████████ B : ███ C : █████ → B → C → A CPU를 짧게 사용하는 프로세스를 먼저 처리하면 전체적으로 프로세스가 기다리는 시간을 줄이는 데 도움이 될 수 있다. 4-3. Round Robin Round Robin은 일정한 시간만큼씩 CPU를 돌아가면서 사용하는 방식 이다. 이때 각 프로세스에게 할당되는 일정한 시간을 타임 슬라이스(Time Slice) 라고 한다. 예를 들어 타임 슬라이스가 2라면, A → B → C → A → B → C ... 각 프로세스가 CPU를 조금씩 나누어 사용한다. 한 프로세스가 CPU를 계속 독점하지 않도록 하기 때문에 여러 프로세스가 번갈아 실행되는 환경에서 사용할 수 있다. 4-4. SRT — Shortest Remaining Time SRT는 남은 실행 시간이 짧은 프로세스를 우선하는 방식 이다. SJF와 비슷하지만, 현재 실행 중인 프로세스보다 남은 실행 시간이 더 짧은 프로세스가 등장하면 CPU를 넘겨줄 수 있다. 노트에서는 SRT를 SJF와 Round Robin의 특징이 결합된 형태로 정리하고 있다. 4-5. Priority Scheduling 말 그대로 우선순위가 높은 프로세스부터 실행하는 방식 이다. 프로세스 A → 우선순위 3 프로세스 B → 우선순위 1 프로세스 C → 우선순위 2 ↓ B → C → A 우선순위의 기준은 시스템의 목적이나 프로세스의 특성에 따라 정해질 수 있다. 다만 우선순위가 낮은 프로세스가 계속해서 실행되지 못하는 문제가 발생할 수 있다. 5. 여러 개의 큐를 사용하는 방법 프로세스를 하나의 큐에서만 관리하는 것이 아니라 여러 개의 큐로 나누어 관리하는 방법 도 있다. Multilevel Queue 프로세스의 특성이나 우선순위 등에 따라 여러 개의 큐를 만들어 관리하는 방식이다. 예를 들어, 높은 우선순위 ┌──────────────┐ │ 큐 1 │ └──────────────┘ ┌──────────────┐ │ 큐 2 │ └──────────────┘ ┌──────────────┐ │ 큐 3 │ └──────────────┘ 낮은 우선순위 각 큐마다 서로 다른 스케줄링 방식을 사용할 수도 있다. Multilevel Feedback Queue Multilevel Feedback Queue는 여러 개의 큐를 사용하면서 프로세스의 실행 특성에 따라 큐를 이동시킬 수 있는 방식 이다. 프로세스가 CPU를 사용하는 방식에 따라 다른 큐로 이동할 수 있기 때문에 하나의 기준만 사용하는 것보다 유연하게 프로세스를 관리할 수 있다. 노트에서는 Multilevel Feedback Queue를 다양한 스케줄링 상황에 적용할 수 있는 일반적인 형태로 정리하고 있다. 6. CPU 스케줄링을 왜 알아야 할까? 지금까지 살펴본 내용을 하나로 연결해보자. 컴퓨터에서는 여러 프로세스가 동시에 실행되고 있다. 하지만 CPU는 한정된 자원이기 때문에 운영체제가 모든 프로세스에게 CPU를 무작정 나눠줄 수는 없다. 그래서 운영체제는 여러 프로세스 ↓ 준비 큐 ↓ CPU 스케줄러 ↓ 실행할 프로세스 선택 ↓ CPU 할당 과 같은 과정을 통해 CPU를 관리한다. 그리고 어떤 프로세스를 먼저 실행할 것인지 결정하는 방법에 따라 FCFS, SJF, Round Robin, SRT, Priority Scheduling, Multilevel Queue, Multilevel Feedback Queue 등의 알고리즘을 사용할 수 있다. 결국 CPU 스케줄링의 핵심은 한정된 CPU 자원을 여러 프로세스에게 어떻게 배분할 것인가 라고 볼 수 있다. 마무리 이번 글에서는 운영체제가 여러 프로세스에게 CPU를 할당하는 CPU 스케줄링 에 대해 알아봤다. 핵심 내용을 정리하면 다음과 같다. CPU 스케줄링 │ ├─ 프로세스의 우선순위 고려 │ ├─ 준비 큐 / 대기 큐 │ ├─ 선점형 / 비선점형 │ └─ 스케줄링 알고리즘 ├─ FCFS ├─ SJF ├─ Round Robin ├─ SRT ├─ Priority ├─ Multilevel Queue └─ Multilevel Feedback Queue 그런데 여기서 또 하나의 문제가 생긴다. 여러 프로세스가 같은 자원을 사용한다면 어떻게 해야 할까? 여러 프로세스가 동시에 하나의 자원에 접근하면 실행 순서에 따라 결과가 달라지는 문제가 발생할 수 있다. 다음 글에서는 이러한 문제를 해결하기 위한 프로세스 동기화 와 함께 임계 구역, 경쟁 조건, 뮤텍스, 세마포어, 모니터 를 살펴본다.
velog
지난 글에서는 운영체제가 무엇인지, 그리고 운영체제가 CPU·메모리·입출력장치 등의 자원을 관리한다는 것을 알아봤다. 그렇다면 한 가지 궁금증이 생긴다. 운영체제는 실제로 실행 중인 프로그램을 어떻게 관리할까? 이번 글에서는 운영체제가 프로그램을 관리하기 위해 사용하는 핵심 개념인 프로세스와 스레드 를 알아본다. 프로세스의 구조부터 PCB, 문맥 교환, 프로세스 상태, 그리고 멀티프로세스와 멀티스레드까지 순서대로 정리해보자. 1. 프로세스란? 프로그램과 프로세스 먼저 프로그램과 프로세스를 구분해야 한다. 프로그램(Program) 은 저장장치에 존재하는 실행할 수 있는 파일이다. 그리고 이 프로그램을 메모리에 적재하고 실행하면 프로세스(Process) 가 된다. 즉, 프로세스 = 실행 중인 프로그램 이라고 정리할 수 있다. 예를 들어 컴퓨터에 게임 프로그램이 설치되어 있다고 생각해보자. 게임을 실행하지 않은 상태에서는 단순히 저장장치에 있는 프로그램이지만, 실행하는 순간 운영체제가 해당 프로그램을 메모리에 적재하고 실행을 관리한다. 이때 실행 중인 프로그램을 프로세스라고 한다. 2. 포그라운드 프로세스와 백그라운드 프로세스 프로세스는 사용자가 볼 수 있는 공간에서 실행되는지에 따라 크게 나누어 볼 수 있다. 포그라운드 프로세스 사용자가 볼 수 있는 공간에서 실행되는 프로세스 다. 예를 들어 우리가 직접 실행해서 사용하는 프로그램이 이에 해당한다. 백그라운드 프로세스 사용자가 직접 보고 있지 않은 공간에서 실행되는 프로세스 다. 사용자와 상호작용하지 않고 정해진 작업을 수행하는 프로세스를 데몬(daemon) 또는 서비스(service) 라고 부르기도 한다. 즉, 화면에 보이지 않는다고 해서 프로그램이 실행되고 있지 않은 것은 아니다. 3. 프로세스 제어 블록(PCB) 모든 프로세스는 실행을 위해 CPU가 필요하다. 하지만 CPU 자원은 한정되어 있다. 따라서 여러 프로세스가 CPU를 사용하려면 운영체제가 프로세스들을 관리하면서 CPU를 번갈아 할당해야 한다. 이때 운영체제가 프로세스를 관리하기 위해 사용하는 자료구조가 바로 프로세스 제어 블록(PCB, Process Control Block) 이다. PCB란? PCB는 프로세스와 관련된 정보를 저장하는 자료구조 다. 프로세스 하나하나에 붙어 있는 태그 라고 생각하면 이해하기 쉽다. 프로세스가 생성되면 커널 영역에 PCB가 생성되고, 프로세스가 종료되면 PCB도 폐기된다. PCB에는 어떤 정보가 들어 있을까? 대표적으로 다음과 같은 정보가 저장된다. 정보 설명 프로세스 ID(PID) 프로세스를 식별하기 위한 고유 번호 레지스터 값 프로그램 카운터(PC) 및 각종 레지스터 값 프로세스 상태 실행 중인지, 대기 중인지 등의 상태 CPU 스케줄링 정보 언제, 어떤 순서로 CPU를 사용할지에 대한 정보 메모리 정보 프로세스가 저장된 주소와 페이지 테이블 정보 파일·입출력장치 정보 할당된 입출력장치 및 열린 파일 목록 특히 PID(Process ID) 는 프로세스를 식별하기 위한 번호다. 학교에서 학생을 학번으로 구분하거나 회사에서 사번으로 구분하는 것과 비슷하게 생각할 수 있다. 4. 문맥 교환 CPU는 한 번에 하나의 프로세스만 실행할 수 있다. 그렇다면 여러 프로세스가 동시에 실행되는 것처럼 보이는 이유는 무엇일까? 프로세스들이 CPU를 아주 빠르게 번갈아 사용하기 때문이다. 예를 들어 다음과 같은 상황을 생각해보자. 프로세스 A 실행 ↓ 프로세스 A의 상태 저장 ↓ 프로세스 B의 상태 복구 ↓ 프로세스 B 실행 ↓ 프로세스 B의 상태 저장 ↓ 프로세스 A의 상태 복구 ↓ 프로세스 A 실행 이처럼 CPU가 한 프로세스에서 다른 프로세스로 실행을 넘길 때 기존 프로세스의 상태를 저장하고 새로운 프로세스의 상태를 복구하는 과정을 문맥 교환(Context Switch) 이라고 한다. 문맥(Context)이란? 하나의 프로세스가 실행을 이어가기 위해 기억해야 하는 정보를 말한다. 운영체제는 이러한 정보를 PCB에 저장해두었다가 다시 해당 프로세스를 실행할 때 복구한다. 즉, 문맥 교환 = 기존 프로세스의 문맥을 저장하고 새로운 프로세스의 문맥을 불러오는 과정 이라고 이해하면 된다. 5. 프로세스의 메모리 영역 프로세스가 실행되면 메모리에는 여러 영역이 구성된다. 대표적으로 다음 네 가지 영역으로 나눌 수 있다. 영역 특징 저장되는 것 코드 영역 정적 할당, 읽기 전용 실행할 기계어 명령어 데이터 영역 정적 할당 전역 변수 등 힙 영역 동적 할당, 크기 변경 가능 프로그래머가 동적으로 할당하는 데이터 스택 영역 동적 할당, 크기 변경 가능 지역 변수, 매개변수 등 코드 영역 프로그램을 실행하기 위한 기계어 명령어 가 저장되는 영역이다. 읽기 전용으로 관리된다. 데이터 영역 프로그램이 실행되는 동안 유지되어야 하는 데이터가 저장된다. 대표적으로 전역 변수처럼 프로그램이 종료될 때까지 유지되는 데이터가 해당한다. 힙 영역 프로그래머가 직접 메모리를 할당할 수 있는 영역이다. 사용한 메모리를 제대로 반환하지 않으면 메모리 누수 가 발생할 수 있다. 스택 영역 지역 변수나 매개변수처럼 함수 실행 과정에서 잠시 사용되는 데이터가 저장된다. 힙과 스택은 어느 방향으로 자랄까? 프로세스의 메모리 구조에서는 힙과 스택이 서로 반대 방향으로 확장된다. 높은 주소 ┌─────────────┐ │ 스택 │ │ ↓ │ ├─────────────┤ │ │ │ 여유 공간 │ │ │ ├─────────────┤ │ ↑ │ │ 힙 │ ├─────────────┤ │ 데이터 │ ├─────────────┤ │ 코드 │ └─────────────┘ 낮은 주소 힙은 낮은 주소에서 높은 주소 방향으로, 스택은 높은 주소에서 낮은 주소 방향으로 쌓인다. 이렇게 서로 반대 방향으로 확장되도록 구성하면 두 영역이 효율적으로 메모리 공간을 사용할 수 있다. 6. 프로세스의 상태 프로세스는 실행되는 동안 항상 같은 상태에 머물러 있는 것이 아니다. 필요에 따라 여러 상태를 오가게 된다. 대표적인 프로세스 상태는 다음과 같다. 생성 ↓ 준비 ─────→ 실행 ─────→ 종료 ↑ │ │ │ │ ↓ └───────── 대기 생성 상태 프로세스가 이제 막 생성되어 메모리에 적재되고 PCB를 할당받은 상태다. 준비 상태 CPU를 할당받으면 바로 실행할 수 있지만, 아직 자신의 차례가 오지 않아 기다리는 상태다. 준비 상태에서 실행 상태로 넘어가는 것을 디스패치(Dispatch) 라고 한다. 실행 상태 CPU를 할당받아 실제로 실행 중인 상태다. 실행 중인 프로세스는 다음과 같은 이유로 다른 상태로 이동할 수 있다. 할당된 시간을 모두 사용함 → 준비 상태 입출력 작업이 필요함 → 대기 상태 대기 상태 입출력 작업처럼 CPU가 아닌 다른 작업이 완료되기를 기다리는 상태다. 입출력이 완료되면 다시 준비 상태로 돌아간다. 종료 상태 프로세스의 실행이 끝난 상태다. 운영체제는 프로세스의 PCB를 폐기하고 할당된 메모리를 정리한다. 7. 프로세스 계층 구조 운영체제는 프로세스를 단순히 나열해서 관리하는 것이 아니라 부모와 자식 관계 로 묶어 관리하기도 한다. 부모 프로세스 새로운 프로세스를 생성한 프로세스 자식 프로세스 부모 프로세스에 의해 생성된 프로세스 부모 프로세스 │ ├── 자식 프로세스 A │ ├── 자식 프로세스 B │ └── 자식 프로세스 C 부모 프로세스와 자식 프로세스는 서로 다른 프로세스이기 때문에 각각 다른 PID를 가진다. 일부 운영체제에서는 자식 프로세스가 자신의 부모 프로세스 PID인 PPID 를 확인할 수도 있다. 다만 운영체제마다 프로세스를 관리하는 방식에는 차이가 있으며, 원본 정리에서는 Windows는 프로세스를 계층적으로 관리하지 않는다는 차이점 을 언급하고 있다. 8. 프로세스 생성 — fork와 exec 부모 프로세스가 새로운 자식 프로세스를 만들고, 자식 프로세스가 부모와 다른 프로그램을 실행하게 만드는 과정에는 fork와 exec 시스템 호출 이 사용된다. fork fork 는 자신의 복사본을 자식 프로세스로 생성하는 시스템 호출 이다. 이 과정에서 메모리 내용이나 열린 파일 목록 등의 정보가 자식 프로세스에 상속될 수 있다. 쉽게 표현하면, fork = 부모 프로세스를 복제해서 자식 프로세스를 만든다. exec exec 는 자신의 메모리 공간을 다른 프로그램으로 교체하는 시스템 호출 이다. 이를 이용하면 자식 프로세스가 부모 프로세스와 다른 새로운 프로그램을 실행할 수 있다. 정리하면, 부모 프로세스 │ fork ↓ 자식 프로세스 │ exec ↓ 다른 프로그램 실행 즉, fork 는 복제 , exec 는 프로그램 교체 라고 기억하면 쉽다. 9. 스레드란? 이제 프로세스보다 한 단계 더 작은 실행 단위를 살펴보자. 스레드(Thread) 는 프로세스를 구성하는 실행 흐름의 단위 다. 하나의 프로세스는 하나 이상의 스레드를 가질 수 있다. 따라서 하나의 프로세스 안에서 여러 실행 흐름이 동시에 동작하도록 만들 수 있다. 예를 들어 어떤 프로그램이 다음과 같은 작업을 수행한다고 생각해보자. 프로세스 ├─ 스레드 1 → 사용자 입력 처리 ├─ 스레드 2 → 파일 처리 └─ 스레드 3 → 화면 업데이트 하나의 프로세스 안에서도 여러 작업을 나누어 처리할 수 있는 것이다. 10. 스레드는 무엇을 공유할까? 스레드는 프로세스에 속해 있기 때문에 프로세스의 자원을 공유할 수 있다. 하지만 모든 정보를 공유하는 것은 아니다. 스레드마다 독립적으로 가지는 것 스레드 ID 프로그램 카운터(PC) 레지스터 값 스택 각 스레드는 자신의 실행 흐름을 관리해야 하기 때문에 이러한 정보는 독립적으로 가지고 있어야 한다. 같은 프로세스의 스레드가 공유하는 것 코드 영역 데이터 영역 힙 영역 프로세스가 연 파일 등의 시스템 자원 즉, 스레드는 실행에 필요한 최소한의 정보는 각자 가지고, 프로세스의 자원은 공유한다. 이 구조가 멀티스레드의 중요한 특징이다. 11. 멀티프로세스와 멀티스레드 여기까지 이해했다면 두 개념을 비교해볼 수 있다. 멀티프로세스 여러 프로세스를 동시에 실행하는 방식이다. 각 프로세스는 기본적으로 독립적인 자원을 가진다. 멀티스레드 하나의 프로세스 안에서 여러 스레드를 동시에 실행하는 방식이다. 같은 프로세스에 속한 스레드들은 프로세스의 자원을 공유한다. 구분 멀티프로세스 멀티스레드 자원 공유 독립적 프로세스의 자원 공유 통신 및 협력 상대적으로 어렵고 비용이 큼 자원 공유로 상대적으로 유리 안정성 한 프로세스의 오류가 다른 프로세스에 미치는 영향이 상대적으로 적음 한 스레드의 오류가 전체 프로세스에 영향을 줄 수 있음 메모리 효율 동일 작업에서 중복 적재가 발생할 수 있음 필요한 정보만 별도로 유지하므로 상대적으로 효율적 둘 중 하나가 항상 더 좋은 것은 아니다. 각각의 구조가 가진 특징과 장단점이 있기 때문에 프로그램의 목적과 상황에 따라 적절한 방식을 선택해야 한다. 마무리 이번 글에서는 프로세스와 스레드가 무엇인지 , 그리고 운영체제가 실행 중인 프로그램을 어떻게 관리하는지 살펴봤다. 전체 흐름을 정리하면 다음과 같다. 프로그램 ↓ 실행 프로세스 ↓ PCB를 통해 운영체제가 관리 ↓ CPU를 할당받아 실행 ↓ 프로세스 상태가 계속 변화 ↓ 하나의 프로세스 안에서 여러 실행 흐름을 만들 수 있음 ↓ 스레드 특히 기억해야 할 개념을 정리하면 다음과 같다. 프로세스 → 실행 중인 프로그램 PCB → 프로세스의 정보를 저장하는 자료구조 문맥 교환 → 프로세스가 바뀔 때 상태를 저장하고 복구하는 과정 프로세스 상태 → 생성, 준비, 실행, 대기, 종료 fork → 프로세스 복제 exec → 실행할 프로그램 교체 스레드 → 프로세스를 구성하는 실행 흐름의 단위 멀티프로세스 → 여러 프로세스를 실행 멀티스레드 → 하나의 프로세스에서 여러 스레드를 실행 다음 글에서는 여러 프로세스가 CPU를 사용하려고 할 때 운영체제가 어떤 기준으로 CPU를 나누어 주는지 , 즉 CPU 스케줄링 에 대해 알아본다.
velog
코드잇 데이터분석가 부트캠프를 통해 배우고 느낀 것을 적습니다. 강의/미션/위클리페이퍼/프로젝트로 나누어 작성합니다. 📅 2026.05.20 | 🗺️ 과정: 1개월차 ▓░░░░░░ | 📖 커리큘럼: EDA 숫자로 적혀 있다고 모두 숫자가 아니라는 것을 배우며, 데이터를 계산하기 전에 의미부터 읽는 습관을 만든 하루였다. 🗓️ 오늘의 일정 교시 시간 내용 1~3교시 09:00~12:00 EDA 강의, 컬럼의 역할 나누기 4~7교시 13:00~17:00 EDA 강의, 이상치와 상관관계, 그래프 해석 8~9교시 17:00~19:00 자습 오늘은 EDA 강의를 듣고, 내일은 같은 흐름을 직접 데이터에 적용하는 미션 4를 진행한다. 1️⃣ 컬럼부터 다시 보기 1-1. dtype은 저장 방식일 뿐이다 ❓ 의문. 컬럼이 float로 저장되어 있는데 왜 연속형이 아니라고 하는지 궁금했다. 📖 배움. dtype은 값이 컴퓨터에 저장된 방식이고, 변수의 종류는 값이 가진 의미로 정한다. 흡연 여부나 청력 같은 범주형 코드가 float로 보이는 이유는 대개 결측치가 섞여 있어서 정수 대신 실수로 저장되었기 때문이다. 즉 의미는 범주인데 저장만 실수인 상태이다. 그래서 info() 를 본 다음에는 컬럼을 하나씩 보며 이 숫자가 계산해도 되는 숫자인지를 다시 물어야 한다. 💭 지금까지는 숫자형이면 평균을 내도 된다고 당연하게 생각했다. 지역 코드의 평균이나 성별 코드의 평균은 계산은 되지만 아무 뜻이 없다는 말을 듣고, 계산이 되는 것과 의미가 있는 것은 다르다는 것을 처음 실감했다. 1-2. 컬럼의 역할을 다섯 가지로 나눈다 📖 배움. 컬럼을 식별자, 명목형, 순서형, 연속형, 특수 처리 변수로 나누면 각 컬럼에 무엇을 해야 하는지가 정해진다. 식별자는 행을 구분하는 용도라서 분석에서 빼고, 명목형은 이름표로 바꿔서 빈도를 본다. 순서형은 순서를 유지한 채 이름표로 바꾸고, 연속형은 평균과 분포를 본다. 특수 처리 변수는 숫자처럼 보이지만 규칙이 숨어 있는 컬럼이다. 아래는 이 기준을 작은 설문 표에 적용해 본 예시이다. import pandas as pd survey = pd.DataFrame({ '응답번호': [101, 102, 103, 104], # 식별자 '지역코드': [11, 26, 11, 41], # 명목형, 크기에 의미가 없다 '만족도': [3, 5, 4, 2], # 순서형, 순서에는 의미가 있다 '키': [160, 172, 165, 158], # 연속형 }) # 역할별로 컬럼을 묶어 두면 이후 처리가 쉬워진다 id_cols = ['응답번호'] nominal_cols = ['지역코드'] ordinal_cols = ['만족도'] numeric_cols = ['키'] print(survey[numeric_cols].mean()) # 평균은 연속형에만 낸다 💡 역할별로 변수 목록을 먼저 만들어 두면, 나중에 상관계수나 평균을 낼 때 어떤 컬럼을 넣어야 하는지 고민이 줄어들 것 같다. 분석을 시작하기 전에 해야 할 일이 하나 생긴 셈이다. 1-3. 특수한 코드는 결측으로 바꾸고, 표시 열을 남긴다 ❓ 의문. 측정값 열에 측정이 아닌 코드가 섞여 있으면 그 행을 버려야 하는지 궁금했다. 📖 배움. 버리지 않고 두 가지로 나눈다. 먼저 그 코드가 있었다는 사실을 별도의 표시 열로 남기고, 원래 열의 특수 코드는 결측으로 바꾼다. 그러면 원래 열은 순수한 측정값만 남아서 평균과 분포를 믿을 수 있고, 표시 열로는 그 상태에 해당하는 사람이 몇 명인지를 따로 볼 수 있다. 모든 알 수 없는 값을 결측 하나로 통일하는 효과도 있어서 이후 결측 처리가 한 방식으로 정리된다. import numpy as np # 나이가 모를 때 999로 적어 둔 표라고 가정한다 people = pd.DataFrame({'나이': [31, 999, 45, 28]}) people['나이_미응답'] = np.where(people['나이'] == 999, 1, 0) # 표시 열을 먼저 남긴다 people['나이'] = people['나이'].replace(999, np.nan) # 원래 열은 결측으로 바꾼다 print(people) 💭 값을 지우는 것이 아니라 정보를 나눠서 보관한다는 발상이 신선했다. 버리지 않고 두 개의 열로 살린다는 점이 마음에 들었다. 1-4. 결측치도 역할별로 다르게 채운다 📖 배움. 수치형 측정값은 중앙값으로, 범주형 코드는 최빈값으로 채우는 것이 기본이다. 중앙값을 쓰는 이유는 극단적인 값이 있어도 평균처럼 끌려가지 않기 때문이다. 다만 채우기 전에 결측이 왜 생겼는지, 채운 값이 분석을 왜곡하지 않는지를 먼저 생각해야 한다. 💭 7일차에 결측을 채우는 방법을 배웠을 때는 방법만 외웠는데, 오늘은 어떤 컬럼에 어떤 방법을 쓰는지를 기준으로 고르는 연습이 되었다. 2️⃣ 이상치를 어떻게 볼 것인가 2-1. describe 한 번으로 읽을 수 있는 것 📖 배움. 기술통계표는 값을 훑는 표가 아니라 질문을 던지는 표이다. 평균과 중앙값이 비슷하면 분포가 비교적 대칭이라는 단서가 되고, 평균이 중앙값보다 훨씬 크면 큰 값 몇 개가 평균을 끌어올렸을 가능성을 의심한다. 최솟값과 최댓값은 상식과 맞는지 확인하는 용도이다. 사람의 허리둘레가 비현실적으로 작거나 크다면 통계 이전에 입력 오류를 먼저 의심한다. 사분위수는 전체의 가운데가 어디에 몰려 있는지를 보여 준다. 💭 표 한 장에서 읽어 낼 수 있는 것이 이렇게 많다는 것이 놀라웠다. 앞으로 describe를 출력하면 숫자를 훑지 말고 하나씩 질문하면서 읽어야겠다고 생각했다. 2-2. IQR이 표준편차보다 믿을 만한 이유 ❓ 의문. 퍼짐 정도는 표준편차로도 알 수 있는데 왜 굳이 IQR을 따로 보는지 궁금했다. 📖 배움. 표준편차는 평균을 기준으로 계산하기 때문에 극단적인 값 하나에도 크게 부풀려진다. IQR은 가운데 절반이 퍼진 폭만 보므로 양쪽 끝의 이상한 값에 흔들리지 않는다. 이런 성질을 강건하다고 부른다. 이상치를 찾는 기준으로 쓰는 것도 이 때문이다. 아래는 직접 만든 예시로 경계를 계산해 본 것이다. scores = pd.Series([62, 70, 71, 74, 75, 78, 80, 150]) # 150은 입력 실수라고 가정 q1 = scores.quantile(0.25) q3 = scores.quantile(0.75) iqr = q3 - q1 lower = q1 - 1.5 * iqr # 아래쪽 경계 upper = q3 + 1.5 * iqr # 위쪽 경계 print(scores[(scores < lower) | (scores > upper)]) # 경계 밖의 값만 확인 💭 평균은 값 하나에 흔들리고 중앙값과 IQR은 흔들리지 않는다는 비교가 와닿았다. 평균만 보고 판단하는 것이 얼마나 위험한지 알게 되었다. 2-3. 지우기 전에 진단하고 처방한다 📖 배움. 이상치 처리는 식별, 진단, 처방, 검증의 네 단계로 한다. 식별은 상식과 시각화, 통계 기준으로 후보를 찾는 단계이다. 진단은 그 값이 명백한 오류인지 현실에서 충분히 나올 수 있는 극단값인지를 가르는 단계로, 여기서 처리 방향이 갈린다. 오류라면 삭제하거나 대체한다. 극단값이라면 오히려 중요한 신호일 수 있으므로 변환, 구간화, 상한과 하한으로 누르기, 별도 분석 중에서 고른다. 마지막으로 처리 전후에 결과가 어떻게 달라졌는지, 중요한 정보를 잃지 않았는지 확인한다. 💡 IQR 경계 밖에 있다는 것만으로 지우면 안 된다는 점이 핵심이다. 혈당이 아주 높은 사람은 오류가 아니라 분석해야 할 집단일 수 있다. 이상치는 먼저 이유를 묻는 대상이라고 정리했다. 3️⃣ 상관관계와 그래프 읽기 3-1. 상관계수는 방향과 강도, 그리고 인과가 아니다 ❓ 의문. 상관계수가 높으면 한 변수가 다른 변수의 원인이라고 말해도 되는지 궁금했다. 📖 배움. 상관계수는 두 변수가 얼마나 일관되게 함께 움직이는지를 방향과 강도로 보여 줄 뿐이다. 양의 상관이면 같이 오르고, 음의 상관이면 한쪽이 오를 때 다른 쪽이 내려간다. 절댓값이 클수록 관계가 강하다. 하지만 원인과 결과를 말해 주지는 않는다. 눈에 보이지 않는 제3의 요인이 둘을 함께 움직이는 경우가 많기 때문이다. 비 오는 날 우산 판매량과 지각 건수가 함께 늘어도, 우산이 지각의 원인은 아니다. 💭 상관관계가 높다는 말을 들으면 원인이라고 읽는 습관이 있었다. 앞으로 "관련이 있다" 정도로만 쓰는 연습을 해야겠다고 느꼈다. 3-2. 산점도는 방향, 형태, 강도 순서로 읽는다 📖 배움. 산점도는 두 수치형 변수의 관계를 확인하는 가장 첫 번째 도구이다. 점들이 어느 쪽으로 뻗는지가 방향이고, 직선인지 곡선인지가 형태이고, 점들이 선 주위에 얼마나 모여 있는지가 강도이다. 점이 촘촘할수록 한 변수 값으로 다른 변수 값을 비교적 잘 짐작할 수 있다는 뜻이다. 구름처럼 흩어져 있다면 관계가 약한 것이다. 3-3. 페어플롯과 히트맵은 역할이 다르다 📖 배움. 페어플롯은 여러 변수 쌍의 산점도와 각 변수의 분포를 한 번에 보여 주므로 모양과 이상한 점을 탐색하는 용도이다. 히트맵은 상관계수를 숫자와 색으로 요약하므로 어떤 관계가 가장 센지를 비교하는 용도이다. 변수가 많아지면 페어플롯은 읽기 어려워져서 몇 개로 줄여서 쓴다. 또 둘 다 직선 관계 위주로 보기 때문에 곡선 관계는 놓칠 수 있다. import seaborn as sns import matplotlib.pyplot as plt # 변수가 많으면 페어플롯이 무거우니 일부만 뽑아서 그린다 small = survey[['키']].copy() small['몸무게'] = [52, 68, 60, 49] sns.heatmap(small.corr(), annot=True, cmap='Blues') # 숫자와 색으로 요약한다 plt.title('상관계수 히트맵 예시') plt.show() 💭 그래프를 하나만 쓰는 것이 아니라 하나는 모양을, 하나는 수치를 맡는다는 구분이 머릿속에 정리되었다. 둘을 함께 보면 서로의 약점을 채워 준다. 3-4. 지표 하나로 판단하면 놓치는 것이 있다 📖 배움. 연령대별로 여러 건강 지표를 막대그래프로 나란히 놓으면 지표마다 움직임이 다르다. 어떤 지표는 나이가 들수록 내려가고, 어떤 지표는 오르거나 높은 채로 유지된다. 한 지표만 보고 판단하면 다른 지표에서 드러나는 위험을 놓칠 수 있다는 점이 이 비교의 핵심이었다. 그래서 의사결정 기준도 하나가 아니라 여러 지표를 조합해서 세워야 한다. 💡 그래프를 읽는다는 것은 추세를 말하는 데서 끝나지 않고 "그래서 어떤 기준을 바꿔야 하는가"까지 이어져야 한다는 것을 알았다. 해석의 끝에는 결정이 있어야 한다. 3-5. 막대그래프가 못 보여 주는 것은 바이올린 플롯으로 📖 배움. 막대그래프는 평균 하나만 보여 준다. 바이올린 플롯은 같은 그룹 안에서 값이 어느 구간에 몰려 있는지, 퍼짐이 어떤지까지 보여 준다. 평균이 비슷해도 분포 모양이 다를 수 있기 때문에 평균 비교 뒤에 보조로 쓰기 좋다. 📝 오늘의 정리 주제 내가 이해한 핵심 더 공부할 점 컬럼의 역할 dtype이 아니라 의미로 변수 종류를 정한다 역할별 컬럼 목록 만들기 특수 코드 표시 열을 남기고 원래 열은 결측으로 바꾼다 코드가 여러 종류일 때 정리 결측 처리 수치형은 중앙값, 범주형은 최빈값 채우기 전에 결측 원인 보기 이상치 지우기 전에 오류인지 극단값인지 진단한다 처리 전후 비교 검증 상관관계 방향과 강도를 보되 인과로 읽지 않는다 곡선 관계 확인 방법 그래프 해석 산점도는 방향, 형태, 강도 순서로 읽는다 해석을 결정으로 연결하기 📌 다음 일차 다음 일차: 13일차 (2026-05-21) 커리큘럼: 미션 4 - 다음 강의자료 예고: 오늘 배운 EDA 흐름을 큰 데이터에 직접 적용하고, 해설과 내 코드를 비교한다.
Балл: 54.4Уверенность: 49%
Балл: 54.4Уверенность: 49%
Балл: 54.4Уверенность: 49%
Балл: 54.4Уверенность: 49%
Балл: 54.4Уверенность: 49%
Балл: 54.4Уверенность: 49%
Балл: 54.4Уверенность: 49%
Балл: 54.4Уверенность: 49%