具體來說,

這兩個分布日志都是通過 wandb 的內置監視實用程序處理的。

wandb.watch(model, log="all", log_freq=10)

通過迭代當前模塊參數,可以輕松完成矩陣范數。兩者為你提供了對同一數據的略有不同的視圖。

數據點:數據集示例隨著時間的推移具有黃金價值。如果損失計算中存在錯誤,損失指標可能會成功下降,即使網絡沒有學到任何有價值的東西。錯誤更難隱藏在人類可理解的數據點中。

最終層 logits:在單標簽、多類別問題中,你可能會使用 softmax 作為損失函數的一部分。由于 softmax 不是硬最大化算法,因此你可以鼓勵模型在正確的類別上創建權重分布。如果你在圖表上記錄最終層 logits,你應該注意到大多數概率質量會隨著時間的推移收斂到正確的值(尤其是在過度擬合運行期間)。在過度擬合過程中,你走得越遠,這個最大值點應該越明顯。但權重應該有一個明顯的轉變,從均勻分散到接近正確值。

這為過度擬合的進展提供了額外的健全性檢查。它確保模型以你期望的方式在統計上發展,并且軟最大化值平穩增加。通常,查看隨時間的變化是分析訓練的一種有用方法。

如有疑問,請始終記錄。

2、從簡單的架構替代開始

大多數核心 ML 活動都有通用的抽象層。Transformers 可以用 RNN 代替,Resnet 可以代替 CNN。這些更簡單的方法無法達到你想要的精度,但可能能夠證明整體線束的梯度流是否存在問題。它們的訓練速度也更快,如果你嘗試對新的過度擬合管道進行快速健全性檢查,這將非常有用。

我還注意到,較新的筆記本電腦在矩陣乘法方面變得出奇地快。當然還不足以訓練整個網絡,但我現在經常發現自己在本地進行初始原型設計。這項工作的重點是簡單架構上的過度擬合、數據的健全性檢查以及確保矢量化在邏輯上正確。

3、使隨機性可重現

現代模型中內置了大量隨機性。

正如預期的那樣,這些技術有助于通過使網絡難以過度擬合來推廣模型。但在過度擬合期間,你確實希望它們過度擬合,理想情況下是積極地記住輸入以測試模型容量和訓練工具。如果輸入、輸出或損失具有隨機性,則很難確定過度擬合期間是否存在問題。

我過去常常在過度擬合時手動禁用模型的所有隨機元素:將 dropout 設置為零,禁用數據增強等。這種方法的缺點是有很多 if elif 語句,而且不一定能捕獲導入的模塊是否在其實現中嵌入了一些隨機性。我沒有采用這種方法,而是在每個訓練和驗證步驟中開始用固定種子為模型播種。在 Pytorch-Lightning 中,這看起來像:

CONSTANT_SEED = 60

class MySmartModule:
def training_step(self, batch):
if self.trainer.overfit_batches:
print("Will reset seed for reproducable overfitting")
pl.seed_everything(CONSTANT_SEED)

def validation_step(self, batch):
if self.trainer.overfit_batches:
print("Will reset seed for reproducable overfitting")
pl.seed_everything(CONSTANT_SEED)

這不會直接消除隨機性,但它應該使隨機值在每個訓練和驗證步驟中保持一致,這實際上是同一件事。這應該允許模型過度擬合以及零隨機性實現。通過日志進行雙重檢查以確認輸入值確實相等。

4、過度擬合1,然后 2,然后 5

任何足夠大的網絡都應該能夠在少數數據點上達到 0 損失。我通常從一個示例(1 個批次,批次大小 1)開始。這應該是可以輕易學習的,因為甚至不需要創建判別輸出空間。如果成功,則擴展到 2 個不同的示例,然后擴展到 5 個不同的示例。

5、將每個自定義矢量化編寫兩次

這聽起來有點矯枉過正,但它省去了很多麻煩。每當我做前饋傳遞值以外的任何事情時,我都會將張量轉換重構為單獨的函數。然后,我使用標準 for 循環和基于單個索引的張量重寫此邏輯。然后運行幾個示例并確保它們的值匹配。這是驗證矢量廣播和其他并行操作是否按預期工作的最簡單方法。

具體來說,我將兩個實現都包裝在描述轉換的類中。假設我們要編寫一個掩蓋特定值顏色的函數。我從 for 循環實現開始,逐個索引地進行。調用矢量化管道的嘗試失敗了。原始類結構如下所示:

class ColorMasking:
def __init__(self, vectorize):
self.vectorize = vectorize

def __call__(self, *args, **kwargs):
if self.vectorize:
return self.vectorized(*args, **kwargs)
else:
logging.warning("Using greedy implementation of ColorMasking")
return self.greedy(*args, **kwargs)

def greedy(self, img):
for y in range(img.shape[0]):
for x in range(img.shape[1]):
...

def vectorized(self, img):
raise NotImplementedError()

這個類可讓你輕松地在顯式(速度慢但更可能正確)和矢量化(速度快但更可能引入錯誤)之間切換。它還內置了一個可單元測試的代碼塊,可以更輕松地檢查一段時間內的實現問題。然后,你可以在神經網絡模塊內選擇是否要全面切換到矢量化,或者使用手動矢量化對幾個時期進行健全性檢查。

class MySmartModule(torch.nn.Module):
def __init__(self):
self.vectorize = False

def forward(img):
mask = ColorMasking(vectorize=self.vectorize)(img)

這也對實現過程進行了補充,到目前為止您可能只有一個明確的實現:

class MySmartModule(torch.nn.Module):
def __init__(self):
self.vectorize = False

def forward(img):
mask = ShapeMask(vectorize=self.vectorize)
mask = ColorMasking(vectorize=False)(img)

有時我會直接用這個非向量化函數運行過度擬合作業,以檢查它是否按照我的意愿運行。有時由于速度限制,我會直接編寫向量化邏輯。

6、單元測試輔助函數

為向量化、數據加載器和訓練管道添加重型單元測試組。

向量化:作為上一節的延續,驗證向量化代碼是否正常工作。通過幾個手寫示例定義預期的轉換。嘗試使用不同的張量大小并記錄預期權重或一些預期的轉換。

數據加載器:這也適用于數據加載器。如果可能,通過反向轉換來保證轉換符合預期。獲取文本 logits 的 argmax 并檢索文本,將圖像像素轉換為可以與靜態工件進行比較的實際 PIL,等等。

訓練管道:額外的集成測試可以驗證訓練管道的某些行為。最大的問題之一通常是梯度流 – 無法正確傳播到網絡中較早的張量的損失。在最好的情況下,你錯過了可驗證的學習 – 在最壞的情況下,較早的層將保持隨機初始化,而網絡的其余部分將猜測隨機輸入噪聲。一種解決方法是進行測試,該測試傳遞一些合成數據并跨過梯度權重并斷言每個范數都非零。網絡的每一層都應該有一些學習。

我的訓練管道傾向于通過 CLI 訓練可執行文件啟動。為了確保單元測試在每次訓練運行中都令人滿意,我在初始化線束之前向此實現添加了 pytest 運行命令。

@click.command()
def train():
pytest.main()

# Training block

7、避免使用全局變量

全局變量在常規軟件工程中通常是一種不好的形式,在機器學習中也同樣糟糕。Jupyter 非常適合原型設計,但當事物被定義為常規單元時,很容易出現錯誤。即使你將一些邏輯重構為函數,它們仍可能在全局狀態下獲取變量。

作為一般工作流程,我完全在全局空間中制作原型。傳遞變量并確保張量大小正確更容易。在這里我通常只處理一個批次。

在開始完整的訓練運行之前,我會將所有單元重構為單獨的函數。這確保沒有全局變量泄漏。它之前已經捕獲了一些微不足道的錯誤,即同一個值被無意中重復使用多次,而不是在更大的列表中進行迭代。

8、確保(靜態)批次隨時間保持不變

添加自定義數據集、自定義加載器和自定義整理函數時,批次可能會隨時間發生細微偏差。當現場操作字典或聚合某些值時,這種情況尤其容易發生。我使用此代碼片段來檢查隨時間推移的相等性。

毋庸置疑,如果你在數據加載器類中引入隨機增強,這將不起作用。對于這些情況,我會暫時禁用轉換,然后運行此驗證。你還可以有選擇地將應隨時間保持不變的鍵列入白名單,同時允許隨機轉換中的鍵發生變化。

first_sample = next(iter(train_loader))
second_sample = next(iter(train_loader))

print("Will check equality...")
for key in first_sample.keys():
first_value = first_sample[key]
second_value = second_sample[key]

if isinstance(first_value, torch.Tensor):
if not torch.equal(first_value, second_value):
print(first_value)
print(second_value)
raise ValueError(f"Unequal iterations: {key} (torch tensor)")
else:
if first_value != second_value:
print(first_value)
print(second_value)
raise ValueError(f"Unequal iterations: {key}")
print("Success...")

9、合成生成不同大小的數據

在較大的網絡中,尤其是通過時間反向傳播,梯度可能會在網絡中較早消失。我發現一種調試這些問題的有用方法是合成生成不同大小的新數據點。

如果你的數據加載器最終接受磁盤上的文件,那么這是一種自然選擇,這在我最終構建的大多數大型架構中都很常見。編寫一個函數,以正確的格式將新數據集轉儲到磁盤。輸出值在這里并不重要,因為網絡應該只記住過度擬合期間的原始值。

tokenizer = Tokenizer()
labels = ["A", "B", "C"]

@contextmanager
def create_synthetic_datapoint(text_length):
random.sample(tokenizer.vocab, text_length)
random.choice(labels)

with tempfile.TemporaryDirectory() as directory:
yield directory

with create_synthetic_datapoint(50) as path:
train_dataset = MyDataset([path])

trainer.overfit(model, train_dataset)

10、盡可能使用 einops

每當需要張量變換(查看、轉置、堆疊等)時,我都會嘗試將其放入 einop 中。它們通過引用字符串值來表示軸的含義,從而使這些操作更具描述性。它們還可以假設一些維度,否則你可能需要 .shape 算法。我嘗試在這些字符串中使用完整的單詞或變量名稱,除非某些東西很明顯,例如 b 表示批處理。

x = rearrange(x, "b height width embedding -> b (height width) embedding")

我發現,當我離開某個功能幾天后,這些 einops 使調試變得容易得多。

11、結束語

得益于出色的開源項目和與出版物一起發布代碼的日益增長的趨勢,成功訓練的道路變得越來越容易。但是,當嘗試一些新穎的東西(無論是在數據集上還是使用新的模型架構)時,成功之路仍然曲折。一個字符的索引錯誤可能會導致結果從 SOTA 變為勉強超過基線。

我有一位老同事說“軟件中一切皆有可能,你只需要花足夠的時間來構建它。” ML 的挑戰在于有些事情是不可能的——至少在目前數據和架構的最新水平下是不可能的。 ML 研究是盡可能減少邏輯錯誤機會的過程。因為失敗可能是因為某事根本不可能——或者因為它可能是一個錯誤。提前勤奮和防御是確保失敗是前者而不是后者的最佳方式。對合理的失敗感到坦然是讓實驗真正取得成功的最好方法。

更多大模型API

冪簡集成是國內領先的API集成管理平臺,專注于為開發者提供全面、高效、易用的API集成解決方案。

冪簡API平臺提供了多種維度發現API的功能:通過關鍵詞搜索API、從API Hub分類瀏覽API、從開放平臺分類瀏覽企業間接尋找API等。

此外,冪簡集成開發者社區會編寫API入門指南、多語言API對接指南、API測評等維度的文章,讓開發者選擇符合自己需求的API。

原文鏈接:http://www.bimant.com/blog/debugging-tips-for-llms-training/

上一篇:

TOP 6 文生圖大模型

下一篇:

避免AI檢測的最佳釋義工具
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費