SMOTE(Synthetic Minority Oversampling Technique),合成少數類過采樣技術.它是基於隨機過采樣算法的一種改進方案,由於隨機過采樣采取簡單復制樣本的策略來增加少數類樣本,這樣容易產生模型過擬合的問題,即使得模型學習到的信息過於特別(Specific)而不夠泛化(General),SMOTE算法的基本思想是對少數類樣本進行分析並根據少數類樣本人工合成新樣本添加到數據集中,具體如下圖所示,算法流程如下。
- (1)對於少數類中每一個樣本x,以歐氏距離為標准計算它到少數類樣本集中所有樣本的距離,得到其k近鄰。
- (2)根據樣本不平衡比例設置一個采樣比例以確定采樣倍率N,對於每一個少數類樣本x,從其k近鄰中隨機選擇若干個樣本,假設選擇的近鄰為o。
- (3)對於每一個隨機選出的近鄰o,分別與原樣本按照公式o(new)=o+rand(0,1)*(x-o)構建新的樣本。


Smote算法的思想其實很簡單,先隨機選定n個少類的樣本,如下圖

再找出最靠近它的m個少類樣本,如下圖

再任選最臨近的m個少類樣本中的任意一點,

在這兩點上任選一點,這點就是新增的數據樣本
# -*- coding: utf-8 -*- import numpy as np import pandas as pd from sklearn.preprocessing import StandardScaler from numpy import * import matplotlib.pyplot as plt #讀數據 data = pd.read_table('supermarket_second_man_clothes_train.txt', low_memory=False) #簡單的預處理 test_date = pd.concat([data['label'], data.iloc[:, 7:10]], axis=1) test_date = test_date.dropna(how='any')
結果:
test_date.head() Out[1]: label max_date_diff max_pay cnt_time 0 0 23.0 43068.0 15 1 0 10.0 1899.0 2 2 0 146.0 3299.0 21 3 0 30.0 31959.0 35 4 0 3.0 24165.0 98 test_date['label'][test_date['label']==0].count()/test_date['label'][test_date['label']==1].count() Out[2]: 67
label是樣本類別判別標簽,0:1=67:1,需要對label=1的數據進行擴充
# 篩選目標變量 aimed_date = test_date[test_date['label'] == 1] # 隨機篩選少類擴充中心 index = pd.DataFrame(aimed_date.index).sample(frac=0.1, random_state=1) index.columns = ['id'] number = len(index) # 生成array格式 aimed_date_new = aimed_date.ix[index.values.ravel(), :]
隨機選取了全量少數樣本的10%作為數據擴充的中心點
# 自變量標准化 sc = StandardScaler().fit(aimed_date_new) aimed_date_new = pd.DataFrame(sc.transform(aimed_date_new)) sc1 = StandardScaler().fit(aimed_date) aimed_date = pd.DataFrame(sc1.transform(aimed_date)) # 定義歐式距離計算 def dist(a, b): a = array(a) b = array(b) d = ((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2 + (a[2] - b[2]) ** 2 + (a[3] - b[3]) ** 2) ** 0.5 return d
下面定義距離計算的方式,所有算法中,涉及到距離的地方都需要標准化去除岡量,也同時加快了計算的速度
這邊采取了歐式距離的方式
# 統計所有檢驗距離樣本個數 row_l1 = aimed_date_new.iloc[:, 0].count() row_l2 = aimed_date.iloc[:, 0].count() a = zeros((row_l1, row_l2)) a = pd.DataFrame(a) # 計算距離矩陣 for i in range(row_l1): for j in range(row_l2): d = dist(aimed_date_new.iloc[i, :], aimed_date.iloc[j, :]) a.ix[i, j] = d b = a.T.apply(lambda x: x.min())
調用上面的計算距離的函數,形成一個距離矩陣
# 找到同類點位置 h = [] z = [] for i in range(number): for j in range(len(a.iloc[i, :])): ai = a.iloc[i, j] bi = b[i] if ai == bi: h.append(i) z.append(j) else: continue new_point = [0, 0, 0, 0] new_point = pd.DataFrame(new_point) for i in range(len(h)): index_a = z[i] new = aimed_date.iloc[index_a, :] new_point = pd.concat([new, new_point], axis=1) new_point = new_point.iloc[:, range(len(new_point.columns) - 1)]
再找到位置的情況下,再去原始的數據集中根據位置查找具體的數據
import random r1 = [] for i in range(len(new_point.columns)): r1.append(random.uniform(0, 1)) new_point_last = [] new_point_last = pd.DataFrame(new_point_last) # 求新點 new_x=old_x+rand()*(append_x-old_x) for i in range(len(new_point.columns)): new_x = (new_point.iloc[1:4, i] - aimed_date_new.iloc[number - 1 - i, 1:4]) * r1[i] + aimed_date_new.iloc[number - 1 - i, 1:4] new_point_last = pd.concat([new_point_last, new_x], axis=1) print new_point_last
最后,再根據smote的計算公式new_x=old_x+rand()*(append_x-old_x)
,計算出新的點即可。
smote算法的偽代碼如下:
import random from sklearn.neighbors import NearestNeighbors import numpy as np class Smote: def __init__(self,samples,N=1,k=5): self.n_samples,self.n_attrs=samples.shape self.N=N self.k=k self.samples=samples self.newindex=0 # self.synthetic=np.zeros((self.n_samples*N,self.n_attrs)) def over_sampling(self): N=int(self.N) self.synthetic = np.zeros((self.n_samples * N, self.n_attrs)) neighbors=NearestNeighbors(n_neighbors=self.k).fit(self.samples) print('neighbors',neighbors) for i in range(len(self.samples)): nnarray=neighbors.kneighbors(self.samples[i].reshape(1,-1),return_distance=False)[0] #print nnarray self._populate(N,i,nnarray) return self.synthetic # for each minority class samples,choose N of the k nearest neighbors and generate N synthetic samples. def _populate(self,N,i,nnarray): for j in range(N): nn=random.randint(0,self.k-1) dif=self.samples[nnarray[nn]]-self.samples[i] gap=random.random() self.synthetic[self.newindex]=self.samples[i]+gap*dif self.newindex+=1 a=np.array([[1,2,3],[4,5,6],[2,3,1],[2,1,2],[2,3,4],[2,3,4]]) s=Smote(a,N=2) #a為少數數據集,N為倍率,即從k-鄰居中取出幾個樣本點 print(s.over_sampling())
該算法主要存在兩方面的問題:一是在近鄰選擇時,存在一定的盲目性。從上面的算法流程可以看出,在算法執行過程中,需要確定K值,即選擇多少個近鄰樣本,這需要用戶自行解決。從K值的定義可以看出,K值的下限是M值(M值為從K個近鄰中隨機挑選出的近鄰樣本的個數,且有M< K),M的大小可以根據負類樣本數量、正類樣本數量和數據集最后需要達到的平衡率決定。但K值的上限沒有辦法確定,只能根據具體的數據集去反復測試。因此如何確定K值,才能使算法達到最優這是未知的。
另外,該算法無法克服非平衡數據集的數據分布問題,容易產生分布邊緣化問題。由於負類樣本的分布決定了其可選擇的近鄰,如果一個負類樣本處在負類樣本集的分布邊緣,則由此負類樣本和相鄰樣本產生的“人造”樣本也會處在這個邊緣,且會越來越邊緣化,從而模糊了正類樣本和負類樣本的邊界,而且使邊界變得越來越模糊。這種邊界模糊性,雖然使數據集的平衡性得到了改善,但加大了分類算法進行分類的難度.
針對SMOTE算法存在的邊緣化和盲目性等問題,很多人紛紛提出了新的改進辦法,在一定程度上改進了算法的性能,但還存在許多需要解決的問題。
Han等人Borderline-SMOTE: A New Over-Sampling Method in Imbalanced Data Sets Learning在SMOTE算法基礎上進行了改進,提出了Borderhne.SMOTE算法,解決了生成樣本重疊(Overlapping)的問題該算法在運行的過程中,查找一個適當的區域,該區域可以較好地反應數據集的性質,然后在該區域內進行插值,以使新增加的“人造”樣本更有效。這個適當的區域一般由經驗給定,因此算法在執行的過程中有一定的局限性。