Contents

CNN Image Recognition Experiment

The cover image was generated by ChatGPT.

Tip
This project was written in August 2023. The following content primarily documents my initial experience learning and experimenting with deep learning at that time. As such, some explanations and viewpoints have been preserved to reflect my understanding during that period.

The year 2023 marked the rapid rise of generative artificial intelligence. From large language models to a wide range of AI-powered applications, artificial intelligence gradually became part of everyday life, making deep learning one of the most prominent and widely discussed technologies of the year.

For me, 2023 also marked the beginning of my journey into the world of deep learning, as I built and experimented with my first deep learning models.

Deep Learning and Neural Networks

Before starting the implementation, I spent some time learning the fundamental concepts of deep learning.

Deep Learning is a branch of Machine Learning, with its foundation built upon Artificial Neural Networks (ANNs). Through the computation of large amounts of data and multiple layers of neurons, models can gradually learn features within the data and perform tasks such as classification, recognition, and prediction.

At that time, I was still unfamiliar with these concepts. Therefore, I began by learning the basic architecture of neural networks, understanding how information is transmitted between the input layer, hidden layers, and output layer through weights and activation functions.

https://upload.wikimedia.org/wikipedia/commons/0/00/Multi-Layer_Neural_Network-Vector-Blank.svg
A simple neural network example, retrieved from Wikipedia.

Looking back today, these concepts may seem like fundamental introductory knowledge in deep learning. However, at that time, they represented an important first step in my journey into the field of artificial intelligence.

Convolutional Neural Networks

After understanding the fundamentals of neural networks, I began exploring Convolutional Neural Networks (CNNs). CNNs are among the most representative models currently used in image recognition. They mainly extract features from images through two key operations: convolution and pooling, and then pass the extracted features to subsequent neural network layers for classification.

At that time, my understanding of convolution was roughly that an image is scanned step by step using a fixed-size window, and a set of convolutional kernels is applied to perform calculations in order to obtain feature information from different locations. Pooling, on the other hand, preserves the most representative information within each window, such as the maximum value, to reduce the data dimensionality while lowering the computational cost of subsequent models. Although I did not deeply study the mathematical derivations at that stage, through reading tutorials and watching animated visualizations, I was able to understand the general workflow of CNNs.

For a more in-depth explanation of the principles behind convolutional neural networks, you can refer to How do Convolutional Neural Networks work?.

Implementation and Experimentation

After understanding the basic concepts, I began attempting to write my first deep learning program. Looking back at the basic C++ syntax I learned in high school and the Python programming experience I gained during university, programming itself was not unfamiliar to me. However, when I actually started using deep learning frameworks, I still needed to adapt to many new concepts, such as data preprocessing, model construction, training procedures, and parameter tuning.

The implementation and learning process mainly followed the playlist Deep Learning - Neural Networks published by 容噗玩Data. Through this series, I gradually learned deep learning concepts starting from the fundamentals of neural networks.

In addition to introducing the basic concepts of neural networks, the course also covered topics including data processing, DNN, CNN, RNN, and LSTM. For someone who was just beginning to explore deep learning, it was a comprehensive and easy-to-understand learning resource.

As the course progressed, I followed the video tutorials to complete several examples and attempted to incorporate my own modifications and adjustments. Looking back at these projects now, the program structure was still quite simple. However, for me at that time, it represented the first time I had successfully completed a deep learning project capable of training a model.

The following are the source codes I kept from that period, which were implemented based on the tutorials and modified to some extent.

  • For model training:
  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
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
### import module
import os
os.environ['TF_ENABLE_ONEDNN_OPTS'] = '0'
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
import datetime
import numpy as np
from keras.api import Input
from keras.api.layers import MaxPooling2D, Conv2D, Dense, Dropout, Flatten
from keras.api.models import Sequential
from keras.api.utils import plot_model
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import train_test_split
from sklearn.utils import shuffle
from tqdm import tqdm
import cv2
import matplotlib.pyplot as plt
import seaborn as sn
sn.set_theme(font_scale=1.1, font='MingLiU')
plt.rcParams['axes.unicode_minus'] = False
import json

### function
# first setting
def config():
    if not os.path.exists('config'):
        print('\033[93mCreating \"config\" folder...\033[0m')
        os.mkdir('config')
        readme_content = [
            r'1. 使用 python 於 cnn_model.py 進行模型訓練前,請先於終端機運行 "pip install -r config\pip_install_need.txt" 安裝所需模組,並同時於 https://graphviz.gitlab.io/download/ 下載安裝所需套件,以避免程式執行時報錯。',
            r'2. 要訓練的圖片請預先分類在不同資料夾,並將資料夾名稱設為類別名稱,如 "人類" 資料夾中含有各種人類的圖片。',
            r'3. 訓練資料夾預設為 "config\train_folder" ,如果訓練資料夾中未含有檔案或任意訓練資料夾的子資料夾未含有檔案,則會報錯。'
                          ]
        pip_install_need_content = ['keras', 'tensorflow', 'tqdm', 'pydot', 'opencv-python', 'matplotlib', 'scikit-learn', 'numpy', 'seaborn', 'json']

        print('\033[93mCreating \"config\\Readme.md\" file...\033[0m')
        with open('config\\Readme.md', 'w', encoding = 'utf-8') as readme_file:
            for item in readme_content:
                readme_file.write(f'{item}\n')
        with open('config\\pip_install_need.txt', 'w', encoding = 'utf-8') as pip_file:
            for item in pip_install_need_content:
                pip_file.write(f'{item}\n')
        print('\033[93mDone.\033[0m')
        print(f'\033[93mIf this is your first time using this program, please read the Readme.md file located in \"{os.path.join(os.getcwd(), 'config\\Readme.md')}\".\033[0m')

# input path
def input_train_folder_path():
    if not os.path.exists('config\\train_folder'):
        os.mkdir('config\\train_folder')
    while True:
        folder_path = input(f'\033[93mPlease enter the trained folder path or enter \"1\" to use the defult folder \"config\\train_folder\":\033[0m ')
        if folder_path == '1':
            return 'config\\train_folder'
        elif os.path.exists(folder_path):
            return folder_path
        print('TypeError: invalid folder path or the folder does not exist. Please enter a valid folder path.')

# input Training-to-Testing split ratio
def train_test_ratio():
    while True:
        rate = input(f'\033[93mPlease enter the Training-to-Testing split ratio:\033[0m ')
        try:
            rate = float(rate)
            if 0 < rate < 1:
                print(f'The Training-to-Testing split ratio has set from train * {rate} and test * {1 - rate}.')
                return rate
            else:
                print('ValueError: Training-to-Testing split ratio must be a float between 0 and 1.')
        except ValueError:
            print('ValueError: Training-to-Testing split ratio must be a float point number.')

# load data from folder and data clean
def load_data(train_folder, image_size, train_rate, class_names_label):
    output, images, labels = [], [], []
    for folder in os.listdir(train_folder): # process from each child-folder
        label = class_names_label[folder]
        print(f'\033[93mLoading {folder}\033[0m')
        for file in tqdm(os.listdir(os.path.join(train_folder, folder))): # process from each image in each child-folder
            img_path = os.path.join(os.path.join(train_folder, folder), file) # get the path from each image
            image = cv2.imdecode(np.fromfile(img_path, dtype = np.uint8), -1) # get the path by using np.uint8 (for encoding utf-8)
            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # change color format from BGR to RGB
            image = cv2.resize(image, image_size)  # change image size to image_size
            images.append(image)  # append image to array
            labels.append(label)  # append folder name to label

    images = np.array(images, dtype = 'float32') # change images to numpy array, and the tpye 'float32'
    labels = np.array(labels, dtype = 'int32') # change labels to numpy array, and the tpye 'int32'

    train_images, test_images, train_labels, test_labels = train_test_split(images, labels, train_size = train_rate)
    output.extend([(train_images, train_labels), (test_images, test_labels)])
    return output

# save picture
def pic_save(pic_path, current_datetime): # use date time to save picture
    filename = pic_path + '_' + current_datetime + '.png'
    plt.savefig(filename)
    return filename

# calculate accuracy
def accuracy(confusion_matrix):
    diagonal_sum = confusion_matrix.trace()
    sum_of_all_elements = confusion_matrix.sum()
    return diagonal_sum / sum_of_all_elements

### main program
config()

train_folder = input_train_folder_path()
train_rate = train_test_ratio()

start_time = datetime.datetime.now().replace(microsecond = 0)
print(f'\033[93mLoading images from \"{train_folder}\".\033[0m')
class_names = os.listdir(train_folder) # classify by train_folder's name
class_names_label = {class_name: i for i, class_name in enumerate(class_names)} # get the label by class_names
classes_amount = len(class_names)  # check how many classes we need to train
image_size = (128, 128)  # set image size
(train_images, train_labels), (test_images, test_labels) = load_data( # deta clean
    train_folder = train_folder,
      image_size = image_size,
      train_rate = train_rate,
      class_names_label = class_names_label)

print(f'\033[93mUsing {datetime.datetime.now().replace(microsecond = 0) - start_time} to load the images.\033[0m')

# Iteration × Batch size = Epoch
while True:
    try:
        epochs = int(input('\033[93mPlease enter the training times (epochs):\033[0m '))
        if epochs <= 0:
            print('ValueError: the epoches must be a positive integer.')
        else:
            break
    except ValueError:
        print('ValueError: the epoches must be a positive integer.')

start_time = datetime.datetime.now().replace(microsecond = 0)
train_images, train_labels = shuffle(train_images, train_labels) # random the train set
# standardized
train_images = train_images / 255.0 # RGB have 255 levels
test_images = test_images / 255.0

# CNN modeling
input_shape = (image_size[0], image_size[1], 3)  # image size and the hight 3 (since the RGB size is 3)

model = Sequential([
    # 輸入
    Input(shape = input_shape),
    # 卷積層 # 特徵擷取
    # 64, (3, 3) >> 神經元數目: 64, 掃描器大小: 3*3; padding: 掃描後與原圖做比較, activation: 激活函數, strides: 掃描頻率
    Conv2D(64, (3, 3), padding = 'same', activation = 'relu', strides = 2),
    # 池化層 # 特徵壓縮
    # pool_size = (2, 2): 壓縮大小 2*2, strides: 壓縮頻率
    MaxPooling2D(pool_size = (2, 2), strides = 2),
    # 隨機丟棄部份資料
    Dropout(0.2),

    # 再次運算 >> 速度變慢,準確度提高
    Conv2D(128, (3, 3), padding = 'same', activation = 'relu', strides = 2),
    MaxPooling2D(pool_size = (2, 2), strides = 2),
    Dropout(0.2),

    # 攤平圖片資訊
    Flatten(),
    Dropout(0.5),

    # 輸出層,分類用softmax
    Dense(classes_amount, activation = 'softmax')
])

# 模型訓練方法
model.compile(optimizer = 'adam',  # 最佳化演算法: adam
              loss = 'sparse_categorical_crossentropy',  # 損失函數: 混淆矩陣
              metrics = ['accuracy'])  # 準確度

history = model.fit(train_images,  # 訓練數據(特徵)
                    train_labels,  # 對應的目標(標籤)
                    # validation_data:驗證數據(特徵和對應的目標),用於在訓練過程中評估模型的性能
                    validation_data = (test_images, test_labels),
                    # verbose: 控制訓練過程中的輸出詳細程度。可以設定為 0(靜默)、1(進度條輸出)、2(每個 epoch 一行輸出)
                    verbose = 1,
                    # callbacks = [earlyStop],
                    batch_size = 256,  # batch_size: 訓練中每個批次的樣本數量
                    epochs = epochs,  # epochs: 訓練的迭代次數
                    shuffle = True)  # shuffle: 是否在每個 epoch 開始時將訓練數據打亂

end_time = datetime.datetime.now().replace(microsecond = 0) - start_time
print(f'\033[93mTraing cost {end_time}.\033[0m')

# output config to json file
config_data = {
    "class_names": class_names,
    "image_size": image_size
}
with open('config\\config.json', 'w', encoding = 'utf-8') as config_file:
    json.dump(obj = config_data, fp = config_file, ensure_ascii = False, indent = 4)

# save model
Saved_model = 'config\\Saved_model.keras'
model.save(Saved_model)
print(f'\033[93mModel had been save at \"{Saved_model}\".\033[0m')

# current date and time (for saving results)
current_datetime = datetime.datetime.now().strftime("%Y-%m-%dT%H_%M_%S")

# loss
if not os.path.exists('config\\result'):
    os.mkdir('config\\result')
plt.figure()
plt.title(f'Train Loss \n epochs: {epochs}, time: {end_time}')
plt.ylabel('loss')
plt.xlabel('Epoch')
plt.plot(history.history['loss'])
save_path = pic_save(pic_path = 'config\\result\\loss', current_datetime = current_datetime)
print(f'\033[93mLoss function had been save at \"{save_path}\".\033[0m')

# test set (for calculate accuracy)
predictions = model.predict(test_images)
pred_labels = np.argmax(predictions, axis = 1)

# confusion matrix
CM = confusion_matrix(test_labels, pred_labels)

# accuracy
print(f'\033[93mAccuracy: {accuracy(CM)}.\033[0m')

# 混淆矩陣視覺化
plt.figure()
ax = plt.axes()
sn.heatmap(data = CM,
           annot = True,
           annot_kws = {'size': 10},
           xticklabels = class_names,
           yticklabels = class_names,
           ax = ax,
           fmt = '.20g')
ax.set_title(f'Confusion Matrix \n epochs: {epochs}, accuracy: {accuracy(CM):.3f}, time: {end_time}')
save_path = pic_save(pic_path = 'config\\result\\confusion_matrix', current_datetime = current_datetime)
print(f'\033[93mConfusion matrix had been save at \"{save_path}\".\033[0m')

# model's image (need to install Graphviz by https://graphviz.gitlab.io/download/)
try:
    pic = 'config\\result\\model_' + current_datetime + '.png'  # model's image file name
    plot_model(model, show_shapes = True, expand_nested = True, to_file = pic)
    print(f'\033[93mModel\'s training image had been saved at \"{pic}\".\033[0m')
except:
    print(f'\033[93mThere has some problem to save model\'s training image, please install Graphviz from https://graphviz.gitlab.io/download/.\033[0m')

# exist
while True:
    if input('\033[93mEnter \"-1\" to exit:\033[0m ') == '-1':
        break

input('Press any key to continue...')
  • For prediction:
 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
### import module
import os
os.environ['TF_ENABLE_ONEDNN_OPTS'] = '0'
import numpy as np
from keras.api.models import load_model
from tqdm import tqdm
import cv2
import json

### function
# predict function
def predict(predicted_set, image_size):
    print('\033[93mPredicting...\033[0m')
    for file in tqdm(os.listdir(predicted_set)):

        # 讀取新圖片並進行預處理
        new_image = cv2.imdecode(np.fromfile(os.path.join(predicted_set, file), dtype = np.uint8), -1)
        new_image = cv2.cvtColor(new_image, cv2.COLOR_BGR2RGB)  # 轉換色彩資訊
        new_image = cv2.resize(new_image, image_size)  # 設定圖片大小

        # 將圖片轉換為 numpy 陣列,數據類型為32位浮點數
        new_image = (np.array(new_image, dtype = 'float32')) / 255.0  # 將色彩資訊轉換至[0,1]
        new_image = np.expand_dims(new_image, axis = 0)

        # 進行預測
        predictions = loaded_model.predict(new_image)
        pred_labels = np.argmax(predictions, axis = 1)

        # 輸出
        pred = {class_names[i]: pred_value for i, pred_value in enumerate(predictions[0])}
        pred = {k: round(v, 3) for k, v in sorted(pred.items(), key=lambda item: item[1], reverse=True)}
        print(f'\033[93mImage:\033[0m {file:<21} \033[93mPredicted:\033[0m {class_names[pred_labels[0]]:<10} \033[93mProbability:\033[0m {predictions[0][pred_labels[0]]*100:<.1f}%\n\033[93mOther proabaility:\033[0m {pred}\n')

### main program
# load saved model
if not os.path.exists('config'):
    print(f'\033[93mFileExistsError: could not find config folder at \"{os.getcwd()}", please create a Convolutional Neural Network model by \"cnn_model.py\" first.\033[0m')
    input('Press any key to continue...')
    raise
print('\033[93mLoading saved CNN models...\033[0m')
try:
    loaded_model = load_model('config\\Saved_model.keras')
except:
    print(f'\033[93mFileExistsError: could not find saved model at \"config\\Saved_model.keras\", please create a Convolutional Neural Network model by \"cnn_model.py\" first.\033[0m')
    input('Press any key to continue...')
    raise
# load config
try:
    with open("config\\config.json", "r", encoding = 'utf-8') as config_file:
        config_data = json.load(config_file)  # load config.json
        class_names = config_data.get("class_names", [])
        image_size = tuple(config_data.get("image_size", ()))
except FileNotFoundError:
    print(f'\033[93mFileExistsError: could not find config file at "config\\config.json", please create a Convolutional Neural Network model by \"cnn_model.py\" first.\033[0m')
    input('Press any key to continue...')
    raise
except json.JSONDecodeError:
    print(f'\033[93mValueError: error decoding JSON from "config\\config.json".\033[0m')
    input('Press any key to continue...')
    raise

# set the predicted folder
predicted_set = 'predicted_folder'
if not os.path.exists(predicted_set):
    os.mkdir(predicted_set)
while True:
    folder_path = input(f'\033[93mPlease enter the predicted folder path or enter \"1\" to use the defult folder \"predicted_folder\":\033[0m ')
    if folder_path == '1':
        break
    elif os.path.exists(folder_path):
        predicted_set = folder_path
        break
    print(f'TypeError: invalid folder path or the folder does not exist. Please enter a valid folder path.')

print(f'\033[93mUsing the predicted folder {predicted_set}.\033[0m')

# predict
predict(predicted_set, image_size)

# exit or predict again
while True:
    exit = input("\033[93mEnter \"1\" to predict the same folder again, or enter \"-1\" to exit:\033[0m ")
    if exit == '-1':
        break
    if exit == '1':
        predict(predicted_set, image_size)

input('Press any key to continue...')

Implementation Results

To test whether the model could recognize different types of images, I used typhoon data from Historical Typhoon Data Download for Taiwan as the testing dataset.

After training the CNN model, it outputs the Training Loss and the Confusion Matrix, which is used to evaluate the classification performance.

https://Josh-test-lab.github.io/posts/CNN%20Image%20Recognition%20Experiment/loss_2024-10-01T18_38_58.webp
Train loss.

https://Josh-test-lab.github.io/posts/CNN%20Image%20Recognition%20Experiment/confusion_matrix_2024-10-01T18_38_58.webp
Confusion matrix.

Through the training process described above, we can obtain the prediction results for new images as follows:

https://Josh-test-lab.github.io/posts/CNN%20Image%20Recognition%20Experiment/result.webp
Prediction results.

Conclusion

Looking back at this project, there are still many aspects that could be improved. For example, the model architecture, data preprocessing methods, hyperparameter settings, and the scale and quality of the dataset could all have a significant impact on the final recognition performance.

If I have the opportunity to improve this project in the future, I would start by refining the model structure, attempting to improve or replace the current architecture. I would also reorganize the program structure to make the overall workflow easier to maintain and extend. In addition, I would enhance the evaluation of prediction results by adding confusion matrices and incorporating more model evaluation metrics.

Although this was only a simple experiment, it became my starting point for exploring deep learning and laid the foundation for my future learning of other models.

References

Warning
The last update time of this article is July 17, 2026, and the content may be outdated. Please be aware. If you notice any errors or broken images, feel free to leave a comment for corrections.