標籤: 台北網頁設計

  • 聯合國:反疫苗運動 助長薩摩亞麻疹疫情惡化

    摘錄自2019年12月6日中央通訊社綜合報導

    聯合國兒童基金會(UNICEF)太平洋島嶼負責人今天(5日)表示,社群媒體巨頭必須嚴厲取締反疫苗接種貼文,這些貼文助長薩摩亞(Samoa)致命麻疹疫情惡化。

    UNICEF地區代表耶特(Sheldon Yett)表示,推特(Twitter)、臉書(Facebook)和Instagram(IG)等網路平台上「極不負責任」的反疫苗接種訊息,加劇了薩摩亞爆發的麻疹疫情,自10月中旬以來已造成62人死亡。耶特告訴法新社:「很明顯地它們必需負起企業責任並展開行動,確保那些人民,特別是弱勢族群能獲得正確資訊,讓孩童得以存活。」

    在麻疹疫情爆發前,薩摩亞的疫苗接種率降至只剩略超過30%,遠低於公認最佳接種率90%,這也使得該海島國家極易受到感染。世界衛生組織(WHO)把矛頭指向反疫苗宣傳運動。耶特表示,這項運動主要是由海外倡議人士在網路上展開。

    「很不幸的是,這項運動在薩摩亞找到願意相信的民眾,那裡有一部分人懷疑醫療保健服務品質,且可能不信任當地(疫苗)供應者。」他說,來自諸如美國和澳洲等富裕已開發國家的運動人士,在網路上張貼反疫苗訊息,他們必須意識到自己的所作所為會對開發中國家帶來衝擊。

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※幫你省時又省力,新北清潔一流服務好口碑

  • 陷垃圾危機 菲律賓計劃禁用一次性塑膠

    摘錄自2019年12月5日中央通訊社綜合報導

    菲律賓環境部長希瑪圖今天(5日)說,由於人們製造數量甚多的廢棄物,清理速度遠遠趕不及,菲律賓正處於垃圾危機中。環境部預計將在2週內規劃完成限用一次性塑膠的全國禁令。

    ABS-CBN新聞網和「菲律賓每日詢問報」(Philippine Daily Inquirer)報導,希瑪圖(Roy Cimatu)說,在馬尼拉都會區,今年第一季製造的廢棄物達3萬4574.77立方公尺,第二季則為3萬2221.17立方公尺,已超過全年基線預估值5萬8112.31立方公尺。

    他引述數據表示,菲律賓是全球第3大海洋塑膠污染來源國。為此,當局須加強固體廢棄物管理政策。菲律賓總統杜特蒂(Rodrigo Duterte)日前提出為因應氣候變遷問題,菲律賓應禁用塑膠。

    希瑪圖說,除了一次性塑膠禁令,環境暨天然資源部(DENR)正擬定的命令也將涵括塑膠回收問題。

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

    ※別再煩惱如何寫文案,掌握八大原則!

  • 特斯拉遭知名放空機構盯上!供需恐出包、股價下挫

    特斯拉遭知名放空機構盯上!供需恐出包、股價下挫

    知名放空機構香櫞研究(Citron Research)去(2015)年10月狙擊加拿大專業製藥公司Valeant Pharmaceuticals International Inc.、質疑該公司認列的藥品收入,導致其股價在一天內慘跌19%後,又在去年12月16日將「2016年年度放空標的」頭銜頒給以色列先進駕駛輔助系統(Advanced Driver Assistance Systems;ADAS)大廠Mobileye N.V.,使其股價一路滾落了42%。   現在,Citron把炮口轉向美國豪華電動車製造商特斯拉(Tesla Motors, Inc.),週二(3月1日)透過Twitter聲稱特斯拉供需出狀況、將使其股價在今年底下探100美元。Citron還指出,消息面看來對股價相當不利。   特斯拉1日聞訊逆勢下挫2.91%、收186.35美元。特斯拉預定3月31日首度公開專為大眾設計的次世代電動車「Model 3」。   其實,在特斯拉於2月中公布第4季財報前,Model X休旅車的產能問題、低油價恐衝擊電動車銷售量等疑慮,就不斷壓抑公司股價。   不過,當特斯拉維持2016年強勁的銷售預估不變、還聲稱會在2017年發售Model 3之後,股價就應聲反彈。   特斯拉執行長Elon Musk更對自家公司展現信心。洛杉磯時報、Forbes報導,Musk已在1月29日執行選擇權,斥資350萬美元購入總值1億美元的特斯拉股票(當時其股價還有191.20美元)。   Citron最近雖因準確看空Valeant、Mobileye而受到矚目,但其實據華爾街日報報導,該機構2013年對特斯拉的空方評價卻成效不彰,特斯拉2013年迄今股價仍上漲了37%。   Citron、Musk這次誰勝誰負,還得看特斯拉能否順利拉高Model X與Model S的產能,而在內華達州占地1,000萬平方英尺的電池廠房也需如期完工,才能生產較為平價的大眾車款Model 3。   (本文內容由授權使用;首圖來源: CC BY 2.0)

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※幫你省時又省力,新北清潔一流服務好口碑

  • 8 縣市 2020 年電動機車補助出爐,新購補助減少、汰舊換新成主力

    8 縣市 2020 年電動機車補助出爐,新購補助減少、汰舊換新成主力

    2020 年 1 月邁向尾聲,各縣市的電動機車補助也陸續出爐,至過年前已有 8 個縣市公布新購電動機車或汰舊換新補助的辦法。

    由於環保署補助在 2020 年退場,因此 2019 年底掀起一股電動機車的購車熱潮。2020 年後環保署政策改變,主力放在淘汰舊型機車減少空氣污染排放。因此雖然沒了新購電動機車補助,但汰舊換新的範圍擴大,2007 年 6 月 30 日前生產的一至四行程老舊燃油機車汰換成電動機車或 7 期燃油機車都能享有補助,重型機車每輛 5,000 元,輕型機車每輛 3,000 元。

    除了環保署政策改變,工業局購買電動機車的補助也下滑,從原先的每輛補助 1 萬元減少至 7,000 元,也是唯一全國適用的新購電動機車補助。因此 2020 年中央政府補助新購電動機車每輛 7,000 元,汰舊換新購買重型電動機車每輛共 12,000 元,汰舊換新購買輕型電動機車每輛共 1 萬元。

    截至 1 月 22 日有 8 個縣市公布電動機車補助額。

    地方政府部分,目前補助公布的縣市包括台北市、台中市、嘉義市、台南市、屏東縣、花蓮縣、台東縣和彰化縣。台北市和屏東縣等縣市跟隨環保署方向,停止補助新購電動機車,但繼續提供汰舊換新補助。彰化縣則是僅公佈汰舊換新補助辦法,尚未宣告新購電動機車補助措施。

    花蓮縣和台東縣補助金額最高但仰賴花東基金,有補助數量限制,每輛電動機車補助 1 萬元,花蓮縣政府僅於汰舊換新補助微幅加碼。花東以外,以嘉義市的補助金額最高,新購電動機車補助 8,000 元,汰舊換新補助更高達 1 萬元。

    有些人認為 2020 年補助減少,電動機車銷量將大幅衰退。電動機車大廠 Gogoro 指出,由於汰購換新補助帶動換車潮,1 月上半銷售量已達 2019 年同期近 80%。不過這樣的觀察僅為少量樣本,電動機車能否維持 2019 年的強勢表現,值得持續關注。

    (合作媒體:。首圖來源:Gogoro)

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

    ※別再煩惱如何寫文案,掌握八大原則!

  • 自動任務調度 – Timer

    自動任務調度 – Timer

    一、概述:

    最近維護一個老項目,裏面使用的是Timer的時間調度器,以前沒接觸過,對着代碼鼓搗了半天,查閱了部分博客,最後總結出自己的見解,新項目一般是不會用這種老掉牙的時間調度器了,但是維護老項目還是用的着的。就當筆記記錄一下了,自己寫的才是符合自己的思路走向的。有時間再補上Quartz調度器,這個才是現在使用最多的。

    二、常用的三種調度器分類

    Java自帶的java.util.Timer類,這個類允許你調度一個java.util.TimerTask任務。使用這種方式可以讓你的程序按照某一個頻度執行,但不能在指定時間運行。

    使用Quartz,這是一個功能比較強大的的調度器,可以讓你的程序在指定時間執行,也可以按照某一個頻度執行,配置起來稍顯複雜。

    Spring3.0以後自帶的task,可以將它看成一個輕量級的Quartz,而且使用起來比Quartz簡單許多。

    三、使用Spring體系來完成代碼的搭建

    1、代碼結構:

                                      

     

     

     

    2、springContext.xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"
           default-lazy-init="true">
    
        <!--定義了一個TimerFactoryBean類,並且把ScheduledTimerTask類的實例作為需要調度的task。-->
        <bean id="timerFactory" class="org.springframework.scheduling.timer.TimerFactoryBean" lazy-init="false">
            <property name="scheduledTimerTasks">
                <list>
                    <ref local="scheduledTask1"/>
                    <ref local="scheduledTask2"/>
                </list>
            </property>
        </bean>
    
        <!--利用ScheduledTimerTask類來配置每個task的啟動時間延時,每次啟動之間的間隔,當然還有最重要的是需要運行那個對象,也就是MethodInvokingTimerTaskFactoryBean類的實例-->
        <bean id="scheduledTask1" class="org.springframework.scheduling.timer.ScheduledTimerTask">
            <property name="delay" value="0" />
            <property name="period" value="1000" />
            <property name="timerTask">
                <ref bean="methodInvokingTask1"/>
            </property>
        </bean>
    
        <bean id="scheduledTask2" class="org.springframework.scheduling.timer.ScheduledTimerTask">
            <property name="delay" value="0" />
            <property name="period" value="1000" />
            <property name="timerTask">
                <ref bean="methodInvokingTask2"/>
            </property>
        </bean>
    
        <!--利用spring提供的MethodInvokingTimerTaskFactoryBean類來實現來實現對對task類和方法的聲明,聲明目標對象和方法,從而使spring知道要運行那個類的那個方法-->
        <bean id="methodInvokingTask1" class="org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean">
            <property name="targetObject" ref="myTask1"/>
            <property name="targetMethod" value="run"/>
        </bean>
    
        <bean id="methodInvokingTask2" class="org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean">
            <property name="targetObject" ref="myTask2"/>
            <property name="targetMethod" value="run"/>
        </bean>
    
        <!--被指定自動任務的類對象-->
        <bean id="myTask1" class="com.turtle.test.MyTask">
            <property name="name" value="啟動一"/>
        </bean>
    
        <bean id="myTask2" class="com.turtle.test.MyTask_2">
            <property name="name" value="啟動二"/>
        </bean>
    
    </beans>

     

    3、MyTask文件

    package com.turtle.test;
    
    import java.util.TimerTask;
    
    /**
     * 自定義一個定時任務
     * 推薦是繼承自 TimerTask
     */
    public class MyTask extends TimerTask {
    
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        private static int i = 0;
    
        // 使用線程中的方法  run
        @Override
        public void run() {
            System.out.println("定時任務啟動"+name+"----出現了"+i++);
        }
    }

     

    4、MyTask_2文件

    package com.turtle.test;
    
    import java.util.TimerTask;
    
    /**
     * 自定義一個定時任務
     * 推薦是繼承自 TimerTask
     */
    public class MyTask_2 extends TimerTask {
    
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        private static int i = 0;
    
        // 使用線程中的方法  run
        @Override
        public void run() {
            System.out.println("定時任務啟動"+name+"----出現了"+i++);
        }
    }

     

     

    5、MyTestTask_Test_01

    package com.turtle.test;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class MyTestTask_Test_01 {
        public static void main(String[] args) {
            // 啟動測試
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("springContext.xml");
        }
    }

     

     

    6、結果:

     

                            

    四、總結:

    如果要使用TImer的調度器的話,推薦使用新的ScheduledExecutorService,這個目前沒使用,就沒進行代碼驗證了,推薦一博客,大概看了下,寫得挺好的

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※幫你省時又省力,新北清潔一流服務好口碑

  • 用python實現對元素的長截圖

    用python實現對元素的長截圖

    一.目標

    瀏覽網頁的時候,看見哪個元素,就能截取哪個元素當圖片,不管那個元素有多長

     

    二.所用工具和第三方庫

    python ,PIL,selenium

    pycharm

    三.代碼部分

    長截圖整體思路:

    1.獲取元素

    2.移動,截圖,移動,截圖,直到抵達元素的底部

    3.把截圖按照元素所在位置切割,在所有圖片中只保留該元素

    4.拼接

     

    如果driver在環境變量中,那麼不用指定路徑

    b=webdriver.Chrome(executable_path=r"C:\Users\Desktop\chromedriver.exe")#指定一下driver
    b.get("")
    b.maximize_window()#最大化窗口

    打開網站

     

     

     我們可以看見一個ID為maincontent的元素,寬度為850PX,長度為3828PX,這個長度必須使用才能長截圖才能完整截下來

     

    el=b.find_element_by_id("maincontent")#找到元素

    我們還需要一個重要的參數,就是你電腦一次能截取多高的像素

    先用下圖代碼獲取一個圖片

    #fp為存放圖片的地址
    b.get_screenshot_as_file(fp)

     

    也就是說用我電腦上截圖的默認高度為614像素

     

     所以我設置一個變量:

    sc_hight=614

    然後設置一下其他變量

        count = int(el.size["height"] / sc_hight)  # 元素的高度除以你每次截多少就是次數
        start_higth = el.location["y"]  # 元素的初始高度
        max_px = start_higth + (count - 1) * sc_hight  # for循環中最大的px
        last_px = el.size["height"] + start_higth - sc_hight  # 元素最底部的位置
        surplus_px = last_px - max_px  # 剩餘的邊的高度
        img_path = []  # 用來存放圖片地址

    註釋:

    1.count為元素的高度/每次截取的高度,比如這次實例中元素高度為3828PX,我每次截614px,需要6.2次,int之後變成6,也就是截6次,還剩一點,那一點後面再說

    2.start_higth為初始高度,這個沒有什麼可說的

    3.max_px為循環結束后,到達的高度

    4.last_px為元素最底部的高度

    5.surplus_px就是移動6次后,還沒有截取的高度

    屏幕每次移動,移動sc_hight個像素,初始位置為(0,元素的Y值)

        for i in range(0, count):
            js = "scrollTo(0,%s)" % (start_higth + i * sc_hight)  # 用於移動滑輪,每次移動614px,初始值是元素的初始高度
            b.execute_script(js)  # 執行js
            time.sleep(0.5)
            fp = r"C:\Users\wdj\Desktop\%s.png" % i  # 圖片地址,運行的話,改一下
            b.get_screenshot_as_file(fp)  # 屏幕截圖,這裡是截取是完整的網頁圖片,你可以打斷點看一下圖片
            img = Image.open(fp=fp)
            img2 = img.crop((el.location["x"], 0, el.size["width"] + el.location["x"], sc_hight))  # 剪切圖片
            img2.save(fp)  # 保存圖片,覆蓋完整的網頁圖片
            img_path.append(fp)  # 添加圖片路徑
            time.sleep(0.5)
            print(js)
        else:
            js = "scrollTo(0,%s)" % last_px  # 滾動到最後一個位置
            b.execute_script(js)
            fp = r"C:\Users\wdj\Desktop\last.png"
            b.get_screenshot_as_file(fp)
            img = Image.open(fp=fp)
            print((el.location["x"], sc_hight - surplus_px, el.size["width"] + el.location["x"], sc_hight))
            img2 = img.crop((el.location["x"], sc_hight - surplus_px, el.size["width"] + el.location["x"], sc_hight))
            img2.save(fp)
            img_path.append(fp)
            print(js)

    上面是把該元素的在頁面都截完,並且剪切,把圖片保存的路徑放入img_path

    最後一步:把所有截圖都貼到新創建的圖片中

        new_img = Image.new("RGB", (el.size["width"], el.size["height"]))  # 創建一個新圖片,大小為元素的大小
        k = 0
        for i in img_path:
            tem_img = Image.open(i)
            new_img.paste(tem_img, (0, sc_hight * k))  # 把圖片貼上去,間隔一個截圖的距離
            k += 1
        else:
            new_img.save(r"C:\Users\wdj\Desktop\test.png")  # 保存

     

    運行效果圖:


    說明完整的截取下來了

     

     

     

    補充優化:

    如果是個小元素怎麼辦,不用長截圖就能截取的那種

    因為很簡單我就直接貼代碼了

        start_higth = el.location["y"]
        js = "scrollTo(0,%s)" % (start_higth)
        b.execute_script(js)  # 執行js
        time.sleep(0.5)
        fp = r"C:\Users\wdj\Desktop\test.png" # 圖片地址,運行的話,改一下
        b.get_screenshot_as_file(fp)
        img = Image.open(fp=fp)
        img2 = img.crop((el.location["x"], 0, el.size["width"] + el.location["x"], el.size["height"]))  # 剪切圖片
        img2.save(fp)

    效果如下:

     

     

    完整代碼:

    from selenium import webdriver
    from PIL import Image
    import time
    def short_sc(el,b):
        start_higth = el.location["y"]
        js = "scrollTo(0,%s)" % (start_higth)
        b.execute_script(js)  # 執行js
        time.sleep(0.5)
        fp = r"C:\Users\wdj\Desktop\test.png" # 圖片地址,運行的話,改一下
        b.get_screenshot_as_file(fp)
        img = Image.open(fp=fp)
        img2 = img.crop((el.location["x"], 0, el.size["width"] + el.location["x"], el.size["height"]))  # 剪切圖片
        img2.save(fp)
    
    def long_sc(el,b):
        count = int(el.size["height"] / sc_hight)  # 元素的高度除以你每次截多少就是次數
        start_higth = el.location["y"]  # 元素的初始高度
        max_px = start_higth + (count - 1) * sc_hight  # for循環中最大的px
        last_px = el.size["height"] + start_higth - sc_hight  # 元素最底部的位置
        surplus_px = last_px - max_px  # 剩餘的邊的高度
        img_path = []  # 用來存放圖片地址
        for i in range(0, count):
            js = "scrollTo(0,%s)" % (start_higth + i * sc_hight)  # 用於移動滑輪,每次移動614px,初始值是元素的初始高度
            b.execute_script(js)  # 執行js
            time.sleep(0.5)
            fp = r"C:\Users\wdj\Desktop\%s.png" % i  # 圖片地址,運行的話,改一下
            b.get_screenshot_as_file(fp)  # 屏幕截圖,這裡是截取是完整的網頁圖片,你可以打斷點看一下圖片
            img = Image.open(fp=fp)
            img2 = img.crop((el.location["x"], 0, el.size["width"] + el.location["x"], sc_hight))  # 剪切圖片
            img2.save(fp)  # 保存圖片,覆蓋完整的網頁圖片
            img_path.append(fp)  # 添加圖片路徑
            time.sleep(0.5)
            print(js)
        else:
            js = "scrollTo(0,%s)" % last_px  # 滾動到最後一個位置
            b.execute_script(js)
            fp = r"C:\Users\wdj\Desktop\last.png"
            b.get_screenshot_as_file(fp)
            img = Image.open(fp=fp)
            print((el.location["x"], sc_hight - surplus_px, el.size["width"] + el.location["x"], sc_hight))
            img2 = img.crop((el.location["x"], sc_hight - surplus_px, el.size["width"] + el.location["x"], sc_hight))
            img2.save(fp)
            img_path.append(fp)
            print(js)
    
        new_img = Image.new("RGB", (el.size["width"], el.size["height"]))  # 創建一個新圖片,大小為元素的大小
        k = 0
        for i in img_path:
            tem_img = Image.open(i)
            new_img.paste(tem_img, (0, sc_hight * k))  # 把圖片貼上去,間隔一個截圖的距離
            k += 1
        else:
            new_img.save(r"C:\Users\wdj\Desktop\test.png")  # 保存
    
    b=webdriver.Chrome(executable_path=r"C:\Users\wdj\Desktop\chromedriver.exe")#指定一下driver
    b.get("https://www.w3school.com.cn/html/html_links.asp")
    b.maximize_window()#最大化窗口
    # b.get_screenshot_as_file(fp)
    sc_hight=614#你屏幕截圖默認的大小,可以去截一張,去畫圖裡面看看是多少像素,我這裡是614像素
    
    # b.switch_to.frame(b.find_element_by_xpath('//*[@id="intro"]/iframe'))
    el=b.find_element_by_id("maincontent")#找到元素
    if el.size["height"]>sc_hight:
        long_sc(el,b)
    else:
        short_sc(el,b)

    完整代碼

     

    PS:

    有些特殊情況,比如截取的元素在iframe中,直接用driver.switch_to.frame(iframe元素)即可

    或者不是iframe,但是元素有overflow屬性,直接用JS把他的overflow去掉就行

     

     

     

     

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

    ※別再煩惱如何寫文案,掌握八大原則!

  • 通俗地說邏輯回歸【Logistic regression】算法(二)sklearn邏輯回歸實戰

    通俗地說邏輯回歸【Logistic regression】算法(二)sklearn邏輯回歸實戰

    前情提要:

    上一篇主要介紹了邏輯回歸中,相對理論化的知識,這次主要是對上篇做一點點補充,以及介紹sklearn 邏輯回歸模型的參數,以及具體的實戰代碼。

    1.邏輯回歸的二分類和多分類

    上次介紹的邏輯回歸的內容,基本都是基於二分類的。那麼有沒有辦法讓邏輯回歸實現多分類呢?那肯定是有的,還不止一種。

    實際上二元邏輯回歸的模型和損失函數很容易推廣到多元邏輯回歸。比如總是認為某種類型為正值,其餘為0值。

    舉個例子,要分類為A,B,C三類,那麼就可以把A當作正向數據,B和C當作負向數據來處理,這樣就可以用二分類的方法解決多分類的問題,這種方法就是最常用的one-vs-rest,簡稱OvR。而且這種方法也可以方便得推廣到其他二分類模型中(當然其他算法可能有更好的多分類辦法)。

    另一種多元邏輯回歸的方法是Many-vs-Many(MvM),它會選擇一部分類別的樣本和另一部分類別的樣本來做邏輯回歸二分類。

    聽起來很不可思議,但其實確實是能辦到的。比如數據有A,B,C三個分類。

    我們將A,B作為正向數據,C作為負向數據,訓練出一個分模型。再將A,C作為正向數據,B作為負向數據,訓練出一個分類模型。最後B,C作為正向數據,C作為負向數據,訓練出一個模型。

    通過這三個模型就能實現多分類,當然這裏只是舉個例子,實際使用中有其他更好的MVM方法。限於篇幅這裏不展開了。

    MVM中最常用的是One-Vs-One(OvO)。OvO是MvM的特例。即每次選擇兩類樣本來做二元邏輯回歸。

    對比下兩種多分類方法,通常情況下,Ovr比較簡單,速度也比較快,但模型精度上沒MvM那麼高。MvM則正好相反,精度高,但速度上比不過Ovr。

    2.邏輯回歸的正則化

    所謂正則化,其目的是為了減弱邏輯回歸模型的精度,難道模型的準確度不是越高越好嘛?看看下面這張圖就明白了:

    左邊那個圖就是過擬合的情況,過擬合其實就是模型的精度太過高了,它能非常好得匹配訓練集的數據,但一旦有新的數據,就會表現得很差。

    而我們要的非過擬合的模型是,精度可以差一些,但泛化性能,也就是對新的數據的識別能力,要比較好。

    正則化就是減弱模型精度,提高泛化效果的這個東西。

    3.sklearn各個參數

    def LogisticRegression(penalty='l2', 
                                        dual=False, 
                                        tol=1e-4, 
                                        C=1.0,
                                        fit_intercept=True, 
                                        intercept_scaling=1, 
                                        class_weight=None,
                                        random_state=None, 
                                        solver='warn', 
                                        max_iter=100,
                                        multi_class='warn', 
                                        verbose=0, 
                                        warm_start=False, 
                                        n_jobs=None,
                                        l1_ratio=None
                                        )
    跟線性回歸一比,邏輯回歸的參數那還真是多啊,不過我們一個一個來看看參數都是什麼意思吧。                                 
    
    - dual:對偶或者原始方法,布爾類型,默認為False。Dual只適用於正則化相為l2的‘liblinear’的情況,通常樣本數大於特徵數的情況下,默認為False。
    
    - tol:停止迭代求解的閾值,單精度類型,默認為1e-4。
    
    - C:正則化係數的倒數,必須為正的浮點數,默認為 1.0,這個值越小,說明正則化效果越強。換句話說,這個值越小,越訓練的模型更泛化,但也更容易欠擬合。
    
    - fit_intercept:是否要使用截距(在決策函數中使用截距),布爾類型,默認為True。
    
    - intercept_scaling:官方解釋比較模糊,我說下個人理解。浮點型,默認值是1.0。這個參數僅在“solver”參數(下面介紹)為“liblinear”“fit_intercept ”參數為True的時候生效。作用是給特徵向量添加一個常量,這個常量就是intercept_scaling。比如原本的向量是[x],那麼添加后就變成[x,intercept_scaling]。
    
    - class_weight:分類權重,可以是一個dict(字典類型),也可以是一個字符串"balanced"字符串。默認是None,也就是不做任何處理,而"balanced"則會去自動計算權重,分類越多的類,權重越低,反之權重越高。也可以自己輸出一個字典,比如一個 0/1 的二元分類,可以傳入{0:0.1,1:0.9},這樣 0 這個分類的權重是0.1,1這個分類的權重是0.9。這樣的目的是因為有些分類問題,樣本極端不平衡,比如網絡攻擊,大部分正常流量,小部分攻擊流量,但攻擊流量非常重要,需要有效識別,這時候就可以設置權重這個參數。
    
    - random_state:設置隨機數種子,可以是int類型和None,默認是None。當"solver"參數為"sag"和"liblinear"的時候生效。
    
    - verbose:輸出詳細過程,int類型,默認為0(不輸出)。當大於等於1時,輸出訓練的詳細過程。僅當"solvers"參數設置為"liblinear"和"lbfgs"時有效。
    
    - warm_start:設置熱啟動,布爾類型,默認為False。若設置為True,則以上一次fit的結果作為此次的初始化,如果"solver"參數為"liblinear"時無效。
    
    - max_iter:最大迭代次數,int類型,默認-1(即無限制)。注意前面也有一個tol迭代限制,但這個max_iter的優先級是比它高的,也就如果限制了這個參數,那是不會去管tol這個參數的。
    

    OK,上述就是對一些比較簡略的參數的說明,但是還有幾個重要的參數沒講到,這是因為這幾個參數我覺得需要單獨拎出來講一講。

    sklearn邏輯回歸參數 –penalty

    正則化類型選擇,字符串類型,可選’l1’,’l2’,’elasticnet’和None,默認是’l2’,通常情況下,也是選擇’l2’。這個參數的選擇是會影響到參數’solver’的選擇的,下面會介紹。

    其中’l1’和’l2’。分別對應L1的正則化和L2的正則化,’elasticnet’則是彈性網絡(這玩意我也不大懂),默認是L2的正則化。

    在調參時如果主要的目的只是為了解決過擬合,一般penalty選擇L2正則化就夠了。但是如果選擇L2正則化發現還是過擬合,即預測效果差的時候,就可以考慮L1正則化。另外,如果模型的特徵非常多,我們希望一些不重要的特徵係數歸零,從而讓模型係數稀疏化的話,也可以使用L1正則化。

    penalty參數的選擇會影響我們損失函數優化算法的選擇。即參數solver的選擇,如果是L2正則化,那麼4種可選的算法{‘newton-cg’,‘lbfgs’,‘liblinear’,‘sag’}都可以選擇。但是如果penalty是L1正則化的話,就只能選擇‘liblinear’了。這是因為L1正則化的損失函數不是連續可導的,而{‘newton-cg’,‘lbfgs’,‘sag’}這三種優化算法時都需要損失函數的一階或者二階連續導數。而‘liblinear’並沒有這個依賴。最後還有一個’elasticnet’,這個只有solver參數為’saga’才能選。

    sklearn邏輯回歸參數 –solver

    優化算法參數,字符串類型,一個有五種可選,分別是”newton-cg”,”lbfgs”,”liblinear”,”sag”,”saga。默認是”liblinear”。分別介紹下各個優化算法:

    • a) liblinear:使用了開源的liblinear庫實現,內部使用了坐標軸下降法來迭代優化損失函數。
    • b) lbfgs:擬牛頓法的一種,利用損失函數二階導數矩陣即海森矩陣來迭代優化損失函數。
    • c) newton-cg:也是牛頓法家族的一種,利用損失函數二階導數矩陣即海森矩陣來迭代優化損失函數。
    • d) sag:即隨機平均梯度下降,是梯度下降法的變種,和普通梯度下降法的區別是每次迭代僅僅用一部分的樣本來計算梯度,適合於樣本數據多的時候。
      在優化參數的選擇上,官方是這樣建議的:
    • e)saga:優化的,無偏估計的sag方法。(‘sag’ uses a Stochastic Average Gradient descent, and ‘saga’ uses its improved, unbiased version named SAGA.)
      對小的數據集,可以選擇”liblinear”,如果是大的數據集,比如說大於10W的數據,那麼選擇”sag”和”saga”會讓訓練速度更快。

    對於多分類問題,只有newton-cg,sag,saga和lbfgs能夠處理多項損失(也就是MvM的情況,還記得上面說到的多分類嘛?),而liblinear僅處理(OvR)的情況。啥意思,就是用liblinear的時候,如果是多分類問題,得先把一種類別作為一個類別,剩餘的所有類別作為另外一個類別。一次類推,遍歷所有類別,進行分類。

    這個的選擇和正則化的參數也有關係,前面說到”penalty”參數可以選擇”l1″,”l2″和None。這裏’liblinear’是可以選擇’l1’正則和’l2’正則,但不能選擇None,’newton-cg’,’lbfgs’,’sag’和’saga’這幾種能選擇’l2’或no penalty,而’saga’則能選怎’elasticnet’正則。好吧,這部分還是挺繞的。

    歸納一下吧,二分類情況下,數據量小,一般默認的’liblinear’的行,數據量大,則使用’sag’。多分類的情況下,在數據量小的情況下,追求高精度,可以用’newton-cg’或’lbfgs’以’MvM’的方式求解。數據量一大還是使用’sag’。

    當然實際情況下還是要調參多次才能確定參數,這裏也只能給些模糊的建議。

    sklearn邏輯回歸參數 –multi_class

    multi_class參數決定了我們分類方式的選擇,有 ovr和multinomial兩個值可以選擇,默認是 ovr。
    ovr即前面提到的one-vs-rest(OvR),而multinomial即前面提到的many-vs-many(MvM)。如果是二元邏輯回歸,ovr和multinomial並沒有任何區別,區別主要在多元邏輯回歸上。

    4.sklearn實例

    實例這部分,就直接引用sklearn官網的,使用邏輯回歸對不同種類的鳶尾花進行分類的例子吧。

    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn import linear_model, datasets
    
    # 加載鳶尾花數據
    iris = datasets.load_iris()
    # 只採用樣本數據的前兩個feature,生成X和Y
    X = iris.data[:, :2]  
    Y = iris.target
    
    h = .02  # 網格中的步長
    
    # 新建模型,設置C參數為1e5,並進行訓練
    logreg = linear_model.LogisticRegression(C=1e5)
    logreg.fit(X, Y)
    
    # 繪製決策邊界。為此我們將為網格 [x_min, x_max]x[y_min, y_max] 中的每個點分配一個顏色。
    x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5
    y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    Z = logreg.predict(np.c_[xx.ravel(), yy.ravel()])
    
    # 將結果放入彩色圖中
    Z = Z.reshape(xx.shape)
    plt.figure(1, figsize=(4, 3))
    plt.pcolormesh(xx, yy, Z, cmap=plt.cm.Paired)
    
    # 將訓練點也同樣放入彩色圖中
    plt.scatter(X[:, 0], X[:, 1], c=Y, edgecolors='k', cmap=plt.cm.Paired)
    plt.xlabel('Sepal length')
    plt.ylabel('Sepal width')
    
    plt.xlim(xx.min(), xx.max())
    plt.ylim(yy.min(), yy.max())
    plt.xticks(())
    plt.yticks(())
    
    plt.show()

    運行上面那段代碼會有如下的結果:

    可以看到,已將三種類型的鳶尾花都分類出來了。

    小結

    邏輯回歸算是比較簡單的一種分類算法,而由於簡單,所以也比較適合初學者初步接觸機器學習算法。學習了之後,對後面一些更複雜的機器學習算法,諸如Svm,或更高級的神經網絡也能有一個稍微感性的認知。

    而實際上,Svm可以看作是邏輯回歸的更高級的演化。而從神經網絡的角度,邏輯回歸甚至可以看作一個最初級,最淺層的神經網絡。

    邏輯回歸就像是金庸小說裏面,獨孤九劍的第一式,最為簡單,卻又是其他威力極大的招式的基礎,其他的招式都又第一式演化而出。

    夯實基礎,才能砥礪前行。

    以上~

    參考文章:

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※幫你省時又省力,新北清潔一流服務好口碑

  • Java I/O體系從原理到應用,這一篇全說清楚了

    Java I/O體系從原理到應用,這一篇全說清楚了

    本文介紹操作系統I/O工作原理,Java I/O設計,基本使用,開源項目中實現高性能I/O常見方法和實現,徹底搞懂高性能I/O之道

    基礎概念

    在介紹I/O原理之前,先重溫幾個基礎概念:

    • (1) 操作系統與內核

    操作系統:管理計算機硬件與軟件資源的系統軟件
    內核:操作系統的核心軟件,負責管理系統的進程、內存、設備驅動程序、文件和網絡系統等等,為應用程序提供對計算機硬件的安全訪問服務

    • 2 內核空間和用戶空間

    為了避免用戶進程直接操作內核,保證內核安全,操作系統將內存尋址空間劃分為兩部分:
    內核空間(Kernel-space),供內核程序使用
    用戶空間(User-space),供用戶進程使用
    為了安全,內核空間和用戶空間是隔離的,即使用戶的程序崩潰了,內核也不受影響

    • 3 數據流

    計算機中的數據是基於隨着時間變換高低電壓信號傳輸的,這些數據信號連續不斷,有着固定的傳輸方向,類似水管中水的流動,因此抽象數據流(I/O流)的概念:指一組有順序的、有起點和終點的字節集合

    抽象出數據流的作用:實現程序邏輯與底層硬件解耦,通過引入數據流作為程序與硬件設備之間的抽象層,面向通用的數據流輸入輸出接口編程,而不是具體硬件特性,程序和底層硬件可以獨立靈活替換和擴展

    I/O 工作原理

    1 磁盤I/O

    典型I/O讀寫磁盤工作原理如下:

    tips: DMA:全稱叫直接內存存取(Direct Memory Access),是一種允許外圍設備(硬件子系統)直接訪問系統主內存的機制。基於 DMA 訪問方式,系統主內存與硬件設備的數據傳輸可以省去CPU 的全程調度

    值得注意的是:

    • 讀寫操作基於系統調用實現
    • 讀寫操作經過用戶緩衝區,內核緩衝區,應用進程並不能直接操作磁盤
    • 應用進程讀操作時需阻塞直到讀取到數據

    2 網絡I/O

    這裏先以最經典的阻塞式I/O模型介紹:

    tips:recvfrom,經socket接收數據的函數

    值得注意的是:

    • 網絡I/O讀寫操作經過用戶緩衝區,Sokcet緩衝區
    • 服務端線程在從調用recvfrom開始到它返回有數據報準備好這段時間是阻塞的,recvfrom返回成功后,線程開始處理數據報

    Java I/O設計

    1 I/O分類

    Java中對數據流進行具體化和實現,關於Java數據流一般關注以下幾個點:

    • (1) 流的方向
      從外部到程序,稱為輸入流;從程序到外部,稱為輸出流

    • (2) 流的數據單位
      程序以字節作為最小讀寫數據單元,稱為字節流,以字符作為最小讀寫數據單元,稱為字符流

    • (3) 流的功能角色

    從/向一個特定的IO設備(如磁盤,網絡)或者存儲對象(如內存數組)讀/寫數據的流,稱為節點流
    對一個已有流進行連接和封裝,通過封裝后的流來實現數據的讀/寫功能,稱為處理流(或稱為過濾流);

    2 I/O操作接口

    java.io包下有一堆I/O操作類,初學時看了容易搞不懂,其實仔細觀察其中還是有規律:
    這些I/O操作類都是在繼承4個基本抽象流的基礎上,要麼是節點流,要麼是處理流

    2.1 四個基本抽象流

    java.io包中包含了流式I/O所需要的所有類,java.io包中有四個基本抽象流,分別處理字節流和字符流:

    • InputStream
    • OutputStream
    • Reader
    • Writer

    2.2 節點流

    節點流I/O類名由節點流類型 + 抽象流類型組成,常見節點類型有:

    • File文件
    • Piped 進程內線程通信管道
    • ByteArray / CharArray (字節數組 / 字符數組)
    • StringBuffer / String (字符串緩衝區 / 字符串)

    節點流的創建通常是在構造函數傳入數據源,例如:

    FileReader reader = new FileReader(new File("file.txt"));
    FileWriter writer = new FileWriter(new File("file.txt"));

    2.3 處理流

    處理流I/O類名由對已有流封裝的功能 + 抽象流類型組成,常見功能有:

    • 緩衝:對節點流讀寫的數據提供了緩衝的功能,數據可以基於緩衝批量讀寫,提高效率。常見有BufferedInputStream、BufferedOutputStream
    • 字節流轉換為字符流:由InputStreamReader、OutputStreamWriter實現
    • 字節流與基本類型數據相互轉換:這裏基本數據類型數據如int、long、short,由DataInputStream、DataOutputStream實現
    • 字節流與對象實例相互轉換:用於實現對象序列化,由ObjectInputStream、ObjectOutputStream實現

    處理流的應用了適配器/裝飾模式,轉換/擴展已有流,處理流的創建通常是在構造函數傳入已有的節點流或處理流:

    FileOutputStream fileOutputStream = new FileOutputStream("file.txt");
    // 擴展提供緩衝寫
    BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
     // 擴展提供提供基本數據類型寫
    DataOutputStream out = new DataOutputStream(bufferedOutputStream);

    3 Java NIO

    3.1 標準I/O存在問題

    Java NIO(New I/O)是一個可以替代標準Java I/O API的IO API(從Java 1.4開始),Java NIO提供了與標準I/O不同的I/O工作方式,目的是為了解決標準 I/O存在的以下問題:

    • (1) 數據多次拷貝

    標準I/O處理,完成一次完整的數據讀寫,至少需要從底層硬件讀到內核空間,再讀到用戶文件,又從用戶空間寫入內核空間,再寫入底層硬件

    此外,底層通過write、read等函數進行I/O系統調用時,需要傳入數據所在緩衝區起始地址和長度
    由於JVM GC的存在,導致對象在堆中的位置往往會發生移動,移動後傳入系統函數的地址參數就不是真正的緩衝區地址了

    可能導致讀寫出錯,為了解決上面的問題,使用標準I/O進行系統調用時,還會額外導致一次數據拷貝:把數據從JVM的堆內拷貝到堆外的連續空間內存(堆外內存)

    所以總共經歷6次數據拷貝,執行效率較低

    • (2) 操作阻塞

    傳統的網絡I/O處理中,由於請求建立連接(connect),讀取網絡I/O數據(read),發送數據(send)等操作是線程阻塞的

    // 等待連接
    Socket socket = serverSocket.accept();
    
    // 連接已建立,讀取請求消息
    StringBuilder req = new StringBuilder();
    byte[] recvByteBuf = new byte[1024];
    int len;
    while ((len = socket.getInputStream().read(recvByteBuf)) != -1) {
        req.append(new String(recvByteBuf, 0, len, StandardCharsets.UTF_8));
    }
    
    // 寫入返回消息
    socket.getOutputStream().write(("server response msg".getBytes()));
    socket.shutdownOutput();

    以上面服務端程序為例,當請求連接已建立,讀取請求消息,服務端調用read方法時,客戶端數據可能還沒就緒(例如客戶端數據還在寫入中或者傳輸中),線程需要在read方法阻塞等待直到數據就緒

    為了實現服務端併發響應,每個連接需要獨立的線程單獨處理,當併發請求量大時為了維護連接,內存、線程切換開銷過大

    3.2 Buffer

    Java NIO核心三大核心組件是Buffer(緩衝區)、Channel(通道)、Selector

    Buffer提供了常用於I/O操作的字節緩衝區,常見的緩存區有ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer,分別對應基本數據類型: byte, char, double, float, int, long, short,下面介紹主要以最常用的ByteBuffer為例,Buffer底層支持Java堆內(HeapByteBuffer)或堆外內存(DirectByteBuffer)

    堆外內存是指與堆內存相對應的,把內存對象分配在JVM堆以外的內存,這些內存直接受操作系統管理(而不是虛擬機,相比堆內內存,I/O操作中使用堆外內存的優勢在於:

    • 不用被JVM GC線回收,減少GC線程資源佔有
    • 在I/O系統調用時,直接操作堆外內存,可以節省一次堆外內存和堆內內存的複製

    ByteBuffer底層堆外內存的分配和釋放基於malloc和free函數,對外allocateDirect方法可以申請分配堆外內存,並返回繼承ByteBuffer類的DirectByteBuffer對象:

    public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }

    堆外內存的回收基於DirectByteBuffer的成員變量Cleaner類,提供clean方法可以用於主動回收,Netty中大部分堆外內存通過記錄定位Cleaner的存在,主動調用clean方法來回收;
    另外,當DirectByteBuffer對象被GC時,關聯的堆外內存也會被回收

    tips: JVM參數不建議設置-XX:+DisableExplicitGC,因為部分依賴Java NIO的框架(例如Netty)在內存異常耗盡時,會主動調用System.gc(),觸發Full GC,回收DirectByteBuffer對象,作為回收堆外內存的最後保障機制,設置該參數之後會導致在該情況下堆外內存得不到清理

    堆外內存基於基礎ByteBuffer類的DirectByteBuffer類成員變量:Cleaner對象,這個Cleaner對象會在合適的時候執行unsafe.freeMemory(address),從而回收這塊堆外內存

    Buffer可以見到理解為一組基本數據類型,存儲地址連續的的數組,支持讀寫操作,對應讀模式和寫模式,通過幾個變量來保存這個數據的當前位置狀態:capacity、 position、 limit:

    • capacity 緩衝區數組的總長度
    • position 下一個要操作的數據元素的位置
    • limit 緩衝區數組中不可操作的下一個元素的位置:limit <= capacity

    3.3 Channel

    Channel(通道)的概念可以類比I/O流對象,NIO中I/O操作主要基於Channel:
    從Channel進行數據讀取 :創建一個緩衝區,然後請求Channel讀取數據
    從Channel進行數據寫入 :創建一個緩衝區,填充數據,請求Channel寫入數據

    Channel和流非常相似,主要有以下幾點區別:

    • Channel可以讀和寫,而標準I/O流是單向的
    • Channel可以異步讀寫,標準I/O流需要線程阻塞等待直到讀寫操作完成
    • Channel總是基於緩衝區Buffer讀寫

    Java NIO中最重要的幾個Channel的實現:

    • FileChannel: 用於文件的數據讀寫,基於FileChannel提供的方法能減少讀寫文件數據拷貝次數,後面會介紹
    • DatagramChannel: 用於UDP的數據讀寫
    • SocketChannel: 用於TCP的數據讀寫,代表客戶端連接
    • ServerSocketChannel: 監聽TCP連接請求,每個請求會創建會一個SocketChannel,一般用於服務端

    基於標準I/O中,我們第一步可能要像下面這樣獲取輸入流,按字節把磁盤上的數據讀取到程序中,再進行下一步操作,而在NIO編程中,需要先獲取Channel,再進行讀寫

    FileInputStream fileInputStream = new FileInputStream("test.txt");
    FileChannel channel = fileInputStream.channel();

    tips: FileChannel僅能運行在阻塞模式下,文件異步處理的 I/O 是在JDK 1.7 才被加入的 java.nio.channels.AsynchronousFileChannel

    // server socket channel:
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.bind(new InetSocketAddress(InetAddress.getLocalHost(), 9091));
    
    while (true) {
        SocketChannel socketChannel = serverSocketChannel.accept();
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        int readBytes = socketChannel.read(buffer);
        if (readBytes > 0) {
            // 從寫數據到buffer翻轉為從buffer讀數據
            buffer.flip();
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            String body = new String(bytes, StandardCharsets.UTF_8);
            System.out.println("server 收到:" + body);
        }
    }

    3.4 Selector

    Selector(選擇器) ,它是Java NIO核心組件中的一個,用於檢查一個或多個NIO Channel(通道)的狀態是否處於可讀、可寫。實現單線程管理多個Channel,也就是可以管理多個網絡連接

    Selector核心在於基於操作系統提供的I/O復用功能,單個線程可以同時監視多個連接描述符,一旦某個連接就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作,常見有select、poll、epoll等不同實現

    Java NIO Selector基本工作原理如下:

    • (1) 初始化Selector對象,服務端ServerSocketChannel對象
    • (2) 向Selector註冊ServerSocketChannel的socket-accept事件
    • (3) 線程阻塞於selector.select(),當有客戶端請求服務端,線程退出阻塞
    • (4) 基於selector獲取所有就緒事件,此時先獲取到socket-accept事件,向Selector註冊客戶端SocketChannel的數據就緒可讀事件事件
    • (5) 線程再次阻塞於selector.select(),當有客戶端連接數據就緒,可讀
    • (6) 基於ByteBuffer讀取客戶端請求數據,然後寫入響應數據,關閉channel

    示例如下,完整可運行代碼已經上傳github(https://github.com/caison/caison-blog-demo):

    Selector selector = Selector.open();
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.bind(new InetSocketAddress(9091));
    // 配置通道為非阻塞模式
    serverSocketChannel.configureBlocking(false);
    // 註冊服務端的socket-accept事件
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    
    while (true) {
        // selector.select()會一直阻塞,直到有channel相關操作就緒
        selector.select();
        // SelectionKey關聯的channel都有就緒事件
        Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
    
        while (keyIterator.hasNext()) {
            SelectionKey key = keyIterator.next();
            // 服務端socket-accept
            if (key.isAcceptable()) {
                // 獲取客戶端連接的channel
                SocketChannel clientSocketChannel = serverSocketChannel.accept();
                // 設置為非阻塞模式
                clientSocketChannel.configureBlocking(false);
                // 註冊監聽該客戶端channel可讀事件,併為channel關聯新分配的buffer
                clientSocketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocateDirect(1024));
            }
    
            // channel可讀
            if (key.isReadable()) {
                SocketChannel socketChannel = (SocketChannel) key.channel();
                ByteBuffer buf = (ByteBuffer) key.attachment();
    
                int bytesRead;
                StringBuilder reqMsg = new StringBuilder();
                while ((bytesRead = socketChannel.read(buf)) > 0) {
                    // 從buf寫模式切換為讀模式
                    buf.flip();
                    int bufRemain = buf.remaining();
                    byte[] bytes = new byte[bufRemain];
                    buf.get(bytes, 0, bytesRead);
                    // 這裏當數據包大於byteBuffer長度,有可能有粘包/拆包問題
                    reqMsg.append(new String(bytes, StandardCharsets.UTF_8));
                    buf.clear();
                }
                System.out.println("服務端收到報文:" + reqMsg.toString());
                if (bytesRead == -1) {
                    byte[] bytes = "[這是服務回的報文的報文]".getBytes(StandardCharsets.UTF_8);
    
                    int length;
                    for (int offset = 0; offset < bytes.length; offset += length) {
                        length = Math.min(buf.capacity(), bytes.length - offset);
                        buf.clear();
                        buf.put(bytes, offset, length);
                        buf.flip();
                        socketChannel.write(buf);
                    }
                    socketChannel.close();
                }
            }
            // Selector不會自己從已selectedKeys中移除SelectionKey實例
            // 必須在處理完通道時自己移除 下次該channel變成就緒時,Selector會再次將其放入selectedKeys中
            keyIterator.remove();
        }
    }

    tips: Java NIO基於Selector實現高性能網絡I/O這塊使用起來比較繁瑣,使用不友好,一般業界使用基於Java NIO進行封裝優化,擴展豐富功能的Netty框架來優雅實現

    高性能I/O優化

    下面結合業界熱門開源項目介紹高性能I/O的優化

    1 零拷貝

    零拷貝(zero copy)技術,用於在數據讀寫中減少甚至完全避免不必要的CPU拷貝,減少內存帶寬的佔用,提高執行效率,零拷貝有幾種不同的實現原理,下面介紹常見開源項目中零拷貝實現

    1.1 Kafka零拷貝

    Kafka基於Linux 2.1內核提供,並在2.4 內核改進的的sendfile函數 + 硬件提供的DMA Gather Copy實現零拷貝,將文件通過socket傳送

    函數通過一次系統調用完成了文件的傳送,減少了原來read/write方式的模式切換。同時減少了數據的copy, sendfile的詳細過程如下:

    基本流程如下:

    • (1) 用戶進程發起sendfile系統調用
    • (2) 內核基於DMA Copy將文件數據從磁盤拷貝到內核緩衝區
    • (3) 內核將內核緩衝區中的文件描述信息(文件描述符,數據長度)拷貝到Socket緩衝區
    • (4) 內核基於Socket緩衝區中的文件描述信息和DMA硬件提供的Gather Copy功能將內核緩衝區數據複製到網卡
    • (5) 用戶進程sendfile系統調用完成並返回

    相比傳統的I/O方式,sendfile + DMA Gather Copy方式實現的零拷貝,數據拷貝次數從4次降為2次,系統調用從2次降為1次,用戶進程上下文切換次數從4次變成2次DMA Copy,大大提高處理效率

    Kafka底層基於java.nio包下的FileChannel的transferTo:

    public abstract long transferTo(long position, long count, WritableByteChannel target)

    transferTo將FileChannel關聯的文件發送到指定channel,當Comsumer消費數據,Kafka Server基於FileChannel將文件中的消息數據發送到SocketChannel

    1.2 RocketMQ零拷貝

    RocketMQ基於mmap + write的方式實現零拷貝:
    mmap() 可以將內核中緩衝區的地址與用戶空間的緩衝區進行映射,實現數據共享,省去了將數據從內核緩衝區拷貝到用戶緩衝區

    tmp_buf = mmap(file, len); 
    write(socket, tmp_buf, len);

    mmap + write 實現零拷貝的基本流程如下:

    • (1) 用戶進程向內核發起系統mmap調用
    • (2) 將用戶進程的內核空間的讀緩衝區與用戶空間的緩存區進行內存地址映射
    • (3) 內核基於DMA Copy將文件數據從磁盤複製到內核緩衝區
    • (4) 用戶進程mmap系統調用完成並返回
    • (5) 用戶進程向內核發起write系統調用
    • (6) 內核基於CPU Copy將數據從內核緩衝區拷貝到Socket緩衝區
    • (7) 內核基於DMA Copy將數據從Socket緩衝區拷貝到網卡
    • (8) 用戶進程write系統調用完成並返回

    RocketMQ中消息基於mmap實現存儲和加載的邏輯寫在org.apache.rocketmq.store.MappedFile中,內部實現基於nio提供的java.nio.MappedByteBuffer,基於FileChannel的map方法得到mmap的緩衝區:

    // 初始化
    this.fileChannel = new RandomAccessFile(this.file, "rw").getChannel();
    this.mappedByteBuffer = this.fileChannel.map(MapMode.READ_WRITE, 0, fileSize);

    查詢CommitLog的消息時,基於mappedByteBuffer偏移量pos,數據大小size查詢:

    public SelectMappedBufferResult selectMappedBuffer(int pos, int size) {
        int readPosition = getReadPosition();
        // ...各種安全校驗
        
        // 返回mappedByteBuffer視圖
        ByteBuffer byteBuffer = this.mappedByteBuffer.slice();
        byteBuffer.position(pos);
        ByteBuffer byteBufferNew = byteBuffer.slice();
        byteBufferNew.limit(size);
        return new SelectMappedBufferResult(this.fileFromOffset + pos, byteBufferNew, size, this);
    }

    tips: transientStorePoolEnable機制
    Java NIO mmap的部分內存並不是常駐內存,可以被置換到交換內存(虛擬內存),RocketMQ為了提高消息發送的性能,引入了內存鎖定機制,即將最近需要操作的CommitLog文件映射到內存,並提供內存鎖定功能,確保這些文件始終存在內存中,該機制的控制參數就是transientStorePoolEnable

    因此,MappedFile數據保存CommitLog刷盤有2種方式:

    • 1 開啟transientStorePoolEnable:寫入內存字節緩衝區(writeBuffer) -> 從內存字節緩衝區(writeBuffer)提交(commit)到文件通道(fileChannel) -> 文件通道(fileChannel) -> flush到磁盤
    • 2 未開啟transientStorePoolEnable:寫入映射文件字節緩衝區(mappedByteBuffer) -> 映射文件字節緩衝區(mappedByteBuffer) -> flush到磁盤

    RocketMQ 基於 mmap+write 實現零拷貝,適用於業務級消息這種小塊文件的數據持久化和傳輸
    Kafka 基於 sendfile 這種零拷貝方式,適用於系統日誌消息這種高吞吐量的大塊文件的數據持久化和傳輸

    tips: Kafka 的索引文件使用的是 mmap+write 方式,數據文件發送網絡使用的是 sendfile 方式

    1.3 Netty零拷貝

    Netty 的零拷貝分為兩種:

    • 1 基於操作系統實現的零拷貝,底層基於FileChannel的transferTo方法
    • 2 基於Java 層操作優化,對數組緩存對象(ByteBuf )進行封裝優化,通過對ByteBuf數據建立數據視圖,支持ByteBuf 對象合併,切分,當底層僅保留一份數據存儲,減少不必要拷貝

    2 多路復用

    Netty中對Java NIO功能封裝優化之後,實現I/O多路復用代碼優雅了很多:

    // 創建mainReactor
    NioEventLoopGroup boosGroup = new NioEventLoopGroup();
    // 創建工作線程組
    NioEventLoopGroup workerGroup = new NioEventLoopGroup();
    
    final ServerBootstrap serverBootstrap = new ServerBootstrap();
    serverBootstrap 
         // 組裝NioEventLoopGroup 
        .group(boosGroup, workerGroup)
         // 設置channel類型為NIO類型
        .channel(NioServerSocketChannel.class)
        // 設置連接配置參數
        .option(ChannelOption.SO_BACKLOG, 1024)
        .childOption(ChannelOption.SO_KEEPALIVE, true)
        .childOption(ChannelOption.TCP_NODELAY, true)
        // 配置入站、出站事件handler
        .childHandler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) {
                // 配置入站、出站事件channel
                ch.pipeline().addLast(...);
                ch.pipeline().addLast(...);
            }
        });
    
    // 綁定端口
    int port = 8080;
    serverBootstrap.bind(port).addListener(future -> {
        if (future.isSuccess()) {
            System.out.println(new Date() + ": 端口[" + port + "]綁定成功!");
        } else {
            System.err.println("端口[" + port + "]綁定失敗!");
        }
    });

    3 頁緩存(PageCache)

    頁緩存(PageCache)是操作系統對文件的緩存,用來減少對磁盤的 I/O 操作,以頁為單位的,內容就是磁盤上的物理塊,頁緩存能幫助程序對文件進行順序讀寫的速度幾乎接近於內存的讀寫速度,主要原因就是由於OS使用PageCache機制對讀寫訪問操作進行了性能優化:

    頁緩存讀取策略:當進程發起一個讀操作 (比如,進程發起一個 read() 系統調用),它首先會檢查需要的數據是否在頁緩存中:

    • 如果在,則放棄訪問磁盤,而直接從頁緩存中讀取
    • 如果不在,則內核調度塊 I/O 操作從磁盤去讀取數據,並讀入緊隨其後的少數幾個頁面(不少於一個頁面,通常是三個頁面),然後將數據放入頁緩存中

    頁緩存寫策略:當進程發起write系統調用寫數據到文件中,先寫到頁緩存,然後方法返回。此時數據還沒有真正的保存到文件中去,Linux 僅僅將頁緩存中的這一頁數據標記為“臟”,並且被加入到臟頁鏈表中

    然後,由flusher 回寫線程周期性將臟頁鏈表中的頁寫到磁盤,讓磁盤中的數據和內存中保持一致,最後清理“臟”標識。在以下三種情況下,臟頁會被寫回磁盤:

    • 空閑內存低於一個特定閾值
    • 臟頁在內存中駐留超過一個特定的閾值時
    • 當用戶進程調用 sync() 和 fsync() 系統調用時

    RocketMQ中,ConsumeQueue邏輯消費隊列存儲的數據較少,並且是順序讀取,在page cache機制的預讀取作用下,Consume Queue文件的讀性能幾乎接近讀內存,即使在有消息堆積情況下也不會影響性能,提供了2種消息刷盤策略:

    • 同步刷盤:在消息真正持久化至磁盤后RocketMQ的Broker端才會真正返回給Producer端一個成功的ACK響應
    • 異步刷盤,能充分利用操作系統的PageCache的優勢,只要消息寫入PageCache即可將成功的ACK返回給Producer端。消息刷盤採用後台異步線程提交的方式進行,降低了讀寫延遲,提高了MQ的性能和吞吐量

    Kafka實現消息高性能讀寫也利用了頁緩存,這裏不再展開

    參考

    《深入理解Linux內核 —— Daniel P.Bovet》

    更多精彩,歡迎關注公眾號 分佈式系統架構

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※幫你省時又省力,新北清潔一流服務好口碑

  • Hibernate一對多、多對一的關係表達

    Hibernate一對多、多對一的關係表達

    一、關係表達:

    1、一對多、多對一表的關係:

    學生表:

     

     

      班級表:

     

     

     在學生表中,學生的學號是主鍵。在班級表中,班級號是主鍵,因此,學生表的外鍵是classno。因此,班級對應學生是一對多,學生對應班級是多對一。因為,一個班級可以有多個學生,但是一個學生只能在一個班級。

    2、對象的一對多、多對一關係:

    (1)在Class類中,定義Set集合,表達一對多的關係:

     

     

     

    package pers.zhb.domain;
    import java.util.HashSet;
    import java.util.Set;
    public class Class {
        private String classno;
        private String department;
        private String monitor;
        private String classname;
        private Set<Student> classes=new HashSet<Student>();//使用set集合表達一對多關係
        public Class(){
        }
        public Set<Student> getClasses() {
            return classes;
        }
        public void setClasses(Set<Student> classes) {
            this.classes = classes;
        }
        public String getMonitor() {
            return monitor;
        }
    
        public void setMonitor(String monitor) {
            this.monitor = monitor;
        }
    
        public String getDepartment() {
            return department;
        }
    
        public void setDepartment(String department) {
            this.department = department;
        }
    
    
    
        public String getClassname() {
            return classname;
        }
    
        public void setClassname(String classname) {
            this.classname = classname;
        }
        public String getClassno() {
            return classno;
        }
    
        public void setClassno(String classno) {
            this.classno = classno;
        }
        @Override
        public String toString() {
            return "Class{" +
                    "classno='" + classno + '\'' +
                    ", department='" + department + '\'' +
                    ", monitor='" + monitor + '\'' +
                    ", classname='" + classname + '\'' +
                    ", classes=" + classes +
                    '}';
        }
    }
    
    package pers.zhb.domain;
    public class Student {
        private Integer studentno;
        private String sname;
        private String sex;
        private String birthday;
        private String classno;
        private Float point;
        private String phone;
        private String email;
        private Clas aClas;
        public Student(){//無參的構造方法
        }
        public Clas getaClas() {
            return aClas;
        }
    
        public void setaClas(Clas aClas) {
            this.aClas = aClas;
        }
        @Override
        public String toString() {
            return "Student{" +
                    "studentno='" + studentno + '\'' +
                    ", sname='" + sname + '\'' +
                    ", sex='" + sex + '\'' +
                    ", birthday='" + birthday + '\'' +
                    ", classno='" + classno + '\'' +
                    ", point=" + point +
                    ", phone='" + phone + '\'' +
                    ", email='" + email + '\'' +
                    '}';
        }
    
        public int getStudentno() {
            return studentno;
        }
    
        public void setStudentno(int studentno) {
            this.studentno = studentno;
        }
    
        public String getSname() {
            return sname;
        }
    
        public void setSname(String sname) {
            this.sname = sname;
        }
    
        public String getSex() {
            return sex;
        }
    
        public void setSex(String sex) {
            this.sex = sex;
        }
    
        public String getBirthday() {
            return birthday;
        }
    
        public void setBirthday(String birthday) {
            this.birthday = birthday;
        }
    
        public String getClassno() {
            return classno;
        }
    
        public void setClassno(String classno) {
            this.classno = classno;
        }
    
        public float getPoint() {
            return point;
        }
    
        public void setPoint(float point) {
            this.point = point;
        }
    
        public String getPhone() {
            return phone;
        }
    
        public void setPhone(String phone) {
            this.phone = phone;
        }
    
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    }
    

      

      

    (2)定義學生和班級的關係:

     

     

     

    package pers.zhb.domain;
    import java.util.HashSet;
    import java.util.Set;
    public class Clas {
        private String classno;
        private String department;
        private String monitor;
        private String classname;
        private Set<Student> students=new HashSet<Student>();//使用set集合表達一對多關係
        public Clas(){
        }
        public Set<Student> getStudents() {
            return students;
        }
        public void setClasses(Set<Student> students) {
            this.students = students;
        }
        public String getMonitor() {
            return monitor;
        }
    
        public void setMonitor(String monitor) {
            this.monitor = monitor;
        }
    
        public String getDepartment() {
            return department;
        }
    
        public void setDepartment(String department) {
            this.department = department;
        }
    
    
    
        public String getClassname() {
            return classname;
        }
    
        public void setClassname(String classname) {
            this.classname = classname;
        }
        public String getClassno() {
            return classno;
        }
    
        public void setClassno(String classno) {
            this.classno = classno;
        }
        @Override
        public String toString() {
            return "Class{" +
                    "classno='" + classno + '\'' +
                    ", department='" + department + '\'' +
                    ", monitor='" + monitor + '\'' +
                    ", classname='" + classname + '\'' +
                    ",students=" + students +
                    '}';
        }
    }
    

      

    3、配置映射文件:

      Class.hbm.xml:

    (1)實現一對多的關係映射,即:一個班級對應多個學生:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE hibernate-mapping PUBLIC
            "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
            "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
    <hibernate-mapping package="pers.zhb.domain">
        <class name="Clas" table="class">
            <id name="classno" column="classno">
                <generator class="native"></generator>
            </id><!--主鍵-->
            <property name="department" column="department"></property>
            <property name="monitor" column="monitor"></property>
            <property name="classname" column="classname"></property>
            <set name="students" table="student"><!--一對多關係配置-->
            <key column="classno" update="false"></key><!--指定了集合表的外鍵-->
                <one-to-many class="Student"></one-to-many>
            </set>
        </class>
    </hibernate-mapping>

     

    <set name="students">

    指定映射的存儲學生的集合的名字。

    <key column="classesno"></key>

    映射的class表的外鍵。

    <one-to-many class="Student"></one-to-many>

    指定學生的類型。

    (2)實現多對一的關係映射,即:多個學生對應一個班級。

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE hibernate-mapping PUBLIC
            "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
            "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
    <hibernate-mapping package="pers.zhb.domain">
        <class name="Student" table="student">
            <id name="studentno" column="studentno" >
                <generator class="native"></generator>
            </id>
            <property name="birthday" column="birthday"></property>
            <property name="classno" column="classno" insert="false" update="false"></property>
            <property name="email" column="email"></property>
            <property name="phone" column="phone"></property>
            <property name="sex" column="sex"></property>
            <property name="sname" column="sname"></property>
            <property name="point" column="point"></property>
            <many-to-one name="aClas" column="classno" class="Clas"></many-to-one>
        </class>
    </hibernate-mapping>

     

    name屬性:映射的班級。

    column屬性:映射的班級對象對應的外鍵。

    class屬性:指定班級的類型。

    4、主配置文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE hibernate-configuration PUBLIC
            "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
            "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
    <hibernate-configuration>
        <!--配置數據庫信息-必須的-->
        <session-factory>
            <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
            <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/stu_mangement</property>
            <property name="hibernate.connection.username">root</property>
            <property name="hibernate.connection.password">root</property>
            <!--配置hibernate信息-可選的-->
            <property name="hibernate.show_sql">true</property><!--輸出底層sql語句-->
            <property name="hibernate.format_sql">true</property><!--格式化輸出sql語句-->
            <property name="hibernate.hbm2ddl.auto">update</property><!--hibernate幫助創建表,如果已經有表更新表,如果沒有則創建新表-->
            <property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
            <property name="hibernate.connection.isolation">4</property>
            <!--指定session與當前線程綁定-->
            <property name="hibernate.current_session_context_class">thread</property>
            <!--配置數據庫的方言,讓hibernate識別框架自己的特有語句-->
            <!--把映射文件放到核心配置文件-->
            <mapping resource="pers/zhb/domain/Student.hbm.xml"/><!--都在src目錄下-->
            <mapping resource="pers/zhb/domain/Class.hbm.xml"/><!--都在src目錄下-->
        </session-factory>
    </hibernate-configuration>
    

     二、具體運用:

    1、增加:

    (1)創建一個新班級併為新班級添加兩名學生:

    public class Test {
        public static void testSel() {
                Session session = HibernateUtils.openSession();//獲得session
                Transaction transaction = session.beginTransaction();//開啟事務
                Clas clas=new Clas();
                clas.setClassname("計科171");
                clas.setClassno(4600);
                clas.setDepartment("一號樓");
                clas.setMonitor("zhai");
    
                Student student=new Student();
                student.setSname("");
                student.setStudentno(2017151411);
                student.setPoint(123f);
                student.setSex("");
                student.setBirthday("2019-11-11");
                student.setPhone("18739496522");
                student.setClassno("221221");
                student.setEmail("34288334@qq.com");
    
                Student student1=new Student();
                student1.setSname("翟hb");
                student1.setStudentno(2017151419);
                student1.setPoint(666f);
                student1.setSex("");
                student1.setBirthday("2019-11-11");
                student1.setPhone("18739496522");
                student1.setClassno("221221");
                student1.setEmail("34288334@qq.com");
    
                clas.getStudents().add(student);//一對多,一個班級下有多個學生
                clas.getStudents().add(student1);//獲取Set集合對象並向其中添加元素
    
                student.setaClas(clas);//多對一,學生屬於哪一個班級
                student1.setaClas(clas);
    
                session.save(clas);
                session.save(student);
                session.save(student1);
    
                transaction.commit();//提交事務
                session.close();//關閉資源
            }

     

     (2)為一個已經存在的班級添加學生:

     public static void testAdd(){
                Session session = HibernateUtils.openSession();//獲得session
                Transaction transaction = session.beginTransaction();//開啟事務
                Clas clas=session.get(Clas.class,80501);//獲得一個已經存在的班級
                Student student=new Student();//創建一個學生對象
                student.setSname("翟zz");
                student.setStudentno(20190000);
                student.setPoint(133f);
                student.setSex("男");
                student.setBirthday("2019-11-16");
                student.setPhone("18739496522");
                student.setEmail("34288334@qq.com");
    
                Student student1=new Student();//再創建一個學生對象
                student1.setSname("翟zz");
                student1.setStudentno(20190000);
                student1.setPoint(133f);
                student1.setSex("男");
                student1.setBirthday("2019-11-16");
                student1.setPhone("18739496522");
                student1.setEmail("34288334@qq.com");
    
                clas.getStudents().add(student);//學生添加到班級
                student.setaClas(clas);//班級與學生對應
                clas.getStudents().add(student1);
                student1.setaClas(clas);
    
                session.save(student);
                session.save(student1);
    
    
                transaction.commit();//提交事務
                session.close();//關閉資源
    
            }
    

      

     

     2、刪除:

    刪除80501班的一名學生信息:

     public static void testDel() {
               Session session = HibernateUtils.openSession();//獲得session
               Transaction transaction = session.beginTransaction();//開啟事務
               Clas clas=session.get(Clas.class,80501);//獲得要刪除的學生屬於那一個班級
               Student student=session.get(Student.class,937221532);//獲得要刪除的學生
               clas.getStudents().remove(student);
               student.setaClas(null);
               transaction.commit();//提交事務
               session.close();//關閉資源
           }
    

     

     

     

     

     

     

     

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

    ※別再煩惱如何寫文案,掌握八大原則!

  • python機器學習——隨機梯度下降

    python機器學習——隨機梯度下降

    上一篇我們實現了使用梯度下降法的自適應線性神經元,這個方法會使用所有的訓練樣本來對權重向量進行更新,也可以稱之為批量梯度下降(batch gradient descent)。假設現在我們數據集中擁有大量的樣本,比如百萬條樣本,那麼如果我們現在使用批量梯度下降來訓練模型,每更新一次權重向量,我們都要使用百萬條樣本,訓練時間很長,效率很低,我們能不能找到一種方法,既能使用梯度下降法,但是又不要每次更新權重都要使用到所有的樣本,於是隨機梯度下降法(stochastic gradient descent)便被提出來了。

    隨機梯度下降法可以只用一個訓練樣本來對權重向量進行更新:
    \[ \eta(y^i-\phi(z^i))x^i \]
    這種方法比批量梯度下降法收斂的更快,因為它可以更加頻繁的更新權重向量,並且使用當個樣本來更新權重,相比於使用全部的樣本來更新更具有隨機性,有助於算法避免陷入到局部最小值,使用這個方法的要注意在選取樣本進行更新時一定要隨機選取,每次迭代前都要打亂所有的樣本順序,保證訓練的隨機性,並且在訓練時的學習率也不是固定不變的,可以隨着迭代次數的增加,學習率逐漸減小,這種方法可以有助於算法收斂。

    現在我們有了使用全部樣本的批量梯度下降法,也有了使用單個樣本的隨機梯度下降法,那麼一種折中的方法,稱為最小批學習(mini-batch learning),它每次使用一部分訓練樣本來更新權重向量。

    接下來我們實現使用隨機梯度下降法的Adaline

    from numpy.random import seed
    class AdalineSGD(object):
        """ADAptive LInear NEuron classifier.
    
        Parameters
        ----------
        eta:float
            Learning rate(between 0.0 and 1.0
        n_iter:int
            Passes over the training dataset.
    
        Attributes
        ----------
        w_: 1d-array
            weights after fitting.
        errors_: list
            Number of miscalssifications in every epoch.
        shuffle:bool(default: True)
            Shuffle training data every epoch
            if True to prevent cycles.
        random_state: int(default: None)
            Set random state for shuffling
            and initalizing the weights.
    
        """
    
        def __init__(self, eta=0.01, n_iter=10, shuffle=True, random_state=None):
            self.eta = eta
            self.n_iter = n_iter
            self.w_initialized = False
            self.shuffle = shuffle
            if random_state:
                seed(random_state)
    
        def fit(self, X, y):
            """Fit training data.
    
            :param X:{array-like}, shape=[n_samples, n_features]
            :param y: array-like, shape=[n_samples]
            :return:
            self:object
    
            """
    
            self._initialize_weights(X.shape[1])
            self.cost_ = []
    
            for i in range(self.n_iter):
                if self.shuffle:
                    X, y = self._shuffle(X, y)
                cost = []
                for xi, target in zip(X, y):
                    cost.append(self._update_weights(xi, target))
                avg_cost = sum(cost)/len(y)
                self.cost_.append(avg_cost)
            return self
        
        def partial_fit(self, X, y):
            """Fit training data without reinitializing the weights."""
            if not self.w_initialized:
                self._initialize_weights(X.shape[1])
            if y.ravel().shape[0] > 1:
                for xi, target in zip(X, y):
                    self._update_weights(xi, target)
            else:
                self._update_weights(X, y)
            return self
        
        def _shuffle(self, X, y):
            """Shuffle training data"""
            r = np.random.permutation(len(y))
            return X[r], y[r]
        
        def _initialize_weights(self, m):
            """Initialize weights to zeros"""
            self.w_ = np.zeros(1 + m)
            self.w_initialized = True
        
        def _update_weights(self, xi, target):
            """Apply Adaline learning rule to update the weights"""
            output = self.net_input(xi)
            error = (target - output)
            self.w_[1:] += self.eta * xi.dot(error)
            self.w_[0] += self.eta * error
            cost = 0.5 * error ** 2
            return cost
        
        def net_input(self, X):
            """Calculate net input"""
            return np.dot(X, self.w_[1:]) + self.w_[0]
        
        def activation(self, X):
            """Computer linear activation"""
            return self.net_input(X)
        
        def predict(self, X):
            """Return class label after unit step"""
            return np.where(self.activation(X) >= 0.0, 1, -1)
    

    其中_shuffle方法中,調用numpy.random中的permutation函數得到0-100的一個隨機序列,然後這個序列作為特徵矩陣和類別向量的下標,就可以起到打亂樣本順序的功能。

    現在開始訓練

    ada = AdalineSGD(n_iter=15, eta=0.01, random_state=1)
    ada.fit(X_std, y)

    畫出分界圖和訓練曲線圖

    plot_decision_region(X_std, y, classifier=ada)
    plt.title('Adaline - Stochastic Gradient Desent')
    plt.xlabel('sepal length [standardized]')
    plt.ylabel('petal length [standardized]')
    plt.legend(loc = 'upper left')
    plt.show()
    plt.plot(range(1, len(ada.cost_) + 1), ada.cost_, marker='o')
    plt.xlabel('Epochs')
    plt.ylabel('Average Cost')
    plt.show()

    從上圖可以看出,平均損失下降很快,在大概第15次迭代后,分界線和使用批量梯度下降的Adaline分界線很類似。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※幫你省時又省力,新北清潔一流服務好口碑