DB 생성
학습에 필요한 로또 자료를 1회차부터 스크래핑하자. 자료를 계속해서 업데이트해올 예정이기 때문에 1회차부터 수집하는 것이 편하다.
library(progress)
library(XML)
library(stringr)
library(rvest)
library(xml2)
library(dplyr)
# 처음 DB 생성
url<-'https://search.naver.com/search.naver?sm=tab_hty.top&where=nexearch&query=%EB%A1%9C%EB%98%90&oquery=%EB%A1%9C%EB%98%90&tqi=TmrKidpVuFdsssc0EvVssssssUd-075322'
line<-read_html(url,encoding="UTF-8")
p1<-html_nodes(line,css='._lotto-btn-current em')%>%html_text()
last=as.numeric(gsub('[가-힣]','',p1))
urls<-paste0(
'https://search.naver.com/search.naver?sm=tab_drt&where=nexearch&query=',
1:last,'%ED%9A%8C%EB%A1%9C%EB%98%90')
rows=list()
for(url in urls){
message(url)
temp=url%>%read_html(encoding='UTF-8')%>%html_nodes(css='.num_box')%>%
html_text()%>%str_trim%>%str_split(' ',simplify = T)
rows[[url]]=as.numeric(temp[,1:6])
}
df=data.frame((rows))
df=data.frame(t(data.frame(rows)))
rownames(df)=NULL
df$num=1:last
#DB에 저장
con <- RMariaDB::dbConnect(RMariaDB::MariaDB(),
port = 3307,
host = "ducj3.iptime.org",
user = "ducj", password = Sys.getenv('db_key'),
dbname='mysql'
)
RMariaDB::dbListTables(con)
#RMariaDB::dbWriteTable(con,'lotto',df,overwrite=T)
RMariaDB::dbDisconnect(con)
DB 자동 업데이트
자료를 수집했다면 매 주 자료가 갱신되게 하자. 난 아래와 같이 크론탭을 등록하여 활용한다. 크론탭을 잘 모른다면 아래 포스팅을 참조하기 바란다.
리눅스 주기적 스크립트 실행
📌Cron? Crontab?cron이란? 소프트웨어 유틸리티 중 하나로 컴퓨터 운영체제의 시간을 기반으로 운영되는 잡 스케줄러 중 하나이다. cron은 주어진 일정에 주기적으로 실행되도록 규정해놓은 파일인 crontab(cron table)파일에 의해 구동된다. 장황하게 말하기는 하였지만, 주기적으로 코드가 실행되게 하고싶을 때 활용한다는게 요지이다. 📌설치cron 설치는 아래와 같이 아주 간단하다. cron을 설치해보자. apt-get install cron�

0,15,30,45 13 * * * /usr/local/bin/Rscript /home/ducj/rstudio/2022/lotto/00_DB갱신.R > /home/ducj/rstudio/2022/lotto/logs/list_update_log.log 2>&1
아래는 크론탭에서 실행되는 00_DB갱신.R 코드이다.
library(progress)
library(XML)
library(stringr)
library(rvest)
library(xml2)
library(dplyr)
con <- RMariaDB::dbConnect(RMariaDB::MariaDB(),
port = 3307,
host = "ducj3.iptime.org",
user = "ducj", password = Sys.getenv('db_key'),
dbname='mysql'
)
lastNum=RMariaDB::dbGetQuery(con,'select max(num) from lotto limit 1')[1,]
url<-'https://search.naver.com/search.naver?sm=tab_hty.top&where=nexearch&query=%EB%A1%9C%EB%98%90&oquery=%EB%A1%9C%EB%98%90&tqi=TmrKidpVuFdsssc0EvVssssssUd-075322'
line<-read_html(url,encoding="UTF-8")
p1<-html_nodes(line,css='._lotto-btn-current em')%>%html_text()
last=as.numeric(gsub('[가-힣]','',p1))
if(last>lastNum){
urls<-paste0(
'https://search.naver.com/search.naver?sm=tab_drt&where=nexearch&query=',
last,'%ED%9A%8C%EB%A1%9C%EB%98%90')
temp=url%>%read_html(encoding='UTF-8')%>%html_nodes(css='.num_box')%>%
html_text()%>%str_trim%>%str_split(' ',simplify = T)
df=data.frame(t(data.frame(as.numeric(temp[,1:6]))))
rownames(df)=NULL
df$num=last
RMariaDB::dbAppendTable(con,'lotto',df)
RMariaDB::dbDisconnect(con)
}
로또 추천
로또 추천 알고리즘은 아래와 같다. 최근 100회차의 확률을 9, 5의 이미지화 하고 4주 동안의 자료를 통해 ConvLSTM 모델을 활용하여 구축하였다. 이렇게해서 뽑은 확률 중 전주차에 나온 번호가 덜 출현하게 보정해 준 뒤 가중치를 포함한 랜덤 샘플링을 통해 번호를 추출하였다. 사실 모델은 좀 터무니 없어보이기도하는데, 재미삼아 만든 것이니 모델부분은 알아서 변경하기 바란다.
library(progress)
library(XML)
library(stringr)
library(rvest)
library(xml2)
library(dplyr)
library(plyr)
library(shiny)
library(DT)
library(keras)
library(tensorflow)
library(reticulate)
tfa=import('tensorflow_addons')
con <- RMariaDB::dbConnect(RMariaDB::MariaDB(),
port = 3307,
host = "ducj3.iptime.org",
user = "ducj", password = Sys.getenv('db_key'),
dbname='mysql'
)
data=RMariaDB::dbReadTable(con,'lotto')
data=data[order(decreasing = T,data$num),]
print(head(data,3))
data2<-matrix(0,ncol=45,nrow=nrow(data));colnames(data2)=1:45
for(i in 1:nrow(data2)){
for(j in 1:6){
for(k in 1:45){
if(data[i,j]==k)data2[i,k]<-1}}}
rownames(data2)<-nrow(data2):1
dl_df=array(0,c(nrow(data),9,5,2))
for(i in 1:(nrow(data)-101)){
dl_df[i,,,1]=t(matrix(apply(data2[i:(i+100),],2,sum)/100,9,5))
}
for(i in 1:(nrow(data)-101)){
dl_df[i,,,2]=t(matrix(apply(data2[i:(i+100),],2,sum)/100,9,5))-
t(matrix(apply(data2,2,sum)/nrow(data),9,5))
}
tr_x=dl_df[100:(nrow(dl_df)-100),,,]
tr_x=array(tr_x,c(nrow(tr_x),1,9,5,2))
tr_y=data2[100:(nrow(data)-100),][-nrow(data),]
te_x=dl_df[2:100,,,]
dim(te_x)=c(nrow(te_x),1,9,5,2)
te_y=data2[1:99,]
arr=list()
for(i in 4:1){
arr[[i]]=abind::abind(tr_x[i:nrow(tr_x),,,,,drop=F],
array(NA,c(i-1,1,9,5,2)),along=1)
}
arr=abind::abind(arr,along=2)
idx=!apply(is.na(arr),1,any)
tr_x=arr[idx,,,,,drop=F]
tr_y=tr_y[idx,]
dim(tr_x)
arr=list()
for(i in 4:1){
arr[[i]]=abind::abind(te_x[i:nrow(te_x),,,,,drop=F],
array(NA,c(i-1,1,9,5,2)),along=1)
}
arr=abind::abind(arr,along=2)
idx=!apply(is.na(arr),1,any)
te_x=arr[idx,,,,]
te_y=te_y[idx,]
#raster::plot(raster::raster(te_x[1,,,1]))
tensorflow::set_random_seed(42)
modelInput=keras::layer_input(shape=c(4,9,5,1))
a_fun='relu'#'swish'
convLstm1=modelInput%>%
keras::layer_conv_lstm_2d(
filters =128,kernel_size = 3,activation = a_fun,
padding = 'same',return_sequences = T)
#norm1=convLstm1%>%layer_batch_normalization()
norm1=tfa$layers$InstanceNormalization()(convLstm1)
convLstm2=norm1%>%
layer_conv_lstm_2d(
filters=23,kernel_size = 3,activation = a_fun,
padding = 'same',return_sequences = T)
#norm2=convLstm2%>%layer_batch_normalization()
norm2=tfa$layers$InstanceNormalization()(convLstm2)
conv3d=
norm2%>%layer_conv_3d(filters=16,kernel_size = 1,activation=a_fun,
padding='same',data_format = 'channels_last')
modelOutput=conv3d%>%layer_flatten()%>%layer_dense(units=45)
tf$random$set_seed(42L)
model=keras::keras_model(modelInput,modelOutput)
model%>%summary()
model %>% compile(
optimizer = optimizer_adam(learning_rate = .0005),
loss = 'binary_crossentropy',metric='acc')
es=keras::callback_early_stopping(monitor='val_loss',mode='min',patience = 20)
cp=keras::callback_model_checkpoint(paste0('./convLstm.h5'),
monitor = 'val_loss',mode='min')
print({
history = model%>%fit(tr_x[,,,,1,drop=F],tr_y,validation_split=.2,
epochs=500L,verbose=1,callbacks=list(es,cp),batch_size=40L)
})
model=load_model_hdf5('convLstm.h5')
pred=(model%>%predict(te_x[,,,,1,drop=F]))
pred[1,]
which(order(pred[1,])%in%c(1:10))
tot=0
for(i in 1:nrow(pred)){
tot=tot+length(intersect(which(order(pred[i,])%in%c(1:10)),data[i,1:6]))
}
tot
tot=0
for(i in 1:nrow(pred)){
set.seed(4)
tot=tot+length(intersect(
sample(1:45,10,prob=ifelse(pred[i,]<0,0,pred[i,])),data[i,1:6]))
}
# 예측
te_x=dl_df[1:100,,,]
dim(te_x)=c(nrow(te_x),1,9,5,2)
arr=list()
for(i in 4:1){
arr[[i]]=abind::abind(te_x[i:nrow(te_x),,,,,drop=F],
array(NA,c(i-1,1,9,5,2)),along=1)
}
arr=abind::abind(arr,along=2)
idx=!apply(is.na(arr),1,any)
te_x=arr[idx,,,,1,drop=F]
n=2
which(order((model%>%predict(te_x))[n,])%in%1:6);data[(n-1):n,]
pred=(model%>%predict(te_x))[1,]
delete=unlist(data[n,1:6])
del=setdiff(delete,
intersect(which(order(pred)%in%1:6),delete))
pred[del]=0
pred=ifelse(pred<0,0,pred)
pred=pred+as.vector(t(te_x[1,1,,,1]))
for(i in 1:10){
set.seed(i)
print(sort(sample(1:45,6,prob=pred)))
}
편리하게 호출하기
~/.bashrc에 아래 명령어를 등록한 뒤 활용하고 있다.
alias lotto="Rscript /home/ducj/rstudio/2022/lotto/예측.R"
향후 계획
argument를 받아오는 형식으로 로또 구매 개수를 변경할 수 있게 활용해보면 좋을 것 같고, 대시보드화 하는 것도 괜찮은 생각일 것 같다.
당첨 목록
재미삼아 매 주 만원씩 구매를 할 예정이다. 우선 2주를 테스트해보았는데 50% 손실이 발생하였다. 재미삼아 한 것 치곤 나쁘지 않은 것 같다.

