[瞎玩兒系列] 使用SQL實現Logistic回歸


本來想發在知乎專欄的,但是文章死活提交不了,我也是醉了,於是乎我就干脆提交到CNBLOGS了。

前言

前段時間我們介紹了Logistic的數學原理和C語言實現,而我呢?其實還是習慣使用Matlab進行計算的,而且是不帶C的Matlab。(主要我們都用Windows)
那為什么要用SQL實現呢?(准確的說是PL/SQL)
因為我發現數據一次性加載進內存里面太大了,直接在SELECT的時候OutOfMemory了(其實數據是勉強能裝進內存的,只是SELECT的時候產生的對象太多)
更主要的原因是因為我的電腦另有它用,留下的內存也不多了。
卧槽,為什么不用服務器算呢?
最近在重裝系統,等我的小服務器安裝好了,次回我會可能帶來使用Hadoop/Spark的Logistic回歸。
關於為什么我對Logistic回歸這么着迷,並不是不會其它的模型,第一它簡單,第二可解釋性好,易於並行或者處理數據流。
為什么使用SQL呢?性能並不是其優勢,反而是其軟肋,但是可以把壓力轉嫁到服務器上,對於我殘破不堪的工作電腦也是一個解脫,其次,對於特別大量的數據要做到隨機梯度下降防止陷入局部極小,用SQL也算是一個解決方案。

誠然這個方案是不合適的,但是那又怎么樣,我和SQL只是玩玩而已

最近本人在找工作,希望找一個能讓我做機器學習的崗位,我希望這家公司是一個腳踏實地的公司,有可持續的盈利模式,不會隨便的喊出深度學習、人工智能和大數據之類的詞匯,能從業務的角度來選擇技術,那么我不會讓你們失望。

准備數據

首先你有一張表,這張表列數不多,但是行數挺多的,其中一列是y,其余的是x,當然還可以有ID之類的一些其他信息。
我們這次的表結構是這樣的:

CREATE TABLE public.jfeatures_cntf
(
    ---y
    cbuy integer,
    ---X
    cview integer,
    cadd integer,
    cdel integer,
    cstar integer,
    cclick integer
)

計算

首先你需要新建一個函數,該函數能做到從數據中隨機取N行數據給你,因為數據量比較大,我們可能只有在Fine Tune的時候才會使用全部數據,平時的計算主要還是使用Radom Batch Gradient Descend/Ascend。
而Logistic的核心是:求偏導,我們也不需要什么都讓SQL做,只要讓SQL完成數據量最大的計算就行了。

隨機取數據的函數

那我們首先構建一個PLSQL的函數:

CREATE OR REPLACE FUNCTION public.get_rand_x_record(x integer)
  RETURNS SETOF jfeatures_cntf AS
$BODY$
DECLARE
    N INTEGER;
BEGIN
    --- N<-Length-x
    SELECT count(*)-x INTO N FROM public.jfeatures_cntf;
    --- Random select
    RETURN QUERY SELECT * FROM public.jfeatures_cntf
        OFFSET floor(random()*N) LIMIT x;
END;
$BODY$
  LANGUAGE plpgsql VOLATILE

這個函數可以每次從數據庫中取出N條數據,比ORDER BY random()快不少。
由於在運算的時候我們也不會增刪記錄,所以可以預先獲取數據大小N,隨后使用這個函數:

CREATE OR REPLACE FUNCTION public.get_rand_x_record(
    x integer,
    n bigint)
  RETURNS SETOF jfeatures_cntf AS
$BODY$
DECLARE
BEGIN
    --- Random select
    RETURN QUERY SELECT * FROM public.jfeatures_cntf
        OFFSET floor(random()*N) LIMIT x;
END;
$BODY$
  LANGUAGE plpgsql VOLATILE

Logistic回歸求偏導的函數

得到數據以后,我們首先會求y,也就是1/(1+exp(1+b^Tx)),隨后將(y-t)廣播的乘到X上,最后求和就得到了結果。

CREATE OR REPLACE FUNCTION public.log_model_lr_random(
    batch_size integer,
    pview double precision,
    padd double precision,
    pdel double precision,
    pstar double precision,
    pclick double precision)
  RETURNS double precision[] AS
$BODY$
DECLARE
    OUT_VALUE RECORD;
BEGIN
    SELECT ---grad<-sum((y-t)*X)
        sum(log(cview+1)*D)/batch_size as pdview,
        sum(log(cadd+1)*D)/batch_size as pdadd,
        sum(log(cdel+1)*D)/batch_size as pddel,
        sum(log(cstar+1)*D)/batch_size as pdstar,
        sum(log(cclick+1)*D)/batch_size as pdclick
    INTO OUT_VALUE
    FROM
    (   ---get y-t from data
        SELECT *,
            (1.0/(exp(
            pview*log(cview+1) + 
            padd*log(cadd+1) +
            pdel*log(cdel+1) +
            pstar*log(cstar+1) +
            pclick*log(cclick+1) + 1.0
            )+1.0)
            ) - (case when cbuy>0 then 1.0 else 0.0 end)
            AS D
        FROM get_rand_x_record(batch_size)
    ) AS SUBS;
    return ARRAY[
        OUT_VALUE.pdview, 
        OUT_VALUE.pdadd,
        OUT_VALUE.pddel,
        OUT_VALUE.pdstar,
        OUT_VALUE.pdclick];
END;$BODY$
  LANGUAGE plpgsql VOLATILE

當然,這個函數也可以由N確定的版本(也就是如果你在計算過程中保證行數不變化的話可以使用的版本),我最終使用的也就是這個版本。

這個就由大家自己寫吧!

性能

關於性能方面,對3,000,000條數據求偏導需要1min!要知道,這在Matlab上(使用bsxfun做了並發)只需要0.5秒,這個性能差了100多倍(當然PostgreSQL在單次任務上不支持並行計算也是一個軟肋),但是這個是有限定的,一個是內存計算,一個是外存計算,當數據量大到一定程度的時候,往往就需要外存算法。
Logistic是支持並行的,用SQL明顯委屈他了,下次咱用Spark發揮出他最大的優勢。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM