使用Keras编写神经网络预测大乐透彩票,并利用历史数据回测

写在最前面

首先郑重声明,这个赚不了钱!赚不了钱!赚不了钱!重要的话说三遍!

纯粹出于兴趣和技术做了个小实验,指望这个赚钱不太可能鸭!emmm,但可能会让你赔钱赔的少一点?

image.png

转载请注明出处:https://blog.csdn.net/aaronjny/article/details/103276212

前言

以前从没买过彩票,前几天一时兴起,随机买了几注,然后兴致勃勃地等开奖。中奖序列出来后,比对一下,握草?!

果然没中~

image.png

然后我就在考虑机器选彩票的技术可行性。

首先,大乐透的中奖序列为35选5+12选2,每个球的选取是随机的,因此,想使用机器学习精准地预测出获奖序列是很难(或者说基本不可能)的。为什么这么说呢?我们可以类比于机器学习选股票。目前有很多机器学习应用在股票选择上的例子,并能够实现盈利。但机器学习选股和选彩票有几点显著差异:

  • 股票只需要预测涨幅(回归)或者简单预测涨跌(二分类),但大乐透中奖序列需要从 C 35 5 ∗ C 12 2 C_{35}^5*C_{12}^2 C355C122种(我不是资深彩民,就是兴致一起随手买了两注,所以对各种彩票的规则了解不是很清楚,不清楚是否还有隐含规则,这个是根据字面意思计算得来的)组合中预测出一种,难度完全不是一个量级的
  • 股票的涨跌存在各种因子、k线、舆情等参数用于评估和训练,但彩票的中奖序列是随机产生的,可供参考的可能仅有时间上的序列的概率分布。

所谓的概率分布指的是,假设彩票的中奖序列是完全随机产生的话,序列中每一个球在一次开奖过程中出现的概率应该是相同的(前区和后区要分开算),并且从时间序列上来看,连续的很多次开奖中,每一个球的出现与否也应当满足某种规律(当然,这是宏观上讲,实际上肯定不会严格满足,但能够体现某种趋势或倾向)。

而我们又不打算靠这个中大奖,目标不是预测出头等奖的开奖序列,而是尽可能多的预测出可能会出现在中奖序列中的球。听上去好像没什么区别,但在模型设计上能够体现出差异——如果是前者,我们的模型应该是一个分类器,从 C 35 5 ∗ C 12 2 C_{35}^5*C_{12}^2 C355C122种类别中预测出其中一个,但考虑数据集规模和概率问题,根本不现实;而后者,我们则设计一个多输入、多输出的模型,输入是七个球的时间序列,输出是每个位置出现某个球的概率,这样就靠谱的多~emmm,我们不求赚钱,少输点就行~!

image.png

行嘛,想了想好像没什么问题,那就开搞?于是,就有了这篇文章。

一、获取数据

想训练个模型的话,第一步肯定是获取数据啦。

我在网上找了一下,很快从[江苏体彩网](https://www.js-lottery.com/Pla 
yZone/lottoData.html)找到了历史开奖记录,可以直接下载,文件格式为csv。

这就很舒服啦,不用写爬虫到处一点点爬了。把文件下载下来看一下,数据是以这种形式组织的:

image.png

很明显,最前面是期号,然后是前区的五个号码,最后是后区的两个号码。

ok,下面让我们来写一个下载数据集的脚本,以便于我们对数据进行更新:

# -*- coding: utf-8 -*-
# @File  : update_data.py
# @Author: AaronJny
# @Date  : 2019/10/29
# @Desc  :
import requests
import settingsprint('开始尝试从 {} 获取最新的大乐透数据...'.format(settings.LOTTO_DOWNLOAD_URL))
try:resp = requests.get(settings.LOTTO_DOWNLOAD_URL)if resp.status_code == 200:# 解析数据,查看数据集中最新的数据期数lines = resp.content.decode('utf-8').split('\n')index = lines[0].replace('"', '').split(',')[0]print('获取成功!开始更新文件...')with open(settings.DATASET_PATH, 'wb') as f:f.write(resp.content)print('完成!当前最新期数为{}期,请确认期数是否正确!'.format(index))else:raise Exception('获取数据失败!')
except Exception as e:print(e)

我把一些常量提取出来了,放到了settings.py里,需要对照的话请看:

# -*- coding: utf-8 -*-
# @File  : settings.py
# @Author: AaronJny
# @Date  : 2019/10/29
# @Desc  :# 训练epochs数量
EPOCHS = 60
# 训练批大小
BATCH_SIZE = 128
# 输入的连续时间序列长度
MAX_STEPS = 256
# 前区号码种类数
FRONT_VOCAB_SIZE = 35
# 后区号码种类数
BACK_VOCAB_SIZE = 12
# dropout随机失活比例
DROPOUT_RATE = 0.5
# 长短期记忆网络单元数
LSTM_UNITS = 64
# 前区需要选择的号码数量
FRONT_SIZE = 5
# 后区需要选择的号码数量
BACK_SIZE = 2
# 保存训练好的参数的路径
CHECKPOINTS_PATH = 'checkpoints'
# 预测下期号码时使用的训练好的模型参数的路径,默认使用完整数据集训练出的模型
PREDICT_MODEL_PATH = '{}/model_checkpoint_x'.format(CHECKPOINTS_PATH)
# 预测的时候,预测几注彩票,默认5注
PREDICT_NUM = 5
# 数据集路径
DATASET_PATH = 'lotto.csv'
# 数据集下载地址
LOTTO_DOWNLOAD_URL = 'https://www.js-lottery.com/PlayZone/downLottoData.html'
# 大乐透中奖及奖金规则(没有考虑浮动情况,税前)
AWARD_RULES = {(5, 2): 10000000,(5, 1): 800691,(5, 0): 10000,(4, 2): 3000,(4, 1): 300,(3, 2): 200,(4, 0): 100,(3, 1): 15,(2, 2): 15,(3, 0): 5,(2, 1): 5,(1, 2): 5,(0, 2): 5
}

我们尝试运行一下,看看效果:

image.png

很好,没有问题。

二、处理数据

虽然数据已经获取到了,但显然,这个数据无法直接应用于训练。我们需要对数据做一下简单的处理。

现在,我们为中奖序列中的数字(或者说球)编一下号,从前往后它们的编号分别为1到7,其中1-5是前区的5个球,6-7是后区的2个球。

好的,我们现在先考虑另外一个问题,假如我们有近一年以来的气温数据,需要预测明天的气温,应该怎么做?

你可能会脱口而出,用循环神经网络做序列的预测。假设按顺序给这近一年来的气温分别编号为1-365,其中t1表示第一天的气温,t365表示今天的气温。气温的变化应该是有规律的(一般情况下),我们想让机器来学习这种规律。我们选定一个合适的时间长度,比如30天,然后将这30天的连续数据作为输入(x),将接下来一天的气温数据作为输出(y),就构成了一条数据。然后使用长度为31天的扫描框,对一年的数据进行一次遍历,我们就得到了一组数据集。用它进行训练,完成后,输入30天前到今天的气温序列,即可预测明天的气温。

回到这个问题上来,其实和预测气温差不多,我们使用连续若干期的球1的数据来预测下期球1的分布概率,球2-球7都是同样的方法。单从输入输出看来就是这样,实际上实现起来肯定会有更多的处理和优化,在这里不讨论,说到模型的时候再细说。

和预测气温的例子不同,气温预测时只有一种因子参与,就是当天的气温值。而在这个例子里,输入的是7个球,输出的也是7个概率分布,所以这是个多输入、多输出的模型。我准备使用keras来实现模型,按照keras的接口,我需要把输入数据处理成这个格式:

x={'x1': [1序列1,1序列2, ... ,1序列n], 'x2': [2序列1,2序列2, ... ,2序列n], ... , 'x7': [7序列1,7序列2, ... ,7序列n]}

相应的,输出数据整理成这个格式:

y={'y1': [序列1的下一期球1, 序列2的下一期球1, ... 序列n的下一期球1], 'y2': [序列1的下一期球2, 序列2的下一期球2, ... 序列n的下一期球2], ... ,'y7': [序列1的下一期球7, 序列2的下一期球7, ... 序列n的下一期球7], }

开搞!

# -*- coding: utf-8 -*-
# @File  : dataset.py
# @Author: AaronJny
# @Date  : 2019/10/29
# @Desc  : 对数据集进行相关处理
import time
import numpy as np
import settingsclass LottoDataSet:def __init__(self, path=settings.DATASET_PATH, train_data_rate=0.9, shuffle=True):# 数据集路径self.path = path# 训练集占整体数据集的比例self.train_data_rate = train_data_rate# 是否打乱数据集顺序self.shuffle = shuffle# 训练集self.train_np_x = {}self.train_np_y = {}# 测试集self.test_np_x = {}self.test_np_y = {}# 加载并处理数据集self.clean_data()def load_data_from_path(self, path=None):"""从给定路径加载数据集:param path: 数据集路径:return: list,若干组开奖记录"""# 如果没有指定路径,就是用初始化实例时传递的pathif not path:path = self.path# 读取数据行with open(path) as f:# 因为csv里面最新的数据放在了最前面,所以我们需要颠倒一下lines = f.readlines()[::-1]# 排除空行lines = [line.strip() for line in lines if line.strip()]return linesdef clean_data(self):"""对数据进行清洗:return:"""# 先从硬盘读取文件lines = self.load_data_from_path()# 去除引号,并使用逗号分割,将数据转成数组x_nums = []for line in lines:# 下标0的位置是期号,直接去掉nums = line.replace('"', '').split(',')[1:]# 所有球的编号都减一,把1-35变成0-34,1-12变成0-11# 这样便于后面做softmaxx_nums.append([int(x) - 1 for x in nums])# 接着,把中奖序列中的七个数字拆开,按位置和时间纵轴组合,变成7组数据num_seqs = {}# 对于每一期的中奖序列for line in x_nums:# 对于一条中奖序列中的每一个数for index, num in enumerate(line):# 最后的数据格式{0: [1,2,3,4,...],1: [1,2,3,4,...],...,6: [1,2,3,4,...]}num_seqs.setdefault(index, []).append(num)# 根据时间序列,拆出来x和y数据集,每MAX_STEPS长度的连续序列构成一条数据的x,max_steps+1构成y# 举例,假设MAX_STEPS=3,有序列[1,2,3,4,5,6],则[1,2,3->4],[2,3,4->5],[3,4,5->6]是组成的数据集x = {}y = {}for index, seqs in num_seqs.items():x[index] = []y[index] = []total = len(seqs)# 序列的长度要求为MAX_STEPS,所以从MAX_STEPS处开始,而不是下标0处for i in range(settings.MAX_STEPS, total, 1):# 存放本条x序列的,存放的是数字形式tmp_x = []# 存放本条y值的,存放的one-hot形式,虽然y只是一个数,但one-hot形式也为listif index < settings.FRONT_SIZE:# 根据index判断当前号码属于前区还是后区,使用相关的号码种类数量来初始化one-hot向量# 因为前区是35选5,后区是12选2,one-hot向量大小不同,所以要区别对待tmp_y = [0 for _ in range(settings.FRONT_VOCAB_SIZE)]else:tmp_y = [0 for _ in range(settings.BACK_VOCAB_SIZE)]# 将从i-MAX_STEPS到i(不包括i)的这一段长为MAX_STEPS的序列,逐个加入到tmp_x中for j in range(i - settings.MAX_STEPS, i, 1):tmp_x.append(seqs[j])# 将这条记录添加到x数据集中x[index].append(tmp_x)# 修改y值的one-hot,并将标签加入到y数据集中tmp_y[seqs[i]] = 1y[index].append(tmp_y)# y在前面已经是one-hot形式了,我们现在需要把x里面的数字也转成one-hot形式,并转成numpy的array类型np_x = {}np_y = {}# 对应7个球构成的七组序列中的每一组for i in range(settings.FRONT_SIZE + settings.BACK_SIZE):# 获取样本数量x_len = len(x[i])# 根据球所处的前后区,分别进行初始化if i < settings.FRONT_SIZE:tmp_x = np.zeros((x_len, settings.MAX_STEPS, settings.FRONT_VOCAB_SIZE))tmp_y = np.zeros((x_len, settings.FRONT_VOCAB_SIZE))else:tmp_x = np.zeros((x_len, settings.MAX_STEPS, settings.BACK_VOCAB_SIZE))tmp_y = np.zeros((x_len, settings.BACK_VOCAB_SIZE))# 分别利用x,y中的数据修改tmp_x和tmp_yfor j in range(x_len):for k, num in enumerate(x[i][j]):tmp_x[j][k][num] = 1for k, num in enumerate(y[i][j]):tmp_y[j][k] = num# 然后将tmp_x和tmp_y按照球所处的位置,加入到np_x和np_y中np_x['x{}'.format(i + 1)] = tmp_xnp_y['y{}'.format(i + 1)] = tmp_y# ok,现在我们可以看一下数组的shape是否正确# for i in range(settings.FRONT_SIZE + settings.BACK_SIZE):#     print(i + 1, np_x['x{}'.format(i + 1)].shape, np_y['y{}'.format(i + 1)].shape)# 划分数据集total_batch = len(np_x['x1'])  # 总样本数train_batch_num = int(total_batch * self.train_data_rate)  # 训练样本数train_np_x = {}train_np_y = {}test_np_x = {}test_np_y = {}for i in range(settings.FRONT_SIZE + settings.BACK_SIZE):x_index = 'x{}'.format(i + 1)y_index = 'y{}'.format(i + 1)train_np_x[x_index] = np_x[x_index][:train_batch_num]train_np_y[y_index] = np_y[y_index][:train_batch_num]test_np_x[x_index] = np_x[x_index][train_batch_num:]test_np_y[y_index] = np_y[y_index][train_batch_num:]# 打乱训练数据if self.shuffle:random_seed = int(time.time())# 使用相同的随机数种子,保证x和y的一一对应没有被破坏for i in range(settings.FRONT_SIZE + settings.BACK_SIZE):np.random.seed(random_seed)np.random.shuffle(train_np_x['x{}'.format(i + 1)])np.random.seed(random_seed)np.random.shuffle(train_np_y['y{}'.format(i + 1)])self.train_np_x = train_np_xself.train_np_y = train_np_yself.test_np_x = test_np_xself.test_np_y = test_np_y@propertydef predict_data(self):"""模型训练好之后,获取预测下期彩票序列(未发生事件)时使用的输入数据.数据处理方式与clean_data方法相似,但只返回最新的连续的MAX_STEPS期开奖序列的x值:return:"""# 先从硬盘读取文件lines = self.load_data_from_path()# 去除引号,并使用逗号分割,将数据转成数组x_nums = []for line in lines:# 下标0的位置是期号,直接去掉nums = line.replace('"', '').split(',')[1:]# 所有球的编号都减一,把1-35变成0-34,1-12变成0-11# 这样便于后面做softmaxx_nums.append([int(x) - 1 for x in nums])# 接着,把中奖序列中的七个数字拆开,按位置和时间纵轴组合,变成7组数据num_seqs = {}# 对于每一期的中奖序列for line in x_nums:# 对于一条中奖序列中的每一个数for index, num in enumerate(line):# 最后的数据格式{0: [1,2,3,4,...],1: [1,2,3,4,...],...,6: [1,2,3,4,...]}num_seqs.setdefault(index, []).append(num)# 根据时间序列,拆出来x和y数据集,每MAX_STEPS长度的连续序列构成一条数据的x,max_steps+1构成y# 举例,假设MAX_STEPS=3,有序列[1,2,3,4,5,6],则[1,2,3->4],[2,3,4->5],[3,4,5->6]是组成的数据集x = {}for index, seqs in num_seqs.items():x[index] = []total = len(seqs)# 存放本条x序列的,存放的是数字形式tmp_x = []# 将从i-MAX_STEPS到i(不包括i)的这一段长为MAX_STEPS的序列,逐个加入到tmp_x中for j in range(total - settings.MAX_STEPS, total, 1):tmp_x.append(seqs[j])# 将这条记录添加到x数据集中x[index].append(tmp_x)# 我们现在需要把x里面的数字也转成one-hot形式,并转成numpy的array类型np_x = {}# 对应7个球构成的七组序列中的每一组for i in range(settings.FRONT_SIZE + settings.BACK_SIZE):# 获取样本数量x_len = len(x[i])# 根据球所处的前后区,分别进行初始化if i < settings.FRONT_SIZE:tmp_x = np.zeros((x_len, settings.MAX_STEPS, settings.FRONT_VOCAB_SIZE))else:tmp_x = np.zeros((x_len, settings.MAX_STEPS, settings.BACK_VOCAB_SIZE))# 分别利用x,y中的数据修改tmp_x和tmp_yfor j in range(x_len):for k, num in enumerate(x[i][j]):tmp_x[j][k][num] = 1# 然后将tmp_x和tmp_y按照球所处的位置,加入到np_x和np_y中np_x['x{}'.format(i + 1)] = tmp_xreturn np_x

需要提一下的是,虽然每个球上的编号都是数字,但我们不应该把它们当数字,因为它们的大小关系在开奖中没有任何意义,且在训练中可能会对模型产生干扰。因此我们选择使用one-hot形式以类别的方式来表示它们,而不是一个数值。

同时,为了softmax和one-hot方便,我将球上的数字都减了一,把1-35变成0-34,1-12变成0-11。

三、编写模型

先给出模型示意图,再细说模型,可能会更清楚一些:

model.png

前面提到“和预测气温差不多,我们使用连续若干期的球1的数据来预测下期球1的分布概率,球2-球7都是同样的方法”,但因为这些球本身并不独立,比如球1开出了3,球2-5就不可能再开出3,而是在剩下的里面选。所以我们在预测最后的概率之前,对球1-5的中间层进行了拼接,再分别预测,这样模型可能会学习到每一期中前区的球之间的某种关系。对于球6和7,也做了类似操作。

而球1-5在前区,球6-7在后区,两者没什么关系,所以这两部分之间没有进行拼接。

另外,最后的输出预测层选择了softmax,值得说一下。严格来说,softmax对于这个问题来说,并不是一个很好地选择,因为开球应该是条件概率,比如球1开了5之后,开球2的概率计算应该是球1==5条件下的条件概率,球3-5同理。但我最终还是选择了softmax,原因一是softmax实现起来更加简单,二是模型输出本身设计的就不是预测头等奖的完全正确序列,而是尽可能多的选中球,两者的区别前面提过。这样看来,softmax也还算合适,大不了重复了就使用轮盘赌法重新选。

其他的就没什么好说的了,模型示意图已经表现的很清楚了,更多的无非是层的选择罢了,直接看代码吧:

# -*- coding: utf-8 -*-
# @File  : models.py
# @Author: AaronJny
# @Date  : 2019/10/29
# @Desc  : 建立深度学习模型
import keras
from keras import layers
from keras import models
import settings# 这是一个多输入模型,inputs用来保存所有的输入层
inputs = []
# 这是一个多输出模型,outputs用来保存所有的输出层
outputs = []
# 前区的中间层列表,用于拼接
front_temps = []
# 后区的中间层
back_temps = []# 处理前区的输入变换
for i in range(settings.FRONT_SIZE):# 输入层x_input = layers.Input((settings.MAX_STEPS, settings.FRONT_VOCAB_SIZE), name='x{}'.format(i + 1))# 双向循环神经网络x = layers.Bidirectional(layers.LSTM(settings.LSTM_UNITS, return_sequences=True))(x_input)# 随机失活x = layers.Dropout(rate=settings.DROPOUT_RATE)(x)x = layers.Bidirectional(layers.LSTM(settings.LSTM_UNITS, return_sequences=True))(x)x = layers.Dropout(rate=settings.DROPOUT_RATE)(x)x = layers.TimeDistributed(layers.Dense(settings.FRONT_VOCAB_SIZE * 3))(x)# 平铺x = layers.Flatten()(x)# 全连接x = layers.Dense(settings.FRONT_VOCAB_SIZE * 3, activation='relu')(x)# 保存输入层inputs.append(x_input)# 保存前区中间层front_temps.append(x)
# 处理后区的输入和变换
for i in range(settings.BACK_SIZE):# 输入层x_input = layers.Input((settings.MAX_STEPS, settings.BACK_VOCAB_SIZE),name='x{}'.format(i + 1 + settings.FRONT_SIZE))# 双向循环神经网络x = layers.Bidirectional(layers.LSTM(settings.LSTM_UNITS, return_sequences=True))(x_input)# 随机失活x = layers.Dropout(rate=settings.DROPOUT_RATE)(x)x = layers.Bidirectional(layers.LSTM(settings.LSTM_UNITS, return_sequences=True))(x)x = layers.Dropout(rate=settings.DROPOUT_RATE)(x)x = layers.TimeDistributed(layers.Dense(settings.BACK_VOCAB_SIZE * 3))(x)# 平铺x = layers.Flatten()(x)# 全连接x = layers.Dense(settings.BACK_VOCAB_SIZE * 3, activation='relu')(x)# 保存输入层inputs.append(x_input)# 保存后区中间层back_temps.append(x)
# 连接
front_concat_layer = layers.concatenate(front_temps)
back_concat_layer = layers.concatenate(back_temps)
# 使用softmax计算分布概率
for i in range(settings.FRONT_SIZE):x = layers.Dense(settings.FRONT_VOCAB_SIZE, activation='softmax', name='y{}'.format(i + 1))(front_concat_layer)outputs.append(x)
for i in range(settings.BACK_SIZE):x = layers.Dense(settings.BACK_VOCAB_SIZE, activation='softmax', name='y{}'.format(i + 1 + settings.FRONT_SIZE))(back_concat_layer)outputs.append(x)
# 创建模型
model = models.Model(inputs, outputs)
# 指定优化器和损失函数
model.compile(optimizer=keras.optimizers.Adam(),loss=[keras.losses.categorical_crossentropy for __ in range(settings.FRONT_SIZE + settings.BACK_SIZE)],loss_weights=[2, 2, 2, 2, 2, 1, 1])
# 查看网络结构
model.summary()# 可视化模型,保存结构图
# from keras.utils import plot_model
# plot_model(model, to_file='model.png')

可视化模型部分,保存的就是上面那张模型图,因为需要额外的依赖,我就给注释掉了。如果确实需要执行的话,请自行安装相关依赖。

四、工具方法

数据和模型都已经准备完毕,可以进行训练了。但这个模型不同于一般的分类模型,我们怎么来评估模型的效果呢?我选择的方法是——回测。

其实很简单,就是划分一部分数据(比如90%)作为训练集,训练模型,剩下的10%作为测试集。划分是按照时间顺序划分的,保证后面10%的数据绝不出现在训练集的结果数据或过程数据中。在使用训练集完成模型的训练后,我们对测试集进行预测,并按照预测结果购买彩票,计算支出和奖金,以最终的净收入的多少来衡量模型效果。

现在,我们需要编写一些工具方法,辅助我们完成回测。

# -*- coding: utf-8 -*-
# @File  : utils.py
# @Author: AaronJny
# @Date  : 2019/10/29
# @Desc  : 对数据进行处理和操作的一些工具方法
import matplotlib.pyplot as plt
import numpy as np
import settingsdef sample(preds, temperature=1.0):"""从给定的preds中随机选择一个下标。当temperature固定时,preds中的值越大,选择其下标的概率就越大;当temperature不固定时,temperature越大,选择值小的下标的概率相对提高,temperature越小,选择值大的下标的概率相对提高。:param preds: 概率分布序列,其和为1.0:param temperature: 当temperature==1.0时,相当于直接对preds进行轮盘赌法:return:"""preds = np.asarray(preds).astype(np.float64)preds = np.log(preds) / temperatureexp_preds = np.exp(preds)preds = exp_preds / np.sum(exp_preds)probas = np.random.multinomial(1, preds, 1)return np.argmax(probas)def search_award(front_match_num, back_match_num, cache={}):"""给定前后区命中数量,使用记忆化搜索查找并计算对应奖金:param front_match_num: 前区命中数量:param back_match_num: 后区命中数量:param cache: 缓存用的记忆字典:return:"""# 前后区都没有命中,奖金为0if front_match_num == 0 and back_match_num == 0:return 0# 尝试直接从缓存里面获取奖金award = cache.get((front_match_num, back_match_num), -1)# 这里使用-1是为了避免0和None在判断上的混淆# 如果缓存里面有,已经计算过了,就直接返回if award != -1:return award# 尝试直接从中奖规则中获取奖金数量award = settings.AWARD_RULES.get((front_match_num, back_match_num), -1)if award == -1:# 如果没找到,就先认为没中奖,然后将前区命中数量或后区命中数量减一,# 递归查找,保留最大的中奖金额award = 0if front_match_num > 0:award = search_award(front_match_num - 1, back_match_num)if back_match_num > 0:award = max(award, search_award(front_match_num, back_match_num - 1))# 缓存下本次计算结果cache[(front_match_num, back_match_num)] = award# 返回奖金数额return awarddef lotto_calculate(winning_sequence, sequence_selected):"""给定中奖序列和选择的序列,计算获奖金额:param winning_sequence:中奖序列:param sequence_selected: 选择的序列:return:"""# 前区命中数量front_match = len(set(winning_sequence[:settings.FRONT_SIZE]).intersection(set(sequence_selected[:settings.FRONT_SIZE])))# 后区命中数量back_match = len(set(winning_sequence[settings.FRONT_SIZE:]).intersection(set(sequence_selected[settings.FRONT_SIZE:])))# 计算奖金award = search_award(front_match, back_match)return awarddef select_seqs(predicts):"""根据给定的概率分布,随机选择一种彩票序列:param predicts:list[list] 每一个球的概率分布组成的列表:return: list 彩票序列"""balls = []# 对于每一种球for predict in predicts:try_cnt = 0while True:try_cnt += 1# 根据预测结果随机选择一个if try_cnt < 100:ball = sample(predict)else:# 如果连续100次都是重复的,就等概率地从所有球里面选择一个ball = sample([1. / len(predict) for __ in predict])# 如果选重复了就重新选if ball in balls:# 序列不长,就没有使用set优化,直接用list了continue# 将球保存下来,跳出,开始选取下一个balls.append(ball)break# 排序,前五个升序,后两个升序balls = sorted(balls[:settings.FRONT_SIZE]) + sorted(balls[settings.FRONT_SIZE:])return ballsdef draw_graph(y):"""绘制给定列表y的折线图和趋势线"""# 横坐标,第几轮训练x = list(range(len(y)))# 拟合一次函数,返回函数参数parameter = np.polyfit(x, y, 1)# 拼接方程f = np.poly1d(parameter)# 绘制图像plt.plot(x, f(x), "r--")plt.plot(y)plt.show()

五、训练模型

数据集、模型和工具方法已经全部写好了,可以正式开始训练了。

我们将数据集按照训练集:测试集=9:1的比例划分数据集,在训练集上训练模型,并使用测试集回测。我准备训练60轮,每一轮训练完成后,都会保存模型的参数,并进行回测。

在训练结束后,将所有回测结果,按时间顺序,绘制出折线图和趋势线。

# -*- coding: utf-8 -*-
# @File  : train.py
# @Author: AaronJny
# @Date  : 2019/10/29
# @Desc  :
import os
import numpy as np
from models import model
from dataset import LottoDataSet
import settings
import utilsdef simulate(test_np_x, test_np_y):"""模拟购买彩票,对测试数据进行回测:param test_np_x: 测试数据输入:param test_np_y: 测试数据输出:return: 本次模拟的净收益"""# 获得的奖金总额money_in = 0# 买彩票花出去的钱总额money_out = 0# 预测predicts = model.predict(test_np_x, batch_size=settings.BATCH_SIZE)# 共有多少组数据samples_num = len(test_np_x['x1'])# 对于每一组数据for j in range(samples_num):# 这一期的真实开奖结果outputs = []for k in range(settings.FRONT_SIZE + settings.BACK_SIZE):outputs.append(np.argmax(test_np_y['y{}'.format(k + 1)][j]))# 每一期彩票买五注money_out += 10for k in range(5):# 存放每个球的概率分布的listprobabilities = []# 对于每一种球,将其概率分布加入到列表中去for i in range(settings.FRONT_SIZE + settings.BACK_SIZE):probabilities.append(predicts[i][j])# 根据概率分布随机选择一个序列balls = utils.select_seqs(probabilities)# 计算奖金award = utils.lotto_calculate(outputs, balls)money_in += awardif award:print('{} 中奖了,{}元! {}/{}'.format(j, award, money_in, money_out))print('买彩票花费金钱共{}元,中奖金额共{}元,赚取{}元'.format(money_out, money_in, money_in - money_out))return money_in - money_out# 初始化数据集
lotto_dataset = LottoDataSet(train_data_rate=0.9)
# 创建保存权重的文件夹
if not os.path.exists(settings.CHECKPOINTS_PATH):os.mkdir(settings.CHECKPOINTS_PATH)
# 开始训练
results = []
for epoch in range(1, settings.EPOCHS + 1):model.fit(lotto_dataset.train_np_x, lotto_dataset.train_np_y, batch_size=settings.BATCH_SIZE, epochs=1)# 保存当前权重model.save_weights('{}/model_checkpoint_{}'.format(settings.CHECKPOINTS_PATH, epoch))print('已训练完第{}轮,尝试模拟购买彩票...'.format(epoch))results.append(simulate(lotto_dataset.test_np_x, lotto_dataset.test_np_y))
# 输出每一轮的模拟结果
print(results)
# 显示每一轮模拟结果的变化趋势
utils.draw_graph(results)

有几点需要注意:

  • 在GPU上进行训练,尽量避免在CPU上训练。一般使用GPU训练在速度上是优于CPU的。当然,如果你的显卡很弱鸡,CPU很强大的话,那就选择CPU吧。
  • 如果你的内存比较小(用CPU的话就是内存,用GPU就是显存),请适量调小训练的batch大小。
  • 如果看到赚到的钱是负的,请不要惊讶,前面已经声明过了,这个模型的目的是尽量少赔点~滑稽.jpg

回测时的输出大致是这样的(输出比较长,截取一部分):

已训练完第17轮,尝试模拟购买彩票...
6 中奖了,5元! 5/70
8 中奖了,5元! 10/90
8 中奖了,5元! 15/90
10 中奖了,5元! 20/110
12 中奖了,15元! 35/130
23 中奖了,5元! 40/240
24 中奖了,5元! 45/250
24 中奖了,5元! 50/250
25 中奖了,5元! 55/260
33 中奖了,5元! 60/340
36 中奖了,15元! 75/370
38 中奖了,5元! 80/390
41 中奖了,5元! 85/420
44 中奖了,5元! 90/450
46 中奖了,5元! 95/470
51 中奖了,15元! 110/520
51 中奖了,5元! 115/520
54 中奖了,5元! 120/550
61 中奖了,5元! 125/620
61 中奖了,5元! 130/620
62 中奖了,5元! 135/630
62 中奖了,5元! 140/630
67 中奖了,5元! 145/680
75 中奖了,5元! 150/760
76 中奖了,5元! 155/770
84 中奖了,5元! 160/850
87 中奖了,5元! 165/880
88 中奖了,5元! 170/890
88 中奖了,5元! 175/890
88 中奖了,15元! 190/890
90 中奖了,5元! 195/910
93 中奖了,5元! 200/940
96 中奖了,15元! 215/970
107 中奖了,5元! 220/1080
114 中奖了,5元! 225/1150
115 中奖了,5元! 230/1160
120 中奖了,100元! 330/1210
123 中奖了,5元! 335/1240
123 中奖了,200元! 535/1240
123 中奖了,15元! 550/1240
125 中奖了,5元! 555/1260
125 中奖了,5元! 560/1260
133 中奖了,5元! 565/1340
136 中奖了,5元! 570/1370
136 中奖了,5元! 575/1370
141 中奖了,5元! 580/1420
142 中奖了,15元! 595/1430
147 中奖了,5元! 600/1480
147 中奖了,5元! 605/1480
149 中奖了,15元! 620/1500
149 中奖了,5元! 625/1500
153 中奖了,5元! 630/1540
155 中奖了,15元! 645/1560
155 中奖了,5元! 650/1560
160 中奖了,15元! 665/1610
164 中奖了,5元! 670/1650
买彩票花费金钱共1660元,中奖金额共670元,赚取-990元

我试着跑了几次,给出几个我跑出来的结果:

[-1335, -1305, -1360, -1420, -1215, -1090, -1140, -1395, -1310, -1355, -1220, -1095, -1375, -1420, -1255, -1270, -730, -1155, -1360, -1330, -1140, -1090, -1030, -1340, -1060, -1150, -1285, -935, -1175, -1290, -1260, -1075, -1275, -1275, -870, -1275, -890, -1175, -1265, -1235, -1260, -1265, -1255, -1270, -1170, -660, -1015, -915, -1095, -850, -560, -890, -980, -670, -1185, -510, -1110, -470, -1180, -655]

image.png

这算是一个比较符合预期的结果?虽然还是在赔钱,但达到了我们的目的——少赔点钱=。=虽然一直在赔钱,但随着训练次数的增加,亏损金额在整体趋势上逐步减少。

[-1095, -1345, -1405, -1390, -1265, -1055, -970, -1375, -1205, -1365, -1260, -1305, -1120, -1280, -1125, -1370, -860, -1285, -1160, -1065, -1295, -1105, -765, -1160, -1055, -1180, -780, -1200, -1205, -760, -1075, -1105, -1130, -955, -1105, -1170, -1140, -915, -735, 8785, 9065, -1035, -1145, -635, -785, 8935, 799876, -995, -1010, -1130, -1125, -1170, -1255, -1035, -920, -935, -1090, -1330, 8930, -1370]

image.png

这个是比较容易血压升高的结果?有一次回测的过程中中了80万…因为80万跟其他回测结果差距太大了,所以图像上基本显示不出其他轮次的起伏了。我们把最高的结果减去79万,看一下其他轮次的趋势:

image.png

这个看起来就顺眼多了。

多次运行的结果可能差距明显,其原因分析如下:

  • 训练数据的原因。前面已经说过了,彩票选号其实是没有严格的规律可言的,否则,哪怕只有极少数一批人能稳定猜中,这个游戏也没法玩啊。如果非要强行说个规律出来,那也只有长期下来的概率分布能勉强凑合。但一来大乐透也只开了一千多期,数据有限,二来,概率这种东西从字面上来看,就知道它不是固定的(哪怕出现的概率最高,也不一定会出现)。这样,当模型的随机初始权重不同,训练数据又很难找到特别清晰的规律时,模型学习到的东西也会产生相应的区别,它们分别倾向到了概率分布的不同表现形式。
  • 回测时选择彩票号码的原因。选择号码时,同样不是一定选择出现概率最大的球,只是出现概率越大,被选中的概率就越大,这样保证了结果的多样性。

两者综合起来,两次的运行结果可能天差地别。但从多次运行的整体来看,还是有一定规律的:

  • 训练一定次数之后,亏损金额大多分布在[-1200,-900]左右,少数情况下在(-900,-400],极少数甚至还有得赚。
  • 大部分都满足“随着训练次数的增加,损失逐步减少”的规律。注意,这里指的是趋势,即图中拟合的一次函数(一条斜直线),因为回测的随机性,单点结果是会出现起伏波动的,所以使用趋势来衡量整体结果会更加合适。

综上,我们的模型应该是起到了一定作用。

你可能说,这亏的也不少啊,我怎么看出来模型到底有没有效果呢?那我们写一个基线模型,来比较一下。

六、基线模型

什么是基线模型?

emmm,怎么说呢,它指的是一个最基础、最简单的模型,它是从概率的角度上来说最糟糕的一个模型。可能解释的不是很清楚,我们直接看例子。

一般极限模型就是都是完全随机的。比如这个问题,我们需要从前区选出五个球,后区选出两个球,我们每个球都随机选择,这就是基线模型。emmm,它类似于彩票中心的机选方案?

我们来实现一下基线模型,并模拟多次购买彩票经历,看使用基线模型我们会亏多少:

# -*- coding: utf-8 -*-
# @File  : random_show.py
# @Author: AaronJny
# @Date  : 2019/10/29
# @Desc  : 随机选择情况下的收益情况
import random
import numpy as np
from dataset import LottoDataSet
import utils
import settingsdef get_one_random_sample():"""获取一种随机序列:return:"""front_balls = list(range(settings.FRONT_VOCAB_SIZE))back_balls = list(range(settings.BACK_VOCAB_SIZE))return random.sample(front_balls, settings.FRONT_SIZE) + random.sample(back_balls, settings.BACK_SIZE)def simulate(test_np_x, test_np_y):# 获得的奖金总额money_in = 0# 买彩票花出去的钱总额money_out = 0# 共有多少组数据samples_num = len(test_np_x['x1'])# 对于每一组数据for j in range(samples_num):# 这一期的真实开奖结果outputs = []for k in range(settings.FRONT_SIZE + settings.BACK_SIZE):outputs.append(np.argmax(test_np_y['y{}'.format(k + 1)][j]))# 每一期彩票买五注money_out += 10for k in range(5):balls = get_one_random_sample()# 计算奖金award = utils.lotto_calculate(outputs, balls)money_in += awardprint('买彩票花费金钱共{}元,中奖金额共{}元,赚取{}元'.format(money_out, money_in, money_in - money_out))return money_in - money_outdataset = LottoDataSet(train_data_rate=0.9)
# 随机买一百次,并记录每一次收入-支出的差值
results = []
for epoch in range(1, 101):results.append(simulate(dataset.test_np_x, dataset.test_np_y))
# 去除最高的和最低的
results = sorted(results)[1:-1]
# 计算平均值
print('mean', sum(results) / len(results))

运行一下,输出的结果是这样的:

image.png

多次运行可以发现,最后的平均值绝大多数落在[-1400,-1200]之间,其中又以-1250左右最多。少数亏得更少或更多,极少数能够小赚。

这样比较下来,我们写的模型还是有用的?至少能少亏一点?滑稽.jpg

平时事情也比较多,所以模型只是大概调了一下,如果对此有兴趣的话,也可以在此基础上,再自行调一下参看看。

七、预测下期彩票序列

如果准备利用模型买彩票,可以分为两种情况:

  • 1.选择我们在上一步训练好的某个模型参数,加载这个参数,输入倒数第MAX_STEPS期到最近一期的数据序列,预测下一期序列。
  • 2.使用完整数据集作为训练集,重新训练模型并保存。然后和第一种情况一样,加载模型参数,输入倒数第MAX_STEPS期到最近一期的数据序列,预测下一期序列。

两者的区别在于:

  • 第一种情况,我们有回测数据,在选择训练好的参数时有一定的参考。而第二种情况使用了完整数据集来训练,就没有回测数据可参考了。
  • 第一种情况的训练数据,少于第二种的训练数据。按理说更多的训练数据通常会产生更好的效果。

各有优缺点吧,酌情选择。但不管怎么样,我们都来实现一下完整训练的脚本:

# -*- coding: utf-8 -*-
# @File    : train_with_whole_dataset.py
# @Author  : AaronJny
# @Date    : 2019/11/26
# @Desc    : 使用全部数据集进行训练
import os
from models import model
from dataset import LottoDataSet
import settings# 初始化数据集
lotto_dataset = LottoDataSet(train_data_rate=1)
# 创建保存权重的文件夹
if not os.path.exists(settings.CHECKPOINTS_PATH):os.mkdir(settings.CHECKPOINTS_PATH)
# 开始训练
model.fit(lotto_dataset.train_np_x, lotto_dataset.train_np_y, batch_size=settings.BATCH_SIZE, epochs=settings.EPOCHS)
# 保存模型
model.save_weights('{}/model_checkpoint_x'.format(settings.CHECKPOINTS_PATH))

好的,不论你选择用哪种方法训练出的模型,都没有关系,我们来看看如何让模型帮我们选号码。

# -*- coding: utf-8 -*-
# @File    : predict.py
# @Author  : AaronJny
# @Date    : 2019/11/26
# @Desc    : 指定一个训练好的模型参数,让模型随机选出下期彩票号码
from dataset import LottoDataSet
from models import model
import settings
import utils# 加载模型参数
model.load_weights(settings.PREDICT_MODEL_PATH)
# 构建数据集
lotto_dataset = LottoDataSet()
# 提取倒数第MAX_STEPS期到最近一期的数据,作为预测的输入
x = lotto_dataset.predict_data
# 开始预测
predicts = model.predict(x, batch_size=1)
# 存放选号结果的列表
result = []
# 存放每个球的概率分布的list
probabilities = [predict[0] for predict in predicts]
# print(probabilities)
# 总共要选出settings.PREDICT_NUM注彩票
for i in range(settings.PREDICT_NUM):# 根据概率分布随机选择一个序列balls = utils.select_seqs(probabilities)# 加入到选号列表中,注意,我们需要把全部的数字+1,恢复原始的编号result.append([ball + 1 for ball in balls])
# 输出要买的彩票序列
print('本次预测结果如下:')
for index, balls in enumerate(result, start=1):print('第{}注 {}'.format(index, ' '.join(map(str, balls))))

模型默认加载使用完整数据作为训练集的模型参数,如果想要加载其他指定参数,直接修改settings中的PREDICT_MODEL_PATH即可。

运行一下,模型输出:

本次预测结果如下:
第1注 2 5 19 25 26 1 11
第2注 2 5 19 26 28 1 12
第3注 1 5 19 21 26 7 11
第4注 2 5 19 26 28 1 11
第5注 1 5 19 21 26 8 11

emmm,我一会儿去买下看看能不能中……滑稽.jpg

八、结语

好的,这次的分享就到这里了,应该没什么遗漏的吧?因为最近事情比较多,所以这篇文章和相关代码编写的时间跨度很长,内容也比较多,虽然我已经通读了几遍,但可能还是会漏下某些没发现的问题,请见谅。分享中涉及到的全部代码,都已经上传到了GitHub,戳这里 (https://github.com/AaronJny/lotto)。

还是要强调一下,这只是一个以技术研究为出发点的娱乐性质的小实验,所以请不要指望这个能帮你赚大钱(如果运气爆棚真的中了,那就当我没说……见面分一半?),能少赔点就不错啦。

最后,请珍惜钱财,远离彩票。小赌怡情,大赌伤身。知难而退,量力而行。

image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/51889.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用 ChatGPT 生成完整的 Node.js API

借助由 OpenAI 训练的大型语言模型 ChatGPT&#xff0c;我们可以创建一个根据我们的特定需求量身定制的 Chrome 扩展程序&#xff0c;并且可以帮助简化我们的日常任务&#xff0c;而无需我们自己编写一行代码。让我们看看这是如何工作的…… 在此分步指南中&#xff0c;我们将向…

巴比特 | 元宇宙每日必读:证监会科技监管局局长姚前建议重点发展基于AIGC技术的合成数据产业,构建大模型训练数据的监管体系...

摘要&#xff1a;证监会科技监管局局长姚前撰文称&#xff0c;除算力瓶颈之外&#xff0c;训练数据将成为大模型产业化的最大掣肘之一。从更深层次考虑&#xff0c;大模型在训练数据方面还存在各种治理问题。为此&#xff0c;作者提出来三点建议&#xff0c;一是重点发展基于AI…

python写诗代码_我们分析了超过50万首诗歌,教你用代码写诗(附代码)

本文为 雷锋字幕组 编译的技术博客&#xff0c;原标题To a Poem is a Bott the Stranger&#xff0c;作者 Carly Stambaugh。 翻译 | 于泽平 马雪洁 整理 | 凡江 编辑 | 吴璇 代码即诗歌 。 这是WordPress软件的哲学。 作为一位程序员和诗人&#xff0c;我一直很喜欢这句话。…

ChatGPT:人工智能语言模型的革命性进步

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

企业微信报错,提示无权限访问

注意打开应用的可见范围 ps.忘记错误码是多少了&#xff0c;上面只是其中一种可能性。

tp5Auth权限实现

下面本人为大家讲解一下如何实现auth权限&#xff0c; 第一步&#xff0c;新建Auth.php&#xff0c;复制下面的代码&#xff0c;把注释中的表都创建一下。把文件放到extend新建文件夹org放进去即可&#xff0c; <?php // ---------------------------------------------…

科技云报道:大模型的中场战事,深入垂直行业腹地

科技云报道原创。 自从OpenAI于2022年11月推出ChatGPT后&#xff0c;一场波及全球科技界的“AI海啸”就此爆发。 自今年以来&#xff0c;国内已有超过30家企业入局大模型赛道。从百度“文心一言”、阿里“通义千问”的发布&#xff0c;到网易“玉言”、科大讯飞“星火”、昆仑…

美国国会听证会探讨“深度伪造(deepfake)”风险及对策

大数据文摘授权转载自腾讯研究院 作者&#xff1a;曹建峰、方龄曼 近日&#xff0c;一段关于扎克伯格的恶搞视频在Instagram上流传。 该视频中&#xff0c;扎克伯格的面部表情极其僵硬&#xff0c;声音与本人的相比差距很大。 事实上&#xff0c;这是以色列一家科技公司利用人…

体验管理|如何快速低成本开始体验相关的数字化工作‼️

Guofu 第 95⭐️ 篇原创文章分享 &#xff08;点击&#x1f446;&#x1f3fb;上方卡片关注我&#xff0c;加⭐️星标⭐️~&#xff09; &#x1f68f; 写在前面 在体验经济时代&#xff0c;传统企业在应对新需求、新挑战的时候&#xff0c;也需要用新的方式进行企业升级和转型…

vant van-uploader组件实现点击图片进行编辑(更换图片)

示例图&#xff1a; 思路&#xff1a; 1.写两个uploader组件&#xff0c;确保他们样式一样&#xff0c;定位将他们重叠放在同一个位置。给其中一个uploader组件设置z-index&#xff0c;让她位于上方&#xff08;以下称为组件1&#xff09;&#xff0c;组件1用于触发选取图片的方…

怎么修改照片大小?一键快速修改图片宽高尺寸的方法

怎么修改照片大小&#xff1f;随着现在手机像素的提升&#xff0c;无论是用手机还是用相机拍摄出来的照片尺寸都越来越清楚&#xff0c;但是随之而来的问题就是图片也越来越大&#xff0c;因此导致大家在传输、使用的时候很不方便&#xff0c;那么有没有什么办法能解决这个问题…

如何编辑图片?图片如何编辑修改?

日常工作中很多情况是需要进行图片处理的&#xff0c;如果我们没合适图片编辑工具&#xff0c;处理图片可能就有些困难了&#xff0c;下载的处理图片软件操作难度过高&#xff0c;上手比较难。其实可以选择在线图片编辑&#xff08;https://www.yasuotu.com/tools&#xff09;网…

tui-image-editor编辑图片的使用

1.安装tui-image-editor 命令&#xff1a;npm i tui-image-editor 如果此步命令执行安装成功后启动还是报错找不到文件的话请检查以下文件 可手动添加到package.json后重新执行npm install 再次启动后便成功 或者单独安装此代码依赖块 npm install --save toast-ui/vue-ima…

数学好=编程能力强?答案或许跟你想的不一样

学好数理化&#xff0c;走遍全天下&#xff01;小时候&#xff0c;这句顺口溜时常在耳边响起&#xff0c;而迈入编程行业以后&#xff0c;又被不小人咨询&#xff0c;我数学不好&#xff0c;能写好代码吗&#xff1f; 不过最近的 MIT 神经科学家在 eLife 期刊发表了一项新研究…

为什么美国学生学的数学比我们简单,却能做出很牛逼的东西?

来源&#xff1a;IT有个 圈儿 &#xff02;美国给予不热爱数学的学生最基础的数学教育&#xff0c;而给予热爱数学的学生最高水平的数学教育。&#xff02; 长久以来&#xff0c;中国人的迷思就是&#xff0c;为何「美国人数学这么差&#xff0c;还能出这么多牛逼科学家&#x…

学计算机语言需要英语基础吗,数学和英语不好的人能学编程吗?

数学和英语不好的人能学编程吗&#xff1f; 有许多小伙伴问&#xff1a;学编程需要什么基础&#xff1f;很多人都会有一个下意识的想法就是英语数学不好就不能学编程&#xff0c;其实这是一个误区。从根本上来说学编程确实需要数学和英语。因为代码是用英文写的&#xff0c;数学…

“编程能力差,90%输在了数学上!”丨多数程序员都是瞎努力!

一流程序员学数学&#xff0c;二流程序员学算法&#xff0c;低端看高端就是黑魔法。 可能有人以为这就是个段子&#xff0c;但有过工作经验的都知道&#xff0c;这其实就是程序员的真实写照&#xff01; 想一想&#xff0c;我们学习、求职、工作的场景中&#xff0c;你一定因…

英语和数学不好是不是学不好编程?

做IT行业观察这个公众号已经三个多月~期间遇到很多想学编程&#xff0c;但又害怕学习编程的人&#xff0c;他们都有同样的问题&#xff1a; 学习编程&#xff0c;是否需要英语&#xff1f; 我数学不好&#xff0c;能学好编程吗&#xff1f; 学习编程&#xff0c;英文和数学肯定…

程序员不需要知道太多数学,你认同吗

之前在知乎看到一个问题&#xff1a; https://www.zhihu.com/question/48617074/answer/111889884 程序员不需要知道太多数学&#xff0c;你认同吗&#xff1f; 我听到的关于学习编程的最常见的顾虑&#xff0c;就是人们认为这需要很多数学知识。其实&#xff0c;大多数编程需…

为何敲代码,学好数学很重要?

数学是编程的灵魂所在。 作者 | Justin Meiners 译者 | 王艳妮&#xff0c;责编 | 屠敏 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09; 以下为译文&#xff1a; 程序员喜欢讨论编程语言。除了辩论它们各自的优点外&#xff0c;我们还喜欢将它们整合到我们的身份认…