---
# System prepended metadata

title: python課程筆記-10/17~11/14

---

# 10/17
## 科學計算

這周主要利用函數庫numpy進行科學計算的實作。

### 本利和計算

本利和公式:
$A$為本利和(amount)
$P$為本金(principal)
$r$為利率(interest rate)
$t$為期數(time)
$n$為一年計息幾次


$\begin{split}A\ &=&\ P\ (\ 1+\dfrac{r}{n})^{nt}\end{split}$




由此可寫出下列程式: (by chatgpt)

```python=

def calculate_compound_interest(principal, rate, time, interval):
    # 計算複利公式：A = P * (1 + r/n)^(n*t)
    # A = 最終本利和 
    # P = 本金
    # r = 年利率
    # t = 年數

    # 這邊的rate是百分比，需要轉換為小數
    rate = rate / 100

    # 根據使用者輸入的計息間隔來計算n值
    n = 1  # 預設每年計息一次
    if interval == "半年":
        n = 2
    elif interval == "季度":
        n = 4
    elif interval == "月份":
        n = 12
    elif interval == "每天":
        n = 365

    # 複利計算公式
    amount = principal * (1 + rate / n) ** (n * time)
    return amount
    
# 假設本金為1000，年利率為5%，時間為5年，計息間隔為每天
principal = 1000
rate = 5
time = 5
interval = "每天"

result = calculate_compound_interest(principal, rate, time, interval)
print(f"複利計算結果為：{result}")
```
可以得到以下結果:
```python
複利計算結果為：1284.003432146984
```

但如果我們讓他用迴圈的方式計算，並在每一次計算後進行四捨五入:
```python=
def calculate_compound_interest(principal, rate, time, interval):
    # 計算複利公式：A = P * (1 + r/n)^(n*t)
    # A = 最終本利和
    # P = 本金
    # r = 年利率
    # t = 年數

    # 這邊的rate是百分比，需要轉換為小數
    rate = rate / 100

    # 根據使用者輸入的計息間隔來計算n值
    n = 1  # 預設每年計息一次
    if interval == "半年":
        n = 2
    elif interval == "季度":
        n = 4
    elif interval == "月份":
        n = 12
    elif interval == "每天":
        n = 365

    # 計算複利
    amount = principal
    for i in range(time * n):
        amount *= (1 + rate / n)
        amount = round(amount)  # 四捨五入到個位數

    return amount
# 假設本金為1000，年利率為5%，時間為5年，計息間隔為每天
principal = 1000
rate = 5
time = 5
interval = "每天"

result = calculate_compound_interest(principal, rate, time, interval)
print(f"複利計算結果為：{result}")
```
可以得到下面結果:
```python
複利計算結果為：1000
```

兩種計算方法的不同，卻差了284.0034左右，所以四拾五入會嚴重影響結果。

我們也可以透過程式來模擬這件事，來觀察在不同情況下，四拾五入對兩種算法的差值。
### quiz5 第二題
```python
from numpy.ma.core import round_
import matplotlib.pyplot as plt



# 計算一年直接複利一次的本利和（不四捨五入）
def simple_interest(principal, rate):
    amount = principal * (1 + rate)
    return amount

# 計算差額並儲存結果
differences = []
principals = list(range(1, 200001))

for principal in principals:
    compound_amount = principal
    for i in range(365):
        compound_amount=round(compound_amount*(1+(0.03/365)))
    simple_amount = simple_interest(principal, 0.03)
    difference = compound_amount - simple_amount
    differences.append(difference)

# 繪製差額的變化圖
plt.figure(figsize=(12, 6))
plt.plot(principals, differences, color='blue')
plt.xlabel('本金')
plt.ylabel('差額')
plt.title('每天複利後四捨五入到整數與一年直接複利一次之間的差額')
plt.grid(True)
plt.show()

# 找出最大和最小的差額及其對應的本金
max_difference = max(differences)
min_difference = min(differences)
max_principal = principals[differences.index(max_difference)]
min_principal = principals[differences.index(min_difference)]

print(f"每天複利最大差額：{max_difference}，對應本金：{max_principal}")
print(f"每天複利最小差額：{min_difference}，對應本金：{min_principal}")
```
會跑出下面的圖形:
![image](https://hackmd.io/_uploads/HJ3LR7cN6.png)
可見兩者的差值關係。
# 10/24
## 網路爬蟲
主要利用requests和bs4進行爬蟲，在chatgpt的問答中也有用另一個函數庫叫selenium來寫的。
request的功能是發送http請求，而bs4則是http的解析工具；
以學校官網https://www.ncku.edu.tw/為目標:
### 首先下載函數庫:
```python=
pip install requests
pip install beautifulsoup4
```
### 發送http請求:
我們向學校官網利用request庫，發送get請求，試圖取得網頁內容:
```python=
import requests

url = "https://www.ncku.edu.tw/"
response=requests.get(url)
```

### 解析回傳:
確認請求成功後，利用beatifulsoup解析網頁文本，以取得想要的數據或資訊，如文字或圖片:
```python=
from bs4 import BeautifulSoup

if response.status_code == 200: #確認請求成功被處理
    soup = BeautifulSoup(response.text, 'html.parser')
```
### 提取數據:
這一步需要我們提早在網頁中，找到我們想要的資源的tag是甚麼，
再透過這些tag在爬蟲中定位我們所需要的資料。
以下為部分程式節錄:
```python=
target_mouter = soup.find_all('div',class_="mouter")

    for element in target_mouter:
      t=element.find('h2',class_='mt-title')
      if t and t.text=='成大快訊':
        print(t.text)
        target_elements = element.find_all('div',class_='d-item v-it col-sm-3')
        for target_element in target_elements:
          print(target_element.text.strip())
```
# 10/31
## 影像處理
主要利用PIL(Python Imaging Library)函數庫中的Image, ImageEnhance模組，
及io中的BytesIO模組對影像進行操作。
Image包含可以開啟、保存、縮放和旋轉圖片等基本功能。
ImageEnhance則提供對圖片進行操作增強圖片色彩、對比度等屬性的方法。
BytesIO則可以處理二進位檔案如圖片畫素等。
### 圖片下載:
這邊示範利用網路連結找到圖片並進行操作，但也可以進行本地圖片修改。
下載之後，圖片會被存在image中。
```python=
from PIL import Image, ImageEnhance
from io import BytesIO
import matplotlib.pyplot as plt
import requests

def download_image(url):
    response = requests.get(url)
    image = Image.open(BytesIO(response.content))
    return image
```
### 合併圖片
將兩張圖片合併顯示，也可以更改兩張圖片如何擺放，這邊是橫放。
```python=
def concatenate_images(image1, image2):
    total_width = image1.width + image2.width
    max_height = max(image1.height, image2.height)

    new_image = Image.new('RGB', (total_width, max_height))
    new_image.paste(image1, (0, 0))
    new_image.paste(image2, (image1.width, 0))
    return new_image
```
### 灰階並列
修改圖片模式從彩色改成灰度模式，即可將彩色圖片改為灰階。
```python=
image_url = "https://web.ncku.edu.tw/var/file/0/1000/img/982196591.jpg"

image = download_image(image_url)
gray_image=image.convert('L')
new_image=concatenate_images(image,gray_image)
plt.imshow(new_image)
plt.axis('off')
plt.show()
```
這是輸出的結果:
![image](https://hackmd.io/_uploads/HktlDE54T.png)
### 負片效果
可以透過取反每一個像素的RGB值實現負片效果。
```python=
image_url = "https://web.ncku.edu.tw/var/file/0/1000/img/982196591.jpg"

image = download_image(image_url)
# 取得圖片的大小
width, height = image.size
revimage = Image.new('RGB',(width,height))
# 遍歷每個像素點，取反 RGB 值
for x in range(width):
    for y in range(height):
        r, g, b = image.getpixel((x, y))
        new_r = 255 - r
        new_g = 255 - g
        new_b = 255 - b
        revimage.putpixel((x, y), (new_r, new_g, new_b))
new_image=concatenate_images(image,revimage)
plt.imshow(new_image)
plt.axis('off')
plt.show()      
```
這是輸出:
![image](https://hackmd.io/_uploads/H1QiO45Ep.png)

還有其他很多效果就不多贅述，如RGB濾光、馬賽克等。
# 11/7
## python遊戲設計
這周是利用python做一些簡單的小遊戲設計。
### 踩地雷
```python=
import random

def create_board(size, mines):
    # 創建一個空白的遊戲版面
    board = [[' ' for _ in range(size)] for _ in range(size)]
    
    # 隨機放置地雷
    for _ in range(mines):
        x, y = random.randint(0, size - 1), random.randint(0, size - 1)
        while board[x][y] == '*':
            x, y = random.randint(0, size - 1), random.randint(0, size - 1)
        board[x][y] = '*'
    
    return board

def print_board(board):
    # 印出遊戲版面
    size = len(board)
    for i in range(size):
        print(' '.join(board[i]))

def count_mines(board, x, y):
    # 計算周圍地雷數量
    count = 0
    size = len(board)
    for i in range(max(0, x - 1), min(size, x + 2)):
        for j in range(max(0, y - 1), min(size, y + 2)):
            if board[i][j] == '*':
                count += 1
    return count

def play_game(size=5, mines=5):
    # 創建遊戲版面
    board = create_board(size, mines)
    
    while True:
        print_board(board)
        x, y = map(int, input("請輸入坐標（範圍 0 到 {}）：".format(size - 1)).split())
        
        # 檢查是否踩到地雷
        if board[x][y] == '*':
            print("踩到地雷了！遊戲結束！")
            break
        
        # 計算周圍地雷數量並顯示
        mines_count = count_mines(board, x, y)
        board[x][y] = str(mines_count)
        
        # 檢查遊戲是否獲勝
        if sum(row.count(' ') for row in board) == mines:
            print_board(board)
            print("恭喜你，你找到了所有的安全區域！遊戲結束！")
            break

# 遊戲開始
play_game()
```

### 井字遊戲
```python=
def print_board(board):
    for row in board:
        print(" | ".join(row))
        print("-" * 9)

def check_winner(board, player):
    for row in board:
        if all(cell == player for cell in row):
            return True
    for col in range(3):
        if all(board[row][col] == player for row in range(3)):
            return True
    if all(board[i][i] == player for i in range(3)) or all(board[i][2 - i] == player for i in range(3)):
        return True
    return False

def play_game():
    board = [[" " for _ in range(3)] for _ in range(3)]
    players = ["X", "O"]
    turn = 0

    while True:
        print_board(board)
        player = players[turn % 2]
        print(f"輪到玩家 {player} 下棋")
        row, col = map(int, input("請輸入座標（範圍 0 到 2），例如 1 2：").split())

        if board[row][col] != " ":
            print("此位置已被佔據，請選擇其他位置！")
            continue

        board[row][col] = player

        if check_winner(board, player):
            print_board(board)
            print(f"恭喜玩家 {player} 贏得了遊戲！")
            break

        if all(all(cell != " " for cell in row) for row in board):
            print_board(board)
            print("遊戲結束，平局！")
            break

        turn += 1

# 遊戲開始
play_game()
```
### 2A1B
```python=
import random

def generate_number():
    digits = list(range(10))
    random.shuffle(digits)
    return digits[:4]

def check_guess(secret, guess):
    A, B = 0, 0
    for i in range(4):
        if guess[i] == secret[i]:
            A += 1
        elif guess[i] in secret:
            B += 1
    return A, B

def play_game():
    secret_number = generate_number()
    attempts = 0

    print("歡迎來到2A1B遊戲！")
    print("請猜測由0到9組成的四位數字，每個數字不重複。")
    print("猜測後會得到幾個A和幾個B的提示。")

    while True:
        guess = input("請輸入你的猜測（四位數字，不重複）：")
        if len(guess) != 4 or not guess.isdigit() or len(set(guess)) != 4:
            print("請輸入正確格式的四位數字，每個數字不重複！")
            continue

        guess = [int(num) for num in guess]
        attempts += 1

        A, B = check_guess(secret_number, guess)
        print(f"你猜得有 {A} 個A和 {B} 個B。")

        if A == 4:
            print(f"恭喜你猜對了！答案是 {''.join(map(str, secret_number))}，你總共猜了 {attempts} 次。")
            break

# 遊戲開始
play_game()
```
# 11/14
## 類神經網路
[類神經網路的可視化模型](https://playground.tensorflow.org/#activation=tanh&batchSize=10&dataset=circle&regDataset=reg-plane&learningRate=0.03&regularizationRate=0&noise=0&networkShape=4,2&seed=0.38938&showTestData=false&discretize=false&percTrainData=50&x=true&y=true&xTimesY=false&xSquared=false&ySquared=false&cosX=false&sinX=false&cosY=false&sinY=false&collectStats=false&problem=classification&initZero=false&hideText=false)
可以先看看上面這個東西。
這周是利用python進行類神經網路的實作，雖然我聽得霧煞煞@@
### 什麼是類神經網路
 類神經網路（Neural Network，NN）是一種模仿人腦神經元結構和運作方式的數學模型。它由大量的人工神經元（也稱為節點或單元）所組成，這些神經元以不同的層次連接在一起。
 
類神經網路（Neural Network，NN）主要由以下幾個基本組件組成：

**神經元（Neuron）**： 也稱為節點或單元，是神經網路的基本單位。每個神經元接收多個輸入，通過權重加權，然後通過激活函數生成一個輸出。

**權重（Weight）**： 連接兩個神經元的連接權重表示了兩者之間的影響程度。在訓練過程中，神經網路學習調整這些權重以提高預測的準確性。

**激活函數（Activation Function）**： 每個神經元的輸出通過激活函數進行轉換，這使得神經網路能夠學習非線性的映射。常見的激活函數包括 Sigmoid、ReLU（Rectified Linear Unit）、Tanh 等。

**層（Layer）**： 神經網路由多個層次組成，包括輸入層、隱藏層和輸出層。每個層次包含多個神經元，並且神經元之間的連接形成一個網狀結構。

**隱藏層（Hidden Layer）**： 在輸入層和輸出層之間的層次被稱為隱藏層。多個隱藏層的神經網路被稱為深度神經網路（Deep Neural Network，DNN）。

**輸入層（Input Layer）**： 接收外部輸入的數據，每個輸入特徵通常對應到輸入層的一個神經元。

**輸出層（Output Layer）**： 生成最終的預測或結果。輸出層的神經元數量通常取決於任務的性質，例如分類問題的輸出層可以有多個神經元，每個神經元代表一個類別。

![image](https://hackmd.io/_uploads/r17CVB9Va.png)

**損失函數（Loss Function）**： 用於衡量模型預測結果與實際結果之間的差異。訓練過程中的目標是最小化損失函數的值。

**優化器（Optimizer）**： 在訓練過程中使用的算法，根據損失函數的梯度來更新神經網路的權重，以提高模型的性能。

這些組件共同構成了一個完整的神經網路，它能夠通過學習從輸入數據中提取特徵，並生成相應的輸出。深度學習模型通常包含多個層和大量的參數，使得它們能夠進行高度複雜的模式識別和預測任務。
這些神經元之間的連接有著不同的權重，每個神經元通常與下一層的每個神經元都有連接。當資訊通過神經網路流動時，每個神經元將輸入的值乘以它的權重並通過激活函數，產生一個輸出值。
訓練神經網路的目標是調整神經元之間的連接權重，使得網路能夠根據輸入資料進行準確的預測或分類。這通常透過反向傳播（Backpropagation）算法來實現，該算法根據預測結果和實際結果之間的差異，逐步調整神經元之間的權重。
類神經網路在許多領域都有廣泛的應用，包括圖像識別、語音識別、自然語言處理、遊戲、金融、醫療等。其靈活性和潛在的強大能力使得它成為現代機器學習和人工智慧的核心技術之一。
~by chatgpt

### 節點如何下決策
對於一個節點而言，會從前一層的其他節點獲得輸入，而每一個節點都會擁有一個**權重(weight)**，這些輸入與權重的內積再與此節點的**偏置(bias)** 共同組成此節點的**激活函數(Activation Function)**，決定此節點的輸出為何。
![image](https://hackmd.io/_uploads/r1ydBSc4T.png)
### 訓練的過程
類神經網路會透過訓練來試圖最佳化結果，而每一次對資料的完整運算就被稱為一個**epoch** ，過程中會透過**損失函數(loss function)** 來評估訓練的狀況，修改節點的權重與偏置，最終達成訓練目標。
### python實作
```python=
import matplotlib.pyplot as plt
import numpy as np

def plot_predictions(X_train, y_train, X_test, y_test, model):
    # 使用模型對訓練集進行預測
    y_pred_train_continuous = model.predict(X_train)
    y_pred_train = np.where(y_pred_train_continuous.flatten() >= 0, 1, -1)

    # 使用模型對測試集進行預測
    y_pred_test_continuous = model.predict(X_test)
    y_pred_test = np.where(y_pred_test_continuous.flatten() >= 0, 1, -1)
    plt.figure(figsize=(8, 8))

    # 訓練集 - 預測正確
    correct_train = (y_train == y_pred_train)
    oc = (y_train == 1) & correct_train
    xc = (y_train == -1) & correct_train
    plt.scatter(X_train[oc, 0], X_train[oc, 1], c='blue', marker='o', label='Train Correct (Positive)')
    plt.scatter(X_train[xc, 0], X_train[xc, 1], c='orange', marker='o', label='Train Correct (Negative)')

    # 訓練集 - 預測錯誤
    incorrect_train = (y_train != y_pred_train)
    oi = (y_train == 1) & incorrect_train
    xi = (y_train == -1) & incorrect_train
    plt.scatter(X_train[oi, 0], X_train[oi, 1], c='blue', marker='x', label='Train Incorrect (Positive)')
    plt.scatter(X_train[xi, 0], X_train[xi, 1], c='orange', marker='x', label='Train Incorrect (Negative)')

    # 測試集 - 預測正確
    correct_test = (y_test == y_pred_test)
    oc = (y_test == 1) & correct_test
    xc = (y_test == -1) & correct_test
    plt.scatter(X_test[oc, 0], X_test[oc, 1], c='cyan', marker='o', label='Test Correct (Positive)')
    plt.scatter(X_test[xc, 0], X_test[xc, 1], c='pink', marker='o', label='Test Correct (Negative)')

    # 測試集 - 預測錯誤
    incorrect_test = (y_test != y_pred_test)
    oi = (y_test == 1) & incorrect_test
    xi = (y_test == -1) & incorrect_test
    plt.scatter(X_test[oi, 0], X_test[oi, 1], c='cyan', marker='x', label='Test Incorrect (Positive)')
    plt.scatter(X_test[xi, 0], X_test[xi, 1], c='pink', marker='x', label='Test Incorrect (Negative)')

    plt.xlabel('X Coordinate')
    plt.ylabel('Y Coordinate')
    plt.title('Model Predictions')
    plt.legend()
    plt.grid(True)
    plt.show()
# 整合並標記數據點
X = np.vstack((np.column_stack((x_o, y_o)), np.column_stack((x_x, y_x))))
y = np.array([1] * N + [-1] * N)  # 1代表o，-1代表x

# 分割數據集, 把 N 中的 20% 做為測試
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 創建神經網路模型
N1 = 3
model = Sequential()
model.add(Dense(N1, input_dim=2, activation='tanh'))  # 第一層有N1個神經元，使用tanh激活函數
model.add(Dense(2, input_dim=2, activation='tanh'))  # 第二層有2個神經元
model.add(Dense(1, activation='tanh'))  # 第三層有1個神經元

# 編譯模型
model.compile(loss='mean_squared_error', optimizer='adam', metrics=['accuracy'])

# 訓練模型並記錄訓練過程
history = model.fit(X_train, y_train, epochs=1000, batch_size=5, verbose=0, validation_data=(X_test, y_test))

# 獲取損失函數值
loss_values = history.history['loss']

# 繪製損失函數圖
plt.figure(figsize=(8, 4))
plt.plot(loss_values, label='Training Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Loss Function During Training')
plt.legend()
plt.grid(True)
plt.show()

# 获取训练和验证集准确度
# train_acc = history.history['accuracy']
# val_acc = history.history['val_accuracy']

# 绘制准确度图
#plt.figure(figsize=(8, 4))
#plt.plot(train_acc, label='Training Accuracy')
#plt.plot(val_acc, label='Validation Accuracy')
#plt.xlabel('Epoch')
#plt.ylabel('Accuracy')
#plt.title('Model Accuracy during Training')
#plt.legend()
#plt.grid(True)
#plt.show()


plot_predictions(X_train, y_train, X_test, y_test, model)


# 獲取第一層的權重和偏置
weights, biases = model.layers[0].get_weights()

# 生成更多隨機點進行預測
x_a, y_a = generate_circle_points(R0, R3, Np)
additional_X = np.column_stack((x_a, y_a))
y_pred_additional = model.predict(additional_X)
y_pred_additional = np.where(y_pred_additional.flatten() >= 0, 1, -1)

# 繪製測試數據和預測結果
plt.figure(figsize=(8, 8))
plt.scatter(x_o, y_o, c='blue', marker='o', label='Class Positive')
plt.scatter(x_x, y_x, c='orange', marker='o', label='Class Negative')
plt.scatter(additional_X[y_pred_additional == 1, 0], additional_X[y_pred_additional == 1, 1], c='blue', marker='o', label='Predicted Positive')
plt.scatter(additional_X[y_pred_additional == -1, 0], additional_X[y_pred_additional == -1, 1], c='orange', marker='o', label='Predicted Negative')

# 繪製第一層的N1條直線
x_values = np.linspace(-2, 2, 100)
y_values = np.linspace(-2, 2, 100)
for i in range(N1):
    a, b = weights[:, i]
    c = biases[i]
    if abs(a) > abs(b):
        x_line = (-b * y_values - c) / a
        plt.plot(x_line, y_values, linestyle='--', label=f'Neuron {i+1} Boundary')
    else:
        y_line = (-a * x_values - c) / b
        plt.plot(x_values, y_line, linestyle='--', label=f'Neuron {i+1} Boundary')

plt.xlabel('X')
plt.ylabel('Y')
plt.title('Additional Points Classification with Neuron Boundaries')
plt.legend()
plt.grid(True)
plt.axis('equal')
plt.show()
```
輸出:
損失函數隨時間下降:
![image](https://hackmd.io/_uploads/HJcSqrqEa.png)
預先生成的資料在經過1000個epoch後的NN的分類狀況:
![image](https://hackmd.io/_uploads/rk189Hc4T.png)
再生成額外資料給訓練完的NN判斷的情況:
![image](https://hackmd.io/_uploads/ryELcS5Vp.png)
