車用鋰電池需求夯!Panasonic傳擬在歐建新廠

日本媒體日刊工業新聞29日報導,因以歐系為主的車廠相繼將電動車(EV)、插電式油電混合車(PHV)等電動化車種視為重點研發對象,帶動車用鋰離子電池需求旺盛,故Panasonic計畫在2022年結束前於歐洲興建車用鋰離子電池新工廠,預估投資額將達數百億日圓。2017年全球電動化車種產量預估為450萬台,2022年將增至約1,900萬台、佔整體產量比重約2成。

報導指出,Panasonic目前正在美國內華達州、中國大連市興建電池工廠,合計投資額達約2,000億日圓,其中美國工廠預計於今年內、大連工廠預計於2017年2月啟用,且Panasonic也正擴增日本國內電池工廠產能,但即便如此,預估Panasonic產能仍無法滿足需求,故計畫在歐洲興建新廠。

據報導,Panasonic為全球車用鋰離子電池龍頭廠,和南韓三星、LG的競爭火熱,而該兩家韓廠也正在東歐興建新工廠,預計於2017-2018年啟用,故Panasonic也期望藉由在歐洲興建新工廠對抗韓廠。

日經新聞11月7日報導,因全球各國紛紛強化汽車環保規範,故日本汽車業龍頭豐田汽車(Toyota)計畫在2020年結束前整備電動車(EV)量產體制、正式搶進EV市場。據報導,目前豐田環保車策略以油電混合車(HV)、燃料電池車(FCV)為主,今後則將擴充陣容、將EV也定位為環保車的主力產品,且將視比亞迪(BYD)、特斯拉(Tesla)等競爭對手動向,決定生產體制。

根據日本市調機構富士經濟(Fuji Keizai)公布的調查報告顯示,PHV、EV在2025年以後的需求增幅將加快,預估2030年左右時,HV、PHV、EV將呈現幾乎相互抗衡的局面,2035年在北美、歐洲、中國需求加持下,PHV、EV市場將進一步擴大,超越HV。

富士經濟指出,全球EV市場當前將持續呈現緩和增長,不過預估自2020年左右起,EV需求將呈現急速擴大,預估2035年全球EV市場規模將達567萬台、將較2015年飆增近16倍(成長1,567%);另外,2035年全球PHV市場規模將達665萬台、將較2015年(21萬台)飆增近31倍(成長3,066%)。

(本文內容由 授權提供。照片來源:Panasonic)

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

※FB行銷專家,教你從零開始的技巧

※聚甘新

3D光達資料解密 市區樹木儲碳力 倫敦堪比熱帶雨林

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

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

【其他文章推薦】

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

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

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

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

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

※聚甘新

全球首例弱陽性寵物狗離世 傳飼主不願驗屍

摘錄自2020年3月18日自由時報報導

香港60歲女富豪周巧兒日前確診武漢肺炎,她的愛犬經檢測呈弱陽性反應,成為全球首隻低程度感染的寵物犬。港媒引述漁護署說法,表示狗狗已離世,且飼主不願驗屍確定死因。

綜合媒體報導,成為香港第85例的周巧兒在上月25日確診,隔天狗狗被送去檢疫,先後幾度採集樣本檢驗,結果「都是弱陽性」,反覆測試後直至本月12、13日,驗到2次陰性,漁護署14日將狗交還飼主。

報導指出,漁護署原本打算之後要再替狗狗安排抽血檢測,確認體內是否有抗體,未料狗狗於16日離世,據稱飼主表示「不願意」愛犬進行驗屍釐清死因。

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

【其他文章推薦】

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

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

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

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

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

※聚甘新

阿聯首座核電廠反應器獲發執照 阿拉伯世界新頁

摘錄自2020年2月17日中央社報導

阿拉伯聯合大公國今(17日)宣布,已向阿拉伯世界第一座核電廠「巴拉卡核電廠」的其中一座核子反應器核發營運執照,阿布達比王儲穆罕默德稱此舉為阿拉伯世界開啟「新頁」。

法新社報導,阿拉伯聯合大公國能源儲量豐富,但全國1000萬人口讓能源消耗量大,因此政府投入鉅資發展太陽能等替代能源。

阿布達比王儲穆罕默德(Sheikh Mohammed bin Zayed al-Nahyan)在推特上表示:「今天,隨著核發營運執照給第一座核電廠巴拉卡(Barakah),寫下我們發展和平核能的新頁。」

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

※聚甘新

摧毀海洋、潟湖、飲用水的瘋狂計畫 土國總統仍要建伊斯坦堡運河

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

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

※FB行銷專家,教你從零開始的技巧

※聚甘新

Oracle調用Java方法(上)如何使用LoadJava命令和如何將簡單的Jar包封裝成Oracle方法

最近在工作中遇到了遇到了一個需求需要將TIPTOP中的數據導出成XML並上傳到FTP主機中,但是4GL這方面的文檔比較少最終決定使用Oracle調用Java的方法,在使用的過程中發現有很多的坑,大部分的博客只粗略的介紹了下導致耗費了很多時間,在這裏分兩個博文詳細的記錄一下這個功能,填一下坑,希望可以幫助到大家。

首先 每個Oracle版本對應的OJVM的版本是不一樣的我的Oracle 版本是 Database 11g Release 2 對應的是jdk1.6 所以寫Java代碼的時候要注意版本號,其次這個功能是默認關閉的所以需要先安裝OJVM,腳本安裝語法如下(使用sys用戶登陸sqlplus安裝):

安裝JVM運行環境

@?/javavm/install/initjvm.sql

卸載JVM運行環境

@?/javavm/install/rmjvm.sql

查詢OJVM是否安裝成功

SELECT * FROM DBA_REGISTRY WHERE COMP_ID = 'JAVAVM';

執行腳本時間比較長要耐心等待一會,安裝完成后需要根據Java文件類型來選擇導入語句
LoadJava參數有

  • -u(用於輸入用戶名及密碼)
  • -v(用於輸出詳細的反饋信息)
  • -resolve(對於沒有編譯的Java文件可以用resolve來編譯)
  • -f(不管之前是否已經導入過該Java類,都強制再次導入)

當導入Class文件時,可以用如下命令:

loadjava –u username/userpassword –v filename.class

當刪除Class文件時,可以用如下命令:

dropjava –u username/userpassword –v filename.class

當導入Java文件時,可以用如下命令:

loadjava –u username/userpassword –v -resolve filename.java

當刪除Java文件時,可以用如下命令:

dropjava –u username/userpassword –v filename.java

當導入jar文件時,可以用如下命令:

loadjava –u username/userpassword –v -resolve filename.jar

當刪除jar文件時,可以用如下命令:

dropjava –u username/userpassword –v -resolve filename.jar

如果是比較簡單的Java方法可以直接使用PL/SQL進行編寫,需要調用的方法必須是public static 修飾的(Java方法若需要用到其他價包,報ORA-29521找不到類的錯 將在下一個博客中說到)

create or replace and compile java source named JavaName as [Java代碼]

當使用這種方法的時候可以在PL/SQL 左邊的Java Sources 文件夾中找得到源碼並編輯,如果導入的是.Java或.Jar格式的就只能在Java Classes 文件夾中找得到並且不能用PL/SQL編輯.

如果導入正常的話使用下面的語句可以查詢當前用戶已經導入的Java類,查詢出來的狀態必須是”VALID”,否則Oracle則不能調用必須解決報錯信息重新導入才能調用

SELECT OBJECT_ID, OBJECT_NAME, OBJECT_TYPE, STATUS
FROM USER_OBJECTS
WHERE OBJECT_TYPE LIKE 'JAVA%'

以上代碼都執行完成后就可以將Java代碼封裝成成Oracle 方法(function) ,存儲過程(Procedures) 這2個主要是返回值的區別 如果有返回值可以封裝成function ,沒有返回值可以封裝成Procedures,如果有多個返回值…emmmm Java沒有多個返回值=。= 我需要封裝成function則封裝的語句如下:

CREATE OR REPLACE FUNCTION FunctionName(str1  NVARCHAR2,
  str2   NVARCHAR2,
  str3   NVARCHAR2,
  str4  NVARCHAR2,
  str5   NVARCHAR2)
  RETURN NVARCHAR2 AS
  LANGUAGE JAVA NAME 'com/xxx/xxx/xxx/xxx.ClassName(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String) return java.lang.String';

這個語句需要注意2點其一就是Java方法的包名需要用“/”而分割類名用“.” 這個當時卡了很久算是一個坑,其二Java方法的傳入類型要和數據庫類型相對應例如: NVARCHAR2和String ,更詳細的對應關係可以看Oracle 官方的文檔

那如果需要傳入的是一大堆表數據呢? 其實這種類型對應並不支持複雜的類型,要想獲得複雜數據 需要在Jar包中加入JDBC包使用JDBC來建立查詢語句來獲取數據 獲取JDBC的Connection對象的時候可以可以這樣寫,不用寫數據庫鏈接名和賬號密碼,可以直接使用數據庫的連接。
Connection conn = DriverManager.getConnection(“jdbc:default:connection:”);

詳情也可以參照Oracle官方文檔中的這一單元:Using the Server-Side Internal JDBC Driver

本文主要介紹了如何導入和如何封裝Java方法,如果你的業務邏輯比較複雜需要用到第三方jar包來實現自己的業務邏輯,或者發生了ORA-29521: referenced name java/xxx/xxx/xxx could not be found錯誤那麼這些問題將在下一篇中說到。

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

【其他文章推薦】

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

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

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

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

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

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

springboot mybatis plus多數據源輕鬆搞定 (上)

在開發中經常會遇到一個程序需要調用多個數據庫的情況,總得來說分為下面的幾種情況:

  1. 一個程序會調用不同結構的兩個數據庫。
  2. 讀寫分離,兩個數據結構可能一樣高,但是不同的操作針對不同的數據庫。
  3. 混合情況,既有不同的結構的數據庫,也可能存在讀寫分離的情況。

下面針對第一種情況,提供一個解決方案。

解決思路

因為兩個數據庫的功能和結構不一樣,所以可以根據功能和結構把DAO分為兩個package。然後再mapperscan中指定不同的package對接不同的數據源,即可達到多個數據源的共存。

配置yml中的數據源設置

spring:
  datasource:
    emanage:
      url: jdbc:mysql://127.0.0.1:3306/emanage?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC&useAffectedRows=true
      username: root
      password: ******
      driver-class-name: com.mysql.cj.jdbc.Driver
    ehr:
      url: jdbc:mysql://127.0.0.1:3306/ehr?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC&useAffectedRows=true
      username: root
      password: ********
      driver-class-name: com.mysql.cj.jdbc.Driver

為了不必要的干擾,我把druid數據源的配置部分給刪除了。

建立兩個datasource的配置

datasource1的 配置

@Configuration
@MapperScan(basePackages = {"com.emanage.ehr.mapper.emanage"},sqlSessionTemplateRef = "sqlTemplate1")
public class DataSourceConfig1 {
    @Bean(name = "datasource1")
    @ConfigurationProperties(prefix = "spring.datasource.emanage")
    public DruidDataSource druidDataSource1()
    {
        return DruidDataSourceBuilder.create().build();
    }


    @Bean(name = "sqlFactory1")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("datasource1") DruidDataSource dataSource)
            throws Exception
    {
        MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        factoryBean.setMapperLocations(resolver.getResources("classpath*:mapper/emanage/**Mapper.xml"));
        return factoryBean.getObject();
    }

    @Bean(name = "sqlTemplate1")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlFactory1") SqlSessionFactory sqlSessionFactory)
    {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

datasource2的配置

@Configuration
@MapperScan(basePackages = {"com.emanage.ehr.mapper.ehr"},sqlSessionTemplateRef = "sqlTemplate2")
public class DataSourceConfig2 {
    @Bean(name = "datasource2")
    @ConfigurationProperties(prefix = "spring.datasource.ehr")
    public DataSource druidDataSource1()
    {
        return DataSourceBuilder.create().build();
    }


    @Bean(name = "sqlFactory2")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("datasource2") DataSource dataSource)
            throws Exception
    {
        MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        factoryBean.setMapperLocations(resolver.getResources("classpath*:mapper/ehr/**Mapper.xml"));
        return factoryBean.getObject();
    }
    @Bean(name = "sqlTemplate2")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlFactory2") SqlSessionFactory sqlSessionFactory)
    {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

兩個datasource的配置基本上一樣。就是建立datasource,sqlsessionFactory,sqlSessionTemplate的注入。然後通過mapperscan來指定具體什麼包採用什麼數據源。然後再對應包里就和以前單數據源一樣操作即可。

注意事項:

如果用myBatis, SqlSessionFactory 部分可以使用SqlSessionFactoryBean來生成。但是如果用mybatis plus一定要用MybatisSqlSessionFactoryBean 來生成SqlSessionFactory。否則會報錯 ,無法直接通過BaseMapper去調用查詢。
如果要再不同的包中混合上XML進行調用。需要在SqlSessionFactory的配置中設置factoryBean.setMapperLocations(resolver.getResources(“classpath*:mapper/ehr/**Mapper.xml”));

優缺點:

優點:
簡單,通過簡單的設置。就可以滿足大多數的情況。

  • 缺點 *:只適合多個數據源的結構完全不一樣,通過package可以分來的方式來調用,不能靈活的在一個package下面隨心所欲的調用數據源。

那麼問題來了:如果要在一個package下面,想調用哪個就調用哪個數據源怎麼辦呢?有時間了,下一篇文章寫寫另外的實現方式。

希望對初學者有價值,如果有疑問歡迎留言交流。

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

【其他文章推薦】

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

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

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

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

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

使用Kubeflow構建機器學習流水線

在此前的文章中,我已經向你介紹了Kubeflow,這是一個為團隊設置的機器學習平台,需要構建機器學習流水線。

在本文中,我們將了解如何採用現有的機器學習詳細並將其變成Kubeflow的機器學習流水線,進而可以部署在Kubernetes上。在進行本次練習的時候,請考慮你該如何將現有的機器學習項目轉換到Kubeflow上。

我將使用Fashion MNIST作為例子,因為在本次練習中模型的複雜性並不是我們需要解決的主要目標。對於這一簡單的例子,我將流水線分為3個階段:

  • Git clone代碼庫

  • 下載並重新處理訓練和測試數據

  • 訓練評估

當然,你可以根據自己的用例將流水線以任意形式拆分,並且可以隨意擴展流水線。

獲取代碼

你可以從Github上獲取代碼:

% git clone https://github.com/benjamintanweihao/kubeflow-mnist.git

以下是我們用來創建流水線的完整清單。實際上,你的代碼很可能跨多個庫和文件。在我們的例子中,我們將代碼分為兩個腳本,preprocessing.py和train.py。

from tensorflow import keras
import argparse
import os
import pickle


def preprocess(data_dir: str):
    fashion_mnist = keras.datasets.fashion_mnist
    (train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()

    train_images = train_images / 255.0
    test_images = test_images / 255.0

    os.makedirs(data_dir, exist_ok=True)

    with open(os.path.join(data_dir, 'train_images.pickle'), 'wb') as f:
  pickle.dump(train_images, f)

    with open(os.path.join(data_dir, 'train_labels.pickle'), 'wb') as f:
  pickle.dump(train_labels, f)

    with open(os.path.join(data_dir, 'test_images.pickle'), 'wb') as f:
        pickle.dump(test_images, f)

    with open(os.path.join(data_dir, 'test_labels.pickle'), 'wb') as f:
        pickle.dump(test_labels, f)

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Kubeflow MNIST training script')
    parser.add_argument('--data_dir', help='path to images and labels.')
    args = parser.parse_args()

    preprocess(data_dir=args.data_dir)

處理腳本採用單個參數data_dir。它下載並預處理數據,並將pickled版本保存在data_dir中。在生產代碼中,這可能是TFRecords的存儲目錄。

train.py

import calendar
import os
import time

import tensorflow as tf
import pickle
import argparse

from tensorflow import keras
from constants import PROJECT_ROOT


def train(data_dir: str):
    # Training
    model = keras.Sequential([
          keras.layers.Flatten(input_shape=(28, 28)),
          keras.layers.Dense(128, activation='relu'),
          keras.layers.Dense(10)])

    model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
                  metrics=['accuracy'])

    with open(os.path.join(data_dir, 'train_images.pickle'), 'rb') as f:
        train_images = pickle.load(f)

    with open(os.path.join(data_dir, 'train_labels.pickle'), 'rb') as f:
        train_labels = pickle.load(f)

    model.fit(train_images, train_labels, epochs=10)

    with open(os.path.join(data_dir, 'test_images.pickle'), 'rb') as f:
        test_images = pickle.load(f)

    with open(os.path.join(data_dir, 'test_labels.pickle'), 'rb') as f:
        test_labels = pickle.load(f)

    # Evaluation
    test_loss, test_acc = model.evaluate(test_images, test_labels, verbose=2)

    print(f'Test Loss: {test_loss}')
    print(f'Test Acc: {test_acc}')

    # Save model
    ts = calendar.timegm(time.gmtime())
    model_path = os.path.join(PROJECT_ROOT, f'mnist-{ts}.h5')
    tf.saved_model.save(model, model_path)

    with open(os.path.join(PROJECT_ROOT, 'output.txt'), 'w') as f:
        f.write(model_path)
        print(f'Model written to: {model_path}')


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Kubeflow FMNIST training script')
    parser.add_argument('--data_dir', help='path to images and labels.')
    args = parser.parse_args()

    train(data_dir=args.data_dir)

在train.py中,將建立模型,並使用data_dir指定訓練和測試數據的位置。模型訓練完畢並且開始執行評估后,將模型寫入帶有時間戳的路徑。請注意,該路徑也已寫入output.txt。稍後將對此進行引用。

開發Kubeflow流水線

為了開始創建Kubeflow流水線,我們需要拉取一些依賴項。我準備了一個environment.yml,其中包括了kfp 0.5.0、tensorflow以及其他所需的依賴項。

你需要安裝Conda,然後執行以下步驟:

% conda env create -f environment.yml
% source activate kubeflow-mnist
% python preprocessing.py --data_dir=/path/to/data
% python train.py --data_dir=/path/to/data

現在我們來回顧一下我們流水線中的幾個步驟:

  • Git clone代碼庫

  • 下載並預處理訓練和測試數據

  • 訓練並進行評估

在我們開始寫代碼之前,需要從宏觀上了解Kubeflow流水線。

流水線由連接組件構成。一個組件的輸出成為另一個組件的輸入,每個組件實際上都在容器中執行(在本例中為Docker)。將發生的情況是,我們會執行一個我們稍後將要指定的Docker鏡像,它包含了我們運行preprocessing.py和train.py所需的一切。當然,這兩個階段會有它們的組件。

我們還需要額外的一個鏡像以git clone項目。我們需要將項目bake到Docker鏡像,但在實際項目中,這可能會導致Docker鏡像的大小膨脹。

說到Docker鏡像,我們應該先創建一個。

Step0:創建一個Docker鏡像

如果你只是想進行測試,那麼這個步驟不是必須的,因為我已經在Docker Hub上準備了一個鏡像。這是Dockerfile的全貌:

FROM tensorflow/tensorflow:1.14.0-gpu-py3
LABEL MAINTAINER "Benjamin Tan <benjamintanweihao@gmail.com>"
SHELL ["/bin/bash", "-c"]

# Set the locale
RUN echo 'Acquire {http::Pipeline-Depth "0";};' >> /etc/apt/apt.conf
RUN DEBIAN_FRONTEND="noninteractive"
RUN apt-get update  && apt-get -y install --no-install-recommends locales && locale-gen en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8

RUN apt-get install -y --no-install-recommends \
    wget \
    git \
    python3-pip \
    openssh-client \
    python3-setuptools \
    google-perftools && \
    rm -rf /var/lib/apt/lists/*

# install conda
WORKDIR /tmp
RUN wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-4.7.12-Linux-x86_64.sh -O ~/miniconda.sh && \
    /bin/bash ~/miniconda.sh -b -p /opt/conda && \
    rm ~/miniconda.sh && \
    ln -s /opt/conda/etc/profile.d/conda.sh /etc/profile.d/conda.sh && \
    echo ". /opt/conda/etc/profile.d/conda.sh" >> ~/.bashrc

# build conda environments
COPY environment.yml /tmp/kubeflow-mnist/conda/
RUN /opt/conda/bin/conda update -n base -c defaults conda
RUN /opt/conda/bin/conda env create -f /tmp/kubeflow-mnist/conda/environment.yml
RUN /opt/conda/bin/conda clean -afy

# Cleanup
RUN rm -rf /workspace/{nvidia,docker}-examples && rm -rf /usr/local/nvidia-examples && \
    rm /tmp/kubeflow-mnist/conda/environment.yml

# switch to the conda environment
RUN echo "conda activate kubeflow-mnist" >> ~/.bashrc
ENV PATH /opt/conda/envs/kubeflow-mnist/bin:$PATH
RUN /opt/conda/bin/activate kubeflow-mnist

# make /bin/sh symlink to bash instead of dash:
RUN echo "dash dash/sh boolean false" | debconf-set-selections && \
    DEBIAN_FRONTEND=noninteractive dpkg-reconfigure dash

# Set the new Allocator
ENV LD_PRELOAD /usr/lib/x86_64-linux-gnu/libtcmalloc.so.

關於Dockerfile值得關注的重要一點是Conda環境是否設置完成並準備就緒。要構建鏡像:

% docker build -t your-user-name/kubeflow-mnist . -f Dockerfile
% docker push your-user-name/kubeflow-mnist

那麼,現在讓我們來創建第一個組件!

在pipeline.py中可以找到以下代碼片段。

Step1:Git Clone

在這一步中,我們將從遠程的Git代碼庫中執行一個git clone。特別是,我想要向你展示如何從私有倉庫中進行git clone,因為這是大多數企業的項目所在的位置。當然,這也是一個很好的機會來演示Rancher中一個很棒的功能,它能簡單地添加諸如SSH密鑰之類的密鑰。

使用Rancher添加密鑰

訪問Rancher界面。在左上角,選擇local,然後選擇二級菜單的Default:

然後,選擇Resources下的Secrets

你應該看到一個密鑰的列表,它們正在被你剛剛選擇的集群所使用。點擊Add Secret:

使用你在下圖中所看到的值來填寫該頁面。如果kubeflow沒有在命名空間欄下展示出來,你可以通過選擇Add to a new namespace並且輸入kubeflow簡單地創建一個。

確保Scope僅是個命名空間。如果將Scope設置為所有命名空間,那麼將使得在Default項目中的任意工作負載都能夠使用你的ssh密鑰。

在Secret Values中,key是id_rsa,值是id_rsa的內容。完成之後,點擊Save。

如果一些進展順利,你將會看到下圖的內容。現在你已經成功地在kubeflow命名空間中添加了你的SSH密鑰,並且無需使用kubectl!

既然我們已經添加了我們的SSH key,那麼是時候回到代碼。我們如何利用新添加的SSH密鑰來訪問私有git倉庫?

def git_clone_darkrai_op(repo_url: str):

    volume_op = dsl.VolumeOp(
        name="create pipeline volume",
        resource_name="pipeline-pvc",
        modes=["ReadWriteOnce"],
        size="3Gi"
    )

    image = 'alpine/git:latest'

    commands = [
        "mkdir ~/.ssh",
        "cp /etc/ssh-key/id_rsa ~/.ssh/id_rsa",
        "chmod 600 ~/.ssh/id_rsa",
        "ssh-keyscan bitbucket.org >> ~/.ssh/known_hosts",
        f"git clone {repo_url} {PROJECT_ROOT}",
        f"cd {PROJECT_ROOT}"]

    op = dsl.ContainerOp(
        name='git clone',
        image=image,
        command=['sh'],
        arguments=['-c', ' && '.join(commands)],
        container_kwargs={'image_pull_policy': 'IfNotPresent'},
        pvolumes={"/workspace": volume_op.volume}
    )

    # Mount Git Secrets
    op.add_volume(V1Volume(name='ssh-key-volume',
                           secret=V1SecretVolumeSource(secret_name='ssh-key-secret')))
    op.add_volume_mount(V1VolumeMount(mount_path='/etc/ssh-key', name='ssh-key-volume', read_only=True))

    return op

首先,創建一個Kubernetes volume,預定義大小為3Gi。其次,將image變量指定為我們將要使用的alpine/git Docker鏡像。之後是在Docker容器中執行的命令列表。這些命令實質上是設置SSH密鑰的,以便於流水線可以從私有倉庫git clone,或者使用git://URL來代替 https://。

該函數的核心是下面一行,返回一個dsl.ContainerOp。

command和arguments指定了執行鏡像之後需要執行的命令。

最後一個變量十分有趣,是pvolumes,它是Pipeline Volumes簡稱。它創建一個Kubernetes volume並允許流水線組件來共享單個存儲。該volume被掛載在/workspace上。那麼這個組件要做的就是把倉庫git clone到/workspace中。

使用Secrets

再次查看命令和複製SSH密鑰的位置。

流水線volume在哪裡創建呢?當我們將所有組件都整合到一個流水線中時,就會看到創建好的volume。我們在/etc/ssh-key/上安裝secrets:

op.add_volume_mount(V1VolumeMount(mount_path='/etc/ssh-key', name='ssh-key-volume', read_only=True))

請記得我們將secret命名為ssh-key-secret:

op.add_volume(V1Volume(name='ssh-key-volume',
                           secret=V1SecretVolumeSource(secret_name='ssh-key-secret')))

通過使用相同的volume名稱ssh-key-volume,我們可以把一切綁定在一起。

Step2:預處理

def preprocess_op(image: str, pvolume: PipelineVolume, data_dir: str):
    return dsl.ContainerOp(
        name='preprocessing',
        image=image,
        command=[CONDA_PYTHON_CMD, f"{PROJECT_ROOT}/preprocessing.py"],
        arguments=["--data_dir", data_dir],
        container_kwargs={'image_pull_policy': 'IfNotPresent'},
        pvolumes={"/workspace": pvolume}
    )

正如你所看到的, 預處理步驟看起來十分相似。

image指向我們在Step0中創建的Docker鏡像。

這裏的command使用指定的conda python簡單地執行了preprocessing.py腳本。變量data_dir被用於執行preprocessing.py腳本。

在這一步驟中pvolume將在/workspace里有倉庫,這意味着我們所有的腳本在這一階段都是可用的。並且在這一步中預處理數據會存儲在/workspace下的data_dir中。

Step3:訓練和評估

def train_and_eval_op(image: str, pvolume: PipelineVolume, data_dir: str, ):
    return dsl.ContainerOp(
        name='training and evaluation',
        image=image,
        command=[CONDA_PYTHON_CMD, f"{PROJECT_ROOT}/train.py"],
        arguments=["--data_dir", data_dir],
        file_outputs={'output': f'{PROJECT_ROOT}/output.txt'},
        container_kwargs={'image_pull_policy': 'IfNotPresent'},
        pvolumes={"/workspace": pvolume}
    )

最後,是時候進行訓練和評估這一步驟。這一步唯一的區別在於file_outputs變量。如果我們再次查看train.py,則有以下代碼段:

    with open(os.path.join(PROJECT_ROOT, 'output.txt'), 'w') as f:
        f.write(model_path)
        print(f'Model written to: {model_path}')

我們正在將模型路徑寫入名為output.txt的文本文件中。通常,可以將其發送到下一個流水線組件,在這種情況下,該參數將包含模型的路徑。

將一切放在一起

要指定流水線,你需要使用dsl.pipeline來註釋流水線功能:

@dsl.pipeline(
    name='Fashion MNIST Training Pipeline',
    description='Fashion MNIST Training Pipeline to be executed on KubeFlow.'
)
def training_pipeline(image: str = 'benjamintanweihao/kubeflow-mnist',
                      repo_url: str = 'https://github.com/benjamintanweihao/kubeflow-mnist.git',
                      data_dir: str = '/workspace'):
    git_clone = git_clone_darkrai_op(repo_url=repo_url)

    preprocess_data = preprocess_op(image=image,
                                    pvolume=git_clone.pvolume,
                                    data_dir=data_dir)

    _training_and_eval = train_and_eval_op(image=image,
                                           pvolume=preprocess_data.pvolume,
                                           data_dir=data_dir)

if __name__ == '__main__':
    import kfp.compiler as compiler
    compiler.Compiler().compile(training_pipeline, __file__ + '.tar.gz')

還記得流水線組件的輸出是另一個組件的輸入嗎?在這裏,git clone、container_op的pvolume將傳遞到preprocess_cp。

最後一部分將pipeline.py轉換為可執行腳本。最後一步是編譯流水線:

% dsl-compile --py pipeline.py --output pipeline.tar.gz

上傳並執行流水線

現在要進行最有趣的部分啦!第一步,上傳流水線。點擊Upload a pipeline:

接下來,填寫Pipeline Name和Pipeline Description,然後選擇Choose file並且指向pipeline.tar.gz以上傳流水線。

下一頁將會展示完整的流水線。我們所看到的是一個流水線的有向無環圖,在本例中這意味着依賴項會通往一個方向並且它不包含循環。點擊藍色按鈕Create run 以開始訓練。

大部分字段已經已經填寫完畢。請注意,Run parameters與使用@ dsl.pipeline註釋的training_pipeline函數中指定的參數相同:

最後,當你點擊藍色的Start按鈕時,整個流水線就開始運轉了!你點擊每個組件並查看日誌就能夠知道發生了什麼。當整個流水線執行完畢時,在所有組件的右方會有一個綠色的確認標誌,如下所示:

結論

如果你從上一篇文章開始就一直在關注,那麼你應該已經安裝了Kubeflow,並且應該能體會到大規模管理機器學習項目的複雜性。

在這篇文章中,我們先介紹了為Kubeflow準備一個機器學習項目的過程,然後是構建一個Kubeflow流水線,最後是使用Kubeflow接口上傳並執行流水線。這種方法的奇妙之處在於,你的機器學習項目可以是簡單的,也可以是複雜的,只要你願意,你就可以使用相同的技術。

因為Kubeflow使用Docker容器作為組件,你可以自由地加入任何你喜歡的工具。而且由於Kubeflow運行在Kubernetes上,你可以讓Kubernetes處理機器學習工作負載的調度。

我們還了解了一個我喜歡的Rancher功能,它十分方便,可以輕鬆添加secrets。立刻,你就可以輕鬆地組織secrets(如SSH密鑰),並選擇將其分配到哪個命名空間,而無需為Base64編碼而煩惱。就像Rancher的應用商店一樣,這些便利性使Kubernetes的工作更加愉快,更不容易出錯。

當然,Rancher提供的服務遠不止這些,我鼓勵你自己去做一些探索。我相信你會偶然發現一些讓你大吃一驚的功能。Rancher作為一個開源的企業級Kubernetes管理平台,Run Kubernetes Everywhere一直是我們的願景和宗旨。開源和無廠商鎖定的特性,可以讓用戶輕鬆地在不同的基礎設施部署和使用Rancher。此外,Rancher極簡的操作體驗也可以讓用戶在不同的場景中利用Rancher提升效率,幫助開發人員專註於創新,而無需在繁瑣的小事中浪費精力。

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

【其他文章推薦】

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

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

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

※超省錢租車方案

※FB行銷專家,教你從零開始的技巧

淺析pplx庫的設計與實現。

主要有三部分組成,threadpool,scheduler,task。

 

 三者關係如上圖示,pplx只着重實現了task部分功能,scheduler跟threadpool只是簡略實現。

 threadpool主要依賴boost.asio達到跨平台的目標,cpprestsdk的 io操作同時也依賴這個threadpool。

pplx提供了兩個版本的scheduler,分別是

linux_scheduler依賴boost.asio.threadpool。

window_schedule依賴win32 ThreadPool。

默認的scheduler只是簡單地將work投遞到threadpool進行分派。

用戶可以根據自己需要,實現scheduler_interface,提供複雜的調度。

 

每個task關聯着一個_Task_impl實現體,一個_TaskCollection_t(喚醒事件,後繼任務隊列,這個隊列的任務之間的關係是並列的),還有一個_PPLTaskHandle代碼執行單元。

task,并行執行的單位任務。通過scheduler將代碼執行單元調度到線程去執行。

task提供類似activeobject模式的功能,可以看作是一個future,通過get()同步阻塞等待執行結果。

task提供拓撲模型,通過then()創建後續task,並作為後繼執行任務。注意的是每個task可以接受不限數量的then(),這些後繼任務之間並不串行。例 task().then().then()串行,(task1.then(), task1.then())并行。一個任務在執行完成時,會將結果傳遞給它的所有直接後繼執行任務。

 

此外,task拓撲除了then()函數外,還可以在執行lambda中添加并行分支,然後可以在後繼任務中同步這些分支。

也就是說後繼任務同步原本task拓撲外的task拓撲才能繼續執行。

 1 auto fork0 =
 2      task([]()->task<void>{
 3         auto fork1 = 
 4  task([]()->task<void>{ 5 auto fork2 = 6  task([](){ 7 // do your fork2 work 8 9  }); 10 // do your fork1 work 11 12 return fork2; 13 }).then([](task<void>& frk2){ frk2.wait(); }); // will sync fork2 14 // do your fork0 work 15 16 return fork1; 17 }).then([](task<void>& frk1){ frk1.wait(); }); // will sync fork1 18 fork0.wait(); // sync fork1, fork2

上面的方式有一個問題,如果裡層的fork先完成,將不要阻塞線程,但是外層fork先完成就不得不阻塞線程等待內層fork完成。

所以可以用when_all

task<task<void> >([]()->task<void> {
    std::vector<task<void> > forks; forks.push_back( task([]() { /* do fork0 work */ }) ); forks.push_back( task([]() { /* do fork1 work */ }) ); forks.push_back( task([]() { /* do fork2 work */ }) ); forks.push_back( task([]() { /* do fork3 work */ }) ); return when_all(std::begin(forks), std::end(forks)); }).then([](task<void> forks){ forks.wait(); }).wait();

通過上面的方式,也可以在lambda中,將其它task拓撲插入到你原來的task拓撲。

在include/cpprest/atreambuf.h實現的_do_while就是這樣一個例子

template<class F, class T = bool>
pplx::task<T> _do_while(F func)
{
    pplx::task<T> first = func();
    return first.then([=](bool guard) -> pplx::task<T> {
        if (guard)
            return pplx::details::_do_while<F, T>(func);
        else
            return first;
    });
}

如果func不返回一個false,就會無限地在first.then()這兩任務拓撲結束前,再插入多一個first.then()任務拓撲,無限地順序地執行下去,如loop一樣地進行。

  

task結束,分兩種情況,完成以及取消。取消執行,只能在執行代碼時通過拋出異常,task並沒有提供取消的接口。任務在執行過程中拋出的異常,就會被task捕捉,並暫存異常,然後取消執行。異常在wait()時重新拋出。下面的時序分析可以看到全過程 。

 

 

值得注意的是,PPL中task原本的設計是的有Async與Inline之分的。在_Task_impl_base::_Wait()有一小段註釋說明

// If this task was created from a Windows Runtime async operation, do not attempt to inline it. The
// async operation will take place on a thread in the appropriate apartment Simply wait for the completed
// event to be set.
            
                

也就是task除了由scheduler調度到線程池分派執行,還可以強制在wait()函數內分派執行,後繼task也不必再次調度而可以在當前線程繼續分派執行。但是pplx沒有實現

class _TaskCollectionImpl
{
    ...
    void _Cancel() { // No cancellation support  } void _RunAndWait() { // No inlining support yet  _Wait(); }

 

現在再來比較 task<_ReturnType> 與 task< task<_ReturnType > >,當一個前驅任務拋出異常中止后,如果前驅任務是task<_ReturnType>的話,後續任務的lambda參數就是_ReturnType,由後續任務執行_Continue時代為執行了前驅任務的get(),這時就會rethrow異常,然後就直接中止後續任務。但是如果後續任務的lambda參數是task<_ReturnType>的話,用戶的lambda就有機會處理前驅任務的錯誤異常。所以就有了 task_from_result<_ReturnType>跟task_from_exception兩個函數,將結果或異常轉化成task,以符合後續任務的lambda的參數要求。

 

 下面是對task的時序分析。

開始的task創建_InitialTaskHandle, 一種只能用於始首的Handle執行單元。

 

通過then()添加的task,創建_ContinuationTaskHandle,(一種可以入鏈的後繼執行單元),並暫存起來。

 

當一個任務在線程池中分派結束時,就會將所有通過then()添加到它結尾的後繼任務一次過向scheduler調度出去。

任務只能通過拋出異常從而自己中止執行,task並暫存異常(及錯誤信息)。

 

 

 後繼任務被調度到線程池繼續分派執行。

 

這裏順便討論一個開銷,在window版本中,每個task都有一個喚醒事件,使用事件內核對象,都要創建釋放一個內核對象,在高并行任務時,可能會消耗過多內核對象,消耗句柄數。

並且continuation後繼任務,在默認scheduler調度下,不會在同一線程中分派,所有後繼任務都會簡單投遞到線程池。由線程池去決定分派的線程。所以由then()串行起來的任務可能會由不同的線程順序分派,從而產生開銷。因為pplx並沒有實現 Inline功能,所有task都會視作Async重新調度到線程池。

 

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

Git 居然可以用來跟女神聊天?

Git 是用來做啥的?想必碼農朋友都知道,Git 是版本控制軟件,是軟件開發過程中團隊協作不可或缺的軟件。

但是,作為版本控制軟件的 Git ,能跟聊天工具扯上關係嗎?這二者似乎毫無關係,但腦洞大開的外國朋友 Ephi Gabay 就開發了一個 GIC ,活生生將 Git 改造成了一個聊天工具,有了它你就可以跟女神親密溝通了!

這位哥們是用 Node.js 寫了這麼一個工具,將 Git 改裝成後台數據庫!更詳細的,他其實是把每一句聊天作為 commit 的內容提交到倉庫里!所以,執行 git log 時,可以看到完整的對話過程。想必當年 Linus 怎麼也不會想到,他寫的 Git 會這麼被人改造!

這個項目的地址如下:

https://github.com/ephigabay/GIC

下面良許帶你一步步實現這個騷過程。

1. 創建一個倉庫

因為整個聊天的過程,其實就是不停在提交的過程,所以我們需要創建一個倉庫。這個倉庫,肯定不能是你現在工作用的倉庫,否則你之前的工作過程就全玩完了。

倉庫的創建不難吧?這裏簡單演示一下:

mkdir gitchat
cd gitchat
git init
echo "chat logs" > README
git add README
git commit -m 'fist commit'

2. 安裝 GIC

既然 GIC 是基於 Git 的,那麼 Git 肯定是需要安裝的。而且 GIC 是使用 Node.js 編寫的,所以需要安裝 nodejs 。後面我們還需要用到 npm 命令,所以我們還需要安裝一個 npm 。

如果是 Ubuntu 平台的話,安裝過程可以使用以下命令:

sudo apt-get install git nodejs npm

如果是其它平台,請參照各自平台的安裝指導手冊。

然後,我們需要將 GIC 這個項目拷到自己的電腦上,如下:

git clone https://github.com/ephigabay/GIC GIC

等 GIC 完整拷備到電腦上后,我們進入到目錄里並安裝一些依賴文件:

cd GIC
npm install

這個安裝過程可能要花費一些時間,靜靜等待即可。

3. 配置 GIC

對於 GIC 我們只需要配置第 1 步所建的那個倉庫路徑即可,需要編輯 config.js 文件的 gitRepo 字段:

module.exports = {
    gitRepo: '/home/pi/tests/gitchat/.git', #配置你的聊天倉庫路徑
    messageCheckInterval: 500,
    branchesCheckInterval: 5000
};

在正式開始聊天之前,我們先試一下配置是否正確:

git clone --quiet /home/pi/tests/gitchat/.git > /dev/null

如果上面那步沒報錯的話,說明你所配置的路徑就是正確的。

接下來,我們就可以正式開始和女神聊天了。

4. 使用 Git 開始聊天

開始聊天時,我們可以在 GIC 目錄里使用以下命令啟動聊天:

npm start

之後,你就會看到一個文字版的聊天窗口了。左邊就是聊天內容,右邊是分支。不同的分支就是不同的通道,相當於不同的聊天室,裏面的聊天內容也是不同的。

但是,請注意,如果倉庫里你當前所在的分支是 master 分支,那麼你就不能在這個分支里聊天,要切到其它分支聊天,否則會報錯。

如果要多人聊天的話,每個用戶只需進到 GIC 目錄,然後執行 npm start 命令即可參与聊天。

前面說了,這個聊天的過程其實是依託 git log ,所以我們在 git log 里可以看到完整的聊天記錄:

pi@raspberrypi:~/tests/gitchat $ git log --pretty=format:"%p %cn %s" dev
371a477 evis hao a, wanshange jiu qu!
b6cc4ae alvin yan wo ye hen hao, yao bu yao qu gongyuan zouzou?
7bfea8f evis fine, good, and you?
017d82f alvin yan hello evis, how are you?
 alvin yan init commit

5. 退出聊天

有兩種方法:

  1. 按一下 Esc ,再按 ctrl+C
  2. 執行以下命令:
sudo kill `pgrep npm`

公眾號:良許Linux

有收穫?希望老鐵們來個三連擊,給更多的人看到這篇文章

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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