🔥알림🔥
① 테디노트 유튜브 -
구경하러 가기!
② LangChain 한국어 튜토리얼
바로가기 👀
③ 랭체인 노트 무료 전자책(wikidocs)
바로가기 🙌
④ RAG 비법노트 LangChain 강의오픈
바로가기 🙌
⑤ 서울대 PyTorch 딥러닝 강의
바로가기 🙌
Gemini API를 활용한 이상 탐지: 임베딩으로 데이터셋 분석하기
이 글에서는 Gemini API를 사용하여 데이터셋에서 이상치를 탐지하는 방법을 알아봅니다. 그리고 임베딩과 t-SNE를 활용한 데이터 시각화 및 이상치 탐지 과정을 단계별로 설명합니다.
Reference
본 튜토리얼은 Google Gemini Pro API 도큐먼트 문서를 참조하여 작성하였습니다.
- 본 문서의 원 저작권자는 2023 Google LLC. 이며, 코드는
Apache License, Version 2.0
에 따라 사용이 허가된 파일입니다. 라이센스는 하단에 고지하였습니다. - 원문 바로가기
실습파일
요약
주요내용
- 📊 Gemini API와 임베딩: 데이터셋에서 이상치를 탐지하는 방법 소개
- 🔍 차원 축소와 시각화: t-SNE를 이용한 데이터 시각화 및 클러스터링
- 🚀 이상치 탐지: 중심점과 유클리드 거리를 활용하여 이상치 식별
개요
이 튜토리얼은 Gemini API에서 제공하는 임베딩을 사용하여 데이터셋에서 잠재적 이상치를 감지하는 방법을 보여줍니다. t-SNE를 사용하여 20 뉴스그룹 데이터셋의 일부를 시각화하고 각 범주별 클러스터의 중심점으로부터 특정 반경 밖에 있는 이상치를 감지합니다.
사전 요구 사항
-
Python 3.9+
-
노트북을 실행하기 위한
jupyter
의 설치 혹은Google Colab
활용
설정
먼저, Gemini API Python 라이브러리를 다운로드하고 설치하세요.
# google.generativeai 라이브러리를 설치하는 명령어입니다.
!pip install -U -q google.generativeai
필요한 모듈을 import 합니다.
import re
import tqdm
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import google.generativeai as genai
import google.ai.generativelanguage as glm
from sklearn.datasets import fetch_20newsgroups
from sklearn.manifold import TSNE
API 키 받기
Gemini API를 사용하기 전에 먼저 API 키를 받아야 합니다. 아직 없다면 Google AI Studio에서 한 번의 클릭으로 키를 생성하세요.
Google Colab 을 사용한다면…
- Colab에서, 왼쪽 패널의 “🔑” 아래에 있는 비밀 관리자에 키를 추가하세요. 이름은
API_KEY
로 지정합니다.
API 키를 받았다면, SDK에 전달하세요. 두 가지 방법이 있습니다:
-
키를
GOOGLE_API_KEY
환경 변수에 넣으세요(SDK가 자동으로 거기서 가져옵니다). -
genai.configure(api_key=...)
에 키를 전달하세요.
# 환경 변수에서 'API_KEY'를 가져오거나, 사용자 데이터에서 'API_KEY'를 가져옵니다.
API_KEY = "API_KEY 입력"
# genai 라이브러리를 사용하기 위해 API 키를 설정합니다.
genai.configure(api_key=API_KEY)
다음으로, 모델을 선택하게 됩니다. 이 튜토리얼에는 어떤 임베딩 모델도 사용할 수 있지만, 실제 애플리케이션에서는 특정 모델을 선택하고 그것을 고수하는 것이 중요합니다. 서로 다른 모델의 출력은 호환되지 않습니다.
참고: 현재 Gemini API는 일부 지역에서만 사용 가능합니다.
genai
객체의 list_models
메소드를 사용하여 사용 가능한 모델들을 리스트로 받아온 후, 각 모델이 ‘embedContent’라는 생성 메소드를 지원하는지 확인합니다.
‘embedContent’ 메소드를 지원하는 모델의 이름만 출력 합니다.
# genai의 모든 모델을 순회하는 반복문
for m in genai.list_models():
# 모델이 지원하는 생성 메소드 중 'embedContent'가 포함되어 있는지 확인
if "embedContent" in m.supported_generation_methods:
# 조건을 만족하는 모델의 이름을 출력
print(m.name)
models/embedding-001
데이터셋 준비하기
20 뉴스그룹 텍스트 데이터셋은 20개 주제에 대한 18,000개의 뉴스그룹 게시물을 포함하고 있으며, 훈련 세트와 테스트 세트로 나뉩니다. 훈련 및 테스트 데이터셋 간의 분할은 특정 날짜 이전과 이후에 게시된 메시지를 기준으로 합니다. 이 튜토리얼에서는 훈련용 부분집합을 사용합니다.
이 코드는 fetch_20newsgroups
함수를 사용하여 뉴스 그룹 데이터 세트의 훈련 부분을 가져오는 것입니다. 가져온 데이터에서 target_names
속성을 사용하여 데이터 세트에 포함된 클래스(뉴스 그룹 카테고리)의 이름 목록을 확인할 수 있습니다.
from sklearn.datasets import fetch_20newsgroups
# 훈련 데이터 세트를 가져옵니다.
newsgroups_train = fetch_20newsgroups(subset="train")
# 데이터 세트의 클래스 이름 목록을 확인합니다.
newsgroups_train.target_names
['alt.atheism', 'comp.graphics', 'comp.os.ms-windows.misc', 'comp.sys.ibm.pc.hardware', 'comp.sys.mac.hardware', 'comp.windows.x', 'misc.forsale', 'rec.autos', 'rec.motorcycles', 'rec.sport.baseball', 'rec.sport.hockey', 'sci.crypt', 'sci.electronics', 'sci.med', 'sci.space', 'soc.religion.christian', 'talk.politics.guns', 'talk.politics.mideast', 'talk.politics.misc', 'talk.religion.misc']
여기 훈련 세트의 첫 번째 예시가 있습니다.
주어진 코드는 newsgroups_train
데이터셋의 첫 번째 문서에서 ‘Lines’라는 단어가 시작하는 위치를 찾아 그 위치부터 문서의 끝까지의 내용을 출력하는 코드입니다.
만약 ‘Lines’라는 단어가 없다면, index
함수는 오류를 발생시킬 것입니다.
# 주어진 코드에 대한 설명을 추가하고 결과를 반환합니다.
# newsgroups_train 데이터셋에서 첫 번째 문서를 가져옵니다.
# 'Lines'라는 단어가 나타나는 첫 번째 인덱스를 찾습니다.
idx = newsgroups_train.data[0].index("Lines")
# 'Lines' 단어부터 문서의 끝까지 내용을 출력합니다.
print(newsgroups_train.data[0][idx:])
Lines: 15 I was wondering if anyone out there could enlighten me on this car I saw the other day. It was a 2-door sports car, looked to be from the late 60s/ early 70s. It was called a Bricklin. The doors were really small. In addition, the front bumper was separate from the rest of the body. This is all I know. If anyone can tellme a model name, engine specs, years of production, where this car is made, history, or whatever info you have on this funky looking car, please e-mail. Thanks, - IL ---- brought to you by your neighborhood Lerxst ----
newsgroups_train.data
에 있는 각 텍스트 데이터에서 이메일 주소, 괄호 안의 이름, 그리고 특정 키워드(‘From: ‘, ‘\nSubject: ‘)를 제거합니다.
또한, 각 텍스트가 5,000자를 초과하는 경우, 초과하는 부분을 잘라내어 5,000자로 제한합니다.
# newsgroups.data의 데이터 포인트에서 이름, 이메일, 그리고 불필요한 단어들을 제거하는 함수를 적용
newsgroups_train.data = [
re.sub(r"[\w\.-]+@[\w\.-]+", "", d) for d in newsgroups_train.data
] # 이메일 제거
newsgroups_train.data = [
re.sub(r"\([^()]*\)", "", d) for d in newsgroups_train.data
] # 이름 제거
newsgroups_train.data = [
d.replace("From: ", "") for d in newsgroups_train.data
] # 'From: ' 제거
newsgroups_train.data = [
d.replace("\nSubject: ", "") for d in newsgroups_train.data
] # '\nSubject: ' 제거
# 각 텍스트 항목을 5,000자 이후로 잘라냄
newsgroups_train.data = [
d[0:5000] if len(d) > 5000 else d for d in newsgroups_train.data
]
텍스트 분류를 위한 훈련 데이터를 준비하는 과정입니다.
먼저 pd.DataFrame
을 사용하여 텍스트 데이터를 포함하는 데이터프레임 df_train
을 생성합니다. 그 다음, 각 텍스트에 해당하는 레이블을 ‘Label’ 열에 추가합니다.
마지막으로, map
함수와 newsgroups_train.target_names.__getitem__
을 사용하여 각 레이블에 해당하는 실제 클래스 이름을 ‘Class Name’ 열에 매핑합니다.
# 훈련 데이터 포인트를 데이터프레임에 넣기
# 'Text'라는 이름의 열로 새 데이터프레임을 생성하고, newsgroups_train.data를 할당
# 'Label' 열을 추가하고, newsgroups_train.target을 할당
# 'Class Name' 열을 추가하고, 'Label'의 각 값에 대해 newsgroups_train.target_names에서 해당하는 이름을 매핑
# 훈련 데이터를 데이터프레임으로 변환
# 'Text' 열에는 텍스트 데이터가, 'Label' 열에는 해당 텍스트의 레이블(숫자)가 들어감
# 'Class Name' 열에는 'Label'에 해당하는 실제 클래스 이름이 매핑되어 들어감
# 최종적으로 데이터프레임 df_train에는 텍스트, 레이블, 클래스 이름이 포함됨
df_train = pd.DataFrame(newsgroups_train.data, columns=["Text"])
df_train["Label"] = newsgroups_train.target
df_train["Class Name"] = df_train["Label"].map(
newsgroups_train.target_names.__getitem__
)
df_train
Text | Label | Class Name | |
---|---|---|---|
0 | WHAT car is this!?\nNntp-Posting-Host: rac3.w... | 7 | rec.autos |
1 | SI Clock Poll - Final Call\nSummary: Final ca... | 4 | comp.sys.mac.hardware |
2 | PB questions...\nOrganization: Purdue Univers... | 4 | comp.sys.mac.hardware |
3 | Re: Weitek P9000 ?\nOrganization: Harris Comp... | 1 | comp.graphics |
4 | Re: Shuttle Launch Question\nOrganization: Sm... | 14 | sci.space |
... | ... | ... | ... |
11309 | Re: Migraines and scans\nDistribution: world... | 13 | sci.med |
11310 | Screen Death: Mac Plus/512\nLines: 22\nOrganiz... | 4 | comp.sys.mac.hardware |
11311 | Mounting CPU Cooler in vertical case\nOrganiz... | 3 | comp.sys.ibm.pc.hardware |
11312 | Re: Sphere from 4 points?\nOrganization: Cent... | 1 | comp.graphics |
11313 | stolen CBR900RR\nOrganization: California Ins... | 8 | rec.motorcycles |
11314 rows × 3 columns
다음으로, 훈련 데이터셋에서 150개의 데이터 포인트를 샘플링하고 몇 가지 카테고리를 선택해 보겠습니다. 이 튜토리얼에서는 과학 카테고리를 사용합니다.
이 코드는 데이터프레임 df_train
에서 특정 작업을 수행하는 과정을 보여줍니다.
먼저, groupby
함수를 사용하여 ‘Label’ 열을 기준으로 데이터를 그룹화하고, 각 그룹에서 SAMPLE_SIZE
만큼의 샘플을 추출합니다. 이때 apply
함수와 lambda
함수를 사용하여 각 그룹별로 샘플링을 수행합니다. 그 후, reset_index
함수를 사용하여 데이터프레임의 인덱스를 재설정합니다.
다음으로, ‘Class Name’ 열에서 ‘sci’ 문자열을 포함하는 행만을 선택하여 과학 관련 카테고리만을 필터링합니다.
마지막으로, reset_index
함수를 다시 호출하여 최종 데이터프레임의 인덱스를 리셋하고, 이전 인덱스는 새로운 열로 추가됩니다.
# df_train에서 각 레이블 카테고리별로 샘플을 추출합니다
SAMPLE_SIZE = 150
# groupby를 통해 'Label' 기준으로 그룹화하고, 각 그룹별로 SAMPLE_SIZE 만큼 샘플을 추출한 후 인덱스를 리셋합니다
# as_index=False는 그룹화 후에도 기존 인덱스를 유지하지 않도록 합니다
# apply 함수를 사용하여 각 그룹에 대해 lambda 함수를 적용합니다
# lambda 함수 내에서 sample 함수를 사용하여 샘플을 추출합니다
# reset_index 함수를 사용하여 샘플 추출 후의 데이터프레임의 인덱스를 리셋합니다
df_train = (
df_train.groupby("Label", as_index=False)
.apply(lambda x: x.sample(SAMPLE_SIZE))
.reset_index(drop=True)
)
# 과학과 관련된 카테고리를 선택합니다
# 'Class Name' 열에서 'sci' 문자열을 포함하는 행만을 필터링하여 df_train을 업데이트합니다
df_train = df_train[df_train["Class Name"].str.contains("sci")]
# 인덱스를 리셋합니다
# reset_index 함수를 호출하여 새로운 인덱스를 부여하고, 기존 인덱스는 'index'라는 새로운 열로 추가됩니다
df_train = df_train.reset_index()
# 최종적으로 처리된 데이터프레임 df_train을 출력합니다
df_train
index | Text | Label | Class Name | |
---|---|---|---|---|
0 | 1650 | Re: Secret algorithm [Re: Clipper Chip and cr... | 11 | sci.crypt |
1 | 1651 | Re: Why the algorithm is secret\nOrganization... | 11 | sci.crypt |
2 | 1652 | Re: I have seen the lobby, and it is us\nNntp... | 11 | sci.crypt |
3 | 1653 | Graham Toal <>Re: Hard drive security for FBI ... | 11 | sci.crypt |
4 | 1654 | Re: Would "clipper" make a good cover for oth... | 11 | sci.crypt |
... | ... | ... | ... | ... |
595 | 2245 | Re: Vandalizing the sky.\nOrganization: Expre... | 14 | sci.space |
596 | 2246 | Space FAQ 08/15 - Addresses\nSupersedes: <>\n... | 14 | sci.space |
597 | 2247 | Re: Vulcan? \nDistribution: na\nOrganization:... | 14 | sci.space |
598 | 2248 | Re: nuclear waste\nOrganization: Ryukoku Univ... | 14 | sci.space |
599 | 2249 | Conference on Manned Lunar Exploration. May ... | 14 | sci.space |
600 rows × 4 columns
이 코드는 df_train
데이터프레임의 "Class Name"
열에 있는 각각의 고유한 값들이 몇 번 등장하는지를 세는 기능을 합니다.
# 주어진 코드에 대한 주석을 추가하고 결과를 반환합니다.
# 'Class Name' 열의 각 고유 값의 빈도수를 계산합니다.
df_train["Class Name"].value_counts()
Class Name sci.crypt 150 sci.electronics 150 sci.med 150 sci.space 150 Name: count, dtype: int64
임베딩 생성하기
이 섹션에서는 데이터프레임의 다양한 텍스트에 대해 Gemini API의 임베딩을 사용하여 임베딩을 생성하는 방법을 알아볼 것입니다.
임베딩 모델 embedding-001의 API 변경 사항
새로운 임베딩 모델인 embedding-001에는 새로운 작업 유형 매개변수와 선택적 제목(단, RETRIEVAL_DOCUMENT
작업 유형에만 유효)이 있습니다.
이 새로운 매개변수들은 가장 최신의 임베딩 모델에만 적용됩니다.
작업 유형 | 설명 |
---|---|
RETRIEVAL_QUERY | 주어진 텍스트가 검색/검색 설정에서 쿼리임을 지정합니다. |
RETRIEVAL_DOCUMENT | 주어진 텍스트가 검색/검색 설정에서 문서임을 지정합니다. |
SEMANTIC_SIMILARITY | 주어진 텍스트가 의미론적 텍스트 유사성(STS)에 사용될 것임을 지정합니다. |
CLASSIFICATION | 임베딩이 분류에 사용될 것임을 지정합니다. |
CLUSTERING | 임베딩이 클러스터링에 사용될 것임을 지정합니다. |
다음의 코드는 텍스트 데이터를 벡터로 변환하는 과정을 담고 있습니다.
make_embed_text_fn
함수는 모델을 인자로 받아 텍스트를 임베딩하는 함수를 반환합니다. 이 함수는 내부적으로 genai.embed_content
를 사용하여 주어진 텍스트에 대한 임베딩을 생성하고, 이를 numpy 배열로 변환하여 반환합니다.
retry.Retry
데코레이터를 사용하여 임베딩 함수가 실패할 경우 재시도할 수 있도록 설정되어 있습니다.
create_embeddings
함수는 데이터프레임을 받아 각 텍스트에 대해 임베딩을 수행하고, 결과를 ‘Embeddings’라는 새로운 열에 저장합니다.
마지막으로, df_train
데이터프레임에 create_embeddings
함수를 적용하고, 불필요한 ‘index’ 열을 제거합니다.
from google.api_core import retry
from tqdm.auto import tqdm
tqdm.pandas()
def make_embed_text_fn(model):
@retry.Retry(timeout=300.0)
def embed_fn(text: str) -> list[float]:
# CLUSTERING 작업 유형을 설정합니다.
embedding = genai.embed_content(
model=model, content=text, task_type="clustering"
)["embedding"]
return np.array(embedding)
return embed_fn
def create_embeddings(df):
model = "models/embedding-001"
df["Embeddings"] = df["Text"].progress_apply(make_embed_text_fn(model))
return df
df_train = create_embeddings(df_train)
df_train.drop("index", axis=1, inplace=True)
0%| | 0/600 [00:00<?, ?it/s]
차원 축소
문서 임베딩 벡터의 차원은 768차원 입니다. 임베딩된 문서들이 어떻게 그룹화되어 있는지 시각화하기 위해서는 차원 축소를 적용해야 합니다. 왜냐하면 임베딩을 2D 또는 3D 공간에서만 시각화할 수 있기 때문입니다. 문맥적으로 유사한 문서들은 공간상에서 서로 가까이 위치해야 하며, 그렇지 않은 문서들과는 멀어져야 합니다.
주어진 코드는 df_train
이라는 데이터프레임에서 ‘Embeddings’라는 컬럼의 첫 번째 요소의 길이를 구하는 코드입니다.
여기서 ‘Embeddings’ 컬럼은 임베딩 벡터를 포함하고 있을 것으로 추정되며, 이 코드는 해당 벡터의 차원 수를 확인하는 데 사용됩니다.
# 주어진 코드에 대한 주석을 추가하고 결과를 반환합니다.
# df_train 데이터프레임의 'Embeddings' 컬럼의 첫 번째 요소의 길이를 반환합니다.
len(df_train["Embeddings"][0])
768
df_train
데이터프레임의 ‘Embeddings’ 열에 있는 데이터를 리스트로 변환한 후, 이를 numpy
배열(np.array
)로 만드는 과정입니다. 여기서 중요한 점은, 배열의 데이터 타입을 float32
로 지정한다는 것입니다. 데이터 타입을 명시적으로 지정하는 것은 메모리 효율성을 높이고, 계산 속도를 개선하기 위해 중요합니다.
# df_train['Embeddings'] 판다스 시리즈를 float32 타입의 np.array로 변환합니다.
X = np.array(df_train["Embeddings"].to_list(), dtype=np.float32)
X.shape
(600, 768)
t-Distributed Stochastic Neighbor Embedding(t-SNE) 접근 방식을 사용하여 차원 축소를 수행할 것입니다. 이 기술은 차원의 수를 줄이면서 클러스터(가까이 있는 점들은 가까이 있게 유지됨)를 보존합니다.
원본 데이터에 대해 모델은 다른 데이터 포인트가 ‘이웃’(예: 유사한 의미를 공유함)인 분포를 구성하려고 시도합니다. 그런 다음 시각화에서 유사한 분포를 유지하기 위해 목적 함수를 최적화합니다.
주어진 코드는 고차원 데이터를 저차원으로 변환하기 위해 t-SNE(t-distributed Stochastic Neighbor Embedding) 알고리즘을 사용합니다. TSNE
클래스를 이용하여 모델을 초기화하고, fit_transform
메소드를 사용하여 데이터 X
를 저차원으로 매핑하는 과정을 수행합니다.
# TSNE 모델을 초기화합니다. 여기서 random_state는 결과의 재현성을 위해 설정됩니다.
tsne = TSNE(random_state=0, n_iter=1000)
# TSNE를 사용하여 데이터 X를 변환합니다. 이 결과는 저차원의 특징 공간으로 매핑됩니다.
tsne_results = tsne.fit_transform(X)
이 코드는 t-SNE 알고리즘을 통해 차원 축소된 결과를 데이터프레임으로 만들고, 각 데이터 포인트의 클래스 정보를 포함시키는 과정을 보여줍니다.
pd.DataFrame(tsne_results, columns=["TSNE1", "TSNE2"])
를 통해 t-SNE 결과를 데이터프레임으로 변환하고, df_tsne["Class Name"] = df_train["Class Name"]
를 통해 원본 데이터셋의 클래스 레이블을 추가합니다. 이렇게 하면 시각화나 분석을 할 때 각 점이 어떤 클래스에 속하는지 쉽게 구별할 수 있습니다.
import pandas as pd
# t-SNE 결과를 데이터프레임으로 변환하고, 컬럼 이름을 'TSNE1', 'TSNE2'로 지정합니다.
df_tsne = pd.DataFrame(tsne_results, columns=["TSNE1", "TSNE2"])
# df_train에서 'Class Name' 컬럼을 df_tsne에 추가합니다. 이렇게 하면 각 데이터 포인트가 어떤 클래스에 속하는지 알 수 있습니다.
df_tsne["Class Name"] = df_train["Class Name"]
# df_tsne 데이터프레임을 출력합니다.
df_tsne
TSNE1 | TSNE2 | Class Name | |
---|---|---|---|
0 | 42.460457 | 9.653869 | sci.crypt |
1 | 53.631695 | 16.949556 | sci.crypt |
2 | 41.523510 | 20.939377 | sci.crypt |
3 | 26.323389 | 13.133236 | sci.crypt |
4 | 48.963657 | 9.673144 | sci.crypt |
... | ... | ... | ... |
595 | -16.323051 | -10.877371 | sci.space |
596 | -2.706111 | -28.230089 | sci.space |
597 | -19.178242 | -33.432446 | sci.space |
598 | -7.234779 | -10.983491 | sci.space |
599 | -3.420572 | -23.735949 | sci.space |
600 rows × 3 columns
이 코드는 t-SNE(t-distributed Stochastic Neighbor Embedding) 기법을 사용하여 뉴스 데이터를 2차원 공간에 시각화하는 산점도를 그리는 과정입니다.
plt.subplots
를 사용하여 그래프의 크기를 설정하고, sns.set_style
로 그리드 스타일을 지정합니다. sns.scatterplot
함수는 t-SNE로 차원 축소된 데이터 포인트들을 산점도로 나타내며, hue
인자를 통해 각 클래스별로 다른 색상을 적용합니다. sns.move_legend
함수는 범례의 위치를 조정하고, plt.title
, plt.xlabel
, plt.ylabel
을 통해 그래프의 제목과 축 라벨을 설정합니다.
t-SNE는 고차원 데이터를 시각적으로 이해하기 쉬운 2차원으로 변환하는데 유용한 기법입니다.
# 한글 폰트 적용
plt.rcParams["font.family"] = "AppleGothic"
plt.rcParams["axes.unicode_minus"] = False
# t-SNE를 사용한 뉴스 데이터의 산점도를 그리기 위한 설정
fig, ax = plt.subplots(figsize=(8, 6)) # 그래프의 크기를 설정합니다
# 스타일을 'darkgrid'로 설정하고, 그리드의 색상과 스타일을 지정합니다
sns.set_style("darkgrid", {"grid.color": ".6", "grid.linestyle": ":"})
# t-SNE 결과를 바탕으로 산점도를 그립니다. 각 클래스별로 색상을 구분합니다
sns.scatterplot(data=df_tsne, x="TSNE1", y="TSNE2", hue="Class Name", palette="Set2")
# 범례를 그래프의 왼쪽 상단으로 이동시킵니다
sns.move_legend(ax, "upper left", bbox_to_anchor=(1, 1))
# 그래프의 제목을 설정합니다
plt.title("t-SNE를 사용한 뉴스 데이터의 산점도")
# x축의 라벨을 설정합니다
plt.xlabel("TSNE1")
# y축의 라벨을 설정합니다
plt.ylabel("TSNE2")
Text(0, 0.5, 'TSNE2')
이상치 탐지
어떤 점들이 이상한지를 결정하기 위해서, 우리는 이상치와 정상치를 구분할 것입니다. 클러스터의 중심을 나타내는 중심점을 찾아 시작하고, 거리를 사용하여 어떤 점들이 이상치인지를 결정합니다.
각 카테고리의 중심점을 구하는 것부터 시작합니다.
다음의 코드는 데이터프레임 df_tsne
에서 각 클러스터의 중심(centroid)을 구하는 함수 get_centroids
를 정의하고 있습니다. 함수는 df_tsne
를 “Class Name” 열을 기준으로 그룹화한 후, 각 그룹의 평균을 계산하여 클러스터의 중심을 찾습니다. 이렇게 계산된 중심들은 centroids
변수에 저장되며, 이 변수는 함수의 결과로 반환됩니다.
def get_centroids(df_tsne):
# 각 클러스터의 중심(centroid)를 구합니다.
centroids = df_tsne.groupby("Class Name").mean()
return centroids
centroids = get_centroids(df_tsne)
centroids
TSNE1 | TSNE2 | |
---|---|---|
Class Name | ||
sci.crypt | 39.034645 | 13.662293 |
sci.electronics | 11.423505 | -3.115122 |
sci.med | -31.671881 | 1.985848 |
sci.space | -12.552220 | -22.125948 |
함수 get_embedding_centroids
는 데이터프레임을 입력으로 받아, 각 클래스별로 임베딩 벡터들의 평균값(중심값)을 계산합니다. 이 함수는 먼저 데이터프레임을 ‘Class Name’ 열을 기준으로 그룹화하고, 각 그룹에 대해 임베딩 벡터들의 평균을 구하여 사전 형태로 반환합니다. 임베딩 벡터들이 768차원이라는 것이며, np.mean
함수를 사용하여 각 차원별 평균값을 계산합니다.
이렇게 계산된 중심값은 머신러닝 모델에서 클래스를 대표하는 벡터로 사용될 수 있습니다.
import numpy as np
# 주어진 데이터프레임에서 각 클래스의 임베딩 중심값을 계산하는 함수입니다.
def get_embedding_centroids(df):
emb_centroids = dict() # 클래스 이름을 키로 하고, 중심값을 값으로 하는 사전을 초기화합니다.
grouped = df.groupby("Class Name") # 데이터프레임을 'Class Name' 열 기준으로 그룹화합니다.
for c in grouped.groups: # 그룹화된 각 클래스에 대해 반복합니다.
sub_df = grouped.get_group(c) # 현재 클래스에 해당하는 데이터만 선택합니다.
# 768차원의 중심값을 계산합니다.
emb_centroids[c] = np.mean(sub_df["Embeddings"], axis=0)
return emb_centroids # 계산된 중심값을 담은 사전을 반환합니다.
get_embedding_centroids
함수를 사용하여 df_train
데이터셋의 임베딩 중심값을 계산하고 그 결과를 emb_c
변수에 저장합니다.
임베딩 중심값(embedding centroids)이란 데이터셋의 각 범주(또는 클러스터)에 대해 임베딩 벡터들의 평균을 계산한 것을 의미합니다.
# df_train 데이터셋에서 임베딩 중심값을 계산하는 함수를 호출하고 결과를 emb_c 변수에 저장한다.
emb_c = get_embedding_centroids(df_train)
t-SNE 기법을 사용하여 뉴스 데이터를 2차원으로 축소한 후, 각 뉴스가 속한 클러스터의 중심점과 함께 산점도를 그리는 과정입니다.
# 한글 폰트 적용
plt.rcParams["font.family"] = "AppleGothic"
plt.rcParams["axes.unicode_minus"] = False
# 클러스터에 대한 중심점을 플롯합니다
fig, ax = plt.subplots(figsize=(8, 6)) # 그림 크기 설정
sns.set_style("darkgrid", {"grid.color": ".6", "grid.linestyle": ":"})
sns.scatterplot(data=df_tsne, x="TSNE1", y="TSNE2", hue="Class Name", palette="Set2")
sns.scatterplot(
data=centroids,
x="TSNE1",
y="TSNE2",
color="black",
marker="X",
s=100,
label="Centroids",
)
sns.move_legend(ax, "upper left", bbox_to_anchor=(1, 1))
plt.title("t-SNE를 사용한 뉴스의 산점도 및 중심점")
plt.xlabel("TSNE1")
plt.ylabel("TSNE2")
Text(0, 0.5, 'TSNE2')
적합한 반지름의 길이를 찾습니다. 이 범위를 벗어나는 모든 것은 해당 카테고리의 중심점으로부터 이상치로 간주됩니다.
이 코드는 데이터프레임 내의 각 데이터 포인트가 주어진 클래스의 중심점으로부터 얼마나 떨어져 있는지를 계산하여, 특정 반지름 이상 떨어진 데이터 포인트를 이상치로 감지하는 기능을 합니다.
calculate_euclidean_distance
함수는 두 점 사이의 유클리드 거리를 계산하고, detect_outlier
함수는 각 데이터 포인트가 해당 클래스의 중심점으로부터 주어진 반지름보다 더 멀리 떨어져 있는지를 확인하여 이상치 여부를 결정합니다.
이상치로 판단된 데이터 포인트의 수를 반환합니다.
import numpy as np
# 유클리드 거리를 계산하는 함수 정의
# 두 점 p1과 p2 사이의 유클리드 거리를 계산하여 반환합니다.
def calculate_euclidean_distance(p1, p2):
return np.sqrt(np.sum(np.square(p1 - p2)))
# 이상치를 감지하는 함수 정의
# 데이터프레임 df와 각 클래스의 중심점을 나타내는 emb_centroids, 그리고 기준이 되는 반지름 radius를 인자로 받습니다.
def detect_outlier(df, emb_centroids, radius):
for idx, row in df.iterrows():
class_name = row["Class Name"] # 각 행의 클래스 이름을 가져옵니다.
# 중심점과의 거리를 비교합니다.
dist = calculate_euclidean_distance(
row["Embeddings"], emb_centroids[class_name]
)
df.at[idx, "Outlier"] = dist > radius # 거리가 radius보다 크면 이상치로 판단합니다.
return len(df[df["Outlier"] == True]) # 이상치의 총 개수를 반환합니다.
numpy
라이브러리를 사용하여 특정 범위 내에서 일정 간격으로 숫자를 생성하고, 이 숫자들을 이용하여 데이터프레임 df_train
의 emb_c
컬럼에서 이상치를 감지하는 작업을 수행합니다.
detect_outlier
함수는 데이터프레임, 컬럼명, 그리고 이상치를 판단할 기준값을 인자로 받아 이상치의 개수를 반환합니다. 이 과정을 통해 다양한 기준값에 따른 이상치의 개수를 분석할 수 있습니다.
# numpy 라이브러리를 np라는 이름으로 가져옵니다.
import numpy as np
# 0.3부터 0.75까지 0.02 간격으로 배열을 생성하고, 소수점 둘째자리까지 반올림한 후 리스트로 변환합니다.
range_ = np.arange(0.3, 0.75, 0.02).round(decimals=2).tolist()
# 이상치의 개수를 저장할 리스트를 초기화합니다.
num_outliers = []
# 생성된 범위(range_) 내의 각 값에 대해 반복합니다.
for i in range_:
# detect_outlier 함수를 사용하여 df_train 데이터프레임의 emb_c 컬럼에서
# i 값을 기준으로 이상치를 감지하고, 그 개수를 num_outliers 리스트에 추가합니다.
num_outliers.append(detect_outlier(df_train, emb_c, i))
데이터의 중심점으로부터의 거리(range_
)와 각 거리에서 발견된 이상치의 수(num_outliers
)를 막대 그래프로 시각화합니다.
# 한글 폰트 적용
plt.rcParams["font.family"] = "AppleGothic"
plt.rcParams["axes.unicode_minus"] = False
# 중심점으로부터의 거리와 이상치의 수를 막대 그래프로 표시합니다.
fig = plt.figure(figsize=(14, 8))
plt.rcParams.update({"font.size": 12})
plt.bar(list(map(str, range_)), num_outliers)
plt.title("중심점으로부터의 거리 대비 이상치의 수")
plt.xlabel("거리")
plt.ylabel("이상치의 수")
for i in range(len(range_)):
plt.text(i, num_outliers[i], num_outliers[i], ha="center")
plt.show()
이상치 탐지기의 민감도를 얼마나 높게 설정하고 싶은지에 따라 사용할 반경을 선택할 수 있습니다. 현재는 0.62를 사용하지만 이 값을 변경할 수 있습니다.
다음의 코드는 데이터셋에서 이상치를 탐지하고, 탐지된 이상치를 확인하기 위한 과정을 담고 있습니다.
먼저 RADIUS
변수를 설정하여 이상치 탐지에 사용될 반경을 정의합니다. detect_outlier
함수는 df_train
데이터셋과 emb_c
, RADIUS
를 인자로 받아 이상치를 탐지하고, 결과를 df_train
데이터셋에 ‘Outlier’라는 컬럼으로 표시합니다.
이후 ‘Outlier’ 컬럼이 True
인 데이터만을 필터링하여 df_outliers
라는 새로운 데이터프레임을 생성하고, head
메소드를 사용하여 그 중 상위 5개 행을 출력합니다.
# 이상치를 확인하는 코드
RADIUS = 0.62 # 이상치 탐지에 사용될 반경을 설정합니다.
detect_outlier(
df_train, emb_c, RADIUS
) # df_train 데이터셋에서 emb_c를 기준으로 RADIUS 반경을 사용하여 이상치를 탐지합니다.
df_outliers = df_train[
df_train["Outlier"] == True
] # 'Outlier' 컬럼이 True인 데이터만을 필터링하여 이상치 데이터셋을 생성합니다.
df_outliers.head() # 이상치 데이터셋의 상위 5개 행을 출력합니다.
Text | Label | Class Name | Embeddings | Outlier | |
---|---|---|---|---|---|
6 | Marc VanHeyningen <>RIPEM Frequently Asked Que... | 11 | sci.crypt | [0.016936606, -0.03775989, -0.031252615, -0.02... | True |
30 | Cryptography FAQ 03/10 - Basic Cryptology\nOrg... | 11 | sci.crypt | [0.023759395, -0.035561457, -0.053390905, -0.0... | True |
43 | Re: Source of random bits on a Unix workstati... | 11 | sci.crypt | [0.024040421, -0.06749866, -0.097997, -0.04392... | True |
44 | List of large integer arithmetic packages\nOr... | 11 | sci.crypt | [0.015404858, -0.021492945, -0.09030825, -0.02... | True |
45 | Re: List of large integer arithmetic packages... | 11 | sci.crypt | [-0.0055461614, 0.0061755483, -0.060710818, -0... | True |
df_tsne
데이터프레임에서 이상치를 식별한 인덱스를 사용하여 해당 이상치들이 프로젝트된 TSNE 점들을 찾습니다. df_outliers['Outlier'].index
를 통해 이상치의 인덱스를 얻고, 이를 df_tsne.loc[]
에 전달하여 이상치에 해당하는 TSNE 점들을 outliers_projected
변수에 저장합니다.
# TSNE 점들에 대응하는 이상치(outlier) 점들을 인덱스를 사용하여 매핑합니다.
outliers_projected = df_tsne.loc[df_outliers["Outlier"].index]
이상치를 플롯하고 투명한 빨간색으로 표시합니다.
t-SNE 기법을 사용하여 차원이 축소된 뉴스 데이터를 시각화하는 산점도를 그리는 과정입니다.
# 한글 폰트 적용
plt.rcParams["font.family"] = "AppleGothic"
plt.rcParams["axes.unicode_minus"] = False
# 그래프와 축을 설정합니다 (가로 8인치, 세로 6인치 크기로)
fig, ax = plt.subplots(figsize=(8, 6))
# 폰트 크기를 설정합니다.
plt.rcParams.update({"font.size": 10})
# 스타일을 'darkgrid'로 설정하고, 그리드 색상과 스타일을 지정합니다.
sns.set_style("darkgrid", {"grid.color": ".6", "grid.linestyle": ":"})
# t-SNE로 차원 축소된 데이터를 산점도로 그립니다. 클래스 이름에 따라 색상을 구분합니다.
sns.scatterplot(data=df_tsne, x="TSNE1", y="TSNE2", hue="Class Name", palette="Set2")
# 중심점을 'X' 표시로 산점도에 추가합니다.
sns.scatterplot(
data=centroids,
x="TSNE1",
y="TSNE2",
color="black",
marker="X",
s=100,
label="Centroids",
)
# 이상치를 빨간색 원으로 표시하여 산점도에 추가합니다.
sns.scatterplot(
data=outliers_projected,
x="TSNE1",
y="TSNE2",
color="red",
marker="o",
alpha=0.5,
s=90,
label="Outliers",
)
# 범례를 그래프의 왼쪽 상단으로 이동합니다.
sns.move_legend(ax, "upper left", bbox_to_anchor=(1, 1))
# 그래프의 제목, x축, y축 이름을 설정합니다.
plt.title("Scatter plot of news with outliers projected with t-SNE")
plt.xlabel("TSNE1")
plt.ylabel("TSNE2")
Text(0, 0.5, 'TSNE2')
데이터프레임의 인덱스 값을 사용하여 각 카테고리에서 이상치가 어떻게 보일 수 있는지 몇 가지 예를 출력해 봅시다. 여기에서 각 카테고리의 첫 번째 데이터 포인트가 출력됩니다. 이상치 또는 이상 현상으로 간주되는 데이터를 보려면 각 카테고리에서 다른 포인트를 탐색해 보시기 바랍니다.
데이터프레임에서 특정 조건을 만족하는 데이터를 필터링하는 과정을 보여줍니다. 여기서는 ‘Class Name’이 ‘sci.crypt’인 데이터만을 선택하여 새로운 데이터프레임 sci_crypt_outliers
에 저장합니다. 그리고 sci_crypt_outliers
데이터프레임의 ‘Text’ 열에서 첫 번째 텍스트 데이터를 출력합니다.
# 데이터프레임 'df_outliers'에서 'Class Name' 열이 'sci.crypt'인 행들을 필터링하여 'sci_crypt_outliers' 변수에 저장합니다.
sci_crypt_outliers = df_outliers[df_outliers["Class Name"] == "sci.crypt"]
# 'sci_crypt_outliers' 데이터프레임의 'Text' 열에서 첫 번째 항목을 출력합니다.
print(sci_crypt_outliers["Text"].iloc[0])
Marc VanHeyningen <>RIPEM Frequently Asked Questions Content-Type: text/x-usenet-FAQ; version=1.0; title="RIPEM FAQ" Originator: Supersedes: <> Nntp-Posting-Host: silver.ucs.indiana.edu Organization: Computer Science, Indiana University Expires: Fri, 30 Apr 1993 00:00:00 GMT Lines: 255 Archive-name: ripem/faq Last-update: Sun, 7 Mar 93 21:00:00 -0500 ABOUT THIS POSTING ------------------ This is a listing of likely questions and information about RIPEM, a program for public key mail encryption. It was written and will be maintained by Marc VanHeyningen, <>. It will be posted to a variety of newsgroups on a monthly basis; follow-up discussion specific to RIPEM is redirected to the group alt.security.ripem. This month, I have reformatted this posting in an attempt to comply with the standards for HyperText FAQ formatting to allow easy manipulation of this document over the World Wide Web. Let me know what you think. DISCLAIMER ---------- Nothing in this FAQ should be considered legal advice, or anything other than one person's opinion. If you want real legal advice, talk to a real lawyer. QUESTIONS AND ANSWERS --------------------- 1) What is RIPEM? RIPEM is a program which performs Privacy Enhanced Mail using the cryptographic techniques of RSA and DES. It allows your electronic mail to have the properties of authentication and privacy RIPEM was written primarily by Mark Riordan <>. Most of the code is in the public domain, except for the RSA routines, which are a library called RSAREF licensed from RSA Data Security Inc. 2) How can I get RIPEM? RIPEM contains the library of cryptographic routines RSAREF, which is considered munitions and thus is export-restricted from distribution to people who are not citizens or permanent residents of the U.S. or Canada. Therefore, the following request is quoted from the README file: #Please do not export the cryptographic code in this distribution #outside of the USA or Canada. This is a personal request from me, #the author of RIPEM, and a condition of your use of RIPEM. Note that RSAREF is not in the public domain, and a license for it is included with the distribution. You should read it before using RIPEM. The best way to get it is to ask a friend for a copy, since this will reduce the load on those sites that do carry it Naturally this requires that you trust the friend. RIPEM is available via anonymous FTP to citizens and permanent residents in the U.S. from rsa.com; cd to rsaref/ and read the README file for info. Last I looked, this site contains only the source tree, and does not contain compiled binaries or the nice Mac version. RIPEM, as well as some other crypt stuff, has its "home site" on rpub.cl.msu.edu, which is open to non-anonymous FTP for users in the U.S. and Canada who are citizens or permanent residents. To find out how to obtain access, ftp there, cd to pub/crypt/, and read the file GETTING_ACCESS. For convenience, binaries for many architectures are available here in addition to the full source tree. 3) Will RIPEM run on my machine? Probably. It has already been ported to MS-DOS and most flavors of Unix Ports to Macintosh include a standard UNIX-style port and a rather nice Mac-like port written by Raymond Lau, author of StuffIt. More ports are expected, and help of users is invited. 4) Will RIPEM work with my mailer? Probably. How easy and clean the effective interface is will depend on the sophistication and modularity of the mailer, though. The users guide, included with the distribution, discusses ways to use RIPEM with many popular mailers, including Berkeley, mush, Elm, and MH. Code is also included in elisp to allow easy use of RIPEM inside GNU Emacs. If you make a new interface for RIPEM or create an improvement on one in the distribution which you believe is convenient to use, secure, and may be useful to others, feel free to post it to alt.security.ripem. 5) What is RSA? RSA is a crypto system which is asymmetric, or public-key. This means that there are two different, related keys: one to encrypt and one to decrypt. Because one cannot be derived from the other, you may publish your encryption, or public key widely and keep your decryption, or private key to yourself. Anyone can use your public key to encrypt a message, but only you hold the private key needed to decrypt it. (Note that the "message" sent with RSA is normally just the DES key to the real message. Note that the above only provides for privacy. For authentication, the fingerprint of the message is encrypted with the sender's private key. The recipient can use the sender's public key to decrypt it and confirm that the message must have come from the sender. RSA was named for the three men who invented it. To find out more about RSA, ftp to rsa.com and look in pub/faq/ or look in sci.crypt. 6) What is DES? DES is the Data Encryption Standard, a wide
이 코드의 핵심은 특정 조건에 맞는 데이터를 데이터프레임에서 필터링하는 것입니다. 여기서는 df_outliers
데이터프레임에서 ‘Class Name’ 컬럼의 값이 ‘sci.electronics’인 행들만을 선택하여 sci_elec_outliers
변수에 저장합니다. 그리고 sci_elec_outliers
의 ‘Text’ 컬럼에서 첫 번째 데이터를 출력하는 것입니다. 중요한 점은 .iloc[0]
을 사용하여 첫 번째 행에 접근한다는 것입니다. 이 코드는 데이터 분석에서 특정 조건을 만족하는 데이터를 추출할 때 자주 사용됩니다.
# sci_elec_outliers 변수에 'Class Name'이 'sci.electronics'인 데이터만 필터링하여 저장합니다.
sci_elec_outliers = df_outliers[df_outliers["Class Name"] == "sci.electronics"]
# sci_elec_outliers의 'Text' 컬럼에서 첫 번째 행의 데이터를 출력합니다.
print(sci_elec_outliers["Text"].iloc[0])
Re: Rumors Nntp-Posting-Host: 223.199.55.11 Organization: Motorola Western MCU Design Center, Chandler Arizona Lines: 17 In article <>, writes: |> I just heard an unbelievable rumor that Motorola has decided to drop their |> integrated circuit manufacture business. Apparently a Digikey rep called |> one of our production coordinators, for out information so that we could |> make plans to deal with this, that Moto was getting out. Anybody else |> get a call about this? |> |> Too much for me. It's about like Intel announcing they were getting out |> of the IC business. This rumor didn't happen to appear on April 1st? If this DigiKey rep was serious, I think I will buy my parts elsewhere. If that is the way they do business, you cannot trust them. Mark
이 코드의 핵심은 df_outliers
데이터프레임에서 ‘Class Name’ 열이 ‘sci.med’인 행들만을 선택하여 새로운 데이터프레임 sci_med_outliers
를 생성하는 것입니다.
그리고 이 데이터프레임에서 ‘Text’ 열의 첫 번째 값을 출력합니다. 이 과정은 특정 분류에 속하는 이상치 데이터를 확인할 때 유용합니다. 중요한 점은 .iloc[0]
을 사용하여 첫 번째 텍스트 데이터에 접근한다는 것입니다.
# sci.med 분류에 해당하는 이상치 데이터를 필터링합니다.
sci_med_outliers = df_outliers[df_outliers["Class Name"] == "sci.med"]
# 필터링된 데이터 중 첫 번째 텍스트 데이터를 출력합니다.
print(sci_med_outliers["Text"].iloc[0])
Re: HELP for Kidney Stones .............. Organization: Lawrence Livermore National Laboratory, NCD Lines: 30 NNTP-Posting-Host: eet1477-10780-t1477r1104.llnl.gov In article <> writes: > My girlfriend is in pain from kidney stones. She says that because she has no > medical insurance, she cannot get them removed. > > My question: Is there any way she can treat them herself, or at least mitigate > their effects? Any help is deeply appreciated. > > Thank you, > > Dave Carvell > First, let me offer you my condolences. I've had kidney stones 4 times and I know the pain she is going through. First, it is best that she see a doctor. However, every time I had kidney stones, I saw my doctor and the only thing they did was to prescribe some pain killers and medication for a urinary tract infection. The pain killers did nothing for me...kidney stones are extremely painful. My stones were judged passable, so we just waited it out. However the last one took 10 days to pass...not fun. Anyway, if she absolutely won't see a doctor, I suggest drinking lots of fluids and perhaps an over the counter sleeping pill. But, I do highly suggest seeing a doctor. Kidney stones are not something to fool around with. She should be x-rayed to make sure there is not a serious problem. Steve
데이터프레임 df_outliers
에서 ‘Class Name’ 컬럼의 값이 ‘sci.space’인 행들만을 선택하여 새로운 데이터프레임 sci_space_outliers
에 저장합니다. 그리고 sci_space_outliers
데이터프레임의 ‘Text’ 컬럼에서 첫 번째 행의 데이터를 출력합니다. 특정 조건에 맞는 데이터를 필터링하고 그 결과를 확인합니다.
# sci_space_outliers 변수에 'Class Name' 컬럼의 값이 'sci.space'인 데이터만 필터링하여 저장합니다.
sci_space_outliers = df_outliers[df_outliers["Class Name"] == "sci.space"]
# sci_space_outliers의 'Text' 컬럼에서 첫 번째 행의 데이터를 출력합니다.
print(sci_space_outliers["Text"].iloc[0])
Sunrise/ sunset times Organization: Drexel University, College of Engineering, Philadelphia, PA Lines: 8 Hello. I am looking for a program that can be used to compute sunrise and sunset times. I would appreciate any advice. Joe Wetstein
라이선스
# @title Licensed under the Apache License, Version 2.0 (the "License");
# 이 파일은 Apache License, Version 2.0에 따라 사용이 허가되었습니다.
# 라이선스를 준수하는 경우에만 이 파일을 사용할 수 있습니다.
# 라이선스는 다음의 링크에서 얻을 수 있습니다:
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# 법률에 의해 요구되거나 서면으로 합의된 경우를 제외하고,
# 라이선스에 따라 배포된 소프트웨어는 "있는 그대로"의 기준으로 제공되며,
# 명시적이거나 묵시적인 어떠한 종류의 보증이나 조건도 부여되지 않습니다.
# 라이선스가 부여하는 허가 및 제한 사항을 정하는 데 적용되는 법률을 참조하십시오.
댓글남기기