自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Go與神經(jīng)網(wǎng)絡(luò):張量運(yùn)算

人工智能
如今大家更多將ChatGPT及相關(guān)應(yīng)用當(dāng)做生產(chǎn)力工具,作為程序員,自然會(huì)首當(dāng)其沖的加入到借助AI提升生產(chǎn)力的陣營。但對(duì)于程序員來說,如果對(duì)一個(gè)計(jì)算機(jī)科學(xué)方面的技術(shù)沒有基本工作原理認(rèn)知或是完全看不懂,那么就會(huì)有一種深深的危機(jī)感。

0. 背景

2023年年初,我們很可能是見證了一次新工業(yè)革命的起點(diǎn),也可能是見證了AGI(Artificial general intelligence,通用人工智能)[1]孕育的開始。ChatGPT應(yīng)用以及后續(xù)GPT-4大模型的出現(xiàn),其震撼程度遠(yuǎn)超當(dāng)年AlphaGo戰(zhàn)勝人類頂尖圍棋選手[2]。相對(duì)于AlphaGo在一個(gè)狹窄領(lǐng)域的建樹,ChatGPT則是以摧枯拉朽之勢(shì)橫掃幾乎所有腦力勞動(dòng)行業(yè)。

如今大家更多將ChatGPT及相關(guān)應(yīng)用當(dāng)做生產(chǎn)力工具,作為程序員,自然會(huì)首當(dāng)其沖的加入到借助AI提升生產(chǎn)力的陣營。但對(duì)于程序員來說,如果對(duì)一個(gè)計(jì)算機(jī)科學(xué)方面的技術(shù)沒有基本工作原理認(rèn)知或是完全看不懂,那么就會(huì)有一種深深的危機(jī)感。

什么是深度學(xué)習(xí)、什么是神經(jīng)網(wǎng)絡(luò)、什么是大模型、上千億的參數(shù)究竟指的是什么、什么是大模型的量化等都是縈繞在頭腦中的未知但又急切想知道的東西。

有人會(huì)說,深度學(xué)習(xí)發(fā)展都十多年了,現(xiàn)在學(xué)還來得及么?其實(shí)大多數(shù)人不是從事機(jī)器學(xué)習(xí)的,普通程序員只需要了解機(jī)器學(xué)習(xí)、深度學(xué)習(xí)(神經(jīng)網(wǎng)絡(luò))的基本運(yùn)作原理即可。此外,有了ChatGPT相關(guān)工具后,獲取和理解知識(shí)的效率可以大幅提升,以前需要以年來計(jì)算學(xué)習(xí)新知識(shí)技能,現(xiàn)在可能僅需以月來計(jì)算,甚至更短。

作為程序員,了解深度學(xué)習(xí),了解神經(jīng)網(wǎng)絡(luò),其實(shí)也是去學(xué)習(xí)一種新的、完全不同于以往的編程范式。以前我們的編程范式是這樣的: 人類學(xué)習(xí)規(guī)則,然后通過手工編碼將規(guī)則內(nèi)置到系統(tǒng)中,系統(tǒng)運(yùn)行后,根據(jù)明確的規(guī)則對(duì)輸入數(shù)據(jù)做處理并給出答案(如下圖所示):

圖片圖片

這個(gè)大編程范式通常又細(xì)分為下面幾類,大家根據(jù)自己的喜好和工作要求選擇不同的編程范式以及編程語言:

  • 命令式編程范式(C、Go等);
  • 面向?qū)ο缶幊谭妒?Java、Ruby);
  • 函數(shù)式編程范式(Haskell、Lisp、Clojure等);
  • 聲明式編程范式(SQL)。

這類范式都?xì)w屬于**符號(hào)主義人工智能(symbolic AI)**,即都是用來手工編寫明確的規(guī)則的。符號(hào)主義人工智能適合用來解決定義明確的邏輯問題,比如下國際象棋,但它難以給出明確規(guī)則來解決更復(fù)雜、更模糊的問題,比如圖像分類、語音識(shí)別或自然語言翻譯。

而機(jī)器學(xué)習(xí)或者說機(jī)器學(xué)習(xí)的結(jié)果人工神經(jīng)網(wǎng)絡(luò)則是另外一種范式,如下圖所示:

圖片圖片

在這個(gè)范式中,程序員無需再學(xué)習(xí)什么規(guī)則,因?yàn)橐?guī)則是模型自己通過數(shù)據(jù)學(xué)習(xí)來的。程序員只需準(zhǔn)備好高質(zhì)量的訓(xùn)練數(shù)據(jù)以及對(duì)應(yīng)的答案(標(biāo)注),然后建立初始模型(初始神經(jīng)網(wǎng)絡(luò))即可,之后的事情就交給機(jī)器了(機(jī)器學(xué)習(xí)并非在數(shù)學(xué)方面做出什么理論突破,而是“蠻力出奇跡”一個(gè)生動(dòng)案例)。模型通過數(shù)據(jù)進(jìn)行自動(dòng)訓(xùn)練(學(xué)習(xí))并生成包含規(guī)則的目標(biāo)模型,而目標(biāo)模型即程序。

了解兩類截然不同的范式之后,我再來澄清幾個(gè)問題:

  • Go與神經(jīng)網(wǎng)絡(luò)系列文章的目的?不是教你如何自己搞出一個(gè)大模型,而是將經(jīng)典機(jī)器學(xué)習(xí)、深度學(xué)習(xí)(與建立人工神經(jīng)網(wǎng)絡(luò))的來龍去脈搞清楚。
  • Why Go? 幫助Go程序員學(xué)習(xí)機(jī)器學(xué)習(xí)。雖然Python代碼看起來很容易理解,代碼量也會(huì)少很多(像Keras這樣的框架,甚至將training dataset都集成在框架中了)。

注:通過閱讀Python的機(jī)器學(xué)習(xí)/深度學(xué)習(xí)代碼,我覺得不會(huì)有什么語言可以代替Python作為AI主力了。用Python做數(shù)據(jù)準(zhǔn)備、訓(xùn)練模型簡(jiǎn)直簡(jiǎn)單的不要不要的了。

  • 從何處開始?張量以及相關(guān)運(yùn)算。

張量在深度學(xué)習(xí)中扮演著非常重要的角色,因?yàn)樗鼈兪谴鎯?chǔ)和處理數(shù)據(jù)的基本單位。張量可以看作是一個(gè)“容器”,可以表示向量、矩陣和更高維度的數(shù)據(jù)結(jié)構(gòu)。深度學(xué)習(xí)中的神經(jīng)網(wǎng)絡(luò)模型使用張量來表示輸入數(shù)據(jù)、模型參數(shù)和輸出結(jié)果,以及在計(jì)算過程中的各種中間變量。通過對(duì)張量進(jìn)行數(shù)學(xué)運(yùn)算和優(yōu)化,深度學(xué)習(xí)模型能夠從大量的數(shù)據(jù)中學(xué)習(xí)到特征和規(guī)律,并用于分類、回歸、聚類等任務(wù)。因此,張量是深度學(xué)習(xí)中必不可少的概念之一。最流行的深度學(xué)習(xí)框架tensorflow都以tensor命名。我們也將從張量(tensor)出發(fā)進(jìn)入機(jī)器學(xué)習(xí)和神經(jīng)網(wǎng)絡(luò)世界。

不過大家要區(qū)分?jǐn)?shù)學(xué)領(lǐng)域與機(jī)器學(xué)習(xí)領(lǐng)域張量在含義上的不同。在數(shù)學(xué)領(lǐng)域,張量是一個(gè)多維數(shù)組,而在機(jī)器學(xué)習(xí)領(lǐng)域,張量是一種數(shù)據(jù)結(jié)構(gòu),用于表示多維數(shù)組和高維矩陣。兩者的相同點(diǎn)在于都是多維數(shù)組,但不同點(diǎn)在于它們的應(yīng)用場(chǎng)景和具體實(shí)現(xiàn)方式不同。如上一段描述那樣,在機(jī)器學(xué)習(xí)中,張量通常用于表示數(shù)據(jù)集、神經(jīng)網(wǎng)絡(luò)的輸入和輸出等。

下面我們就來了解一下張量與張量的運(yùn)算,包括如何創(chuàng)建張量、執(zhí)行基本和高級(jí)張量操作,以及張量廣播(broadcast)與重塑(reshape)操作。

1. 理解張量

張量是目前所有機(jī)器學(xué)習(xí)系統(tǒng)都使用的基本數(shù)據(jù)結(jié)構(gòu)。張量這一概念的核心在于,它是一個(gè)數(shù)據(jù)容器。它包含的數(shù)據(jù)通常是同類型的數(shù)值數(shù)據(jù),因此它是一個(gè)同構(gòu)的數(shù)字容器。

前面提到過,張量可以表示數(shù)字、向量、矩陣甚至更高維度的數(shù)據(jù)。很多語言采用多維數(shù)組來實(shí)現(xiàn)張量,不過也有采用平坦數(shù)組(flat array)來實(shí)現(xiàn)的,比如:gorgonia/tensor[3]。

無論實(shí)現(xiàn)方式是怎樣的,從邏輯上看,張量的表現(xiàn)是一致的,即張量是一個(gè)有如下屬性的同構(gòu)數(shù)據(jù)類型。

1.1 階數(shù)(ndim)

張量的維度通常叫作軸(axis),張量軸的個(gè)數(shù)也叫作階(rank)。下面是從0階張量到4階張量的示意圖:

圖片圖片

  • 0階張量

僅包含一個(gè)數(shù)字的張量,也被稱為標(biāo)量(scalar),也叫標(biāo)量張量。0階張量有0個(gè)軸。

  • 1階張量

1階張量,也稱為向量(vector),有一個(gè)軸。這個(gè)向量可以是n維向量,與張量的階數(shù)沒有關(guān)系。比如在上面圖中的一階張量表示的就是一個(gè)4維向量。所謂維度即沿著某個(gè)軸上的元素的個(gè)數(shù)。這個(gè)圖中一階張量表示的向量中有4個(gè)元素,因此是一個(gè)4維向量。

  • 2階張量

2階張量,也稱為矩陣(matrix),有2個(gè)軸。在2階張量中,這兩個(gè)軸也稱為矩陣的行(axis-0)和列(axis-1),每個(gè)軸上的向量都有自己的維度。例如上圖中的2階張量的axis-0軸上有3個(gè)元素(每個(gè)元素又都是一個(gè)向量),因此是axis-0的維度為3,由此類推,axis-1軸的維度為4。

注:張量的軸的下標(biāo)從0開始,如axis-0、axis-1、...、axis-n。

2階張量也可以看成是1階張量的數(shù)組。

  • 3階或更高階張量

3階張量有3個(gè)軸,如上圖中的3階張量,可以看成是多個(gè)2階張量組成的數(shù)組。

以此類推,擴(kuò)展至N階張量,可以看成是N-1階張量的數(shù)組。

1.2 形狀(shape)

張量有一個(gè)屬性為shape,shape由張量每個(gè)軸上的維度(軸上元素的個(gè)數(shù))組成。以上圖中的3階張量為例,其axis-0軸上有2個(gè)元素,axis-1軸上有3個(gè)元素,axis-2軸上有4個(gè)元素,因此該3階張量的shape為(2, 3, 4)。axis-0軸也被稱為樣本軸,下圖是按照每一級(jí)張量的樣本軸對(duì)張量做拆解的示意圖:

圖片圖片

我們首先對(duì)3階張量(shape(2,3,4))沿著其樣本軸方向進(jìn)行拆解,我們將其拆解2個(gè)2階張量(shape(3,4))。接下來,我們對(duì)得到的2階張量進(jìn)行拆解,同樣沿著其樣本軸方向拆解為3個(gè)1階張量(shape(4,))。我們看到,每個(gè)1階張量是一個(gè)4維向量,可拆解為4個(gè)0階張量。

1.3 元素?cái)?shù)據(jù)類型dtype

張量是同構(gòu)數(shù)據(jù)類型,無論是幾階張量,最終都是由一個(gè)個(gè)標(biāo)量組合而成,標(biāo)量的類型就是張量的元素?cái)?shù)據(jù)類型(dtype),在上圖中,我們的張量的dtype為float32。浮點(diǎn)數(shù)與整型數(shù)是機(jī)器學(xué)習(xí)中張量最常用的元素?cái)?shù)據(jù)類型。

了解了張量的概念與屬性后,我們就來看看在Go中如何創(chuàng)建張量。

2. 在Go中創(chuàng)建張量

Go提供了幾個(gè)機(jī)器學(xué)習(xí)庫,可以用來創(chuàng)建和操作張量。在Go中執(zhí)行張量操作的兩個(gè)流行庫是Tensorflow[4]和Gorgonia[5]。

不過Tensorflow官方團(tuán)隊(duì)已經(jīng)不再對(duì)go binding API提供維護(hù)支持了(由Go社區(qū)第三方負(fù)責(zé)維護(hù)[6]),并且該binding需要依賴cgo調(diào)用tensorflow的庫,因此在本文中,我們使用gorgonia來定義張量以及進(jìn)行張量運(yùn)算。

Gorgonia提供了tensor包[7]用來定義tensor并提供基于tensor的基本運(yùn)算函數(shù)。下面的例子使用tensor包定義了對(duì)應(yīng)上面圖中1階到3階的三個(gè)張量:

// https://github.com/bigwhite/experiments/blob/master/go-and-nn/tensor-operations/tensor.go
package main

import (
    "fmt"

    "gorgonia.org/tensor"
)

func main() {
    // define an one-rank tensor
    oneRankTensor := tensor.New(tensor.WithBacking([]float32{1.7, 2.6, 1.3, 3.2}), tensor.WithShape(4))
    fmt.Println("\none-rank tensor:")
    fmt.Println(oneRankTensor)
    fmt.Println("ndim:", oneRankTensor.Dims())
    fmt.Println("shape:", oneRankTensor.Shape())
    fmt.Println("dtype", oneRankTensor.Dtype())

    // define an two-rank tensor
    twoRankTensor := tensor.New(tensor.WithBacking([]float32{1.7, 2.6, 1.3, 3.2,
        2.7, 2.8, 1.5, 2.9,
        3.7, 2.4, 1.7, 3.1}), tensor.WithShape(3, 4))
    fmt.Println("\ntwo-rank tensor:")
    fmt.Println(twoRankTensor)
    fmt.Println("ndim:", twoRankTensor.Dims())
    fmt.Println("shape:", twoRankTensor.Shape())
    fmt.Println("dtype", twoRankTensor.Dtype())

    // define an three-rank tensor
    threeRankTensor := tensor.New(tensor.WithBacking([]float32{1.7, 2.6, 1.3, 3.2,
        2.7, 2.8, 1.5, 2.9,
        3.7, 2.4, 1.7, 3.1,
        1.5, 2.7, 1.4, 3.3,
        2.5, 2.8, 1.9, 2.9,
        3.5, 2.5, 1.7, 3.6}), tensor.WithShape(2, 3, 4))
    fmt.Println("\nthree-rank tensor:")
    fmt.Println(threeRankTensor)
    fmt.Println("ndim:", threeRankTensor.Dims())
    fmt.Println("shape:", threeRankTensor.Shape())
    fmt.Println("dtype", threeRankTensor.Dtype())
}

tensor.New接受一個(gè)變長(zhǎng)參數(shù)列表,這里我們顯式傳入了存儲(chǔ)張量數(shù)據(jù)的平坦數(shù)組數(shù)據(jù)以及tensor的shape屬性,這樣我們便能得到一個(gè)滿足我們要求的tensor變量。運(yùn)行上面程序,你將看到下面內(nèi)容:

$ASSUME_NO_MOVING_GC_UNSAFE_RISK_IT_WITH=go1.20 go run tensor.go

one-rank tensor:
[1.7  2.6  1.3  3.2]
ndim: 1
shape: (4)
dtype float32

two-rank tensor:
?1.7  2.6  1.3  3.2?
?2.7  2.8  1.5  2.9?
?3.7  2.4  1.7  3.1?

ndim: 2
shape: (3, 4)
dtype float32

three-rank tensor:
?1.7  2.6  1.3  3.2?
?2.7  2.8  1.5  2.9?
?3.7  2.4  1.7  3.1?

?1.5  2.7  1.4  3.3?
?2.5  2.8  1.9  2.9?
?3.5  2.5  1.7  3.6?


ndim: 3
shape: (2, 3, 4)
dtype float32

tensor.New返回的*tensor.Dense類型實(shí)現(xiàn)了fmt.Stringer接口,可以按shape形式打印出tensor,但是人類肉眼也就識(shí)別到3階tensor吧。3階以上的tensor輸出的格式用人眼識(shí)別和理解就有些困難了。

此外,我們看到Gorgonia的tensor包基于平坦的數(shù)組來存儲(chǔ)tensor數(shù)據(jù),tensor包根據(jù)shape屬性對(duì)數(shù)組中數(shù)據(jù)做切分,劃分出不同軸上的數(shù)據(jù)。數(shù)組的元素類型可以自定義,如果我們使用float64的切片,那么tensor的dtype就為float64。

3. Go中的基本張量運(yùn)算

現(xiàn)在我們知道了如何使用Gorgonia/tensor創(chuàng)建張量了,讓我們來探索Go中的一些基本張量運(yùn)算。

3.1. 加法和減法

將兩個(gè)相同形狀(shape)的張量相加或相減是機(jī)器學(xué)習(xí)算法中的一個(gè)常見操作。在Go中,我們可以使用Gorgonia/tensor提供的Add和Sub函數(shù)進(jìn)行加減操作。下面是一個(gè)使用tensor包進(jìn)行加減運(yùn)算的示例代碼片斷:

// https://github.com/bigwhite/experiments/blob/master/go-and-nn/tensor-operations/tensor_add_sub.go

func main() {

    // define two two-rank tensor
    ta := tensor.New(tensor.WithBacking([]float32{1.7, 2.6, 1.3, 3.2,
        2.7, 2.8, 1.5, 2.9,
        3.7, 2.4, 1.7, 3.1}), tensor.WithShape(3, 4))
    fmt.Println("\ntensor a:")
    fmt.Println(ta)

    tb := tensor.New(tensor.WithBacking([]float32{1.7, 2.6, 1.3, 3.2,
        2.7, 2.8, 1.5, 2.9,
        3.7, 2.4, 1.7, 3.1}), tensor.WithShape(3, 4))
    fmt.Println("\ntensor b:")
    fmt.Println(ta)

    tc, _ := tensor.Add(ta, tb)
    fmt.Println("\ntensor a+b:")
    fmt.Println(tc)

    td, _ := tensor.Sub(ta, tb)
    fmt.Println("\ntensor a-b:")
    fmt.Println(td)

 // add in-place
 tensor.Add(ta, tb, tensor.UseUnsafe())
 fmt.Println("\ntensor a+b(in-place):")
 fmt.Println(ta)

 // tensor add scalar
 tg, err := tensor.Add(tb, float32(3.14))
 if err != nil {
     fmt.Println("add scalar error:", err)
     return
 }
 fmt.Println("\ntensor b+3.14:")
 fmt.Println(tg)

    // add two tensors of different shape
    te := tensor.New(tensor.WithBacking([]float32{1.7, 2.6, 1.3,
        3.2, 2.7, 2.8}), tensor.WithShape(2, 3))
    fmt.Println("\ntensor e:")
    fmt.Println(te)

    tf, err := tensor.Add(ta, te)
    fmt.Println("\ntensor a+e:")
    if err != nil {
        fmt.Println("add error:", err)
        return
    }
    fmt.Println(tf)
}

運(yùn)行該示例:

$ASSUME_NO_MOVING_GC_UNSAFE_RISK_IT_WITH=go1.20 go run tensor_add_sub.go

tensor a:
?1.7  2.6  1.3  3.2?
?2.7  2.8  1.5  2.9?
?3.7  2.4  1.7  3.1?


tensor b:
?1.7  2.6  1.3  3.2?
?2.7  2.8  1.5  2.9?
?3.7  2.4  1.7  3.1?


tensor a+b:
?3.4  5.2  2.6  6.4?
?5.4  5.6    3  5.8?
?7.4  4.8  3.4  6.2?


tensor a-b:
?0  0  0  0?
?0  0  0  0?
?0  0  0  0?

tensor a+b(in-place):
?3.4  5.2  2.6  6.4?
?5.4  5.6    3  5.8?
?7.4  4.8  3.4  6.2?

tensor b+3.14:
?     4.84       5.74       4.44       6.34?
?     5.84       5.94  4.6400003       6.04?
?     6.84       5.54       4.84       6.24?

tensor e:
?1.7  2.6  1.3?
?3.2  2.7  2.8?


tensor a+e:
add error: Add failed: Shape mismatch. Expected (2, 3). Got (3, 4)

我們看到:tensor加減法是一個(gè)逐元素(element-wise)進(jìn)行的操作,要求參與張量運(yùn)算的張量必須有相同的shape,同位置的兩個(gè)元素相加,否則會(huì)像示例中最后的a+e那樣報(bào)錯(cuò);tensor加法支持tensor與一個(gè)scalar(標(biāo)量)進(jìn)行加減,原理就是tensor中每個(gè)元素都與這個(gè)標(biāo)量相加減;此外若傳入tensor.Unsafe這個(gè)option后,參與加減法操作的第一個(gè)tensor的值會(huì)被結(jié)果重寫掉(override)。

3.2. 乘法和除法

兩個(gè)張量的相乘或相除是機(jī)器學(xué)習(xí)算法中另一個(gè)常見的操作。在Go中,我們可以使用Gorgonia/tensor提供的Mul和Div函數(shù)進(jìn)行乘除運(yùn)算。下面是一個(gè)使用Gorgonia/tensor進(jìn)行乘法和除法運(yùn)算的示例代碼:

// https://github.com/bigwhite/experiments/blob/master/go-and-nn/tensor-operations/tensor_mul_div.go

func main() {

 // define two two-rank tensor
 ta := tensor.New(tensor.WithBacking([]float32{1.7, 2.6, 1.3, 3.2,
  2.7, 2.8, 1.5, 2.9,
  3.7, 2.4, 1.7, 3.1}), tensor.WithShape(3, 4))
 fmt.Println("\ntensor a:")
 fmt.Println(ta)

 tb := tensor.New(tensor.WithBacking([]float32{1.7, 2.6, 1.3, 3.2,
  2.7, 2.8, 1.5, 2.9,
  3.7, 2.4, 1.7, 3.1}), tensor.WithShape(3, 4))
 fmt.Println("\ntensor b:")
 fmt.Println(tb)

 tc, err := tensor.Mul(ta, tb)
 if err != nil {
  fmt.Println("multiply error:", err)
  return
 }
 fmt.Println("\ntensor a x b:")
 fmt.Println(tc)

 // multiple tensor and a scalar
 td, err := tensor.Mul(ta, float32(3.14))
 if err != nil {
  fmt.Println("multiply error:", err)
  return
 }
 fmt.Println("\ntensor ta x 3.14:")
 fmt.Println(td)

 // divide two tensors  
 td, err = tensor.Div(ta, tb)
 if err != nil {
  fmt.Println("divide error:", err)
  return
 }
 fmt.Println("\ntensor ta / tb:")
 fmt.Println(td)

 // multiply two tensors of different shape
 te := tensor.New(tensor.WithBacking([]float32{1.7, 2.6, 1.3,
  3.2, 2.7, 2.8}), tensor.WithShape(2, 3))
 fmt.Println("\ntensor e:")
 fmt.Println(te)

 tf, err := tensor.Mul(ta, te)
 fmt.Println("\ntensor a x e:")
 if err != nil {
  fmt.Println("mul error:", err)
  return
 }
 fmt.Println(tf)
}

運(yùn)行該示例,我們可以看到如下結(jié)果:

$ASSUME_NO_MOVING_GC_UNSAFE_RISK_IT_WITH=go1.20 go run tensor_mul_div.go

tensor a:
?1.7  2.6  1.3  3.2?
?2.7  2.8  1.5  2.9?
?3.7  2.4  1.7  3.1?


tensor b:
?1.7  2.6  1.3  3.2?
?2.7  2.8  1.5  2.9?
?3.7  2.4  1.7  3.1?


tensor a x b:
?     2.89  6.7599993  1.6899998  10.240001?
?7.2900004  7.8399997       2.25   8.410001?
?13.690001       5.76       2.89       9.61?


tensor ta x 3.14:
?5.3380003      8.164      4.082     10.048?
? 8.478001      8.792       4.71   9.106001?
?11.618001  7.5360007  5.3380003      9.734?

tensor ta / tb:
?1  1  1  1?
?1  1  1  1?
?1  1  1  1?

tensor e:
?1.7  2.6  1.3?
?3.2  2.7  2.8?


tensor a x e:
mul error: Mul failed: Shape mismatch. Expected (2, 3). Got (3, 4)

我們看到,和加減法一樣,tensor的乘除法也是逐元素進(jìn)行的,同時(shí)也支持與scalar的乘除。但對(duì)于shape不同的兩個(gè)tensor,Mul和Div會(huì)報(bào)錯(cuò)。

了解了加減、乘除等基本操作后,下面我們?cè)偬剿饕粚懜呒?jí)的張量操作。

4. Go中的高級(jí)張量操作

除了基本的張量操作外,Go還提供了一些高級(jí)的張量操作,用于復(fù)雜的機(jī)器學(xué)習(xí)算法中。讓我們來探討一下Go中的一些高級(jí)張量操作。

4.1. 點(diǎn)積

點(diǎn)積運(yùn)算,也叫張量積(tensor product,不要與上面的逐元素的乘積弄混),是線性代數(shù)和機(jī)器學(xué)習(xí)算法中的一個(gè)作最常見也最有用的張量運(yùn)算。與逐元素的運(yùn)算不同,它將輸入張量的元素合并在一起。

它涉及到將兩個(gè)張量元素相乘,然后將結(jié)果相加。這里借用魚書中的圖來直觀的看一下二階tensor計(jì)算過程:

圖片圖片

圖中是兩個(gè)shape為(2, 2)的tensor的點(diǎn)積。

下面是更一般的兩個(gè)二階tensor t1和t2:

tensor t1: shape(a, b) 
tensor t2: shape(c, d)

t1和t2可以做點(diǎn)積的前提是b == c,即第一個(gè)tensor t1的shape[1] == 第二個(gè)tensor t2的shape[0]。

在Go中,我們可以Dot函數(shù)來實(shí)現(xiàn)點(diǎn)積操作。下面是使用Gorgonia/tensor進(jìn)行點(diǎn)積操作的例子:

// https://github.com/bigwhite/experiments/blob/master/go-and-nn/tensor-operations/tensor_dot.go

func main() {

 // define two two-rank tensor
 ta := tensor.New(tensor.WithBacking([]float32{1, 2, 3, 4}), tensor.WithShape(2, 2))
 fmt.Println("\ntensor a:")
 fmt.Println(ta)

 tb := tensor.New(tensor.WithBacking([]float32{5, 6, 7, 8}), tensor.WithShape(2, 2))
 fmt.Println("\ntensor b:")
 fmt.Println(tb)

 tc, err := tensor.Dot(ta, tb)
 if err != nil {
  fmt.Println("dot error:", err)
  return
 }
 fmt.Println("\ntensor a dot b:")
 fmt.Println(tc)

 td := tensor.New(tensor.WithBacking([]float32{5, 6, 7, 8, 9, 10}), tensor.WithShape(2, 3))
 fmt.Println("\ntensor d:")
 fmt.Println(td)
 te, err := tensor.Dot(ta, td)
 if err != nil {
  fmt.Println("dot error:", err)
  return
 }
 fmt.Println("\ntensor a dot d:")
 fmt.Println(te)

 // three-rank tensor dot two-rank tensor
 tf := tensor.New(tensor.WithBacking([]float32{23: 12}), tensor.WithShape(2, 3, 4))
 fmt.Println("\ntensor f:")
 fmt.Println(tf)

 tg := tensor.New(tensor.WithBacking([]float32{11: 12}), tensor.WithShape(4, 3))
 fmt.Println("\ntensor g:")
 fmt.Println(tg)

 th, err := tensor.Dot(tf, tg)
 if err != nil {
  fmt.Println("dot error:", err)
  return
 }
 fmt.Println("\ntensor f dot g:")
 fmt.Println(th)
}

運(yùn)行該示例,我們可以看到如下結(jié)果:

$ASSUME_NO_MOVING_GC_UNSAFE_RISK_IT_WITH=go1.20 go run tensor_dot.go

tensor a:
?1  2?
?3  4?


tensor b:
?5  6?
?7  8?


tensor a dot b:
?19  22?
?43  50?


tensor d:
? 5   6   7?
? 8   9  10?


tensor a dot d:
?21  24  27?
?47  54  61?


tensor f:
? 0   0   0   0?
? 0   0   0   0?
? 0   0   0   0?

? 0   0   0   0?
? 0   0   0   0?
? 0   0   0  12?



tensor g:
? 0   0   0?
? 0   0   0?
? 0   0   0?
? 0   0  12?


tensor f dot g:
?  0    0    0?
?  0    0    0?
?  0    0    0?

?  0    0    0?
?  0    0    0?
?  0    0  144?

我們看到大于2階的高階tensor也可以做點(diǎn)積,只要其形狀匹配遵循與前面2階張量相同的原則:

(a, b, c, d) . (d,) -> (a, b, c)
(a, b, c, d) . (d, e) -> (a, b, c, e)

4.2. 轉(zhuǎn)置

轉(zhuǎn)置張量包括翻轉(zhuǎn)其行和列。這是機(jī)器學(xué)習(xí)算法中的一個(gè)常見操作,廣泛應(yīng)用在圖像處理和自然語言處理等領(lǐng)域。在Go中,我們可以使用tensor包提供的Transpose函數(shù)對(duì)tensor進(jìn)行轉(zhuǎn)置:

// https://github.com/bigwhite/experiments/blob/master/go-and-nn/tensor-operations/tensor_transpose.go

func main() {

 // define two-rank tensor
 ta := tensor.New(tensor.WithBacking([]float32{1, 2, 3, 4, 5, 6}), tensor.WithShape(3, 2))
 fmt.Println("\ntensor a:")
 fmt.Println(ta)

 tb, err := tensor.Transpose(ta)
 if err != nil {
  fmt.Println("transpose error:", err)
  return
 }
 fmt.Println("\ntensor a transpose:")
 fmt.Println(tb)

 // define three-rank tensor
 tc := tensor.New(tensor.WithBacking([]float32{1, 2, 3, 4, 5, 6,
  7, 8, 9, 10, 11, 12,
  13, 14, 15, 16, 17, 18,
  19, 20, 21, 22, 23, 24}), tensor.WithShape(2, 3, 4))
 fmt.Println("\ntensor c:")
 fmt.Println(tc)
 fmt.Println("tc shape:", tc.Shape())

 td, err := tensor.Transpose(tc)
 if err != nil {
  fmt.Println("transpose error:", err)
  return
 }
 fmt.Println("\ntensor c transpose:")
 fmt.Println(td)
 fmt.Println("td shape:", td.Shape())
}

運(yùn)行上面示例:

$ASSUME_NO_MOVING_GC_UNSAFE_RISK_IT_WITH=go1.20 go run tensor_transpose.go

tensor a:
?1  2?
?3  4?
?5  6?


tensor a transpose:
?1  3  5?
?2  4  6?


tensor c:
? 1   2   3   4?
? 5   6   7   8?
? 9  10  11  12?

?13  14  15  16?
?17  18  19  20?
?21  22  23  24?


tc shape: (2, 3, 4)

tensor c transpose:
? 1  13?
? 5  17?
? 9  21?

? 2  14?
? 6  18?
?10  22?

? 3  15?
? 7  19?
?11  23?

? 4  16?
? 8  20?
?12  24?

td shape: (4, 3, 2)

接下來,我們?cè)賮硖接憙蓚€(gè)張量的高級(jí)操作:重塑(也叫變形)與廣播。

5. 在Go中重塑與廣播張量

在機(jī)器學(xué)習(xí)算法中,經(jīng)常需要對(duì)張量進(jìn)行重塑和廣播,使其與不同的操作兼容。Go提供了幾個(gè)函數(shù)來重塑和廣播張量。讓我們來探討如何在Go中重塑和廣播張量。

5.1. 重塑張量

重塑一個(gè)張量涉及到改變它的尺寸到一個(gè)新的形狀。在Go中,我們可以使用Gorgonia/tensor提供的Dense類型的Reshape方法來重塑張量自身。

下面是一個(gè)使用Gorgonia重塑張量的示例代碼:

// https://github.com/bigwhite/experiments/blob/master/go-and-nn/tensor-operations/tensor_reshape.go

func main() {

 // define two-rank tensor
 ta := tensor.New(tensor.WithBacking([]float32{1, 2, 3, 4, 5, 6}), tensor.WithShape(3, 2))
 fmt.Println("\ntensor a:")
 fmt.Println(ta)
 fmt.Println("ta shape:", ta.Shape())

 err := ta.Reshape(2, 3)
 if err != nil {
  fmt.Println("reshape error:", err)
  return
 }
 fmt.Println("\ntensor a reshape(2,3):")
 fmt.Println(ta)
 fmt.Println("ta shape:", ta.Shape())

 err = ta.Reshape(1, 6)
 if err != nil {
  fmt.Println("reshape error:", err)
  return
 }
 fmt.Println("\ntensor a reshape(1, 6):")
 fmt.Println(ta)
 fmt.Println("ta shape:", ta.Shape())

 err = ta.Reshape(2, 1, 3)
 if err != nil {
  fmt.Println("reshape error:", err)
  return
 }
 fmt.Println("\ntensor a reshape(2, 1, 3):")
 fmt.Println(ta)
 fmt.Println("ta shape:", ta.Shape())
}

運(yùn)行上述代碼,我們將看到:

$ASSUME_NO_MOVING_GC_UNSAFE_RISK_IT_WITH=go1.20 go run tensor_reshape.go

tensor a:
?1  2?
?3  4?
?5  6?

ta shape: (3, 2)

tensor a reshape(2,3):
?1  2  3?
?4  5  6?

ta shape: (2, 3)

tensor a reshape(1, 6):
R[1  2  3  4  5  6]
ta shape: (1, 6)

tensor a reshape(2, 1, 3):
?1  2  3?
?4  5  6?

ta shape: (2, 1, 3)

由此看來,張量轉(zhuǎn)置其實(shí)是張量重塑的一個(gè)特例,只是將將軸對(duì)調(diào)。

5.2. 廣播張量

廣播張量涉及到擴(kuò)展其維度以使其與其他操作兼容。下面是魚書中關(guān)于廣播(broadcast)的圖解:

圖片圖片

我們看到圖中這個(gè)標(biāo)量(Scalar)擴(kuò)展維度后與第一個(gè)張量做乘法操作,與我們前面說到的張量與標(biāo)量(scalar)相乘是一樣的。如上圖中這種標(biāo)量10被擴(kuò)展成了2 × 2的形狀后再與矩陣A進(jìn)行乘法運(yùn)算,這個(gè)的功能就稱為廣播(broadcast)。

在魚書中還提到了“借助這個(gè)廣播功能,不同形狀的張量之間也可以順利地進(jìn)行運(yùn)算”以及下面圖中這個(gè)示例:


圖片

但Gorgonia/tensor包目前并不支持除標(biāo)量之外的“廣播”。

6. 小結(jié)

張量操作在機(jī)器學(xué)習(xí)和數(shù)據(jù)科學(xué)中是必不可少的,它允許我們有效地操縱多維數(shù)組。在這篇文章中,我們探討了如何使用Go創(chuàng)建和執(zhí)行基本和高級(jí)張量操作。我們還學(xué)習(xí)了廣播和重塑張量,使它們與不同的機(jī)器學(xué)習(xí)模型兼容。

我希望這篇文章能為后續(xù)繼續(xù)探究深度學(xué)習(xí)與神經(jīng)網(wǎng)絡(luò)奠定一個(gè)基礎(chǔ),讓你開始探索Go中的張量操作,并使用它們來解決現(xiàn)實(shí)世界的問題。

注:說實(shí)話,Go在機(jī)器學(xué)習(xí)領(lǐng)域的應(yīng)用并不廣泛,前景也不明朗,零星的幾個(gè)開源庫似乎也不是很活躍。這里也僅是基于Go去學(xué)習(xí)理解機(jī)器學(xué)習(xí)的概念和操作,真正為生產(chǎn)編寫和訓(xùn)練的機(jī)器學(xué)習(xí)模型與程序還是要使用Python。

本文涉及的源碼可以在這里[8]下載 - https://github.com/bigwhite/experiments/blob/master/go-and-nn/tensor-operations

7. 參考資料

  • 《Python深度學(xué)習(xí)(第二版)》[9] - https://book.douban.com/subject/36078304/
  • 魚書《深度學(xué)習(xí)入門:基于Python的理論與實(shí)現(xiàn)》[10] - https://book.douban.com/subject/30270959/
  • 蘋果書《深入淺出神經(jīng)網(wǎng)絡(luò)與深度學(xué)習(xí)》[11] - https://book.douban.com/subject/35128111/
  • 《機(jī)器學(xué)習(xí):Go語言實(shí)現(xiàn)》[12] - https://book.douban.com/subject/30457083/
責(zé)任編輯:武曉燕 來源: TonyBai
相關(guān)推薦

2024-06-28 08:15:02

2024-07-10 11:09:35

2017-09-10 07:07:32

神經(jīng)網(wǎng)絡(luò)數(shù)據(jù)集可視化

2018-07-03 16:10:04

神經(jīng)網(wǎng)絡(luò)生物神經(jīng)網(wǎng)絡(luò)人工神經(jīng)網(wǎng)絡(luò)

2023-11-14 16:29:14

深度學(xué)習(xí)

2017-11-30 18:05:18

2020-08-20 07:00:00

深度學(xué)習(xí)人工智能技術(shù)

2025-02-25 14:13:31

2017-05-04 18:30:34

大數(shù)據(jù)卷積神經(jīng)網(wǎng)絡(luò)

2019-05-07 19:12:28

機(jī)器學(xué)習(xí)神經(jīng)網(wǎng)絡(luò)Python

2022-04-07 09:01:52

神經(jīng)網(wǎng)絡(luò)人工智能

2020-08-06 10:11:13

神經(jīng)網(wǎng)絡(luò)機(jī)器學(xué)習(xí)算法

2018-03-21 10:14:38

JavaScript交叉GPU

2019-01-05 08:40:17

VGG神經(jīng)網(wǎng)絡(luò)

2023-06-18 23:00:39

神經(jīng)網(wǎng)絡(luò)損失函數(shù)隨機(jī)變量

2019-11-19 08:00:00

神經(jīng)網(wǎng)絡(luò)AI人工智能

2017-08-29 09:40:26

JavaScript代碼神經(jīng)網(wǎng)絡(luò)

2020-09-09 10:20:48

GraphSAGE神經(jīng)網(wǎng)絡(luò)人工智能

2017-12-22 08:47:41

神經(jīng)網(wǎng)絡(luò)AND運(yùn)算

2023-05-04 07:39:14

圖神經(jīng)網(wǎng)絡(luò)GNN
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)