Max Coding blog

Max Coding blog

Zero in your target,and go for it.

TensorFlow1基礎使用方法

Tensorflow

import tensorflow as tf

tf.add()

進行兩個變數的加減。 tf.math.add(x, y, name=None)

1
2
3
x = [1,2,3,4,5]
y = 2
print(tf.add(x,y))#output:tf.Tensor([3 4 5 6 7], shape=(5,), dtype=int32)
上述程式可以看做下面這段程式:
1
2
3
4
import tensorflow as tf
x = tf.convert_to_tensor([1,2,3,4,5])
y = tf.convert_to_tensor(2)
print(x+y)
或者可以相加兩個一維張量:
1
2
3
4
import tensorflow as tf
x = tf.constant([1,2,3,4,5])
y = tf.constant([1,1,1,1,1])
tf.add(x,y)#output = <tf.Tensor: shape=(5,), dtype=int32, numpy=array([2, 3, 4, 5, 6], dtype=int32)>
## convert_to_tensor() tf.convert_to_tensor(value, dtype=None, dtype_hint=None, name=None) 將一個變數轉成tensor的形式。 如果將一個tensor和一個non-tensor做運算,可能會發生不可預期的事。
1
2
3
4
5
def func(val):
val = tf.convert_to_tensor(val)
return val
v1 = func(tf.constant([[1,2,3],[4,5,6]]))
print(v1)
## TF變數 常見的TF的變數主要有兩種,tf.constant、tf.Variable,後面會一個一個介紹。 ## tf.constant() tf.constant(value, dtype=None, shape=None, name='Const')
基礎TensorFlow1和2與機器學習

心情抒發

了解基礎numpy、pandas、matplotlib後我們就要進入深度學習了,我在進行深度學習的訓練的時候有時是使用linux作業系統,因為我的mac M1在下載tensorflow的時候遇到很多問題,因為mac M1和之前intel版本的mac不太一樣,之前的intel版本是x86架構的,但是mac M1是arm架構的,我花了三天還是沒辦法在我的mac上下載tensorflow,因此我在做機器學習時有兩種做法,第一種是使用google的Colaboratory,另一種是使用linux,所以我幫我的ASUS灌雙系統,為何不直接用windows10呢?因為我比較喜歡mac,所以我有時會在mac上寫程式,再透過ssh的方式在linux上進行,在這裡不得不讚嘆mac的界面設計,光看到就很舒服,而linux的界面有點太舊了,看了有點不習慣,那廢話不多說就進入我們的Machine Learning。 ## Linear Regression(線性回歸)design 所謂的線性回歸就是在觀察和歸納樣本的過程中向量和函數值呈現線性的關係,那這種關係可以用一個關係式來表達: Formula: Y = X1*W1 + X2*W2 + B 其中W和X都是矩陣,代表我們的參數可以不只一個,W代表Weights(權重),B代表Bias(偏移量)。 而運用這個公式有一個缺點,就是它的數值會介於正無限大到負無限大,所以這時候會加入活化函數,這個我稍後再提。

然而我們在進行機器學習有一個很重要的目標,就是讓Loss變小,什麼是Loss呢?我來粗略的介紹一下,在解釋前先看看這兩個關係式: 1. MSE(Mean-Square Error) 2. MAE(Mean-Absolute Error)

我們在做機器學習的時候希望計算機幫我們做預測的時候誤差值越低越好,總不能我們叫它預測一個一次函數,結果它算出來的答案是三次函數吧,所以Loss是在計算電腦在進行運算時的誤差值,我們稱之為loss function,loss function有很多種,而我目前只略懂這兩種。

首先來說MSE(Mean-Square Error),MSE顧名思義,均方誤差(MSE)計算的是預測值和實際觀測值間差的平方的均值。它只考慮誤差的平均大小,不考慮其向量,前面有提到y = f(x) = wx+b,這個y是我們預測的值,而我們要減去真實數據,即可得到差值,但為何要平方?因為我們的差值有可能是負的,怕會到時相消,所以這裡將它平方,可以保證為正值,但MSE方法仍然有個小問題,在「單位的解釋」上我們有點難以解釋數據,例如身高的平方的意義是?此時我們會使用RMSE(Root Mean Squared Error)將它開根號。

接著來說MAE(Mean-Absolute Error),其實MAE和MSE觀念很像,那觀念一樣的地方我就不多做贅述,MAE和MSE的關係上只差在一個是平方,另一個則是取絕對值,既然如此為何要分這兩種呢?老實說我也不太能理解,我在網路上看資料的時候他是這樣說的,「會有「在等於0時」不可微分的問題,不可微分會有什麼問題? 簡單來說,我們會沒辦法透過微分決定ML模型的修正方向。」對於微分我不是很理解,因為正在寫此篇的我還是小高一,但也無妨,實際上在做微分這運算的不是我,而是計算機,所以我也不用太著急,但MAE完全不能使用嗎? 倒也不完全是不能用,它也是有他的優勢的,那優勢在哪,有機會再來說吧!

處理機器學習的三大步驟

在進行ML的時候常常會因為其中的一個步驟做的不好而導致訓練失敗,所以每個環節都是非常重要的。 首先第一個步驟是設定一個未知的函式,就如同Linear regression那邊一樣,設定一個函式,再把參數加進去,算出y。 第二步,定義loss function,就如同前面所說的,後續會深入介紹。 第三步,optimization,這個步驟是很容易被遺忘的,而這個會在後面的例子中說到。 ## nonlinear regression(非線性函數) 前面有提到活化函式,這個活化函式是用在線性回歸之後,這邊我使用sigmoid()函式做介紹,故公式會變成下面那個: Formula: Y = Sigmoid(X1*W1 + X2*W2 + B) sigmoid()是一個非線性函數,它的作用是在於使整個輸出保持在0~1之間。 ## 函數數值訓練 ### 首先先看個code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import tensorflow.compat.v1 as tf
import numpy as np
import matplotlib.pyplot as plt

tf.disable_v2_behavior()

x = np.random.rand(200).astype(np.float32)
y = x*0.5+0.8
plt.plot(x,y,color = 'red')
weights = tf.Variable(tf.random.uniform([1],-1.0,1.0))
biases = tf.Variable(tf.zeros([1]))

func = weights*x + biases

loss = tf.reduce_mean(tf.square(func - y)) # MSE

optimization = tf.train.GradientDescentOptimizer(0.5)
train = optimization.minimize(loss)

init = tf.global_variables_initializer()

sess = tf.Session()
sess.run(init)

for i in range(201):
sess.run(train)
if i % 10 == 0:
plt.scatter(x,x*sess.run(weights)+sess.run(biases))
print(i,sess.run(weights),sess.run(biases))
plt.show()
### output : 0 [0.96190155] [0.7515451] 10 [0.69052416] [0.6988468] 20 [0.5954242] [0.7493372] 30 [0.5477933] [0.7746255] 40 [0.5239374] [0.7872911] 50 [0.5119891] [0.7936347] 60 [0.50600475] [0.79681194] 70 [0.50300753] [0.79840326] 80 [0.50150627] [0.7992003] 90 [0.5007544] [0.79959947] 100 [0.5003779] [0.7997994] 110 [0.50018924] [0.7998995] 120 [0.5000948] [0.7999497] 130 [0.50004745] [0.7999748] 140 [0.5000238] [0.7999874] 150 [0.50001186] [0.7999937] 160 [0.50000596] [0.79999685] 170 [0.50000304] [0.7999984] 180 [0.50000155] [0.7999992] 190 [0.5000008] [0.7999996] 200 [0.5000004] [0.7999998] 注意:此輸出不是固定的!由於我們是設計這個程式讓計算機去學習,所以它每次的學習效果都不一樣,因此它每次的輸出會不一樣。

基礎pytorch與機器學習

深度學習與pytorch

中和高中吳振榮

Torch 和 Numpy

Torch自稱是神經網路的numpy,但是還是有許多人對numpy比較熱愛,所以Torch在和numpy做調配的時候非常方便。 ### array to torch:from_numpy() 首先利用torch把numpy的array轉成torch的tensor。

1
2
3
4
5
6
7
8
9
import torch
import numpy as np

np_data = np.arange(1,7).reshape((2,3))
torch_data = torch.from_numpy(np_data)

print(np_data)
print()
print(torch_data)
把array放進from_numpy()即可得到一個tensor(張量)。
OpenCV

OpenCV

讀取圖片

1
2
3
4
5
6
import numpy as np
import cv2

img1 = cv2.imread('2cut.jpeg',cv2.IMREAD_COLOR)
img2 = cv2.imread('2cut.jpeg',cv2.IMREAD_GRAYSCALE)
img3 = cv2.imread('2cut.jpeg',cv2.cv2.IMREAD_UNCHANGED)

想輸入一個圖片可以使用cv2.imread(),cv2.imread()第一個參數是圖片名稱,第二個參數可以放三種,以上面為例,cv2.IMREAD_COLOR代表彩色的形式,cv2.IMREAD_GRAYSCALE代表灰階的形式,cv2.IMREAD_UNCHANGED代表讀取image的所有channels,包含透明的channel。 ## 查看型態和numpy的陣列大小

1
2
3
4
5
6
7
import numpy as np
import cv2

img = cv2.imread('2cut.jpeg',cv2.IMREAD_GRAYSCALE)
print(type(img))
print(img.shape)
print(img)
其實我們這邊的圖都有厚度,在這邊做圖的資料分析要知道黑白圖的厚度是1,而彩色是3,彩色之所以是3是因為有RGB三種顏色。 ## 顯示輸入的圖片
1
2
3
4
5
6
7
8
import numpy as np
import cv2

img = cv2.imread('2cut.jpeg',cv2.IMREAD_GRAYSCALE)

cv2.imshow('my image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
利用cv2.imshow()將圖輸出,而cv2.waitKey(0),cv2.destroyAllWindows()代表當使用者按了鍵盤的任意鍵視窗就會關閉。

by 中和高中 吳振榮
pipenv

pipenv

創建虛擬環境

pipenv --python 3.6.9 ## 創建虛擬環境(2) pipenv install --python=$(which python) ## 查看所有項目 pipenv list ## 進入虛擬環境 pipenv shell ## 查看當前虛擬目錄 pipenv --venv ## 給其他人使用的 pipenv install flask ## 給開發者使用的 pipenv install --dev requests ## 運行pipenv檔案裡的python pipenv run python main.py ## 腳本 [scripts] start = "text" test = "text" list = "text ## pyenv ## 在一個目錄中選擇一種python版本 pyenv local 3.6.9 ## 查看使用哪個路徑下的python which python

by 中和高中 吳振榮
指標

指標(pointer)

定義:儲存變數的記憶體位置。
1
2
3
4
5
6
7
int main(){
int a = 10;
int * p = &a;
cout << "a的值:" << a << endl;
cout << "a的記憶體位置:" << p << endl;
cout << "從記憶體位置取值:" << *p << endl;
}

new/delete

由於指標是儲存記憶體位置,所以今天必須給他一個記憶體位置,如果沒給就要進行值的轉換,例如以下:(以下為錯誤示範)

1
2
3
4
5
int main(){
int * a;
*a = 100;
cout << *a << endl;
}
這樣寫的話會出現Runtime Error(記憶體區段錯誤) 所以我們應該利用new來配置一個記憶體位置給他
1
2
3
4
int * a = new int;
cout << "a的記憶體位置:" << a << endl;
*a = 100;
cout << "a的值:" << *a << endl;
如果我們今天要把這個記憶體位置釋放,我們使用delete,這樣這個記憶體空間就被釋放了
1
2
3
4
5
6
7
int * a = new int;
cout << "a的記憶體位置:" << a << endl;
*a = 100;
cout << "a的值:" << *a << endl;
delete a;//此記憶體位置被釋放了
cout << *a << endl;
cout << a << endl;
ctime

ctime

概述

ctime是由C語言的time.h沿用到C++的,也沒什麼好說的,開始使用吧! ## 取得現在的時間 time_t time (time_t* timer); 這個函式會從 1970 年 1 月 1 日 00:00 UTC 起計算到使用者使用的現在,來看花使用:

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <ctime>
using namespace std;
int main(){
time_t a = time(NULL);
cout << a << endl;

return 0;
}
從上述程式碼我們可以得到一個輸出,以我現在為例,我得到的輸出是1623735092,代表我從1970 年 1 月 1 日 00:00 UTC到現在的秒數,但我們要的不僅僅是秒數,我們要的是年月日時分秒,所以我們要使用另一個函式來達成這個目標,我們先去C++ reference看一下如果我們要達成目標該用哪個函式呢?我們可以在ctime裡看到我們可以使用此函式來達到這個目的,但又有另一個問題,就是我們該傳什麼參數進去呢?再去看一次C++ reference 時看到我們應該傳一個time_t的指標進去, char* ctime (const time_t * timer); 在前面的char*就是一個string,只是在C語言是這個用法,那我們將一個time_t的指標傳入,看看會發生什麼事。
1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include <ctime>
using namespace std;
int main(){
time_t a = time(NULL);
cout << a << endl;//output:1623746252

char *b = ctime(&a);
cout << b << endl;//output:Tue Jun 15 16:37:32 2021

return 0;
}
果然輸出我們要的結果,Tue Jun 15 16:37:32 2021。 但如果我們今天要的只有某一部分的時間,就是說我不要年月日時分秒全部輸出,我只要年月日的話怎麼辦?
1
2
3
4
5
6
7
8
9
10
#include <iostream>
#include <ctime>
using namespace std;
int main(){
time_t t = time(NULL);
struct tm * pt = localtime(&t);
cout << pt->tm_year+1900 << "/" << pt->tm_mon + 1 << "/" << pt->tm_mday <<endl;

return 0;
}
這邊就比較難理解了,我們可以看到我們此處使用了一個結構,名叫tm,接著我們去看看C++ reference,可以看到我們要宣告的名稱需要是一個指標, struct tm * localtime (const time_t * timer); 且在localtime()裡也需要一個time_t的指標,所以我才會這樣做宣告,接著要把年月日輸出,由於我們tm_year是結構裡的成員,所以我們這邊需利用箭號運算子,來求值,故輸出為2021/6/15。

by 中和高中 吳振榮
Linked_List

Linked list

Linked list是一種常見的資料結構,利用指標的指向來得到下一個資料,那為什麼要用Linked list呢?為何不使用array就好,接下來說說Linked list的優缺點。

優點:

Linked list和array不一樣的地方就是Linked list可以動態配置大小,而array的大小是固定的,接著Linked list要新增或刪除元素都比較方便快速。

缺點:

Linked list是沒有index的,所以在搜尋資料中會相對的耗時。

接著來看看Linked list的樣子大概長怎樣,我們每個node都有一個記憶體位置,而它所存取的內容會有資料以及下一個node的記憶體位置,而最後一個node存取的記憶體位置為NULL,這樣一個Linked list就結束了,看看圖增加印象吧!

建立兩個節點:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <stdlib.h>
typedef struct linked_list *link_ptr;
typedef struct linked_list{
int data;
link_ptr add;
};
int main(){
link_ptr vertex1 = NULL;
link_ptr vertex2 = NULL;
vertex1 = malloc(sizeof(linked_list));
vertex2 = malloc(sizeof(linked_list));
vertex2->add = NULL;
vertex2->data = 100;
vertex1->add = vertex2;
vertex1->data = 10;
printf("%d\n", vertex1->add->data);
printf("%p\n", vertex2);

return 0;
}

我們這裡使用C語言撰寫,首先建立一個結構叫linked_list,接著宣告一個結構指標叫做link_ptr,在main裡我們利用link_ptr來宣兩個變數,分別初始化為NULL,由於我們使用link_ptr宣告,代表vertex1和vertex2也是指標結構,同時擁有資料和下一個位置的記憶體位置,接著我們使用malloc()做動態記憶體配置,由於vertex2是最後一個元素,所以它指向NULL,而vertex1則指向vertex2,即完成linked list。 ## 例子:

Stringstream

Stringstream

在C語言的時候處理字串非常麻煩,而且在輸入字串的時候要使用char[]或char*,很不直觀,到了C++的時候多了string這個資料型態,在C++11的時候又有了stringstream,此時要做字串處理變得很方便。 要使用stringstream前要先引用一個標頭檔,#include <sstream>,stringstream專門拿來讀取字串並且處理。 ## 1. int 轉換成 string

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <sstream>
using namespace std;
int main(){
stringstream ss;
string int_to_str;
int num = 12345;
ss << num;
ss >> int_to_str;
cout << int_to_str << endl;

return 0;
}
## 2. string 轉換成 int
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <sstream>
using namespace std;
int main(){
stringstream ss;
string int_to_str = "11804";
int num;
ss << int_to_str;
ss >> num;
cout << num << endl;

return 0;
}
## 3. 清除stringstream 如果使用過的stringstream還要繼續使用,必須先清除,如果未清除則會出現無法預測的答案,以下為未清除的時候。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <sstream>
using namespace std;
int main(){
stringstream ss;
string int_to_str = "11804";
int num;
ss << int_to_str;
ss >> num;
cout << num << " ";
string str = "12345";
ss << str;
int a;
ss >> a;
cout << a ;

return 0;
}
此時的輸出是:11804 0 但我們希望的結果為11804 12345 所以我們要進行刪除,利用ss.str(""); ss.clear();。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <sstream>
using namespace std;
int main(){
stringstream ss;
string int_to_str = "11804";
int num;
ss << int_to_str;
ss >> num;
cout << num << " ";
ss.str("");
ss.clear();
string str = "12345";
ss << str;
int a;
ss >> a;
cout << a;

return 0;
}
此時就可以正常輸出了。 ## 4. 利用stringstream進行基本輸出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <sstream>
using namespace std;
int main(){
stringstream ss1("he");
stringstream ss2,ss3,ss4;
string str("max");
ss2.str("hello");
ss3.str(str);
ss4 << "hey";
cout << ss1.str() << endl;//output = he
cout << ss2.str() << endl;//output = hello
cout << ss3.str() << endl;//output = max
cout << ss4.str() << endl;//output = hey

return 0;
}
## 5. 分割stringstream ### 用空格做切割
1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <sstream>
using namespace std;
int main(){
stringstream ss1("my name is max ");
string str("");
while(ss1 >> str)
cout << str << endl;

return 0;
}
### 用getline()把stringstream用特定的string取出 第一個arguement放stringstream,第二個arguement放string,第三個arguement只能放char,不能放string。
1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include <sstream>
using namespace std;
int main(){
stringstream ss("hello my name is max");
string str;
while(getline(ss,str,'m')){
cout << str << endl;
}

return 0;
}
output : hello y na e is ax

運算子重載

運算子重載(operator overloading)

學完物件導向程式設計後可以來了解重載運算子重載,operator overloading也可以叫做運算子覆載,或運算子多載,反正意思差不多,這裡就稱為重載運算子重載,至於為何要使用運算子重載呢?先來看一個例子:

1
2
3
4
5
int main(void){
int a = 4, b = 6, c;
c = a + b;
std::cout << c; //output:10
}
這個例子中可以很清楚的看到我用一個「加號」將a和b相加,這當然沒問題,那下面這個例子呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
using namespace std;
class data{
public:
int a,b;
data(int,int);
};
data::data(int x = 0,int y = 0){
a = x;
b = y;
}
int main(){
data d1(3,4), d2(6,3), d3;
d3 = d1 + d2;

return 0;
}
這個例子中會得到一個錯誤,因為此時這個「加號」它不知道該怎麼用,因為在這個class裡有兩個數,程式不知道要加哪一個,此時我們就會用到運算子重載,重新定義一個運算子,那定義方式如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
using namespace std;
class data{
public:
int a,b;
data(int,int);
void print_data();
data operator+(data &y_data){
int a_plus = a + y_data.a;
int b_plus = b + y_data.b;
data z_data(a_plus,b_plus);
return z_data;
}
};
data::data(int x = 0,int y = 0){
a = x;
b = y;
}
int main(){
data d1(3,4), d2(6,3), d3;
d3 = d1 + d2;
cout << d3.a << " " << d3.b << endl;

return 0;
}
此時可以完整解決原本的問題,我們將「加號」定義成第一個物件的a加上第二個物件的b,接著我將它存成z_data,並回傳。

在使用sort()函式時也可以使用運算子重載:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <ctime>
using namespace std;
class data{
public:
int a,b;
data(int,int);
bool operator < (data &y_data){
if(b < y_data.b);
else return b < y_data.b;
}
};
data::data(int x = 0,int y = 0){
a = x;
b = y;
}
int main(){
data arr[5];
srand(time(NULL));
for(int i = 0;i < 5;i++){
arr[i].a = i;
arr[i].b = rand()%30+1;
}
for(int i = 0;i < 5;i++) cout << arr[i].a << " " << arr[i].b << endl;
sort(arr,arr+5);
cout << "===========" << endl;
for(int i = 0;i < 5;i++)
cout << arr[i].b << " ";
cout << endl;
return 0;
}
此處我們將 < 這個符號重新定義,使我們的sort在比較的時候,能清楚知道如何比較,然而我們這邊這樣定義運算子重載是不太好的,好一點的作法如下:
1
2
3
4
bool const operator < (data &y_data) const {
if(b < y_data.b);
else return b < y_data.b;
}
avatar
Max Wu
Hello