⑴ 如何處理數據不平衡問題
基於上一篇文章,面試被虐成渣,所以來整理和記錄下第一個問題,關於數據不平衡的問題。
以下內容參考:
https://www.cnblogs.com/charlotte77/p/10455900.html https://www.leiphone.com/news/201706/dTRE5ow9qBVLkZSY.html
數據不平衡也可稱作數據傾斜。在實際應用中,數據集的樣本特別是分類問題上,不同標簽的樣本比例很可能是不均衡的。因此,如果直接使用演算法訓練進行分類,訓練效果可能會很差。
解決實際應用中數據不平衡問題可以從三個方面入手,分別是對數據進行處理、選擇合適的評估方法和使用合適的演算法。
1)過采樣:
主動獲取更多的比例少的樣本數據。由於樣本比例不均衡,在條件允許的情況下可以嘗試獲取佔比少的類型的樣本數據。(PS:這就是為什麼我幾乎沒有遇到過數據不平衡的問題。每次測試使用的數據集都盡可能的完美均衡) 也可以通過使用 重復 、 自舉 或 合成 少數類過采樣等方法(SMOTE)來生成新的稀有樣品。
直接簡單復制重復的話,如果特徵少,會導致過擬合的問題。經過改進的過抽樣方法通過在少數類中加入隨機雜訊、干擾數據或通過一定規則產生新的合成樣本 (數據增強)。
2)欠采樣:
數據量足夠時,可以通過保留比例小的樣本數據和減少比例大的樣本數據來平衡數據集。缺點是會丟失多數類中的一些重要信息。
3)改變權重:
對不同樣本數量的類別賦予不同的權重(通常會設置為與樣本量成反比)
4)使用K-fold交叉驗證
值得注意的是,使用過采樣方法來解決不平衡問題時應適當地應用交叉驗證。這是因為過采樣會觀察到罕見的樣本,並根據分布函數應用自舉生成新的隨機數據,如果在過采樣之後應用交叉驗證,那麼我們所做的就是將我們的模型過擬合於一個特定的人工引導結果。這就是為什麼在過度采樣數據之前應該始終進行交叉驗證,就像實現特徵選擇一樣。只有重復采樣數據可以將隨機性引入到數據集中,以確保不會出現過擬合問題。
K-fold交叉驗證就是把原始數據隨機分成K個部分,在這K個部分中選擇一個作為測試數據,剩餘的K-1個作為訓練數據。交叉驗證的過程實際上是將實驗重復做K次,每次實驗都從K個部分中選擇一個不同的部分作為測試數據,剩餘的數據作為訓練數據進行實驗,最後把得到的K個實驗結果平均。
此外,還應注意訓練集和測試集的樣本的概率分布問題。若實際數據不平衡,將采樣平衡後的數據集作為訓練集訓練後,模型應用在測試集上效果仍會不好。因此,實際應用中盡可能 保持訓練和測試的樣本的概率分布是一致的。
1)謹慎選擇AUC作為評價指標:對於數據極端不平衡時,可以觀察觀察不同演算法在同一份數據下的訓練結果的precision和recall,這樣做有兩個好處,一是可以了解不同演算法對於數據的敏感程度,二是可以明確採取哪種評價指標更合適。針對機器學習中的數據不平衡問題,建議更多PR(Precision-Recall曲線),而非ROC曲線,具體原因畫圖即可得知,如果採用ROC曲線來作為評價指標,很容易因為AUC值高而忽略實際對少量樣本的效果其實並不理想的情況。
2)不要只看Accuracy:Accuracy可以說是最模糊的一個指標了,因為這個指標高可能壓根就不能代表業務的效果好,在實際生產中更關注precision/recall/mAP等具體的指標,具體側重那個指標,得結合實際情況看。
1)選擇對數據傾斜相對不敏感的演算法。如樹模型等。
2)集成學習。即多模型Bagging。首先從多數類中獨立隨機抽取出若乾子集,將每個子集與少數類數據聯合起來訓練生成多個基分類器,再加權組成新的分類器,如加法模型、Adaboost、隨機森林等。
3)轉化成異常檢測或者一分類問題。(具體內容後續有時間再跟進學習)
補充:什麼是數據增強(Data Augmentation)?
參考鏈接:https://www.jianshu.com/p/3e9f4812abbc
數據增強讓有限的數據產生更多的數據,增加訓練樣本的數量以及多樣性(雜訊數據), 提升模型魯棒性, 一般用於訓練集。神經網路需要大量的參數,許許多多的神經網路的參數都是數以百萬計,而使得這些參數可以正確工作則需要大量的數據進行訓練,但在很多實際的項目中,我們難以找到充足的數據來完成任務。隨機改變訓練樣本可以降低模型對某些屬性的依賴,從而提高模型的泛化能力。
數據增強方法:
例如,我們可以對圖像進行不同方式的裁剪,讓物體以不同的實例出現在圖像的不同位置,這同樣能夠降低模型對目標位置的敏感性。此外,調整亮度、對比度、飽和度和色調 等因素來降低模型對色彩的敏感度。再有,隨機裁剪、隨機反轉、隨機對比度增強、顏色變化等等。一般來講隨機反轉和一個小比例的random resize,再接隨機裁剪比較常用。NLP中將字和詞連接起來就形成了一個新樣本,也屬於數據增強。
數據增強的分類:
數據增強可以分為兩類,一類是離線增強,一類是在線增強。
· 離線增強 : 直接對數據集進行處理,數據的數目會變成增強因子乘以原數據集的數目,這種方法常常用於數據集很小的時候。
· 在線增強 : 這種增強的方法用於,獲得批量(batch)數據之後,然後對這個批量(batch)的數據進行增強,如旋轉、平移、翻折等相應的變化,由於有些數據集不能接受線性級別的增長,這種方法長用於大的數據集,很多機器學習框架已經支持了這種數據增強方式,並且可以使用 GPU 優化計算。
⑵ 幾種數據傾斜的情況,並解釋為什麼會傾斜,以及如何解決
Mapjoin是一種避免避免數據傾斜的手段
允許在map階段進行join操作,MapJoin把小表全部讀入內存中,在map階段直接拿另外一個表的數據和內存中表數據做匹配,由於在map是進行了join操作,省去了rece運行的效率也會高很多
在《hive:join遇到問題》有具體操作
在對多個表join連接操作時,將小表放在join的左邊,大表放在Jion的右邊,
在執行這樣的join連接時小表中的數據會被緩存到內存當中,這樣可以有效減少發生內存溢出錯誤的幾率
2. 設置參數
hive.map.aggr = true
hive.groupby.skewindata=true 還有其他參數
3.SQL語言調節
比如: group by維度過小時:採用sum() group by的方式來替換count(distinct)完成計算
4.StreamTable
將在recer中進行join操作時的小table放入內存,而大table通過stream方式讀取
⑶ hive數據傾斜及處理
火山日常啰嗦
學習了一些大數據的相關框架後,發現應用層的東西確實不難,真正難的都是底層原理,所以我查看了很多資料,借鑒了前人的方法再加上自己的理解,寫下了這篇文章。
數據傾斜的直白概念:
數據傾斜就是數據的分布不平衡,某些地方特別多,某些地方又特別少,導致的在處理數據的時候,有些很快就處理完了,而有些又遲遲未能處理完,導致整體任務最終遲遲無法完成,這種現象就是數據傾斜。
針對maprece的過程來說就是,有多個rece,其中有一個或者若干個rece要處理的數據量特別大,而其他的rece處理的數據量則比較小,那麼這些數據量小的rece很快就可以完成,而數據量大的則需要很多時間,導致整個任務一直在等它而遲遲無法完成。
跑mr任務時常見的rece的進度總是卡在99%,這種現象很大可能就是數據傾斜造成的。
產生數據傾斜的原因:
1) key的分布不均勻或者說某些key太集中。
上面就說過,rece的數據量大小差異過大,而rece的數據是分區的結果,分區是對key求hash值,根據hash值決定該key被分到某個分區,進而進入到某個rece,而如果key很集中或者相同,那麼計算得到它們的hash值可能一樣,那麼就會被分配到同一個rece,就會造成這個rece所要處理的數據量過大。
2) 業務數據自身的特性。
比如某些業務數據作為key的欄位本就很集中,那麼結果肯定會導致數據傾斜啊。
還有其他的一些原因,但是,根本原因還是key的分布不均勻,而其他的原因就是會造成key不均勻,進而導致數據傾斜的後果,所以說根本原因是key的分布不均勻。
既然有數據傾斜這種現象,就必須要有數據傾斜對應的處理方案啊。
簡單地說數據傾斜這種現象導致的任務遲遲不能完成,耗費了太多時間,極大地影響了性能,所以我們數據傾斜的解決方案設計思路就是往如何提高性能,即如何縮短任務的處理時間這方面考慮的,而要提高性能,就要讓key分布相對均衡,所以我們的終極目標就是考慮如何預處理數據才能夠使得它的key分布均勻。
常見的數據傾斜處理方案:
1 設置參數
1)設置hive.map.aggr=true //開啟map端部分聚合功能,就是將key相同的歸到一起,減少數據量,這樣就可以相對地減少進入rece的數據量,在一定程度上可以提高性能,當然,如果數據的減少量微乎其微,那對性能的影響幾乎沒啥變化。
2)設置hive.groupby.skewindata=true //如果發生了數據傾斜就可以通過它來進行負載均衡。當選項設定為 true,生成的查詢計劃會有兩個 MR Job。第一個 MR Job 中,Map 的輸出結果集合會隨機分布到 Rece 中,每個 Rece 做部分聚合操作,並輸出結果,這樣處理的結果是相同的Key 有可能被分發到不同的 Rece 中,從而達到負載均衡的目的;第二個 MR Job 再根據預處理的數據結果按照Key 分布到 Rece 中(這個過程是按照key的hash值進行分區的,不同於mr job1的隨機分配,這次可以保證相同的Key 被分布到同一個 Rece 中),最後完成最終的聚合操作。所以它主要就是先通過第一個mr job將key隨機分配到rece,使得會造成數據傾斜的key可能被分配到不同的rece上,從而達到負載均衡的目的。到第二個mr job中,因為第一個mr job已經在rece中對這些數據進行了部分聚合(就像單詞統計的例子,a這個字母在不同的rece中,已經算出它在每個rece中的個數,但是最終的總的個數還沒算出來,那麼就將它傳到第二個mr job,這樣就可以得到總的單詞個數),所以這里直接進行最後的聚合就可以了。
3)hive.exec.recers.bytes.per.recer=1000000000 (單位是位元組)
每個rece能夠處理的數據量大小,默認是1G
4)hive.exec.recers.max=999
最大可以開啟的rece個數,默認是999個
在只配了hive.exec.recers.bytes.per.recer以及hive.exec.recers.max的情況下,實際的rece個數會根據實際的數據總量/每個rece處理的數據量來決定。
5)mapred.rece.tasks=-1
實際運行的rece個數,默認是-1,可以認為指定,但是如果認為在此指定了,那麼就不會通過實際的總數據量/hive.exec.recers.bytes.per.recer來決定rece的個數了。
2 sql語句優化
給幾個具體的場景以及在這些場景下的處理方案:
1)進行表的join這種業務操作時,經常會產生數據傾斜。
原因就是這些業務數據本就存在key會分布不均勻的風險,所以我們join時不能使用普通的join(rece端join)或者可以使用普通join,但是是優化後的。
但是這種操作有個前提條件就是僅適用於小表join大表,而小表怎麼定義它的大小,多小的表才算小表,這里有個參數可以確定的(但是這個參數名我暫時忘記了),如果小表的數據大小小於這個值,就可以使用map join,而是在這種情況下是自動使用map join這種方案的。所以如果是大小表join,直接用map join,避免數據傾斜。
方法1:(普通join)
select * from log a join users b on (a.user_id is not null and a.user_id = b.user_id );
這是屬於表的內連接的,兩張表不滿足條件的記錄都不保留。
方法2:檢測到user_id是null時給它賦予一個新值(這個新值由一個字元串(比如我自己給它定一個 hive)加上一個隨機數組成),這樣就可以將原來集中的key分散開來,也避免了數據傾斜的風險。而且因為這些數據本來就是無效數據,根本不會出現在結果表中,所以,這樣處理user_id(由一個字元串(比如我自己給它定一個 hive)加上一個隨機數),它也無法關聯的,因為有效的數據的user_id沒有這種形式的,所以就算這些無效數據出現在不同的rece中還是不會影響結果的,我這樣處理只是為了將它們分散開而已,所以用這種方法處理,結果表中也不會出現null這些無效數據,跟過濾處理方案得到的結果是一樣的。(普通join)
select *
from log a
join users b
on case when a.user_id is null then concat(『hive』,rand() ) else a.user_id end = b.user_id;
但是這兩種方案只是適用於大表join大表的內連接,兩張表的無效數據都不保留。
但是如果對於左外連接或者右外連接這種情況,即使驅動表中某些記錄在另一張表中沒有數據與它對應,但我們是依然需要保留驅動表的這些數據的,那該怎麼辦呢?其實很簡單,只需要將上述方法得到的結果再與驅動表的這些無數據取並集就可以了。
如下:
select * from log a
left outer join users b
on a.user_id is not null
and a.user_id = b.user_id
union all
select * from log a
where a.user_id is null;
2)雖然都是大表,但是呢對於某些業務數據而言,其有用的部分只佔它所在表的很少一部分,那麼我們就可以將它們先取出來,得到的結果應該是一張小表,那麼就可以使用map join來避免數據傾斜了。
場景:用戶表中user_id欄位為int,log表中user_id欄位既有string類型也有int類型。
當按照user_id進行兩個表的Join操作時,因為我們在連接時要進行user_id的比較,所以需要user_id的類型都相同,如果我們選擇將log表中的String類型轉換為int類型,那麼就可能會出現這種情況:String類型轉換為int類型得到的都是null值(這就是類型轉換的問題了,String類型數據轉換為int類型會失敗,數據丟失,就會賦null值),如果所有的String類型的user_id都變成了null,那麼就又出現了集中的key,分區後就又會導致數據傾斜。所以我們進行類型轉換時不能選擇將String類型轉換為int,而應該將int類型轉換為String,因為int轉換為String不會出問題,int類型原來的值是什麼,轉換為String後對應的字元串就會是什麼,形式沒變,只是類型變了而已。
解決方法:把int類型轉換成字元串類型
select * from users a
join logs b
on (a.usr_id = cast(b.user_id as string));
比如有一份日誌,要你從日誌中統計某天有多少個用戶訪問網站,即統計有多少個不同的user_id;但是呢這個網站卻又恰巧遭到攻擊,日誌中大部分都是同一個user_id的記錄,其他的user_id屬於正常訪問,訪問量不會很大,在這種情況下,當你直接使用count(distinct user_id)時,這也是要跑mr任務的啊,這時這些大量的相同的user_id就是集中的key了,結果就是通過分區它們都被分到一個rece中,就會造成這個rece處理的數據特別大,而其中的rece處理的數據都很小,所以就會造成數據傾斜。
那麼要怎麼優化呢?
方法1:可以先找出這個user_id是什麼,過濾掉它,然後通過count(distinct user_id)計算出剩餘的那些user_id的個數,最後再加1(這1個就是那個被過濾掉的user_id,雖然它有大量的記錄,但是ser_id相同的都是同一個用戶,而我們要計算的就是用戶數)
sql語句展示:
分組求和後降序排序,就可以得到這個數據量最大的user_id是什麼,然後我們下一步操作時就過濾它,等計算完其他的再加上它這一個。
select user_id,count(user_id) from log group by user_id desc limit 2;
select count(distinct user_id)+1 as sum from log;
sum就是最終的結果--用戶數
方法2:我們可以先通過group by分組,然後再在分組得到的結果的基礎之上進行count
sql語句展示:
select count(*) from (select user_id from log group by user_id) new_log;
總的來說就是,數據傾斜的根源是key分布不均勻,所以應對方案要麼是從源頭解決(不讓數據分區,直接在map端搞定),要麼就是在分區時將這些集中卻無效的key過濾(清洗)掉,或者是想辦法將這些key打亂,讓它們進入到不同的rece中。
性能調優是指通過調整使得機器處理任務的速度更快,所花的時間更少,而數據傾斜的處理是hive性能調優的一部分,通過處理能夠大大減少任務的運行時間。
除了數據傾斜的處理之外,hive的優化還有其他方面的,例如where子句優化:
select * from a left outer join b on (a.key=b.key) where a.date='2017-07-11' and b.date='2017-07-11';
這是一個左外連接。
這個sql語句執行的結果是:得到的結果是表a與表b的連接表,且表中的記錄的date都是'2017-07-11'。
而這個sql語句的執行過程是:逐條獲取到a表的記錄,然後掃描b表,尋找欄位key值為a.key的記錄,找到後將b表的這條記錄連接到a表上,然後判斷連接後的這條記錄是否滿足條件a.date='2017-07-11' and b.date='2017-07-11',如果滿足,則顯示,否則,丟棄。
因為這是一個左外連接,且a為驅動表,連接時在a中發現key而在b中沒有發現與之相等的key時,b中的列將置為null,包括列date,一個不為null,一個為null,這樣後邊的where就沒有用了。
簡答的說這個方案的做法就是先按連接條件進行連接,連接後再看where條件,如果不滿足就丟棄,那之前連接所做的那些功夫就浪費了,白白耗費了資源(cpu等),增加了運行的總時間,如果有一種方案可以在未進行連接之前就直接判斷出不滿足最終的條件,那麼就可以直接丟棄它,這樣對於這樣的記錄就不要浪費資源以及時間去連接了,這樣也是能提升性能的,下面就看看這種方案:
sql語句:
將剛才的where限制條件直接放到on裡面,那麼就變成了滿足這三個條件才會進行連接,不滿足的直接過濾掉,就像上面所說的,少了無效連接那一步,就相對地節約了時間,如果這樣的無效連接的記錄很多的話,那麼採用這種改進版的方案無疑能夠較大程度地提高性能。
select * from a left outer join b on (a.key=b.key and a.date='2017-07-11' and b.date='2017-07-11');
不管怎麼說,我們在運行任務時,總是希望能加快運行速度,縮短運行時間,更快地得到結果,即提升性能,這是我們的目的,這就是我們所謂的性能調優。
關於小表join大表的補充:
表join時的操作是這樣的:
當操作到驅動表的某條記錄時,就會全局掃描另一張表,尋找滿足條件的記錄,而當掃描它時,為了讀取速度更快,一般都選先將它載入到內存,而內存的大小是有限的,為了不佔據過多的內存或者避免內存溢出,載入進入內存的表一般是小表,即數據量比較小,map join就是這樣做的。
即驅動表不放進內存,而另一張表(即要連接到驅動表的那一張表)就要先載入進內存,為了掃描速度更快,提高性能。
比如select * from a left outer join b on (a.key=b.key);
左外連接,驅動表是a,表b的記錄是要被連接到表a的,所以每在a上連接一條記錄就要被全局掃描一次的表是b,所以表b應先載入到內存(前提是表b是小表,如果是大表的話,估計會產生oom異常--out of memory內存溢出異常)。
select * from aa right outer join bb on (a.key=b.key);
右外連接,驅動表是bb,aa應先載入到內存(前提是小表)。
ps:希望我的分享能幫助到有需要的夥伴哦。我不是大神的哦,如果文中有誤,還請大家不吝賜教,幫忙指正,謝謝了!!!