728x90

공간분석 프로젝트에서 사용된 주요 전처리 함수 정리하여 포스팅하였습니다.


import pandas as pd
import numpy as np
import os
from datetime import datetime
from collections import OrderedDict
from shapely.geometry import Point
import geopandas as gpd
import requests
from tqdm import tqdm

## 구급일지 data preprocessing
# 나이대별 개월수 정의
age_dic = {
    '0세' : list(range(12)),
    '1세' : list(range(12,24)),
    '2세' : list(range(24,36)),
    '3세' : list(range(36,48)),
    '4세' : list(range(48,60)),
    '5세' : list(range(60,71)),
    '6세' : list(range(72,83))
}

# 연령 → 개월수 변환
def pre_age_calc(value):

    if value in age_dic['0세']:
        age_ = 0
    elif value in age_dic['1세']:
        age_ = 1
    elif value in age_dic['2세']:
        age_ = 2
    elif value in age_dic['3세']:
        age_ = 3
    elif value in age_dic['4세']:
        age_ = 4
    elif value in age_dic['5세']:
        age_ = 5
    elif value in age_dic['6세']:
        age_ = 6
    else:
        age_ = 999
    return age_

# 주민등록번호에서 환자 개월수 추출
def pre_month_calc(row):
    jumin_number = row['환자주민등록번호']
    specific_date = row['출동년월일변환']
   
    # 개월수 계산
    try:
        birth_year = int(jumin_number[:2])
        birth_month = int(jumin_number[2:4])
        birth_day = int(jumin_number[4:6])

        specific_year = int(specific_date[:2])
        specific_month = int(specific_date[2:4])
        specific_day = int(specific_date[4:6])

        age_in_months = (specific_year - birth_year) * 12 + (specific_month - birth_month)

        # 생일이 지나지 않은 경우는 1 차감
        if specific_month < birth_month & (specific_month == birth_month and specific_day < birth_day):
            age_in_months -= 1

    except:
        age_in_months = np.nan

    # 환자연령계산
    age = pre_age_calc(age_in_months)
   
    return pd.Series([age_in_months,age])

# 날짜형식변환
def pre_date_trans(original_date):
   
    try:
        # 날짜 문자열을 datetime 객체로 파싱
        date_obj = datetime.strptime(original_date, "%Y-%m-%d")

        # 새로운 형식으로 날짜 문자열 생성
        new_format_date = date_obj.strftime("%y%m%d")

        # 결과 출력
        return new_format_date
    except:
        return 999999
   
# 중복되는 부분을 제거하고 하나로 통합
def remove_duplicate_part(address):
    parts = address.split()
    unique_parts = []
    for part in parts:
        if part not in unique_parts:
            unique_parts.append(part)
    return ' '.join(unique_parts)

# 심정지 여부 컬럼 생성
def pre_coma_check(df):
    df['심정지여부'] = '미포함'  # 초기값을 "미포함"으로 설정
    for index, row in df[['환자증상1','환자증상2','환자증상3','환자증상4','환자증상5']].iterrows():
        for col in ['환자증상1','환자증상2','환자증상3','환자증상4','환자증상5']:
            value = row[col]
            if isinstance(value, str) and '심정지' in value:
                df.at[index, '심정지여부'] = '포함'
                break
    return df

# 신고to현장 소요시간 계산
def pre_report_to_arrival(df):
    df['신고년월시각'] = pd.to_datetime(df['신고년월일'].astype('str') + ' ' + df['신고시각'].astype('str'))
    df.loc[df[df['현장도착년일시'] != ':'].index,'현장도착년일시'] = df.loc[df[df['현장도착년일시'] != ':'].index,'현장도착년일시'].str.replace(':', ' ', 1) #현장도착시각 변환
    df['현장도착년일시_변환'] = pd.to_datetime(df[df['현장도착년일시'] != ':']['현장도착년일시'])
    df['신고현장_소요시간'] = df['현장도착년일시_변환']  - df['신고년월시각']
    return df

# 현장to병원 소요시간 계산
def pre_spot_to_hospital(df):
    df['현장출발년월시각'] = pd.to_datetime(df[df['현장출발년월일'].notnull()]['현장출발년월일'].astype('str') + ' ' + df[df['현장출발시각'].notnull()]['현장출발시각'].astype('str'))
    df['병원도착년원일시각'] = df[df['병원도착년월일'].notnull()]['병원도착년월일'].astype('str') + ' ' + df[df['도착시간1_변환'].notnull()]['도착시간1_변환'].astype('str')
    df['병원도착년원일시각'] = pd.to_datetime(df[df['병원도착년월일'].notnull()]['병원도착년월일'].astype('str') + ' ' + df[df['병원도착시각'].notnull()]['병원도착시각'].astype('str'))
    df['현장병원_소요시간'] = df['도착시간1_변환'] - df['현장출발년월시각']
    return df

# 이송거부에 따른 재이송
def pre_arrival_1(row):
   
    if pd.isna(row[0]):
        return pd.Series([pd.NaT, "미재이송"])
   
    # row[0](도착시간1이 존재 하는 경우)
    else:
        try:
            date_string = row[0].astype(str).replace('.',"")
        except:
            date_string = str(row[0]).replace('.',"")
           
        # 문자열을 datetime 객체로 파싱
        if len(date_string) == 15:
            date_string = date_string[:-1] # 맨끝자리제거

        try:
            original_format = "%Y%m%d%H%M%S"  
            parsed_datetime = datetime.strptime(date_string, original_format)
        except:
            original_format = "%Y%m%d%H%M%S"  
            parsed_datetime = datetime.strptime(date_string, original_format)

        # 새로운 형식으로 날짜 문자열 생성
        new_format = "%Y-%m-%d %H:%M"  # 원하는 출력 문자열 형식
        formatted_date_string =pd.to_datetime(parsed_datetime.strftime(new_format))
       
        if pd.isna(row[1]):
            return pd.Series([formatted_date_string, '미재이송'])

        else:
            return pd.Series([formatted_date_string, '재이송'])

#좌표계 변환
def pre_crs(df, trans_after_crs='EPSG:5179', trans_before_crs='EPSG:4326'):
    df_temp = df.copy()
    # df의 초기 설정 좌표가 없는경우
    if df.crs == None:
        df_temp.crs = trans_before_crs # 초기 crs
        df_temp = df_temp.to_crs(trans_after_crs) # 변환 crs
        is_inf = df_temp['geometry'].apply(lambda geom: 'Infinity' in str(geom))      

        if len(df_temp[is_inf]) >= 1:  # 모두 inf 값인 경우
            df.crs = {'init': 'epsg:{}'.format(int(trans_after_crs.split(':')[1]))}
        else:
            df.crs = trans_before_crs
            df = df.to_crs(trans_after_crs)

            return df#.to_crs(trans_after_crs)#.reset_index(drop=True, inplace=True)
       
    # 좌표계가 기존에 지정이 된 경우      
    else:
        df = df.to_crs(trans_after_crs)
    return df

def pre_cc(file_nm=None, path='D:\더아이엠씨\소방청\데이터구축\행안부_도로명DB전국수치',encoding='utf8'):
   
    # 빈 데이터프레임 생성
    df = pd.DataFrame()
   
    # path 경로 안에 있는 파일 리스트
    path_info =  os.listdir(path)
   
    # 파일 이름이 각각 같은 경우
    if file_nm:
        for city in tqdm(path_info):
            # city는 폴더가 서울,경상북도 등 17개 광역이 폴더로 존재 했을 경우이며 그 안에 파일 명이 모두 동일하다
            df_ = gpd.read_file(f'{path}/{city}/{file_nm}.shp', encoding=encoding)
            df_ = pre_crs(df_)

            # 병합
            df = pd.concat([df, df_], ignore_index=True)
   
    # 파일 이름이 각각 다른 경우
    else:
        file_nms = [file for file in path_info if file.endswith('.shp')]
        for file in tqdm(file_nms):
            df_ = gpd.read_file(f'{path}/{file}', encoding=encoding)
            df_ = pre_crs(df_)
           
            # 병합
            df = pd.concat([df, df_], ignore_index=True)
           
    return df

## spatial data preprocessing
# 위경도를 geometry 타입으로 변경
def pre_trans_geometry(df,var1='Longitude',var2='Latitude'):
    from shapely.geometry import Point
    import geopandas as gpd
   
    # Point 객체를 생성하여 'geometry' 열에 저장
    geometry = [Point(xy) for xy in zip(df[var1], df[var2])]

    # GeoDataFrame 생성
    geo_df = gpd.GeoDataFrame(df, geometry=geometry)

    return geo_df

# pandasframe → geopandas 변환
def pre_trans_geodataframe(df, col: str):
    from shapely import wkt
    df_temp = df.rename(columns={col:'geometry'})
    df_temp['geometry'] = df_temp['geometry'].apply(lambda x: wkt.loads(str(x)) if pd.notnull(x) else x)
    df = gpd.GeoDataFrame(df_temp, geometry='geometry')
    df = pre_crs(df)
    return df

# 중복되는 부분을 제거하고 하나로 통합
def remove_duplicate_part(address):
    parts = address.split()
    unique_parts = []
    for part in parts:
        if part not in unique_parts:
            unique_parts.append(part)
    return ' '.join(unique_parts)

# 공간 내 카운트 함수
def pre_count(df1, df2, groupby_on = 'gid', nm = 'cnt', predicate='contains'):
   
    # 그리드에 포함된 출입구 개수를 알기 위한 sjoin
    temp = gpd.sjoin(df1,df2, how = 'inner', predicate = predicate)
   
    # 'gid' 열을 기준으로 그룹화하고 각 그룹의 행 수 세기
    grouped = temp.groupby(groupby_on).size().reset_index(name=nm)
   
    return pd.merge(df1,grouped, on ='gid',  how = 'left')

## geocoding
# vworld geocoding api
def geocoder(address):
    key = 'API KEY'
    params = {
        "service": "address",
        "request": "getcoord",
        "crs": "epsg:4326",
        "address": str(address),
        "format": "json",
        "type": "PARCEL",
        "key": f"{key}"
    }
    response = requests.get(apiurl, params=params)
    if response.status_code == 200:
        data = response.json()
        try:
            x = data['response']['result']['point']['x']
            y = data['response']['result']['point']['y']
        except:
            x = 0
            y = 0
    return pd.Series([y,x])

# naver geocode api
def sobang_naver_geo(data, client_id, client_pw) :
    import pandas as pd                                 # 데이터 프레임을 다루는 pandas 라이브러리
    from tqdm import tqdm                               # 진행 상황을 시각적으로 보여주는 tqdm 라이브러리
    import numpy as np                                  # 수학적 연산에 사용되는 numpy 라이브러리
    import urllib                                       # url 정보를 다루는 urllib 라이브러리
    import json                                         # json 파일을 다루는 json 라이브러리

    import warnings                                     # 파이썬 경고 관련 warnings 라이브러리
    warnings.filterwarnings('ignore')                   # 파이썬 경고 무시

    # 기본 url

    # 데이터 저장용 리스트
    geo_coordi = []
    geo_distance = []

    # 사업장주소 컬럼 위도/경도 변환
    for add in tqdm(data['주소1']):
        add_urlenc = urllib.parse.quote(add)

        # 주소 포함 url
        url = api_url + add_urlenc

        # url 요청용 주소 변환
        request = urllib.request.Request(url)
        # url 요청용 주소에 id 값 추가
        request.add_header("X-NCP-APIGW-API-KEY-ID", client_id)
        # url 요청용 주소에 Password 값 추가
        request.add_header("X-NCP-APIGW-API-KEY", client_pw)

        # url 요청
        try :
            response = urllib.request.urlopen(request)
        # 주소가 검색이 안되면 위도/경도 값 0으로 설정
        except urllib.error.HTTPError as e:
            latitude = 0
            longitude = 0
            geo_state = '확인불가'

        else :
            # 주소가 정상적으로 반환되면
            rescode = response.getcode()
            if rescode == 200 :
                # 반환된 정보 저장
                response_body = response.read().decode('utf-8')
                response_body = json.loads(response_body)
                if 'addresses' in response_body:
                    try:
                        latitude = response_body['addresses'][0]['y']
                        longitude = response_body['addresses'][0]['x']
                        geo_state = response_body['addresses'][0]['distance']
                       
                    except:
                        latitude = 0
                        longitude = 0
                        geo_state = '확인불가'
                # 반환된 정보에 내용이 없을 때 위도/경도 값 0으로 설정
                else :
                    latitude = 0
                    longitude = 0
                    geo_state = '확인불가'

        # 반환된 값 저장    
        geo_coordi.append([latitude, longitude])
        geo_distance.append(geo_state)
   
    # 위도/경도 데이터 추가
    df = pd.concat([data, pd.DataFrame(geo_coordi, columns = ['위도', '경도'])], axis = 1)

    # 좌표 거리 데이터 추가
    # print('geo_distance',geo_distance)
    df['좌표 변환 거리'] = geo_distance
    # # 데이터 저장
    # df.to_csv('./청주시 양봉농가 등록현황_위도 경도 추가.csv', index = False, encoding = 'cp949')
   
    return df

# kakao geocode api
def kakao_search(keyword2):
    from PyKakao import Local
    api = Local(service_key = "API KEY")

    df1 = api.search_address(keyword2, dataframe=True)
    if df1.empty:
        df2 =  api.search_keyword(keyword2, dataframe=True)
       
        if df2.empty:
            return None
        else:
            return [df2.loc[0]['y'],df2.loc[0]['x'], df2['address_name'][0]]
    else:
        return [df1.loc[0]['y'],df1.loc[0]['x'], df1['address_name'][0]]

# 다양한 주소 형태의 api 적용
def kakao_grid_search(keword1):
    words = keword1.split(" ")
    unique_words = list(OrderedDict.fromkeys(words))
   
    # 시,구,동, 번지
    keword_1 = " ".join(unique_words[:4])
    result_1 = kakao_search(keword_1)
    if result_1 is None:
        # 시,구,동, 건물
        try:
            keword_2 = " ".join(unique_words[:3] + [unique_words[4]])
        except:
            keword_2 = " ".join(unique_words[:3])
        result_2 = kakao_search(keword_2)
       
        if result_2 is None:
            # 시,구,동
            keword_3 = " ".join(unique_words[:3])
            result_3 = kakao_search(keword_3)
            if result_3 is None:
                return 0,0,np.nan
            else:
                return result_3[0], result_3[1], result_3[2]
        else:
            return result_2[0], result_2[1], result_2[2]
    else:
        return result_1[0], result_1[1], result_1[2]

## text data preprocessing
def process_commas(text):
    words = text.split(',')
    cleaned_words = [word.strip() for word in words if word.strip()]
    result = ','.join(cleaned_words)
    return result.replace(',,','')

## 시각화
def eda_viz_region_month(df,region='강원'):
    import seaborn as sns
    import matplotlib.pyplot as plt
    import matplotlib.font_manager as fm
    import matplotlib.patheffects as path_effects
    font_path = 'C:\Windows\Fonts\GULIM.ttc'  # 여기에 ttf 파일의 정확한 경로를 지정하세요.
    font_prop = fm.FontProperties(fname=font_path)
    plt.rcParams['font.family'] = font_prop.get_name()



    re_month = df.groupby(['지역','개월수']).count().reset_index()[['지역','개월수','환자연령_주민','구급보고서번호']]
    # 바 차트 그리기
    temp_gang = re_month[re_month['지역']==region][['개월수','구급보고서번호']]
    plt.figure(figsize=(30,15))
    plt.bar(temp_gang['개월수'], temp_gang['구급보고서번호'])

    # 각 막대에 값 레이블 추가
    bars = plt.bar(temp_gang['개월수'], temp_gang['구급보고서번호'])
    for bar in bars:
        yval = bar.get_height()
        plt.text(bar.get_x() + bar.get_width()/2, yval, '%d' % int(yval),
                va='bottom', ha='center', fontsize=12, #rotation=90,
                path_effects=[path_effects.withStroke(linewidth=3, foreground='white')])

    # 레이블 추가
    plt.xlabel('개월수',size=20)
    plt.ylabel('구급보고서번호',size=20)
    plt.title(f'전체 {region} 개월수 별 사고건수',size=20)

    # 그래프의 x축 눈금을 설정합니다.
    plt.xticks(temp_gang['개월수'].to_list(), size=15)

    # 바 차트 표시
    plt.show()
728x90

'데이터분석' 카테고리의 다른 글

사용자 사전 추가된 Mecab 형태소 분석 in Corab  (0) 2023.12.04
728x90

단어 통일화 및 사용자 사전 구축에 따른 mecab 형태소 분석을 Class로 정의하였습니다.

클래스 내 함수 def _morph는 변수명이 하드코딩으로 되어 있으므로 수정이 필요합니다.

또한, 해당 함수는 형태소분석기로 추출된 단어가 사고유형, 장소, 시간 등에 따라 분류가 되는 코드입니다.

만일 추출된 단어별로 재 분류가 필요 없다면 _ morph 함수를 수정해야 합니다.

 

# 사용자 사전 추가 및 형태소 분석 in corab
class Corab_morph():
    def __init__(self, df_path, ner_dic_path, user_dic_path, text_col = None, encoding = None):
        self.encoding = encoding
        self.df = self._read_file(df_path,encoding=self.encoding)
        self.ner_dic = self._read_file(ner_dic_path, sheet_name='전체')
        self.user_dic = self._read_file(user_dic_path, sheet_name='전체')
        self.text_col = text_col
        self.tokenizer = Mecab()

    def _read_file(self, path, sheet_name=None,  encoding = None):
        if '.csv' in path:
            file = pd.read_csv(path, encoding = encoding)
            return file
       
        if '.xlsx' in path or '.xls' in path:
            file = pd.read_excel(path)
            return file
        else:
            raise ImportError

    def _get_jongsung_TF(self, sample_text):
        sample_text_list = list(sample_text)
        last_word = sample_text_list[-1]
        last_word_jamo_list = list(j2hcj(h2j(last_word)))
        last_jamo = last_word_jamo_list[-1]

        jongsung_TF = "T"

        if last_jamo in ['ㅏ', 'ㅑ', 'ㅓ', 'ㅕ', 'ㅗ', 'ㅛ', 'ㅜ', 'ㅠ', 'ㅡ', 'ㅣ', 'ㅘ', 'ㅚ', 'ㅙ', 'ㅝ', 'ㅞ', 'ㅢ', 'ㅐ,ㅔ', 'ㅟ', 'ㅖ', 'ㅒ']:
            jongsung_TF = "F"

        return jongsung_TF
   
   
    # 사용자 사전 추가
    def _add_dic(self, word_list:list):
        with open("/tmp/mecab-ko-dic-2.1.1-20180720/user-dic/nnp.csv", 'r', encoding='utf-8') as f:
            file_data = f.readlines()

        # 단어추가
        for word in word_list:
            jongsung_TF = self._get_jongsung_TF(word)
            line = '{},,,,NNP,*,{},{},*,*,*,*,*\n'.format(word, jongsung_TF, word)
            file_data.append(line)

        # 덮어쓰기
        with open('/tmp/mecab-ko-dic-2.1.1-20180720/user-dic/nnp.csv','w', encoding='utf-8') as f:
            for line in file_data:
                print(line)
                f.write(line)
   
    def _replace_words(self, text):
        # word[0] : 본문내 단어, word[1] : 변경한 단어 e.g 제 주 -> 제주
        for idx, words in self.user_dic.iterrows():
            if len(words[0]) != 1:
                try:
                    text = text.replace(words[0], words[1])
                except:
                    pass
            else: # 한 글자는 변경x
                pass
        return text    
   
    # 형태소 분석
    def _morph(self, df, tokenizer):
        for idx,sent in tqdm(enumerate(df['정제_평가소견내용'])):
            spot_cls = []
            spot_word = []
            types_cls = []
            types_word = []
            cause_cls = []
            cause_word = []
            times_cls = []
            times_word = []
            dangers_cls = []
            dangers_word = []

            mecab_sent = tokenizer.morphs(sent)
            for word in mecab_sent: # 형태소 분리된 문장 sent

                if word in self.ner_dic['단어'].to_list():
                    ner = self.ner_dic[self.ner_dic['단어'] == word]['개체명'].to_list()[0]
                    cls = self.ner_dic[self.ner_dic['단어'] == word]['분류'].to_list()[0]

                    if ner == '사고유형':
                        if word not in types_word: # 중복 없애기 위함
                            types_cls.append(cls)
                            types_word.append(word)

                    elif ner == '장소':
                        if word not in spot_word:
                            spot_cls.append(cls)
                            spot_word.append(word)
                        # print('spot',spot)

                    elif ner == '위해품목':
                        if word not in dangers_word:
                            dangers_cls.append(cls)
                            dangers_word.append(word)

                    # print('cause',cause)
                    elif ner ==  '사고원인':
                        if word not in cause_word:
                            cause_cls.append(cls)
                            cause_word.append(word)

                    elif ner == '시간':
                        if word not in times_word:
                            times_cls.append(cls)
                            times_word.append(word)

                    # 위 세가지 경우말고는 다른건 제외
                    else:
                        pass

                # 단어가 사전에 없은 경우
                else:
                    continue

            # df에 넣기
            df.loc[idx,'types'] = ",".join(types_cls)
            df.loc[idx,'types_detail'] = ",".join(types_word)

            df.loc[idx,'spot'] =  ",".join(spot_cls)
            df.loc[idx,'spot_detail'] =  ",".join(spot_word)

            df.loc[idx,'cause']=  ",".join(cause_cls)
            df.loc[idx,'cause_detail']=  ",".join(cause_word)

            df.loc[idx,'object']=  ",".join(dangers_cls)
            df.loc[idx,'object_detail']=  ",".join(dangers_word)

            df.loc[idx,'time']=  ",".join(times_cls)
            df.loc[idx,'time_detail']=  ",".join(times_word)

        return df

    def run(self, word_rep = True):
       
        # 개체명 단어 추가
        self._add_dic(self.ner_dic['단어'].to_list())
       
        # 업데이트
        !bash /tmp/mecab-ko-dic-2.1.1-20180720/tools/add-userdic.sh # 해당코드를 실행시키면 자기가 관리하고 있는 모든 유저 딕셔너리를 실행, 업데이트
        !cd /tmp/mecab-ko-dic-2.1.1-20180720
        !make install
       
        # 데이터 정제
        if word_rep:
            self.df[f'정제_{self.text_col}'] = self.df[self.text_col].apply(lambda x: self._replace_words(x))
       
        # 형태소 분석
        df2 = self._morph(self.df, self.tokenizer)
       
        return df2
       
if __name__ == "__main__":
    # Mecab 설치
    !apt-get update
    !apt-get install g++ openjdk-8-jdk
    !pip3 install konlpy JPype1-py3
   
    # mecab-python의 버전 오류로 인해 아래 패키지를 설치하면 코랩에서 Mecab을 사용가능
    !pip install mecab-python3

    # 패키지 import
    from konlpy.tag import Mecab
    import pandas as pd
    from tqdm import tqdm
    try:
        from jamo import h2j, j2hcj
    except:
        ! pip install jamo
        from jamo import h2j, j2hcj

    files = Corab_morph(
        df_path='경로1',\
        ner_dic_path='경로2',\
        user_dic_path='경로3',\
        text_col = '평가소견내용' ,\
        encoding='cp949')
    df = files.run(word_rep=False)

 

728x90

'데이터분석' 카테고리의 다른 글

공간분석 프로젝트_함수정리  (0) 2023.12.04
728x90

선형회귀분석 중 조절변수, 매개변수, 스케일링에 따른 해석차이와 방법을 알고자 공부한 내용을 공유드려요

이번 기회를 통하여 여러분도 선형회귀분석의 새로운 관점을 얻고 가시길 바랍니다 :]

틀린내용이 있다면 댓글로 남겨주시면 감사하겠습니다

선형회귀_최종본.ipynb
1.07MB

 

 

728x90
728x90

개요

대화체 문장간의 유사도를 구하여 추천 알고리즘으로 만들기 위한 목적으로 스터디 하고 있습니다.

임베딩는 크게 단어임베딩 VS 문장임베딩으로 나눌수가 있습니다.

단어임베딩은 각 단어에 대해서 vector 진행 후 유사도를 구하는데 이에 단점은 문맥을 고려 하지 않는다는 점이다. 문맥을 포함한 임베딩을 만들기 위해서 문장임베딩이 사용이 되는데, 여기에는 Doc2vec 혹은 Transformer과 같은 딥러닝의 임베딩을 예로 들수가 있다.

 

필자는 Word2vec(단어임베딩)를 사용하여 문장 전체 임베딩을 구하는 sentence2vec을 사용하여 실험해 보았습니다.

자세한 코드 내용은 https://github.com/stanleyfok/sentence2vec 에서 볼수 있습니다.

사용된 데이터는 유튜브 댓글임을 알려드립니다.

 

코드

import re
import numpy as np
from numpy import dot
from numpy.linalg import norm
from gensim.models import Word2Vec
from nltk import word_tokenize


class Sentence2Vec:
    def __init__(self, model_file):
        self.load(model_file)

    def load(self, model_file):
        self.model = Word2Vec.load(model_file)

    def get_vector(self, sentence):
        # convert to lowercase, ignore all special characters - keep only
        # alpha-numericals and spaces
        sentence = re.sub('[^ ㄱ-ㅣ가-힣]+', '', str(sentence))

        vectors = [self.model.wv[w] for w in word_tokenize(sentence)
                   if w in self.model.wv]

        v = np.zeros(self.model.vector_size)

        if (len(vectors) > 0):
            v = (np.array([sum(x) for x in zip(*vectors)])) / v.size

        return v

    def similarity(self, x, y): #코사인 유사도 
        xv = self.get_vector(x)
        yv = self.get_vector(y)

        score = 0

        if xv.size > 0 and yv.size > 0:
            score = dot(xv, yv) / (norm(xv) * norm(yv))

        return score

문장 벡터를 구하기 위한 Class를 선언 합니다. def similarity 함수에서 문장간의 유사도를 구할수 있습니다.

 

 

import pandas as pd
import re
import logging
import nltk
from nltk import word_tokenize
from nltk.corpus import stopwords
from gensim.models import Word2Vec
nltk.download('stopwords')
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s',
                    level=logging.INFO)

def cleanData(documents):
    # for i,document in enumerate(documents):
        
        
        #(), [] 패턴 삭제
        # document = re.sub('\[[a-zA-Z0-9가-힣-=+’:\'./!★\s]{0,60}\]|\([a-zA-Z0-9가-힣-=.\'_/‘’:眞&?★·!,\s]{0,60}\)',"",document)
        
        # #한글 및 띄어쓰기만 남기고 제거
    document = re.sub('[^ ㄱ-ㅣ가-힣]+',"",str(documents))
        #특수문자 제거
        # document = re.sub('[★◆●▶♥●◆!@#$-=+,#/\?:^$.@*\"※~&%ㆍ!』\\‘|\(\)\[\]\<\>`\'…》;:?↑→‘’]',"",document)  
        
    return document

df = pd.read_csv('./testing_csv_raw.csv')

# drop duplicate rows
df = df.drop_duplicates(subset='string')

# clean data
df['string'] = df['string'].map(lambda x: cleanData(x))
# print('strings',df['string'] )

# get array of strings
strings = df['string'].values.tolist()

# tokenize the each string
tok_strings = [word_tokenize(string) for string in strings]

# refer to here for all parameters:
# https://radimrehurek.com/gensim/models/word2vec.html
model = Word2Vec(tok_strings, sg=1, vector_size=100, window=5, min_count=5, workers=4,
                 epochs=100)

# save model to file
model.save('./model/utube_strings.model')

문장 정제 후 word_tokenize로 문장내 단어간의 분리를 한뒤 각 Word2Vec, 즉 각 단어 별로 벡터화를 진행해줍니다.

각 단어별로 단어를 구하면 아래 그림처럼 평균으로 만들어서 문장벡터로 변환할수 있습니다.

 

 

해당 코드 결과, "잘 어울려요"와 비슷한 문장을 내림차순으로 정렬한 모습입니다.

제 개인적인 생각으로는 "잘" 이라는 요소 때문에 어울린다는 말과는 별개로 추천되고 있다는 느낌을 받았습니다.

아마도 단어 기반의 벡터로 유사도를 구했기 때문임을 추측해 볼수 있습니다. 결국 댓글과 같은 문맥이 중요한 텍스트는 문장 임베디을 사용하는 것이 유리하다고 판단 됩니다. 

따라서 문장 임베딩 기법 중 Doc2vec에 대하여 스터디 진행해 볼 계획입니다. 

 

728x90
728x90

mlops에 관심을 가지게 되어 패캠의 머신러닝 서비스 구축을 위한 실전 MLOps 올인원 패키지 Online 이라는 강의를 수강하게 되었습니다. 생소한 단어와 개념이 많아서 블로그에 정리를 하고자 합니다. 만일 설명이 틀리거나 수정할 부분이 있다면 수정하도록 하겠습니다! 실습 자료는 아래 링크를 통해 볼수 있습니다.

https://rigorous-firefly-d50.notion.site/MLOps-486a7bcd320b4e9f93a70b5691b88dd1

 

패스트캠퍼스 MLOps 실습자료

도커와 쿠버네티스

rigorous-firefly-d50.notion.site

 

 

mlops 수행에 있어서 도커 & 쿠버네티스를 왜 사용해야하는가?

AI 개발을 하다보면 가장 만나는 난관이 패키지의 버전 문제라고 생각한다. 실례로 회사 GPU 서버에 연결하기 위해서 cuda와 pytorch를 설치했는데 버전 문제때문에 시간을 할애해야 했다. 그렇다면 나의 코드를 다른 곳에서 작동시킬려면 다시 패키지를 설치해야 할까? 우리가 불편함을 느끼는 기술은 이미 구축되어 있다. 나의 ML 코드를 작동시키기 위한 패키지를 코드와 함께 묶어서 전달하면 된다. 어떠한 OS 이든 환경이든 상관하지 않은채 내 코드만의 독립된 환경을 구축하는 겁니다(Reproducibility)

 

그뿐만아니라 ML 학습의 스케쥴링, 병렬작업 관리, 유휴작업관리 등 스케쥴링 기능을 하며(job Scheduling) 자운용중인 서버에서 발새하는 장애, 트랙픽 대응하는 역할(auto-healing & auto-scaling)도 포함됩니다.

위에 언급된 기술들은 Docker와 쿠버네티스로 구축할수 있습니다.

 

 

*도커와 쿠버네티스

docker의 containerization, 쿠버네티스의 container Orchestration에 대해서 알아봅시다.

containerization는 위 기술 중 Reproducibility에 해당한다. 아래 그림을 보면

 

내가 필요한 것들을 한 곳에 모아서 묶어 패키징 하는 것을 containerization이라 합니다. 참고로 도커 이미지란 어떤 애플리케이션에 대해서,단순히 애플리케이션 코드뿐만이 아니라,그 애플리케이션과 dependent 한 모든 것을 함께 패키징한 데이터를 말한다. 다시 본론으로 돌아와서 이러한 containerization이 수십개가 동시에 운용이 되어야 한다면 사람이 일일이 괸리 해야할까? 아닙니다. 복수의 containerization을 사람대신 자동으로 일해주는 기술이 있는데 이를 쿠버네티스의 오케스트레이션이라 합니다.

 

오케스트레이션을 통해서, 메모리가 많이 필요한 컨테이너는 메모리가 많이 있는 서버로 연결해주는 등 사람이 하는 일을 컴퓨터가 알아서 할수 있도록 돕는다. 말 그대로 컨테이너를 지휘하는 역할입니다.

 

쿠버네티스는 사용감이 까다롭기 때문에 복잡성을 보완한 서비스가 이용되고 있다. 대표적으로 위 세개의 프로그램인데 데 물론 공짜는 아니고 사용료를 지불해야 합니다. 따라서 오픈형 쿠버네티스 버전을 이용하여 무료로 접근하고자 합니다. 

 

 

미니큐브라는 것을 통해 "나만의 작은 쿠버네티스"를 구축 할수 있다고 합니다. 그래서 본 강의는 미니큐브를 설치하여 진행하고 있습니다.

 

코드 알고리즘을 짜게되면 일일이 하나씩 다 지정을 해줘야 하는 번거로움이 존재합니다. 이렇게 하나부터 열까지 모두 명령을 쳐야 하는 것을 명령형 인터페이스 라고 합니다. 하지만 쿠버네티스는 사용자가 무엇을 하고자 하는지 목적만 받으면 내부적으로 어떻게 만들어야 할지는 알아서 진행해줍니다. 위 그림의 예시를 보면 이해가 되실거라 생각됩니다. 그러면 쿠버네티스가 오직 사용자의 목적만 입력 받으면 안에 내부적으로 어떻게 진행이 되는지 대략적으로만 살펴보겠습니다. 

 

 

필기 크기가 작은 점 양해 바랍니다 ;; 

쿠버네티스는 크게 control plane과 node로 나뉩니다. 제가 이해한 것을 예시로 들자면 control plane은 회사에서 부장급이라고 보면 됩니다. node는 실질적으로 일하는 직원입니다. 고객의 요구가 들어오면 부장이 스케쥴링을 하여 효율적으로 직원들에게 일을 뿌려 주는 모습과 흡사합니다.

 

도커의 컨테이너를 지휘하는 기술은 오케스트레이션 뿐만 아니라 다양하게 있지만 현재 가장 많이 사용되는 것이 바로 이 쿠버네티스입니다.

 

 

 

 

강의에서는 윈도우,맥 사용자 구분없이 수업에 통일성을 주기위해서 가상머신(Virtual Box)을 설치 한 후에 그위에 우분투(20.004.3 LTS)를 올려서 실습을 진행함을 말씀드립니다. 

 

 

 

가상머신의 설치와 환경설정들은 실습자료 사이트를 참고하시면 했으며 아래 내용은 제가 직접 실습하면서 겪었던 오류나 기억해야할만한 과정들만 기록해 두었습니다.(저처럼 초보분들이 당황할만한 내용을 공유하고자)

 

1. 복붙하기

- 복붙은 언제나 필수 기능입니다. 윈도우에서 복사 후 shift+inert로 가상머신 터미널에 넣으면 되는데, 수행이 되지 않았습니다. 구글링하면서 양방향 설정도 해봤지만 실패 하였습니다. 실질적으로 도움 받았던 블로그는 아래 링크를 걸어두었습니다. 저처럼 복붙이 안되시는 분이 있다면 참고 바라겠습니다.

 

https://whareview.tistory.com/4

 

VirtualBox 가상머신(Ubuntu Linux)으로 복사/붙히기 안될 때

VirtualBox 로 Ubuntu 를 사용할 때 host (windows 10) 으로 clipboard 복사 붙히기가 안되어서 매우 짜증날 때가 있다. virtualBox 머신 세팅에 보면 아래 스크린 샷과 같이 클리보드 공유 -> 양방향 (Bidirectio..

whareview.tistory.com

 

2. vi(편집기)

실습 내용에 따라 Dockerfile을 만들고 vi를 통해서 편집기 모드로 변환했습니다. 해당 모드에서는 명령어를 자유롭게 기술하는 편리함을 제공받을수 있습니다.

 

2.1 입력모드 진입

- 처음 진입 시 입력어가 먹질 않는다. vi는 명령어모드와 입력모드로 나뉘어 집니다. 처음 들어가게 되면 자동으로 명령어 모드로 셋팅되어 있으며 입력하고자 할때는 "i" or "a" 키를 먼저 눌러주면 입력이 가능합니다. 반대로 입력모드에서 명령어 모드로 갈려면 esc를 눌러주면 됩니다.

그렇다면 명령어모드에서는 어떤걸 할수 있는지 궁금해 집니다. 해당 내용은 https://opentutorials.org/course/730/4561 블로그를 참고 해주시면 될것같습니다. (참고로 편집기를 종료하고자 할때는 명령어모드에서 수행해야합니다)

 

2.2 입력한 문자를 수정 or 삭제

첫줄에 입력하고 enter를 치면 아랫줄로 변경된다. 첫줄을 수정 할려고 윗쪽 방향키를 누르면 "A"가 나올뿐 첫줄로 옮겨 지지 않았습니다. 이것저것 눌러보니까 자유로운 줄 변경을 위해서는 shift + 왼쪽 or 아래 방향키를 먼저 눌러준 후에 이동 가능합니다.

 

 

3. registry(저장소)

실습 자료 사진

 

- 도커의 이미지를 남들과 공유하기 위해서 저장을 해야합니다. 저장된 저장소로부터 이미지를 다운 받아서 사용할수 있게 됩니다. 

   -d(백그라운드)에서 실행하여 5000번포트에서 registry라는 이름의 도커 컨테이너스를 run(실행)한다.

- registry라는 곳에 image를 태깅 후 push해준다. 아래 그림 참고바랍니다.

실습 사이트 사진

728x90
728x90

2021.12.09 - [NLP/패캠_자연어 입문] - 10. 차원축소

 

10. 차원축소

차원이 높아질수록 sparse하게 분포되므로 모델링하기가 어려워진다. 예를들어 Kmean는 고차원의 데이터를 다룰 경우 성능이 저하되며 해석도 난해하게 된다. 따라서 쓸데없이 공간만 차지 하는

ghdrldud329.tistory.com

 

728x90
728x90

2021.12.09 - [NLP/패캠_자연어 입문] - 9.Mse Loss

 

9.Mse Loss

Classification는 multinomial(다항분포)를 따른다고 가정하에 CE를 통한 minimize를 진행했다. 이산부포가 아닌 연속형 분포일 경우에는 뉴럴 네트워크의 출력이 가우시안 분포를 따른다는 가정하에 MSE가

ghdrldud329.tistory.com

 

728x90
728x90

차원이 높아질수록 sparse하게 분포되므로 모델링하기가 어려워진다. 예를들어 Kmean는 고차원의 데이터를 다룰 경우 성능이 저하되며 해석도 난해하게 된다. 따라서 쓸데없이 공간만 차지 하는 차원 때문에 성능 저하에 우려가 된다. 이를 해결하기 위해선 적절하게 저차원에서 실행하는 것이 옳다.

 

*차원 축소

 

PCA는 어떤 샘플이 있을 때, 이를 잘 설명하는 새로운 축을 찾아내는 방법이다. 축이 분포를 잘 설명한다는 뜻은 무엇일까를 한번 생각해보자.

 

위 두 조건을 만족했을 때 설명을 잘 해준다고 볼수 있다. 검은점이 기존의 분포이며 이 것들이 빨간점으로 projection된다. 이때 빨간점끼리 서로 멀어지도록 해야 한다. 두번째 조건은 projection할 때, 검은점과 검은 선 사이의 거리의 합이 최소가 되야 한다. 최소가 되어야 하는 이유는 Projection되는 거리 만큼 정보의 손실이 발생하기 때문이다.(손실 압축)

728x90

'NLP > 패캠_자연어 입문' 카테고리의 다른 글

9.Mse Loss  (0) 2021.12.09
8.Cross_entropy  (0) 2021.12.07
7.Kullback  (0) 2021.12.07
6. MAP(Maximum A Posterior)  (0) 2021.12.01
5. MLE 수식  (0) 2021.11.29

+ Recent posts