Aclicee

个性签名:

就是个臭打游戏

  • 2024-09-15
  • 发表了主题帖: 【Follow me第二期】入门任务 - 开发环境配置+LED点亮+串口打印+LED点阵驱动

            我们非常有幸参加本期的Follow me活动,能够亲自体验Arduino这一享誉盛名的开源硬件平台。Arduino以其易用性和灵活性,成为全球电子爱好者和教育者的首选工具,期待本次活动有所收获。 1. 开箱         9月6日,我们收到了DigiKey提供的包裹,内含Arduino UNO R4 WiFi开发板一套,以及SHT40温湿度传感器和LTR-329光照传感器的扩展板各一块。此外,还包括了一条用于连接的数据线。在开箱过程中,我们注意到两个传感器扩展板均附带了排针,这表明用户可以通过焊接操作,将传感器直接连接至开发板的排母上,从而实现快速部署。         我们对所收到的产品进行了仔细检查,确认所有配件均齐全且完好无损。开发板的制造工艺精良,细节处理得当,体现了制造商的高标准和对品质的严格要求。值得一提的是,开发板还附带了一个亚克力底座,这一设计不仅提升了产品的美观度,更重要的是,它能够有效防止开发板背面的管脚在操作过程中发生误触,确保了操作的安全性。 2. 入门任务(必做):搭建环境         在进行Arduino开发之前,搭建一个稳定且高效的开发环境是至关重要的。鉴于之前没有使用过Arduino,我们首先查阅了其数据手册【ABX00087-datasheet.pdf (digikey.com)】。该手册不仅详尽地列出了板卡的技术参数、管脚功能及其布局,还提供了基本的使用指南,为我们的后续开发工作奠定了基础。         Arduino提供了两种主要的开发工具:本地IDE和在线编辑器。用户可以通过访问Arduino的官方网站【Arduino - Home】,在顶部菜单中找到对应开发板的使用说明,并下载所需的编辑器。此外,左侧菜单中的Learn Arduino部分提供了不同编辑器的初步教程,为初学者提供了详尽的指导。         本地编辑器的下载地址可以在Software模块找到【Software | Arduino】,不过作为初步尝试,我们选择先Arduino的在线编辑器开发一些短平快的小demo。用户可以通过Software模块顶部的Go To Cloud Editor选项进入在线编辑器,同时,相关的使用教程也在【Using the Arduino Cloud Editor | Arduino Documentation】中提供。         在使用在线编辑器之前,需要注册一个Arduino账户,并安装Arduino Create Agent插件。安装并激活插件后,其图标将显示在电脑右下角的工具栏中。         至此,Arduino在线编辑器的环境配置已基本完成。与我们之前使用的一些单片机开发环境相比,Arduino的配置过程显得尤为简单直观。         完成环境配置后,将Arduino开发板通过USB线连接至电脑,即可开始编写代码并进行烧录。这一过程的便捷性,再次体现了Arduino平台的易用性。 3. 入门任务(必做):LED点亮以及串口打印         LED点亮和串口打印功能这两项基础功能是电子项目开发中不可或缺的部分,对于初学者而言,掌握它们是进入更高级应用的前提。         在Arduino的在线编辑器中,通过点击左侧菜单的Sketches选项,我们可以创建一个新的工程。对于我们这样初次接触Arduino编程的用户,可能会感到无从下手。此时,可以利用编辑器左侧的Examples菜单,其中包含了丰富的示例代码,为我们提供了宝贵的参考。         我们以“Blink”例程为例,该例程展示了如何控制LED的点亮与熄灭。Arduino程序主要由两个函数组成:setup()和loop()。setup()函数负责初始化设置,如配置管脚模式和初始化外设,它仅在程序开始时执行一次。随后,程序将进入loop()函数,这是程序的主循环,所有持续执行的任务都在这里进行。         在“Blink”例程中,我们首先将LED对应的管脚设置为输出模式。这样,我们就可以通过改变该管脚的电平状态(高电平或低电平),来控制LED的点亮与熄灭。在loop()函数中,我们通过编写代码,使LED以一定的频率闪烁。         将上述代码复制到我们的工程中,并烧录到Arduino开发板上。烧录过程中,开发板上的两盏LED指示灯会短暂点亮,以指示烧录过程正在进行。烧录完成后,只有我们指定的LED会按照预设的频率反复点亮和熄灭,实现闪烁效果。         串口打印功能的实现与LED点亮类似。在setup()函数中,我们需要初始化串口通信,并设置适当的波特率。在loop()函数中,我们编写代码,使开发板能够周期性地通过串口发送字符串信息。         我们将LED点亮和串口打印的代码合并到一个工程中,进行烧录,具体代码如下: void setup() {   pinMode(LED_BUILTIN, OUTPUT);   Serial.begin(9600); } void loop() {   Serial.println("Hello EEWorld!");   digitalWrite(LED_BUILTIN, HIGH);   delay(1000);   digitalWrite(LED_BUILTIN, LOW);   delay(1000); }         烧录完成后,开发板上的LED将按照预设频率闪烁,同时,通过打开编辑器的串口监视器,我们可以观察到开发板发送的字符信息。 4. 基础任务(必做):驱动12x8点阵LED         接下来尝试驱动一下LED的点阵。与单个LED的控制相似,点阵LED的控制原理也是通过设置对应位置的管脚电平来控制LED的点亮或熄灭。然而,由于涉及的LED数量较多,我们无法对每个LED进行单独配置,因此需要采用一种编码规则来实现有效的控制。         我们首先需要引入Arduino_LED_Matrix.h这个头文件,它包含了控制矩阵LED所需的函数。这个库可以通过Arduino IDE的Libraries菜单搜索并添加到项目中。         查看教程中有关LED Matrix的部分【Using the Arduino UNO R4 WiFi LED Matrix | Arduino Documentation】,我们学习了如何使用这个库来控制LED点阵。相关的例程也可以在LED Matrix库中找到。大致学习了一下教程和例程,对点阵进行编码的方式可以总结为如下两种。         第一种是直接构建一个8x12的数组,每个元素代表一个LED的状态,0代表熄灭,1代表点亮。这种方法直观且易于理解。例如,我们参考了“DisplaySingleFrame”例程,设计了一个笑脸图案,如下:         在循环主体中,我们使用matrix.renderBitmap()函数来驱动LED矩阵,将数组中的0和1映射到对应的LED位置,实现图案的显示。这个Bitmap非常生动形象的概括了点亮LED的过程,即将各1位的0或1比特映射到对应的LED矩阵位置。         第二种方法是对整个矩阵进行简化表达,使用三个32位的uint32_t变量来表示整个矩阵。我们将上述数组按照4位一组转换成16进制数,从而简化了代码的复杂性,如下:         在主体循环中,我们使用matrix.loadFrame()函数来驱动LED矩阵,这种方法使得代码更加简洁和优雅。Frame表示已经把整个图像框起来打包好了,是一个整体。         在浏览例程时,我们注意到“GameOfLife”例程提供了动态效果的实现。受此启发,我们制作了一个动态点亮笑脸图案的小动画。该动画的原理是随机选择矩阵中的行和列进行点亮或熄灭,然后不断刷新新的Bitmap以展示动态效果。         为了避免因随机性导致的长时间卡顿,我们引入了一个矩阵来记录已经随机过的位置。这样,下一次遇到重复位置时,我们可以跳过,从而避免了循环中的卡顿。通过统计与目标图案点亮位置相同的个数,一旦匹配完成,即结束随机点亮过程,不必再等待所有位置都被随机遍历一遍了。         即便如此,有时候最后几个位置还是比较慢才会随机到。主要是C语言很久没使用了,平时写Python和Matlab习惯了,一下子遇到C语言不太会写了,写的非常笨重,循环套循环的,希望大家批评指正。具体代码如下: #include "ArduinoGraphics.h" #include "Arduino_LED_Matrix.h" ArduinoLEDMatrix matrix; #define ROWS 8 #define COLUMNS 12 #define BITNUM 24 uint8_t Figure[8][12] = { { 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0 }, { 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0 }, { 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, { 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1 }, { 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0 } }; void setup() { Serial.begin(115200); matrix.begin(); uint8_t initFigure[ROWS][COLUMNS] = {0}; uint8_t currentFrame[ROWS][COLUMNS] = {0}; int count = 0; for (int i = 0; i < ROWS*COLUMNS; i++) { bool placed = false; while (!placed) { int row = random(ROWS); int col = random(COLUMNS); if (initFigure[row][col] == 0) { initFigure[row][col] = 1; if (Figure[row][col] == 1) { currentFrame[row][col] = 1; count++; } placed = true; } if (count == 30){ break; } } matrix.renderBitmap(currentFrame, ROWS, COLUMNS); } }         下图是其随机生成过程中以及快要画完时候的LED矩阵。         除了动态图案,我们还尝试了使用LED矩阵显示字符。这主要借助于ArduinoGraphics.h库。在“TextWithArduinoGraphics”例程中,我们学习了如何使用matrix.beginDraw()和matrix.endDraw()构建显示框架,并设置了字体大小和内容。通过matrix.textScrollSpeed()函数,我们可以控制字符的滚动速度和方向。具体代码如下: void loop() { matrix.beginDraw(); matrix.stroke(0xFFFFFFFF); matrix.textScrollSpeed(200); const char text[] = " Hello EEWorld! "; matrix.textFont(Font_5x7); matrix.beginText(0, 1, 0xFFFFFF); matrix.println(text); matrix.endText(SCROLL_LEFT); matrix.endDraw(); delay(500); }         显示的结果如下:         综上所述,我们实现了LED矩阵的多种驱动方式和功能。我们将随机生成笑脸图案的代码放在setup()函数中,作为一次性的开屏动画。动画结束后,开发板将循环播放字符串滚动效果。         具体的实验过程和效果,可参考随附的视频演示。         [localvideo]d98f5afefadc17e0b205f9fe24c2b00e[/localvideo]

  • 2024-09-14
  • 加入了学习《串口打印Hello EEWorld!》,观看 控制LED8*12矩阵

  • 2024-09-06
  • 发表了主题帖: 《人工智能实践教程——从Python入门到机器学习》阅读报告(3)

    本帖最后由 Aclicee 于 2024-9-6 00:42 编辑         本书的第三部分,作者深入探讨了神经网络的构建与应用,这一部分是全书的精华所在。以下是对书中核心内容的简要介绍: 感知机:作者首先从感知机的原理出发,详细阐述了其从逻辑电路到多层神经网络的演变过程。书中不仅介绍了基础的激活函数,还深入探讨了如何通过这些函数构建更为复杂的神经网络结构; 反向传播算法:通过链式法则和计算图,作者清晰地推导了反向传播算法的理论基础。书中不仅涵盖了常见的激活函数,还特别介绍了Softmax等复杂函数的推导过程,为读者揭示了误差反向传播在神经网络学习中的核心作用; 训练方法:在优化器的选择上,作者提供了SGD、Momentum、AdaGrad和Adam等多种算法的比较。这一部分可以参考李宏毅老师的课程,其中有一些动态图像会提供更直观的理解,帮助读者把握这些方法之间的差异; 训练的优化:书中对网络参数初始化、批量归一化、正则化以及超参数选择等训练优化技术进行了深入分析。虽然这些内容在实际操作中可能需要大量的试错,但作者的理论分析为读者提供了坚实的基础; 卷积网络(CNN):作者介绍了卷积神经网络(CNN)的原理和优势,并以图像处理中的应用案例为例,展示了CNN的强大能力。尽管我的研究领域并非图像处理,但我仍然能够利用CNN的特性来丰富我的项目实践。 对这一部分的总体评价:         总体来说,这部分内容是全面的,尤其是在理论推导方面,作者的详细讲解极大地提升了阅读兴趣。书中对于训练过程的介绍,从优化器的选择到网络优化方法,都配有清晰的公式和示例代码,极大地方便了读者的理解和学习。然而,书中对于计算机视觉和自然语言处理两大应用领域的承诺,似乎在介绍完CNN后戛然而止,对于时间序列相关的网络如RNN、LSTM以及当前热门的Transformer模型并未涉及,这不免让人感到遗憾。 对全书的测评如下:         综合考虑,我认为这本书只能算是无功无过。它的内容全面,但与市场上的其他教材相比,缺乏一定的创新性和深度。虽然作为一本实践教程,它在理论深度上略显不足,但如果仅作为快速查找代码的操作手册,它的实用性又不如直接上网搜索或咨询人工智能助手来得高效。此外,书中未能涵盖自然语言处理的相关方法和网络,这在一定程度上影响了它的完整性。尽管如此,书中的代码开源是一个值得称赞的亮点。         回到测评的实践任务,按照测评计划,我将结合我目前正在进行的科研项目,运用神经网络技术对电池的衰退程度进行预测。专业概念和电池衰退的定义颇为复杂,我在这里就不深入讨论了,可以通过查阅相关论文来获得更多信息。         简单的背景就是电池在经历反复的充放电循环后,其可用容量会逐渐减少。为了量化这一衰退过程,我们引入了一个衡量指标——电池健康状态(State of Health, SOH)。SOH是通过计算电池每个周期最大可用容量与其出厂时标称容量的比值来定义的。与之前仅进行衰退与否的二元判断不同,我们现在的目标是预测一个连续变化的值,即将问题从分类任务转变为回归任务。上一期阅读报告的链接:【《人工智能实践教程——从Python入门到机器学习》阅读报告(2) - 编程基础 - 电子工程世界-论坛 (eeworld.com.cn)】。         而我们选择的特征,我选择了之前提到的马里兰大学的电池数据集(Battery Research Data | Center for Advanced Life Cycle Engineering (umd.edu))作为研究对象。由于实际使用的数据集暂时无法公开,我在此基础上胡诌了一个数据集,但我们采用的原理和方法是相同的。我们从电池每个充放电周期中提取了5个不同充电量时的电压值作为特征,这些特征能够刻画电池的充电过程,并作为评估其SOH的依据。 1. 数据的预处理和读取         在开始模型训练之前,我们已经对数据集进行了预处理,包括归一化等步骤。具体的预处理过程可以参考我上一次的测评报告。此外,我们根据电池的完整衰退数据,选择了两节电池的数据作为训练集,剩余一节作为测试集,训练集和测试集的样本数量比大约为2.5:1。 输出:Training Samples: 1442        Testing Samples: 615         在之前的分类问题研究中,我们已经展示了不同特征之间的相关性。在本次回归任务中,由于预测目标是一个连续变化的值,我们直接绘制了某个特征与输出结果之间的关联图。从图中可以看出,因为我们的选取的特征还是比较和预测目标相关的,所以线性度比较强。 2. 线性回归模型(Linear Regression,LR)         基于此,我们首先考虑实现一个基础的线性回归算法。这个算法将作为我们后续与神经网络方法进行比较的基准。通过线性回归,我们可以初步探索特征与电池衰退程度(SOH)之间的关系。         线性回归是一种预测连续目标值的统计方法,它假设输入特征与目标值之间存在线性关系。在模型构建过程中,我们的目标是找到一组权重,使得模型预测的输出尽可能接近实际的目标值。具体来说,模型的预测公式可以表示为:,其中是预测值,是输入特征,是权重,是偏置项。在之前的以及其他作者的阅读报告中已经提及,一般可以通过最小二乘等方法来求解线性回归问题。这里我就直接调用python的sklearn包了,代码如下: from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error, r2_score LRmodel = LinearRegression() LRmodel.fit(X_train_LR, y_train) y_train_pred = LRmodel.predict(X_train_LR) y_test_pred = LRmodel.predict(X_test_LR) train_mse = mean_squared_error(y_train, y_train_pred) test_mse = mean_squared_error(y_test, y_test_pred) train_r2 = r2_score(y_train, y_train_pred) test_r2 = r2_score(y_test, y_test_pred) print(f'Training MSE: {train_mse:.6f}, R^2: {train_r2:.4f}') print(f'Test MSE: {test_mse:.6f}, R^2: {test_r2:.4f}')  输出:Training MSE: 0.000126, R^2: 0.9753        Test MSE: 0.000125, R^2: 0.9641         为了评估模型的性能,我们使用了两个关键指标:均方误差(MSE)和拟合优度(R^2)。MSE衡量的是预测值与实际值之间差异的平方的平均值,而R^2则反映了模型预测值与实际值之间的相关程度。理想情况下,MSE应尽可能小,R^2应接近1。         下面的左右两图边是利用训练集的数据拟合的5个特征预测的输出结果和实际结果的对应关系。上面的每一个散点表示一个样本,纵坐标为其预测的目标值,横坐标为其实际的目标值。黑线代表预测结果和实际结果完全相同的情况,因此输出的散点图分布越接近黑线,准确度越高。         在我们的实验中,由于特征与SOH之间存在较强的线性关系,线性回归模型表现出了较好的预测效果。散点图显示,大多数数据点都紧密地围绕着代表完美预测的黑线分布,这表明模型的预测精度较高。 3. 引入非线性问题         既然如此,我们不妨对数据进行一些干扰,来模拟采集过程中的噪声,来降低两者之间的线性相关性。这种干扰降低了特征与预测目标之间的线性相关性,从而对模型的预测能力提出了挑战。具体代码如下: import numpy as np np.random.seed(8) noise_level = 0.1 X_train_LR = X_train + np.random.randn(*X_train.shape) * noise_level X_test_LR = X_test + np.random.randn(*X_test.shape) * noise_level         如下图所示:         在引入噪声后,线性回归模型的预测效果受到了影响。测试集上的误差增加,拟合优度下降,这表明模型在面对非理想数据时的预测能力有所降低。如下图所示:         输出如下:         *** Linear Regression ***         Training MSE: 0.000797, R^2: 0.8435         Test MSE: 0.000857, R^2: 0.7535   4. 多层感知机(Multi-Layer Perceptron,MLP)         此时因为问题的非线性特征,我们引入神经网络。神经网络相较于传统的线性回归模型,其最大的优势在于能够通过激活函数引入非线性,从而更准确地刻画数据中的复杂关系。我设计了一个包含三个层的网络。输入层接收当前周期的5个特征值,这与我们在线性回归模型中使用的特征相同。在隐层,我们将特征维度扩展到32和64,以增强模型的表达能力。最后,通过一个全连接层输出预测的SOH值。具体代码如下: import torch import torch.nn as nn class MLP(nn.Module): def __init__(self, input_dim): super(MLP, self).__init__() self.fc1 = nn.Linear(input_dim, 32) self.fc2 = nn.Linear(32, 64) self.fc3 = nn.Linear(64, 1) self.relu = nn.ReLU() def forward(self, x): x = self.relu(self.fc1(x)) x = self.relu(self.fc2(x)) x = self.fc3(x) return x         为了确保训练过程的稳定性和可重复性,我们从训练集中划分出一部分数据作为验证集。这样做的目的是为了在训练过程中选择更合适的模型超参数。我们还控制了随机种子,以确保每次运行时数据的随机分配都是一致的。具体的代码如下: import torch.optim as optim from torch.utils.data import DataLoader, TensorDataset, random_split torch.manual_seed(8) X_train_tensor = torch.tensor(X_train_LR, dtype=torch.float32) y_train_tensor = torch.tensor(y_train, dtype=torch.float32).view(-1, 1) X_test_tensor = torch.tensor(X_test_LR, dtype=torch.float32) y_test_tensor = torch.tensor(y_test, dtype=torch.float32).view(-1, 1) train_size = int(0.8 * len(X_train_tensor)) val_size = len(X_train_tensor) - train_size train_dataset, val_dataset = random_split(TensorDataset(X_train_tensor, y_train_tensor), [train_size, val_size]) train_loader = DataLoader(dataset=train_dataset, batch_size=64, shuffle=True) val_loader = DataLoader(dataset=val_dataset, batch_size=64, shuffle=False)         训练过程的代码如下: input_dim = X_train_LR.shape[1] model = MLP(input_dim) criterion = nn.MSELoss() optimizer = optim.Adam(model.parameters(), lr=0.0005) num_epochs = 200 train_losses = [] avg_val_losses = [] for epoch in range(num_epochs): for inputs, targets in train_loader: optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, targets) loss.backward() optimizer.step() model.eval() with torch.no_grad(): val_losses = [] for inputs, targets in val_loader: outputs = model(inputs) val_loss = criterion(outputs, targets) val_losses.append(val_loss.item()) avg_val_loss = sum(val_losses) / len(val_losses) train_losses.append(loss.item()) avg_val_losses.append(avg_val_loss) if (epoch+1) % 10 == 0: print(f'Epoch [{epoch+1}/{num_epochs}], Traing Loss: {loss.item():.6f}, Validation Loss: {avg_val_loss:.6f}')         测试的代码如下: model.eval() with torch.no_grad(): y_train_pred = model(X_train_tensor) y_test_pred = model(X_test_tensor) train_mse = mean_squared_error(y_train, y_train_pred) test_mse = mean_squared_error(y_test, y_test_pred) train_r2 = r2_score(y_train, y_train_pred) test_r2 = r2_score(y_test, y_test_pred) print(f'Training MSE: {train_mse:.6f}, R^2: {train_r2:.4f}') print(f'Test MSE: {test_mse:.6f}, R^2: {test_r2:.4f}')         在MLP模型训练完成后,我们评估了其预测效果。与直接的线性回归模型相比,MLP在预测误差和R^2值上都有所提升。尽管我还没有对模型的超参数进行细致的调整,但我相信通过进一步的优化,模型的性能还有很大的提升空间。目前,模型的准确度虽然有所提高,但仍然有待提高。最终的预测效果如下图所示:         输出如下:         *** MLP ***         Training MSE: 0.000638, R^2: 0.8748         Test MSE: 0.000728, R^2: 0.7907           考虑到电池的衰退不仅与当前周期有关,而且与之前的周期也有密切联系,将某一周期割裂来看确实会因为测量的噪声的问题出现预测的误差。我们将单一周期的数据扩展到连续5个周期的充电曲线特征。这样的数据重构有助于捕捉电池衰退过程中的时间序列特性。 X_train_CNN = X_train.reshape(-1,5,5) X_test_CNN = X_test.reshape(-1,5,5) X_train_CNN = X_train_CNN + np.random.randn(*X_train_CNN.shape) * noise_level X_test_CNN = X_test_CNN + np.random.randn(*X_test_CNN.shape) * noise_level         理论上,对于时间序列数据,长短期记忆网络(LSTM)可能是更合适的选择。然而,卷积神经网络(CNN)在处理此类数据时也展现出了其独特的优势。之前有研究利用CNN对多周期的充电曲线特征进行深入的相关性和特征提取。因此,我们也尝试复现这一过程,以探索CNN在电池衰退预测中的潜力。 5. 卷积神经网络(Convolutional Neural Networks,CNN)         我们的CNN模型采用了3x3的卷积核,对输入的特征矩阵进行两次卷积运算。每次卷积之后,我们使用最大值池化(Max Pooling)来降低特征维度,同时保留重要的信息。最终,所有通道的关键特征被展平,将它们从2D结构压缩到1D结构,并通过一系列线性层和非线性激活函数进行处理,以生成最终的预测输出。具体代码如下: class CNN(nn.Module): def __init__(self): super(CNN, self).__init__() self.conv1 = nn.Conv2d(1, 16, kernel_size=3, padding=1) self.pool = nn.MaxPool2d(2, 2, padding=1) self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1) self.fc1 = nn.Linear(32 * 2 * 2, 64) self.fc2 = nn.Linear(64, 32) self.fc3 = nn.Linear(32, 1) def forward(self, x): x = self.pool(torch.relu(self.conv1(x))) x = self.pool(torch.relu(self.conv2(x))) x = x.view(-1, 32 * 2 * 2) x = torch.relu(self.fc1(x)) x = torch.relu(self.fc2(x)) x = self.fc3(x) return x         CNN的训练过程与MLP相似,我们采用了相同的训练策略和验证方法。在这里,我们不再重复训练过程的详细描述。最终的预测结果如下:         输出如下:         *** CNN ***         Training MSE: 0.000285, R^2: 0.9441         Test MSE: 0.000315, R^2: 0.9094           经过训练,CNN模型展现出了较好的预测效果。与MLP相比,训练集和测试集的预测结果更接近理想的黑线,这表明模型的预测精度有所提高。然而,我们也观察到预测结果普遍高于实际值,这可能与模型参数的调优不足有关。         通过绘制训练集和验证集的损失下降曲线,我们发现两者都有明显的波动。为了更清晰地观察这种波动,我们将纵坐标调整为对数尺度。这种波动至少揭示了两个问题:首先,我们的学习率可能设置得过高,导致模型在训练过程中难以稳定地收敛到损失的局部最小值;其次,模型可能没有在最佳时机停止训练,因为在预设的训练周期结束时,验证集的损失已经开始上升,这可能预示着过拟合的风险。 6. CNN的优化         对此,参考书中的介绍,一一给出解决方案: 对于学习率的设置问题,手动调整学习率并进行多次交叉验证虽然能找到最优解,但过程繁琐且耗时。书中提到了两种自适应学习率的优化器:Adagrad和Adam,我们这里选择了后者。同时引入学习率衰减策略控制具体的学习率衰退,即随着训练的进行逐渐减小学习率,这有助于模型在初期快速下降损失,在训练后期则能细致地逼近局部最小值。 import torch.optim.lr_scheduler as lr_scheduler scheduler = lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.98) 对于合适的训练周期数问题,确定合适的训练周期数(epoch数)对于避免过拟合和欠拟合至关重要。我们通过监控验证集上的损失,并引入早停机制(Early Stopping)来解决这一问题。如果在设定的耐心值(patience)周期内,验证集损失没有显著下降,训练将自动停止。这样可以避免在损失尚未稳定时过早结束训练,同时防止过长时间的训练导致资源浪费和趋向过拟合。以下是实现早停机制的代码: num_epochs = 1000 patience = 50 best_loss = float('inf') patience_counter = 0 train_losses = [] avg_val_losses = [] for epoch in range(num_epochs): for inputs, targets in train_loader: optimizer.zero_grad() outputs = model_opt(inputs) loss = criterion(outputs, targets) loss.backward() optimizer.step() scheduler.step() model_opt.eval() with torch.no_grad(): val_losses = [] for inputs, targets in val_loader: outputs = model_opt(inputs) val_loss = criterion(outputs, targets) val_losses.append(val_loss.item()) avg_val_loss = sum(val_losses) / len(val_losses) train_losses.append(loss.item()) avg_val_losses.append(avg_val_loss) if (epoch+1) % 10 == 0: print(f'Epoch [{epoch+1}/{num_epochs}], Traing Loss: {loss.item():.6f}, Validation Loss: {avg_val_loss:.6f}') if avg_val_loss < best_loss: best_loss = avg_val_loss patience_counter = 0 torch.save(model.state_dict(), 'best_model.pth') else: patience_counter += 1 if patience_counter >= patience: print(f'Early stopping at epoch {epoch + 1}') break 对于过拟合的问题,它会导致模型在训练集上表现良好,但在测试集上表现不佳。书中给出了两种比较好的解决思路。一是引入正则化,通过在损失函数中添加参数的L2范数,减少模型对特定样本的敏感度;二是使用dropout技术,在训练过程中随机“丢弃”一部分神经元,增加模型的泛化能力。在PyTorch中,正则化可以通过优化器的权重衰减参数来实现,它的值越大,正则化的强度就越高,对权重的惩罚也就越大。 class OptCNN(nn.Module): def __init__(self): super(OptCNN, self).__init__() self.conv1 = nn.Conv2d(1, 16, kernel_size=3, padding=1) self.pool = nn.MaxPool2d(2, 2, padding=1) self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1) self.fc1 = nn.Linear(32 * 2 * 2, 64) self.fc2 = nn.Linear(64, 32) self.fc3 = nn.Linear(32, 1) self.dropout = nn.Dropout(p=0.3) def forward(self, x): x = self.pool(torch.relu(self.conv1(x))) x = self.pool(torch.relu(self.conv2(x))) x = x.view(-1, 32 * 2 * 2) x = torch.relu(self.fc1(x)) x = self.dropout(x) x = torch.relu(self.fc2(x)) x = self.fc3(x) return x model_opt = OptCNN() criterion = nn.MSELoss() optimizer = optim.Adam(model_opt.parameters(), lr=0.001, weight_decay=1e-5) scheduler = lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.98)         最终结果如下:         输出如下:         *** Optimized CNN ***         Early stopping at epoch 237         Training MSE: 0.000274, R^2: 0.9462         Test MSE: 0.000252, R^2: 0.9276           我们还监控了训练集和验证集的损失下降过程。         通过引入正则化和dropout,验证集的损失抖动明显减少,这表明模型的稳定性得到了提升。同时,损失值稳定下降,且训练周期数减少,这得益于早停机制和学习率衰减策略的有效应用。最后所有的模型汇总如下:   LR MLP CNN Opt. CNN MSE 0.000857 0.000728 0.000315 0.000252 R^2 0.7535 0.7907 0.9094 0.9276   7. 最后总结         虽然书中的实例主要聚焦于计算机视觉领域,这与我的研究方向有所差异,但我依然能够将书中的理论知识与我的研究内容相结合,对具体的任务进行了深入的实践探索。通过本次测评,我不仅体验了神经网络的调用、搭建以及优化的全过程,而且对这些环节有了更深刻的理解。尽管我采取了一些措施在一定程度上提高了模型的性能,但我清楚地意识到,这些优化远非最优解。神经网络的参数调整,常被戏称为“炼丹”,这一比喻形象地说明了其复杂性和不确定性,后续的学习还是永无止境啊!

  • 2024-08-22
  • 加入了学习《【Follow me第二季第1期】使用Makecode图形化完成任务》,观看 【Follow me第二季第1期】使用Makecode图形化完成任务

  • 加入了学习《【Follow me第二季第1期】全部任务演示》,观看 全部任务演示2.0

  • 发表了主题帖: 《人工智能实践教程——从Python入门到机器学习》阅读报告(2)

    本帖最后由 Aclicee 于 2024-8-22 21:55 编辑         本书的第二部分为机器学习模块,介绍了一些经典的机器学习算法。机器学习与深度学习,二者虽同根生,却各有千秋。个人的理解上,机器学习算法,如KNN、随机森林和支持向量机等,它们是依赖于严格的数学计算的。这些算法的精髓在于其算法本身的设计,而非依赖于人工介入的超参数调整。不像深度学习需要炼丹那样,精心调配各种网络结构和参数,以期达到最佳的训练效果。         本书在这一部分中,为我们详细介绍了以下几个关键的机器学习算法: PCA(主成分分析):一种通过正交变换将数据转换到新的坐标系统中,使得数据的任何投影的第一大方差在第一个坐标(称为第一主成分)上,第二大方差在第二个坐标上,依此类推的算法。它不仅帮助我们降维,更让我们能够洞察数据的本质。 Kmeans:一种经典的聚类算法,通过迭代优化,将数据点划分为K个簇,使得每个簇内的点尽可能相似,而簇间的点尽可能不同。它简单而高效,是探索数据结构的有力工具。 KNN(K近邻算法):一种基于距离的分类算法,通过测量不同特征值之间的距离来进行分类。它的简洁和直观,使得KNN在多种应用场景中都表现出了不俗的效果。 线性回归与多项式回归:回归算法是预测数值型数据的重要工具。线性回归以其直观的模型和易于理解的特性,为我们提供了数据预测的基础。而多项式回归则在此基础上增加了模型的复杂度,以适应更复杂的数据关系。 对这一部分的总体评价:         有一说一,这本书的介绍逻辑比较奇怪,之前学习其他课程往往会从回归问题开始讲解,因为线性回归是后面很多算法的前置(比如支持向量机,甚至是推导梯度下降时最简单情形的案例),不明白为什么编者要放在最后,反而在最前面花了大量的篇幅讲PCA。         一方面,对初学者而言,初次接触机器学习便直接学习PCA可能会感到困惑,因为它属于是数据降维和特征工程相关的内容,有点像是锦上添花的作用,并不直接关联到具体的分类或回归任务。如果读者在没有充分理解机器学习基本概念的情况下学习PCA,可能会对其作用和重要性缺乏直观的认识。         另一方面,在PCA推导过程中利用到了梯度的概念以及梯度上升算法,此时读者根本不知道什么是梯度,也不知道梯度下降算法,强行插入对梯度上升算法的介绍,完全不合逻辑(并且推导也是没头没尾的,又要去参考十万八千里以外的梯度下降法)。         一些个人浅薄之见,欢迎各位批评指正,阅读下来总觉得编者这样剑走偏锋安排的讲解方式对读者尤其是初学者十分不友好,建议编者考虑是否重新安排一下逻辑和数学上的连贯性。         回到评测计划上来,我之前在计划中也提到过,我目前在做一个预测电池容量衰退情况的工作。简单介绍一下任务的背景,在当今快速发展的新能源领域,锂离子电池作为核心能量存储单元,其性能稳定性和寿命预测显得尤为重要。电池的循环充放电过程中,不可避免地会出现电解液浓度降低、内阻增大等现象,这些因素共同作用导致电池容量逐渐衰退。因此我们通过采集锂离子电池在不同充放电周期的关键特征数据,建立一个模型,以评估电池的衰退情况。         为了构建一个具有代表性的数据集,我们从马里兰大学提供的公开电池数据(Battery Research Data | Center for Advanced Life Cycle Engineering (umd.edu))中挑选了几组电池数据。在每节电池的充放电周期中,我们选取了电池的一些充放电表现作为特征。因为我实际项目的数据集暂时不能公开,所以我胡诌了一个数据集,仅作为演示使用,每个样本为某一节电池某一周期随意选择充电曲线上的五个位置(作为特征),标签为是否出现衰退的二元标签(0-Negative表示未衰退,1-Positive表示已衰退)。然后生成了训练集和测试集,本质上就是一个二元分类的问题。 1.数据处理(使用了PCA进行降维)         对数据集进行初步探索性分析时,绘制了一下其中两个特征的关系图。由于特征我是随意选择的,可以看到其中第一个和第二个特征(如图1)之间存在非常强烈的线性关系,且有大量数据点彼此重叠。这种重叠的情况暗示了这两个特征可能在区分样本方面并不具有足够的能力。进一步观察其他特征,比如第一个特征和第三个特征(如图2),也发现了一定程度的线性相关性,这表明数据中存在冗余。         为了解决这一问题,可以使用PCA方法对数据进行降维处理。书中虽然提供了使用梯度上升法手动实现PCA的方法和详细的推导过程,也可以参考其他作者发表的阅读报告。但为了简化流程并提高效率,我选择直接使用Python内置的PCA工具。考虑到可视化的便利性,我决定仅保留前两个主成分。具体代码如下: from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler() scaler.fit(X_train) X_train_norm = scaler.transform(X_train) from sklearn.decomposition import PCA pca = PCA(n_components=2) pca.fit(X_train_norm) X_Rtrain = pca.fit_transform(X_train_norm)         通过PCA转换后的特征分布图,我们可以看到大部分数据点现在分布在了水平方向上,原先的线性关系得到了有效的消解。这表明主成分分析成功地捕捉了数据中分布方差最大的方向。此外,原本重叠的数据区域也有所减少,这进一步证实了所选主成分在表示样本差异性方面的有效性。         在完成训练集的PCA降维处理后,我们面临着对测试集进行相同操作的任务。至关重要的一点是,我们必须确保测试集的处理方式与训练集保持完全一致。这样做不仅有助于避免数据泄露,确保模型评估的公正性。我们首先使用训练集的最大值-最小值归一化测试集,然后将训练集上学习到的主成分分析模型直接应用于测试集的归一化数据。具体的代码如下: X_test_norm = scaler.transform(X_test) X_Rtest = pca.transform(X_test_norm)         在对测试集进行PCA降维处理后,我们观察到正负样本的分布形态与训练集保持了高度的一致性。这种一致性对于确保模型评估的准确性至关重要,因为它意味着我们的数据预处理步骤没有引入偏差,模型在训练集和测试集上的表现将是可比的。 2.分类问题(使用了逻辑回归和KNN两种方法)         接下来就是使用书中介绍的机器学习算法,来完成这个二元分类任务。         从样本分布的形状可以看出,存在一定的交叠,最好的方法一定是支持向量机或者神经网络了,但是书中既然没有提到支持向量机,我们就试试别的方法。         首先是回归的方法,逻辑回归是一种特殊的线性回归,它通过引入sigmoid函数将线性回归的输出映射到0和1之间,从而实现二元分类。这一变换不仅保留了线性模型的直观解释性,而且通过引入非线性,增强了模型对复杂数据分布的适应能力。具体来说,当预测值小于0.5时,我们将其分类为0;当预测值大于0.5时,我们将其分类为1。这种方法为我们提供了一种既简单又有效的二元分类界面。逻辑回归的内容书中没有讲,实际只是在线性回归的基础上套一个sigmoid函数,应该是很简单的。具体推导的过程也可以参考其他作者的阅读报告,我这里就直接调用Python的工具了,具体代码如下: from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score log_reg = LogisticRegression(max_iter=1000) log_reg.fit(X_Rtrain, y_train) y_pred = log_reg.predict(X_Rtest) accuracy = accuracy_score(y_test, y_pred) print(f"Accuracy: {accuracy}") 输出:Accuracy: 0.8097643097643098         通过逻辑回归模型的初步分类尝试,我们观察到分类界面(如上图)呈现出明显的线性特征,其实际表现比较一般,正确率大约是80%左右。         为了进一步提升分类效果,书中推荐了KNN(K近邻算法)作为另一种经典的分类算法。KNN的核心思想在于,通过计算测试集中每个样本与训练集中样本的距离,找出最近的K个邻居。这些邻居的多数投票结果将决定测试样本的类别。这种方法能够提供更加灵活的非线性决策边界,从而有望提高分类的准确性。书中给出了手动计算的过程(也非常简单),但我依旧选择是直接调用了Python中的KNN工具,设置用于投票的邻居数量k=3,如下: from sklearn.neighbors import KNeighborsClassifier from sklearn.metrics import accuracy_score knn = KNeighborsClassifier(n_neighbors=3) knn.fit(X_Rtrain, y_train) y_pred = knn.predict(X_Rtest) accuracy = accuracy_score(y_test, y_pred) print(f"Accuracy: {accuracy}") 输出:Accuracy: 0.9124579124579124         可以看到这一结果(如上图)不仅在数值上更加接近实际的分类情况,而且在视觉上也更加符合我们的预期,正确率大约在91%左右。         为了进一步探究特征选择和降维对模型性能的影响,我们还尝试了不进行PCA降维,直接使用原始的五个特征进行KNN分类。由于高维数据的可视化难度较大,我们无法直观地展示分类界面,但通过模型评估,我们发现正确率大约为87%左右,略低于经过PCA降维后的结果。         这一对比实验强调了PCA在特征工程中的重要性。通过PCA降维,我们不仅减少了特征间的冗余,还去除了可能影响模型性能的噪声。更重要的是,PCA帮助我们捕捉了特征间的本质差异性,这在高维数据中尤为重要。降维后的数据更加精炼,使得KNN算法能够更加聚焦于数据的关键信息,从而提高了分类的准确性。

  • 2024-08-07
  • 发表了主题帖: 《人工智能实践教程——从Python入门到机器学习》阅读报告(1)

            7月24日收到的书,外观比较完整,书脊上略有一些磨损和褶皱,不过不影响阅读         按照之前的测评计划,今天分享一下对本书第一部分“Python编程”的相关内容,包含一、二、三章。         第一章的内容涵盖了Python的基础语法,包括变量定义、运算操作以及函数的使用。这些概念与其他编程语言有着共通之处,个人体验来说Python相对来说是比较好上手的。之前在使用Python时,常常也是通过官方文档可以找到详尽的语法说明和应用方法。         最近重点学习一下第二章面向对象的相关内容。在之前的项目实践中(大多数情况下是参考或者套用开源的Python代码),频繁地遇到类的定义和实例化,但对其中的深层原理并不十分了解。         通过对第二章内容的学习,理解了类的概念,尤其是建立了面向对象的编程思路。在之前的认知里,“类”无非也是一个类似函数的封装,把具有相同特性的一组变量同统一定义,来方便调用。现在有了更加系统的认识,它定义了一组具有相同属性和行为的对象的结构。每个类都可以看作是一个模板,用来创建具有相同特征的多个实例,同时将数据(属性)和行为(方法)封装在一起,保护了数据的完整性和安全性。         对于类,在之前的接触中其实就有几个不太理解的问题(以之前最常使用的基于torch.nn来定义神经网络的类为例,假定定义的类为myClass,其中需要传入几个参数如para_a, para_b, ...):         1. 为什么定义类的时候括号里写的是def myClass(nn.Module),但是调用的时候同样的myClass(para_a,para_b,...),括号里写的是需要传入的参数变量?         这是因为定义的类继承自python构建神经网络模块的基础类nn.Module,自定义的新类myClass继承了nn.Module中所有属性和方法,也可以根据自己的需求,增加定义新的属性和方法,来获得所需的新网络;而在后续的调用,实际上是基于定义好的类(模板)实例化一个具体的对象,需要给这个把参数传递给具体对象的构造器__init__。         2. 所有class在定义过程中都会有def __init__(self, para_a, para_b, ...)这个过程,是什么作用?         __init__为构造器,用于在创建类的新实例时,初始化对象的属性。其需要传入的这些参数用于具体实例对象的创建,参数列表中的第一个参数总是self,它代表当前的实例对象本身。         3. 使用def myClass(nn.Module)定义网络的类的时候,def __init__(self, para_a, para_b, ...)初始化参数总是需要在下边第一行写super(myClass, self).init()?         super()函数在用于调用父类的方法,因为自定义的类继承自nn.Module,需要确保父类被正确地初始化。         4. 初始化有一些参数会在def __init__ (self, para_a, para_b)里面定义,但是在同一类后续的新方法def func()中,不能通过直接的para_a来传递参数,需要使用self.para_a=para_a 在__init__步骤中先赋值,这是为什么?         在类的__init__方法中定义参数时,实际上是在为类的对象实例设置属性。当你写self.para_a=para_a时,你不仅仅是在执行一个赋值操作,你还在定义了一个属性para_a,这个属性与创建的对象相关联,并且存储在对象的实例字典中。如果在__init__方法之外直接使用para_a,Python解释器会认为它是一个局部变量,只在__init__的作用域内是可见的。如果尝试在其他方法中直接使用,会因为Python在当前方法的作用域找不到这个变量而报错。         5. 为什么init前后需要加__,而后面定义的那些方法不需要?         __开头和结尾的方法或属性被为特殊方法。这些方法通常不需要直接调用,而是由Python解释器在特定的情况下自动调用。如果类名为myClass,那么__init__方法实际上在类内部被改写为_myClass__init__。这样做同时也能防止子类不小心覆盖父类的特殊方法。           另外有一些之前没有接触过的新知识,帮助更好的定义和使用类:         1. 可以通过__str__(self, 'myString')的方法,控制对象实例在被print()函数调用时所展示的内容。这个方法允许输出特定的字符串,而不是默认的对象内存地址。例如,之前曾遇到过想试试通过print查看对象的结果,打印输出默认的内存地址<object at 0x10e9e1be0>之类的,可以定义__str__方法来提供更有意义的信息,如对象的状态或属性值。         2. 使用@property装饰器,可以为类的属性定义只读访问器,或者在设置属性值之前添加逻辑限制。这不仅增强了数据的安全性,还提供了一种优雅的方式来实现属性的验证和封装。         3. 使用@total_ordering装饰器,可以更高效地实现类实例之间的比较运算。这个装饰器允许只定义一部分比较方法,Python会自动提供其他的比较方法。         第三章高级编程另外还涉及比如Python闭包、其他的装饰器、迭代器和生成器。比较重要的是迭代器和生成器,它们是Python中实现惰性求值的强大工具。通过使用迭代器,可以遍历大量数据而无需一次性将它们全部加载到内存中。生成器则是一种特殊的迭代器,它在每次迭代时计算下一个值,从而有效避免了不必要的内存占用。这种按需计算的方式,不仅优化了性能,也提高了程序的可扩展性。         从之前的经验来看,这部分暂时会被使用到的不多。等后续涉及具体的项目时,再回头来查看作为进阶的内容。         有一些小错误,还希望作者和编辑再仔细校对。另外一些概念的讲解并不是很容易理解,前后逻辑顺序读着略有些混乱,阅读体验并不是很好。  

  • 2024-07-17
  • 回复了主题帖: 共读入围名单:《人工智能实践教程——从Python入门到机器学习》

    个人信息无误,确认可以完成评测计划。

最近访客

< 1/1 >

统计信息

已有16人来访过

  • 芯积分:68
  • 好友:--
  • 主题:4
  • 回复:1

留言

你需要登录后才可以留言 登录 | 注册


现在还没有留言