# W4_PyTorch教學_助教.m4a|長度 01:26:10|model large-v3 on cuda [00:00:01] 歡迎來到國立清華大學自然語言處理課程,本次助教課給大家講的主要是PyTorch的使用方法、模型的訓練方法等實際操作的內容,那這裡是高雄宇老師的助教。 [00:00:15] 在講實際的code該怎麼寫之前,我們先大概介紹一下Python環境的架設。 [00:00:22] 假如大家要在自己的電腦上面跑Python,或者跑深度學習的話,這邊推薦Anaconda或者MiniConda來構建Python的環境。 [00:00:34] 這邊以Windows系統為例,大家可以直接去他們的官網下載安裝包,然後也可以通過指令來下載。 [00:00:46] 下載完了以後,在Windows系統上這邊會有一個Anaconda prompt的檔,打開檔以後大家可以看到一個像Shell一樣的黑框,那這個黑框就是我們可以操作環境的窗口。 [00:01:03] 左上角的括號base就是現在我們所處的環境的名稱,大家如果創建了自己的環境的話,在這邊它的base就會換成你自己環境的名稱。 [00:01:16] 那這邊大家可以通過一些指令來操作環境,這裡有簡單的列出幾個,比方說可以通過create,conda create,來創建環境,或者是conda activate來激活環境。 [00:01:32] 然後我們創建好了基本的Python環境以後,接下來就是安裝PyTorch。 [00:01:41] 這裡的PyTorch不推薦大家直接用PIP install來安裝,也不推薦什麼。 [00:01:46] 這裡的PyTorch不推薦大家直接用PIP install來安裝,也不推薦什麼。 [00:01:47] 這裡的PyTorch不推薦大家直接用PIP install來安裝,也不推薦什麼。 [00:01:47] 這裡的PyTorch不推薦大家直接用PIP install來安裝,也不推薦什麼。 [00:01:47] 這裡的PyTorch不推薦大家直接用PIP install來安裝,也不推薦什麼。 [00:01:50] 因為直接用PIP或者conda你很難指定安裝什麼版本的PyTorch,而我們這裡可以看到PyTorch有支援Linux、Mac、Windows有不同的作業系統版本,然後也有支持不同ientes版本的PyTorch。 [00:01:50] 而我們這裡可以看到PyTorch有支援Linux、Mac、Windows有不同的作業系統版本,然後也有支持不同 analytical sci-fi版本的PyTorch。 [00:01:53] 而我們這裡可以看到PyTorch有支援Linux、Mac、Windows有不同的作業系統版本,然後也有支持不同 evangelical sci-fi版本的PyTorch。 [00:02:09] 所以大家最好是上官網去選擇一個符合自己電腦需求的 [00:02:16] 這樣一個PyTorch版本 [00:02:17] 選好自己的需求以後 [00:02:21] 下面就會有對應的安裝指令 [00:02:24] 那裝好 [00:02:25] 那大家直接把這個安裝指令輸入到Anaconda Prompt [00:02:31] 就是可以操作環境的窗口當中 [00:02:34] 就可以安裝PyTorch了 [00:02:36] 那這邊要提醒大家的是 [00:02:38] 安裝PyTorch不需要額外安裝CUDA [00:02:41] 因為PyTorch裡面有整合CUDA的內容 [00:02:44] 這是PyTorch跟TensorFlow不同的地方 [00:02:48] 因為TensorFlow它裡面並沒有去整合這些內容 [00:02:52] 所以當我們裝TensorFlow的時候 [00:02:54] 我們要去查TensorFlow跟CUDA有哪些版本的對應關係 [00:02:58] 並安裝對應版本的CUDA [00:03:00] 但是PyTorch就不需要這麼做 [00:03:05] 但相比之下 [00:03:07] PyTorch的包 [00:03:08] 其實會有點難受 [00:03:08] 可能會更大一點 [00:03:10] 然後安裝 [00:03:12] 那麼接下來 [00:03:14] 如果大家想要安裝早些版本的PyTorch [00:03:17] 或者是安裝一些其他CUDA版本之類的 [00:03:21] 那麼就可以點擊這裡 [00:03:23] 這邊有個鏈接 [00:03:24] 點進去以後就會看到過去各個版本的PyTorch的安裝指令 [00:03:31] 那安裝好了基本的環境架設以後 [00:03:35] 接下來呢 [00:03:35] 就是有關於Deep Learning [00:03:38] 我們先從Deep Learning開始介紹 [00:03:41] 那Deep Learning [00:03:42] 我們大概可以有這樣一個印象 [00:03:44] 就是我們會有一個模型 [00:03:45] 那我們也會有一堆資料 [00:03:49] 模型呢 [00:03:49] 他會不斷的去吃資料 [00:03:51] 然後讓自己的效能不斷的提升 [00:03:53] 就是我們相當於我們不斷的給模型範例 [00:03:58] input是什麼樣 [00:03:58] output應該要是什麼樣 [00:04:00] 不斷的給他這種範例以後 [00:04:02] 模型會自我調整裡面的參數 [00:04:04] 然後讓自己的效能越來越好 [00:04:07] 那這裡wide [00:04:08] 就等於ax加b [00:04:09] 假如說這個模型 [00:04:11] 他的input跟output是像這樣的一個多項式關係 [00:04:16] 他有兩個參數 [00:04:17] 一個是A一個是B [00:04:20] 那那這樣的話 [00:04:23] 這個模型他我們要訓練的就是這兩個參數 [00:04:27] A跟B這兩個參數 [00:04:28] 那用什麼來訓練 [00:04:29] 他我們這裡面給了三個資料 [00:04:32] 其實是在空間當中的三個點 [00:04:36] 當input是-1的時候 [00:04:38] output需要是1 [00:04:40] 當input是-1的時候 [00:04:41] output是2 [00:04:42] 當input是-2的時候 [00:04:43] output是3 [00:04:44] 給出這三個點以後 [00:04:47] 我們就可以發現當A跟B [00:04:49] 我們取一個固定的A跟B以後 [00:04:52] 把x把橫座標輸入進去 [00:04:55] 我們就可以獲得他重座標的值 [00:04:58] 如果A跟B都是-1 [00:05:00] A跟B都是1的話 [00:05:01] 那output出來的重座標就是225 [00:05:05] 那明顯這個225 [00:05:06] 他是不符合我們的期待的 [00:05:08] 因為我們希望他是123 [00:05:10] 那我們要如何衡量 [00:05:12] 這個225他跟123中間有多大的距離呢 [00:05:16] 我們需要去衡量出這個量化的距離 [00:05:19] 並且最小化這個距離 [00:05:21] 所以這邊的距離 [00:05:23] 我們把它表示成 [00:05:26] 標準答案減去預測結果的平方 [00:05:29] 再取平均值 [00:05:31] 也就是這三個點會取平均值 [00:05:34] 獲得我們最終跟結果的誤差 [00:05:37] 也就是L [00:05:38] 那這個方式 [00:05:40] 其實我們之後會介紹到叫做mean square error [00:05:43] 那這邊我們把它表示成一個L [00:05:47] 然後我們就會發現 [00:05:48] 當A跟B各取一個值的時候 [00:05:52] A跟B取任何不同的實數 [00:05:54] 我們都可以獲得一組225 [00:05:57] 就是一組這個output prediction [00:06:00] 一組預測結果 [00:06:02] 那有了這種預測結果 [00:06:03] 我們就會有一個對應的誤差 [00:06:06] 就是一個對應的L [00:06:09] 那麼問題就來了 [00:06:10] 就是我們是不是可以把A跟B和L的對應關係 [00:06:16] 畫成一張圖 [00:06:17] 就像這樣 [00:06:20] A跟B在取不同的座標點的時候 [00:06:24] 都會有一個對應的L [00:06:26] 那這是我通過3D的繪圖 [00:06:30] 軟體來畫出來的一張圖 [00:06:32] 畫出來的一個區面 [00:06:34] 這個區面我們要找的就是它A跟B [00:06:39] 在某個值的位置中 [00:06:40] 在某個值的時候 [00:06:41] L的算出的結果會最低 [00:06:44] 也就是最底下這個點 [00:06:46] 我們如何獲得最底下這個點 [00:06:48] 就是通過梯度下降的方式 [00:06:50] 那梯度下降 [00:06:51] 大家肯定在微積分裡面有學過 [00:06:55] 通過偏微分來算出梯度 [00:06:58] 然後再通過梯度來更新 [00:07:02] 來尋找出最低的那個點 [00:07:06] 所以這邊Y等於AX加B [00:07:09] 就是我們描述XY等於B [00:07:10] 我們把這個點定為一個等式 [00:07:11] 我們把它叫做模型 [00:07:13] 我們定義模型的預測結果 [00:07:17] 和標準答案之間距離的這樣一個等式 [00:07:20] 叫做loss function [00:07:22] 損失函數 [00:07:25] 另外我們用來做梯度下降的工具 [00:07:30] 叫做optimizer [00:07:31] 就是優化器 [00:07:33] 中文翻譯叫優化器 [00:07:36] 所以我們可以用一張圖來表示 [00:07:39] 這個訓練的過程 [00:07:40] 首先data會餵給模型 [00:07:44] data會餵給模型 [00:07:45] 然後模型會產出預測結果 [00:07:48] 預測結果跟ground truth [00:07:51] 跟標準答案之間會算出一個loss值 [00:07:54] 這個loss值 [00:07:55] 我們為了最小化這個loss值 [00:07:57] 會用梯度下降的方法 [00:07:59] 所以loss值會獲得一個梯度 [00:08:01] 那梯度再通過這個optimizer [00:08:05] 來更新模型的參數 [00:08:07] 像這樣一個模型獲得預測的參數 [00:08:09] 預測在獲得梯度 [00:08:10] 梯度在更新模型 [00:08:11] 這樣循環往復的這個過程 [00:08:13] 就是我們深度學習當中的主循環 [00:08:16] 也就是main loop [00:08:18] 為了完成這個main loop [00:08:20] 我們就可以想說 [00:08:21] 我們需要一個特殊的資料結構 [00:08:23] 那這個資料結構需要做什麼呢 [00:08:25] 就是首先 [00:08:26] 它需要能夠計算 [00:08:28] 我data餵給模型以後 [00:08:30] 我們通過一個基本的資料結構去算出一個預測結果 [00:08:36] 另外這個資料結果還 [00:08:39] 這個資料結構 [00:08:40] 它可以保存我們的計算過程 [00:08:43] 並且通過這個計算過程來算出梯度 [00:08:47] 這個梯度 [00:08:48] 還能繼續回來更新這個 [00:08:52] 資料結構裡面的數值 [00:08:55] 那當我們這個模型越來越大 [00:08:58] 就是參數量越來越大的時候 [00:09:00] 我們會希望 [00:09:01] 這個模型能夠加速運算 [00:09:04] 所以 [00:09:05] 也就是說在GPU上運算 [00:09:08] 所以這裡 [00:09:09] 就誕生了一個資料結構 [00:09:10] 叫做Tensor [00:09:12] 就是張量 [00:09:13] 所謂的張量 [00:09:14] 我們大名鼎鼎的TensorFlow [00:09:16] 大家應該都有聽過 [00:09:18] 叫做張 [00:09:18] 中文直譯叫張量流 [00:09:21] 那意思就是說 [00:09:22] 在它這個資料 [00:09:23] 在它這個包裡面 [00:09:24] 數 [00:09:25] 就是data都是通過張量的方式 [00:09:28] 就通過Tensor的方式 [00:09:30] 來進行處理的 [00:09:32] 所以 [00:09:33] 大家理解Tensor [00:09:35] 可以把它大概的理解成矩陣 [00:09:38] 只不過它是 [00:09:40] 維度特別自由 [00:09:41] 就是它可以是很高維的矩陣 [00:09:43] 它跟我們平常會 [00:09:45] 在現代裡面接觸過那種二維矩 [00:09:48] 大多數都是二維矩陣的 [00:09:50] 還不太一樣 [00:09:52] 它可以非常高維 [00:09:55] 它的基本運算方式其實也就是矩陣的運算方式 [00:09:59] 我們之後會介紹到 [00:10:00] 那它確實完成了可以計算T度 [00:10:03] 並且在GPU上運行的這樣一個需求 [00:10:08] 然後我們就 [00:10:10] 我們介紹了大概Tensor的由來 [00:10:12] 那接下來就是我們如何先創建一個Tensor [00:10:16] Tensor的創建可以通過這個Python的基本型別 [00:10:20] 就是list [00:10:21] 來創建 [00:10:22] 但是要注意這邊因為Tensor它 [00:10:25] 我們大概理解成的是一個矩陣 [00:10:28] 所以它不能是 [00:10:30] 參差不齊的list [00:10:32] 什麼意思呢就是 [00:10:34] 現在這裡是一個二乘二的list [00:10:37] 那 [00:10:37] 我不能說 [00:10:39] 第一個 [00:10:40] 第一個維度它只有一個數 [00:10:42] 第二個維度它有兩個三個數 [00:10:44] 它必須是要對齊 [00:10:46] 就是 [00:10:46] 二乘二 [00:10:47] 那在這個方方正正的 [00:10:49] 矩陣當中 [00:10:50] 每一個它都有一個 [00:10:52] 就每一個它都不是空的 [00:10:54] 它至少都是有一個數的 [00:10:57] 然後並 [00:10:58] 另外呢它也可以通過nonpy來 [00:11:01] 初始化 [00:11:01] 然後也可以隨機初始化就是torch.ran [00:11:06] 這個 [00:11:07] randn [00:11:08] 後面多了一個n的 [00:11:10] 它是 [00:11:11] 以normal distribution [00:11:13] 的機率分佈來初始化的 [00:11:15] 然後後面沒有多一個n的 [00:11:17] 它是均勻初始化的 [00:11:22] 然後 [00:11:23] 我們也可以創建全都是1的Tensor [00:11:27] 也可以也有全都是0的 [00:11:29] 還有就是對角矩陣 [00:11:31] 這個在我們 [00:11:32] 往往深度學習 [00:11:34] 的一些中間 [00:11:36] 變量的處理當中 [00:11:38] 是經常會遇到的 [00:11:41] 然後呢 [00:11:42] 創建好了一個Tensor以後 [00:11:45] 我們就可以 [00:11:46] 通過一些Tensor當中的一些成員變量 [00:11:49] 去獲得Tensor的一些屬性 [00:11:51] 比方說data [00:11:53] 就是 [00:11:54] 相當於是return一個Tensor的copy [00:11:57] 就return它自己 [00:11:59] 那 [00:12:01] 這個 [00:12:01] 這邊還有一個如果說你是單一的變量的話 [00:12:05] 你不是一個 [00:12:06] 一串 [00:12:07] 一個vector或者一個矩陣 [00:12:09] 那麼 [00:12:10] 你可以用 [00:12:11] item來 [00:12:12] 回傳出它裡面的數值 [00:12:15] 那另外呢你也可以通過這個.shape來看它是幾乘幾 [00:12:19] 像這個就是二乘二 [00:12:21] 然後也有這個裡面是什麼數值 [00:12:24] 像假如說 [00:12:26] 它是一點二點就是 [00:12:28] 它是float的話這邊也會 [00:12:30] return出這個 [00:12:32] torch.float [00:12:33] 那 [00:12:36] 然後再往後 [00:12:37] 也可以看說這個 [00:12:39] Tensor它在CPU上還是GPU上 [00:12:42] 畢竟因為Tensor它 [00:12:43] 需要能夠滿足 [00:12:44] 我們在GPU上運算的這樣一個需求 [00:12:48] 然後 [00:12:48] 我們也可以看說這個Tensor它是不是一個需要回傳T度的Tensor [00:12:54] 然後 [00:12:56] 這個 [00:12:59] 我們也可以把它的T度 [00:13:01] 給 [00:13:01] 回傳出來 [00:13:04] 然後這邊有一個比較有趣的insight [00:13:07] 有關於Tensor.data [00:13:09] 那Tensor.data我們 [00:13:11] 之前有講過 [00:13:12] 它是 [00:13:13] 把這個Tensor裡面的 [00:13:14] 相當於它裡面的一些 [00:13:16] 內容給回傳出來的這樣一個動作 [00:13:19] 我剛剛解釋說 [00:13:20] 是把Tensor給複製一份 [00:13:22] 那在 [00:13:23] 它實作的過程當中 [00:13:25] 也確實有複製的這個動作 [00:13:27] Tensor的儲存方式 [00:13:29] 是底層有一個 [00:13:31] 最底層有一個 [00:13:33] Tensor內部data的儲存 [00:13:37] 它往往是一個連續的空間 [00:13:40] 是一字排開的空間 [00:13:42] 那在這個空間之上會有一個view [00:13:44] 所謂的view就是說 [00:13:46] 這一串的data [00:13:49] 它要通過怎樣的組合 [00:13:51] 來構成我們這個Tensor [00:13:54] 然後在我們實際寫code的時候 [00:13:56] 我們會有一個變量 [00:13:57] 比方說它這個變量叫A [00:13:59] A來指向這個view [00:14:02] 這邊有個簡單的範例 [00:14:05] 假如說我們命出一個A [00:14:07] A是 [00:14:09] 一個二乘二的矩陣 [00:14:10] 就是0123 [00:14:12] 這樣一個二乘二的矩陣 [00:14:14] 那B把A.data的這個 [00:14:18] A.data的這個屬性 [00:14:20] 給複製給B [00:14:21] 然後再把A跟B [00:14:23] 他們中間的 [00:14:25] 在memory當中的位置給print出來 [00:14:28] 然後 [00:14:30] 再把A的01這個位置 [00:14:33] 也就是 [00:14:34] 因為0123把1的那個位置 [00:14:37] 改成100 [00:14:38] 再把A跟B給print出來 [00:14:40] 那從這個結果當中我們可以發現 [00:14:44] A跟B [00:14:45] 它雖然A.data把它複製給了B [00:14:50] 但是A跟B中間 [00:14:53] 他們用的並不是同一塊記憶體 [00:14:56] 但是我們修改A的時候 [00:14:59] B的值也會一起改 [00:15:00] 意思是什麼呢 [00:15:01] 意思就是說 [00:15:03] 在我們當我們把A.data給B的時候 [00:15:06] 它實際上是把A的這個view給複製了一份 [00:15:12] 複製了一份新的view [00:15:14] 給了B [00:15:16] 但它的底層 [00:15:18] 0123這個最底層的資料 [00:15:20] 存儲 [00:15:22] 並沒有重新複製一份 [00:15:24] 所以 [00:15:25] A跟B它的 [00:15:27] 在memory當中 [00:15:28] 它這個view在memory當中的位置是不一樣的 [00:15:30] 但是 [00:15:31] 當我們改A裡面的值的時候 [00:15:33] B也會跟著一起改 [00:15:35] 因為它的 [00:15:36] 底層的 [00:15:37] 資料存儲 [00:15:38] 並沒有改變 [00:15:42] 然後 [00:15:43] 接下來就是 [00:15:44] 有關於 [00:15:45] 就是 [00:15:46] Tensor的運算 [00:15:48] Tensor加上一個常量 [00:15:50] 就是Tensor [00:15:51] 加上一個具體的數 [00:15:52] 或者減具體的數 [00:15:54] 就是在Tensor當中的每一個 [00:15:57] 數值 [00:15:58] 做同樣的 [00:15:59] 加減乘除的操作 [00:16:03] 這邊呢其實我們會經常會 [00:16:06] 假如說Tensor是一個 [00:16:08] 矩陣運算的話 [00:16:10] 這邊會多一個符號叫做這個小老鼠 [00:16:13] 那 [00:16:14] 矩陣運算 [00:16:15] 比方說我舉個例子叫 [00:16:16] A矩陣是1234 [00:16:18] 那B矩陣是0123 [00:16:20] 如果說它是小老鼠的話 [00:16:22] 它OP出來是他們矩陣乘法的結果 [00:16:26] 但如果是 [00:16:26] 單單是這個星號 [00:16:28] 星星 [00:16:29] 那麼他們return出來就是一個 [00:16:32] 元素相加的結果 [00:16:33] 就是 [00:16:34] 元素相乘的結果 [00:16:35] 就是0乘1 2乘1 [00:16:38] 3乘2 [00:16:40] 4乘3的這樣一個結果 [00:16:43] 只有這個 [00:16:44] 除了這個例外以外 [00:16:46] 其他像加減啊 [00:16:48] 還有除啊這些 [00:16:50] 還有乘次方啊 [00:16:52] 都是 [00:16:54] 矩陣兩個矩陣當中對應元素的加減 [00:16:57] 加減乘除次方 [00:17:01] 然後 [00:17:03] 在Tensor [00:17:05] 除了它基本的矩陣運算以外 [00:17:07] 還有一些自己的一些 [00:17:09] 操作 [00:17:10] 比方說CAT [00:17:12] CAT就是把兩個Tensor給拼起來 [00:17:14] Concatenate [00:17:16] 那麼 [00:17:17] A跟B [00:17:18] 都是2乘2的矩陣 [00:17:20] 那 [00:17:21] 他們 [00:17:22] 在shape就是2乘2 [00:17:23] shape在0這個位置是 [00:17:25] 的大小是2 [00:17:26] 1的這個大小也是2 [00:17:29] 我們把它在 [00:17:31] 維度為0的這個地方拼接 [00:17:34] 它就會變成4乘2的矩陣 [00:17:37] 也就是它在shape等於0 [00:17:39] 那個第一個2 [00:17:40] 的這個位置拼接起來 [00:17:42] 那它在 [00:17:43] 它就會變成4乘2 [00:17:45] 如果說它在 [00:17:46] 第二個2 [00:17:47] 就是 [00:17:47] 維度為1的這個地方拼接起來 [00:17:49] 它就會變成2乘4 [00:17:51] 另外它還可以split [00:17:53] 把一個Tensor給切成很多塊 [00:17:56] 像這邊 [00:17:57] 初始化一個Tensor是567 [00:18:00] 5乘6乘7的Tensor [00:18:02] 它在維度為0的時候 [00:18:04] 去切分 [00:18:05] 並且我們希望它切出來的Tensor [00:18:07] 在這個維度的大小 [00:18:09] 是2這個值 [00:18:11] 所以 [00:18:12] 它切的是維度是5 [00:18:14] 並且我們希望切出來的大小是2 [00:18:16] 那我會 [00:18:17] 我們會不難想象就是5 [00:18:18] 它其實是沒有辦法被2整除的 [00:18:21] 所以我們切 [00:18:22] 就切出一個267 [00:18:24] 在第二個還是267 [00:18:26] 在 [00:18:27] 最後一個只剩1了 [00:18:28] 它實際上 [00:18:29] 並不會報錯 [00:18:30] 而是把最後那個1 [00:18:32] 給 [00:18:32] 也給放在後面 [00:18:34] 最後一個會是它的 [00:18:36] 就是 [00:18:37] 它是會是一個餘數 [00:18:40] 那這邊還有squeeze跟unsqueeze [00:18:42] 這個是加 [00:18:43] 加維度跟減維度 [00:18:45] A是一個2乘2的矩陣 [00:18:47] 那A在最後一個維度 [00:18:50] unsqueeze [00:18:51] 那麼它就會最後一個維度加一個 [00:18:53] 加一個1就變成2乘2乘1的矩陣 [00:18:56] 然後A再squeeze [00:18:58] 它就又會變成1234 [00:19:00] 就又會變成2乘2的矩陣 [00:19:04] 然後 [00:19:04] 這邊還有transpose [00:19:06] 那transpose [00:19:08] 就是我們常說矩陣 [00:19:10] 矩陣 [00:19:11] 運算當中的轉制 [00:19:14] 把1234變成1324 [00:19:18] 就是按對角線翻轉一下 [00:19:23] 然後 [00:19:25] 我們可以 [00:19:25] 然後以上是一些有關tensor的基本操作 [00:19:30] 那接下來 [00:19:31] 這樣這個 [00:19:32] stride函數 [00:19:34] 它主要介紹的是 [00:19:35] tensor它在底 [00:19:37] tensor它是否 [00:19:38] 連續或者說它在底層是怎樣存儲的這樣一個 [00:19:42] function [00:19:44] stride它返回的是什麼呢 [00:19:45] 假如說我們有一個3乘3的 [00:19:47] tensor [00:19:48] 3乘3是就是 [00:19:49] 012 [00:19:50] 345678 [00:19:52] 對吧 [00:19:53] 就像這樣的一個tensor [00:19:57] 這邊 [00:19:57] 它底層 [00:19:58] 它在 [00:19:59] 它在 [00:20:00] 存儲的時候 [00:20:01] 它會 [00:20:03] 它會有 [00:20:05] 先是第一排 [00:20:07] 在前面 [00:20:08] 然後是第二排 [00:20:09] 然後是第三排 [00:20:10] 它是一排二排三排這樣子連成一條 [00:20:14] 連成一串的存儲方式 [00:20:17] 那 [00:20:18] stride [00:20:19] 就是說 [00:20:20] 在 [00:20:21] stride它會回傳一個tuple [00:20:24] tuple在第零個維度的時候 [00:20:26] 它在第一個維度就表示 [00:20:29] 在這個維度在零維度的時候 [00:20:32] index加1 [00:20:33] 它在底層存儲的時候會跳幾格 [00:20:37] 在這邊 [00:20:40] 第零個維度就是零跳到3 [00:20:44] 這一步 [00:20:45] 它在底層會跳三格 [00:20:47] 0到1到2到3 [00:20:49] 會跳三格 [00:20:50] 它在 [00:20:52] 它在第一維度的時候 [00:20:55] 也就是說從零到一 [00:20:57] 那麼它在底層會跳一格 [00:21:00] 那 [00:21:01] 這樣 [00:21:01] 如果我們可以通過檢查stride這個函數來判斷這個 [00:21:06] tensor它是否 [00:21:08] 是所謂的連續的tensor [00:21:11] 也就是說是否是像這樣子存儲方式 [00:21:13] 第一排第二排第三排的 [00:21:15] 存儲方式的這樣一個tensor [00:21:19] 然後 [00:21:20] 我們就可以再看就是transpose這個tensor [00:21:22] 這邊還有另外一個有趣的inside [00:21:26] 就是transpose這個function [00:21:31] 它實際上並不會創建新的tensor [00:21:34] 而是創建了一個新的view [00:21:36] 其實tensor的操作有很多 [00:21:40] 都不是創建新的tensor [00:21:42] 而是創建新的view [00:21:44] 像是 [00:21:45] 本來是012345678 [00:21:48] 它的底層是這樣存的 [00:21:50] 它通過transpose以後 [00:21:52] 把上面的翻到旁邊來 [00:21:55] 變成這樣 [00:21:57] 但是它們底層的其實並沒有改變 [00:21:59] 它只是創建了一個新的view出來 [00:22:02] 那這時候它的stride就變成了13 [00:22:04] 就是在第0維度從0跳到1的時候 [00:22:07] 它在底層只會跳一格 [00:22:11] 從0跳到3它會跳三格 [00:22:13] 也就是說它從連續變成了不連續的矩陣 [00:22:19] 那這邊呼叫contiguous [00:22:21] contiguous就是把 [00:22:23] 一個不連續的矩陣 [00:22:24] 變成連續的矩陣 [00:22:25] 如果a本身就是連續的話 [00:22:29] 那麼 [00:22:29] 它其實不會做任何處理 [00:22:31] 就是把a給return出來 [00:22:35] 所以這邊 [00:22:37] 這邊就是說 [00:22:38] 它呼叫了contiguous [00:22:40] 那麼它會在底層的存儲 [00:22:42] 又 [00:22:43] 創建一個新的存儲空間 [00:22:46] 並且把它存成 [00:22:48] 像 [00:22:49] 這樣子排列方式的 [00:22:50] 也就是 [00:22:51] 把這邊的第1排變成 [00:22:54] 變到開頭 [00:22:55] 然後再是第2排 [00:22:56] 再是第3排的這樣一個連續的存儲 [00:22:58] 然後這樣的話 [00:23:00] 它的stride [00:23:01] 就變回了3e [00:23:03] 也就是說它變回了連續的 [00:23:05] 連續空間存儲的這樣一個 [00:23:09] 存儲方式 [00:23:11] 然後 [00:23:12] 接下來是有關於reshape [00:23:14] 一個2x2的矩陣 [00:23:15] 它可以被reshape成 [00:23:17] 1x4或者4x1 [00:23:20] 的 [00:23:20] 形狀 [00:23:22] 那PyTorch在reshape這個功能之下 [00:23:26] 是做了兩個函數 [00:23:27] 一個叫做viewed [00:23:28] 還有一個叫reshape [00:23:30] 之前我們有介紹過PyTorch [00:23:32] 它是底層的 [00:23:33] 資料 [00:23:34] 在上面再蓋一個view [00:23:36] 那這個view我們就很好理解 [00:23:38] 它實際上也是一個創建view的一個動作 [00:23:41] 然後reshape呢 [00:23:43] 它的 [00:23:44] 功能會比view要更強一些 [00:23:47] 在這邊有一個介紹就是 [00:23:49] view [00:23:50] 它 [00:23:51] 無法 [00:23:51] 去操作不連續的矩陣 [00:23:55] 它只能去操作連續的 [00:23:57] 並且 [00:23:58] 它是在一個連續矩陣的基礎之上 [00:24:01] 再創建一個新的view [00:24:05] 然後它不會去改變底層的存儲 [00:24:07] 但是reshape不一樣 [00:24:09] reshape是contiguous加view [00:24:12] 假如說你的這個input data [00:24:13] 它並不是連續的 [00:24:16] 那麼它就會創建一個新的 [00:24:19] 那麼它就會創建一個新的內部存儲空間 [00:24:22] 相當於它先contiguous [00:24:24] 再跑view [00:24:25] 所以 [00:24:27] reshape就加了這樣一個判斷 [00:24:29] 它的功能也會比view要強大一些 [00:24:35] 然後 [00:24:37] 接下來 [00:24:37] 我們介紹了一些有關於 [00:24:40] tensor的 [00:24:40] 基本的 [00:24:41] 操作以後 [00:24:42] 這邊會建議說大家 [00:24:45] 在做tensor的運算的時候 [00:24:47] 儘量用矩陣運算的方式來運算 [00:24:50] 而不要用循序的方式來運算 [00:24:52] 因為tensor它內部 [00:24:55] 它對於這個矩陣運算有自己的 [00:24:58] 並行化的一些加速過程 [00:25:01] 假如我們這邊舉的一個範例是我們要normalize [00:25:05] 所謂的normalize [00:25:06] 就是 [00:25:07] 減平均除以標準差 [00:25:11] 這個過程我們可以 [00:25:12] 寫兩個套兩個復迴圈來寫 [00:25:15] 也可以 [00:25:15] 就是 [00:25:16] 用矩陣 [00:25:18] 矩陣運算的方式來寫 [00:25:20] 那這邊我們會發現 [00:25:22] 它的效能會差很多 [00:25:24] 因為這就是 [00:25:25] 我們就 [00:25:26] 因為矩陣運算它實際上 [00:25:28] 相當於利用了PyTorch內部的一些加速功能 [00:25:31] 但是如果你是循序的話 [00:25:34] 它實際上就是你就是強制它一個一個來算 [00:25:37] 這其實是 [00:25:40] 相當耗時間的 [00:25:41] 也是一個相當沒有效率的做法 [00:25:45] 然後 [00:25:47] 接下來我們介紹了 [00:25:50] 建構模型的或者和資料的一個基本單位就是Tensor [00:25:55] 那接下來我們就是要 [00:25:57] 通過這個Tensor來建構一個模型 [00:25:59] 那Tensor在模型當中的主要的一個 [00:26:03] 角色 [00:26:03] 就是 [00:26:04] 就是它的參數 [00:26:07] 在一個模型當中 [00:26:08] 它底層的參數 [00:26:10] 就是通過Tensor來存儲的 [00:26:12] 並且在Tensor的這個參數之上 [00:26:16] 有一個大的資料結構 [00:26:18] 叫做torch.nn.module [00:26:21] 也就是 [00:26:22] 模組 [00:26:23] 所謂的模組 [00:26:25] 我們所有的模型它都是 [00:26:28] 它都需要是一個模組的物件 [00:26:31] 那假如說我們要創建一個新的 [00:26:33] 我們自己的模型 [00:26:34] 我們就繼承著這個模組 [00:26:37] torch.nn.module [00:26:39] 然後再去複寫它的 [00:26:43] 我們自己特有的這樣一個 [00:26:46] 初始化的函數跟 [00:26:49] forward函數 [00:26:51] 初始化的函數它主要是定義 [00:26:53] 模型當中有哪些層 [00:26:55] 比方說我們可以有 [00:26:56] 線性的層還有什麼卷積層RNN層 [00:27:00] 各種各樣的層 [00:27:01] 然後 [00:27:02] 這個forward函數 [00:27:04] 就是當我們有一筆資料投進來的時候 [00:27:07] 模型要吃那筆資料的時候 [00:27:09] 那筆資料 [00:27:11] 它需要通過怎樣的處理方式 [00:27:14] 來獲得模型的output [00:27:19] 那 [00:27:19] 這邊 [00:27:21] 也就是我們有這樣一個物件以後 [00:27:23] 新創建出來一個 [00:27:26] 這樣一個物件 [00:27:27] 然後物件在吃資料 [00:27:29] 這個input就是資料 [00:27:31] 把資料丟給這個模型以後 [00:27:34] 模型就會 [00:27:35] 自己處理去 [00:27:37] return出這個 [00:27:38] output就是我們所謂的logits [00:27:41] 那這樣一個 [00:27:42] 這樣一個function這樣一個 [00:27:45] 把資料丟進來的這樣一個過程 [00:27:48] 它會呼叫這個torch.nn.module裡面的這個call [00:27:51] 這個function [00:27:52] 然後這個call function [00:27:54] 會直接去呼叫它的forward [00:28:00] 然後 [00:28:01] 這邊 [00:28:03] 有一個大概的就是在我們initialize模型的時候我們可以定義的一些層 [00:28:08] 像是linear [00:28:09] 就是 [00:28:10] 常用的線性層 [00:28:11] 所謂的線性層就是 [00:28:13] 乘以一個矩陣 [00:28:15] 那當然我們 [00:28:17] 如果 [00:28:18] 形象的表示它其實就是一個 [00:28:20] fully connected [00:28:21] 全連接的層 [00:28:23] 那 [00:28:23] 這邊 [00:28:24] convolution [00:28:25] 這邊是卷積層 [00:28:27] 第二個是卷積層 [00:28:28] 卷積層大家在圖像處理的時候肯定是 [00:28:32] 經常會遇到 [00:28:33] 是一個非常有名的層 [00:28:34] 它可以是2D 1D 3D [00:28:37] 在早期的NLP的使作過程當中 [00:28:39] 也會 [00:28:40] 時不時的出現這個卷積層 [00:28:43] 然後RNN跟LSTM [00:28:45] 這個在我們NLP的課上應該經常有 [00:28:49] 接觸到 [00:28:50] 那這兩個層其實就是序列資料生成的 [00:28:53] 序列資料生成的一個模組 [00:28:57] 然後max pooling這個也是常用於圖像處理的 [00:29:01] 內容 [00:29:03] 然後embedding [00:29:04] embedding往往是把資料 [00:29:06] 往往是把 [00:29:07] 一些電腦 [00:29:09] 實際上看沒辦法處理的資料轉化成能看得懂的 [00:29:12] 也就是說把一個 [00:29:14] 某個東西轉化成一個向量 [00:29:16] 那這個東西它又會有個對應的ID [00:29:20] 通過這個embedding層把ID映射到向量 [00:29:25] 它內部其實也就是一個 [00:29:27] 矩陣乘法的一個形式 [00:29:29] 然後再是Batch Normalize [00:29:31] 這個是我們在運算的時候它會有 [00:29:34] 它會需要 [00:29:36] 時不時的 [00:29:38] 進行一些 [00:29:39] 減平均除以標準差的動作 [00:29:41] 來防止它的資料過於發散過於極端 [00:29:45] 然後Sequential跟ModuleList、ModuleDict [00:29:48] 是 [00:29:49] 通過 [00:29:50] 大家可以就ModuleList跟ModuleDict [00:29:52] 就是 [00:29:54] 模組版本的Python的List跟Dictionary [00:29:58] 那這個Sequential其實就是把 [00:30:00] 很多個 [00:30:01] 層包成一個 [00:30:03] 上下串成一個大層 [00:30:07] 然後我們 [00:30:09] 在這個模型背面 [00:30:11] 建構出來以後 [00:30:12] 模型也會有一些自己的 [00:30:14] Member Function [00:30:15] 比方說 [00:30:16] Forward [00:30:17] 剛剛我們有講過就是 [00:30:18] 資料進來以後要怎麼處理 [00:30:20] 然後StateDictionary [00:30:23] 它會回傳一個OrderDictionary [00:30:25] 那這個Dictionary它的key [00:30:27] 是 [00:30:28] 參數的名稱 [00:30:30] 然後它的value就是參數的值 [00:30:32] 參數的內容 [00:30:34] 也就是參數對應的向量 [00:30:36] 那這邊 [00:30:37] 這邊 [00:30:38] StateDictionary它其實 [00:30:40] 我們可以連 [00:30:41] 它其實跟NameParameter有一點比較像一點 [00:30:45] 但是NameParameter它 [00:30:46] 回傳的是一個 [00:30:48] 回傳的是一個迭代器 [00:30:50] 就是我們可以用 [00:30:52] 復迴圈 [00:30:53] 來展現它裡面 [00:30:55] 把裡面的內容給Print出來 [00:30:57] 它裡面Print也是 [00:30:59] 參數的名稱跟Tensor [00:31:01] 那這邊 [00:31:02] 沒有Name的Parameter它實際上只會 [00:31:05] 它這個迭代器只會OP出來一些 [00:31:07] 只會吐一些 [00:31:08] 就是參數的 [00:31:09] 內容 [00:31:12] 然後model.train和EVAL [00:31:15] 它就是把模型的 [00:31:17] 模型的 [00:31:19] 一個模式轉換 [00:31:20] 就是把它變成Train [00:31:22] 或者是 [00:31:23] Evaluation [00:31:25] 訓練模式跟驗證模式之間的轉換 [00:31:29] 首先我們來看就是ModuleList該怎麼 [00:31:32] 大概的做法 [00:31:34] 左邊是我們創建出來的一個模型 [00:31:37] 那這個模型它裡面有三層 [00:31:39] 一層 [00:31:40] 三層都是Linear而且差不多 [00:31:43] 那在一個Input進來以後 [00:31:45] 這個Input會分別位給這三層 [00:31:47] 並且獲得他們各自的Output [00:31:50] 這個各自的Output串成一串 [00:31:52] 然後 [00:31:54] Return出來 [00:31:55] 那這一個方式其實就可以改寫成 [00:31:58] 就是因為它這三個 [00:32:00] 這三個Linear它實際上是 [00:32:02] 從 [00:32:03] 我們可以把它直接寫成一個 [00:32:05] List [00:32:06] 的形式 [00:32:08] 那這個List [00:32:11] 它其實 [00:32:12] 它也就是跟Python的List有點相似的地方 [00:32:15] 就是它可以通過這個中括號一個整數的方式 [00:32:18] 來取 [00:32:19] 它我們要的是其中的 [00:32:21] 某一層 [00:32:23] 當這個層越來越多就當這邊的Linear越來越多的時候 [00:32:28] 很多很多的Linear [00:32:29] 那實際上用這種方式其實會大大減少我們的 [00:32:33] 就是Coding的難度吧 [00:32:34] 就Coding的複雜程度 [00:32:39] 然後 [00:32:39] 第二個 [00:32:41] 是有關於Sequential [00:32:43] Sequential它底層的做法 [00:32:45] Sequential它有一個 [00:32:46] 自帶的Forward [00:32:48] 就是它的Input喂進來 [00:32:50] 它會 [00:32:51] 依序的餵給它自己的每一層 [00:32:55] 每一個模組每一個 [00:32:57] 麾下的模組 [00:32:58] 然後 [00:33:00] 這個前一個output [00:33:02] 就會餵給下一個的 [00:33:03] 喂到下一個作為下一個的Input [00:33:06] 這樣一層一層喂下去 [00:33:08] 一直到獲得這個最終的output [00:33:13] 就像在這裡我們這邊的左邊的模型它有三層 [00:33:17] 第一層是Linear [00:33:18] Linear的output再過一層Renew [00:33:21] 就是過一層 [00:33:22] 激活函數 [00:33:23] 然後再 [00:33:24] 過一層Linear [00:33:26] 那麼這邊就可以寫成把這三層包成一個大的Sequential [00:33:31] 這個Sequential就是 [00:33:32] 第一層是Linear第二層是Renew [00:33:34] 第三層還是Linear [00:33:36] 在一筆資料進來的時候 [00:33:37] 就可以直接丟給這個大的 [00:33:40] 組合起來這個層 [00:33:42] 它就會直接在底 [00:33:43] 底層幫我們 [00:33:45] 第一層的output會給第二層 [00:33:46] 第二層的output會給第三層 [00:33:48] 第三層的output再output出來 [00:33:49] 再return出來 [00:33:50] 作為 [00:33:51] Forward return value [00:33:54] 然後這個是 [00:33:56] 這個是Sequential這個 [00:33:59] 這個函數 [00:34:00] 這個功能 [00:34:02] 是這個模組它能夠 [00:34:04] 完成的一些功能 [00:34:07] 然後另外 [00:34:09] 為什麼我們就 [00:34:10] 接下來我們就會可能會產生一個問題就是為什麼我們不用 [00:34:13] Python它自己的 [00:34:15] List [00:34:16] 而我們一定要用 [00:34:17] Module List [00:34:19] 因為Module List其實我們剛剛也看到 [00:34:21] 它實際上完成的功能也就是 [00:34:25] 很大程度上 [00:34:27] 也就是 [00:34:28] Python List的這樣一個功能 [00:34:32] 就是 [00:34:32] 存儲各種各樣的模型然後串成一個List的功能 [00:34:35] 那為什麼我不直接用Python的List [00:34:38] 但實際上 [00:34:39] 我們就會發現 [00:34:40] 這個 [00:34:41] 我們 [00:34:42] 仔細去看它每一個 [00:34:43] 每一個Member Function的底層Code [00:34:46] 我們就會發現 [00:34:48] 它很多的Code的實作 [00:34:50] 都是用一個遞迴的方式實作 [00:34:52] 也就是當我們 [00:34:54] 比方說像下面這個例子 [00:34:56] 當我們去呼叫StateDictionary的時候 [00:34:58] 它實際上 [00:34:59] 我們回顧一下StateDictionary [00:35:02] 它需要return回來的 [00:35:04] 是這個模型 [00:35:07] 的所有參數名稱 [00:35:09] 跟參數的 [00:35:11] 內容 [00:35:12] 參數的Tensor [00:35:13] 它組成了一個 [00:35:15] OrderDictionary [00:35:17] 那也就是說 [00:35:18] 我們呼叫這個函數的時候 [00:35:21] 我們的Python需要往下找 [00:35:23] 找出所有的 [00:35:26] 所有的 [00:35:27] 參數 [00:35:28] 和它們對應的名稱並且return回來 [00:35:31] 這個尋找的過程 [00:35:32] 是一個 [00:35:33] 遞迴的過程 [00:35:35] 在呼叫StateDictionary的時候在下面 [00:35:38] 它會呼叫 [00:35:39] 自己每一個模組 [00:35:42] 每一個子模組的 [00:35:43] 呼叫StateDictionary [00:35:45] 並把它給加進來 [00:35:47] 加到自己的 [00:35:49] 加到這個 [00:35:51] 副模組 [00:35:52] 它的StateDictionary裡面去 [00:35:55] 所以 [00:35:56] 這邊是 [00:35:57] 通過NameCommon [00:35:58] 通過這個復迴圈來便利Self.下滑線Module的這個 [00:36:03] 成員變量 [00:36:04] 這個成員變量其實存的就是 [00:36:06] 模組類型的東西 [00:36:07] 那假如所以假如說 [00:36:09] 我們把 [00:36:11] 我們把 [00:36:12] 我們不用 [00:36:14] 模組類別的東西我們就用Python的基本類別 [00:36:16] 它如果只是一個List [00:36:18] 那麼 [00:36:19] 實際上它就並不是一個模組類別的東西 [00:36:23] 所以它就不會被存進來 [00:36:25] 就像這個範例 [00:36:26] 左邊 [00:36:27] 是一個 [00:36:29] 用ModuleList來實作的一個 [00:36:33] 實作的一個模型 [00:36:34] 右邊我們只是用Python的基本型 [00:36:37] 也就是 [00:36:37] 增刮號的那個模組 [00:36:40] 然後我們再把我們 [00:36:41] 創建出這樣一個物件以後在下面就創建出一個物件以後 [00:36:45] 再把這個物件的下滑線模組這個東西 [00:36:48] 也就是我們StateDictionary [00:36:50] 要遞迴要去尋找的這樣 [00:36:52] 就是 [00:36:53] 用來遞迴的這個 [00:36:55] 成員變量 [00:36:56] 把它印出來以後發現 [00:36:58] 用 [00:36:59] ModuleList [00:37:01] 它實際上能夠順利的存到這個下滑線模組這個 [00:37:05] 成員變量裡面去 [00:37:06] 但是如果我們用List [00:37:07] 它實際上這個模組是空的 [00:37:09] 也就是當我們 [00:37:11] 需要去回 [00:37:11] 回傳它所有參數的名稱的時候我們去呼叫StateDictionary的時候 [00:37:16] 如果我們用Python的基本型 [00:37:19] 實際上我們回傳出來是個空的東西 [00:37:21] 因為 [00:37:22] 它這個模組它裡面沒有存任何東西 [00:37:27] 所以這就是告訴我們就是 [00:37:28] 告訴我們一個結論就是 [00:37:30] 在 [00:37:31] 寫模型的時候 [00:37:33] 只要我們的 [00:37:35] 模組它裡面是有參數只要裡面有東 [00:37:38] 這個成員變量它裡面有參數 [00:37:40] 它是一個層的定義 [00:37:42] 模組的定義 [00:37:43] 那我們就要用Module [00:37:44] Torch.nn.Module [00:37:47] 這一個類別的東西 [00:37:50] 來實現對於 [00:37:52] 來實現對於這些 [00:37:54] 模組的存儲 [00:37:55] 而不要用Python的基本 [00:37:56] 型別 [00:38:00] 然後 [00:38:01] 接下來就是有關於訓練模式跟 [00:38:04] 驗證模式 [00:38:05] 因為剛剛我們在介紹 [00:38:07] Model [00:38:08] 介紹模型的 [00:38:09] 成員函數的時候我們有介紹過 [00:38:12] 就是在訓練模式跟驗證模式之間的轉換 [00:38:15] 在訓練模式當中 [00:38:18] 因為我們要對模型做訓練 [00:38:21] 那模型當中可能會有一些像Dropout [00:38:23] 就在訓練時候為了防止overfitting [00:38:26] 所做的一些 [00:38:28] 所做的一個 [00:38:30] 拋棄部分 [00:38:32] 拋棄部分元素的一個處理 [00:38:34] 一個Dropout和一個BatchNormalize [00:38:37] 這個也是在訓練過程當中我們會需要用到的 [00:38:41] 那這兩個 [00:38:42] 它會在Train訓練模式的時候被激活 [00:38:46] 它只會在訓練模式的時候 [00:38:49] 發揮作用 [00:38:50] 就Dropout才會真的丟東西 [00:38:52] 而在 [00:38:53] 驗證模式的時候 [00:38:56] Dropout就會不起作用 [00:38:58] 它這個Dropout跟BatchNormalize [00:39:00] 就會不起作用 [00:39:04] 那這是訓練模式跟驗證模式的區別 [00:39:06] 一般來說訓練模式就是我們在訓練模型之前 [00:39:09] 我們會Model.Train [00:39:11] 把它 [00:39:12] 把這個模型以及下面所有的子模組都變成訓練模式 [00:39:16] 然後在驗證的時候 [00:39:18] 就在我們有 [00:39:18] Evaluate [00:39:19] 就是 [00:39:20] 驗證集驗證Dataset進來的時候 [00:39:23] 會 [00:39:24] 呼叫這個Model.EVAL的這個 [00:39:27] 函數 [00:39:28] 把它變成驗證的模式 [00:39:31] 然後這兩個模式 [00:39:32] 它都是用遞迴的方式 [00:39:34] 也就是我們呼叫一個模型的 [00:39:38] Train的時候 [00:39:39] 它會 [00:39:40] 尋找下面的每一個 [00:39:42] 尋找下面的各個模組 [00:39:45] 也子模組就是每一層 [00:39:46] 每一個torch.n.module [00:39:48] 然後就類別的東西 [00:39:49] 它都會去呼叫它的Train [00:39:52] 它也是這樣遞迴下去的 [00:39:54] 所以這也是印證了我們之前說的 [00:39:56] 要在模組裡面寫模組 [00:39:57] 不要在模組裡面寫Python基本性別 [00:40:01] 然後 [00:40:04] 我們介紹完了模型大概的建構 [00:40:06] 接下來我們就是要訓練這個模型 [00:40:09] 那訓練我們會有兩步吧 [00:40:10] 就首先 [00:40:11] 資料會 [00:40:12] 未進來獲得預測結果 [00:40:14] 預測結果會傳出loss [00:40:15] 我們最小化loss就會回傳T度 [00:40:19] 那這個T度 [00:40:20] 通過 [00:40:21] 一個叫 [00:40:21] 反向傳播的方式來計算 [00:40:23] 也就是 [00:40:24] 這邊的loss.backward [00:40:27] 這個backward它完成的具體功能 [00:40:31] 就是 [00:40:32] 逐層的往回 [00:40:34] 去計算偏微分 [00:40:35] 去計算T度 [00:40:38] 通過這個偏微分來計算T度以後 [00:40:41] 再把T度存在 [00:40:43] 各個參數的 [00:40:44] .grade這個變量裡面 [00:40:47] 注意是各個參數的grade這個變量 [00:40:50] 而不是中間計算結果的變量 [00:40:53] 的grade [00:40:54] 成員變量這裡面 [00:40:57] 它在計算的時候呢 [00:40:58] 會通過一個 [00:41:00] 有象 [00:41:01] 無環圖 [00:41:02] 就是 [00:41:03] DAG [00:41:05] 會構建這個DAG [00:41:07] 然後 [00:41:09] 用一種類似於BFS的方式 [00:41:11] 來 [00:41:12] 逐層的去 [00:41:13] 一層一層 [00:41:15] 往回 [00:41:15] 去回傳 [00:41:17] T度 [00:41:18] 去計算那個偏微分 [00:41:20] 那接下來 [00:41:22] 這邊有一個簡單的範例 [00:41:24] A跟B就是我們的參數 [00:41:26] 我們 [00:41:27] 傳進來兩個變量A跟B [00:41:30] 然後呢 [00:41:31] 這個A跟B [00:41:32] 會經過這樣的運算 [00:41:35] 就是B先經過relude [00:41:37] 然後A跟C再 [00:41:39] 矩陣乘法 [00:41:40] 然後 [00:41:41] C跟D再加一加 [00:41:43] 就會獲得我們的E [00:41:44] 這個E就是相當於我們模型的output [00:41:47] 假設這個E就是我們模型的output [00:41:50] 那 [00:41:51] 這邊 [00:41:51] 他在forward [00:41:53] 這個變 [00:41:54] 在跑這個forward過程當中 [00:41:56] 他會去記錄說 [00:41:58] 這個E他是如何從AB給算出來的 [00:42:02] 他會經歷這個C這個中間函數 [00:42:05] 跟D這個中間函數 [00:42:08] C這個中間的tensor跟D這個中間的tensor [00:42:12] 然後 [00:42:13] 然後說B是通過relude來算出這個C的 [00:42:16] 他這邊就會記錄 [00:42:18] C如果要返回去 [00:42:19] 就要算這個backward function [00:42:22] 那麼 [00:42:22] 他會經過這個relude backward [00:42:25] 來回來回到這個B這個地方 [00:42:30] 然後 [00:42:31] 這邊呢 [00:42:31] 就是他這邊是矩陣 [00:42:38] 這邊是矩陣乘法 [00:42:40] 來算出通過矩陣乘法來算出 [00:42:43] 這個D這個變量 [00:42:45] 然後 [00:42:46] 所以 [00:42:47] 這邊也 [00:42:48] 這邊D也儲存了一個矩陣乘法的backward function [00:42:52] 那每一個 [00:42:54] 中間的變量都會有一個乘一個 [00:42:57] 他是如何算出來的 [00:42:58] 那麼這個計算方式對應的backward function [00:43:02] 並且還有一個dependency [00:43:04] 就是他們的依賴 [00:43:06] 這個依賴表示的意思就是說 [00:43:08] 假如說這個D這個 [00:43:10] 我們要跑D [00:43:11] 往回 [00:43:12] 算A跟C的 [00:43:15] 這個偏微分的時候 [00:43:17] 我們需要先算出 [00:43:19] 就是E跟D [00:43:20] 從E到D的這個偏微分 [00:43:23] 為什麼因為 [00:43:24] 我們實際上是 [00:43:25] 我們實際上在backward [00:43:27] 往前傳遞的時候 [00:43:29] 我們是從這個E開始算 [00:43:32] 各個T度的 [00:43:33] 然後我們要優化的其實也是 [00:43:36] 就是把這個E的這個值 [00:43:38] 可能是最小化或者 [00:43:40] 怎樣 [00:43:41] 所以 [00:43:41] 我們其實要算假如說我們要算這個A的T度 [00:43:44] 我們是算 [00:43:45] E對A的偏微分 [00:43:47] 那E對A的偏微分要怎麼算呢就是 [00:43:49] 先算E對D的偏微分再D對A的偏微分 [00:43:54] 它是用一個這樣 [00:43:56] 就是 [00:43:57] 它是這樣子 [00:43:58] 通過中間的這樣一些變量給傳遞回去的 [00:44:02] 所以我們要算 [00:44:03] 我們這個D要算往前的偏微分的時候 [00:44:07] 我們要先算E跟D的偏微分 [00:44:09] E對D的偏微分再把D再傳回去 [00:44:12] 所以D就是depend [00:44:14] D就是依賴E [00:44:16] 回傳的這一步 [00:44:17] 所以它會依賴一個 [00:44:20] 就是E的這個偏微分 [00:44:22] 而C呢 [00:44:23] C [00:44:24] 它會依賴兩個 [00:44:26] 就是它有兩條路是E先走D再到C [00:44:29] 還有就是 [00:44:30] E就是直接到C [00:44:31] 所以它的 [00:44:32] 依賴就會是2 [00:44:34] 它有兩條路來算這個偏微分 [00:44:37] OK [00:44:38] 那我們 [00:44:40] 直接去跑它的 [00:44:42] 就是反向傳播 [00:44:43] 當我們呼叫backward的時候 [00:44:45] 它會就是 [00:44:46] PyTorch會構建這樣一個 [00:44:49] 反向的DAG [00:44:53] E [00:44:54] 通過E來跑backward的時候 [00:44:56] E的dependency是0 [00:44:58] 它這邊會有一個function task queue [00:45:01] 這個function task就是我們現在可以執行的backward function [00:45:06] 什麼叫現在可以執行呢 [00:45:07] 就是dependency [00:45:08] 它沒有 [00:45:09] 依賴於其他 [00:45:11] 沒有依賴於其他backward function要先完成 [00:45:15] 它自己才能做 [00:45:16] 那它就不能馬上 [00:45:18] 執行嘛 [00:45:19] 如果說別人要先做它自己才能做的話 [00:45:22] 那它不能馬上執行 [00:45:23] 它就不會被加到這個function task queue [00:45:26] 你看如果D是0它可以直接做了 [00:45:28] 那麼它就會被加到這個 [00:45:30] function task queue裡面 [00:45:32] 這個E它的D是0 [00:45:35] 它就會被加進來 [00:45:36] 並且 [00:45:37] 它會算著E到D的偏微分 [00:45:40] E到C的偏微分 [00:45:41] 這兩個偏微分的計算 [00:45:42] 其實就寫在這個 [00:45:44] add backward裡面 [00:45:47] 然後 [00:45:47] 這個add backward [00:45:49] 跑完以後 [00:45:51] 它這兩個變量 [00:45:53] 的D就會減1 [00:45:54] 然後 [00:45:58] 也就是在這兩個變量的位置 [00:46:02] 它會有算到這個 [00:46:03] E對D的偏微分跟E對C的偏微分 [00:46:07] 但注意這個算出來的偏微分並不會被存在這些中間變量的點gradient [00:46:14] 成員變量裡面 [00:46:15] 因為這個C跟D它是一個 [00:46:18] 中間tensor [00:46:20] 它並不是 [00:46:22] 它並不是一個 [00:46:24] 參數 [00:46:25] 它並不是像A跟B這樣的leaf node [00:46:27] 它只是一個 [00:46:28] 中間的 [00:46:29] 中間node而已 [00:46:30] 所以中間node它並不會去 [00:46:32] 存 [00:46:34] t度這個東西 [00:46:36] 然後 [00:46:37] E [00:46:38] 算出往回算出這個 [00:46:41] 對D跟C的偏微分以後 [00:46:43] 它們的dependency會各減1 [00:46:45] 因為畢竟E已經好了嘛 [00:46:47] 這時候我們會發現D的dependency變成了0 [00:46:53] 那這樣的話我們就可以算D了 [00:46:56] 因為只有0 [00:46:57] 接下來我們就會把D的這個 [00:46:59] MN backward [00:47:01] 就是它是通過 [00:47:02] 矩陣乘法來算出來的 [00:47:04] 把這個MN backward給丟給這個function task queue [00:47:08] 之後E的那個backward會 [00:47:11] 從這裡pop出來 [00:47:13] 然後再把MN backward給丟進去 [00:47:15] 在這個D在做反向傳播 [00:47:20] 然後 [00:47:22] D在算了以後 [00:47:24] 這邊 [00:47:26] 我們就會算 [00:47:27] 算出在C這個點 [00:47:29] 因為D它 [00:47:30] 一條線是指向A一條線是指向C [00:47:33] 這個A其實也會被算出來 [00:47:34] 然後接下來就是 [00:47:36] 它會算出C這個點它的 [00:47:38] 它的這個點它的偏微分是多少 [00:47:40] 那這個偏微分就是 [00:47:42] 首先就是一條路 [00:47:44] E到C這條路 [00:47:45] 前面這個是在第一步就算好的 [00:47:48] 第二步算出來是 [00:47:49] E通過D來算C [00:47:52] 就是 [00:47:53] 先對D做偏微分 [00:47:54] 再D再做對C做偏微分 [00:47:57] 是這樣一個動作 [00:47:59] 然後這個算好以後 [00:48:01] 那C的偏微分也完成了 [00:48:04] 那麼 [00:48:05] 這樣的話 [00:48:07] 我們就可以跑C的backward [00:48:10] 就是relude backward [00:48:12] 然後C再往前 [00:48:13] 再傳給B [00:48:15] 然後這下我們就可以算出來 [00:48:18] A的偏微分就是 [00:48:20] E [00:48:22] 傳到D再傳到A [00:48:24] 是這樣是這個運算的結果 [00:48:26] 那B的偏微分 [00:48:27] 就是E通過D [00:48:28] EDCB [00:48:30] 跟ECB [00:48:31] 這兩條路的偏微分 [00:48:33] 算出來的偏微分相加 [00:48:35] 然後也會存在B的 [00:48:38] gradient的這個變量裡面 [00:48:40] 然後這下來 [00:48:41] C的算完以後 [00:48:43] 把它的relude backward [00:48:45] 丟出去 [00:48:46] 這樣的話A跟B的 [00:48:49] 梯度就被算完了 [00:48:51] 那這就大概就是一個backward function它的底層實作的方法 [00:48:55] 那我們在 [00:48:56] 具體在 [00:48:57] 呼叫這個backward的時候其實我們就只要寫一行 [00:49:01] 就是loss [00:49:02] 就是我們算出來那個loss的tensor [00:49:04] 點backward [00:49:05] 這個過程其實就被完成了 [00:49:09] 所以大家其實這個過程它是 [00:49:11] 其實是 [00:49:12] Pytorch自己有 [00:49:14] 實作好的 [00:49:17] 那另外呢 [00:49:18] 另外這邊還有三種方法可以規避 [00:49:20] 梯度的計算 [00:49:23] 首先第一種是 [00:49:24] tensor.requires [00:49:26] grade [00:49:26] 就是 [00:49:27] 需不需要傳梯度呢 [00:49:29] 不需要 [00:49:31] 把它設定成false [00:49:33] 把它設定成false以後 [00:49:35] 它底層的代碼就會 [00:49:37] 看說它這個地方是不需要傳梯度的 [00:49:39] 那麼 [00:49:41] 它這個梯度就 [00:49:42] 就會 [00:49:43] 相當於是把這個 [00:49:45] 相當於是把這個node [00:49:47] 看作 [00:49:48] 一個 [00:49:49] 非leave node [00:49:50] 就看作相當於一箇中間結果的樣子 [00:49:53] 然後它這個 [00:49:54] 它這個 [00:49:55] 算出在這個這一步算出來 [00:49:56] 算出梯度就不會被保存下來 [00:49:58] 然後 [00:49:59] 還有一 [00:50:00] 還有一種是torch.nodegrade [00:50:01] 就是它這邊有一個context manager [00:50:04] 就是上下文管理器吧 [00:50:06] 我們可以叫做 [00:50:07] 在這個width.backward [00:50:09] 這個 [00:50:10] 管理器下的code [00:50:12] 也不會被算梯度 [00:50:15] 就是 [00:50:16] 也會當成中間的東西 [00:50:18] 不會被存下來梯度 [00:50:21] 第三個就是detach [00:50:23] detach跟前面兩個不一樣 [00:50:25] 它指的是在tensor [00:50:27] 比方說我們中間 [00:50:29] 中間我們比方說算出了某一個 [00:50:31] 中間產物 [00:50:32] detach就是說 [00:50:34] 讓這個中間產物跟原來的這個因為我們 [00:50:37] 算梯度的時候會有一個 [00:50:39] 有像 [00:50:41] 有像的 [00:50:42] dag嘛 [00:50:43] 就有一個graph [00:50:45] 那 [00:50:46] detach就是說這個 [00:50:47] tensor [00:50:48] 這一個 [00:50:49] 變量 [00:50:50] 它從graph當中 [00:50:51] 被摘出來 [00:50:53] 所以 [00:50:55] 它會創建一個新的tensor [00:50:57] 這個tensor它不會改變這個 [00:50:59] 底層的存儲 [00:51:01] 它會創建一個 [00:51:02] 新的view [00:51:03] 然後 [00:51:04] 它的連續性也不會改變 [00:51:06] 但是它 [00:51:07] 是已經從這個 [00:51:09] forward backward的這個系統當中脫離的這樣一個tensor [00:51:15] 然後我們介紹完了這個backward [00:51:18] forward backward就介紹完了 [00:51:20] 接下來就是我們要train一個模型了 [00:51:23] 模型 [00:51:23] 它會 [00:51:25] 它會吃data [00:51:27] data會算出預測結果 [00:51:29] 然後預測結果 [00:51:31] 再算出loss [00:51:32] loss再回傳梯度 [00:51:34] 那這樣一個 [00:51:35] 那模型 [00:51:36] 的訓練 [00:51:38] 參數更新大概就是這樣的一個過程 [00:51:40] 那 [00:51:41] 另外呢 [00:51:42] 在 [00:51:43] 這個主循在主循環的之外 [00:51:46] 我們還需要有比方說我們需要準備好dataset [00:51:50] 對吧 [00:51:50] 我們需要有data [00:51:52] data在之前我們的regression [00:51:54] 有那些 [00:51:56] 像是什麼線性迴歸啦或者是 [00:51:59] 之前那個二 [00:52:00] 之前那個多項式迴歸當中 [00:52:02] 就是一個 [00:52:03] 一些空間當中的點 [00:52:05] 那 [00:52:07] 第二個就是我們需要構建出我們的模型 [00:52:09] 模型就是input跟output之間 [00:52:12] 它是什麼樣的關係 [00:52:13] 它可能是線性關係也可能是卷積的關係 [00:52:15] 它也可能是rn的關係 [00:52:17] 等等等等 [00:52:19] 另外呢我們會需要構建出我們的optimizer [00:52:22] 就是我們需要定義 [00:52:23] 我們要用什麼樣的optimizer [00:52:25] 什麼樣的梯度下降工具 [00:52:28] 然後我們需要 [00:52:30] 對於這個特定的任務 [00:52:32] 我們要有什麼樣的loss function來衡量 [00:52:34] 模型的預測結果 [00:52:36] 跟我們希望它 [00:52:38] 就跟標準答案之間的距離 [00:52:41] 然後 [00:52:42] 我們還需要在模型訓練完以後去驗證這個模型 [00:52:46] 那實際上就是 [00:52:47] 把 [00:52:48] 測試級 [00:52:49] 我們會有一個專門獨立於訓練級以外的測試級 [00:52:52] 這個測試級 [00:52:54] 的資料一個個丟給模型看看模型能不能準確的預測 [00:52:58] 這些這些資料 [00:53:00] 注意它是模型訓練的時候沒有接觸過 [00:53:02] 不要用訓練的資料來測試 [00:53:07] 然後 [00:53:08] loss function [00:53:09] 那我們之前有講 [00:53:10] loss function的作用是什麼呢 [00:53:12] 它的作用就是 [00:53:13] 評價 [00:53:14] 模型的output [00:53:15] 跟標準答案之間的距離 [00:53:18] 它是一個量化的指標 [00:53:20] 就算 [00:53:22] 就算可能 [00:53:24] 比方說是分類任務它照樣也會有一個像這樣的量化指標 [00:53:28] 那這邊有很多我們在 [00:53:31] 在實際PyTorch訓練DL [00:53:34] 訓練深度學習模型當中經常會遇到的 [00:53:37] 經常會用到的一些loss function [00:53:39] 比方說cross entropy loss [00:53:40] 交叉上 [00:53:41] 就是我們所謂的 [00:53:43] 分類任務當中會需要的 [00:53:46] 需要的loss function [00:53:48] 那MSE就是我們 [00:53:50] 在 [00:53:51] 這一節助教課開頭所講的 [00:53:54] 就是標準答案減去 [00:53:56] 預測結果 [00:53:57] 在平方在平均 [00:53:59] 它是專門 [00:54:00] 它一般會用於 [00:54:02] 迴歸任務 [00:54:03] 也就是我們會有一個具體的數值 [00:54:05] 我們希望 [00:54:06] 這往往是一個像打分之類的 [00:54:09] 工作 [00:54:10] 我會希望模型的output是一個具體的數值 [00:54:13] 而 [00:54:15] 標準答案也是一個具體的數值 [00:54:17] 然後它的作用就是把兩個具體的數值 [00:54:20] 評價兩個具體數值之間的距離 [00:54:23] 第三個是KL [00:54:25] Divergence loss [00:54:27] 那這個往往被用於 [00:54:29] 資料的 [00:54:30] 知識蒸餾的這個方法 [00:54:32] 所謂知識蒸餾就是我們有一個 [00:54:34] Teacher模型 [00:54:35] 就是我們有一個模型當作老師 [00:54:37] 這個模型往往是更大更復雜的模型 [00:54:41] 然後我們會有個student模型 [00:54:42] 就是會有一個小模型來做 [00:54:45] 大模型的student [00:54:47] 我們希望在小模型當中去 [00:54:50] 用一個比較小的模型去編碼大模型的能力 [00:54:54] 所以我們會希望小模型的分佈 [00:54:56] 就它的output出來的分佈 [00:54:58] 跟大模型的越接近越好 [00:55:00] 所以KL divergence [00:55:02] 所評價的就是 [00:55:04] 兩個機率分佈之間的距離 [00:55:06] 然後 [00:55:07] BCE [00:55:08] BCE就是 [00:55:09] 所謂的 [00:55:10] Binary Cross Entropy [00:55:12] 那這個 [00:55:13] 它對於cross entropy之間的 [00:55:16] 不同點就是 [00:55:17] BCE [00:55:18] 它只能用於 [00:55:20] 它只能用於二元分類 [00:55:22] 兩個class的分類 [00:55:24] 它並不能使用在 [00:55:26] 很多class的分類 [00:55:29] 那麼另外 [00:55:30] 還有一些是有關於 [00:55:31] 我們其實 [00:55:33] 在未來的實作當中可能會經常遇到 [00:55:36] 但是PyTorch裡面並沒有實際寫出來的一些loss function [00:55:40] 比方說contrastive loss [00:55:42] 就是 [00:55:43] 它是適用於所謂的contrastive learning [00:55:45] 對比學習 [00:55:47] 就是在 [00:55:48] 就是我們output出來往往是一個 [00:55:51] 一個 [00:55:52] vector representation [00:55:54] 是一個具體的vector [00:55:56] 在高維的空間當中 [00:55:57] 我們希望vector去 [00:55:59] 靠近某一個 [00:56:00] 正樣本的 [00:56:02] 值 [00:56:03] 並且儘可能去遠離那些負樣本 [00:56:06] 那我們就會用到這個contrastive loss [00:56:08] 那下面這個entropy [00:56:10] entropy它往往可能不一定會是一個完整的 [00:56:13] loss function [00:56:14] 它往往 [00:56:15] 它有的時候在很多論文裡面 [00:56:17] 它會作為一個loss function [00:56:19] 裡面的一個 [00:56:21] 模組而存在 [00:56:23] 為的就是提升 [00:56:25] 模型預測結果的信心度 [00:56:27] 就是 [00:56:28] 當我們模型預測的 [00:56:30] 分數 [00:56:31] 大多數集中在某一個 [00:56:33] 某一個類別而不是分散在 [00:56:35] 平均分配的時候 [00:56:37] 也就是模型 [00:56:38] 它很自信 [00:56:40] 我的預測結果就是某一類 [00:56:43] 的時候 [00:56:44] 它的entropy的值就會小 [00:56:46] 也就是它的loss function [00:56:47] 它的這個loss值就會小 [00:56:50] 加這個也就是說希望模型能夠 [00:56:53] 儘可能的去 [00:56:55] 提高它的信心度 [00:56:57] 那這是我們常用的一些loss function [00:56:59] 接下來就是optimizer [00:57:01] 相對於loss function這種不同的任務我們會有不同的loss function [00:57:06] 那optimizer實際上就相對簡單 [00:57:08] 我們一般來說 [00:57:10] 我們一般來說會用到的 [00:57:12] 基本上就是這個adam [00:57:14] adamw就是加了weight decay的adam [00:57:19] 基本上沒有特別的情況我們都會 [00:57:22] 默認就是adam [00:57:23] 來作為我們的optimizer [00:57:26] 優化器來使用 [00:57:29] 那我們介紹了前面的各個深度學習的組件以後 [00:57:33] 接下來就是有關於 [00:57:35] 在training的過程當中 [00:57:37] 會有這樣一個最 [00:57:39] 一些基本的操作 [00:57:40] 就是首先 [00:57:42] 把資料為給模型算出模型的預測結果 [00:57:46] 模型的預測結果 [00:57:48] 在跟ground truth一起 [00:57:50] 這邊其實還有一個ground truth [00:57:52] 模型的預測結果跟ground truth一起算出loss值 [00:57:55] 然後loss在反向傳播 [00:57:59] 然後算出 [00:58:01] 就是 [00:58:02] 算出梯度 [00:58:04] 梯度再給optimizer [00:58:06] 來更新模型參數 [00:58:08] 那注意就是每一輪 [00:58:10] 每一個step訓練 [00:58:11] 每一輪梯度計算之後 [00:58:14] 我們會需要 [00:58:16] 把這個optimizer的 [00:58:18] 前一輪算出來的梯度 [00:58:20] 給清除掉 [00:58:21] 因為不然的話我們 [00:58:22] 在 [00:58:24] 我們不斷的去算梯度的過程當中這個梯度 [00:58:26] 他會不斷的累加累加累加 [00:58:29] 實際上可能對於我們 [00:58:31] 模型訓練的過程當中來說 [00:58:33] 是我們不願意看到的 [00:58:37] OK [00:58:37] 那這一個主循環的實作呢我們會有一個範例 [00:58:42] 這個範例是一個線性迴歸的範例 [00:58:45] 大家可能在作業的說明當中也有看到過 [00:58:48] 這線性迴歸就是在 [00:58:51] 這個二維空間當中有這樣一些點 [00:58:53] 這些點呢 [00:58:55] 我們希望用一條 [00:58:56] 用一個直線 [00:58:58] 去表示這個點 [00:58:59] 那這個點呢就是他 [00:59:00] 他們各自的分母他並不會是一個直線 [00:59:03] 就像在實際生活當中 [00:59:05] 現實中現實的任務往往是一個複雜非線性的 [00:59:09] 非線性的任務 [00:59:10] 那他往往每一筆data就每一個現實狀況他都會有自己的bias [00:59:15] 就像這些紅點一樣 [00:59:16] 他並不會完全的一條直線 [00:59:18] 來排列 [00:59:19] 那我們 [00:59:22] 往往對現實的 [00:59:23] data做建模的時候 [00:59:25] 會用一個比方說這邊我們希望用一個 [00:59:28] 直線 [00:59:29] 來對 [00:59:29] 這些現實的 [00:59:30] data來做建模 [00:59:32] 用直線來表示這些紅點 [00:59:36] 所以 [00:59:38] 所以 [00:59:40] 接下來就是 [00:59:41] 有關於 [00:59:42] 我們把這個 [00:59:44] 程式寫出來以後 [00:59:47] 首先就是有一些像這些 [00:59:49] input data跟output data的 [00:59:52] tensor [00:59:53] 那input data他會有x座標有y座標 [00:59:57] 然後 [00:59:58] 模型的定義 [01:00:00] 模型的定義 [01:00:01] 這邊因為是linear regression [01:00:03] 所以 [01:00:04] input size是1 [01:00:05] output size也是1 [01:00:08] 然後在input size跟output size都是1的時候 [01:00:11] 並且他 [01:00:12] 默認是有bias [01:00:14] nn.linear他的bias [01:00:16] 他裡面有個參數是bias [01:00:18] bias的默認值是true [01:00:19] 大家也可以把它變成false [01:00:23] 這個他的關係就是 [01:00:24] 這個他在他定義input跟output之間的關係 [01:00:27] 就是y等於wx加b [01:00:29] output等於w乘以input [01:00:30] input再加一個bias [01:00:33] 然後 [01:00:34] 那 [01:00:35] 那input就是我們的xtrain那output就是ytrain [01:00:40] 下面的criterion就是 [01:00:42] 因為我們是linear regression [01:00:45] 我們是一個迴歸任務 [01:00:46] 我們的 [01:00:48] output是一個數值 [01:00:51] 我們的ground truth是數值模型的預測也是數值 [01:00:53] 要讓數值更接近我們用的是 [01:00:56] mean square error loss [01:01:00] 這邊我們就定義 [01:01:01] 我們的loss function [01:01:03] 然後 [01:01:04] 再是optimizer,optimizer我們採用 [01:01:07] 一個比較基本的 [01:01:08] stochastic gradient descent [01:01:10] 就是一個t度下降的optimizer [01:01:13] 然後 [01:01:14] optimized的對象就是模型的 [01:01:18] 參數 [01:01:18] 然後他這邊會定義一個learning rate [01:01:21] learning rate就是 [01:01:23] 我們算出t度以後 [01:01:25] 我們的步長 [01:01:26] 我們所謂的步長就是我們 [01:01:29] 一般來說learning rate都會定義一個 [01:01:31] 在 [01:01:32] 1的 [01:01:33] 如果模型大的話可能是10的-5次方 [01:01:36] 如果模型比較小的話可能是-3甚至有可能是-2 [01:01:40] 次方 [01:01:41] 但一般來說是這樣一個數量級 [01:01:45] 然後接下來就是我們進入我們的模型訓練的主循環 [01:01:49] 就像我們一開始的範例給大家介紹的 [01:01:52] 把資料丟給模型算出這些資料的output [01:01:56] 就算出這些資料的預測結果 [01:01:58] 這些預測結果再跟ground truth [01:02:01] 來算出 [01:02:02] loss值 [01:02:05] 也就是 [01:02:05] 在這個資料集上 [01:02:07] 模型會 [01:02:08] 模型的效能評估 [01:02:10] 所謂的效能評估 [01:02:11] 量化的評估 [01:02:12] 就是這個loss值 [01:02:15] 預測結果跟output之間的 [01:02:18] 跟ground truth之間的距離 [01:02:20] 然後 [01:02:21] 最小化這個loss值 [01:02:23] 就會需要從 [01:02:25] 把前一輪的t度清空 [01:02:27] 然後再算出通過loss的backward [01:02:30] 算出新的t度 [01:02:31] 然後再根據這個t度 [01:02:34] 來 [01:02:35] 優化模型參數 [01:02:39] 然後 [01:02:40] 這樣一輪輪跑完以後 [01:02:42] 就會我們就會獲得這樣一條 [01:02:44] 大概就可以代表這個 [01:02:45] 紅點這個趨勢的這樣一條 [01:02:48] 直線 [01:02:52] 在這個過程當中訓練過程當中我們把 [01:02:55] 我們一步一步往前訓練我們就會發現這個loss值 [01:02:57] 就模型 [01:02:59] 算出來這個loss就越來越小越來越小 [01:03:04] 然後呢 [01:03:05] 我們再 [01:03:06] 進一步的去觀察它裡面的行為 [01:03:08] 那這個模型是y.wx加b [01:03:11] 它裡面就有兩個參數一個是w一個是b [01:03:13] 就是我們所謂的 [01:03:14] 第一個是weight [01:03:15] 第二個是bias [01:03:17] 那weight因為它是 [01:03:19] 因為它input跟output都是一維 [01:03:20] 不是一維是 [01:03:22] 只有一個數 [01:03:23] 所以它的weight [01:03:25] 它的weight就是 [01:03:26] 也是一個數 [01:03:27] 它的bias也是一個數 [01:03:29] 然後我們跑zero grade的時候 [01:03:32] 它的weight的 [01:03:34] t度就會是 [01:03:36] 空的 [01:03:37] 然後 [01:03:38] 它的bias也是空的 [01:03:39] 然後我們在 [01:03:41] loss點backward的時候我們再把它印出來會發現 [01:03:44] weight跟bias [01:03:45] 都有了一個新的值 [01:03:46] 這個值就是我們算出來的t度 [01:03:51] 然後我們在optimizer.step的時候 [01:03:55] 我們就會通過這個weight [01:03:57] 在乘上learning rate [01:03:59] 來更新我們的 [01:04:02] 模型的參數就是 [01:04:03] 我們的w跟b [01:04:05] 那這時候我們會發現 [01:04:07] 0.41 [01:04:08] 0.65被更新到了0.4065 [01:04:13] 也就是 [01:04:13] 這個weight在乘上learning rate [01:04:16] 這個learning rate [01:04:17] 它會是10的 [01:04:20] 可能是10的-3次方之類的大小 [01:04:24] 然後 [01:04:26] 然後 [01:04:27] optimizer.step更新完模型的權重 [01:04:30] 更新完模型的權重以後 [01:04:31] 模型裡面的參數以後 [01:04:33] 再optimizer.zero grade [01:04:35] 那這個gradient [01:04:36] 就是這個 [01:04:38] t度 [01:04:39] 它又會被清空 [01:04:41] 變成none [01:04:42] 回到none [01:04:43] 然後來準備下一次的更新 [01:04:45] 那這就是我們 [01:04:47] 這就是我們那個 [01:04:49] 一個最簡單的linear regression的範例 [01:04:53] 那麼在 [01:04:54] NLP [01:04:55] 接下來我們就會引入NLP [01:04:57] 自然語言處理的任務當中 [01:04:59] 模型的訓練的行為它大概是什麼 [01:05:02] 我們之前 [01:05:04] 所 [01:05:05] 給大家的範例其實都是一些非常簡單的模型 [01:05:07] 它裡面的參數其實就那麼一兩個 [01:05:10] 就像我們之前說的 [01:05:12] 第一個範例多項式 [01:05:14] 它的參數就是a跟b [01:05:16] 第二個linear regression它的參數就是w跟b [01:05:19] 那對於我們的自然語言處理來說 [01:05:21] 因為自然語言 [01:05:22] 它實際上是一個更加複雜的非線性系統 [01:05:26] 所以 [01:05:27] 它裡面的特徵 [01:05:29] 實際上會比這種最簡單的 [01:05:31] 就在 [01:05:32] 空間裡面的點這種特徵 [01:05:34] 要更要複雜的多 [01:05:37] 所以 [01:05:39] 我們在就是 [01:05:41] 我們在訓練這個自然語言處理的時候 [01:05:43] 我們的模型不管是模型還是資料 [01:05:46] 都會 [01:05:47] 遠遠遠遠高於之前的那些linear regression [01:05:51] 它可能會到它可能會到達的量級 [01:05:54] 就模型參數的量級可能是 [01:05:57] 多少個million [01:05:58] 或者多少個billion [01:06:01] 就是有可能你這個很多就是 [01:06:04] 你看那個GPU好像有好幾十個 [01:06:07] G的 [01:06:07] 那個大小 [01:06:08] 這VRAM大小 [01:06:09] 實際上你可能 [01:06:10] 模型塞進去都不一定塞得下 [01:06:13] 然後資料量就是更加恐怖 [01:06:15] 它有可能會上億 [01:06:17] 就上 [01:06:17] 千萬上億的這個 [01:06:19] 級別 [01:06:20] 所以 [01:06:21] 這就 [01:06:22] 產生了一個問題就是我們之前算loss的時候 [01:06:24] 我們之前算loss的那個 [01:06:27] 的值 [01:06:28] 它的在空間當中的變化 [01:06:30] 的那個區面的時候 [01:06:32] 實際上 [01:06:33] 我們是把所有的data [01:06:35] 全部丟給模型來算 [01:06:37] 變化 [01:06:38] 但是 [01:06:40] 但是在 [01:06:41] 自然源處理的任務當中 [01:06:43] 我們不可能把所有的資料丟給模型 [01:06:45] 因為資料量實在是太大了 [01:06:47] 所以 [01:06:48] 我們的處理方法 [01:06:49] 就是在資料量裡面 [01:06:51] 取出一批一批的資料 [01:06:53] 通過這一批資料來代表整一個資料dataset的分佈 [01:06:58] 所以這也是為什麼 [01:06:59] 我們如果經常 [01:07:01] 就如果有接觸 [01:07:02] deep learning的話 [01:07:03] 就大家經常會聽到說 [01:07:05] 儘可能的把data的這個 [01:07:07] badge size設大 [01:07:08] 會比較好 [01:07:09] 為什麼呢 [01:07:10] 就是因為 [01:07:11] 大badge的 [01:07:12] badge size大了以後 [01:07:14] 他這個badge [01:07:15] 就更能夠 [01:07:17] 去表示這個 [01:07:18] dataset的分佈 [01:07:19] 如果小badge如果是一筆的話他有可能會有各種自己的bias [01:07:24] 所以我們需要 [01:07:26] 我們需要有的 [01:07:28] 在 [01:07:28] 在 [01:07:29] 贈源處理當中 [01:07:30] 我們需要有的其實是在 [01:07:32] 像CV領域其實像還有 [01:07:34] 訊號處理其實也是一樣 [01:07:36] 我們需要給data [01:07:38] 來 [01:07:38] 分批 [01:07:39] 位給模型 [01:07:41] 所以我們需要有這個dataset跟dataloader的 [01:07:44] 物件 [01:07:45] 那dataset就是我們用來存儲 [01:07:47] 就是PyTorch裡面自帶的 [01:07:49] 一個用來存儲 [01:07:52] 存儲 [01:07:53] 資料的這樣一個資料結構 [01:07:56] 它裡面 [01:07:58] 它裡面的 [01:08:00] 會有三個成員變量就是我們一定要寫出來的 [01:08:03] 第一個就是初始化的這樣一個 [01:08:06] 動作 [01:08:07] 他是把 [01:08:09] 他是把 [01:08:10] 我們 [01:08:11] 我們的data往往會在initialize [01:08:13] 作為一個參數給傳進來 [01:08:15] 然後存在 [01:08:16] 這邊是命為self.data [01:08:19] 存在這個self.data的 [01:08:21] 那個成員變量裡面 [01:08:22] 然後再通過這個getitem [01:08:24] 兩個下滑線getitem兩個下滑線 [01:08:26] 就是 [01:08:27] 通過這個index這個指標 [01:08:29] 這個 [01:08:31] 指標 [01:08:32] 來中括號 [01:08:33] 取出這個getitem也就是我們平常 [01:08:36] 我們寫Python list的時候 [01:08:38] 我們後面的那個 [01:08:40] 中括號一個整數來取出 [01:08:42] 幾筆的這樣一個動作 [01:08:45] 然後 [01:08:46] 接下來就是有關這個length [01:08:48] 這個length在我們 [01:08:49] 之後 [01:08:50] 獲得這個我們在之後在實作這個data loader的時候 [01:08:55] 是也會需要 [01:08:56] 寫出來的 [01:08:57] 然後當我們 [01:08:59] 去 [01:08:59] 建立一個新的data loader的變量的時候 [01:09:02] data loader的物件的時候 [01:09:04] 我們會 [01:09:04] 第一個闡述我們可以會傳入我們的 [01:09:07] dataset的 [01:09:07] 這樣一個大資料集 [01:09:11] 然後 [01:09:11] 第二個我們會定義它的 [01:09:13] 定義它的best size是多少 [01:09:16] 它會有什麼樣的correct function [01:09:18] 然後它是否 [01:09:19] 要打亂 [01:09:20] 然後再通過這個一個復迴圈我們就可以一筆一筆資料把它取出來 [01:09:27] 那具體的資料處理過程就像這樣 [01:09:29] 如圖所示 [01:09:31] 在dataset的這個物件裡面 [01:09:33] data會一筆一筆的 [01:09:35] 通過這個getitem的這個 [01:09:38] 呃 [01:09:39] 成員變量就成員函數 [01:09:41] 把它取出來然後 [01:09:43] 這個testdata [01:09:44] 第一筆取到這裡然後第二筆再取到這裡 [01:09:47] 一筆一筆取出來 [01:09:49] 那這個地方我們可以加一些 [01:09:52] 單筆資料的 [01:09:54] 預處理 [01:09:55] 然後 [01:09:58] 做取出這些data以後 [01:10:00] 相當於取出這一批data以後然後這一批data會打包成一個tuple [01:10:04] 這個tuple再位給correct function [01:10:06] correct function [01:10:08] 它 [01:10:09] 它 [01:10:10] 是定義在我們的data loader裡面的 [01:10:13] data loader去 [01:10:14] 的 [01:10:15] input就是它的 [01:10:20] data loader [01:10:21] input是這個tuple以後 [01:10:23] 然後它會 [01:10:24] 在 [01:10:25] 在batch的這個維度在資料分批的這個維度 [01:10:29] 再對資料做一些處理 [01:10:31] 比方說你可以在correct function裡面把資料打包成tensor [01:10:35] 之類的動作 [01:10:37] 然後 [01:10:38] data loader [01:10:39] 復迴圈 [01:10:41] 我們用復迴圈去 [01:10:43] 去取出data loader這樣一批一批資料的時候 [01:10:46] 實際上取出的就是correct function的return value [01:10:50] 這個return value大家 [01:10:52] 我們可以在裡面把它打包成tensor取出來的就是tensor [01:10:57] 然後呢 [01:11:00] 就是 [01:11:01] 對於一個RNN模型來說 [01:11:03] 那接下來我們就要進入這個 [01:11:05] 自然源處理的一些模型 [01:11:08] RNN模型來說 [01:11:10] 它裡面的資料的處理過程 [01:11:13] 就是 [01:11:14] 比方說我們有一筆資料叫iloveai [01:11:16] 然後 [01:11:18] 這一筆資料 [01:11:20] 它會有每一個token [01:11:22] 它一共四個token [01:11:24] 每一個token都會有自己的ID [01:11:26] 比方說 [01:11:27] i [01:11:28] 它會有一個id [01:11:30] 它會相當於一個1號vector [01:11:32] 它對應的是這個i這個token在dictionary裡面 [01:11:36] 它是第幾個字符 [01:11:37] 然後它在embedding這個層的時候 [01:11:40] 它會 [01:11:41] 相當於通過一個矩陣乘法的方式 [01:11:44] 來把這個id [01:11:45] 通過這個id取出這個 [01:11:47] token [01:11:48] 的embedding [01:11:51] 也就是 [01:11:52] 這裡相當於是1號vector實際上是一個 [01:11:55] 非常離散是一個sparse vector [01:11:58] 它取出來以後變成embedding [01:12:00] 它就會變成這個hidden size是768 [01:12:03] 那它會取出一個 [01:12:05] 768維的 [01:12:07] 呃 [01:12:08] 768維的 [01:12:10] dense factor [01:12:12] 然後再把這個dense factor [01:12:13] 位給下面的隱藏層 [01:12:17] 然後 [01:12:18] 隱藏層 [01:12:20] 隱藏層的output [01:12:22] 就隱藏層 [01:12:24] 的這個 [01:12:25] input [01:12:26] 就是我們 [01:12:27] embedding [01:12:28] embedding層 [01:12:30] output出來的這些dense factor [01:12:32] 那這些dense factor就會 [01:12:34] 經過這個rnn模型 [01:12:35] 這邊有一個hidden state [01:12:37] hidden state加上 [01:12:37] 加上第1個token [01:12:39] 然後再 [01:12:39] 傳過來再加上第2個token [01:12:41] 再傳過來再加上第3個token [01:12:42] 這個大家在課上應該 [01:12:45] 有講老師應該有講到說 [01:12:47] rnn的模型的處理方式 [01:12:49] 那對於torch.nn.rnn的這樣一個 [01:12:53] function就這樣一個 [01:12:55] 物件來說 [01:12:56] 它吃到的這個input [01:12:59] 就是 [01:13:01] 它吃到的input [01:13:02] 就是這邊的這個dense factor [01:13:05] 它吃到的output [01:13:07] 它的return value [01:13:09] 就是 [01:13:09] 模型 [01:13:10] 它 [01:13:11] 處理好的這個 [01:13:12] 它這樣子按照rnn的這樣一個處理方式 [01:13:16] 處理好的 [01:13:17] 對每一個token對應的這個output變量 [01:13:22] 然後 [01:13:24] 另外呢它後面會有個hn [01:13:26] 這個hn它就是最後一個h [01:13:29] 最後一個hidden state [01:13:30] 那麼這個hn [01:13:32] 假如說 [01:13:33] 這個hn的作用就是假如說我們 [01:13:35] 這個rnn我們的output的這個h [01:13:38] 我們要 [01:13:38] 運用到 [01:13:39] 下面可能一些其他的模組裡面那麼我們就會用這個hn [01:13:43] 來傳給下面的 [01:13:44] 模組 [01:13:45] 比方說下面可能會有一個像 [01:13:47] 類似於decoder之類的東西 [01:13:51] 然後呢訓練rnn的時候我們 [01:13:53] 我在作業的講解當中 [01:13:55] 有講過 [01:13:56] 就是generative learning跟teacher forcing之間的區別 [01:14:00] 那麼我們這邊會建議大家用teacher forcing [01:14:03] 那大家如果用generative learning [01:14:06] 訓練出來也能訓練出來的話實際上 [01:14:08] 也應該不會有大問題 [01:14:11] 但是 [01:14:12] 這邊為什麼推薦teacher forcing [01:14:14] 其實也不光是因為teacher forcing [01:14:17] 它的coding難度低 [01:14:19] 還有一個原因 [01:14:20] 就是teacher forcing [01:14:22] 它訓練起來是會更穩定的 [01:14:24] 這邊是一個簡單的理由 [01:14:25] 範例 [01:14:27] 假如說我們給模型 [01:14:28] 的ground truth是iloveai [01:14:31] 那麼我們給rnn模型的第一個token是i [01:14:35] 我們就規定它從i開始生成 [01:14:38] 那 [01:14:39] 假如說我們直接跑一遍模型的生成 [01:14:41] 那它 [01:14:43] 預測出來的結果 [01:14:44] 可不一定是iloveai [01:14:46] 它可能是ieatapple [01:14:47] 一個句號 [01:14:50] 我們讓這個模型完整的生成完了以後 [01:14:54] 再去 [01:14:54] 再去 [01:14:55] 優化它的這個模型 [01:14:57] 我們就會發現 [01:14:58] iloveai跟ieatapple [01:15:00] 它實際上從第一個token的預測開始就錯了 [01:15:05] 在這種 [01:15:06] 訓練的過程當中 [01:15:08] 它第一個token的錯誤 [01:15:10] 它就會被利用到接下來的 [01:15:11] 訓練過程 [01:15:13] 像是 [01:15:14] 我們i [01:15:15] 會生成eat [01:15:17] 那 [01:15:18] 但是我們ground truth是love [01:15:19] 所以我們要 [01:15:21] 優化的點就是 [01:15:24] i的情況下生出love [01:15:26] 這個沒有什麼問題 [01:15:28] 但是 [01:15:29] 接下來一個token就是我們 [01:15:31] input是ieat [01:15:33] 但是我們的ground truth是ai [01:15:36] 所以我們要優化的就變成了i [01:15:38] 這個其實上非常的奇怪 [01:15:42] 就是因為 [01:15:43] 如果我們直接用generative learning [01:15:45] 我們直接用生成的方式訓練的話 [01:15:47] 會產生這樣子 [01:15:49] 不穩定會產生這樣子有問題的 [01:15:51] 表達 [01:15:52] 所以 [01:15:55] 在通過生成的方式來訓練 [01:15:58] 往往會產生像這樣子的問題 [01:16:01] 也就是早期的預測結果的錯誤 [01:16:04] 會被 [01:16:05] 一步步去 [01:16:06] 影響後面的預測結果 [01:16:08] 後面的訓練過程 [01:16:09] 所以 [01:16:10] generative learning [01:16:11] 它實際上 [01:16:12] 不太 [01:16:13] 穩定 [01:16:14] 沒有teacher forcing穩定 [01:16:16] 為什麼teacher forcing會更穩定呢 [01:16:18] 是因為我們在 [01:16:19] input的時候 [01:16:20] 就直接給他ground truth [01:16:22] 所以 [01:16:23] 他的 [01:16:24] 他要optimize [01:16:25] 他要去 [01:16:26] 優化的這些內容 [01:16:27] 他並不會有那種 [01:16:28] 奇怪的像i eat ai這種 [01:16:30] 奇怪的東西出現 [01:16:32] 他就是i的情況下 [01:16:33] 優化love [01:16:35] i love的情況下 [01:16:36] 優化ai [01:16:37] i love ai [01:16:38] 優化 [01:16:38] 驚歎號 [01:16:40] 他實際上就是一個 [01:16:42] 就沒有什麼 [01:16:43] 沒有特別奇怪的東西在這裡面 [01:16:46] 所以他會更加的穩定 [01:16:47] 所以我們也會更加推薦大家用teacher forcing [01:16:50] 而不是生成 [01:16:53] 然後再優化的這樣一個過程 [01:16:58] 另外呢 [01:16:59] 接下來 [01:17:00] 就是有關我們接下來會大概的先帶到一點 [01:17:03] 怎樣在 [01:17:04] 怎樣去使 [01:17:06] 怎樣通過hugging face這個套件 [01:17:09] 就這樣一個 [01:17:10] 平臺 [01:17:12] 去調用 [01:17:13] transformer模型 [01:17:15] 那大家 [01:17:16] 在上課應該 [01:17:17] 也已經接觸也已經講到transformer模型他大概是怎樣一回事 [01:17:21] 那transformer模型他相對於 [01:17:24] 之前的循環神經網路來說 [01:17:26] 那他其實會有更強的處理能力 [01:17:28] 並且有更加好的平行化能力 [01:17:32] 所以 [01:17:34] 這邊以bert為例嘛 [01:17:36] 就是bert [01:17:37] 他是 [01:17:39] 在transformer架構 [01:17:40] 在基礎上 [01:17:42] 經過了一個 [01:17:43] pretraining的動作 [01:17:44] 也就是 [01:17:45] Google在開發這個bert的時候 [01:17:47] 他用大量的 [01:17:49] 就是大量的文本大量的沒有標註的資料 [01:17:53] 用完形填空的方式去訓練這個模型 [01:17:56] 當然還有就是預測 [01:17:58] 某個句子是不是 [01:17:59] 哪個句子接下來 [01:18:01] 某個句子是不是哪個句子 [01:18:03] 在往後接下來寫 [01:18:04] 他兩個句子 [01:18:05] 接在一起通不通順 [01:18:06] 這種 [01:18:08] 各種各樣的就是這種 [01:18:10] 多個stage的 [01:18:11] 預訓練任務 [01:18:14] 然後 [01:18:16] 這個預訓練往往會用到非常多的硬體資源 [01:18:20] 跟 [01:18:20] 時間資源還有 [01:18:22] 各種各樣的 [01:18:23] 什麼電力成本啊這些 [01:18:25] 所以 [01:18:27] 如果我們要下載這個 [01:18:30] 這個已經pretrain好的 [01:18:32] 像bert模型的話 [01:18:34] 我們一般會透過hugging face的這個平臺 [01:18:37] 來下載這個模型 [01:18:40] 那這個hugging face的 [01:18:41] 套件調用 [01:18:43] 就是我們接下來會講的 [01:18:44] 那我們下載了這個預訓練模型以後 [01:18:46] 實際上這個 [01:18:48] 通過預訓練的這個通過pretraining這個方法 [01:18:51] 讓模型先學會這個 [01:18:53] 語言的 [01:18:54] 表達方式啊文法結構啊 [01:18:56] 他會更加的能夠對下游任務有更好的理解 [01:19:00] 能力 [01:19:00] 相當於我們直接下游任務的任務 [01:19:03] 下游任務的資料其實並不會 [01:19:06] 資料量其實並 [01:19:07] 一般來說不會很充足 [01:19:08] 那 [01:19:09] 我們直接訓練過去其實就 [01:19:11] 太遙遠 [01:19:13] 所以我們會通過pretraining先讓他學會 [01:19:16] 這門語言 [01:19:17] 然後再把他 [01:19:18] 適應的任務再一點點調過去 [01:19:20] 那這樣會顯著減少 [01:19:22] 他的資料量的需求 [01:19:23] 資料的需求量 [01:19:26] 所以 [01:19:26] 在transform [01:19:27] 就在hugging face這個平臺上面其實他有打包出這樣一個transformer [01:19:33] 的這個包 [01:19:34] 透過 [01:19:35] t-transformer.autotokenizer [01:19:38] tokenizer [01:19:39] 我們就可以 [01:19:41] 把tokenizer就是 [01:19:43] 把birth模型給 [01:19:44] 給 [01:19:45] load進來 [01:19:47] 然後 [01:19:47] 可以指定一個本地的 [01:19:49] 存儲的地點來存儲這個模型 [01:19:53] 然後 [01:19:54] 什麼叫tokenizer,tokenizer所完成的目標 [01:19:57] 就是把 [01:19:58] 就像是這裡一樣就batch encode plus [01:20:01] 我經常會用這個 [01:20:03] 我個人經常會用這個 [01:20:05] 這個 [01:20:07] 就encode [01:20:08] tokenizer的方式 [01:20:09] 這個其實 [01:20:10] 確實非常的方便 [01:20:12] 他可以 [01:20:13] 直接這樣子一行 [01:20:14] 他所完成的 [01:20:16] 結果就是第一步 [01:20:18] 把 [01:20:19] 我們的比方說像iloveai就是個自然語言的段落 [01:20:23] 給變成 [01:20:24] 每一個token並且每個token他自己的ID [01:20:28] 把它切好token然後把每個token再換成自己的ID [01:20:31] 然後 [01:20:33] 並且他可以 [01:20:34] 做到padding [01:20:35] 就是假如說我們有一筆 [01:20:36] 比方說第一筆叫什麼iloveai [01:20:39] 第二筆叫做i eat apple today [01:20:43] 然後這兩筆資料 [01:20:44] 他的 [01:20:46] 他的長度是不一樣的 [01:20:48] 那麼他可以做到padding就是 [01:20:50] 把短的資料 [01:20:51] 添零補位 [01:20:53] 變成跟長的資料 [01:20:55] 一樣的長度 [01:20:56] 並且他也可以做translation [01:20:58] 就是 [01:21:00] 我們可以定義一個最大的長度 [01:21:02] 然後 [01:21:03] 超過那個長度我們就把後面給截 [01:21:05] 後面的都不要 [01:21:08] 然後 [01:21:09] 並且他也可以自動幫我們做到 [01:21:11] 就打包成一個pytorch tensor [01:21:13] 這樣一個功能 [01:21:15] 然後這個data [01:21:19] 這個data所回傳出來的是這樣一個bash encoding的這樣一個data [01:21:24] 資料結構 [01:21:25] 那這個資料結構 [01:21:27] 大家可以把它當成一個dictionary來使用 [01:21:30] 那麼 [01:21:31] 他 [01:21:31] 默認回傳出來的東西 [01:21:33] 是在cpu上面的 [01:21:36] 大家如果要在gpu上跑的話 [01:21:38] 需要把這些 [01:21:39] 資料再搬到gpu上去跑 [01:21:42] 然後有關於模型權重的下載 [01:21:44] 這邊就是 [01:21:46] transformer.automodel [01:21:49] 然後再frompretrained [01:21:52] 再通過這個指令 [01:21:54] 來 [01:21:54] 調用 [01:21:55] 這個模型的 [01:21:58] 來調用這個模型他的 [01:22:00] 內部的權重啊模型架構這些 [01:22:03] 然後 [01:22:04] 這樣子調用出來以後 [01:22:05] 他其實相當於就是一個torch.nn.module [01:22:08] 就是一個 [01:22:09] 模組的 [01:22:11] 就是一個模組的型別的東西 [01:22:14] 大家就是按照 [01:22:15] 之前所講的 [01:22:17] Pytorch的 [01:22:18] torch.nn.module [01:22:19] 的使用方式來使用 [01:22:23] 大概就可以 [01:22:25] 然後在 [01:22:26] transformer的auto encoder裡面也有一個自己的 [01:22:29] 就是 [01:22:30] 比方說我們要對他做分類任務 [01:22:33] 如果我們要對他做分類任務那麼也會有一個自己的處理方式 [01:22:37] 那auto encoder跟 [01:22:38] rnn之間的 [01:22:39] 會有一些 [01:22:40] 明顯的不一樣的點 [01:22:42] 就是auto encoder他 [01:22:44] 並不像rnn那樣 [01:22:45] 他是 [01:22:46] 先處理第1個然後第1個再 [01:22:48] encode到 [01:22:49] hidden state再處理第2個再一直往後往後一直處理到結尾 [01:22:54] auto encoder他是 [01:22:55] 所有的token平行的往前處理 [01:22:59] 所以 [01:23:00] 這邊embedding層他照樣是 [01:23:04] 通過這個embedding層 [01:23:05] 把資料變成 [01:23:07] dense vector [01:23:08] 然後 [01:23:10] 這個dense vector在這一批dense vector在位到這個hidden [01:23:15] layers裡面去 [01:23:17] 然後 [01:23:18] 這個hidden layers [01:23:20] 就是 [01:23:21] birth model他會有自己的 [01:23:23] 處理的過程他會有 [01:23:25] attention fee for network [01:23:27] 然後他會有好多好多層的這樣的 [01:23:30] 層 [01:23:31] 然後output出來以後也會是一個 [01:23:34] 同樣大小的這樣一個 [01:23:37] dense vector [01:23:38] 那這個dense vector他跟input [01:23:40] 也是一個1對應的關係就是 [01:23:43] 第1個token就是cls [01:23:45] 就是 [01:23:45] 我們在 [01:23:46] birth處理的時候他表示的是 [01:23:49] 一個能夠代表這整句話訊息的 [01:23:52] 這樣一個token [01:23:53] 然後接下來就是每一個token他對應的 [01:23:57] 向量 [01:23:58] 然後最後是給sep表示這句話的結束 [01:24:03] 如果我們要做sequence classification的話 [01:24:05] 我們會採用第1個就是他能夠表示整一個句子 [01:24:09] 訊息的這個變量 [01:24:12] 來取出來然後對這個token做down projection [01:24:15] 做一個映射 [01:24:17] 然後 [01:24:19] 獲得比方說我們是二分類任務的話 [01:24:21] 那麼會獲得這兩個類別他相應自己的 [01:24:25] 分數 [01:24:27] 如果我們要對每一個token來做 [01:24:31] 對每一個token來做分類任務的話 [01:24:34] 做分類的話 [01:24:35] 比方說是一些比方說ner任務啊 [01:24:38] 那這邊也是 [01:24:40] 就是 [01:24:41] 假設是分兩類 [01:24:41] 那麼我們就把每一個token [01:24:44] 所對應的 [01:24:45] dense factor給取出來 [01:24:47] 然後再做 [01:24:48] 再做一個 [01:24:50] linear層再過一個linear層 [01:24:52] 然後會得到每一個token他對他的分類結果 [01:24:57] OK那這個就是 [01:24:59] 一個transformer的基本的實作過程 [01:25:02] 基本的 [01:25:03] 資料的大概處理過程 [01:25:05] 在之後的作業當中 [01:25:08] 我們會 [01:25:09] 要求大家能夠 [01:25:11] 是構建 [01:25:12] 一個 [01:25:12] 比方說從hugging face上面下載模型 [01:25:14] 然後並且 [01:25:15] 並且寫出一個自己模型的這樣一個過程 [01:25:18] 並且通過這個模型 [01:25:20] 來完成各種各樣 [01:25:21] 可能是分類可能是生成 [01:25:23] 任務的 [01:25:25] 結果 [01:25:26] 這任務的這樣一個目標 [01:25:27] 所以 [01:25:29] 所以 [01:25:32] 大家可以先 [01:25:33] 從 [01:25:34] hugging face [01:25:35] 開始 [01:25:36] 來瞭解 [01:25:37] 這個transformer [01:25:39] 這個大類模型它的使用方法 [01:25:41] 是什麼樣的 [01:25:43] 那在未來的助教課當中 [01:25:45] 助教會再 [01:25:47] 進一步的去講解一些其他的API [01:25:50] 其中也包含就是transformer更細節的使用 [01:25:54] 還有一些像是 [01:25:56] 像是其他一些好用的API [01:25:59] 我們應該怎麼調用的 [01:26:00] 這樣一個實作方法 [01:26:03] 那 [01:26:03] 本場 [01:26:04] 就是本次助教課大概講的內容就是 [01:26:07] 如上所示 [01:26:08] 謝謝大家