mybatis緩存之一級緩存(一)

對於mybatis框架。彷彿工作中一直是在copy着使用。對於mybatis緩存。並沒有一個準確的認知。趁着假期。學習下mybatis的緩存。這篇主要學習mybatis的一級緩存。

為什麼使用緩存

其實,大家工作久了,就知道很多瓶頸就是在數據庫上。

初識mybatis一級緩存

當然我們還是通過代碼來認識下mybatis的一級緩存

代碼演示

詳細代碼見github,這裏只展示重要的代碼片段

  1. tempMapper.xml
    <select id="getById" resultType="entity.TempEntity">
       select * from  temp where id = #{id}
    </select>
  1. TempTest
public class TempTest {

    Logger logger = Logger.getLogger(this.getClass());
    @Test
    public  void test() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = build.openSession();
        TempEntity tempEntity1 = sqlSession.selectOne("dao.TempDao.getById", 1);
        logger.info(tempEntity1);
        TempEntity tempEntity2 = sqlSession.selectOne("dao.TempDao.getById", 1);
        logger.info(tempEntity2);
        logger.info(tempEntity1 == tempEntity2);
    }
}

3.運行結果

2020-06-26 08:57:37,453 DEBUG [dao.TempDao.getById] - ==>  Preparing: select * from temp where id = ? 
2020-06-26 08:57:37,513 DEBUG [dao.TempDao.getById] - ==> Parameters: 1(Integer)
2020-06-26 08:57:37,538 DEBUG [dao.TempDao.getById] - <==      Total: 1
2020-06-26 08:57:37,538 INFO [TempTest] - TempEntity{id=1, value1='11111', value2='aaaaa'}
2020-06-26 08:57:37,538 INFO [TempTest] - TempEntity{id=1, value1='11111', value2='aaaaa'}
2020-06-26 08:57:37,538 INFO [TempTest] - true
  1. 總結

4.1 從上面的結果,我們可以看到,第二次查詢的時候,就直接沒有查詢數據庫,並且返回的是同一個對象。證明第二次走的就是緩存。
4.2 一級緩存是默認開啟的。我們並沒有在代碼中配置任何關於緩存的配置
4.3 代碼回顧

mybatis一級緩存命中原則

mybatis是怎麼樣判斷某兩次查詢是完全相同的查詢?

1.statementId

1.1 mapper.xml

    <select id="getById1" resultType="entity.TempEntity">
       select * from  temp where id = #{id}
    </select>

    <select id="getById2" resultType="entity.TempEntity">
       select * from  temp where id = #{id}
    </select>

1.2 test

    @Test
    public  void test() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = build.openSession();
        TempEntity tempEntity1 = sqlSession.selectOne("dao.Temp2Dao.getById1", 1);
        logger.info(tempEntity1);
        TempEntity tempEntity2 = sqlSession.selectOne("dao.Temp2Dao.getById2", 1);
        logger.info(tempEntity2);
        logger.info(tempEntity1 == tempEntity2);
    }

1.3 結果

2020-06-26 09:19:09,926 DEBUG [dao.Temp2Dao.getById1] - ==>  Preparing: select * from temp where id = ? 
2020-06-26 09:19:09,957 DEBUG [dao.Temp2Dao.getById1] - ==> Parameters: 1(Integer)
2020-06-26 09:19:09,969 DEBUG [dao.Temp2Dao.getById1] - <==      Total: 1
2020-06-26 09:19:09,969 INFO [TempTest] - TempEntity{id=1, value1='11111', value2='aaaaa'}
2020-06-26 09:19:09,969 DEBUG [dao.Temp2Dao.getById2] - ==>  Preparing: select * from temp where id = ? 
2020-06-26 09:19:09,970 DEBUG [dao.Temp2Dao.getById2] - ==> Parameters: 1(Integer)
2020-06-26 09:19:09,970 DEBUG [dao.Temp2Dao.getById2] - <==      Total: 1
2020-06-26 09:19:09,971 INFO [TempTest] - TempEntity{id=1, value1='11111', value2='aaaaa'}

1.4 總結
要求查詢的statementId必須完全相同,否則無法命中緩存,即時兩個查詢語句、參數完全相同

2.查詢參數

我們用不同的參數查詢,一個傳1 一個傳2
2.1 test

    @Test
    public  void testParam() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = build.openSession();
        TempEntity tempEntity1 = sqlSession.selectOne("dao.Temp2Dao.getById1", 1);
        logger.info(tempEntity1);
        TempEntity tempEntity2 = sqlSession.selectOne("dao.Temp2Dao.getById1", 2);
        logger.info(tempEntity2);
        logger.info(tempEntity1 == tempEntity2);
    }

2.2 結果

2020-06-26 09:24:33,107 DEBUG [dao.Temp2Dao.getById1] - ==>  Preparing: select * from temp where id = ? 
2020-06-26 09:24:33,148 DEBUG [dao.Temp2Dao.getById1] - ==> Parameters: 1(Integer)
2020-06-26 09:24:33,162 DEBUG [dao.Temp2Dao.getById1] - <==      Total: 1
2020-06-26 09:24:33,162 INFO [TempTest] - TempEntity{id=1, value1='11111', value2='aaaaa'}
2020-06-26 09:24:33,162 DEBUG [dao.Temp2Dao.getById1] - ==>  Preparing: select * from temp where id = ? 
2020-06-26 09:24:33,163 DEBUG [dao.Temp2Dao.getById1] - ==> Parameters: 2(Integer)
2020-06-26 09:24:33,164 DEBUG [dao.Temp2Dao.getById1] - <==      Total: 1
2020-06-26 09:24:33,164 INFO [TempTest] - TempEntity{id=2, value1='22222', value2='bbbb'}
2020-06-26 09:24:33,164 INFO [TempTest] - false


2.3 總結

要求傳遞給sql的傳遞參數相同,否則不會命中緩存

3.分頁參數

3.1 傳不同的分頁參數

    @Test
    public  void testPage() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = build.openSession();
        RowBounds rowBounds1 = new RowBounds(0,1);
        List<TempEntity> tempEntity1 = sqlSession.selectList("dao.Temp2Dao.getList", null,rowBounds1);
        logger.info(tempEntity1);
        RowBounds rowBounds2 = new RowBounds(0,2);
        List<TempEntity> tempEntity2 = sqlSession.selectList("dao.Temp2Dao.getList",null, rowBounds2);
        logger.info(tempEntity2);
        logger.info(tempEntity1 == tempEntity2);
    }

3.2 結果

2020-06-26 10:10:33,060 DEBUG [dao.Temp2Dao.getList] - ==>  Preparing: select * from temp where 1=1 
2020-06-26 10:10:33,101 DEBUG [dao.Temp2Dao.getList] - ==> Parameters: 
2020-06-26 10:10:33,116 INFO [TempTest] - [TempEntity{id=1, value1='11111', value2='aaaaa'}]
2020-06-26 10:10:33,116 DEBUG [dao.Temp2Dao.getList] - ==>  Preparing: select * from temp where 1=1 
2020-06-26 10:10:33,116 DEBUG [dao.Temp2Dao.getList] - ==> Parameters: 
2020-06-26 10:10:33,118 INFO [TempTest] - [TempEntity{id=1, value1='11111', value2='aaaaa'}, TempEntity{id=2, value1='22222', value2='bbbb'}]
2020-06-26 10:10:33,118 INFO [TempTest] - false

3.3 總結

要求分頁參數必須相同,否則無法命中緩存。緩存的粒度是整個分頁查詢結果,而不是結果中的每個對象

4. sql語句

4.1 mapper文件

    <select id="getById" resultType="entity.TempEntity">
       select * from  temp 
       <where>
           <if test="type ==1">
           id = #{id}
            </if>
           <if test="type ==2">
               1=1 and id = #{id}
           </if>
       </where> 
    </select>

這個就不測試了。
4.2 總結
要求傳遞給jdbc的sql 必須完全相同。就算是1=1 不起作用 也不行

5.環境

這裏的環境指的的是<environment id="dev"><environment id="test"> 也是會影響的

    <environments default="dev">
        <environment id="dev">
            <!--指定事務管理的類型,這裏簡單使用Java的JDBC的提交和回滾設置-->
            <transactionManager type="JDBC"></transactionManager>
            <!--dataSource 指連接源配置,POOLED是JDBC連接對象的數據源連接池的實現-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"></property>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/test"></property>
                <property name="username" value="root"></property>
                <property name="password" value="root"></property>
            </dataSource>
        </environment>
        <environment id="test">
            <!--指定事務管理的類型,這裏簡單使用Java的JDBC的提交和回滾設置-->
            <transactionManager type="JDBC"></transactionManager>
            <!--dataSource 指連接源配置,POOLED是JDBC連接對象的數據源連接池的實現-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"></property>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/test"></property>
                <property name="username" value="root"></property>
                <property name="password" value="root"></property>
            </dataSource>
        </environment>
    </environments>

總結

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

【其他文章推薦】

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

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

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

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

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

Alink漫談(八) : 二分類評估 AUC、K-S、PRC、Precision、Recall、LiftChart 如何實現

Alink漫談(八) : 二分類評估 AUC、K-S、PRC、Precision、Recall、LiftChart 如何實現

目錄

  • Alink漫談(八) : 二分類評估 AUC、K-S、PRC、Precision、Recall、LiftChart 如何實現
    • 0x00 摘要
    • 0x01 相關概念
    • 0x02 示例代碼
      • 2.1 主要思路
    • 0x03 批處理
      • 3.1 EvalBinaryClassBatchOp
      • 3.2 BaseEvalClassBatchOp
        • 3.2.0 調用關係綜述
        • 3.2.1 calLabelPredDetailLocal
          • 3.2.1.1 flatMap
          • 3.2.1.2 reduceGroup
          • 3.2.1.3 mapPartition
        • 3.2.2 ReduceBaseMetrics
        • 3.2.3 SaveDataAsParams
        • 3.2.4 計算混淆矩陣
          • 3.2.4.1 原始矩陣
          • 3.2.4.2 計算標籤
          • 3.2.4.3 具體代碼
    • 0x04 流處理
      • 4.1 示例
        • 4.1.1 主類
        • 4.1.2 TimeMemSourceStreamOp
        • 4.1.3 Source
      • 4.2 BaseEvalClassStreamOp
        • 4.2.1 PredDetailLabel
        • 4.2.2 AllDataMerge
        • 4.2.3 SaveDataStream
        • 4.2.4 Union
          • 4.2.4.1 allOutput
        • 4.2.4.2 windowOutput
    • 0xFF 參考

0x00 摘要

Alink 是阿里巴巴基於實時計算引擎 Flink 研發的新一代機器學習算法平台,是業界首個同時支持批式算法、流式算法的機器學習平台。二分類評估是對二分類算法的預測結果進行效果評估。本文將剖析Alink中對應代碼實現。

0x01 相關概念

如果對本文某些概念有疑惑,可以參見之前文章 [白話解析] 通過實例來梳理概念 :準確率 (Accuracy)、精準率(Precision)、召回率(Recall) 和 F值(F-Measure)

0x02 示例代碼

public class EvalBinaryClassExample {

    AlgoOperator getData(boolean isBatch) {
        Row[] rows = new Row[]{
                Row.of("prefix1", "{\"prefix1\": 0.9, \"prefix0\": 0.1}"),
                Row.of("prefix1", "{\"prefix1\": 0.8, \"prefix0\": 0.2}"),
                Row.of("prefix1", "{\"prefix1\": 0.7, \"prefix0\": 0.3}"),
                Row.of("prefix0", "{\"prefix1\": 0.75, \"prefix0\": 0.25}"),
                Row.of("prefix0", "{\"prefix1\": 0.6, \"prefix0\": 0.4}")
        };

        String[] schema = new String[]{"label", "detailInput"};

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

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

        BinaryClassMetrics metrics = new EvalBinaryClassBatchOp()
                .setLabelCol("label")
                .setPredictionDetailCol("detailInput")
                .linkFrom(batchData)
                .collectMetrics();

        System.out.println("RocCurve:" + metrics.getRocCurve());
        System.out.println("AUC:" + metrics.getAuc());
        System.out.println("KS:" + metrics.getKs());
        System.out.println("PRC:" + metrics.getPrc());
        System.out.println("Accuracy:" + metrics.getAccuracy());
        System.out.println("Macro Precision:" + metrics.getMacroPrecision());
        System.out.println("Micro Recall:" + metrics.getMicroRecall());
        System.out.println("Weighted Sensitivity:" + metrics.getWeightedSensitivity());
    }
}

程序輸出

RocCurve:([0.0, 0.0, 0.0, 0.5, 0.5, 1.0, 1.0],[0.0, 0.3333333333333333, 0.6666666666666666, 0.6666666666666666, 1.0, 1.0, 1.0])
AUC:0.8333333333333333
KS:0.6666666666666666
PRC:0.9027777777777777
Accuracy:0.6
Macro Precision:0.3
Micro Recall:0.6
Weighted Sensitivity:0.6

在 Alink 中,二分類評估有批處理,流處理兩種實現,下面一一為大家介紹( Alink 複雜之一在於大量精細的數據結構,所以下文會大量打印程序中變量以便大家理解)。

2.1 主要思路

  • 把 [0,1] 分成假設 100000個桶(bin)。所以得到positiveBin / negativeBin 兩個100000的數組。

  • 根據輸入給positiveBin / negativeBin賦值。positiveBin就是 TP + FP,negativeBin就是 TN + FN。這些是後續計算的基礎。

  • 遍歷bins中每一個有意義的點,計算出totalTrue和totalFalse,並且在每一個點上計算該點的混淆矩陣,tpr,以及rocCurve,recallPrecisionCurve,liftChart在該點對應的數據;

  • 依據曲線內容計算並且存儲 AUC/PRC/KS

具體後續還有詳細調用關係綜述。

0x03 批處理

3.1 EvalBinaryClassBatchOp

EvalBinaryClassBatchOp是二分類評估的實現,功能是計算二分類的評估指標(evaluation metrics)。

輸入有兩種:

  • label column and predResult column
  • label column and predDetail column。如果有predDetail,則predResult被忽略

我們例子中 "prefix1" 就是 label,"{\"prefix1\": 0.9, \"prefix0\": 0.1}" 就是 predDetail

Row.of("prefix1", "{\"prefix1\": 0.9, \"prefix0\": 0.1}")

具體類摘錄如下:

public class EvalBinaryClassBatchOp extends BaseEvalClassBatchOp<EvalBinaryClassBatchOp> implements BinaryEvaluationParams <EvalBinaryClassBatchOp>, EvaluationMetricsCollector<BinaryClassMetrics> {
  
	@Override
	public BinaryClassMetrics collectMetrics() {
		return new BinaryClassMetrics(this.collect().get(0));
	}  
}

可以看到,其主要工作都是在基類BaseEvalClassBatchOp中完成,所以我們會首先看BaseEvalClassBatchOp。

3.2 BaseEvalClassBatchOp

我們還是從 linkFrom 函數入手,其主要是做了幾件事:

  • 獲取配置信息
  • 從輸入中提取某些列:”label”,”detailInput”
  • calLabelPredDetailLocal會按照partition分別計算evaluation metrics
  • 綜合reduce上述計算結果
  • SaveDataAsParams函數會把最終數值輸入到 output table

具體代碼如下

@Override
public T linkFrom(BatchOperator<?>... inputs) {
    BatchOperator<?> in = checkAndGetFirst(inputs);
    String labelColName = this.get(MultiEvaluationParams.LABEL_COL);
    String positiveValue = this.get(BinaryEvaluationParams.POS_LABEL_VAL_STR);

    // Judge the evaluation type from params.
    ClassificationEvaluationUtil.Type type = ClassificationEvaluationUtil.judgeEvaluationType(this.getParams());

    DataSet<BaseMetricsSummary> res;
    switch (type) {
        case PRED_DETAIL: {
            String predDetailColName = this.get(MultiEvaluationParams.PREDICTION_DETAIL_COL);
            // 從輸入中提取某些列:"label","detailInput" 
            DataSet<Row> data = in.select(new String[] {labelColName, predDetailColName}).getDataSet();
            // 按照partition分別計算evaluation metrics
            res = calLabelPredDetailLocal(data, positiveValue, binary);
            break;
        }
        ......
    }

    // 綜合reduce上述計算結果
    DataSet<BaseMetricsSummary> metrics = res
        .reduce(new EvaluationUtil.ReduceBaseMetrics());

    // 把最終數值輸入到 output table
    this.setOutput(metrics.flatMap(new EvaluationUtil.SaveDataAsParams()),
        new String[] {DATA_OUTPUT}, new TypeInformation[] {Types.STRING});

    return (T)this;
}

// 執行中一些變量如下
labelColName = "label"
predDetailColName = "detailInput"  
type = {ClassificationEvaluationUtil$Type@2532} "PRED_DETAIL"
binary = true
positiveValue = null  

3.2.0 調用關係綜述

因為後續代碼調用關係複雜,所以先給出一個調用關係

  • 從輸入中提取某些列:”label”,”detailInput”,in.select(new String[] {labelColName, predDetailColName}).getDataSet()。因為可能輸入還有其他列,而只有某些列是我們計算需要的,所以只提取這些列。
  • 按照partition分別計算evaluation metrics,即調用 calLabelPredDetailLocal(data, positiveValue, binary);
    • flatMap會從label列和prediction列中,取出所有labels(注意是取出labels的名字 ),發送給下游算子。
    • reduceGroup主要功能是通過 buildLabelIndexLabelArray 去重 “labels名字”,然後給每一個label一個ID,得到一個 <labels, ID>的map,最後返回是二元組(map, labels),即({prefix1=0, prefix0=1},[prefix1, prefix0])。從後文看,<labels, ID>Map看來是多分類才用到。二分類只用到了labels。
    • mapPartition 分區調用 CalLabelDetailLocal 來計算混淆矩陣,主要是分區調用getDetailStatistics,前文中得到的二元組(map, labels)會作為參數傳遞進來 。
      • getDetailStatistics 遍歷 rows 數據,提取每一個item(比如 “prefix1,{“prefix1”: 0.8, “prefix0”: 0.2}”),然後通過updateBinaryMetricsSummary累積計算混淆矩陣所需數據。
        • updateBinaryMetricsSummary 把 [0,1] 分成假設 100000個桶(bin)。所以得到positiveBin / negativeBin 兩個100000的數組。positiveBin就是 TP + FP,negativeBin就是 TN + FN。
          • 如果某個 sample 為 正例 (positive value) 的概率是 p, 則該 sample 對應的 bin index 就是 p * 100000。如果 p 被預測為正例 (positive value) ,則positiveBin[index]++,
          • 否則就是被預測為負例(negative value) ,則negativeBin[index]++。
  • 綜合reduce上述計算結果,metrics = res.reduce(new EvaluationUtil.ReduceBaseMetrics());
    • 具體計算是在BinaryMetricsSummary.merge,其作用就是Merge the bins, and add the logLoss。
  • 把最終數值輸入到 output table,setOutput(metrics.flatMap(new EvaluationUtil.SaveDataAsParams()..);
    • 歸併所有BaseMetrics后,得到total BaseMetrics,計算indexes存入params。collector.collect(t.toMetrics().serialize());
      • 實際業務在BinaryMetricsSummary.toMetrics,即基於bin的信息計算,然後存儲到params。
        • extractMatrixThreCurve函數取出非空的bins,據此計算出ConfusionMatrix array(混淆矩陣), threshold array, rocCurve/recallPrecisionCurve/LiftChart.
          • 遍歷bins中每一個有意義的點,計算出totalTrue和totalFalse,並且在每一個點上計算:
          • curTrue += positiveBin[index]; curFalse += negativeBin[index];
          • 得到該點的混淆矩陣 new ConfusionMatrix(new long[][] {{curTrue, curFalse}, {totalTrue – curTrue, totalFalse – curFalse}});
          • 得到 tpr = (totalTrue == 0 ? 1.0 : 1.0 * curTrue / totalTrue);
          • rocCurve,recallPrecisionCurve,liftChart在該點對應的數據;
        • 依據曲線內容計算並且存儲 AUC/PRC/KS
        • 對生成的rocCurve/recallPrecisionCurve/LiftChart輸出進行抽樣
        • 依據抽樣后的輸出存儲 RocCurve/RecallPrecisionCurve/LiftChar
        • 存儲正例樣本的度量指標
        • 存儲Logloss
        • Pick the middle point where threshold is 0.5.

3.2.1 calLabelPredDetailLocal

本函數按照partition分別計算評估指標 evaluation metrics。是的,這代碼很短,但是有個地方需要注意。有時候越簡單的地方越容易疏漏。容易疏漏點是:

第一行代碼的結果 labels 是第二行代碼的參數,而並非第二行主體。第二行代碼主體和第一行代碼主體一樣,都是data。

private static DataSet<BaseMetricsSummary> calLabelPredDetailLocal(DataSet<Row> data, final String positiveValue, oolean binary) {
  
    DataSet<Tuple2<Map<String, Integer>, String[]>> labels = data.flatMap(new FlatMapFunction<Row, String>() {
        @Override
        public void flatMap(Row row, Collector<String> collector) {
            TreeMap<String, Double> labelProbMap;
            if (EvaluationUtil.checkRowFieldNotNull(row)) {
                labelProbMap = EvaluationUtil.extractLabelProbMap(row);
                labelProbMap.keySet().forEach(collector::collect);
                collector.collect(row.getField(0).toString());
            }
        }
    }).reduceGroup(new EvaluationUtil.DistinctLabelIndexMap(binary, positiveValue));

    return data
        .rebalance()
        .mapPartition(new CalLabelDetailLocal(binary))
        .withBroadcastSet(labels, LABELS);
}

calLabelPredDetailLocal中具體分為三步驟:

  • 在flatMap會從label列和prediction列中,取出所有labels(注意是取出labels的名字 ),發送給下游算子。
  • reduceGroup的主要功能是去重 “labels名字”,然後給每一個label一個ID,最後結果是一個<labels, ID>Map。
  • mapPartition 是分區調用 CalLabelDetailLocal 來計算混淆矩陣。

下面具體看看。

3.2.1.1 flatMap

在flatMap中,主要是從label列和prediction列中,取出所有labels(注意是取出labels的名字 ),發送給下游算子。

EvaluationUtil.extractLabelProbMap 作用就是解析輸入的json,獲得具體detailInput中的信息。

下游算子是reduceGroup,所以Flink runtime會對這些labels自動去重。如果對這部分有興趣,可以參見我之前介紹reduce的文章。CSDN : [源碼解析] Flink的groupBy和reduce究竟做了什麼 博客園 : [源碼解析] Flink的groupBy和reduce究竟做了什麼

程序中變量如下

row = {Row@8922} "prefix1,{"prefix1": 0.9, "prefix0": 0.1}"
 fields = {Object[2]@8925} 
  0 = "prefix1"
  1 = "{"prefix1": 0.9, "prefix0": 0.1}"
    
labelProbMap = {TreeMap@9008}  size = 2
 "prefix0" -> {Double@9015} 0.1
 "prefix1" -> {Double@9017} 0.9
    
labelProbMap.keySet().forEach(collector::collect); //這裏發送 "prefix0", "prefix1" 
collector.collect(row.getField(0).toString());  // 這裏發送 "prefix1"   
// 因為下一個操作是reduceGroup,所以這些label會被runtime去重
3.2.1.2 reduceGroup

主要功能是通過buildLabelIndexLabelArray去重labels,然後給每一個label一個ID,最後結果是一個<labels, ID>的Map。

reduceGroup(new EvaluationUtil.DistinctLabelIndexMap(binary, positiveValue));

DistinctLabelIndexMap的作用是從label列和prediction列中,取出所有不同的labels,返回一個<labels, ID>的map,根據後續代碼看,這個map是多分類才用到。Get all the distinct labels from label column and prediction column, and return the map of labels and their IDs.

前面已經提到,這裏的參數rows已經被自動去重。

public static class DistinctLabelIndexMap implements
    GroupReduceFunction<String, Tuple2<Map<String, Integer>, String[]>> {
    ......
    @Override
    public void reduce(Iterable<String> rows, Collector<Tuple2<Map<String, Integer>, String[]>> collector) throws Exception {
        HashSet<String> labels = new HashSet<>();
        rows.forEach(labels::add);
        collector.collect(buildLabelIndexLabelArray(labels, binary, positiveValue));
    }
}

// 變量為
labels = {HashSet@9008}  size = 2
 0 = "prefix1"
 1 = "prefix0"
binary = true

buildLabelIndexLabelArray的作用是給每一個label一個ID,得到一個 <labels, ID>的map,最後返回是二元組(map, labels),即({prefix1=0, prefix0=1},[prefix1, prefix0])。

// Give each label an ID, return a map of label and ID.
public static Tuple2<Map<String, Integer>, String[]> buildLabelIndexLabelArray(HashSet<String> set,boolean binary, String positiveValue) {
    String[] labels = set.toArray(new String[0]);
    Arrays.sort(labels, Collections.reverseOrder());

    Map<String, Integer> map = new HashMap<>(labels.length);
    if (binary && null != positiveValue) {
        if (labels[1].equals(positiveValue)) {
            labels[1] = labels[0];
            labels[0] = positiveValue;
        } 
        map.put(labels[0], 0);
        map.put(labels[1], 1);
    } else {
        for (int i = 0; i < labels.length; i++) {
            map.put(labels[i], i);
        }
    }
    return Tuple2.of(map, labels);
}

// 程序變量如下
labels = {String[2]@9013} 
 0 = "prefix1"
 1 = "prefix0"
map = {HashMap@9014}  size = 2
 "prefix1" -> {Integer@9020} 0
 "prefix0" -> {Integer@9021} 1
3.2.1.3 mapPartition

這裏主要功能是分區調用 CalLabelDetailLocal 來為後來計算混淆矩陣做準備。

return data
    .rebalance()
    .mapPartition(new CalLabelDetailLocal(binary)) //這裡是業務所在
    .withBroadcastSet(labels, LABELS);

具體工作是 CalLabelDetailLocal 完成的,其作用是分區調用getDetailStatistics

// Calculate the confusion matrix based on the label and predResult.
static class CalLabelDetailLocal extends RichMapPartitionFunction<Row, BaseMetricsSummary> {
        private Tuple2<Map<String, Integer>, String[]> map;
        private boolean binary;

        @Override
        public void open(Configuration parameters) throws Exception {
            List<Tuple2<Map<String, Integer>, String[]>> list = getRuntimeContext().getBroadcastVariable(LABELS);
            this.map = list.get(0);// 前文生成的二元組(map, labels)
        }

        @Override
        public void mapPartition(Iterable<Row> rows, Collector<BaseMetricsSummary> collector) {
            // 調用到了 getDetailStatistics
            collector.collect(getDetailStatistics(rows, binary, map));
        }
    }  

getDetailStatistics 的作用是:初始化分類評估的度量指標 base classification evaluation metrics,累積計算混淆矩陣需要的數據。主要就是遍歷 rows 數據,提取每一個item(比如 “prefix1,{“prefix1”: 0.8, “prefix0”: 0.2}”),然後累積計算混淆矩陣所需數據。

// Initialize the base classification evaluation metrics. There are two cases: BinaryClassMetrics and MultiClassMetrics.
    private static BaseMetricsSummary getDetailStatistics(Iterable<Row> rows,
                                         String positiveValue,
                                         boolean binary,
                                         Tuple2<Map<String, Integer>, String[]> tuple) {
        BinaryMetricsSummary binaryMetricsSummary = null;
        MultiMetricsSummary multiMetricsSummary = null;
        Tuple2<Map<String, Integer>, String[]> labelIndexLabelArray = tuple;  // 前文生成的二元組(map, labels)

        Iterator<Row> iterator = rows.iterator();
        Row row = null;
        while (iterator.hasNext() && !checkRowFieldNotNull(row)) {
            row = iterator.next();
        }

        Map<String, Integer> labelIndexMap = null;
        if (binary) {
           // 二分法在這裏 
            binaryMetricsSummary = new BinaryMetricsSummary(
                new long[ClassificationEvaluationUtil.DETAIL_BIN_NUMBER],
                new long[ClassificationEvaluationUtil.DETAIL_BIN_NUMBER],
                labelIndexLabelArray.f1, 0.0, 0L);
        } else {
            // 
            labelIndexMap = labelIndexLabelArray.f0; // 前文生成的<labels, ID>Map看來是多分類才用到。
            multiMetricsSummary = new MultiMetricsSummary(
                new long[labelIndexMap.size()][labelIndexMap.size()],
                labelIndexLabelArray.f1, 0.0, 0L);
        }

        while (null != row) {
            if (checkRowFieldNotNull(row)) {
                TreeMap<String, Double> labelProbMap = extractLabelProbMap(row);
                String label = row.getField(0).toString();
                if (ArrayUtils.indexOf(labelIndexLabelArray.f1, label) >= 0) {
                    if (binary) {
                        // 二分法在這裏 
                        updateBinaryMetricsSummary(labelProbMap, label, binaryMetricsSummary);
                    } else {
                        updateMultiMetricsSummary(labelProbMap, label, labelIndexMap, multiMetricsSummary);
                    }
                }
            }
            row = iterator.hasNext() ? iterator.next() : null;
        }

        return binary ? binaryMetricsSummary : multiMetricsSummary;
}

//變量如下
tuple = {Tuple2@9252} "({prefix1=0, prefix0=1},[prefix1, prefix0])"
 f0 = {HashMap@9257}  size = 2
  "prefix1" -> {Integer@9264} 0
  "prefix0" -> {Integer@9266} 1
 f1 = {String[2]@9258} 
  0 = "prefix1"
  1 = "prefix0"
 
row = {Row@9271} "prefix1,{"prefix1": 0.8, "prefix0": 0.2}"
 fields = {Object[2]@9276} 
  0 = "prefix1"
  1 = "{"prefix1": 0.8, "prefix0": 0.2}"
    
labelIndexLabelArray = {Tuple2@9240} "({prefix1=0, prefix0=1},[prefix1, prefix0])"
 f0 = {HashMap@9288}  size = 2
  "prefix1" -> {Integer@9294} 0
  "prefix0" -> {Integer@9296} 1
 f1 = {String[2]@9242} 
  0 = "prefix1"
  1 = "prefix0"
    
labelProbMap = {TreeMap@9342}  size = 2
 "prefix0" -> {Double@9378} 0.1
 "prefix1" -> {Double@9380} 0.9    

先回憶下混淆矩陣:

預測值 0 預測值 1
真實值 0 TN FP
真實值 1 FN TP

針對混淆矩陣,BinaryMetricsSummary 的作用是Save the evaluation data for binary classification。函數具體計算思路是:

  • 把 [0,1] 分成ClassificationEvaluationUtil.DETAIL_BIN_NUMBER(100000)這麼多桶(bin)。所以binaryMetricsSummary的positiveBin/negativeBin分別是兩個100000的數組。如果某一個 sample 為 正例(positive value) 的概率是 p, 則該 sample 對應的 bin index 就是 p * 100000。如果 p 被預測為正例(positive value) ,則positiveBin[index]++,否則就是被預測為負例(negative value) ,則negativeBin[index]++。positiveBin就是 TP + FP,negativeBin就是 TN + FN。

  • 所以這裡會遍歷輸入,如果某一個輸入(以"prefix1", "{\"prefix1\": 0.9, \"prefix0\": 0.1}"為例),0.9 是prefix1(正例) 的概率,0.1 是為prefix0(負例) 的概率。

    • 既然這個算法選擇了 prefix1(正例) ,所以就說明此算法是判別成 positive 的,所以在 positiveBin 的 90000 處 + 1。
    • 假設這個算法選擇了 prefix0(負例) ,則說明此算法是判別成 negative 的,所以應該在 negativeBin 的 90000 處 + 1。

具體對應我們示例代碼的5個採樣,分類如下:

Row.of("prefix1", "{\"prefix1\": 0.9, \"prefix0\": 0.1}"),  positiveBin 90000處+1
Row.of("prefix1", "{\"prefix1\": 0.8, \"prefix0\": 0.2}"),  positiveBin 80000處+1
Row.of("prefix1", "{\"prefix1\": 0.7, \"prefix0\": 0.3}"),  positiveBin 70000處+1
Row.of("prefix0", "{\"prefix1\": 0.75, \"prefix0\": 0.25}"), negativeBin 75000處+1
Row.of("prefix0", "{\"prefix1\": 0.6, \"prefix0\": 0.4}")  negativeBin 60000處+1

具體代碼如下

public static void updateBinaryMetricsSummary(TreeMap<String, Double> labelProbMap,
                                              String label,
                                              BinaryMetricsSummary binaryMetricsSummary) {
    binaryMetricsSummary.total++;
    binaryMetricsSummary.logLoss += extractLogloss(labelProbMap, label);

    double d = labelProbMap.get(binaryMetricsSummary.labels[0]);
    int idx = d == 1.0 ? ClassificationEvaluationUtil.DETAIL_BIN_NUMBER - 1 :
        (int)Math.floor(d * ClassificationEvaluationUtil.DETAIL_BIN_NUMBER);
    if (idx >= 0 && idx < ClassificationEvaluationUtil.DETAIL_BIN_NUMBER) {
        if (label.equals(binaryMetricsSummary.labels[0])) {
            binaryMetricsSummary.positiveBin[idx] += 1;
        } else if (label.equals(binaryMetricsSummary.labels[1])) {
            binaryMetricsSummary.negativeBin[idx] += 1;
        } else {
					.....
        }
    }
}

private static double extractLogloss(TreeMap<String, Double> labelProbMap, String label) {
   Double prob = labelProbMap.get(label);
   prob = null == prob ? 0. : prob;
   return -Math.log(Math.max(Math.min(prob, 1 - LOG_LOSS_EPS), LOG_LOSS_EPS));
}

// 變量如下
ClassificationEvaluationUtil.DETAIL_BIN_NUMBER=100000
  
// 當 "prefix1", "{\"prefix1\": 0.9, \"prefix0\": 0.1}" 時候
labelProbMap = {TreeMap@9305}  size = 2
 "prefix0" -> {Double@9331} 0.1
 "prefix1" -> {Double@9333} 0.9
  
d = 0.9
idx = 90000
binaryMetricsSummary = {BinaryMetricsSummary@9262} 
 labels = {String[2]@9242} 
  0 = "prefix1"
  1 = "prefix0"
 total = 1
 positiveBin = {long[100000]@9263}  // 90000處+1
 negativeBin = {long[100000]@9264} 
 logLoss = 0.10536051565782628
   
// 當 "prefix0", "{\"prefix1\": 0.6, \"prefix0\": 0.4}" 時候  
labelProbMap = {TreeMap@9514}  size = 2
 "prefix0" -> {Double@9546} 0.4
 "prefix1" -> {Double@9547} 0.6
   
d = 0.6
idx = 60000    
 binaryMetricsSummary = {BinaryMetricsSummary@9262} 
 labels = {String[2]@9242} 
  0 = "prefix1"
  1 = "prefix0"
 total = 2
 positiveBin = {long[100000]@9263}  
 negativeBin = {long[100000]@9264} // 60000處+1
 logLoss = 1.0216512475319812  

3.2.2 ReduceBaseMetrics

ReduceBaseMetrics作用是把局部計算的 BaseMetrics 聚合起來。

DataSet<BaseMetricsSummary> metrics = res
    .reduce(new EvaluationUtil.ReduceBaseMetrics());

ReduceBaseMetrics如下

public static class ReduceBaseMetrics implements ReduceFunction<BaseMetricsSummary> {
    @Override
    public BaseMetricsSummary reduce(BaseMetricsSummary t1, BaseMetricsSummary t2) throws Exception {
        return null == t1 ? t2 : t1.merge(t2);
    }
}

具體計算是在BinaryMetricsSummary.merge,其作用就是Merge the bins, and add the logLoss。

@Override
public BinaryMetricsSummary merge(BinaryMetricsSummary binaryClassMetrics) {
    for (int i = 0; i < this.positiveBin.length; i++) {
        this.positiveBin[i] += binaryClassMetrics.positiveBin[i];
    }
    for (int i = 0; i < this.negativeBin.length; i++) {
        this.negativeBin[i] += binaryClassMetrics.negativeBin[i];
    }
    this.logLoss += binaryClassMetrics.logLoss;
    this.total += binaryClassMetrics.total;
    return this;
}

// 程序變量是
this = {BinaryMetricsSummary@9316} 
 labels = {String[2]@9322} 
  0 = "prefix1"
  1 = "prefix0"
 total = 2
 positiveBin = {long[100000]@9320} 
 negativeBin = {long[100000]@9323} 
 logLoss = 1.742969305058623

3.2.3 SaveDataAsParams

this.setOutput(metrics.flatMap(new EvaluationUtil.SaveDataAsParams()),
    new String[] {DATA_OUTPUT}, new TypeInformation[] {Types.STRING});

當歸併所有BaseMetrics之後,得到了total BaseMetrics,計算indexes,存入到params。

public static class SaveDataAsParams implements FlatMapFunction<BaseMetricsSummary, Row> {
    @Override
    public void flatMap(BaseMetricsSummary t, Collector<Row> collector) throws Exception {
        collector.collect(t.toMetrics().serialize());
    }
}

實際業務在BinaryMetricsSummary.toMetrics中完成,即基於bin的信息計算,得到confusionMatrix array, threshold array, rocCurve/recallPrecisionCurve/LiftChart等等,然後存儲到params。

public BinaryClassMetrics toMetrics() {
    Params params = new Params();
    // 生成若干曲線,比如rocCurve/recallPrecisionCurve/LiftChart
    Tuple3<ConfusionMatrix[], double[], EvaluationCurve[]> matrixThreCurve =
        extractMatrixThreCurve(positiveBin, negativeBin, total);

    // 依據曲線內容計算並且存儲 AUC/PRC/KS
    setCurveAreaParams(params, matrixThreCurve.f2);

    // 對生成的rocCurve/recallPrecisionCurve/LiftChart輸出進行抽樣
    Tuple3<ConfusionMatrix[], double[], EvaluationCurve[]> sampledMatrixThreCurve = sample(
        PROBABILITY_INTERVAL, matrixThreCurve);

    // 依據抽樣后的輸出存儲 RocCurve/RecallPrecisionCurve/LiftChar
    setCurvePointsParams(params, sampledMatrixThreCurve);
    ConfusionMatrix[] matrices = sampledMatrixThreCurve.f0;
  
    // 存儲正例樣本的度量指標
    setComputationsArrayParams(params, sampledMatrixThreCurve.f1, sampledMatrixThreCurve.f0);
  
    // 存儲Logloss
    setLoglossParams(params, logLoss, total);
  
    // Pick the middle point where threshold is 0.5.
    int middleIndex = getMiddleThresholdIndex(sampledMatrixThreCurve.f1);  
    setMiddleThreParams(params, matrices[middleIndex], labels);
    return new BinaryClassMetrics(params);
}

extractMatrixThreCurve是全文重點。這裡是 Extract the bins who are not empty, keep the middle threshold 0.5,然後初始化了 RocCurve, Recall-Precision Curve and Lift Curve,計算出ConfusionMatrix array(混淆矩陣), threshold array, rocCurve/recallPrecisionCurve/LiftChart.。

/**
 * Extract the bins who are not empty, keep the middle threshold 0.5.
 * Initialize the RocCurve, Recall-Precision Curve and Lift Curve.
 * RocCurve: (FPR, TPR), starts with (0,0). Recall-Precision Curve: (recall, precision), starts with (0, p), p is the precision with the lowest. LiftChart: (TP+FP/total, TP), starts with (0,0). confusion matrix = [TP FP][FN * TN].
 *
 * @param positiveBin positiveBins.
 * @param negativeBin negativeBins.
 * @param total       sample number
 * @return ConfusionMatrix array, threshold array, rocCurve/recallPrecisionCurve/LiftChart.
 */
static Tuple3<ConfusionMatrix[], double[], EvaluationCurve[]> extractMatrixThreCurve(long[] positiveBin, long[] negativeBin, long total) {
    ArrayList<Integer> effectiveIndices = new ArrayList<>();
    long totalTrue = 0, totalFalse = 0;
  
    // 計算totalTrue,totalFalse,effectiveIndices
    for (int i = 0; i < ClassificationEvaluationUtil.DETAIL_BIN_NUMBER; i++) {
        if (0L != positiveBin[i] || 0L != negativeBin[i]
            || i == ClassificationEvaluationUtil.DETAIL_BIN_NUMBER / 2) {
            effectiveIndices.add(i);
            totalTrue += positiveBin[i];
            totalFalse += negativeBin[i];
        }
    }

// 以我們例子,得到  
effectiveIndices = {ArrayList@9273}  size = 6
 0 = {Integer@9277} 50000 //這裏加入了中間點
 1 = {Integer@9278} 60000
 2 = {Integer@9279} 70000
 3 = {Integer@9280} 75000
 4 = {Integer@9281} 80000
 5 = {Integer@9282} 90000
totalTrue = 3
totalFalse = 2
  
    // 繼續初始化,生成若干curve
    final int length = effectiveIndices.size();
    final int newLen = length + 1;
    final double m = 1.0 / ClassificationEvaluationUtil.DETAIL_BIN_NUMBER;
    EvaluationCurvePoint[] rocCurve = new EvaluationCurvePoint[newLen];
    EvaluationCurvePoint[] recallPrecisionCurve = new EvaluationCurvePoint[newLen];
    EvaluationCurvePoint[] liftChart = new EvaluationCurvePoint[newLen];
    ConfusionMatrix[] data = new ConfusionMatrix[newLen];
    double[] threshold = new double[newLen];
    long curTrue = 0;
    long curFalse = 0;
  
// 以我們例子,得到 
length = 6
newLen = 7
m = 1.0E-5
  
    // 計算, 其中rocCurve,recallPrecisionCurve,liftChart 都可以從代碼中看出
    for (int i = 1; i < newLen; i++) {
        int index = effectiveIndices.get(length - i);
        curTrue += positiveBin[index];
        curFalse += negativeBin[index];
        threshold[i] = index * m;
        // 計算出混淆矩陣
        data[i] = new ConfusionMatrix(
            new long[][] {{curTrue, curFalse}, {totalTrue - curTrue, totalFalse - curFalse}});
        double tpr = (totalTrue == 0 ? 1.0 : 1.0 * curTrue / totalTrue);
        // 比如當 90000 這點,得到 curTrue = 1 curFalse = 0 i = 1 index = 90000 tpr = 0.3333333333333333。totalTrue = 3 totalFalse = 2, 
        // 我們也知道,TPR = TP / (TP + FN) ,所以可以計算 tpr = 1 / 3   
        rocCurve[i] = new EvaluationCurvePoint(totalFalse == 0 ? 1.0 : 1.0 * curFalse / totalFalse, tpr, threshold[i]);
        recallPrecisionCurve[i] = new EvaluationCurvePoint(tpr, curTrue + curTrue == 0 ? 1.0 : 1.0 * curTrue / (curTrue + curFalse), threshold[i]);
        liftChart[i] = new EvaluationCurvePoint(1.0 * (curTrue + curFalse) / total, curTrue, threshold[i]);
    }
  
// 以我們例子,得到 
curTrue = 3
curFalse = 2
  
threshold = {double[7]@9349} 
 0 = 0.0
 1 = 0.9
 2 = 0.8
 3 = 0.7500000000000001
 4 = 0.7000000000000001
 5 = 0.6000000000000001
 6 = 0.5  
   
rocCurve = {EvaluationCurvePoint[7]@9315} 
 1 = {EvaluationCurvePoint@9440} 
  x = 0.0
  y = 0.3333333333333333
  p = 0.9
 2 = {EvaluationCurvePoint@9448} 
  x = 0.0
  y = 0.6666666666666666
  p = 0.8
 3 = {EvaluationCurvePoint@9449} 
  x = 0.5
  y = 0.6666666666666666
  p = 0.7500000000000001
 4 = {EvaluationCurvePoint@9450} 
  x = 0.5
  y = 1.0
  p = 0.7000000000000001
 5 = {EvaluationCurvePoint@9451} 
  x = 1.0
  y = 1.0
  p = 0.6000000000000001
 6 = {EvaluationCurvePoint@9452} 
  x = 1.0
  y = 1.0
  p = 0.5
    
recallPrecisionCurve = {EvaluationCurvePoint[7]@9320} 
 1 = {EvaluationCurvePoint@9444} 
  x = 0.3333333333333333
  y = 1.0
  p = 0.9
 2 = {EvaluationCurvePoint@9453} 
  x = 0.6666666666666666
  y = 1.0
  p = 0.8
 3 = {EvaluationCurvePoint@9454} 
  x = 0.6666666666666666
  y = 0.6666666666666666
  p = 0.7500000000000001
 4 = {EvaluationCurvePoint@9455} 
  x = 1.0
  y = 0.75
  p = 0.7000000000000001
 5 = {EvaluationCurvePoint@9456} 
  x = 1.0
  y = 0.6
  p = 0.6000000000000001
 6 = {EvaluationCurvePoint@9457} 
  x = 1.0
  y = 0.6
  p = 0.5
    
liftChart = {EvaluationCurvePoint[7]@9325} 
 1 = {EvaluationCurvePoint@9458} 
  x = 0.2
  y = 1.0
  p = 0.9
 2 = {EvaluationCurvePoint@9459} 
  x = 0.4
  y = 2.0
  p = 0.8
 3 = {EvaluationCurvePoint@9460} 
  x = 0.6
  y = 2.0
  p = 0.7500000000000001
 4 = {EvaluationCurvePoint@9461} 
  x = 0.8
  y = 3.0
  p = 0.7000000000000001
 5 = {EvaluationCurvePoint@9462} 
  x = 1.0
  y = 3.0
  p = 0.6000000000000001
 6 = {EvaluationCurvePoint@9463} 
  x = 1.0
  y = 3.0
  p = 0.5
    
data = {ConfusionMatrix[7]@9339} 
 0 = {ConfusionMatrix@9486} 
  longMatrix = {LongMatrix@9488} 
   matrix = {long[2][]@9491} 
    0 = {long[2]@9492} 
     0 = 0
     1 = 0
    1 = {long[2]@9493} 
     0 = 3
     1 = 2
   rowNum = 2
   colNum = 2
  labelCnt = 2
  total = 5
  actualLabelFrequency = {long[2]@9489} 
   0 = 3
   1 = 2
  predictLabelFrequency = {long[2]@9490} 
   0 = 0
   1 = 5
  tpCount = 2.0
  tnCount = 2.0
  fpCount = 3.0
  fnCount = 3.0
 1 = {ConfusionMatrix@9435} 
  longMatrix = {LongMatrix@9469} 
   matrix = {long[2][]@9472} 
    0 = {long[2]@9474} 
     0 = 1
     1 = 0
    1 = {long[2]@9475} 
     0 = 2
     1 = 2
   rowNum = 2
   colNum = 2
  labelCnt = 2
  total = 5
  actualLabelFrequency = {long[2]@9470} 
   0 = 3
   1 = 2
  predictLabelFrequency = {long[2]@9471} 
   0 = 1
   1 = 4
  tpCount = 3.0
  tnCount = 3.0
  fpCount = 2.0
  fnCount = 2.0
  ......  
    
    threshold[0] = 1.0;
    data[0] = new ConfusionMatrix(new long[][] {{0, 0}, {totalTrue, totalFalse}});
    rocCurve[0] = new EvaluationCurvePoint(0, 0, threshold[0]);
    recallPrecisionCurve[0] = new EvaluationCurvePoint(0, recallPrecisionCurve[1].getY(), threshold[0]);
    liftChart[0] = new EvaluationCurvePoint(0, 0, threshold[0]);

    return Tuple3.of(data, threshold, new EvaluationCurve[] {new EvaluationCurve(rocCurve),
        new EvaluationCurve(recallPrecisionCurve), new EvaluationCurve(liftChart)});
}

3.2.4 計算混淆矩陣

這裏再給大家講講混淆矩陣如何計算,這裏思路比較繞。

3.2.4.1 原始矩陣

調用之處是:

// 調用之處
data[i] = new ConfusionMatrix(
        new long[][] {{curTrue, curFalse}, {totalTrue - curTrue, totalFalse - curFalse}});
// 調用時候各種賦值
i = 1
index = 90000
totalTrue = 3
totalFalse = 2
curTrue = 1
curFalse = 0

得到原始矩陣,以下都有cur,說明只針對當前點來說

curTrue = 1 curFalse = 0
totalTrue – curTrue = 2 totalFalse – curFalse = 2
3.2.4.2 計算標籤

後續ConfusionMatrix計算中,由此可以得到

actualLabelFrequency = longMatrix.getColSums();
predictLabelFrequency = longMatrix.getRowSums();

actualLabelFrequency = {long[2]@9322} 
 0 = 3
 1 = 2
predictLabelFrequency = {long[2]@9323} 
 0 = 1
 1 = 4  

可以看出來,Alink算法認為:每列的sum和實際標籤有關;每行sum和預測標籤有關。

得到新矩陣如下

predictLabelFrequency
curTrue = 1 curFalse = 0 1 = curTrue + curFalse
totalTrue – curTrue = 2 totalFalse – curFalse = 2 4 = total – curTrue – curFalse
actualLabelFrequency 3 = totalTrue 2 = totalFalse

後續計算將要基於這些來計算:

計算中就用到longMatrix 對角線上的數據,即longMatrix(0)(0)和 longMatrix(1)(1)。一定要注意,這裏考慮的都是 當前狀態 (畫重點強調)

longMatrix(0)(0) :curTrue

longMatrix(1)(1) :totalFalse – curFalse

totalFalse :( TN + FN )

totalTrue :( TP + FP )

double numTrueNegative(Integer labelIndex) {
  // labelIndex為 0 時候,return 1 + 5 - 1 - 3 = 2;
  // labelIndex為 1 時候,return 2 + 5 - 4 - 2 = 1;
	return null == labelIndex ? tnCount : longMatrix.getValue(labelIndex, labelIndex) + total - predictLabelFrequency[labelIndex] - actualLabelFrequency[labelIndex];
}

double numTruePositive(Integer labelIndex) {
  // labelIndex為 0 時候,return 1; 這個是 curTrue,就是真實標籤是True,判別也是True。是TP
  // labelIndex為 1 時候,return 2; 這個是 totalFalse - curFalse,總判別錯 - 當前判別錯。這就意味着“本來判別錯了但是當前沒有發現”,所以認為在當前狀態下,這也算是TP
	return null == labelIndex ? tpCount : longMatrix.getValue(labelIndex, labelIndex);
}

double numFalseNegative(Integer labelIndex) {
  // labelIndex為 0 時候,return 3 - 1; 
  // actualLabelFrequency[0] = totalTrue。所以return totalTrue - curTrue,即當前“全部正確”中沒有“判別為正確”,這個就可以認為是“判別錯了且判別為負”
  // labelIndex為 1 時候,return 2 - 2;   
  // actualLabelFrequency[1] = totalFalse。所以return totalFalse - ( totalFalse - curFalse )  = curFalse
	return null == labelIndex ? fnCount : actualLabelFrequency[labelIndex] - longMatrix.getValue(labelIndex, labelIndex);
}

double numFalsePositive(Integer labelIndex) {
  // labelIndex為 0 時候,return 1 - 1;
  // predictLabelFrequency[0] = curTrue + curFalse。
  // 所以 return = curTrue + curFalse - curTrue = curFalse = current( TN + FN ) 這可以認為是判斷錯了實際是正確標籤
  // labelIndex為 1 時候,return 4 - 2; 
  // predictLabelFrequency[1] = total - curTrue - curFalse。
  // 所以 return = total - curTrue - curFalse - (totalFalse - curFalse) = totalTrue - curTrue = ( TP + FP ) - currentTP = currentFP 
	return null == labelIndex ? fpCount : predictLabelFrequency[labelIndex] - longMatrix.getValue(labelIndex, labelIndex);
}

// 最後得到
tpCount = 3.0
tnCount = 3.0
fpCount = 2.0
fnCount = 2.0
3.2.4.3 具體代碼
// 具體計算 
public ConfusionMatrix(LongMatrix longMatrix) {
  
longMatrix = {LongMatrix@9297} 
  0 = {long[2]@9324} 
   0 = 1
   1 = 0
  1 = {long[2]@9325} 
   0 = 2
   1 = 2
     
    this.longMatrix = longMatrix;
    labelCnt = this.longMatrix.getRowNum();
    // 這裏就是計算
    actualLabelFrequency = longMatrix.getColSums();
    predictLabelFrequency = longMatrix.getRowSums();
  
actualLabelFrequency = {long[2]@9322} 
 0 = 3
 1 = 2
predictLabelFrequency = {long[2]@9323} 
 0 = 1
 1 = 4  
labelCnt = 2
total = 5  

    total = longMatrix.getTotal();
    for (int i = 0; i < labelCnt; i++) {
        tnCount += numTrueNegative(i);
        tpCount += numTruePositive(i);
        fnCount += numFalseNegative(i);
        fpCount += numFalsePositive(i);
    }
}

0x04 流處理

4.1 示例

Alink原有python示例代碼中,Stream部分是沒有輸出的,因為MemSourceStreamOp沒有和時間相關聯,而Alink中沒有提供基於時間的StreamOperator,所以只能自己仿照MemSourceBatchOp寫了一個。雖然代碼有些丑,但是至少可以提供輸出,這樣就能夠調試。

4.1.1 主類

public class EvalBinaryClassExampleStream {

    AlgoOperator getData(boolean isBatch) {
        Row[] rows = new Row[]{
                Row.of("prefix1", "{\"prefix1\": 0.9, \"prefix0\": 0.1}")
        };
        String[] schema = new String[]{"label", "detailInput"};
        if (isBatch) {
            return new MemSourceBatchOp(rows, schema);
        } else {
            return new TimeMemSourceStreamOp(rows, schema, new EvalBinaryStreamSource());
        }
    }

    public static void main(String[] args) throws Exception {
        EvalBinaryClassExampleStream test = new EvalBinaryClassExampleStream();
        StreamOperator streamData = (StreamOperator) test.getData(false);
        StreamOperator sOp = new EvalBinaryClassStreamOp()
                .setLabelCol("label")
                .setPredictionDetailCol("detailInput")
                .setTimeInterval(1)
                .linkFrom(streamData);
        sOp.print();
        StreamOperator.execute();
    }
}

4.1.2 TimeMemSourceStreamOp

這個是我自己炮製的。借鑒了MemSourceStreamOp。

public final class TimeMemSourceStreamOp extends StreamOperator<TimeMemSourceStreamOp> {

    public TimeMemSourceStreamOp(Row[] rows, String[] colNames, EvalBinaryStrSource source) {
        super(null);
        init(source, Arrays.asList(rows), colNames);
    }

    private void init(EvalBinaryStreamSource source, List <Row> rows, String[] colNames) {
        Row first = rows.iterator().next();
        int arity = first.getArity();
        TypeInformation <?>[] types = new TypeInformation[arity];

        for (int i = 0; i < arity; ++i) {
            types[i] = TypeExtractor.getForObject(first.getField(i));
        }

        init(source, colNames, types);
    }

    private void init(EvalBinaryStreamSource source, String[] colNames, TypeInformation <?>[] colTypes) {
        DataStream <Row> dastr = MLEnvironmentFactory.get(getMLEnvironmentId())
                .getStreamExecutionEnvironment().addSource(source);
        StringBuilder sbd = new StringBuilder();
        sbd.append(colNames[0]);
      
        for (int i = 1; i < colNames.length; i++) {
            sbd.append(",").append(colNames[i]);
        }
        this.setOutput(dastr, colNames, colTypes);
    }

    @Override
    public TimeMemSourceStreamOp linkFrom(StreamOperator<?>... inputs) {
        return null;
    }
}

4.1.3 Source

定時提供Row,加入了隨機數,讓概率有變化。

class EvalBinaryStreamSource extends RichSourceFunction[Row] {

  override def run(ctx: SourceFunction.SourceContext[Row]) = {
    while (true) {
      val rdm = Math.random() // 這裏加入了隨機數,讓概率有變化
      val rows: Array[Row] = Array[Row](
        Row.of("prefix1", "{\"prefix1\": " + rdm + ", \"prefix0\": " + (1-rdm) + "}"),
        Row.of("prefix1", "{\"prefix1\": 0.8, \"prefix0\": 0.2}"),
        Row.of("prefix1", "{\"prefix1\": 0.7, \"prefix0\": 0.3}"),
        Row.of("prefix0", "{\"prefix1\": 0.75, \"prefix0\": 0.25}"),
        Row.of("prefix0", "{\"prefix1\": 0.6, \"prefix0\": 0.4}"))
      for(row <- rows) {
        println(s"當前值:$row")
        ctx.collect(row)
      }
      Thread.sleep(1000)
    }
  }

  override def cancel() = ???
}

4.2 BaseEvalClassStreamOp

Alink流處理類是 EvalBinaryClassStreamOp,主要工作在其基類 BaseEvalClassStreamOp,所以我們重點看後者。

public class BaseEvalClassStreamOp<T extends BaseEvalClassStreamOp<T>> extends StreamOperator<T> {
    @Override
    public T linkFrom(StreamOperator<?>... inputs) {
        StreamOperator<?> in = checkAndGetFirst(inputs);
        String labelColName = this.get(MultiEvaluationStreamParams.LABEL_COL);
        String positiveValue = this.get(BinaryEvaluationStreamParams.POS_LABEL_VAL_STR);
        Integer timeInterval = this.get(MultiEvaluationStreamParams.TIME_INTERVAL);

        ClassificationEvaluationUtil.Type type = ClassificationEvaluationUtil.judgeEvaluationType(this.getParams());

        DataStream<BaseMetricsSummary> statistics;

        switch (type) {
            case PRED_RESULT: {
              ......
            }
            case PRED_DETAIL: {               
                String predDetailColName = this.get(MultiEvaluationStreamParams.PREDICTION_DETAIL_COL);
                // 
                PredDetailLabel eval = new PredDetailLabel(positiveValue, binary);
                // 獲取輸入數據,重點是timeWindowAll
                statistics = in.select(new String[] {labelColName, predDetailColName})
                    .getDataStream()
                    .timeWindowAll(Time.of(timeInterval, TimeUnit.SECONDS))
                    .apply(eval);
                break;
            }
        }
        // 把各個窗口的數據累積到 totalStatistics,注意,這裡是新變量了。
        DataStream<BaseMetricsSummary> totalStatistics = statistics
            .map(new EvaluationUtil.AllDataMerge())
            .setParallelism(1); // 并行度設置為1

        // 基於兩種 bins 計算&序列化,得到當前的 statistics
        DataStream<Row> windowOutput = statistics.map(
            new EvaluationUtil.SaveDataStream(ClassificationEvaluationUtil.WINDOW.f0));
        // 基於bins計算&序列化,得到累積的 totalStatistics
        DataStream<Row> allOutput = totalStatistics.map(
            new EvaluationUtil.SaveDataStream(ClassificationEvaluationUtil.ALL.f0));

      	// "當前" 和 "累積" 做聯合,最終返回
        DataStream<Row> union = windowOutput.union(allOutput);

        this.setOutput(union,
            new String[] {ClassificationEvaluationUtil.STATISTICS_OUTPUT, DATA_OUTPUT},
            new TypeInformation[] {Types.STRING, Types.STRING});

        return (T)this;
    }
}

具體業務是:

  • PredDetailLabel 會進行去重標籤名字 和 累積計算混淆矩陣所需數據
    • buildLabelIndexLabelArray 去重 “labels名字”,然後給每一個label一個ID,最後結果是一個<labels, ID>Map。
    • getDetailStatistics 遍歷 rows 數據,提取每一個item(比如 “prefix1,{“prefix1”: 0.8, “prefix0”: 0.2}”),然後通過updateBinaryMetricsSummary累積計算混淆矩陣所需數據。
  • 根據標籤從Window中獲取數據 statistics = in.select().getDataStream().timeWindowAll() .apply(eval);
  • EvaluationUtil.AllDataMerge 把各個窗口的數據累積到 totalStatistics 。
  • 得到windowOutput ——– EvaluationUtil.SaveDataStream,對”當前數據statistics”做處理。實際業務在BinaryMetricsSummary.toMetrics,即基於bin的信息計算,然後存儲到params,並序列化返回Row。
    • extractMatrixThreCurve函數取出非空的bins,據此計算出ConfusionMatrix array(混淆矩陣), threshold array, rocCurve/recallPrecisionCurve/LiftChart.
    • 依據曲線內容計算並且存儲 AUC/PRC/KS
    • 對生成的rocCurve/recallPrecisionCurve/LiftChart輸出進行抽樣
    • 依據抽樣后的輸出存儲 RocCurve/RecallPrecisionCurve/LiftChar
    • 存儲正例樣本的度量指標
    • 存儲Logloss
    • Pick the middle point where threshold is 0.5.
  • 得到allOutput ——– EvaluationUtil.SaveDataStream , 對”累積數據totalStatistics”做處理。
    • 詳細處理流程同windowOutput。
  • windowOutput 和 allOutput 做聯合。最終返回 DataStream union = windowOutput.union(allOutput);

4.2.1 PredDetailLabel

static class PredDetailLabel implements AllWindowFunction<Row, BaseMetricsSummary, TimeWindow> {
    @Override
    public void apply(TimeWindow timeWindow, Iterable<Row> rows, Collector<BaseMetricsSummary> collector) throws Exception {
        HashSet<String> labels = new HashSet<>();
        // 首先還是獲取 labels 名字
        for (Row row : rows) {
            if (EvaluationUtil.checkRowFieldNotNull(row)) {
                labels.addAll(EvaluationUtil.extractLabelProbMap(row).keySet());
                labels.add(row.getField(0).toString());
            }
        }
labels = {HashSet@9757}  size = 2
 0 = "prefix1"
 1 = "prefix0"   
        // 之前介紹過,buildLabelIndexLabelArray 去重 "labels名字",然後給每一個label一個ID,最後結果是一個<labels, ID>Map。
        // getDetailStatistics 遍歷 rows 數據,累積計算混淆矩陣所需數據( "TP + FN"  /  "TN + FP")。
        if (labels.size() > 0) {
            collector.collect(
                getDetailStatistics(rows, binary, buildLabelIndexLabelArray(labels, binary, positiveValue)));
        }
    }
}

4.2.2 AllDataMerge

EvaluationUtil.AllDataMerge 把各個窗口的數據累積

/**
 * Merge data from different windows.
 */
public static class AllDataMerge implements MapFunction<BaseMetricsSummary, BaseMetricsSummary> {
    private BaseMetricsSummary statistics;
    @Override
    public BaseMetricsSummary map(BaseMetricsSummary value) {
        this.statistics = (null == this.statistics ? value : this.statistics.merge(value));
        return this.statistics;
    }
}

4.2.3 SaveDataStream

SaveDataStream具體調用的函數之前批處理介紹過,實際業務在BinaryMetricsSummary.toMetrics,即基於bin的信息計算,存儲到params。

這裏與批處理不同的是直接就把”構建出的度量信息“返回給用戶。

public static class SaveDataStream implements MapFunction<BaseMetricsSummary, Row> {
    @Override
    public Row map(BaseMetricsSummary baseMetricsSummary) throws Exception {
        BaseMetricsSummary metrics = baseMetricsSummary;
        BaseMetrics baseMetrics = metrics.toMetrics();
        Row row = baseMetrics.serialize();
        return Row.of(funtionName, row.getField(0));
    }
}

// 最後得到的 row 其實就是最終返回給用戶的度量信息
row = {Row@10008} "{"PRC":"0.9164636268708667","SensitivityArray":"[0.38461538461538464,0.6923076923076923,0.6923076923076923,1.0,1.0,1.0]","ConfusionMatrix":"[[13,8],[0,0]]","MacroRecall":"0.5","MacroSpecificity":"0.5","FalsePositiveRateArray":"[0.0,0.0,0.5,0.5,1.0,1.0]" ...... 還有很多其他的

4.2.4 Union

DataStream<Row> windowOutput = statistics.map(
    new EvaluationUtil.SaveDataStream(ClassificationEvaluationUtil.WINDOW.f0));
DataStream<Row> allOutput = totalStatistics.map(
    new EvaluationUtil.SaveDataStream(ClassificationEvaluationUtil.ALL.f0));

DataStream<Row> union = windowOutput.union(allOutput);

最後返回兩種統計數據

4.2.4.1 allOutput
all|{"PRC":"0.7341146115890359","SensitivityArray":"[0.3333333333333333,0.3333333333333333,0.6666666666666666,0.7333333333333333,0.8,0.8,0.8666666666666667,0.8666666666666667,0.9333333333333333,1.0]","ConfusionMatrix":"[[13,10],[2,0]]","MacroRecall":"0.43333333333333335","MacroSpecificity":"0.43333333333333335","FalsePositiveRateArray":"[0.0,0.5,0.5,0.5,0.5,1.0,1.0,1.0,1.0,1.0]","TruePositiveRateArray":"[0.3333333333333333,0.3333333333333333,0.6666666666666666,0.7333333333333333,0.8,0.8,0.8666666666666667,0.8666666666666667,0.9333333333333333,1.0]","AUC":"0.5666666666666667","MacroAccuracy":"0.52", ......

4.2.4.2 windowOutput

window|{"PRC":"0.7638888888888888","SensitivityArray":"[0.3333333333333333,0.3333333333333333,0.6666666666666666,1.0,1.0,1.0]","ConfusionMatrix":"[[3,2],[0,0]]","MacroRecall":"0.5","MacroSpecificity":"0.5","FalsePositiveRateArray":"[0.0,0.5,0.5,0.5,1.0,1.0]","TruePositiveRateArray":"[0.3333333333333333,0.3333333333333333,0.6666666666666666,1.0,1.0,1.0]","AUC":"0.6666666666666666","MacroAccuracy":"0.6","RecallArray":"[0.3333333333333333,0.3333333333333333,0.6666666666666666,1.0,1.0,1.0]","KappaArray":"[0.28571428571428564,-0.15384615384615377,0.1666666666666666,0.5454545454545455,0.0,0.0]","MicroFalseNegativeRate":"0.4","WeightedRecall":"0.6","WeightedPrecision":"0.36","Recall":"1.0","MacroPrecision":"0.3",......

0xFF 參考

[[白話解析] 通過實例來梳理概念 :準確率 (Accuracy)、精準率(Precision)、召回率(Recall) 和 F值(F-Measure)](

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

【其他文章推薦】

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

※超省錢租車方案

2020/6/10 JavaScript高級程序設計 BOM

BOM(瀏覽器對象模型):提供用於訪問瀏覽器的對象。

8.1 window對象

 window是BOM的核心對象,表示瀏覽器的一個實例。

  • JavaScript訪問瀏覽器窗口的接口
  • ECMAScript規定的Global對象

8.1.1 全局作用域

全局變量會成為window的屬性,但是定義全局變量和直接在window對象上定義屬性是有差別的——全局變量不能通過delete刪除,但window對象上定義的可以

這是因為使用var添加的window屬性[[Configurable]]被設置為false(不可刪除)。

訪問未聲明的變量會發生錯誤,但通過查詢window對象,可以知道某個可能未聲明的變量是否存在。

//這裡會拋出錯誤,因為oldValue未定義
var newValue = oldValue;

//這裏不會拋出錯誤,因為這是一次屬性查詢
var newValue = window.oldValue;  //newValue的值是undefined

8.1.2 窗口關係及框架

如果頁面中包含框架,則每個框架都擁有自己的window對象,並保存在frames集合中。在frames集合中可以通過數值索引/框架名稱來訪問相應的window對象。每個window對象都有一個name屬性,其中包含框架的名稱。

PS1:對於最高層窗口來說:除非最高層窗口是通過window.open()打開的,否則其window對象的name屬性不會包含任何值。

與框架有關的window對象屬性(同時也是對象):

  • top:始終指向最高(最外)層的框架,也就是瀏覽器窗口。使用它可以正確地在一個框架中訪問另一個框架。因為對任意一個框架中的代碼來說,window對象指向的都是那個框架的特定實例,而非最高層框架。
  • parent:始終指向當前框架的直接上層框架。在沒有框架的情況下,parent等於top。
  • self:始終指向window。引入self的目的僅僅是為了和top和parent對象對應,因此他不包含其他值。

8.1.3 窗口位置

用來確定window對象位置的屬性:screenLeft, screenTop / screenX, screenY,分別表示窗口相對於屏幕左邊和上邊的位置。

兩組方法分別支持的瀏覽器:

screenLeft, screenTop IE、Safari、Opera、Chrome
screenX, screenY Firefox、Safari、Chrome

跨瀏覽器取得窗口位置的代碼

var leftPos = (typeof window.screenLeft == "number") ?
                        window.screenLeft : window.screenX;
var topPos = (typeof window.screenTop == "number") ?
                        window.screenTop : window.screenY;

缺點:

  • screenTop表示的是由從屏幕上邊到window對象表示的頁面可見區域的距離(即頁面可見區域上方瀏覽器工具欄的像素高度)。
  • screenY表示整個瀏覽器窗口相對於屏幕的坐標值(0)。

將窗口精確移動到一個新位置的方法:

  • moveTo():接收最新位置的x,y坐標值。
  • moveBy():接收在水平和垂直方向上移動的像素數。

PS:這兩個方法很可能會被瀏覽器禁用(Opera和IE7+),且不適合框架,只能對最外層window對象使用。

8.1.4 窗口大小

  IE9+、Safari、Firefox Opera Chrome
innerWidth、innerHeight 視圖區大小 該容器中頁面視圖區的大小(減去邊框寬度) 視口大小
outerWidth、outerHeight 瀏覽器窗口本身的尺寸 頁面視圖容器的大小(單個標籤頁對應瀏覽器窗口的大小) 視口大小

document.documentElement.clientWidth / document.documentElement.clientHeight:保存了頁面視口的信息。

resizeTo()和resizeBy():調整瀏覽器窗口的大小。(分別接收新寬度高度和新窗口與原窗口的寬度和高度之差,同樣可能被瀏覽器禁用)

8.1.5 導航和打開窗口

 

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

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

※超省錢租車方案

皮草業者毫無改善 芬蘭組織最新調查 「怪物狐狸」慘況依舊

環境資訊中心記者 鄒敏惠 報導

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

【其他文章推薦】

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

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

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

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

※回頭車貨運收費標準

中國工業設計「奧斯卡獎」由陽光電源電動車控制器摘取

2014年12月12日,中國工業設計最高榮譽、被媲美為中國工業設計界之「奧斯卡獎」的中國創新設計紅星獎頒獎典禮在北京舉行。陽光電源電動車控制器產品從全球千餘家企業、6000多件參評作品中脫穎而出,獲得紅星獎,也是唯一獲得該獎項的電動車控制器類產品。  

  陽光電源自主研發的電動車控制器系列產品,結合了陽光電源近20年電力電子領域的技術研發和產業應用經驗,相比同類產品,該型號體積更小、重量更輕,適應範圍更廣也更環保。它採用模組化設計功能完善、能耗低、續駛里程長、可靠性高,目前成功應用於合肥、大連、舒城等多個城市電動公車。

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※台北網頁設計公司全省服務真心推薦

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

TUV萊茵移駕深圳 設電動車電池實驗室

德國萊茵TUV集團19日宣佈其位於深圳的全新電動車電池實驗室正式啟動。該實驗室新增設備涵蓋大尺寸電動車電池測試和更多認證服務,可依據 UN/ECE R100標準提供全尺寸電動汽車電池和非可擕式電池的測試服務,也可按照企業需求定製檢測方案,已獲 CNAS、DAkkS、CBTL、NRTL、CATL、CTIA 等多項國際認可和授權。   TUV萊茵的深圳電池實驗室除了提供動力電池相關測試外,還可為客戶提供歐洲、北美、日本、韓國、俄羅斯、巴西等國際市場准入服務。其先進的電池性能和安全測試設備,以及來自世界各國的資深專家團隊,可根據歐盟、美國等主要國家與地區的相關標準和要求,對原電池、鉛酸蓄電池、二次鋰離子電池、鎳鎘電池、鎳氫電池、輕型電動自行車電池等產品提供檢測認證服務,以保證電池符合安全、性能、儲存、運輸、化學及電磁相容等各項要求。

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

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

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

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

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

※回頭車貨運收費標準

東京計畫奧運前 6,000 輛燃料電池車上路

  東京籌備 2020 年奧運會,擬砸 452 億日圓(台幣 122 億元)補助燃料電池車(FCV)和興建加氫站,目前正和豐田(Toyota)、本田(Honda)汽車協商合作,目標在東京奧運前,至少讓 6,000 輛燃料電池車上路趴趴走。   東京都廳能源部門規劃團隊主管 Makoto Fujimoto 表示,將在東京打造 35 座加氫站,正和豐田汽車、本田汽車協商,希望 2020 年前能讓 6,000 輛這種氫燃料電池車上路。Fujimoto 說,購買燃料電池車的日本民眾將獲得 300 萬日圓補助,其中 100 萬日圓由東京都廳補貼,200 萬日圓由中央政府補貼;到 2025 年時,盼在東京的氫燃料電池車將達 10 萬輛小客車、100 輛公車,且具有 80 個加氫站的目標。   此外,Fujimoto 指出,東京都廳將補助興建加氫站的費用 80%,業者負擔費用最高 1 億日圓,相當於一般蓋加油站的費用;若小企業想蓋加氫站,政府可能支付全部興建費用。   日本對燃料電池車的補助,超過中國、美國和歐洲提供購買電動車的補貼誘因,也較現行日本提供三菱汽車純電動車 i-MiEV 的補助 95 萬日圓,多出 2 倍以上。     (Source:)

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

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

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

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

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

第十六屆上海國際汽車工業展覽會

新能源展區

展會日期:2015年4月22日至29日
媒體日:2015年4月20-21日
展覽會地點:國家會展中心(上海)(盈港東路168號)

展覽會面積:30萬平方米(預計)
新能源展覽面積:5萬平方米(預計)
展覽會觀眾:100萬人次(預計)
新能源專業觀眾:3萬人次(預計)
展覽會宣傳:3,000家媒體及10,000名記者的熱情報導(預計)

主辦單位:
中國汽車工業協會
中國國際貿易促進委員會上海市分會
中國國際貿易促進委員會汽車行業分會

承辦單位:
上海市國際展覽有限公司
特別支援單位:
中國機械工業聯合會
歐洲地區協辦單位:
德國慕尼克國際博覽集團
IMAG國際交易會展覽會有限公司
支援單位:
中國汽車工程學會
新能源展區招展:
北京盛大超越國際展覽有限公司

展會概況:

Auto Shanghai 上海國際汽車展,創辦於1985年,至今已連續成功舉辦了十五屆。30年來,見證了中國汽車工業的欣欣向榮和中國汽車市場的迅猛發展,更是為中外車企構築起廣泛交流與合作的平臺,同時也為汽車愛好者打造了汽車文化的盛會。而上海國際汽車展本身也逐步成長為中國乃至全球最具讚譽的汽車大展之一。在我們迎來第十六屆上海國際汽車展之際,主承辦單位由衷地感謝中外汽車界、新聞界以及廣大熱情的觀眾對展會的厚愛與支持。

Auto Shanghai 上海國際汽車展,一貫秉承“以人為本”的服務理念,凝聚創新與突破,主承辦各方將銳意進取,不斷豐富展會內涵,樹立展會個性,提升展會品質,使上海國際汽車展成為中國乃至國際汽車工業最具品牌價值與影響力的展示、發佈及貿易平臺之一。

Auto Shanghai 上海國際汽車展,始終關注汽車工業的發展步伐,在汽車誕生逾一個世紀以來,科技不斷推動著汽車設計開啟新的篇章。智慧化、新能源、多功能將成為未來汽車發展的趨勢,我們相信科技的不斷進步將使未來的汽車更安全、更便捷、更舒適。這新一輪的技術變革,也讓未來的汽車工業及人類的汽車生活有了更多的可能。中國乃至全球汽車產業也因此將面臨新的機遇與創造。將于明年4月揭開序幕的第十六屆上海國際汽車展以“創新 ▪ 升級”為主題,屆時我們將再次領略汽車工業所迸發的無窮魅力,汽車科技所推動的技術變革,汽車文化所帶來的生活理念。同時,2015年4月的第十六屆上海國際汽車展將移師國家會展中心(上海)這一全新的舞臺舉辦。我們將在中外汽車鉅子攜手搭建的全新舞臺上感受到未來正在發生!

本屆車展是舉辦30年以來首次設立新能源展區,數家車企現場角力。新能源車包括混合動力汽車、電動汽車、燃料電池汽車等新能源汽車、驅動系統、充電設施、相關零部件、汽車設計等。

主承辦單位在長期的辦展歷程中,始終以簡潔、高效、創新的風格精心策劃和運營展會,使廣大觀眾、眾多媒體在車展現場感受到汽車文化的魅力與底蘊。我們熱情邀請您參與和光臨第十六屆上海國際汽車展。

讓我們共同期待2015上海國際汽車工業展覽會成為“創新 ▪ 升級”的舞臺!

新能源展區參展範圍:
節能汽車、純電動車,混合動力車,燃料電池車,輕型電動車,天然氣(液化氣)車,醇類燃料車及其他代用燃料車;
先進內燃機、高效變速器、整車優化設計等節能技術和產品;
電池,燃料電池,電池管理系統,電池的回收和包裝;
電力電容器,飛輪,能源管理系統;
電機,電機保護與控制技術;
充電器及充電站設備及相關配套專案企業、機構;
電動車及其他替代能源汽車的零部件及零部件總成;
加氣設備,儲運設備及技術;
網路管理,可再生能源發電,新型元器件和材料,輕量化;
智慧社區和電網,汽車共用和通訊服務;
檢測,維修,監控,實驗,安全防護裝備;
城市推廣應用示範展示、製造設備、工具及媒體等.

展會諮詢:
北京盛大超越國際展覽有限公司
連絡人:嶽巍                           
手  機:+86 135 5286 5285                         
電  話: +86 10 6329 0215                  
E-mail:                    

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

【其他文章推薦】

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

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

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

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

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

2014年歐洲電動汽車銷量榜出爐 日產leaf市占率26%拔頭籌

據統計,2014年歐洲電動汽車總銷量達5.6萬輛,其中日產聆風(Leaf)的銷量佔據了主導地位,達14658輛,市場佔有率為26%。聆風目前與Juke在英國同一條生產線生產,因此價格有所降低,供應量也更大。2014年聆風電動汽車僅在英國就售出4051輛,比2013年的1812輛多出一倍以上。這款車型佔據了55%的英國純電動汽車市場。   另外,雷諾Zoe在歐洲的銷量為11227輛,排在電動汽車市場的第二位,市場佔有率為20%。特斯拉Model S電動汽車以8734輛的成績排名第三,考慮到其與奧迪A6相當的高昂售價,這一表現算得上十分出色。而BMWi3售出5804輛排在第四位,市場佔有率10%。但是i3在2014年年末剛剛上市而且受生產難度限制產量有限,也算是在電動車市場上為德國企業的表現扳回一成。   而電動車銷量榜的第5和第6位分別為大眾汽車的兩款車型。其中e-Up!電動車以5363輛的銷量幾乎比肩BMWi3。可能受限於34900歐元的售價和較晚的發售時間,其全新的電動版高爾夫(e-Golf)只售出3328輛。

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

【其他文章推薦】

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

※超省錢租車方案

與特斯拉比拼!BMW 及福斯將在美國東西岸設 100 座快速充電站

  為了拉抬自家電動車買氣,BMW、福斯(Volkswagen)宣布與美國充電設施營運商 ChargePoint 合作,將在美國東岸與西岸設置共 100 座快速充電站。除了要藉此建立更完善的充電站網絡外,更希望能讓消費者不被充電便利性受限,而提高購買電動車的意願。   ChargePoint 是目前全美最大的充電設施營運商,在全國一共設置超過 2 萬座充電站,BMW、福斯與其合作,計畫在 2015 年底前,於美國東岸與西岸設置 100 座快速充電站,相當於平均每 50 英里(約 80.5 公里)就有一座快速充電站。   每座充電站配有 2 個輸出功率分別為 50 kW DC 或 24 kW DC 的 2 款高速充電樁,駕駛只要花 20 至 30 分鐘,就能讓電動車充滿 80% 的電量,相較於需花費好幾個小時充電的 ChargePoint 其他充電站,明顯快上許多。  
 

  (Source:)   據紐約時報報導,有別於特斯拉(Telsa)在全美設置的 358 座免費超級充電站(Supercharger),只能提供給自家車款使用,BMW 與福斯設置的充電站,則是與大多數電動車都相容,不過,使用這些充電站的駕駛就沒像特斯拉車主那麼好康了,得自掏腰包付「充電費」。   根據統計,2014 年美國電動車銷量約為 12 萬輛,較 2013 年成長 20%,但2014 年全美各類車種總銷量一共為 1,650 萬輛,電動車占比仍少得可憐。   或許是意識到快速充電樁數量稀少是影響電動車銷售量的一大因素,近來不少電動車製造商設法填補充電樁不足的缺口,希望能讓消費者不被充電便利性受限,好替電動車銷量帶來生機。   (首圖來源:)

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

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

※超省錢租車方案