首個基于Transformer的分割檢測+視覺大模型視頻課程(23年新課+源碼+課件)
學(xué)習(xí)資料地址1:https://pan.baidu.com/s/1mpYHRFi68lzNuA8neYI15w 提取碼:pwjd?
學(xué)習(xí)資料地址2:https://share.weiyun.com/tnVNHGMD 密碼:3fj7iy
自動駕駛是高安全型應(yīng)用,需要高性能和高可靠的深度學(xué)習(xí)模型,Vision Transformer是理想的選摔。現(xiàn)在主流的自動駕駛感知算法基本都使用了Vision Transformer相關(guān)技術(shù),比如分割、2D/3D檢測,以及最近大火的大模型 (如SAM),Vision Transformer在自動駕駛領(lǐng)域的落地方面遍地開花。5一方面,在自動駕駛或圖像處理相關(guān)算法崗位的面試題中,Vision Transformer是必考題,需要對其理論知識有深入理解,并且在項目中真實的使用過相關(guān)技術(shù)。
Transformer出自于Google于2017年發(fā)表的論文《Attention is all you need》,最開始是用于機器翻譯,并且取得了非常好的效果。但是自提出以來,Transformer不僅僅在NLP領(lǐng)域大放異彩,并且在CV、RS等領(lǐng)域也取得了非常不錯的表現(xiàn)。尤其是2020年,絕對稱得上是Transformer的元年,比如在CV領(lǐng)域,基于Transformer的模型橫掃各大榜單,完爆基于CNN的模型。為什么Transformer模型表現(xiàn)如此優(yōu)異?它的原理是什么?它成功的關(guān)鍵又包含哪些?本文將簡要地回答一下這些問題。
我們知道Transformer模型最初是用于機器翻譯的,機器翻譯應(yīng)用的輸入是某種語言的一個句子,輸出是另外一種語言的句子。
var i *int = nil
fmt.Println("i.size:", unsafe.Sizeof(i)) //8
var i8 *int8 = nil
fmt.Println("i8.size:", unsafe.Sizeof(i8)) //8
var s *string = nil
fmt.Println("s.size:", unsafe.Sizeof(s)) //8
var ps *struct{} = nil
fmt.Println("ps.size:", unsafe.Sizeof(ps)) //8
var si []int = nil
var si1 []int = nil
fmt.Println("si.size:", unsafe.Sizeof(si)) //24
var ii interface{} = nil
fmt.Println("ii.size:", unsafe.Sizeof(ii)) //16
我們以生成我,愛,機器,學(xué)習(xí),翻譯成<bos>,i,love,machine,learning,<eos>這個例子做生成過程來解釋。
訓(xùn)練:
把“我/愛/機器/學(xué)習(xí)”embedding后輸入到encoder里去,最后一層的encoder最終輸出的outputs [10, 512](假設(shè)我們采用的embedding長度為512,而且batch size = 1),此outputs 乘以新的參數(shù)矩陣,可以作為decoder里每一層用到的K和V;
將<bos>作為decoder的初始輸入,將decoder的最大概率輸出詞向量A1和‘i’做cross entropy(交叉熵)計算error。
將<bos>,“i” 作為decoder的輸入,將decoder的最大概率輸出詞 A2 和‘love’做cross entropy計算error。
將<bos>,“i”,“l(fā)ove” 作為decoder的輸入,將decoder的最大概率輸出詞A3和’machine’ 做cross entropy計算error。
將<bos>,“i”,"love ",“machine” 作為decoder的輸入,將decoder最大概率輸出詞A4和‘learning’做cross entropy計算error。
將<bos>,“i”,"love ",“machine”,“l(fā)earning” 作為decoder的輸入,將decoder最大概率輸出詞A5和終止符做cross entropy計算error。
那么并行的時候是怎么做的呢,我們會有一個mask矩陣在這叫seq mask,因為他起到的作用是在decoder編碼我們的target seq的時候?qū)γ恳粋€詞的生成遮蓋它之后的詞的信息。
func main() {
s := []string{"a", "b", "c"}
fmt.Println("s:origin", s)
changes1(s)
fmt.Println("s:f1", s)
changes2(s)
fmt.Println("s:f2", s)
changes3(s)
fmt.Println("s:f3", s)
}
func changes1(s []string) {
var tmp = []string{"x", "y", "z"}
s = tmp
}
func changes2(s []string) {
// item只是一個副本,不能改變s中元素的值
for i, item := range s {
item = "d"
fmt.Printf("item=%s;s[%d]=%s", item, i, s[i])
}
}
func changes3(s []string) {
for i := range s {
s[i] = "d"
}
}
首先我們需要為每個輸入向量(也就是詞向量)創(chuàng)建3個向量,分別叫做Query、Key、Value。那么如何創(chuàng)建呢?我們可以對輸入詞向量分別乘上3個矩陣來得到Q、K、V向量,這3個矩陣的參數(shù)在訓(xùn)練的過程是可以訓(xùn)練的。注意Q、K、V向量的維度是一樣的,但是它們的維度可以比輸入詞向量小一點,比如設(shè)置成64,其實這步也不是必要的,這樣設(shè)置主要是為了與后面的Mulit-head注意力機制保持一致(當(dāng)使用8頭注意力時,單頭所處理的詞向量維度為512/8=64,此時Q、K、V向量與輸入詞向量就一致了)。我們假設(shè)輸入序列為英文的"Thinking Machines"
想要深度理解Attention機制,就需要了解一下它產(chǎn)生的背景、在哪類問題下產(chǎn)生,以及最初是為了解決什么問題而產(chǎn)生。
首先回顧一下機器翻譯領(lǐng)域的模型演進歷史:
機器翻譯是從RNN開始跨入神經(jīng)網(wǎng)絡(luò)機器翻譯時代的,幾個比較重要的階段分別是: Simple RNN, Contextualize RNN,Contextualized RNN with attention, Transformer(2017),下面來一一介紹。
「Simple RNN」 :這個encoder-decoder模型結(jié)構(gòu)中,encoder將整個源端序列(不論長度)壓縮成一個向量(encoder output),源端信息和decoder之間唯一的聯(lián)系只是: encoder output會作為decoder的initial states的輸入。這樣帶來一個顯而易見的問題就是,隨著decoder長度的增加,encoder output的信息會衰減。
func main(){
var c = make(chan int)
fmt.Printf("c.pointer=%p\n", c) //c.pointer=0xc000022180
go func() {
c <- 1
addChannel(c)
close(c)
}()
for item := range c {
//item: 1
//item: 2
fmt.Println("item:", item)
}
}
func addChannel(done chan int) {
done <- 2
fmt.Printf("done.pointer=%p\n", done) //done.pointer=0xc000022180
}
在測試模型的時候,Test:decoder沒有l(wèi)abel,采用自回歸一個詞一個詞的輸出,要翻譯的中文正常從encoder并行輸入(和訓(xùn)練的時候一樣)得到每個單詞的embedding,然后decoder第一次先輸入bos再此表中的id,得到翻譯的第一個單詞,然后自回歸,如此循環(huán)直到預(yù)測達到eos停止標記
type visit struct {
a1? unsafe.Pointer
a2? unsafe.Pointer
typ Type
}
func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool {
if !v1.IsValid() || !v2.IsValid() {
return v1.IsValid() == v2.IsValid()
}
if v1.Type() != v2.Type() {
return false
}
// We want to avoid putting more in the visited map than we need to.
// For any possible reference cycle that might be encountered,
// hard(v1, v2) needs to return true for at least one of the types in the cycle,
// and it's safe and valid to get Value's internal pointer.
hard := func(v1, v2 Value) bool {
switch v1.Kind() {
case Pointer:
if v1.typ.ptrdata == 0 {
// not-in-heap pointers can't be cyclic.
// At least, all of our current uses of runtime/internal/sys.NotInHeap
// have that property. The runtime ones aren't cyclic (and we don't use
// DeepEqual on them anyway), and the cgo-generated ones are
// all empty structs.
return false
}
fallthrough
case Map, Slice, Interface:
// Nil pointers cannot be cyclic. Avoid putting them in the visited map.
return !v1.IsNil() && !v2.IsNil()
}
return false
}
if hard(v1, v2) {
// For a Pointer or Map value, we need to check flagIndir,
// which we do by calling the pointer method.
// For Slice or Interface, flagIndir is always set,
// and using v.ptr suffices.
ptrval := func(v Value) unsafe.Pointer {
switch v.Kind() {
case Pointer, Map:
return v.pointer()
default:
return v.ptr
}
}
addr1 := ptrval(v1)
addr2 := ptrval(v2)
if uintptr(addr1) > uintptr(addr2) {
// Canonicalize order to reduce number of entries in visited.
// Assumes non-moving garbage collector.
addr1, addr2 = addr2, addr1
}
// Short circuit if references are already seen.
typ := v1.Type()
v := visit{addr1, addr2, typ}
if visited[v] {
return true
}
// Remember for later.
visited[v] = true
}
switch v1.Kind() {
case Array:
for i := 0; i < v1.Len(); i++ {
if !deepValueEqual(v1.Index(i), v2.Index(i), visited) {
return false
}
}
return true
case Slice:
if v1.IsNil() != v2.IsNil() {
return false
}
if v1.Len() != v2.Len() {
return false
}
if v1.UnsafePointer() == v2.UnsafePointer() {
return true
}
// Special case for []byte, which is common.
if v1.Type().Elem().Kind() == Uint8 {
return bytealg.Equal(v1.Bytes(), v2.Bytes())
}
for i := 0; i < v1.Len(); i++ {
if !deepValueEqual(v1.Index(i), v2.Index(i), visited) {
return false
}
}
return true
case Interface:
if v1.IsNil() || v2.IsNil() {
return v1.IsNil() == v2.IsNil()
}
return deepValueEqual(v1.Elem(), v2.Elem(), visited)
case Pointer:
if v1.UnsafePointer() == v2.UnsafePointer() {
return true
}
return deepValueEqual(v1.Elem(), v2.Elem(), visited)
case Struct:
for i, n := 0, v1.NumField(); i < n; i++ {
if !deepValueEqual(v1.Field(i), v2.Field(i), visited) {
return false
}
}
return true
case Map:
if v1.IsNil() != v2.IsNil() {
return false
}
if v1.Len() != v2.Len() {
return false
}
if v1.UnsafePointer() == v2.UnsafePointer() {
return true
}
for _, k := range v1.MapKeys() {
val1 := v1.MapIndex(k)
val2 := v2.MapIndex(k)
if !val1.IsValid() || !val2.IsValid() || !deepValueEqual(val1, val2, visited) {
return false
}
}
return true
case Func:
if v1.IsNil() && v2.IsNil() {
return true
}
// Can't do better than this:
return false
case Int, Int8, Int16, Int32, Int64:
return v1.Int() == v2.Int()
case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
return v1.Uint() == v2.Uint()
case String:
return v1.String() == v2.String()
case Bool:
return v1.Bool() == v2.Bool()
case Float32, Float64:
return v1.Float() == v2.Float()
case Complex64, Complex128:
return v1.Complex() == v2.Complex()
default:
// Normal equality suffices
return valueInterface(v1, false) == valueInterface(v2, false)
}
}
這便是encoder的整體計算流程圖了,Transformer模型中堆疊了多個這樣的encoder,無非就是輸出連接輸入罷了,常規(guī)操作。
最后再附上一個Transformer的代碼實現(xiàn),讀者有興趣可以跟著自己復(fù)現(xiàn)一下Transformer模型的代碼。
? ?package main
? ?import (
? ? ? ?"log"
? ? ? ?"sync"
? ?)
? ?func init() {
? ? ? ?log.SetFlags(log.Lshortfile)
? ?}
? ?func main() {
? ? ? ?lock := sync.Mutex{}
? ? ? ?//Go 1.18 新增,是一種非阻塞模式的取鎖操作。當(dāng)調(diào)用 TryLock() 時,
? ? ? ?//該函數(shù)僅簡單地返回 true 或者 false,代表是否加鎖成功
? ? ? ?//在某些情況下,如果我們希望在獲取鎖失敗時,并不想停止執(zhí)行,
? ? ? ?//而是可以進入其他的邏輯就可以使用TryLock()
? ? ? ?log.Println("TryLock:", lock.TryLock())
? ? ? ?//已經(jīng)通過TryLock()加鎖,不能再次加鎖
? ? ? ?lock.Lock()
? ?}