点击下方卡片,关注3D视觉工坊公众号

3D视觉干货第一时间送达

作者:王浩,3D视觉开发者社区签约作者,毕业于北京航空航天大学,人工智能领域优质创作者,CSDN博客认证专家。

编辑:3D视觉开发者社区

摘要

YOLOX: Exceeding YOLO Series in 2021

•代码:https://github.com/Megvii-BaseDetection/YOLOX•论文:https://arxiv.org/abs/2107.08430

YOLOX 是旷视开源的高性能检测器。旷视的研究者将解耦头、数据增强、无锚点以及标签分类等目标检测领域的优秀进展与 YOLO 进行了巧妙的集成组合,提出了 YOLOX,不仅实现了超越 YOLOv3、YOLOv4 和 YOLOv5 的 AP,而且取得了极具竞争力的推理速度。如下图:

其中YOLOX-L版本以 68.9 FPS 的速度在 COCO 上实现了 50.0% AP,比 YOLOv5-L 高出 1.8% AP!还提供了支持 ONNX、TensorRT、NCNN 和 Openvino 的部署版本,本文将详细介绍如何使用 YOLOX进行物体检测。

一、 配置环境

本机的环境:

操作系统 Win10
Pytorch版本 1.8.0
Cuda版本 11.1

1.1 下载源码

GitHub地址:https://github.com/Megvii-BaseDetection/YOLOX,下载完成后放到D盘根目录,然后用PyCharm打开。

1.2 安装依赖包

点击“Terminal”,如下图,

然后执行下面的命令,安装所有的依赖包。

pip install -r requirements.txt

1.3 安装yolox

python setup.py install

看到如下信息,则说明安装完成了

1.4 安装apex

APEX是英伟达开源的,完美支持PyTorch框架,用于改变数据格式来减小模型显存占用的工具。其中最有价值的是amp(Automatic Mixed Precision),将模型的大部分操作都用Float16数据类型测试,一些特别操作仍然使用Float32。并且用户仅仅通过三行代码即可完美将自己的训练代码迁移到该模型。实验证明,使用Float16作为大部分操作的数据类型,并没有降低参数,在一些实验中,反而由于可以增大Batch size,带来精度上的提升,以及训练速度上的提升。

安装步骤:

1) 到官网下载apex,地址:mirrors / nvidia / apex · CODE CHINA (csdn.net)[1]

2) 下载完成后,解压后,在Shell里,进入到apex-master中。

3) 执行安装命令

pip install -r requirements.txtpython setup.py install

看到如下log,则表明安装成功。

1.5 安装pycocotools

pip install pycocotools

注:如果出现环境问题,可以参考博客:https://blog.csdn.net/hhhhhhhhhhwwwwwwwwww/article/details/105858384

1.6 验证环境

下载预训练模型,本文选用的是YOLOX-s,下载地址:https://github.com/Megvii-BaseDetection/YOLOX/releases/download/0.1.1rc0/yolox_s.pth。

下载完成后,将预训练模型放到工程的根目录,如下图:

然后验证环境,执行:

python tools/demo.py image -f exps/default/yolox_s.py -c ./yolox_s.pth --path assets/dog.jpg --conf 0.3 --nms 0.65 --tsize 640 --save_result --device gpu

参数说明

参数 说明
-c 权重的路径
-path 测试图片的路径
-conf 置信度阈值
-nms nms的IOU阈值
-tsize 测试图片resize的大小
-save_result 是否保存推理结果
--device 选用gpu或cpu推理

查看运行结果:

看到上图说明环境没有问题了。

二、 制作数据集

数据集我们采用VOC数据集,原始数据集是Labelme标注的数据集。下载地址:https://pan.baidu.com/s/1kj-diqEK2VNVqd2n4ROa5g (提取码rrnz)

新建labelme2voc.py文件

import os
from typing import List, Any
import numpy as np
import codecs
import json
from glob import glob
import cv2
import shutil
from sklearn.model_selection import train_test_split# 1.标签路径labelme_path = "LabelmeData/"  # 原始labelme标注数据路径
saved_path = "VOC2007/"  # 保存路径
isUseTest = True  # 是否创建test集
# 2.创建要求文件夹
if not os.path.exists(saved_path + "Annotations"):os.makedirs(saved_path + "Annotations")
if not os.path.exists(saved_path + "JPEGImages/"):os.makedirs(saved_path + "JPEGImages/")
if not os.path.exists(saved_path + "ImageSets/Main/"):os.makedirs(saved_path + "ImageSets/Main/")
# 3.获取待处理文件
files = glob(labelme_path + "*.json")
files = [i.replace("\\", "/").split("/")[-1].split(".json")[0] for i in files]
print(files)
# 4.读取标注信息并写入 xml
for json_file_ in files:json_filename = labelme_path + json_file_ + ".json"json_file = json.load(open(json_filename, "r", encoding="utf-8"))height, width, channels = cv2.imread(labelme_path + json_file_ + ".jpg").shape
with codecs.open(saved_path + "Annotations/" + json_file_ + ".xml", "w", "utf-8") as xml:xml.write('<annotation>\n')xml.write('\t<folder>' + 'WH_data' + '</folder>\n')xml.write('\t<filename>' + json_file_ + ".jpg" + '</filename>\n')xml.write('\t<source>\n')xml.write('\t\t<database>WH Data</database>\n')xml.write('\t\t<annotation>WH</annotation>\n')xml.write('\t\t<image>flickr</image>\n')xml.write('\t\t<flickrid>NULL</flickrid>\n')xml.write('\t</source>\n')xml.write('\t<owner>\n')xml.write('\t\t<flickrid>NULL</flickrid>\n')xml.write('\t\t<name>WH</name>\n')xml.write('\t</owner>\n')xml.write('\t<size>\n')xml.write('\t\t<width>' + str(width) + '</width>\n')xml.write('\t\t<height>' + str(height) + '</height>\n')xml.write('\t\t<depth>' + str(channels) + '</depth>\n')xml.write('\t</size>\n')xml.write('\t\t<segmented>0</segmented>\n')
for multi in json_file["shapes"]:points = np.array(multi["points"])labelName = multi["label"]xmin = min(points[:, 0])xmax = max(points[:, 0])ymin = min(points[:, 1])ymax = max(points[:, 1])label = multi["label"]
if xmax <= xmin:
pass
elif ymax <= ymin:
pass
else:xml.write('\t<object>\n')xml.write('\t\t<name>' + labelName + '</name>\n')xml.write('\t\t<pose>Unspecified</pose>\n')xml.write('\t\t<truncated>1</truncated>\n')xml.write('\t\t<difficult>0</difficult>\n')xml.write('\t\t<bndbox>\n')xml.write('\t\t\t<xmin>' + str(int(xmin)) + '</xmin>\n')xml.write('\t\t\t<ymin>' + str(int(ymin)) + '</ymin>\n')xml.write('\t\t\t<xmax>' + str(int(xmax)) + '</xmax>\n')xml.write('\t\t\t<ymax>' + str(int(ymax)) + '</ymax>\n')xml.write('\t\t</bndbox>\n')xml.write('\t</object>\n')
print(json_filename, xmin, ymin, xmax, ymax, label)xml.write('</annotation>')
# 5.复制图片到 VOC2007/JPEGImages/下
image_files = glob(labelme_path + "*.jpg")
print("copy image files to VOC007/JPEGImages/")
for image in image_files:shutil.copy(image, saved_path + "JPEGImages/")
# 6.split files for txt
txtsavepath = saved_path + "ImageSets/Main/"
ftrainval = open(txtsavepath + '/trainval.txt', 'w')
ftest = open(txtsavepath + '/test.txt', 'w')
ftrain = open(txtsavepath + '/train.txt', 'w')
fval = open(txtsavepath + '/val.txt', 'w')
total_files = glob("./VOC2007/Annotations/*.xml")
total_files = [i.replace("\\", "/").split("/")[-1].split(".xml")[0] for i in total_files]
trainval_files = []
test_files = []
if isUseTest:trainval_files, test_files = train_test_split(total_files, test_size=0.15, random_state=55)
else:trainval_files = total_files
for file in trainval_files:ftrainval.write(file + "\n")
# split
train_files, val_files = train_test_split(trainval_files, test_size=0.15, random_state=55)
# train
for file in train_files:ftrain.write(file + "\n")
# val
for file in val_files:fval.write(file + "\n")
for file in test_files:
print(file)ftest.write(file + "\n")
ftrainval.close()
ftrain.close()
fval.close()
ftest.close()

运行上面的代码就可以得到VOC2007数据集。如下图所示:

VOC的目录如下,所以要新建data/VOCdevkit目录,然后将上面的结果复制进去

├── data
│   ├── VOCdevkit
│   │   ├── VOC2007
│   │   │   ├── Annotations #xml文件
│   │   │   ├── JPEGImages #图片
│   │   │   ├── ImageSets
│   │   │   │   ├── Main
│   │   │   │   │   ├── test.txt
│   │   │   │   │   ├── trainval.txt

到这里,数据集制作完成。

三、 修改数据配置文件

3.1 修改类别

文件路径:exps/example/yolox_voc/yolox_voc_s.py,本次使用的类别有2类,所以将num_classes修改为2。

打开yolox/data/datasets/voc_classes.py文件,修改为自己的类别名:

3.2 修改数据集目录

文件路径:exps/example/yolox_voc/yolox_voc_s.py,data_dir修改为“./data/VOCdevkit”,image_sets删除2012的,最终结果如下:

接着往下翻,修改test的路径,如下图:

打开yolox/data/datasets/voc.py,这里面有个错误。画框位置,将大括号的“%s”去掉,否则验证的时候一直报找不到文件的错误。

修改完成后,执行

python setup.py install

重新编译yolox。

四、 训练

推荐使用命令行的方式训练。

执行命令:

python tools/train.py -f exps/example/yolox_voc/yolox_voc_s.py -d 1 -b 4 --fp16  -c yolox_s.pth

就可以开始训练了。如果不喜欢使用命令行的方式,想直接运行train.py,那就需要就如train.py修改参数了。首先把train.py从tools里面复制一份到工程的根目录(建议这样做,否则需要修改的路径比较多,新手容易犯错误),如图:

打开,修改里面的参数。需要修改的参数如下:

parser.add_argument("-b", "--batch-size", type=int, default=4, help="batch size")parser.add_argument(
"-d", "--devices", default=1, type=int, help="device for training"
)parser.add_argument(
"-f",
"--exp_file",
default="exps/example/yolox_voc/yolox_voc_s.py",type=str,help="plz input your expriment description file",
)parser.add_argument("-c", "--ckpt", default='yolox_s.pth', type=str, help="checkpoint file")parser.add_argument(
"--fp16",dest="fp16",
default=True,action="store_true",help="Adopting mix precision training.",
)

按照上面的参数配置就可以运行了,如下图:

如果训练了一段时间,再想接着以前的模型再训练,应该如何做呢?修改train.py的参数即可,需要修改的参数如下:

parser.add_argument(
"--resume", default=True, action="store_true", help="resume training"
)parser.add_argument("-c", "--ckpt", default='YOLOX_outputs/yolox_voc_s/best_ckpt.pth', type=str, help="checkpoint file")parser.add_argument(
"-e",
"--start_epoch",
default=100,type=int,help="resume training start epoch",
)

命令行:

python tools/train.py -f exps/example/yolox_voc/yolox_voc_s.py -d 1 -b 4 -c YOLOX_outputs/yolox_voc_s/latest_ckpt.pth.tar -resume -start_epoch=100

再次训练,你发现epoch不是从0开始了。

五、 测试

修改yolox/data/datasets/\__init__.py,导入“VOC_CLASSES”,如下图:

修改tools/demo.py中代码,将“COCO_CLASSES”,改为“VOC_CLASSES”。

将“295”行的Predictor类初始化传入的“COCO_CLASSES”改为“VOC_CLASSES”,如下图:

5.1 单张图片预测

使用训练好的模型进行测试。测试调用tools/demo.py,先用命令行的方式演示:

python tools/demo.py image -f exps/example/yolox_voc/yolox_voc_s.py -c YOLOX_outputs/yolox_voc_s/latest_ckpt.pth --path ./assets/aircraft_107.jpg --conf 0.3 --nms 0.65 --tsize 640 --save_result --device gpu

运行结果:

如果不想使用命令行,将demo.py复制一份放到工程的根目录,然后修改里面的参数。

parser = argparse.ArgumentParser("YOLOX Demo!")parser.add_argument("-do","--demo", default="image", help="demo type, eg. image, video and webcam")parser.add_argument("-expn", "--experiment-name", type=str, default=None)parser.add_argument("-n", "--name", type=str, default=None, help="model name")parser.add_argument(
"--path", default="./assets/aircraft_107.jpg", help="path to images or video"
)
# exp fileparser.add_argument(
"-f",
"--exp_file",
default="exps/example/yolox_voc/yolox_voc_s.py",type=str,help="pls input your expriment description file",
)parser.add_argument("-c", "--ckpt", default="YOLOX_outputs/yolox_voc_s/best_ckpt.pth", type=str, help="ckpt for eval")parser.add_argument(
"--device",
default="gpu",type=str,help="device to run our model, can either be cpu or gpu",
)parser.add_argument("--conf", default=0.3, type=float, help="test conf")parser.add_argument("--nms", default=0.45, type=float, help="test nms threshold")parser.add_argument("--tsize", default=640, type=int, help="test img size")parser.add_argument(
"--fp16",dest="fp16",
default=False,action="store_true",help="Adopting mix precision evaluating.",
)parser.add_argument(
"--fuse",dest="fuse",
default=False,action="store_true",help="Fuse conv and bn for testing.",
)

然后直接运行demo.py,运行结果如下图:

5.2 批量预测

批量预测很简单,将path参数由文件路径改为图片的文件夹路径就可以。例:

parser.add_argument(
"--path", default="./assets", help="path to images or video"
)

这样就可以预测assets文件夹下面所有的图片了。

六、 保存测试结果

demo.py只有将结果画到图片上,没有保存结果,所以要增加这部分的功能。

打开yolox/utils.visualize.py文件,修改vis方法,将结果返回到上层的方法。

def vis(img, boxes, scores, cls_ids, conf=0.5, class_names=None):result_list = []
for i in range(len(boxes)):box = boxes[i]cls_id = int(cls_ids[i])score = scores[i]
if score < conf:
continuex0 = int(box[0])y0 = int(box[1])x1 = int(box[2])y1 = int(box[3])class_name = class_names[cls_id]one_line = (str(x0), str(y0), str(x1), str(y1), class_name, str(float(score)))str_one_line = " ".join(one_line)result_list.append(str_one_line)color = (_COLORS[cls_id] * 255).astype(np.uint8).tolist()text = '{}:{:.1f}%'.format(class_name, score * 100)txt_color = (0, 0, 0) if np.mean(_COLORS[cls_id]) > 0.5 else (255, 255, 255)font = cv2.FONT_HERSHEY_SIMPLEXtxt_size = cv2.getTextSize(text, font, 0.4, 1)[0]cv2.rectangle(img, (x0, y0), (x1, y1), color, 2)txt_bk_color = (_COLORS[cls_id] * 255 * 0.7).astype(np.uint8).tolist()cv2.rectangle(img,
(x0, y0 + 1),
(x0 + txt_size[0] + 1, y0 + int(1.5 * txt_size[1])),txt_bk_color,
-1
)cv2.putText(img, text, (x0, y0 + txt_size[1]), font, 0.4, txt_color, thickness=1)return img, result_list

在demo.py的178行增加获取结果,并返回上层方法,如下图:

然后在182,修改image_demo函数,增加获取结果,保存结果的逻辑,具体代码如下:

def image_demo(predictor, vis_folder, path, current_time, save_result):
if os.path.isdir(path):files = get_image_list(path)
else:files = [path]files.sort()
for image_name in files:outputs, img_info = predictor.inference(image_name)result_image, result_list = predictor.visual(outputs[0], img_info, predictor.confthre)
print(result_list)
if save_result:save_folder = os.path.join(vis_folder, time.strftime("%Y_%m_%d_%H_%M_%S", current_time)
)os.makedirs(save_folder, exist_ok=True)save_file_name = os.path.join(save_folder, os.path.basename(image_name))logger.info("Saving detection result in {}".format(save_file_name))txt_name = os.path.splitext(save_file_name)[0]+".txt"
print(txt_name)f = open(txt_name, "w")
for line in result_list:f.write(str(line) + '\n')f.close()cv2.imwrite(save_file_name, result_image)ch = cv2.waitKey(0)
if ch == 27 or ch == ord("q") or ch == ord("Q"):
break

然后运行demo.py,就可以将结果保存到txt中。

遇到的错误

1、RuntimeError: DataLoader worker (pid(s) 9368, 12520, 6392, 7384) exited unexpectedly

错误原因:torch.utils.data.DataLoader中的num_workers错误 将num_workers改为0即可,0是默认值。num_workers是用来指定开多进程的数量,默认值为0,表示不启用多进程。

打开yolox/exp/yolox_base.py,将data_num_workers设置为0,如下图:

将num_workers设置为0,程序报错,并提示设置环境变量KMP_DUPLICATE_LIB_OK=TRUE 那你可以在设置环境变量KMP_DUPLICATE_LIB_OK=TRUE 或者使用临时环境变量:(在代码开始处添加这行代码)

import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'


2、RuntimeError: cuDNN error: CUDNN_STATUS_INTERNAL_ERROR

执行命令

python tools/train.py -f exps/example/yolox_voc/yolox_voc_s.py -d 1 -b 4 --fp16 -o -c yolox_s.pth.tar

报的错误,把-“-o”去掉后就正常了。

python tools/train.py -f exps/example/yolox_voc/yolox_voc_s.py -d 1 -b 4 --fp16  -c yolox_s.pth.tar

References

[1] mirrors / nvidia / apex · CODE CHINA (csdn.net): https://codechina.csdn.net/mirrors/nvidia/apex?utm_source=csdn_github_accelerator
[2] Win10 安装pycocotools_AI浩-CSDN博客:https://blog.csdn.net/hhhhhhhhhhwwwwwwwwww/article/details/105858384

本文仅做学术分享,如有侵权,请联系删文。

下载1

在「3D视觉工坊」公众号后台回复:3D视觉即可下载 3D视觉相关资料干货,涉及相机标定、三维重建、立体视觉、SLAM、深度学习、点云后处理、多视图几何等方向。

下载2

在「3D视觉工坊」公众号后台回复:3D视觉github资源汇总即可下载包括结构光、标定源码、缺陷检测源码、深度估计与深度补全源码、点云处理相关源码、立体匹配源码、单目、双目3D检测、基于点云的3D检测、6D姿态估计汇总等。

下载3

在「3D视觉工坊」公众号后台回复:相机标定即可下载独家相机标定学习课件与视频网址;后台回复:立体匹配即可下载独家立体匹配学习课件与视频网址。

重磅!3DCVer-学术论文写作投稿 交流群已成立

扫码添加小助手微信,可申请加入3D视觉工坊-学术论文写作与投稿 微信交流群,旨在交流顶会、顶刊、SCI、EI等写作与投稿事宜。

同时也可申请加入我们的细分方向交流群,目前主要有3D视觉CV&深度学习SLAM三维重建点云后处理自动驾驶、多传感器融合、CV入门、三维测量、VR/AR、3D人脸识别、医疗影像、缺陷检测、行人重识别、目标跟踪、视觉产品落地、视觉竞赛、车牌识别、硬件选型、学术交流、求职交流、ORB-SLAM系列源码交流、深度估计等微信群。

一定要备注:研究方向+学校/公司+昵称,例如:”3D视觉 + 上海交大 + 静静“。请按照格式备注,可快速被通过且邀请进群。原创投稿也请联系。

▲长按加微信群或投稿

▲长按关注公众号

3D视觉从入门到精通知识星球:针对3D视觉领域的视频课程(三维重建系列三维点云系列结构光系列、手眼标定、相机标定、orb-slam3知识点汇总、入门进阶学习路线、最新paper分享、疑问解答五个方面进行深耕,更有各类大厂的算法工程人员进行技术指导。与此同时,星球将联合知名企业发布3D视觉相关算法开发岗位以及项目对接信息,打造成集技术与就业为一体的铁杆粉丝聚集区,近2000星球成员为创造更好的AI世界共同进步,知识星球入口:

学习3D视觉核心技术,扫描查看介绍,3天内无条件退款

圈里有高质量教程资料、可答疑解惑、助你高效解决问题

觉得有用,麻烦给个赞和在看~  

超详细!手把手教你使用YOLOX进行物体检测(附数据集)相关推荐

  1. 【超详细】手把手教你使用YOLOX进行物体检测(附数据集)

    作者:王浩 毕业于北京航空航天大学,人工智能领域优质创作者 编辑:3D视觉开发者社区 ✨如果觉得文章内容不错,别忘了三连支持下哦

  2. 超详细——手把手教你用threejs实现一个酷炫的模型发光扫描效果(三)

    上一篇文章 voidjay,公众号:web前端可视化超详细--手把手教你用threejs实现一个酷炫的模型发光扫描效果(二) 上一篇文章已完成基本效果的实现,本文则完成整个项目的灵魂:发光效果以及模型 ...

  3. 干货 :手把手教你Tableau高级数据分析功能(附数据集)

    原文标题:A Step-by-Step Guide to learn Advanced Tableau – for Data Science and Business Intelligence Pro ...

  4. aspen二元体系共沸组分_超详细 | 手把手教你组分结构预测

    好久不见,读者朋友们,笔者本次介绍USPEX官方案例16-18:大体系定组分稳定结构预测.以德拜温度为目的进行定组分结构预测.计算量惊人的三元体系的变组分结构预测. 01 USPEX官方案例讲解(16 ...

  5. 超详细——手把手教你用threejs实现一个酷炫的模型发光扫描效果(一)

    前言 模型特效是大家在3d可视化项目所追求的,但很多人苦于无法实现一个好的模型效果,本次就手把手一步一步教你实现一个酷炫的模型发光扫描特效,帮让你的项目提升一个逼格.话不多说,先上效果: 本文所使用的 ...

  6. 超详细手把手教你cordova开发使用指南+自定义插件,jsbridge

    Cordova是什么 使用前端技术 开发跨平台web App的工具 底层原理:HTML+CSS搭建页面, JS和原生交互 交互原理:Cordova插件 环境配置 安卓开发基础环境搭建的文章可以参考一下 ...

  7. 手把手教你Tableau高级数据分析功能(附数据集)

    原文标题:A Step-by-Step Guide to learn Advanced Tableau – for Data Science and Business Intelligence Pro ...

  8. 转:干货 :手把手教你Tableau高级数据分析功能(附数据集)

    地址:https://blog.csdn.net/Tw6cy6uKyDea86Z/article/details/79694011 原文标题:A Step-by-Step Guide to learn ...

  9. ESP8266 Non-OS SDK 开发之旅 基础篇① 初识 Non-OS SDK,史上超级详细手把手教小白20分钟快速搭建SDK软件开发环境,完成第一个例子Hello World!

    文章目录 1.前言 2. SDK概述 2.1 SDK使用流程 2.2 ESP8266 HDK -- 硬件开发工具 2.3 ESP8266 SDK -- 软件开发工具包 2.3.1 Non-OS SDK ...

最新文章

  1. R语言dplyr包cumall函数、cumany函数和cummean函数实战
  2. 性能测试相关名词解释
  3. html悬浮弹窗后面背景变深,JS+CSS实现Div弹出窗口同时背景变暗的方法
  4. js判断是否在iframe中
  5. java多线程问题,线程交替执行
  6. 【微信小程序】微信Web开发者工具下载及安装
  7. Abaqus汉化问题
  8. 火狐扩展教程_5个Firefox扩展保护您的隐私
  9. linux命令系列 grep
  10. 计算签到记录的次数统计
  11. Centos版Linux 一些常用操作命令 收集
  12. MATALB APP DESIGNER 回调函数创建及StartupFcn函数
  13. 调节阀振动原因分析及解决方案
  14. charles + 雷电模拟器4进行手机抓包
  15. 【观察】大数据3.0新时代 星环科技的思与行
  16. hive报错Execution Error, return code 2 from org.apache.hadoop.hive.ql.exec.mr.MapRedTask
  17. 966. Vowel Spellchecker
  18. LIVE555流媒体服务器MKV文件点播分析
  19. jira系统(jira系统使用教程)
  20. [自问自答]Android开发 如何在虚拟机上模拟sim卡?

热门文章

  1. 卸载 Creative Cloud 桌面应用程序
  2. sudo apt install 和 sudo apt -get install 有什么区别?
  3. Flash Builder4注册机
  4. Sweet Snippet 之 PlayMode实现
  5. 查询学过“001”并且也学过编号“002”课程的同学的学号、姓名
  6. 【office考试】计算机office二级考试题库
  7. linux下静态编译mupdf,在Qt中调用Mupdf库进行pdf显示
  8. 万得资讯 java开发面经
  9. python金融工程的工具包_《华尔街学堂:三大金融技能工具——Wind. Excel 与 Python》...
  10. 读书笔记 摘自:《让成长带你穿透迷茫》 俞敏洪