Kategorik Verilerdeki Outlier Tespiti İçin Yeni Algoritma Önerisi

Volkan Yurtseven
Akbank Teknoloji
Published in
10 min readAug 15, 2023

Observation Based Outlier Detection

GİRİŞ

Bugünlerde yapay zeka ve makine öğrenimi teknolojileri hakkında bir şey duymayan kalmamıştır. Her ne kadar daha çok, müşteriye dokunan kısımlar ön plana çıkmış olsa da kurumlar middle ve back office çalışmalarında da bu teknolojileri kullanmaktadır.

İşte ben de bu gönderimde, back office kategorisinde değerlendirilebilecek bir çalışmanın detaylarını paylaşacağım. “İleri Analitik Yöntemlerle Veri Kalitesi Takibi” isimli bir önceki gönderimde kısaca belirttiğimiz bu çalışma, kendi geliştirdiğimiz bir ML algoritmasıdır. Bu arada, veri kalitesinden kaynaklı problemler günün sonunda müşterilere yanlış teklif çıkılmasına kadar gidebileceği için bu çalışma aslında dolaylı olarak front office kapsamında da düşünülebilir.

Kısaca hatırlayacak olursak, bu algoritma ile iki feature (Kolon) arasında bir anomali olup olmadığını bulmaya çalışıyoruz. Önceki gönderimde verdiğim örneği tekrarlamamak adına farklı bir örnek verelim; mesela “Müşteri Tipi” ve “Müşteri Segmenti” diye iki kategorik feature düşünelim. Bunlardaki dağılım aşağıdaki gibi olsun:

Bu tabloda kırmızı işaretli satırların outlier olduğu çok rahat tespit edilebiliyor. Ancak olası sayısız kolon kombinasyonunun alabildiği farklı değerleri de düşünecek olursak veri setini tek tek taramak yerine bunun programatik tespitini yapan bir algoritmaya ihtiyacımız vardır. Üstelik bu tespiti hem mevcut hem de sahadan yeni gelen veride yapabilmeliyiz ki bunun için neye outlier, neye inlier diyeceğini bilen, yani bunu öğrenmiş olan bir algoritma gereklidir.

Bununla birlikte buradaki çıktıların işbirimi gözüyle mutlaka değerlendirilmesi gerekmektedir, zira her outlier hatalı olmak durumunda değildir. Mesela özel ve ender bir durumdan dolayı kurumsal tipli müşteriler için de “Affluent Segment” tayin ediliyor olabilir. Veri bilimci olarak bizlerin amacı bu outlier kayıtları ortaya çıkarmak. Ancak onlara gerçekten nasıl muamele edileceğine iş birimiyle birlikte karar vermek gerekiyor.

Algoritmanın detaylarına geçmeden önce kısa bir terminolojik bilgilendirme yapmak faydalı olacaktır. Literatürde mevcut verideki problemlerin tespiti genel olarak Outlier veya Anomaly Detection olarak geçmektedir ki biz de bu ifadeleri birbiri yerine kullanacağız. Yeni gelen verideki outlier’ların, özellikle mevcut veri temizken sadece yeni gelenlerdeki outlier’ların tespiti ise, Novelty Detection olarak geçmektedir. Piyasadaki bazı algoritmalar bunlardan sadece birine odaklanırken bazısı her iki amaçla da kullanılabilmektedir. Bizim algoritmamız iki amaçla da kullanılabilmektedir.

YÖNTEM

Algoritmamızın mantığı oldukça basittir. Adından anlaşılacağı üzere, ilgili kolonlardaki değerlerin(*) birlikte görülme olasılığı (Prior probability, kısaca pp) ne kadar küçükse, outlier olma ihtimali o kadar yüksektir. Ancak bu görülme olasılığına iki yönlü de bakılmalıdır. Örneğin, yukarıdaki pp’ler belirlenirken, bireysel-pure bireysel ikilisinin görülme olasılığı 1. kolon açısından bakıldığında %58,8 iken 2. kolon açısından bakıldığında %100’dür. Bu iki olasılığın nasıl hesaba dahil olduğunu birazdan göreceğiz.

Algoritmanın adım adım aşamaları şöyledir:

1. Gözlemlerin frekans tablosu (Contingency table) çıkartılır.

2. Bu gözlemlerin her biri için pp hesaplanır (Her iki yönden de bakılarak 2 adet pp).

3. Eğer pp, belirlenen bir threshold’dan (Default %1) küçükse, bu kombinasyon outlier olarak işaretlenir (Diğerleri inlier). Ancak bunun bir istisnası vardır. Eğer ilgili kombinasyonun gözlem sıklığı belirlenmiş olan minimum değerin (local_cluster_threshold, default 100) üzerindeyse buna local cluster gözüyle bakılabilir ve pp’si ilgili threshold’dan küçük olsa bile outlier olarak işaretlenmez. Örnekte bulunan mavi satırdaki durum buna örnektir.

4. Outlier olan kombinasyonlar da inlier olanlar da bir dataframe’de tutularak eğitim (Fit) işlemi tamamlanır.

5. Son olarak, istenirse mevcut veri için, istenirse de sahadan yeni gelen veri için predict metodu ile tahminleme yapılır. Tahminleme yapılırken ham verideki ilgili kolon kombinasyonu eğitilmiş verideki inlier’lar ile kıyaslanır. Eşleşme varsa 1 (Inlier), yoksa -1 (Outlier) yazılır. Inlier’larla kıyaslanma sebebi, daha önce görülmeyen bir kombinasyonun da sahadan gelme ihtimali nedeniyledir. Örneğin, yukarıdaki tabloda bulunmayan Kurumsal-Private ikilisinin de outlier olarak işaretlenmesi gerekecektir.

* Örnek anlatımı basit olsun diye 2’li kombinasyon seçilmiştir. Ancak algoritma 3’lü kombinasyon için de çalışmaktadır. Çıktının gösterimi 4’ten itibaren zorlaştığı için 4 ve üzeri kombinasyonlarda çalışmamaktadır.

Bunlardan 3. maddeyi biraz daha açmak gerekir diye düşünüyorum, zira burada şu soru sorulabilir: Yukarıda iki tür pp belirlendi, siz hangisini dikkate alıyorsunuz? Cevap: İki orandan birisi koşulu sağlıyorsa outlier olarak işaretlemek için yeterlidir. Tabii üçlü bir kolon kombinasyon varsa o zaman toplamda üç farklı oran çıkar, bunlardan en az ikisi threshold’dan küçükse outlier olarak etiketlenir.

Mavi satırdaki durumu da biraz daha açmak gerekirse; burada pp’lerden ikincisi %100’dür, yani large corporation segmentindekilerin tamamı kurumsal tiplidir. Bu gayet normal iken, tersten bakıldığında ise anomali olduğu görülüyor. Ancak yukarıda bahsettiğimiz local cluster durumundan ötürü nihai olarak buna anomali demiyoruz.

Şimdi bu algoritmanın nasıl kodlandığına bakalım:

from sklearn.base import BaseEstimator, OutlierMixin, TransformerMixin
class ObservationBasedOutlierDetector(BaseEstimator, OutlierMixin):

def __init__(self, column_comb : list, outlier_threshold : float = 0.01, local_cluster_exception_list = [], local_cluster_thresh: int=100, local_cluster_jumping_decision_point: int = 10, local_cluster_ratio_decision_point:float = 0.05, returnOutlierLabels=True):

self.column_comb = column_comb
self.outlier_threshold = outlier_threshold
self.local_cluster_thresh = local_cluster_thresh
self.returnOutlierLabels = returnOutlierLabels
self.local_cluster_jumping_decision_point = local_cluster_jumping_decision_point
self.local_cluster_ratio_decision_point = local_cluster_ratio_decision_point
self.local_cluster_exception_list = local_cluster_exception_list
self.to_bo_evaluated = True

def fit(self,df:object):
try:
thresh=self.outlier_threshold
cols_df=df[self.column_comb].value_counts().reset_index()
cols_df["columns_combination"]=str(self.column_comb)
cols_df=cols_df.rename(columns={self.column_comb[0]:"first",self.column_comb[1]:"second",0:"count"})
if len(self.column_comb)==3:
cols_df=cols_df.rename(columns={self.column_comb[2]:"third"})
else:
cols_df["third"]=pd.NA
cols_df=cols_df[["columns_combination","first","second","third","count"]] #sort columns
if len(self.column_comb)==2:
cols_df["sum_12_or_only1"]=cols_df.groupby(["columns_combination","first"])["count"].transform("sum")
cols_df["sum_13_or_only2"]=cols_df.groupby(["columns_combination","second"])["count"].transform("sum")
cols_df["sum_23_or_nan"]=pd.NA
cols_df["ratio_12_or_only1"]=cols_df["count"]/cols_df["sum_12_or_only1"]
cols_df["ratio_13_or_only2"]=cols_df["count"]/cols_df["sum_13_or_only2"]
cols_df["ratio_23_or_nan"]=pd.NA
else:
cols_df["sum_12_or_only1"]=cols_df.groupby(["columns_combination","first","second"])["count"].transform("sum")
cols_df["sum_13_or_only2"]=cols_df.groupby(["columns_combination","first","third"])["count"].transform("sum")
cols_df["sum_23_or_nan"]=cols_df.groupby(["columns_combination","second","third"])["count"].transform("sum")
cols_df["ratio_12_or_only1"]=cols_df["count"]/cols_df["sum_12_or_only1"]
cols_df["ratio_13_or_only2"]=cols_df["count"]/cols_df["sum_13_or_only2"]
cols_df["ratio_23_or_nan"]=cols_df["count"]/cols_df["sum_23_or_nan"]
numberofTrue_atleast=len(self.column_comb)-1
conditions=np.array([(cols_df["ratio_12_or_only1"]<thresh)*1, (cols_df["ratio_13_or_only2"]<thresh)*1, (cols_df["ratio_23_or_nan"]<thresh)*1])
self.outlier_df_=cols_df[np.logical_and(cols_df["count"]<self.local_cluster_thresh,conditions.sum(axis=0)>=numberofTrue_atleast)]
self.inlier_df_=cols_df[np.logical_or(cols_df["count"]>=self.local_cluster_thresh,conditions.sum(axis=0)<numberofTrue_atleast)].copy() #bu niye copy de üstteki dğeil
self.inlier_df_["threevalues"]=self.inlier_df_["first"].astype(str)+"-"+self.inlier_df_["second"].astype(str)+"-"+self.inlier_df_["third"].astype(str)

if self.column_comb not in self.local_cluster_exception_list:
self.inlier_df_["count1"]=self.inlier_df_["count"].shift(1)
self.inlier_df_["count_prev_ratio"]=self.inlier_df_["count1"]/self.inlier_df_["count"]
self.inlier_df_.sort_values("count",inplace=True,ignore_index=True)
jumping_point=0
for idx,p in self.inlier_df_["count_prev_ratio"].iteritems():
if p>self.local_cluster_jumping_decision_point:
jumping_point=idx
break
lc_ratio_calculated=sum(self.inlier_df_["count"][:idx+1])/sum(self.inlier_df_["count"])
self.local_cluster_values={"jumping_point":jumping_point,
"lc_ratio_calculated":lc_ratio_calculated,
"max_ratio":np.round(self.inlier_df_["count_prev_ratio"].max(),2)
}
if lc_ratio_calculated>self.local_cluster_ratio_decision_point:
logging.debug(f"{self.column_comb} kombinasyonda, local clusterların toplam verisetinin büyüklüğüne oranı {lc_ratio_calculated:.2f} olup eşik değerden({self.local_cluster_ratio_decision_point}) yüksek olduğu için modellemeye dahil olmayacak")
self.to_bo_evaluated=False #buna göre de predict de sonuç üretmiyor, None dönüyoruz
except Exception as e:
logging.exception(f'ML fit error: {e}')

return self

def predict(self,df:object):
try:
if self.to_bo_evaluated:
third="-"+df[self.column_comb[2]].astype(str) if len(self.column_comb)==3 else "-<NA>"
threevalues=df[self.column_comb[0]].astype(str)+"-"+df[self.column_comb[1]].astype(str)+third
col_=str(self.column_comb)
self.labels_=np.where(threevalues.isin(self.inlier_df_[self.inlier_df_["columns_combination"]==col_]["threevalues"].values),1,-1)
if self.returnOutlierLabels:
self.labels_ = np.argwhere(self.labels_==-1).reshape(1,-1)[0]
return self.labels_
else:
return None
except Exception as e:
logging.exception(f'ML predict error: {traceback.format_exc()}')
return None

Kodun oldukça açıklayıcı olduğunu düşünmekle birlikte bir kısmını sözel olarak da ifade etmek faydalı olacaktır. Algoritmamız, kombinasyonların 2 veya 3 adet kolon içermesine göre farklı davranabiliyor. Ancak çıktının bir dahsboard’dan standart şekilde sunulabilmesi için üretilen dataframe’in de standart kolon başlıkları içermesi gerekli. Mesela “sum_12_or_only1” isimli kolon, kombinasyon sayısı 2 ise ‘sadece 1.kolon baz alındığındaki gözlem sayısı’nı verirken, kombinasyon sayısı 3 iken ‘1. ve 2. grup baz alınarak 3. kolondaki gözlem sayısı’nı vermektedir. Diğer kolonlar da benzer şekilde yorumlanabilir.

KAPSAM

Çalışmayı sadece kategorik veriler için başlatmıştık, ancak günün sonunda numerik verilerdeki outlier detection algoritmalarının büyük çoğunluğunun density/distance bazlı olması sebebiyle bizdeki gibi büyük veri setlerinde çalışmadığını, “Isolation Forest” gibi tree bazlı algoritmaların ise hem çok uzun sürelerde çalıştığını hem de false tahminlerinin yüksek olduğunu gördük. O yüzden numerikleri discretize ederek (Baremlere ayırarak) onları da kategorik hale getirmeye ve bunlar için de kendi algoritmamızı kullanmaya karar verdik. Sonuçların onlar için de oldukça başarılı olduğunu gördük. Tabii sonuçların başarılı olması kadar çalışma hızı da oldukça iyiydi. Bu arada discretization için sklearn içinde her ne kadar KbinsDiscretizer algoritması bulunsa da sonuçları 1, 2, 3 gibi nominal değerlere döndürdüğü ancak bizim ilgili aralıkların kendisine ihtiyacımız olduğu için (Dashboard’da göstermek amacıyla) bunun için de ayrı bir algoritma yazdık. Bunun kodu da aşağıdaki gibidir:

class CustomDiscretizer(BaseEstimator, TransformerMixin):
def __init__(self, features, bins=100):
self.features = features
self.bins = bins

def fit(self, df):
self.bin_dict_={}
tempRemove=[]
for f in self.features:
if df[f].nunique()>=self.bins:
self.bin_dict_[f] = [np.NINF]+sorted([x.right for x in pd.qcut(df[f], self.bins, duplicates="drop").unique()])+[np.PINF]
else:
logging.warn(f"{f} feature için distinct value sayısı bins değerinden küçük olduğu için baremleme yapılmadı.")
tempRemove.append(f)

for t in tempRemove:
self.features.remove(t)
return self

def transform(self, df, returnType=1):
"""
returnType:1: whole df, 2:original numerics+baremliler, 3:sadece baremliler
"""
for f in self.features:
df[f+"_bins"]=pd.cut(df[f], bins=self.bin_dict_[f], right=True, duplicates="drop")

if returnType==1:
return df
elif returnType==2:
return df[self.features + [x+"_bins" for x in self.features]]
elif returnType==3:
return df[[x+"_bins" for x in self.features]]
else:
logging.warn("Wrong value for returnType. It must be one of 1,2,3")

Şimdi de mix veri setlerine bir örnek verelim. Bu örnekle aynı zamanda 3 kolonlu bir veri seti için de inceleme yapmış olalım. Bu sefer toplam gözlem sayısını ve pp yazmaya gerek duymadım, aynı mantık geçerli olacak. Aşağıdaki tabloda yüksek bir ilişki çıkacağı aşikar (Bunun binlerce satırlık bir tablodan küçük bir örneklem olduğunu düşünelim.). Hatta öncelikle ikili korelasyonlara bakıldığında aylık ve toplam tutar arasında ilişki bulunacaktır. Ancak üçlü kombinasyonların da tespiti sırasında ödeme periyodunun da devreye girmesiyle daha doğru bir ilişki yakalanabilecektir. Şöyle ki aylık ödeme türünün ağır bastığı bir senaryo düşünelim (Toplam verinin %80–90’ı böyle olsun.). Böyle bir durumda sadece ikili korelasyona baksaydık, 3. satırdan itibaren tüm kayıtlar (Son satır hariç) anomali çıkacaktı; zira iki kolon arasında ilişki de genelde 1–1 olacaktı, yani korelasyon değeri 1’e yakın çıkacaktı. Ancak ödeme periyodunu da denkleme sokunca sadece kırmızılı satırların anomali olduğu anlaşılmaktadır. İlk kırmızılı kayıtta ya son kolon 250 olmalı ya da ilk kolon “3 Aylık” olmalıdır. Aynı şekilde ikinci kırmızılı kayıtta, ya ilk kolon “Aylık” ya da son kolon 6000 olmalıdır (Aylıklarda aylık ve toplam tutar eşitken, 3 aylık olanlarda 3 kat, yıllıklarda 12 katlık bir ilişki beklenir.). İşte bu şekilde 3 kolona birden bakıldığında ilişkinin aslında ikili korelasyona göre daha da yüksek olduğu görülecektir.

KOMBİNASYONLARIN BELİRLENMESİ

Bu algoritmanın hali hazırda kullanımda olan diğer algoritmalardan önemli bir farkı, sadece 2 veya 3 kolon ile çalışıyor olmasıdır ki bizim durumumuzda zaten olması gereken de budur. Diğer domain’lerdeki outlier detection çalışmalarında belki tüm kolonlara birden bakmak anlamlı olabilir. Öyle bile olsa bazı algoritmalar kendi içinde feature selection yapabilmekte veya öncesinde bir dimension reduction işlemi uygulanabilmektedir. Ancak biz burada doğrudan ilişki düzeyi en yüksek olan kolon kombinasyonlarına bakmayı yeğledik. Nitekim konu bankacılık verisi olduğunda domain bilgisi bize genelde 2, nadiren 3, çok ender olarak da daha fazla kolon arasında ilişki olduğunu söylüyor.

Diğer yandan günün sonunda verilerin gösterim şekli de önemli olduğu için maksimum 3 kolon için bu ilişkiyi değerlendirdik. Üçten fazla kombinasyonun çıktısının gösteriminde ve bunun yorumlanmasında sıkıntılar çıkacağını düşünüyoruz.

Kombinasyonları belirlerken, ikililer için cramers_v metriğini, üçlüler için de yine aynı mantıkla (Contingency table’a bir de layer ekleyerek) elde ettiğimiz chi2 istatistiğine ve dolaylı olarak phi katsayısına bakıyoruz. “p value” değerlerini de göz önüne alarak ilgili kombinasyonu nihai kombinasyon listesine ekleyip eklememeye karar veriyoruz. Eğer ikili kombinasyonlardan biri üçlü bir kombinasyonun içinde varsa ve korelasyon skoru da üçlününkinden daha iyi değilse (En az 1 puan daha yüksek değilse), bu kombinasyon mükerrerliğe neden olmaması adına nihai listeden çıkarılır.

Bu arada önemli bir nokta da şudur; ikili kombinasyonlarda kullandığımız cramers_v metriği simetrik iken, üçlülerde kullandığımız yöntem ise simetrik değildir. Bu nedenle ikililer için tüm kombinasyonlara bakarken üçlülerde permutasyonlara bakıyoruz. Ancak computational cost’un çok yükselmemesi için toplam permutasyon adedine bağlı olarak bunlar içinden random örneklem seçebiliyoruz ki bu da bazı iyi kombinasyonları kaçırmamıza neden oluyor olabilir. Tüm kombinasyon listesi çıktıktan sonra bunlar seri veya paralel şekilde yukarıdaki algoritmaya gönderilir. Tabii paralel işlemede elinizdeki ana dataframe tüm cpu’lara dağılacağı için memory sorunlarıyla karşılaşmak da yüksek olasılıktır. Bu nedenle seri mi paralel mi işletileceğine dikkatli karar verilmelidir.

Kombinasyon belirleme işlemi modelleme aşamasından önce yer almakla birlikte, modelleme sırasında da bazı kombinasyonları eliyoruz. Şöyle ki fit (Eğitim) aşamasında oluşan inlier’ları küçükten büyüğe boy sırasına dizip, her bir değer kümesi için popülasyon miktarını bir sonrakiyle karşılaştırıyoruz. Yani bir değişim oranı elde ediyoruz. Bu oranın belirlenmiş orandan (local_cluster_jumping_decision_point, default 10) daha büyük olduğu noktayı işaretleyip, bu noktaya kadar olan kümelerdeki popülasyonu toplam popülasyona oranladığımızda elde ettiğimiz yeni oran, belirlenmiş olan orandan (local_cluster_ratio_decision_point, default 0.05) büyükse ‘local cluster miktarı çok fazla’ diye düşünüp bu kombinasyon için sonuç üretmiyoruz. Biraz karışık gibi görünen bu aşama, local cluster sayısının anlamlı düzeyde olup olmadığına bakmak için eklenmiştir. Aksi halde birbirini küçük farklarla takip eden birçok local cluster olacaktır; bu da kategorik veriler için çok anlamlı bir durum değildir. Biz burada biraz da pareto prensibine yakın bir durum bekliyoruz, yani birçok büyük cluster ve sadece birkaç local cluster.

Bir diğer husus da her ne kadar belli kolonlar arasında yüksek korelasyon çıksa da bunların iş birimi tarafından alakasız (Irrelevant) görülüp çıkarılabilmesidir. O yüzden uçtan uca çalışan bir sistemde iş birimi müdahelesini de kapsayacak bir kurgu yapılmalıdır. Biz bunları bir inputlist olarak veri sahibinden alıyoruz.

EĞİTİM VE TAHMİNLEME KODLARI

Her bir kombinasyon için algoritma çalıştırılır ve ilgili kombinasyon için oluşturulan modeller pickle formatından kaydedilir. Bu pickle dosyası daha sonra sahadan yeni gelen veriler için de çağrılarak bunların tahminlenmesinde kullanılır.

İlk çalıştırma sırasında mevcuttaki tüm veriye fit_predict metodu ile bakılırken, sahadan yeni gelen veri üzerinde ise sadece predict işlemi yapılır. Tabii ki öncesinde gerekli preprocessing’ler yapıldıktan sonra.

Bunlara ait kod örneklerini de aşağıda bulabilirsiniz:

#İlk çalıştırma
obodmodels={}
for e,col_comb in tqdm(enumerate(col_combs)):
col_=",".join(col_comb)
OBOD=ObservationBasedOutlierDetector(col_comb,outlier_threshold=findThresh(corrlist[e]),
local_cluster_exception_list=lc_exception_list) #26.04.2023
ongoruler=OBOD.fit_predict(finaldatadf)
obodmodels[col_]=OBOD #pickle yapacağımız dictionary'ye atıyoruz
outlier_indices_= ongoruler
if outlier_indices_ is not None:
if len(outlier_indices_)>0:
resultdf=finaldatadf.iloc[outlier_indices_][col_comb]
resultdf["col_comb"]=col_
resultdf=resultdf.rename(columns={col_comb[0]:"first",col_comb[1]:"second"})
if len(col_comb)==3:
resultdf=resultdf.rename(columns={col_comb[2]:"third"})
resultdf["score_12_or_only1"]=pd.merge(OBOD.outlier_df_,resultdf,how="right")["ratio_12_or_only1"].fillna(0).values
resultdf["score_13_or_only2"]=pd.merge(OBOD.outlier_df_,resultdf,how="right")["ratio_13_or_only2"].fillna(0).values
resultdf["score_23_or_nan"]=pd.merge(OBOD.outlier_df_,resultdf,how="right")["ratio_23_or_nan"].fillna(0).values
resultdf["observation"]=pd.merge(OBOD.outlier_df_,resultdf,how="right")["count"].fillna(0).values
results.append(resultdf)
# Yeni Data
results=[]
for col_,model in obod_pkl.items():
col_comb=col_.split(",")
ongoruler=model.predict(finaldatadf)
outlier_indices_= ongoruler
if outlier_indices_ is not None:
if len(outlier_indices_)>0:
resultdf=finaldatadf.iloc[outlier_indices_][col_comb]
resultdf["col_comb"]=col_
resultdf=resultdf.rename(columns={col_comb[0]:"first",col_comb[1]:"second"})
if len(col_comb)==3:
resultdf=resultdf.rename(columns={col_comb[2]:"third"})
resultdf["score_12_or_only1"]=pd.merge(model.outlier_df_,resultdf,how="right")["ratio_12_or_only1"].fillna(0).values
resultdf["score_13_or_only2"]=pd.merge(model.outlier_df_,resultdf,how="right")["ratio_13_or_only2"].fillna(0).values
resultdf["score_23_or_nan"]=pd.merge(model.outlier_df_,resultdf,how="right")["ratio_23_or_nan"].fillna(0).values
resultdf["observation"]=pd.merge(model.outlier_df_,resultdf,how="right")["count"].fillna(0).values
results.append(resultdf)

Son durumda çıktımız ise aşağıdaki gibi olacaktır. Bu tablonun nasıl okunması gerektiğini yukarıda YÖNTEM başlığının son paragrafında açıklamıştık:

SONUÇ

Yukarıda belirttiğimiz gibi, algoritmamız her ne kadar 2 ve 3 kolon için çalışsa da veri kalitesi söz konusu olduğunda daha fazlasını aramak genelde gereksizdir. Diğer öne çıkan noktaları şöyle özetleyebiliriz:

  • O(n) complexity’ye sahip olduğu için çok hızlı çalışmaktadır.
  • Numerik kolonların discretize edilmesiyle numerik kolonlarda da yüksek hızlı çıktı elde etmek mümkün olmaktadır.
  • Dolayısıyla bunu sadece kategorik veriler için çalışan bir algoritma gibi düşünmek yanlış olacaktır. Hem full kategorik hem full numerik hem de mix veri setleri için kullanılabilir.
  • Tune edilecek dört parametremiz var: Threshold (default 0,01), local_cluster_threshold (default 100), local_cluster_jumping_decision_point (default 10) ve local_cluster_ratio_decision_point (default 0.05). Gerçi unsupervised learning söz konusu iken her ne kadar üzerinden karar vereceğimiz sağlıklı bir metrik bulunmuyorsa da kendiniz bir kriter belirleyebilirsiniz. Örneğin, anomali sayısının daha az veya daha çok çıkmasını istiyor olabilirsiniz, buna göre oynamalar yapabilir veya ParameterGrid kullanıp sonuçlara bakarak karar verebilirsiniz. Bu arada her bir kombinasyon için değişken parametre setleri de düşünülebilir (Örn: [A,B] ikilisi için sırayla 0,01, 100, 10 ve 0.05 iken, [B-C] için 0,001, 10, 5, 0.01) ki bu algoritmanın kendisinin modifikasyonu anlamına gelir. İhtiyaç duyulduğunda bu da kolaylıkla implemente edilebilir. Biz buna şimdilik gerek duymadık.
  • Hem Anomaly/Outlier Detection amaçlı, hem de Novelty Detection amaçlı kullanılabilmektedir.

Umarım bu algoritmayı kendi işlerinizde kullanır ve faydasını görürsünüz. Öyle olması durumunda yorumlarda görüşlerinizi belirtmeniz bu makaleyi okuyan herkes için çok faydalı olacaktır.

Okuduğunuz için teşekkür ederim. Bir sonraki yazımızda görüşmek üzere…

İlave okumalar:

--

--

Volkan Yurtseven
Akbank Teknoloji

Once self-taught-Data Science Enthusiast,now graduate one