Python学习散记

打包exe过程及其解决方案

  • 可以使用anaconda创建一个比较简洁的虚拟环境,这样打包出来的文件比较小

  • 安装 pyinstaller——pip install pyinstaller(或者anaconda图形化安装)

  • win+R->cmd进入命令行

  • 1
    2
    3
    4
    5
    6
    7
    
    conda env list
    activate env_name
    pyinstaller -F --icon=my.ico test.py #打包成exe,并设置图标
    #或者
    pyinstaller -F -w yourfilename.py #打包成exe,且不包含控制台,打包pyqt
    #或者
    pyinstaller -F -c yourfilename.py #打包成exe,使用控制台,无界面(默认)
    
1
2
3
4
5
- Pyinstaller打包出现UnicodeDecodeError: 'utf-8' codec can't decode byte 0xce in position 
- 解决方案-在你打包的命令行中先输入`chcp 65001` 然后再输入打包命令
    #注意在同一目录下 
    chcp 65001
    pyinstaller -F  -w xxx.py
  • 有时候用pyinstaller 打包的时候会遇到:

    RecursionError: maximum recursion depth exceeded 这个递归错误,大概率是自己调用自己太多次导致的。解决办法:pyinstaller 之后会生成一个和xxx.py文件对一个的 xxx.spec 文件。打开xxx.spec文件,在行首导入sys包,然后设置一下递归调用的限制次数,可以尽量大一点,设置100万次后就没有报错了,代码如下:

1
2
3
import sys
sys.setrecursionlimit(1000000)
block_cipher= None

Anaconda换源

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
//换源
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/msys2/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
//搜素设置展示通道
conda config --set show_channel_urls yes
       
//显示.condarc所在位置、源信息
conda config --show-sources 

//查看conda信息
conda info
  
//删除default
vim.condarc
    
//有时候清华源不行,可以改为阿里源,中科大源

Jupyter Notebook切换虚拟环境

有两个方法

  • 方法一:直接在anaconda的base环境安装自动关联包(简单)

    1
    
    conda install nb_conda
    
    • 假设打开jupyter的时候,弹出错误

      ​ EnvironmentLocationNotFound: Not a conda environmen

    • 解决办法:

      找到以下两个文件夹,打开envmanager.py文件

      • D:\Anaconda\install\pkgs\nb_conda-2.2.1-py37_0\Lib\site-packages\nb_conda
      • D:\Anaconda\install\Lib\site-packages\nb_conda

      找到以下代码

      1
      2
      3
      4
      
      return {
          "environments": [root_env] + [get_info(env)
                                        for env in info['envs']]
      }
      

      修改为

      1
      2
      3
      4
      5
      
      return {
      
          "environments": [root_env] + [get_info(env) for env in info['envs']
                                        if env != root_env['dir']]
      }
      
    • 重启Jupyter即可

  • 方法二:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    	//1列出环境
    conda env list
        //2激活环境
    activate xxx
        //3安装依赖
    conda install ipykernel
        //4设置虚拟环境
    python -m ipykernel install --user --name xxx --display-name "xxx"
     例如python -m ipykernel install --user --pytorch2 --display-name "pytorch2"
    

    此方式有点问题,假如不行,打开

    C:\Users"你的用户名"\AppData\Roaming\jupyter\kernels\

    里面创建的虚拟环境,打开对应的.json文件

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    {
     "language": "python",
     "display_name": "tensorflow",
     "argv": [
      "C:\\ProgramData\\Anaconda3\\envs\\pytorch\\python.exe",
      "-m",
      "ipykernel_launcher",
      "-f",
      "{connection_file}"
     ]
    }
    
    //******************************************
    上面的"C:\\ProgramData\\Anaconda3\\envs\\pytorch\\python.exe"设置为
    你实际虚拟python.exe的位置
    

基本知识

字符串

  • 单引号和双引号没区别
  • 字符串和tuple差不多,不可以直接更改,但是可以和list互换
  • 字符串的拼接直接用+号即可
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 初始化
str=''

# str和list的互换
str="hello"
print(str)
str_child=list(str)
str_child[2]='f'
str="".join(str_child)
print(str_child)
print(str)
# 输出
#hello
#['h', 'e', 'f', 'l', 'o']
#heflo
# 输出

yield

  1. 在 Python 中,使用了 yield 的函数被称为生成器(generator)。
  2. 跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
  3. 在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下 一次执行 next() 方法时从当前位置继续运行。
  4. 调用一个生成器函数,返回的是一个迭代器对象。
  •  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    def myGun():
        print("生成器")
        yield 1
        yield 2
    
    mygun=myGun()
    for each in mygun:
        print(each)
    
    
    def libs(n):
        a = 0
        b = 1
        while True:
            a, b = b, a + b
            if a > n:
                return
            yield a
    
    for each in libs(100):
        print(each, end=' ')
    
    
    结果
    生成器
    1
    2
    1 1 2 3 5 8 13 21 34 55 89 
    

Numpy函数

import numpy as np

1.numpy.random.normal

  • 函数原型:numpy.random.normal(loc=0.0, scale=1.0, size=None)

  • 从正态(高斯)分布中抽取随机样本。

  • 参数:

    • loc 浮点型数据或者浮点型数据组成的数组 分布的均值(中心)
    • scale 浮点型数据或者浮点型数据组成的数组 分布的标准差(宽度)
    • size 整数或者整数组成的元组,可选参数 输出值的维度。如果给定的维度为(m, n, k),那么就从分布中抽取m * n * k个样本。如果size为None(默认值)并且loc和scale均为标量,那么就会返回一个值。否则会返回np.broadcast(loc, scale).size个值
  • 返回值:n维数组

  • 1
    2
    3
    4
    
    num_inputs = 2
    num_examples = 1000
    np.random.normal(0, 1, (num_examples,num_inputs))
    #返回二维数组,每组1000个随机点
    

2.random.shuffle/permutation()

  • permutation、shuffle,对给定序列实现随机排列;
  • 前者返回一个新数组,后者是 inplace 操作,即改变原数组

pyqt5 使用

1. 搭建环境

  • 添加pyqt5

以下添加示例上网自查

  • 添加QtDesigner——在拓展工具
  • 添加PyUIC——在拓展工具

  • 参考https://www.cnblogs.com/beyang/p/9504581.html

2. 快速使用

  • 用QT designer制作的界面为.ui文件,通过命令可以编译为.py文件,但此时如果对.py文件作修改,下一次使用.ui生成的.py文件不会存在这些修改,这就是没有将界面实现与逻辑分离开,因此我们需要实现将界面与逻辑分离开来。

  • 做法很简单,就是新建一个.py文件,继承界面文件的主窗口类即可,具体的逻辑实现也应该在这个.py文件中实现。

  • ui生成的py文件

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    class Ui_Form(object):
        def setupUi(self, Form):
            Form.setObjectName("Form")
            Form.resize(869, 607)
            Form.setWindowTitle("ID_CARD_Det")
            self.test = QtWidgets.QPushButton(Form)
            self.test.setGeometry(QtCore.QRect(160, 100, 93, 28))
            self.test.setObjectName("test")
    
            self.retranslateUi(Form)
            QtCore.QMetaObject.connectSlotsByName(Form)
    
        def retranslateUi(self, Form):
            _translate = QtCore.QCoreApplication.translate
            self.test.setText(_translate("Form", "test"))
    
  • 自己写的MainWindow类

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    from PyQt5 import QtCore, QtGui, QtWidgets
    from idcard import Ui_Form
    class MainWindow(QtWidgets.QMainWindow, Ui_Form):
        def __init__(self, parent=None):
            super(MainWindow, self).__init__(parent)
            self.setupUi(self)
            #信号和槽的实现函数
            self.test.clicked.connect(self.on1click)
    
        def on1click(self):
            print("hello world")
            QtWidgets.QMessageBox.warning(self,"警告头","警告信息")
    
  • main函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    from PyQt5 import QtCore, QtGui, QtWidgets
    from mainwindow import MainWindow
    import sys
    
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        mainWindow = MainWindow()
        mainWindow.show()
        sys.exit(app.exec_())
    
  • 效果展示

绘图步骤

  • from matplotlib import pyplot as plt

  • 创建画板

  • plt.figure,主要接收一个元组作为 figsize 参数设置图形大小,返回一个 figure 对象用于提供画板

  • plt.axes,接收一个 figure 或在当前画板上添加一个子图,返回该 axes 对 象,并将其设置为"当前"图,缺省时会在绘图前自动添加

  • plt.subplot,主要接收 3 个数字或 1 个 3 位数(自动解析成 3 个数字,要 求解析后数值合理)作为子图的行数、列数和当前子图索引,索引从 1 开始(与 MATLAB 保存一致),返回一个 axes 对象用于绘图操作。这里,可以理解成是 先隐式执行了 plt.figure,然后在创建的 figure 对象上添加子图,并返回当前子 图实例

  • plt.subplots,主要接收一个行数 nrows 和列数 ncols 作为参数(不含第三 个数字),创建一个 figure 对象和相应数量的 axes 对象,同时返回该 figure 对 象和 axes 对象嵌套列表,并默认选择最后一个子图作为"当前"图

  • 1
    2
    3
    4
    5
    6
    
    plt.figure()
    plt.subplot(2,2,1)
    plt.plot([1,2])
    plt.subplot(2,2,2)
    plt.plot([2,1])
    plt.show()
    
  • 绘制图表

    • plot,折线图或点图,实际是调用了 line 模块下的 Line2D 图表接口
    • scatter,散点图,常用于表述两组数据间的分布关系,也可由特殊形式下 的 plot 实现
    • bar/barh,条形图或柱状图,常用于表达一组离散数据的大小关系,比如 一年内每个月的销售额数据;默认竖直条形图,可选 barh 绘制水平条形图
    • hist,直方图,形式上与条形图很像,但表达意义却完全不同:直方图用 于统计一组连续数据的分区间分布情况,比如有 1000 个正态分布的随机抽样, 那么其直方图应该是大致满足钟型分布;条形图主要是适用于一组离散标签下 的数量对比
    • pie,饼图,主要用于表达构成或比例关系,一般适用于少量对比
    • imshow,显示图像,根据像素点数据完成绘图并显示
  • 配置比例:

    • title,设置图表标题
    • axis/xlim/ylim,设置相应坐标轴范围,其中 axis 是对后 xlim 和 ylim 的集 成,接受 4 个参数分别作为 x 和 y 轴的范围参数
    • grid,添加图表网格线
    • legend,在图表中添加 label 图例参数后,通过 legend 进行显示
    • xlabel/ylabel,分别用于设置 x、y 轴标题
    • xticks/yticks,分别用于自定义坐标轴刻度显示
    • text/arrow/annotation,分别在图例指定位置添加文字、箭头和标记,一 般很少用
  • 还有设置子图位置的

    • plt.axes 可通过接收尺寸参数实现多子图绘制:在添加子图时传入 一个含有 4 个数值的元组,分别表示子图的底坐标和左坐标(设置子图原点位置)、 宽度和高度(设置子图大小),从而间接实现子图仅占据画板的一块子区域。相应 的方法接口在面向对象接口中是 fig.add_axes(),仅仅是接口名字不同,但参数和原理 是一致的。

    • 1
      2
      3
      4
      5
      6
      7
      
      plt.figure()
      x=np.linspace(0,10,100)
      plt.axes((0.1,0.5,0.8,0.4))
      plt.plot(np.sin(x))
      plt.axes((0.1,0.1,0.8,0.4))
      plt.plot(np.cos(x))
      plt.show()
      

paddleOCR

1. 安装

  • 查看cuda版本

    • nvidia-smi

  • pip安装记得把梯子拆下来,不然安装出错

  • 安装参考https://www.paddlepaddle.org.cn/install/quick?docurl=/documentation/docs/zh/install/pip/windows-pip.html

  • 个人安装命令参考

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    conda env list
    activate paddle_env
    
    # 安装cpu版本的paddlepaddle
    python -m pip install paddlepaddle==2.2.0 -i https://mirror.baidu.com/pypi/simple 
    #安装完成后您可以使用 python 进入python解释器,输入import paddle ,再输入 paddle.utils.run_check()
    #如果出现PaddlePaddle is installed successfully!,说明您已成功安装。
    
    # 安装paddleocr
    pip install "paddleocr>=2.0.1" # 推荐使用2.0.1+版本
    
  • 下载三个推理模型

    • 然后放在python同一工程文件名下新建一个models文件夹,注意文件目录在工程目录里面
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    from paddleocr import PaddleOCR, draw_ocr
    from PIL import Image
    # Paddleocr目前支持的多语言语种可以通过修改lang参数进行切换
    # 例如`ch`, `en`, `fr`, `german`, `korean`, `japan`
    img_path = './imgs/1.png'
    # 记得传入三个推理模型:检测、定位、识别
    ocr = PaddleOCR(use_angle_cls=True, lang="ch",use_gpu=False,
                    rec_model_dir='./models/ch_PP-OCRv2_rec_infer/',
                    cls_model_dir='./models/ch_ppocr_mobile_v2.0_cls_infer/',
                    det_model_dir='./models/ch_PP-OCRv2_det_infer/')
    result = ocr.ocr(img_path, cls=True)
    for line in result:
        print(line)
    
    # 保存显示结果
    image = Image.open(img_path).convert('RGB')
    boxes = [line[0] for line in result]
    txts = [line[1][0] for line in result]
    scores = [line[1][1] for line in result]
    im_show = draw_ocr(image, boxes, txts, scores, font_path='./fonts/simfang.ttf')
    im_show = Image.fromarray(im_show)
    im_show.save('result.jpg')
    
  • 文件目录

2. 和Opencv联调

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
from paddleocr import PaddleOCR, draw_ocr
# from PIL import Image
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont

font=cv2.FONT_HERSHEY_SIMPLEX

def putText_Chinese(img,strText,pos,color,fontSize):
    fontpath = "./simsun.ttc" # <== 这里是宋体路径
    font = ImageFont.truetype(fontpath, fontSize)
    img_pil = Image.fromarray(img)
    draw = ImageDraw.Draw(img_pil)
    draw.text(pos,strText, font=font, fill=color)
    img = np.array(img_pil)
    return img

# Paddleocr目前支持的多语言语种可以通过修改lang参数进行切换
# 例如`ch`, `en`, `fr`, `german`, `korean`, `japan`
img_path = './imgs/1.png'
# 记得传入三个推理模型:检测、定位、识别
ocr = PaddleOCR(use_angle_cls=True, lang="ch", use_gpu=False,
                rec_model_dir='./models/ch_PP-OCRv2_rec_infer/',
                cls_model_dir='./models/ch_ppocr_mobile_v2.0_cls_infer/',
                det_model_dir='./models/ch_PP-OCRv2_det_infer/')
print('---------------PaddleOCR Start---------------------')
result = ocr.ocr(img_path, cls=True)


img = cv2.imread(img_path)
cv2.imshow("src", img)
ret=""
for line in result:
    print('----------------------------')
    # print(type(line[1][0]))
    ret=ret+line[1][0]
    # 左上角顺时针旋转的四个直角点坐标
    pt1 = ((int)(line[0][0][0]),(int)(line[0][0][1]))
    pt2 = ((int)(line[0][1][0]),(int)(line[0][1][1]))
    pt3 = ((int)(line[0][2][0]),(int)(line[0][2][1]))
    pt4 = ((int)(line[0][3][0]),(int)(line[0][3][1]))
    # 绘制矩形
    cv2.line(img,pt1,pt2,(0,0,255),1,cv2.LINE_AA)
    cv2.line(img,pt2,pt3,(0,0,255),1,cv2.LINE_AA)
    cv2.line(img,pt3,pt4,(0,0,255),1,cv2.LINE_AA)
    cv2.line(img,pt1,pt4,(0,0,255),1,cv2.LINE_AA)
    # list,最后是tuple不可更改
    img = putText_Chinese(img,line[1][0],(pt1[0],pt1[1]-20),(255,0,255),20)

print(ret)
print(len(ret))
cv2.imshow("OCR-Result", img)
# cv2.imwrite("result.png", img)
cv2.waitKey()
cv2.destroyAllWindows()

Opencv函数

cv2.findContours

  • 1
    
    counter,hier=cv2.findContours(can.copy(),cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
    

    opencv的轮廓定义细分为两种,

    • 外黑内白的边界为外轮廓(external);
    • 外白内黑的边界为孔轮廓(hole)

    关于轮廓的获取算法 opencv应用的是suzuki85轮廓跟踪算法。

    寻找轮廓,参数解析

    • 第一个输入图片,注意会在原图上修改
    • 第二个为轮廓搜索方式
      • cv.RETE_LIST: 提取所有轮廓但不建立任何层次关系
      • cv.RETE_EXTERNAL: 只提取外轮廓(可能有多个外轮廓,只是不提取孔轮廓
      • cv.RETE_TREE: 树形结构表示轮廓的从属关系
      • cv.RETE_CCOMP: 提取所有轮廓,把它们组织成两级结构。第一级是连通域的外边界(external boundaries),第二级是孔边界(boundaries of holes)。如果在孔中间还有另外的连通域,则被当成另一个外边界。
    • 第三个为近似方法。即轮廓如何呈现的方法,有三种可选的方法:
      • cv::CHAIN_APPROX_NONE:将轮廓中的所有点的编码转换成点;
      • cv::CHAIN_APPROX_SIMPLE:压缩水平、垂直和对角直线段,仅保留它们的端点;
      • cv::CHAIN_APPROX_TC89_L1 or cv::CHAIN_APPROX_TC89_KCOS:应用Teh-Chin链近似算法中的一种风格
    • 返回值第一个是轮廓位置,第二个是拓扑信息

cv2.drawContous

  • 1
    
    cv2.drawContours(src,counter,-1,(0,255,0),3)
    
  • 作用是绘制轮廓

  • 第一个参数是输入图像,注意会修改,最好是RGB图像

  • 第二个参数是轮廓值

  • 第三个参数是int contourIdx, // 需要绘制的轮廓的指数 (-1 表示 "all")

  • 第四个参数是颜色

  • 第五个参数是线宽

cv2.arcLength(cnt, True)

  • 参数说明:cnt为输入的单个轮廓值,第二个是轮廓是否闭合
  • 作用是计算轮廓的周长

多边形拟合cv2.approxPolyDP(curve, 0.018 * peri, True)

  • approxPolyDP()函数是opencv中对指定的点集进行多边形逼近的函数,其逼近的精度可通过参数设置。

    对应的函数为: void approxPolyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed);

    例如:approxPolyDP(contourMat, approxCurve, 10, true);//找出轮廓的多边形拟合曲线

    第一个参数 InputArray curve:输入的点集 第二个参数double epsilon:指定的精度,也即是原始曲线与近似曲线之间的最大距离。 第三个参数bool closed:若为true,则说明近似曲线是闭合的;反之,若为false,则断开。

x, y, w, h = cv2.boudingrect(cnt)

  • 作用是获得外接矩形
  • 参数说明:x,y, w, h 分别表示外接矩形的x轴和y轴的坐标,以及矩形的宽和高, cnt表示输入的轮廓值

pytorch函数

  • 注意:tensor是一个类,torch是一个包。

  • torch.index_select(input, dim, index, out=None)

    • 第一个参数是索引的对象,

    • 第二个参数为索引维度,0表示按行索引,1表示按列进行索引,

    • 第三个参数是一个tensor,就是索引的序号,比如b里面tensor[0, 2]表示第0行和第2行,c里面tensor[1, 3]表示第1列和第3列。

    •  1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      
      a = torch.linspace(1, 12, steps=12).view(3, 4)
      print(a)
      b = torch.index_select(a, 0, torch.tensor([0, 2]))
      print(b)
      print(a.index_select(0, torch.tensor([0, 2])))
      c = torch.index_select(a, 1, torch.tensor([1, 3]))
      print(c)
      
      
      
      //结果
      tensor([[ 1.,  2.,  3.,  4.],
              [ 5.,  6.,  7.,  8.],
              [ 9., 10., 11., 12.]])
      tensor([[ 1.,  2.,  3.,  4.],
              [ 9., 10., 11., 12.]])
      tensor([[ 1.,  2.,  3.,  4.],
              [ 9., 10., 11., 12.]])
      tensor([[ 2.,  4.],
              [ 6.,  8.],
              [10., 12.]])
      
  • torch.mm

    • torch.mm(mat1, mat2, out=None) → Tensor
    • 对矩阵 mat1 和 mat2 进行相乘。 如果 mat1 是一个 n×m 张量,mat2 是一个 m×p 张 量,将会输出一个 n×p 张量 out。
  • tensor.item

    • 另外一个常用的函数就是 item() , 它可以将⼀一个标量量 Tensor 转换成⼀一个Python number

    • 1
      2
      3
      
      x = torch.randn(1)
      print(x)
      print(x.item())
      
  • tensor.data()

    • tensor.data 返回和 x 的相同数据 tensor, 而且这个新的tensor和原来的tensor是共用数据的,一者改变,另一者也会跟着改变,而且新分离得到的tensor的requires_grad = False, 即不可求导的。(这一点其实detach是一样的)。

    • 常用于梯度计算

    • 1
      
      param.data -= lr * param.grad / batch_size # 注意这⾥更改param时用的param.data
      
  • torch.set_default_tensor_type(t)

    • 将默认torch.Tensor类型设置为浮点张量类型 t

    • 1
      2
      3
      4
      5
      
      >>> torch.tensor([1.2, 3]).dtype    # initial default for floating point is torch.float32
      torch.float32
      >>> torch.set_default_tensor_type(torch.DoubleTensor)
      >>> torch.tensor([1.2, 3]).dtype    # a new floating point tensor
      torch.float64
      
  • tensor.to()

    • 更换tensor的类型,最好从下往上提升,避免出错

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      
      import torch
      a=torch.empty(5,3,dtype=torch.float64)
      print(a.dtype)
      a=a.to(dtype=torch.float)
      print(a.dtype)
      
      #输出
      torch.float64
      torch.float32
      
  • torch.utils.data

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    # 读取样本数据的包
    import torch.utils.data as Data
    # 批量个数
    batch_size = 10
    # 将训练数据的特征和标签组合,加载数据,第一个参数为X变量(样本),第二个参数为y输出(标签)
    dataset = Data.TensorDataset(features, labels)
    # 随机读取⼩批量,第一个参数为数据,第二个为批量个数,第三个设置为 True 时会在每个 epoch 重新打乱数据(默认: False),返回值为迭代器
    data_iter = Data.DataLoader(dataset, batch_size, shuffle=True)
    # 迭代读取
    for X, y in data_iter:
        print(X, y)
        break