菜单

“真实世界”全栈开发-3.8-博文的增查改删

2019年2月17日 - 金沙编程资讯

(对于模型中隐藏 $hidden 的字段,要求动用 makeVisible()
方法来目前平息 hidden, 放置写入数据库时出错)

为博文操作新建路由对象

跟在此之前的步调一样,大家第1要为博文的装有操作(其跟U凯雷德L都将是/api/articles)新建三个Router对象。

新建文件/routes/api/articles.js,写入:

const router = require('express').Router();
const passport = require('passport');
const mongoose = require('mongoose');
const Article = mongoose.model('Article');
const User = mongoose.model('User');
const auth = require('../auth');

module.exports = router;

相同地,那些路由索要登记到API的主路由上。打开routes/api/index.js,加入:

router.use('/profiles', require('./profiles'));
// +++
router.use('/articles', require('./articles'));
// +++

路由对象建好了,也注册到主路由上了,接下去就该兑现具体的中间件了。

多少写入的风味

崭新的数据集

在以前的训练/评估的首先片段,我们介绍了何等准备数据集:

全新的数据意味着不相同的练习/测试图像,差别的object name
label映射关系,不一致的互联网模型定义参数。首先,大家须求依照新的图像数据集生成模型的输入部分,约等于上面的八个文件。

  1. VGG_ILSVRC_16_layers_fc_reduced.caffemodel是预练习好的VGG_16的卷积层的参数,直接下载应用即可,那里不再介绍怎样重新磨练VGG_十四分类模型。

  2. labelmap_dbname.prototxt是标注文件中object的name和label的炫耀文件,一般项目不会太多,直接编写此文件即可。例如,2个或者的投射文件:

    item {
      name: "none_of_the_above"
      label: 0
      display_name: "background"
    }
    item {
      name: "Car"
      label: 1
      display_name: "car"
    }
    item {
      name: "Bus"
      label: 2
      display_name: "bus"
    }
    item {
      name: "Van"
      label: 3
      display_name: "van"
    }
    ...
    
  3. test_name_size.txt文件保留了装有测试图像的id height
    width信息,由create_list.sh剧本达成创制。通过分析create_list.sh本子可精通,该脚本共创造了三个txt文件,分别是trainval.txt
    test.txtdbname_name_size.txt

    • trainval.txttest.txt中,每一行保存了图像文件的路径和图像标注文件的门径,中间以空格分开。片段如下:

    VOC2012/JPEGImages/2010_003429.jpg VOC2012/Annotations/2010_003429.xml
    VOC2007/JPEGImages/008716.jpg VOC2007/Annotations/008716.xml
    VOC2012/JPEGImages/2009_004804.jpg VOC2012/Annotations/2009_004804.xml
    VOC2007/JPEGImages/005293.jpg VOC2007/Annotations/005293.xml
    

    瞩目,trainval中的顺序是乱糟糟的,test中的顺序不必打乱。

    • test_name_size.txt文本是由.../caffe/get_image_size次第生成的,其源码位于.../caffe/tools/get_image_size.cpp中。那段程序的作用是基于test.txt中提供的测试图像的路子消息和数码集根目录消息(两段路径拼合得到图像的相对路径),自动总括每张图像的heightwidthget_image_size.cpp中的宗旨代码段为:

    // Storing to outfile
    boost::filesystem::path root_folder(argv[1]);
    std::ofstream outfile(argv[3]);
    if (!outfile.good()) {
      LOG(FATAL) << "Failed to open file: " << argv[3];
    }
    int height, width;
    int count = 0;
    for (int line_id = 0; line_id < lines.size(); ++line_id) {
      boost::filesystem::path img_file = root_folder / lines[line_id].first;
      GetImageSize(img_file.string(), &height, &width);
      std::string img_name = img_file.stem().string();
      if (map_name_id.size() == 0) {
        outfile << img_name << " " << height << " " << width << std::endl;
      } else {
        CHECK(map_name_id.find(img_name) != map_name_id.end());
        int img_id = map_name_id.find(img_name)->second;
        outfile << img_id << " " << height << " " << width << std::endl;
      }
    
      if (++count % 1000 == 0) {
        LOG(INFO) << "Processed " << count << " files.";
      }
    }
    // write the last batch
    if (count % 1000 != 0) {
      LOG(INFO) << "Processed " << count << " files.";
    }
    outfile.flush();
    outfile.close();
    

    保存到test_name_size.txt中的内容片段如下:

    000001 500 353
    000002 500 335
    000003 375 500
    000004 406 500
    000006 375 500
    000008 375 500
    000010 480 354
    

    现在,trainval.txt
    test.txttest_name_size.txt的始末早已很清晰了,可以使用现成的代码程序,适当修改图像数据集名称和路线就可以创立那多个公文。当然,也可以依照本身的编程喜好,重新编写脚本生成符合地方格式的txt文件即可。

  4. dbname_trainval_lmdb
    转移该数据库文件的程序为create_data.sh,其主导代码是实施python脚本.../caffe/scripts/create_annoset.py,该脚本须要事先准备的
    labelmap_dbname.prototxttrainval.txt
    作为输入,以及多少个可安插项。
    .../caffe/scripts/create_annoset.py剧本的主导代码是实践.../caffe/build/tools/convert_annoset程序。labelmap_dbname.prototxt

    trainval.txt就是为convert_annoset次第准备的,其源码在.../caffe/tools/convert_annoset.cpp中。成立并写入数据库的基本代码片段如下:

// 创建一个新的数据库
scoped_ptr<db::DB> db(db::GetDB(FLAGS_backend));
db->Open(argv[3], db::NEW);
scoped_ptr<db::Transaction> txn(db->NewTransaction());

// 把数据存储到数据库
std::string root_folder(argv[1]);
AnnotatedDatum anno_datum;
Datum* datum = anno_datum.mutable_datum();
int count = 0;
int data_size = 0;
bool data_size_initialized = false;

for (int line_id = 0; line_id < lines.size(); ++line_id) {
  bool status = true;
  std::string enc = encode_type;
  if (encoded && !enc.size()) {
    // Guess the encoding type from the file name
    string fn = lines[line_id].first;
    size_t p = fn.rfind('.');
    if ( p == fn.npos )
      LOG(WARNING) << "Failed to guess the encoding of '" << fn << "'";
    enc = fn.substr(p);
    std::transform(enc.begin(), enc.end(), enc.begin(), ::tolower);
  }
  filename = root_folder + lines[line_id].first;
  if (anno_type == "classification") {
    label = boost::get<int>(lines[line_id].second);
    status = ReadImageToDatum(filename, label, resize_height, resize_width,
        min_dim, max_dim, is_color, enc, datum);
  } else if (anno_type == "detection") {
    labelname = root_folder + boost::get<std::string>(lines[line_id].second);
    status = ReadRichImageToAnnotatedDatum(filename, labelname, resize_height,
        resize_width, min_dim, max_dim, is_color, enc, type, label_type,
        name_to_label, &anno_datum);
    anno_datum.set_type(AnnotatedDatum_AnnotationType_BBOX);
  }
  if (status == false) {
    LOG(WARNING) << "Failed to read " << lines[line_id].first;
    continue;
  }
  if (check_size) {
    if (!data_size_initialized) {
      data_size = datum->channels() * datum->height() * datum->width();
      data_size_initialized = true;
    } else {
      const std::string& data = datum->data();
      CHECK_EQ(data.size(), data_size) << "Incorrect data field size "
          << data.size();
    }
  }
  // 序列化
  string key_str = caffe::format_int(line_id, 8) + "_" + lines[line_id].first;

  // 把数据Put到数据库
  string out;
  CHECK(anno_datum.SerializeToString(&out));
  txn->Put(key_str, out);

  if (++count % 1000 == 0) {
    // Commit db
    txn->Commit();
    txn.reset(db->NewTransaction());
    LOG(INFO) << "Processed " << count << " files.";
  }// end if
}//end for
// 写入最后一个batch的数据
if (count % 1000 != 0) {
  txn->Commit();
  LOG(INFO) << "Processed " << count << " files.";
}

那段代码中最着重的一行是对ReadRichImageToAnnotatedDatum()办法的调用,将图像文件和标注新闻一起写入到了anno_datum变量中,再体系化,提交到数据库缓存区,缓存到自然数量的记录后两次性写入数据库。

ReadRichImageToAnnotatedDatum()措施由Caffe提供,是caffe/src/util/io.cpp中定义的三个艺术,该措施及其其调用的ReadImageToDatum方法和GetImageSize方法源码如下:

bool ReadImageToDatum(const string& filename, const int label,
    const int height, const int width, const int min_dim, const int max_dim,
    const bool is_color, const std::string & encoding, Datum* datum) {
  cv::Mat cv_img = ReadImageToCVMat(filename, height, width, min_dim, max_dim,
                                    is_color);
  if (cv_img.data) {
    if (encoding.size()) {
      if ( (cv_img.channels() == 3) == is_color && !height && !width &&
          !min_dim && !max_dim && matchExt(filename, encoding) ) {
        datum->set_channels(cv_img.channels());
        datum->set_height(cv_img.rows);
        datum->set_width(cv_img.cols);
        return ReadFileToDatum(filename, label, datum);
      }
      EncodeCVMatToDatum(cv_img, encoding, datum);
      datum->set_label(label);
      return true;
    }
    CVMatToDatum(cv_img, datum);
    datum->set_label(label);
    return true;
  } else {
    return false;
  }
}

void GetImageSize(const string& filename, int* height, int* width) {
  cv::Mat cv_img = cv::imread(filename);
  if (!cv_img.data) {
    LOG(ERROR) << "Could not open or find file " << filename;
    return;
  }
  *height = cv_img.rows;
  *width = cv_img.cols;
}

bool ReadRichImageToAnnotatedDatum(const string& filename,
    const string& labelfile, const int height, const int width,
    const int min_dim, const int max_dim, const bool is_color,
    const string& encoding, const AnnotatedDatum_AnnotationType type,
    const string& labeltype, const std::map<string, int>& name_to_label,
    AnnotatedDatum* anno_datum) {
  // Read image to datum.
  bool status = ReadImageToDatum(filename, -1, height, width,
                                 min_dim, max_dim, is_color, encoding,
                                 anno_datum->mutable_datum());
  if (status == false) {
    return status;
  }
  anno_datum->clear_annotation_group();
  if (!boost::filesystem::exists(labelfile)) {
    return true;
  }
  switch (type) {
    case AnnotatedDatum_AnnotationType_BBOX:
      int ori_height, ori_width;
      GetImageSize(filename, &ori_height, &ori_width);
      if (labeltype == "xml") {
        return ReadXMLToAnnotatedDatum(labelfile, ori_height, ori_width,
                                       name_to_label, anno_datum);
      } else if (labeltype == "json") {
        return ReadJSONToAnnotatedDatum(labelfile, ori_height, ori_width,
                                        name_to_label, anno_datum);
      } else if (labeltype == "txt") {
        return ReadTxtToAnnotatedDatum(labelfile, ori_height, ori_width,
                                       anno_datum);
      } else {
        LOG(FATAL) << "Unknown label file type.";
        return false;
      }
      break;
    default:
      LOG(FATAL) << "Unknown annotation type.";
      return false;
  }
}

能够见见在地点的艺术中继续调用了io.cpp中的五个艺术ReadFileToDatumReadXMLToAnnotatedDatum,分别把图像和图像的标注XML写入到了anno_datum中。其中,图像保存到了anno_datummutable_datum中,XML标注音信被保留到了anno_datumanno_group->anno->bbox中,anno_group还保存了label等信息。

  1. dbname_test_lmdb
    4.dbname_trainval_lmdb
  2. 使用examples/ssd.ipynb审验上不熟悉成的文本的不易

\[1\]We use
examples/convert_model.ipynb
to extract a VOC model from a pretrained COCO model.

  1. 概念好模型 xxx.php
  2. 概念好数据变动的规则 database/factories/XxxlFactory.php
  3. 写入生成数据的代码,控制好转变的多少数目,对转移后的多少做出修改 database/seeds/XxxTableSeeder.php
  4. 注册

始建博文的模子

如本体系第②局地所说的,博文应该有如下数据:

大家先来为地点的数额新建Mongoose情势,并将其注册为可用的模型。新建models/Article.js文件,写入:

const mongoose = require('mongoose');
const uniqueValidator = require('mongoose-unique-validator');
const slug = require('slug');

const ArticleSchema = new mongoose.Schema({
  slug: {type: String, lowercase: true, unique: true},
  title: String,
  description: String,
  body: String,
  favoritesCount: {type: Number, default: 0},
  tagList: [{type: String}],
  author: {type: mongoose.Schema.Types.ObjectId, ref: 'User'}
}, {timestamps: true});

ArticleSchema.plugin(uniqueValidator, {message: 'is already taken'});

mongoose.model('Article', ArticleSchema);

犹如用户方式里一样,大家那边运用了mongoose-unique-validator来验证slug是唯一的。

上边的代码已经导入了slug包。接下来,大家要定义1个新章程,使用这么些包来生成博文slug。为了有限协理slug的唯一性,大家会在原标题生成的字符串后再添加两个随机字符。

ArticleSchema.plugin(uniqueValidator, {message: 'is already taken'});

// +++
ArticleSchema.methods.slugify = function() {
  this.slug = slug(this.title) + '-' + (Math.random() * Math.pow(36, 6) | 0).toString(36);
};
// +++

mongoose.model('Article', ArticleSchema);

什么样时候调用这一个格局呢?每当一篇新博文创立后要么是一篇已有博文的题目暴发变更后,往数据库保存的时候,大家须要调用那么些办法生成新的slug。

大家得以倚重Mongoose中间件来在上述的场地自动调用slugify方法。

models/Article.js投入以下代码:

// +++
ArticleSchema.pre('validate', function (next) {
  if (!this.slug || this.isModified('title'))
    this.slugify();
  return next();
});
// +++

mongoose.model('Article', ArticleSchema);

slug的变更应该暴发在Mongoose检验数据此前,否则没有slug字段,检验必然战败。那也是下面代码中pre'validate'发挥的情趣。

别的注意,那里回调函数中的this本着的是眼前的博文对象;因而,定义Mongoose中间件时也无法用箭头函数。

末段,大家还索要加上二个艺术,再次回到博文的JSON对象。不要忘了投入Mongoose自动创建的createAtupdateAt那两条数据。

// +++
ArticleSchema.methods.toJSONFor = function (user) {
  return {
    slug: this.slug,
    title: this.title,
    description: this.description,
    body: this.body,
    createdAt: this.createdAt,
    updatedAt: this.updatedAt,
    tagList: this.tagList,
    favoritesCount: this.favoritesCount,
    author: this.author.toProfileJSONFor(user)
  };
};
// +++

ArticleSchema.pre('validate', ...);

值得提出的是,author一行,大家用到了前边为用户模型定义的toProfileJSONFor(user)方法。

最终,我们必要在后端应用中运营方面的台本,否则之后的中间件无法利用Article模型。

app.js文本里投入一行代码:

require('./models/User');
// +++
require('./models/Article');
// +++
require('./config/passport');

到此博文模型就创办完毕了。我们接下去要做的就是添加增查改删博文的路由及相应的中间件。

写入平稳、持续、高并发高吞吐:时序数据的写入是相比平静的,那点与行使数据不相同,应用数据一般与使用的访问量成正比,而选用的访问量平日存在波峰波谷。时序数据的发出寻常是以一个永恒的时辰频率爆发,不会受其他因素的掣肘,其数额变动的快慢是对峙比较稳定的。时序数据是由各类个体独立生成,所以当个人数量众多时,经常写入的产出和吞吐量都以相比较高的,越发是在物联网场景下。写入并发和吞吐量,可以简简单单的通过个人数量和数量生成频率来计量,例假设您有1000个个体以10秒的频率暴发多少,则你平分每秒发生的现身和写入量就是100。
写多读少:时序数据上95%-99%的操作都是写操作,是首屈一指的写多读少的数目。那与其数额个性相关,例如监控数据,你的监察项大概很多,不过你真的去读的大概比较少,平时只会关注几个特定的紧要目标恐怕在一定的场景下才会去读数据。
实时写入近期变化的数据,无更新:时序数据的写入是实时的,且每一遍写入都是近日变动的数目,那与其数量变动的个性有关,因为其数额变化是随着时间推进的,而新生成的多少会实时的展开写入。数据写入无更新,在时光这么些维度上,随着岁月的推进,每趟数据都是新数据,不会存在旧数据的翻新,可是不清除人为的对数据做改正。

训练/评估

  1. 教练你自个儿的模子并评估.

# 创建模型定义文件并保存模型训练快照到如下路径:
#   - $CAFFE_ROOT/models/VGGNet/VOC0712/SSD_300x300/
# and job file, log file, and the python script in:
#   - $CAFFE_ROOT/jobs/VGGNet/VOC0712/SSD_300x300/
# 保存当前评估结果到:
#   - $HOME/data/VOCdevkit/results/VOC2007/SSD_300x300/
# 120K次迭代之后,应该可以达到77.*的mAP.
python examples/ssd/ssd_pascal.py

如果不乐意自个儿锻炼模型,可以在here下载预陶冶好的模型.注意是用PASCAL
VOC数据集练习的。

通过分析ssd_pascal.py的源码,可以精通磨炼ssd模型需求多少个公文输入,分别是
train_data = "examples/VOC0712/VOC0712_trainval_lmdb"
test_data = "examples/VOC0712/VOC0712_test_lmdb"
name_size_file = "data/VOC0712/test_name_size.txt"
pretrain_model = "models/VGGNet/VGG_ILSVRC_16_layers_fc_reduced.caffemodel"
label_map_file = "data/VOC0712/labelmap_voc.prototxt"
train_net_file = "models/VGGNet/VOC0712/SSD_300x300/train.prototxt"
test_net_file = "models/VGGNet/VOC0712/SSD_300x300/test.prototxt"
deploy_net_file = "models/VGGNet/VOC0712/SSD_300x300/deploy.prototxt"
solver_file = "models/VGGNet/VOC0712/SSD_300x300/solver.prototxt"

其中,train_datatest_data是之前创设的LMDB数据库文件,用于练习和测试模型。name_size_file是事先制造的测试图像集的图像id和size文件,用于模型的测试。pretrain_model是base
network部分(VGG_16的卷积层)的预练习参数。label_map_file封存的是实体的name和label的炫耀文件,用于陶冶和测试。那八个文件是事先都准备好的.

末端的多少个公文,train_net_file test_net_file
deploy_net_filesolver_file是在ssd_pascal.py剧本中根据模型定义和教练方针参数自动生成的。例如,train_net_file,也就是train.prototxt,生成语句是shutil.copy(train_net_file, job_dir),具体的代码片段如下:

# Create train net.
net = caffe.NetSpec()
net.data, net.label = CreateAnnotatedDataLayer(train_data, batch_size=batch_size_per_device,
        train=True, output_label=True, label_map_file=label_map_file,
        transform_param=train_transform_param, batch_sampler=batch_sampler)

VGGNetBody(net, from_layer='data', fully_conv=True, reduced=True, dilated=True,
    dropout=False)

AddExtraLayers(net, use_batchnorm, lr_mult=lr_mult)

mbox_layers = CreateMultiBoxHead(net, data_layer='data', from_layers=mbox_source_layers,
        use_batchnorm=use_batchnorm, min_sizes=min_sizes, max_sizes=max_sizes,
        aspect_ratios=aspect_ratios, steps=steps, normalizations=normalizations,
        num_classes=num_classes, share_location=share_location, flip=flip, clip=clip,
        prior_variance=prior_variance, kernel_size=3, pad=1, lr_mult=lr_mult)

# Create the MultiBoxLossLayer.
name = "mbox_loss"
mbox_layers.append(net.label)
net[name] = L.MultiBoxLoss(*mbox_layers, multibox_loss_param=multibox_loss_param,
        loss_param=loss_param, include=dict(phase=caffe_pb2.Phase.Value('TRAIN')),
        propagate_down=[True, True, False, False])

with open(train_net_file, 'w') as f:
    print('name: "{}_train"'.format(model_name), file=f)
    print(net.to_proto(), file=f)
shutil.copy(train_net_file, job_dir)
  1. 动用新型模型快照评估模型.

  # 如果你需要对训练的模型进行评估,执行脚本:
  python examples/ssd/score_ssd_pascal.py
  1. 接纳webcam录像头测试模型. 注意: 按 <kbd>esc</kbd> 为止.

  # 连接webcam摄像头和预训练的模型进行演示,运行:
  python examples/ssd/ssd_pascal_webcam.py

Here
显示了三个在
MSCOCO
数据集上陶冶的模型SSD500的言传身教录制.

  1. 查看
    examples/ssd_detect.ipynb
    或者
    examples/ssd/ssd_detect.cpp金沙网上娱乐赌场
    怎么样采用ssd模型检测物体. 查看
    examples/ssd/plot_detections.py
    如何绘制 ssd_detect.cpp的检测结果.

  2. 假设利用其余数据集练习, 请参考data/OTHE奇骏DATASET 了然越来越多细节.
    近年来支撑COCO 和 ILSVGL450C二〇一六多少集. 提出采纳
    examples/ssd.ipynb
    检查新的数据集是或不是适合要求.

转移博文

端点是PUT /api/articles/:slug,身份验证是须求的,请求体的数据会覆盖相应字段。其余,大家还需确保当前报到的用户必须是该博文的笔者,否则再次回到403。

routes/api/articles.js中写入:

// +++
const checkAuthor = (req, res, next) => {
  if (!res.locals.user.equals(res.locals.article.author))
    return res.status(403).json({errors: {user: 'not the author'}});
  return next();
};

router.put('/:slug', auth.required, loadCurrentUser, checkAuthor,
  (req, res, next) => {
    ['title', 'description', 'body'].forEach(propName => {
      if (!req.body.article.hasOwnProperty(propName)) return;
      res.locals.article[propName] = req.body.article[propName];
    });
    res.locals.article.save()
      .then(() => next())
      .catch(next);
  }, respondArticle);
// +++

module.exports = router;

  从DB-Engines的数据库序列流行度趋势榜上得以观看,时序数据库(Time
Series DB)的流行度在方今的两年内,一贯都以保持三个很高的升高方向。
  接下去笔者会写几篇小说,分别来分析:
  1. 时序数据的基本概念,包蕴模型、天性和骨干的询问和处理操作。
  2. 几个流行开源时序数据库的尾部完结分析
  3. Ali云表格存储(TableStore)的时序数据存储和测算化解方案

SSD: Single Shot MultiBox Detector

By Wei
Liu,
Dragomir
Anguelov,
Dumitru
Erhan,
Christian
Szegedy,
Scott
Reed,
澳门金沙官方网手机版,Cheng-Yang
Fu,
Alexander C. Berg.

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图