최초로 데이터를 받은 이후에, 가장 먼저할 작업은 탐색적 데이터 분석이다. 탐색적 데이터 분석을 통해 각 변수별로 처리 방법을 계획하여, 분석하기 쉬운 형태의 데이터로 만들게 된다. 이 과정에서 변수의 변환, 제거, 생성, 결측값 처리 등의 절차를 수행하게 된다. 본 포스팅은 pyspark 를 통해 데이터를 탐색적으로 확인하는 방법과 간단한 데이터 처리 방법 몇 가지를 다룬다.
pyspark 를 통한 탐색적 분석 문법은 pandas 와 유사한 부분도 있고, 그렇지 않은 부분도 있다. 자주 등장하는 pyspark 의 특이적인 문법이 존재하는데, R, pandas 만 활용해온 사람이라면 이러한 부분에 점점 익숙해질 필요가 있을 것 같다.
# 데이터 다운로드 받기
# https://www.kaggle.com/datasets/mansoordaku/ckdisease?resource=download
import pyspark
import pandas as pd
from pyspark.sql import SparkSession
from pyspark import SparkContext, SparkConf
import os
import sys
# 환경변수 PYSPARK_PYTHON 와 PYSPARK_DRIVER_PYTHON 의 python path 를 동일하게 맞춰준다.
os.environ['PYSPARK_PYTHON'] = sys.executable
os.environ['PYSPARK_DRIVER_PYTHON'] = sys.executable
# 환경 세팅
conf_spark = SparkConf().set("spark.driver.host", "127.0.0.1") # local 환경임을 명시적으로 지정해준다.
sc = SparkContext(conf=conf_spark)
0. 데이터 로드
pyspark 에서 csv 파일을 load 하는 것은 아래 문법으로 수행할 수 있다.
# data = pd.read_csv("data/kidney_disease.csv") # pandas 에서 csv 파일 읽기
spark = SparkSession.builder.master("local[1]").appName("kidney_disease").getOrCreate()
data = spark.read.option("header", True).csv("data/kidney_disease.csv") # header가 있는 경우, option 구문 추가
data_pd = pd.read_csv("data/kidney_disease.csv")
1. Schema: 데이터 스키마 확인하기
어떤 구조와 변수형으로 데이터가 들어가 있는지 먼저 파악한다.
- 데이터를 직접 추출하여 만들었다면, 각 컬럼이 무엇을 의미하는지 알겠지만.. - 연습을 위한 토이 데이터이거나, 다른 사람한테 받은 데이터라면, 각 컬럼이 무엇을 의미하는지 이해하고 넘어갈 필요가 있다.
우선 연속형 변수를 double 형으로 변환해준다. 여기서도 withColumn 구문을 활용할 수 있다.
# 연속형 변수들은 double 로 형변환 수행
numeric_cols = ['age', 'bp', 'sg', 'al', 'su', 'bgr', 'bu', 'sc', 'sod', 'hemo', 'pcv', 'wc', 'rc']
for c in numeric_cols:
data = data.withColumn(c, data[c].cast('double'))
위 결과를 pandas dataframe 으로 변환하고, transpose 를 시켜 보기 쉬운 데이터 형태로 변환한다.
# 첫번째 로우를 header 로 변경
numeric_dist = data.describe(numeric_cols).toPandas().T
new_header = numeric_dist.iloc[0] #grab the first row for the header
numeric_dist = numeric_dist[1:] #take the data less the header row
numeric_dist.columns = new_header #set the header row as the df header
# 인덱스컬럼의 값을 추출해서 새로운 컬럼으로 지정
numeric_dist['col'] = numeric_dist.index.values
numeric_dist.reset_index(drop=True, inplace=True)
#### agg 함수 사용: quantile 값 추출
- 아래와 같이 agg 함수를 활용해 연속형 변수의 quantile 값을 추출할 수 있다.
- pyspark.sql.functions 의 expr 함수를 활용해, agg 함수 내에 원하는 집계 방법을 string 형태로 넣어서 quantile 을 구할 수 있다.
import pyspark.sql.functions as F
perc = pd.DataFrame({'col':[], '%25':[], '%50':[], '%75':[], '%90':[], '%99':[]})
for c in numeric_cols :
result_new = data.agg(
F.expr('percentile(' + c + ',array(0.25))')[0].alias('%25'),
F.expr('percentile(' + c + ',array(0.50))')[0].alias('%50'),
F.expr('percentile(' + c + ',array(0.75))')[0].alias('%75'),
F.expr('percentile(' + c + ',array(0.90))')[0].alias('%90'),
F.expr('percentile(' + c + ',array(0.99))')[0].alias('%99'),
).toPandas()
result_new['col'] = c
perc = pd.concat([perc, result_new], axis=0)
#### 두 결과 테이블 조인하기
- 위 두 테이블을 조인하여 최종적으로 원하는 형태의 연속형 변수 분포 테이블을 만들어 볼 수 있다.
5. Distribution: 단변수 분포 확인하기 - Categorical variable
#### 범주형 변수에서 unique 한 값 추출하기
- 아래 스크립트에서 rdd.map 이 pyspark 특이적인 문법이라고 볼 수 있다.
ㄴ rdd 는 dataframe 을 rdd 형태로 다룰 것임을 의미하며, rdd 에 lambda 함수를 적용해 결과를 추출한다.
ㄴ collect 함수는 결과를 list 형태로 만들어준다.
ㄴ 이러한 문법은 pandas 나 R 에서는 잘 나오지 않기 때문에 익숙해질 필요가 있을 것 같다.
from pyspark.sql.functions import when
from pyspark.sql import functions as F
# Label 변수 확인하기
print(f"labels: {data.select('classification').distinct().rdd.map(lambda r: r[0]).collect()}")
# data_pd['classification'].unique() # pandas 의 경우는 이와 같이 한다.
labels: ['notckd', 'ckd', 'ckd\t']
#### 분포 확인하기
- sql.function 내 agg 함수 내에 원하는 집계 (count) 함수를 넣어, 아래와 같이 그룹별 행수를 계산할 수 있다.
result = data.select(['classification']).\
groupBy('classification').\
agg(F.count('classification').alias('user_count')).toPandas()
result
6. Drop useless columns: 컬럼 드랍하기 (Option)
- 필요 없는 컬럼이 있는 경우 drop 함수로 드랍한다.
data = data.drop('pot')
print(f"Row count: {data.count()}")
print(f"Column count: {len(data.columns)}")
- 컬럼별로 결측값 개수를 구하는 것은 다양한 방식으로 구현할 수 있지만, 가장 간단하게 for 을 활용하는 방법은 아래와 같다.
# 각 컬럼별로 결측값 개수를 확인함
# 만약 결측값 코딩이 0으로 되어 있는 경우, filter 조건을 변경하면 된다.
result = pd.DataFrame({'variable':[], 'missing_count':[]})
for col in data.columns:
df_new = pd.DataFrame({'variable':[], 'missing_count':[]})
df_new.at[0, 'variable'] = col
df_new.at[0, 'missing_count'] = data.filter(data[col].isNull()).count()
result = pd.concat([result, df_new], axis=0)
result.astype({'missing_count': 'int32'}) # missing_count 을 int 형으로 변환함
result['missing_rate'] = result['missing_count'] / data.count() # 결측률 계산
# 결측률이 높은 순서대로 출력한다.
# rbc 컬럼의 결측율이 38% 로 가장 높은 것을 확인할 수 있다.
result.sort_values(by=['missing_rate'], ascending=False)
9. Replace: 값 치환하기
- 데이터를 다루다보면 종종 값을 치환해야할 경우가 있다. (값이 잘못 들어가 있는 경우, 카테고리를 합치는 경우 등..)
- withColumn 구문과 when 구문을 합쳐여 값 치환을 구현해볼 수 있다.
# label 에 'ckd\t' 로 데이터가 잘못 들어가 있는 것을 확인할 수 있다.
# 이를 when 구문을 통해 원하는 값으로 바꾸어준다.
data = data.withColumn("classification", \
when(data["classification"] == 'ckd\t', 'ckd').otherwise(data["classification"]))
# 재출력하기
print(f"처리후 labels: {data.select('classification').distinct().rdd.map(lambda r: r[0]).collect()}")
처리후 labels: ['notckd', 'ckd']
10. Imputation: 평균대치법 (for numeric variable)
- 본 데이터셋은 결측값이 null 로 채워져 있다. 평균 대치법으로 결측값을 처리하자.
- 먼저, 특정 컬럼의 평균을 구하는 함수를 작성한다.
ㄴ pyspark 에서는 이와 관련한 readymade 함수가 없기 때문에 직접 작성한다.
- 여기서도 df.select(avg(df[col]))) 이나 rdd.map 같은 pyspark 특이적 문법이 등장한다.
from pyspark.sql.functions import avg
from pyspark.sql.functions import when, lit
# 컬럼별 평균값 계산하기
def mean_of_pyspark_columns(df, numeric_cols, verbose=False):
col_with_mean=[]
for col in numeric_cols:
mean_value = df.select(avg(df[col]))
avg_col = mean_value.columns[0]
res = mean_value.rdd.map(lambda row : row[avg_col]).collect()
if (verbose==True): print(mean_value.columns[0], "\t", res[0])
col_with_mean.append([col, res[0]])
return col_with_mean
# with column when 구문으로 null 일 때, 평균으로 치환한다.
for col, mean in col_with_mean:
data = data.withColumn(col, when(data[col].isNull() == True,
lit(mean)).otherwise(data[col]))
for col, mode in col_with_mode:
data = data.withColumn(col, when(data[col].isNull()==True,
lit(mode)).otherwise(data[col]))
결측값 치환이 잘 되었는지를 확인한다. 결측값이 없는 데이터가 되었음을 확인할 수 있다.
# 각 컬럼별로 결측값 개수를 확인함
# 만약 결측값 코딩이 0으로 되어 있는 경우, filter 조건을 변경하면 된다.
result = pd.DataFrame({'variable':[], 'missing_count':[]})
for col in data.columns:
df_new = pd.DataFrame({'variable':[], 'missing_count':[]})
df_new.at[0, 'variable'] = col
df_new.at[0, 'missing_count'] = data.filter(data[col].isNull()).count()
result = pd.concat([result, df_new], axis=0)
result.astype({'missing_count': 'int32'}) # missing_count 을 int 형으로 변환함
result['missing_rate'] = result['missing_count'] / data.count() # 결측률 계산
# 결측률이 높은 순서대로 출력한다.
# rbc 컬럼의 결측율이 38% 로 가장 높은 것을 확인할 수 있다.
result.sort_values(by=['missing_rate'], ascending=False)
한 마디로 빅데이터 환경에서 전통적인 데이터 처리 툴들 (R, pandas) 을 활용하기 어렵기 때문이다. 토이 데이터의 경우, 10GB 를 넘는 경우가 드물지만, 실제 회사의 빅데이터 환경에서는 하나의 데이터셋이 10GB 를 넘는 경우가 많으며, 크게는 10TB를 넘는 경우도 있다. 기본적으로 R과 python pandas 는 in-memory 처리 방식이다. 모든 데이터를 메모리에 적재한 후, 처리한다. 만약 램이 8GB 인 머신을 사용한다고 하면, 이러한 데이터들을 로드조차 하지 못하고, out-of-memory 에러로 커널이 죽는 모습을 확인할 수 있게 된다.
pyspark 환경에서는메모리 사용량을 최소화하는 방식으로 용량이 크고, 포맷이 다양한 데이터들을 "특정 데이터 구조" 로 로드하고 처리하는 것이 가능하다. 즉, pyspark 는 시간 및 컴퓨팅 자원 측면에서 효율적으로 데이터 처리/분석을 할 수 있도록 도와준다.
만약, 데이터 분석 공부를 하거나, 큰 데이터를 접할 일이 없는 도메인에서 일을 하는 경우에는 R 또는 pandas 만 사용하여도 괜찮다. 하지만 빅데이터 환경에서 데이터를 처리하고 분석하는 것이 필요하다면, pyspark 를 활용하는 것이 더욱 효율적이며, 이것이 pyspark 를 배우면 좋은 이유이다.
PySpark DataFrame
pyspark 의 핵심 데이터 타입은 dataframe 이다. pyspark dataframe 은 쉽게 말해 여러 클러스터에 분산 되어 있는 테이블이라고 할 수 있다. 그 이름처럼 R이나 pandas의 dataframe 과 비슷한 함수들을 갖고 있다. 분산 실행을 위해서는 다른 데이터 타입이 아닌 pyspark 의 dataframe 객체를 이용하면 된다. pyspark dataframe을 활용하는 것은 R 과 pandas 와 거의 비슷하기 때문에, 둘 중 하나에 익숙한 경우 쉽게 활용할 수 있다.
pyspark 와 pandas 의 큰 차이점 중 하나는 pyspark 는 lazy 하고, pandas 는 eager 하다는 것이다. pyspark 에서는 실제 결과가 필요할 때까지 실행을 유보한다 (lazy evaluation). 예를 들어, hive 환경에 있는 테이블을 읽어온 후, 특정 변수를 변환하는 코드를 짰다고 하자. 하지만 이 코드를 실행하는 즉시, 이 작업이 실행되지 않는다. 실제 데이터가 필요한 경우에만 이 작업이 실행횐다 예를 들어, 변환된 테이블을 다시 hive 환경에 파일로 저장하는 등의 경우를 들 수 있다. 이러한 방식이 좋은 점은 전체 데이터를 메모리에 저장하지 않아도 되기 때문에, 효율적으로 데이터를 처리할 수 있다. 반면, pandas 에서는 함수가 호출되는 즉시 실행되며 (eager evaluation), 모든 것은 메모리에 저장된다. pyspark 환경에서의 데이터 처리를 한다고 했을 때, eager operation 은 사용하는 것은 지양하는 것이, 리소스 절감 측면에서 바람직하다고 할 수 있다.
가장 깔끔한 방법은 유니크한 personid 를 갖는 병합 테이블을 하나 만들고, 이 테이블에 left outer join 을 하는 것이다.
create table total_user as
select ditinct account_id from (
select distinct account_id from employee1
union all
select distinct account_id from employee2
union all
select distinct account_id from employee3
union all
select distinct account_id from employee4
)
select t0.personid,
`name`, sal, place, dt
from total_user t0
left outer join employee1 t1 on t0.personid = t1.personid
left outer join employee1 t2 on t0.personid = t2.personid
left outer join employee1 t3 on t0.personid = t3.personid
left outer join employee1 t4 on t0.personid = t4.personid
참고) 잘못된 SQL
-> 이렇게 하게 되면, 테이블1과 계속해서 full outer join 을 하기 때문에, 중복된 personid 가 생기게 된다.
select coalesce(f.personid, u.personid, v.personid, v_in.personid) as personid,f.name,u.sal,v.place,v_in.dt
from employee1 f FULL OUTER JOIN employee2 u on f.personid=u.personid
FULL OUTER JOIN employee3 v on f.personid=v.personid
FULL OUTER JOIN employee4 v_in on f.personid=v_in.personid;
1) 어떤 분야든 Self-report 는 100% 신뢰할 수 없다. (물론 분야별 신뢰도의 차이는 있겠지만..)
2) 세대별로 인터넷을 이용하는 방식은 다르지만, 거짓 정보에 대한 취약하다는 사실은 동일하다.
Lateral reading 이라고 불리는 읽기 습관은 사실 관계 확인 절차에 있어 핵심적인 부분이다. (Lateral reading: 온라인 정보 파편의 사실 관계나 출처 등을 확인하기 위해, 수많은 브라우저 탭을 열고, 검색을 하는 등의 방법으로 다양한 정보를 습득하는 것을 의미) Poynter, YouGov, Google 의 최신 연구 결과는 Z세대 (Generation Z) 는 이러한 테크닉을 이전 어느 세대보다 잘 활용하고 있다는 것을 보여주었으며, 이는 매우 희망적인 결과라고 할 수 있다.
2022년 8월 11일, 구글 검색 엔진팀은 다양한 연령대 및 국가별로 잘못된 정보를 처리하는 방법의 차이점에 대한 연구를 발표하였다. 이 연구에서는 8,000명 이상의 연구 대상자에게, 온라인 콘텐츠 조사 방법에 대한 설문 조사를 실시하였으며, Z 세대와 (18-25세) 부터 Silent Generation (>68세) 에 이르는 연령대별로, 7개 이상의 국가별로 수집한 설문 결과를 종합하였다.
기본적으로, 이 연구의 결론은젊은 사람일수록 의도치 않게 거짓 정보 또는 오해의 소지가 있는 정보가 공유 됐을 가능성을 생각하는 경향이 있다는 것이다. 그들은 또한 "고급 팩트 체킹 기술" 을 활용하는데에도 능숙하다고 한다.
3분의 1 이상의 Z 세대 응답자들중 사실 관계 확인을 위한 lateral reading 에 많은 시간을 투자한다고 응답한 비율은 베이비부머 세대의 응답자들보다 2배 이상 높았다. 또한 3분의 1이상의 젊은 사람들은 검색 결과 비교를 위해 다양한 검색 엔진을 활용하며, 검색 결과 2페이지 이상을 탐색한다고 답했다.
62% 의 응답자들은 매주 잘못된 정보를 접하고 있다고 답했다. Z세대, 밀레니얼 세대, X세대의 응답자들은 잘못된 정보 판단에 있어 다른 세대 보다 상대적으로 더 자신감이 있었으며, 그들의 가족 또는 친구가 잘못된 정보를 믿지 않을까하는 걱정하는 경향을 보였다.
그러나, 연구 결과는설문 조사 결과에 의존하고 있고, 이는 대상자들이 믿는 자신의 신념과 습관에 불과하다. Z 세대의 "실제 습관" 과 관련된 수치는 이와 상반된다. 팩트 체킹을 연구하는 스탠포드 Sam Wineburg 교수에 따르면, 인터넷에서 사람들이 어떻게 행동하는지를 파악하기 위해 "자기 응답" (self-report) 을 활용하는 것은, 그의 표현을 빌리자면 "bullshit" 이라고 한다.
"사람들이 실제로 하는 것과 한다고 말하는 것의 괴리는 사회 심리학 초기 연구 자료에서 쉽게 확인할 수 있다." 그는 말했다. 그의 연구 결과는 누군가의 개입이 없는 경우, 젊은 사람들은 자발적으로는 lateral reading 이나 사실 관계 확인을 거의 하지 않는다는 것을 보여주었다.
Wineburg 와 그의 연구팀에 의해 수행된 최근 연구에서는 대학생들을 대상으로 사실 관계 확인 기술에 관한 온라인 수업이 사실 관계를 확인 습관에 도움을 줄 수 있을지를 확인하였다. 수업 전 사전 조사에서 87명중 불과 3명의 학생만이 lateral reading 을 능동적으로 하는 것으로 확인 되었다.
"만약 사람들이 자발적으로 lateral reading 을 한다면, 우리는 훨씬 더 나은 곳에 살 수 있을 겁니다."Wineburg 는 말했다.
좀 더 큰 규모의 연구에서는 3,000명 이상의 고등학생들이 온라인내에 존재하는 일련의 주장들을 조사하도록 요청 받았다. 결과는 더욱 암울했다. 50% 이상의 학생들이 러시아에서 촬영된 익명의 페이스북 동영상을 보고, 이것이 "부정 투표" 에 관한 강력한 증거라고 믿었다.
Z 세대는 다른 세대들과 인터넷을 다른 방식으로 이용하는 것은 확실하다. 그러나 Z 세대도 이전 세대들과 마찬가지로, 지난 몇 년간 온라인 생태계를 악화시킨, 정보의 함정, 전략적인 잘못된 정보의 영향에 취약하다.
사각사각 거리는 키감이 너무 좋다. 저소음 모델 답게 소음이 작아 화상 회의 하면서 키보드를 사용해도 신경쓰이지 않을 것 같다. 레오폴드 750R 갈축의 소음과 비교하면 소음 크기는 체감상 거의 20~30% 수준인 것 같다.
처음 키보드를 쳐봤을 때 드는 생각은 '생각보다 키압이 있는데?' 였다. 레오폴드 750R 갈축과 키압이 비슷한 것 같다. 하지만 계속 쓰다보니 불편한 정도는 아니었고, 오히려 이 정도가 딱 적당하다는 생각이 들었다. (물론 30g 는 아직 써보지 않아 써보면 생각이 바뀔 수도..)
결론은 39만원이 아깝지 않을 정도로 마음에 든다!
블로그 쓰는 모습 (with Realforce R3)
구성품은 매우 단촐 (키보드/aa건전지 2개/usb연결잭)
색감은 완전 새까만 블랙은 아니고, 자판 부분은 약간 챠콜색이다. 우측 상단 동그라미 부분은 전원버튼/블루투스 연결 기기를 선택할 수 있는 버튼이다. (총 4대 전환하면서 연결 가능, Fn+1~4 를 통해 전환할 수 있다.)
레오폴드와 크기 비교.. 리얼포스R3 가 텐키레스임에도 불구하고 생각보다 크기가 크다. 무게도 더 묵직하다.
예를 들어, 우리의 친구 Justine 이 좋아할만한 소설을 예측하고 싶다고 가정하자. 현재 가지고 있는 정보는 "그녀가 최근 읽은 몇 가지 소설" 및 "그 소설들에 대해 그녀가 좋아했는지 안 좋아했는지 여부" 를 알고 있다.
표준적인 supervised machine learning 패러다임에서는positive example 과 negative example 들의 feature들을 비교하여, 새로운 소설 (unseen) 에 대해 그녀가 좋아할지 안좋아할지를 예측하는 모델을 만든다.
반면, semi-supervied paradigm 은 Justine 이 읽은 소설만 활용하는 것이 아니라 그녀가 읽지 않은 소설 (unlabeled) 까지 활용하여 그녀가 선호할만한 소설을 예측하는 시스템을 만든다. 당연히 전체 소설중, 그녀가 읽지 않은 소설이 훨씬 많을 것이며, 놀랍게도, 이를 활용하면 (unlabeled 데이터를 활용하면) 더욱 효율적인 시스템을 만들 수 있다는 것이다.
Why semi-supervised learning works?
왜 unlabeld 데이터가 예측에 도움이 될 수 있는지를 이해하기 위해 위 그림을 보자.
초록색: positive cases
빨간색: negative cases
선: decision line
unlabeled 데이터를 활용하면 오른쪽 그림처럼 더욱 정교한 decision line 을 그릴 수 있다. 이를 통해 모델의 성능 및 일반화에 도움을 줄 수 있다.
PU learning 의 직관적 이해
Positive-unlabeled learning 은 semi-supervised learning 은 한 가지 패러다임으로, positive case 와 unlabeld case 만 존재하는 문제에서, 사용할 수 있는 방법이다. 위 예시에서 Justine 이 좋아하는 소설에 대한 데이터만 갖고 있는 것이다. real-world 에서 이러한 상황은 생각보다 자주 존재한다. (항상 toy 문제처럼 positive case 와 negative case 가 잘 들어가 있지는 않다.)
초록색: positive cases
선: decision line
PU learning 이 왜 워킹 하는지에 대해 직관적으로 이해해보자.
1) 왼쪽 그림만을 보고, positive와 negative 를 구분하는 선을 만들라고 하면 어떻게 할 것인가? (머신러닝 모델이 아닌 사람의 직감으로) 다른 데이터들이 어떻게 놓여있는지를 알 수 없기 때문에, 타원 모양의 decision boundary 를 그리는 것이 한 가지 방법이 될 것이다.
2) 오른쪽 그림을 보고, 다시 positive 와 negative 를 구분하는 선을 만들라고 하면 어떻게 할 것인가? 왼쪽 그림과 비교하여 추가적인 정보 (unlabeled 데이터가 어디에 놓여있는지) 를 알고 있다. 그렇기 때문에 그림처럼 다른 decision boundary 를 그릴 수 있게 된다.
PU learning 의 몇 가지 테크닉들
1) Direct application of a standard classifier
가장 기본적인 접근 방법은 unlabeled case 를 negative 로 취급하고 분류 모델을 학습하는 것이다. 이 모델은 각 데이터 포인트에 점수를 주는데, positive case 에 대해서 평균적으로 더 높은 점수를 준다. 만약 unlabeled 데이터 중에 높은 점수를 받은 데이터 포인트가 있다면, positive case 일 확률이 높을 것이다.
이러한 '가장 나이브한 방식' 의 정당성은 이 논문에서 확인되었다 (2008년도). 이 논문의 주요 결과는, 몇 가지 특정한 가정하에, positive+unlabeled 데이터로 학습한 모델의 성능은 positive+negative 데이터로 학습한 결과와 비례한다는것이다.
저자의 코멘트에 따르면, 이 의미는 다음과 같다: "모델이 잘 학습되었다고 가정하면, PU 모델은 어떤 데이터 포인트가 positive 에 속할 가능성에 대한 순위를 매기는데 활용할 수 있다."
2) PU bagging
더욱 정교한 접근 방법은 bagging 의 변형을 활용하는 것이다.
- positive data 와 함께 "unlabeled data 로 부터 random sampling 한 데이터(with replacement)" 를 같이 활용한다.
- 위 boostrap sample 을 negative 로 하여 모델을 학습한다.
- out of bag 샘플 (boostrap sample 에 포함되지 않은 unlabled data) 에 대해 스코어를 매기고, 저장한다.
- 이러한 방법을 반복적으로 적용하고, unlabeled 데이터들에 대해 나온 스코어들을 평균낸다.
이러한 접근 방법이 소개된 논문 (2013년) 에서 저자들은 특히, 갖고 있는 positive sample 숫자가 적고, unlabeled 데이터 중, negative 의 비율이 적은 PU learning 상황에서 state-of-the-art 성능을 달성했다고 주장했다 (2013년도 기준). 또한 unlabeled 데이터의 규모가 큰 경우, 이러한 bagging 방식은 더 빠르게 모델을 학습할 수 있다.
3) Two-step approaches
많은 PU learning 전략은 two-step 접근 방법 카테고리에 속한다. (이 방법이 소개된 논문 (2014년))
Step1 - unlabeled 중에 negative 인 것이 가장 확실한 포인트들을 찾는다. (이를 reliable negative 라고 한다.)
Step2 - positive 와 reliable negative 로 모델을 학습하고, 나머지 unlabeled 데이터에 적용한다.
일반적으로 Step2 의 결과를 통해 Step1 으로 되돌아가서, reliable negative 를 찾고 이를 반복하게 된다. 당연히 reliable negative 의 규모가 충분히 크고, 실제 negative 를 많이 포함하고 있을 수록 더 좋은 모델을 구축할 수 있다. 얼마나 반복해서 적절한 수의 reliable negative 를 찾을 수 있을지가 핵심적인 부분이라고 할 수 있다.
* 이 포스트는 Mit technology review 의 22년 6월 28일 아티클 "AI's progress isn't the same as creating human intelligence in machines " 개인의 의견을 담아 리뷰한 글입니다.
제목에서 알 수 있듯 위 아티클은 AI 의 발전은 기계로 인간 지능을 창조하는 것과 동일하지 않다. 고 말하고 있다.
AI 의 두 분야
1. 인간의 지능을 컴퓨터에 구현하는 것 (Scientific AI)
2. 엄청난 양의 데이터를 모델링하는 것 (Data-centric AI)
두 분야는 매우 다르다. 두 분야의 개념 차이만큼이나 두 분야의 야망과 최근 발전 상황이 다르다고 할 수 있다.
이 기사의 요지중 하나는 최근 AI 분야의 발전은 대부분 Data-centric AI 에서 이루어졌다는 것이다.
1) Scientific AI
인간 수준의 지능을 컴퓨터에 구현하기 위한 분야를 말하며, 1950년대부터 수십년간 이어져온 과학의 매우 심오하고 도전적인 과제라고 할 수 있다. 최근 컴퓨터에 구현된 인간 수준의 지능과 관련하여 Artificial general intelligence (AGI) 라는 용어를 많이 사용한다.
2) Data-centric AI
1970년 현대와 비교하면 비교적 간단한 “decision trees” 모델을 만드는 것으로부터 시작되었으며, 최근에는 흔히 '딥러닝' 으로 일컫는 신경망 모델의 발전으로 큰 유명세를 얻고 있는 분야이다. Data-centric AI 는 “narrow AI” 또는 “weak AI" 라고도 불리지만, 최근 다양한 분야에서 여러 잠재력을 보여주었다.
수많은 양의 학습 데이터와 컴퓨팅 파워가 결합된 딥러닝 모델은, 음성인식, 게임 등 다양한 분야의 "narrow tasks" 에 적용되어 왔다. AI 기반의 예측 모델은 수많은 반복 훈련을 통해 점점 정확도가 높은 모델을 만들어 내고 있다. AI 분야의 bottle neck 은 사람이 labeling 한 학습용 데이터가 필요하다는 것인데, 최근 AI 가 가상의 labeled 데이터를 만들어내는 분야도 크게 발전하고 있다.
GPT-3 는 언어 모델은 OpenAI 에 2020년에 발표한 모델로 이러한' 학습용 데이터 생성' 분야의 잠재력을 보여주었다. GPT-3 은 수십억개의 문장을 통해 학습되었으며, 그럴듯한 문장을 자동으로 만들어낸다. 또한 질문에 대해 정말로 사람이 쓸법한 문장으로 적절한 답을 하기도 한다.
GPT-3 데모사이트 에는 다양한 분야의 GPT-3 open API 를 활용한 다양한 어플리케이션들의 리스트를 확인할 수 있다.
그러나 GPT-3 에는 여러 문제점들도 존재한다.
1) 비일관성: 같은 질문에 대해 답이 달라지는 경우가 있으며, 서로 모순되기도 한다.
2)할루시네이션: GPT-3 한테 1492년에 미국 대통령이 누구냐고 물으면 기쁘게 답변한다.
(실제로 1492년은 콜롬버스가 신대륙을 발견한 때로 미국 대통령은 존재하지 않았다.)
3) 높은 비용: 학습하는데 수많은 양의 데이터가 필요하고, 레이블이 있는 학습데이터는 비싸므로, 높은 비용이 요구되는 모델이다.
4)불투명성: 왜 GPT-3 가 그런 결론을 냈는지에 대해 종종 이해하기 어려울 때가 있다.
5)학습데이터 편향: GPT-3 는 학습 데이터를 흉내낸다. 학습 데이터는 웹으로부터 많이 수집되기 때문에 "toxic content" 가 많이 포함되어 있다. (sexism, racism, xenophobia 등등..)
요약하자면, GPT-3 는 신뢰하기 어려울 때가 많다. 즉, 인간 수준의 지능을 가지고 있는 것은 아니다.
이러한 한계점에도 불구하고 연구자들은 GPT-3 의 multi-modal versions 을 연구하고 있고 (multi-modal 이란 커뮤니케이션 채널이 여러개라는 의미이다.), 그 중 하나가 DALL-E2 와 같은 모델이다. DALL-E2 는 자연어를 인풋으로 받아, 실제적인 이미지를 생성하는 모델이다. GPT-3 는 인풋으로 텍스트를 받아 텍스트를 아웃풋으로 보여주는 모델이라면, DALL-E2 는 인풋으로 텍스트를 받아 이미지를 아웃풋으로 보여준다.
AI 개발자들은 이러한 DALL-E2 모델을 로봇, 생명과학, 화학 등 다양한 분야에 어떻게 적용할 수 있을지를 고민하고 있다.
Data-centric AI 의 한계점중 하나는 이러한 시스템들이 인간에 의해 만들어진다는 것이다. 어떠한 문제를 formulation 하고, 이를 해결하는 시스템을 설계하고 만드는 것은 인간이다. 피카소의 유명한 말이 있다.
“Computers are useless. They only give you answers.”
기술을 기반으로한 인간의 발전은 좋은 질문을 던지는 것에서부터 오는 경우가 많다고 생각한다. 예를 들어, 자율 주행차를 생각해보면, "자동차 운전 조작을 자동화할 수 있지 않을까?" 라는 질문에서 시작됐을 것이다. 이러한 질문을 해결하기 위한 방법을 공학적으로 설계하고, 여기에 AI 기술을 활용하여 "자율 주행차" 가 세상에 나올 수 있었을 것이다.
현재의 AI 는 질문에 대한 좋은 답을 줄 수 있지만, 새롭고 창의적인 질문을 던지거나, 해결 방법을 설계하지는 못한다. AI 가 "자동차 운전의 자동화" 를 생각하고, 이를 어떻게 구현할지 제시하지는 못하는 것처럼 말이다. 아직까지 컴퓨터는 답을 줄 뿐, 질문을 만들지는 못한다.
AI 시스템이 좋은 질문을 만들게 될 날이 오는 것을 기대할 것이다. 먼 훗날이 될 수 있겠지만 그 날이 오면, 오래된 과학의 도전적인 과제 중 하나인 인간 수준의 지능을 이해하고 구현하는 것에 대하여 해결의 실마리를 찾을 수 있을지도 모른다.
R 에서 난수를 생성 또는 랜덤 샘플링 작업의 결과가 일별로 바뀌도록 하고 싶을 때가 있다.
방법은 간단하게 일별로 random seed 를 동일하게 맞춰주면 된다.
특정 날짜 '01/06/2022' 를 integer 형으로 변환하면 일별로 동일한 숫자가 나오도록 구현할 수 있다.
library(tidyverse)
dayYear <- as.Date(Sys.Date(),format='%d/%m/%Y') %>% lubridate::yday() %>% as.integer()
set.seed(dayYear)
sample(nrow(10)) # 같은 날에는 동일한 순서의 숫자 10개가 나온다.
주의할점은 값이 정수를 갖도록 as.integer 함수를 통해 변환해주어야한다.
(만약 double 인 경우, 실제 시드는 매번 달라진다. 이는 컴퓨터가 double 형을 메모리에 저장하는 방식 때문일듯하다.)
최근 다양한 도메인에서 머신러닝이 활용되고 있다. 머신러닝의 문제점은 training data 가 필요하다는 것이다.
현실에서는 label 이 있는 데이터를 수집하기 어렵거나 높은 비용이 요구되는 상황이 많다.
semi-supervised learning 이러한 상황에서 전체 데이터의 일부에만 label 이 있을 때 사용한다.
왜할까?
semi-supervised learning 의 핵심 과정 중 하나는 unlabled data 를 labled data 로 변환하는 것이다. (이를 pseudo-labeling 이라고 한다.) 그런데, 전체 데이터의 일부에 lable 이 있다고 하면, 그 데이터를 training 데이터로 모델을 만들면 안 되는가?
위 그림을 보면, 소수의 labeled data 로 만든 decision boundary 보다 unlabled data 를 사용한 것이 더 세밀하다는 것을 직관적으로 알 수 있다. 즉, 더 많은 데이터들에 대한 generalization 이 잘 된다는 것이다.
semi-supervised learning 의 이점
labled data 와 unlabled data 를 combine 하는 것은 accuracy 를 상승시킨다. (may be due to generalization)
unlabled data 를 획득하는 것은 상대적으로 cheap 하다. -> 잘만 되면 더 비용효율적이다.
semi-supervised learning 의 가정
1) Continuity / smoothness assumption
"다차원 공간 상에서 가까운 거리에 있는 샘플들은 labeld 이 아마 같을 것이다." 라는 가정이다. 이는 지도학습에서도 마찬가지로 있는 가정이다. 다만 semi-supervised learning 에서는 가까운 거리에 있는 샘플들은 예외 없이 같은 label 이 된다. 라는 점이 다르다.
2) Cluster assumption
"데이터는 클러스터를 형성할 것이며, 같은 클러스터에 속한 샘플은 같은 label 을 공유할 가능성이 높다"라는 가정이다. (같은 label 을 가진 샘플들이 다양한 클러스터에 존재할 수는 있다.) 이는 clustering 알고리즘에서의 smoothness assumption 으로 볼 수 있다.
3) Manifold assumption
"고차원 공간의 데이터를 저차원 공간에 표현할 수 있다." 라는 가정이다. 이는 모델링에서는 당연한 가정이라고 볼 수 있다. 예를 들어, 수많은 피쳐 x 를 통해 하나의 y를 예측하는 과정이니 말이다. manifold assumption 성립하지 않으면 예측이 불가능하다고 볼 수 있다.
pseudo-labeling
labeled data 를 통해 unlabled data 를 labled data 로 변환하는 것을 말한다.
Active Learning
labeled data 를 주어진대로만 사용하지 않고, 재구성하는 전략을 말한다.
가장 성능을 높이는데 효과적인 샘플 (labeled or unlabeld) 에 대해 labeling 을 수행하는 전략이다.
supervised learning 에서 학습이 잘되는 일부 labeling data 만 사용할 수 있다.
semi-supervised learning 에서 pseudo-labeling 하는 것도 active learning 의 일종이다.
Margin sampling
margin sampling 은 active learning 의 예시로 decision boundary 를 효율적으로 찾는 방법중 하나로 가장 애매한 (uncertain) 샘플을 labeled data 로 변환해 나가면서 decision boundary 를 업데이트 해나가는 방법이다. 아래와 같이 random 하게 업데이트 하는 것보다 빠르게 클래스를 잘 분류할 수 있는 boundary 를 찾을 수 있다.
Weak supervision
ground truth label 이 없을 때, subject matter expert (SME) 가 휴리스틱한 방법으로 labeling 하는 것을 말한다.
만약 이 방법으로 어떤 샘플이 A 레이블에 할당되었다고 하더라도, 실제로는 A 레이블이 아닐 가능성을 갖고 있다.
이를 noisy label 이라고 한다.
Snorkel
스탠포드에서 2016년에 개발되었다. manual labeling 을 줄이는 방법으로 training data 를 구축하기위한 라이브러리이다. snokel 은 weak supervision 상황을 해결하는 것을 돕는다.
semi-supervised learning 의 분야
semi-supervised learning 의 핵심과정인 pseudo-labeling 을 어떻게 수행할 것인가?
단순히 model prediction 을 통해 pseudo-labeling 을 하는 것보다 성능이 좋은 다양한 방법론들이 존재한다.
위 분류에서는 semi-supervised learning 을 크게 transductive 와 inductive로 나눈다. [1]
ㄴ transductive 와 inductive 의 차이를 데이터 과점에서 본 포스팅 (link)
참고자료 [2] 에서는 1) graph-based 방법과 2) consistency-based 방법으로 나누기도했다. Graph-based method은 대표적으로 Label propagation 방법이 있다. consitency-based method 는 최근 각광받고 있는 방법으로 Mixmatch 를 예로 들 수 있다.
참고자료
[1] Van Engelen, Jesper E., and Holger H. Hoos. "A survey on semi-supervised learning." Machine Learning 109.2 (2020): 373-440