javascript减少循环迭代次数(”Dufff's Device算法“与“分时处理”)

<<高性能JavaScript>>中有一章提到对循环进行优化,策略之一就是减少循环迭代次数:

大家可能都听过C语言实现的“达夫设备”算法(Duff's Device),Jeff Greenberg被认为是将”达夫设备“代码从原始的C实现移植到JavaScript中的第一人,一个典型实现如下:

//为了方便调用,把它封装成一个函数

function duff(items) {
    var len = items.length, //缓存局部变量
        iterations = Math.floor(len / 8),  //商数,存放duff迭代次数
        startAt = len % 8,    //余数,存放duff一次迭代调用process的次数
        i = 0;
        
    do {
        switch(startAt) {
            case 0:
                process(items[i++]);
            case 7:
                process(items[i++]);
            case 6:
                process(items[i++]);
            case 5:
                process(items[i++]);
            case 4:
                process(items[i++]);
            case 3:
                process(items[i++]);
            case 2:
                process(items[i++]);
            case 1:
                process(items[i++]);
        }
        
        startAt = 0;
    } while(iterations--);    //书上是--iterations,貌似不对吧,应该是iterations--
};

Duff’s Device背后的基本理念是:每次循环中最多可调用8次process()。循环的迭代次数iterations为总数除以8,startAt用来存放余数,表示一次循环中应调用多少次process()。

假设我们定义process函数为:

function process(item) {

      alert(item);

};

调用一下duff函数:

duff([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);  //依次弹出1~12

//在这个调用过程中,我们可以知道iterations为1,startAt为4,也就是说,循环了两次,第一次循环调用process函数4次,第二次循环调用了8次。

可见,可以满足我们的遍历需求,而据书中介绍,如果迭代次数超过1000次,duff遍历算法的执行效率将明显提升。


据书中介绍,该算法有一个稍快的版本如下:(取消了switch语句,并将余数处理和主循环分开)

function duff2(items) {
    var len = items.length,
        i = len % 8;
    
    while(i) {
        process(items[i--]);
    };
    
    i = Math.floor(len / 8);
    while(i){
      process(items[i--]);
      process(items[i--]);
      process(items[i--]);
      process(items[i--]);
      process(items[i--]);
      process(items[i--]);
      process(items[i--]);
      process(items[i--]);
    }
};

<<高性能JavaScript>>这本书中是这样写这个算法的,不过仔细一看就知道,这个算法明显有问题,它无法遍历一个完整的数组,正确的算法应该是:

function duff2(items) {
    var len = items.length,

         t = 0,
        i = len % 8;
    
    while(i--) {
        process(items[t++]);
    };
    
    i = Math.floor(len / 8);
    while(i--){
      process(items[t++]);
      process(items[t++]);
      process(items[t++])
      process(items[t++]);
      process(items[t++]);
      process(items[t++]);
      process(items[t++]);
      process(items[t++]);
    }
};

这里有个老外写的Duff's device各种优化方法

这个算法对于处理数据量比较大的数组,还是很有执行效率的,比如对于处理省、市、区等级联下拉框的时候,如果你用数组来存放这些数据的话,就可以使用这个算法来遍历数组,可以感觉明显快很多。


还有一种优化数组遍历的方法,“分时处理”,这里有个不错的解析:

这个方法用着有点不放心(毕竟内部存在着定时器,搞不好数组还没遍历并更新完成,后面的代码都快执行完了,如果这个时候,后面的代码需要使用这个被遍历的数组,那更悲剧了,拿到的不一定是新数组),呵呵,视具体情况而定,比如说:

var a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];

//我现在想用分时处理的方式来遍历这个数组
chunk(a, function(v, i) { 
    a[i] = v + 1; // 假如我想在这里这个数组的每一项都+1
}, window);

//那么假如接下来,我想立即使用这个已经更新过的数组
console.log(a.toString());  //结果得到啥?

setTimeout(function() {
    console.log(a.toString()); //这里又是啥?
}, 100);


//未完待续,临时有事,晚上回去再继续~~~


先写到这里,不保证文章的正确性,仅供参考!

### 回答1: 好的,这个任务需要一定的时间和精力,我会尽力帮助你完成。首先我们需要了解一下Fruits 360数据集的情况和处理方式。Fruits 360数据集是一个包含约9万个水果图像的数据集,共有122种不同类型的水果,每种水果有多张图像。我们可以使用pytorch内置的torchvision库来加载数据集。 接下来,我们需要对数据集进行数据增强,以提高模型的泛化能力。数据增强可以包括旋转、翻转、缩放、裁剪等操作。这里我们可以使用torchvision.transforms库来实现数据增强,代码如下: ```python import torchvision.transforms as transforms train_transforms = transforms.Compose([ transforms.RandomResizedCrop(size=256, scale=(0.8, 1.0)), transforms.RandomRotation(degrees=15), transforms.RandomHorizontalFlip(), transforms.CenterCrop(size=224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) test_transforms = transforms.Compose([ transforms.Resize(size=256), transforms.CenterCrop(size=224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) ``` 这里我们定义了两个转换函数,train_transforms用于训练集数据增强,test_transforms用于测试集数据预处理。其中包括随机裁剪、随机旋转、随机水平翻转、中心裁剪、转换为张量和归一化。 接下来我们需要实现模型。这里我们可以使用ResNet模型,由于Fruits 360数据集比较大,我们可以使用ResNet50模型,代码如下: ```python import torch.nn as nn import torchvision.models as models class FruitsClassifier(nn.Module): def __init__(self, num_classes): super(FruitsClassifier, self).__init__() self.resnet = models.resnet50(pretrained=True) num_features = self.resnet.fc.in_features self.resnet.fc = nn.Linear(num_features, num_classes) def forward(self, x): x = self.resnet(x) return x ``` 这里我们定义了一个名为FruitsClassifier的类,继承自nn.Module。在类的构造函数中,我们使用ResNet50预训练模型,并将最后一层的全连接层替换为包含num_classes个输出的线性层。 接下来我们需要实现标准量化和批量归一化。标准化可以使得输入数据的均值为0,方差为1,从而加速模型的训练。批量归一化可以在每个批次中对输入数据进行标准化,从而增强模型的泛化能力。代码实现如下: ```python class FruitsClassifier(nn.Module): def __init__(self, num_classes): super(FruitsClassifier, self).__init__() self.resnet = models.resnet50(pretrained=True) num_features = self.resnet.fc.in_features self.resnet.fc = nn.Linear(num_features, num_classes) self.bn = nn.BatchNorm1d(num_features) def forward(self, x): x = self.resnet(x) x = self.bn(x) return x ``` 这里我们在类的构造函数中添加了一个BatchNorm1d层,并在forward函数中对输出进行标准化处理。 接下来我们需要实现权重衰减,梯度裁剪和Adam优化。权重衰减可以防止过拟合,梯度裁剪可以避免梯度爆炸,Adam优化可以提高模型的收敛速度。代码实现如下: ```python import torch.optim as optim criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-5) for epoch in range(num_epochs): running_loss = 0.0 for i, data in enumerate(trainloader, 0): inputs, labels = data inputs, labels = inputs.to(device), labels.to(device) optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() nn.utils.clip_grad_norm_(model.parameters(), max_norm=1) optimizer.step() running_loss += loss.item() print('Epoch [%d], Loss: %.4f' % (epoch+1, running_loss/len(trainloader))) ``` 这里我们定义了交叉熵损失函数,Adam优化器,并在训练循环中实现了权重衰减和梯度裁剪。 最后,我们需要将训练好的模型保存下来,并利用该模型实现一个有前后端的分类系统。代码实现如下: ```python from flask import Flask, request, jsonify from PIL import Image app = Flask(__name__) model = FruitsClassifier(num_classes=122) model.load_state_dict(torch.load('fruits_classifier.pth')) model.eval() @app.route('/predict', methods=['POST']) def predict(): file = request.files['file'] img = Image.open(file.stream) img_tensor = test_transforms(img).unsqueeze(0) with torch.no_grad(): outputs = model(img_tensor) _, predicted = torch.max(outputs.data, 1) result = {'class': str(predicted.item())} return jsonify(result) if __name__ == '__main__': app.run() ``` 这里我们使用Flask框架实现了一个简单的Web应用,通过POST请求上传图片并返回分类结果。我们首先加载训练好的模型,然后定义了一个名为predict的路由,该路由接收上传的文件,将其转换为张量并传入模型中进行分类预测,最后将结果封装为JSON格式返回给客户端。 完成以上步骤后,我们就可以运行该应用并测试分类系统的性能了。 ### 回答2: 基于PyTorch的水果图像识别与分类系统的设计与实现主要包含以下几个步骤: 1. 数据集准备:数据集使用Fruits 360,首先需要对数据集进行预处理和划分,包括图像加载、大小标准化、数据增强等操作。使用PyTorch提供的图像转换函数,如transforms.RandomRotation、transforms.RandomHorizontalFlip等实现数据增强。 2. 模型设计:选择适合水果图像分类的网络结构,如使用卷积神经网络(CNN)。根据具体的需求,可以选择不同的CNN模型,如ResNet、VGG等。在模型设计中加入标准量化和批量归一化(Batch Normalization)操作,以提高模型的性能和稳定性。 3. 模型训练:使用PyTorch提供的训练工具和优化器,结合权重衰减、梯度裁剪和Adam优化方法,对设计好的模型进行训练。权重衰减可以用于控制模型参数的大小,避免过拟合;梯度裁剪可以防止梯度爆炸问题;Adam优化算法可以加速模型的收敛速度。 4. 模型保存:在模型训练完成后,将训练好的模型保存下来,使用PyTorch提供的torch.save函数,保存模型的参数和结构。 5. 构建分类系统:利用保存好的模型,搭建一个有前后端的分类系统。前端可以使用Web开发技术,如HTML、CSS和JavaScript,实现用户界面和图像上传功能;后端可以使用PyTorch提供的加载模型和进行预测的函数,对用户上传的图像进行分类,并返回分类结果给前端展示。 通过以上步骤实现基于PyTorch的水果图像识别与分类系统,可以对水果图像进行准确的分类和识别,并提供用户友好的界面。 ### 回答3: 基于PyTorch的水果图像识别与分类系统的设计与实现,我们可以按照以下步骤进行: 1. 数据集准备: - 使用Fruits 360数据集,包括水果的各种图像。 - 运用PyTorch提供的数据增强函数对数据集进行预处理,例如随机旋转、翻转、裁剪等操作,以增强模型的泛化能力。 2. 模型设计: - 设计卷积神经网络模型,包括卷积层、池化层和全连接层等。 - 在模型中实现标准量化和批量归一化,提升模型的稳定性和泛化能力。 - 为了防止过拟合,引入权重衰减(Weight Decay)来限制权重的大小。 - 为了避免梯度爆炸问题,利用梯度裁剪(Gradient Clipping)来限制梯度的范围。 - 使用Adam优化器进行模型训练,加快训练速度和提高准确率。 3. 模型训练与保存: - 划分数据集为训练集和验证集,用于模型的训练与评估。 - 迭代训练模型,在每个epoch结束时计算验证集上的准确率,保存验证集上准确率最高的模型参数。 - 最后将训练好的模型保存下来,以备后续使用。 4. 前后端分类系统实现: - 在前端界面中添加上传图片的功能,用户可以选择一张水果图片进行上传。 - 后端接受到上传的图片后,调用训练好的模型进行图像识别与分类。 - 根据模型的分类结果,在前端界面中显示出预测的水果类别和置信度。 通过以上步骤,我们可以设计和实现一个基于PyTorch的水果图像识别与分类系统,并将训练好的模型保存下来供后续使用。用户通过前端界面上传水果图片后,后端会通过模型对该图片进行分类,并返回分类结果到前端界面展示。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值