掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
有時(shí)作為數(shù)據(jù)科學(xué)家,我們忘了自己是干什么的。我們主要是開(kāi)發(fā)者,然后是研究者,最后可能是數(shù)學(xué)家。我們的首要責(zé)任是快速開(kāi)發(fā)沒(méi)有錯(cuò)誤的解決方案。

為平房等地區(qū)用戶提供了全套網(wǎng)頁(yè)設(shè)計(jì)制作服務(wù),及平房網(wǎng)站建設(shè)行業(yè)解決方案。主營(yíng)業(yè)務(wù)為網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站、平房網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠(chéng)的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長(zhǎng)期合作。這樣,我們也可以走得更遠(yuǎn)!
就因?yàn)槲覀兡軜?gòu)建模型并不意味著我們就是神,這沒(méi)有給我們編寫垃圾代碼的自由。
自一開(kāi)始,我犯過(guò)很多錯(cuò)誤,想透露一下我認(rèn)為是機(jī)器學(xué)習(xí)工程最常見(jiàn)的技能。我認(rèn)為,這也是眼下業(yè)界最缺乏的技能。
我稱他們?yōu)椴欢浖臄?shù)據(jù)科學(xué)家,因?yàn)樗麄冎泻芏嗳硕际遣皇怯?jì)算機(jī)專業(yè)科班出身的工程師。而我本人就是那樣。
如果要聘一位優(yōu)秀的數(shù)據(jù)科學(xué)家和一位優(yōu)秀的機(jī)器學(xué)習(xí)工程師,我會(huì)聘后者。
1. 學(xué)習(xí)編寫抽象類。
一旦你開(kāi)始編寫抽象類,就知道可以如何讓你的代碼庫(kù)清晰許多。它們強(qiáng)制執(zhí)行同樣的方法和方法名稱。如果很多人從事同一個(gè)項(xiàng)目,每個(gè)人會(huì)開(kāi)始采用不同的方法。這會(huì)造成嚴(yán)重的混亂。
- import os
- from abc import ABCMeta, abstractmethod
- class DataProcessor(metaclass=ABCMeta):
- """Base processor to be used for all preparation."""
- def __init__(self, input_directory, output_directory):
- self.input_directory = input_directory
- self.output_directory = output_directory
- @abstractmethod
- def read(self):
- """Read raw data."""
- @abstractmethod
- def process(self):
- """Processes raw data. This step should create the raw dataframe with all the required features. Shouldn't implement statistical or text cleaning."""
- @abstractmethod
- def save(self):
- """Saves processed data."""
- class Trainer(metaclass=ABCMeta):
- """Base trainer to be used for all models."""
- def __init__(self, directory):
- self.directory = directory
- self.model_directory = os.path.join(directory, 'models')
- @abstractmethod
- def preprocess(self):
- """This takes the preprocessed data and returns clean data. This is more about statistical or text cleaning."""
- @abstractmethod
- def set_model(self):
- """Define model here."""
- @abstractmethod
- def fit_model(self):
- """This takes the vectorised data and returns a trained model."""
- @abstractmethod
- def generate_metrics(self):
- """Generates metric with trained model and test data."""
- @abstractmethod
- def save_model(self, model_name):
- """This method saves the model in our required format."""
- class Predict(metaclass=ABCMeta):
- """Base predictor to be used for all models."""
- def __init__(self, directory):
- self.directory = directory
- self.model_directory = os.path.join(directory, 'models')
- @abstractmethod
- def load_model(self):
- """Load model here."""
- @abstractmethod
- def preprocess(self):
- """This takes the raw data and returns clean data for prediction."""
- @abstractmethod
- def predict(self):
- """This is used for prediction."""
- class BaseDB(metaclass=ABCMeta):
- """ Base database class to be used for all DB connectors."""
- @abstractmethod
- def get_connection(self):
- """This creates a new DB connection."""
- @abstractmethod
- def close_connection(self):
- """This closes the DB connection."""
2. 搞定最上面的seed。
試驗(yàn)的可重現(xiàn)性很重要,而seed是大敵。處理好seed。不然,它會(huì)導(dǎo)致神經(jīng)網(wǎng)絡(luò)中訓(xùn)練/測(cè)試數(shù)據(jù)的不同分隔和權(quán)重的不同初始化。這會(huì)導(dǎo)致結(jié)果不一致。
- def set_seed(args):
- random.seed(args.seed)
- np.random.seed(args.seed)
- torch.manual_seed(args.seed)
- if args.n_gpu > 0:
- torch.cuda.manual_seed_all(args.seed)
3. 從幾行入手。
如果你的數(shù)據(jù)太龐大,又處在編程的后期階段(比如清理數(shù)據(jù)或建模),就使用nrows避免每次加載龐大數(shù)據(jù)。如果你只想測(cè)試代碼、不想實(shí)際運(yùn)行全部代碼,就使用這招。
如果你的本地PC配置不足以處理數(shù)據(jù)大小,這一招很有用,但你喜歡在Jupyter/VS code/Atom上進(jìn)行本地開(kāi)發(fā)。
- df_train = pd.read_csv(‘train.csv’, nrows=1000)
4. 預(yù)料失敗(這是成熟開(kāi)發(fā)者的標(biāo)志)。
始終檢查數(shù)據(jù)中的NA,因?yàn)檫@些會(huì)在以后給你帶來(lái)問(wèn)題。即便你目前的數(shù)據(jù)沒(méi)有任何NA,也并不意味著它不會(huì)出現(xiàn)在將來(lái)的再訓(xùn)練循環(huán)中。所以無(wú)論如何要檢查。
- print(len(df))
- df.isna().sum()
- df.dropna()
- print(len(df))
5. 顯示處理的進(jìn)度。
你在處理龐大數(shù)據(jù)時(shí),知道要花多少時(shí)間、處于整個(gè)處理過(guò)程中的哪個(gè)環(huán)節(jié),絕對(duì)讓人安心。
方法1 — tqdm
- from tqdm import tqdm
- import time
- tqdm.pandas()
- df['col'] = df['col'].progress_apply(lambda x: x**2)
- text = ""
- for char in tqdm(["a", "b", "c", "d"]):
- time.sleep(0.25)
- text = text + char
方法2 — fastprogress
- from fastprogress.fastprogress import master_bar, progress_bar
- from time import sleep
- mb = master_bar(range(10))
- for i in mb:
- for j in progress_bar(range(100), parent=mb):
- sleep(0.01)
- mb.child.comment = f'second bar stat'
- mb.first_bar.comment = f'first bar stat'
- mb.write(f'Finished loop {i}.')
6. Pandas可能很慢。
如果你接觸過(guò)pandas,就知道它有時(shí)會(huì)變得多慢,尤其是執(zhí)行g(shù)roupby操作時(shí)。不必絞盡腦汁為提速尋找“出色的”解決方案,只要更改一行代碼,使用modin就行。
- import modin.pandas as pd
7. 為函數(shù)計(jì)時(shí)。
不是所有函數(shù)都是一樣的。
即便全部代碼沒(méi)問(wèn)題,也不意味著你編寫的是出色的代碼。一些軟錯(cuò)誤實(shí)際上會(huì)使代碼運(yùn)行起來(lái)比較慢,有必要把它們找出來(lái)。使用這個(gè)裝飾器來(lái)記錄函數(shù)的時(shí)間。
- import time
- def timing(f):
- """Decorator for timing functions
- Usage:
- @timing
- def function(a):
- pass
- """
- @wraps(f)
- def wrapper(*args, **kwargs):
- start = time.time()
- result = f(*args, **kwargs)
- end = time.time()
- print('function:%r took: %2.2f sec' % (f.__name__, end - start))
- return result
- return wrapper
8. 別把錢耗費(fèi)在云上。
沒(méi)人喜歡浪費(fèi)云資源的工程師。
一些試驗(yàn)可能持續(xù)數(shù)小時(shí)。很難跟蹤試驗(yàn)、云實(shí)例用完后關(guān)閉。本人就犯過(guò)這種錯(cuò)誤,也見(jiàn)過(guò)有人任由實(shí)例運(yùn)行數(shù)天。
只是在執(zhí)行結(jié)束時(shí)調(diào)用該函數(shù),永遠(yuǎn)不會(huì)有麻煩!
但用try包主代碼,并用except再采用這種方法,那樣如果出現(xiàn)了錯(cuò)誤,服務(wù)器不會(huì)處于繼續(xù)運(yùn)行的狀態(tài)。是的,我也處理過(guò)這種情況。
不妨負(fù)責(zé)任一點(diǎn),別生成二氧化碳了。
- import os
- def run_command(cmd):
- return os.system(cmd)
- def shutdown(seconds=0, os='linux'):
- """Shutdown system after seconds given. Useful for shutting EC2 to save costs."""
- if os == 'linux':
- run_command('sudo shutdown -h -t sec %s' % seconds)
- elif os == 'windows':
- run_command('shutdown -s -t %s' % seconds)
9. 創(chuàng)建和保存報(bào)告。
建模中某個(gè)點(diǎn)之后,所有寶貴的信息只來(lái)自錯(cuò)誤和度量分析。確保為你自己和你的經(jīng)理創(chuàng)建和保存格式完好的報(bào)告。
不管怎樣,管理層愛(ài)看報(bào)告,不是嗎?
- import json
- import os
- from sklearn.metrics import (accuracy_score, classification_report,
- confusion_matrix, f1_score, fbeta_score)
- def get_metrics(y, y_pred, beta=2, average_method='macro', y_encoder=None):
- if y_encoder:
- y = y_encoder.inverse_transform(y)
- y_pred = y_encoder.inverse_transform(y_pred)
- return {
- 'accuracy': round(accuracy_score(y, y_pred), 4),
- 'f1_score_macro': round(f1_score(y, y_pred, average=average_method), 4),
- 'fbeta_score_macro': round(fbeta_score(y, y_pred, beta, average=average_method), 4),
- 'report': classification_report(y, y_pred, output_dict=True),
- 'report_csv': classification_report(y, y_pred, output_dict=False).replace('\n','\r\n')
- }
- def save_metrics(metrics: dict, model_directory, file_name):
- path = os.path.join(model_directory, file_name + '_report.txt')
- classification_report_to_csv(metrics['report_csv'], path)
- metrics.pop('report_csv')
- path = os.path.join(model_directory, file_name + '_metrics.json')
- json.dump(metrics, open(path, 'w'), indent=4)
10. 編寫出色的API。
所有結(jié)尾不好的代碼都是不好的。
你的數(shù)據(jù)清理和建??赡茏龅煤芎茫詈筮€是會(huì)造成大混亂。經(jīng)驗(yàn)告訴我,許多人不清楚如何編寫優(yōu)秀的API、文檔和服務(wù)器配置。
以下是負(fù)載不太高(比如1000/分鐘)的典型的機(jī)器學(xué)習(xí)和深度學(xué)習(xí)部署的好方法。
不妨見(jiàn)識(shí)這對(duì)組合Fastapi + uvicorn
運(yùn)行這些命令使用4個(gè)workers來(lái)部署。通過(guò)負(fù)載測(cè)試來(lái)優(yōu)化workers的數(shù)量。
- pip install fastapi uvicorn
- uvicorn main:app --workers 4 --host 0.0.0.0 --port 8000

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流