🔥알림🔥
① 테디노트 유튜브 - 구경하러 가기!
② LangChain 한국어 튜토리얼 바로가기 👀
③ 랭체인 노트 무료 전자책(wikidocs) 바로가기 🙌

23 분 소요

이 글에서는 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에서 한 번의 클릭으로 키를 생성하세요.

API 키 받기

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_trainemb_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
#
# 법률에 의해 요구되거나 서면으로 합의된 경우를 제외하고,
# 라이선스에 따라 배포된 소프트웨어는 "있는 그대로"의 기준으로 제공되며,
# 명시적이거나 묵시적인 어떠한 종류의 보증이나 조건도 부여되지 않습니다.
# 라이선스가 부여하는 허가 및 제한 사항을 정하는 데 적용되는 법률을 참조하십시오.

댓글남기기