คณิตศาสตร์เบื้องหลัง AI: เริ่มต้นง่ายๆ
คุณจะเขียนโปรแกรมเพื่อบวกเลขสองจำนวนที่น้อยกว่า 100 ได้อย่างไร? ง่ายใช่ไหม? โดยปกติแล้วคุณจะเขียนอะไรที่ตรงไปตรงมาแบบนี้:
#include <iostream>
using namespace std;
int main() {
int a, b, sum;
cout << "กรุณาใส่หมายเลขแรก: ";
cin >> a;
cout << "กรุณาใส่หมายเลขที่สอง: ";
cin >> b;
sum = a + b;
cout << "ผลรวมคือ: " << sum << endl;
return 0;
}
แต่เรามาลองเส้นทางที่น่าตื่นเต้นกว่านี้—มาสอนเครื่องให้เรียนรู้การบวกด้วยตัวเอง โดยใช้ neural network กันเถอะ Neural networks เป็นหัวใจของ AI สมัยใหม่ ขับเคลื่อนทุกอย่างตั้งแต่การรู้จำภาพและการแปลภาษาไปจนถึงระบบขั้นสูงอย่าง ChatGPT
ในบทความนี้ เราจะแสดงให้เห็นว่า neural network สามารถ "เรียนรู้" การบวกเลขสองจำนวนได้อย่างไรผ่านการฝึก ไม่ใช่โดยการทำตามกฎที่ตั้งไว้ คุณจะเห็นว่า neural network เรียนรู้ที่จะ "บวก" โดยการมองหาลวดลาย เราจะเปิดเผยตัวเลขจริง (เวทมนตร์เบื้องหลังม่าน) ที่ทำให้สิ่งนี้เกิดขึ้น
นี่คือภาพรวมโครงสร้างภายในของ neural network ที่ได้รับการฝึกแล้ว:
// น้ำหนักและอคติของชั้นซ่อน
double weight1_node1 = 0.658136;
double weight2_node1 = 0.840666;
double bias_node1 = -0.893218;
double weight1_node2 = -0.720667;
double weight2_node2 = -0.369172;
double bias_node2 = 0.036762;
double weight1_node3 = 0.512252;
double weight2_node3 = 0.292342;
double bias_node3 = -0.0745917;
// น้ำหนักและอคติของชั้นผลลัพธ์
double weight_out_node1 = 1.93108;
double weight_out_node2 = -0.718584;
double weight_out_node3 = 0.589741;
double bias_out = -0.467899;
เมื่อมองแวบแรก ค่าต่างๆ เหล่านี้อาจดูสุ่ม แต่พวกมันแทนสมองเทียมเล็กๆ ที่สามารถเรียนรู้วิธีการบวกเลขสองจำนวนได้
ใช้สูตรนี้ในการคำนวณผลลัพธ์ อย่ากังวลหากมันดูซับซ้อนในตอนนี้—แค่ตามไปเรื่อยๆ:
ที่นี่, x1
และ x2
คือสองหมายเลขที่คุณต้องการบวก (ปรับขนาดโดยการหารด้วย 100):
double x1 = 0.3; // 30 / 100
double x2 = 0.5; // 50 / 100
// ชั้นซ่อน
double z1 = x1 * weight1_node1 + x2 * weight2_node1 + bias_node1;
double h1 = sigmoid(z1);
double z2 = x1 * weight1_node2 + x2 * weight2_node2 + bias_node2;
double h2 = sigmoid(z2);
double z3 = x1 * weight1_node3 + x2 * weight2_node3 + bias_node3;
double h3 = sigmoid(z3);
// ชั้นผลลัพธ์
double sum = (h1 * weight_out_node1 + h2 * weight_out_node2 + h3 * weight_out_node3 + bias_out) * 200.0f;
มาดูว่าทำไมเราถึงคูณด้วย 200: เราได้ปรับขนาดข้อมูลนำเข้าและผลลัพธ์ให้พอดีกับช่วง 0 ถึง 1 เพื่อการฝึกที่ง่ายขึ้น เนื่องจากผลรวมสูงสุดของเลขสองจำนวนที่น้อยกว่า 100 คือ 200 เราจึงปรับขนาดผลลัพธ์กลับขึ้นโดยการคูณด้วย 200
ฟังก์ชัน Sigmoid:
double sigmoid(double x) {
return 1.0 / (1.0 + exp(-x));
}
มาลองใช้กับตัวเลขจริงกันเถอะ:
เราจะใช้ x1 = 30
และ x2 = 50
. เราจะปรับขนาดพวกมันก่อน:
x1 = 30 / 100 = 0.3
x2 = 50 / 100 = 0.5
ตอนนี้เรามาทำการคำนวณกัน:
z1 = (0.3 × 0.658136) + (0.5 × 0.840666) - 0.893218
≈ 0.1974408 + 0.420333 - 0.893218
≈ -0.2754442
h1 = sigmoid(z1) ≈ 0.431844
z2 = (0.3 × -0.720667) + (0.5 × -0.369172) + 0.036762
≈ -0.2162001 - 0.184586 + 0.036762
≈ -0.3640241
h2 = sigmoid(z2) ≈ 0.409872
z3 = (0.3 × 0.512252) + (0.5 × 0.292342) - 0.0745917
≈ 0.1536756 + 0.146171 - 0.0745917
≈ 0.2252549
h3 = sigmoid(z3) ≈ 0.556078
// ชั้นผลลัพธ์สุดท้าย
output = (h1 × 1.93108) + (h2 × -0.718584) + (h3 × 0.589741) - 0.467899
≈ (0.834389) + (-0.294320) + (0.328013) - 0.467899
≈ 0.400183
ผลรวมที่คาดการณ์ = 0.400183 × 200 ≈ 80.0366
// ทำไมถึงคูณด้วย 200? เพราะในระหว่างการฝึก เราได้ปรับขนาดข้อมูลนำเข้าและผลลัพธ์ทั้งหมดให้คงอยู่ระหว่าง 0 ถึง 1 เพื่อความเสถียรในการเรียนรู้ที่ดีกว่า เนื่องจากผลรวมสูงสุดของข้อมูลนำเข้าสองตัวคือ 100 + 100 = 200 เราจึงปรับขนาดผลลัพธ์ของเครือข่ายกลับขึ้นโดยการคูณด้วย 200.
Boom! ผลลัพธ์คือประมาณ 80 ซึ่งเป็นผลรวมที่ถูกต้องของ 30 และ 50.
ถ้าคุณไม่เชื่อว่าเครือข่ายเล็กๆ นี้สามารถบวกได้จริง ลองทดสอบอีกครั้งด้วยหมายเลขที่แตกต่างกัน: 20 และ 30.
เราจะปรับขนาดข้อมูลนำเข้าสำหรับหมายเลขเหล่านี้ก่อน:
x1 = 20 / 100 = 0.2
x2 = 30 / 100 = 0.3
จากนั้นเราจะทำการคำนวณ:
z1 = (0.2 × 0.658136) + (0.3 × 0.840666) - 0.893218
≈ 0.1316272 + 0.2521998 - 0.893218
≈ -0.509391
h1 = sigmoid(z1) ≈ 0.375241
z2 = (0.2 × -0.720667) + (0.3 × -0.369172) + 0.036762
≈ -0.1441334 - 0.1107516 + 0.036762
≈ -0.218123
h2 = sigmoid(z2) ≈ 0.445714
z3 = (0.2 × 0.512252) + (0.3 × 0.292342) - 0.0745917
≈ 0.1024504 + 0.0877026 - 0.0745917
≈ 0.1155613
h3 = sigmoid(z3) ≈ 0.528860
// ชั้นผลลัพธ์สุดท้าย
output = (h1 × 1.93108) + (h2 × -0.718584) + (h3 × 0.589741) - 0.467899
≈ (0.724688) + (-0.320095) + (0.311644) - 0.467899
≈ 0.248338
ผลรวมที่คาดการณ์ = 0.248338 × 200 ≈ 49.6676
มันคาดการณ์ประมาณ 49.67 ซึ่งใกล้เคียงกับผลรวมที่แท้จริง: 50!
เจ๋งใช่ไหม?
แล้ว... Neural Network คืออะไร?
บทนำอย่างรวดเร็ว: Neural network เป็นเหมือนเวอร์ชันทางคณิตศาสตร์ของสมองของคุณ มันประกอบด้วยหน่วยที่เรียกว่า neurons ที่ประมวลผลข้อมูลนำเข้าโดยการคูณด้วยน้ำหนัก เพิ่มอคติ และจากนั้นใช้ฟังก์ชันการกระตุ้น ฟังก์ชันการกระตุ้นที่ใช้กันทั่วไปคือ sigmoid:
sigmoid(x) = 1 / (1 + e^(-x))
นี่จะบีบข้อมูลนำเข้าสู่ค่าระหว่าง 0 และ 1 ช่วยให้เครือข่ายสามารถจับลวดลายที่ซับซ้อนได้
นี่คือกราฟของฟังก์ชัน sigmoid ที่แสดงให้เห็นว่าข้อมูลนำเข้าสามารถถูกแปลงเป็นค่าระหว่าง 0 และ 1 ได้อย่างราบรื่น:

ยังมีฟังก์ชันการกระตุ้นอื่นๆ เช่น ReLU (Rectified Linear Unit), tanh, และ softmax ซึ่งแต่ละตัวมีกรณีการใช้งานและพฤติกรรมของตัวเอง แต่ sigmoid เป็นจุดเริ่มต้นที่ดีเพราะมันเรียบง่ายและราบรื่น—เหมาะสำหรับ neural networks ขนาดเล็กอย่างของเรา
การตั้งค่า: สอนเครือข่ายของเราให้บวก
สำหรับการทดลองนี้ เราได้สร้างเครือข่ายขนาดเล็กที่มี:
- 2 input nodes (สำหรับสองหมายเลขที่จะบวก)
- 3 hidden nodes (ทำงานหนักด้วยเวทมนตร์ sigmoid)
- 1 output node (ให้ผลรวมเรา)
นี่คือแผนภาพที่แสดงโครงสร้างของ neural network ที่เรากำลังใช้เพื่อบวกเลขสองจำนวน คุณสามารถเห็นได้ว่าแต่ละ input node เชื่อมต่อกับ hidden layer nodes ทั้งหมด น้ำหนักและอคติถูกนำไปใช้ และทุกอย่างไหลไปยังผลลัพธ์สุดท้าย:

และใช่ เราได้ปรับขนาดทุกอย่าง เนื่องจากหมายเลขของเราอยู่ที่น้อยกว่า 100 เราจึงหารข้อมูลนำเข้าสำหรับ 100 และปรับขนาดผลลัพธ์กลับขึ้นโดยการคูณด้วย 200 ในตอนท้าย
นี่คือโค้ดที่เราจะเขียนเพื่อบวกเลขสองจำนวนโดยใช้ neural network:
#include <iostream>
#include <cmath>
using namespace std;
float sigmoid(float x) {
return 1.0f / (1.0f + exp(-x));
}
float sigmoid_derivative(float y) {
return y * (1 - y); // y = sigmoid(x)
}
int main() {
float weight1_node1 = 0.5f, weight2_node1 = 0.5f, bias_node1 = 0.0f;
float weight1_node2 = -0.5f, weight2_node2 = -0.5f, bias_node2 = 0.0f;
float weight1_node3 = 0.25f, weight2_node3 = -0.25f, bias_node3 = 0.0f;
float weight_out_node1 = 0.5f, weight_out_node2 = 0.5f, weight_out_node3 = 0.5f, bias_out = 0.0f;
float learning_rate = 0.01f;
int epochs = 1000;
for (int epoch = 0; epoch < epochs; ++epoch) {
float total_loss = 0.0f;
for (int a = 0; a < 100; ++a) {
for (int b = 0; b < 100; ++b) {
float x1 = a / 100.0f;
float x2 = b / 100.0f;
float target = (a + b) / 200.0f; // Normalize target to 0-1 range
// Forward pass for hidden layer
float z1 = x1 * weight1_node1 + x2 * weight2_node1 + bias_node1;
float h1 = sigmoid(z1);
float z2 = x1 * weight1_node2 + x2 * weight2_node2 + bias_node2;
float h2 = sigmoid(z2);
float z3 = x1 * weight1_node3 + x2 * weight2_node3 + bias_node3;
float h3 = sigmoid(z3);
// Output layer with linear activation
float y_pred = h1 * weight_out_node1 + h2 * weight_out_node2 + h3 * weight_out_node3 + bias_out;
float error = target - y_pred;
total_loss += error * error;
// Backward pass (output layer - linear)
float delta_out = error;
float grad_out_w1 = delta_out * h1;
float grad_out_w2 = delta_out * h2;
float grad_out_w3 = delta_out * h3;
float grad_out_b = delta_out;
// Backward pass (hidden layer)
float delta_h1 = delta_out * weight_out_node1 * sigmoid_derivative(h1);
float delta_h2 = delta_out * weight_out_node2 * sigmoid_derivative(h2);
float delta_h3 = delta_out * weight_out_node3 * sigmoid_derivative(h3);
float grad_w1_node1 = delta_h1 * x1;
float grad_w2_node1 = delta_h1 * x2;
float grad_b_node1 = delta_h1;
float grad_w1_node2 = delta_h2 * x1;
float grad_w2_node2 = delta_h2 * x2;
float grad_b_node2 = delta_h2;
float grad_w1_node3 = delta_h3 * x1;
float grad_w2_node3 = delta_h3 * x2;
float grad_b_node3 = delta_h3;
// Update weights
weight_out_node1 += learning_rate * grad_out_w1;
weight_out_node2 += learning_rate * grad_out_w2;
weight_out_node3 += learning_rate * grad_out_w3;
bias_out += learning_rate * grad_out_b;
weight1_node1 += learning_rate * grad_w1_node1;
weight2_node1 += learning_rate * grad_w2_node1;
bias_node1 += learning_rate * grad_b_node1;
weight1_node2 += learning_rate * grad_w1_node2;
weight2_node2 += learning_rate * grad_w2_node2;
bias_node2 += learning_rate * grad_b_node2;
weight1_node3 += learning_rate * grad_w1_node3;
weight2_node3 += learning_rate * grad_w2_node3;
bias_node3 += learning_rate * grad_b_node3;
}
}
if ((epoch + 1) % 100 == 0 || epoch == 0)
cout << "[Summary] Epoch " << epoch + 1 << ": Loss = " << total_loss << endl;
}
// ทดสอบโมเดล
float a, b;
cout << "\nการคาดการณ์ผลรวม (a + b)\nกรุณาใส่ a: ";
cin >> a;
cout << "กรุณาใส่ b: ";
cin >> b;
float x1 = a / 100.0f;
float x2 = b / 100.0f;
float h1 = sigmoid(x1 * weight1_node1 + x2 * weight2_node1 + bias_node1);
float h2 = sigmoid(x1 * weight1_node2 + x2 * weight2_node2 + bias_node2);
float h3 = sigmoid(x1 * weight1_node3 + x2 * weight2_node3 + bias_node3);
float y_pred = h1 * weight_out_node1 + h2 * weight_out_node2 + h3 * weight_out_node3 + bias_out;
float predicted_sum = y_pred * 200.0f;
cout << "w1_node1: " << weight1_node1 << ", w2_node1: " << weight2_node1
<< ", w1_node2: " << weight1_node2 << ", w2_node2: " << weight2_node2
<< ", w1_node3: " << weight1_node3 << ", w2_node3: " << weight2_node3
<< ", out_w1: " << weight_out_node1 << ", out_w2: " << weight_out_node2
<< ", out_w3: " << weight_out_node3 << ", bias_out: " << bias_out
<< ", bias_node1: " << bias_node1 << ", bias_node2: " << bias_node2 << ", bias_node3: " << bias_node3 << endl;
cout << "ผลรวมที่คาดการณ์: " << predicted_sum << "\nผลรวมที่แท้จริง: " << (a + b) << endl;
return 0;
}
แล้ว... มันเรียนรู้ได้อย่างไร?
นี่คือส่วนที่น่าตื่นเต้น: เราไม่ได้เขียนกฎสำหรับการบวก เราแค่ให้เครือข่ายมีคู่หมายเลขและผลรวมของพวกมัน และมันค่อยๆ ปรับน้ำหนักและอคติจนกว่าการคาดการณ์จะดีขึ้นเรื่อยๆ
มันก็เหมือนการลองผิดลองถูก แต่ฉลาดกว่า—พร้อมกับการคำนวณเล็กน้อย
มันเรียนรู้การบวกได้จริงหรือ?
เอ่อ ไม่เหมือนที่เราทำ มันไม่เข้าใจว่าคำว่า “บวก” หมายถึงอะไร แต่ มัน เรียนรู้การประมาณที่ดีมาก มันเห็นลวดลายในตัวเลขและคาดการณ์ผลรวมที่มักจะถูกต้อง
มันเหมือนกับการเรียนรู้จากการรู้สึกถึงลวดลายมากกว่าการรู้กฎ หากคุณสนใจ คุณสามารถเปลี่ยนหมายเลขนำเข้าและทดสอบมันได้ ยิ่งคุณเล่นมากเท่าไหร่ คุณก็จะเข้าใจว่าเครือข่ายเล็กๆ เหล่านี้ทำงานอย่างไร
สรุป
การสอน neural network ให้บวกเลขสองจำนวนอาจดูเหมือนเรื่องตลกในตอนแรก แต่เป็นวิธีที่ดีในการมองเข้าไปในจิตใจของ AI เบื้องหลังความฮือฮาทั้งหมด มันก็แค่มีน้ำหนัก อคติ และคณิตศาสตร์อันชาญฉลาด
ในส่วนถัดไป เราจะเจาะลึกลงไปในวิธีการทำงานของ neural networks: เราจะสำรวจเลเยอร์ ฟังก์ชันการกระตุ้น อนุพันธ์ และสิ่งที่เกิดขึ้นจริงในระหว่างการฝึก