1
0
Files
ai-school/exp2/modified/predict.py
2025-12-06 13:10:02 +08:00

136 lines
4.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from pathlib import Path
import sys
import numpy
import torch
import torch.nn.functional as F
from PIL import Image, ImageFile
import matplotlib.pyplot as plt
from model import Cnn
sys.path.append(str(Path(__file__).resolve().parent.parent.parent))
import gpu_utils
class PredictResult:
"""预测的结果"""
possibilities: torch.Tensor
"""每个数字不同的概率"""
def __init__(self, possibilities: torch.Tensor):
"""
创建预测结果。
:param possibilities: 传入的tensor表示每个数字不同的概率是经过softmax后的数值。
其shape为二维。dim 0为batch应当只有一维dim 1为每个数字对应的概率。
"""
self.possibilities = possibilities
def chosen_number(self) -> int:
"""
获取最终选定的数字
:return: 以当前概率分布,推测的最终数字。
"""
# 输出出来是10个数字各自的可能性所以要选取最高可能性的那个对比
# 在dim=1上找最大的那个就选那个。dim=0是批次所以不管他。
return self.possibilities.argmax(1).item()
def number_possibilities(self) -> list[float]:
"""
获取每个数字出现的概率
:return: 返回一个具有10个元素的列表列表的每一项表示当前index所代表数字的概率。
"""
return list(self.possibilities[0][i].item() for i in range(10))
class Predictor:
device: torch.device
model: Cnn
def __init__(self):
self.device = gpu_utils.get_gpu_device()
self.model = Cnn().to(self.device)
# 加载保存好的模型参数
file_path = Path(__file__).resolve().parent.parent / 'models' / 'cnn.pth'
self.model.load_state_dict(torch.load(file_path))
def __predict_tensor(self, in_data: torch.Tensor) -> PredictResult:
"""
其它预测函数都要使用的预测后端。其它预测函数将数据处理成Tensor然后传递给此函数进行实际预测。
:param in_data: 传入的tensor该tensor的shape必须是28x28dtype为float32。
:return: 预测结果。
"""
# 上传tensor到GPU
in_data = in_data.to(self.device)
# 为了满足要求要在第一维度挤出2下
# 一次是灰度通道,一次是批次。
# 相当于batch size = 1的计算
in_data = in_data.unsqueeze(0).unsqueeze(0)
# 开始预测由于模型输出的是没有softmax的数值因此最后还需要softmax一下
with torch.no_grad():
out_data = self.model(in_data)
out_data = F.softmax(out_data, dim=-1)
return PredictResult(out_data)
def predict_sketchpad(self, image: list[list[bool]]) -> PredictResult:
"""
以sketchpad的数据进行预测。
:param image: 该列表的shape必须为28x28。
:return: 预测结果。
"""
input = torch.tensor(image, dtype=torch.float32)
assert(input.dim() == 2)
assert(input.size(0) == 28)
assert(input.size(1) == 28)
return self.__predict_tensor(input)
def predict_image(self, image: ImageFile.ImageFile) -> PredictResult:
"""
以Pillow图像的数据进行预测。
:param image: Pillow图像数据。该图像必须为28x28大小。
:return: 预测结果。
"""
# 确保图像为灰度图像,以及宽高合适
grayscale_image = image.convert('L')
assert(grayscale_image.width == 28)
assert(grayscale_image.height == 28)
# 转换为numpy数组。注意这里的numpy数组是只读的所以要先拷贝一份
numpy_data = numpy.reshape(grayscale_image, (28, 28), copy=True)
# 转换到Tensor设置dtype
data = torch.from_numpy(numpy_data).float()
# 归一化到255又因为图像输入是白底黑字需要做转换。
data.div_(255.0).sub_(1).mul_(-1)
return self.__predict_tensor(data)
def main():
predictor = Predictor()
# 遍历测试目录中的所有图片,并处理。
test_dir = Path(__file__).resolve().parent.parent / 'test_images'
for image_path in test_dir.glob('*.png'):
if image_path.is_file():
print(f'Predicting {image_path} ...')
image = Image.open(image_path)
rv = predictor.predict_image(image)
print(f'Predict digit: {rv.chosen_number()}')
plt.figure(f'Image - {image_path}')
plt.imshow(image)
plt.axis('on')
plt.title(f'Predict digit: {rv.chosen_number()}')
plt.show()
if __name__ == "__main__":
gpu_utils.print_gpu_availability()
main()