336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

네 번째 프로젝트 순서

 

1. 간단한 intro

2. 웹 크롤링 및 전처리

3. 모델 학습 및 평가

 


기간 : 21.06.17 ~ 21.06.24

 

언어 : Python

패키지 : Tensorflow, Scikit-learn, Okt, Beautiful Soup, Pandas, Maplotlib

툴 : Colab, PyCharm, Jupyter Notebook

 

주제 선정 이유 : 보고 있는 카테고리에서, 불필요한 상품까지 노출되는 경우 필터링. 카테고리 분류를 조금 더 깔끔하게 하기 위함.

 

 

 

 

팀원 전체가 프로젝트에 치이고 있는 상태라서 이번에는 지금까지 했던 것을 정리하는 시간을 갖기로 했다. 도서, 영화 등 다른 것도 분류를 할 수 있었으나 이미 다른 팀에서 주제를 선정해서 넘어가게 됐다. 

 

새로운 팀원들이 코드를 다루거나 이해하는 것이 쉽지 않다는 의견이 있어서, 함께 기초부터 같이 하기로 했다. 학원에서 배운 크롤링 기법은 대부분 Selenium을 사용해서 시간이 오래 걸렸다. 그래서 웬만하면 시간이 적게 드는 bs4로 해결하기 위한 코드를 새로 만들었다. 동적인 크롤링을 요하는 상황에서는 어쩔 수 없이 사용하긴 했지만.. 

 

동적 크롤링 : Selenium정적 크롤링 : Beautiful Soup

 

 

 

 

 

GitHub - kes76963/asia_project: NLP - Word2Vec, TFIDF / CNN - Category Classify

NLP - Word2Vec, TFIDF / CNN - Category Classify. Contribute to kes76963/asia_project development by creating an account on GitHub.

github.com

셀레니움보다 bs4가 더 확실하게 긁어오긴 하지만, 처음에 코드를 짜는데 은근히 시간이 걸린다. 셀레니움은 그냥 순서대로 가고 xpath 붙여넣기를 하면 되는데, bs4는 정적이다 보니 각 페이지별 url이나 num_id 값을 구해야하해서 고려할 요소들이 많다. 그리고 코드를 새로 만들면서 느낀 것이 홈페이지 개발자분들께서 가끔씩 특이한 이벤트를 집어넣으셔서 오류가 생긴다. try except 문을 반드시 써주도록 하자 ㅠㅠ

 

 

 

 

시간이 있는 김에 저번에 designer로 만들었던 gui로 앱 느낌의 구현을 하려고 했으나 실패하여, 다섯번째 프로젝트에 적용을 했다. 이 부분은 기대하셔도 좋다. 엄청난 발전이 있었다고 장담한다. (안드로이드 스튜디오로 앱을 만들 때처럼 노가다 느낌이 강하게 느껴진다.)

 

 

 

 

 

 

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

세 번째 프로젝트 순서 

 

1. 식물 병충해 자료 파일분류

2. CNN 모델링

3. 이미지화

4. CNN, AlexNet, VGG-16 모델 평가

 


프로젝트 발표 준비 전 모델 평가 및 성능 테스트? 단계

 

참고로 그린라이트로 정하게 된 배경에는.. 예전에 마녀사냥에서 자주 사용했던 그린라이트, 불빛이 들어온다는 느낌에서 약간의 언어유희를 사용했다. 식물이 아픈지 안 아픈지 제대로 정의해준다(right)와 밝혀준다(light)의 조합이랄까..

 

 

 

 

이미지 인식 모델에서 자주 사용 되는 모델 AlexNet, VGG-16, GoogleNet(2012~2014), ResNet, InceptionV3 (2014 이후) 매년  ImageNet Large Scale Visual Recognition Challenge( ILSVRV ) 대회에서 성능 비교를 한다. 요즘은 점차 정확도에 차이가 줄어서 0.01% 차이로도 순위가 갈린다고 수업시간에 들었다.

 

 

 

 

 

AlexNet

2012AlexNet 은 이전의 모든 경쟁자를 압도할 정도로, 상위 5개 오류를 26%에서 15.3%로 줄였다고 한다. 총 8개의 layer로 구성 됐고, 5개는 convolutional layer로 구성이 됐다.  11x11, 5x5, 3x3, 컨볼루션, 최대 풀링, 드롭아웃, 데이터 증대, ReLU 활성화 등으로 구성이 됐다. 

 

입력 크기 256x256x3

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
def Alexnet_model():
    inputs = Input(shape=(64643))
 
    conv1 = Conv2D(filters=48, kernel_size=(1010), strides=1, padding="valid", activation='relu')(inputs)
    pool1 = MaxPooling2D(pool_size=(33), strides=2, padding="valid")(conv1)
    nor1 = tf.nn.local_response_normalization(pool1, depth_radius=4, bias=1.0, alpha=0.001/9.0, beta=0.75)
 
    conv2 = Conv2D(filters=128, kernel_size=(33), strides=1, padding="same", groups=2, activation='relu')(nor1)
    pool2 = MaxPooling2D(pool_size=(33), strides=2, padding="valid")(conv2)
    nor2 = tf.nn.local_response_normalization(pool2, depth_radius=4, bias=1.0, alpha=0.001 / 9.0, beta=0.75)
 
    conv3 = Conv2D(filters=192, kernel_size=(33), strides=1, padding="same", activation='relu')(nor2)
    conv4 = Conv2D(filters=192, kernel_size=(33), strides=1, padding="same", activation='relu')(conv3)
    conv5 = Conv2D(filters=128, kernel_size=(33), strides=1, padding="same", activation='relu')(conv4)
    pool3 = MaxPooling2D(pool_size=(33), strides=2, padding="valid")(conv5)
    drop1 = Dropout(0.5)(pool3)
    nor3 = tf.nn.local_response_normalization(drop1, depth_radius=4, bias=1.0, alpha=0.001 / 9.0, beta=0.75)
 
    flat = Flatten()(nor3)
    dense1 = Dense(units=2048, activation='relu')(flat)
    dense2 = Dense(units=1024, activation='relu')(dense1)
    logits = Dense(units=20, activation='softmax')(dense2)
 
    return Model(inputs=inputs, outputs=logits)
 
 
model=Alexnet_model()
model.summary()
model.compile(loss='binary_crossentropy', optimizer=otm, metrics=["accuracy"])
cs

 

 

 

 

 

GoogleNet

ILSVRC 2014 대회의 우승자는 Google의 GoogLeNet(Inception V1이라고도 함)이다. 6.67%의 상위 5위 오류율을 달성. 인간 수준의 성과에 매우 가까웠다고 한다. 27개의 pooling layer가 포함된 22개의 layer로 구성된다. 

 

 

 

 

EarlyStopping을 했을 때 이미지 사이즈에 맞게 변형해서 만든 저희 모델들의 결과

 

 

 

 

 

VGG-16

ILSVRC 2014 대회의 준우승은 커뮤니티에서 VGGNet이다. VGGNet은 16개의 컨볼루션 레이어로 구성되며 매우 균일한 아키텍처로 구성됐다. AlexNet과 유사한 3x3 회선만 있지만 필터가 더 많다. 이미지에서 특징을 추출하기 위해 커뮤니티에서 선호되고 있다. 단점은 훈련에 많은 시간이 걸리고, 네트워크 아키텍처 가중치가 크다.

 

이미지 크기 244x244

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
def vgg_16():
    inputs = Input(shape=(64643))
    conv1_1 = Conv2D(filters=32, kernel_size=(33), strides=1, padding="same", activation='relu')(inputs)
    conv1_2 = Conv2D(filters=32, kernel_size=(33), strides=1, padding="same", activation='relu')(conv1_1)
    pool1 = MaxPooling2D(pool_size=(22), strides=2, padding="valid")(conv1_2)
    nor1 = BatchNormalization()(pool1)
 
    conv2_1 = Conv2D(filters=64, kernel_size=(33), strides=1, padding="same", activation='relu')(nor1)
    conv2_2 = Conv2D(filters=64, kernel_size=(33), strides=1, padding="same", activation='relu')(conv2_1)
    pool2 = MaxPooling2D(pool_size=(22), strides=2, padding="valid")(conv2_2) # 16
    nor2 = BatchNormalization()(pool2)
 
    conv3_1 = Conv2D(filters=128, kernel_size=(33), strides=1, padding="same", activation='relu')(nor2)
    conv3_2 = Conv2D(filters=128, kernel_size=(33), strides=1, padding="same", activation='relu')(conv3_1)
    conv3_3 = Conv2D(filters=128, kernel_size=(33), strides=1, padding="same", activation='relu')(conv3_2)
    pool3 = MaxPooling2D(pool_size=(22), strides=2, padding="valid")(conv3_3)
    nor3 = BatchNormalization()(pool3)
 
    # 4 layers
    conv4_1 = Conv2D(filters=128, kernel_size=(33), strides=1, padding="same", activation='relu')(nor3)
    conv4_2 = Conv2D(filters=128, kernel_size=(33), strides=1, padding="same", activation='relu')(conv4_1)
    conv4_3 = Conv2D(filters=128, kernel_size=(33), strides=1, padding="same", activation='relu')(conv4_2)
    pool4 = MaxPooling2D(pool_size=(22), strides=2, padding="valid")(conv4_3) # 4
    nor4 = BatchNormalization()(pool4)
 
    # 5 layers
    conv5_1 = Conv2D(filters=128, kernel_size=(33), strides=1, padding="same", activation='relu')(nor4)
    conv5_2 = Conv2D(filters=128, kernel_size=(33), strides=1, padding="same", activation='relu')(conv5_1)
    conv5_3 = Conv2D(filters=128, kernel_size=(33), strides=1, padding="same", activation='relu')(conv5_2)
    pool5 = MaxPooling2D(pool_size=(22), strides=2, padding="valid")(conv5_3)
    nor5 = BatchNormalization()(pool5)
    drop5 = Dropout(0.5)(nor5)
 
    flatten1 = Flatten()(drop5)
    dense1 = Dense(units=2048, activation=tf.nn.relu)(flatten1)
    dense2 = Dense(units=1024, activation=tf.nn.relu)(dense1)
 
    logits = Dense(units=20, activation='softmax')(dense2)
    return Model(inputs=inputs, outputs=logits)
 
model=vgg_16()
model.compile(loss='categorical_crossentropy', optimizer=otm, metrics=["accuracy"])
model.summary()
cs

 

 

 

모델 성능 평가

외부 데이터를 수집하려고 직접 찾아보면서 고생을 하다가, 관련 자료를 깃허브에 친절하게 올려주신 분이 있어서 감사히 썼습니다.

 

https://github.com/spMohanty/PlantVillage-Dataset/tree/master/raw/color

 

GitHub - spMohanty/PlantVillage-Dataset: Dataset of diseased plant leaf images and corresponding labels

Dataset of diseased plant leaf images and corresponding labels - GitHub - spMohanty/PlantVillage-Dataset: Dataset of diseased plant leaf images and corresponding labels

github.com

 

 

 

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
##########################
# 테스트 데이터 50개 랜덤으로 평가
##########################
 
 
########################################
#class name으로 변경
from os import rename, listdir
 
path2 = "./tests"  #TEST_DATA => tests로 수정함
 
list3 = list(str(i) for i in range(20))
dict3 = dict(zip(combined_labels,list3))
 
for fname in os.listdir(path2):
    newname = dict3.get(fname)
    try:
        if newname not in os.listdir(path2) :    
            os.rename(os.path.join(path2, fname), os.path.join(path2,newname))
    except:
        counter = True
 
if counter :        
    print("이미 변경 완료")
else :
    print("class name으로 변경완료")
 
 
########################################
### 필수 아님 #####
 
#labeled_name으로 되돌리고 싶을 때
list3 = list(str(i) for i in range(20))
dict3 = dict(zip(list3, combined_labels))
 
for fname in os.listdir(path2):
    newname = dict3.get(fname)
    try :
        if newname not in os.listdir(path2) :    
            os.rename(os.path.join(path2, fname), os.path.join(path2,newname))
    except:
        counter = True
 
if counter :        
    print("이미 변경 완료")
else :
    print("labeled name으로 변경완료")
 
 
########################################
import os, re, glob
import cv2
import numpy as np
from sklearn.model_selection import train_test_split
from tensorflow.keras.preprocessing.image import img_to_array
import random
 
# class에 따른 본래 label
t_list = list(str(i) for i in range(20))
t_dict = dict(zip(t_list, combined_labels))
 
 
 
########################################
#테스트 데이터 list
tests ='./tests'
tests_list = os.listdir(tests)
 
model = load_model('model1.h5')     # 자신의 model load
 
def convert_image_to_array(image_dir):
    try:
        image = cv2.imread(image_dir)
        if image is not None :
            image = cv2.resize(image, dsize=(64,64))
            return img_to_array(image)
        else :
            return np.array([])
    except Exception as e:
        print(f"Error : {e}")
        return None
 
def predict_disease(image_path, num):
    global count
    image_array = convert_image_to_array(image_path)
    np_image = np.array(image_array, dtype=np.float32) / 225.0
    np_image = np.expand_dims(np_image,0)
    result = model.predict_classes(np_image)
    c = result.astype(str)[0]
    if c == num : 
        count += 1
    return count
 
for i in range(20):
    num = str(i)
    tests_file = os.listdir(f'tests/{num}')
    count = 0
    max = len(tests_file)
    
    for j in range(50):
        ran_num = random.randint(0,max) # 임의의 숫자 추출
        tests_path =  f'tests/{num}/' + os.listdir(f'./tests/{num}')[ran_num]
        predict_disease(tests_path, num)
 
    print(f'###### 테스트 데이터 {t_dict.get(num)} 의 정확도 입니다 #######' )
    print('accuracy: {:0.5f}'.format(count/50))
 
print('테스트 완료')
cs

 

###### 테스트 데이터 Corn_(maize)_Common_rust_ 의 정확도 입니다 #######
accuracy: 1.00000
###### 테스트 데이터 Corn_(maize)_healthy 의 정확도 입니다 #######
accuracy: 1.00000
###### 테스트 데이터 Grape_Black_rot 의 정확도 입니다 #######
accuracy: 0.96000
###### 테스트 데이터 Grape_Esca_(Black_Measles) 의 정확도 입니다 #######
accuracy: 0.94000
###### 테스트 데이터 Grape_Leaf_blight_(lsariopsis_Leaf_Spot) 의 정확도 입니다 #######
accuracy: 0.98000
###### 테스트 데이터 Orange_Haunglongbing_(Citrus_greening) 의 정확도 입니다 #######
accuracy: 0.98000
###### 테스트 데이터 Pepper,_bell_Bacterial_spot 의 정확도 입니다 #######
accuracy: 0.94000
###### 테스트 데이터 Pepper,_bell_healthy 의 정확도 입니다 #######
accuracy: 0.98000
###### 테스트 데이터 Potato_Early_blight 의 정확도 입니다 #######
accuracy: 1.00000
###### 테스트 데이터 Potato_Late_blight 의 정확도 입니다 #######
accuracy: 0.98000
###### 테스트 데이터 Soybean_healthy 의 정확도 입니다 #######
accuracy: 0.96000
###### 테스트 데이터 Squash_Powdery_mildew 의 정확도 입니다 #######
accuracy: 0.94000
###### 테스트 데이터 Tomato_Bacterial_spot 의 정확도 입니다 #######
accuracy: 0.90000
###### 테스트 데이터 Tomato_Early_blight 의 정확도 입니다 #######
accuracy: 0.90000
###### 테스트 데이터 Tomato_Late_blight 의 정확도 입니다 #######
accuracy: 0.84000
###### 테스트 데이터 Tomato_Septoria_leaf_spot 의 정확도 입니다 #######
accuracy: 0.96000
###### 테스트 데이터 Tomato_Spider_mites_Two-spotted_spider_mite 의 정확도 입니다 #######
accuracy: 0.96000
###### 테스트 데이터 Tomato_Target_Spot 의 정확도 입니다 #######
accuracy: 0.96000
###### 테스트 데이터 Tomato_Tomato_Yellow_Leaf_Curl_Virus 의 정확도 입니다 #######
accuracy: 0.98000
###### 테스트 데이터 Tomato_healthy 의 정확도 입니다 #######
accuracy: 0.98000
테스트 완료
----------------------------------------------------------------------------------------------------
테스트 정확도 :  0.957

 

 

 

 

 

 

이건 팀원이 정확도와 loss 값 그래프를 보고 그린 그래프

수고하셨습니다

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

세 번째 프로젝트 순서

1. 식물 병충해 자료 파일분류
2. CNN 모델링
3. 이미지화
4. CNN, AlexNet, VGG-16 모델 평가


예전에 네이버 블로그를 하듯이 글을 자유롭게 쓰고 싶은데, 혹시나 틀릴까 하는 마음에 쉽게 글이 작성이 되지 않는다. 너무나 쉽게 생각을 한 것이 아닌가 싶기도 하지만.. 그래도 팀프로젝트 5개 + 현재하고 있는 파이널 프로젝트 관련된 글을 작성하려고 한다.

이번에는 실패했던 시각화에 대한 내용이라 너무 관심 있게 보지는 말아주셨으면 한다. (부끄러우니까)

CNN 모델링을 했을 때 정확도가 90퍼센트 이상이 나왔지만 이를 어떻게 하면 더 개선시킬 수 있을까에 대한 고민을 많이 했다. 그러다가 생각한 것이 학습에 필요한 데이터는 전체 사진이 아닌 식물의 사진만 중요하지 않을까? 그래서 배경을 제외하고 학습을 해보기로 했다.

-배경과 식물 분리하는 mask
-sharpen 값을 줘서 병충해가 있는 데이터는 조금 더 부각이 되게


여러 예제들을 찾아보다가 이거다! 하는 예제가 있어서 처음에 단순히 따라하는 식으로 해봤다! 이건 그냥 너무 쉽게 되겠는 걸?






결과는 참담했다.



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
#배경과 분리하기 위해서 hsv 값 조정
def create_mask_for_plant(image):
    image_hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
 
    sensitivity = 30
    lower_hsv = np.array([60 - sensitivity, 10050])
    upper_hsv = np.array([60 + sensitivity, 255255])
 
    mask = cv2.inRange(image_hsv, lower_hsv, upper_hsv)
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (11,11))
    mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
    
    return mask
 
#비트연산 and 모든 색이 같을 때만 색 표현
def segment_plant(image):
    mask = create_mask_for_plant(image)
    output = cv2.bitwise_and(image, image, mask = mask)
    return output
 
#샤픈, 특징을 조금더 부각시키게
def sharpen_image(image):
    image_blurred = cv2.GaussianBlur(image, (00), 3)
    image_sharp = cv2.addWeighted(image, 1.5, image_blurred, -0.50)
    return image_sharp
cs

sensitivity를 어떻게 하느냐에 따라서 결과값이 많이 차이가 났다. 위의 경우가 1을 뒀을 때고, 아래의 경우가 30일 때 실행을 했는데 단점이 배경 없이 잎만 찍은 데이터의 경우 분간이 제대로 되지가 않았다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
%matplotlib inline
import os
import matplotlib
import matplotlib.pyplot as plt
import pandas as pd
import cv2
import numpy as np
from glob import glob
import seaborn as sns
 
image = cv2.imread('8_9_2.jpg')
 
image_mask = create_mask_for_plant(image)
image_segmented = segment_plant(image)
image_sharpen = sharpen_image(image_segmented)
 
fig, axs = plt.subplots(14, figsize=(2020))
axs[0].imshow(image)
axs[1].imshow(image_mask)
axs[2].imshow(image_segmented)
axs[3].imshow(image_sharpen)
cs

그래도 이전보다는 배경과 잎의 경계가 뚜렷하게 나눠지는 것을 확인할 수 있었다. 그런데도 문제가 되는 부분은 잎을 나눈데 있어서 hsv 값을 비슷하게 판단해서인지? 식물의 잎도 같이 사라지는 마술을 보여줬다. 병충해가 있어서 저렇게 빵꾸가 뚤렸다고 생각하면 좋겠지만, 병충해가 있는 부분은 중간 아래부분이다.

라이트룸이나 사진 편집을 할 때, 어떤 식으로 특정 색만 색깔을 남길까 생각을 해봤을 때.. hsv 전체 색 공간이 아닌 말 그대로 특정색만 추출하면 되는데 왜 이생각을 못 했을까 하고 무릎을 탁 쳤다. 그래서 초록색을 가장 잘 나타낼 수 있는 값들을 조절해봤다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np 
import cv2 
img_color = cv2.imread('4_7_5.jpg'
 
img_hsv = cv2.cvtColor(img_color, cv2.COLOR_BGR2HSV) # cvtColor 함수를 이용하여 hsv 색공간으로 변환 
lower_green = (03030)
upper_green = (80255255
img_mask = cv2.inRange(img_hsv, lower_green, upper_green) #hsv 색공간 전체가 아닌 지정한 범위 내의 
img_result = cv2.bitwise_and(img_color, img_color, mask = img_mask) # 바이너리 이미지를 마스크로 사용하여 원본이미지에서 범위값에 해당하는 부분을 획득 
 
 
fig, axs = plt.subplots(13, figsize=(2020))
axs[0].imshow(img_color) 
axs[1].imshow(img_mask) 
axs[2].imshow(img_result) 
cs

이정도면 나름 선방한 결과였다. 하지만 이 이미지들을 다 다시 저장시키고, 식물마다 톤이 다르고, 사진 찍은 곳 조명이 달라서 lower_green과 upper_green을 일일이 다 조정해야 한다는 치명적인 단점이 있어서 실패를 했다.

그래도 어떤 식으로 사진과 배경을 분리하고, 샤픈을 줘서 CNN이 사진의 특징을 학습할 때 조금 더 도움을 줄 수 있지 않을까 하는 생각이 든다.

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

세 번째 프로젝트 순서 

 

1. 식물 병충해 자료 파일분류

2. CNN 모델링

3. 이미지화

4. CNN, AlexNet, VGG-16 모델 평가




쉼호흡부터 한 번 후~ 하~ 파이썬 팀프로젝트 당시에도 내용이 어려웠는데, 지금 다시 봐도 내용이 어렵다. 그래도 블로그로 글을 쓰면서 다시 정리한다는 생각으로 일단 간단하게 정리를 해봤다. 

 

 

인공지능 : 인간의 지능을 기계로 구현한 것

머신러닝 : 인간이 개발한 알고리즘을 컴퓨터 언어를 통해 기계에게 학습. 데이터로부터 스스로 학습을 통해 데이터를 가장 잘 표현하는 규칙, 패턴을 찾는 것(함수로 정의하는 과정 - x와 y관계)

딥러닝 : 인간의 뇌 신경망을 모방한 인공 신경망, 분류 예측 등의 머신러닝 수행  (입력층과 출력층 사이에 여러 층을 거쳐서 학습)

 

지도학습 : 입력 데이터(feature, 독립변수)와 출력데이터(class, target, 종속변수)를 이용해서 학습 – 분류, 회귀

비지도학습 : 입력 데이터를(feature)를 이용한 학습 – 군집

강화 학습 : 학습 결과에 대한 보상이 주는 방식으로 학습

 

train data : 모델 구축시 사용되는 데이터 (학습)

test data  : 구축된 모델 검증하는데 사용 (검증)

 

케라스(keras) ㅡ 저차원의 딥러닝 라이브러리를 래핑한 고차원의 라이브러리

sequential – 딥러닝의 구조를 한 층 한층 순차적으로 층을 쌓은 신경망 모델

dense – 신경망 모델에 포함된 완전 연결층 (각 층이 각각 어떤특성을 가질지 옵션을 설정)

activation – 다음 층으로 어떻게 값을 넘길지 결정하는 부분(relu, sigmoid, softmax 등)

loss – 한 번 신경망이 실행될 때마다 오차 값을 추적하는 함수 (mse, rmse 등)

optimizer – 오차를 어떻게 줄여 나갈지 정하는 함수 (adam, 경사하강법, 확률적 경사하강법)

input_shape – 입력 데이터의 형태

epoch - 주어진 데이터를 신경망 모델에서 한 번 훈련하는 단위

 

 

 

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
from keras.models import Sequential
from keras.callbacks import EarlyStopping, ModelCheckpoint
from keras.layers import Dropout, Activation, Dense
from keras.layers import Flatten, MaxPooling2D
from keras.layers import Conv2D
from keras.models import load_model
from keras.optimizers import Adam
from tensorflow.keras.layers import BatchNormalization
import os
import numpy as np
 
# dataset 불러오기
if not os.path.exists("./model/"):
    os.mkdir('./model/')
X_train, X_test, Y_train, Y_test = np.load('./data/img_data.npy', allow_pickle=True)
 
# 기본 설정
categories = list(str(i) for i in range(20))
EPOCHS = 30
BS = 32
INIT_LR = 1e-3
n_classes = len(categories)
 
 
# CNN 모델
model = Sequential()
model.add(Conv2D(32, (33), padding="same", input_shape=X_train.shape[1:], activation='relu'))
model.add(MaxPooling2D(pool_size=(22)))
model.add(Dropout(0.2))
 
model.add(Conv2D(64, (33), padding="same", activation='relu'))
model.add(MaxPooling2D(pool_size=(22)))
model.add(Dropout(0.2))
 
model.add(Conv2D(64, (33), padding="same", activation='relu'))
model.add(MaxPooling2D(pool_size=(22)))
model.add(Dropout(0.25))
 
model.add(Flatten())
model.add(Dense(256, activation='relu'))
model.add(Dropout(0.5))
 
model.add(Dense(n_classes, activation='softmax'))
 
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
 
 
# 모델 요약
print(model.summary())
 
# 모델 학습
# opt = Adam(lr=INIT_LR, decay=INIT_LR / EPOCHS)
# model.compile(loss="binary_crossentropy", optimizer=opt,metrics=["accuracy"])
# print("트레이닝 ")
 
#EarlyStopping
model_dir = './model'
model_filepath = "model/se_cnn.h5"
if os.path.exists(model_filepath):
    model.load_weights(model_filepath)
else:
    model_path = model_dir + '/multi_img_classification.model'
    checkpoint = ModelCheckpoint(filepath=model_path, monitor='val_loss', verbose=1, save_best_only=True)
    early_stopping = EarlyStopping(monitor='val_loss', patience=6)
 
    model.fit(
        X_train,
        Y_train,
        batch_size=BS,
        validation_data=(X_test, Y_test),
        steps_per_epoch=len(X_train) // BS,
        epochs=EPOCHS, verbose=1,
        callbacks=[checkpoint, early_stopping]
    )
    model.save_weights(model_filepath)
 
cs

지속적으로 층을 바꿔가면서 모델링을 진행했다. 솔직히 어떤 부분에서 무엇을 바꿔야 하는지 이해는 쉽지 않았다. 같은 모델을 돌린다고 해도 train, test 데이터가 바뀌어서 학습이 진행되기 때문에, 데이터 중에 인간도 판별하기 힘든 데이터가 많이 섞여 있으면 정확도가 계속 떨어졌다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#모델 시각화
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(acc) + 1)

#모델 정확도
plt.rc('font',family='Malgun Gothic')
plt.plot(epochs, acc, 'b', label='Training accurarcy')
plt.plot(epochs, val_acc, 'r', label='Testing accurarcy')
plt.title('학습과 훈련 정확도')
plt.legend()
plt.figure()
 
#모델 손실
plt.plot(epochs, loss, 'b', label='Training loss')
plt.plot(epochs, val_loss, 'r', label='Testing loss')
plt.title('학습과 훈련 손실')
plt.legend()
plt.show()
cs

신기한 부분은 모델을 돌리게 되면 정확도가 90%가 넘는게 쉽지 않은데... 데이터 자체가 양질의 데이터고 분류가 잘 돼서 더 잘 학습이 되는 것으로 보인다. 다음에는 시각화와 어떻게 하면 정확도를 높일 수 있을지에 대한 고민이 담긴 글이다. 실패는 했지만 그래도 어떻게 하면 계선을 할 수 있을까 많은 고민을 했다.

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

세 번째 프로젝트 순서 

 

1. 식물 병충해 자료 파일분류

2. CNN 모델링

3. 이미지화

4. CNN, AlexNet, VGG-16 모델 평가


언어 : Python

패키지 : Tensorflow, Keras, NumPy, Maplotlib

툴 : Colab, PyCharm, Jupyter Notebook

 

여러가지 주제를 가지고 고민을 하다가 딥러닝을 제대로 배우지도 않은 상태에서 진행을 하려고 해서 쉽지 않았다. 그래서 이런 저런 자료들을 찾아보다가 '정보통신산업진흥원 주최  2020 인공지능 문제해결 경진대회 예선 문제' 데이터셋이 있어서 배운 것을 적용해보는 시간을 가지려고 했다.

 

 

 

train 데이터가 16000개, test 데이터가 3997개로 구성되어 있었다. 하지만 test 데이터는 라벨링이 되어있지 않아서, 모델을 학습시킨다고 해서 딥러닝 모델이 얼마나 좋은지 성능을 평가할 수 없었다. 대회 자체가 워낙 폐쇄적이라서 현재 데이터도 이미 가지고 있던 팀원이 있어서 구할 수 있었다. 직접 전화도 해봤지만 정답을 알려주긴 어렵다는 말을 들었다. 그래서 추후에 데이터를 따로 수집을 했다.

 

 

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
56
plant_label = ["Apple""Blueberry""Cherry_(including_sour)""Corn_(maize)",
              "Grape""Orange""Peach""Pepper,_bell""Potato""Raspberry",
              "Soybean""Squash""Strawberry""Tomato"]
 
disease_label = ["Apple_scab""Bacterial_spot""Black_rot""Cedar_apple_rust",
                 "Cercospora_leaf_spot_Gray_leaf_spot""Common_rust_",
                 "Early_blight""Esca_(Black_Measles)""Haunglongbing_(Citrus_greening)",
                 "Late_blight""Leaf_Mold""Leaf_blight_(lsariopsis_Leaf_Spot)",
                 "Leaf_scorch""Northem_Leaf_Blight""Powdery_mildew""Septoria_leaf_spot",
                 "Spider_mites_Two-spotted_spider_mite""Target_Spot",
                 "Tomato_Yellow_Leaf_Curl_Virus""Tomato_mosaic_virus""healthy"]
 
combined_labels = ["Corn_(maize)_Common_rust_""Corn_(maize)_healthy""Grape_Black_rot",
                   "Grape_Esca_(Black_Measles)""Grape_Leaf_blight_(lsariopsis_Leaf_Spot)",
                   "Orange_Haunglongbing_(Citrus_greening)""Pepper,_bell_Bacterial_spot",
                   "Pepper,_bell_healthy""Potato_Early_blight""Potato_Late_blight",
                   "Soybean_healthy""Squash_Powdery_mildew""Tomato_Bacterial_spot",
                   "Tomato_Early_blight""Tomato_Late_blight""Tomato_Septoria_leaf_spot",
                   "Tomato_Spider_mites_Two-spotted_spider_mite""Tomato_Target_Spot",
                   "Tomato_Tomato_Yellow_Leaf_Curl_Virus""Tomato_healthy"]
len(combined_labels)


####################################################################################
#라벨 폴더 생성
import shutil, glob, os
path = "./train"
if not os.path.isdir(path):                                                           
    os.mkdir(path)
 
for i in range(len(combined_labels)):
    path2 = f"./train/{combined_labels[i]}"
    if not os.path.isdir(path2):                                                           
        os.mkdir(path2)
 
 
####################################################################################
#사진 폴더별로 복사붙여넣기 
path_data = './eda/식물병충해data/train'
file_list = os.listdir(path_data)
 
#plant_label 딕셔너리
list1 = list(range(14))
dict1 = dict(zip(list1,plant_label))
 
#disease_label 딕셔너리
list2 = list(range(21))
dict2 = dict(zip(list2,disease_label))
 
#
for i in range(16000):
    cl_list = file_list[i][:-4].split('_'#이름을 나누기 plant + disease + 번호
    find_name = dict1.get(int(cl_list[0])) + '_' + dict2.get(int(cl_list[1])) #Combined Labels 이름 찾기
    if file_list[i] not in path_data :
        shutil.copy(f'{path_data}/{file_list[i]}', f'{path}/{find_name}')
    #print('복사완료')
cs

폴더명이 숫자 클래스로 필요할 수도 있고 combined_labels로 필요할 수도 있어서 폴더명을 바꾸기 쉽게 수정하는 코드를 추가했다. 

 

 

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
#class name으로 변경
from os import rename, listdir
 
path = "./train"
 
list3 = list(str(i) for i in range(20))
dict3 = dict(zip(combined_labels,list3))
 
for fname in os.listdir(path):
    newname = dict3.get(fname)
    if newname not in os.listdir(path) :    
        os.rename(os.path.join(path, fname), os.path.join(path,newname))
print("class name으로 변경완료")
 
 
 
#labeled_name으로 되돌리고 싶을 때
list3 = list(str(i) for i in range(20))
dict3 = dict(zip(list3, combined_labels))
 
for fname in os.listdir(path):
    newname = dict3.get(fname)
    if newname not in os.listdir(path) :    
        os.rename(os.path.join(path, fname), os.path.join(path,newname))
print("labeled name으로 변경완료")
cs

 

 

 

 

반복해서 학습을 위해서 numpy 배열을 npy 파일로 저장을 하려고 했다. 하지만 모든 팀원들 컴퓨터 사양이 좋지 않아서 이미지 사이즈를 128x128로 하려다가 다들 맛이 가서 64x64로 어쩔 수 없이 저장했다.. ㅠㅠㅠ

 

 

 

 

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
import os, re, glob
import cv2
import numpy as np
from sklearn.model_selection import train_test_split
  
groups_folder_path = './train/'
categories = list(str(i) for i in range(20))
 
num_classes = len(categories)
  
image_w = 64
image_h = 64
  
= []
= []
  
for idex, categorie in enumerate(categories):
    label = [0 for i in range(num_classes)]
    label[idex] = 1
    image_dir = groups_folder_path + categorie + '/'
  
    for top, dir, f in os.walk(image_dir):
        for filename in f:
            print(image_dir+filename)
            img = cv2.imread(image_dir+filename)
            img = cv2.resize(img, dsize=(image_w, image_h))
            X.append(img/256)
            Y.append(label)
 
= np.array(X)
= np.array(Y)
 
X_train, X_test, Y_train, Y_test = train_test_split(X,Y)
xy = (X_train, X_test, Y_train, Y_test)
 
np.save("./img_data.npy", xy)
cs

#NumPy 배열을 npy 외부 파일로 저장

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

두번째 프로젝트 순서

1. 프로젝트 주제 정하기

2. 기획 및 데이터 수집, 전처리

3. 데이터 저장(판다스 열/행 관련 정리)

4. 시각화 및 자동화


프로젝트 마무리

매 10분마다 크롤링을 진행하고, 19시가 되면 이슈와 사설을 이메일로 보내준다. 자동화는 코드가 어디있는지 주섬주섬 다시 찾아봐야 한다 ㅠㅠ. 진행을 이슈파트 따로 사설파트 따로 해서, 사설은 어떤 식으로 코드 진행이 됐는지 잘 모르겠지만 나쁘지 않은 결과가 나왔다.

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
import os
from datetime import datetime
 
base_dir = 'C:/Workspace/project2_final/output/'
os.makedirs(base_dir, exist_ok=True)
 
from wordcloud import WordCloud
wc = WordCloud(font_path=r'C:\Windows\Fonts\MalgunBD.ttf',background_color="white",
               max_words=150, max_font_size=300, width=800, height=800)
cloud = wc.generate_from_frequencies(dict(sum_search))
 
cloud.to_file(base_dir+'IssueKeyWord '+ datetime.today().strftime('%Y%m%d'+ '.png')
 
cs

하루 일정이 마무리 되면 실시간 TOP100개의 키워드와 언급된 게시물 조회수의 비중에 따라 워드클라우드를 생성한다.

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#### 상위 5개 키워드 ####
 
df = pd.read_excel(path2, header=[0,1], index_col=[0] )
df_col = list(df.columns.levels[0])
 
hit_top5 = []
 
for i in df_col :
    hit_max = df[i,'조회수'].max()
    hit_top5.append(hit_max)
 
hit_dict = dict(zip(df_col,hit_top5)) #딕셔너리 값으로 저장
 
keyword_top5 = sorted(hit_dict, key=hit_dict.get, reverse = True)[:5#탑 5개 keyword 추출
print(keyword_top5)
cs

 



 

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
#### 19시에 코드 보내기
 
import matplotlib.pyplot as plt
import seaborn as sns
 
title_font = {'fontsize'16'fontweight''bold'}
plt.rc('font',family='Malgun Gothic')
plt.figure(figsize=[20,10])
plt.style.use('ggplot')
plt.show(block=False)
plt.pause(1)
plt.close()
 
ymd = today.strftime('%Y-%m-%d'
hms = '19:00:00'
 
if hms == '19:00:00':
    path_png = "C:/Workspace/project2_final/output/graphs"
    if not os.path.isdir(path_png):                                                           
        os.mkdir(path_png)
    
    path_date = f"C:/Workspace/project2_final/output/graphs/{ymd}"
    if not os.path.isdir(path_date):                                                           
        os.mkdir(path_date)    
    
    ##### 그래프 조회수 변동 ######
    for i in keyword_top5 :
        plt.title (f"키워드 : '{i}' 조회수 변동", fontsize=20)
        df.index,df[i,'조회수'].plot( kind='bar')
        #    plt.bar(df.index,df[i,'조회수'])
        #plt.show()
        plt.savefig(f'{path_date}/조회수변동-({i}).png', bbox_inches='tight')
        plt.close()
                 #.plot( kind='bar')
 
    ##### 그래프 키워드 관심도 #####
    for i in keyword_top5 :
        text =f'{i} 키워드 관심도'
        sub_keys = df[i].groupby(['서브키워드'])['조회수'].mean().sort_values()
        plt.title(text, fontdict=title_font, loc='center', pad= 20)
        sub_keys.plot(kind='pie', autopct = '%1.1f%%', shadow = True, startangle=110 )
        #plt.show()
 
        plt.savefig(f'{path_date}/관심도-({i}).png')
        plt.close()
    
    print('그래프 시각화 완료')
cs

19시가 되면 조회수 변동, 키워드에 따른 서브 키워드가 얼마나 변했는지 확인할 수 있다. 이거는 강의실에 와서 발표전 30분 데이터를 취합해서 만든 것이라 서브 키워드 변동도 거의 없고, 조회수 변동폭도 크지 않음을 확인할 수 있다.

 

 

 

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
56
57
import smtplib
from email.mime.multipart import MIMEMultipart;
# 메일의 본문 내용을 만드는 모듈
from email.mime.text import MIMEText;
# 메일의 첨부 파일을 base64 형식으로 변환
from email.mime.application import MIMEApplication;
# 메일의 이미지 파일을 base64 형식으로 변환(Content-ID 생성)
from email.mime.image import MIMEImage;
# 메일의 음악 파일을 base64 형식으로 변환(Content-ID 생성)
from email.mime.audio import MIMEAudio;
# 파일 IO
import io;
 
# 메일 서버와 통신하기 전에 메시지를 만든다.
data = MIMEMultipart();
# 송신자 설정
data['From'= "본인의 이메일";
# 수신자 설정 (복수는 콤마 구분이다.)
data['To'= "이메일1","이메일2";
# 메일 제목
data['Subject'= "제목"
with open("C:\\workspace\\project2_final\\mailsource\\hi.png"'rb'as fp:
    img = MIMEImage(fp.read(), Name = "hi.png")
    img.add_header('Content-ID''<hi>')
    data.attach(img)
 
with open("C:\\workspace\\project2_final\\mailsource\\issue.png"'rb'as fp:
    img = MIMEImage(fp.read(), Name = "issue.png")
    img.add_header('Content-ID''<issue>')
    data.attach(img)
    
with open("C:\\workspace\\project2_final\\mailsource\\news.png"'rb'as fp:
    img = MIMEImage(fp.read(), Name = "news.png")
    img.add_header('Content-ID''<news>')
    data.attach(img)    
    
with open("C:\\workspace\\project2_final\\mailsource\\tw.png"'rb'as fp:
    img = MIMEImage(fp.read(), Name = "tw.png")
    img.add_header('Content-ID''<tw>')
    data.attach(img)    
    
with open("C:\\workspace\\project2_final\\mailsource\\fb.png"'rb'as fp:
    img = MIMEImage(fp.read(), Name = "fb.png")
    img.add_header('Content-ID''<fb>')
    data.attach(img)    
 
with open("C:\\workspace\\project2_final\\output\\IssueKeyWord {}.png".format(datetime.today().strftime('%Y%m%d')), 'rb'as fp:
    img = MIMEImage(fp.read(), Name = "wc1.png")
    img.add_header('Content-ID''<wc1>')
    data.attach(img)
    
with open("C:\\workspace\\project2_final\\output\\KeyWord {}.png".format(datetime.today().strftime('%Y%m%d')), 'rb'as fp:
    img = MIMEImage(fp.read(), Name = "wc2.png")
    img.add_header('Content-ID''<wc2>')
    data.attach(img)
    
 
cs

 

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# Html 형식의 본문 내용 (cid로 이미 첨부 파일을 링크했다.)
 
html = """
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <title>하루 이슈</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body style="margin: 0; padding: 0;">
 <table align="center" border="0" cellpadding="0" cellspacing="0" width="600" style="border: 1px solid #cccccc;">
 <tr>
  <td align="center" bgcolor="#bdd7ee" style="padding: 40px 0 30px 0;">
 <img src="cid:hi" alt="Creating Email Magic" width="600" height="380" style="display: block;" />
</td>
 </tr>
 <tr>
  <td bgcolor="#ffffff" style="padding: 40px 30px 40px 30px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
 <tr>
  <td font-family: 'Noto Sans KR', sans-serif;>
   <table border="0" cellpadding="0" cellspacing="0" width="100%">
 <tr>
  <td width="260" valign="top" font-family:Noto Sans KR, sans-serif; font-size: 16px; line-height: 20px;"> 
   <h2>커뮤니티 인기 키워드 100</h2>
   <img src="cid:wc1" alt="Creating Email Magic" width="250" height="250" style="display: block;" />
  </td>
  <td style="font-size: 0; line-height: 0;" width="20">
   &nbsp;
  </td>
  <td width="260" valign="top" font-family:Noto Sans KR, sans-serif; font-size: 16px; line-height: 20px;"> 
   <h2>사설 주요 키워드 100</h2>
   <img src="cid:wc2" alt="Creating Email Magic" width="250" height="250" style="display: block;" />
  </td>
 </tr>
</table>
  </td>
 </tr>
 <tr>
  <td style="padding: 20px 0 30px 0; font-family:Noto Sans KR, sans-serif; font-size: 16px; line-height: 20px;">
   <p>오늘 하루도 수고하셨습니다. 하이와 함께 행복한 하루를 마무리하세요!</p>
   <br>
   <br>
  </td>
 </tr>
 <tr>
  <td>
  <table border="0" cellpadding="0" cellspacing="0" width="100%">
 <tr>
  <td width="260" valign="top">
   <table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tr>
     <td bgcolor="#3B89A3" align="center" style="padding: 20px 30px 20px 30px;">
      <img src="cid:issue" alt="" width="50%" height="100" style="display: block;" />
     </td>
    </tr>
    <tr>
     <td style="padding: 10px 0 30px 0; font-family:Noto Sans KR, sans-serif; font-size: 16px; line-height: 20px;"> 
     <h3 style="font-family:Noto Sans KR, sans-serif;">오늘의 커뮤니티 인기 키워드</h3>
      <details><p style="line-height:180%">{1}</p>
       <summary>{0}
       </summary>
      </details>
      <br>
      <details><p style="line-height:180%">{3}</p>
       <summary>{2}
       </summary>
      </details>
      <br>
      <details><p style="line-height:180%">{5}</p>
       <summary>{4}
       </summary>
      </details>
      <br>
      <details><p style="line-height:180%">{7}</p>
       <summary>{6}
       </summary>
      </details>
      <br>
      <details><p style="line-height:180%">{9}</p>
       <summary>{8}
       </summary>
      </details>
     </td>
    </tr>
   </table>
  </td>
  <td style="font-size: 0; line-height: 0;" width="20">
   &nbsp;
  </td>
  <td width="260" valign="top">
   <table border="0" cellpadding="0" cellspacing="0" width="100%">
    <tr>
     <td bgcolor="#3B89A3" align="center" style="padding: 20px 30px 20px 30px;">
      <img src="cid:news" alt="" width="50%" height="100" style="display: block;" />
     </td>
    </tr>
    <tr>
     <td style="padding: 10px 0 30px 0; font-family:Noto Sans KR, sans-serif; font-size: 16px; line-height: 20px;"> 
     <h3 style="font-family:Noto Sans KR, sans-serif;">오늘의 사설 주요 키워드</h3>
      <details><p style="line-height:180%">{11}</p>
       <summary>{10}
       </summary>
      </details>
      <br>
      <details><p style="line-height:180%">{13}</p>
       <summary>{12}
       </summary>
      </details>
      <br>
      <details><p style="line-height:180%">{15}</p>
       <summary>{14}
       </summary>
      </details>
      <br>
      <details><p style="line-height:180%">{16}</p>
       <summary>오늘 자주 등장한 키워드
       </summary>
      </details>
      
     </td>
    </tr>
   </table>
  </td>
 </tr>
</table>
  </td>
 </tr>
</table>
</td>
 </tr>
 <tr>
  <td bgcolor="#bdd7ee" style="padding: 30px 30px 30px 30px;">
 <table border="0" cellpadding="0" cellspacing="0" width="100%">
 <tr>
 <td width="75%"; style="color: #000000; font-family: Arial, sans-serif; font-size: 14px;">
 &reg; 하루이슈, Hi 2021<br/>
 <a href="" style="color: #000000;"><font color="#000000">Unsubscribe</font></a> to this newsletter instantly
</td>
  <td align="right">
 <table border="0" cellpadding="0" cellspacing="0">
  <tr>
   <td>
    <a href="http://www.twitter.com/">
     <img src="cid:tw" alt="Twitter" width="38" height="38" style="display: block;" border="0" />
    </a>
   </td>
   <td style="font-size: 0; line-height: 0;" width="20">&nbsp;</td>
   <td>
    <a href="http://www.facebook.com/">
     <img src="cid:fb" alt="Facebook" width="38" height="38" style="display: block;" border="0" />
    </a>
   </td>
  </tr>
 </table>
</td>
 </tr>
</table>
</td>
 </tr>
</table>
</body>
 
</html>
""".format(keyword_top5[0], articlestr1, keyword_top5[1], articlestr2, keyword_top5[2], articlestr3, keyword_top5[3], articlestr4, keyword_top5[4], articlestr5, sendingKeywords[0], content1, sendingKeywords[1], content2, sendingKeywords[2], content3, dfCSV["text"].tolist())
 
 
msg = MIMEText(html, 'html')
 
 
# 메시지를 확인한다.
# Data 영역의 메시지에 바운더리 추가
data.attach(msg);
print(data);
# 메일 서버와 telnet 통신 개시
server = smtplib.SMTP_SSL('smtp.naver.com',465);
#server = smtplib.SMTP('smtp.gmail.com',587);
# 메일 통신시 디버그
server.set_debuglevel(1);
# 헤로 한번 해주자.(의미 없음)
server.ehlo();
# tls 설정 주문 - tls 587 포트의 경우
#server.starttls();
# 헤로 또 해주자.(의미 없음)
server.ehlo();
# 로그인 한다.
server.login("ID", "P/W!"); # 아이디, 패스워드 입력
# 심심하니 또 헤로 해주자.(의미 없음)
server.ehlo();
# MAIL(송신자) 설정
sender = data['From'];
# RCPT(수신자), 리스트로 보낸다.
# 수신자 추가
receiver = data['To'].split(",");
# # 참조자 추가
# if data['Cc'] is not None:
#     receiver += data['Cc'].split(",");
# # 숨은 참조자 추가
# if data['Bcc'] is not None:
#     receiver += data['Bcc'].split(",");
# 메일 프로토콜 상 MAIL, RCPT, DATA 순으로 메시지를 보내야 하는데 이걸 sendmail함수에서 자동으로 해준다.
server.sendmail(sender, receiver, data.as_string());
# QUIT을 보내고 접속을 종료하고 메일을 보낸다.
server.quit();
 
cs

 

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

두번째 프로젝트 순서

1. 프로젝트 주제 정하기

2. 기획 및 데이터 수집, 전처리

3. 데이터 저장(판다스 열/행 관련 정리)

4. 시각화 및 자동화


데이터를 어떤 식으로 저장할지에 대해서 고민이 많았다. 구글링을 엄청 많이 했는데 잘 나오지 않아서, 영어로도 검색을 많이 했다. 한 열 안에 2개의 정보(멀티인덱스)가 들어가야 했고, 새로운 행이 생성될 때 계속 새로운 열이 생성됐기 때문에 쉽지 않았다. 그래서 판다스(pandas) 관련 개념을 정리해보려고 한다.

 

 

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
#pandas 개념 정리
 
'''
df_review_one_sentence는 컬럼이 titles(제목), reviews(리뷰)로 구성이 됐다.
 
[info]
 #   Column   Non-Null Count  Dtype 
---  ------   --------------  ----- 
 0   titles   3498 non-null   object
 1   reviews  3498 non-null   object
 
[head]
                                             titles                                            reviews
0  13시간 (13 Hours: The Secret Soldiers of Benghazi)  토요일 내리다 황사 몰아치다 일요일 오전 난생 처음 홍대 찾다 파괴 마이클 베이 형...
1    1942: 최정예특수부대 스페츠나츠 (The Dawns Here Are Quiet)  처음 리뷰 킬링타임 용이 땡기다 빠지다 듭니 처음 부분 별로 여군 샤워 커버 해주다...
2                                       33 (The 33)  패트리시아 리건 출연 안토니오 라스 로드 산토 줄리엣 비노 미국 칠레 평점 리뷰 예...
3                          400번의 구타 (The 400 Blows)  달리기 어딘에선 달아나다 모습 기억 남다 처음 만난 우산 가지 오지 내리다 기다 흡...
4                                  45년 후 (45 Years)  압구정 괜찮다 거짓말 괜찮다 괜찮다 괜찮다 살아가다 많다 괜찮다 외치다 괜찮다 괜찮...
 
iloc => 행번호로 선택
loc => 라벨이나 조건으로 선택
'''
 
print(df_review_one_sentence.iloc[0]) # 첫번째 행만 추출 => 제목, 리뷰 다나옴
print(df_review_one_sentence.iloc[0,1]) #첫번째 행, 1번 열  / 요소 1개 뽑기 - 리뷰만 나옴
print(df_review_one_sentence.iloc[0:5,0]) # 1~5번째 행의 제목 5개
print(df_review_one_sentence.loc[:, 'titles']) #컬럼명으로 보겠다 / 모든 제목이 출력된다.
print(df_review_one_sentence['titles'][0]) #타이틀에 0번째

df.iloc[row,col] 숫자로
df.loc['Tom','math'] 컬럼명으로
cs
1
2
3
4
5
6
7
8
9
# enumerate 정리
ls = ['겨울왕국','라이온킹','알라딘']
print(list(enumerate(ls))) #튜플 리스트로 보여줌 / [(0, '겨울왕국'), (1, '라이온킹'), (2, '알라딘')]
for idx,i in enumerate(ls) : #0 겨울왕국, 1 라이온킹, 2 알라딘 출력 / 인덱스 값 같이 보여줌
    print(idx, i)
 
for idx, i in enumerate(ls):
    if i == '라이온킹':
        print(idx)
cs

 

 

 

 

1-4. 파이썬 EDA 데이터 분석 팀 프로젝트 판다스, 시각화 (Matplotlib, Json)

- 첫 프로젝트 글 순서 - 1. 파이썬(python) EDA 데이터분석 주제 정하기 2. 실패한 여기어때 후기 웹스크래핑(web scraping) 3. 데이터 수집 방법 & 데이터 추출, 정제 4. 판다스(pandas) 데이터 처리 / Matpl.

0goodmorning.tistory.com

특정 값을 추출하고 groupbpy를 통해서 묶거나 더하는 것이 필요하다면 이전 코드를 한 번 참조해주세요! 와 근데 지금 다시 보니까 진짜 코드가 엉망진창인 것 같습니다.. 첫번째에 비해 두번째 프로젝트 코드가 훨씬 나아진 것 같고, 현재는 더 나아졌다고 생각이 든다.

이제부터는 안 됐던 코드들을 같이 첨부하려고 합니다. 보시면서 여러분들은 같은 실수는 하지 말아주세요 ㅠㅠㅠ

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
##### 엑셀파일 생성될 때 기본 틀 갖추기 ####
 
import pandas as pd
import numpy as np
from itertools import chain
 
def frame(key_list, day1, day2, path2) :
    
    key_lists = list(chain.from_iterable(zip(key_list,key_list))) #key_list_1로 설정해서 기준을 만들어줘야 함
    multi_key = ['조회수','서브키워드']
    multi_keys = ['조회수','서브키워드'* len(key_list)
 
    range = pd.date_range(f'{day1} 19:00:00',f'{day2} 18:50:00', freq='10min'
    df = pd.DataFrame(columns = [key_lists, multi_keys],index = range)
    
    df.to_csv(f'{path2}', index=False, encoding='UTF-8-sig')
    
    print('저장완료')
cs

처음에는 이런 식으로 먼저 19시부터 익일 18시 50분(def 함수 만들기 전 사진이라 19시까지 들어갔네요. 19시 기준은 퇴근 시간 기준으로 집에 가는 길이나 집에서 하루 이슈를 마무리할 수 있게 잡았습니다.)까지 데이터를 만들어서 저장을 하는 방식인데 고려할 것이 여러개 생겼습니다.

 

 

 

파일을 불러올 경우에 멀티인덱스가 제대로 안 보여지는 것 같습니다. 한강 한강이 병합돼서 하나의 한강 밑에 조회수/ 서브키워드가 있어야 하는데 Unnamed: 0, Unnamed: 2, Unnamed: 4 이런 식으로 생성되는 것을 확인할 수 있습니다. 그리고 시간 마다 새로운 열이 추가되고, 기존 열은 또 사용을 해서 기존 열에 있으면 새로운 열을 생성하는 식의 코드를 짜봤는데 계속 에러가 떠서 포기했습니다. 

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import pandas as pd
import numpy as np
 
 
df1 = pd.read_excel(path2, header=[0,1], index_col=[0] )
 
 
# 조회수, 서브 키워드 입력
def jos(i, t) :
    df.loc[t][(i,'조회수')] = sum_search[i]
    df.loc[t][(i,'서브키워드')] = sub_key[i]
    
= '2021-05-04 19:20:00' #현재 시간 설정
 
for i in sum_search : 
    if i not in key_list_1 :  # 기준이 되는 key_list 값에 없을 경우 열 추가
        a = [i]
        df = df.join(pd.DataFrame(columns=pd.MultiIndex.from_product([a, multi_key]),index=df.index))
        jos(i, t)
    else :
        jos(i, t)
        
df.to_csv(path2)
cs

이런 식으로 진행을 하려고 했는데 조회만 추가가 되고 서브키워드는 추가가 안 되는 문제가 발생했습니다.

 

 

 

 

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
import pandas as pd
import numpy as np
 
#복제
from itertools import chain
key_lists = list(chain.from_iterable(zip(key_list,key_list))) #key_list_1로 설정해서 기준을 만들어줘야 함
multi_key = ['조회수','서브키워드']
multi_keys = ['조회수','서브키워드'* len(key_list)
 
# 조회수, 서브 키워드 입력
def jos(i, time) :
    df.loc[time][(i,'조회수')] = sum_search[i]
    df.loc[time][(i,'서브키워드')] = sub_key[i]
    
# generate time series index
range = pd.date_range('05-03-21''05-04-21', freq='10min')              # if문 7~ +1 6시50분 시간을 기준으로 삼아서
df = pd.DataFrame(columns = [key_lists, multi_keys],index = range)
 
time = "2021-05-03 00:10:00" #현재 시간 설정
 
 
 
for i in sum_search : 
    if i not in key_lists :
        a = [i]
        df = df.join(pd.DataFrame(columns=pd.MultiIndex.from_product([a, multi_key]),index=df.index))
        jos(i, time)
    else :
        jos(i, time)
df
cs

코드를 바꿔서 진행했는데 이제는 조회수와 서브키워드에 채워지는 것을 확인할 수 있습니다. 그런데 또 시간이 바뀌고 새로운 데이터를 크콜링하게 되면 오류가 나가너 코드가 복잡해져서 방법을 더 찾았습니다.

 

 

 

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
#### 행 생성 ####
import pandas as pd
import numpy as np
from itertools import chain
 
#복제
key_lists = list(chain.from_iterable(zip(key_list,key_list))) #멀티 인덱스를 위해서 [a,b,c] => [a,a,b,b,c,c] 로 만들어줌
multi_keys = ['조회수','서브키워드'* len(key_list)
 
# 조회수, 서브 키워드 입력
def hit_sub(i, time) :
    df1.loc[time][(i,'조회수')] = sum_search[i]
    df1.loc[time][(i,'서브키워드')] = sub_key[i]
    
# generate time series index
time = nowDatetime    #현재 시간 설정
 
df1 = pd.DataFrame(columns = [key_lists, multi_keys],index = [time])
 
for i in sum_search : 
    hit_sub(i, time)
    
#### 다음 행에 추가 ####
df = pd.read_excel(path2, header=[0,1], index_col=[0] )   #number 해결
df = df.append(df1, sort=False)    #컬럼 수가 다를 때 NaN 값을 넣고 행 추가
cs

if 문을 사용하지 않고 멀티 인덱스가 있는 상황에서, 새로운 행과 열이 생성되고, 기존 열은 유지되는 함수

=> append를 사용하시면 됩니다!!!

 

 

 

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
56
57
58
#subject 디렉토리내 파일 삭제하기
 
import shutil
 
pathTest = r"C:/Workspace/project2_final/output/temp"
 
try:
    shutil.rmtree(pathTest)
except OSError as e:
    print(e)
else:
    print("The directory is deleted successfully")
 
 
#### 새로운 파일 생성 ###
 
import os
from datetime import datetime, date, time, timedelta
import datetime
import openpyxl
import pickle
 
#디렉토리 폴더 생성
path = "C:/Workspace/project2_final/output"
if not os.path.isdir(path):                                                           
    os.mkdir(path)
 
 
today =datetime.datetime.now()
 
hms = today.strftime('%H:%M:%S'#시분초
ymd = today.strftime('%Y-%m-%d'#년월일  - 오늘 날짜
yesterday = (today - datetime.timedelta(1)).strftime('%Y-%m-%d'#어제날짜
 
wb = openpyxl.Workbook()
 
if hms < '19:00:00':
    file_name = yesterday
    path2 = f"C:/Workspace/project2_final/output/{file_name}.xlsx"
    
    if not os.path.isfile(path2):  
        wb.save(path2)
        print('파일 생성완료1')
 
    else :
        print('파일을 작업중입니다....')
 
else : 
    file_name = ymd
    path2 = f"C:/Workspace/project2_final/output/{file_name}.xlsx"
    
    if not os.path.isfile(path2):  
        wb.save(path2)
        print('파일 생성완료2')
 
    else :
        print('파일을 작업중입니다....')
 
cs

이런 식으로 특정 시간이 되면 새로운 폴더와 파일을 생성 하는 코드입니다.

 

 

 

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

두번째 프로젝트 순서

1. 프로젝트 주제 정하기

2. 기획 및 데이터 수집, 전처리

3. 데이터 저장(판다스 열/행 관련 정리)

4. 시각화 및 자동화


각종 커뮤니티를 모두 크롤링하기에는 시간도 없고 벅차서, 이슈링크라는 싸이트에서 이미 친절하게 각종 커뮤니티를 크롤링해주고 있어서 이슈링크 싸이트를 이용하였다. 봇을 이용하여 글들을 긁어와주는 걸로 보인다. 

 

하지만 확인 결과, 오늘의 이슈태그 Top5커뮤니티 베스트 키워드들은 실제로 다수의 사람들이 관심이 있는 것이 아니었다. 분석하기로는 얼마나 커뮤니티에서 자주 언급되는지에 따라서 순위가 올라가는 것으로 보인다.

 

그래서 다수의 사람들이 관심이 있는 키워드와 이슈거리를 어떻게 하면 찾을 수 있을까 생각하면서 파이썬 프로젝트를 진행했다.

 

 

 

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
#현재 시간 설정
from datetime import datetime, date, time, timedelta
 
now = datetime.now()
nowDatetime = now.strftime('%Y-%m-%d %H:%M')
print(nowDatetime)
 
 
#### 이슈 빼오기 #####
import requests
from bs4 import BeautifulSoup
 
url = 'https://www.issuelink.co.kr/community/listview/all/3/adj/_self/blank/blank/blank'
headers = {'user-agent''Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36'}
html_rank = requests.get(url, headers=headers).text
soup_rank = BeautifulSoup(html_rank, 'lxml')
keywords = soup_rank.select('div.ibox.float-e-margins > div > table > tbody > tr > td > a')
 
 
key_list = []
for k in keywords:
    keyword = k.text
    key_list.append(keyword)
 
###### 7시를 기준으로 기준 리스트를 하나 만들어야 함 / datetime에서 시간 분만 빼와서 if로 비교
 
key_list.pop(0)
print(key_list)
cs

 

 

 

예측을 한 것인데 실제로 확인을 해보니 총조회수가 높지 않지만 많이 언급될 수록 상위권에 있는 것으로 확인이 됐다. 이제 이 과정을 코드로 시행.

 

 

 

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
### 이슈 실제 조회수 ###
 
from bs4 import BeautifulSoup
import requests
import urllib
import operator
 
sum_list = []
search_list = key_list
 
def sum(search) :
    
    url = f'https://www.issuelink.co.kr/community/listview/read/3/adj/_self/blank/{search}'
    headers = {'user-agent''Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36'}
    r = requests.get(url, headers=headers)
    soup = BeautifulSoup(r.text, 'html.parser')
 
    hits = soup.select('span.hit')
    
    sum=0
    
    for hit in hits :
        sum += int(hit.text.replace(',',''))
    
    sum_list.append(sum)  
    print('*',end='')
    
for search in search_list :
    sum(search)
 
#조회수, 키워드 합치기
sum_search = dict(zip(key_list,sum_list))
 
#조회수 순으로 정렬
#a = sorted(sum_search.items(), key=lambda x:x[1], reverse = True)
 
print()
print(sum_search)
cs

이슈가 되는 키워드를 검색했을 때, 각 커뮤니티별로 얼만큼의 조회수를 보이고 있는지 확인을 했다. 문제는 각 사람들마다 성향이 있어서 특정 싸이트를 보여주면, 극도로 싫어하는 사람들이 있다. 그래서 커뮤니티 글 중에 조회수가 높은 글을 보여주기에는 애매해서, 이를 네이버 뉴스로 보여주려고 했다.

 

다만 네이버 뉴스를 예로 들었을 때, 당시 '브레이브걸스' 로션은 알테니 스킵을 아재 팬들이 몰라서 이슈가 됐었다. 하지만 당시 브레이브걸스 자체가 가지고 있는 인기가 있어서, 브레이브걸스를 뉴스에서 검색해도 이 알테니 스킵이라는 이슈와는 다른 이슈가 검색이 될 수도 있다. 그래서 서브키워드를 같이 추출하기 위해서 형태소 분석을 했다.

 

 

 

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
#### 키워드 제목 추출 탐30 개별로 ####
 
from bs4 import BeautifulSoup
import requests
import urllib
import os
 
#디렉토리 폴더 생성
path = "./subject"
if not os.path.isdir(path):                                                           
    os.mkdir(path)
 
keyword_list=[]
 
def subject(search) :
    url = f'https://www.issuelink.co.kr/community/listview/all/3/adj/_self/blank/{search}'
    headers = {'user-agent''Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36'}
    r = requests.get(url, headers=headers)
    soup = BeautifulSoup(r.text, 'html.parser')
 
    sub = soup.select('span.title')
    
    keyword_list.clear()
    
    for i in sub :
        split_string = i.get_text().split(' [',1)
        substring = split_string[0]    
        keyword_list.append(substring)
        
    with open(f'./subject/{search}.txt','w', encoding = 'utf-8'as file :
        file.writelines(keyword_list)
    
    print('**', end="")
            
    
for search in search_list :
    subject(search)
 
print()
print('완료')
cs

 

 

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
#### 키워드 형태소 카운팅 ####
 
""" 형태소 분석기
    명사 추출 및 빈도수 체크
    python [모듈 이름] [텍스트 파일명.txt] [결과파일명.txt]
"""
 
import sys
from konlpy.tag import Twitter
from collections import Counter
 
 
def get_tags(text, ntags=50):
    spliter = Twitter()
    nouns = spliter.nouns(text)
    count = Counter(nouns)
    return_list = []
    for n, c in count.most_common(ntags):
        temp = {'tag': n, 'count': c}
        return_list.append(temp)
    return return_list
 
 
def main(search):
    # 분석할 파일
    noun_count = 50
    # count.txt 에 저장
    open_text_file = open(f'./subject/{search}.txt''r',-1,"utf-8")
    # 분석할 파일을 open 
    text = open_text_file.read() #파일을 읽습니다.
    tags = get_tags(text, noun_count) # get_tags 함수 실행
    open_text_file.close()   #파일 close
    open_output_file = open(f"./subject/{search}-count.txt"'w',-1,"utf-8")
    # 결과로 쓰일 count.txt 열기
    for tag in tags:
        noun = tag['tag']
        count = tag['count']
        open_output_file.write('{} {}\n'.format(noun, count))
    # 결과 저장
    open_output_file.close() 
 
for search in search_list :
    main(search)
    
print('완료')
 
cs

당시에 형태소 분석을 제대로 다루지 못해서 구글을 통해서 검색을 했다. 그리고 제목보다는 서브키워드가 중요하다고 생각하여서, 제목은 잠시 파일에 저장하고 시간이 지나면 삭제하는 식의 과정을 진행했다.

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests
from bs4 import BeautifulSoup
 
sub_key = {}
 
for i in sum_search :
      
    with open(f'C:/Workspace/project2_final/output/temp/{i}-count.txt','r', encoding = 'utf-8'as file :
        data = str(file.readlines()[1])
 
    split_string = data.split(' ',1
    substring = split_string[0]           #빈도수 제거 
    #print(substring)
    
    sub_key[i] = substring
    
    
print(sub_key)
cs

 

 

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
#네이버 검색, 키워드 서브키워드 
import requests
from bs4 import BeautifulSoup
 
art_lists = []
 
def search(key, b) :
    
    art_list = [b]
    
    #url = f'https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=1&ie=utf8&query={key}'
    url = f'https://search.naver.com/search.naver?where=news&sm=tab_jum&query={key}'
    html = requests.get(url).text
    soup = BeautifulSoup(html, 'lxml')
    
    for i in range(2) :
        news = soup.select('div.info_group > a:nth-of-type(2)')[i].attrs["href"]
        art_list.append(news)
    
    art_lists.append(art_list)
    
= sorted(sum_search.items(), key=lambda x:x[1], reverse = True#value 값 기준으로 정렬, 상위 5개 키워드
 
 
for i in range(5) :
    b= a[i][0]     #정렬 후 dic -> list 함수로 변환돼서 [i][0]으로 빼옴 
    #print(b)
    
    with open(f'C:/Workspace/project2_final/output/temp/{b}-count.txt','r', encoding = 'utf-8'as file :
        data = str(file.readlines()[1])
 
    split_string = data.split(' ',1
    substring = split_string[0]           #빈도수 제거 
    #print(substring)
    
    key = b + " " + substring
    search(key, b)
    
print(art_lists)
print(sum_search.items())
 
cs

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

두번째 프로젝트 순서

1. 프로젝트 주제 정하기

2. 기획 및 데이터 수집, 전처리

3. 데이터 저장(판다스 열/행 관련 정리)

4. 시각화 및 자동화

 


 

 

주제 : 하루 동안 있었던 이슈와 볼거리 정리

일정 : 4/29~5/7 (거의 5월 프로젝트인데 이제서야 정리를 하네요...)

기획 : 가십거리와 함께 전문적인 의견들에 대해서 하루를 정리하는 시간에 받아 보는 서비스

 

 

 

출처 : 조선일보

[네이버 실시간 검색어 폐지]

네이버 실시간 검색어도 갑자기 폐지됐다. 실검에 자꾸 광고성 키워드들이 올라와서 실검을 폐지했다는데 황당하다. 상품 키워드들만 올라가는 창을 만들든 트래픽을 분석해서 억제를 시키면 되는데, 소비자들의 편의성을 없앴다. 솔직히 실검보려고 네이버를 사용하는 사람들이 많았을텐데 얼마나 영향을 끼쳤을지 궁금하다.

 

(네이버는 이전에도 홈 화면을 뉴스나 실검이 나오는 창이 아닌 구글을 따라하듯이 바꿨는데, 이 막대한 손해는 어떻게 극복했는지도 궁금하다. 네이버 블로그도 바이럴 마케팅에 먹힌지 너무 오래 됐고, 인플루언서도 팔로워를 돈으로 사는 마당에 신뢰도를 어떻게 회복할지도 궁금한데.. 이건 뭐 뻘소리라서 패스..)

 

 

 

 

[이슈 생성 과정]

있다가 없어지면 불편하기 마련, 그래서 사람들이 어떤 것에 관심을 가지고 있고,

어떤 사건이 일어나는지 이슈의 생성 과정을 분석해보았다.

 

1. SNS 또는 커뮤니티에서 먼저 사건이 커진다. 

2. 그것이 커뮤니티로 돌고 돌아 공유가 돼서 커뮤니티에 상주하는 기자들이 기사를 쓰기 시작한다.

 (기사가 화제가 되는 경우도 있다.)

3. 기사를 본 사람들이 관련 내용이 어떤지 궁금해서 찾아보게 된다.

4. 실시간 검색어 순위에 오르게 되고, 기사나 관련 내용을 모르는 사람들은 실검을 클릭하게 된다.

 

=> 이러한 점을 미루어 봤을 때 커뮤니티에서 어떤 것이 이슈가 되고 있는지 파악해서 이슈를 예측해본다.

 

 

 

파이썬 업무 자동화를 배워서 이번에는 자동으로 시스템이 돌아가고, 자동으로 메일을 보내는 시스템을 구현하려고 한다. 원래는 Django를 통해서 웹 서비스를 구현해보려고 했는데, 생각보다 시간이 많이 걸려서 다음에 해보기로 했다.

 

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

- 첫 프로젝트 글 순서 -
1. 파이썬(python) EDA 데이터분석 주제 정하기
2. 실패한 여기어때 후기 웹스크래핑(web scraping)
3. 데이터 수집 방법 & 데이터 추출, 정제
4. 판다스(pandas) 데이터 처리 / Matplotlib, Json 시각화
5. 정리

지난 번에 글을 올린지 딱 2달이 지났다. 이후에 프로젝트를 벌써 3개를 더했는데... 틈만 나면 해야지 해야지 하는데 시간이 많이 나지를 않았다. 머신러닝, 인공지능 등 개념학습 따라가기도 너무 벅찼고, 모델은 아직까지 이해도 안 되는 수준이긴 하지만 더 늦어지면 안 될 것 같아서 미리미리 간단하게 쓰기로 했다.

솔직히 EDA 데이터 분석 팀프로젝트 5번째 항목 정리를 왜 했지라는 생각이 든다. 그래서 간단하게 어떻게 진행을 했고 어떤 것을 사용했는지, 결론은 어떻게 도출했는지만 간단하게 쓰려고 한다. 부지런하게 남은 프로젝트들도 업로드하면서 github도 사용해봐야 하는데 쉽지 않다. 개인적으로 마케팅 분석하고 sns까지 하다보니 시간이 남지 않는다. 해커톤까지 ???

나중에 알게 된건데 필수로 써야하는 부분이 몇개 있다.

1. 언어(파이썬, 자바, C 등..), 패키지(matplolib, pandas 등), 툴(주피터노트북, 코랩, 파이참 등..)
2. 일정표
3. 과정, 순서 등





이번에 주로 사용했던 것은 pandas, matplolib, json을 사용하였다. sns도 사용하려고 했는데 당시에는 생각보다 쉽지 않아서 matplotlib으로 데이터 시각화를 대부분 담당했다. 나중에도 시각화하는데 많이 사용하기 때문에 알아두는 것이 좋다.


과정, 순서 등은 어떤 부분에서 얼만큼의 시간을 썼는지 쓰면 좋을 것이다.


프로젝트를 할 때 주제가 많이 제한적이게 된 이유는 데이터를 구하기 너무 어렵다. 예전에 데이터가 곧 권력이고 힘이라고 했던 부분, 그리고 스프링쿨러? 온도, 습도 등의 데이터를 모아놨던 회사가 구글에 데이터를 고가에 팔았다는 것, 머신러닝을 하면서 더 느끼게 됐다.


데이터를 토대로 결론 도출을 해봤다
- 창업시 고려해야할 위치와 업종을 데이터를 통해 파악하기 쉽다.
- 업종에서는 소매업이 가장 많고, 그 다음이 음식점, 그리고 도민의 소매업이 평상시에도 많다.
- 성수기 때 슈퍼마켓, 체인화 편의점 이용률이 많이 늘어났다. 숙박업을 할 경우 미리 물품 구매를 하면 더 좋을 것이다.

=> 하고 싶은 업종을 입력했을 때, 위치, 경쟁, 타겟층을 보여주는 프로그램을 보여줘도 나쁘지 않을 것 같다.

+ Recent posts