從品質啟程:啟辰全新70系邀你見證“航天品質”

無論是航天製造,還是汽車製造,都離不開富有工匠精神的“螺絲釘”,他們貢獻者微小卻關鍵的力量,在打造“高品質”的道路上不息探索。啟辰T70是東風日產啟辰品牌傾力打造的首款SUV車型,在上市之初贏得了不錯的市場表現,啟辰T70基於雷諾日產C平台打造,和老款奇駿,逍客是出自同一平台,在9月8號啟辰70系列迎來了它的新款車型,新款啟辰T70,T70X外觀方面,啟辰T70採用家族式前臉,大量平直線條的運用,讓整個車身看起來更加動感時尚,新款啞光飾條的前雙輻的進氣格柵,熏黑處理的大燈,使得整體外觀看上去更加精緻,車燈

11月14日,由東風日產啟辰品牌與鳳凰網聯合主辦、以“從品質啟程”為主題的航天品質沙龍在北京舉行。航空航天領域的專家與啟辰品牌代表、工程師共同探討了中國航天的革新之路,以及航天品質背後精益求精的匠心精神。繼日前神舟十一號載人飛船成功發射之後,此次航天技術與汽車工業的跨界對話,讓“高大上”的航天工程再次走進大眾的視野,為各個領域的製造者實現人類更高品質的未來生活帶來啟發與思考。

自2011年起,東風日產就成立了以啟辰領銜的品質聯盟。2013年,啟辰發起了品質透明行動,通過百媒見證、萬里測試、千人證言共同見證了啟辰的高品質。可以說,啟辰的高品質已經深入人心,本次沙龍的一大主角——啟辰全新70系,正是啟辰高品質的體現,其經過了1697道質量檢驗工序的重重把關,從根本上就確保了品質的“零缺陷”。

我們可以看到,啟辰每一項技術進步的背後,都離不開人對更高品質生活的追求。此外,東風日產乘用車公司技術中心啟辰品牌開發總姚斌也與大家分享了啟辰的航天品質是如何煉成的。以啟辰T70為例,從商品企劃到整車下線、量產上市,整個過程都用工業製作的最高標準來要求自己。無論是航天製造,還是汽車製造,都離不開富有工匠精神的“螺絲釘”,他們貢獻者微小卻關鍵的力量,在打造“高品質”的道路上不息探索。

啟辰T70是東風日產啟辰品牌傾力打造的首款SUV車型,在上市之初贏得了不錯的市場表現,啟辰T70基於雷諾日產C平台打造,和老款奇駿,逍客是出自同一平台,在9月8號啟辰70系列迎來了它的新款車型,新款啟辰T70,T70X

外觀方面,啟辰T70採用家族式前臉,大量平直線條的運用,讓整個車身看起來更加動感時尚,新款啞光飾條的前雙輻的進氣格柵,熏黑處理的大燈,使得整體外觀看上去更加精緻,車燈部分的鍍鉻裝飾也提升了整車的質感。

內飾方面,採用T字型的中控布局,層次分明,線條比較簡潔,銀色裝飾條讓車廂看起來具有年輕,活力的氛圍,多媒體系統新增了流行的手機互聯功能百度Carlife,安全配置方面,360度全景影像,定速巡航,制動力分配和剎車輔助系統均有配備

啟辰T70的長寬高分別是4542mm.1786mm,1642mm,軸距為2630mm,啟辰T70X長寬高分別為458,2mm,1840mm,1644mm,軸距為2630mm,啟辰T70X增加了黑色外包圍,配置也更豐富。

動力方面,啟辰T70使用的是1.6和2.0自然吸氣發動機,1.6發動機最大功率121馬力,最大扭矩154牛米,2.0發動機最大功率144馬力,最大扭矩198牛米,兩款發動機均來自老款日產逍客,匹配手動擋和XTRONIC CVT變速箱,延續了日產品牌質量好,省油,大空間,舒適性好的優勢。

啟辰T70在外觀設計硬朗,空間方面也比較令人滿意,動力系統匹配成熟,質量可靠性和品質相比同級別車型有一定的優勢,相信新款啟辰70系列會將有一個不錯的市場表現.本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

疫情影響人類海洋活動大減 瀕危欖蠵龜在墨西哥孵化數創新高

摘錄自2020年10月29日自由時報報導

武漢肺炎(COVID-19)疫情持續影響全球,人類因防疫降低活動量,生態保育因此出現改善跡象;近期,墨西哥北部有一批瀕臨絕種的欖蠵龜(Olive ridley sea turtle)在海灘上孵化,數量創下新紀錄,普遍看法解讀為武漢肺炎期間,人類海洋活動減少的因素導致。

根據《BBC》報導,欖蠵龜在今年5月至9月間,在墨西哥一些沿海州上產卵;根據墨國索諾拉州(Sonora)原住民社區「斯里(Seri)」統計指出,已有超過2250隻小欖蠵龜在今年孵化並成功回到加利福尼亞灣中。

比較過往平均紀錄,今年較以往每年僅約500隻回歸大海的數量多出不少,判斷與武漢肺炎疫情期間,觀光客與捕魚作業大減有關;這些海洋活動降低,可以大幅降低欖蠵龜的巢被干擾的機率,使更多欖蠵龜寶寶得以順利孵化。

生物多樣性
海洋
國際新聞
墨西哥
海龜
瀕危物種

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

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

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

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※推薦台中搬家公司優質服務,可到府估價

澳研究:吃下塑膠的魚類行為更大膽活躍 生存率降低

摘錄自2020年10月28日聯合新聞網報導

澳洲最新研究指出,塑膠微粒能夠改變魚類的行為,吃下塑膠微粒的魚類,牠們傾向於變得更大膽、活躍,比起其他魚類生存率更低。

每日郵報報導,澳洲詹姆斯庫克大學(James Cook University)研究團隊在澳洲大堡礁水域捕捉幼魚,並連續四天餵食牠們除了豐年蝦(brine shrimp)以外的塑膠微粒。科學家將牠們標記後放回海裡觀察其行為,研究塑膠微粒是否對魚類造成影響。

科學家表示,他們不認為這是因為塑膠對魚類產生有毒影響,而是魚類在吃進塑膠後會更飢餓,這種「營養需求壓力」促使牠們更傾向於冒險尋找食物,而該冒險行為使牠們更容易被掠食者吃掉。

該研究領導學者麥考密克(Mark McCormick)表示,魚類行為上的改變及其對生存的影響,是因魚類吃下塑膠微粒後變得飽足但事實上卻沒有獲得營養。

海洋
污染治理
國際新聞
澳洲
塑膠微粒

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

【其他文章推薦】

網頁設計最專業,超強功能平台可客製化

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家公司費用怎麼算?

核食檢測眉角多 日政府無心 民間自發學習輻射測定

文:宋瑞文(媽媽監督核電廠聯盟特約撰述)

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

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

颶風伊塔威力增強 預計登陸中美洲地區

摘錄自2020年11月3日中央社報導

颶風伊塔(Eta)在加勒比海(Caribbean Sea)增強威力後,今(3日)預計登陸中美洲地區,宏都拉斯和尼加拉瓜等國恐面臨災難性的強風及洪水威脅。

法新社報導,美國國家颶風中心(National Hurricane Center)在午夜預警指出,這個「極度危險」颶風的風速達到每小時240公里,正往尼加拉瓜海岸前進。中美洲部分地區預計將出現暴潮、強風、暴洪及土石流等災情。

尼加拉瓜近海的密斯基多群島(Miskito Cays)當地的婦女和孩童已從村莊撤離,只剩男性留下來看顧房子,倘若情況變得更加危險,男性也會撤離避難。

加勒比海與中美洲的其他地區也可能面臨颶風伊塔的侵襲,國家颶風中心指出,牙買加、墨西哥東南部、薩爾瓦多、海地南部及開曼群島都可能出現洪患。

氣候變遷
國際新聞
中美洲
颶風

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

【其他文章推薦】

※回頭車貨運收費標準

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

降低產銷流程碳排 IKEA設二手家具店

摘錄自2020年11月10日公視新聞網報導

瑞典家具品牌IKEA,這個月開了第一間賣二手家具的示範店面,他們把回收而來的家具重新整修,希望能夠延長傢俱的使用壽命。

客戶在上千坪的店面參觀選購,看起來跟全球其他400多間IKEA店面沒有兩樣。不過在另一個角落,卻有幾位員工把舊傢俱堆放在一起,有的負責表面清潔,有的檢查各部位零件是否牢固,以及外表是否有髒污或刮痕。

標榜產品製程環保高標準的IKEA,為了在2030年之前,達成進一步降低產銷流程碳排放的目標,今年下半年展開一系列回收二手家具再降價轉賣的試辦計畫。首先是在9月間,位於英國愛丁堡格拉斯哥的分店,試辦接受消費者把以前購買,後來卻不敷所需的IKEA傢俱,賣回給業者。由於試辦相當成功,11月開始還要擴大到全英國,以及鄰近的愛爾蘭。

IKEA行銷經理夫亞爾說:「我們IKEA的目標,是(讓產銷成為)完整的循環,在2030年前達成兼顧永續跟獲利。從生產到如何做好售後服務,延長產品的使用壽命。」

氣候變遷
循環經濟
國際新聞
減碳
二手

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

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

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

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※推薦台中搬家公司優質服務,可到府估價

分解塑膠救星?科學家在垃圾場中發現吃聚氨酯的微生物

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

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

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

台中搬家公司費用怎麼算?

【Spring註解開發】組件註冊-使用@Configuration和@Bean給容器中註冊組件

寫在前面

在之前的Spring版本中,我們只能通過寫XML配置文件來定義我們的Bean,XML配置不僅繁瑣,而且很容易出錯,稍有不慎就會導致編寫的應用程序各種報錯,排查半天,發現是XML文件配置不對!另外,每個項目編寫大量的XML文件來配置Spring,也大大增加了項目維護的複雜度,往往很多個項目的Spring XML文件的配置大部分是相同的,只有很少量的配置不同,這也造成了配置文件上的冗餘。

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

Spring IOC和DI

在Spring容器的底層,最重要的功能就是IOC和DI,也就是控制反轉和依賴注入。

IOC:控制反轉,將類的對象的創建交給Spring類管理創建。
DI:依賴注入,將類裏面的屬性在創建類的過程中給屬性賦值。
DI和IOC的關係:DI不能單獨存在,DI需要在IOC的基礎上來完成。

在Spring內部,所有的組件都會放到IOC容器中,組件之間的關係通過IOC容器來自動裝配,也就是我們所說的依賴注入。接下來,我們就使用註解的方式來完成容器組件的註冊、管理及依賴、注入等功能。

在介紹使用註解完成容器組件的註冊、管理及依賴、注入等功能之前,我們先來看看使用XML文件是如何注入Bean的。

通過XML文件注入JavaBean

首先,我們在工程的io.mykit.spring.bean包下創建Person類,作為測試的JavaBean,代碼如下所示。

package io.mykit.spring.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;

/**
 * @author binghe
 * @version 1.0.0
 * @description 測試實體類
 */
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Person implements Serializable {
    private static final long serialVersionUID = 7387479910468805194L;
    private String name;
    private Integer age;
}

接下來,我們在工程的resources目錄下創建Spring的配置文件beans.xml,通過beans.xml文件將Person類注入到Spring的IOC容器中,配置如下所示。

<?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.xsd">

    <bean id = "person" class="io.mykit.spring.bean.Person">
        <property name="name" value="binghe"></property>
        <property name="age" value="18"></property>
    </bean>
</beans>

到此,我們使用XML方式注入JavaBean就配置完成了。接下來,我們創建一個SpringBeanTest類來進行測試,這裏,我使用的是Junit進行測試,測試方法如下所示。

@Test
public void testXmlConfig(){
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    Person person = (Person) context.getBean("person");
    System.out.println(person);
}

運行testXmlConfig()方法,輸出的結果信息如下。

Person(name=binghe, age=18)

從輸出結果中,我們可以看出,Person類通過beans.xml文件的配置,已經注入到Spring的IOC容器中了。

通過註解注入JavaBean

通過XML文件,我們可以將JavaBean注入到Spring的IOC容器中。那使用註解又該如何實現呢?別急,其實使用註解比使用XML文件要簡單的多,我們在項目的io.mykit.spring.plugins.register.config包下創建PersonConfig類,並在PersonConfig類上添加@Configuration註解來標註PersonConfig類是一個Spring的配置類,通過@Bean註解將Person類注入到Spring的IOC容器中。

package io.mykit.spring.plugins.register.config;

import io.mykit.spring.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author binghe
 * @version 1.0.0
 * @description 以註解的形式來配置Person
 */
@Configuration
public class PersonConfig {
     @Bean
    public Person person(){
        return new Person("binghe001", 18);
    }
}

沒錯,通過PersonConfig類我們就能夠將Person類注入到Spring的IOC容器中,是不是很Nice!!主要我們在類上加上@Configuration註解,並在方法上加上@Bean註解,就能夠將方法中創建的JavaBean注入到Spring的IOC容器中。

接下來,我們在SpringBeanTest類中創建一個testAnnotationConfig()方法來測試通過註解注入的Person類,如下所示。

@Test
public void testAnnotationConfig(){
    ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig.class);
    Person person = context.getBean(Person.class);
    System.out.println(person);
}

運行testAnnotationConfig()方法,輸出的結果信息如下所示。

Person(name=binghe001, age=18)

可以看出,通過註解將Person類注入到了Spring的IOC容器中。

到這裏,我們已經明確,通過XML文件和註解兩種方式都可以將JavaBean注入到Spring的IOC容器中。那麼,使用註解將JavaBean注入到IOC容器中時,使用的bean的名稱是什麼呢? 我們可以在testAnnotationConfig()方法中添加如下代碼來獲取Person類型下的註解名稱。

//按照類型找到對應的bean名稱數組
String[] names = context.getBeanNamesForType(Person.class);
Arrays.stream(names).forEach(System.out::println);

完整的testAnnotationConfig()方法的代碼如下所示。

@Test
public void testAnnotationConfig(){
    ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig.class);
    Person person = context.getBean(Person.class);
    System.out.println(person);

    //按照類型找到對應的bean名稱數組
    String[] names = context.getBeanNamesForType(Person.class);
    Arrays.stream(names).forEach(System.out::println);
}

運行testAnnotationConfig()方法輸出的結果信息如下所示。

Person(name=binghe001, age=18)
person

那這裏的person是啥?我們修改下PersonConfig類中的person()方法,將person()方法修改成person01()方法,如下所示。

package io.mykit.spring.plugins.register.config;

import io.mykit.spring.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author binghe
 * @version 1.0.0
 * @description 以註解的形式來配置Person
 */
@Configuration
public class PersonConfig {

    @Bean
    public Person person01(){
        return new Person("binghe001", 18);
    }
}

此時,我們再次運行testAnnotationConfig()方法,輸出的結果信息如下所示。

Person(name=binghe001, age=18)
person01

看到這裏,大家應該有種豁然開朗的感覺了,沒錯!!使用註解注入Javabean時,bean在IOC中的名稱就是使用@Bean註解標註的方法名稱。我們可不可以為bean單獨指定名稱呢?那必須可以啊!只要在@Bean註解中明確指定名稱就可以了。比如下面的PersonConfig類的代碼,我們將person01()方法上的@Bean註解修改成@Bean(“person”)註解,如下所示。

package io.mykit.spring.plugins.register.config;

import io.mykit.spring.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author binghe
 * @version 1.0.0
 * @description 以註解的形式來配置Person
 */
@Configuration
public class PersonConfig {

    @Bean("person")
    public Person person01(){
        return new Person("binghe001", 18);
    }
}

此時,我們再次運行testAnnotationConfig()方法,輸出的結果信息如下所示。

Person(name=binghe001, age=18)
person

可以看到,此時,輸出的JavaBean的名稱為person。

結論:我們在使用註解方式向Spring的IOC容器中注入JavaBean時,如果沒有在@Bean註解中明確指定bean的名稱,就使用當前方法的名稱來作為bean的名稱;如果在@Bean註解中明確指定了bean的名稱,則使用@Bean註解中指定的名稱來作為bean的名稱。

好了,咱們今天就聊到這兒吧!別忘了給個在看和轉發,讓更多的人看到,一起學習一起進步!!

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

寫在最後

如果覺得文章對你有點幫助,請微信搜索並關注「 冰河技術 」微信公眾號,跟冰河學習Spring註解驅動開發。公眾號回復“spring註解”關鍵字,領取Spring註解驅動開發核心知識圖,讓Spring註解驅動開發不再迷茫。

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

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

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

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※推薦台中搬家公司優質服務,可到府估價

“造輪運動”之 ORM框架系列(三)~ 乾貨呈上

   這一趴裏面,我就來正式介紹一下CoffeeSQL的乾貨。

    首先要給CoffeeSQL來個定位:最開始就是由於本人想要了解ORM框架內部的原理,所以就四處搜尋有關的博客與學習資料,就是在那個夏天,在博客園上看到了一位7tiny老哥的博客(https://www.cnblogs.com/7tiny/p/9575230.html),裏面基本上包含了我所想要了解的全套內容。幸得7tiny老哥的博客和代碼都寫的非常清晰,所以沒花多久時間就看完了源碼並洞悉其中奧妙,於是自己就有個想法:在7tiny的開源代碼的基礎上歸納自己的ORM框架。於是出於學習與自我使用的目的就開始了擴展功能的道路,到現在為止,自己已經在公司的一個項目中用上了,效果還不錯。在這裏也感謝7tiny老哥對我提出的一些問題及時的回復和指導,真心感謝。

一、框架模塊介紹

  根據CoffeeSQL的功能模塊組成來劃分,可以分為:數據庫連接管理、SQL命令執行入口、SQL命令生成器、SQL查詢引擎、ORM緩存機制、實體數據驗證 這六個部分,CoffeeSQL的操作入口與其他的ORM框架一樣,都是以數據庫上下文(DBContext)的方式進行操作。整體結構圖如下:

 

下面就大致地介紹一下每一個模塊的具體功能與實現的思路:

1、數據庫連接管理(DBConnectionManagement)

   數據庫連接的管理實際上就是對數據庫連接字符串與其對應的數據庫連接對象的管理機制,它可以保證在進行一主多從的數據庫部署時ORM幫助我們自動地切換連接的數據庫,而且還支持 <最小使用>與 <輪詢>兩種數據庫連接切換策略。

 

2、SQL命令執行入口(QueryExecute)

   QueryExecute是CoffeeSQL生成的所有sql語句執行的入口,執行sql語句並返回結果,貫穿整個CoffeeSQL最核心的功能就是映射sql查詢結果到實體,這裏採用的是構建表達式樹的技術,性能大大優於反射獲取實體的方式,具體的兩者速度對比的實驗在7tiny的博客中有詳細介紹,大家可以移步觀看(https://www.cnblogs.com/7tiny/p/9861166.html),在我的博客(https://www.cnblogs.com/MaMaNongNong/p/12173620.html)中我使用表達式樹的技術造了個簡練版的OOM框架。

   這裏貼出核心代碼,方便查看:

   

  1     /// <summary>
  2     /// Auto Fill Adapter
  3     /// => Fill DataRow to Entity
  4     /// </summary>
  5     public class EntityFillAdapter<Entity>
  6     {
  7         private static readonly Func<DataRow, Entity> funcCache = GetFactory();
  8 
  9         public static Entity AutoFill(DataRow row)
 10         {
 11             return funcCache(row);
 12         }
 13 
 14         private static Func<DataRow, Entity> GetFactory()
 15         {
 16             #region get Info through Reflection
 17             var entityType = typeof(Entity);
 18             var rowType = typeof(DataRow);
 19             var convertType = typeof(Convert);
 20             var typeType = typeof(Type);
 21             var columnCollectionType = typeof(DataColumnCollection);
 22             var getTypeMethod = typeType.GetMethod("GetType", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string) }, null);
 23             var changeTypeMethod = convertType.GetMethod("ChangeType", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(object), typeof(Type) }, null);
 24             var containsMethod = columnCollectionType.GetMethod("Contains");
 25             var rowIndexerGetMethod = rowType.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(string) }, new[] { new ParameterModifier(1) });
 26             var columnCollectionIndexerGetMethod = columnCollectionType.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(int) }, new[] { new ParameterModifier(1) });
 27             var entityIndexerSetMethod = entityType.GetMethod("set_Item", BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(string), typeof(object) }, null);
 28             var properties = entityType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
 29             #endregion
 30 
 31             #region some Expression class that can be repeat used
 32             //DataRow row
 33             var rowDeclare = Expression.Parameter(rowType, "row");
 34             //Student entity
 35             var entityDeclare = Expression.Parameter(entityType, "entity");
 36             //Type propertyType
 37             var propertyTypeDeclare = Expression.Parameter(typeof(Type), "propertyType");
 38             //new Student()
 39             var newEntityExpression = Expression.New(entityType);
 40             //row == null
 41             var rowEqualnullExpression = Expression.Equal(rowDeclare, Expression.Constant(null));
 42             //row.Table.Columns
 43             var rowTableColumns = Expression.Property(Expression.Property(rowDeclare, "Table"), "Columns");
 44             //int loopIndex
 45             var loopIndexDeclare = Expression.Parameter(typeof(int), "loopIndex");
 46             //row.Table.Columns[loopIndex].ColumnName
 47             var columnNameExpression = Expression.Property(Expression.Call(rowTableColumns, columnCollectionIndexerGetMethod, loopIndexDeclare), "ColumnName");
 48             //break;
 49             LabelTarget labelBreak = Expression.Label();
 50             //default(Student)
 51             var defaultEntityValue = Expression.Default(entityType);
 52             #endregion
 53 
 54             var setRowNotNullBlockExpressions = new List<Expression>();
 55                         
 56             #region entity = new Student();loopIndex = 0;
 57             setRowNotNullBlockExpressions.Add(Expression.Assign(entityDeclare, newEntityExpression));
 58             setRowNotNullBlockExpressions.Add(Expression.Assign(loopIndexDeclare, Expression.Constant(0)));
 59 
 60             #endregion
 61 
 62             #region loop Fill DataRow's field to Entity Indexer
 63             /*
 64              * while (true)
 65              * {
 66              *     if (loopIndex < row.Table.Columns.Count)
 67              *     {
 68              *         entity[row.Table.Columns[loopIndex].ColumnName] = row[row.Table.Columns[loopIndex].ColumnName];
 69              *         loopIndex++;
 70              *     }
 71              *     else break;
 72              * } 
 73              */
 74 
 75             setRowNotNullBlockExpressions.Add(
 76 
 77                 Expression.Loop(
 78                     Expression.IfThenElse(
 79                         Expression.LessThan(loopIndexDeclare, Expression.Property(rowTableColumns, "Count")),
 80                         Expression.Block(
 81                             Expression.Call(entityDeclare, entityIndexerSetMethod, columnNameExpression, Expression.Call(rowDeclare, rowIndexerGetMethod, columnNameExpression)),
 82                             Expression.PostIncrementAssign(loopIndexDeclare)
 83                         ),
 84                         Expression.Break(labelBreak)
 85                     ),
 86                     labelBreak
 87                 )
 88             );
 89             #endregion
 90 
 91             #region assign for Entity property
 92             foreach (var propertyInfo in properties)
 93             {
 94                 var columnAttr = propertyInfo.GetCustomAttribute(typeof(ColumnAttribute), true) as ColumnAttribute;
 95 
 96                 // no column , no translation
 97                 if (null == columnAttr) continue;
 98 
 99                 if (propertyInfo.CanWrite)
100                 {
101                     var columnName = Expression.Constant(columnAttr.GetName(propertyInfo.Name), typeof(string));
102 
103                     //entity.Id
104                     var propertyExpression = Expression.Property(entityDeclare, propertyInfo);
105                     //row["Id"]
106                     var value = Expression.Call(rowDeclare, rowIndexerGetMethod, columnName);
107                     //default(string)
108                     var defaultValue = Expression.Default(propertyInfo.PropertyType);
109                     //row.Table.Columns.Contains("Id")
110                     var checkIfContainsColumn = Expression.Call(rowTableColumns, containsMethod, columnName);
111                     //!row["Id"].Equals(DBNull.Value)
112                     var checkDBNull = Expression.NotEqual(value, Expression.Constant(System.DBNull.Value));
113                     
114                     var propertyTypeName = Expression.Constant(propertyInfo.PropertyType.ToString(), typeof(string));
115 
116                     /*
117                      * if (row.Table.Columns.Contains("Id") && !row["Id"].Equals(DBNull.Value))
118                      * {
119                      *     propertyType = Type.GetType("System.String");
120                      *     entity.Id = (string)Convert.ChangeType(row["Id"], propertyType);
121                      * }
122                      * else
123                      *     entity.Id = default(string);
124                      */
125                     setRowNotNullBlockExpressions.Add(
126 
127                         Expression.IfThenElse(
128                             Expression.AndAlso(checkIfContainsColumn, checkDBNull),
129                             Expression.Block(
130                                 Expression.Assign(propertyTypeDeclare, Expression.Call(getTypeMethod, propertyTypeName)),
131                                 Expression.Assign(propertyExpression, Expression.Convert(Expression.Call(changeTypeMethod, value, propertyTypeDeclare), propertyInfo.PropertyType))
132                             ),
133                             Expression.Assign(propertyExpression, defaultValue)
134                         )
135                     );
136                 }
137             }
138 
139             #endregion
140 
141             var checkIfRowIsNull = Expression.IfThenElse(
142                 rowEqualnullExpression,
143                 Expression.Assign(entityDeclare, defaultEntityValue),
144                 Expression.Block(setRowNotNullBlockExpressions)
145             );
146 
147             var body = Expression.Block(
148 
149                 new[] { entityDeclare, loopIndexDeclare, propertyTypeDeclare },
150                 checkIfRowIsNull,
151                 entityDeclare   //return Student;
152             );
153 
154             return Expression.Lambda<Func<DataRow, Entity>>(body, rowDeclare).Compile();
155         }
156     }
157 
158     #region
159     //public class Student : EntityDesign.EntityBase
160     //{
161     //    [Column]
162     //    public string Id { get; set; }
163 
164     //    [Column("StudentName")]
165     //    public string Name { get; set; }
166     //}
167     ////this is the template of "GetFactory()" created.
168     //public static Student StudentFillAdapter(DataRow row)
169     //{
170     //    Student entity;
171     //    int loopIndex;
172     //    Type propertyType;
173 
174     //    if (row == null)
175     //        entity = default(Student);
176     //    else
177     //    {
178     //        entity = new Student();
179     //        loopIndex = 0;
180 
181     //        while (true)
182     //        {
183     //            if (loopIndex < row.Table.Columns.Count)
184     //            {
185     //                entity[row.Table.Columns[loopIndex].ColumnName] = row[row.Table.Columns[loopIndex].ColumnName];
186     //                loopIndex++;
187     //            }
188     //            else break;
189     //        }
190 
191     //        if (row.Table.Columns.Contains("Id") && !row["Id"].Equals(DBNull.Value))
192     //        {
193     //            propertyType = Type.GetType("System.String");
194     //            entity.Id = (string)Convert.ChangeType(row["Id"], propertyType);
195     //        }
196     //        else
197     //            entity.Id = default(string);
198 
199     //        if (row.Table.Columns.Contains("StudentName") && !row["StudentName"].Equals(DBNull.Value))
200     //        {
201     //            propertyType = Type.GetType("System.String");
202     //            entity.Name = (string)Convert.ChangeType(row["StudentName"], propertyType);
203     //        }
204     //        else
205     //            entity.Name = default(string);
206     //    }
207 
208     //    return entity;
209     //}
210     #endregion

EntityFillAdapter(表達式樹技術)

 

3、SQL查詢引擎(QueryEngine)

  SQL查詢引擎的功能主要就是以函數的形式來構建查詢SQL的結構。將sql語句使用高級語言的函數來進行構建能大大減輕程序員必須一絲不苟編寫sql語句的壓力。特別是在使用強類型查詢引擎時以Lambda表達式的方式編寫程序,相當舒適的體驗;對於稍微複雜的sql,建議使用弱類型查詢引擎來構建sql查詢語句,同時也提供方便的分頁功能,用法與Dapper類似;再複雜一點的數據庫查詢邏輯可能你就要考慮使用存儲過程查詢引擎了,總之,有了這三個查詢引擎,所有的查詢需求都能滿足了。最後一個是update的執行引擎,它被用來構建update的語句。

 

4、實體數據驗證(EntityValidation)

  實體數據驗證是完全獨立的一部分,主要用來檢驗實體類中字段值的合法性,相當於在高級語言層面對即將持久化到數據庫表中的數據進行預先的字段合法性校驗,避免在持久化過程中發生不必要的字段格式不合法的錯誤。

 

5、ORM緩存機制(ORMCache)

  這裏的ORM緩存主要分為兩級緩存,一級緩存為以sql語句為緩存鍵的緩存,緩存的內容就是當前執行的sql語句的執行結果;而二級緩存則是以表名為緩存鍵的表緩存,就是會把一整個表的數據全部存入緩存中,所以表緩存最適合那些數據量不大且查詢頻繁的表

 

6、SQL命令生成器【強類型】(CommandTextGenerator)

  在使用諸如強類型查詢引擎、Update執行引擎等進行了強類型的SQL語句構造后,相應的sql構造信息都要通過SQL命令生成器來生成最終可由數據庫執行的sql語句。SQL命令生成器扮演的就是類似於翻譯官的角色,將高級語言中的語句轉化為數據庫中的sql語句。在實際的應用場景中還可以根據不同的數據庫類型將SQL命令生成器擴展成諸如Mysql-SQL命令生成器或者Oracle-SQL命令生成器以符合不同類型數據庫的不同sql語法。

 

7、數據庫上下文(DBContext)

  作為整個CoffeeSQL的操作入口,DBContext類涵蓋了各種配置參数字段與增刪改查的API調用函數。其中在事務處理中,由於寫操作都是通過對主庫的操作,所以在事務處理中是以主庫作為事務處理的對象。

二、使用方式

  下載CoffeeSql源碼進行編譯,你會得到 CoffeeSql.Core.dll、CoffeeSql.Oracle.dll、CoffeeSql.Mysql.dll 三個dll文件,其中CoffeeSql.Core.dll為必選,然後根據你的數據庫類型選擇是CoffeeSql.Oracle.dll或者CoffeeSql.Mysql.dll,目前還只支持這兩種數據庫,後續會支持更多數據庫。

 

 

三、展望

  路漫漫其修遠兮,吾將上下而求索,對比市面上火熱的ORM框架,CoffeeSQL還是缺少了一些實用的功能,對這個ORM框架的展望中我會考慮以下一些功能:

    1、CodeFirst、DbFirst功能的支持,可以快捷方便地進行實體類與數據庫建表sql的生成;

    2、批量插入操作的實現,可以提高批量插入數據的性能;

    3、對多表聯合查詢的lambda語法支持;

  

  介紹的再多都不如讀一遍源碼來的實在,有想深入了解orm原理的小夥伴可以閱讀一下源碼,真的SO EASY!

   源碼地址:https://gitee.com/xiaosen123/CoffeeSqlORM

   本文為作者原創,轉載請註明出處:https://www.cnblogs.com/MaMaNongNong/p/12896787.html

 

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

【其他文章推薦】

網頁設計最專業,超強功能平台可客製化

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家公司費用怎麼算?

Alink漫談(六) : TF-IDF算法的實現

Alink漫談(六) : TF-IDF算法的實現

目錄

  • Alink漫談(六) : TF-IDF算法的實現
    • 0x00 摘要
    • 0x01 TF-IDF
      • 1.1 原理
      • 1.2 計算方法
    • 0x02 Alink示例代碼
      • 2.1 示例代碼
      • 2.2 TF-IDF模型
      • 2.3 TF-IDF預測
    • 0x03 分詞 Segment
      • 3.1 結巴分詞
      • 3.2 分詞過程
    • 0x04 訓練
      • 4.1 計算IDF
      • 4.2 排序
        • 4.2.1 SortUtils.pSort
          • 採樣SampleSplitPoint
          • 歸併 SplitPointReducer
          • SplitData把真實數據IDF插入
          • reduceGroup計算同類型單詞數目
        • 4.2.2 localSort
      • 4.3 過濾
    • 0x05 生成模型
      • 5.1 DocCountVectorizerModelData
      • 5.2 BuildDocCountModel
    • 0x06 預測
    • 0x07 參考

0x00 摘要

Alink 是阿里巴巴基於實時計算引擎 Flink 研發的新一代機器學習算法平台,是業界首個同時支持批式算法、流式算法的機器學習平台。TF-IDF(term frequency–inverse document frequency)是一種用於信息檢索與數據挖掘的常用加權技術。本文將為大家展現Alink如何實現TF-IDF。

0x01 TF-IDF

TF-IDF(term frequency–inverse document frequency)是一種統計方法,一種用於信息檢索與數據挖掘的常用加權技術。

TF是詞頻(Term Frequency),IDF是逆文本頻率指數(Inverse Document Frequency)。

為什麼要用TF-IDF?因為計算機只能識別数字,對於一個一個的單詞,計算機是看不懂的,更別說是一句話,或是一篇文章。而TF-IDF就是用來將文本轉換成計算機看得懂的語言,或者說是機器學習或深度學習模型能夠進行學習訓練的數據集

1.1 原理

TF-IDF用以評估一個詞對於一個文件集或一個語料庫中的其中一份文件的重要程度。字詞的重要性隨着它在文件中出現的次數成正比增加,但同時會隨着它在語料庫中出現的頻率成反比下降。

TF-IDF的主要思想是:如果某個詞或短語在一篇文章中出現的頻率TF高,並且在其他文章中很少出現,則認為此詞或者短語具有很好的類別區分能力,適合用來分類。

TF-IDF實際上是:TF * IDF,TF詞頻(Term Frequency),IDF逆向文件頻率(Inverse Document Frequency)。

詞頻(term frequency,TF)指的是某一個給定的詞語在該文件中出現的頻率。這個数字是對詞數(term count)的歸一化,以防止它偏向長的文件(同一個詞語在長文件里可能會比短文件有更高的詞數,而不管該詞語重要與否)。

IDF逆向文件頻率 (inverse document frequency, IDF)反應了一個詞在所有文本(整個文檔)中出現的頻率,如果一個詞在很多的文本中出現,那麼它的IDF值應該低。而反過來如果一個詞在比較少的文本中出現,那麼它的IDF值應該高。比如一些專業的名詞如“Machine Learning”。這樣的詞IDF值應該高。一個極端的情況,如果一個詞在所有的文本中都出現,那麼它的IDF值應該為0。

如果單單以TF或者IDF來計算一個詞的重要程度都是片面的,因此TF-IDF綜合了TF和IDF兩者的優點,用以評估一字詞對於一個文件集或一個語料庫中的其中一份文件的重要程度。字詞的重要性隨着它在文件中出現的次數成正比增加,但同時會隨着它在語料庫中出現的頻率成反比下降。上述引用總結就是:一個詞語在一篇文章中出現次數越多, 同時在所有文檔中出現次數越少, 越能夠代表該文章,越能與其它文章區分開來。

1.2 計算方法

TF的計算公式如下:

\[TF_w = \frac {N_w}{N} \]

其中 N_w 是在某一文本中詞條w出現的次數,N 是該文本總詞條數。

IDF的計算公式如下:

\[IDF_w = log(\frac {Y}{Y_w + 1}) \]

其中 Y 是語料庫的文檔總數,Y_w 是包含詞條w的文檔數,分母加一是為了避免w 未出現在任何文檔中從而導致分母為0 的情況。

TF-IDF 就是將TF和IDF相乘 :

\[TF-IDF_w = TF_w * IDF_w \]

從以上計算公式便可以看出,某一特定文件內的高詞語頻率,以及該詞語在整個文件集合中的低文件頻率,可以產生出高權重的TF-IDF。因此,TF-IDF傾向於過濾掉常見的詞語,保留重要的詞語。

0x02 Alink示例代碼

2.1 示例代碼

首先我們給出示例代碼,下文是通過一些語料來訓練出一個模型,然後用這個模型來做預測:

public class DocCountVectorizerExample {

    AlgoOperator getData(boolean isBatch) {
        Row[] rows = new Row[]{
                Row.of(0, "二手舊書:醫學電磁成像"),
                Row.of(1, "二手美國文學選讀( 下冊 )李宜燮南開大學出版社 9787310003969"),
                Row.of(2, "二手正版圖解象棋入門/謝恩思主編/華齡出版社"),
                Row.of(3, "二手中國糖尿病文獻索引"),
                Row.of(4, "二手郁達夫文集( 國內版 )全十二冊館藏書")
        };

        String[] schema = new String[]{"id", "text"};

        if (isBatch) {
            return new MemSourceBatchOp(rows, schema);
        } else {
            return new MemSourceStreamOp(rows, schema);
        }
    }

    public static void main(String[] args) throws Exception {
        DocCountVectorizerExample test = new DocCountVectorizerExample();
        BatchOperator batchData = (BatchOperator) test.getData(true);

         // 分詞
        SegmentBatchOp segment = new SegmentBatchOp() 
                                                .setSelectedCol("text")
                                                .linkFrom(batchData);
        // TF-IDF訓練
        DocCountVectorizerTrainBatchOp model = new DocCountVectorizerTrainBatchOp()
                                                .setSelectedCol("text")
                                                .linkFrom(segment);
        // TF-IDF預測
        DocCountVectorizerPredictBatchOp predictBatch = new 
            																		DocCountVectorizerPredictBatchOp()
                                                .setSelectedCol("text")
                                                .linkFrom(model, segment);
        model.print();
        predictBatch.print();
    }
}

2.2 TF-IDF模型

TF-IDF模型打印出來如下:

model_id|model_info
--------|----------
0|{"minTF":"1.0","featureType":"\"WORD_COUNT\""}
1048576|{"f0":"二手","f1":0.0,"f2":0}
2097152|{"f0":"/","f1":1.0986122886681098,"f2":1}
3145728|{"f0":"出版社","f1":0.6931471805599453,"f2":2}
4194304|{"f0":")","f1":0.6931471805599453,"f2":3}
5242880|{"f0":"(","f1":0.6931471805599453,"f2":4}
6291456|{"f0":"入門","f1":1.0986122886681098,"f2":5}
......
36700160|{"f0":"美國","f1":1.0986122886681098,"f2":34}
37748736|{"f0":"謝恩","f1":1.0986122886681098,"f2":35}
38797312|{"f0":"象棋","f1":1.0986122886681098,"f2":36}

2.3 TF-IDF預測

TF-IDF預測結果如下:

id|text
--|----
0|$37$0:1.0 6:1.0 10:1.0 25:1.0 26:1.0 28:1.0
1|$37$0:1.0 1:1.0 2:1.0 4:1.0 11:1.0 15:1.0 16:1.0 19:1.0 20:1.0 32:1.0 34:1.0
2|$37$0:1.0 3:2.0 4:1.0 5:1.0 8:1.0 22:1.0 23:1.0 24:1.0 29:1.0 35:1.0 36:1.0
3|$37$0:1.0 12:1.0 27:1.0 31:1.0 33:1.0
4|$37$0:1.0 1:1.0 2:1.0 7:1.0 9:1.0 13:1.0 14:1.0 17:1.0 18:1.0 21:1.0 30:1.0

0x03 分詞 Segment

中文分詞(Chinese Word Segmentation) 指的是將一個漢字序列切分成一個一個單獨的詞。分詞就是將連續的字序列按照一定的規範重新組合成詞序列的過程。

示例代碼中,分詞部分如下:

    SegmentBatchOp segment = new SegmentBatchOp() 
                                            .setSelectedCol("text")
                                            .linkFrom(batchData);

分詞主要是如下兩個類,其作用就是把中文文檔分割成單詞。

public final class SegmentBatchOp extends MapBatchOp <SegmentBatchOp>
	implements SegmentParams <SegmentBatchOp> {

	public SegmentBatchOp(Params params) {
		super(SegmentMapper::new, params);
	}
}

public class SegmentMapper extends SISOMapper {
	private JiebaSegmenter segmentor;
}

3.1 結巴分詞

有經驗的同學看到這裏就會露出微笑:結巴分詞。

jieba分詞是國內使用人數最多的中文分詞工具https://github.com/fxsjy/jieba。jieba分詞支持四種分詞模式:

  • 精確模式,試圖將句子最精確地切開,適合文本分析;
  • 全模式,把句子中所有的可以成詞的詞語都掃描出來, 速度非常快,但是不能解決歧義;
  • 搜索引擎模式,在精確模式的基礎上,對長詞再次切分,提高召回率,適合用於搜索引擎分詞。
  • paddle模式,利用PaddlePaddle深度學習框架,訓練序列標註(雙向GRU)網絡模型實現分詞。

Alink使用了com.alibaba.alink.operator.common.nlp.jiebasegment.viterbi.FinalSeg;來 完成分詞。具體是在https://github.com/huaban/jieba-analysis的基礎上稍微做了調整。

public class JiebaSegmenter implements Serializable {
    private static FinalSeg finalSeg = FinalSeg.getInstance();
    private WordDictionary wordDict;
    ......
    private Map<Integer, List<Integer>> createDAG(String sentence) 
}

從Alink代碼中看,實現了索引分詞和查詢分詞兩種模式,應該是有分詞粒度粗細之分。

createDAG函數的作用是 :在處理句子過程中,基於前綴詞典實現高效的詞圖掃描,生成句子中漢字所有可能成詞情況所構成的有向無環圖 (DAG)。

結巴分詞對於未登錄詞,採用了基於漢字成詞能力的 HMM 模型,使用了 Viterbi 算法。

3.2 分詞過程

分詞過程主要是在SegmentMapper.mapColumn函數中完成的,當輸入是 “二手舊書:醫學電磁成像”,結巴分詞將這個句子分成了六個單詞。具體參見如下:

input = "二手舊書:醫學電磁成像"
tokens = {ArrayList@9619}  size = 6
 0 = {SegToken@9630} "[二手, 0, 2]"
 1 = {SegToken@9631} "[舊書, 2, 4]"
 2 = {SegToken@9632} "[:, 4, 5]"
 3 = {SegToken@9633} "[醫學, 5, 7]"
 4 = {SegToken@9634} "[電磁, 7, 9]"
 5 = {SegToken@9635} "[成像, 9, 11]"
 
mapColumn:44, SegmentMapper (com.alibaba.alink.operator.common.nlp)
apply:-1, 35206803 (com.alibaba.alink.common.mapper.SISOMapper$$Lambda$646)
handleMap:75, SISOColsHelper (com.alibaba.alink.common.mapper)
map:52, SISOMapper (com.alibaba.alink.common.mapper)
map:21, MapperAdapter (com.alibaba.alink.common.mapper)
map:11, MapperAdapter (com.alibaba.alink.common.mapper)
collect:79, ChainedMapDriver (org.apache.flink.runtime.operators.chaining)
collect:35, CountingCollector (org.apache.flink.runtime.operators.util.metrics)
invoke:196, DataSourceTask (org.apache.flink.runtime.operators)

0x04 訓練

訓練是在DocCountVectorizerTrainBatchOp類完成的,其通過linkFrom完成了模型的構建。其實計算TF IDF相對 簡單,複雜之處在於之後的大規模排序。

public DocCountVectorizerTrainBatchOp linkFrom(BatchOperator<?>... inputs) {
        BatchOperator<?> in = checkAndGetFirst(inputs);
  
        DataSet<DocCountVectorizerModelData> resDocCountModel = generateDocCountModel(getParams(), in);

        DataSet<Row> res = resDocCountModel.mapPartition(new MapPartitionFunction<DocCountVectorizerModelData, Row>() {
            @Override
            public void mapPartition(Iterable<DocCountVectorizerModelData> modelDataList, Collector<Row> collector) {
                new DocCountVectorizerModelDataConverter().save(modelDataList.iterator().next(), collector);
            }
        });
        this.setOutput(res, new DocCountVectorizerModelDataConverter().getModelSchema());
        return this;
}

4.1 計算IDF

計算 IDF 的工作是在generateDocCountModel完成的,具體步驟如下:

第一步 通過DocWordSplitCount和UDTF的混合使用得到了文檔中的單詞數目docWordCnt

BatchOperator<?> docWordCnt = in.udtf(
        params.get(SELECTED_COL),
        new String[] {WORD_COL_NAME, DOC_WORD_COUNT_COL_NAME},
        new DocWordSplitCount(NLPConstant.WORD_DELIMITER),
        new String[] {});

DocWordSplitCount.eval的輸入是已經分詞的句子,然後按照空格分詞,按照單詞計數。其結果是:

map = {HashMap@9816}  size = 6
 "醫學" -> {Long@9833} 1
 "電磁" -> {Long@9833} 1
 ":" -> {Long@9833} 1
 "成像" -> {Long@9833} 1
 "舊書" -> {Long@9833} 1
 "二手" -> {Long@9833} 1

第二步 得到了文檔數目docCnt

BatchOperator docCnt = in.select("COUNT(1) AS " + DOC_COUNT_COL_NAME);

這個數目會廣播出去 .withBroadcastSet(docCnt.getDataSet(), "docCnt");,後面的CalcIdf會繼續使用,進行 行數統計。

第三步 會通過CalcIdf計算出每一個單詞的DF和IDF

open時候會獲取docCnt。然後reduce會計算IDF,具體計算如下:

double idf = Math.log((1.0 + docCnt) / (1.0 + df));
collector.collect(Row.of(featureName, -wordCount, idf));

具體得到如下

df = 1.0
wordCount = 1.0
featureName = "中國"
idf = 1.0986122886681098
docCnt = 5

這裏一個重點是:返回值中,是 -wordCount,因為單詞越多權重越小,為了比較所以取負

4.2 排序

得到所有單詞的IDF之後,就得到了一個IDF字典,這時候需要對字典按照權重進行排序。排序具體分為兩步。

4.2.1 SortUtils.pSort

第一步是SortUtils.pSort,大規模并行抽樣排序。

Tuple2<DataSet<Tuple2<Integer, Row>>, DataSet<Tuple2<Integer, Long>>> partitioned = SortUtils.pSort(sortInput, 1);

這步非常複雜,Alink參考了論文,如果有興趣的兄弟可以深入了解下。

* reference: Yang, X. (2014). Chong gou da shu ju tong ji (1st ed., pp. 25-29).
* Note: This algorithm is improved on the base of the parallel sorting by regular sampling(PSRS).

pSort返回值是:

* @return f0: dataset which is indexed by partition id, f1: dataset which has partition id and count.

pSort中又分如下幾步

採樣SampleSplitPoint

SortUtils.SampleSplitPoint.mapPartition這裏完成了採樣。

DataSet <Tuple2 <Object, Integer>> splitPoints = input
   .mapPartition(new SampleSplitPoint(index))
   .reduceGroup(new SplitPointReducer());

這裏的輸入row就是上文IDF的返回數值。

用allValues記錄了本task目前處理的句子有多少個單詞。

用splitPoints做了採樣。如何選擇呢,通過genSampleIndex函數。

public static Long genSampleIndex(Long splitPointIdx, Long count, Long splitPointSize) {
   splitPointIdx++;
   splitPointSize++;

   Long div = count / splitPointSize;
   Long mod = count % splitPointSize;

   return div * splitPointIdx + ((mod > splitPointIdx) ? splitPointIdx : mod) - 1;
}

後續操作也使用同樣的genSampleIndex函數來做選擇,這樣保證在操作所有序列上可以選取同樣的採樣點。

allValues = {ArrayList@10264}  size = 8  //本task有多少單詞
 0 = {Double@10266} -2.0
 1 = {Double@10271} -1.0
 2 = {Double@10272} -1.0
 3 = {Double@10273} -1.0
 4 = {Double@10274} -1.0
 5 = {Double@10275} -1.0
 6 = {Double@10276} -1.0
 7 = {Double@10277} -1.0
 
splitPoints = {ArrayList@10265}  size = 7 //採樣了7個
 0 = {Double@10266} -2.0
 1 = {Double@10271} -1.0
 2 = {Double@10272} -1.0
 3 = {Double@10273} -1.0
 4 = {Double@10274} -1.0
 5 = {Double@10275} -1.0
 6 = {Double@10276} -1.0

最後返回採樣數據,返回時候附帶當前taskIDnew Tuple2 <Object, Integer>(obj,taskId)

這裡有一個trick點

  for (Object obj : splitPoints) {
     Tuple2 <Object, Integer> cur
        = new Tuple2 <Object, Integer>(
        obj,
        taskId); //這裏返回的是類似 (-5.0,2) :其中2就是task id,-5.0是-wordcount。
     out.collect(cur);
  }

  out.collect(new Tuple2(
     getRuntimeContext().getNumberOfParallelSubtasks(),
     -taskId - 1));//這裏返回的是一個特殊元素,類似(4,-2) :其中4是本應用中并行task數目,-2是當前-taskId - 1。這個task數目後續就會用到。

具體數據參見如下:

row = {Row@10211} "中國,-1.0,1.0986122886681098"
 fields = {Object[3]@10214} 
 
cur = {Tuple2@10286} "(-5.0,2)" // 返回採樣數據,返回時候附帶當前taskID
 f0 = {Double@10285} -5.0 // -wordcount。
 f1 = {Integer@10300} 2 // 當前taskID
歸併 SplitPointReducer

歸併所有task生成的sample。然後再次sample,把sample數據組成一個數據塊,這個數據塊選擇的原則是:每個task都盡量選擇若干sample

這裏其實是有一個轉換,就是從正常單詞的抽樣 轉換到 某一類單詞的抽樣,這某一類的意思舉例是:出現次數為一,或者出現次數為五 這種單詞

這裏all是所有採樣數據,其中一個元素內容舉例 (-5.0,2) :其中2就是task id,-5.0是-wordcount。

這裏用 Collections.sort(all, new PairComparator()); 來對所有採樣數據做排序。排序基準是首先對 -wordcount,然後對task ID。

SplitPointReducer的返回採樣數值就作為廣播變量存儲起來:.withBroadcastSet(splitPoints, "splitPoints");

這裏的trick點是:

for (Tuple2 <Object, Integer> value : values) {
   if (value.f1 < 0) { 
      instanceCount = (int) value.f0;  // 特殊數據,類似(4,-2) :其中4是本應用中task數目,這個就是後續選擇哪些taskid的基準
      continue;
   }
   all.add(new Tuple2 <>(value.f0, value.f1)); // (-5.0,2) 正常數據
}

選擇sample index splitPoints.add(allValues.get(index));也使用了同樣的genSampleIndex。

計算中具體數據如下:

for (int i = 0; i < splitPointSize; ++i) {
		int index = genSampleIndex(
					Long.valueOf(i),
					Long.valueOf(count),
					Long.valueOf(splitPointSize))
					.intValue();
		spliters.add(all.get(index));
}
for (Tuple2 <Object, Integer> spliter : spliters) {
		out.collect(spliter);
}

count = 33
all = {ArrayList@10245}  size = 33 // 所有採樣數據,
0 = {Tuple2@10256} "(-5.0,2)"// 2就是task id,-5.0是-wordcount。
1 = {Tuple2@10285} "(-2.0,0)"
......
6 = {Tuple2@10239} "(-1.0,0)"
7 = {Tuple2@10240} "(-1.0,0)"
8 = {Tuple2@10241} "(-1.0,0)"
9 = {Tuple2@10242} "(-1.0,0)"
10 = {Tuple2@10243} "(-1.0,0)"
11 = {Tuple2@10244} "(-1.0,1)"
......
16 = {Tuple2@10278} "(-1.0,1)"
......
24 = {Tuple2@10279} "(-1.0,2)"
......
32 = {Tuple2@10313} "(-1.0,3)"
  
// spliters是返回結果,這裏分別選取了all中index為8,16,24這個三個record。每個task都選擇了一個元素。
spliters = {HashSet@10246}  size = 3
 0 = {Tuple2@10249} "(-1.0,0)" // task 0 被選擇。就是說,這裏從task 0中選擇了一個count是1的元素,具體選擇哪個單詞其實不重要,就是為了選擇count是1的這種即可。
 1 = {Tuple2@10250} "(-1.0,1)" // task 1 被選擇。具體同上。
 2 = {Tuple2@10251} "(-1.0,2)" // task 2 被選擇。具體同上。
SplitData把真實數據IDF插入

use binary search to partition data into sorted subsets。前面函數給出的是詞的count,但是沒有IDF。這裏將用二分法查找 找到IDF,然後把IDF插入到partition data中。

首先要注意一點:splitData的輸入就是原始輸入input, 和splitPoints的輸入是一樣 的

DataSet <Tuple2 <Integer, Row>> splitData = input
   .mapPartition(new SplitData(index))
   .withBroadcastSet(splitPoints, "splitPoints");

open函數中會取出廣播變量 splitPoints。

splitPoints = {ArrayList@10248}  size = 3
 0 = {Tuple2@10257} "(-1.0,0)"
 1 = {Tuple2@10258} "(-1.0,1)"
 2 = {Tuple2@10259} "(-1.0,2)"

本函數的輸入舉例

row = {Row@10232} "入門,-1.0,1.0986122886681098"

會在splitPoints中二分法查找,得到splits中每一個 sample 對應的真實IDF。然後發送出去。

這裏需要特殊說明下,這個二分法查找查找的是IDF數值,比如count為1的這種單詞對應的IDF數值,可能很多單詞都是count為1,所以找到一個這樣單詞的IDF即可

splitPoints = {ArrayList@10223}  size = 3
 0 = {Tuple2@10229} "(-1.0,0)"
 1 = {Tuple2@10230} "(-1.0,1)"
 2 = {Tuple2@10231} "(-1.0,2)"
curTuple.f0 = {Double@10224} -1.0
  
int bsIndex = Collections.binarySearch(splitPoints, curTuple, new PairComparator());

		int curIndex;
		if (bsIndex >= 0) {
			curIndex = bsIndex;
		} else {
			curIndex = -bsIndex - 1;
		}

// 假設單詞是 "入門",則發送的是 "入門" 這類單詞在本partition的index,和 "入門" 的單詞本身
// 其實,從調試過程看,是否發送單詞信息本身並不重要,因為接下來的那一步操作中,並沒有用到單詞本身信息
out.collect(new Tuple2 <>(curIndex, row)); 
reduceGroup計算同類型單詞數目

這裡是計算在某一partition中,某一種類單詞的數目。比如count為1的單詞,這種單詞總共有多少個

後續會把new Tuple2 <>(id, count)作為partitionCnt廣播變量存起來。

id就是這類單詞在這partition中間的index,我們暫時稱之為partition index。count就是這類單詞在本partition的數目。

// 輸入舉例
value = {Tuple2@10312} "(0,入門,-1.0,1.0986122886681098)"
 f0 = {Integer@10313} 0
 
// 計算數目
for (Tuple2 <Integer, Row> value : values) {
		id = value.f0;
		count++;
}

out.collect(new Tuple2 <>(id, count));  
  
// 輸出舉例,假如是序號為0的這類單詞,其總體數目是12。這個序號0就是這類單詞在某一partition中的序號。就是上面的 curIndex。
id = {Integer@10313} 0
count = {Long@10338} 12

4.2.2 localSort

第二步是localSort。Sort a partitioned dataset. 最終排序並且會返回最終數值,比如 (29, “主編,-1.0,1.0986122886681098″), 29就是”主編” 這個單詞在 IDF字典中的序號。

DataSet<Tuple2<Long, Row>> ordered = localSort(partitioned.f0, partitioned.f1, 1);

open函數中會獲取partitionCnt。然後計算出某一種類單詞,其在本partition之前所有partition中,這類單詞數目。

public void open(Configuration parameters) throws Exception {
		List <Tuple2 <Integer, Long>> bc = getRuntimeContext().getBroadcastVariable("partitionCnt");
		startIdx = 0L;
		int taskId = getRuntimeContext().getIndexOfThisSubtask();
		for (Tuple2 <Integer, Long> pcnt : bc) {
			if (pcnt.f0 < taskId) {
					startIdx += pcnt.f1;
			}
		}
}

bc = {ArrayList@10303}  size = 4
 0 = {Tuple2@10309} "(0,12)"  // 就是task0裏面,這種單詞有12個
 1 = {Tuple2@10310} "(2,9)"// 就是task1裏面,這種單詞有2個
 2 = {Tuple2@10311} "(1,7)"// 就是task2裏面,這種單詞有1個
 3 = {Tuple2@10312} "(3,9)"// 就是task3裏面,這種單詞有3個
// 如果本task id是4,則其startIdx為30。就是所有partition之中,它前面index所有單詞的和。  

然後進行排序。Collections.sort(valuesList, new RowComparator(field));

valuesList = {ArrayList@10405}  size = 9
 0 = {Row@10421} ":,-1.0,1.0986122886681098"
 1 = {Row@10422} "主編,-1.0,1.0986122886681098"
 2 = {Row@10423} "國內,-1.0,1.0986122886681098"
 3 = {Row@10424} "文獻,-1.0,1.0986122886681098"
 4 = {Row@10425} "李宜燮,-1.0,1.0986122886681098"
 5 = {Row@10426} "糖尿病,-1.0,1.0986122886681098"
 6 = {Row@10427} "美國,-1.0,1.0986122886681098"
 7 = {Row@10428} "謝恩,-1.0,1.0986122886681098"
 8 = {Row@10429} "象棋,-1.0,1.0986122886681098"
  
  
// 最後返回時候,就是  (29, "主編,-1.0,1.0986122886681098"),29就是“主編”這個單詞在最終字典中的序號。
// 這個序號是startIdx + cnt,startIdx是某一種類單詞,其在本partition之前所有partition中,這類單詞數目。比如在本partition之前,這類單詞有28個,則本partition中,從29開始計數。就是最終序列號
	for (Row row : valuesList) {
		out.collect(Tuple2.of(startIdx + cnt, row));
		cnt++; // 這裏就是在某一類單詞中,單調遞增,然後賦值一個字典序列而已
	}  
cnt = 1
row = {Row@10336} "主編,-1.0,1.0986122886681098"
 fields = {Object[3]@10339} 
startIdx = 28

4.3 過濾

最後還要進行過濾,如果文字個數超出了字典大小,就拋棄多餘文字。

ordered.filter(new FilterFunction<Tuple2<Long, Row>>() {
    @Override
    public boolean filter(Tuple2<Long, Row> value) {
        return value.f0 < vocabSize;
    }
})

0x05 生成模型

具體生成模型代碼如下。

DataSet<DocCountVectorizerModelData> resDocCountModel = ordered.filter(new FilterFunction<Tuple2<Long, Row>>() {
    @Override
    public boolean filter(Tuple2<Long, Row> value) {
        return value.f0 < vocabSize;
    }
}).mapPartition(new BuildDocCountModel(params)).setParallelism(1);
return resDocCountModel;

其中關鍵類是 DocCountVectorizerModelData 和 BuildDocCountModel。

5.1 DocCountVectorizerModelData

這是向量信息。

/**
 * Save the data for DocHashIDFVectorizer.
 *
 * Save a HashMap: index(MurMurHash3 value of the word), value(Inverse document frequency of the word).
 */
public class DocCountVectorizerModelData {
    public List<String> list;
    public String featureType;
    public double minTF;
}

5.2 BuildDocCountModel

最終生成的模型信息如下,這個也就是之前樣例代碼給出的輸出。

modelData = {DocCountVectorizerModelData@10411} 
 list = {ArrayList@10409}  size = 37
  0 = "{"f0":"9787310003969","f1":1.0986122886681098,"f2":19}"
  1 = "{"f0":"下冊","f1":1.0986122886681098,"f2":20}"
  2 = "{"f0":"全","f1":1.0986122886681098,"f2":21}"
  3 = "{"f0":"華齡","f1":1.0986122886681098,"f2":22}"
  4 = "{"f0":"圖解","f1":1.0986122886681098,"f2":23}"
  5 = "{"f0":"思","f1":1.0986122886681098,"f2":24}"
  6 = "{"f0":"成像","f1":1.0986122886681098,"f2":25}"
  7 = "{"f0":"舊書","f1":1.0986122886681098,"f2":26}"
  8 = "{"f0":"索引","f1":1.0986122886681098,"f2":27}"
  9 = "{"f0":":","f1":1.0986122886681098,"f2":28}"
  10 = "{"f0":"主編","f1":1.0986122886681098,"f2":29}"
  11 = "{"f0":"國內","f1":1.0986122886681098,"f2":30}"
  12 = "{"f0":"文獻","f1":1.0986122886681098,"f2":31}"
  13 = "{"f0":"李宜燮","f1":1.0986122886681098,"f2":32}"
  14 = "{"f0":"糖尿病","f1":1.0986122886681098,"f2":33}"
  15 = "{"f0":"美國","f1":1.0986122886681098,"f2":34}"
  16 = "{"f0":"謝恩","f1":1.0986122886681098,"f2":35}"
  17 = "{"f0":"象棋","f1":1.0986122886681098,"f2":36}"
  18 = "{"f0":"二手","f1":0.0,"f2":0}"
  19 = "{"f0":")","f1":0.6931471805599453,"f2":1}"
  20 = "{"f0":"/","f1":1.0986122886681098,"f2":2}"
  21 = "{"f0":"出版社","f1":0.6931471805599453,"f2":3}"
  22 = "{"f0":"(","f1":0.6931471805599453,"f2":4}"
  23 = "{"f0":"入門","f1":1.0986122886681098,"f2":5}"
  24 = "{"f0":"醫學","f1":1.0986122886681098,"f2":6}"
  25 = "{"f0":"文集","f1":1.0986122886681098,"f2":7}"
  26 = "{"f0":"正版","f1":1.0986122886681098,"f2":8}"
  27 = "{"f0":"版","f1":1.0986122886681098,"f2":9}"
  28 = "{"f0":"電磁","f1":1.0986122886681098,"f2":10}"
  29 = "{"f0":"選讀","f1":1.0986122886681098,"f2":11}"
  30 = "{"f0":"中國","f1":1.0986122886681098,"f2":12}"
  31 = "{"f0":"書","f1":1.0986122886681098,"f2":13}"
  32 = "{"f0":"十二冊","f1":1.0986122886681098,"f2":14}"
  33 = "{"f0":"南開大學","f1":1.0986122886681098,"f2":15}"
  34 = "{"f0":"文學","f1":1.0986122886681098,"f2":16}"
  35 = "{"f0":"郁達夫","f1":1.0986122886681098,"f2":17}"
  36 = "{"f0":"館藏","f1":1.0986122886681098,"f2":18}"
 featureType = "WORD_COUNT"
 minTF = 1.0

0x06 預測

預測業務邏輯是DocCountVectorizerModelMapper

首先我們可以看到 FeatureType,這個可以用來配置輸出哪種信息。比如可以輸出以下若干種:

public enum FeatureType implements Serializable {
    /**
     * IDF type, the output value is inverse document frequency.
     */
    IDF(
        (idf, termFrequency, tokenRatio) -> idf
    ),
    /**
     * WORD_COUNT type, the output value is the word count.
     */
    WORD_COUNT(
        (idf, termFrequency, tokenRatio) -> termFrequency
    ),
    /**
     * TF_IDF type, the output value is term frequency * inverse document frequency.
     */
    TF_IDF(
        (idf, termFrequency, tokenRatio) -> idf * termFrequency * tokenRatio
    ),
    /**
     * BINARY type, the output value is 1.0.
     */
    BINARY(
        (idf, termFrequency, tokenRatio) -> 1.0
    ),
    /**
     * TF type, the output value is term frequency.
     */
    TF(
        (idf, termFrequency, tokenRatio) -> termFrequency * tokenRatio
    );
}

其次,在open函數中,會加載模型,比如:

wordIdWeight = {HashMap@10838}  size = 37
 "醫學" -> {Tuple2@10954} "(6,1.0986122886681098)"
 "選讀" -> {Tuple2@10956} "(11,1.0986122886681098)"
 "十二冊" -> {Tuple2@10958} "(14,1.0986122886681098)"
...
 "華齡" -> {Tuple2@11022} "(22,1.0986122886681098)"
 "索引" -> {Tuple2@11024} "(27,1.0986122886681098)"
featureType = {DocCountVectorizerModelMapper$FeatureType@10834} "WORD_COUNT"

最後,預測時候調用predictSparseVector函數,會針對輸入 二手 舊書 : 醫學 電磁 成像來進行匹配。生成稀疏向量SparseVector。

0|$37$0:1.0 6:1.0 10:1.0 25:1.0 26:1.0 28:1.0

以上表示那幾個單詞 分別對應0 6 10 25 26 28 這幾個字典中對應序號的單詞,其在本句對應的出現數目都是一個。

0x07 參考

Tf-Idf詳解及應用

https://github.com/fxsjy/jieba

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

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價